1 // ========================================================================== 2 // Project: SproutCore - JavaScript Application Framework 3 // Copyright: ©2006-2011 Strobe Inc. and contributors. 4 // Portions ©2008-2011 Apple Inc. All rights reserved. 5 // License: Licensed under MIT license (see license.js) 6 // ========================================================================== 7 8 /** @class 9 10 The Builder class makes it easy to create new chained-builder API's such as 11 those provided by CoreQuery or jQuery. Usually you will not create a new 12 builder yourself, but you will often use instances of the Builder object to 13 configure parts of the UI such as menus and views. 14 15 # Anatomy of a Builder 16 17 You can create a new Builder much like you would any other class in 18 SproutCore. For example, you could create a new CoreQuery-type object with 19 the following: 20 21 SC.$ = SC.Builder.create({ 22 // methods you can call go here. 23 }); 24 25 Unlike most classes in SproutCore, Builder objects are actually functions 26 that you can call to create new instances. In the example above, to use 27 the builder, you must call it like a function: 28 29 buildit = SC.$(); 30 31 If you define an init() method on a builder, it will be invoked wheneve the 32 builder is called as a function, including any passed params. Your init() 33 method MUST return this, unlike regular SC objects. i.e. 34 35 SC.$ = SC.Builder.create({ 36 init: function(args) { 37 this.args = SC.A(args); 38 return this; 39 } 40 }); 41 42 buildit = SC.$('a', 'b'); 43 buildit.args => ['a','b'] 44 45 In addition to defining a function like this, all builder objects also have 46 an 'fn' property that contains a hash of all of the helper methods defined 47 on the builder function. Once a builder has been created, you can add 48 addition "plugins" for the builder by simply adding new methods to the 49 fn property. 50 51 # Writing Builder Functions 52 53 All builders share a few things in common: 54 55 * when a new builder is created, it's init() method will be called. The default version of this method simply copies the passed parameters into the builder as content, but you can override this with anything you want. 56 * the content the builder works on is stored as indexed properties (i.e. 0,1,2,3, like an array). The builder should also have a length property if you want it treated like an array. 57 *- Builders also maintain a stack of previous builder instances which you can pop off at any time. 58 59 To get content back out of a builder once you are ready with it, you can 60 call the method done(). This will return an array or a single object, if 61 the builder only works on a single item. 62 63 You should write your methods using the getEach() iterator to work on your 64 member objects. All builders implement SC.Enumerable in the fn() method. 65 66 CoreQuery = SC.Builder.create({ 67 ... 68 }) ; 69 70 CoreQuery = new SC.Builder(properties) { 71 ... 72 } ; 73 74 CoreQuery2 = CoreQuery.extend() { 75 } 76 77 @constructor 78 */ 79 SC.Builder = function (props) { return SC.Builder.create(props); }; 80 81 /** 82 Create a new builder object, applying the passed properties to the 83 builder's fn property hash. 84 85 @param {Hash} properties 86 @returns {SC.Builder} 87 */ 88 SC.Builder.create = function create(props) { 89 90 // generate new fn with built-in properties and copy props 91 var fn = SC.mixin(SC.beget(this.fn), props||{}) ; 92 if (props.hasOwnProperty('toString')) fn.toString = props.toString; 93 94 // generate new constructor and hook in the fn 95 var construct = function() { 96 var ret = SC.beget(fn); // NOTE: using closure here... 97 98 // the defaultClass is usually this for this constructor. 99 // e.g. SC.View.build() -> this = SC.View 100 ret.defaultClass = this ; 101 ret.constructor = construct ; 102 103 // now init the builder object. 104 return ret.init.apply(ret, arguments) ; 105 } ; 106 construct.fn = construct.prototype = fn ; 107 108 // the create() method can be used to extend a new builder. 109 // eg. SC.View.buildCustom = SC.View.build.extend({ ...props... }) 110 construct.extend = SC.Builder.create ; 111 construct.mixin = SC.Builder.mixin ; 112 113 return construct; // return new constructor 114 } ; 115 116 SC.Builder.mixin = function() { 117 var len = arguments.length, idx; 118 for(idx=0;idx<len;idx++) SC.mixin(this, arguments[idx]); 119 return this ; 120 }; 121 122 /** This is the default set of helper methods defined for new builders. */ 123 SC.Builder.fn = { 124 125 /** 126 Default init method for builders. This method accepts either a single 127 content object or an array of content objects and copies them onto the 128 receiver. You can override this to provide any kind of init behavior 129 that you want. Any parameters passed to the builder method will be 130 forwarded to your init method. 131 132 @returns {SC.Builder} receiver 133 */ 134 init: function(content) { 135 if (content !== undefined) { 136 if (SC.typeOf(content) === SC.T_ARRAY) { 137 var loc=content.length; 138 while(--loc >= 0) { 139 this[loc] = content.objectAt ? content.objectAt(loc) : content[loc]; 140 } 141 this.length = content.length ; 142 } else { 143 this[0] = content; this.length=1; 144 } 145 } 146 return this ; 147 }, 148 149 /** Return the number of elements in the matched set. */ 150 size: function() { return this.length; }, 151 152 /** 153 Take an array of elements and push it onto the stack (making it the 154 new matched set.) The receiver will be saved so it can be popped later. 155 156 @param {Object|Array} content 157 @returns {SC.Builder} new instance 158 */ 159 pushStack: function() { 160 // Build a new CoreQuery matched element set 161 var ret = this.constructor.apply(this,arguments); 162 163 // Add the old object onto the stack (as a reference) 164 ret.prevObject = this; 165 166 // Return the newly-formed element set 167 return ret; 168 }, 169 170 /** 171 Returns the previous object on the stack so you can continue with that 172 transform. If there is no previous item on the stack, an empty set will 173 be returned. 174 */ 175 end: function() { 176 return this.prevObject || this.constructor(); 177 }, 178 179 // toString describes the builder 180 toString: function() { 181 return "%@$(%@)".fmt(this.defaultClass.toString(), 182 SC.A(this).invoke('toString').join(',')); 183 }, 184 185 /** You can enhance the fn using this mixin method. */ 186 mixin: SC.Builder.mixin 187 188 }; 189 190 // Apply SC.Enumerable. Whenever possible we want to use the Array version 191 // because it might be native code. 192 (function() { 193 var enumerable = SC.Enumerable, fn = SC.Builder.fn, key, value ; 194 for(key in enumerable) { 195 if (!enumerable.hasOwnProperty(key)) continue ; 196 value = Array.prototype[key] || enumerable[key]; 197 fn[key] = value ; 198 } 199 })(); 200 201 202 203