sudojs-rails 0.2.9 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -75,33 +75,40 @@ and directories (with your shiny new `Spam` namespace).
75
75
 
76
76
  #### sudojs:install USAGE
77
77
 
78
- Description:
79
- Install sudo.js along with it's particular directory hierarchy.
80
-
81
- Example:
82
- rails g sudojs:install namespace [which_sudo] [which_markup] [use_coffee]
83
-
84
- The only mandatory argument is the initial `namespace`.
85
-
86
- The install generator uses these optional arguments to expand the values in the sudo_js.yml file:
87
- 1. `which_sudo` => which version of sudo.js are you loading? As sudo.js can be rebuilt into any number
88
- of custom configurations, indicate here what name should be placed into the application manifest.
89
- Defaults to 'sudo-x' if omitted.
90
- 2. `which_markup` => defaults to `erb`.
91
- 3. `use_coffee` => defaults to `false` pass `true` if you are of the coffee persuasion.
92
-
93
- This will create:
94
- app/
95
- assets/
96
- javascripts/
97
- application/
98
- yourNamespace.js
99
- model.js
100
- manifests/
101
- application.js
102
- views/
103
- config/
104
- sudo_js.yml
78
+ Description:
79
+ Install sudo.js along with it's particular directory hierarchy.
80
+
81
+ Example:
82
+ rails g sudojs:install namespace [which_sudo] [html_extension] [js_extension] [css_extension] [--skip-css=true/false]
83
+
84
+ The only mandatory argument is the initial `namespace`.
85
+
86
+ The install generator uses these optional arguments to expand the values in the sudo_js.yml file:
87
+ 1. `which_sudo` => which version of sudo.js are you loading? As sudo.js can be rebuilt into any number
88
+ of custom configurations, indicate here what name should be placed into the application manifest.
89
+ Defaults to 'sudo-x' if omitted.
90
+ 2. `html_extension` => Use the full extension, i.e. `.haml`. defaults to `.erb`.
91
+ 3. `js_extension` => defaults to `.js` pass `.js.coffee` if you are of the coffee persuasion.
92
+ 4. `css_extension` => defaults to `.css`. Sass for example would need `.css.scss`
93
+
94
+ NOTE: to skip any and all generating of css files pass the option `--skip-css=true` when invoking the install generator.
95
+ Obviously, you could then leave the `css_extension` argument out. The option defaults to false.
96
+
97
+ This will create:
98
+ app/
99
+ assets/
100
+ stylesheets/
101
+ manifests/
102
+ application.css
103
+ javascripts/
104
+ application/
105
+ yourNamespace.js
106
+ model.js
107
+ manifests/
108
+ application.js
109
+ views/
110
+ config/
111
+ sudo_js.yml
105
112
 
106
113
  #### sudojs:class USAGE
107
114
 
