nali 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/README.md +19 -14
  2. data/lib/client/javascripts/nali/application.js.coffee +59 -0
  3. data/lib/client/javascripts/nali/collection.js.coffee +188 -0
  4. data/lib/{assets → client}/javascripts/nali/connection.js.coffee +32 -32
  5. data/lib/{assets → client}/javascripts/nali/controller.js.coffee +28 -30
  6. data/lib/{assets → client}/javascripts/nali/cookie.js.coffee +5 -5
  7. data/lib/client/javascripts/nali/deferred.js.coffee +16 -0
  8. data/lib/client/javascripts/nali/extensions.js.coffee +18 -0
  9. data/lib/{assets → client}/javascripts/nali/index.js +2 -1
  10. data/lib/{assets → client}/javascripts/nali/jbone.min.js +1 -1
  11. data/lib/{assets → client}/javascripts/nali/model.js.coffee +208 -118
  12. data/lib/{assets → client}/javascripts/nali/nali.js.coffee +32 -32
  13. data/lib/client/javascripts/nali/notice.js.coffee +29 -0
  14. data/lib/{assets → client}/javascripts/nali/router.js.coffee +20 -22
  15. data/lib/{assets → client}/javascripts/nali/view.js.coffee +63 -60
  16. data/lib/generator/Gemfile +8 -8
  17. data/lib/generator/app/{assets → client}/javascripts/application.js.coffee +1 -1
  18. data/lib/generator/app/{assets → client}/javascripts/controllers/homes.js.coffee +4 -4
  19. data/lib/generator/app/client/javascripts/models/home.js.coffee +5 -0
  20. data/lib/generator/app/client/javascripts/views/home/index.js.coffee +9 -0
  21. data/lib/generator/app/{assets → client}/stylesheets/application.css.sass +1 -1
  22. data/lib/generator/app/{assets → client}/stylesheets/home/index.css.sass +5 -5
  23. data/lib/generator/app/{assets → client}/stylesheets/notices/error.css.sass +1 -1
  24. data/lib/generator/app/{assets → client}/stylesheets/notices/info.css.sass +1 -1
  25. data/lib/generator/app/{assets → client}/stylesheets/notices/warning.css.sass +1 -1
  26. data/lib/generator/app/{templates → client/templates}/application.html.erb +1 -1
  27. data/lib/generator/app/{templates → client/templates}/home/index.html +1 -1
  28. data/lib/generator/app/client/templates/notice/error.html +1 -0
  29. data/lib/generator/app/client/templates/notice/info.html +1 -0
  30. data/lib/generator/app/client/templates/notice/warning.html +1 -0
  31. data/lib/generator/app/server/clients.rb +15 -0
  32. data/lib/generator/app/{controllers → server/controllers}/application_controller.rb +2 -2
  33. data/lib/generator/app/{models → server/models}/access.yml +0 -0
  34. data/lib/generator/{config → app/server}/routes.rb +3 -3
  35. data/lib/generator/config/environments/development.rb +2 -2
  36. data/lib/generator/config/environments/production.rb +4 -4
  37. data/lib/generator/config/environments/test.rb +5 -5
  38. data/lib/nali/application.rb +24 -23
  39. data/lib/nali/connection.rb +14 -31
  40. data/lib/nali/controller.rb +36 -16
  41. data/lib/nali/generator.rb +38 -23
  42. data/lib/nali/helpers.rb +3 -3
  43. data/lib/nali/model.rb +36 -22
  44. data/lib/nali/tasks.rb +16 -25
  45. data/lib/nali/version.rb +2 -2
  46. metadata +85 -86
  47. data/lib/assets/javascripts/nali/application.js.coffee +0 -28
  48. data/lib/assets/javascripts/nali/collection.js.coffee +0 -150
  49. data/lib/assets/javascripts/nali/extensions.js.coffee +0 -19
  50. data/lib/assets/javascripts/nali/notice.js.coffee +0 -14
  51. data/lib/generator/app/assets/javascripts/models/home.js.coffee +0 -3
  52. data/lib/generator/app/assets/javascripts/views/home/index.js.coffee +0 -1
  53. data/lib/generator/app/templates/notice/error.html +0 -1
  54. data/lib/generator/app/templates/notice/info.html +0 -1
  55. data/lib/generator/app/templates/notice/warning.html +0 -1
  56. data/lib/generator/config/clients.rb +0 -19
  57. data/lib/generator/config/environments/development.rb~ +0 -32
  58. data/lib/generator/config/environments/production.rb~ +0 -80
@@ -1,13 +1,13 @@
1
1
  Nali.extend Cookie:
2
-
2
+
3
3
  set: ( name, value, options = {} ) ->
4
4
  set = "#{ name }=#{ escape( value ) }"
5
5
  if options.live? and typeof options.live is 'number'
6
6
  date = new Date
7
- date.setDate date.getDate() + options.live
8
- date.setMinutes date.getMinutes() - date.getTimezoneOffset()
7
+ date.setDate date.getDate() + options.live
8
+ date.setMinutes date.getMinutes() - date.getTimezoneOffset()
9
9
  set += "; expires=#{ date.toUTCString() }"
10
- set += '; domain=' + escape options.domain if options.domain?
10
+ set += '; domain=' + escape options.domain if options.domain?
11
11
  set += '; path=' + if options.path? then escape options.path else '/'
12
12
  set += '; secure' if options.secure?
13
13
  document.cookie = set
@@ -18,4 +18,4 @@ Nali.extend Cookie:
18
18
  if get then unescape( get[2] ) else null
19
19
 
20
20
  remove: ( name ) ->
