ultimate-base 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
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"