@@ -109,53 +116,63 @@ After installing a second generator becomes available, run `rails g` again and `
109
116
  The reason for this is that `:class` depends on a yaml file to be placed in `config/` to function. Executing the command
110
117
  `rails g sudojs:class -h` would reveal this:
111
118
 
112
- Description:
113
- Create a skeletal sudo.js Class and place the files correctly for a given controller#name.
114
-
115
- Example:
116
- `rails g sudojs:class View foo#baz`
117
-
118
- Where `View` is a Class Object that this new Object will inherit from. Sudo.js itself
119
- will recognize 5 types:
120
-
121
- 1. 'Base'
122
- 2. 'Container'
123
- 3. 'View'
124
- 4. 'ViewController'
125
- 5. 'Dataview'
126
-
127
- The inheritance for any of these will be set as `_.<Name>` as we assume sudo has taken the
128
- global `_` char as an alias. If the argument is any other string we assume you are inheriting
129
- from a custom class and pass it through as is.
130
-
131
-
132
- Where `foo#baz` *can* match a controller#action (like `home#show`) it does not
133
- have to. The controller argument (`foo`) allows the generator to place the file correctly and
134
- create/modify the correct manifest file. The name argument will be the proper name of the
135
- Class Object itself
136
-
137
- If a corresponding views/controller/name.html.* is found, a skeletal instantiation of the newly
138
- created View will be placed there
139
-
140
- This will create:
141
- app/
142
- assests/
143
- javascripts/
144
- manifests/
145
- foo.js
146
- views/
147
- foo/
148
- baz.js
149
-
150
- And modify (if found):
151
- app/
152
- views/
153
- foo/
154
- baz.html.*
155
-
156
- Note:
157
- If the `route` is application level (application#baz) the file will be placed
158
- in the application level directory and the manifests/application file will be modified
119
+ Description:
120
+ Create a skeletal sudo.js Class and place the files correctly for a given controller#name.
121
+
122
+ Example:
123
+ `rails g sudojs:class View foo#baz`
124
+
125
+ Where `View` is a Class Object that this new Object will inherit from. Sudo.js itself
126
+ will recognize 6 types:
127
+
128
+ 1. 'Base'
129
+ 2. 'Model'
130
+ 3. 'Container'
131
+ 4. 'View'
132
+ 5. 'ViewController'
133
+ 6. 'Dataview'
134
+
135
+ The inheritance for any of these will be set as `_.<Name>` as we assume sudo has taken the
136
+ global `_` char as an alias. If the argument is any other string we assume you are inheriting
137
+ from a custom class and pass it through as is.
138
+
139
+ Where `foo#baz` *can* match a controller#action (like `home#show`) it does not
140
+ have to. The controller argument (`foo`) allows the generator to place the file correctly and
141
+ create/modify the correct manifest file. The name argument will be the proper name of the
142
+ Class Object itself.
143
+
144
+ If a corresponding views/controller/name.html* is found, a skeletal instantiation of the newly
145
+ created View will be placed there.
146
+
147
+ This will create (or modify):
148
+ app/
149
+ assests/
150
+ stylesheets/
151
+ manifests/
152
+ foo.css
153
+ views/
154
+ foo/
155
+ baz.css(*)
156
+ javascripts/
157
+ manifests/
158
+ foo.js
159
+ views/
160
+ foo/
161
+ baz.js(*)
162
+
163
+ And modify (if found):
164
+ app/
165
+ views/
166
+ foo/
167
+ baz.html(*)
168
+
169
+ Note:
170
+ If the `route` is application level (application#baz) the file will be placed
171
+ in the application level directory and the manifests/application file will be modified
172
+
173
+ Note:
174
+ If the `route` is application level (application#baz) the file will be placed
175
+ in the application level directory and the manifests/application file will be modified
159
176
 
160
177
  ## Contributing
161
178
 
@@ -1,3 +1,3 @@
1
1
  module Sudojs
2
- VERSION = '0.2.9'
2
+ VERSION = '0.3.0'
3
3
  end
@@ -6,11 +6,11 @@ var sudo = {
6
6
  //
7
7
  // `namespace`
8
8
  delegates: {},
9
- // The sudo.ext namespace holds the `extensions` which are stand alone `modules` that
10
- // can be `implemented` (mixed-in) to sudo Class Objects
9
+ // The sudo.extensions namespace holds the objects that are stand alone `modules` which
10
+ // can be `implemented` (mixed-in) in sudo Class Objects
11
11
  //
12
12
  // `namespace`
13
- ext: {},
13
+ extensions: {},
14
14
  // ###getPath
15
15
  // Extract a value located at `path` relative to the passed in object
16
16
  //
@@ -433,9 +433,9 @@ sudo.Container.prototype.removeFromParent = function removeFromParent() {
433
433
  sudo.Container.prototype.role = 'container';
434
434
  // ###send
435
435
  // The call to the specific method on a (un)specified target happens here.
436
- // If this Object is part of a `sudo.ext.container` maintained hierarchy
436
+ // If this Object is part of a `sudo.Container` maintained hierarchy
437
437
  // the 'target' may be left out, causing the `bubble()` method to be called.
438
- // What this does is allow children of a `sudo.ext.container` to simply pass
438
+ // What this does is allow children of a `sudo.Container` to simply pass
439
439
  // events upward, delegating the responsibility of deciding what to do to the parent.
440
440
  //
441
441
  // `param` {*} Any number of arguments is supported, but the first is the only one searched for info.
@@ -820,7 +820,7 @@ sudo.template = function template(str, data, scope) {
820
820
  // that the child will use to place itself in it's `renderTarget`.
821
821
  // 4. Has a `render` method that when called re-hydrates it's $el by passing its
822
822
  // internal data store to its template
823
- // 5. Handles event binding/unbinding by implementing the sudo.ext.listener
823
+ // 5. Handles event binding/unbinding by implementing the sudo.extensions.listener
824
824
  // extension object
825
825
  //
826
826
  //`constructor`
@@ -828,9 +828,9 @@ sudo.Dataview = function(el, data) {
828
828
  var d = data || {}, t;
829
829
  sudo.View.call(this, el, d);
830
830
  // implements the listener extension
831
- $.extend(this, sudo.ext.listener);
831
+ $.extend(this, sudo.extensions.listener);
832
832
  // dataview's models are observable
833
- $.extend(this.model, sudo.ext.observable);
833
+ $.extend(this.model, sudo.extensions.observable);
834
834
  // dont autoRender on the setting of events,
835
835
  // add to this to prevent others if needed
836
836
  this.autoRenderBlacklist = {event: true, events: true};
@@ -888,11 +888,182 @@ sudo.Dataview.prototype.render = function render(change) {
888
888
  };
889
889
  // `private`
890
890
  sudo.Dataview.prototype.role = 'dataview';
891
+ // ##Navigator Class Object
892
+
893
+ // Abstracts location and history events, parsing their information into a
894
+ // normalized object that is then set to an Observable class instance
895
+ //
896
+ // `constructor`
897
+ sudo.Navigator = function(data) {
898
+ this.started = false;
899
+ this.slashStripper = /^\/+|\/+$/g;
900
+ this.leadingStripper = /^[#\/]|\s+$/g;
901
+ this.trailingStripper = /\/$/;
902
+ this.construct(data);
903
+ };
904
+ // Navigator inherits from `sudo.Model`
905
+ sudo.Navigator.prototype = Object.create(sudo.Model.prototype);
906
+ // ###getFragment
907
+ // 'Fragment' is defined as any URL information after the 'root' path
908
+ // including the `search` or `hash`
909
+ //
910
+ // `returns` {String} `fragment`
911
+ // `returns` {String} the normalized current fragment
912
+ sudo.Navigator.prototype.getFragment = function getFragment(fragment) {
913
+ var root = this.data.root;
914
+ if(!fragment) {
915
+ // intentional use of coersion
916
+ if (this.isPushState) {
917
+ fragment = window.location.pathname;
918
+ root = root.replace(this.trailingStripper, '');
919
+ if(!fragment.indexOf(root)) fragment = fragment.substr(root.length);
920
+ } else {
921
+ fragment = this.getHash();
922
+ }
923
+ }
924
+ return decodeURIComponent(fragment.replace(this.leadingStripper, ''));
925
+ };
926
+ // `returns` {String} the normalized current `hash`
927
+ sudo.Navigator.prototype.getHash = function getHash(fragment) {
928
+ fragment || (fragment = window.location.href);
929
+ var match = fragment.match(/#(.*)$/);
930
+ return match ? match[1] : '';
931
+ };
932
+ // `returns` {String} the normalized current `search`
933
+ sudo.Navigator.prototype.getSearch = function getSearch(fragment) {
934
+ fragment || (fragment = window.location.href);
935
+ var match = fragment.match(/\?(.*)$/);
936
+ return match ? match[1] : '';
937
+ };
938
+ // ###getUrl
939
+ // fetch the URL in the form <root + fragment>
940
+ //
941
+ // `returns` {String}
942
+ sudo.Navigator.prototype.getUrl = function getUrl() {
943
+ // note that delegate(_role_) returns the deleagte
944
+ return this.data.root + this.data.fragment;
945
+ };
946
+ // ###go
947
+ // If the passed in 'fragment' is different than the currently stored one,
948
+ // push a new state entry / hash event and set the data where specified
949
+ //
950
+ // `param` {string} `fragment`
951
+ // `returns` {*} call to `setData`
952
+ sudo.Navigator.prototype.go = function go(fragment) {
953
+ if(!this.started) return false;
954
+ if(!this.urlChanged(fragment)) return;
955
+ // TODO ever use replaceState?
956
+ if(this.isPushState) {
957
+ window.history.pushState({}, document.title, this.getUrl());
958
+ } else if(this.isHashChange) {
959
+ window.location.hash = '#' + this.data.fragment;
960
+ }
961
+ return this.setData();
962
+ };
963
+ // ###handleChange
964
+ // Bound to either the `popstate` or `hashchange` events, if the
965
+ // URL has indeed changed then parse the relevant data and set it -
966
+ // triggering change observers
967
+ //
968
+ // `returns` {*} call to `setData` or undefined
969
+ sudo.Navigator.prototype.handleChange = function handleChange(e) {
970
+ if(this.urlChanged()) {
971
+ return this.setData();
972
+ }
973
+ };
974
+ // ###parseQuery
975
+ // Parse and return a hash of the key value pairs contained in
976
+ // the current `query`
977
+ //
978
+ // `returns` {object}
979
+ sudo.Navigator.prototype.parseQuery = function parseQuery() {
980
+ var obj = {}, seg = this.data.query,
981
+ i, s;
982
+ if(seg) {
983
+ seg = seg.split('&');
984
+ for(i = 0; i < seg.length; i++) {
985
+ if(!seg[i]) continue;
986
+ s = seg[i].split('=');
987
+ obj[s[0]] = s[1];
988
+ }
989
+ return obj;
990
+ }
991
+ };
992
+ // ###setData
993
+ // Using the current `fragment` (minus any search or hash data) as a key,
994
+ // use `parseQuery` as the value for the key, setting it into the specified
995
+ // model (a stated `Observable` or `this.data`)
996
+ //
997
+ // `returns` {object} `this`
998
+ sudo.Navigator.prototype.setData = function setData() {
999
+ var frag = this.data.fragment,
1000
+ // data is set in a specified model or in self
1001
+ observable = this.data.observable || this;
1002
+ if(this.data.query) {
1003
+ // we want to set the key minus any search/hash
1004
+ frag = frag.indexOf('?') !== -1 ? frag.split('?')[0] : frag.split('#')[0];
1005
+ }
1006
+ observable.set(frag, this.parseQuery());
1007
+ return this;
1008
+ };
1009
+ // ###start
1010
+ // Gather the necessary information about the current environment and
1011
+ // bind to either (push|pop)state or hashchange
1012
+ //
1013
+ // `returns` {object} `this`
1014
+ sudo.Navigator.prototype.start = function start() {
1015
+ var hasPushState, atRoot, loc, tmp;
1016
+ if(this.started) return;
1017
+ hasPushState = window.history && window.history.pushState;
1018
+ this.started = true;
1019
+ // setup the initial configuration
1020
+ this.isHashChange = this.data.useHashChange && 'onhashchange' in window ||
1021
+ (!hasPushState && 'onhashchange' in window);
1022
+ this.isPushState = !this.isHashChange && !!hasPushState;
1023
+ // normalize the root to always contain a leading and trailing slash
1024
+ this.data['root'] = ('/' + this.data['root'] + '/').replace(this.slashStripper, '/');
1025
+ // Get a snapshot of the current fragment
1026
+ this.urlChanged();
1027
+ // monitor URL changes via popState or hashchange
1028
+ if (this.isPushState) {
1029
+ $(window).on('popstate', this.handleChange.bind(this));
1030
+ } else if (this.isHashChange) {
1031
+ $(window).on('hashchange', this.handleChange.bind(this));
1032
+ } else return;
1033
+ // Does the current URL need to changed? (hashchange vs popstate)
1034
+ atRoot = window.location.pathname.replace(/[^\/]$/, '$&/') === this.data['root'];
1035
+ // somehow a pushstate URL got here (and here is hashchange)
1036
+ if(this.isHashChange && !atRoot) {
1037
+ window.location.replace(this.data['root'] + window.location.search + '#' +
1038
+ this.data.fragment);
1039
+ // return early as browser will redirect
1040
+ return true;
1041
+ // the converse of the above
1042
+ } else if(this.isPushState && atRoot && window.location.hash) {
1043
+ tmp = this.getHash().replace(this.leadingStripper, '');
1044
+ window.history.replaceState({}, document.title, this.data['root'] +
1045
+ tmp + window.location.search);
1046
+ }
1047
+ // TODO provide option to `go` from inital `start` state?
1048
+ return this;
1049
+ };
1050
+ // Is a passed in fragment different from the currently set one?
1051
+ //
1052
+ // `param` {String} `fragment`
1053
+ sudo.Navigator.prototype.urlChanged = function urlChanged(fragment) {
1054
+ var current = this.getFragment(fragment);
1055
+ // nothing has changed
1056
+ if (current === this.data.fragment) return false;
1057
+ this.data.fragment = current;
1058
+ this.data.query = this.getSearch(current) || this.getHash(current);
1059
+ return true;
1060
+ };
891
1061
  // ## Observable Extension Object
1062
+ //
892
1063
  // Implementaion of the ES6 Harmony Observer pattern.
893
1064
  // Extend a `sudo.Model` class with this object if
894
1065
  // data-mutation-observation is required
895
- sudo.ext.observable = {
1066
+ sudo.extensions.observable = {
896
1067
  // ###_deliver_
897
1068
  // Called from deliverChangeRecords when ready to send
898
1069
  // changeRecords to observers.
@@ -1120,7 +1291,7 @@ sudo.ext.observable = {
1120
1291
  // in a changeRecord recieved via observe().
1121
1292
  //
1122
1293
  // `namespace`
1123
- sudo.ext.bindable = {
1294
+ sudo.extensions.bindable = {
1124
1295
  // List of attributes - $.attr() to be used.
1125
1296
  //
1126
1297
  // `private`
@@ -1233,14 +1404,6 @@ sudo.ext.bindable = {
1233
1404
  readOnly: true,
1234
1405
  selected: true
1235
1406
  },
1236
- // ###setBinding
1237
- // Use case when you know there is only a single binding,
1238
- // create the expected methods and return
1239
- //
1240
- // `returns` {Object} `this`
1241
- setBinding: function setBinding() {
1242
- return this._setBinding_(this.model.data.binding);
1243
- },
1244
1407
  // ###_setBinding_
1245
1408
  // Given a single explicit binding, create it. Called from
1246
1409
  // _setbindings_ as a convenience for normalizing the
@@ -1299,7 +1462,7 @@ sudo.ext.bindable = {
1299
1462
  // C. data: A hash that will be passed as the custom jQuery Event.data object
1300
1463
  // D. fn -> If a {String} bound to the named function on this object, if a
1301
1464
  // function assumed to be anonymous and called with no scope manipulation
1302
- sudo.ext.listener = {
1465
+ sudo.extensions.listener = {
1303
1466
  // ###bindEvents
1304
1467
  // Bind the events in the data store to this object's $el
1305
1468
  //
@@ -1343,6 +1506,137 @@ sudo.ext.listener = {
1343
1506
  return this;
1344
1507
  }
1345
1508
  };
1509
+ // ##sudo persistable extension
1510
+ //
1511
+ // A mixin providing restful CRUD operations for a sudo.Model instance.
1512
+ //
1513
+ // create : POST
1514
+ // read : GET
1515
+ // update : PUT or PATCH (configurable)
1516
+ // destroy : DELETE
1517
+ //
1518
+ // Before use be sure to set an `ajax` property {object} with at least
1519
+ // a `baseUrl: ...` key. The model's id (if present -- indicating a persisted model)
1520
+ // is appended to the baseUrl (baseUrl/id) by default. You can override this behavior
1521
+ // by simply setting a `url: ...` in the `ajax` options hash or pass in the same when
1522
+ // calling any of the methods (or override the model.url() method).
1523
+ //
1524
+ // Place any other default options in the `ajax` hash
1525
+ // that you would want sent to a $.ajax({...}) call. Again, you can also override those
1526
+ // defaults by passing in a hash of options to any method:
1527
+ // `this.model.update({patch: true})` etc...
1528
+ sudo.extensions.persistable = {
1529
+ // ###create
1530
+ //
1531
+ // Save this model on the server. If a subset of this model's attributes
1532
+ // has not been stated (ajax:{data:{...}}) send all of the model's data.
1533
+ // Anticipate that the server response will send back the
1534
+ // state of the model on the server and set it here (via a success callback).
1535
+ //
1536
+ // `param` {object} `params` Hash of options for the XHR call
1537
+ // `returns` {object} The jQuery XHR object
1538
+ create: function create(params) {
1539
+ return this._sendData_('POST', params);
1540
+ },
1541
+ // ###destroy
1542
+ //
1543
+ // (because `delete` is reserved)
1544
+ //
1545
+ // `param` {object} `params` Optional hash of options for the XHR
1546
+ // `returns` {object} jqXhr
1547
+ destroy: function _delete(params) {
1548
+ return this._sendData_('DELETE', params);
1549
+ },
1550
+ // ###_normalizeParams_
1551
+ // Abstracted logic for preparing the options object. This looks at
1552
+ // the set `ajax` property, allowing any passed in params to override.
1553
+ //
1554
+ // Sets defaults: JSON dataType and a success callback that simply `sets()` the
1555
+ // data returned from the server
1556
+ //
1557
+ // `returns` {object} A normalized params object for the XHR call
1558
+ _normalizeParams_: function _normalizeParams_(meth, opts, params) {
1559
+ opts || (opts = this.data.ajax);
1560
+ opts.url || (opts.url = this.url(opts.baseUrl));
1561
+ opts.type || (opts.type = meth);
1562
+ opts.dataType || (opts.dataType = 'json');
1563
+ // the default success callback is to set the data returned from the server
1564
+ // or just the status as `ajaxStatus` if no data was returned
1565
+ opts.success || (opts.success = function(data, status, jqXhr) {
1566
+ data ? this.sets(data) : this.set('ajaxStatus', status);
1567
+ }.bind(this));
1568
+ // allow the passed in params to override any set in this model's `ajax` options
1569
+ return params ? $.extend(opts, params) : opts;
1570
+ },
1571
+ // ###read
1572
+ //
1573
+ // Fetch this models state from the server and set it here. The
1574
+ // `Model.sets()` method is used with the returned data (we are
1575
+ // asssuming the default json dataType). Pass in (via the params arg)
1576
+ // a success function to override this default.
1577
+ //
1578
+ // Maps to the http GET method.
1579
+ //
1580
+ // `param` {object} `params`. Optional info for the XHR call. If
1581
+ // present will override any set in this model's `ajax` options object.
1582
+ // `returns` {object} The jQuery XHR object
1583
+ read: function post(params) {
1584
+ return $.ajax(this._normalizeParams_('GET', null, params));
1585
+ },
1586
+ // ###save
1587
+ //
1588
+ // Convenience method removing the need to know if a model is new (not yet persisted)
1589
+ // or has been loaded/refreshed from the server.
1590
+ //
1591
+ // `param` {object} `params` Hash of options for the XHR call
1592
+ // `returns` {object} The jQuery XHR object
1593
+ save: function save(params) {
1594
+ return ('id' in this.data) ? this.update(params) : this.create(params);
1595
+ },
1596
+ // ###_sendData_
1597
+ // The Create, Update and Patch methods all send data to the server,
1598
+ // varying only in their HTTP method. Abstracted logic is here.
1599
+ //
1600
+ // `returns` {object} jqXhr
1601
+ _sendData_: function _sendData_(meth, params) {
1602
+ opts = this.data.ajax;
1603
+ opts.contentType || (opts.contentType = 'application/json');
1604
+ opts.data || (opts.data = this.data);
1605
+ // non GET requests do not 'processData'
1606
+ if(!('processData' in opts)) opts.processData = false;
1607
+ return $.ajax(this._normalizeParams_(meth, opts, params));
1608
+ },
1609
+ // ###update
1610
+ //
1611
+ // If this model has been persisted to/from the server (it has an `id` attribute)
1612
+ // send the specified data (or all the model's data) to the server at `url` via
1613
+ // the `PUT` http verb or `PATCH` if {patch: true} is in the ajax options (or the
1614
+ // passed in params)
1615
+ //
1616
+ // NOTE: update does not check is this is a new model or not, do that yourself
1617
+ // or use the `save()` method (that does check).
1618
+ //
1619
+ // `param` {object} `params` Optional hash of options for the XHR
1620
+ // `returns` {object|bool} the jqXhr if called false if not
1621
+ update: function update(params) {
1622
+ return this._sendData_((this.data.ajax.patch || params && params.patch) ?
1623
+ 'PATCH' : 'PUT', params);
1624
+ },
1625
+ // ###url
1626
+ //
1627
+ // Takes the base url and appends this models id if present
1628
+ // (narmalizing the trailong slash if needed).
1629
+ // Override if you need to change the format of the calculated url.
1630
+ //
1631
+ // `param` {string} `base` the baseUrl set in this models ajax options
1632
+ url: function url(base) {
1633
+ // could possibly be 0...
1634
+ if('id' in this.data) {
1635
+ return base + (base.charAt(base.length - 1) === '/' ?
1636
+ '' : '/') + encodeURIComponent(this.data.id);
1637
+ } else return base;
1638
+ }
1639
+ };
1346
1640
  //##Change Delegate
1347
1641
 
1348
1642
  // Delegates, if present, can override or extend the behavior
@@ -1373,7 +1667,7 @@ sudo.delegates.Change.prototype.filter = function(change) {
1373
1667
  // assemble the object to return to the method
1374
1668
  obj.type = change.type;
1375
1669
  obj.value = name.indexOf('.') === -1 ? change.object[change.name] :
1376
- this.getPath(name, change.object);
1670
+ sudo.getPath(name, change.object);
1377
1671
  obj.oldValue = change.oldValue;
1378
1672
  return this.delegator[filters[name]].call(this.delegator, obj);
1379
1673
  }
@@ -1401,28 +1695,28 @@ sudo.delegates.Data.prototype = Object.create(sudo.Model.prototype);
1401
1695
  //
1402
1696
  // `param` {Object} `obj`
1403
1697
  sudo.delegates.Data.prototype.filter = function(obj) {
1404
- var filters = this.data.filters, key, o, k;
1405
- for(key in filters) {
1406
- if(filters.hasOwnProperty(key)) {
1407
- // keys and paths need different handling
1408
- if(key.indexOf('.') === -1) {
1409
- if(key in obj) this.delegator[filters[key]].call(
1410
- this.delegator, obj[key]);
1411
- } else {
1412
- // the chars after the last refinement are the key we need to check for
1413
- k = key.slice(key.lastIndexOf('.') + 1);
1414
- // and the ones prior are the object
1415
- o = sudo.getPath(key.slice(0, key.lastIndexOf('.')), obj);
1416
- if(o && k in o) this.delegator[filters[key]].call(
1417
- this.delegator, o[k]);
1418
- }
1698
+ var filters = this.data.filters,
1699
+ ary = Object.keys(filters), key, i, o, k;
1700
+ for(i = 0; i < ary.length; i++) {
1701
+ key = ary[i];
1702
+ // keys and paths need different handling
1703
+ if(key.indexOf('.') === -1) {
1704
+ if(key in obj) this.delegator[filters[key]].call(
1705
+ this.delegator, obj[key]);
1706
+ } else {
1707
+ // the chars after the last refinement are the key we need to check for
1708
+ k = key.slice(key.lastIndexOf('.') + 1);
1709
+ // and the ones prior are the object
1710
+ o = sudo.getPath(key.slice(0, key.lastIndexOf('.')), obj);
1711
+ if(o && k in o) this.delegator[filters[key]].call(
1712
+ this.delegator, o[k]);
1419
1713
  }
1420
1714
  }
1421
1715
  };
1422
1716
  // `private`
1423
1717
  sudo.delegates.Data.prototype.role = 'data';
1424
1718
 
1425
- sudo.version = "0.9.1";
1719
+ sudo.version = "0.9.2";
1426
1720
  window.sudo = sudo;
1427
1721
  if(typeof window._ === "undefined") window._ = sudo;
1428
1722
  }).call(this, this);
@@ -6,11 +6,11 @@ var sudo = {
6
6
  //
7
7
  // `namespace`
8
8
  delegates: {},
9
- // The sudo.ext namespace holds the `extensions` which are stand alone `modules` that
10
- // can be `implemented` (mixed-in) to sudo Class Objects
9
+ // The sudo.extensions namespace holds the objects that are stand alone `modules` which
10
+ // can be `implemented` (mixed-in) in sudo Class Objects
11
11
  //
12
12
  // `namespace`
13
- ext: {},
13
+ extensions: {},
14
14
  // ###getPath
15
15
  // Extract a value located at `path` relative to the passed in object
16
16
  //
@@ -433,9 +433,9 @@ sudo.Container.prototype.removeFromParent = function removeFromParent() {
433
433
  sudo.Container.prototype.role = 'container';
434
434
  // ###send
435
435
  // The call to the specific method on a (un)specified target happens here.
436
- // If this Object is part of a `sudo.ext.container` maintained hierarchy
436
+ // If this Object is part of a `sudo.Container` maintained hierarchy
437
437
  // the 'target' may be left out, causing the `bubble()` method to be called.
438
- // What this does is allow children of a `sudo.ext.container` to simply pass
438
+ // What this does is allow children of a `sudo.Container` to simply pass
439
439
  // events upward, delegating the responsibility of deciding what to do to the parent.
440
440
  //
441
441
  // `param` {*} Any number of arguments is supported, but the first is the only one searched for info.
@@ -584,10 +584,11 @@ sudo.View.prototype.$ = function(sel) {
584
584
  return this.$el.find(sel);
585
585
  };
586
586
  // ## Observable Extension Object
587
+ //
587
588
  // Implementaion of the ES6 Harmony Observer pattern.
588
589
  // Extend a `sudo.Model` class with this object if
589
590
  // data-mutation-observation is required
590
- sudo.ext.observable = {
591
+ sudo.extensions.observable = {
591
592
  // ###_deliver_
592
593
  // Called from deliverChangeRecords when ready to send
593
594
  // changeRecords to observers.
@@ -808,7 +809,7 @@ sudo.ext.observable = {
808
809
  return this.deliverChangeRecords();
809
810
  }
810
811
  };
811
- sudo.version = "0.9.1";
812
+ sudo.version = "0.9.2";
812
813
  window.sudo = sudo;
813
814
  if(typeof window._ === "undefined") window._ = sudo;
814
815
  }).call(this, this);
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sudojs-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.9
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-14 00:00:00.000000000 Z
12
+ date: 2013-02-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -78,7 +78,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
78
78
  version: '0'
79
79
  segments:
80
80
  - 0
81
- hash: 1612881101538659310
81
+ hash: -2092482263484943963
82
82
  required_rubygems_version: !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
@@ -87,7 +87,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
87
87
  version: '0'
88
88
  segments:
89
89
  - 0
90
- hash: 1612881101538659310
90
+ hash: -2092482263484943963
91
91
  requirements: []
92
92
  rubyforge_project:
93
93
  rubygems_version: 1.8.24