21
- @set name, '', live: -1
21
+ @set name, '', live: -1
@@ -0,0 +1,16 @@
1
+ Nali.extend Deferred:
2
+
3
+ new: ( callback ) ->
4
+ obj = Object.create @
5
+ obj.defer = callback
6
+ obj.awaits = []
7
+ obj.results = []
8
+ obj
9
+
10
+ await: ( callback ) ->
11
+ wrapper = ( args... ) =>
12
+ @results.push callback args...
13
+ @awaits.splice @awaits.indexOf( wrapper ), 1
14
+ @defer @results unless @awaits.length
15
+ @awaits.push wrapper
16
+ wrapper
@@ -0,0 +1,18 @@
1
+ String::upper = ->
2
+ "#{ @toUpperCase() }"
3
+
4
+ String::lower = ->
5
+ "#{ @toLowerCase() }"
6
+
7
+ String::capitalize = ->
8
+ @charAt(0).upper() + @slice(1)
9
+
10
+ String::uncapitalize = ->
11
+ @charAt(0).lower() + @slice(1)
12
+
13
+ String::camel = ->
14
+ @replace /(_[^_]+)/g, ( match ) -> match[ 1.. ].capitalize()
15
+
16
+ String::underscore = ->
17
+ str = @replace /([A-Z])/g, ( match ) -> '_' + match.lower()
18
+ if str[ 0...1 ] is '_' then str[ 1.. ] else str
@@ -1,10 +1,11 @@
1
1
  //= require ./jbone.min
2
2
  //= require ./extensions
3
3
  //= require ./nali
4
+ //= require ./deferred
4
5
  //= require ./application
5
6
  //= require ./connection
6
7
  //= require ./router
7
8
  //= require ./view
8
9
  //= require ./model
9
10
  //= require ./collection
10
- //= require_tree .
11
+ //= require_tree .
@@ -7,4 +7,4 @@
7
7
  * Released under the MIT license.
8
8
  */
9
9
 
