ultimate-base 0.3.0 → 0.3.1

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/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ultimate-base (0.3.0)
4
+ ultimate-base (0.3.1)
5
5
 
6
6
  GEM
7
7
  remote: http://rubygems.org/
@@ -2,10 +2,9 @@
2
2
  # jquery ~> 1.7.0
3
3
  # underscore ~> 1.3.0
4
4
 
5
+ #= require ultimate/base
5
6
  #= require ultimate/helpers
6
7
 
7
- @Ultimate ||= {}
8
-
9
8
  @Ultimate.Backbone =
10
9
 
11
10
  debugMode: false
@@ -1,5 +1,7 @@
1
1
  #= require ./base
2
2
 
3
+ # TODO ready options
4
+
3
5
  class Ultimate.Backbone.Collection extends Backbone.Collection
4
6
 
5
7
  constructor: ->
@@ -18,8 +18,11 @@
18
18
  // restored later on, if `noConflict` is used.
19
19
  var previousBackbone = root.Backbone;
20
20
 
21
- // Create a local reference to splice.
22
- var splice = Array.prototype.splice;
21
+ // Create a local reference to array methods.
22
+ var ArrayProto = Array.prototype;
23
+ var push = ArrayProto.push;
24
+ var slice = ArrayProto.slice;
25
+ var splice = ArrayProto.splice;
23
26
 
24
27
  // The top-level namespace. All public Backbone classes and modules will
25
28
  // be attached to this. Exported for both CommonJS and the browser.
@@ -183,7 +186,7 @@
183
186
  attributes || (attributes = {});
184
187
  if (options && options.collection) this.collection = options.collection;
185
188
  if (options && options.parse) attributes = this.parse(attributes);
186
- if (defaults = getValue(this, 'defaults')) {
189
+ if (defaults = _.result(this, 'defaults')) {
187
190
  attributes = _.extend({}, defaults, attributes);
188
191
  }
189
192
  this.attributes = {};
@@ -336,9 +339,7 @@
336
339
  options.success = function(resp, status, xhr) {
337
340
  if (!model.set(model.parse(resp, xhr), options)) return false;
338
341
  if (success) success(model, resp, options);
339
- model.trigger('sync', model, resp, options);
340
342
  };
341
- options.error = Backbone.wrapError(options.error, model, options);
342
343
  return this.sync('read', this, options);
343
344
  },
344
345
 
@@ -383,11 +384,9 @@
383
384
  if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
384
385
  if (!model.set(serverAttrs, options)) return false;
385
386
  if (success) success(model, resp, options);
386
- model.trigger('sync', model, resp, options);
387
387
  };
388
388
 
389
389
  // Finish configuring and sending the Ajax request.
390
- options.error = Backbone.wrapError(options.error, model, options);
391
390
  var xhr = this.sync(this.isNew() ? 'create' : 'update', this, options);
392
391
 
393
392
  // When using `wait`, reset attributes to original values unless
@@ -415,7 +414,6 @@
415
414
  options.success = function(resp) {
416
415
  if (options.wait || model.isNew()) destroy();
417
416
  if (success) success(model, resp, options);
418
- if (!model.isNew()) model.trigger('sync', model, resp, options);
419
417
  };
420
418
 
421
419
  if (this.isNew()) {
@@ -423,7 +421,6 @@
423
421
  return false;
424
422
  }
425
423
 
426
- options.error = Backbone.wrapError(options.error, model, options);
427
424
  var xhr = this.sync('delete', this, options);
428
425
  if (!options.wait) destroy();
429
426
  return xhr;
@@ -433,7 +430,7 @@
433
430
  // using Backbone's restful methods, override this to change the endpoint
434
431
  // that will be called.
435
432
  url: function() {
436
- var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
433
+ var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError();
437
434
  if (this.isNew()) return base;
438
435
  return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + encodeURIComponent(this.id);
439
436
  },
@@ -527,8 +524,8 @@
527
524
 
528
525
  // Check if the model is currently in a valid state. It's only possible to
529
526
  // get into an *invalid* state if you're using silent changes.
530
- isValid: function() {
531
- return !this.validate || !this.validate(this.attributes);
527
+ isValid: function(options) {
528
+ return !this.validate || !this.validate(this.attributes, options);
532
529
  },
533
530
 
534
531
  // Run validation against the next complete set of model attributes,
@@ -539,11 +536,8 @@
539
536
  attrs = _.extend({}, this.attributes, attrs);
540
537
  var error = this.validate(attrs, options);
541
538
  if (!error) return true;
542
- if (options && options.error) {
543
- options.error(this, error, options);
544
- } else {
545
- this.trigger('error', this, error, options);
546
- }
539
+ if (options && options.error) options.error(this, error, options);
540
+ this.trigger('error', this, error, options);
547
541
  return false;
548
542
  }
549
543
 
@@ -561,7 +555,10 @@
561
555
  if (options.comparator !== void 0) this.comparator = options.comparator;
562
556
  this._reset();
563
557
  this.initialize.apply(this, arguments);
564
- if (models) this.reset(models, {silent: true, parse: options.parse});
558
+ if (models) {
559
+ if (options.parse) models = this.parse(models);
560
+ this.reset(models, {silent: true, parse: options.parse});
561
+ }
565
562
  };
566
563
 
567
564
  // Define the Collection's inheritable methods.
@@ -589,59 +586,51 @@
589
586
  // Add a model, or list of models to the set. Pass **silent** to avoid
590
587
  // firing the `add` event for every new model.
591
588
  add: function(models, options) {
592
- var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];
593
- options || (options = {});
589
+ var i, args, length, model, existing;
590
+ var at = options && options.at;
594
591
  models = _.isArray(models) ? models.slice() : [models];
595
592
 
596
593
  // Begin by turning bare objects into model references, and preventing
597
- // invalid models or duplicate models from being added.
594
+ // invalid models from being added.
598
595
  for (i = 0, length = models.length; i < length; i++) {
599
- if (!(model = models[i] = this._prepareModel(models[i], options))) {
600
- throw new Error("Can't add an invalid model to a collection");
601
- }
602
- cid = model.cid;
603
- id = model.id;
604
- if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {
605
- dups.push(i);
606
- continue;
607
- }
608
- cids[cid] = ids[id] = model;
596
+ if (models[i] = this._prepareModel(models[i], options)) continue;
597
+ throw new Error("Can't add an invalid model to a collection");
609
598
  }
610
599
 
