Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Initial commit

  • Loading branch information...
commit 2ea0ee5b85a16cf0b5e792a6c705a2c8c13ec884 0 parents
Tore Darell authored
4 README
... ... @@ -0,0 +1,4 @@
  1 +JavascriptRoutes
  2 +================
  3 +
  4 +Description goes here
22 Rakefile
... ... @@ -0,0 +1,22 @@
  1 +require 'rake'
  2 +require 'rake/testtask'
  3 +require 'rake/rdoctask'
  4 +
  5 +desc 'Default: run unit tests.'
  6 +task :default => :test
  7 +
  8 +desc 'Test the javascript_routes plugin.'
  9 +Rake::TestTask.new(:test) do |t|
  10 + t.libs << 'lib'
  11 + t.pattern = 'test/**/*_test.rb'
  12 + t.verbose = true
  13 +end
  14 +
  15 +desc 'Generate documentation for the javascript_routes plugin.'
  16 +Rake::RDocTask.new(:rdoc) do |rdoc|
  17 + rdoc.rdoc_dir = 'rdoc'
  18 + rdoc.title = 'JavascriptRoutes'
  19 + rdoc.options << '--line-numbers' << '--inline-source'
  20 + rdoc.rdoc_files.include('README')
  21 + rdoc.rdoc_files.include('lib/**/*.rb')
  22 +end
