// ========================================================================== // Project: SproutCore - JavaScript Application Framework // Copyright: ©2006-2011 Strobe Inc. and contributors. // Portions ©2008-2011 Apple Inc. All rights reserved. // License: Licensed under MIT license (see license.js) // ==========================================================================

/*global SC */

/** @class

This is a basic designer used for all `SC.Object`s that are created in
design mode.

FIXME: have `SC.ViewDesigner` subclass this designer.....

@extends SC.Object
@since SproutCore 1.0

*/ SC.ObjectDesigner = SC.Object.extend( /** @scope SC.ViewDesigner.prototype */ {

/** The object managed by this designer. */
object: null,

/** The class for the design.  Set when the object is created. */
objectClass: null,

/** Set to `YES` if the object is currently selected for editing. */
designIsSelected: NO,

/** Set to `YES` if this particular designer should not be enabled. */
designIsEnabled: YES,

/**
  The current page.  Comes from the object.

  @property {SC.Page}
*/
page: function() {
  var v = this.get('object');
  return (v) ? v.get('page') : null;
}.property('object').cacheable(),

/**
  The design controller from the page.  Comes from page

  @property {SC.PageDesignController}
*/
designController: function() {
  var p = this.get('page');
  return (p) ? p.get('designController') : null ;  
}.property('page').cacheable(),

concatenatedProperties: ['designProperties', 'localizedProperties', 'excludeProperties'],

// ..........................................................
// GENERIC PROPERTIES
// 
// Adds support for adding generic properties to a object.  These will
// overwrite whatever you write out using specifically supported props.

// ..........................................................
// HANDLE ENCODING OF VIEW DESIGN
// 

/**
  Encodes any simple properties that can just be copied from the object onto
  the coder.  This is used by encodeDesignProperties() and 
  encodeLocalizedProperties().
*/
encodeSimpleProperties: function(props, coder) {
  var object = this.get('object'), proto = this.get('objectClass').prototype ;
  props.forEach(function(prop) {
    var val = object[prop] ; // avoid get() since we don't want to exec props
    if (val !== undefined && (val !== proto[prop])) {
      coder.encode(prop, val) ;
    }
  }, this);
},

/** 
  Array of properties that can be encoded directly.  This is an easy way to
  add support for simple properties that need to be written to the design
  without added code.  These properties will be encoded by 
  `encodeDesignProperties()`.

  You can add to this array in your subclasses.
*/
designProperties: [],

/*
  Array of properties specifically not displayed in the editable properties
  list
*/

excludeProperties: [],

/*
  Array of properties available to edit in greenhouse

*/
editableProperties: function(){

  var con = this.get('designAttrs'), 
      obj = this.get('object'),
      ret = [],
      designProperties = this.get('designProperties'),
      excludeProperties = this.get('excludeProperties');
  if(con) con = con[0];
  for(var i in con){
    if(con.hasOwnProperty(i) && excludeProperties.indexOf(i) < 0){
      if(!SC.none(obj[i])) ret.pushObject(SC.Object.create({value: obj[i], key: i, view: obj}));
    }
  }
  designProperties.forEach(function(k){
    if(excludeProperties.indexOf(k) < 0){
      ret.pushObject(SC.Object.create({value: obj[k], key: k, view: obj}));
    }
  });

  return ret; 
}.property('designProperties').cacheable(),

/** 
  Invoked by a design coder to encode design properties.  The default 
  implementation invoked `encodeDesignProperties()` and
  `encodeChildViewsDesign()`.  You can override this method with your own
  additional encoding if you like.
*/
encodeDesign: function(coder) {
  coder.set('className', SC._object_className(this.get('objectClass')));
  this.encodeDesignProperties(coder);
  return YES ;
},

/**
  Encodes the design properties for the object.  These properties are simply
  copied from the object onto the coder.  As an optimization, the value of 
  each property will be checked against the default value in the class. If
  they match, the property will not be emitted.
*/
encodeDesignProperties: function(coder) {
  return this.encodeSimpleProperties(this.get('designProperties'), coder);
},

/** 
  Array of localized that can be encoded directly.  This is an easy way to
  add support for simple properties that need to be written to the 
  localization without added code.  These properties will be encoded by 
  `encodeLocalizedProperties()`.

  You can add to this array in your subclasses.
*/
localizedProperties: [],

/** 
  Invoked by a localization coder to encode design properties.  The default 
  implementation invoked `encodeLocalizedProperties()` and
  `encodeChildViewsLoc()`.  You can override this method with your own
  additional encoding if you like.
*/
encodeLoc: function(coder) {
  coder.set('className', SC._object_className(this.get('objectClass')));
  this.encodeLocalizedProperties(coder);
  return YES ;
},

/**
  Encodes the localized properties for the object.  These properties are 
  simply copied from the object onto the coder.  As an optimization, the value 
  of  each property will be checked against the default value in the class. 
  If they match, the property will not be emitted.
*/
encodeLocalizedProperties: function(coder) {
  return this.encodeSimpleProperties(this.get('localizedProperties'),coder);
},

/**
  This method is invoked when the designer is instantiated.  You can use 
  this method to reload any state saved in the object.  This method is called
  before any observers or bindings are setup to give you a chance to 
  configure the initial state of the designer.
*/
awakeDesign: function() {},

/**
  The `unknownProperty` handler will pass through to the object by default.
  This will often provide you the support you need without needing to 
  customize the Designer.  Just make sure you don't define a conflicting
  property name on the designer itself!
*/
unknownProperty: function(key, value) {
  if (value !== undefined) {
    this.object.set(key, value);
    return value ;
  } else return this.object.get(key);
},

// ......................................
// PRIVATE METHODS
//

init: function() {

  // setup design from object state...
  this.awakeDesign();

  // setup bindings, etc
  sc_super();

  // and register with designController, if defined...
  var c= this.get('designController');
  if (c) c.registerDesigner(this) ;

},

destroy: function() {
  sc_super();
  this.set('object', null); // clears the object observer...  
},

tryToPerform: function(methodName, arg1, arg2) {
  // only handle event if we are in design mode
  var page = this.object ? this.object.get('page') : null ;
  var isDesignMode = page ? page.get('needsDesigner') || page.get('isDesignMode') : NO ;

  // if we are in design mode, route event handling to the designer
  // otherwise, invoke default method.
  if (isDesignMode) {
    return sc_super();
  } else {
    return SC.Object.prototype.tryToPerform.apply(this.object, arguments);
  }
}

}) ;

