Initial commit
This commit is contained in:
commit
2ea0ee5b85
4
README
Normal file
4
README
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
JavascriptRoutes
|
||||||
|
================
|
||||||
|
|
||||||
|
Description goes here
|
22
Rakefile
Normal file
22
Rakefile
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
require 'rake'
|
||||||
|
require 'rake/testtask'
|
||||||
|
require 'rake/rdoctask'
|
||||||
|
|
||||||
|
desc 'Default: run unit tests.'
|
||||||
|
task :default => :test
|
||||||
|
|
||||||
|
desc 'Test the javascript_routes plugin.'
|
||||||
|
Rake::TestTask.new(:test) do |t|
|
||||||
|
t.libs << 'lib'
|
||||||
|
t.pattern = 'test/**/*_test.rb'
|
||||||
|
t.verbose = true
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'Generate documentation for the javascript_routes plugin.'
|
||||||
|
Rake::RDocTask.new(:rdoc) do |rdoc|
|
||||||
|
rdoc.rdoc_dir = 'rdoc'
|
||||||
|
rdoc.title = 'JavascriptRoutes'
|
||||||
|
rdoc.options << '--line-numbers' << '--inline-source'
|
||||||
|
rdoc.rdoc_files.include('README')
|
||||||
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||||
|
end
|
208
bin/jsmin.rb
Executable file
208
bin/jsmin.rb
Executable file
|
@ -0,0 +1,208 @@
|
||||||
|
#!/usr/bin/ruby
|
||||||
|
# jsmin.rb 2007-05-22
|
||||||
|
# Author: Uladzislau Latynski
|
||||||
|
# This work is a translation from C to Ruby of jsmin.c published by
|
||||||
|
# Douglas Crockford. Permission is hereby granted to use the Ruby
|
||||||
|
# version under the same conditions as the jsmin.c on which it is
|
||||||
|
# based.
|
||||||
|
#
|
||||||
|
# /* jsmin.c
|
||||||
|
# 2003-04-21
|
||||||
|
#
|
||||||
|
# Copyright (c) 2002 Douglas Crockford (www.crockford.com)
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
# this software and associated documentation files (the "Software"), to deal in
|
||||||
|
# the Software without restriction, including without limitation the rights to
|
||||||
|
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
# of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
# so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
|
# copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# The Software shall be used for Good, not Evil.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
# SOFTWARE.
|
||||||
|
|
||||||
|
class JSMin
|
||||||
|
|
||||||
|
EOF = -1
|
||||||
|
|
||||||
|
def initialize(input, output)
|
||||||
|
@input, @output = input, output
|
||||||
|
@theA, @theB = '', ''
|
||||||
|
end
|
||||||
|
|
||||||
|
# isAlphanum -- return true if the character is a letter, digit, underscore,
|
||||||
|
# dollar sign, or non-ASCII character
|
||||||
|
def isAlphanum(c)
|
||||||
|
return false if !c || c == EOF
|
||||||
|
return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
|
||||||
|
(c >= 'A' && c <= 'Z') || c == '_' || c == '$' ||
|
||||||
|
c == '\\' || c[0] > 126)
|
||||||
|
end
|
||||||
|
|
||||||
|
# get -- return the next character from stdin. Watch out for lookahead. If
|
||||||
|
# the character is a control character, translate it to a space or linefeed.
|
||||||
|
def get()
|
||||||
|
c = @input.getc
|
||||||
|
return EOF if(!c)
|
||||||
|
c = c.chr
|
||||||
|
return c if (c >= " " || c == "\n" || c.unpack("c") == EOF)
|
||||||
|
return "\n" if (c == "\r")
|
||||||
|
return " "
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get the next character without getting it.
|
||||||
|
def peek()
|
||||||
|
lookaheadChar = @input.getc
|
||||||
|
@input.ungetc(lookaheadChar)
|
||||||
|
return lookaheadChar.chr
|
||||||
|
end
|
||||||
|
|
||||||
|
# mynext -- get the next character, excluding comments.
|
||||||
|
# peek() is used to see if a '/' is followed by a '/' or '*'.
|
||||||
|
def mynext()
|
||||||
|
c = get
|
||||||
|
if (c == "/")
|
||||||
|
if(peek == "/")
|
||||||
|
while(true)
|
||||||
|
c = get
|
||||||
|
if (c <= "\n")
|
||||||
|
return c
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if(peek == "*")
|
||||||
|
get
|
||||||
|
while(true)
|
||||||
|
case get
|
||||||
|
when "*"
|
||||||
|
if (peek == "/")
|
||||||
|
get
|
||||||
|
return " "
|
||||||
|
end
|
||||||
|
when EOF
|
||||||
|
raise "Unterminated comment"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return c
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# action -- do something! What you do is determined by the argument: 1
|
||||||
|
# Output A. Copy B to A. Get the next B. 2 Copy B to A. Get the next B.
|
||||||
|
# (Delete A). 3 Get the next B. (Delete B). action treats a string as a
|
||||||
|
# single character. Wow! action recognizes a regular expression if it is
|
||||||
|
# preceded by ( or , or =.
|
||||||
|
def action(a)
|
||||||
|
if(a==1)
|
||||||
|
@output.write @theA
|
||||||
|
end
|
||||||
|
if(a==1 || a==2)
|
||||||
|
@theA = @theB
|
||||||
|
if (@theA == "\'" || @theA == "\"")
|
||||||
|
while (true)
|
||||||
|
@output.write @theA
|
||||||
|
@theA = get
|
||||||
|
break if (@theA == @theB)
|
||||||
|
raise "Unterminated string literal" if (@theA <= "\n")
|
||||||
|
if (@theA == "\\")
|
||||||
|
@output.write @theA
|
||||||
|
@theA = get
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if(a==1 || a==2 || a==3)
|
||||||
|
@theB = mynext
|
||||||
|
if (@theB == "/" && (@theA == "(" || @theA == "," || @theA == "=" ||
|
||||||
|
@theA == ":" || @theA == "[" || @theA == "!" ||
|
||||||
|
@theA == "&" || @theA == "|" || @theA == "?" ||
|
||||||
|
@theA == "{" || @theA == "}" || @theA == ";" ||
|
||||||
|
@theA == "\n"))
|
||||||
|
@output.write @theA
|
||||||
|
@output.write @theB
|
||||||
|
while (true)
|
||||||
|
@theA = get
|
||||||
|
if (@theA == "/")
|
||||||
|
break
|
||||||
|
elsif (@theA == "\\")
|
||||||
|
@output.write @theA
|
||||||
|
@theA = get
|
||||||
|
elsif (@theA <= "\n")
|
||||||
|
raise "Unterminated RegExp Literal"
|
||||||
|
end
|
||||||
|
@output.write @theA
|
||||||
|
end
|
||||||
|
@theB = mynext
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# jsmin -- Copy the input to the output, deleting the characters which are
|
||||||
|
# insignificant to JavaScript. Comments will be removed. Tabs will be
|
||||||
|
# replaced with spaces. Carriage returns will be replaced with linefeeds.
|
||||||
|
# Most spaces and linefeeds will be removed.
|
||||||
|
def jsmin
|
||||||
|
@theA = "\n"
|
||||||
|
action(3)
|
||||||
|
while (@theA != EOF)
|
||||||
|
case @theA
|
||||||
|
when " "
|
||||||
|
if (isAlphanum(@theB))
|
||||||
|
action(1)
|
||||||
|
else
|
||||||
|
action(2)
|
||||||
|
end
|
||||||
|
when "\n"
|
||||||
|
case (@theB)
|
||||||
|
when "{","[","(","+","-"
|
||||||
|
action(1)
|
||||||
|
when " "
|
||||||
|
action(3)
|
||||||
|
else
|
||||||
|
if (isAlphanum(@theB))
|
||||||
|
action(1)
|
||||||
|
else
|
||||||
|
action(2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
case (@theB)
|
||||||
|
when " "
|
||||||
|
if (isAlphanum(@theA))
|
||||||
|
action(1)
|
||||||
|
else
|
||||||
|
action(3)
|
||||||
|
end
|
||||||
|
when "\n"
|
||||||
|
case (@theA)
|
||||||
|
when "}","]",")","+","-","\"","\\", "'", '"'
|
||||||
|
action(1)
|
||||||
|
else
|
||||||
|
if (isAlphanum(@theA))
|
||||||
|
action(1)
|
||||||
|
else
|
||||||
|
action(3)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
action(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
JSMin.new($stdin, $stdout).jsmin if __FILE__ == $0
|
4
init.rb
Normal file
4
init.rb
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
require 'javascript_routes'
|
||||||
|
|
||||||
|
ActionController::Routing::Routes.load!
|
||||||
|
JavascriptRoutes.generate(:lite => ENV['ROUTES_JS_LITE'])
|
1
install.rb
Normal file
1
install.rb
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# Install hook code here
|
148
lib/javascript_routes.rb
Normal file
148
lib/javascript_routes.rb
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
module JavascriptRoutes
|
||||||
|
|
||||||
|
JS = File.join(File.dirname(__FILE__), 'javascripts', 'routes.js')
|
||||||
|
JS_PACKED = File.join(File.dirname(__FILE__), 'javascripts', 'routes-min.js')
|
||||||
|
JS_AJAX = File.join(File.dirname(__FILE__), 'javascripts', 'routes-ajax.js')
|
||||||
|
FILENAME = File.join(RAILS_ROOT, 'public', 'javascripts', 'routes.js')
|
||||||
|
FILENAME_AJAX = File.join(RAILS_ROOT, 'public', 'javascripts', 'routes-ajax.js')
|
||||||
|
|
||||||
|
def self.generate(options = {})
|
||||||
|
options.symbolize_keys!.reverse_merge!(:pack => true)
|
||||||
|
|
||||||
|
routes = options[:routes] || ActionController::Routing::Routes.routes.select{|r|
|
||||||
|
r.conditions[:method].nil? || r.conditions[:method] == :get
|
||||||
|
}
|
||||||
|
named_routes = options[:named_routes] || ActionController::Routing::Routes.named_routes.routes.select{|n,r|
|
||||||
|
r.conditions[:method].nil? || r.conditions[:method] == :get
|
||||||
|
}
|
||||||
|
filename = options[:filename] || FILENAME
|
||||||
|
filename_ajax = options[:filename_ajax] || FILENAME_AJAX
|
||||||
|
|
||||||
|
#Will only create simple functions for named routes
|
||||||
|
if options[:lite]
|
||||||
|
|
||||||
|
File.open(filename, 'w') do |file|
|
||||||
|
routes_object = ' var r = "{'
|
||||||
|
named_routes.each_with_index do |a,i|
|
||||||
|
n,r = *a
|
||||||
|
routes_object << "#{n}: function(#{r.segments.select{|s| s.respond_to?(:key) }.map(&:key).join(', ')}){ "
|
||||||
|
routes_object << 'return '
|
||||||
|
|
||||||
|
r.segments.each_with_index do |s,j|
|
||||||
|
if s.respond_to?(:key)
|
||||||
|
routes_object << "'" unless i==0 || r.segments[j-1].respond_to?(:key)
|
||||||
|
routes_object << "+#{s.key}"
|
||||||
|
else
|
||||||
|
routes_object << '+' if j > 0 && r.segments[j-1].respond_to?(:key)
|
||||||
|
routes_object << "'" if j == 0 || r.segments[j-1].respond_to?(:key)
|
||||||
|
routes_object << s.to_s unless j != 0 && j == r.segments.size-1 && s.to_s == '/'
|
||||||
|
routes_object << "'" if j == r.segments.size-1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
routes_object << ';'
|
||||||
|
routes_object << " }"
|
||||||
|
routes_object << ',' if i < named_routes.size-1
|
||||||
|
end
|
||||||
|
routes_object << "}\";\n"
|
||||||
|
|
||||||
|
#Find words with 5 or more characters that appear more than once
|
||||||
|
words = routes_object.scan(/[a-z_]{5,}/).group_by{|s| s }.inject([]){|r,a| r << a.first if a.last.size > 1; r }
|
||||||
|
#Replace words with placeholders
|
||||||
|
words.each_with_index{|w,i| routes_object.gsub!(w, "$#{i}") }
|
||||||
|
|
||||||
|
file << "var Routes = (function(){\n"
|
||||||
|
|
||||||
|
#Export words to JS
|
||||||
|
file << " var s = [" + words.map{|w| "'#{w}'" }.join(',') + "];\n"
|
||||||
|
file << routes_object
|
||||||
|
#Put the words back in (using JS) and eval the result
|
||||||
|
file << " return eval('('+r.replace(/\\$(\\d+)/g, function(m,i){ return s[i]; })+')');\n"
|
||||||
|
|
||||||
|
file << "})();"
|
||||||
|
end
|
||||||
|
|
||||||
|
#Will create all routes with generation logic (from lib/routes.js)
|
||||||
|
else
|
||||||
|
|
||||||
|
File.open(filename, 'w') do |file|
|
||||||
|
file << File.read(options[:pack] ? JS_PACKED : JS)
|
||||||
|
|
||||||
|
file << "\n\n(function(){\n\n"#Don't pollute the global namespace
|
||||||
|
|
||||||
|
#This is ugly, but it works. It builds a JS array
|
||||||
|
#with an object for each route. Most of the ugliness
|
||||||
|
#is to reduce the amount of space it takes up.
|
||||||
|
routes_array = ''
|
||||||
|
routes_array << 'var r = "['
|
||||||
|
routes.each_with_index do |r,i|
|
||||||
|
routes_array << '{'
|
||||||
|
|
||||||
|
#Append a name if this is a named route
|
||||||
|
named_route = named_routes.find{|name,route| route.equal?(r) }
|
||||||
|
routes_array << "n:'#{named_route.first}'," if named_route
|
||||||
|
|
||||||
|
#Append segments as a string with @ between. This will
|
||||||
|
#be split() using JS.
|
||||||
|
routes_array << "s:'"
|
||||||
|
routes_array << r.segments.map do |s|
|
||||||
|
if s.is_a?(ActionController::Routing::PathSegment)
|
||||||
|
'*' + s.to_s[1..-1] + (s.is_optional ? 't' : 'f')
|
||||||
|
else
|
||||||
|
s.to_s + (s.is_optional ? 't' : 'f')
|
||||||
|
end
|
||||||
|
end.join('@')
|
||||||
|
routes_array << "'"
|
||||||
|
|
||||||
|
#Append params object
|
||||||
|
routes_array << ',r:{'
|
||||||
|
routes_array << r.requirements.map do |k,v|
|
||||||
|
"#{k}:'#{v}'"
|
||||||
|
end.join(',')
|
||||||
|
routes_array << '}'
|
||||||
|
|
||||||
|
routes_array << '}'
|
||||||
|
routes_array << ',' unless i == routes.size-1
|
||||||
|
end
|
||||||
|
routes_array << "]\";\n"
|
||||||
|
|
||||||
|
#Find words that occur more than once and are more than 5 characters in length
|
||||||
|
words = routes_array.scan(/[a-z_]{5,}/).group_by{|s| s }.inject([]){|r,a| r << a.first if a.last.size > 1; r }
|
||||||
|
#Replace words with placeholders
|
||||||
|
words.each_with_index{|w,i| routes_array.gsub!(w, "$#{i}") }
|
||||||
|
|
||||||
|
#Export words to JS
|
||||||
|
file << " var s = [" + words.map{|w| "'#{w}'" }.join(',') + "];\n"
|
||||||
|
file << ' '+routes_array
|
||||||
|
#Put the words back in (using JS) and eval the result
|
||||||
|
file << " r = eval('('+r.replace(/\\$(\\d+)/g, function(m,i){ return s[i]; })+')');\n\n"
|
||||||
|
|
||||||
|
#Add routes
|
||||||
|
file << " for (var i = 0; i < r.length; i++) {\n"
|
||||||
|
file << " var s=[];\n"
|
||||||
|
file << " var segs=r[i].s.split('@');\n"
|
||||||
|
file << " for (var j = 0; j < segs.length; j++) {\n"
|
||||||
|
file << " s.push(Route.S(segs[j].slice(0, -1), segs[j].slice(-1) == 't'));\n"
|
||||||
|
file << " }\n"
|
||||||
|
file << " Routes.push(new Route(s, r[i].r, r[i].n));\n"
|
||||||
|
file << " }\n\n"
|
||||||
|
file << " Routes.extractNamed();\n\n"
|
||||||
|
|
||||||
|
file << "})();"
|
||||||
|
end
|
||||||
|
|
||||||
|
#Add ajax extras
|
||||||
|
File.open filename_ajax, 'w' do |f|
|
||||||
|
f.write(File.read(JS_AJAX))
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
rescue => e
|
||||||
|
|
||||||
|
warn("\n\nCould not write routes.js: \"#{e.class}:#{e.message}\"\n\n")
|
||||||
|
File.truncate(filename, 0) rescue nil
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
41
lib/javascripts/routes-ajax.js
Normal file
41
lib/javascripts/routes-ajax.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
//Michael Schuerig
|
||||||
|
|
||||||
|
Route.Object = function (url) {
|
||||||
|
this.url = url;
|
||||||
|
};
|
||||||
|
|
||||||
|
Route.Object.prototype = {
|
||||||
|
toString: function() { return this.url; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//Replace Route.prototype.generate
|
||||||
|
(function(oldGenerate){
|
||||||
|
|
||||||
|
Route.prototype.generate = function(){
|
||||||
|
var path = oldGenerate.apply(this, arguments);
|
||||||
|
return path && new Route.Object(path);
|
||||||
|
};
|
||||||
|
|
||||||
|
})(Route.prototype.generate);
|
||||||
|
|
||||||
|
|
||||||
|
if (window.Prototype) {
|
||||||
|
Object.extend(Route.Object.prototype, {
|
||||||
|
get: function(options) { this.method = 'get'; return this.request(options) },
|
||||||
|
post: function(options) { this.method = 'post'; return this.request(options) },
|
||||||
|
put: function(options) { this.method = 'put'; return this.request(options) },
|
||||||
|
'delete': function(options) { this.method = 'delete'; return this.request(options) },
|
||||||
|
request: function(options) {
|
||||||
|
var result = this;
|
||||||
|
options = options || {};
|
||||||
|
var async = $H(options).any(function(p) { return /^on[A-Z1-5]/.test(p[0]); });
|
||||||
|
options = Object.extend({ asynchronous: async, method: this.method }, options);
|
||||||
|
if (!async) {
|
||||||
|
options.onComplete = function(r) { result = r.responseText; };
|
||||||
|
}
|
||||||
|
new Ajax.Request(this.url, options);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
18
lib/javascripts/routes-min.js
vendored
Normal file
18
lib/javascripts/routes-min.js
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
|
||||||
|
(function(){var iterate=function(o,fn){if(typeof o.length==='number'){for(var i=0;i<o.length;i++){if(fn(o[i],i,o)===false){return false;}}
|
||||||
|
return true;}else{for(var p in o){if(o.hasOwnProperty(p)){fn(p,o[p]);}}}};var all=function(arr,fn){var allTrue=true;iterate(arr,function(e,i,a){if(!fn(e,i,a)){allTrue=false;return false;}});return allTrue;};var extend=function(target,source){iterate(source,function(k,v){target[k]=v;});return target;};var merge=function(t,s){var rv={};extend(rv,t);extend(rv,s);return rv;};var Route=function(segments,params,name){this.segments=segments||[];this.params=params||{};this.name=name;};Route.prototype={generate:function(pa,op){var options=merge(Routes.defaultOptions,op||{});var params=options.noDefaults?merge({},pa||{}):merge(Routes.defaultParams,pa||{});var path='';var hasParam=false;var routeMatch=true;iterate(this.params,function(k,v){hasParam=false;if((typeof v==='string'&&v===params[k])||((v.constructor===RegExp)&&(new RegExp('^'+v.source+'$')).test(params[k]))){hasParam=true;delete params[k];}
|
||||||
|
if(!hasParam){routeMatch=false;return;}});if(!routeMatch){return false;}
|
||||||
|
try{iterate(this.segments,function(segment,index,segments){switch(segment.type){case'divider':path=path+segment.value;break;case'static':path=path+segment.value;break;case'dynamic':if(params[segment.value]){path=path+params[segment.value];delete params[segment.value];}else if(!segment.optional){throw'nomatch';}else{delete params[segment.value];throw'done';}
|
||||||
|
break;case'path':if(params[segment.value]){if(params[segment.value]instanceof Array){path=path+params[segment.value].join('/');}else{path=path+(params[segment.value]||'');}
|
||||||
|
delete params[segment.value];}else if(!segment.optional){throw'nomatch';}else{delete params[segment.value];throw'done';}
|
||||||
|
break;}});}catch(e){if(e!=='done'){if(e==='nomatch'){return false;}else{throw e;}}}
|
||||||
|
if(!options.includeSlash&&path.match(/.+\/$/)){path=path.slice(0,-1);}
|
||||||
|
if(!options.onlyPath){var portString=options.port?':'+options.port:'';path=[options.protocol,options.host,portString,path].join('')}
|
||||||
|
var leftOvers=[];iterate(params,function(k,v){leftOvers.push(k+'='+v);});if(leftOvers.length>0){path=path+'?'+leftOvers.join('&');}
|
||||||
|
if(options.escape){path=encodeURI(path);}
|
||||||
|
return path;},toString:function(){return this.segments.join('');}};Route.Segment=function(value,type,optional){this.value=value;this.type=type||'static';this.optional=(typeof optional==='boolean'?optional:true);};Route.Segment.prototype={isDynamic:function(){return this.type==='dynamic'||this.type==='path';},toString:function(){if(this.type==='dynamic'){return':'+this.value;}else if(this.type==='path'){return'*'+this.value;}else{return this.value;}},equal:function(other){return other.constructor===this.constructor&&other.value===this.value&&other.type===this.type&&other.optional===this.optional;}};Route.createSegment=function(s,optional){if(s.match(/^[\/;?.]$/)){return new Route.Segment(s,'divider',optional);}else if(s.indexOf(':')===0){return new Route.Segment(s.slice(1),'dynamic',optional);}else if(s.indexOf('*')===0){return new Route.Segment(s.slice(1),'path',optional);}else{return new Route.Segment(s,'static',optional);}};Route.S=Route.createSegment;var Routes=[];Routes.named=[];Routes.defaultParams={action:'index'};Routes.defaultOptions={escape:true,port:window.location.port,protocol:window.location.protocol+'//',host:window.location.hostname};Routes.extractNamed=function(){var route;for(var i=0;i<this.length;i++){route=this[i];if(route.name){this.named.push(route);this.named[route.name]=route;this[route.name]=(function(route){var fn=function(){var params={},options={},count;for(var p in route.params){if(route.params.hasOwnProperty(p)&&route.params[p].constructor!==RegExp){params[p]=route.params[p];}}
|
||||||
|
if(typeof arguments[0]==='object'&&!(arguments[0]instanceof Array)){extend(params,arguments[0]);options=arguments[1];}else{if(typeof arguments[arguments.length-1]==='object'){options=arguments[arguments.length-1];}
|
||||||
|
var count=0;for(var i=0;i<route.segments.length;i++){if(route.segments[i].isDynamic()){if(arguments.length>count){params[route.segments[i].value]=arguments[count];}
|
||||||
|
count++;}}}
|
||||||
|
return route.generate(params,options);};fn.toParams=function(params){return merge(route.params,params||{});};return fn;})(route);}}};Routes.generate=function(params,options){params=params||{};var path;for(var i=0;i<this.length;i++){path=this[i].generate(params,options);if(path){return path;}}
|
||||||
|
return false;};Routes.named.toString=Routes.toString=function(){return this.join(', ');};window['Route']=Route;window['Routes']=Routes;})();
|
275
lib/javascripts/routes.js
Normal file
275
lib/javascripts/routes.js
Normal file
|
@ -0,0 +1,275 @@
|
||||||
|
(function(){
|
||||||
|
|
||||||
|
var iterate = function(o, fn){
|
||||||
|
if (typeof o.length === 'number') {//Array
|
||||||
|
for (var i = 0; i < o.length; i++) {
|
||||||
|
//Stop iterating if return value is false
|
||||||
|
if (fn(o[i], i, o) === false){ return false; }
|
||||||
|
}
|
||||||
|
return true;//All done
|
||||||
|
} else {//Object
|
||||||
|
for (var p in o){
|
||||||
|
if (o.hasOwnProperty(p)) {
|
||||||
|
fn(p, o[p]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var all = function(arr, fn){
|
||||||
|
var allTrue = true;
|
||||||
|
iterate(arr, function(e,i,a){
|
||||||
|
if (!fn(e,i,a)) { allTrue = false; return false; }
|
||||||
|
});
|
||||||
|
return allTrue;
|
||||||
|
};
|
||||||
|
|
||||||
|
var extend = function(target, source){
|
||||||
|
iterate(source, function(k,v){ target[k] = v; });
|
||||||
|
return target;
|
||||||
|
};
|
||||||
|
|
||||||
|
var merge = function(t,s){
|
||||||
|
var rv = {};
|
||||||
|
extend(rv, t);
|
||||||
|
extend(rv, s);
|
||||||
|
return rv;
|
||||||
|
};
|
||||||
|
|
||||||
|
var Route = function(segments, params, name){
|
||||||
|
this.segments = segments || [];
|
||||||
|
this.params = params || {};
|
||||||
|
this.name = name;
|
||||||
|
};
|
||||||
|
|
||||||
|
Route.prototype = {
|
||||||
|
generate: function(pa, op){
|
||||||
|
var options = merge(Routes.defaultOptions, op || {});
|
||||||
|
var params = options.noDefaults ? merge({}, pa || {}) : merge(Routes.defaultParams, pa || {});
|
||||||
|
|
||||||
|
var path = '';
|
||||||
|
|
||||||
|
var hasParam = false;
|
||||||
|
var routeMatch = true;
|
||||||
|
iterate(this.params, function(k,v){
|
||||||
|
hasParam = false;
|
||||||
|
if (
|
||||||
|
(typeof v === 'string' && v === params[k]) ||
|
||||||
|
((v.constructor === RegExp) && (new RegExp('^'+v.source+'$')).test(params[k]))
|
||||||
|
) {
|
||||||
|
hasParam = true;
|
||||||
|
delete params[k];
|
||||||
|
}
|
||||||
|
if (!hasParam) {
|
||||||
|
routeMatch = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!routeMatch) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
iterate(this.segments, function(segment, index, segments){
|
||||||
|
switch (segment.type) {
|
||||||
|
case 'divider':
|
||||||
|
path = path + segment.value;
|
||||||
|
break;
|
||||||
|
case 'static':
|
||||||
|
path = path + segment.value;
|
||||||
|
break;
|
||||||
|
case 'dynamic':
|
||||||
|
if (params[segment.value]) {
|
||||||
|
path = path + params[segment.value];
|
||||||
|
delete params[segment.value];
|
||||||
|
} else if (!segment.optional) {
|
||||||
|
throw 'nomatch';
|
||||||
|
} else {
|
||||||
|
delete params[segment.value];
|
||||||
|
throw 'done';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'path':
|
||||||
|
if (params[segment.value]) {
|
||||||
|
if (params[segment.value] instanceof Array) {
|
||||||
|
path = path + params[segment.value].join('/');
|
||||||
|
} else {
|
||||||
|
path = path + (params[segment.value] || '');
|
||||||
|
}
|
||||||
|
delete params[segment.value];
|
||||||
|
} else if (!segment.optional) {
|
||||||
|
throw 'nomatch';
|
||||||
|
} else {
|
||||||
|
delete params[segment.value];
|
||||||
|
throw 'done';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (e !== 'done') { //done == don't append any more segments
|
||||||
|
if (e === 'nomatch') { //params don't match this route
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.includeSlash && path.match(/.+\/$/)) {
|
||||||
|
path = path.slice(0,-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.onlyPath) {
|
||||||
|
var portString = options.port ? ':'+options.port : '';
|
||||||
|
path = [options.protocol, options.host, portString, path].join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
var leftOvers = [];
|
||||||
|
iterate(params, function(k,v){
|
||||||
|
leftOvers.push(k + '=' + v);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (leftOvers.length > 0) {
|
||||||
|
path = path + '?' + leftOvers.join('&');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.escape) {
|
||||||
|
path = encodeURI(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
},
|
||||||
|
|
||||||
|
toString: function(){
|
||||||
|
return this.segments.join('');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Route.Segment = function(value, type, optional){
|
||||||
|
this.value = value;
|
||||||
|
this.type = type || 'static';
|
||||||
|
this.optional = (typeof optional === 'boolean' ? optional : true);
|
||||||
|
};
|
||||||
|
|
||||||
|
Route.Segment.prototype = {
|
||||||
|
isDynamic: function(){
|
||||||
|
return this.type === 'dynamic' || this.type === 'path';
|
||||||
|
},
|
||||||
|
toString: function(){
|
||||||
|
if (this.type === 'dynamic') {
|
||||||
|
return ':'+this.value;
|
||||||
|
} else if (this.type === 'path') {
|
||||||
|
return '*'+this.value;
|
||||||
|
} else {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
equal: function(other){
|
||||||
|
return other.constructor === this.constructor && other.value === this.value &&
|
||||||
|
other.type === this.type && other.optional === this.optional;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Route.createSegment = function(s, optional){
|
||||||
|
if (s.match(/^[\/;?.]$/)) {
|
||||||
|
return new Route.Segment(s, 'divider', optional);
|
||||||
|
} else if (s.indexOf(':') === 0) {
|
||||||
|
return new Route.Segment(s.slice(1), 'dynamic', optional);
|
||||||
|
} else if (s.indexOf('*') === 0) {
|
||||||
|
return new Route.Segment(s.slice(1), 'path', optional);
|
||||||
|
} else {
|
||||||
|
return new Route.Segment(s, 'static', optional);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Route.S = Route.createSegment;
|
||||||
|
|
||||||
|
var Routes = [];
|
||||||
|
Routes.named = [];
|
||||||
|
Routes.defaultParams = {action:'index'};//Default parameters for route generation
|
||||||
|
Routes.defaultOptions = {//Defaults for second parameter
|
||||||
|
escape: true,
|
||||||
|
port: window.location.port,
|
||||||
|
protocol: window.location.protocol+'//',
|
||||||
|
host: window.location.hostname
|
||||||
|
};
|
||||||
|
|
||||||
|
Routes.extractNamed = function(){
|
||||||
|
var route;
|
||||||
|
for (var i = 0; i < this.length; i++) {
|
||||||
|
route = this[i];
|
||||||
|
if (route.name) {
|
||||||
|
this.named.push(route);
|
||||||
|
this.named[route.name] = route;
|
||||||
|
|
||||||
|
this[route.name] = (function(route){
|
||||||
|
var fn = function(){
|
||||||
|
var params = {},
|
||||||
|
options = {},
|
||||||
|
count;
|
||||||
|
|
||||||
|
//Add defaults from route
|
||||||
|
for (var p in route.params) {
|
||||||
|
if (route.params.hasOwnProperty(p) && route.params[p].constructor !== RegExp) {
|
||||||
|
params[p] = route.params[p];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Allows Routes.name('foo', 'bar', {opts}) or Routes.name({foo:'foo', bar:'bar'}, {opts})
|
||||||
|
if (typeof arguments[0] === 'object' && !(arguments[0] instanceof Array)) {
|
||||||
|
extend(params, arguments[0]);
|
||||||
|
options = arguments[1];
|
||||||
|
} else {
|
||||||
|
if (typeof arguments[arguments.length-1] === 'object') {
|
||||||
|
options = arguments[arguments.length-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
var count = 0;
|
||||||
|
for (var i=0; i < route.segments.length; i++) {
|
||||||
|
if (route.segments[i].isDynamic()) {
|
||||||
|
if (arguments.length > count) { params[route.segments[i].value] = arguments[count]; }
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return route.generate(params, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Routes.name.toParams() => {...}
|
||||||
|
//Like hash_for_x in Rails, kind of
|
||||||
|
fn.toParams = function(params){
|
||||||
|
return merge(route.params, params || {});
|
||||||
|
};
|
||||||
|
|
||||||
|
return fn;
|
||||||
|
})(route); //Pass the route to keep it in scope
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Routes.generate = function(params, options){
|
||||||
|
params = params || {};
|
||||||
|
var path;
|
||||||
|
for (var i = 0; i < this.length; i++) {
|
||||||
|
path = this[i].generate(params, options);
|
||||||
|
if (path) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
Routes.named.toString = Routes.toString = function(){
|
||||||
|
return this.join(', ');
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
window['Route'] = Route;
|
||||||
|
window['Routes'] = Routes;
|
||||||
|
|
||||||
|
})();
|
31
tasks/javascript_routes_tasks.rake
Normal file
31
tasks/javascript_routes_tasks.rake
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
require File.join(RAILS_ROOT, 'config', 'environment')
|
||||||
|
require File.join(File.dirname(__FILE__), '..', 'lib', 'javascript_routes')
|
||||||
|
require File.join(File.dirname(__FILE__), '..', 'bin', 'jsmin')
|
||||||
|
|
||||||
|
namespace :routes do
|
||||||
|
namespace :js do
|
||||||
|
|
||||||
|
desc 'Generate routes.js based on routes defined in routes.rb'
|
||||||
|
task :generate do
|
||||||
|
ActionController::Routing::Routes.load!
|
||||||
|
JavascriptRoutes.generate(:lite => ENV['lite'], :pack => ENV['pack'] != 'false')
|
||||||
|
puts "Generated #{JavascriptRoutes::FILENAME}"
|
||||||
|
puts "Generated #{JavascriptRoutes::FILENAME_AJAX}"
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'Minify the routes.js base file'
|
||||||
|
task :minify do
|
||||||
|
infile = JavascriptRoutes::JS
|
||||||
|
outfile = JavascriptRoutes::JS_PACKED
|
||||||
|
|
||||||
|
File.open(infile, 'r') do |input|
|
||||||
|
File.open(outfile, 'w') do |output|
|
||||||
|
JSMin.new(input, output).jsmin
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "#{File.size(infile)} #{infile}"
|
||||||
|
puts "#{File.size(outfile)} #{outfile}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
8
test/javascript_routes_test.rb
Normal file
8
test/javascript_routes_test.rb
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
require 'test/unit'
|
||||||
|
|
||||||
|
class JavascriptRoutesTest < Test::Unit::TestCase
|
||||||
|
# Replace this with your real tests.
|
||||||
|
def test_this_plugin
|
||||||
|
flunk
|
||||||
|
end
|
||||||
|
end
|
1
uninstall.rb
Normal file
1
uninstall.rb
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# Uninstall hook code here
|
Loading…
Reference in a new issue