208 bin/jsmin.rb
... ... @@ -0,0 +1,208 @@
  1 +#!/usr/bin/ruby
  2 +# jsmin.rb 2007-05-22
  3 +# Author: Uladzislau Latynski
  4 +# This work is a translation from C to Ruby of jsmin.c published by
  5 +# Douglas Crockford. Permission is hereby granted to use the Ruby
  6 +# version under the same conditions as the jsmin.c on which it is
  7 +# based.
  8 +#
  9 +# /* jsmin.c
  10 +# 2003-04-21
  11 +#
  12 +# Copyright (c) 2002 Douglas Crockford (www.crockford.com)
  13 +#
  14 +# Permission is hereby granted, free of charge, to any person obtaining a copy of
  15 +# this software and associated documentation files (the "Software"), to deal in
  16 +# the Software without restriction, including without limitation the rights to
  17 +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
  18 +# of the Software, and to permit persons to whom the Software is furnished to do
  19 +# so, subject to the following conditions:
  20 +#
  21 +# The above copyright notice and this permission notice shall be included in all
  22 +# copies or substantial portions of the Software.
  23 +#
  24 +# The Software shall be used for Good, not Evil.
  25 +#
  26 +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  27 +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  28 +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  29 +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  30 +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  31 +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  32 +# SOFTWARE.
  33 +
  34 +class JSMin
  35 +
  36 + EOF = -1
  37 +
  38 + def initialize(input, output)
  39 + @input, @output = input, output
  40 + @theA, @theB = '', ''
  41 + end
  42 +
  43 + # isAlphanum -- return true if the character is a letter, digit, underscore,
  44 + # dollar sign, or non-ASCII character
  45 + def isAlphanum(c)
  46 + return false if !c || c == EOF
  47 + return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
  48 + (c >= 'A' && c <= 'Z') || c == '_' || c == '$' ||
  49 + c == '\\' || c[0] > 126)
  50 + end
  51 +
  52 + # get -- return the next character from stdin. Watch out for lookahead. If
  53 + # the character is a control character, translate it to a space or linefeed.
  54 + def get()
  55 + c = @input.getc
  56 + return EOF if(!c)
  57 + c = c.chr
  58 + return c if (c >= " " || c == "\n" || c.unpack("c") == EOF)
  59 + return "\n" if (c == "\r")
  60 + return " "
  61 + end
  62 +
  63 + # Get the next character without getting it.
  64 + def peek()
  65 + lookaheadChar = @input.getc
  66 + @input.ungetc(lookaheadChar)
  67 + return lookaheadChar.chr
  68 + end
  69 +
  70 + # mynext -- get the next character, excluding comments.
  71 + # peek() is used to see if a '/' is followed by a '/' or '*'.
  72 + def mynext()
  73 + c = get
  74 + if (c == "/")
  75 + if(peek == "/")
  76 + while(true)
  77 + c = get
  78 + if (c <= "\n")
  79 + return c
  80 + end
  81 + end
  82 + end
  83 + if(peek == "*")
  84 + get
  85 + while(true)
  86 + case get
  87 + when "*"
  88 + if (peek == "/")
  89 + get
  90 + return " "
  91 + end
  92 + when EOF
  93 + raise "Unterminated comment"
  94 + end
  95 + end
  96 + end
  97 + end
  98 + return c
  99 + end
  100 +
  101 +
  102 + # action -- do something! What you do is determined by the argument: 1
  103 + # Output A. Copy B to A. Get the next B. 2 Copy B to A. Get the next B.
  104 + # (Delete A). 3 Get the next B. (Delete B). action treats a string as a
  105 + # single character. Wow! action recognizes a regular expression if it is
  106 + # preceded by ( or , or =.
  107 + def action(a)
  108 + if(a==1)
  109 + @output.write @theA
  110 + end
  111 + if(a==1 || a==2)
  112 + @theA = @theB
  113 + if (@theA == "\'" || @theA == "\"")
  114 + while (true)
  115 + @output.write @theA
  116 + @theA = get
  117 + break if (@theA == @theB)
  118 + raise "Unterminated string literal" if (@theA <= "\n")
  119 + if (@theA == "\\")
  120 + @output.write @theA
  121 + @theA = get
  122 + end
  123 + end
  124 + end
  125 + end
  126 + if(a==1 || a==2 || a==3)
  127 + @theB = mynext
  128 + if (@theB == "/" && (@theA == "(" || @theA == "," || @theA == "=" ||
  129 + @theA == ":" || @theA == "[" || @theA == "!" ||
  130 + @theA == "&" || @theA == "|" || @theA == "?" ||
  131 + @theA == "{" || @theA == "}" || @theA == ";" ||
  132 + @theA == "\n"))
  133 + @output.write @theA
  134 + @output.write @theB
  135 + while (true)
  136 + @theA = get
  137 + if (@theA == "/")
  138 + break
  139 + elsif (@theA == "\\")
  140 + @output.write @theA
  141 + @theA = get
  142 + elsif (@theA <= "\n")
  143 + raise "Unterminated RegExp Literal"
  144 + end
  145 + @output.write @theA
  146 + end
  147 + @theB = mynext
  148 + end
  149 + end
  150 + end
  151 +
  152 + # jsmin -- Copy the input to the output, deleting the characters which are
  153 + # insignificant to JavaScript. Comments will be removed. Tabs will be
  154 + # replaced with spaces. Carriage returns will be replaced with linefeeds.
  155 + # Most spaces and linefeeds will be removed.
  156 + def jsmin
  157 + @theA = "\n"
  158 + action(3)
  159 + while (@theA != EOF)
  160 + case @theA
  161 + when " "
  162 + if (isAlphanum(@theB))
  163 + action(1)
  164 + else
  165 + action(2)
  166 + end
  167 + when "\n"
  168 + case (@theB)
  169 + when "{","[","(","+","-"
  170 + action(1)
  171 + when " "
  172 + action(3)
  173 + else
  174 + if (isAlphanum(@theB))
  175 + action(1)
  176 + else
  177 + action(2)
  178 + end
  179 + end
  180 + else
  181 + case (@theB)
  182 + when " "
  183 + if (isAlphanum(@theA))
  184 + action(1)
  185 + else
  186 + action(3)
  187 + end
  188 + when "\n"
  189 + case (@theA)
  190 + when "}","]",")","+","-","\"","\\", "'", '"'
  191 + action(1)
  192 + else
  193 + if (isAlphanum(@theA))
  194 + action(1)
  195 + else
  196 + action(3)
  197 + end
  198 + end
  199 + else
  200 + action(1)
  201 + end
  202 + end
  203 + end
  204 + end
  205 +
  206 +end
  207 +
  208 +JSMin.new($stdin, $stdout).jsmin if __FILE__ == $0
4 init.rb
... ... @@ -0,0 +1,4 @@
  1 +require 'javascript_routes'
  2 +
  3 +ActionController::Routing::Routes.load!
  4 +JavascriptRoutes.generate(:lite => ENV['ROUTES_JS_LITE'])
