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.
Files changed (95) hide show
  1. checksums.yaml +6 -14
  2. data/.gitignore +4 -0
  3. data/Gemfile +1 -1
  4. data/app/assets/javascripts/kojac.js +393 -125
  5. data/app/assets/javascripts/kojac_canjs.js +34 -34
  6. data/app/assets/javascripts/kojac_ember.js +110 -152
  7. data/app/controllers/{kojac_controller.rb → kojac_base_controller.rb} +18 -11
  8. data/app/policies/kojac_base_policy.rb +114 -0
  9. data/app/serializers/kojac_base_serializer.rb +35 -0
  10. data/kojac.gemspec +12 -10
  11. data/lib/kojac/app_serialize.rb +31 -29
  12. data/lib/kojac/concentric.rb +152 -0
  13. data/lib/kojac/kojac_policy.rb +70 -0
  14. data/lib/kojac/kojac_rails.rb +200 -49
  15. data/lib/kojac/version.rb +1 -1
  16. data/spec/can_cache_spec.js +19 -19
  17. data/spec/demo/.gitignore +16 -0
  18. data/spec/demo/.ruby-gemset +1 -0
  19. data/spec/demo/.ruby-version +1 -0
  20. data/spec/demo/Gemfile +59 -0
  21. data/spec/demo/Gemfile.lock +153 -0
  22. data/spec/demo/README.rdoc +15 -248
  23. data/spec/demo/Rakefile +25 -1
  24. data/spec/demo/app/{mailers/.gitkeep → assets/images/.keep} +0 -0
  25. data/spec/demo/app/assets/javascripts/application.js +3 -3
  26. data/spec/demo/app/controllers/application_controller.rb +6 -1
  27. data/spec/demo/app/{models/.gitkeep → controllers/concerns/.keep} +0 -0
  28. data/spec/demo/app/controllers/users_controller.rb +5 -0
  29. data/spec/demo/{lib/assets/.gitkeep → app/mailers/.keep} +0 -0
  30. data/spec/demo/{log/.gitkeep → app/models/.keep} +0 -0
  31. data/spec/demo/app/models/concerns/.keep +0 -0
  32. data/spec/demo/app/models/user.rb +36 -0
  33. data/spec/demo/app/policies/user_policy.rb +42 -0
  34. data/spec/demo/bin/bundle +3 -0
  35. data/spec/demo/bin/rails +4 -0
  36. data/spec/demo/bin/rake +4 -0
  37. data/spec/demo/config.ru +1 -1
  38. data/spec/demo/config/application.rb +14 -46
  39. data/spec/demo/config/application.yml +4 -0
  40. data/spec/demo/config/boot.rb +3 -9
  41. data/spec/demo/config/database.yml +6 -6
  42. data/spec/demo/config/environment.rb +4 -2
  43. data/spec/demo/config/environments/development.rb +11 -19
  44. data/spec/demo/config/environments/production.rb +40 -27
  45. data/spec/demo/config/environments/test.rb +13 -14
  46. data/spec/demo/config/initializers/concentric_config.rb +9 -0
  47. data/spec/demo/config/initializers/filter_parameter_logging.rb +4 -0
  48. data/spec/demo/config/initializers/inflections.rb +6 -5
  49. data/spec/demo/config/initializers/initialize_kojac.rb +16 -0
  50. data/spec/demo/config/initializers/secret_token.rb +7 -2
  51. data/spec/demo/config/initializers/session_store.rb +0 -5
  52. data/spec/demo/config/initializers/wrap_parameters.rb +6 -6
  53. data/spec/demo/config/locales/en.yml +20 -2
  54. data/spec/demo/config/routes.rb +24 -24
  55. data/spec/demo/db/migrate/20131212034312_add_user.rb +14 -0
  56. data/spec/demo/db/migrate/20140107085351_add_owner_id.rb +5 -0
  57. data/spec/demo/db/schema.rb +28 -0
  58. data/spec/demo/db/seeds.rb +7 -0
  59. data/spec/demo/lib/assets/.keep +0 -0
  60. data/spec/demo/lib/tasks/.keep +0 -0
  61. data/spec/demo/log/.keep +0 -0
  62. data/spec/demo/public/404.html +43 -11
  63. data/spec/demo/public/422.html +43 -11
  64. data/spec/demo/public/500.html +43 -11
  65. data/spec/demo/public/robots.txt +5 -0
  66. data/spec/demo/spec/controllers/allowed_fields_spec.rb +171 -0
  67. data/spec/demo/spec/factories/users.rb +9 -0
  68. data/spec/demo/spec/features/concentric_spec.rb +63 -0
  69. data/spec/demo/spec/features/serialization_spec.rb +86 -0
  70. data/spec/demo/spec/spec_helper.rb +133 -0
  71. data/spec/demo/spec/spec_utils.rb +42 -0
  72. data/spec/demo/vendor/assets/javascripts/.keep +0 -0
  73. data/spec/demo/vendor/assets/stylesheets/.keep +0 -0
  74. data/spec/ember_factory_spec.js +1 -1
  75. data/spec/ember_model_spec.js +13 -3
  76. data/spec/ember_tojsono_spec.js +105 -0
  77. data/spec/error_handling_spec.js +90 -0
  78. data/spec/external/underscore_plus.js +318 -9
  79. data/spec/kojac_caching_spec.js +3 -1
  80. data/spec/kojac_ember_cache_spec.js +9 -0
  81. data/spec/kojac_mock_spec.js +4 -4
  82. data/spec/kojac_operations_spec.js +4 -4
  83. data/spec/local_provider_spec.js +184 -0
  84. data/spec/model_ring_spec.rb +2 -2
  85. data/spec/operation_include_spec.js +2 -2
  86. data/spec/run.html +34 -24
  87. data/spec/type_conversion_spec.js +38 -0
  88. data/vendor/assets/javascripts/jstorage.js +950 -0
  89. metadata +115 -129
  90. data/Gemfile.lock +0 -157
  91. data/app/serializers/default_kojac_serializer.rb +0 -10
  92. data/lib/kojac/ring_strong_parameters.rb +0 -195
  93. data/spec/.DS_Store +0 -0
  94. data/spec/demo/script/rails +0 -6
  95. data/spec/external/.DS_Store +0 -0
