extjs-mvc 0.4.0.j → 0.4.0.k

Sign up to get free protection for your applications and to get access to all the features.
Files changed (169) hide show
  1. data/Rakefile +1 -1
  2. data/VERSION +1 -1
  3. data/lib/extjs-mvc.rb +0 -14
  4. data/lib/extjs-mvc/api.rb +14 -27
  5. data/lib/extjs-mvc/src/App.js +219 -0
  6. data/lib/extjs-mvc/src/MVC.js +260 -0
  7. data/lib/extjs-mvc/src/Presenter.js +52 -0
  8. data/lib/extjs-mvc/src/README.rdoc +69 -0
  9. data/lib/extjs-mvc/src/controller/Controller.js +278 -0
  10. data/lib/extjs-mvc/src/controller/CrudController.js +460 -0
  11. data/lib/extjs-mvc/src/lib/Array.js +26 -0
  12. data/lib/extjs-mvc/src/lib/Booter.js +417 -0
  13. data/lib/extjs-mvc/src/lib/ClassManager.js +191 -0
  14. data/lib/extjs-mvc/src/lib/ControllerClassManager.js +95 -0
  15. data/lib/extjs-mvc/src/lib/Dependencies.js +44 -0
  16. data/lib/extjs-mvc/src/lib/DispatchMatcher.js +98 -0
  17. data/lib/extjs-mvc/src/lib/Dispatcher.js +129 -0
  18. data/lib/extjs-mvc/src/lib/Environment.js +43 -0
  19. data/lib/extjs-mvc/src/lib/Inflector.js +155 -0
  20. data/lib/extjs-mvc/src/lib/ModelClassManager.js +19 -0
  21. data/lib/extjs-mvc/src/lib/Route.js +139 -0
  22. data/lib/extjs-mvc/src/lib/Router.js +282 -0
  23. data/lib/extjs-mvc/src/lib/String.js +94 -0
  24. data/lib/extjs-mvc/src/lib/ViewClassManager.js +229 -0
  25. data/lib/extjs-mvc/src/lib/notes.txt +32 -0
  26. data/lib/extjs-mvc/src/model/AdapterManager.js +30 -0
  27. data/lib/extjs-mvc/src/model/Association.js +26 -0
  28. data/lib/extjs-mvc/src/model/Base.js +63 -0
  29. data/lib/extjs-mvc/src/model/BelongsToAssociation.js +116 -0
  30. data/lib/extjs-mvc/src/model/Cache.js +131 -0
  31. data/lib/extjs-mvc/src/model/HasManyAssociation.js +160 -0
  32. data/lib/extjs-mvc/src/model/Model.js +331 -0
  33. data/lib/extjs-mvc/src/model/UrlBuilder.js +106 -0
  34. data/lib/extjs-mvc/src/model/adapters/AbstractAdapter.js +296 -0
  35. data/lib/extjs-mvc/src/model/adapters/MemoryAdapter.js +103 -0
  36. data/lib/extjs-mvc/src/model/adapters/RESTAdapter.js +345 -0
  37. data/lib/extjs-mvc/src/model/adapters/RESTJSONAdapter.js +68 -0
  38. data/lib/extjs-mvc/src/model/adapters/notes.txt +42 -0
  39. data/lib/extjs-mvc/src/model/associations/Association.js +192 -0
  40. data/lib/extjs-mvc/src/model/associations/notes.txt +87 -0
  41. data/lib/extjs-mvc/src/model/validations/Errors.js +136 -0
  42. data/lib/extjs-mvc/src/model/validations/Plugin.js +139 -0
  43. data/lib/extjs-mvc/src/model/validations/Validations.js +276 -0
  44. data/lib/extjs-mvc/src/notes/Charts.graffle +0 -0
  45. data/lib/extjs-mvc/src/overrides/Ext.Component.js +21 -0
  46. data/lib/extjs-mvc/src/overrides/Ext.extend.js +142 -0
  47. data/lib/extjs-mvc/src/spec/Array.spec.js +15 -0
  48. data/lib/extjs-mvc/src/spec/ExtMVC.spec.js +65 -0
  49. data/lib/extjs-mvc/src/spec/Model.spec.js +370 -0
  50. data/lib/extjs-mvc/src/spec/OS.spec.js +83 -0
  51. data/lib/extjs-mvc/src/spec/Router.spec.js +99 -0
  52. data/lib/extjs-mvc/src/spec/SpecHelper.js +106 -0
  53. data/lib/extjs-mvc/src/spec/String.spec.js +83 -0
  54. data/lib/extjs-mvc/src/spec/model/AbstractAdapter.spec.js +49 -0
  55. data/lib/extjs-mvc/src/spec/model/Associations.spec.js +99 -0
  56. data/lib/extjs-mvc/src/spec/model/Cache.spec.js +5 -0
  57. data/lib/extjs-mvc/src/spec/model/RESTAdapter.spec.js +19 -0
  58. data/lib/extjs-mvc/src/spec/model/ValidationErrors.spec.js +64 -0
  59. data/lib/extjs-mvc/src/spec/model/Validations.spec.js +166 -0
  60. data/lib/extjs-mvc/src/spec/model/ValidationsPlugin.spec.js +108 -0
  61. data/lib/extjs-mvc/src/spec/suite.html +60 -0
  62. data/lib/extjs-mvc/src/specs-old/JSSpec.css +216 -0
  63. data/lib/extjs-mvc/src/specs-old/JSSpec.js +1512 -0
  64. data/lib/extjs-mvc/src/specs-old/all.html +66 -0
  65. data/lib/extjs-mvc/src/specs-old/base.js +14 -0
  66. data/lib/extjs-mvc/src/specs-old/controller.js +17 -0
  67. data/lib/extjs-mvc/src/specs-old/diff_match_patch.js +1 -0
  68. data/lib/extjs-mvc/src/specs-old/model.js +70 -0
  69. data/lib/extjs-mvc/src/specs-old/route.js +38 -0
  70. data/lib/extjs-mvc/src/specs-old/router.js +59 -0
  71. data/lib/extjs-mvc/src/specs-old/string.js +22 -0
  72. data/lib/extjs-mvc/src/testrunner/JSpecFormatter.js +111 -0
  73. data/lib/extjs-mvc/src/testrunner/TestClient.js +181 -0
  74. data/lib/extjs-mvc/src/testrunner/TestGrid.js +351 -0
  75. data/lib/extjs-mvc/src/testrunner/TestRunner.js +110 -0
  76. data/lib/extjs-mvc/src/testrunner/TestViewport.js +94 -0
  77. data/lib/extjs-mvc/src/vendor.yml +30 -0
  78. data/lib/extjs-mvc/src/vendor/ext-3.1.1/vendor.yml +16 -0
  79. data/lib/extjs-mvc/src/view/FormWindow.js +184 -0
  80. data/lib/extjs-mvc/src/view/HasManyEditorGridPanel.js +211 -0
  81. data/lib/extjs-mvc/src/view/scaffold/Edit.js +46 -0
  82. data/lib/extjs-mvc/src/view/scaffold/Index.js +561 -0
  83. data/lib/extjs-mvc/src/view/scaffold/New.js +20 -0
  84. data/lib/extjs-mvc/src/view/scaffold/ScaffoldFormPanel.js +255 -0
  85. data/lib/{vendor.yml → extjs-mvc/vendor.yml} +0 -0
  86. data/test/app/vendor/extjs-mvc/App.js +219 -0
  87. data/test/app/vendor/extjs-mvc/MVC.js +260 -0
  88. data/test/app/vendor/extjs-mvc/Presenter.js +52 -0
  89. data/test/app/vendor/extjs-mvc/README.rdoc +69 -0
  90. data/test/app/vendor/extjs-mvc/controller/Controller.js +278 -0
  91. data/test/app/vendor/extjs-mvc/controller/CrudController.js +460 -0
  92. data/test/app/vendor/extjs-mvc/lib/Array.js +26 -0
  93. data/test/app/vendor/extjs-mvc/lib/Booter.js +417 -0
  94. data/test/app/vendor/extjs-mvc/lib/ClassManager.js +191 -0
  95. data/test/app/vendor/extjs-mvc/lib/ControllerClassManager.js +95 -0
  96. data/test/app/vendor/extjs-mvc/lib/Dependencies.js +44 -0
  97. data/test/app/vendor/extjs-mvc/lib/DispatchMatcher.js +98 -0
  98. data/test/app/vendor/extjs-mvc/lib/Dispatcher.js +129 -0
  99. data/test/app/vendor/extjs-mvc/lib/Environment.js +43 -0
  100. data/test/app/vendor/extjs-mvc/lib/Inflector.js +155 -0
  101. data/test/app/vendor/extjs-mvc/lib/ModelClassManager.js +19 -0
  102. data/test/app/vendor/extjs-mvc/lib/Route.js +139 -0
  103. data/test/app/vendor/extjs-mvc/lib/Router.js +282 -0
  104. data/test/app/vendor/extjs-mvc/lib/String.js +94 -0
  105. data/test/app/vendor/extjs-mvc/lib/ViewClassManager.js +229 -0
  106. data/test/app/vendor/extjs-mvc/lib/notes.txt +32 -0
  107. data/test/app/vendor/extjs-mvc/model/AdapterManager.js +30 -0
  108. data/test/app/vendor/extjs-mvc/model/Association.js +26 -0
  109. data/test/app/vendor/extjs-mvc/model/Base.js +63 -0
  110. data/test/app/vendor/extjs-mvc/model/BelongsToAssociation.js +116 -0
  111. data/test/app/vendor/extjs-mvc/model/Cache.js +131 -0
  112. data/test/app/vendor/extjs-mvc/model/HasManyAssociation.js +160 -0
  113. data/test/app/vendor/extjs-mvc/model/Model.js +331 -0
  114. data/test/app/vendor/extjs-mvc/model/UrlBuilder.js +106 -0
  115. data/test/app/vendor/extjs-mvc/model/adapters/AbstractAdapter.js +296 -0
  116. data/test/app/vendor/extjs-mvc/model/adapters/MemoryAdapter.js +103 -0
  117. data/test/app/vendor/extjs-mvc/model/adapters/RESTAdapter.js +345 -0
  118. data/test/app/vendor/extjs-mvc/model/adapters/RESTJSONAdapter.js +68 -0
  119. data/test/app/vendor/extjs-mvc/model/adapters/notes.txt +42 -0
  120. data/test/app/vendor/extjs-mvc/model/associations/Association.js +192 -0
  121. data/test/app/vendor/extjs-mvc/model/associations/notes.txt +87 -0
  122. data/test/app/vendor/extjs-mvc/model/validations/Errors.js +136 -0
  123. data/test/app/vendor/extjs-mvc/model/validations/Plugin.js +139 -0
  124. data/test/app/vendor/extjs-mvc/model/validations/Validations.js +276 -0
  125. data/test/app/vendor/extjs-mvc/notes/Charts.graffle +0 -0
  126. data/test/app/vendor/extjs-mvc/overrides/Ext.Component.js +21 -0
  127. data/test/app/vendor/extjs-mvc/overrides/Ext.extend.js +142 -0
  128. data/test/app/vendor/extjs-mvc/spec/Array.spec.js +15 -0
  129. data/test/app/vendor/extjs-mvc/spec/ExtMVC.spec.js +65 -0
  130. data/test/app/vendor/extjs-mvc/spec/Model.spec.js +370 -0
  131. data/test/app/vendor/extjs-mvc/spec/OS.spec.js +83 -0
  132. data/test/app/vendor/extjs-mvc/spec/Router.spec.js +99 -0
  133. data/test/app/vendor/extjs-mvc/spec/SpecHelper.js +106 -0
  134. data/test/app/vendor/extjs-mvc/spec/String.spec.js +83 -0
  135. data/test/app/vendor/extjs-mvc/spec/model/AbstractAdapter.spec.js +49 -0
  136. data/test/app/vendor/extjs-mvc/spec/model/Associations.spec.js +99 -0
  137. data/test/app/vendor/extjs-mvc/spec/model/Cache.spec.js +5 -0
  138. data/test/app/vendor/extjs-mvc/spec/model/RESTAdapter.spec.js +19 -0
  139. data/test/app/vendor/extjs-mvc/spec/model/ValidationErrors.spec.js +64 -0
  140. data/test/app/vendor/extjs-mvc/spec/model/Validations.spec.js +166 -0
  141. data/test/app/vendor/extjs-mvc/spec/model/ValidationsPlugin.spec.js +108 -0
  142. data/test/app/vendor/extjs-mvc/spec/suite.html +60 -0
  143. data/test/app/vendor/extjs-mvc/specs-old/JSSpec.css +216 -0
  144. data/test/app/vendor/extjs-mvc/specs-old/JSSpec.js +1512 -0
  145. data/test/app/vendor/extjs-mvc/specs-old/all.html +66 -0
  146. data/test/app/vendor/extjs-mvc/specs-old/base.js +14 -0
  147. data/test/app/vendor/extjs-mvc/specs-old/controller.js +17 -0
  148. data/test/app/vendor/extjs-mvc/specs-old/diff_match_patch.js +1 -0
  149. data/test/app/vendor/extjs-mvc/specs-old/model.js +70 -0
  150. data/test/app/vendor/extjs-mvc/specs-old/route.js +38 -0
  151. data/test/app/vendor/extjs-mvc/specs-old/router.js +59 -0
  152. data/test/app/vendor/extjs-mvc/specs-old/string.js +22 -0
  153. data/test/app/vendor/extjs-mvc/testrunner/JSpecFormatter.js +111 -0
  154. data/test/app/vendor/extjs-mvc/testrunner/TestClient.js +181 -0
  155. data/test/app/vendor/extjs-mvc/testrunner/TestGrid.js +351 -0
  156. data/test/app/vendor/extjs-mvc/testrunner/TestRunner.js +110 -0
  157. data/test/app/vendor/extjs-mvc/testrunner/TestViewport.js +94 -0
  158. data/test/app/vendor/extjs-mvc/vendor.yml +30 -0
  159. data/test/app/vendor/extjs-mvc/vendor/ext-3.1.1/vendor.yml +16 -0
  160. data/test/app/vendor/extjs-mvc/view/FormWindow.js +184 -0
  161. data/test/app/vendor/extjs-mvc/view/HasManyEditorGridPanel.js +211 -0
  162. data/test/app/vendor/extjs-mvc/view/scaffold/Edit.js +46 -0
  163. data/test/app/vendor/extjs-mvc/view/scaffold/Index.js +561 -0
  164. data/test/app/vendor/extjs-mvc/view/scaffold/New.js +20 -0
  165. data/test/app/vendor/extjs-mvc/view/scaffold/ScaffoldFormPanel.js +255 -0
  166. data/test/helper.rb +7 -1
  167. data/test/test_extjs-mvc.rb +46 -0
  168. metadata +167 -7
  169. data/test/test_extjs-mvc-gem.rb +0 -7