1  install.rb
... ... @@ -0,0 +1 @@
  1 +# Install hook code here
148 lib/javascript_routes.rb
... ... @@ -0,0 +1,148 @@
  1 +module JavascriptRoutes
  2 +
  3 + JS = File.join(File.dirname(__FILE__), 'javascripts', 'routes.js')
  4 + JS_PACKED = File.join(File.dirname(__FILE__), 'javascripts', 'routes-min.js')
  5 + JS_AJAX = File.join(File.dirname(__FILE__), 'javascripts', 'routes-ajax.js')
  6 + FILENAME = File.join(RAILS_ROOT, 'public', 'javascripts', 'routes.js')
  7 + FILENAME_AJAX = File.join(RAILS_ROOT, 'public', 'javascripts', 'routes-ajax.js')
  8 +
  9 + def self.generate(options = {})
  10 + options.symbolize_keys!.reverse_merge!(:pack => true)
  11 +
  12 + routes = options[:routes] || ActionController::Routing::Routes.routes.select{|r|
  13 + r.conditions[:method].nil? || r.conditions[:method] == :get
  14 + }
  15 + named_routes = options[:named_routes] || ActionController::Routing::Routes.named_routes.routes.select{|n,r|
  16 + r.conditions[:method].nil? || r.conditions[:method] == :get
  17 + }
  18 + filename = options[:filename] || FILENAME
  19 + filename_ajax = options[:filename_ajax] || FILENAME_AJAX
  20 +
  21 + #Will only create simple functions for named routes
  22 + if options[:lite]
  23 +
  24 + File.open(filename, 'w') do |file|
  25 + routes_object = ' var r = "{'
  26 + named_routes.each_with_index do |a,i|
  27 + n,r = *a
  28 + routes_object << "#{n}: function(#{r.segments.select{|s| s.respond_to?(:key) }.map(&:key).join(', ')}){ "
  29 + routes_object << 'return '
  30 +
  31 + r.segments.each_with_index do |s,j|
  32 + if s.respond_to?(:key)
  33 + routes_object << "'" unless i==0 || r.segments[j-1].respond_to?(:key)
  34 + routes_object << "+#{s.key}"
  35 + else
  36 + routes_object << '+' if j > 0 && r.segments[j-1].respond_to?(:key)
  37 + routes_object << "'" if j == 0 || r.segments[j-1].respond_to?(:key)
  38 + routes_object << s.to_s unless j != 0 && j == r.segments.size-1 && s.to_s == '/'
  39 + routes_object << "'" if j == r.segments.size-1
  40 + end
  41 + end
  42 +
  43 + routes_object << ';'
  44 + routes_object << " }"
  45 + routes_object << ',' if i < named_routes.size-1
  46 + end
  47 + routes_object << "}\";\n"
  48 +
  49 + #Find words with 5 or more characters that appear more than once
  50 + words = routes_object.scan(/[a-z_]{5,}/).group_by{|s| s }.inject([]){|r,a| r << a.first if a.last.size > 1; r }
  51 + #Replace words with placeholders
  52 + words.each_with_index{|w,i| routes_object.gsub!(w, "$#{i}") }
  53 +
  54 + file << "var Routes = (function(){\n"
  55 +
  56 + #Export words to JS
  57 + file << " var s = [" + words.map{|w| "'#{w}'" }.join(',') + "];\n"
  58 + file << routes_object
  59 + #Put the words back in (using JS) and eval the result
  60 + file << " return eval('('+r.replace(/\\$(\\d+)/g, function(m,i){ return s[i]; })+')');\n"
  61 +
  62 + file << "})();"
  63 + end
  64 +
  65 + #Will create all routes with generation logic (from lib/routes.js)
  66 + else
  67 +
  68 + File.open(filename, 'w') do |file|
  69 + file << File.read(options[:pack] ? JS_PACKED : JS)
  70 +
  71 + file << "\n\n(function(){\n\n"#Don't pollute the global namespace
  72 +
  73 + #This is ugly, but it works. It builds a JS array
  74 + #with an object for each route. Most of the ugliness
  75 + #is to reduce the amount of space it takes up.
  76 + routes_array = ''
  77 + routes_array << 'var r = "['
  78 + routes.each_with_index do |r,i|
  79 + routes_array << '{'
  80 +
  81 + #Append a name if this is a named route
  82 + named_route = named_routes.find{|name,route| route.equal?(r) }
  83 + routes_array << "n:'#{named_route.first}'," if named_route
  84 +
  85 + #Append segments as a string with @ between. This will
  86 + #be split() using JS.
  87 + routes_array << "s:'"
  88 + routes_array << r.segments.map do |s|
  89 + if s.is_a?(ActionController::Routing::PathSegment)
  90 + '*' + s.to_s[1..-1] + (s.is_optional ? 't' : 'f')
  91 + else
  92 + s.to_s + (s.is_optional ? 't' : 'f')
  93 + end
  94 + end.join('@')
  95 + routes_array << "'"
  96 +
  97 + #Append params object
  98 + routes_array << ',r:{'
  99 + routes_array << r.requirements.map do |k,v|
  100 + "#{k}:'#{v}'"
  101 + end.join(',')
  102 + routes_array << '}'
  103 +
  104 + routes_array << '}'
  105 + routes_array << ',' unless i == routes.size-1
  106 + end
  107 + routes_array << "]\";\n"
  108 +
  109 + #Find words that occur more than once and are more than 5 characters in length
  110 + words = routes_array.scan(/[a-z_]{5,}/).group_by{|s| s }.inject([]){|r,a| r << a.first if a.last.size > 1; r }
  111 + #Replace words with placeholders
  112 + words.each_with_index{|w,i| routes_array.gsub!(w, "$#{i}") }
  113 +
  114 + #Export words to JS
  115 + file << " var s = [" + words.map{|w| "'#{w}'" }.join(',') + "];\n"
  116 + file << ' '+routes_array
  117 + #Put the words back in (using JS) and eval the result
  118 + file << " r = eval('('+r.replace(/\\$(\\d+)/g, function(m,i){ return s[i]; })+')');\n\n"
  119 +
  120 + #Add routes
  121 + file << " for (var i = 0; i < r.length; i++) {\n"
  122 + file << " var s=[];\n"
  123 + file << " var segs=r[i].s.split('@');\n"
  124 + file << " for (var j = 0; j < segs.length; j++) {\n"
  125 + file << " s.push(Route.S(segs[j].slice(0, -1), segs[j].slice(-1) == 't'));\n"
  126 + file << " }\n"
  127 + file << " Routes.push(new Route(s, r[i].r, r[i].n));\n"
  128 + file << " }\n\n"
  129 + file << " Routes.extractNamed();\n\n"
  130 +
  131 + file << "})();"
  132 + end
  133 +
  134 + #Add ajax extras
  135 + File.open filename_ajax, 'w' do |f|
  136 + f.write(File.read(JS_AJAX))
  137 + end
  138 +
  139 + end
  140 +
  141 + rescue => e
  142 +
  143 + warn("\n\nCould not write routes.js: \"#{e.class}:#{e.message}\"\n\n")
  144 + File.truncate(filename, 0) rescue nil
  145 +
  146 + end
  147 +
  148 +end