10
- !function(a){function b(b){var c=b.length,d=typeof b;return o(d)||b===a?!1:1===b.nodeType&&c?!0:p(d)||0===c||"number"==typeof c&&c>0&&c-1 in b}function c(a,b){var c,d;this.originalEvent=a,d=function(a,b){this[a]="preventDefault"===a?function(){return this.defaultPrevented=!0,b[a]()}:o(b[a])?function(){return b[a]()}:b[a]};for(c in a)(a[c]||"function"==typeof a[c])&&d.call(this,c,a);q.extend(this,b)}var d,e=a.$,f=a.jBone,g=/^<(\w+)\s*\/?>$/,h=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,i=[].slice,j=[].splice,k=Object.keys,l=document,m=function(a){return"string"==typeof a},n=function(a){return a instanceof Object},o=function(a){var b={};return a&&"[object Function]"===b.toString.call(a)},p=function(a){return Array.isArray(a)},q=function(a,b){return new d.init(a,b)};q.noConflict=function(){return a.$=e,a.jBone=f,q},d=q.fn=q.prototype={init:function(a,b){var c,d,e,f;if(!a)return this;if(m(a)){if(d=g.exec(a))return this[0]=l.createElement(d[1]),this.length=1,n(b)&&this.attr(b),this;if((d=h.exec(a))&&d[1]){for(f=l.createDocumentFragment(),e=l.createElement("div"),e.innerHTML=a;e.lastChild;)f.appendChild(e.firstChild);return c=i.call(f.childNodes),q.merge(this,c)}if(q.isElement(b))return q(b).find(a);try{return c=l.querySelectorAll(a),q.merge(this,c)}catch(j){return this}}return a.nodeType?(this[0]=a,this.length=1,this):o(a)?a():a instanceof q?a:q.makeArray(a,this)},pop:[].pop,push:[].push,reverse:[].reverse,shift:[].shift,sort:[].sort,splice:[].splice,slice:[].slice,indexOf:[].indexOf,forEach:[].forEach,unshift:[].unshift,concat:[].concat,join:[].join,every:[].every,some:[].some,filter:[].filter,map:[].map,reduce:[].reduce,reduceRight:[].reduceRight,length:0},d.constructor=q,d.init.prototype=d,q.setId=function(b){var c=b.jid;b===a?c="window":void 0===b.jid&&(b.jid=c=++q._cache.jid),q._cache.events[c]||(q._cache.events[c]={})},q.getData=function(b){b=b instanceof q?b[0]:b;var c=b===a?"window":b.jid;return{jid:c,events:q._cache.events[c]}},q.isElement=function(a){return a&&a instanceof q||a instanceof HTMLElement||m(a)},q._cache={events:{},jid:0},q.merge=function(a,b){for(var c=b.length,d=a.length,e=0;c>e;)a[d++]=b[e++];return a.length=d,a},q.contains=function(a,b){var c;return a.reverse().some(function(a){return a.contains(b)?c=a:void 0}),c},q.extend=function(a){var b,c,d,e;return j.call(arguments,1).forEach(function(f){if(f)for(b=k(f),c=b.length,d=0,e=a;c>d;d++)e[b[d]]=f[b[d]]}),a},q.makeArray=function(a,c){var d=c||[];return null!==a&&(b(a)?q.merge(d,m(a)?[a]:a):d.push(a)),d},q.Event=function(a,b){var c,d;return a.type&&!b&&(b=a,a=a.type),c=a.split(".").splice(1).join("."),d=a.split(".")[0],a=l.createEvent("Event"),a.initEvent(d,!0,!0),q.extend(a,{namespace:c,isDefaultPrevented:function(){return a.defaultPrevented}},b)},d.on=function(a){var b,d,e,f,g,h,i,j,k=arguments,l=this.length,m=0;for(2===k.length?b=k[1]:(d=k[1],b=k[2]),j=function(j){q.setId(j),g=q.getData(j).events,a.split(" ").forEach(function(a){h=a.split(".")[0],e=a.split(".").splice(1).join("."),g[h]=g[h]||[],f=function(a){a.namespace&&a.namespace!==e||(i=null,d?(~q(j).find(d).indexOf(a.target)||(i=q.contains(q(j).find(d),a.target)))&&(i=i||a.target,a=new c(a,{currentTarget:i}),b.call(i,a)):b.call(j,a))},g[h].push({namespace:e,fn:f,originfn:b}),j.addEventListener&&j.addEventListener(h,f,!1)})};l>m;m++)j(this[m]);return this},d.one=function(a){var b,c,d,e=arguments,f=0,g=this.length;for(2===e.length?b=e[1]:(c=e[1],b=e[2]),d=function(d){a.split(" ").forEach(function(a){var e=function(c){q(d).off(a,e),b.call(d,c)};c?q(d).on(a,c,e):q(d).on(a,e)})};g>f;f++)d(this[f]);return this},d.trigger=function(a){var b,c=[],d=0,e=this.length;if(!a)return this;for(m(a)?c=a.split(" ").map(function(a){return q.Event(a)}):(a=a instanceof Event?a:q.Event(a),c=[a]),b=function(a){c.forEach(function(b){b.type&&a.dispatchEvent&&a.dispatchEvent(b)})};e>d;d++)b(this[d]);return this},d.off=function(a,b){var c,d,e,f,g=0,h=this.length,i=function(a,c,d,e,f){var g;(b&&f.originfn===b||!b)&&(g=f.fn),a[c][d].fn===g&&(e.removeEventListener(c,g),q._cache.events[q.getData(e).jid][c].splice(d,1))};for(e=function(b){var e,g,h;return(c=q.getData(b).events)?!a&&c?k(c).forEach(function(a){for(g=c[a],e=g.length;e--;)i(c,a,e,b,g[e])}):void a.split(" ").forEach(function(a){if(f=a.split(".")[0],d=a.split(".").splice(1).join("."),c[f])for(g=c[f],e=g.length;e--;)h=g[e],(!d||d&&h.namespace===d)&&i(c,f,e,b,h);else d&&k(c).forEach(function(a){for(g=c[a],e=g.length;e--;)h=g[e],h.namespace.split(".")[0]===d.split(".")[0]&&i(c,a,e,b,h)})}):void 0};h>g;g++)e(this[g]);return this},d.find=function(a){for(var b=[],c=0,d=this.length,e=function(c){o(c.querySelectorAll)&&[].forEach.call(c.querySelectorAll(a),function(a){b.push(a)})};d>c;c++)e(this[c]);return q(b)},d.get=function(a){return this[a]},d.eq=function(a){return q(this[a])},d.parent=function(){for(var a,b=[],c=0,d=this.length;d>c;c++)!~b.indexOf(a=this[c].parentElement)&&a&&b.push(a);return q(b)},d.toArray=function(){return i.call(this)},d.is=function(){var a=arguments;return this.some(function(b){return b.tagName.toLowerCase()===a[0]})},d.has=function(){var a=arguments;return this.some(function(b){return b.querySelectorAll(a[0]).length})},d.attr=function(a,b){var c,d=arguments,e=0,f=this.length;if(m(a)&&1===d.length)return this[0]&&this[0].getAttribute(a);for(2===d.length?c=function(c){c.setAttribute(a,b)}:n(a)&&(c=function(b){k(a).forEach(function(c){b.setAttribute(c,a[c])})});f>e;e++)c(this[e]);return this},d.val=function(a){var b=0,c=this.length;if(0===arguments.length)return this[0]&&this[0].value;for(;c>b;b++)this[b].value=a;return this},d.css=function(b,c){var d,e=arguments,f=0,g=this.length;if(m(b)&&1===e.length)return this[0]&&a.getComputedStyle(this[0])[b];for(2===e.length?d=function(a){a.style[b]=c}:n(b)&&(d=function(a){k(b).forEach(function(c){a.style[c]=b[c]})});g>f;f++)d(this[f]);return this},d.data=function(a,b){var c,d=arguments,e={},f=0,g=this.length,h=function(a,b,c){n(c)?(a.jdata=a.jdata||{},a.jdata[b]=c):a.dataset[b]=c},i=function(a){return"true"===a?!0:"false"===a?!1:a};if(0===d.length)return this[0].jdata&&(e=this[0].jdata),k(this[0].dataset).forEach(function(a){e[a]=i(this[0].dataset[a])},this),e;if(1===d.length&&m(a))return this[0]&&i(this[0].dataset[a]||this[0].jdata&&this[0].jdata[a]);for(1===d.length&&n(a)?c=function(b){k(a).forEach(function(c){h(b,c,a[c])})}:2===d.length&&(c=function(c){h(c,a,b)});g>f;f++)c(this[f]);return this},d.removeData=function(a){for(var b,c,d=0,e=this.length;e>d;d++)if(b=this[d].jdata,c=this[d].dataset,a)b&&b[a]&&delete b[a],delete c[a];else{for(a in b)delete b[a];for(a in c)delete c[a]}return this},d.html=function(a){var b,c=arguments;return 1===c.length&&void 0!==a?this.empty().append(a):0===c.length&&(b=this[0])?b.innerHTML:this},d.append=function(a){var b,c=0,d=this.length;for(m(a)&&h.exec(a)?a=q(a):n(a)||(a=document.createTextNode(a)),a=a instanceof q?a:q(a),b=function(b,c){a.forEach(function(a){b.appendChild(c?a.cloneNode():a)})};d>c;c++)b(this[c],c);return this},d.appendTo=function(a){return q(a).append(this),this},d.empty=function(){for(var a,b=0,c=this.length;c>b;b++)for(a=this[b];a.lastChild;)a.removeChild(a.lastChild);return this},d.remove=function(){var a,b=0,c=this.length;for(this.off();c>b;b++)a=this[b],delete a.jdata,a.parentNode&&a.parentNode.removeChild(a);return this},"object"==typeof module&&module&&"object"==typeof module.exports?module.exports=q:"function"==typeof define&&define.amd?(define(function(){return q}),a.jBone=a.$=q):"object"==typeof a&&"object"==typeof a.document&&(a.jBone=a.$=q)}(window);
10
+ !function(a){function b(b){var c=b.length,d=typeof b;return o(d)||b===a?!1:1===b.nodeType&&c?!0:p(d)||0===c||"number"==typeof c&&c>0&&c-1 in b}function c(a,b){var c,d;this.originalEvent=a,d=function(a,b){this[a]="preventDefault"===a?function(){return this.defaultPrevented=!0,b[a]()}:o(b[a])?function(){return b[a]()}:b[a]};for(c in a)(a[c]||"function"==typeof a[c])&&d.call(this,c,a);q.extend(this,b)}var d,e=a.$,f=a.jBone,g=/^<(\w+)\s*\/?>$/,h=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,i=[].slice,j=[].splice,k=Object.keys,l=document,m=function(a){return"string"==typeof a},n=function(a){return a instanceof Object},o=function(a){var b={};return a&&"[object Function]"===b.toString.call(a)},p=function(a){return Array.isArray(a)},q=function(a,b){return new d.init(a,b)};q.noConflict=function(){return a.$=e,a.jBone=f,q},d=q.fn=q.prototype={init:function(a,b){var c,d,e,f;if(!a)return this;if(m(a)){if(d=g.exec(a))return this[0]=l.createElement(d[1]),this.length=1,n(b)&&this.attr(b),this;if((d=h.exec(a))&&d[1]){for(f=l.createDocumentFragment(),e=l.createElement("div"),e.innerHTML=a;e.lastChild;)f.appendChild(e.firstChild);return c=i.call(f.childNodes),q.merge(this,c)}if(q.isElement(b))return q(b).find(a);try{return c=l.querySelectorAll(a),q.merge(this,c)}catch(j){return this}}return a.nodeType?(this[0]=a,this.length=1,this):o(a)?a():a instanceof q?a:q.makeArray(a,this)},pop:[].pop,push:[].push,reverse:[].reverse,shift:[].shift,sort:[].sort,splice:[].splice,slice:[].slice,indexOf:[].indexOf,forEach:[].forEach,unshift:[].unshift,concat:[].concat,join:[].join,every:[].every,some:[].some,filter:[].filter,map:[].map,reduce:[].reduce,reduceRight:[].reduceRight,length:0},d.constructor=q,d.init.prototype=d,q.setId=function(b){var c=b.jid;b===a?c="window":void 0===b.jid&&(b.jid=c=++q._cache.jid),q._cache.events[c]||(q._cache.events[c]={})},q.getData=function(b){b=b instanceof q?b[0]:b;var c=b===a?"window":b.jid;return{jid:c,events:q._cache.events[c]}},q.isElement=function(a){return a&&a instanceof q||a instanceof HTMLElement||m(a)},q._cache={events:{},jid:0},q.merge=function(a,b){for(var c=b.length,d=a.length,e=0;c>e;)a[d++]=b[e++];return a.length=d,a},q.contains=function(a,b){var c;return a.reverse().some(function(a){return a.contains(b)?c=a:void 0}),c},q.extend=function(a){var b,c,d,e;return j.call(arguments,1).forEach(function(f){if(f)for(b=k(f),c=b.length,d=0,e=a;c>d;d++)e[b[d]]=f[b[d]]}),a},q.makeArray=function(a,c){var d=c||[];return null!==a&&(b(a)?q.merge(d,m(a)?[a]:a):d.push(a)),d},q.Event=function(a,b){var c,d;return a.type&&!b&&(b=a,a=a.type),c=a.split(".").splice(1).join("."),d=a.split(".")[0],a=l.createEvent("Event"),a.initEvent(d,!0,!0),q.extend(a,{namespace:c,isDefaultPrevented:function(){return a.defaultPrevented}},b)},d.on=function(a){var b,d,e,f,g,h,i,j,k=arguments,l=this.length,m=0;for(2===k.length?b=k[1]:(d=k[1],b=k[2]),j=function(j){q.setId(j),g=q.getData(j).events,a.split(" ").forEach(function(a){h=a.split(".")[0],e=a.split(".").splice(1).join("."),g[h]=g[h]||[],f=function(a){a.namespace&&a.namespace!==e||(i=null,d?(~q(j).find(d).indexOf(a.target)||(i=q.contains(q(j).find(d),a.target)))&&(i=i||a.target,a=new c(a,{currentTarget:i}),b.call(i,a)):b.call(j,a))},g[h].push({namespace:e,fn:f,originfn:b}),j.addEventListener&&j.addEventListener(h,f,!1)})};l>m;m++)j(this[m]);return this},d.one=function(a){var b,c,d,e=arguments,f=0,g=this.length;for(2===e.length?b=e[1]:(c=e[1],b=e[2]),d=function(d){a.split(" ").forEach(function(a){var e=function(c){q(d).off(a,e),b.call(d,c)};c?q(d).on(a,c,e):q(d).on(a,e)})};g>f;f++)d(this[f]);return this},d.trigger=function(a){var b,c=[],d=0,e=this.length;if(!a)return this;for(m(a)?c=a.split(" ").map(function(a){return q.Event(a)}):(a=a instanceof Event?a:q.Event(a),c=[a]),b=function(a){c.forEach(function(b){b.type&&a.dispatchEvent&&a.dispatchEvent(b)})};e>d;d++)b(this[d]);return this},d.off=function(a,b){var c,d,e,f,g=0,h=this.length,i=function(a,c,d,e,f){var g;(b&&f.originfn===b||!b)&&(g=f.fn),a[c][d].fn===g&&(e.removeEventListener(c,g),q._cache.events[q.getData(e).jid][c].splice(d,1))};for(e=function(b){var e,g,h;return(c=q.getData(b).events)?!a&&c?k(c).forEach(function(a){for(g=c[a],e=g.length;e--;)i(c,a,e,b,g[e])}):void a.split(" ").forEach(function(a){if(f=a.split(".")[0],d=a.split(".").splice(1).join("."),c[f])for(g=c[f],e=g.length;e--;)h=g[e],(!d||d&&h.namespace===d)&&i(c,f,e,b,h);else d&&k(c).forEach(function(a){for(g=c[a],e=g.length;e--;)h=g[e],h.namespace.split(".")[0]===d.split(".")[0]&&i(c,a,e,b,h)})}):void 0};h>g;g++)e(this[g]);return this},d.find=function(a){for(var b=[],c=0,d=this.length,e=function(c){o(c.querySelectorAll)&&[].forEach.call(c.querySelectorAll(a),function(a){b.push(a)})};d>c;c++)e(this[c]);return q(b)},d.get=function(a){return this[a]},d.eq=function(a){return q(this[a])},d.parent=function(){for(var a,b=[],c=0,d=this.length;d>c;c++)!~b.indexOf(a=this[c].parentElement)&&a&&b.push(a);return q(b)},d.toArray=function(){return i.call(this)},d.is=function(){var a=arguments;return this.some(function(b){return b.tagName.toLowerCase()===a[0]})},d.has=function(){var a=arguments;return this.some(function(b){return b.querySelectorAll(a[0]).length})},d.attr=function(a,b){var c,d=arguments,e=0,f=this.length;if(m(a)&&1===d.length)return this[0]&&this[0].getAttribute(a);for(2===d.length?c=function(c){c.setAttribute(a,b)}:n(a)&&(c=function(b){k(a).forEach(function(c){b.setAttribute(c,a[c])})});f>e;e++)c(this[e]);return this},d.val=function(a){var b=0,c=this.length;if(0===arguments.length)return this[0]&&this[0].value;for(;c>b;b++)this[b].value=a;return this},d.css=function(b,c){var d,e=arguments,f=0,g=this.length;if(m(b)&&1===e.length)return this[0]&&a.getComputedStyle(this[0])[b];for(2===e.length?d=function(a){a.style[b]=c}:n(b)&&(d=function(a){k(b).forEach(function(c){a.style[c]=b[c]})});g>f;f++)d(this[f]);return this},d.data=function(a,b){var c,d=arguments,e={},f=0,g=this.length,h=function(a,b,c){n(c)?(a.jdata=a.jdata||{},a.jdata[b]=c):a.dataset[b]=c},i=function(a){return"true"===a?!0:"false"===a?!1:a};if(0===d.length)return this[0].jdata&&(e=this[0].jdata),k(this[0].dataset).forEach(function(a){e[a]=i(this[0].dataset[a])},this),e;if(1===d.length&&m(a))return this[0]&&i(this[0].dataset[a]||this[0].jdata&&this[0].jdata[a]);for(1===d.length&&n(a)?c=function(b){k(a).forEach(function(c){h(b,c,a[c])})}:2===d.length&&(c=function(c){h(c,a,b)});g>f;f++)c(this[f]);return this},d.removeData=function(a){for(var b,c,d=0,e=this.length;e>d;d++)if(b=this[d].jdata,c=this[d].dataset,a)b&&b[a]&&delete b[a],delete c[a];else{for(a in b)delete b[a];for(a in c)delete c[a]}return this},d.html=function(a){var b,c=arguments;return 1===c.length&&void 0!==a?this.empty().append(a):0===c.length&&(b=this[0])?b.innerHTML:this},d.append=function(a){var b,c=0,d=this.length;for(m(a)&&h.exec(a)?a=q(a):n(a)||(a=document.createTextNode(a)),a=a instanceof q?a:q(a),b=function(b,c){a.forEach(function(a){b.appendChild(c?a.cloneNode():a)})};d>c;c++)b(this[c],c);return this},d.appendTo=function(a){return q(a).append(this),this},d.empty=function(){for(var a,b=0,c=this.length;c>b;b++)for(a=this[b];a.lastChild;)a.removeChild(a.lastChild);return this},d.remove=function(){var a,b=0,c=this.length;for(this.off();c>b;b++)a=this[b],delete a.jdata,a.parentNode&&a.parentNode.removeChild(a);return this},"object"==typeof module&&module&&"object"==typeof module.exports?module.exports=q:"function"==typeof define&&define.amd?(define(function(){return q}),a.jBone=a.$=q):"object"==typeof a&&"object"==typeof a.document&&(a.jBone=a.$=q)}(window);
@@ -1,22 +1,24 @@
1
1
  Nali.extend Model:
