Skip to content

Commit

Permalink
Merge pull request #69 from le0pard/master
Browse files Browse the repository at this point in the history
Support route globbing
  • Loading branch information
bogdan committed Apr 2, 2013
2 parents cab4df0 + 076e051 commit 488cff0
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 47 deletions.
19 changes: 10 additions & 9 deletions lib/js_routes.rb
Expand Up @@ -153,15 +153,14 @@ def any_match?(route, parent_route, matchers)
def build_js(route, parent_route)
name = [parent_route.try(:name), route.name].compact
parent_spec = parent_route.try(:path).try(:spec)
required_parts = route.required_parts.clone
optional_parts = route.optional_parts.clone
required_parts, optional_parts = route.required_parts.clone, route.optional_parts.clone
optional_parts.push(required_parts.delete :format) if required_parts.include?(:format)
route_name = "#{name.join('_')}_path"
route_name = route_name.camelize(:lower) if true == @options[:camel_case]
route_name = generate_route_name(name)
url_link = generate_url_link(name, route_name, required_parts)
_ = <<-JS.strip!
// #{name.join('.')} => #{parent_spec}#{route.path.spec}
#{route_name}: function(#{build_params(required_parts)}) {
if (!#{LAST_OPTIONS_KEY}){ #{LAST_OPTIONS_KEY} = {}; }
return Utils.build_path(#{json(required_parts)}, #{json(optional_parts)}, #{json(serialize(route.path.spec, parent_spec))}, arguments);
}#{",\n" + url_link if url_link.length > 0}
JS
Expand All @@ -170,16 +169,18 @@ def build_js(route, parent_route)
def generate_url_link(name, route_name, required_parts)
return "" unless @options[:url_links]
raise "invalid URL format in url_links (ex: http[s]://example.com)" if @options[:url_links].match(URI::regexp(%w(http https))).nil?
url_route_name = "#{name.join('_')}_url"
url_route_name = url_route_name.camelize(:lower) if true == @options[:camel_case]
_ = <<-JS.strip!
#{url_route_name}: function(#{build_params(required_parts)}) {
if (!#{LAST_OPTIONS_KEY}){ #{LAST_OPTIONS_KEY} = {}; }
#{generate_route_name(name, true)}: function(#{build_params(required_parts)}) {
return "" + #{@options[:url_links].inspect} + this.#{route_name}(#{build_params(required_parts)});
}
JS
end

def generate_route_name(name, is_url = false)
route_name = "#{name.join('_')}_#{is_url ? "url" : "path"}"
@options[:camel_case] ? route_name.camelize(:lower) : route_name
end

def json(string)
self.class.json(string)
end
Expand All @@ -188,7 +189,7 @@ def build_params required_parts
params = required_parts.map do |name|
# prepending each parameter name with underscore
# to prevent conflict with JS reserved words
"_" + name.to_s
"_#{name}"
end << LAST_OPTIONS_KEY
params.join(", ")
end
Expand Down
73 changes: 60 additions & 13 deletions lib/routes.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

98 changes: 78 additions & 20 deletions lib/routes.js.coffee
Expand Up @@ -6,21 +6,21 @@ defaults =

NodeTypes = NODE_TYPES
Utils =

serialize: (obj) ->
return "" unless obj
if window.jQuery
result = window.jQuery.param(obj)
return (if not result then "" else "?#{result}")
return (if not result then "" else result)
s = []
for own key, prop of obj
if prop?
if prop instanceof Array
for val, i in prop
s.push "#{key}#{encodeURIComponent("[]")}=#{encodeURIComponent(val.toString())}"
else
s.push "#{key}=#{encodeURIComponent(prop.toString())}"
for own key, prop of obj when prop?
if @getObjectType(prop) is "array"
for val, i in prop
s.push "#{key}#{encodeURIComponent("[]")}=#{encodeURIComponent(val.toString())}"
else
s.push "#{key}=#{encodeURIComponent(prop.toString())}"
return "" unless s.length
"?#{s.join("&")}"
s.join("&")

clean_path: (path) ->
path = path.split("://")
Expand All @@ -37,12 +37,12 @@ Utils =
anchor = ""
if options.hasOwnProperty("anchor")
anchor = "##{options.anchor}"
options.anchor = null
delete options.anchor
anchor

extract_options: (number_of_params, args) ->
ret_value = {}
if args.length > number_of_params and typeof (args[args.length - 1]) is "object"
if args.length > number_of_params and @getObjectType(args[args.length - 1]) is "object"
ret_value = args.pop()
ret_value

Expand All @@ -51,13 +51,13 @@ Utils =
# null, undefined, false or ''
return "" unless object
property = object
if typeof (object) is "object"
if @getObjectType(object) is "object"
property = object.to_param or object.id or object
property = property.call(object) if typeof (property) is "function"
property = property.call(object) if @getObjectType(property) is "function"
property.toString()

clone: (obj) ->
return obj if null is obj or "object" isnt typeof obj
return obj if !obj? or "object" isnt @getObjectType(obj)
copy = obj.constructor()
copy[key] = attr for own key, attr of obj
copy
Expand All @@ -73,8 +73,10 @@ Utils =
throw new Error("Too many parameters provided for path") if args.length > required_parameters.length
parameters = @prepare_parameters(required_parameters, args, opts)
@set_default_url_options optional_parts, parameters
result = "#{Utils.get_prefix()}#{@visit(route, parameters)}"
Utils.clean_path("#{result}#{Utils.extract_anchor(parameters)}") + Utils.serialize(parameters)
result = "#{@get_prefix()}#{@visit(route, parameters)}"
url = Utils.clean_path("#{result}#{@extract_anchor(parameters)}")
url += "?#{url_params}" if (url_params = @serialize(parameters)).length
url
#
# This function is JavaScript impelementation of the
# Journey::Visitors::Formatter that builds route by given parameters
Expand All @@ -86,11 +88,13 @@ Utils =
# If set to `true`, this method will not throw when encountering
# a missing parameter (used in recursive calls).
#
visit: (route, parameters, optional) ->
visit: (route, parameters, optional = false) ->
[type, left, right] = route
switch type
when NodeTypes.GROUP, NodeTypes.STAR
when NodeTypes.GROUP
@visit left, parameters, true
when NodeTypes.STAR
@visit_globbing left, parameters, true
when NodeTypes.LITERAL, NodeTypes.SLASH, NodeTypes.DOT
left
when NodeTypes.CAT
Expand All @@ -101,7 +105,7 @@ Utils =
when NodeTypes.SYMBOL
value = parameters[left]
if value?
parameters[left] = null
delete parameters[left]
return @path_identifier(value)
if optional
"" # missing parameter
Expand All @@ -115,11 +119,65 @@ Utils =
else
throw new Error("Unknown Rails node type")

#
# This method convert value for globbing in right value for rails route
#
visit_globbing: (route, parameters, optional) ->
[type, left, right] = route
value = parameters[left]
return @visit(route, parameters, optional) unless value?
parameters[left] = switch @getObjectType(value)
when "array"
value.join("/")
else
value
@visit route, parameters, optional

#
# This method check and return prefix from options
#
get_prefix: ->
prefix = defaults.prefix
prefix = (if prefix.match("/$") then prefix else "#{prefix}/") if prefix isnt ""
prefix

#
# This is helper method to define object type.
# The typeof operator is probably the biggest design flaw of JavaScript, simply because it's basically completely broken.
#
# Value Class Type
# -------------------------------------
# "foo" String string
# new String("foo") String object
# 1.2 Number number
# new Number(1.2) Number object
# true Boolean boolean
# new Boolean(true) Boolean object
# new Date() Date object
# new Error() Error object
# [1,2,3] Array object
# new Array(1, 2, 3) Array object
# new Function("") Function function
# /abc/g RegExp object
# new RegExp("meow") RegExp object
# {} Object object
# new Object() Object object
#
# What is why I use Object.prototype.toString() to know better type of variable. Or use jQuery.type, if it available.
# _classToTypeCache used for perfomance cache of types map (underscore at the beginning mean private method - of course it doesn't realy private).
#
_classToTypeCache: null
_classToType: ->
return @_classToTypeCache if @_classToTypeCache?
@_classToTypeCache = {}
for name in "Boolean Number String Function Array Date RegExp Undefined Null".split(" ")
@_classToTypeCache["[object " + name + "]"] = name.toLowerCase()
@_classToTypeCache
getObjectType: (obj) ->
return window.jQuery.type(obj) if window.jQuery and window.jQuery.type?
strType = Object::toString.call(obj)
@_classToType()[strType] or "object"

namespace: (root, namespaceString) ->
parts = (if namespaceString then namespaceString.split(".") else [])
return unless parts.length
Expand All @@ -129,4 +187,4 @@ Utils =

Utils.namespace window, "NAMESPACE"
window.NAMESPACE = ROUTES
window.NAMESPACE.options = defaults
window.NAMESPACE.options = defaults

0 comments on commit 488cff0

Please sign in to comment.