41 lib/javascripts/routes-ajax.js
... ... @@ -0,0 +1,41 @@
  1 +//Michael Schuerig
  2 +
  3 +Route.Object = function (url) {
  4 + this.url = url;
  5 +};
  6 +
  7 +Route.Object.prototype = {
  8 + toString: function() { return this.url; }
  9 +};
  10 +
  11 +
  12 +//Replace Route.prototype.generate
  13 +(function(oldGenerate){
  14 +
  15 + Route.prototype.generate = function(){
  16 + var path = oldGenerate.apply(this, arguments);
  17 + return path && new Route.Object(path);
  18 + };
  19 +
  20 +})(Route.prototype.generate);
  21 +
  22 +
  23 +if (window.Prototype) {
  24 + Object.extend(Route.Object.prototype, {
  25 + get: function(options) { this.method = 'get'; return this.request(options) },
  26 + post: function(options) { this.method = 'post'; return this.request(options) },
  27 + put: function(options) { this.method = 'put'; return this.request(options) },
  28 + 'delete': function(options) { this.method = 'delete'; return this.request(options) },
  29 + request: function(options) {
  30 + var result = this;
  31 + options = options || {};
  32 + var async = $H(options).any(function(p) { return /^on[A-Z1-5]/.test(p[0]); });
  33 + options = Object.extend({ asynchronous: async, method: this.method }, options);
  34 + if (!async) {
  35 + options.onComplete = function(r) { result = r.responseText; };
  36 + }
  37 + new Ajax.Request(this.url, options);
  38 + return result;
  39 + }
  40 + });
  41 +}
18 lib/javascripts/routes-min.js
... ... @@ -0,0 +1,18 @@
  1 +
  2 +(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;}}
  3 +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];}
  4 +if(!hasParam){routeMatch=false;return;}});if(!routeMatch){return false;}
  5 +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';}
  6 +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]||'');}
  7 +delete params[segment.value];}else if(!segment.optional){throw'nomatch';}else{delete params[segment.value];throw'done';}
  8 +break;}});}catch(e){if(e!=='done'){if(e==='nomatch'){return false;}else{throw e;}}}
  9 +if(!options.includeSlash&&path.match(/.+\/$/)){path=path.slice(0,-1);}
  10 +if(!options.onlyPath){var portString=options.port?':'+options.port:'';path=[options.protocol,options.host,portString,path].join('')}
  11 +var leftOvers=[];iterate(params,function(k,v){leftOvers.push(k+'='+v);});if(leftOvers.length>0){path=path+'?'+leftOvers.join('&');}
  12 +if(options.escape){path=encodeURI(path);}
  13 +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];}}
  14 +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];}
  15 +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];}
  16 +count++;}}}
  17 +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;}}
  18 +return false;};Routes.named.toString=Routes.toString=function(){return this.join(', ');};window['Route']=Route;window['Routes']=Routes;})();
