nali 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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