2
-
2
+
3
3
  extension: ->
4
4
  if @_name isnt 'Model'
5
5
  @table = @tables[ @_name ] ?= []
6
6
  @table.index = {}
7
+ @parseRelations()
7
8
  @adapt()
8
9
  @
9
-
10
+
10
11
  cloning: ->
11
12
  @views = {}
12
-
13
+
13
14
  tables: {}
14
15
  hasOne: []
15
16
  hasMany: []
16
17
  attributes: {}
17
18
  updated: 0
18
19
  noticesWait: []
19
-
20
+ destroyed: false
21
+
20
22
  adapt: ->
21
23
  for name, method of @ when /^_\w+/.test name
22
24
  do ( name, method ) =>
@@ -24,7 +26,7 @@ Nali.extend Model:
24
26
  @[ name[ 1.. ] ] = ( args... ) -> @[ name ] args...
25
27
  @adaptViews()
26
28
  @
27
-
29
+
28
30
  adaptViews: ->
29
31
  @views = {}
30
32
  for name, view of @View.extensions when name.indexOf( @_name ) >= 0
@@ -35,13 +37,13 @@ Nali.extend Model:
35
37
  @show short
36
38
  @
37
39
  @
38
-
40
+
39
41
  notice: ( params ) ->
40
42
  # добавляет уведомление в очередь на выполнение, запускает выполнение очереди
