class Volt::URL
Attributes
Public Class Methods
# File lib/volt/models/url.rb, line 14 def initialize(router = nil) @router = router @location = Location.new end
Public Instance Methods
# File lib/volt/models/url.rb, line 19 def params @params ||= Model.new({}, persistor: Persistors::Params) end
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
# 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
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
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
# 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
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 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
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
# 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
# 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
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
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