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
data/spec/kojac_caching_spec.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
describe("Kojac Caching", function() {
|
|
2
2
|
|
|
3
3
|
beforeEach(function () {
|
|
4
|
-
this.cache = new Kojac.
|
|
4
|
+
this.cache = new Kojac.Cache();
|
|
5
5
|
this.kojac = new Kojac.Core({
|
|
6
6
|
cache: this.cache,
|
|
7
7
|
remoteProvider: new Kojac.RemoteProvider({useMockFileValues: true,mockFilePath: 'mockjson/'})
|
|
@@ -37,6 +37,7 @@ describe("Kojac Caching", function() {
|
|
|
37
37
|
waitsFor(function() { return readReq.isResolved(); }, "request done", 3000);
|
|
38
38
|
runs(function() {
|
|
39
39
|
expect(readReq.results).toEqual(products_records); // should equal normal response
|
|
40
|
+
expect(this.kojac.cache.products).toEqual(products_records.products)
|
|
40
41
|
products_server_response = {}; // kill server response - non-cache reads should return empty now
|
|
41
42
|
readReq = this.kojac.readRequest('products',{cacheResults: false}); // read from server to check this, and don't store result in cache
|
|
42
43
|
});
|
|
@@ -44,6 +45,7 @@ describe("Kojac Caching", function() {
|
|
|
44
45
|
runs(function() {
|
|
45
46
|
expect(readReq.results).toEqual({}); // indeed, server now returns empty, but lets try cache
|
|
46
47
|
readReq = this.kojac.cacheReadRequest('products'); // read from cache
|
|
48
|
+
console.log('after cacheReadRequest');
|
|
47
49
|
});
|
|
48
50
|
waitsFor(function() { return readReq.isResolved(); }, "request done", 3000);
|
|
49
51
|
runs(function() {
|
data/spec/kojac_mock_spec.js
CHANGED
|
@@ -13,7 +13,7 @@ describe("Kojac Mock", function() {
|
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
beforeEach(function () {
|
|
16
|
-
App.cache = new Kojac.
|
|
16
|
+
App.cache = new Kojac.Cache();
|
|
17
17
|
var factory = new Kojac.ObjectFactory();
|
|
18
18
|
factory.register([
|
|
19
19
|
[/^order_item(__|$)/,OrderItem],
|
|
@@ -195,7 +195,7 @@ describe("Kojac Mock", function() {
|
|
|
195
195
|
});
|
|
196
196
|
});
|
|
197
197
|
|
|
198
|
-
it("try executing", function() {
|
|
198
|
+
it("try executing, should not be cached", function() {
|
|
199
199
|
var op;
|
|
200
200
|
var req;
|
|
201
201
|
App.kojac.remoteProvider.mockReadOperationHandler = function(aOp) {
|
|
@@ -205,7 +205,7 @@ describe("Kojac Mock", function() {
|
|
|
205
205
|
};
|
|
206
206
|
};
|
|
207
207
|
runs(function() {
|
|
208
|
-
req = App.kojac.executeRequest('results',{a: 1,b:2}
|
|
208
|
+
req = App.kojac.executeRequest('results',{a: 1,b:2});
|
|
209
209
|
expect(req).toBeDefined('ops');
|
|
210
210
|
expect(req).toBeDefined('options');
|
|
211
211
|
expect(req.ops.length).toEqual(1);
|
|
@@ -223,7 +223,7 @@ describe("Kojac Mock", function() {
|
|
|
223
223
|
expect(op.result_key).toEqual(op.key);
|
|
224
224
|
expect(typeof(op.results)).toBe('object');
|
|
225
225
|
expect(op.results.results).toEqual([1,2,3]);
|
|
226
|
-
expect(App.cache.results).
|
|
226
|
+
expect(App.cache.results).toBeUndefined();
|
|
227
227
|
});
|
|
228
228
|
});
|
|
229
229
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
describe("Kojac Operations", function() {
|
|
2
2
|
|
|
3
3
|
beforeEach(function () {
|
|
4
|
-
this.cache =
|
|
4
|
+
this.cache = Kojac.EmberCache.create();
|
|
5
5
|
var factory = new Kojac.ObjectFactory();
|
|
6
6
|
this.kojac = new Kojac.Core({
|
|
7
7
|
cache: this.cache,
|
|
@@ -22,17 +22,17 @@ describe("Kojac Operations", function() {
|
|
|
22
22
|
expect(op.verb).toEqual('READ');
|
|
23
23
|
expect(op.key).toEqual('order_item');
|
|
24
24
|
expect(op.result_key).toEqual(op.key);
|
|
25
|
-
expect(op.results).toEqual(
|
|
25
|
+
expect(op.results).toEqual(null);
|
|
26
26
|
op = req.ops[1];
|
|
27
27
|
expect(op.verb).toEqual('READ');
|
|
28
28
|
expect(op.key).toEqual('product');
|
|
29
29
|
expect(op.result_key).toEqual(op.key);
|
|
30
|
-
expect(op.results).toEqual(
|
|
30
|
+
expect(op.results).toEqual(null);
|
|
31
31
|
op = req.ops[2];
|
|
32
32
|
expect(op.verb).toEqual('READ');
|
|
33
33
|
expect(op.key).toEqual('order_item__50');
|
|
34
34
|
expect(op.result_key).toEqual(op.key);
|
|
35
|
-
expect(op.results).toEqual(
|
|
35
|
+
expect(op.results).toEqual(null);
|
|
36
36
|
});
|
|
37
37
|
});
|
|
38
38
|
});
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
describe("LocalStorageRemoteProvider", function() {
|
|
2
|
+
|
|
3
|
+
var Waiter = Kojac.Model.extend({
|
|
4
|
+
id: Int,
|
|
5
|
+
name: String,
|
|
6
|
+
phone: String,
|
|
7
|
+
gender: String, // M or F
|
|
8
|
+
option: String,
|
|
9
|
+
queued: Number,
|
|
10
|
+
smsSent: false,
|
|
11
|
+
completed: false,
|
|
12
|
+
created_at: String,
|
|
13
|
+
sendingSms: false
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
beforeEach(function () {
|
|
17
|
+
$.jStorage.flush();
|
|
18
|
+
this.cache = new Kojac.Cache;
|
|
19
|
+
|
|
20
|
+
var factory = new Kojac.ObjectFactory();
|
|
21
|
+
factory.register([
|
|
22
|
+
[/^waiters(__|$)/,Waiter]
|
|
23
|
+
]);
|
|
24
|
+
this.kojac = new Kojac.Core({
|
|
25
|
+
cache: this.cache,
|
|
26
|
+
remoteProvider: new Kojac.LocalStorageRemoteProvider(),
|
|
27
|
+
objectFactory: factory
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("try create and then read item", function() {
|
|
32
|
+
var op;
|
|
33
|
+
var req;
|
|
34
|
+
var newKey;
|
|
35
|
+
var createValues = {
|
|
36
|
+
name: 'John',
|
|
37
|
+
phone: '0412 123456'
|
|
38
|
+
};
|
|
39
|
+
runs(function() {
|
|
40
|
+
var waiter = new Waiter(createValues);
|
|
41
|
+
req = this.kojac.createRequest({waiters: waiter});
|
|
42
|
+
});
|
|
43
|
+
waitsFor(function() { return req.isResolved() }, "request done", 3000);
|
|
44
|
+
runs(function() {
|
|
45
|
+
expect(req.ops).toBeDefined();
|
|
46
|
+
expect(req.options).toBeDefined();
|
|
47
|
+
expect(req.ops.length).toEqual(1);
|
|
48
|
+
op = req.ops[0];
|
|
49
|
+
expect(op.verb).toEqual('CREATE');
|
|
50
|
+
expect(op.key).toEqual('waiters');
|
|
51
|
+
newKey = op.result_key;
|
|
52
|
+
expect(op.result_key).not.toEqual(op.key);
|
|
53
|
+
var parts = keySplit(op.result_key);
|
|
54
|
+
expect(parts[0]).toEqual('waiters');
|
|
55
|
+
var id = _.toInt(parts[1]);
|
|
56
|
+
expect(id).toBeGreaterThan(Math.pow(2,32));
|
|
57
|
+
expect(id).toBeLessThan(Math.pow(2,52));
|
|
58
|
+
expect(op.result.id).toEqual(id);
|
|
59
|
+
expect(op.results).not.toEqual({});
|
|
60
|
+
expect(op.result instanceof Waiter).toBeTruthy();
|
|
61
|
+
|
|
62
|
+
req = this.kojac.readRequest(newKey);
|
|
63
|
+
});
|
|
64
|
+
waitsFor(function() { return req.isResolved() }, "request done", 3000);
|
|
65
|
+
runs(function() {
|
|
66
|
+
op = req.op;
|
|
67
|
+
expect(op.result_key).toEqual(newKey);
|
|
68
|
+
expect(op.result.name).toEqual(createValues.name);
|
|
69
|
+
expect(op.result.phone).toEqual(createValues.phone);
|
|
70
|
+
expect(op.result instanceof Waiter).toBeTruthy();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("try create and then read collection", function() {
|
|
75
|
+
var op;
|
|
76
|
+
var req;
|
|
77
|
+
var newKey;
|
|
78
|
+
var createValues = {
|
|
79
|
+
name: 'John',
|
|
80
|
+
phone: '0412 123456'
|
|
81
|
+
};
|
|
82
|
+
runs(function() {
|
|
83
|
+
var waiter = new Waiter(createValues);
|
|
84
|
+
req = this.kojac.createRequest({waiters: waiter});
|
|
85
|
+
});
|
|
86
|
+
waitsFor(function() { return req.isResolved() }, "request done", 3000);
|
|
87
|
+
runs(function() {
|
|
88
|
+
req = this.kojac.readRequest('waiters');
|
|
89
|
+
});
|
|
90
|
+
waitsFor(function() { return req.isResolved() }, "request done", 3000);
|
|
91
|
+
runs(function() {
|
|
92
|
+
op = req.op;
|
|
93
|
+
expect(op.result_key).toEqual('waiters');
|
|
94
|
+
expect(op.result instanceof Array).toBeTruthy();
|
|
95
|
+
expect(op.result.length).toEqual(1);
|
|
96
|
+
expect(_.keys(op.results).length).toBe(2);
|
|
97
|
+
expect(op.results[op.result_key]).toBe(op.result);
|
|
98
|
+
var id = op.result[0];
|
|
99
|
+
var key = keyJoin('waiters',id);
|
|
100
|
+
var waiter = op.results[key];
|
|
101
|
+
expect(waiter).toBeDefined();
|
|
102
|
+
expect(waiter.id).toEqual(id);
|
|
103
|
+
|
|
104
|
+
expect(waiter.name).toEqual(createValues.name);
|
|
105
|
+
expect(waiter.phone).toEqual(createValues.phone);
|
|
106
|
+
expect(waiter instanceof Waiter).toBeTruthy();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("create, update then read item", function() {
|
|
111
|
+
var op;
|
|
112
|
+
var req;
|
|
113
|
+
var newKey;
|
|
114
|
+
var createValues = {
|
|
115
|
+
name: 'Sam',
|
|
116
|
+
phone: '0333 777777'
|
|
117
|
+
};
|
|
118
|
+
var updateValues = {
|
|
119
|
+
phone: '0444 444444'
|
|
120
|
+
};
|
|
121
|
+
var combinedValues = _.extend({},createValues,updateValues);
|
|
122
|
+
runs(function() {
|
|
123
|
+
var waiter = new Waiter(createValues);
|
|
124
|
+
req = this.kojac.createRequest({waiters: waiter});
|
|
125
|
+
});
|
|
126
|
+
waitsFor(function() { return req.isResolved() }, "request done", 3000);
|
|
127
|
+
runs(function() {
|
|
128
|
+
newKey = req.op.result_key;
|
|
129
|
+
req = this.kojac.updateRequest([newKey,updateValues]);
|
|
130
|
+
});
|
|
131
|
+
waitsFor(function() { return req.isResolved() }, "request done", 3000);
|
|
132
|
+
runs(function() {
|
|
133
|
+
op = req.op;
|
|
134
|
+
expect(op.result_key).toEqual(newKey);
|
|
135
|
+
expect(op.result.name).toEqual(combinedValues.name);
|
|
136
|
+
expect(op.result.phone).toEqual(combinedValues.phone);
|
|
137
|
+
expect(op.result instanceof Waiter).toBeTruthy();
|
|
138
|
+
req = this.kojac.readRequest(newKey);
|
|
139
|
+
});
|
|
140
|
+
waitsFor(function() { return req.isResolved() }, "request done", 3000);
|
|
141
|
+
runs(function() {
|
|
142
|
+
op = req.op;
|
|
143
|
+
expect(op.result_key).toEqual(newKey);
|
|
144
|
+
expect(op.result.name).toEqual(combinedValues.name);
|
|
145
|
+
expect(op.result.phone).toEqual(combinedValues.phone);
|
|
146
|
+
expect(op.result instanceof Waiter).toBeTruthy();
|
|
147
|
+
})
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("create and then destroy item", function() {
|
|
151
|
+
var op;
|
|
152
|
+
var req;
|
|
153
|
+
var newKey;
|
|
154
|
+
var createValues = {
|
|
155
|
+
name: 'John',
|
|
156
|
+
phone: '0412 123456'
|
|
157
|
+
};
|
|
158
|
+
runs(function() {
|
|
159
|
+
var waiter = new Waiter(createValues);
|
|
160
|
+
req = this.kojac.createRequest({waiters: waiter});
|
|
161
|
+
});
|
|
162
|
+
waitsFor(function() { return req.isResolved() }, "request done", 3000);
|
|
163
|
+
runs(function() {
|
|
164
|
+
newKey = req.op.result_key;
|
|
165
|
+
expect(this.kojac.cache[newKey]).toBeDefined();
|
|
166
|
+
req = this.kojac.destroyRequest(newKey);
|
|
167
|
+
});
|
|
168
|
+
waitsFor(function() { return req.isResolved() }, "request done", 3000);
|
|
169
|
+
runs(function() {
|
|
170
|
+
expect(this.kojac.cache[newKey]).toBeUndefined();
|
|
171
|
+
req = this.kojac.readRequest(newKey);
|
|
172
|
+
});
|
|
173
|
+
waitsFor(function() { return req.isResolved() }, "request done", 3000);
|
|
174
|
+
runs(function() {
|
|
175
|
+
op = req.op;
|
|
176
|
+
expect(op.result_key).toEqual(newKey);
|
|
177
|
+
expect(this.kojac.cache[newKey]).toBeUndefined();
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
|
data/spec/model_ring_spec.rb
CHANGED
|
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
|
3
3
|
describe "Simple Item1 rings" do
|
|
4
4
|
|
|
5
5
|
class Item1 < ActiveRecord::Base
|
|
6
|
-
include
|
|
6
|
+
include Concentric::Model
|
|
7
7
|
|
|
8
8
|
ring 3, :write => [:name,:address]
|
|
9
9
|
ring 3, :read => [:name,:address,:member_code]
|
|
@@ -24,7 +24,7 @@ end
|
|
|
24
24
|
describe "Item2 rings with abilities" do
|
|
25
25
|
|
|
26
26
|
class Item2 < ActiveRecord::Base
|
|
27
|
-
include
|
|
27
|
+
include Concentric::Model
|
|
28
28
|
|
|
29
29
|
ring 2, [:write,:delete]
|
|
30
30
|
ring 3, :write => [:name,:address]
|
|
@@ -13,7 +13,7 @@ describe("Operation include spec", function() {
|
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
beforeEach(function () {
|
|
16
|
-
App.cache = new Kojac.
|
|
16
|
+
App.cache = new Kojac.Cache();
|
|
17
17
|
var factory = new Kojac.ObjectFactory();
|
|
18
18
|
factory.register([
|
|
19
19
|
[/^order_item(__|$)/,OrderItem],
|
|
@@ -49,7 +49,7 @@ describe("Operation include spec", function() {
|
|
|
49
49
|
expect(req.options).toEqual({});
|
|
50
50
|
expect(req.ops.length).toEqual(1);
|
|
51
51
|
op = req.ops[0];
|
|
52
|
-
expect(op.options).toEqual({include: 'product', cacheResults: true});
|
|
52
|
+
expect(op.options).toEqual({include: 'product', cacheResults: true, manufacture : true});
|
|
53
53
|
expect(op.verb).toEqual('READ');
|
|
54
54
|
expect(op.key).toEqual('order_item__50');
|
|
55
55
|
expect(op.result_key).toEqual(op.key);
|
data/spec/run.html
CHANGED
|
@@ -9,41 +9,51 @@
|
|
|
9
9
|
<script type="text/javascript" src="external/jasmine/jasmine.js"></script>
|
|
10
10
|
<script type="text/javascript" src="external/jasmine/jasmine-html.js"></script>
|
|
11
11
|
|
|
12
|
-
<script src="
|
|
13
|
-
<script type="text/javascript" src="
|
|
14
|
-
<script type="text/javascript" src="
|
|
15
|
-
<script type="text/javascript" src="external/underscore_plus.js"></script>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
<script type="text/javascript" src="
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
12
|
+
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/json3/3.2.4/json3.min.js"></script>
|
|
13
|
+
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.js"></script>
|
|
14
|
+
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.3/underscore.js"></script>
|
|
15
|
+
<script type="text/javascript" src="../spec/external/underscore_plus.js"></script>
|
|
16
|
+
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/moment.js/2.2.1/moment.js"></script>
|
|
17
|
+
|
|
18
|
+
<!--<script type='text/javascript' src='https://raw.github.com/bitovi/steal/0a8113ec6404f3287dcf370216a439d650cc3f61/steal.js'></script>-->
|
|
19
|
+
<!--<script type="text/javascript" src="http://cloud.github.com/downloads/bitovi/canjs/can.jquery-1.1.0.js"></script>-->
|
|
20
|
+
<!--<script type="text/javascript" src="https://raw.github.com/bitovi/canjs/v1.1.0/control/plugin/plugin.js"></script>-->
|
|
21
|
+
<!--<script type="text/javascript" src="https://raw.github.com/bitovi/canjs/v1.1.0/construct/super/super.js"></script>-->
|
|
22
|
+
<!--<script type="text/javascript" src="https://raw.github.com/bitovi/canjs/v1.1.0/control/view/view.js"></script>-->
|
|
23
|
+
<!--<script type="text/javascript" src="https://raw.github.com/bitovi/canjs/v1.1.0/view/modifiers/modifiers.js"></script>-->
|
|
24
|
+
<!--<script type="text/javascript" src="https://raw.github.com/bitovi/canjs/v1.1.0/observe/attributes/attributes.js"></script>-->
|
|
25
|
+
|
|
26
|
+
<script type="text/javascript" src="http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars-v1.1.2.js"></script>
|
|
27
|
+
<script type="text/javascript" src="http://builds.emberjs.com/tags/v1.2.0/ember.js"></script>
|
|
28
|
+
|
|
29
|
+
<!--<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.0.0-rc.4/handlebars.js"></script>-->
|
|
30
|
+
<!--<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/ember.js/1.0.0-rc.6/ember.js"></script>-->
|
|
31
|
+
|
|
32
|
+
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/jStorage/0.4.4/jstorage.min.js"></script>
|
|
33
|
+
|
|
34
|
+
<!--<script type="text/javascript" src="../app/assets/javascripts/can_extensions.js"></script>-->
|
|
35
|
+
<script type="text/javascript" src="../app/assets/javascripts/kojac.js"></script>
|
|
36
|
+
<!--<script type="text/javascript" src="../app/assets/javascripts/kojac_canjs.js"></script>-->
|
|
31
37
|
|
|
32
38
|
<script type="text/javascript" src="kojac_object_spec.js"></script>
|
|
33
39
|
<script type="text/javascript" src="kojac_model_spec.js"></script>
|
|
34
40
|
<script type="text/javascript" src="handler_stack_spec.js"></script>
|
|
35
|
-
|
|
36
|
-
<script type="text/javascript" src="
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
<script type="text/javascript" src="kojac_caching_spec.js"></script>
|
|
42
|
+
<script type="text/javascript" src="error_handling_spec.js"></script>
|
|
43
|
+
<!--<script type="text/javascript" src="can_cache_spec.js"></script>-->
|
|
44
|
+
<!--<script type="text/javascript" src="can_model_spec.js"></script>-->
|
|
45
|
+
<!--<script type="text/javascript" src="can_factory_spec.js"></script>-->
|
|
39
46
|
|
|
40
|
-
<script type="text/javascript" src="../
|
|
47
|
+
<script type="text/javascript" src="../app/assets/javascripts/kojac_ember.js"></script>
|
|
41
48
|
<script type="text/javascript" src="ember_model_spec.js"></script>
|
|
42
49
|
<script type="text/javascript" src="ember_factory_spec.js"></script>
|
|
50
|
+
<script type="text/javascript" src="ember_tojsono_spec.js"></script>
|
|
43
51
|
|
|
44
52
|
<script type="text/javascript" src="kojac_operations_spec.js"></script>
|
|
45
53
|
<script type="text/javascript" src="kojac_mock_spec.js"></script>
|
|
46
54
|
<script type="text/javascript" src="operation_include_spec.js"></script>
|
|
55
|
+
<script type="text/javascript" src="type_conversion_spec.js"></script>
|
|
56
|
+
<script type="text/javascript" src="local_provider_spec.js"></script>
|
|
47
57
|
|
|
48
58
|
<script type="text/javascript">
|
|
49
59
|
(function() {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
describe("Type Conversion", function() {
|
|
2
|
+
|
|
3
|
+
it("local Date to ISO UTC String",function(){
|
|
4
|
+
var values = {
|
|
5
|
+
y: 2011,
|
|
6
|
+
mo: 0,
|
|
7
|
+
d: 1,
|
|
8
|
+
h: 0,
|
|
9
|
+
mi: 21,
|
|
10
|
+
s: 36
|
|
11
|
+
};
|
|
12
|
+
var date = new Date(values['y'], values['mo'], values['d'], values['h'], values['mi'], values['s']);
|
|
13
|
+
moment().zone(8);
|
|
14
|
+
var actual = Kojac.interpretValueAsType(date,String);
|
|
15
|
+
expect(actual).toEqual(_.format("{y}-{mo}-{d}T{h}:{mi}:{s}.000Z",{y:2010,mo:12,d:31,h:16,mi:21,s:36}));
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("ISO UTC String to local Date",function(){
|
|
19
|
+
moment().zone(8);
|
|
20
|
+
var actual = Kojac.interpretValueAsType("2010-12-31T16:21:36Z",Date);
|
|
21
|
+
expect(actual).toEqual(new Date(2011,0,1,0,21,36));
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("ISO local String to local Date",function(){
|
|
25
|
+
moment().zone(8);
|
|
26
|
+
var actual = Kojac.interpretValueAsType("2011-01-01T00:21:36+08:00",Date);
|
|
27
|
+
expect(actual).toEqual(new Date(2011,0,1,0,21,36));
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// it("Number to Date",function(){
|
|
31
|
+
//
|
|
32
|
+
// });
|
|
33
|
+
//
|
|
34
|
+
// it("Date to Number",function(){
|
|
35
|
+
//
|
|
36
|
+
// });
|
|
37
|
+
|
|
38
|
+
});
|
|
@@ -0,0 +1,950 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ----------------------------- JSTORAGE -------------------------------------
|
|
3
|
+
* Simple local storage wrapper to save data on the browser side, supporting
|
|
4
|
+
* all major browsers - IE6+, Firefox2+, Safari4+, Chrome4+ and Opera 10.5+
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) 2010 - 2012 Andris Reinman, andris.reinman@gmail.com
|
|
7
|
+
* Project homepage: www.jstorage.info
|
|
8
|
+
*
|
|
9
|
+
* Licensed under MIT-style license:
|
|
10
|
+
*
|
|
11
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
* in the Software without restriction, including without limitation the rights
|
|
14
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
* furnished to do so, subject to the following conditions:
|
|
17
|
+
*
|
|
18
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
* SOFTWARE.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
(function(){
|
|
28
|
+
var
|
|
29
|
+
/* jStorage version */
|
|
30
|
+
JSTORAGE_VERSION = "0.4.5",
|
|
31
|
+
|
|
32
|
+
/* detect a dollar object or create one if not found */
|
|
33
|
+
$ = window.jQuery || window.$ || (window.$ = {}),
|
|
34
|
+
|
|
35
|
+
/* check for a JSON handling support */
|
|
36
|
+
JSON = {
|
|
37
|
+
parse:
|
|
38
|
+
window.JSON && (window.JSON.parse || window.JSON.decode) ||
|
|
39
|
+
String.prototype.evalJSON && function(str){return String(str).evalJSON();} ||
|
|
40
|
+
$.parseJSON ||
|
|
41
|
+
$.evalJSON,
|
|
42
|
+
stringify:
|
|
43
|
+
Object.toJSON ||
|
|
44
|
+
window.JSON && (window.JSON.stringify || window.JSON.encode) ||
|
|
45
|
+
$.toJSON
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Break if no JSON support was found
|
|
49
|
+
if(!('parse' in JSON) || !('stringify' in JSON)){
|
|
50
|
+
throw new Error("No JSON support found, include //cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js to page");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
var
|
|
54
|
+
/* This is the object, that holds the cached values */
|
|
55
|
+
_storage = {__jstorage_meta:{CRC32:{}}},
|
|
56
|
+
|
|
57
|
+
/* Actual browser storage (localStorage or globalStorage['domain']) */
|
|
58
|
+
_storage_service = {jStorage:"{}"},
|
|
59
|
+
|
|
60
|
+
/* DOM element for older IE versions, holds userData behavior */
|
|
61
|
+
_storage_elm = null,
|
|
62
|
+
|
|
63
|
+
/* How much space does the storage take */
|
|
64
|
+
_storage_size = 0,
|
|
65
|
+
|
|
66
|
+
/* which backend is currently used */
|
|
67
|
+
_backend = false,
|
|
68
|
+
|
|
69
|
+
/* onchange observers */
|
|
70
|
+
_observers = {},
|
|
71
|
+
|
|
72
|
+
/* timeout to wait after onchange event */
|
|
73
|
+
_observer_timeout = false,
|
|
74
|
+
|
|
75
|
+
/* last update time */
|
|
76
|
+
_observer_update = 0,
|
|
77
|
+
|
|
78
|
+
/* pubsub observers */
|
|
79
|
+
_pubsub_observers = {},
|
|
80
|
+
|
|
81
|
+
/* skip published items older than current timestamp */
|
|
82
|
+
_pubsub_last = +new Date(),
|
|
83
|
+
|
|
84
|
+
/* Next check for TTL */
|
|
85
|
+
_ttl_timeout,
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* XML encoding and decoding as XML nodes can't be JSON'ized
|
|
89
|
+
* XML nodes are encoded and decoded if the node is the value to be saved
|
|
90
|
+
* but not if it's as a property of another object
|
|
91
|
+
* Eg. -
|
|
92
|
+
* $.jStorage.set("key", xmlNode); // IS OK
|
|
93
|
+
* $.jStorage.set("key", {xml: xmlNode}); // NOT OK
|
|
94
|
+
*/
|
|
95
|
+
_XMLService = {
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Validates a XML node to be XML
|
|
99
|
+
* based on jQuery.isXML function
|
|
100
|
+
*/
|
|
101
|
+
isXML: function(elm){
|
|
102
|
+
var documentElement = (elm ? elm.ownerDocument || elm : 0).documentElement;
|
|
103
|
+
return documentElement ? documentElement.nodeName !== "HTML" : false;
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Encodes a XML node to string
|
|
108
|
+
* based on http://www.mercurytide.co.uk/news/article/issues-when-working-ajax/
|
|
109
|
+
*/
|
|
110
|
+
encode: function(xmlNode) {
|
|
111
|
+
if(!this.isXML(xmlNode)){
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
try{ // Mozilla, Webkit, Opera
|
|
115
|
+
return new XMLSerializer().serializeToString(xmlNode);
|
|
116
|
+
}catch(E1) {
|
|
117
|
+
try { // IE
|
|
118
|
+
return xmlNode.xml;
|
|
119
|
+
}catch(E2){}
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Decodes a XML node from string
|
|
126
|
+
* loosely based on http://outwestmedia.com/jquery-plugins/xmldom/
|
|
127
|
+
*/
|
|
128
|
+
decode: function(xmlString){
|
|
129
|
+
var dom_parser = ("DOMParser" in window && (new DOMParser()).parseFromString) ||
|
|
130
|
+
(window.ActiveXObject && function(_xmlString) {
|
|
131
|
+
var xml_doc = new ActiveXObject('Microsoft.XMLDOM');
|
|
132
|
+
xml_doc.async = 'false';
|
|
133
|
+
xml_doc.loadXML(_xmlString);
|
|
134
|
+
return xml_doc;
|
|
135
|
+
}),
|
|
136
|
+
resultXML;
|
|
137
|
+
if(!dom_parser){
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
resultXML = dom_parser.call("DOMParser" in window && (new DOMParser()) || window, xmlString, 'text/xml');
|
|
141
|
+
return this.isXML(resultXML)?resultXML:false;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
////////////////////////// PRIVATE METHODS ////////////////////////
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Initialization function. Detects if the browser supports DOM Storage
|
|
150
|
+
* or userData behavior and behaves accordingly.
|
|
151
|
+
*/
|
|
152
|
+
function _init(){
|
|
153
|
+
/* Check if browser supports localStorage */
|
|
154
|
+
var localStorageReallyWorks = false;
|
|
155
|
+
if("localStorage" in window){
|
|
156
|
+
try {
|
|
157
|
+
window.localStorage.setItem('_tmptest', 'tmpval');
|
|
158
|
+
localStorageReallyWorks = true;
|
|
159
|
+
window.localStorage.removeItem('_tmptest');
|
|
160
|
+
} catch(BogusQuotaExceededErrorOnIos5) {
|
|
161
|
+
// Thanks be to iOS5 Private Browsing mode which throws
|
|
162
|
+
// QUOTA_EXCEEDED_ERRROR DOM Exception 22.
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if(localStorageReallyWorks){
|
|
167
|
+
try {
|
|
168
|
+
if(window.localStorage) {
|
|
169
|
+
_storage_service = window.localStorage;
|
|
170
|
+
_backend = "localStorage";
|
|
171
|
+
_observer_update = _storage_service.jStorage_update;
|
|
172
|
+
}
|
|
173
|
+
} catch(E3) {/* Firefox fails when touching localStorage and cookies are disabled */}
|
|
174
|
+
}
|
|
175
|
+
/* Check if browser supports globalStorage */
|
|
176
|
+
else if("globalStorage" in window){
|
|
177
|
+
try {
|
|
178
|
+
if(window.globalStorage) {
|
|
179
|
+
if(window.location.hostname == 'localhost'){
|
|
180
|
+
_storage_service = window.globalStorage['localhost.localdomain'];
|
|
181
|
+
}
|
|
182
|
+
else{
|
|
183
|
+
_storage_service = window.globalStorage[window.location.hostname];
|
|
184
|
+
}
|
|
185
|
+
_backend = "globalStorage";
|
|
186
|
+
_observer_update = _storage_service.jStorage_update;
|
|
187
|
+
}
|
|
188
|
+
} catch(E4) {/* Firefox fails when touching localStorage and cookies are disabled */}
|
|
189
|
+
}
|
|
190
|
+
/* Check if browser supports userData behavior */
|
|
191
|
+
else {
|
|
192
|
+
_storage_elm = document.createElement('link');
|
|
193
|
+
if(_storage_elm.addBehavior){
|
|
194
|
+
|
|
195
|
+
/* Use a DOM element to act as userData storage */
|
|
196
|
+
_storage_elm.style.behavior = 'url(#default#userData)';
|
|
197
|
+
|
|
198
|
+
/* userData element needs to be inserted into the DOM! */
|
|
199
|
+
document.getElementsByTagName('head')[0].appendChild(_storage_elm);
|
|
200
|
+
|
|
201
|
+
try{
|
|
202
|
+
_storage_elm.load("jStorage");
|
|
203
|
+
}catch(E){
|
|
204
|
+
// try to reset cache
|
|
205
|
+
_storage_elm.setAttribute("jStorage", "{}");
|
|
206
|
+
_storage_elm.save("jStorage");
|
|
207
|
+
_storage_elm.load("jStorage");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
var data = "{}";
|
|
211
|
+
try{
|
|
212
|
+
data = _storage_elm.getAttribute("jStorage");
|
|
213
|
+
}catch(E5){}
|
|
214
|
+
|
|
215
|
+
try{
|
|
216
|
+
_observer_update = _storage_elm.getAttribute("jStorage_update");
|
|
217
|
+
}catch(E6){}
|
|
218
|
+
|
|
219
|
+
_storage_service.jStorage = data;
|
|
220
|
+
_backend = "userDataBehavior";
|
|
221
|
+
}else{
|
|
222
|
+
_storage_elm = null;
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Load data from storage
|
|
228
|
+
_load_storage();
|
|
229
|
+
|
|
230
|
+
// remove dead keys
|
|
231
|
+
_handleTTL();
|
|
232
|
+
|
|
233
|
+
// start listening for changes
|
|
234
|
+
_setupObserver();
|
|
235
|
+
|
|
236
|
+
// initialize publish-subscribe service
|
|
237
|
+
_handlePubSub();
|
|
238
|
+
|
|
239
|
+
// handle cached navigation
|
|
240
|
+
if("addEventListener" in window){
|
|
241
|
+
window.addEventListener("pageshow", function(event){
|
|
242
|
+
if(event.persisted){
|
|
243
|
+
_storageObserver();
|
|
244
|
+
}
|
|
245
|
+
}, false);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Reload data from storage when needed
|
|
251
|
+
*/
|
|
252
|
+
function _reloadData(){
|
|
253
|
+
var data = "{}";
|
|
254
|
+
|
|
255
|
+
if(_backend == "userDataBehavior"){
|
|
256
|
+
_storage_elm.load("jStorage");
|
|
257
|
+
|
|
258
|
+
try{
|
|
259
|
+
data = _storage_elm.getAttribute("jStorage");
|
|
260
|
+
}catch(E5){}
|
|
261
|
+
|
|
262
|
+
try{
|
|
263
|
+
_observer_update = _storage_elm.getAttribute("jStorage_update");
|
|
264
|
+
}catch(E6){}
|
|
265
|
+
|
|
266
|
+
_storage_service.jStorage = data;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
_load_storage();
|
|
270
|
+
|
|
271
|
+
// remove dead keys
|
|
272
|
+
_handleTTL();
|
|
273
|
+
|
|
274
|
+
_handlePubSub();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Sets up a storage change observer
|
|
279
|
+
*/
|
|
280
|
+
function _setupObserver(){
|
|
281
|
+
if(_backend == "localStorage" || _backend == "globalStorage"){
|
|
282
|
+
if("addEventListener" in window){
|
|
283
|
+
window.addEventListener("storage", _storageObserver, false);
|
|
284
|
+
}else{
|
|
285
|
+
document.attachEvent("onstorage", _storageObserver);
|
|
286
|
+
}
|
|
287
|
+
}else if(_backend == "userDataBehavior"){
|
|
288
|
+
setInterval(_storageObserver, 1000);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Fired on any kind of data change, needs to check if anything has
|
|
294
|
+
* really been changed
|
|
295
|
+
*/
|
|
296
|
+
function _storageObserver(){
|
|
297
|
+
var updateTime;
|
|
298
|
+
// cumulate change notifications with timeout
|
|
299
|
+
clearTimeout(_observer_timeout);
|
|
300
|
+
_observer_timeout = setTimeout(function(){
|
|
301
|
+
|
|
302
|
+
if(_backend == "localStorage" || _backend == "globalStorage"){
|
|
303
|
+
updateTime = _storage_service.jStorage_update;
|
|
304
|
+
}else if(_backend == "userDataBehavior"){
|
|
305
|
+
_storage_elm.load("jStorage");
|
|
306
|
+
try{
|
|
307
|
+
updateTime = _storage_elm.getAttribute("jStorage_update");
|
|
308
|
+
}catch(E5){}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if(updateTime && updateTime != _observer_update){
|
|
312
|
+
_observer_update = updateTime;
|
|
313
|
+
_checkUpdatedKeys();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
}, 25);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Reloads the data and checks if any keys are changed
|
|
321
|
+
*/
|
|
322
|
+
function _checkUpdatedKeys(){
|
|
323
|
+
var oldCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32)),
|
|
324
|
+
newCrc32List;
|
|
325
|
+
|
|
326
|
+
_reloadData();
|
|
327
|
+
newCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32));
|
|
328
|
+
|
|
329
|
+
var key,
|
|
330
|
+
updated = [],
|
|
331
|
+
removed = [];
|
|
332
|
+
|
|
333
|
+
for(key in oldCrc32List){
|
|
334
|
+
if(oldCrc32List.hasOwnProperty(key)){
|
|
335
|
+
if(!newCrc32List[key]){
|
|
336
|
+
removed.push(key);
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if(oldCrc32List[key] != newCrc32List[key] && String(oldCrc32List[key]).substr(0,2) == "2."){
|
|
340
|
+
updated.push(key);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
for(key in newCrc32List){
|
|
346
|
+
if(newCrc32List.hasOwnProperty(key)){
|
|
347
|
+
if(!oldCrc32List[key]){
|
|
348
|
+
updated.push(key);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
_fireObservers(updated, "updated");
|
|
354
|
+
_fireObservers(removed, "deleted");
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Fires observers for updated keys
|
|
359
|
+
*
|
|
360
|
+
* @param {Array|String} keys Array of key names or a key
|
|
361
|
+
* @param {String} action What happened with the value (updated, deleted, flushed)
|
|
362
|
+
*/
|
|
363
|
+
function _fireObservers(keys, action){
|
|
364
|
+
keys = [].concat(keys || []);
|
|
365
|
+
if(action == "flushed"){
|
|
366
|
+
keys = [];
|
|
367
|
+
for(var key in _observers){
|
|
368
|
+
if(_observers.hasOwnProperty(key)){
|
|
369
|
+
keys.push(key);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
action = "deleted";
|
|
373
|
+
}
|
|
374
|
+
for(var i=0, len = keys.length; i<len; i++){
|
|
375
|
+
if(_observers[keys[i]]){
|
|
376
|
+
for(var j=0, jlen = _observers[keys[i]].length; j<jlen; j++){
|
|
377
|
+
_observers[keys[i]][j](keys[i], action);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if(_observers["*"]){
|
|
381
|
+
for(var j=0, jlen = _observers["*"].length; j<jlen; j++){
|
|
382
|
+
_observers["*"][j](keys[i], action);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Publishes key change to listeners
|
|
390
|
+
*/
|
|
391
|
+
function _publishChange(){
|
|
392
|
+
var updateTime = (+new Date()).toString();
|
|
393
|
+
|
|
394
|
+
if(_backend == "localStorage" || _backend == "globalStorage"){
|
|
395
|
+
try {
|
|
396
|
+
_storage_service.jStorage_update = updateTime;
|
|
397
|
+
} catch (E8) {
|
|
398
|
+
// safari private mode has been enabled after the jStorage initialization
|
|
399
|
+
_backend = false;
|
|
400
|
+
}
|
|
401
|
+
}else if(_backend == "userDataBehavior"){
|
|
402
|
+
_storage_elm.setAttribute("jStorage_update", updateTime);
|
|
403
|
+
_storage_elm.save("jStorage");
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
_storageObserver();
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Loads the data from the storage based on the supported mechanism
|
|
411
|
+
*/
|
|
412
|
+
function _load_storage(){
|
|
413
|
+
/* if jStorage string is retrieved, then decode it */
|
|
414
|
+
if(_storage_service.jStorage){
|
|
415
|
+
try{
|
|
416
|
+
_storage = JSON.parse(String(_storage_service.jStorage));
|
|
417
|
+
}catch(E6){_storage_service.jStorage = "{}";}
|
|
418
|
+
}else{
|
|
419
|
+
_storage_service.jStorage = "{}";
|
|
420
|
+
}
|
|
421
|
+
_storage_size = _storage_service.jStorage?String(_storage_service.jStorage).length:0;
|
|
422
|
+
|
|
423
|
+
if(!_storage.__jstorage_meta){
|
|
424
|
+
_storage.__jstorage_meta = {};
|
|
425
|
+
}
|
|
426
|
+
if(!_storage.__jstorage_meta.CRC32){
|
|
427
|
+
_storage.__jstorage_meta.CRC32 = {};
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* This functions provides the "save" mechanism to store the jStorage object
|
|
433
|
+
*/
|
|
434
|
+
function _save(){
|
|
435
|
+
_dropOldEvents(); // remove expired events
|
|
436
|
+
try{
|
|
437
|
+
_storage_service.jStorage = JSON.stringify(_storage);
|
|
438
|
+
// If userData is used as the storage engine, additional
|
|
439
|
+
if(_storage_elm) {
|
|
440
|
+
_storage_elm.setAttribute("jStorage",_storage_service.jStorage);
|
|
441
|
+
_storage_elm.save("jStorage");
|
|
442
|
+
}
|
|
443
|
+
_storage_size = _storage_service.jStorage?String(_storage_service.jStorage).length:0;
|
|
444
|
+
}catch(E7){/* probably cache is full, nothing is saved this way*/}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Function checks if a key is set and is string or numberic
|
|
449
|
+
*
|
|
450
|
+
* @param {String} key Key name
|
|
451
|
+
*/
|
|
452
|
+
function _checkKey(key){
|
|
453
|
+
if(!key || (typeof key != "string" && typeof key != "number")){
|
|
454
|
+
throw new TypeError('Key name must be string or numeric');
|
|
455
|
+
}
|
|
456
|
+
if(key == "__jstorage_meta"){
|
|
457
|
+
throw new TypeError('Reserved key name');
|
|
458
|
+
}
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Removes expired keys
|
|
464
|
+
*/
|
|
465
|
+
function _handleTTL(){
|
|
466
|
+
var curtime, i, TTL, CRC32, nextExpire = Infinity, changed = false, deleted = [];
|
|
467
|
+
|
|
468
|
+
clearTimeout(_ttl_timeout);
|
|
469
|
+
|
|
470
|
+
if(!_storage.__jstorage_meta || typeof _storage.__jstorage_meta.TTL != "object"){
|
|
471
|
+
// nothing to do here
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
curtime = +new Date();
|
|
476
|
+
TTL = _storage.__jstorage_meta.TTL;
|
|
477
|
+
|
|
478
|
+
CRC32 = _storage.__jstorage_meta.CRC32;
|
|
479
|
+
for(i in TTL){
|
|
480
|
+
if(TTL.hasOwnProperty(i)){
|
|
481
|
+
if(TTL[i] <= curtime){
|
|
482
|
+
delete TTL[i];
|
|
483
|
+
delete CRC32[i];
|
|
484
|
+
delete _storage[i];
|
|
485
|
+
changed = true;
|
|
486
|
+
deleted.push(i);
|
|
487
|
+
}else if(TTL[i] < nextExpire){
|
|
488
|
+
nextExpire = TTL[i];
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// set next check
|
|
494
|
+
if(nextExpire != Infinity){
|
|
495
|
+
_ttl_timeout = setTimeout(_handleTTL, nextExpire - curtime);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// save changes
|
|
499
|
+
if(changed){
|
|
500
|
+
_save();
|
|
501
|
+
_publishChange();
|
|
502
|
+
_fireObservers(deleted, "deleted");
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Checks if there's any events on hold to be fired to listeners
|
|
508
|
+
*/
|
|
509
|
+
function _handlePubSub(){
|
|
510
|
+
var i, len;
|
|
511
|
+
if(!_storage.__jstorage_meta.PubSub){
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
var pubelm,
|
|
515
|
+
_pubsubCurrent = _pubsub_last;
|
|
516
|
+
|
|
517
|
+
for(i=len=_storage.__jstorage_meta.PubSub.length-1; i>=0; i--){
|
|
518
|
+
pubelm = _storage.__jstorage_meta.PubSub[i];
|
|
519
|
+
if(pubelm[0] > _pubsub_last){
|
|
520
|
+
_pubsubCurrent = pubelm[0];
|
|
521
|
+
_fireSubscribers(pubelm[1], pubelm[2]);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
_pubsub_last = _pubsubCurrent;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Fires all subscriber listeners for a pubsub channel
|
|
530
|
+
*
|
|
531
|
+
* @param {String} channel Channel name
|
|
532
|
+
* @param {Mixed} payload Payload data to deliver
|
|
533
|
+
*/
|
|
534
|
+
function _fireSubscribers(channel, payload){
|
|
535
|
+
if(_pubsub_observers[channel]){
|
|
536
|
+
for(var i=0, len = _pubsub_observers[channel].length; i<len; i++){
|
|
537
|
+
// send immutable data that can't be modified by listeners
|
|
538
|
+
try{
|
|
539
|
+
_pubsub_observers[channel][i](channel, JSON.parse(JSON.stringify(payload)));
|
|
540
|
+
}catch(E){};
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Remove old events from the publish stream (at least 2sec old)
|
|
547
|
+
*/
|
|
548
|
+
function _dropOldEvents(){
|
|
549
|
+
if(!_storage.__jstorage_meta.PubSub){
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
var retire = +new Date() - 2000;
|
|
554
|
+
|
|
555
|
+
for(var i=0, len = _storage.__jstorage_meta.PubSub.length; i<len; i++){
|
|
556
|
+
if(_storage.__jstorage_meta.PubSub[i][0] <= retire){
|
|
557
|
+
// deleteCount is needed for IE6
|
|
558
|
+
_storage.__jstorage_meta.PubSub.splice(i, _storage.__jstorage_meta.PubSub.length - i);
|
|
559
|
+
break;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if(!_storage.__jstorage_meta.PubSub.length){
|
|
564
|
+
delete _storage.__jstorage_meta.PubSub;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Publish payload to a channel
|
|
571
|
+
*
|
|
572
|
+
* @param {String} channel Channel name
|
|
573
|
+
* @param {Mixed} payload Payload to send to the subscribers
|
|
574
|
+
*/
|
|
575
|
+
function _publish(channel, payload){
|
|
576
|
+
if(!_storage.__jstorage_meta){
|
|
577
|
+
_storage.__jstorage_meta = {};
|
|
578
|
+
}
|
|
579
|
+
if(!_storage.__jstorage_meta.PubSub){
|
|
580
|
+
_storage.__jstorage_meta.PubSub = [];
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
_storage.__jstorage_meta.PubSub.unshift([+new Date, channel, payload]);
|
|
584
|
+
|
|
585
|
+
_save();
|
|
586
|
+
_publishChange();
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* JS Implementation of MurmurHash2
|
|
592
|
+
*
|
|
593
|
+
* SOURCE: https://github.com/garycourt/murmurhash-js (MIT licensed)
|
|
594
|
+
*
|
|
595
|
+
* @author <a href="mailto:gary.court@gmail.com">Gary Court</a>
|
|
596
|
+
* @see http://github.com/garycourt/murmurhash-js
|
|
597
|
+
* @author <a href="mailto:aappleby@gmail.com">Austin Appleby</a>
|
|
598
|
+
* @see http://sites.google.com/site/murmurhash/
|
|
599
|
+
*
|
|
600
|
+
* @param {string} str ASCII only
|
|
601
|
+
* @param {number} seed Positive integer only
|
|
602
|
+
* @return {number} 32-bit positive integer hash
|
|
603
|
+
*/
|
|
604
|
+
|
|
605
|
+
function murmurhash2_32_gc(str, seed) {
|
|
606
|
+
var
|
|
607
|
+
l = str.length,
|
|
608
|
+
h = seed ^ l,
|
|
609
|
+
i = 0,
|
|
610
|
+
k;
|
|
611
|
+
|
|
612
|
+
while (l >= 4) {
|
|
613
|
+
k =
|
|
614
|
+
((str.charCodeAt(i) & 0xff)) |
|
|
615
|
+
((str.charCodeAt(++i) & 0xff) << 8) |
|
|
616
|
+
((str.charCodeAt(++i) & 0xff) << 16) |
|
|
617
|
+
((str.charCodeAt(++i) & 0xff) << 24);
|
|
618
|
+
|
|
619
|
+
k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
|
|
620
|
+
k ^= k >>> 24;
|
|
621
|
+
k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
|
|
622
|
+
|
|
623
|
+
h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^ k;
|
|
624
|
+
|
|
625
|
+
l -= 4;
|
|
626
|
+
++i;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
switch (l) {
|
|
630
|
+
case 3: h ^= (str.charCodeAt(i + 2) & 0xff) << 16;
|
|
631
|
+
case 2: h ^= (str.charCodeAt(i + 1) & 0xff) << 8;
|
|
632
|
+
case 1: h ^= (str.charCodeAt(i) & 0xff);
|
|
633
|
+
h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
h ^= h >>> 13;
|
|
637
|
+
h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
|
|
638
|
+
h ^= h >>> 15;
|
|
639
|
+
|
|
640
|
+
return h >>> 0;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
////////////////////////// PUBLIC INTERFACE /////////////////////////
|
|
644
|
+
|
|
645
|
+
$.jStorage = {
|
|
646
|
+
/* Version number */
|
|
647
|
+
version: JSTORAGE_VERSION,
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Sets a key's value.
|
|
651
|
+
*
|
|
652
|
+
* @param {String} key Key to set. If this value is not set or not
|
|
653
|
+
* a string an exception is raised.
|
|
654
|
+
* @param {Mixed} value Value to set. This can be any value that is JSON
|
|
655
|
+
* compatible (Numbers, Strings, Objects etc.).
|
|
656
|
+
* @param {Object} [options] - possible options to use
|
|
657
|
+
* @param {Number} [options.TTL] - optional TTL value
|
|
658
|
+
* @return {Mixed} the used value
|
|
659
|
+
*/
|
|
660
|
+
set: function(key, value, options){
|
|
661
|
+
_checkKey(key);
|
|
662
|
+
|
|
663
|
+
options = options || {};
|
|
664
|
+
|
|
665
|
+
// undefined values are deleted automatically
|
|
666
|
+
if(typeof value == "undefined"){
|
|
667
|
+
this.deleteKey(key);
|
|
668
|
+
return value;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if(_XMLService.isXML(value)){
|
|
672
|
+
value = {_is_xml:true,xml:_XMLService.encode(value)};
|
|
673
|
+
}else if(typeof value == "function"){
|
|
674
|
+
return undefined; // functions can't be saved!
|
|
675
|
+
}else if(value && typeof value == "object"){
|
|
676
|
+
// clone the object before saving to _storage tree
|
|
677
|
+
value = JSON.parse(JSON.stringify(value));
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
_storage[key] = value;
|
|
681
|
+
|
|
682
|
+
_storage.__jstorage_meta.CRC32[key] = "2." + murmurhash2_32_gc(JSON.stringify(value), 0x9747b28c);
|
|
683
|
+
|
|
684
|
+
this.setTTL(key, options.TTL || 0); // also handles saving and _publishChange
|
|
685
|
+
|
|
686
|
+
_fireObservers(key, "updated");
|
|
687
|
+
return value;
|
|
688
|
+
},
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Looks up a key in cache
|
|
692
|
+
*
|
|
693
|
+
* @param {String} key - Key to look up.
|
|
694
|
+
* @param {mixed} def - Default value to return, if key didn't exist.
|
|
695
|
+
* @return {Mixed} the key value, default value or null
|
|
696
|
+
*/
|
|
697
|
+
get: function(key, def){
|
|
698
|
+
_checkKey(key);
|
|
699
|
+
if(key in _storage){
|
|
700
|
+
if(_storage[key] && typeof _storage[key] == "object" && _storage[key]._is_xml) {
|
|
701
|
+
return _XMLService.decode(_storage[key].xml);
|
|
702
|
+
}else{
|
|
703
|
+
return _storage[key];
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
return typeof(def) == 'undefined' ? null : def;
|
|
707
|
+
},
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Deletes a key from cache.
|
|
711
|
+
*
|
|
712
|
+
* @param {String} key - Key to delete.
|
|
713
|
+
* @return {Boolean} true if key existed or false if it didn't
|
|
714
|
+
*/
|
|
715
|
+
deleteKey: function(key){
|
|
716
|
+
_checkKey(key);
|
|
717
|
+
if(key in _storage){
|
|
718
|
+
delete _storage[key];
|
|
719
|
+
// remove from TTL list
|
|
720
|
+
if(typeof _storage.__jstorage_meta.TTL == "object" &&
|
|
721
|
+
key in _storage.__jstorage_meta.TTL){
|
|
722
|
+
delete _storage.__jstorage_meta.TTL[key];
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
delete _storage.__jstorage_meta.CRC32[key];
|
|
726
|
+
|
|
727
|
+
_save();
|
|
728
|
+
_publishChange();
|
|
729
|
+
_fireObservers(key, "deleted");
|
|
730
|
+
return true;
|
|
731
|
+
}
|
|
732
|
+
return false;
|
|
733
|
+
},
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Sets a TTL for a key, or remove it if ttl value is 0 or below
|
|
737
|
+
*
|
|
738
|
+
* @param {String} key - key to set the TTL for
|
|
739
|
+
* @param {Number} ttl - TTL timeout in milliseconds
|
|
740
|
+
* @return {Boolean} true if key existed or false if it didn't
|
|
741
|
+
*/
|
|
742
|
+
setTTL: function(key, ttl){
|
|
743
|
+
var curtime = +new Date();
|
|
744
|
+
_checkKey(key);
|
|
745
|
+
ttl = Number(ttl) || 0;
|
|
746
|
+
if(key in _storage){
|
|
747
|
+
|
|
748
|
+
if(!_storage.__jstorage_meta.TTL){
|
|
749
|
+
_storage.__jstorage_meta.TTL = {};
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// Set TTL value for the key
|
|
753
|
+
if(ttl>0){
|
|
754
|
+
_storage.__jstorage_meta.TTL[key] = curtime + ttl;
|
|
755
|
+
}else{
|
|
756
|
+
delete _storage.__jstorage_meta.TTL[key];
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
_save();
|
|
760
|
+
|
|
761
|
+
_handleTTL();
|
|
762
|
+
|
|
763
|
+
_publishChange();
|
|
764
|
+
return true;
|
|
765
|
+
}
|
|
766
|
+
return false;
|
|
767
|
+
},
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* Gets remaining TTL (in milliseconds) for a key or 0 when no TTL has been set
|
|
771
|
+
*
|
|
772
|
+
* @param {String} key Key to check
|
|
773
|
+
* @return {Number} Remaining TTL in milliseconds
|
|
774
|
+
*/
|
|
775
|
+
getTTL: function(key){
|
|
776
|
+
var curtime = +new Date(), ttl;
|
|
777
|
+
_checkKey(key);
|
|
778
|
+
if(key in _storage && _storage.__jstorage_meta.TTL && _storage.__jstorage_meta.TTL[key]){
|
|
779
|
+
ttl = _storage.__jstorage_meta.TTL[key] - curtime;
|
|
780
|
+
return ttl || 0;
|
|
781
|
+
}
|
|
782
|
+
return 0;
|
|
783
|
+
},
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Deletes everything in cache.
|
|
787
|
+
*
|
|
788
|
+
* @return {Boolean} Always true
|
|
789
|
+
*/
|
|
790
|
+
flush: function(){
|
|
791
|
+
_storage = {__jstorage_meta:{CRC32:{}}};
|
|
792
|
+
_save();
|
|
793
|
+
_publishChange();
|
|
794
|
+
_fireObservers(null, "flushed");
|
|
795
|
+
return true;
|
|
796
|
+
},
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Returns a read-only copy of _storage
|
|
800
|
+
*
|
|
801
|
+
* @return {Object} Read-only copy of _storage
|
|
802
|
+
*/
|
|
803
|
+
storageObj: function(){
|
|
804
|
+
function F() {}
|
|
805
|
+
F.prototype = _storage;
|
|
806
|
+
return new F();
|
|
807
|
+
},
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* Returns an index of all used keys as an array
|
|
811
|
+
* ['key1', 'key2',..'keyN']
|
|
812
|
+
*
|
|
813
|
+
* @return {Array} Used keys
|
|
814
|
+
*/
|
|
815
|
+
index: function(){
|
|
816
|
+
var index = [], i;
|
|
817
|
+
for(i in _storage){
|
|
818
|
+
if(_storage.hasOwnProperty(i) && i != "__jstorage_meta"){
|
|
819
|
+
index.push(i);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
return index;
|
|
823
|
+
},
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* How much space in bytes does the storage take?
|
|
827
|
+
*
|
|
828
|
+
* @return {Number} Storage size in chars (not the same as in bytes,
|
|
829
|
+
* since some chars may take several bytes)
|
|
830
|
+
*/
|
|
831
|
+
storageSize: function(){
|
|
832
|
+
return _storage_size;
|
|
833
|
+
},
|
|
834
|
+
|
|
835
|
+
/**
|
|
836
|
+
* Which backend is currently in use?
|
|
837
|
+
*
|
|
838
|
+
* @return {String} Backend name
|
|
839
|
+
*/
|
|
840
|
+
currentBackend: function(){
|
|
841
|
+
return _backend;
|
|
842
|
+
},
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* Test if storage is available
|
|
846
|
+
*
|
|
847
|
+
* @return {Boolean} True if storage can be used
|
|
848
|
+
*/
|
|
849
|
+
storageAvailable: function(){
|
|
850
|
+
return !!_backend;
|
|
851
|
+
},
|
|
852
|
+
|
|
853
|
+
/**
|
|
854
|
+
* Register change listeners
|
|
855
|
+
*
|
|
856
|
+
* @param {String} key Key name
|
|
857
|
+
* @param {Function} callback Function to run when the key changes
|
|
858
|
+
*/
|
|
859
|
+
listenKeyChange: function(key, callback){
|
|
860
|
+
_checkKey(key);
|
|
861
|
+
if(!_observers[key]){
|
|
862
|
+
_observers[key] = [];
|
|
863
|
+
}
|
|
864
|
+
_observers[key].push(callback);
|
|
865
|
+
},
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* Remove change listeners
|
|
869
|
+
*
|
|
870
|
+
* @param {String} key Key name to unregister listeners against
|
|
871
|
+
* @param {Function} [callback] If set, unregister the callback, if not - unregister all
|
|
872
|
+
*/
|
|
873
|
+
stopListening: function(key, callback){
|
|
874
|
+
_checkKey(key);
|
|
875
|
+
|
|
876
|
+
if(!_observers[key]){
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
if(!callback){
|
|
881
|
+
delete _observers[key];
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
for(var i = _observers[key].length - 1; i>=0; i--){
|
|
886
|
+
if(_observers[key][i] == callback){
|
|
887
|
+
_observers[key].splice(i,1);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
},
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* Subscribe to a Publish/Subscribe event stream
|
|
894
|
+
*
|
|
895
|
+
* @param {String} channel Channel name
|
|
896
|
+
* @param {Function} callback Function to run when the something is published to the channel
|
|
897
|
+
*/
|
|
898
|
+
subscribe: function(channel, callback){
|
|
899
|
+
channel = (channel || "").toString();
|
|
900
|
+
if(!channel){
|
|
901
|
+
throw new TypeError('Channel not defined');
|
|
902
|
+
}
|
|
903
|
+
if(!_pubsub_observers[channel]){
|
|
904
|
+
_pubsub_observers[channel] = [];
|
|
905
|
+
}
|
|
906
|
+
_pubsub_observers[channel].push(callback);
|
|
907
|
+
},
|
|
908
|
+
|
|
909
|
+
/**
|
|
910
|
+
* Publish data to an event stream
|
|
911
|
+
*
|
|
912
|
+
* @param {String} channel Channel name
|
|
913
|
+
* @param {Mixed} payload Payload to deliver
|
|
914
|
+
*/
|
|
915
|
+
publish: function(channel, payload){
|
|
916
|
+
channel = (channel || "").toString();
|
|
917
|
+
if(!channel){
|
|
918
|
+
throw new TypeError('Channel not defined');
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
_publish(channel, payload);
|
|
922
|
+
},
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* Reloads the data from browser storage
|
|
926
|
+
*/
|
|
927
|
+
reInit: function(){
|
|
928
|
+
_reloadData();
|
|
929
|
+
},
|
|
930
|
+
|
|
931
|
+
/**
|
|
932
|
+
* Removes reference from global objects and saves it as jStorage
|
|
933
|
+
*
|
|
934
|
+
* @param {Boolean} option if needed to save object as simple 'jStorage' in windows context
|
|
935
|
+
*/
|
|
936
|
+
noConflict: function( saveInGlobal ) {
|
|
937
|
+
delete window.$.jStorage
|
|
938
|
+
|
|
939
|
+
if ( saveInGlobal ) {
|
|
940
|
+
window.jStorage = this;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
return this;
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
|
|
947
|
+
// Initialize jStorage
|
|
948
|
+
_init();
|
|
949
|
+
|
|
950
|
+
})();
|