611
- // Remove duplicates.
612
- i = dups.length;
613
- while (i--) {
614
- dups[i] = models.splice(dups[i], 1)[0];
615
- }
600
+ for (i = models.length - 1; i >= 0; i--) {
601
+ model = models[i];
602
+ existing = model.id != null && this._byId[model.id];
616
603
 
617
- // Listen to added models' events, and index models for lookup by
618
- // `id` and by `cid`.
619
- for (i = 0, length = models.length; i < length; i++) {
620
- (model = models[i]).on('all', this._onModelEvent, this);
604
+ // If a duplicate is found, splice it out and optionally merge it into
605
+ // the existing model.
606
+ if (existing || this._byCid[model.cid]) {
607
+ if (options && options.merge && existing) {
608
+ existing.set(model, options);
609
+ }
610
+ models.splice(i, 1);
611
+ continue;
612
+ }
613
+
614
+ // Listen to added models' events, and index models for lookup by
615
+ // `id` and by `cid`.
616
+ model.on('all', this._onModelEvent, this);
621
617
  this._byCid[model.cid] = model;
622
618
  if (model.id != null) this._byId[model.id] = model;
623
619
  }
624
620
 
625
- // Insert models into the collection, re-sorting if needed, and triggering
626
- // `add` events unless silenced.
627
- this.length += length;
628
- index = options.at != null ? options.at : this.models.length;
629
- splice.apply(this.models, [index, 0].concat(models));
630
-
631
- // Merge in duplicate models.
632
- if (options.merge) {
633
- for (i = 0, length = dups.length; i < length; i++) {
634
- if (model = this._byId[dups[i].id]) model.set(dups[i], options);
635
- }
636
- }
621
+ // Update `length` and splice in new models.
622
+ this.length += models.length;
623
+ args = [at != null ? at : this.models.length, 0];
624
+ push.apply(args, models);
625
+ splice.apply(this.models, args);
637
626
 
638
627
  // Sort the collection if appropriate.
639
- if (this.comparator && options.at == null) this.sort({silent: true});
628
+ if (this.comparator && at == null) this.sort({silent: true});
640
629
 
641
- if (options.silent) return this;
642
- for (i = 0, length = this.models.length; i < length; i++) {
643
- if (!cids[(model = this.models[i]).cid]) continue;
644
- options.index = i;
630
+ if (options && options.silent) return this;
631
+
632
+ // Trigger `add` events.
633
+ while (model = models.shift()) {
645
634
  model.trigger('add', model, this, options);
646
635
  }
647
636
 
@@ -735,35 +724,35 @@
735
724
  // normal circumstances, as the set will maintain sort order as each item
736
725
  // is added.
737
726
  sort: function(options) {
738
- options || (options = {});
739
- if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
740
- var boundComparator = _.bind(this.comparator, this);
741
- if (this.comparator.length === 1) {
742
- this.models = this.sortBy(boundComparator);
727
+ if (!this.comparator) {
728
+ throw new Error('Cannot sort a set without a comparator');
729
+ }
730
+
731
+ if (_.isString(this.comparator) || this.comparator.length === 1) {
732
+ this.models = this.sortBy(this.comparator, this);
743
733
  } else {
744
- this.models.sort(boundComparator);
734
+ this.models.sort(_.bind(this.comparator, this));
745
735
  }
746
- if (!options.silent) this.trigger('reset', this, options);
736
+
737
+ if (!options || !options.silent) this.trigger('reset', this, options);
747
738
  return this;
748
739
  },
749
740
 
750
741
  // Pluck an attribute from each model in the collection.
751
742
  pluck: function(attr) {
752
- return _.map(this.models, function(model){ return model.get(attr); });
743
+ return _.invoke(this.models, 'get', attr);
753
744
  },
754
745
 
755
746
  // When you have more items than you want to add or remove individually,
756
747
  // you can reset the entire set with a new list of models, without firing
757
748
  // any `add` or `remove` events. Fires `reset` when finished.
758
749
  reset: function(models, options) {
759
- models || (models = []);
760
- options || (options = {});
761
750
  for (var i = 0, l = this.models.length; i < l; i++) {
762
751
  this._removeReference(this.models[i]);
763
752
  }
764
753
  this._reset();
765
- this.add(models, _.extend({silent: true}, options));
766
- if (!options.silent) this.trigger('reset', this, options);
754
+ if (models) this.add(models, _.extend({silent: true}, options));
755
+ if (!options || !options.silent) this.trigger('reset', this, options);
767
756
  return this;
768
757
  },
769
758
 
@@ -778,9 +767,7 @@
778
767
  options.success = function(resp, status, xhr) {
779
768
  collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
780
769
  if (success) success(collection, resp, options);
781
- collection.trigger('sync', collection, resp, options);
782
770
  };
783
- options.error = Backbone.wrapError(options.error, collection, options);
784
771
  return this.sync('read', this, options);
785
772
  },
786
773
 
@@ -788,14 +775,14 @@
788
775
  // collection immediately, unless `wait: true` is passed, in which case we
789
776
  // wait for the server to agree.
790
777
  create: function(model, options) {
791
- var coll = this;
778
+ var collection = this;
792
779
  options = options ? _.clone(options) : {};
793
780
  model = this._prepareModel(model, options);
794
781
  if (!model) return false;
795
- if (!options.wait) coll.add(model, options);
782
+ if (!options.wait) collection.add(model, options);
796
783
  var success = options.success;
797
784
  options.success = function(model, resp, options) {
798
- if (options.wait) coll.add(model, options);
785
+ if (options.wait) collection.add(model, options);
799
786
  if (success) success(model, resp, options);
800
787
  };
801
788
  model.save(null, options);
@@ -864,16 +851,32 @@
864
851
  });
865
852
 
866
853
  // Underscore methods that we want to implement on the Collection.
867
- var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
868
- 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
869
- 'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
870
- 'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
871
- 'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
854
+ var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
855
+ 'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
856
+ 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
857
+ 'max', 'min', 'sortedIndex', 'toArray', 'size', 'first', 'head', 'take',
858
+ 'initial', 'rest', 'tail', 'last', 'without', 'indexOf', 'shuffle',
859
+ 'lastIndexOf', 'isEmpty'];
872
860
 
873
861
  // Mix in each Underscore method as a proxy to `Collection#models`.
874
862
  _.each(methods, function(method) {
875
863
  Collection.prototype[method] = function() {
876
- return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
864
+ var args = slice.call(arguments);
865
+ args.unshift(this.models);
866
+ return _[method].apply(_, args);
867
+ };
868
+ });
869
+
870
+ // Underscore methods that take a property name as an argument.
871
+ var attributeMethods = ['groupBy', 'countBy', 'sortBy'];
872
+
873
+ // Use attributes instead of properties.
874
+ _.each(attributeMethods, function(method) {
875
+ Collection.prototype[method] = function(value, context) {
876
+ var iterator = _.isFunction(value) ? value : function(model) {
877
+ return model.get(value);
878
+ };
879
+ return _[method](this.models, iterator, context);
877
880
  };
878
881
  });
879
882
 
@@ -909,7 +912,6 @@
909
912
  // });
910
913
  //
