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 +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"
|