41
43
  @noticesWait.push params
42
44
  @runNotices()
43
45
  @
44
-
46
+
45
47
  runNotices: ->
46
48
  # запускает выполнение уведомлений на существующих моделях
47
49
  for item, index in @noticesWait[ 0.. ]
@@ -49,131 +51,154 @@ Nali.extend Model:
49
51
  model[ item.notice ] item.params
50
52
  @noticesWait.splice @noticesWait.indexOf( item ), 1
51
53
  @
52
-
54
+
53
55
  # работа с моделями
54
-
56
+
55
57
  accessing: ->
56
58
  # устанавливает геттеры доступа к атрибутам и связям
57
59
  @access @attributes
58
60
  @setRelations()
59
61
  @
60
-
62
+
61
63
  force: ( params = {} ) ->
62
64
  # создает новую модель с заданными атрибутами
63
- attributes = @default_attributes()
65
+ attributes = @defaultAttributes()
64
66
  attributes[ name ] = value for name, value of params
65
67
  attributes[ name ] = @normalizeValue value for name, value of attributes
66
68
  @clone( attributes: attributes ).accessing()
67
-
69
+
68
70
  save: ( success, failure ) ->
69
71
  # отправляет на сервер запрос на сохранение модели, вызывает success в случае успеха и failure при неудаче
70
72
  if @isValid()
71
- @query "#{ @_name.lowercase() }s.save", @attributes,
73
+ @query "#{ @_name.lower() }s.save", @attributes,
72
74
  ( { attributes, created, updated } ) =>
