├── test ├── test.html └── compose.js ├── package.js ├── package.json ├── compose.js └── README.md /test/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 |
10 | 11 | -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | var miniExcludes = { 2 | "compose/README.md": 1, 3 | "compose/package": 1 4 | }, 5 | amdExcludes = { 6 | }, 7 | isJsRe = /\.js$/, 8 | isTestRe = /\/test\//; 9 | 10 | var profile = { 11 | resourceTags: { 12 | test: function(filename, mid){ 13 | return isTestRe.test(filename); 14 | }, 15 | 16 | miniExclude: function(filename, mid){ 17 | return isTestRe.test(filename) || mid in miniExcludes; 18 | }, 19 | 20 | amd: function(filename, mid){ 21 | return isJsRe.test(filename) && !(mid in amdExcludes); 22 | } 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "compose", 3 | "version": "0.1.2", 4 | "author": "Kris Zyp", 5 | "description": "Fast and light object composition based on mixins and traits", 6 | "licenses": [ 7 | { 8 | "type": "AFLv2.1", 9 | "url": "http://trac.dojotoolkit.org/browser/dojo/trunk/LICENSE#L43" 10 | }, 11 | { 12 | "type": "BSD", 13 | "url": "http://trac.dojotoolkit.org/browser/dojo/trunk/LICENSE#L13" 14 | } 15 | ], 16 | "repository": { 17 | "type":"git", 18 | "url":"http://github.com/kriszyp/compose" 19 | }, 20 | "dependencies": { 21 | "patr": "0.3.0" 22 | }, 23 | "directories": { 24 | "lib": "." 25 | }, 26 | "main": "./compose", 27 | "mappings":{ 28 | "patr": "http://github.com/kriszyp/patr/zipball/v0.2.5" 29 | }, 30 | "icon": "http://icons.iconarchive.com/icons/kawsone/teneo/64/BiiBall-icon.png", 31 | "dojoBuild": "package.js" 32 | } 33 | -------------------------------------------------------------------------------- /compose.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ComposeJS, object composition for JavaScript, featuring 3 | * JavaScript-style prototype inheritance and composition, multiple inheritance, 4 | * mixin and traits-inspired conflict resolution and composition 5 | */ 6 | (function(define){ 7 | "use strict"; 8 | define([], function(){ 9 | // function for creating instances from a prototype 10 | function Create(){ 11 | } 12 | var delegate = Object.create ? 13 | function(proto){ 14 | return Object.create(typeof proto == "function" ? proto.prototype : proto || Object.prototype); 15 | } : 16 | function(proto){ 17 | Create.prototype = typeof proto == "function" ? proto.prototype : proto; 18 | var instance = new Create(); 19 | Create.prototype = null; 20 | return instance; 21 | }; 22 | function validArg(arg){ 23 | if(!arg){ 24 | throw new Error("Compose arguments must be functions or objects"); 25 | } 26 | return arg; 27 | } 28 | // this does the work of combining mixins/prototypes 29 | function mixin(instance, args, i){ 30 | // use prototype inheritance for first arg 31 | var value, argsLength = args.length; 32 | for(; i < argsLength; i++){ 33 | var arg = args[i]; 34 | if(typeof arg == "function"){ 35 | // the arg is a function, use the prototype for the properties 36 | var prototype = arg.prototype; 37 | for(var key in prototype){ 38 | value = prototype[key]; 39 | var own = prototype.hasOwnProperty(key); 40 | if(typeof value == "function" && key in instance && value !== instance[key]){ 41 | var existing = instance[key]; 42 | if(value == required){ 43 | // it is a required value, and we have satisfied it 44 | value = existing; 45 | } 46 | else if(!own){ 47 | // if it is own property, it is considered an explicit override 48 | // TODO: make faster calls on this, perhaps passing indices and caching 49 | if(isInMethodChain(value, key, getBases([].slice.call(args, 0, i), true))){ 50 | // this value is in the existing method's override chain, we can use the existing method 51 | value = existing; 52 | }else if(!isInMethodChain(existing, key, getBases([arg], true))){ 53 | // the existing method is not in the current override chain, so we are left with a conflict 54 | console.error("Conflicted method " + key + ", final composer must explicitly override with correct method."); 55 | } 56 | } 57 | } 58 | if(value && value.install && own && !isInMethodChain(existing, key, getBases([arg], true))){ 59 | // apply modifier 60 | value.install.call(instance, key); 61 | }else{ 62 | instance[key] = value; 63 | } 64 | } 65 | }else{ 66 | // it is an object, copy properties, looking for modifiers 67 | for(var key in validArg(arg)){ 68 | var value = arg[key]; 69 | if(typeof value == "function"){ 70 | if(value.install){ 71 | // apply modifier 72 | value.install.call(instance, key); 73 | continue; 74 | } 75 | if(key in instance){ 76 | if(value == required){ 77 | // required requirement met 78 | continue; 79 | } 80 | } 81 | } 82 | // add it to the instance 83 | instance[key] = value; 84 | } 85 | } 86 | } 87 | return instance; 88 | } 89 | // allow for override (by es5 module) 90 | Compose._setMixin = function(newMixin){ 91 | mixin = newMixin; 92 | }; 93 | function isInMethodChain(method, name, prototypes){ 94 | // searches for a method in the given prototype hierarchy 95 | for(var i = 0; i < prototypes.length;i++){ 96 | var prototype = prototypes[i]; 97 | if(prototype[name] == method){ 98 | // found it 99 | return true; 100 | } 101 | } 102 | } 103 | // Decorator branding 104 | function Decorator(install, direct){ 105 | function Decorator(){ 106 | if(direct){ 107 | return direct.apply(this, arguments); 108 | } 109 | throw new Error("Decorator not applied"); 110 | } 111 | Decorator.install = install; 112 | return Decorator; 113 | } 114 | Compose.Decorator = Decorator; 115 | // aspect applier 116 | function aspect(handler){ 117 | return function(advice){ 118 | return Decorator(function install(key){ 119 | var baseMethod = this[key]; 120 | (advice = this[key] = baseMethod ? handler(this, baseMethod, advice) : advice).install = install; 121 | }, advice); 122 | }; 123 | }; 124 | // around advice, useful for calling super methods too 125 | Compose.around = aspect(function(target, base, advice){ 126 | return advice.call(target, base); 127 | }); 128 | Compose.before = aspect(function(target, base, advice){ 129 | return function(){ 130 | var results = advice.apply(this, arguments); 131 | if(results !== stop){ 132 | return base.apply(this, results || arguments); 133 | } 134 | }; 135 | }); 136 | var stop = Compose.stop = {}; 137 | var undefined; 138 | Compose.after = aspect(function(target, base, advice){ 139 | return function(){ 140 | var results = base.apply(this, arguments); 141 | var adviceResults = advice.apply(this, arguments); 142 | return adviceResults === undefined ? results : adviceResults; 143 | }; 144 | }); 145 | 146 | // rename Decorator for calling super methods 147 | Compose.from = function(trait, fromKey){ 148 | if(fromKey){ 149 | return (typeof trait == "function" ? trait.prototype : trait)[fromKey]; 150 | } 151 | return Decorator(function(key){ 152 | if(!(this[key] = (typeof trait == "string" ? this[trait] : 153 | (typeof trait == "function" ? trait.prototype : trait)[fromKey || key]))){ 154 | throw new Error("Source method " + fromKey + " was not available to be renamed to " + key); 155 | } 156 | }); 157 | }; 158 | 159 | // Composes an instance 160 | Compose.create = function(base){ 161 | // create the instance 162 | var instance = mixin(delegate(base), arguments, 1); 163 | var argsLength = arguments.length; 164 | // for go through the arguments and call the constructors (with no args) 165 | for(var i = 0; i < argsLength; i++){ 166 | var arg = arguments[i]; 167 | if(typeof arg == "function"){ 168 | instance = arg.call(instance) || instance; 169 | } 170 | } 171 | return instance; 172 | } 173 | // The required function, just throws an error if not overriden 174 | function required(){ 175 | throw new Error("This method is required and no implementation has been provided"); 176 | }; 177 | Compose.required = required; 178 | // get the value of |this| for direct function calls for this mode (strict in ES5) 179 | 180 | function extend(){ 181 | var args = [this]; 182 | args.push.apply(args, arguments); 183 | return Compose.apply(0, args); 184 | } 185 | // Compose a constructor 186 | function Compose(base){ 187 | var args = arguments; 188 | var prototype = (args.length < 2 && typeof args[0] != "function") ? 189 | args[0] : // if there is just a single argument object, just use that as the prototype 190 | mixin(delegate(validArg(base)), args, 1); // normally create a delegate to start with 191 | function Constructor(){ 192 | var instance; 193 | if(this instanceof Constructor){ 194 | // called with new operator, can proceed as is 195 | instance = this; 196 | }else{ 197 | // we allow for direct calls without a new operator, in this case we need to 198 | // create the instance ourself. 199 | Create.prototype = prototype; 200 | instance = new Create(); 201 | } 202 | // call all the constructors with the given arguments 203 | for(var i = 0; i < constructorsLength; i++){ 204 | var constructor = constructors[i]; 205 | var result = constructor.apply(instance, arguments); 206 | if(typeof result == "object"){ 207 | if(result instanceof Constructor){ 208 | instance = result; 209 | }else{ 210 | for(var j in result){ 211 | if(result.hasOwnProperty(j)){ 212 | instance[j] = result[j]; 213 | } 214 | } 215 | } 216 | } 217 | } 218 | return instance; 219 | } 220 | // create a function that can retrieve the bases (constructors or prototypes) 221 | Constructor._getBases = function(prototype){ 222 | return prototype ? prototypes : constructors; 223 | }; 224 | // now get the prototypes and the constructors 225 | var constructors = getBases(args), 226 | constructorsLength = constructors.length; 227 | if(typeof args[args.length - 1] == "object"){ 228 | args[args.length - 1] = prototype; 229 | } 230 | var prototypes = getBases(args, true); 231 | Constructor.extend = extend; 232 | if(!Compose.secure){ 233 | prototype.constructor = Constructor; 234 | } 235 | Constructor.prototype = prototype; 236 | return Constructor; 237 | }; 238 | 239 | Compose.apply = function(thisObject, args){ 240 | // apply to the target 241 | return thisObject ? 242 | mixin(thisObject, args, 0) : // called with a target object, apply the supplied arguments as mixins to the target object 243 | extend.apply.call(Compose, 0, args); // get the Function.prototype apply function, call() it to apply arguments to Compose (the extend doesn't matter, just a handle way to grab apply, since we can't get it off of Compose) 244 | }; 245 | Compose.call = function(thisObject){ 246 | // call() should correspond with apply behavior 247 | return mixin(thisObject, arguments, 1); 248 | }; 249 | 250 | function getBases(args, prototype){ 251 | // this function registers a set of constructors for a class, eliminating duplicate 252 | // constructors that may result from diamond construction for classes (B->A, C->A, D->B&C, then D() should only call A() once) 253 | var bases = []; 254 | function iterate(args, checkChildren){ 255 | outer: 256 | for(var i = 0; i < args.length; i++){ 257 | var arg = args[i]; 258 | var target = prototype && typeof arg == "function" ? 259 | arg.prototype : arg; 260 | if(prototype || typeof arg == "function"){ 261 | var argGetBases = checkChildren && arg._getBases; 262 | if(argGetBases){ 263 | iterate(argGetBases(prototype)); // don't need to check children for these, this should be pre-flattened 264 | }else{ 265 | for(var j = 0; j < bases.length; j++){ 266 | if(target == bases[j]){ 267 | continue outer; 268 | } 269 | } 270 | bases.push(target); 271 | } 272 | } 273 | } 274 | } 275 | iterate(args, true); 276 | return bases; 277 | } 278 | // returning the export of the module 279 | return Compose; 280 | }); 281 | })(typeof define != "undefined" ? 282 | define: // AMD/RequireJS format if available 283 | function(deps, factory){ 284 | if(typeof module !="undefined"){ 285 | module.exports = factory(); // CommonJS environment, like NodeJS 286 | // require("./configure"); 287 | }else{ 288 | Compose = factory(); // raw script, assign to Compose global 289 | } 290 | }); 291 | -------------------------------------------------------------------------------- /test/compose.js: -------------------------------------------------------------------------------- 1 | (function(define){ 2 | define(function(require, exports, module){ 3 | 4 | var assert = require("patr/assert"), 5 | Compose = require("../compose"), 6 | required = Compose.required, 7 | around = Compose.around, 8 | from = Compose.from, 9 | create = Compose.create, 10 | Widget, MessageWidget, SpanishWidget; 11 | 12 | exports.testCompose = function() { 13 | Widget = Compose({ 14 | render: function(node){ 15 | node.innerHTML = "
23 | var Compose = require("compose");
24 |
25 | Or an AMD module loader (RequireJS, Dojo, etc), you can load it:
26 |
27 | define(["compose"], function(Compose){
28 | ...
29 | });
30 |
31 |
32 | If ComposeJS is loaded as a plain script, it will create Compose as a global variable.
33 |
34 | Now to start using Compose, let's create a simple object constructor:
35 |
36 | Widget = Compose({
37 | render: function(node){
38 | node.innerHTML = "hi";
39 | }
40 | });
41 | var widget = new Widget();
42 | widget.render(node);
43 |
44 | And the equivalent JavaScript:
45 |
46 | Widget = function(){
47 | };
48 | Widget.prototype = {
49 | render: function(node){
50 | node.innerHTML = "hi";
51 | }
52 | }
53 | var widget = new Widget();
54 | widget.render(node);
55 |
56 | One the features provided by ComposeJS is that it creates constructors that will work
57 | regardless of whether they are called with the new operator, making them less prone
58 | to coding mistakes. One can also choose to omit the new operator to save bytes (for faster
59 | download), although calling with the new operator is slightly faster at runtime (so
60 | the faster overall would depend on how many times it is called).
61 |
62 | ## Extending existing constructor
63 |
64 | To extend our Widget we can simply include the Widget in Compose arguments:
65 |
66 | HelloWidget = Compose(Widget, {
67 | message: "Hello, World",
68 | render: function(){
69 | this.node.innerHTML = "" + this.message + "";
70 | }
71 | });
72 | var widget = new HelloWidget();
73 | widget.render(node);
74 |
75 | And the equivalent JavaScript:
76 |
77 | HelloWidget = function(){
78 | this.message = "Hello, World";
79 | };
80 | HelloWidget.prototype = new Widget();
81 | HelloWidget.prototype.render: function(){
82 | this.node.innerHTML = "" + this.message + "";
83 | };
84 | var widget = new HelloWidget();
85 | widget.render(node);
86 |
87 |
88 | Now let's create the constructor with a function to be executed on instantiation. Any
89 | functions in the arguments will be executed on construction, so our provided argument
90 | can be used to prepare the object on instantiation:
91 |
92 | Widget = Compose(function(node){
93 | this.node = node;
94 | },{
95 | render: function(){
96 | this.node.innerHTML = "hi";
97 | },
98 | getNode: function(){
99 | return this.node;
100 | }
101 | });
102 | var widget = new Widget(node);
103 | widget.render();
104 |
105 | And the equivalent JavaScript:
106 |
107 | Widget = function(node){
108 | this.node = node;
109 | };
110 | Widget.prototype = {
111 | render: function(){
112 | this.node.innerHTML = "hi";
113 | },
114 | getNode: function(){
115 | return this.node;
116 | }
117 | }
118 | var widget = new Widget(node);
119 | widget.render();
120 |
121 |
122 | Compose can compose constructors from multiple base constructors, effectively
123 | providing multiple inheritance. For example, we could create a new widget from Widget
124 | and Templated base constructors:
125 |
126 | TemplatedWidget = Compose(Widget, Templated, {
127 | // additional functionality
128 | });
129 |
130 | Again, latter argument's methods override former argument's methods. In this case,
131 | Templated's methods will override any Widget's method of the same name. However,
132 | Compose is carefully designed to avoid any confusing conflict resolution in ambiguous cases.
133 | Automatic overriding will only apply when later arguments have their own methods.
134 | If a later argument constructor or object inherits a method, this will not automatically override
135 | former base constructor's methods unless it has already overriden this method in another base
136 | constructor's hierarchy. In such cases, the appropriate method must be designated in the final
137 | object or else it will remain in a conflicted state. This essentially means that explicit ordering
138 | provides straightforward, easy to use, method overriding, without ambiguous magical conflict
139 | resolution (C3MRO).
140 |
141 | We can specify required methods that must be overriden as well. For example, we can
142 | define the Widget to require a generateHTML method:
143 |
144 | var required = Compose.required;
145 | Widget = Compose({
146 | generateHTML: required,
147 | ...
148 | });
149 |
150 |
151 | And now to extend the Widget constructor, we must provide a generateHTML method.
152 | Failure to do so will result in an error being thrown when generateHTML is called.
153 |
154 | ## Apply to an existing object
155 |
156 | Compose can also be applied to existing objects to add/mixin functionality to that object.
157 | This is done by using the standard call() or apply() function methods to define |this| for the
158 | call. When Compose is applied in this way, the target object will have the methods from
159 | all the provide objects or constructors added to it. For example:
160 |
161 | var object = {a: 1};
162 | Compose.call(object, {b: 2});
163 | object -> {a: 1, b: 2}
164 |
165 |
166 | We can use this form of Compose to add methods during construction. This is one style
167 | of creating instances that have private and public methods. For example, we could extend
168 | Widget with:
169 |
170 | var required = Compose.required;
171 | Widget = Compose(Widget, function(innerHTML){
172 | // this will mixin the provide methods into |this|
173 | Compose.call(this, {
174 | generateHTML: function(){
175 | return "" + generateInner() + "";
176 | }
177 | });
178 | // private function
179 | function generateInner(){
180 | return innerHTML;
181 | }
182 | });
183 |
184 |
185 | Applying Compose can also be conveniently leveraged to make constructors that mixin properties
186 | from an object argument. This is a common pattern for constructors and allows an
187 | instance to be created with preset properties provided to the constructor. This also
188 | also makes it easy to have independent optional named parameters with defaults.
189 | We can implement this pattern by simple having Compose be a base constructor
190 | for our composition. For example, we can create a widget that extends Compose
191 | and therefore we can instantiate Widgets with an object argument that provides initial property settings:
192 |
193 | Widget = Compose(Compose, {
194 | render: function(){
195 | this.node.innerHTML = "hi";
196 | }
197 | });
198 | var widget = new Widget({node: byId("some-id")});
199 | widget.node -> byId("some-id")
200 | widget.render();
201 |
202 | This is a powerful way to build constructors since constructors can be created that include
203 | all the functionality that Compose provides, including decorators and multiple
204 | objects or constructors as arguments.
205 |
206 | ## Compose.create
207 |
208 | Compose.create() is another function provided by the ComposeJS library. This function
209 | is similar to Compose() and takes exactly the same type of arguments (any mixture
210 | of constructors or objects), but rather
211 | than creating a constructor, it directly creates an instance object. Calling the constructor
212 | returned from Compose with no arguments and calling Compose.create act approximately
213 | the same action, i.e. Compose(...)() acts the same as Compose.create(...). The main
214 | difference is that Compose.create is optimized for instance creation and avoids
215 | unnecessary prototype creation involved in creating a constructor.
216 |
217 | Compose.create is particularly useful in conjunction with the closure-style constructors.
218 | A closure-style constructor (sometimes called the module pattern) can have private
219 | variables and generally returns an object created using object literal syntax.
220 | For base constructors that don't extend anything else, This is well-supported by native JavaScript
221 | there is no need to use ComposeJS (or another library) to create a simple base constructor.
222 | But for extending base constructors, Compose.create is very useful. For example,
223 | we could create a base widget using this pattern (again, we can just use native JavaScript):
224 |
225 | Widget = function(node){ // node is a private variable
226 | return {
227 | render: function(){
228 | node.innerHTML = this.message;
229 | },
230 | message: "Hello"
231 | };
232 | };
233 |
234 | And now we could extend this widget, continuing to use the closure-style constructor,
235 | with help from Compose.create. Here we will call base constructor, and use the returned
236 | base instance to compose an extended instance. The "node" variable continues to stay
237 | protected from direct access:
238 |
239 | BoldWidget = function(node){
240 | baseWidget = Widget(node);
241 | return Compose.create(baseWidget, {
242 | render: function(){
243 | baseWidget.render();
244 | node.style.fontWeight = "bold";
245 | }
246 | });
247 | };
248 |
249 |
250 | ##Constructor.extend
251 | Constructors created with Compose also include a "static" extend method that can be
252 | used for convenience in creating subclasses. The extend method behaves the same
253 | as Compose with the target class being the first parameter:
254 |
255 | MyClass = Compose(...);
256 | SubClass = MyClass.extend({
257 | subMethod: function(){}
258 | });
259 | // same as:
260 | SubClass = Compose(MyClass,{
261 | subMethod: function(){}
262 | });
263 |
264 |
265 | ## Decorators
266 | Decorators provides a customized way to add properties/methods to target objects.
267 | Several decorators are provided with ComposeJS:
268 |
269 | ### Aspects (or Super-calls)
270 |
271 | Compose provides an aspect-oriented decorator to add functionality to existing method
272 | instead of completely overriding or replacing the method. This provides super-call type
273 | functionality. The after() function allows one to add code that will be executed after
274 | the base method:
275 |
276 | var after = Compose.after;
277 | WidgetWithTitle = Compose(Widget, {
278 | render: after(function(){
279 | // called after the original render() from Widget
280 | this.node.insertBefore(header, this.node.firstChild);
281 | }
282 | });
283 |
284 |
285 | The after() advice (provided function) can return a value that will be returned to the original caller. If
286 | nothing is returned, the inherited method's return value will be returned.
287 |
288 | The before() function allows one to add code that will be executed before
289 | the base method:
290 |
291 | var before = Compose.before;
292 | BoldWidget = Compose(Widget, {
293 | render: before(function(){
294 | // called before the original render() from Widget
295 | this.node.style.fontWeight = "bold";
296 | }
297 | });
298 |
299 |
300 | The before() advice can return an array that will be used as the arguments for the
301 | inherited function. If nothing is returned, the original calling arguments are passed to
302 | the inherited function. If Compose.stop is returned, the inherited function will not be
303 | called.
304 |
305 | The around function allows one to closure around an overriden method to combine
306 | functionality. For example, we could override the render function in Widget, but still
307 | call the base function:
308 |
309 | var around = Compose.around;
310 | BoldWidgetWithTitle = Compose(Widget, {
311 | render: around(function(baseRender){
312 | // return the new render function
313 | return function(){
314 | this.node.style.fontWeight = "bold";
315 | baseRender.call(this);
316 | this.node.insertBefore(header, this.node.firstChild);
317 | };
318 | });
319 | });
320 |
321 |
322 | ### Composition Control: Method Aliasing and Exclusion
323 | One of the key capabilities of traits-style composition is control of which method to
324 | keep or exclude from the different components that are being combined. The from()
325 | decorator provides simple control over which method to use. We can use from() with
326 | the base constructor to indicate the appropriate method to keep. For example, if we
327 | composed from Widget and Templated, we could use from() to select the save()
328 | method from Widget and render() from Templated:
329 |
330 | var from = Compose.from;
331 | TemplatedWidget = Compose(Widget, Templated, {
332 | save: from(Widget),
333 | render: from(Templated)
334 | });
335 |
336 |
337 | We can also alias methods, making them available under a new name. This is very useful
338 | when we need to access multiple conflicting methods. We can provide a string argument
339 | that indicates the method name to retrieve (it will be aliased to the property name that
340 | it is being applied to). With the string argument, the constructor argument is optional
341 | (defaults to whatever method would naturally be selected for the given name):
342 |
343 | var from = Compose.from;
344 | TemplatedWidget = Compose(Widget, Templated, {
345 | widgetRender: from(Widget, "render"),
346 | templateRender: from(Templated, "render"),
347 | saveTemplate: from("save"),
348 | render: function(){
349 | this.widgetRender();
350 | this.templateRender();
351 | // do other stuff
352 | },
353 | save: function(){
354 | this.saveTemplate();
355 | //...
356 | }
357 | });
358 |
359 |
360 | #### Conflict Example
361 | To help understand conflicts, here is the simplest case where Compose would give a conflict error:
362 |
363 | A = Compose({
364 | foo: function(){ console.log("A foo"); }
365 | });
366 |
367 | B = Compose({
368 | foo: function(){ console.log("B foo"); }
369 | });
370 |
371 | C = Compose(B, {});
372 |
373 | D = Compose(A, C);
374 | new D().foo()
375 |
376 | Compose considers class D's foo() method to be a conflict because for C takes
377 | precedence over A, but C only inherits foo, it doesn't directly have foo. In other
378 | words, a bread-first linearization of methods would give A's foo precedence,
379 | but a depth-first linearization of methods would give B's foo precedence, and
380 | since this disagree, it is considered ambiguous. Note that these are all conflict free:
381 |
382 | D = Compose(C, A); // A's own foo() wins
383 | D = Compose(A, B); // B's own foo() wins
384 | D = Compose(A, C, {foo: Compose.from(A)}); // explicitly chose A's foo
385 | D = Compose(A, C, {foo: Compose.from(B)}); // explicitly chose B's foo
386 |
387 | ### Creating Decorators
388 | Decorators are created by newing the Decorator constructor with a function argument
389 | that is called with the property name. The function's |this| will be the target object, and
390 | the function can add a property anyway it sees fit. For example, you could create a decorator
391 | that would explicitly override another methods, and fail if an existing method as not there.
392 |
393 | overrides = function(method){
394 | return new Compose.Decorator(function(key){
395 | var baseMethod = this[key];
396 | if(!baseMethod){
397 | throw new Error("No method " + key + " exists to override");
398 | }
399 | this[key] = method;
400 | });
401 | };
402 |
403 | Widget = Compose({
404 | render: function(){
405 | ...
406 | }
407 | });
408 | SubWidget = Compose(Widget, {
409 | render: overrides(function(){
410 | ...
411 | })
412 | });
413 |
414 |
415 | In addition, the Decorator function accepts a second argument, which is the function
416 | that would be executed if the decorated method is directly executed and does not override another method.
417 |
418 | ### Security
419 | By default Compose will add a constructor property to your constructor's prototype to make
420 | the constructor available from instances:
421 |
422 | Widget = Compose({...});
423 | var widget = new Widget();
424 | widget.constructor == Widget // true
425 |
426 | However, in the context of object capability security, providing access to the constructor
427 | from instances is considered a violation of principle of least access. If you would like to
428 | disable this feature for the purposes of using Compose in secure environments, you can
429 | set:
430 | 431 | Compose.secure = true; 432 |433 | 434 | ### Enumeration on Legacy Internet Explorer 435 | Internet Explorer 8 and earlier have a known issue with enumerating properties that 436 | shadow dontEnum properties (toString, toValue, hasOwnProperty, etc. for objects), which 437 | means that these properties will not be copied to your class on these versions of IE. There is a known 438 | fix for this, but it does meet the high performance and space priorities of Compose. 439 | However, if you need to override one of these methods, you can easily workaround this 440 | issue by setting the method in the constructor instead of the provide object. For example: 441 |
442 | Widget = Compose(function(){
443 | this.toString = function(){
444 | // my custom toString method
445 | };
446 | },{
447 | // my other methods
448 | });
449 |
450 |
451 | # License
452 |
453 | ComposeJS is freely available under *either* the terms of the modified BSD license *or* the
454 | Academic Free License version 2.1. More details can be found in the [LICENSE](LICENSE).
455 | The ComposeJS project follows the IP guidelines of Dojo foundation packages and all
456 | contributions require a Dojo CLA.
--------------------------------------------------------------------------------