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 +1 -1
- data/app/assets/javascripts/ultimate/backbone/base.js.coffee +1 -2
- data/app/assets/javascripts/ultimate/backbone/collection.js.coffee +2 -0
- data/app/assets/javascripts/ultimate/backbone/lib/backbone.js +154 -147
- data/app/assets/javascripts/ultimate/base.js.coffee +11 -0
- data/app/assets/javascripts/ultimate/helpers/asset_tag.js.coffee +4 -4
- data/app/assets/javascripts/ultimate/helpers/base.js.coffee +1 -1
- data/app/assets/javascripts/ultimate/helpers/translation.js.coffee +97 -0
- data/app/assets/javascripts/ultimate/jquery-plugin-adapter.js.coffee +63 -0
- data/app/assets/javascripts/ultimate/jquery-plugin-class.js.coffee +104 -0
- data/lib/ultimate/base/version.rb +1 -1
- data/test/javascripts/tests/helpers/translation_test.js.coffee +140 -0
- data/test/javascripts/tests/jquery-plugin-adapter_test.js.coffee +11 -0
- metadata +14 -9
- data/app/assets/javascripts/ultimate/backbone/extra/jquery-plugin-adapter.js.coffee +0 -60
data/Gemfile.lock
CHANGED
@@ -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
|
22
|
-
var
|
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 =
|
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 =
|
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
|
-
|
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)
|
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,
|
593
|
-
|
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
|
594
|
+
// invalid models from being added.
|
598
595
|
for (i = 0, length = models.length; i < length; i++) {
|
599
|
-
if (
|
600
|
-
|
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
|
-
|
612
|
-
|
613
|
-
|
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
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
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
|
-
//
|
626
|
-
|
627
|
-
this.length
|
628
|
-
|
629
|
-
splice.apply(this.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 &&
|
628
|
+
if (this.comparator && at == null) this.sort({silent: true});
|
640
629
|
|
641
|
-
if (options.silent) return this;
|
642
|
-
|
643
|
-
|
644
|
-
|
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
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
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(
|
734
|
+
this.models.sort(_.bind(this.comparator, this));
|
745
735
|
}
|
746
|
-
|
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 _.
|
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
|
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)
|
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)
|
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', '
|
868
|
-
'
|
869
|
-
'
|
870
|
-
'
|
871
|
-
'
|
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
|
-
|
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(
|
967
|
+
var History = Backbone.History = function() {
|
966
968
|
this.handlers = [];
|
967
969
|
_.bindAll(this, 'checkUrl');
|
968
|
-
|
969
|
-
|
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.
|
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
|
-
|
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.
|
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.
|
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,
|
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
|
-
|
1122
|
-
if (this.fragment ===
|
1123
|
-
this.fragment =
|
1124
|
-
var url =
|
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,
|
1134
|
-
if (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,
|
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.
|
1164
|
+
var href = location.href.replace(/(javascript:|#).*$/, '');
|
1165
|
+
location.replace(href + '#' + fragment);
|
1155
1166
|
} else {
|
1156
|
-
|
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 =
|
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({},
|
1293
|
-
if (this.id) attrs.id =
|
1294
|
-
if (this.className) attrs['class'] =
|
1295
|
-
this.setElement(this.make(
|
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 =
|
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
|
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 &&
|
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
|
-
|
1428
|
-
|
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
|
-
|
1448
|
+
_.extend(child, parent, staticProps);
|
1436
1449
|
|
1437
|
-
//
|
1438
|
-
|
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
|
-
//
|
1447
|
-
|
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() {
|
@@ -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
|
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
|
50
|
+
"#{@without_extension(source)}.#{ext}"
|
51
51
|
|
52
52
|
without_extension: (source) ->
|
53
|
-
source.replace
|
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
|
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))
|
@@ -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
|
@@ -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 <World></a>', translate(:'translations.interpolated_html', :word => '<World>')
|
99
|
+
assert_equal '<a>Hello <World></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 <One></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.
|
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-
|
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: &
|
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: *
|
24
|
+
version_requirements: *21906680
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: sqlite3
|
27
|
-
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: *
|
35
|
+
version_requirements: *21906240
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: coffee-rails
|
38
|
-
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: *
|
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"
|