73
75
  @update( attributes, updated, created ).write()
74
76
  success? @
75
77
  else failure? @
76
78
  @
77
-
79
+
78
80
  sync: ( { _name, attributes, created, updated, destroyed } ) ->
79
81
  # синхронизирует пришедшую с сервера модель с локальной, либо создает новую
80
- if model = @extensions[ _name ].find attributes.id
82
+ if model = @extensions[ _name ].find attributes.id
81
83
  if destroyed then model.remove()
82
84
  else model.update attributes, updated, created
83
85
  else
84
- model = @extensions[ _name ].build attributes
86
+ model = @extensions[ _name ].new attributes
85
87
  model.updated = updated
86
88
  model.created = created
87
89
  model.write()
88
90
  @
89
-
90
- select: ( filters, success, failure ) ->
91
- # отправляет на сервер запрос на выборку моделей по фильтру, вызывает success в случае успеха и failure при неудаче
92
- @query @_name.lowercase() + 's.select', filters, success, failure if Object.keys( filters ).length
93
-
91
+
92
+ select: ( options ) ->
93
+ # отправляет на сервер запрос на выборку моделей
94
+ obj = {}
95
+ if typeof options is 'object'
96
+ obj.selector = Object.keys( options )[0]
97
+ obj.params = options[ obj.selector ]
98
+ else
99
+ obj.selector = options
100
+ obj.params = {}
101
+ @query @_name.lower() + 's.select', obj
102
+ @
103
+
104
+ written: ->
105
+ @ in @table
106
+
94
107
  write: ->
95
- # добавляет модель во временную таблицу, генерирует событие create
96
- unless @ in @table
97
- @table.push @
98
- @table.index[ @id ] = @
108
+ # добавляет модель в локальную таблицу, генерирует событие create
109
+ @table.index[ @id ] = @ if @id and not @table.index[ @id ]
110
+ unless @written()
111
+ @table.push @
99
112
  @onCreate?()
100
- @Model.trigger "create.#{ @_name.lowercase() }", @
113
+ @Model.trigger "create.#{ @_name.lower() }", @
101
114
  @Model.runNotices()
102
115
  @
103
-
116
+
104
117
  remove: ->
105
- # удаляет модель из временной таблицы, генерирует событие destroy
106
- if @ in @table
118
+ # удаляет модель из локальной таблицы, генерирует событие destroy
119
+ if @written()
120
+ @destroyed = true
107
121
  delete @table.index[ @id ]
108
- @table.splice @table.indexOf( @ ), 1
109
- @trigger 'destroy', @
122
+ @table.splice @table.indexOf( @ ), 1
123
+ @trigger 'destroy'
110
124
  @onDestroy?()
111
125
  @unsubscribeAll()
112
- @
126
+ @
113
127
 
114
- build: ( attributes ) ->
128
+ new: ( attributes ) ->
115
129
  # создает модель, не сохраняя её на сервере
116
130
  @force attributes
117
-
131
+
118
132
  create: ( attributes, success, failure ) ->
119
133
  # создает модель, и сохраняет её на сервере, вызывает success в случае успеха и failure при неудаче
120
- @build( attributes ).save success, failure
121
-
134
+ @new( attributes ).save success, failure
135
+
122
136
  update: ( attributes, updated = 0, created = 0 ) ->
123
137
  # обновляет атрибуты модели, проверяя их валидность, генерирует событие update
124
138
  if not updated or updated > @updated
125
139
  @created = created if created
126
140
  changed = []
127
- changed.push name for name, value of attributes when @update_attribute name, value
141
+ changed.push name for name, value of attributes when @updateAttribute name, value
128
142
  if changed.length
129
143
  @updated = updated if updated
130
144
  @onUpdate? changed
131
- @trigger 'update', @, changed
145
+ @trigger 'update', changed
146
+ @Model.trigger "update.#{ @_name.lower() }", @
132
147
  @
133
-
134
- update_attribute: ( name, value ) ->
135
- # обновляет один атрибут модели, проверяя его валидность, генерирует событие update.{ propertyName }
148
+
149
+ updateAttribute: ( name, value ) ->
150
+ # обновляет один атрибут модели, проверяя его валидность, генерирует событие update.attributeName
136
151
  value = @normalizeValue value
137
152
  if @attributes[ name ] isnt value and @isValidAttributeValue( name, value )
138
153
  @attributes[ name ] = value
139
154
  @[ 'onUpdate' + name.capitalize() ]?()
140
- @trigger "update.#{ name }", @
155
+ @trigger "update.#{ name }"
141
156
  true
142
157
  else false
143
-
158
+
144
159
  destroy: ( success, failure ) ->
145
160
  # отправляет на сервер запрос на удаление модели, вызывает success в случае успеха и failure при неудаче
146
- @query @_name.lowercase() + 's.destroy', @attributes, success, failure
147
-
161
+ @query @_name.lower() + 's.destroy', @attributes, success, failure
162
+ @
163
+
148
164
  # поиск моделей
149
-
165
+
150
166
  find: ( id ) ->
151
167
  # находит модель по её id используя индекс
152
168
  @table.index[ id ]
153
-
169
+
154
170
  where: ( filters ) ->
155
- # находит все модели соответствующие фильтру, также отправляет запрос с фильтром на сервер,
156
- # возвращает коллекцию моделей, модели найденные на сервере также попадут в эту коллекцию
157
- collection = @Collection.clone model: @, filters: filters
171
+ # возвращает коллекцию моделей соответствующих фильтру
172
+ collection = @Collection.new @, filters
158
173
  collection.add model for model in @table when model.isCorrect filters
159
174
  if @forced and not collection.length
160
175
  attributes = {}
161
176
  attributes[ key ] = value for key, value of filters when typeof value in [ 'number', 'string' ]
162
- collection.add @build attributes
163
- @select filters
164
- collection
165
-
177
+ collection.add @new attributes
178
+ collection
179
+
180
+ all: ->
181
+ @where id: /./
182
+
166
183
  # работа с аттрибутами
