class Volt::URL

Attributes

location[R]
router[RW]

Public Class Methods

new(router = nil) click to toggle source
# File lib/volt/models/url.rb, line 14
def initialize(router = nil)
  @router = router
  @location = Location.new
end

Public Instance Methods

params() click to toggle source
# File lib/volt/models/url.rb, line 19
def params
  @params ||= Model.new({}, persistor: Persistors::Params)
end
parse(url) click to toggle source

Parse takes in a url and extracts each sections. It also assigns and changes to the params.

# File lib/volt/models/url.rb, line 25
def parse(url)
  if url[0] == '#'
    # url only updates fragment
    self.fragment = url[1..-1]
    update!
  else
    host = location.host
    protocol = location.protocol

    if url !~ /[:]\/\//
      # Add the host for local urls
      url = protocol + "//#{host}" + url
    else
      # Make sure its on the same protocol and host, otherwise its external.
      if url !~ /#{protocol}\/\/#{host}/
        # Different host, don't process
        return false
      end
    end

    matcher         = url.match(/^(#{protocol[0..-2]})[:]\/\/([^\/]+)(.*)$/)
    self.scheme     = matcher[1]
    self.host, port = matcher[2].split(':')

    self.port = (port || 80).to_i

    path           = matcher[3]
    path, fragment = path.split('#', 2)
    path, query    = path.split('?', 2)

    self.path     = path
    self.fragment = fragment
    self.query    = query

    result = assign_query_hash_to_params
  end

  scroll

  result
end
scroll() click to toggle source
# File lib/volt/models/url.rb, line 125
def scroll
  if Volt.in_browser?
    frag = fragment
    if frag.present?
      # Scroll to anchor via http://www.w3.org/html/wg/drafts/html/master/browsers.html#scroll-to-fragid
      # Sometimes the fragment will cause a jquery parsing error, so we
      # catch any exceptions.
      `
      try {
        var anchor = $('#' + frag);
        if (anchor.length == 0) {
          anchor = $('*[name="' + frag + '"]:first');
        }
        if (anchor && anchor.length > 0) {
          console.log('scroll to: ', anchor.offset().top);
          $(document.body).scrollTop(anchor.offset().top);
        }
      }
      catch(e) {}
    `
    else
      # Scroll to the top by default
      `$(document.body).scrollTop(0);`
    end
  end
end
update!() click to toggle source

Called when the state has changed and the url in the browser should be updated Called when an attribute changes to update the url

# File lib/volt/models/url.rb, line 111
def update!
  if Volt.in_browser?
    new_url = url_for(params.to_h)

    # Push the new url if pushState is supported
    # TODO: add fragment fallback
    `
    if (document.location.href != new_url && history && history.pushState) {
      history.pushState(null, null, new_url);
    }
  `
  end
end
url_for(params) click to toggle source

Full url rebuilds the url from it's constituent parts. The params passed in are used to generate the urls.

# File lib/volt/models/url.rb, line 69
def url_for(params)
  host_with_port = host
  host_with_port += ":#{port}" if port && port != 80

  path, params = @router.params_to_url(params)

  if path == nil
    raise "No route matched, make sure you have the base route defined last: `client '/', {}`"
  end

  new_url    = "#{scheme}://#{host_with_port}#{path.chomp('/')}"

  # Add query params
  params_str = ''
  unless params.empty?
    query_parts = []
    nested_params_hash(params).each_pair do |key, value|
      # remove the _ from the front
      value = Volt::Parsing.encodeURI(value)
      query_parts << "#{key}=#{value}"
    end

    if query_parts.size > 0
      query = query_parts.join('&')
      new_url += "?#{query}"
    end
  end

  # Add fragment
  frag    = fragment
  new_url += '#' + frag if frag.present?

  new_url
end
url_with(passed_params) click to toggle source
# File lib/volt/models/url.rb, line 104
def url_with(passed_params)
  url_for(params.to_h.merge(passed_params))
end

Private Instance Methods

assign_from_old(params, new_params) click to toggle source

Loop through the old params, and overwrite any existing values, and delete the values that don't exist in the new params. Also remove any assigned to the new params (query_hash)

# File lib/volt/models/url.rb, line 185
def assign_from_old(params, new_params)
  queued_deletes = []

  params.attributes.each_pair do |name, old_val|
    # If there is a new value, see if it has [name]
    new_val = new_params ? new_params[name] : nil

    if !new_val
      # Queues the delete until after we finish the each_pair loop
      queued_deletes << name
    elsif new_val.is_a?(Hash)
      assign_from_old(old_val, new_val)
    else
      # assign value
      params.set(name, new_val) if old_val != new_val
      new_params.delete(name)
    end
  end

  queued_deletes.each { |name| params.delete(name) }
end
assign_new(params, new_params) click to toggle source

Assign any new params, which weren't in the old params.

# File lib/volt/models/url.rb, line 208
def assign_new(params, new_params)
  new_params.each_pair do |name, value|
    if value.is_a?(Hash)
      assign_new(params.get(name), value)
    else
      # assign
      params.set(name, value)
    end
  end
end
assign_query_hash_to_params() click to toggle source

Assigning the params is tricky since we don't want to trigger changed on any values that have not changed. So we first loop through all current url params, removing any not present in the params, while also removing them from the list of new params as added. Then we loop through the remaining new parameters and assign them.

# File lib/volt/models/url.rb, line 161
def assign_query_hash_to_params
  # Get a nested hash representing the current url params.
  query_hash = parse_query

  # Get the params that are in the route
  new_params = @router.url_to_params(path)

  fail "no routes match path: #{path}" if new_params == false

  return false if new_params == nil

  query_hash.merge!(new_params)

  # Loop through the .params we already have assigned.
  lparams = params
  assign_from_old(lparams, query_hash)
  assign_new(lparams, query_hash)

  true
end
nested_params_hash(params, path = []) click to toggle source
# File lib/volt/models/url.rb, line 265
def nested_params_hash(params, path = [])
  results = {}

  params.each_pair do |key, value|
    unless value.nil?
      if value.respond_to?(:persistor) && value.persistor && value.persistor.is_a?(Persistors::Params)
        # TODO: Should be a param
        results.merge!(nested_params_hash(value, path + [key]))
      else
        results[query_key(path + [key])] = value
      end
    end
  end

  results
end
parse_query() click to toggle source
# File lib/volt/models/url.rb, line 219
def parse_query
  query_hash = {}
  qury = query

  if qury
    qury.split('&').reject { |v| v == '' }.each do |part|
      parts = part.split('=').reject { |v| v == '' }
      parts[1] = Volt::Parsing.decodeURI(parts[1])

      sections = query_key_sections(parts[0])

      hash_part = query_hash
      sections.each_with_index do |section, index|
        if index == sections.size - 1
          hash_part[section] = parts[1] # Last part, assign the value
        else
          hash_part = (hash_part[section] ||= {})
        end
      end
    end
  end

  query_hash
end
query_key(path) click to toggle source

Generate the key for a nested param attribute

# File lib/volt/models/url.rb, line 253
def query_key(path)
  i = 0
  path.map do |v|
    i += 1
    if i != 1
      "[#{v}]"
    else
      v
    end
  end.join('')
end
query_key_sections(key) click to toggle source

Splits a key from a ?key=value&… parameter into its nested parts. It also adds back the _'s used to access them in params. Example: user=Ryan would parse as [:_user, :_name]

# File lib/volt/models/url.rb, line 248
def query_key_sections(key)
  key.split(/\[([^\]]+)\]/).reject(&:empty?)
end