@@ -0,0 +1,131 @@
1
+ /**
2
+ * @class ExtMVC.model.Cache
3
+ * @extends Ext.util.Observable
4
+ * Provides an interface for caching model objects which have been fetched from some database/backend
5
+ */
6
+ ExtMVC.model.Cache = function(config) {
7
+ var config = config || {};
8
+
9
+ ExtMVC.model.Cache.superclass.constructor.call(this, config);
10
+
11
+ this.addEvents(
12
+ /**
13
+ * @event beforeadd
14
+ * Fires before an item is added to the cache
15
+ * @param {ExtMVC.model} modelObject The model which is about to be added
16
+ */
17
+ 'beforeadd',
18
+
19
+ /**
20
+ * @event add
21
+ * Fires after an item is added to the cache
22
+ * @param {ExtMVC.model} modelObject The model which was just added
23
+ */
24
+ 'add',
25
+
26
+ /**
27
+ * @event beforeclear
28
+ * Fires before the cache is cleared
29
+ * @param {Number} seconds The number of seconds worth of caches which will be saved
30
+ */
31
+ 'beforeclear',
32
+
33
+ /**
34
+ * @event clear
35
+ * Fires after the cache has been cleared
36
+ * @param {Number} seconds The number of seconds worth of caches which were saved
37
+ */
38
+ 'clear'
39
+ );
40
+ };
41
+
42
+ Ext.extend(ExtMVC.model.Cache, Ext.util.Observable, {
43
+
44
+ /**
45
+ * @property caches
46
+ * @type Object
47
+ * Maintains all cached objects
48
+ */
49
+ caches: {},
50
+
51
+ /**
52
+ * Adds the given model object to the cache. Automatically stores the datetime of the add
53
+ * @param {ExtMVC.model} modelObject The model you want to store in the cache
54
+ */
55
+ add: function(modelObject) {
56
+ if (this.fireEvent('beforeadd', modelObject)) {
57
+ var modelName = modelObject.className;
58
+ var modelId = modelObject.data.id;
59
+
60
+ if (modelName && modelId) {
61
+ modelObject.cachedAt = new Date();
62
+
63
+ this.caches[modelName] = this.caches[modelName] || {};
64
+ this.caches[modelName][modelId] = modelObject;
65
+
66
+ this.fireEvent('add', modelObject);
67
+ return true;
68
+ } else {
69
+
70
+ return false;
71
+ };
72
+ }
73
+ },
74
+
75
+ /**
76
+ * Fetches an object from the cache
77
+ * @param {Object} params params object which must contain at least modelName and id. Optionally
78
+ * supply staleTime, which is the number of seconds old the cached object is allowed to be to get a hit,
79
+ * or a Date which will restrict hits to anything cached after that date
80
+ * @return {ExtMVC.model/null} The model if found, or null
81
+ */
82
+ fetch: function(params) {
83
+ this.caches[params['modelName']] = this.caches[params['modelName']] || {};
84
+
85
+ var params = params || {};
86
+ var hit = this.caches[params['modelName']][params['id']];
87
+
88
+ if (hit) {
89
+ if (params.staleTime) {
90
+ if (typeof params.staleTime == 'number') {
91
+ var date = new Date();
92
+ date.setTime(date.getTime() - (1000 * params.staleTime));
93
+ params.staleTime = date;
94
+ };
95
+
96
+ //make sure we have a date object
97
+ if (params.staleTime.getTime && hit.cachedAt > params.staleTime) {
98
+ return hit;
99
+ }
100
+ } else {
101
+ return hit;
102
+ };
103
+ };
104
+ },
105
+
106
+ /**
107
+ * Clears all objects more than the given number of seconds old (defaults to clearing all objects)
108
+ * @param {Number} seconds The number of seconds to keep cached objects for (e.g. setting this to 10 would delete anything cached more than 10 seconds ago)
109
+ */
110
+ clear: function(seconds) {
111
+ var seconds = seconds || 0;
112
+ var date = new Date();
113
+ date.setTime(date.getTime() - (1000 * seconds));
114
+
115
+ if (this.fireEvent('beforeclear', seconds)) {
116
+ if (seconds == 0) {
117
+ this.caches = {};
118
+ } else {
119
+ for (var i=0; i < this.caches.length; i++) {
120
+ for (var j=0; j < this.caches[i].length; j++) {
121
+ if (this.caches[i][j].cachedAt < date) {
122
+ delete this.caches[i][j];
123
+ };
124
+ };
125
+ };
126
+ };
127
+
128
+ this.fireEvent('clear', seconds);
129
+ }
130
+ }
131
+ });
@@ -0,0 +1,160 @@
1
+ /**
2
+ * @class ExtMVC.model.HasManyAssociation
3
+ * @extends ExtMVC.model.Association
4
+ */
5
+ ExtMVC.model.HasManyAssociation = function(ownerObject, config) {
6
+ var config = config || {};
7
+
8
+ Ext.applyIf(config, {
9
+ offset: 0,
10
+ limit: 25,
11
+ associationName: ExtMVC.model.Association.hasManyAssociationName(config.name)
12
+ });
13
+
14
+ //TODO: these should be abstracted to a parent object (as should private vars and funcs below)
15
+ Ext.applyIf(config, {
16
+ primaryKey: 'id',
17
+ foreignKey: ownerObject.foreignKeyName,
18
+ extend: {},
19
+
20
+ className: (ownerObject.constructor.namespace ? ownerObject.constructor.namespace + '.' + config.name : config.name)
21
+ });
22
+
23
+ //get a reference to the class definition function of the associated object
24
+ //(e.g. a hasMany: ['Post'] association will return a reference to Post)
25
+ var associatedObjectClass = eval(config.className);
26
+
27
+ /**
28
+ * Private, calls the ownerObject's class method with the supplied args
29
+ */
30
+ function callOwnerObjectClassMethod(method, args, scope) {
31
+ return ownerObject.constructor[method].apply(scope || ownerObject.constructor, args || []);
32
+ };
33
+
34
+ /**
35
+ * Private, calls the associated object's class method with the supplied args
36
+ */
37
+ function callAssociatedObjectClassMethod (method, args, scope) {
38
+ return associatedObjectClass[method].apply(scope || associatedObjectClass, args || []);
39
+ }
40
+
41
+ return Ext.applyIf(config.extend, {
42
+
43
+ /**
44
+ * @property associationName
45
+ * @type String
46
+ * Returns the name of this association so that the model can add it to its definition
47
+ */
48
+ associationName: config.associationName,
49
+
50
+ /**
51
+ * @property associationType
52
+ * @type String
53
+ * The type of association (hasMany or belongsTo)
54
+ */
55
+ associationType: 'hasMany',
56
+
57
+ /**
58
+ * Returns the URL for this association - e.g. if model User hasMany Posts, user.posts.url() will
59
+ * return something like /users/1/posts.
60
+ * Pass additional parameter options as arguments to pass straight through to the URL builder - e.g.:
61
+ * user.posts.url('published'); // = /users/1/posts/published
62
+ * @return {String} The URL for the has many resource
63
+ */
64
+ url: function() {
65
+ var args = [ownerObject, associatedObjectClass];
66
+ for (var i=0; i < arguments.length; i++) {
67
+ args.push(arguments[i]);
68
+ };
69
+
70
+ return ExtMVC.UrlBuilder.urlFor.apply(ExtMVC.UrlBuilder, args);
71
+ },
72
+
73
+ /**
74
+ * Passes through to the owner class's findById class method, adding a foreign key constraint first
75
+ * @param {String} id The ID of the associated object to retrieve
76
+ * @param {Object} options Options passed along to the associated model's Class's findById method. Pass in loadSuccess, loadFailure, conditions, order etc here
77
+ */
78
+ findById: function(id, options) {
79
+ var options = options || {};
80
+
81
+ //add a condition to constrain to the owner object's id
82
+ if (!options.conditions) { options.conditions = []; }
83
+ options.conditions.push({
84
+ key: config.foreignKey,
85
+ value: ownerObject.data[config.primaryKey]
86
+ });
87
+
88
+ return callAssociatedObjectClassMethod('findById', [id, options]);
89
+ },
90
+
91
+ findAll: function(storeOptions) {
92
+ var storeOptions = storeOptions || {};
93
+ Ext.applyIf(storeOptions, {
94
+ url: this.url(),
95
+
96
+ listeners: {
97
+
98
+ //Once we have fetched all hasMany records, make sure newRecord is false, and set the parent
99
+ //relationship to point to this ownerObject (The object which hasMany of these records)
100
+ 'load': {
101
+ scope: this,
102
+ fn: function(store, records, options) {
103
+ Ext.each(records, function(record) {
104
+ record.newRecord = false;
105
+ if (record.parent && record.parent.set) {
106
+ record.parent.set(ownerObject);
107
+ }
108
+ }, this);
109
+ }
110
+ }
111
+ }
112
+ });
113
+
114
+ return callAssociatedObjectClassMethod('findAll', [storeOptions]);
115
+ },
116
+
117
+ /**
118
+ * Creates (builds and attempts to save) this associated model
119
+ * @param {Object} fields Object with keys and values to initialise this object
120
+ * @param {Object} saveConfig Passed to the Ext.Ajax request, supply success and failure options here
121
+ */
122
+ create: function(fields, saveConfig) {
123
+ return this.build(fields).save(saveConfig);
124
+ },
125
+
126
+ /**
127
+ * Builds an instantiation of the associated model with the supplied data.
128
+ * Automatically links in the correct foreign key
129
+ * @param {Object} fields The data to initialize this object with
130
+ */
131
+ build: function(fields) {
132
+ var fields = fields || {};
133
+
134
+ //instantiate the new object with the augmented fields
135
+ var obj = new associatedObjectClass(fields);
136
+
137
+ //set up the object's belongsTo association. This also sets up the foreign key
138
+ var assocName = ExtMVC.model.Association.belongsToAssociationName(ownerObject.className);
139
+ obj[assocName].set(ownerObject);
140
+
141
+ return obj;
142
+ },
143
+
144
+ /**
145
+ * Adds an existing (saved) instantiation of the associated model to this model's hasMany collection
146
+ * @param {ExtMVC.model} modelObject The existing, saved model
147
+ */
148
+ add: function(modelObject) {
149
+ //TODO: implement this
150
+
151
+ },
152
+
153
+ destroy: function(id) {
154
+ //TODO: implement this
155
+
156
+ }
157
+ });
158
+ };
159
+
160
+ // Ext.extend(ExtMVC.model.HasManyAssociation, ExtMVC.model.Association);
@@ -0,0 +1,331 @@
1
+ /**
2
+ * @class ExtMVC.model
3
+ * @extends Object
4
+ * Manages the definition and creation of model classes.
5
+ *
6
+ * <h2>Defining models</h2>
7
+ * <p>Models in your application are defined using Ext.model.define, which is given 2 arguments - the String name of your model and a config object</p>
8
+ * <p>
9
+ * <pre><code>
10
+ ExtMVC.model.define("MyModel", {
11
+ fields: [
12
+ {name: 'id', type: 'int'},
13
+ {name: 'title', type: 'string'},
14
+ {name: 'price', type: 'int'}
15
+ ],
16
+
17
+ validatesPresenceOf: ['id', 'title'],
18
+ classMethods: {
19
+ doSomething: function() {alert('oh hi!');}
20
+ }
21
+ });
22
+ </code></pre>
23
+ *
24
+ * Fields are passed straight to the underlying Ext.data.Record.
25
+ * classMethods are defined on the constructor function, e.g. from the example above:
26
+ *
27
+ <pre><code>
28
+ MyModel.doSomething(); //alerts 'oh hi'
29
+ </code></pre>
30
+ *
31
+ * All other properties are simply assigned to the Model's prototype, but may be intercepted by plugins
32
+ *
33
+ * <h2>Extending other models</h2>
34
+ * Models can extend other models using the 'extend' property:
35
+ <pre></code>
36
+ ExtMVC.model.define("Product", {
37
+ fields: [...]
38
+ }
39
+
40
+ ExtMVC.model.define("Flower", {
41
+ extend: "Product",
42
+ fields: [...]
43
+ }
44
+ </code></pre>
45
+ *
46
+ * The class builds a simple dependency graph to allow models to extend other models, e.g.:
47
+ *
48
+ <pre><code>
49
+ //this model definition will not actually be created until SuperUser has been defined
50
+ ExtMVC.model.define("SuperUser", {
51
+ extend: "User",
52
+ fields: [
53
+ {name: 'isAdmin', type: 'bool'}
54
+ ]
55
+ });
56
+
57
+ //SuperUser does not extend anything, so is created immediately. User is then also created
58
+ ExtMVC.model.define("User", {
59
+ fields: [
60
+ {name: 'id', type: 'int'},
61
+ {name: 'username', type: 'string'}
62
+ ],
63
+
64
+ validatesPresenceOf: ['id', 'username']
65
+ });
66
+
67
+ //At this point both SuperUser and User have been created and are instantiable and extendable.
68
+ </code></pre>
69
+ *
70
+ * When a model extends another one it inherits all of that model's instance and class methods. It also
71
+ * inherits all of the superclass model's fields, overwriting if redefined in the subclass. In the example
72
+ * above the SuperUser model would have fields 'id', 'username' and 'isAdmin', and will also have inherited
73
+ * User's validatesPresenceOf declaration
74
+ *
75
+ * @singleton
76
+ */
77
+ ExtMVC.model = {
78
+ /**
79
+ * @property pendingCreation
80
+ * @type Object
81
+ * An object of model creation configurations awaiting definition because their dependency model(s) have not yet
82
+ * been defined. e.g. {'User': [{name: 'SuperUser', config: someConfigObject}, {name: 'AdminUser', config: anotherCfgObj}]}
83
+ * signifies that SuperUser and AdminUser should be defined as soon as User has been defined
84
+ */
85
+ pendingCreation: {},
86
+
87
+ /**
88
+ * Returns an array of any Model subclasses waiting for this model to be defined
89
+ * @param {String} modelName The dependency model name to check against
90
+ * @return {Array} An array of model definitions (e.g. [{name: 'MyModel', config: someObject}])
91
+ */
92
+ getModelsPendingDefinitionOf: function(modelName) {
93
+ return this.pendingCreation[modelName] || [];
94
+ },
95
+
96
+ /**
97
+ * Adds a model definition to the pendingCreation object if it is waiting for another model to be defined first
98
+ * @param {String} dependencyModelName The name of another model which must be created before this one
99
+ * @param {String} dependentModelName The name of the new model to be defined after its dependency
100
+ * @param {Object} config The new model's config object, as sent to ExtMVC.model.define
101
+ */
102
+ setModelPendingDefinitionOf: function(dependencyModelName, dependentModelName, config) {
103
+ var arr = this.pendingCreation[dependencyModelName] || [];
104
+
105
+ arr.push({name: dependentModelName, config: config});
106
+
107
+ this.pendingCreation[dependencyModelName] = arr;
108
+ },
109
+
110
+ /**
111
+ * @property strictMode
112
+ * @type Boolean
113
+ * Throws errors rather than return false when performing operations such as overwriting existing models
114
+ * Defaults to false
115
+ */
116
+ strictMode: false,
117
+
118
+ /**
119
+ * @property modelNamespace
120
+ * @type Object
121
+ * The object into which Models are defined. This defaults to window, meaning calls to ExtMVC.model.create
122
+ * will create models globally scoped unless this is modified. Setting this instead to MyApp.models would
123
+ * mean that a model called 'User' would be defined as MyApp.models.User instead
124
+ */
125
+ modelNamespace: function() {
126
+ Ext.ns('ExtMVC.modelsTemp');
127
+
128
+ return ExtMVC.modelsTemp;
129
+ }(),
130
+
131
+ /**
132
+ * Sets a model up for creation. If this model doesn't extend any other Models that haven't been defined yet
133
+ * it is returned immediately, otherwise it is placed into a queue and defined as soon as its dependency models
134
+ * are in place. Example:
135
+ *
136
+ * ExtMVC.model.define('MyApp.models.MyModel', {
137
+ * fields: [
138
+ * {name: 'title', type: 'string'},
139
+ * {name: 'price', type: 'number'},
140
+ * {name: 'available', type: 'bool'}
141
+ * ],
142
+ *
143
+ * //Adds tax to the price field
144
+ * calculatePrice: function() {
145
+ * return this.data.price * 1.15;
146
+ * },
147
+ *
148
+ * classMethods: {
149
+ * findAvailable: function() {
150
+ * //some logic to find all available MyModel's
151
+ * }
152
+ * }
153
+ * });
154
+ *
155
+ * var m = new MyApp.models.MyModel({title: 'Test', available: true, price: 100});
156
+ * m.calculatePrice(); // => 115
157
+ * MyApp.models.MyModel.findAvailable(); // => Returns as defined above
158
+ *
159
+ * @param {String} modelName The name of the model to create (e.g. 'User')
160
+ * @param {Object} extensions An object containing field definitions and any extension methods to add to this model
161
+ * @return {ExtMVC.model.Base/Null} The newly defined model constructor, or null if the model can't be defined yet
162
+ */
163
+ define: function(modelName, extensions) {
164
+ var createNow = true,
165
+ extensions = extensions || {};
166
+
167
+ if (typeof extensions.extend != 'undefined') {
168
+ var superclass = this.modelNamespace[extensions.extend];
169
+ if (typeof superclass == 'undefined') {
170
+ //the model we're extending hasn't been created yet
171
+ createNow = false;
172
+ this.setModelPendingDefinitionOf(extensions.extend, modelName, extensions);
173
+ };
174
+ };
175
+
176
+ if (createNow) return this.create.apply(this, arguments);
177
+ },
178
+
179
+ /**
180
+ * @ignore
181
+ * Creates a new ExtMVC.model.Base subclass and sets up all fields, instance and class methods.
182
+ * Don't use this directly unless you know what you're doing - use define instead (with the same arguments)
183
+ *
184
+ * @param {String} modelName The full model name to define, including namespace (e.g. 'MyApp.models.MyModel')
185
+ * @param {Object} extensions An object containing field definitions and any extension methods to add to this model
186
+ */
187
+ create: function(modelName, extensions) {
188
+ extensions = extensions || {};
189
+
190
+ //check that this model has not already been defined
191
+ if (this.isAlreadyDefined(modelName)) {
192
+ if (this.strictMode) throw new Error(modelName + ' is already defined');
193
+ return false;
194
+ }
195
+
196
+ //get a handle on the super class model if extending (this will be undefined if we are not extending another model)
197
+ var superclassModel = this.modelNamespace[extensions.extend];
198
+
199
+ var fields = this.buildFields(extensions.fields, superclassModel);
200
+ delete extensions.fields;
201
+
202
+ //create the base Ext.data.Record, which we'll extend in a moment, and assign it to our model namespace
203
+ var model = this.modelNamespace[modelName] = Ext.data.Record.create(fields);
204
+
205
+ //separate out any methods meant to operate at class level
206
+ var classMethods = extensions.classMethods || {};
207
+ delete extensions.classMethods;
208
+
209
+ //extend our new record firstly with Model.Base, then apply any user extensions
210
+ Ext.apply(model.prototype, extensions);
211
+
212
+ //if we're extending another model, add class and instance methods now
213
+ if (typeof superclassModel != 'undefined') {
214
+ Ext.applyIf(classMethods, superclassModel);
215
+ Ext.applyIf(model.prototype, superclassModel.prototype);
216
+ };
217
+
218
+ //set up the various string names associated with this model
219
+ model.prototype.modelName = modelName;
220
+ this.setupNames(model);
221
+
222
+ //add any class methods to the class level
223
+ for (var methodName in classMethods) {
224
+ if (methodName != 'prototype') model[methodName] = classMethods[methodName];
225
+ };
226
+
227
+ this.initializePlugins(model);
228
+ this.afterCreate(modelName);
229
+
230
+ return model;
231
+ },
232
+
233
+ /**
234
+ * @ignore
235
+ * Creates any other models that were waiting for this one to be created. Do not override this
236
+ * unless you really know what you are doing...
237
+ * @param {String} modelName The name of the model that was just created
238
+ */
239
+ afterCreate: function(modelName) {
240
+ var awaiting = this.getModelsPendingDefinitionOf(modelName);
241
+ if (awaiting) {
242
+ Ext.each(awaiting, function(obj) {
243
+ this.create(obj.name, obj.config);
244
+ }, this);
245
+ };
246
+ },
247
+
248
+ /**
249
+ * Checks if a given model name has already been defined, or is awaiting creation.
250
+ * @param {String} modelName the name of the new model to check
251
+ * @return {Boolean} True if the model has already been defined somewhere
252
+ */
253
+ isAlreadyDefined: function(modelName) {
254
+ if (typeof this.modelNamespace[modelName] != "undefined") return true;
255
+
256
+ var found = false;
257
+
258
+ //check that this model is not awaiting creation
259
+ for (superclass in this.pendingCreation) {
260
+ var subclasses = this.pendingCreation[superclass];
261
+ Ext.each(subclasses, function(s) {
262
+ if (s.name == modelName) found = true;
263
+ }, this);
264
+ }
265
+
266
+ return found;
267
+ },
268
+
269
+ /**
270
+ * @ignore
271
+ * Builds an array of fields for this model, adding fields from the super class if present
272
+ */
273
+ buildFields: function(subclassFields, superclass) {
274
+ subclassFields = subclassFields || [];
275
+
276
+ var fields = new Ext.util.MixedCollection(false, function(field) { return field.name; });
277
+ fields.addAll(subclassFields);
278
+
279
+ if (typeof superclass != 'undefined') {
280
+ superclass.prototype.fields.each(function(field) {
281
+ if (typeof fields.get(field.name) == 'undefined') fields.add(field);
282
+ });
283
+ };
284
+
285
+ return fields.items;
286
+ },
287
+
288
+ /**
289
+ * Sets up the various names required by this model, such as tableName, humanName etc
290
+ * @param {Object} model The model to set up names on
291
+ * @return {Object} The model, decorated with names
292
+ */
293
+ setupNames: function(model) {
294
+ var p = model.prototype,
295
+ i = ExtMVC.Inflector;
296
+
297
+ Ext.applyIf(model.prototype, {
298
+ tableName : i.pluralize(p.modelName.underscore()),
299
+ foreignKeyName : i.singularize(p.modelName.underscore()) + '_id',
300
+ singularHumanName: p.modelName.humanize().titleize(),
301
+ pluralHumanName : i.pluralize(p.modelName.humanize().titleize())
302
+ });
303
+ },
304
+
305
+ /**
306
+ * @property plugins
307
+ * @type Array
308
+ * An array containing all plugin constructor functions - these get applied at model creation time
309
+ */
310
+ plugins: [],
311
+
312
+ /**
313
+ * Makes Model aware of a new plugin. All plugins defined here will be initialized when a model is created
314
+ * @param {Function} plugin The plugin object
315
+ */
316
+ addPlugin: function(plugin) {
317
+ this.plugins.push(plugin);
318
+ },
319
+
320
+ /**
321
+ * Runs each plugin's initialize method with a newly created model constructor
322
+ * @param {ExtMVC.model} model The model to initialize the plugin with
323
+ */
324
+ initializePlugins: function(model) {
325
+ Ext.each(this.plugins, function(plugin) {
326
+ plugin.initialize(model);
327
+ }, this);
328
+ }
329
+ };
330
+
331
+ Ext.ns('ExtMVC.model.plugin');