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