167
-
168
- default_attributes: ->
184
+
185
+ guid: ->
186
+ # генерирует случайный идентификатор
187
+ date = new Date().getTime()
188
+ 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace /[xy]/g, ( sub ) ->
189
+ rand = ( date + Math.random() * 16 ) % 16 | 0
190
+ date = Math.floor date / 16
191
+ ( if sub is 'x' then rand else ( rand & 0x7 | 0x8 ) ).toString 16
192
+
193
+ defaultAttributes: ->
169
194
  # возвращает объект аттрибутов по умолчанию
170
- attributes = id: null
195
+ attributes = id: @guid()
171
196
  for name, value of @attributes
172
- if value instanceof Object
197
+ if value instanceof Object
173
198
  attributes[ name ] = if value.default? then value.default else null
174
199
  else attributes[ name ] = value
175
200
  attributes
176
-
201
+
177
202
  normalizeValue: ( value ) ->
178
203
  # приводит значения к нормальному виду, если в строке только числа - преобразуется к числу
179
204
  # т.е. строка '123' становится числом 123, '123.5' становится 123.5, а '123abc' остается строкой
@@ -184,11 +209,12 @@ Nali.extend Model:
184
209
 
185
210
  isCorrect: ( filters = {} ) ->
186
211
  # проверяет соответствие аттрибутов модели определенному набору фильтров, возвращает true либо false
212
+ return filters.call @ if typeof filters is 'function'
187
213
  return false unless Object.keys( filters ).length
188
214
  return false for name, filter of filters when not @isCorrectAttribute @attributes[ name ], filter
189
215
  return true
190
-
191
- isCorrectAttribute: ( attribute, filter ) ->
216
+
217
+ isCorrectAttribute: ( attribute, filter ) ->
192
218
  # проверяет соответствие аттрибута модели определенному фильтру, возвращает true либо false
193
219
  return false unless attribute
194
220
  if filter instanceof RegExp
@@ -199,69 +225,132 @@ Nali.extend Model:
199
225
  + attribute is filter
200
226
  else if filter instanceof Array
201
227
  '' + attribute in filter or + attribute in filter
202
- else false
203
-
204
- # работа со связями
205
-
228
+ else false
229
+
230
+ # работа со связями
231
+
232
+ parseRelations: ( type, options ) ->
233
+ # производит разбор связей
234
+ @relations = {}
235
+ for type in [ 'belongsTo', 'hasOne', 'hasMany' ]
236
+ for options in [].concat( @[ type ] ) when @[ type ]?
237
+ params = @[ 'parse' + type.capitalize() ] @parseInitialRelationParams options
238
+ section = type + if params.through? then 'Through' else ''
239
+ ( @relations[ section ] ?= [] ).push params
240
+ @
241
+
242
+ parseInitialRelationParams: ( options ) ->
243
+ # дает начальные параметры настроек связи
244
+ if typeof options is 'object'
245
+ params = name: Object.keys( options )[0]
246
+ params.through = through if through = options[ params.name ].through
247
+ params.key = key if key = options[ params.name ].key
248
+ params.model = @Model.extensions[ model.capitalize() ] if model = options[ params.name ].model
249
+ else
250
+ params = name: options
251
+ params
252
+
253
+ parseBelongsTo: ( params ) ->
254
+ # производит разбор связей belongsTo
255
+ params.model ?= @Model.extensions[ params.name.capitalize() ]
256
+ params.key ?= params.name.lower() + '_id'
257
+ params
258
+
259
+ parseHasOne: ( params ) ->
260
+ # производит разбор связей hasOne
261
+ params.model ?= @Model.extensions[ params.name.capitalize() ]
262
+ params.key ?= ( if params.through then params.name else @_name + '_id' ).lower()
263
+ params
264
+
265
+ parseHasMany: ( params ) ->
266
+ # производит разбор связей hasMany
267
+ params.model ?= @Model.extensions[ params.name[ ...-1 ].capitalize() ]
268
+ params.key ?= ( if params.through then params.name[ ...-1 ] else @_name + '_id' ).lower()
269
+ params
270
+
206
271
  setRelations: ->
207
- # устанавливает геттеры к объектам связанным с моделью
208
- @belongsToRelation attribute for attribute of @attributes when /_id$/.test attribute
209
- @hasOneRelation attribute for attribute in [].concat @hasOne
210
- @hasManyRelation attribute for attribute in [].concat @hasMany
211
- @
212
-
213
- belongsToRelation: ( attribute ) ->
214
- # устанавливает геттер типа belongs_to возвращающий связанную модель
215
- name = attribute.replace '_id', ''
216
- model = @Model.extensions[ name.capitalize() ]
217
- @getter name, => model.find @[ attribute ]
218
- @
219
-
220
- hasOneRelation: ( name ) ->
221
- # устанавливает геттер типа has_one возвращающий связанную модель
222
- @getter name, =>
272
+ # запускает установку связей у модели
273
+ @setRelationsType type for type in [ 'belongsTo', 'hasOne', 'hasMany', 'hasOneThrough', 'hasManyThrough' ]
274
+ @
275
+
276
+ setRelationsType: ( type ) ->
277
+ # запускает установку связей определенного типа
278
+ if params = @relations[ type ]
279
+ @[ 'set' + type.capitalize() ] param for param in params
280
+ @
281
+
282
+ setBelongsTo: ( { key, model, name, through } ) ->
283
+ # устанавливает геттер типа belongsTo возвращающий связанную модель
284
+ @getter name, => model.find @[ key ]
285
+ @
286
+
287
+ setHasOne: ( { key, model, name, through } ) ->
288
+ # устанавливает геттер типа hasOne возвращающий связанную модель
289
+ @getter name, =>
223
290
  delete @[ name ]
224
- ( filters = {} )[ "#{ @_name.lowercase() }_id" ] = @id
225
- relation = @Model.extensions[ name.capitalize() ].where filters
226
- @getter name, => relation.first()
227
- relation.first()
291
+ ( filters = {} )[ key ] = @id
292
+ collection = model.where filters
293
+ @getter name, => collection.first()
294
+ @[ name ]
228
295
  @