// Set default Designer for object if (!SC.Object.Designer) SC.Object.Designer = SC.ObjectDesigner ;

// .….….….….….….….….….….….….….….. // DESIGN NOTIFICATION METHODS // // These methods are invoked automatically on the designer class whenever it // is loaded.

SC.ObjectDesigner.mixin({

/**
  Invoked whenever a designed object is loaded.  This will save the design
  attributes for later use by a designer.
*/
didLoadDesign: function(designedObject, sourceObject, attrs) {
  designedObject.isDesign = YES ; // indicates that we need a designer.
  designedObject.designAttrs = attrs;
  //designedObject.sourceObject = sourceObject; TODO: don't need this..
},

/**
  Invoked whenever a location is applied to a designed object.  Saves the 
  attributes separately for use by the design object.
*/
didLoadLocalization: function(designedObject, attrs) {
  // nothing to do for now.
},

/**
  Invoked whenver a object is created.  This will create a peer designer if 
  needed.
*/
didCreateObject: function(object, attrs) {
  // GATEKEEP: If this isn't a design, bail.
  if (!object.constructor.isDesign) return;

  // add designer if page is in design mode
  var page = object.get('page'),
      design = object.constructor;

  if (page && page.get('needsDesigner')) {

    // find the designer class
    var cur = design, origDesign = design;
    while(cur && !cur.Designer) cur = cur.superclass;
    var DesignerClass = (cur) ? cur.Designer : SC.Object.Designer;

    // next find the first superclass object that is not a design (and a real
    // class).  This is important to make sure that we can determine the 
    // real name of a object's class.
    while (design && design.isDesign) design = design.superclass;
    if (!design) design = SC.Object;

    object.designer = DesignerClass.create({
      object: object,
      objectClass: design,
      designAttrs: origDesign.designAttrs
      //sourceObject: origDesign.sourceObject TODO: don't need this
    });
  }
}

});