kojac 0.9.1 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ })();