livelist-rails 0.0.11 → 0.0.12
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.rspec +1 -1
- data/app/assets/javascripts/livelist-rails.js +1 -1
- data/app/assets/javascripts/livelist.coffee +4 -3
- data/app/assets/javascripts/livelist.js +3 -2
- data/app/assets/javascripts/livelist.min.js +1 -1
- data/app/assets/javascripts/mustache.min.js +6 -0
- data/app/assets/javascripts/underscore-min.js +26 -25
- data/lib/livelist/rails/active_record.rb +12 -9
- data/lib/livelist/rails/filter.rb +10 -0
- data/lib/livelist/rails/filter_collection.rb +8 -2
- data/lib/livelist/rails/filter_criteria.rb +18 -8
- data/lib/livelist/rails/railtie.rb +1 -1
- data/lib/livelist/rails/version.rb +4 -4
- data/livelist-rails.gemspec +3 -1
- data/spec/livelist/rails/active_record_spec.rb +66 -7
- data/spec/livelist/rails/filter_criterion_spec.rb +1 -1
- data/spec/spec_helper.rb +7 -4
- metadata +30 -8
- data/app/assets/javascripts/mustache.js +0 -435
data/.gitignore
CHANGED
data/.rspec
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
--color
|
2
|
-
--format
|
2
|
+
--format documentation
|
@@ -1,5 +1,5 @@
|
|
1
1
|
class window.Utilities
|
2
|
-
setOptions: (options, context
|
2
|
+
setOptions: (options, context = @) =>
|
3
3
|
_.each( options, (value, option) => context[option] = value )
|
4
4
|
|
5
5
|
class window.LiveList extends Utilities
|
@@ -7,8 +7,9 @@ class window.LiveList extends Utilities
|
|
7
7
|
@listSelector = options.list.renderTo
|
8
8
|
@resourceName = options.global.resourceName
|
9
9
|
@resourceNameSingular = options.global.resourceNameSingular
|
10
|
+
@urlPrefix = options.global.urlPrefix || "/#{@resourceName}"
|
11
|
+
@httpMethod = options.global.httpMethod || 'get'
|
10
12
|
@eventName = "livelist:#{@resourceName}"
|
11
|
-
@urlPrefix = "/#{@resourceName}"
|
12
13
|
@search = new Search(options.search, @)
|
13
14
|
@filters = new Filters(options.filters, @)
|
14
15
|
@pagination = new Pagination(options.pagination, @)
|
@@ -65,7 +66,7 @@ class window.List extends Utilities
|
|
65
66
|
$(@renderTo).html( listHTML )
|
66
67
|
@removeFetchingIndication()
|
67
68
|
|
68
|
-
window.LiveList.version = '0.0.
|
69
|
+
window.LiveList.version = '0.0.7'
|
69
70
|
|
70
71
|
class window.Filters extends Utilities
|
71
72
|
constructor: (options, livelist) ->
|
@@ -29,8 +29,9 @@
|
|
29
29
|
this.listSelector = options.list.renderTo;
|
30
30
|
this.resourceName = options.global.resourceName;
|
31
31
|
this.resourceNameSingular = options.global.resourceNameSingular;
|
32
|
+
this.urlPrefix = options.global.urlPrefix || ("/" + this.resourceName);
|
33
|
+
this.httpMethod = options.global.httpMethod || 'get';
|
32
34
|
this.eventName = "livelist:" + this.resourceName;
|
33
|
-
this.urlPrefix = "/" + this.resourceName;
|
34
35
|
this.search = new Search(options.search, this);
|
35
36
|
this.filters = new Filters(options.filters, this);
|
36
37
|
this.pagination = new Pagination(options.pagination, this);
|
@@ -114,7 +115,7 @@
|
|
114
115
|
|
115
116
|
})(Utilities);
|
116
117
|
|
117
|
-
window.LiveList.version = '0.0.
|
118
|
+
window.LiveList.version = '0.0.7';
|
118
119
|
|
119
120
|
window.Filters = (function(_super) {
|
120
121
|
|
@@ -1 +1 @@
|
|
1
|
-
((function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b=Object.prototype.hasOwnProperty,c=function(a,c){function e(){this.constructor=a}for(var d in c)b.call(c,d)&&(a[d]=c[d]);return e.prototype=c.prototype,a.prototype=new e,a.__super__=c.prototype,a};window.Utilities=function(){function b(){this.setOptions=a(this.setOptions,this)}return b.prototype.setOptions=function(a,b){var c=this;return b==null&&(b=this),_.each(a,function(a,c){return b[c]=a})},b}(),window.LiveList=function(a){function b(a){this.listSelector=a.list.renderTo,this.resourceName=a.global.resourceName,this.resourceNameSingular=a.global.resourceNameSingular,this.
|
1
|
+
((function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b=Object.prototype.hasOwnProperty,c=function(a,c){function e(){this.constructor=a}for(var d in c)b.call(c,d)&&(a[d]=c[d]);return e.prototype=c.prototype,a.prototype=new e,a.__super__=c.prototype,a};window.Utilities=function(){function b(){this.setOptions=a(this.setOptions,this)}return b.prototype.setOptions=function(a,b){var c=this;return b==null&&(b=this),_.each(a,function(a,c){return b[c]=a})},b}(),window.LiveList=function(a){function b(a){this.listSelector=a.list.renderTo,this.resourceName=a.global.resourceName,this.resourceNameSingular=a.global.resourceNameSingular,this.urlPrefix=a.global.urlPrefix||"/"+this.resourceName,this.httpMethod=a.global.httpMethod||"get",this.eventName="livelist:"+this.resourceName,this.search=new Search(a.search,this),this.filters=new Filters(a.filters,this),this.pagination=new Pagination(a.pagination,this),this.list=new List(a.list,this)}return c(b,a),b}(Utilities),window.List=function(b){function d(b,c){this.renderIndex=a(this.renderIndex,this),this.removeFetchingIndication=a(this.removeFetchingIndication,this),this.displayFetchingIndication=a(this.displayFetchingIndication,this);var d=this;this.fetchRequest=null,this.livelist=c,this.listTemplate="{{#"+this.livelist.resourceName+"}}{{>"+this.livelist.resourceNameSingular+"}}{{/"+this.livelist.resourceName+"}}",this.listItemTemplate="<li>{{id}}</li>",this.fetchingIndicationClass="updating",this.renderTo="ul#"+this.livelist.resourceName,this.setOptions(b),$(this.renderTo).bind(this.livelist.eventName,function(a,b){return d.fetch({presets:null,page:b!=null?b.page:void 0})}),this.fetch({presets:this.livelist.filters.getPresets()})}return c(d,b),d.prototype.displayFetchingIndication=function(){return $(this.renderTo).addClass(this.fetchingIndicationClass)},d.prototype.removeFetchingIndication=function(){return $(this.renderTo).removeClass(this.fetchingIndicationClass)},d.prototype.renderIndex=function(a,b,c){return this.livelist.data=a,this.render(),this.livelist.pagination.render(this.livelist.data),this.livelist.filters.render(this.livelist.data)},d.prototype.fetch=function(a){var b,c;return this.fetchRequest&&this.fetchRequest.abort(),c=this.livelist.search.searchTerm(),b={},b.filters=this.livelist.filters.setPresets(a.presets),c&&(b.q=c),a.page&&(b.page=a.page),this.fetchRequest=$.ajax({url:this.livelist.urlPrefix,type:this.livelist.httpMethod,dataType:"json",data:b,beforeSend:this.displayFetchingIndication,success:this.renderIndex})},d.prototype.render=function(){var a,b;return b={},b[this.livelist.resourceNameSingular]=this.listItemTemplate,a=Mustache.to_html(this.listTemplate,this.livelist.data,b),$(this.renderTo).html(a),this.removeFetchingIndication()},d}(Utilities),window.LiveList.version="0.0.7",window.Filters=function(b){function d(b,c){this.handleAdvancedOptionsClick=a(this.handleAdvancedOptionsClick,this);var d=this;this.livelist=c,this.filters=b.presets?_.keys(b.presets):[],this.initializeCookies(),this.setOptions(b),$("input.filter_option",this.renderTo).live("change",function(){return $(d.livelist.listSelector).trigger(d.livelist.eventName)}),$(this.advancedOptionsToggleSelector).click(this.handleAdvancedOptionsClick)}return c(d,b),d.prototype.initializeCookies=function(){if(jQuery.cookie&&this.useCookies&&this.cookieName)return this.cookieName="livelist_filter_presets"},d.prototype.getPresets=function(){var a;return jQuery.cookie&&this.useCookies&&(a=jQuery.cookie(this.cookieName)),this.useCookies&&a?JSON.parse(a):this.presets},d.prototype.setPresets=function(a){var b;return b={},jQuery.isEmptyObject(a)?(b=this.selections(),jQuery.cookie&&this.setCookie(b)):b=a,b},d.prototype.setCookie=function(a){if(!jQuery.isEmptyObject(a))return jQuery.cookie(this.cookieName,JSON.stringify(a))},d.prototype.template="{{#filters}}\n<div class='filter'>\n <h3>\n {{name}}\n </h3>\n <ul id='{{filter_slug}}_filter_options'>\n {{#options}}\n <label>\n <li>\n <input {{#selected}}checked='checked'{{/selected}}\n class='left filter_option'\n id='filter_{{slug}}'\n name='filters[]'\n type='checkbox'\n value='{{value}}' />\n <div class='left filter_name'>{{name}}</div>\n <div class='right filter_count'>{{count}}</div>\n <div class='clear'></div>\n </li>\n </label>\n {{/options}}\n </ul>\n</div>\n{{/filters}}",d.prototype.selections=function(){var a,b=this;return a={},_.each(this.filters,function(b){return a[b]=_.pluck($("#"+b+"_filter_options input.filter_option:checked"),"value")}),a},d.prototype.noFiltersSelected=function(a){return _.all(a.filters,function(a){return _.all(a.options,function(a){return!a.selected})})},d.prototype.sortOptions=function(a){return _.map(a,function(a){return a.options=_.sortBy(a.options,function(a){return a.name}),a})},d.prototype.sort=function(a){return _.sortBy(a,function(a){return a.name})},d.prototype.render=function(a){var b;this.filters=_.pluck(a.filters,"filter_slug"),this.sort(a.filters),this.sortOptions(a.filters),b=Mustache.to_html(this.template,a),$(this.renderTo).html(b);if(this.noFiltersSelected(a)&&a[this.livelist.resourceName].length>0)return $('input[type="checkbox"]',this.renderTo).attr("checked","checked")},d.prototype.handleAdvancedOptionsClick=function(a){return a.preventDefault(),$(this.renderTo).slideToggle()},d}(Utilities),window.Pagination=function(b){function d(b,c){this.handlePaginationLinkClick=a(this.handlePaginationLinkClick,this),this.livelist=c,this.pagination=null,this.maxPages=30,this.emptyListMessage="<p>No "+this.livelist.resourceName+" matched your filter criteria</p>",this.setOptions(b),$(""+this.renderTo+" a").live("click",function(a){return a.preventDefault()}),$(""+this.renderTo+" li:not(.disabled) a").live("click",this.handlePaginationLinkClick)}return c(d,b),d.prototype.template="{{#isEmpty}}\n {{{emptyListMessage}}}\n{{/isEmpty}}\n{{^isEmpty}}\n<div class=\"pagination\">\n <ul>\n <li class=\"{{^previousPage}}disabled{{/previousPage}}\">\n <a href='{{urlPrefix}}?page={{previousPage}}' data-page='{{previousPage}}'>← Previous</a>\n </li>\n\n {{#pages}}\n <li class=\"{{#currentPage}}active disabled{{/currentPage}}\">\n <a href='{{urlPrefix}}?page={{page}}' data-page='{{page}}'>{{page}}</a>\n </li>\n {{/pages}}\n\n <li class=\"{{^nextPage}}disabled{{/nextPage}}\">\n <a href='{{urlPrefix}}?page={{nextPage}}' data-page='{{nextPage}}'>Next →</a>\n </li>\n </ul>\n</div>\n{{/isEmpty}}",d.prototype.pagesJSON=function(a,b){var c,d,e,f,g,h;return d=Math.floor(this.maxPages/2),c=a<=d?1:a-d,f=c+d*2-1,e=f>=b?b:f,_.map(function(){h=[];for(var a=c;c<=e?a<=e:a>=e;c<=e?a++:a--)h.push(a);return h}.apply(this),function(b){return{page:b,currentPage:a===b}})},d.prototype.paginationJSON=function(a){return{isEmpty:a.total_pages===0,emptyListMessage:this.emptyListMessage,currentPage:a.current_page,nextPage:a.next_page,previousPage:a.previous_page,urlPrefix:this.livelist.urlPrefix,pages:this.pagesJSON(a.current_page,a.total_pages)}},d.prototype.render=function(a){var b;return this.pagination=this.paginationJSON(a.pagination),b=Mustache.to_html(this.template,this.pagination),$(this.renderTo).html(b)},d.prototype.handlePaginationLinkClick=function(a){return a.preventDefault(),$(this.livelist.listSelector).trigger(this.livelist.eventName,{page:$(a.target).data("page")})},d}(Utilities),window.Search=function(b){function d(b,c){this.handleSearchFormSubmit=a(this.handleSearchFormSubmit,this);var d=this;this.livelist=c,this.setOptions(b),$(this.formSelector).submit(function(a){return d.handleSearchFormSubmit(a)})}return c(d,b),d.prototype.searchTerm=function(){var a;return a=$(this.searchTextInputSelector).val(),!a||a===""?null:a},d.prototype.handleSearchFormSubmit=function(a){return a.preventDefault(),$(this.livelist.listSelector).trigger(this.livelist.eventName)},d}(Utilities)})).call(this);
|
@@ -0,0 +1,6 @@
|
|
1
|
+
/*! Version: 0.4.2 */
|
2
|
+
/*
|
3
|
+
mustache.js — Logic-less templates in JavaScript
|
4
|
+
|
5
|
+
See http://mustache.github.com/ for more info.
|
6
|
+
*/var Mustache=function(){function g(a){return String(a).replace(/&(?!#?\w+;)|[<>"']/g,function(a){return f[a]||a})}var a=Object.prototype.toString;Array.isArray=Array.isArray||function(b){return a.call(b)=="[object Array]"};var b=String.prototype.trim,c;if(b)c=function(a){return a==null?"":b.call(a)};else{var d,e;/\S/.test(" ")?(d=/^[\s\xA0]+/,e=/[\s\xA0]+$/):(d=/^\s+/,e=/\s+$/),c=function(a){return a==null?"":a.toString().replace(d,"").replace(e,"")}}var f={"&":"&","<":"<",">":">",'"':""","'":"'"},h={},i=function(){};return i.prototype={otag:"{{",ctag:"}}",pragmas:{},buffer:[],pragmas_implemented:{"IMPLICIT-ITERATOR":!0},context:{},render:function(a,b,c,d){d||(this.context=b,this.buffer=[]);if(!this.includes("",a)){if(d)return a;this.send(a);return}a=this.render_pragmas(a);var e=this.render_section(a,b,c);e===!1&&(e=this.render_tags(a,b,c,d));if(d)return e;this.sendLines(e)},send:function(a){a!==""&&this.buffer.push(a)},sendLines:function(a){if(a){var b=a.split("\n");for(var c=0;c<b.length;c++)this.send(b[c])}},render_pragmas:function(a){if(!this.includes("%",a))return a;var b=this,c=this.getCachedRegex("render_pragmas",function(a,b){return new RegExp(a+"%([\\w-]+) ?([\\w]+=[\\w]+)?"+b,"g")});return a.replace(c,function(a,c,d){if(!b.pragmas_implemented[c])throw{message:"This implementation of mustache doesn't understand the '"+c+"' pragma"};b.pragmas[c]={};if(d){var e=d.split("=");b.pragmas[c][e[0]]=e[1]}return""})},render_partial:function(a,b,d){a=c(a);if(!d||d[a]===undefined)throw{message:"unknown_partial '"+a+"'"};return!b||typeof b[a]!="object"?this.render(d[a],b,d,!0):this.render(d[a],b[a],d,!0)},render_section:function(a,b,c){if(!this.includes("#",a)&&!this.includes("^",a))return!1;var d=this,e=this.getCachedRegex("render_section",function(a,b){return new RegExp("^([\\s\\S]*?)"+a+"(\\^|\\#)\\s*(.+?)\\s*"+b+"\n*([\\s\\S]*?)"+a+"\\/\\s*\\3\\s*"+b+"\\s*([\\s\\S]*)$","g")});return a.replace(e,function(a,e,f,g,h,i){var j=e?d.render_tags(e,b,c,!0):"",k=i?d.render(i,b,c,!0):"",l,m=d.find(g,b);return f==="^"?!m||Array.isArray(m)&&m.length===0?l=d.render(h,b,c,!0):l="":f==="#"&&(Array.isArray(m)?l=d.map(m,function(a){return d.render(h,d.create_context(a),c,!0)}).join(""):d.is_object(m)?l=d.render(h,d.create_context(m),c,!0):typeof m=="function"?l=m.call(b,h,function(a){return d.render(a,b,c,!0)}):m?l=d.render(h,b,c,!0):l=""),j+l+k})},render_tags:function(a,b,c,d){var e=this,f=function(){return e.getCachedRegex("render_tags",function(a,b){return new RegExp(a+"(=|!|>|&|\\{|%)?([^#\\^]+?)\\1?"+b+"+","g")})},h=f(),i=function(a,d,i){switch(d){case"!":return"";case"=":return e.set_delimiters(i),h=f(),"";case">":return e.render_partial(i,b,c);case"{":case"&":return e.find(i,b);default:return g(e.find(i,b))}},j=a.split("\n");for(var k=0;k<j.length;k++)j[k]=j[k].replace(h,i,this),d||this.send(j[k]);if(d)return j.join("\n")},set_delimiters:function(a){var b=a.split(" ");this.otag=this.escape_regex(b[0]),this.ctag=this.escape_regex(b[1])},escape_regex:function(a){if(!arguments.callee.sRE){var b=["/",".","*","+","?","|","(",")","[","]","{","}","\\"];arguments.callee.sRE=new RegExp("(\\"+b.join("|\\")+")","g")}return a.replace(arguments.callee.sRE,"\\$1")},find:function(a,b){function d(a){return a===!1||a===0||a}a=c(a);var e;if(a.match(/([a-z_]+)\./ig)){var f=this.walk_context(a,b);d(f)&&(e=f)}else d(b[a])?e=b[a]:d(this.context[a])&&(e=this.context[a]);return typeof e=="function"?e.apply(b):e!==undefined?e:""},walk_context:function(a,b){var c=a.split("."),d=b[c[0]]!=undefined?b:this.context,e=d[c.shift()];while(e!=undefined&&c.length>0)d=e,e=e[c.shift()];return typeof e=="function"?e.apply(d):e},includes:function(a,b){return b.indexOf(this.otag+a)!=-1},create_context:function(a){if(this.is_object(a))return a;var b=".";this.pragmas["IMPLICIT-ITERATOR"]&&(b=this.pragmas["IMPLICIT-ITERATOR"].iterator);var c={};return c[b]=a,c},is_object:function(a){return a&&typeof a=="object"},map:function(a,b){if(typeof a.map=="function")return a.map(b);var c=[],d=a.length;for(var e=0;e<d;e++)c.push(b(a[e]));return c},getCachedRegex:function(a,b){var c=h[this.otag];c||(c=h[this.otag]={});var d=c[this.ctag];d||(d=c[this.ctag]={});var e=d[a];return e||(e=d[a]=b(this.otag,this.ctag)),e}},{name:"mustache.js",version:"0.4.2",to_html:function(a,b,c,d){var e=new i;d&&(e.send=d),e.render(a,b||{},c);if(!d)return e.buffer.join("\n")}}}();
|
@@ -1,30 +1,31 @@
|
|
1
|
-
// Underscore.js 1.
|
2
|
-
// (c) 2009-
|
1
|
+
// Underscore.js 1.3.1
|
2
|
+
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
|
3
3
|
// Underscore is freely distributable under the MIT license.
|
4
4
|
// Portions of Underscore are inspired or borrowed from Prototype,
|
5
5
|
// Oliver Steele's Functional, and John Resig's Micro-Templating.
|
6
6
|
// For all details and documentation:
|
7
7
|
// http://documentcloud.github.com/underscore
|
8
|
-
(function(){function
|
9
|
-
c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&
|
10
|
-
h)&&!f--)break;g=!f}}d.pop();return g}var
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
var
|
22
|
-
c){var d={};c||(c=b.identity);return function(){var
|
23
|
-
a.apply(d,e);i();g=true}};b.debounce=function(a,b){var d;return function(){var e=this,f=arguments;clearTimeout(d);d=setTimeout(function(){d=null;a.apply(e,f)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=
|
24
|
-
function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=
|
25
|
-
1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return
|
26
|
-
|
27
|
-
|
28
|
-
b[c]=a[c])})};var
|
29
|
-
|
30
|
-
a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];
|
8
|
+
(function(){function q(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
|
9
|
+
c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&q(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&q(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c,
|
10
|
+
h)&&!f--)break;g=!f}}d.pop();return g}var r=this,G=r._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,H=k.unshift,l=o.toString,I=o.hasOwnProperty,w=k.forEach,x=k.map,y=k.reduce,z=k.reduceRight,A=k.filter,B=k.every,C=k.some,p=k.indexOf,D=k.lastIndexOf,o=Array.isArray,J=Object.keys,s=Function.prototype.bind,b=function(a){return new m(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else r._=b;b.VERSION="1.3.1";var j=b.each=
|
11
|
+
b.forEach=function(a,c,d){if(a!=null)if(w&&a.forEach===w)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(d,a[e],e,a)===n)break}else for(e in a)if(b.has(a,e)&&c.call(d,a[e],e,a)===n)break};b.map=b.collect=function(a,c,b){var e=[];if(a==null)return e;if(x&&a.map===x)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});if(a.length===+a.length)e.length=a.length;return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var f=arguments.length>2;a==
|
12
|
+
null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=
|
13
|
+
function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e=
|
14
|
+
e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck=
|
15
|
+
function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&(e={value:a,computed:b})});
|
16
|
+
return e.value};b.shuffle=function(a){var b=[],d;j(a,function(a,f){f==0?b[0]=a:(d=Math.floor(Math.random()*(f+1)),b[f]=b[d],b[d]=a)});return b};b.sortBy=function(a,c,d){return b.pluck(b.map(a,function(a,b,g){return{value:a,criteria:c.call(d,a,b,g)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c<d?-1:c>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,
|
17
|
+
c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:a.toArray?a.toArray():b.isArray(a)?i.call(a):b.isArguments(a)?i.call(a):b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=b.head=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-1]};b.rest=
|
18
|
+
b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,e=[];b.reduce(d,function(d,g,h){if(0==h||(c===true?b.last(d)!=g:!b.include(d,g)))d[d.length]=g,e[e.length]=a[h];return d},[]);
|
19
|
+
return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,
|
20
|
+
d){if(a==null)return-1;var e;if(d)return d=b.sortedIndex(a,c),a[d]===c?d:-1;if(p&&a.indexOf===p)return a.indexOf(c);for(d=0,e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(D&&a.lastIndexOf===D)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){arguments.length<=1&&(b=a||0,a=0);for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;)g[f++]=a,a+=d;return g};
|
21
|
+
var F=function(){};b.bind=function(a,c){var d,e;if(a.bind===s&&s)return s.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));F.prototype=a.prototype;var b=new F,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,
|
22
|
+
c){var d={};c||(c=b.identity);return function(){var e=c.apply(this,arguments);return b.has(d,e)?d[e]:d[e]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(a,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i=b.debounce(function(){h=g=false},c);return function(){d=this;e=arguments;var b;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);i()},c));g?h=true:
|
23
|
+
a.apply(d,e);i();g=true}};b.debounce=function(a,b){var d;return function(){var e=this,f=arguments;clearTimeout(d);d=setTimeout(function(){d=null;a.apply(e,f)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(i.call(arguments,0));return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};
|
24
|
+
b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments,
|
25
|
+
1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};
|
26
|
+
b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};
|
27
|
+
b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a),
|
28
|
+
function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+
|
29
|
+
u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]=
|
30
|
+
function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=
|
31
|
+
true;return this};m.prototype.value=function(){return this._wrapped}}).call(this);
|
@@ -5,23 +5,26 @@ module Livelist
|
|
5
5
|
module Rails
|
6
6
|
module ActiveRecord
|
7
7
|
|
8
|
-
def
|
8
|
+
def filters
|
9
9
|
@filters ||= FilterCollection.new
|
10
|
-
|
10
|
+
end
|
11
|
+
|
12
|
+
def filter_for(slug, options = {})
|
13
|
+
filters.create_filter(
|
11
14
|
:reference_criteria => options[:reference_criteria],
|
15
|
+
:name => options[:name],
|
12
16
|
:model_name => model_name,
|
13
17
|
:slug => slug
|
14
18
|
)
|
19
|
+
end
|
15
20
|
|
16
|
-
|
17
|
-
|
18
|
-
end
|
19
|
-
|
20
|
-
def filter(params)
|
21
|
-
@filters.relation(scoped, params)
|
22
|
-
end
|
21
|
+
def filters_as_json(params, options = {})
|
22
|
+
filters.as_json(scoped, params, options)
|
23
23
|
end
|
24
24
|
|
25
|
+
def filter(params, options = {})
|
26
|
+
filters.relation(scoped, params, options)
|
27
|
+
end
|
25
28
|
end
|
26
29
|
end
|
27
30
|
end
|
@@ -5,6 +5,10 @@ module Livelist
|
|
5
5
|
module Rails
|
6
6
|
|
7
7
|
class Filter
|
8
|
+
DEFAULT_FILTER_OPTIONS = {
|
9
|
+
:reference_criteria => nil
|
10
|
+
}
|
11
|
+
|
8
12
|
attr_accessor :slug,
|
9
13
|
:name,
|
10
14
|
:key_name,
|
@@ -29,6 +33,12 @@ module Livelist
|
|
29
33
|
)
|
30
34
|
end
|
31
35
|
|
36
|
+
def prepare_options(options)
|
37
|
+
options ||= {}
|
38
|
+
options.reverse_merge!(DEFAULT_FILTER_OPTIONS)
|
39
|
+
@criteria.set_criteria(options[:reference_criteria]) if options[:reference_criteria]
|
40
|
+
end
|
41
|
+
|
32
42
|
def group_by
|
33
43
|
case @type
|
34
44
|
when :attribute then "#{model_name.tableize}.#{@slug}"
|
@@ -8,22 +8,28 @@ module Livelist
|
|
8
8
|
alias :filters :values
|
9
9
|
alias :find_filter :[]
|
10
10
|
|
11
|
+
def slugs
|
12
|
+
keys.map(&:to_sym)
|
13
|
+
end
|
14
|
+
|
11
15
|
def create_filter(options)
|
12
16
|
options.merge!(:filter_collection => self)
|
13
17
|
self[options[:slug]] = Filter.new(options)
|
14
18
|
end
|
15
19
|
|
16
|
-
def relation(query, params)
|
20
|
+
def relation(query, params, options = {})
|
17
21
|
params ||= {}
|
18
22
|
filters.each do |filter|
|
23
|
+
filter.prepare_options(options[filter.slug])
|
19
24
|
query = filter.relation(query, params[filter.slug.to_s], params.empty?)
|
20
25
|
end
|
21
26
|
query
|
22
27
|
end
|
23
28
|
|
24
|
-
def as_json(query, params)
|
29
|
+
def as_json(query, params, options = {})
|
25
30
|
params ||= {}
|
26
31
|
filters.map do |filter|
|
32
|
+
filter.prepare_options(options[filter.slug])
|
27
33
|
filter.set_criteria_counts(query, params)
|
28
34
|
filter.as_json(params[filter.slug])
|
29
35
|
end
|
@@ -10,18 +10,28 @@ module Livelist
|
|
10
10
|
attr_reader :slug
|
11
11
|
|
12
12
|
def initialize(options)
|
13
|
-
@filter
|
14
|
-
@
|
15
|
-
@reference_criteria = @reference_criteria.call if @reference_criteria.respond_to?(:call)
|
16
|
-
@slug = options[:slug]
|
13
|
+
@filter = options[:filter]
|
14
|
+
@slug = options[:slug]
|
17
15
|
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
initialize_criteria(options[:reference_criteria])
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize_criteria(reference_criteria)
|
20
|
+
reference_criteria ||= default_reference_criteria
|
21
|
+
reference_criteria = reference_criteria.call if reference_criteria.respond_to?(:call)
|
22
|
+
reference_criteria.each { |reference| create_criterion(reference) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def set_criteria(reference_criteria)
|
26
|
+
clear
|
27
|
+
reference_criteria.each { |reference| create_criterion(reference) }
|
21
28
|
end
|
22
29
|
|
23
30
|
def default_reference_criteria
|
24
|
-
|
31
|
+
case @filter.type
|
32
|
+
when :attribute then @filter.model_class.select("distinct #{@filter.slug}")
|
33
|
+
when :association then @filter.slug.to_s.classify.constantize.scoped
|
34
|
+
end
|
25
35
|
end
|
26
36
|
|
27
37
|
def create_criterion(reference)
|
@@ -18,7 +18,7 @@ module Livelist
|
|
18
18
|
livelist_filename = 'livelist'
|
19
19
|
end
|
20
20
|
|
21
|
-
config.action_view.javascript_expansions[:livelist_dependencies] = %W(mustache underscore
|
21
|
+
config.action_view.javascript_expansions[:livelist_dependencies] = %W(mustache.min underscore-min)
|
22
22
|
config.action_view.javascript_expansions[:livelist] = [livelist_filename]
|
23
23
|
end
|
24
24
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
module Livelist
|
2
2
|
module Rails
|
3
|
-
VERSION = '0.0.
|
4
|
-
LIVELIST_VERSION = '
|
5
|
-
MUSTACHE_VERSION = '
|
6
|
-
UNDERSCORE_VERSION = '1.
|
3
|
+
VERSION = '0.0.12'
|
4
|
+
LIVELIST_VERSION = '0.0.7'
|
5
|
+
MUSTACHE_VERSION = '0.4.2'
|
6
|
+
UNDERSCORE_VERSION = '1.3.1'
|
7
7
|
end
|
8
8
|
end
|
data/livelist-rails.gemspec
CHANGED
@@ -18,6 +18,8 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
|
+
s.add_runtime_dependency 'activesupport'
|
22
|
+
s.add_runtime_dependency 'activerecord'
|
21
23
|
s.add_development_dependency 'rspec'
|
22
|
-
|
24
|
+
s.add_development_dependency 'simplecov'
|
23
25
|
end
|
@@ -1,23 +1,82 @@
|
|
1
1
|
require 'spec_helper.rb'
|
2
|
+
require 'active_support/core_ext/string/inflections'
|
2
3
|
require File.expand_path('./lib/livelist/rails/active_record.rb')
|
3
4
|
|
4
5
|
describe Livelist::Rails::ActiveRecord do
|
6
|
+
module ActiveRecord::ClassMethods
|
7
|
+
def model_name
|
8
|
+
'User'
|
9
|
+
end
|
10
|
+
|
11
|
+
def column_names
|
12
|
+
['state', 'status']
|
13
|
+
end
|
14
|
+
|
15
|
+
def reflect_on_all_associations
|
16
|
+
[]
|
17
|
+
end
|
18
|
+
|
19
|
+
def select(*args)
|
20
|
+
[]
|
21
|
+
end
|
22
|
+
|
23
|
+
def scoped
|
24
|
+
ActiveRecord::Relation.new(nil, nil)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
5
28
|
class User
|
29
|
+
extend ActiveRecord::ClassMethods
|
6
30
|
extend Livelist::Rails::ActiveRecord
|
7
|
-
filters :status, :state
|
8
31
|
end
|
9
32
|
|
10
33
|
subject { User }
|
11
34
|
|
12
|
-
context :
|
13
|
-
|
35
|
+
context :filters do
|
36
|
+
it 'filters should be a FilterCollection object' do
|
37
|
+
subject.filters.should be_kind_of(Livelist::Rails::FilterCollection)
|
38
|
+
end
|
14
39
|
end
|
15
40
|
|
16
|
-
|
41
|
+
context :filter_for do
|
42
|
+
let(:name) { 'State' }
|
43
|
+
let(:reference_criteria) { ['South Carolina', 'Virginia'] }
|
44
|
+
let(:options) do
|
45
|
+
{
|
46
|
+
:reference_criteria => reference_criteria,
|
47
|
+
:name => name,
|
48
|
+
:model_name => 'User',
|
49
|
+
:slug => :state
|
50
|
+
}
|
51
|
+
end
|
17
52
|
|
18
|
-
|
53
|
+
it 'should call create a filter with the proper options' do
|
54
|
+
subject.filters.should_receive(:create_filter).with(options)
|
55
|
+
subject.filter_for(:state, :reference_criteria => reference_criteria, :name => name)
|
56
|
+
end
|
57
|
+
end
|
19
58
|
|
20
|
-
|
59
|
+
context 'Runtime Methods' do
|
60
|
+
let(:options) do
|
61
|
+
{}
|
62
|
+
end
|
21
63
|
|
22
|
-
|
64
|
+
let(:params) do
|
65
|
+
{}
|
66
|
+
end
|
67
|
+
|
68
|
+
context :filters_as_json do
|
69
|
+
it do
|
70
|
+
subject.filters.should_receive(:as_json).with(nil, params, options)
|
71
|
+
subject.filters_as_json(params, options)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context :filter do
|
76
|
+
it do
|
77
|
+
subject.filters.should_receive(:filter).with(nil, params, options)
|
78
|
+
subject.filter(params, options)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
23
82
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -2,10 +2,13 @@
|
|
2
2
|
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
3
|
# Require this file using `require "spec_helper.rb"` to ensure that it is only
|
4
4
|
# loaded once.
|
5
|
-
|
5
|
+
|
6
|
+
require 'simplecov'
|
7
|
+
SimpleCov.start
|
8
|
+
|
6
9
|
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
10
|
RSpec.configure do |config|
|
8
|
-
config.treat_symbols_as_metadata_keys_with_true_values = true
|
9
|
-
config.run_all_when_everything_filtered = true
|
10
|
-
config.filter_run :focus
|
11
|
+
#config.treat_symbols_as_metadata_keys_with_true_values = true
|
12
|
+
#config.run_all_when_everything_filtered = true
|
13
|
+
#config.filter_run :focus
|
11
14
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: livelist-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.12
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,33 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-03-
|
12
|
+
date: 2012-03-28 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activesupport
|
16
|
+
requirement: &8072460 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *8072460
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: activerecord
|
27
|
+
requirement: &8072040 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *8072040
|
14
36
|
- !ruby/object:Gem::Dependency
|
15
37
|
name: rspec
|
16
|
-
requirement: &
|
38
|
+
requirement: &8071620 !ruby/object:Gem::Requirement
|
17
39
|
none: false
|
18
40
|
requirements:
|
19
41
|
- - ! '>='
|
@@ -21,10 +43,10 @@ dependencies:
|
|
21
43
|
version: '0'
|
22
44
|
type: :development
|
23
45
|
prerelease: false
|
24
|
-
version_requirements: *
|
46
|
+
version_requirements: *8071620
|
25
47
|
- !ruby/object:Gem::Dependency
|
26
|
-
name:
|
27
|
-
requirement: &
|
48
|
+
name: simplecov
|
49
|
+
requirement: &8071200 !ruby/object:Gem::Requirement
|
28
50
|
none: false
|
29
51
|
requirements:
|
30
52
|
- - ! '>='
|
@@ -32,7 +54,7 @@ dependencies:
|
|
32
54
|
version: '0'
|
33
55
|
type: :development
|
34
56
|
prerelease: false
|
35
|
-
version_requirements: *
|
57
|
+
version_requirements: *8071200
|
36
58
|
description: ! 'livelist-rails is a Rails 3.1 Engine/Extension incorporating the following
|
37
59
|
javascript libraries: Mustache.js, underscore.js, jQuery and livelist.js, and providing
|
38
60
|
ActiveRecord filtering extenstions.'
|
@@ -52,7 +74,7 @@ files:
|
|
52
74
|
- app/assets/javascripts/livelist.coffee
|
53
75
|
- app/assets/javascripts/livelist.js
|
54
76
|
- app/assets/javascripts/livelist.min.js
|
55
|
-
- app/assets/javascripts/mustache.js
|
77
|
+
- app/assets/javascripts/mustache.min.js
|
56
78
|
- app/assets/javascripts/underscore-min.js
|
57
79
|
- lib/livelist-rails.rb
|
58
80
|
- lib/livelist/rails.rb
|
@@ -1,435 +0,0 @@
|
|
1
|
-
/*
|
2
|
-
mustache.js — Logic-less templates in JavaScript
|
3
|
-
|
4
|
-
See http://mustache.github.com/ for more info.
|
5
|
-
*/
|
6
|
-
|
7
|
-
var Mustache = function () {
|
8
|
-
var _toString = Object.prototype.toString;
|
9
|
-
|
10
|
-
Array.isArray = Array.isArray || function (obj) {
|
11
|
-
return _toString.call(obj) == "[object Array]";
|
12
|
-
}
|
13
|
-
|
14
|
-
var _trim = String.prototype.trim, trim;
|
15
|
-
|
16
|
-
if (_trim) {
|
17
|
-
trim = function (text) {
|
18
|
-
return text == null ? "" : _trim.call(text);
|
19
|
-
}
|
20
|
-
} else {
|
21
|
-
var trimLeft, trimRight;
|
22
|
-
|
23
|
-
// IE doesn't match non-breaking spaces with \s.
|
24
|
-
if ((/\S/).test("\xA0")) {
|
25
|
-
trimLeft = /^[\s\xA0]+/;
|
26
|
-
trimRight = /[\s\xA0]+$/;
|
27
|
-
} else {
|
28
|
-
trimLeft = /^\s+/;
|
29
|
-
trimRight = /\s+$/;
|
30
|
-
}
|
31
|
-
|
32
|
-
trim = function (text) {
|
33
|
-
return text == null ? "" :
|
34
|
-
text.toString().replace(trimLeft, "").replace(trimRight, "");
|
35
|
-
}
|
36
|
-
}
|
37
|
-
|
38
|
-
var escapeMap = {
|
39
|
-
"&": "&",
|
40
|
-
"<": "<",
|
41
|
-
">": ">",
|
42
|
-
'"': '"',
|
43
|
-
"'": '''
|
44
|
-
};
|
45
|
-
|
46
|
-
function escapeHTML(string) {
|
47
|
-
return String(string).replace(/&(?!\w+;)|[<>"']/g, function (s) {
|
48
|
-
return escapeMap[s] || s;
|
49
|
-
});
|
50
|
-
}
|
51
|
-
|
52
|
-
var regexCache = {};
|
53
|
-
var Renderer = function () {};
|
54
|
-
|
55
|
-
Renderer.prototype = {
|
56
|
-
otag: "{{",
|
57
|
-
ctag: "}}",
|
58
|
-
pragmas: {},
|
59
|
-
buffer: [],
|
60
|
-
pragmas_implemented: {
|
61
|
-
"IMPLICIT-ITERATOR": true
|
62
|
-
},
|
63
|
-
context: {},
|
64
|
-
|
65
|
-
render: function (template, context, partials, in_recursion) {
|
66
|
-
// reset buffer & set context
|
67
|
-
if (!in_recursion) {
|
68
|
-
this.context = context;
|
69
|
-
this.buffer = []; // TODO: make this non-lazy
|
70
|
-
}
|
71
|
-
|
72
|
-
// fail fast
|
73
|
-
if (!this.includes("", template)) {
|
74
|
-
if (in_recursion) {
|
75
|
-
return template;
|
76
|
-
} else {
|
77
|
-
this.send(template);
|
78
|
-
return;
|
79
|
-
}
|
80
|
-
}
|
81
|
-
|
82
|
-
// get the pragmas together
|
83
|
-
template = this.render_pragmas(template);
|
84
|
-
|
85
|
-
// render the template
|
86
|
-
var html = this.render_section(template, context, partials);
|
87
|
-
|
88
|
-
// render_section did not find any sections, we still need to render the tags
|
89
|
-
if (html === false) {
|
90
|
-
html = this.render_tags(template, context, partials, in_recursion);
|
91
|
-
}
|
92
|
-
|
93
|
-
if (in_recursion) {
|
94
|
-
return html;
|
95
|
-
} else {
|
96
|
-
this.sendLines(html);
|
97
|
-
}
|
98
|
-
},
|
99
|
-
|
100
|
-
/*
|
101
|
-
Sends parsed lines
|
102
|
-
*/
|
103
|
-
send: function (line) {
|
104
|
-
if (line !== "") {
|
105
|
-
this.buffer.push(line);
|
106
|
-
}
|
107
|
-
},
|
108
|
-
|
109
|
-
sendLines: function (text) {
|
110
|
-
if (text) {
|
111
|
-
var lines = text.split("\n");
|
112
|
-
for (var i = 0; i < lines.length; i++) {
|
113
|
-
this.send(lines[i]);
|
114
|
-
}
|
115
|
-
}
|
116
|
-
},
|
117
|
-
|
118
|
-
/*
|
119
|
-
Looks for %PRAGMAS
|
120
|
-
*/
|
121
|
-
render_pragmas: function (template) {
|
122
|
-
// no pragmas
|
123
|
-
if (!this.includes("%", template)) {
|
124
|
-
return template;
|
125
|
-
}
|
126
|
-
|
127
|
-
var that = this;
|
128
|
-
var regex = this.getCachedRegex("render_pragmas", function (otag, ctag) {
|
129
|
-
return new RegExp(otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + ctag, "g");
|
130
|
-
});
|
131
|
-
|
132
|
-
return template.replace(regex, function (match, pragma, options) {
|
133
|
-
if (!that.pragmas_implemented[pragma]) {
|
134
|
-
throw({message:
|
135
|
-
"This implementation of mustache doesn't understand the '" +
|
136
|
-
pragma + "' pragma"});
|
137
|
-
}
|
138
|
-
that.pragmas[pragma] = {};
|
139
|
-
if (options) {
|
140
|
-
var opts = options.split("=");
|
141
|
-
that.pragmas[pragma][opts[0]] = opts[1];
|
142
|
-
}
|
143
|
-
return "";
|
144
|
-
// ignore unknown pragmas silently
|
145
|
-
});
|
146
|
-
},
|
147
|
-
|
148
|
-
/*
|
149
|
-
Tries to find a partial in the curent scope and render it
|
150
|
-
*/
|
151
|
-
render_partial: function (name, context, partials) {
|
152
|
-
name = trim(name);
|
153
|
-
if (!partials || partials[name] === undefined) {
|
154
|
-
throw({message: "unknown_partial '" + name + "'"});
|
155
|
-
}
|
156
|
-
if (!context || typeof context[name] != "object") {
|
157
|
-
return this.render(partials[name], context, partials, true);
|
158
|
-
}
|
159
|
-
return this.render(partials[name], context[name], partials, true);
|
160
|
-
},
|
161
|
-
|
162
|
-
/*
|
163
|
-
Renders inverted (^) and normal (#) sections
|
164
|
-
*/
|
165
|
-
render_section: function (template, context, partials) {
|
166
|
-
if (!this.includes("#", template) && !this.includes("^", template)) {
|
167
|
-
// did not render anything, there were no sections
|
168
|
-
return false;
|
169
|
-
}
|
170
|
-
|
171
|
-
var that = this;
|
172
|
-
|
173
|
-
var regex = this.getCachedRegex("render_section", function (otag, ctag) {
|
174
|
-
// This regex matches _the first_ section ({{#foo}}{{/foo}}), and captures the remainder
|
175
|
-
return new RegExp(
|
176
|
-
"^([\\s\\S]*?)" + // all the crap at the beginning that is not {{*}} ($1)
|
177
|
-
|
178
|
-
otag + // {{
|
179
|
-
"(\\^|\\#)\\s*(.+)\\s*" + // #foo (# == $2, foo == $3)
|
180
|
-
ctag + // }}
|
181
|
-
|
182
|
-
"\n*([\\s\\S]*?)" + // between the tag ($2). leading newlines are dropped
|
183
|
-
|
184
|
-
otag + // {{
|
185
|
-
"\\/\\s*\\3\\s*" + // /foo (backreference to the opening tag).
|
186
|
-
ctag + // }}
|
187
|
-
|
188
|
-
"\\s*([\\s\\S]*)$", // everything else in the string ($4). leading whitespace is dropped.
|
189
|
-
|
190
|
-
"g");
|
191
|
-
});
|
192
|
-
|
193
|
-
|
194
|
-
// for each {{#foo}}{{/foo}} section do...
|
195
|
-
return template.replace(regex, function (match, before, type, name, content, after) {
|
196
|
-
// before contains only tags, no sections
|
197
|
-
var renderedBefore = before ? that.render_tags(before, context, partials, true) : "",
|
198
|
-
|
199
|
-
// after may contain both sections and tags, so use full rendering function
|
200
|
-
renderedAfter = after ? that.render(after, context, partials, true) : "",
|
201
|
-
|
202
|
-
// will be computed below
|
203
|
-
renderedContent,
|
204
|
-
|
205
|
-
value = that.find(name, context);
|
206
|
-
|
207
|
-
if (type === "^") { // inverted section
|
208
|
-
if (!value || Array.isArray(value) && value.length === 0) {
|
209
|
-
// false or empty list, render it
|
210
|
-
renderedContent = that.render(content, context, partials, true);
|
211
|
-
} else {
|
212
|
-
renderedContent = "";
|
213
|
-
}
|
214
|
-
} else if (type === "#") { // normal section
|
215
|
-
if (Array.isArray(value)) { // Enumerable, Let's loop!
|
216
|
-
renderedContent = that.map(value, function (row) {
|
217
|
-
return that.render(content, that.create_context(row), partials, true);
|
218
|
-
}).join("");
|
219
|
-
} else if (that.is_object(value)) { // Object, Use it as subcontext!
|
220
|
-
renderedContent = that.render(content, that.create_context(value),
|
221
|
-
partials, true);
|
222
|
-
} else if (typeof value == "function") {
|
223
|
-
// higher order section
|
224
|
-
renderedContent = value.call(context, content, function (text) {
|
225
|
-
return that.render(text, context, partials, true);
|
226
|
-
});
|
227
|
-
} else if (value) { // boolean section
|
228
|
-
renderedContent = that.render(content, context, partials, true);
|
229
|
-
} else {
|
230
|
-
renderedContent = "";
|
231
|
-
}
|
232
|
-
}
|
233
|
-
|
234
|
-
return renderedBefore + renderedContent + renderedAfter;
|
235
|
-
});
|
236
|
-
},
|
237
|
-
|
238
|
-
/*
|
239
|
-
Replace {{foo}} and friends with values from our view
|
240
|
-
*/
|
241
|
-
render_tags: function (template, context, partials, in_recursion) {
|
242
|
-
// tit for tat
|
243
|
-
var that = this;
|
244
|
-
|
245
|
-
var new_regex = function () {
|
246
|
-
return that.getCachedRegex("render_tags", function (otag, ctag) {
|
247
|
-
return new RegExp(otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" + ctag + "+", "g");
|
248
|
-
});
|
249
|
-
};
|
250
|
-
|
251
|
-
var regex = new_regex();
|
252
|
-
var tag_replace_callback = function (match, operator, name) {
|
253
|
-
switch(operator) {
|
254
|
-
case "!": // ignore comments
|
255
|
-
return "";
|
256
|
-
case "=": // set new delimiters, rebuild the replace regexp
|
257
|
-
that.set_delimiters(name);
|
258
|
-
regex = new_regex();
|
259
|
-
return "";
|
260
|
-
case ">": // render partial
|
261
|
-
return that.render_partial(name, context, partials);
|
262
|
-
case "{": // the triple mustache is unescaped
|
263
|
-
return that.find(name, context);
|
264
|
-
default: // escape the value
|
265
|
-
return escapeHTML(that.find(name, context));
|
266
|
-
}
|
267
|
-
};
|
268
|
-
var lines = template.split("\n");
|
269
|
-
for(var i = 0; i < lines.length; i++) {
|
270
|
-
lines[i] = lines[i].replace(regex, tag_replace_callback, this);
|
271
|
-
if (!in_recursion) {
|
272
|
-
this.send(lines[i]);
|
273
|
-
}
|
274
|
-
}
|
275
|
-
|
276
|
-
if (in_recursion) {
|
277
|
-
return lines.join("\n");
|
278
|
-
}
|
279
|
-
},
|
280
|
-
|
281
|
-
set_delimiters: function (delimiters) {
|
282
|
-
var dels = delimiters.split(" ");
|
283
|
-
this.otag = this.escape_regex(dels[0]);
|
284
|
-
this.ctag = this.escape_regex(dels[1]);
|
285
|
-
},
|
286
|
-
|
287
|
-
escape_regex: function (text) {
|
288
|
-
// thank you Simon Willison
|
289
|
-
if (!arguments.callee.sRE) {
|
290
|
-
var specials = [
|
291
|
-
'/', '.', '*', '+', '?', '|',
|
292
|
-
'(', ')', '[', ']', '{', '}', '\\'
|
293
|
-
];
|
294
|
-
arguments.callee.sRE = new RegExp(
|
295
|
-
'(\\' + specials.join('|\\') + ')', 'g'
|
296
|
-
);
|
297
|
-
}
|
298
|
-
return text.replace(arguments.callee.sRE, '\\$1');
|
299
|
-
},
|
300
|
-
|
301
|
-
/*
|
302
|
-
find `name` in current `context`. That is find me a value
|
303
|
-
from the view object
|
304
|
-
*/
|
305
|
-
find: function (name, context) {
|
306
|
-
name = trim(name);
|
307
|
-
|
308
|
-
// Checks whether a value is thruthy or false or 0
|
309
|
-
function is_kinda_truthy(bool) {
|
310
|
-
return bool === false || bool === 0 || bool;
|
311
|
-
}
|
312
|
-
|
313
|
-
var value;
|
314
|
-
|
315
|
-
// check for dot notation eg. foo.bar
|
316
|
-
if (name.match(/([a-z_]+)\./ig)) {
|
317
|
-
var childValue = this.walk_context(name, context);
|
318
|
-
if (is_kinda_truthy(childValue)) {
|
319
|
-
value = childValue;
|
320
|
-
}
|
321
|
-
} else {
|
322
|
-
if (is_kinda_truthy(context[name])) {
|
323
|
-
value = context[name];
|
324
|
-
} else if (is_kinda_truthy(this.context[name])) {
|
325
|
-
value = this.context[name];
|
326
|
-
}
|
327
|
-
}
|
328
|
-
|
329
|
-
if (typeof value == "function") {
|
330
|
-
return value.apply(context);
|
331
|
-
}
|
332
|
-
if (value !== undefined) {
|
333
|
-
return value;
|
334
|
-
}
|
335
|
-
// silently ignore unkown variables
|
336
|
-
return "";
|
337
|
-
},
|
338
|
-
|
339
|
-
walk_context: function (name, context) {
|
340
|
-
var path = name.split('.');
|
341
|
-
// if the var doesn't exist in current context, check the top level context
|
342
|
-
var value_context = (context[path[0]] != undefined) ? context : this.context;
|
343
|
-
var value = value_context[path.shift()];
|
344
|
-
while (value != undefined && path.length > 0) {
|
345
|
-
value_context = value;
|
346
|
-
value = value[path.shift()];
|
347
|
-
}
|
348
|
-
// if the value is a function, call it, binding the correct context
|
349
|
-
if (typeof value == "function") {
|
350
|
-
return value.apply(value_context);
|
351
|
-
}
|
352
|
-
return value;
|
353
|
-
},
|
354
|
-
|
355
|
-
// Utility methods
|
356
|
-
|
357
|
-
/* includes tag */
|
358
|
-
includes: function (needle, haystack) {
|
359
|
-
return haystack.indexOf(this.otag + needle) != -1;
|
360
|
-
},
|
361
|
-
|
362
|
-
// by @langalex, support for arrays of strings
|
363
|
-
create_context: function (_context) {
|
364
|
-
if (this.is_object(_context)) {
|
365
|
-
return _context;
|
366
|
-
} else {
|
367
|
-
var iterator = ".";
|
368
|
-
if (this.pragmas["IMPLICIT-ITERATOR"]) {
|
369
|
-
iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator;
|
370
|
-
}
|
371
|
-
var ctx = {};
|
372
|
-
ctx[iterator] = _context;
|
373
|
-
return ctx;
|
374
|
-
}
|
375
|
-
},
|
376
|
-
|
377
|
-
is_object: function (a) {
|
378
|
-
return a && typeof a == "object";
|
379
|
-
},
|
380
|
-
|
381
|
-
/*
|
382
|
-
Why, why, why? Because IE. Cry, cry cry.
|
383
|
-
*/
|
384
|
-
map: function (array, fn) {
|
385
|
-
if (typeof array.map == "function") {
|
386
|
-
return array.map(fn);
|
387
|
-
} else {
|
388
|
-
var r = [];
|
389
|
-
var l = array.length;
|
390
|
-
for(var i = 0; i < l; i++) {
|
391
|
-
r.push(fn(array[i]));
|
392
|
-
}
|
393
|
-
return r;
|
394
|
-
}
|
395
|
-
},
|
396
|
-
|
397
|
-
getCachedRegex: function (name, generator) {
|
398
|
-
var byOtag = regexCache[this.otag];
|
399
|
-
if (!byOtag) {
|
400
|
-
byOtag = regexCache[this.otag] = {};
|
401
|
-
}
|
402
|
-
|
403
|
-
var byCtag = byOtag[this.ctag];
|
404
|
-
if (!byCtag) {
|
405
|
-
byCtag = byOtag[this.ctag] = {};
|
406
|
-
}
|
407
|
-
|
408
|
-
var regex = byCtag[name];
|
409
|
-
if (!regex) {
|
410
|
-
regex = byCtag[name] = generator(this.otag, this.ctag);
|
411
|
-
}
|
412
|
-
|
413
|
-
return regex;
|
414
|
-
}
|
415
|
-
};
|
416
|
-
|
417
|
-
return({
|
418
|
-
name: "mustache.js",
|
419
|
-
version: "0.4.0-dev",
|
420
|
-
|
421
|
-
/*
|
422
|
-
Turns a template and view into HTML
|
423
|
-
*/
|
424
|
-
to_html: function (template, view, partials, send_fun) {
|
425
|
-
var renderer = new Renderer();
|
426
|
-
if (send_fun) {
|
427
|
-
renderer.send = send_fun;
|
428
|
-
}
|
429
|
-
renderer.render(template, view || {}, partials);
|
430
|
-
if (!send_fun) {
|
431
|
-
return renderer.buffer.join("\n");
|
432
|
-
}
|
433
|
-
}
|
434
|
-
});
|
435
|
-
}();
|