]> git.proxmox.com Git - extjs.git/blob - extjs/packages/core/src/class/Mixin.js
add extjs 6.0.1 sources
[extjs.git] / extjs / packages / core / src / class / Mixin.js
1 /**
2 * This class is a base class for mixins. These are classes that extend this class and are
3 * designed to be used as a `mixin` by user code.
4 *
5 * It provides mixins with the ability to "hook" class methods of the classes in to which
6 * they are mixed. For example, consider the `destroy` method pattern. If a mixin class
7 * had cleanup requirements, it would need to be called as part of `destroy`.
8 *
9 * Starting with a basic class we might have:
10 *
11 * Ext.define('Foo.bar.Base', {
12 * destroy: function () {
13 * console.log('B');
14 * // cleanup
15 * }
16 * });
17 *
18 * A derived class would look like this:
19 *
20 * Ext.define('Foo.bar.Derived', {
21 * extend: 'Foo.bar.Base',
22 *
23 * destroy: function () {
24 * console.log('D');
25 * // more cleanup
26 *
27 * this.callParent(); // let Foo.bar.Base cleanup as well
28 * }
29 * });
30 *
31 * To see how using this class help, start with a "normal" mixin class that also needs to
32 * cleanup its resources. These mixins must be called explicitly by the classes that use
33 * them. For example:
34 *
35 * Ext.define('Foo.bar.Util', {
36 * destroy: function () {
37 * console.log('U');
38 * }
39 * });
40 *
41 * Ext.define('Foo.bar.Derived', {
42 * extend: 'Foo.bar.Base',
43 *
44 * mixins: {
45 * util: 'Foo.bar.Util'
46 * },
47 *
48 * destroy: function () {
49 * console.log('D');
50 * // more cleanup
51 *
52 * this.mixins.util.destroy.call(this);
53 *
54 * this.callParent(); // let Foo.bar.Base cleanup as well
55 * }
56 * });
57 *
58 * var obj = new Foo.bar.Derived();
59 *
60 * obj.destroy();
61 * // logs D then U then B
62 *
63 * This class is designed to solve the above in simpler and more reliable way.
64 *
65 * ## mixinConfig
66 *
67 * Using `mixinConfig` the mixin class can provide "before" or "after" hooks that do not
68 * involve the derived class implementation. This also means the derived class cannot
69 * adjust parameters to the hook methods.
70 *
71 * Ext.define('Foo.bar.Util', {
72 * extend: 'Ext.Mixin',
73 *
74 * mixinConfig: {
75 * after: {
76 * destroy: 'destroyUtil'
77 * }
78 * },
79 *
80 * destroyUtil: function () {
81 * console.log('U');
82 * }
83 * });
84 *
85 * Ext.define('Foo.bar.Class', {
86 * mixins: {
87 * util: 'Foo.bar.Util'
88 * },
89 *
90 * destroy: function () {
91 * console.log('D');
92 * }
93 * });
94 *
95 * var obj = new Foo.bar.Derived();
96 *
97 * obj.destroy();
98 * // logs D then U
99 *
100 * If the destruction should occur in the other order, you can use `before`:
101 *
102 * Ext.define('Foo.bar.Util', {
103 * extend: 'Ext.Mixin',
104 *
105 * mixinConfig: {
106 * before: {
107 * destroy: 'destroyUtil'
108 * }
109 * },
110 *
111 * destroyUtil: function () {
112 * console.log('U');
113 * }
114 * });
115 *
116 * Ext.define('Foo.bar.Class', {
117 * mixins: {
118 * util: 'Foo.bar.Util'
119 * },
120 *
121 * destroy: function () {
122 * console.log('D');
123 * }
124 * });
125 *
126 * var obj = new Foo.bar.Derived();
127 *
128 * obj.destroy();
129 * // logs U then D
130 *
131 * ### Chaining
132 *
133 * One way for a mixin to provide methods that act more like normal inherited methods is
134 * to use an `on` declaration. These methods will be injected into the `callParent` chain
135 * between the derived and superclass. For example:
136 *
137 * Ext.define('Foo.bar.Util', {
138 * extend: 'Ext.Mixin',
139 *
140 * mixinConfig: {
141 * on: {
142 * destroy: function () {
143 * console.log('M');
144 * }
145 * }
146 * }
147 * });
148 *
149 * Ext.define('Foo.bar.Base', {
150 * destroy: function () {
151 * console.log('B');
152 * }
153 * });
154 *
155 * Ext.define('Foo.bar.Derived', {
156 * extend: 'Foo.bar.Base',
157 *
158 * mixins: {
159 * util: 'Foo.bar.Util'
160 * },
161 *
162 * destroy: function () {
163 * this.callParent();
164 * console.log('D');
165 * }
166 * });
167 *
168 * var obj = new Foo.bar.Derived();
169 *
170 * obj.destroy();
171 * // logs M then B then D
172 *
173 * As with `before` and `after`, the value of `on` can be a method name.
174 *
175 * Ext.define('Foo.bar.Util', {
176 * extend: 'Ext.Mixin',
177 *
178 * mixinConfig: {
179 * on: {
180 * destroy: 'onDestroy'
181 * }
182 * }
183 *
184 * onDestroy: function () {
185 * console.log('M');
186 * }
187 * });
188 *
189 * Because this technique leverages `callParent`, the derived class controls the time and
190 * parameters for the call to all of its bases (be they `extend` or `mixin` flavor).
191 *
192 * ### Derivations
193 *
194 * Some mixins need to process class extensions of their target class. To do this you can
195 * define an `extended` method like so:
196 *
197 * Ext.define('Foo.bar.Util', {
198 * extend: 'Ext.Mixin',
199 *
200 * mixinConfig: {
201 * extended: function (baseClass, derivedClass, classBody) {
202 * // This function is called whenever a new "derivedClass" is created
203 * // that extends a "baseClass" in to which this mixin was mixed.
204 * }
205 * }
206 * });
207 *
208 * @protected
209 */
210 Ext.define('Ext.Mixin', function (Mixin) { return {
211
212 statics: {
213 addHook: function (hookFn, targetClass, methodName, mixinClassPrototype) {
214 var isFunc = Ext.isFunction(hookFn),
215 hook = function () {
216 var a = arguments,
217 fn = isFunc ? hookFn : mixinClassPrototype[hookFn],
218 result = this.callParent(a);
219 fn.apply(this, a);
220 return result;
221 },
222 existingFn = targetClass.hasOwnProperty(methodName) &&
223 targetClass[methodName];
224
225 if (isFunc) {
226 hookFn.$previous = Ext.emptyFn; // no callParent for these guys
227 }
228
229 hook.$name = methodName;
230 hook.$owner = targetClass.self;
231
232 if (existingFn) {
233 hook.$previous = existingFn.$previous;
234 existingFn.$previous = hook;
235 } else {
236 targetClass[methodName] = hook;
237 }
238 }
239 },
240
241 onClassExtended: function(cls, data) {
242 var mixinConfig = data.mixinConfig,
243 hooks = data.xhooks,
244 superclass = cls.superclass,
245 onClassMixedIn = data.onClassMixedIn,
246 parentMixinConfig,
247 befores, afters, extended;
248
249 if (hooks) {
250 // Legacy way
251 delete data.xhooks;
252 (mixinConfig || (data.mixinConfig = mixinConfig = {})).on = hooks;
253 }
254
255 if (mixinConfig) {
256 parentMixinConfig = superclass.mixinConfig;
257
258 if (parentMixinConfig) {
259 data.mixinConfig = mixinConfig = Ext.merge({}, parentMixinConfig, mixinConfig);
260 }
261
262 data.mixinId = mixinConfig.id;
263
264 //<debug>
265 if (mixinConfig.beforeHooks) {
266 Ext.raise('Use of "beforeHooks" is deprecated - use "before" instead');
267 }
268 if (mixinConfig.hooks) {
269 Ext.raise('Use of "hooks" is deprecated - use "after" instead');
270 }
271 if (mixinConfig.afterHooks) {
272 Ext.raise('Use of "afterHooks" is deprecated - use "after" instead');
273 }
274 //</debug>
275
276 befores = mixinConfig.before;
277 afters = mixinConfig.after;
278 hooks = mixinConfig.on;
279 extended = mixinConfig.extended;
280 }
281
282 if (befores || afters || hooks || extended) {
283 // Note: tests are with Ext.Class
284 data.onClassMixedIn = function (targetClass) {
285 var mixin = this.prototype,
286 targetProto = targetClass.prototype,
287 key;
288
289 if (befores) {
290 Ext.Object.each(befores, function (key, value) {
291 targetClass.addMember(key, function () {
292 if (mixin[value].apply(this, arguments) !== false) {
293 return this.callParent(arguments);
294 }
295 });
296 });
297 }
298
299 if (afters) {
300 Ext.Object.each(afters, function (key, value) {
301 targetClass.addMember(key, function () {
302 var ret = this.callParent(arguments);
303 mixin[value].apply(this, arguments);
304 return ret;
305 });
306 });
307 }
308
309 if (hooks) {
310 for (key in hooks) {
311 Mixin.addHook(hooks[key], targetProto, key, mixin);
312 }
313 }
314
315 if (extended) {
316 targetClass.onExtended(function () {
317 var args = Ext.Array.slice(arguments, 0);
318 args.unshift(targetClass);
319 return extended.apply(this, args);
320 }, this);
321 }
322
323 if (onClassMixedIn) {
324 onClassMixedIn.apply(this, arguments);
325 }
326 };
327 }
328 }
329 };});