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 /*global SC */ 9 10 /** @class 11 This is a basic designer used for all `SC.Object`s that are created in 12 design mode. 13 14 FIXME: have `SC.ViewDesigner` subclass this designer..... 15 16 @extends SC.Object 17 @since SproutCore 1.0 18 */ 19 SC.ObjectDesigner = SC.Object.extend( 20 /** @scope SC.ViewDesigner.prototype */ { 21 22 /** The object managed by this designer. */ 23 object: null, 24 25 /** The class for the design. Set when the object is created. */ 26 objectClass: null, 27 28 /** Set to `YES` if the object is currently selected for editing. */ 29 designIsSelected: NO, 30 31 /** Set to `YES` if this particular designer should not be enabled. */ 32 designIsEnabled: YES, 33 34 /** 35 The current page. Comes from the object. 36 37 @property {SC.Page} 38 */ 39 page: function() { 40 var v = this.get('object'); 41 return (v) ? v.get('page') : null; 42 }.property('object').cacheable(), 43 44 /** 45 The design controller from the page. Comes from page 46 47 @property {SC.PageDesignController} 48 */ 49 designController: function() { 50 var p = this.get('page'); 51 return (p) ? p.get('designController') : null ; 52 }.property('page').cacheable(), 53 54 55 concatenatedProperties: ['designProperties', 'localizedProperties', 'excludeProperties'], 56 57 // .......................................................... 58 // GENERIC PROPERTIES 59 // 60 // Adds support for adding generic properties to a object. These will 61 // overwrite whatever you write out using specifically supported props. 62 63 // .......................................................... 64 // HANDLE ENCODING OF VIEW DESIGN 65 // 66 67 /** 68 Encodes any simple properties that can just be copied from the object onto 69 the coder. This is used by encodeDesignProperties() and 70 encodeLocalizedProperties(). 71 */ 72 encodeSimpleProperties: function(props, coder) { 73 var object = this.get('object'), proto = this.get('objectClass').prototype ; 74 props.forEach(function(prop) { 75 var val = object[prop] ; // avoid get() since we don't want to exec props 76 if (val !== undefined && (val !== proto[prop])) { 77 coder.encode(prop, val) ; 78 } 79 }, this); 80 }, 81 82 83 /** 84 Array of properties that can be encoded directly. This is an easy way to 85 add support for simple properties that need to be written to the design 86 without added code. These properties will be encoded by 87 `encodeDesignProperties()`. 88 89 You can add to this array in your subclasses. 90 */ 91 designProperties: [], 92 93 /* 94 Array of properties specifically not displayed in the editable properties 95 list 96 */ 97 98 excludeProperties: [], 99 100 101 /* 102 Array of properties available to edit in greenhouse 103 104 */ 105 editableProperties: function(){ 106 107 var con = this.get('designAttrs'), 108 obj = this.get('object'), 109 ret = [], 110 designProperties = this.get('designProperties'), 111 excludeProperties = this.get('excludeProperties'); 112 if(con) con = con[0]; 113 for(var i in con){ 114 if(con.hasOwnProperty(i) && excludeProperties.indexOf(i) < 0){ 115 if(!SC.none(obj[i])) ret.pushObject(SC.Object.create({value: obj[i], key: i, view: obj})); 116 } 117 } 118 designProperties.forEach(function(k){ 119 if(excludeProperties.indexOf(k) < 0){ 120 ret.pushObject(SC.Object.create({value: obj[k], key: k, view: obj})); 121 } 122 }); 123 124 return ret; 125 }.property('designProperties').cacheable(), 126 127 /** 128 Invoked by a design coder to encode design properties. The default 129 implementation invoked `encodeDesignProperties()` and 130 `encodeChildViewsDesign()`. You can override this method with your own 131 additional encoding if you like. 132 */ 133 encodeDesign: function(coder) { 134 coder.set('className', SC._object_className(this.get('objectClass'))); 135 this.encodeDesignProperties(coder); 136 return YES ; 137 }, 138 139 /** 140 Encodes the design properties for the object. These properties are simply 141 copied from the object onto the coder. As an optimization, the value of 142 each property will be checked against the default value in the class. If 143 they match, the property will not be emitted. 144 */ 145 encodeDesignProperties: function(coder) { 146 return this.encodeSimpleProperties(this.get('designProperties'), coder); 147 }, 148 149 /** 150 Array of localized that can be encoded directly. This is an easy way to 151 add support for simple properties that need to be written to the 152 localization without added code. These properties will be encoded by 153 `encodeLocalizedProperties()`. 154 155 You can add to this array in your subclasses. 156 */ 157 localizedProperties: [], 158 159 /** 160 Invoked by a localization coder to encode design properties. The default 161 implementation invoked `encodeLocalizedProperties()` and 162 `encodeChildViewsLoc()`. You can override this method with your own 163 additional encoding if you like. 164 */ 165 encodeLoc: function(coder) { 166 coder.set('className', SC._object_className(this.get('objectClass'))); 167 this.encodeLocalizedProperties(coder); 168 return YES ; 169 }, 170 171 /** 172 Encodes the localized properties for the object. These properties are 173 simply copied from the object onto the coder. As an optimization, the value 174 of each property will be checked against the default value in the class. 175 If they match, the property will not be emitted. 176 */ 177 encodeLocalizedProperties: function(coder) { 178 return this.encodeSimpleProperties(this.get('localizedProperties'),coder); 179 }, 180 181 /** 182 This method is invoked when the designer is instantiated. You can use 183 this method to reload any state saved in the object. This method is called 184 before any observers or bindings are setup to give you a chance to 185 configure the initial state of the designer. 186 */ 187 awakeDesign: function() {}, 188 189 /** 190 The `unknownProperty` handler will pass through to the object by default. 191 This will often provide you the support you need without needing to 192 customize the Designer. Just make sure you don't define a conflicting 193 property name on the designer itself! 194 */ 195 unknownProperty: function(key, value) { 196 if (value !== undefined) { 197 this.object.set(key, value); 198 return value ; 199 } else return this.object.get(key); 200 }, 201 202 // ...................................... 203 // PRIVATE METHODS 204 // 205 206 init: function() { 207 208 // setup design from object state... 209 this.awakeDesign(); 210 211 // setup bindings, etc 212 sc_super(); 213 214 // and register with designController, if defined... 215 var c= this.get('designController'); 216 if (c) c.registerDesigner(this) ; 217 218 }, 219 220 destroy: function() { 221 sc_super(); 222 this.set('object', null); // clears the object observer... 223 }, 224 225 tryToPerform: function(methodName, arg1, arg2) { 226 // only handle event if we are in design mode 227 var page = this.object ? this.object.get('page') : null ; 228 var isDesignMode = page ? page.get('needsDesigner') || page.get('isDesignMode') : NO ; 229 230 // if we are in design mode, route event handling to the designer 231 // otherwise, invoke default method. 232 if (isDesignMode) { 233 return sc_super(); 234 } else { 235 return SC.Object.prototype.tryToPerform.apply(this.object, arguments); 236 } 237 } 238 }) ; 239 240 // Set default Designer for object 241 if (!SC.Object.Designer) SC.Object.Designer = SC.ObjectDesigner ; 242 243 // .......................................................... 244 // DESIGN NOTIFICATION METHODS 245 // 246 // These methods are invoked automatically on the designer class whenever it 247 // is loaded. 248 249 SC.ObjectDesigner.mixin({ 250 /** 251 Invoked whenever a designed object is loaded. This will save the design 252 attributes for later use by a designer. 253 */ 254 didLoadDesign: function(designedObject, sourceObject, attrs) { 255 designedObject.isDesign = YES ; // indicates that we need a designer. 256 designedObject.designAttrs = attrs; 257 //designedObject.sourceObject = sourceObject; TODO: don't need this.. 258 }, 259 260 /** 261 Invoked whenever a location is applied to a designed object. Saves the 262 attributes separately for use by the design object. 263 */ 264 didLoadLocalization: function(designedObject, attrs) { 265 // nothing to do for now. 266 }, 267 268 /** 269 Invoked whenver a object is created. This will create a peer designer if 270 needed. 271 */ 272 didCreateObject: function(object, attrs) { 273 // GATEKEEP: If this isn't a design, bail. 274 if (!object.constructor.isDesign) return; 275 276 // add designer if page is in design mode 277 var page = object.get('page'), 278 design = object.constructor; 279 280 if (page && page.get('needsDesigner')) { 281 282 // find the designer class 283 var cur = design, origDesign = design; 284 while(cur && !cur.Designer) cur = cur.superclass; 285 var DesignerClass = (cur) ? cur.Designer : SC.Object.Designer; 286 287 // next find the first superclass object that is not a design (and a real 288 // class). This is important to make sure that we can determine the 289 // real name of a object's class. 290 while (design && design.isDesign) design = design.superclass; 291 if (!design) design = SC.Object; 292 293 object.designer = DesignerClass.create({ 294 object: object, 295 objectClass: design, 296 designAttrs: origDesign.designAttrs 297 //sourceObject: origDesign.sourceObject TODO: don't need this 298 }); 299 } 300 } 301 302 }); 303