sudojs-rails 0.2.9 → 0.3.0

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.
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