275 lib/javascripts/routes.js
... ... @@ -0,0 +1,275 @@
  1 +(function(){
  2 +
  3 + var iterate = function(o, fn){
  4 + if (typeof o.length === 'number') {//Array
  5 + for (var i = 0; i < o.length; i++) {
  6 + //Stop iterating if return value is false
  7 + if (fn(o[i], i, o) === false){ return false; }
  8 + }
  9 + return true;//All done
  10 + } else {//Object
  11 + for (var p in o){
  12 + if (o.hasOwnProperty(p)) {
  13 + fn(p, o[p]);
  14 + }
  15 + }
  16 + }
  17 + };
  18 +
  19 + var all = function(arr, fn){
  20 + var allTrue = true;
  21 + iterate(arr, function(e,i,a){
  22 + if (!fn(e,i,a)) { allTrue = false; return false; }
  23 + });
  24 + return allTrue;
  25 + };
  26 +
  27 + var extend = function(target, source){
  28 + iterate(source, function(k,v){ target[k] = v; });
  29 + return target;
  30 + };
  31 +
  32 + var merge = function(t,s){
  33 + var rv = {};
  34 + extend(rv, t);
  35 + extend(rv, s);
  36 + return rv;
  37 + };
  38 +
  39 + var Route = function(segments, params, name){
  40 + this.segments = segments || [];
  41 + this.params = params || {};
  42 + this.name = name;
  43 + };
  44 +
  45 + Route.prototype = {
  46 + generate: function(pa, op){
  47 + var options = merge(Routes.defaultOptions, op || {});
  48 + var params = options.noDefaults ? merge({}, pa || {}) : merge(Routes.defaultParams, pa || {});
  49 +
  50 + var path = '';
  51 +
  52 + var hasParam = false;
  53 + var routeMatch = true;
  54 + iterate(this.params, function(k,v){
  55 + hasParam = false;
  56 + if (
  57 + (typeof v === 'string' && v === params[k]) ||
  58 + ((v.constructor === RegExp) && (new RegExp('^'+v.source+'$')).test(params[k]))
  59 + ) {
  60 + hasParam = true;
  61 + delete params[k];
  62 + }
  63 + if (!hasParam) {
  64 + routeMatch = false;
  65 + return;
  66 + }
  67 + });
  68 +
  69 + if (!routeMatch) {
  70 + return false;
  71 + }
  72 +
  73 + try {
  74 + iterate(this.segments, function(segment, index, segments){
  75 + switch (segment.type) {
  76 + case 'divider':
  77 + path = path + segment.value;
  78 + break;
  79 + case 'static':
  80 + path = path + segment.value;
  81 + break;
  82 + case 'dynamic':
  83 + if (params[segment.value]) {
  84 + path = path + params[segment.value];
  85 + delete params[segment.value];
  86 + } else if (!segment.optional) {
  87 + throw 'nomatch';
  88 + } else {
  89 + delete params[segment.value];
  90 + throw 'done';
  91 + }
  92 + break;
  93 + case 'path':
  94 + if (params[segment.value]) {
  95 + if (params[segment.value] instanceof Array) {
  96 + path = path + params[segment.value].join('/');
  97 + } else {
  98 + path = path + (params[segment.value] || '');
  99 + }
  100 + delete params[segment.value];
  101 + } else if (!segment.optional) {
  102 + throw 'nomatch';
  103 + } else {
  104 + delete params[segment.value];
  105 + throw 'done';
  106 + }
  107 + break;
  108 + }
  109 + });
  110 + } catch (e) {
  111 + if (e !== 'done') { //done == don't append any more segments
  112 + if (e === 'nomatch') { //params don't match this route
  113 + return false;
  114 + } else {
  115 + throw e;
  116 + }
  117 + }
  118 + }
  119 +
  120 + if (!options.includeSlash && path.match(/.+\/$/)) {
  121 + path = path.slice(0,-1);
  122 + }
  123 +
  124 + if (!options.onlyPath) {
  125 + var portString = options.port ? ':'+options.port : '';
  126 + path = [options.protocol, options.host, portString, path].join('')
  127 + }
  128 +
  129 + var leftOvers = [];
  130 + iterate(params, function(k,v){
  131 + leftOvers.push(k + '=' + v);
  132 + });
  133 +
  134 + if (leftOvers.length > 0) {
  135 + path = path + '?' + leftOvers.join('&');
  136 + }
  137 +
  138 + if (options.escape) {
  139 + path = encodeURI(path);
  140 + }
  141 +
  142 + return path;
  143 + },
  144 +
  145 + toString: function(){
  146 + return this.segments.join('');
  147 + }
  148 + };
  149 +
  150 +
  151 +
  152 + Route.Segment = function(value, type, optional){
  153 + this.value = value;
  154 + this.type = type || 'static';
  155 + this.optional = (typeof optional === 'boolean' ? optional : true);
  156 + };
  157 +
  158 + Route.Segment.prototype = {
  159 + isDynamic: function(){
  160 + return this.type === 'dynamic' || this.type === 'path';
  161 + },
  162 + toString: function(){
  163 + if (this.type === 'dynamic') {
  164 + return ':'+this.value;
  165 + } else if (this.type === 'path') {
  166 + return '*'+this.value;
  167 + } else {
  168 + return this.value;
  169 + }
  170 + },
  171 + equal: function(other){
  172 + return other.constructor === this.constructor && other.value === this.value &&
  173 + other.type === this.type && other.optional === this.optional;
  174 + }
  175 + };
  176 +
  177 + Route.createSegment = function(s, optional){
  178 + if (s.match(/^[\/;?.]$/)) {
  179 + return new Route.Segment(s, 'divider', optional);
  180 + } else if (s.indexOf(':') === 0) {
  181 + return new Route.Segment(s.slice(1), 'dynamic', optional);
  182 + } else if (s.indexOf('*') === 0) {
  183 + return new Route.Segment(s.slice(1), 'path', optional);
  184 + } else {
  185 + return new Route.Segment(s, 'static', optional);
  186 + }
  187 + };
  188 + Route.S = Route.createSegment;
  189 +
  190 + var Routes = [];
  191 + Routes.named = [];
  192 + Routes.defaultParams = {action:'index'};//Default parameters for route generation
  193 + Routes.defaultOptions = {//Defaults for second parameter
  194 + escape: true,
  195 + port: window.location.port,
  196 + protocol: window.location.protocol+'//',
  197 + host: window.location.hostname
  198 + };
  199 +
  200 + Routes.extractNamed = function(){
  201 + var route;
  202 + for (var i = 0; i < this.length; i++) {
  203 + route = this[i];
  204 + if (route.name) {
  205 + this.named.push(route);
  206 + this.named[route.name] = route;
  207 +
  208 + this[route.name] = (function(route){
  209 + var fn = function(){
  210 + var params = {},
  211 + options = {},
  212 + count;
  213 +
  214 + //Add defaults from route
  215 + for (var p in route.params) {
  216 + if (route.params.hasOwnProperty(p) && route.params[p].constructor !== RegExp) {
  217 + params[p] = route.params[p];
  218 + }
  219 + }
  220 +
  221 + //Allows Routes.name('foo', 'bar', {opts}) or Routes.name({foo:'foo', bar:'bar'}, {opts})
  222 + if (typeof arguments[0] === 'object' && !(arguments[0] instanceof Array)) {
  223 + extend(params, arguments[0]);
  224 + options = arguments[1];
  225 + } else {
  226 + if (typeof arguments[arguments.length-1] === 'object') {
  227 + options = arguments[arguments.length-1];
  228 + }
  229 +
  230 + var count = 0;
  231 + for (var i=0; i < route.segments.length; i++) {
  232 + if (route.segments[i].isDynamic()) {
  233 + if (arguments.length > count) { params[route.segments[i].value] = arguments[count]; }
  234 + count++;
  235 + }
  236 + }
  237 + }
  238 +
  239 + return route.generate(params, options);
  240 + };
  241 +
  242 + //Routes.name.toParams() => {...}
  243 + //Like hash_for_x in Rails, kind of
  244 + fn.toParams = function(params){
  245 + return merge(route.params, params || {});
  246 + };
  247 +
  248 + return fn;
  249 + })(route); //Pass the route to keep it in scope
  250 +
  251 + }
  252 + }
  253 + };
  254 +
  255 + Routes.generate = function(params, options){
  256 + params = params || {};
  257 + var path;
  258 + for (var i = 0; i < this.length; i++) {
  259 + path = this[i].generate(params, options);
  260 + if (path) {
  261 + return path;
  262 + }
  263 + }
  264 + return false;
  265 + };
  266 +
  267 + Routes.named.toString = Routes.toString = function(){
  268 + return this.join(', ');
  269 + };
  270 +
  271 +
  272 + window['Route'] = Route;
  273 + window['Routes'] = Routes;
  274 +
  275 +})();