229
-
230
- hasManyRelation: ( name ) ->
231
- # устанавливает геттер типа has_many возвращающий коллекцию связанных моделей
232
- @getter name, =>
296
+
297
+ setHasMany: ( { key, model, name, through } ) ->
298
+ # устанавливает геттер типа hasMany возвращающий коллекцию связанных моделей
299
+ @getter name, =>
233
300
  delete @[ name ]
234
- ( filters = {} )[ "#{ @_name.lowercase() }_id" ] = @id
235
- @[ name ] = @Model.extensions[ name[ ...-1 ].capitalize() ].where filters
236
- @
237
-
301
+ ( filters = {} )[ key ] = @id
302
+ @[ name ] = model.where filters
303
+ @
304
+
305
+ setHasOneThrough: ( { key, model, name, through } ) ->
306
+ # устанавливает геттер типа hasOne возвращающий модель,
307
+ # связанную с текущей через модель through
308
+ @getter name, =>
309
+ delete @[ name ]
310
+ @getter name, => @[ through ][ key ]
311
+ @[ name ]
312
+ @
313
+
314
+ setHasManyThrough: ( { key, model, name, through } ) ->
315
+ # устанавливает геттер типа hasMany возвращающий коллекцию моделей,
316
+ # связанных с текущей через модель through
317
+ @getter name, =>
318
+ delete @[ name ]
319
+ @[ name ] = @Collection.new model, ->
320
+ return true for model in @[ through ] when model[ key ] is @
321
+ false
322
+ @[ name ].add @[ through ].pluck key
323
+ @[ name ].subscribeTo @[ through ], 'update.length.add', ( collection, model ) -> @add model[ key ]
324
+ @[ name ].subscribeTo @[ through ], 'update.length.remove', ( collection, model ) -> @remove model[ key ]
325
+ @[ name ]
326
+ @
327
+
238
328
  # работа с видами
239
-
329
+
240
330
  view: ( name ) ->
241
331
  # возвращает объект вида, либо новый, либо ранее созданный
242
332
  unless ( view = @views[ name ] )?
243
- if ( view = @::views[ name ] )?
244
- view = @views[ name ] = view.clone model: @
333
+ if ( view = @::views[ name ] )?
334
+ view = @views[ name ] = view.clone model: @
245
335
  else console.error "View %s of model %O does not exist", name, @
246
336
  view
247
-
337
+
248
338
  show: ( name, insertTo ) ->
249
- # вставляет html-код вида в указанное место ( это может быть селектор, html-элемент или ничего - тогда
250
- # вставка произойдет в элемент указанный в самом виде либо в элемент приложения )
339
+ # вставляет html-код вида в указанное место ( это может быть селектор, html-элемент или ничего - тогда
340
+ # вставка произойдет в элемент указанный в самом виде либо в элемент-контейнер приложения )
251
341
  # функция возвращает объект вида при успехе либо null при неудаче
252
- if ( view = @view( name ) )? then view.show insertTo else null
253
-
342
+ if ( view = @view( name ) )? then view.show insertTo else null
343
+
254
344
  hide: ( name ) ->
255
345
  # удаляет html-код вида со страницы
256
346
  # функция возвращает объект вида при успехе либо null при неудаче
257
- if ( view = @view( name ) )? then view.hide() else null
258
-
347
+ if ( view = @view( name ) )? then view.hide() else null
348
+
259
349
  # валидации
260
-
261
- validations:
350
+
351
+ validations:
262
352
  # набор валидационных проверок
263
353
  presence: ( value, filter ) -> if filter then value? else not value?
264
- match: ( value, filter ) -> not value? or filter.test value
265
354
  inclusion: ( value, filter ) -> not value? or value in filter
266
355
  exclusion: ( value, filter ) -> not value? or value not in filter
267
356
  length: ( value, filter ) ->
@@ -271,23 +360,24 @@ Nali.extend Model:
271
360
  return false if filter.max? and value.length > filter.max
272
361
  return false if filter.is? and value.length isnt filter.is
273
362
  true
274
- format: ( value, filter ) ->
363
+ format: ( value, filter ) ->
275
364
  return true if not value?
276
- return true if filter is 'boolean' and /^true|false$/.test value
277
- return true if filter is 'number' and /^[0-9]+$/.test value
278
- return true if filter is 'letters' and /^[A-zА-я]+$/.test value
279
- return true if filter is 'email' and /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,4})+$/.test value
365
+ return true if filter instanceof RegExp and filter.test value
366
+ return true if filter is 'boolean' and /^true|false$/.test value
367
+ return true if filter is 'number' and /^[0-9]+$/.test value
368
+ return true if filter is 'letters' and /^[A-zА-я]+$/.test value
369
+ return true if filter is 'email' and /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,4})+$/.test value
280
370
  false
281
-
371
+
282
372
  isValid: ->
283
- # проверяет валидна ли модель, вызывается перед сохранением модели на сервер если модель валидна,
373
+ # проверяет валидна ли модель, вызывается перед сохранением модели на сервер если модель валидна,
284
374
  # то вызов model.isValid() вернет true, иначе false
285
- return false for name, value of @attributes when not @isValidAttributeValue( name, value )
375
+ return false for name, value of @attributes when not @isValidAttributeValue( name, value )
286
376
  true
287
-
377
+
288
378
  isValidAttributeValue: ( name, value ) ->
289
- # проверяет валидно ли значение для определенного атрибута модели, вызывается при проверке
290
- # валидности модели, а также в методе update_attribute() перед изменением значения атрибута, если значение
379
+ # проверяет валидно ли значение для определенного атрибута модели, вызывается при проверке
380
+ # валидности модели, а также в методе updateAttribute() перед изменением значения атрибута, если значение
291
381
  # валидно то вызов model.isValidAttributeValue( name, value )? вернет true, иначе false
292
382
  for validation, tester of @validations when ( filter = @::attributes[ name ]?[ validation ] )?
293
383
  unless tester.call @, value, filter
@@ -295,4 +385,4 @@ Nali.extend Model:
295
385
  for type in [ 'info', 'warning', 'error' ] when ( message = @::attributes[ name ][ type ] )?
296
386
  @Notice[ type ] message: message
297
387
  return false
298
- true
388
+ true