911
914
  route: function(route, name, callback) {
912
- Backbone.history || (Backbone.history = new History);
913
915
  if (!_.isRegExp(route)) route = this._routeToRegExp(route);
914
916
  if (!callback) callback = this[name];
915
917
  Backbone.history.route(route, _.bind(function(fragment) {
@@ -962,16 +964,23 @@
962
964
 
963
965
  // Handles cross-browser history management, based on URL fragments. If the
964
966
  // browser does not support `onhashchange`, falls back to polling.
965
- var History = Backbone.History = function(options) {
967
+ var History = Backbone.History = function() {
966
968
  this.handlers = [];
967
969
  _.bindAll(this, 'checkUrl');
968
- this.location = options && options.location || root.location;
969
- this.history = options && options.history || root.history;
970
+
971
+ // #1653 - Ensure that `History` can be used outside of the browser.
972
+ if (typeof window !== 'undefined') {
973
+ this.location = window.location;
974
+ this.history = window.history;
975
+ }
970
976
  };
971
977
 
972
- // Cached regex for cleaning leading hashes and slashes .
978
+ // Cached regex for cleaning leading hashes and slashes.
973
979
  var routeStripper = /^[#\/]/;
974
980
 
981
+ // Cached regex for stripping leading and trailing slashes.
982
+ var rootStripper = /^\/+|\/+$/g;
983
+
975
984
  // Cached regex for detecting MSIE.
976
985
  var isExplorer = /msie [\w.]+/;
977
986
 
@@ -1001,7 +1010,7 @@
1001
1010
  if (fragment == null) {
1002
1011
  if (this._hasPushState || !this._wantsHashChange || forcePushState) {
1003
1012
  fragment = this.location.pathname;
1004
- var root = this.options.root.replace(trailingSlash, '');
1013
+ var root = this.root.replace(trailingSlash, '');
1005
1014
  if (!fragment.indexOf(root)) fragment = fragment.substr(root.length);
1006
1015
  } else {
1007
1016
  fragment = this.getHash();
@@ -1019,6 +1028,7 @@
1019
1028
  // Figure out the initial configuration. Do we need an iframe?
1020
1029
  // Is pushState desired ... is it available?
1021
1030
  this.options = _.extend({}, {root: '/'}, this.options, options);
1031
+ this.root = this.options.root;
1022
1032
  this._wantsHashChange = this.options.hashChange !== false;
1023
1033
  this._wantsPushState = !!this.options.pushState;
1024
1034
  this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState);
@@ -1026,8 +1036,8 @@
1026
1036
  var docMode = document.documentMode;
1027
1037
  var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
1028
1038
 
1029
- // Normalize root to always include trailing slash
1030
- if (!trailingSlash.test(this.options.root)) this.options.root += '/';
1039
+ // Normalize root to always include a leading and trailing slash.
1040
+ this.root = ('/' + this.root + '/').replace(rootStripper, '/');
1031
1041
 
1032
1042
  if (oldIE && this._wantsHashChange) {
1033
1043
  this.iframe = Backbone.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
@@ -1048,13 +1058,13 @@
1048
1058
  // opened by a non-pushState browser.
1049
1059
  this.fragment = fragment;
1050
1060
  var loc = this.location;
1051
- var atRoot = (loc.pathname.replace(/[^/]$/, '$&/') === this.options.root) && !loc.search;
1061
+ var atRoot = (loc.pathname.replace(/[^/]$/, '$&/') === this.root) && !loc.search;
1052
1062
 
1053
1063
  // If we've started off with a route from a `pushState`-enabled browser,
1054
1064
  // but we're currently in a browser that doesn't support it...
1055
1065
  if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
1056
1066
  this.fragment = this.getFragment(null, true);
1057
- this.location.replace(this.options.root + this.location.search + '#' + this.fragment);
1067
+ this.location.replace(this.root + this.location.search + '#' + this.fragment);
1058
1068
  // Return immediately as browser will do redirect to new url
1059
1069
  return true;
1060
1070
 
@@ -1062,7 +1072,7 @@
1062
1072
  // in a browser where it could be `pushState`-based instead...
1063
1073
  } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
1064
1074
  this.fragment = this.getHash().replace(routeStripper, '');
1065
- this.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
1075
+ this.history.replaceState({}, document.title, this.root + this.fragment);
1066
1076
  }
1067
1077
 
1068
1078
  if (!this.options.silent) return this.loadUrl();
@@ -1118,10 +1128,10 @@
1118
1128
  navigate: function(fragment, options) {
1119
1129
  if (!History.started) return false;
1120
1130
  if (!options || options === true) options = {trigger: options};
1121
- var frag = (fragment || '').replace(routeStripper, '');
1122
- if (this.fragment === frag) return;
1123
- this.fragment = frag;
1124
- var url = (frag.indexOf(this.options.root) !== 0 ? this.options.root : '') + frag;
1131
+ fragment = this.getFragment(fragment || '');
1132
+ if (this.fragment === fragment) return;
1133
+ this.fragment = fragment;
1134
+ var url = this.root + fragment;
1125
1135
 
1126
1136
  // If pushState is available, we use it to set the fragment as a real URL.
1127
1137
  if (this._hasPushState) {
@@ -1130,13 +1140,13 @@
1130
1140
  // If hash changes haven't been explicitly disabled, update the hash
1131
1141
  // fragment to store history.
1132
1142
  } else if (this._wantsHashChange) {
1133
- this._updateHash(this.location, frag, options.replace);
1134
- if (this.iframe && (frag !== this.getFragment(this.getHash(this.iframe)))) {
1143
+ this._updateHash(this.location, fragment, options.replace);
1144
+ if (this.iframe && (fragment !== this.getFragment(this.getHash(this.iframe)))) {
1135
1145
  // Opening and closing the iframe tricks IE7 and earlier to push a
1136
1146
  // history entry on hash-tag change. When replace is true, we don't
1137
1147
  // want this.
1138
1148
  if(!options.replace) this.iframe.document.open().close();
1139
- this._updateHash(this.iframe.location, frag, options.replace);
1149
+ this._updateHash(this.iframe.location, fragment, options.replace);
1140
1150
  }
1141
1151
 
1142
1152
  // If you've told us that you explicitly don't want fallback hashchange-
@@ -1151,14 +1161,19 @@
1151
1161
  // a new one to the browser history.
1152
1162
  _updateHash: function(location, fragment, replace) {
1153
1163
  if (replace) {
1154
- location.replace(location.href.replace(/(javascript:|#).*$/, '') + '#' + fragment);
1164
+ var href = location.href.replace(/(javascript:|#).*$/, '');
1165
+ location.replace(href + '#' + fragment);
1155
1166
  } else {
1156
- location.hash = fragment;
1167
+ // #1649 - Some browsers require that `hash` contains a leading #.
1168
+ location.hash = '#' + fragment;
1157
1169
  }
1158
1170
  }
1159
1171
 
1160
1172
  });
1161
1173
 
1174
+ // Create the default Backbone.history.
1175
+ Backbone.history = new History;
1176
+
1162
1177
  // Backbone.View
1163
1178
  // -------------
1164
1179
 
@@ -1201,9 +1216,19 @@
1201
1216
  return this;
1202
1217
  },
1203
1218
 
1219
+ // Clean up references to this view in order to prevent latent effects and
1220
+ // memory leaks.
1221
+ dispose: function() {
1222
+ this.undelegateEvents();
1223
+ if (this.model) this.model.off(null, null, this);
1224
+ if (this.collection) this.collection.off(null, null, this);
1225
+ return this;
1226
+ },
1227
+
1204
1228
  // Remove this view from the DOM. Note that the view isn't present in the
1205
1229
  // DOM by default, so calling this method may be a no-op.
1206
1230
  remove: function() {
1231
+ this.dispose();
1207
1232
  this.$el.remove();
1208
1233
  return this;
1209
1234
  },
@@ -1246,7 +1271,7 @@
1246
1271
  // This only works for delegate-able events: not `focus`, `blur`, and
1247
1272
  // not `change`, `submit`, and `reset` in Internet Explorer.
1248
1273
  delegateEvents: function(events) {
1249
- if (!(events || (events = getValue(this, 'events')))) return;
1274
+ if (!(events || (events = _.result(this, 'events')))) return;
1250
1275
  this.undelegateEvents();
1251
1276
  for (var key in events) {
1252
1277
  var method = events[key];
@@ -1289,10 +1314,10 @@
1289
1314
  // an element from the `id`, `className` and `tagName` properties.
1290
1315
  _ensureElement: function() {
1291
1316
  if (!this.el) {
1292
- var attrs = _.extend({}, getValue(this, 'attributes'));
1293
- if (this.id) attrs.id = getValue(this, 'id');
1294
- if (this.className) attrs['class'] = getValue(this, 'className');
1295
- this.setElement(this.make(getValue(this, 'tagName'), attrs), false);
1317
+ var attrs = _.extend({}, _.result(this, 'attributes'));
1318
+ if (this.id) attrs.id = _.result(this, 'id');
1319
+ if (this.className) attrs['class'] = _.result(this, 'className');
1320
+ this.setElement(this.make(_.result(this, 'tagName'), attrs), false);
1296
1321
  } else {
1297
1322
  this.setElement(this.el, false);
1298
1323
  }
@@ -1300,14 +1325,6 @@
1300
1325
 
1301
1326
  });
1302
1327
 
1303
- // The self-propagating extend function that Backbone classes use.
1304
- var extend = function(protoProps, classProps) {
1305
- return inherits(this, protoProps, classProps);
1306
- };
1307
-
1308
- // Set up inheritance for the model, collection, and view.
1309
- Model.extend = Collection.extend = Router.extend = View.extend = extend;
1310
-
1311
1328
  // Backbone.sync
1312
1329
  // -------------
1313
1330
 
@@ -1345,7 +1362,7 @@
1345
1362
 
1346
1363
  // Ensure that we have a URL.
1347
1364
  if (!options.url) {
1348
- params.url = getValue(model, 'url') || urlError();
1365
+ params.url = _.result(model, 'url') || urlError();
1349
1366
  }
1350
1367
 
1351
1368
  // Ensure that we have the appropriate request data.
@@ -1377,6 +1394,18 @@
1377
1394
  params.processData = false;
1378
1395
  }
1379
1396
 
1397
+ var success = options.success;
1398
+ options.success = function(resp, status, xhr) {
1399
+ if (success) success(resp, status, xhr);
1400
+ model.trigger('sync', model, resp, options);
1401
+ };
1402
+
1403
+ var error = options.error;
1404
+ options.error = function(xhr, status, thrown) {
1405
+ if (error) error(model, xhr, options);
1406
+ model.trigger('error', model, xhr, options);
1407
+ };
1408
+
1380
1409
  // Make the request, allowing the user to override any Ajax options.
1381
1410
  return Backbone.ajax(_.extend(params, options));
1382
1411
  };
@@ -1386,69 +1415,47 @@
1386
1415
  return Backbone.$.ajax.apply(Backbone.$, arguments);
1387
1416
  };
1388
1417
 
1389
- // Wrap an optional error callback with a fallback error event.
1390
- Backbone.wrapError = function(onError, originalModel, options) {
1391
- return function(model, resp) {
1392
- resp = model === originalModel ? resp : model;
1393
- if (onError) {
1394
- onError(originalModel, resp, options);
1395
- } else {
1396
- originalModel.trigger('error', originalModel, resp, options);
1397
- }
1398
- };
1399
- };
1400
-
1401
1418
  // Helpers
1402
1419
  // -------
1403
1420
 
1404
- // Shared empty constructor function to aid in prototype-chain creation.
1405
- var ctor = function(){};
1406
-
1407
1421
  // Helper function to correctly set up the prototype chain, for subclasses.
1408
1422
  // Similar to `goog.inherits`, but uses a hash of prototype properties and
1409
1423
  // class properties to be extended.
1410
- var inherits = function(parent, protoProps, staticProps) {
1424
+ var extend = function(protoProps, staticProps) {
1425
+ var parent = this;
1411
1426
  var child;
1412
1427
 
1413
1428
  // The constructor function for the new subclass is either defined by you
1414
1429
  // (the "constructor" property in your `extend` definition), or defaulted
1415
1430
  // by us to simply call the parent's constructor.
1416
- if (protoProps && protoProps.hasOwnProperty('constructor')) {
1431
+ if (protoProps && _.has(protoProps, 'constructor')) {
1417
1432
  child = protoProps.constructor;
1418
1433
  } else {
1419
1434
  child = function(){ parent.apply(this, arguments); };
1420
1435
  }
1421
1436
 
1422
- // Inherit class (static) properties from parent.
1423
- _.extend(child, parent);
1424
-
1425
1437
  // Set the prototype chain to inherit from `parent`, without calling
1426
1438
  // `parent`'s constructor function.
1427
- ctor.prototype = parent.prototype;
1428
- child.prototype = new ctor();
1439
+ function Surrogate(){ this.constructor = child; };
1440
+ Surrogate.prototype = parent.prototype;
1441
+ child.prototype = new Surrogate;
1429
1442
 
1430
1443
  // Add prototype properties (instance properties) to the subclass,
1431
1444
  // if supplied.
1432
1445
  if (protoProps) _.extend(child.prototype, protoProps);
1433
1446
 
1434
1447
  // Add static properties to the constructor function, if supplied.
1435
- if (staticProps) _.extend(child, staticProps);
1448
+ _.extend(child, parent, staticProps);
1436
1449
 
1437
- // Correctly set child's `prototype.constructor`.
1438
- child.prototype.constructor = child;
1439
-
1440
- // Set a convenience property in case the parent's prototype is needed later.
1450
+ // Set a convenience property in case the parent's prototype is needed
1451
+ // later.
1441
1452
  child.__super__ = parent.prototype;
1442
1453
 
1443
1454
  return child;
1444
1455
  };
1445
1456
 
1446
- // Helper function to get a value from a Backbone object as a property
1447
- // or as a function.
1448
- var getValue = function(object, prop) {
1449
- if (!(object && object[prop])) return null;
1450
- return _.isFunction(object[prop]) ? object[prop]() : object[prop];
1451
- };
1457
+ // Set up inheritance for the model, collection, router, and view.
1458
+ Model.extend = Collection.extend = Router.extend = View.extend = extend;
1452
1459
 
1453
1460
  // Throw an error when a URL is needed, and none is supplied.
1454
1461
  var urlError = function() {
@@ -0,0 +1,11 @@
1
+ #= require ./helpers
2
+
3
+ @Ultimate =
4
+
5
+ debugMode: false
6
+
7
+ debug: ->
8
+ if @debugMode
9
+ a = ["info", "Ultimate"]
10
+ Array::push.apply a, arguments if arguments.length > 0
11
+ cout.apply @, a
@@ -23,7 +23,7 @@
23
23
  if matches = size.match(/^(\d+)x(\d+)$/)
24
24
  options['width'] = matches[1]
25
25
  options['height'] = matches[2]
26
- Ultimate.Helpers.Tag.tag 'img', options
26
+ Ultimate.Helpers.Tag.tag('img', options)
27
27
 
28
28
  image_alt: (src) ->
29
29
  _.string.capitalize @without_extension(@basename(src)).replace(/-[A-Fa-f0-9]{32}/, '')
@@ -47,16 +47,16 @@
47
47
  source
48
48
 
49
49
  rewrite_extension: (source, ext) ->
50
- "#{@without_extension source}.#{ext}"
50
+ "#{@without_extension(source)}.#{ext}"
51
51
 
52
52
  without_extension: (source) ->
53
- source.replace /^(.+)(\.\w+)$/, '$1'
53
+ source.replace(/^(.+)(\.\w+)$/, '$1')
54
54
 
55
55
  ASSET_ID: ''
56
56
  asset_ids_cache: {}
57
57
  # Use the ASSET_ID inscope variable or the random hash as its cache-busting asset id.
58
58
  asset_id: (source) ->
59
- if _.isString @ASSET_ID
59
+ if _.isString(@ASSET_ID)
60
60
  @ASSET_ID
61
61
  else
62
62
  @asset_ids_cache[source] or (@asset_ids_cache[source] = 10000000 + Math.floor(Math.random() * 90000000))
@@ -1,3 +1,3 @@
1
- @Ultimate ||= {}
1
+ #= require ultimate/base
2
2
 
3
3
  @Ultimate.Helpers ||= {}
@@ -0,0 +1,97 @@
1
+ #= require ./base
2
+ #= require ./tag
3
+
4
+ #module I18n
5
+ # class ExceptionHandler
6
+ # include Module.new {
7
+ # def call(exception, locale, key, options)
8
+ # exception.is_a?(MissingTranslation) && options[:rescue_format] == :html ? super.html_safe : super
9
+ # end
10
+ # }
11
+ # end
12
+ #end
13
+
14
+ @Ultimate.Helpers.Translation =
15
+
16
+ ###**
17
+ * Delegates to <tt>I18n#translate</tt> but also performs three additional functions.
18
+ *
19
+ * First, it'll pass the <tt>:rescue_format => :html</tt> option to I18n so that any
20
+ * thrown +MissingTranslation+ messages will be turned into inline spans that
21
+ *
22
+ * * have a "translation-missing" class set,
23
+ * * contain the missing key as a title attribute and
24
+ * * a titleized version of the last key segment as a text.
25
+ *
26
+ * E.g. the value returned for a missing translation key :"blog.post.title" will be
27
+ * <span class="translation_missing" title="translation missing: en.blog.post.title">Title</span>.
28
+ * This way your views will display rather reasonable strings but it will still
29
+ * be easy to spot missing translations.
30
+ *
31
+ * Second, it'll scope the key by the current partial if the key starts
32
+ * with a period. So if you call <tt>translate(".foo")</tt> from the
33
+ * <tt>people/index.html.erb</tt> template, you'll actually be calling
34
+ * <tt>I18n.translate("people.index.foo")</tt>. This makes it less repetitive
35
+ * to translate many keys within the same partials and gives you a simple framework
36
+ * for scoping them consistently. If you don't prepend the key with a period,
37
+ * nothing is converted.
38
+ *
39
+ * Third, it'll mark the translation as safe HTML if the key has the suffix
40
+ * "_html" or the last element of the key is the word "html". For example,
41
+ * calling translate("footer_html") or translate("footer.html") will return
42
+ * a safe HTML string that won't be escaped by other HTML helper methods. This
43
+ * naming convention helps to identify translations that include HTML tags so that
44
+ * you know what kind of output to expect when you call translate in a template.
45
+ ###
46
+ translate: (key, options = {}) ->
47
+ _.extend(options, rescue_format: 'html') unless _.has(options, 'rescue_format')
48
+ options['default'] = @wrap_translate_defaults(options['default']) if options['default']
49
+ if @html_safe_translation_key(key)
50
+ html_safe_options = _.clone(options)
51
+ options.except(I18n.RESERVED_KEYS).each do |name, value|
52
+ unless name == :count && value.is_a?(Numeric)
53
+ html_safe_options[name] = ERB::Util.html_escape(value.to_s)
54
+ end
55
+ end
56
+ translation = I18n.translate(scope_key_by_partial(key), html_safe_options)
57
+
58
+ translation.respond_to?(:html_safe) ? translation.html_safe : translation
59
+ else
60
+ I18n.translate(scope_key_by_partial(key), options)
61
+ end
62
+ end
63
+ alias :t :translate
64
+
65
+ # Delegates to <tt>I18n.localize</tt> with no additional functionality.
66
+ #
67
+ # See http://rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
68
+ # for more information.
69
+ localize: ->
70
+ I18n.localize(arguments...)
71
+
72
+ l: -> @localize(arguments...)
73
+
74
+ # private
75
+
76
+ scope_key_by_partial: (key) ->
77
+ if /^\./.test(key)
78
+ if @virtual_path?
79
+ @virtual_path.replace(/_/g, ".") + key
80
+ else
81
+ throw new Error("Cannot use t(#{key.inspect}) shortcut because path is not available")
82
+ else
83
+ key
84
+
85
+ html_safe_translation_key: (key) ->
86
+ /(\b|_|\.)html$/.test(key)
87
+
88
+ wrap_translate_defaults: (defaults) ->
89
+ new_defaults = []
90
+ defaults = _.toArray(defaults)
91
+ while key = defaults.shift()
92
+ if _.isString(key)
93
+ new_defaults.push (_, options) -> @translate(key, _.extend(options, default: defaults))
94
+ break
95
+ else
96
+ new_defaults.push key
97
+ new_defaults
@@ -0,0 +1,63 @@
1
+ #= require ./base
2
+
3
+ ###*
4
+ * Make $.fn.pluginName() as adapter to plugin object (instance of Ultimate.Plugin or Backbone.View).
5
+ * First call on jQuery object invoke View functionality for the first element in the set of matched elements.
6
+ * Subsequent calls forwarding on view methods or return view property.
7
+ * If last argument {Boolean} true, then returns {Plugin or View}.
8
+ * @usage
9
+ * construction .pluginName([Object options = {}]) : {jQuery} jContainer
10
+ * updateOptions .pluginName({Object} options) : {Object} view options
11
+ * get options .pluginName("options") : {Object} view options
12
+ * some method .pluginName("methodName", *methodArguments) : {jQuery} chanin object or {AnotherType} method result
13
+ ###
14
+
15
+ Ultimate.createJQueryPlugin = (pluginName, pluginClass) ->
16
+ Ultimate.debug(".createJQueryPlugin()", pluginName, pluginClass) if _.isFunction(Ultimate.debug)
17
+ jQuery.fn[pluginName] = -> @ultimatePluginAdapter pluginName, pluginClass, arguments
18
+
19
+
20
+
21
+ do ($ = jQuery) ->
22
+
23
+ $.fn.ultimatePluginAdapter = (pluginName, pluginClass, pluginArgs) ->
24
+ args = _.toArray(pluginArgs)
25
+ # Shall return the Plugin, if have arguments and last argument of the call is a Boolean true.
26
+ _returnPlugin =
27
+ if args.length and _.isBoolean(_.last(args))
28
+ args.pop()
29
+ else
30
+ false
31
+ unless @length
32
+ return if _returnPlugin then null else @
33
+ # get the first
34
+ jContainer = @first()
35
+ # try to get the plugin object, controlling everything that happens in our magical container
36
+ plugin = jContainer.data(pluginName)
37
+ if plugin? and plugin.$el[0] is jContainer[0]
38
+ command = null
39
+ if args.length and _.isString(args[0])
40
+ command = args.shift()
41
+ else
42
+ if args.length and $.isPlainObject(args[0])
43
+ command = "_configure"
44
+ else
45
+ return if _returnPlugin then plugin else jContainer
46
+ if command of plugin
47
+ value = plugin[command]
48
+ return if _.isFunction(value)
49
+ plugin.$el.removeData(pluginName) if command is "destroy"
50
+ result = value.apply(plugin, args)
51
+ if command is "_configure" then plugin.options else result
52
+ else
53
+ value
54
+ else
55
+ $.error "Command [#{command}] does not exist on jQuery.#{pluginName}()"
56
+ else
57
+ options = args[0] or {}
58
+ if $.isPlainObject(options)
59
+ plugin = new pluginClass(_.extend(options, el: jContainer[0]))
60
+ plugin.$el.data(pluginName, plugin)
61
+ else
62
+ $.error "First argument of jQuery.#{pluginName}() must be plain object"
63
+ if _returnPlugin then plugin else jContainer
@@ -0,0 +1,104 @@
1
+ # `pluginClass` must store propery `$el` as jQuery object wrapped on the target DOM-object in his instance.
2
+
3
+ #= require ./base
4
+
5
+ class Ultimate.Plugin
6
+ $el: null
7
+ nodes: {}
8
+ events: {}
9
+
10
+ options: {}
11
+
12
+ locale: "en"
13
+ translations: {}
14
+
15
+ constructor: (options) ->
16
+ @$el = $(options.el)
17
+ @delegateEvents()
18
+ @findNodes()
19
+
20
+ findNodes: (jRoot = @$el, nodes = @nodes) ->
21
+ jNodes = {}
22
+ nodes = nodes() if _.isFunction(nodes)
23
+ if _.isObject(nodes)
24
+ for nodeName, selector of nodes
25
+ _isObject = _.isObject(selector)
26
+ if _isObject
27
+ nestedNodes = selector
28
+ selector = _.outcasts.delete(nestedNodes, "selector")
29
+ jNodes[nodeName] = @[nodeName] = jRoot.find(selector)
30
+ if _isObject
31
+ _.extend jNodes, @findNodes(jNodes[nodeName], nestedNodes)
32
+ jNodes
33
+
34
+ undelegateEvents: ->
35
+ @$el.unbind ".delegateEvents#{@cid}"
36
+
37
+ # Cached regex to split keys for `delegate`, from backbone.js.
38
+ delegateEventSplitter = /^(\S+)\s*(.*)$/
39
+
40
+ # delegateEvents() from backbone.js
41
+ _delegateEvents: (events = _.result(@, "events")) ->
42
+ return unless events
43
+ @undelegateEvents()
44
+ for key, method of events
45
+ method = @[events[key]] unless _.isFunction(method)
46
+ throw new Error("Method \"#{events[key]}\" does not exist") unless method
47
+ [[], eventName, selector] = key.match(delegateEventSplitter)
48
+ method = _.bind(method, @)
49
+ eventName += ".delegateEvents#{@cid}"
50
+ if selector is ''
51
+ @$el.bind(eventName, method)
52
+ else
53
+ @$el.delegate(selector, eventName, method)
54
+
55
+ # Overload and proxy parent method Backbone.View.delegateEvents() as hook for normalizeEvents().
56
+ delegateEvents: (events) ->
57
+ args = []
58
+ Array::push.apply args, arguments if arguments.length > 0
59
+ args[0] = @normalizeEvents(events)
60
+ @_delegateEvents args...
61
+
62
+ normalizeEvents: (events) ->
63
+ events = _.result(@, "events") unless events
64
+ if events
65
+ normalizedEvents = {}
66
+ for key, method of events
67
+ [[], eventName, selector] = key.match(delegateEventSplitter)
68
+ selector = _.result(@, selector)
69
+ selector = selector.selector if selector instanceof jQuery
70
+ if _.isString(selector)
71
+ key = "#{eventName} #{selector}"
72
+ normalizedEvents[key] = method
73
+ events = normalizedEvents
74
+ events
75
+
76
+ _configure: (options) ->
77
+ _.extend @options, options
78
+ @initTranslations()
79
+ @reflectOptions()
80
+
81
+ reflectOptions: (viewOptions = _.result(@, "viewOptions"), options = @options) ->
82
+ @[attr] = options[attr] for attr in viewOptions when typeof options[attr] isnt "undefined"
83
+ @[attr] = value for attr, value of options when typeof @[attr] isnt "undefined"
84
+ @
85
+
86
+ # use I18n, and modify locale and translations in options
87
+ # modify and return merged data
88
+ initTranslations: (options = @options) ->
89
+ # if global compatible I18n
90
+ if I18n? and I18n.locale and I18n.t
91
+ options["locale"] ||= I18n.locale
92
+ if options["locale"] is I18n.locale
93
+ # pointing to defaults locales of language specified in I18n
94
+ _defaultLocales = @constructor.defaultLocales?[I18n.locale] ||= {}
95
+ unless _defaultLocales["loaded"]
96
+ _defaultLocales["loaded"] = true
97
+ # try read localized strings
98
+ if _localesFromI18n = I18n.t(options["i18nKey"] or _.underscored(@constructor.pluginName or @constructor.name))
99
+ # fill it from I18n
100
+ _.extend _defaultLocales, _localesFromI18n
101
+ @locale = options["locale"] if options["locale"]
102
+ translations = if @locale then @constructor.defaultLocales?[@locale] or {} else {}
103
+ $.extend true, options, translations: translations, options
104
+ options
@@ -1,5 +1,5 @@
1
1
  module Ultimate
2
2
  module Base
3
- VERSION = "0.3.0"
3
+ VERSION = "0.3.1"
4
4
  end
5
5
  end
@@ -0,0 +1,140 @@
1
+ ###
2
+ require 'abstract_unit'
3
+
4
+ class TranslationHelperTest < ActiveSupport::TestCase
5
+ include ActionView::Helpers::TagHelper
6
+ include ActionView::Helpers::TranslationHelper
7
+
8
+ attr_reader :request, :view
9
+
10
+ def setup
11
+ I18n.backend.store_translations(:en,
12
+ :translations => {
13
+ :templates => {
14
+ :found => { :foo => 'Foo' },
15
+ :array => { :foo => { :bar => 'Foo Bar' } },
16
+ :default => { :foo => 'Foo' }
17
+ },
18
+ :foo => 'Foo',
19
+ :hello => '<a>Hello World</a>',
20
+ :html => '<a>Hello World</a>',
21
+ :hello_html => '<a>Hello World</a>',
22
+ :interpolated_html => '<a>Hello %{word}</a>',
23
+ :array_html => %w(foo bar),
24
+ :array => %w(foo bar),
25
+ :count_html => {
26
+ :one => '<a>One %{count}</a>',
27
+ :other => '<a>Other %{count}</a>'
28
+ }
29
+ }
30
+ )
31
+ @view = ::ActionView::Base.new(ActionController::Base.view_paths, {})
32
+ end
33
+
34
+ def test_delegates_to_i18n_setting_the_rescue_format_option_to_html
35
+ I18n.expects(:translate).with(:foo, :locale => 'en', :rescue_format => :html).returns("")
36
+ translate :foo, :locale => 'en'
37
+ end
38
+
39
+ def test_delegates_localize_to_i18n
40
+ @time = Time.utc(2008, 7, 8, 12, 18, 38)
41
+ I18n.expects(:localize).with(@time)
42
+ localize @time
43
+ end
44
+
45
+ def test_returns_missing_translation_message_wrapped_into_span
46
+ expected = '<span class="translation_missing" title="translation missing: en.translations.missing">Missing</span>'
47
+ assert_equal expected, translate(:"translations.missing")
48
+ assert_equal true, translate(:"translations.missing").html_safe?
49
+ end
50
+
51
+ def test_returns_missing_translation_message_using_nil_as_rescue_format
52
+ expected = 'translation missing: en.translations.missing'
53
+ assert_equal expected, translate(:"translations.missing", :rescue_format => nil)
54
+ assert_equal false, translate(:"translations.missing", :rescue_format => nil).html_safe?
55
+ end
56
+
57
+ def test_i18n_translate_defaults_to_nil_rescue_format
58
+ expected = 'translation missing: en.translations.missing'
59
+ assert_equal expected, I18n.translate(:"translations.missing")
60
+ assert_equal false, I18n.translate(:"translations.missing").html_safe?
61
+ end
62
+
63
+ def test_translation_returning_an_array
64
+ expected = %w(foo bar)
65
+ assert_equal expected, translate(:"translations.array")
66
+ end
67
+
68
+ def test_finds_translation_scoped_by_partial
69
+ assert_equal 'Foo', view.render(:file => 'translations/templates/found').strip
70
+ end
71
+
72
+ def test_finds_array_of_translations_scoped_by_partial
73
+ assert_equal 'Foo Bar', @view.render(:file => 'translations/templates/array').strip
74
+ end
75
+
76
+ def test_default_lookup_scoped_by_partial
77
+ assert_equal 'Foo', view.render(:file => 'translations/templates/default').strip
78
+ end
79
+
80
+ def test_missing_translation_scoped_by_partial
81
+ expected = '<span class="translation_missing" title="translation missing: en.translations.templates.missing.missing">Missing</span>'
82
+ assert_equal expected, view.render(:file => 'translations/templates/missing').strip
83
+ end
84
+
85
+ def test_translate_does_not_mark_plain_text_as_safe_html
86
+ assert_equal false, translate(:'translations.hello').html_safe?
87
+ end
88
+
89
+ def test_translate_marks_translations_named_html_as_safe_html
90
+ assert translate(:'translations.html').html_safe?
91
+ end
92
+
93
+ def test_translate_marks_translations_with_a_html_suffix_as_safe_html
94
+ assert translate(:'translations.hello_html').html_safe?
95
+ end
96
+
97
+ def test_translate_escapes_interpolations_in_translations_with_a_html_suffix
98
+ assert_equal '<a>Hello &lt;World&gt;</a>', translate(:'translations.interpolated_html', :word => '<World>')
99
+ assert_equal '<a>Hello &lt;World&gt;</a>', translate(:'translations.interpolated_html', :word => stub(:to_s => "<World>"))
100
+ end
101
+
102
+ def test_translate_with_html_count
103
+ assert_equal '<a>One 1</a>', translate(:'translations.count_html', :count => 1)
104
+ assert_equal '<a>Other 2</a>', translate(:'translations.count_html', :count => 2)
105
+ assert_equal '<a>Other &lt;One&gt;</a>', translate(:'translations.count_html', :count => '<One>')
106
+ end
107
+
108
+ def test_translation_returning_an_array_ignores_html_suffix
109
+ assert_equal ["foo", "bar"], translate(:'translations.array_html')
110
+ end
111
+
112
+ def test_translate_with_default_named_html
113
+ translation = translate(:'translations.missing', :default => :'translations.hello_html')
114
+ assert_equal '<a>Hello World</a>', translation
115
+ assert_equal true, translation.html_safe?
116
+ end
117
+
118
+ def test_translate_with_two_defaults_named_html
119
+ translation = translate(:'translations.missing', :default => [:'translations.missing_html', :'translations.hello_html'])
120
+ assert_equal '<a>Hello World</a>', translation
121
+ assert_equal true, translation.html_safe?
122
+ end
123
+
124
+ def test_translate_with_last_default_named_html
125
+ translation = translate(:'translations.missing', :default => [:'translations.missing', :'translations.hello_html'])
126
+ assert_equal '<a>Hello World</a>', translation
127
+ assert_equal true, translation.html_safe?
128
+ end
129
+
130
+ def test_translate_with_string_default
131
+ translation = translate(:'translations.missing', default: 'A Generic String')
132
+ assert_equal 'A Generic String', translation
133
+ end
134
+
135
+ def test_translate_with_array_of_string_defaults
136
+ translation = translate(:'translations.missing', default: ['A Generic String', 'Second generic string'])
137
+ assert_equal 'A Generic String', translation
138
+ end
139
+ end
140
+ ###
@@ -0,0 +1,11 @@
1
+ #= require ultimate/underscore/underscore
2
+ #= require ultimate/jquery-plugin-class
3
+ #= require ultimate/jquery-plugin-adapter
4
+
5
+ module "jquery-plugin-adapter"
6
+
7
+ class TestPlugin extends Ultimate.Plugin
8
+
9
+ test "Ultimate.createJQueryPlugin", ->
10
+ testPlugin = Ultimate.createJQueryPlugin("testPlugin", TestPlugin)
11
+ ok _.isFunction(testPlugin)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ultimate-base
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-10 00:00:00.000000000 Z
12
+ date: 2012-09-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
16
- requirement: &18599780 !ruby/object:Gem::Requirement
16
+ requirement: &21906680 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 3.2.8
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *18599780
24
+ version_requirements: *21906680
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: sqlite3
27
- requirement: &18599360 !ruby/object:Gem::Requirement
27
+ requirement: &21906240 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *18599360
35
+ version_requirements: *21906240
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: coffee-rails
38
- requirement: &16981960 !ruby/object:Gem::Requirement
38
+ requirement: &21905660 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,7 +43,7 @@ dependencies:
43
43
  version: 3.2.1
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *16981960
46
+ version_requirements: *21905660
47
47
  description: Ultimate UI core, base helpers and improves for Ruby on Rails Front-end
48
48
  email:
49
49
  - koderfunk@gmail.com
@@ -63,13 +63,13 @@ files:
63
63
  - app/assets/javascripts/ultimate/backbone/base.js.coffee
64
64
  - app/assets/javascripts/ultimate/backbone/collection.js.coffee
65
65
  - app/assets/javascripts/ultimate/backbone/extra/jquery-ext.js.coffee
66
- - app/assets/javascripts/ultimate/backbone/extra/jquery-plugin-adapter.js.coffee
67
66
  - app/assets/javascripts/ultimate/backbone/lib/backbone.js
68
67
  - app/assets/javascripts/ultimate/backbone/model.js.coffee
69
68
  - app/assets/javascripts/ultimate/backbone/router.js.coffee
70
69
  - app/assets/javascripts/ultimate/backbone/view.js.coffee
71
70
  - app/assets/javascripts/ultimate/backbone/views/slider.js.coffee
72
71
  - app/assets/javascripts/ultimate/backbone/views/typed-fields.js.coffee
72
+ - app/assets/javascripts/ultimate/base.js.coffee
73
73
  - app/assets/javascripts/ultimate/experimental/_inflections/inflections.js.coffee
74
74
  - app/assets/javascripts/ultimate/experimental/_inflections/plur.js
75
75
  - app/assets/javascripts/ultimate/experimental/fuzzy-json-generator.js.coffee
@@ -80,6 +80,7 @@ files:
80
80
  - app/assets/javascripts/ultimate/helpers/number.js.coffee
81
81
  - app/assets/javascripts/ultimate/helpers/record_tag.js.coffee
82
82
  - app/assets/javascripts/ultimate/helpers/tag.js.coffee
83
+ - app/assets/javascripts/ultimate/helpers/translation.js.coffee
83
84
  - app/assets/javascripts/ultimate/helpers/url.js.coffee
84
85
  - app/assets/javascripts/ultimate/improves/datepicker.js.coffee
85
86
  - app/assets/javascripts/ultimate/improves/devise.js.coffee
@@ -89,6 +90,8 @@ files:
89
90
  - app/assets/javascripts/ultimate/improves/magic-radios.js.coffee
90
91
  - app/assets/javascripts/ultimate/improves/tablesorter.js
91
92
  - app/assets/javascripts/ultimate/improves/typed-fields.js.coffee
93
+ - app/assets/javascripts/ultimate/jquery-plugin-adapter.js.coffee
94
+ - app/assets/javascripts/ultimate/jquery-plugin-class.js.coffee
92
95
  - app/assets/javascripts/ultimate/jquery.base.js.coffee
93
96
  - app/assets/javascripts/ultimate/underscore/underscore.inflection.js
94
97
  - app/assets/javascripts/ultimate/underscore/underscore.js
@@ -167,9 +170,11 @@ files:
167
170
  - test/javascripts/tests/helpers/number_test.js.coffee
168
171
  - test/javascripts/tests/helpers/record_tag_test.js.coffee
169
172
  - test/javascripts/tests/helpers/tag_test.js.coffee
173
+ - test/javascripts/tests/helpers/translation_test.js.coffee
170
174
  - test/javascripts/tests/helpers/url_test.js.coffee
171
175
  - test/javascripts/tests/helpers_test.js.coffee
172
176
  - test/javascripts/tests/improves/i18n-lite_test.js.coffee
177
+ - test/javascripts/tests/jquery-plugin-adapter_test.js.coffee
173
178
  - test/javascripts/tests/underscore/underscore.outcasts.test.js.coffee
174
179
  - test/stylesheets/test_helper.css
175
180
  - ultimate-base.gemspec
@@ -1,60 +0,0 @@
1
- #= require ./jquery-ext
2
-
3
- do ($ = jQuery) ->
4
- $.fn.ultimateBackboneViewPluginAdapter = (pluginName, viewClass, pluginArgs) ->
5
- a = []
6
- Array::push.apply a, pluginArgs if pluginArgs.length > 0
7
- argsLength = a.length
8
- # Shall return the View, if have arguments and last argument of the call is a Boolean true.
9
- _returnView =
10
- if argsLength and _.isBoolean(_.last(a))
11
- argsLength--
12
- a.pop()
13
- else
14
- false
15
- unless @length
16
- return if _returnView then undefined else @
17
- # get the first TODO analize, maybe each?
18
- jContainer = @first()
19
- # try to get the View-object, controlling everything that happens in our magical container
20
- view = jContainer.getView(viewClass)
21
- if view and view.$el[0] is jContainer[0]
22
- command = null
23
- if argsLength and _.isString(a[0])
24
- command = a.shift()
25
- else
26
- return view if _returnView
27
- return jContainer unless argsLength
28
- command = "_configure"
29
- if view[command]
30
- return _.result(view, command)
31
- else
32
- $.error "Command [#{command}] does not exist on jQuery.#{pluginName}()"
33
- else
34
- options = if argsLength then a[0] else {}
35
- if $.isPlainObject(options)
36
- view = new viewClass(_.extend({}, options, el: jContainer[0]))
37
- else
38
- $.error "First argument of jQuery.#{pluginName}() must be plain object"
39
- if _returnView then view else jContainer
40
-
41
-
42
- ###*
43
- * Make $.fn.pluginName() as wrapper under Backbone.View.
44
- * First call on jQuery object invoke View functionality for the first element in the set of matched elements.
45
- * Subsequent calls forwarding on view methods or return view property.
46
- * If last argument {Boolean} true, then returns {View}.
47
- * @usage
48
- * construction .pluginName([Object options = {}]) : {jQuery} jContainer
49
- * updateOptions .pluginName({Object} options) : {Object} view options
50
- * get options .pluginName("options") : {Object} view options
51
- * some method .pluginName("methodName", *methodArguments) : {jQuery} chanin object || {AnotherType} method result
52
- ###
53
-
54
- Ultimate.Backbone.createJQueryPlugin = (pluginName, viewClass) ->
55
- Ultimate.Backbone.debug ".createJQueryPlugin()", pluginName, viewClass
56
- if Ultimate.Backbone.isViewClass(viewClass)
57
- viewClass.pluginName = pluginName
58
- jQuery.fn[pluginName] = -> @ultimateBackboneViewPluginAdapter pluginName, viewClass, arguments
59
- else
60
- jQuery.error "Second argument of Ultimate.Backbone.createJQueryPlugin() must be View class inherited from Backbone.View"