@@ -1,7 +1,7 @@
1
1
  describe("Kojac Caching", function() {
2
2
 
3
3
  beforeEach(function () {
4
- this.cache = new Kojac.Object();
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() {
@@ -0,0 +1,9 @@
1
+ describe("Kojac Ember Cache", function() {
2
+
3
+ it("should update models existing in cache from jsono");
4
+
5
+ it("should pass jsono objects for models not in the cache to factory");
6
+
7
+ it("should replace non-objects in cache");
8
+
9
+ });
@@ -13,7 +13,7 @@ describe("Kojac Mock", function() {
13
13
  });
14
14
 
15
15
  beforeEach(function () {
16
- App.cache = new Kojac.Object();
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},{cacheResults: true});
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).toEqual([1,2,3]);
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 = Ember.Object.create();
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
+
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe "Simple Item1 rings" do
4
4
 
5
5
  class Item1 < ActiveRecord::Base
6
- include RingStrongParameters::Model
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 RingStrongParameters::Model
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.Object();
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="external/json2.js"></script>
13
- <script type="text/javascript" src="external/jquery/jquery-1.9.1.js"></script>
14
- <script type="text/javascript" src="external/underscore.js"></script>
15
- <script type="text/javascript" src="external/underscore_plus.js"></script>
16
-
17
- <script type='text/javascript' src='external/steal/steal-121115.js'></script>
18
- <script type="text/javascript" src="external/canjs-1.1.0/can.jquery.js"></script>
19
- <script type="text/javascript" src="external/canjs-1.1.0/can.control.plugin.js"></script>
20
- <script type="text/javascript" src="external/canjs-1.1.0/can.construct.super.js"></script>
21
- <script type="text/javascript" src="external/canjs-1.1.0/can.control.view.js"></script>
22
- <script type="text/javascript" src="external/canjs-1.1.0/can.view.modifiers.js"></script>
23
- <script type="text/javascript" src="external/canjs-1.1.0/can.observe.attributes.js"></script>
24
-
25
- <script type="text/javascript" src="external/ember/handlebars-1.0.0-rc.4.js"></script>
26
- <script type="text/javascript" src="external/ember/ember-1.0.0-rc.6.js"></script>
27
-
28
- <script type="text/javascript" src="../src/can_extensions.js"></script>
29
- <script type="text/javascript" src="../src/kojac.js"></script>
30
- <script type="text/javascript" src="../src/kojac_canjs.js"></script>
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
- <script type="text/javascript" src="kojac_caching_spec.js"></script>
36
- <script type="text/javascript" src="can_cache_spec.js"></script>
37
- <script type="text/javascript" src="can_model_spec.js"></script>
38
- <script type="text/javascript" src="can_factory_spec.js"></script>
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="../src/kojac_ember.js"></script>
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
+ })();