kojac 0.9.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +6 -14
- data/.gitignore +4 -0
- data/Gemfile +1 -1
- data/app/assets/javascripts/kojac.js +393 -125
- data/app/assets/javascripts/kojac_canjs.js +34 -34
- data/app/assets/javascripts/kojac_ember.js +110 -152
- data/app/controllers/{kojac_controller.rb → kojac_base_controller.rb} +18 -11
- data/app/policies/kojac_base_policy.rb +114 -0
- data/app/serializers/kojac_base_serializer.rb +35 -0
- data/kojac.gemspec +12 -10
- data/lib/kojac/app_serialize.rb +31 -29
- data/lib/kojac/concentric.rb +152 -0
- data/lib/kojac/kojac_policy.rb +70 -0
- data/lib/kojac/kojac_rails.rb +200 -49
- data/lib/kojac/version.rb +1 -1
- data/spec/can_cache_spec.js +19 -19
- data/spec/demo/.gitignore +16 -0
- data/spec/demo/.ruby-gemset +1 -0
- data/spec/demo/.ruby-version +1 -0
- data/spec/demo/Gemfile +59 -0
- data/spec/demo/Gemfile.lock +153 -0
- data/spec/demo/README.rdoc +15 -248
- data/spec/demo/Rakefile +25 -1
- data/spec/demo/app/{mailers/.gitkeep → assets/images/.keep} +0 -0
- data/spec/demo/app/assets/javascripts/application.js +3 -3
- data/spec/demo/app/controllers/application_controller.rb +6 -1
- data/spec/demo/app/{models/.gitkeep → controllers/concerns/.keep} +0 -0
- data/spec/demo/app/controllers/users_controller.rb +5 -0
- data/spec/demo/{lib/assets/.gitkeep → app/mailers/.keep} +0 -0
- data/spec/demo/{log/.gitkeep → app/models/.keep} +0 -0
- data/spec/demo/app/models/concerns/.keep +0 -0
- data/spec/demo/app/models/user.rb +36 -0
- data/spec/demo/app/policies/user_policy.rb +42 -0
- data/spec/demo/bin/bundle +3 -0
- data/spec/demo/bin/rails +4 -0
- data/spec/demo/bin/rake +4 -0
- data/spec/demo/config.ru +1 -1
- data/spec/demo/config/application.rb +14 -46
- data/spec/demo/config/application.yml +4 -0
- data/spec/demo/config/boot.rb +3 -9
- data/spec/demo/config/database.yml +6 -6
- data/spec/demo/config/environment.rb +4 -2
- data/spec/demo/config/environments/development.rb +11 -19
- data/spec/demo/config/environments/production.rb +40 -27
- data/spec/demo/config/environments/test.rb +13 -14
- data/spec/demo/config/initializers/concentric_config.rb +9 -0
- data/spec/demo/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/demo/config/initializers/inflections.rb +6 -5
- data/spec/demo/config/initializers/initialize_kojac.rb +16 -0
- data/spec/demo/config/initializers/secret_token.rb +7 -2
- data/spec/demo/config/initializers/session_store.rb +0 -5
- data/spec/demo/config/initializers/wrap_parameters.rb +6 -6
- data/spec/demo/config/locales/en.yml +20 -2
- data/spec/demo/config/routes.rb +24 -24
- data/spec/demo/db/migrate/20131212034312_add_user.rb +14 -0
- data/spec/demo/db/migrate/20140107085351_add_owner_id.rb +5 -0
- data/spec/demo/db/schema.rb +28 -0
- data/spec/demo/db/seeds.rb +7 -0
- data/spec/demo/lib/assets/.keep +0 -0
- data/spec/demo/lib/tasks/.keep +0 -0
- data/spec/demo/log/.keep +0 -0
- data/spec/demo/public/404.html +43 -11
- data/spec/demo/public/422.html +43 -11
- data/spec/demo/public/500.html +43 -11
- data/spec/demo/public/robots.txt +5 -0
- data/spec/demo/spec/controllers/allowed_fields_spec.rb +171 -0
- data/spec/demo/spec/factories/users.rb +9 -0
- data/spec/demo/spec/features/concentric_spec.rb +63 -0
- data/spec/demo/spec/features/serialization_spec.rb +86 -0
- data/spec/demo/spec/spec_helper.rb +133 -0
- data/spec/demo/spec/spec_utils.rb +42 -0
- data/spec/demo/vendor/assets/javascripts/.keep +0 -0
- data/spec/demo/vendor/assets/stylesheets/.keep +0 -0
- data/spec/ember_factory_spec.js +1 -1
- data/spec/ember_model_spec.js +13 -3
- data/spec/ember_tojsono_spec.js +105 -0
- data/spec/error_handling_spec.js +90 -0
- data/spec/external/underscore_plus.js +318 -9
- data/spec/kojac_caching_spec.js +3 -1
- data/spec/kojac_ember_cache_spec.js +9 -0
- data/spec/kojac_mock_spec.js +4 -4
- data/spec/kojac_operations_spec.js +4 -4
- data/spec/local_provider_spec.js +184 -0
- data/spec/model_ring_spec.rb +2 -2
- data/spec/operation_include_spec.js +2 -2
- data/spec/run.html +34 -24
- data/spec/type_conversion_spec.js +38 -0
- data/vendor/assets/javascripts/jstorage.js +950 -0
- metadata +115 -129
- data/Gemfile.lock +0 -157
- data/app/serializers/default_kojac_serializer.rb +0 -10
- data/lib/kojac/ring_strong_parameters.rb +0 -195
- data/spec/.DS_Store +0 -0
- data/spec/demo/script/rails +0 -6
- data/spec/external/.DS_Store +0 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
def setup_common_data
|
|
2
|
+
end
|
|
3
|
+
|
|
4
|
+
def stub_login_user(aValues=nil)
|
|
5
|
+
aValues ||= {}
|
|
6
|
+
aValues[:ring] ||= USER_RING
|
|
7
|
+
user = FactoryGirl.create(:user,aValues)
|
|
8
|
+
ApplicationController.any_instance.stub(:current_user).and_return(user)
|
|
9
|
+
user
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def do_op(read_op)
|
|
13
|
+
content = {
|
|
14
|
+
options: {},
|
|
15
|
+
ops: [
|
|
16
|
+
read_op
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
request.accept = "application/json"
|
|
20
|
+
post :receive, format: :json, kojac: content
|
|
21
|
+
|
|
22
|
+
output = JSON.parse response.body
|
|
23
|
+
output['ops'].should be_is_a Array
|
|
24
|
+
output['ops'].length.should == 1
|
|
25
|
+
op = output['ops'].first
|
|
26
|
+
result = op['results'][op['result_key']]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# from http://openhood.com/rails/rails%203/2010/07/20/add-routes-at-runtime-rails-3/
|
|
30
|
+
def draw_routes(&block)
|
|
31
|
+
begin
|
|
32
|
+
app = Rails::Application.subclasses.first
|
|
33
|
+
_routes = app.routes
|
|
34
|
+
_routes.disable_clear_and_finalize = true
|
|
35
|
+
_routes.clear!
|
|
36
|
+
app.routes_reloader.paths.each{ |path| load(path) }
|
|
37
|
+
_routes.draw(&block)
|
|
38
|
+
ActiveSupport.on_load(:action_controller) { _routes.finalize! }
|
|
39
|
+
ensure
|
|
40
|
+
_routes.disable_clear_and_finalize = false
|
|
41
|
+
end
|
|
42
|
+
end
|
|
File without changes
|
|
File without changes
|
data/spec/ember_factory_spec.js
CHANGED
|
@@ -27,7 +27,7 @@ describe("Ember Factory", function() {
|
|
|
27
27
|
nothing: null
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
var emberObject = factory.
|
|
30
|
+
var emberObject = factory.manufacture(source,'order_item__51');
|
|
31
31
|
expect(emberObject).toBeDefined();
|
|
32
32
|
expect(emberObject.constructor).toBe(OrderItem);
|
|
33
33
|
expect(emberObject.id).toBe(source.id);
|
data/spec/ember_model_spec.js
CHANGED
|
@@ -4,23 +4,26 @@ describe("Kojac Ember Model", function() {
|
|
|
4
4
|
name: String,
|
|
5
5
|
purchases: Int,
|
|
6
6
|
weight: Number,
|
|
7
|
-
isMember: Boolean
|
|
7
|
+
isMember: Boolean,
|
|
8
|
+
start: Date
|
|
8
9
|
};
|
|
9
10
|
var Product1 = Kojac.EmberModel.extend(product1Values);
|
|
10
11
|
|
|
12
|
+
// can only include static values here ie not a Date or other object
|
|
11
13
|
var product2Values = {
|
|
12
14
|
name: 'John',
|
|
13
15
|
purchases: 56,
|
|
14
16
|
weight: 12.3,
|
|
15
17
|
isMember: true
|
|
16
|
-
}
|
|
18
|
+
};
|
|
17
19
|
var Product2 = Kojac.EmberModel.extend(product2Values);
|
|
18
20
|
|
|
19
21
|
var wrongProductValues = {
|
|
20
22
|
name: 123,
|
|
21
23
|
purchases: 2.34,
|
|
22
24
|
weight: [],
|
|
23
|
-
isMember: 1
|
|
25
|
+
isMember: 1,
|
|
26
|
+
start: "2010-06-01T23:59:56+08:00"
|
|
24
27
|
};
|
|
25
28
|
|
|
26
29
|
it("extend and check null values", function() {
|
|
@@ -29,6 +32,7 @@ describe("Kojac Ember Model", function() {
|
|
|
29
32
|
expect(product.purchases).toBeNull();
|
|
30
33
|
expect(product.weight).toBeNull();
|
|
31
34
|
expect(product.isMember).toBeNull();
|
|
35
|
+
expect(product.start).toBeNull();
|
|
32
36
|
expect(Product1.getDefinitions()).toEqual(product1Values);
|
|
33
37
|
});
|
|
34
38
|
|
|
@@ -79,14 +83,17 @@ describe("Kojac Ember Model", function() {
|
|
|
79
83
|
});
|
|
80
84
|
|
|
81
85
|
it("check create values requiring conversion", function() {
|
|
86
|
+
moment().zone(8);
|
|
82
87
|
var product = Product1.create(wrongProductValues);
|
|
83
88
|
expect(product.get('name')).toBe('123');
|
|
84
89
|
expect(product.get('purchases')).toBe(2);
|
|
85
90
|
expect(product.get('weight')).toBe(null);
|
|
86
91
|
expect(product.get('isMember')).toBe(true);
|
|
92
|
+
expect(product.get('start')).toEqual(new Date(2010,5,1,23,59,56));
|
|
87
93
|
});
|
|
88
94
|
|
|
89
95
|
it("check set values requiring conversion", function() {
|
|
96
|
+
moment().zone(8);
|
|
90
97
|
var product = Product1.create();
|
|
91
98
|
for (p in wrongProductValues)
|
|
92
99
|
product.set(p,wrongProductValues[p]);
|
|
@@ -95,15 +102,18 @@ describe("Kojac Ember Model", function() {
|
|
|
95
102
|
expect(product.purchases).toBe(2);
|
|
96
103
|
expect(product.weight).toBe(null);
|
|
97
104
|
expect(product.isMember).toBe(true);
|
|
105
|
+
expect(product.start).toEqual(new Date(2010,5,1,23,59,56));
|
|
98
106
|
});
|
|
99
107
|
|
|
100
108
|
it("check setProperties values requiring conversion", function() {
|
|
109
|
+
moment().zone(8);
|
|
101
110
|
var product = Product1.create();
|
|
102
111
|
product.setProperties(wrongProductValues);
|
|
103
112
|
expect(product.name).toBe('123');
|
|
104
113
|
expect(product.purchases).toBe(2);
|
|
105
114
|
expect(product.weight).toBe(null);
|
|
106
115
|
expect(product.isMember).toBe(true);
|
|
116
|
+
expect(product.start).toEqual(new Date(2010,5,1,23,59,56));
|
|
107
117
|
});
|
|
108
118
|
|
|
109
119
|
it("check init", function() {
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
describe("EmberModel toJsono", function() {
|
|
2
|
+
|
|
3
|
+
var productProperties = {
|
|
4
|
+
name: String,
|
|
5
|
+
purchases: Int,
|
|
6
|
+
weight: Number,
|
|
7
|
+
isMember: Boolean,
|
|
8
|
+
embedded: null
|
|
9
|
+
};
|
|
10
|
+
var Product = Kojac.EmberModel.extend(productProperties);
|
|
11
|
+
|
|
12
|
+
var Summer = Kojac.EmberModel.extend({
|
|
13
|
+
a: Int,
|
|
14
|
+
b: Int,
|
|
15
|
+
sum: function() {
|
|
16
|
+
return this.get('a') + this.get('b');
|
|
17
|
+
}.property('a','b')
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
var productValues1 = {
|
|
21
|
+
name: 'John',
|
|
22
|
+
purchases: 56,
|
|
23
|
+
weight: 12.3,
|
|
24
|
+
isMember: true,
|
|
25
|
+
embedded: null
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
var values2 = {
|
|
29
|
+
colour: 'crimson',
|
|
30
|
+
content: 23,
|
|
31
|
+
explosive: true,
|
|
32
|
+
embedded: null
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
var simpleArray = [3,4,5,true,false,"efg",3.4,null];
|
|
36
|
+
|
|
37
|
+
it("simple values", function() {
|
|
38
|
+
_.each(simpleArray,function(v){
|
|
39
|
+
expect(Kojac.Utils.toJsono(v)).toEqual(v);
|
|
40
|
+
})
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("array of simple values", function() {
|
|
44
|
+
var jsono = Kojac.Utils.toJsono(simpleArray);
|
|
45
|
+
expect(jsono).toEqual(simpleArray);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("simple object", function() {
|
|
49
|
+
var jsono = Kojac.Utils.toJsono(productValues1);
|
|
50
|
+
expect(jsono).toEqual(productValues1);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("simple object with embedded model", function() {
|
|
54
|
+
var input = _.clone(values2);
|
|
55
|
+
input.embedded = Product.create(productValues1);
|
|
56
|
+
|
|
57
|
+
var actual = Kojac.Utils.toJsono(input);
|
|
58
|
+
|
|
59
|
+
var expected = _.clone(values2);
|
|
60
|
+
expected.embedded = productValues1;
|
|
61
|
+
|
|
62
|
+
expect(actual).toEqual(expected);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("simple model with embedded object", function() {
|
|
66
|
+
var input = Product.create(productValues1);
|
|
67
|
+
input.embedded = values2;
|
|
68
|
+
|
|
69
|
+
var actual = Kojac.Utils.toJsono(input);
|
|
70
|
+
|
|
71
|
+
var expected = _.clone(productValues1);
|
|
72
|
+
expected.embedded = values2;
|
|
73
|
+
|
|
74
|
+
expect(actual).toEqual(expected);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("simple model", function() {
|
|
78
|
+
var product1 = Product.create(productValues1);
|
|
79
|
+
var jsono = Kojac.Utils.toJsono(product1);
|
|
80
|
+
expect(jsono).toEqual(productValues1)
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("model with include", function(){
|
|
84
|
+
var input = Summer.create({a: 17,b:28});
|
|
85
|
+
var actual = Kojac.Utils.toJsono(input,{include: ['sum']});
|
|
86
|
+
expect(actual).toEqual({a: 17,b:28,sum:45});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("date toJsono", function() {
|
|
90
|
+
var actual = Kojac.Utils.toJsono(new Date(2011,0,1));
|
|
91
|
+
expect(actual).toEqual("2010-12-31T16:00:00.000Z");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("date serialization", function() {
|
|
95
|
+
var Product = Kojac.EmberModel.extend({
|
|
96
|
+
dob: Date
|
|
97
|
+
});
|
|
98
|
+
var product = Product.create({dob: new Date(2011,0,1)});
|
|
99
|
+
var jsono = Kojac.Utils.toJsono(product);
|
|
100
|
+
expect(jsono).toEqual({
|
|
101
|
+
dob: "2010-12-31T16:00:00.000Z"
|
|
102
|
+
})
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
describe("Error Handling (requires web server)", function() {
|
|
2
|
+
|
|
3
|
+
beforeEach(function () {
|
|
4
|
+
});
|
|
5
|
+
|
|
6
|
+
it("handles not found", function() {
|
|
7
|
+
var App = {};
|
|
8
|
+
App.cache = new Kojac.Cache();
|
|
9
|
+
App.kojac = new Kojac.Core({
|
|
10
|
+
cache: App.cache,
|
|
11
|
+
remoteProvider: new Kojac.RemoteProvider({
|
|
12
|
+
serverPath: '/sdfhjdfsdfhsjkhjdfskhjdfsjhkdfsdsf',
|
|
13
|
+
timeout: 2000
|
|
14
|
+
})
|
|
15
|
+
});
|
|
16
|
+
var op;
|
|
17
|
+
var req;
|
|
18
|
+
runs(function() {
|
|
19
|
+
req = App.kojac.readRequest(['order_item']).done(function(aKR){
|
|
20
|
+
console.log(aKR);
|
|
21
|
+
}).fail(function(aKR){
|
|
22
|
+
console.log(aKR);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
waitsFor(function() {
|
|
26
|
+
return req.isResolved() || req.isRejected()
|
|
27
|
+
}, "request done", 3000);
|
|
28
|
+
runs(function() {
|
|
29
|
+
var e = req.error;
|
|
30
|
+
expect(e).toBeDefined();
|
|
31
|
+
expect(e.format).toEqual('KojacError');
|
|
32
|
+
expect(e.http_code).toEqual(404);
|
|
33
|
+
expect(e.kind).toEqual('NotFound');
|
|
34
|
+
expect(e.message).toBeTruthy();
|
|
35
|
+
expect(e.debug_message).toBeTruthy();
|
|
36
|
+
expect(e.xhr).toBeTruthy();
|
|
37
|
+
expect(e.headers).toBeTruthy();
|
|
38
|
+
expect(e.response).toBeTruthy();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("Handles application Error", function(){
|
|
43
|
+
var App = {};
|
|
44
|
+
App.cache = new Kojac.Cache();
|
|
45
|
+
App.kojac = new Kojac.Core({
|
|
46
|
+
cache: App.cache,
|
|
47
|
+
remoteProvider: new Kojac.RemoteProvider({
|
|
48
|
+
//serverPath: '/sdfhjdfsdfhsjkhjdfskhjdfsjhkdfsdsf',
|
|
49
|
+
timeout: 2000
|
|
50
|
+
})
|
|
51
|
+
});
|
|
52
|
+
App.kojac.remoteProvider.mockReadOperationHandler = function(aOp) {
|
|
53
|
+
aOp.error = {
|
|
54
|
+
format: 'AppError',
|
|
55
|
+
kind: 'ValidationError',
|
|
56
|
+
errors: [{
|
|
57
|
+
field: 'name',
|
|
58
|
+
message: 'must be given'
|
|
59
|
+
}]
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
var op;
|
|
63
|
+
var req;
|
|
64
|
+
runs(function() {
|
|
65
|
+
req = App.kojac.executeRequest(['calc']).done(function(aKR){
|
|
66
|
+
console.log(aKR);
|
|
67
|
+
}).fail(function(aKR){
|
|
68
|
+
console.log(aKR);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
waitsFor(function() {
|
|
72
|
+
return req.isResolved() || req.isRejected()
|
|
73
|
+
}, "request done", 3000);
|
|
74
|
+
runs(function() {
|
|
75
|
+
expect(req.error).toBeDefined();
|
|
76
|
+
expect(req.error).toEqual(req.ops[0].error);
|
|
77
|
+
expect(req.results).toBeUndefined();
|
|
78
|
+
expect(req.error).toEqual(
|
|
79
|
+
{
|
|
80
|
+
format: 'AppError',
|
|
81
|
+
kind: 'ValidationError',
|
|
82
|
+
errors: [{
|
|
83
|
+
field: 'name',
|
|
84
|
+
message: 'must be given'
|
|
85
|
+
}]
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// underscore_plus.js
|
|
2
|
-
// (c)
|
|
2
|
+
// (c) 2013 Gary McGhee, Buzzware Solutions
|
|
3
3
|
// https://github.com/buzzware/underscore_plus
|
|
4
4
|
// Underscore may be freely distributed under the MIT license.
|
|
5
5
|
|
|
@@ -120,6 +120,24 @@ _.removeKey = function(aObject,aKey) {
|
|
|
120
120
|
return result;
|
|
121
121
|
};
|
|
122
122
|
|
|
123
|
+
// for Twitter Typeahead. See http://code.whatcould.com/2013/02/28/underscore-templates-with-twitter-typeahead.html
|
|
124
|
+
_.templateTypeaheadCompatible = {
|
|
125
|
+
compile: function(str) {
|
|
126
|
+
return new _.templateTypeaheadCompiled(str);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
_.templateTypeaheadCompiled = (function() {
|
|
130
|
+
function templateTypeaheadCompiled(str) {
|
|
131
|
+
this.compiledTemplate = _.template(str);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
templateTypeaheadCompiled.prototype.render = function(context) {
|
|
135
|
+
return this.compiledTemplate(context);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return templateTypeaheadCompiled;
|
|
139
|
+
})();
|
|
140
|
+
|
|
123
141
|
// // find all dynamic objects in an array that have matching values for the contents of aAndCriteria
|
|
124
142
|
// public static function ObjectArrayFind(aArray:Array, aAndCriteria:Object):Array {
|
|
125
143
|
// return aArray.filter(
|
|
@@ -202,6 +220,7 @@ _.bite = function(aString, aPrefix) {
|
|
|
202
220
|
return (i===0) ? aString.substring(aPrefix.length) : aString;
|
|
203
221
|
}
|
|
204
222
|
|
|
223
|
+
// undefined null function arguments string number date regexp object
|
|
205
224
|
_.typeOf = function(aSomething) {
|
|
206
225
|
if (aSomething===undefined)
|
|
207
226
|
return 'undefined';
|
|
@@ -237,25 +256,315 @@ _.cloneComplexValues = function (aDestination, aSource) {
|
|
|
237
256
|
|
|
238
257
|
_.originalClone = _.originalClone || _.clone;
|
|
239
258
|
// Create a copy (shallow or deep) of an object from https://github.com/cederberg/underscore/commits/feature-clone
|
|
240
|
-
_.clone = function(obj, deep) {
|
|
241
|
-
if (!deep)
|
|
259
|
+
_.clone = function(obj, deep, ignoreFns) {
|
|
260
|
+
if (!deep && !ignoreFns)
|
|
242
261
|
return _.originalClone(obj);
|
|
243
262
|
if (!_.isObject(obj) || _.isFunction(obj)) return obj;
|
|
244
263
|
if (_.isDate(obj)) return new Date(obj.getTime());
|
|
245
264
|
if (_.isRegExp(obj)) return new RegExp(obj.source, obj.toString().replace(/.*\//, ""));
|
|
246
265
|
var isArr = (_.isArray(obj) || _.isArguments(obj));
|
|
247
266
|
if (deep) {
|
|
248
|
-
var func = function
|
|
249
|
-
if (
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
267
|
+
var func = function(memo, value, key) {
|
|
268
|
+
if (!ignoreFns || !_.isFunction(value)) {
|
|
269
|
+
if (isArr)
|
|
270
|
+
memo.push(_.clone(value, true));
|
|
271
|
+
else
|
|
272
|
+
memo[key] = _.clone(value, true);
|
|
273
|
+
}
|
|
253
274
|
return memo;
|
|
254
275
|
};
|
|
255
276
|
return _.reduce(obj, func, isArr ? [] : {});
|
|
256
277
|
} else {
|
|
257
|
-
|
|
278
|
+
var result;
|
|
279
|
+
if (isArr)
|
|
280
|
+
result = slice.call(obj);
|
|
281
|
+
else {
|
|
282
|
+
result = {};
|
|
283
|
+
var v;
|
|
284
|
+
for (var p in obj) {
|
|
285
|
+
v = obj[p];
|
|
286
|
+
if (ignoreFns && _.isFunction(v))
|
|
287
|
+
continue;
|
|
288
|
+
result[p] = v;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return result;
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
_.randomString = function (aLength, aCharSet) {
|
|
296
|
+
var result = [];
|
|
297
|
+
|
|
298
|
+
aLength = aLength || 5;
|
|
299
|
+
aCharSet = aCharSet || 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
300
|
+
aCharSet = aCharSet.split('');
|
|
301
|
+
|
|
302
|
+
while (--aLength) {
|
|
303
|
+
result.push(aCharSet[Math.floor(Math.random() * aCharSet.length)]);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return result.join('');
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
_.formatNumber = function(aValue,aDecimals) {
|
|
310
|
+
if (aValue===null)
|
|
311
|
+
return '';
|
|
312
|
+
if (!aDecimals && aDecimals!==0)
|
|
313
|
+
aDecimals = 2;
|
|
314
|
+
return aValue.toFixed(aDecimals);
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
_.groupDigits = function(x) {
|
|
318
|
+
var parts = x.toString().split(".");
|
|
319
|
+
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
320
|
+
return parts.join(".");
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
_.sum = function() {
|
|
324
|
+
var result = NaN;
|
|
325
|
+
for (var i=0;i<arguments.length;i++) {
|
|
326
|
+
var v = arguments[i];
|
|
327
|
+
if (!_.isNumber(v))
|
|
328
|
+
return NaN;
|
|
329
|
+
if (i==0)
|
|
330
|
+
result = 0;
|
|
331
|
+
result += v;
|
|
332
|
+
}
|
|
333
|
+
return result;
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
_.formatCurrency = function(aNumber,aPrecision,aSymbol) {
|
|
337
|
+
if (!aPrecision)
|
|
338
|
+
aPrecision = 0;
|
|
339
|
+
if (!aSymbol)
|
|
340
|
+
aSymbol = '$';
|
|
341
|
+
return _.isFinite(aNumber) ? aSymbol+_.groupDigits(_.formatNumber(aNumber,aPrecision)) : '';
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
_.formatPercent = function(aNumber,aPrecision,aSymbol) {
|
|
345
|
+
if (!aPrecision)
|
|
346
|
+
aPrecision = 0;
|
|
347
|
+
if (!aSymbol)
|
|
348
|
+
aSymbol = '%';
|
|
349
|
+
return _.isFinite(aNumber) ? _.groupDigits(_.formatNumber(aNumber*100.0,aPrecision))+aSymbol : '';
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
_.toNull = function(aValue,aDefault) {
|
|
353
|
+
if (arguments.length==1)
|
|
354
|
+
aDefault = null;
|
|
355
|
+
return ((aValue===null) || (aValue===undefined) || (aValue===[]) || (aValue===0) || (aValue==={}) || _.isNaN(aValue)) ? aDefault : aValue;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
_.toFinite = function(aValue,aDefault) {
|
|
359
|
+
return _.toNumber(aValue,aDefault);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
_.toInt = function(aValue,aDefault) {
|
|
363
|
+
if (arguments.length==1)
|
|
364
|
+
aDefault = null;
|
|
365
|
+
var t = _.typeOf(aValue);
|
|
366
|
+
var result;
|
|
367
|
+
switch(t) {
|
|
368
|
+
case 'undefined':
|
|
369
|
+
case 'null':
|
|
370
|
+
case 'array':
|
|
371
|
+
case 'object':
|
|
372
|
+
case 'function':
|
|
373
|
+
case 'class':
|
|
374
|
+
case 'instance':
|
|
375
|
+
case 'error':
|
|
376
|
+
result = aDefault;
|
|
377
|
+
break;
|
|
378
|
+
case 'string':
|
|
379
|
+
case 'number':
|
|
380
|
+
aValue = Number(aValue);
|
|
381
|
+
if (!_.isFinite(aValue))
|
|
382
|
+
result = aDefault;
|
|
383
|
+
else
|
|
384
|
+
result = Math.round(aValue);
|
|
385
|
+
break;
|
|
386
|
+
case 'boolean':
|
|
387
|
+
result = aValue ? 1 : 0;
|
|
388
|
+
break;
|
|
389
|
+
default:
|
|
390
|
+
result = aDefault;
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
return result;
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
_.toNumber = function(aValue,aDefault) {
|
|
397
|
+
if (arguments.length==1)
|
|
398
|
+
aDefault = null;
|
|
399
|
+
var t = _.typeOf(aValue);
|
|
400
|
+
var result;
|
|
401
|
+
switch(t) {
|
|
402
|
+
case 'undefined':
|
|
403
|
+
case 'null':
|
|
404
|
+
case 'array':
|
|
405
|
+
case 'object':
|
|
406
|
+
case 'function':
|
|
407
|
+
case 'class':
|
|
408
|
+
case 'instance':
|
|
409
|
+
case 'error':
|
|
410
|
+
result = aDefault;
|
|
411
|
+
break;
|
|
412
|
+
case 'string':
|
|
413
|
+
case 'number':
|
|
414
|
+
aValue = Number(aValue);
|
|
415
|
+
if (!_.isFinite(aValue))
|
|
416
|
+
result = aDefault;
|
|
417
|
+
else
|
|
418
|
+
result = aValue;
|
|
419
|
+
break;
|
|
420
|
+
case 'boolean':
|
|
421
|
+
result = aValue ? 1 : 0;
|
|
422
|
+
break;
|
|
423
|
+
default:
|
|
424
|
+
result = aDefault;
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
return result;
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
_.toBoolean = function(aValue,aDefault) {
|
|
431
|
+
if (arguments.length==1)
|
|
432
|
+
aDefault = false;
|
|
433
|
+
if (aValue===true || aValue===false)
|
|
434
|
+
return aValue;
|
|
435
|
+
if (aValue===0)
|
|
436
|
+
return false;
|
|
437
|
+
if (_.isString(aValue)) {
|
|
438
|
+
var t = Number(aValue);
|
|
439
|
+
if (_.isFinite(t)) {
|
|
440
|
+
return !!t;
|
|
441
|
+
} else {
|
|
442
|
+
t = aValue.toLowerCase();
|
|
443
|
+
if (t==="true" || t==="yes" || t==="on")
|
|
444
|
+
return true;
|
|
445
|
+
if (t==="false" || t==="no" || t==="off")
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
} else if (_.isNumber(aValue)) {
|
|
449
|
+
return !!aValue;
|
|
450
|
+
}
|
|
451
|
+
return aDefault;
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
// converts simple object to array of object with id and name fields
|
|
455
|
+
// eg.
|
|
456
|
+
// _.expand_options({"cash": "Cash","consumer_mortgage": "Consumer Mortgage")
|
|
457
|
+
// => [{id: "cash", name:"Cash"}, {id: "consumer_mortgage",name: "Consumer Mortgage"}]
|
|
458
|
+
// _.expand_options(["cash","consumer_mortgage"])
|
|
459
|
+
// => [{id: "cash", name:"cash"}, {id: "consumer_mortgage",name: "consumer_mortgage"}]
|
|
460
|
+
_.expand_options = function(aObject,aIdField,aNameField) {
|
|
461
|
+
if (!aIdField)
|
|
462
|
+
aIdField = 'id';
|
|
463
|
+
if (!aNameField)
|
|
464
|
+
aNameField = 'name';
|
|
465
|
+
if (_.isArray(aObject)) {
|
|
466
|
+
return _.map(aObject,function(v){
|
|
467
|
+
var result = {};
|
|
468
|
+
result[aIdField] = v;
|
|
469
|
+
result[aNameField] = String(v);
|
|
470
|
+
return result;
|
|
471
|
+
});
|
|
472
|
+
} else {
|
|
473
|
+
return _.map(aObject,function(v,k){
|
|
474
|
+
var result = {};
|
|
475
|
+
result[aIdField] = k;
|
|
476
|
+
result[aNameField] = v;
|
|
477
|
+
return result;
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
// returns array of keys on object beginning with aPrefix
|
|
483
|
+
_.keysWithPrefix = function(aObject,aPrefix) {
|
|
484
|
+
var results = [];
|
|
485
|
+
var keys = _.keys(aObject);
|
|
486
|
+
for (var i=0;i<keys.length;i++) {
|
|
487
|
+
var k = keys[i];
|
|
488
|
+
if (!_.beginsWith(k,aPrefix))
|
|
489
|
+
continue;
|
|
490
|
+
results.push(k);
|
|
258
491
|
}
|
|
492
|
+
return results;
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
// returns copy of object containing only properties beginning with aPrefix
|
|
496
|
+
_.pickWithPrefix = function(aObject,aPrefix) {
|
|
497
|
+
var result = {};
|
|
498
|
+
_.each(aObject,function(v, k){
|
|
499
|
+
if (!_.beginsWith(k,aPrefix))
|
|
500
|
+
return;
|
|
501
|
+
result[k] = v;
|
|
502
|
+
});
|
|
503
|
+
return result;
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
_.round = function(aNumber,aDecimals) {
|
|
507
|
+
var mult = Math.pow(10, aDecimals);
|
|
508
|
+
return Math.round(aNumber*mult)/mult;
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
// copies properties, excluding functions
|
|
512
|
+
_.copyProperties = function(aDest,aSource,aProperties,aExclude) {
|
|
513
|
+
var p;
|
|
514
|
+
var v;
|
|
515
|
+
if (!aDest)
|
|
516
|
+
aDest = {};
|
|
517
|
+
if (aProperties && !_.isArray(aProperties))
|
|
518
|
+
aProperties = [aProperties];
|
|
519
|
+
if (aExclude && !_.isArray(aExclude))
|
|
520
|
+
aExclude = [aExclude];
|
|
521
|
+
if (aProperties) {
|
|
522
|
+
for (var i=0;i<aProperties.length;i++) {
|
|
523
|
+
p = aProperties[i];
|
|
524
|
+
v = aSource[p];
|
|
525
|
+
if (_.isFunction(v))
|
|
526
|
+
continue;
|
|
527
|
+
if (aExclude && aExclude.indexOf(p)>=0)
|
|
528
|
+
continue;
|
|
529
|
+
if (p in aSource) aDest[p] = v;
|
|
530
|
+
}
|
|
531
|
+
} else {
|
|
532
|
+
for (p in aSource) {
|
|
533
|
+
v = aSource[p];
|
|
534
|
+
if (_.isFunction(v))
|
|
535
|
+
continue;
|
|
536
|
+
if (aExclude && aExclude.indexOf(p)>=0)
|
|
537
|
+
continue;
|
|
538
|
+
if (p in aSource) aDest[p] = v;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
return aDest;
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
var formatRegexes = {};
|
|
546
|
+
// format a string
|
|
547
|
+
// Usage: _.format("{0} i{2}a night{1}", "This", "mare", "s ");
|
|
548
|
+
// Could support _.format("{number} {street}, {suburb}", {number: 27, street: "Constant St", suburb: "Highgate"}); - might already
|
|
549
|
+
// see http://stackoverflow.com/questions/2534803/string-format-in-javascript/2534870#2534870
|
|
550
|
+
_.format = function(aFormat, aValues) {
|
|
551
|
+
var src,v;
|
|
552
|
+
if (_.isObject(aValues)) {
|
|
553
|
+
for (p in aValues)
|
|
554
|
+
aFormat = aFormat.replace(formatRegexes[p] || (formatRegexes[p] = RegExp("\\{" + p + "\\}", "gm")), aValues[p]);
|
|
555
|
+
} else {
|
|
556
|
+
for (var args = arguments, i = args.length; --i;)
|
|
557
|
+
aFormat = aFormat.replace(formatRegexes[i - 1] || (formatRegexes[i - 1] = RegExp("\\{" + (i - 1) + "\\}", "gm")), args[i]);
|
|
558
|
+
}
|
|
559
|
+
return aFormat;
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
_.randomInt = function(aValues) {
|
|
563
|
+
return Math.floor(Math.random()*aValues);
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
_.randomIntRange = function(aMin,aMax) {
|
|
567
|
+
return aMin + Math.floor(Math.random()*(aMax-aMin+1));
|
|
259
568
|
};
|
|
260
569
|
|
|
261
570
|
}).call(this);
|