31 tasks/javascript_routes_tasks.rake
... ... @@ -0,0 +1,31 @@
  1 +require File.join(RAILS_ROOT, 'config', 'environment')
  2 +require File.join(File.dirname(__FILE__), '..', 'lib', 'javascript_routes')
  3 +require File.join(File.dirname(__FILE__), '..', 'bin', 'jsmin')
  4 +
  5 +namespace :routes do
  6 + namespace :js do
  7 +
  8 + desc 'Generate routes.js based on routes defined in routes.rb'
  9 + task :generate do
  10 + ActionController::Routing::Routes.load!
  11 + JavascriptRoutes.generate(:lite => ENV['lite'], :pack => ENV['pack'] != 'false')
  12 + puts "Generated #{JavascriptRoutes::FILENAME}"
  13 + puts "Generated #{JavascriptRoutes::FILENAME_AJAX}"
  14 + end
  15 +
  16 + desc 'Minify the routes.js base file'
  17 + task :minify do
  18 + infile = JavascriptRoutes::JS
  19 + outfile = JavascriptRoutes::JS_PACKED
  20 +
  21 + File.open(infile, 'r') do |input|
  22 + File.open(outfile, 'w') do |output|
  23 + JSMin.new(input, output).jsmin
  24 + end
  25 + end
  26 +
  27 + puts "#{File.size(infile)} #{infile}"
  28 + puts "#{File.size(outfile)} #{outfile}"
  29 + end
  30 + end
  31 +end
8 test/javascript_routes_test.rb
... ... @@ -0,0 +1,8 @@
  1 +require 'test/unit'
  2 +
  3 +class JavascriptRoutesTest < Test::Unit::TestCase
  4 + # Replace this with your real tests.
  5 + def test_this_plugin
  6 + flunk
  7 + end
  8 +end
1  uninstall.rb
... ... @@ -0,0 +1 @@
  1 +# Uninstall hook code here

0 comments on commit 2ea0ee5

Please sign in to comment.
Something went wrong with that request. Please try again.