<commit_before>// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple, Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/**
  @class

  A Record is the core model class in SproutCore. It is analogous to
  NSManagedObject in Core Data and EOEnterpriseObject in the Enterprise
  Objects Framework (aka WebObjects), or ActiveRecord::Base in Rails.

  To create a new model class, in your SproutCore workspace, do:

  {{{
    $ sc-gen model MyApp.MyModel
  }}}

  This will create MyApp.MyModel in clients/my_app/models/my_model.js.

  The core attributes hash is used to store the values of a record in a
  format that can be easily passed to/from the server.  The values should
  generally be stored in their raw string form.  References to external
  records should be stored as primary keys.

  Normally you do not need to work with the attributes hash directly.
  Instead you should use get/set on normal record properties.  If the
  property is not defined on the object, then the record will check the
  attributes hash instead.

  You can bulk update attributes from the server using the
  updateAttributes() method.

  @extends SC.Object
  @since SproutCore 1.0
*/
SC.Record = SC.Object.extend(
/** @scope SC.Record.prototype */ {

  // ...............................
  // PROPERTIES
  //

  /**
    This is the primary key used to distinguish records.  If the keys
    match, the records are assumed to be identical.

    @property
    @type {String}
  */
  primaryKey: 'guid',

  /**
    Returns the id for the record instance.  The id is used to uniquely
    identify this record instance from all others of the same type.  If you
    have a primaryKey set on this class, then the id will be the value of the
    primaryKey property on the underlying JSON hash.
  */
  id: function() {
    return SC.Store.idFor(this.storeKey);
  }.property('storeKey').cacheable(),

  /**
    The record's status changes as it is loaded from the server.

    @property {Number}
  */
  status: function() {
    return this.store.readStatus(this.storeKey);
  }.property('storeKey').cacheable(),

  /**
    The store that owns this record.  All changes will be buffered into this
    store and committed to the rest of the store chain through here.

    This property is set when the record instance is created and should not be
    changed or else it will break the record behavior.

    @property {SC.Store}
  */
  store: null,

  /**
    This is the store key for the record, it is used to link it back to the
    dataHash. If a record is reused, this value will be replaced.

    You should not edit this store key but you may sometimes need to refer to
    this store key when implementing a Server object.

    @property {Integer}
  */
  storeKey: null,

  // ...............................
  // CRUD OPERATIONS
  //

  /**
    Refresh the record from the persistent store.  If the record was loaded
    from a persistent store, then the store will be asked to reload the
    record data from the server.  If the record is new and exists only in
    memory then this call will have no effect.

    @returns {SC.Record} receiver
  */
  refresh: function() {
    this.get('store').refreshRecord(null, null, this.get('storeKey'));
    return this ;
  },

  /**
    Deletes the record along with any dependent records.  This will mark the
    records destroyed in the store as well as changing the isDestroyed
    property on the record to YES.  If this is a new record, this will avoid
    creating the record in the first place.

    @returns {SC.Record} receiver
  */
  destroy: function() {
    this.get('store').destroyRecord(null, null, this.get('storeKey'));
    return this ;
  },

  /**
    You can invoke this method anytime you need to make the record as dirty.
    This will cause the record to be commited when you commitChanges()
    on the underlying store.

    If you use the writeAttribute() primitive, this method will be called for
    you.

    @returns {SC.Record} reciever
  */
  recordDidChange: function() {
    this.get('store').recordDidChange(null, null, this.get('storeKey'));
    return this ;
  },

  // ...............................
  // ATTRIBUTES
  //

  /** @private
    Current edit level.  Used to defer editing changes.
  */
  _editLevel: 0 ,

  /**
    Defers notification of record changes until you call a matching
    endEditing() method.  This method is called automatically whenever you
    set an attribute, but you can call it yourself to group multiple changes.

    Calls to beginEditing() and endEditing() can be nested.

    @returns {SC.Record} receiver
  */
  beginEditing: function() {
    this._editLevel++;
    return this ;
  },

  /**
    Notifies the store of record changes if this matches a top level call to
    beginEditing().  This method is called automatically whenever you set an
    attribute, but you can call it yourself to group multiple changes.

    Calls to beginEditing() and endEditing() can be nested.

    @returns {SC.Record} receiver
  */
  endEditing: function() {
    if(--this._editLevel <= 0) {
      this._editLevel = 0;
      this.recordDidChange();
    }
    return this ;
  },

  /**
    Reads the raw attribute from the underlying data hash.  This method does
    not transform the underlying attribute at all.

    @param {String} key the attribute you want to read
    @returns {Object} the value of the key, or null if it doesn't exist
  */
  readAttribute: function(key) {
    var store = this.get('store'), storeKey = this.storeKey;
    var attrs = store.readDataHash(storeKey);
    return attrs ? attrs[key] : undefined ;
  },

  /**
    Updates the passed attribute with the new value.  This method does not
    transform the value at all.  If instead you want to modify an array or
    hash already defined on the underlying json, you should instead get
    an editable version of the attribute using editableAttribute()

    @param {String} key the attribute you want to read
    @param {Object} value the attribute you want to read
    @returns {SC.Record} receiver
  **/
  writeAttribute: function(key, value) {
    this.beginEditing();
    var store = this.get('store'), storeKey = this.storeKey;
    var attrs = store.readEditableDataHash(storeKey);
    if (!attrs) throw SC.Record.BAD_STATE_ERROR;
    attrs[key] = value;
    this.endEditing();
    return this ;
  },

  /**
    This will return the raw attributes that you can edit directly.  If you
    make changes to this hash, be sure to call beginEditing() before you get
    the attributes and endEditing() aftwards.

    @returns {Object} the current attributes of the receiver
  **/
  attributes: function() {
    var store = this.get('store'), storeKey = this.storeKey;
    return store.readEditableDataHash(storeKey);
  }.property(),

  /**
    Called by the store whenever the underlying data hash has changed.  This
    will notify any observers interested in data hash properties that they
    have changed.

    @returns {SC.Record} receiver
  */
  storeDidChangeProperties: function(statusOnly) {
    if (statusOnly) {
      this.notifyPropertyChange('status');
    } else {
      this.allPropertiesDidChange();
    }
  },

  /**
    Normalizing a record will ensure that the underlying hash conforms
    to the record attributes such as their types (transforms) and default
    values.

    This method will write the conforming hash to the store and return
    the materialized record.

    By normalizing the record, you can use .attributes() and be
    assured that it will conform to the defined model. For example, this
    can be useful in the case where you need to send a JSON representation
    to some server after you have used .createRecord(), since this method
    will enforce the 'rules' in the model such as their types and default
    values. You can also include null values in the hash with the
    includeNull argument.

    @param {Boolean} includeNull will write empty (null) attributes
    @returns {SC.Record} the normalized record
  */

  normalize: function(includeNull) {

    var primaryKey = this.primaryKey, dataHash = {}, recordId = this.get('id');
    var store = this.get('store'), storeKey = this.get('storeKey'), attrValue, isRecord;

    dataHash[primaryKey] = recordId;

    for(var key in this) {
      // make sure property is a record attribute. if record attribute is a class (SC.Record)
      // do not add to hash unless includeNull argument is true.
      if(this[key] && this[key]['typeClass']) {
        isRecord = SC.typeOf(this[key].typeClass())==='class';

        if (!isRecord) {
          attrValue = this.get(key);
          if(attrValue || includeNull) dataHash[key] = attrValue;
        }
        else if(isRecord) {

          if(SC.typeOf(this.get(key))===SC.T_OBJECT) {
            // if relationships are present
            var ids = this.get(key).getEach('id');
            dataHash[key] = ids.get('length')>1 ? ids : ids[0];
          }
          else {
            // otherwise write default
            dataHash[key] = (this.get(key).get) ? this.get(key).get('id') : this.get(key);
          }

        }
        else if(includeNull) {
          dataHash[key] = null;
        }

      }
    }

    store.writeDataHash(storeKey, dataHash);
    return store.materializeRecord(storeKey);
  },

  /**
    If you try to get/set a property not defined by the record, then this
    method will be called. It will try to get the value from the set of
    attributes.

    @param {String} key the attribute being get/set
    @param {Object} value the value to set the key to, if present
    @returns {Object} the value
  */
  unknownProperty: function(key, value) {
    if (value !== undefined) {

      // if we're modifying the PKEY, then SC.Store needs to relocate where
      // this record is cached. store the old key, update the value, then let
      // the store do the housekeeping...
      var primaryKey = this.get('primaryKey');
      this.writeAttribute(key,value);

      // update ID if needed
      if (key === primaryKey) {
        SC.Store.replaceIdFor(this.get('storeKey'), value);
      }

    }
    return this.readAttribute(key);
  },

  // ...............................
  // PRIVATE
  //

  toString: function() {
    var attrs = this.get('attributes');
    return "%@(%@)".fmt(this.constructor.toString(), SC.inspect(attrs));
  }

}) ;

// Class Methods
SC.Record.mixin( /** @scope SC.Record */ {

  // Record States
  CLEAN:            0x0001,
  DIRTY:            0x0002,

  EMPTY:            0x0100,
  ERROR:            0x1000,

  READY:            0x0200,
  READY_CLEAN:      0x0201,
  READY_DIRTY:      0x0202,
  READY_NEW:        0x0203,

  DESTROYED:        0x0400,
  DESTROYED_CLEAN:  0x0401,
  DESTROYED_DIRTY:  0x0402,

  BUSY:             0x0800,
  BUSY_LOADING:     0x0804,
  BUSY_CREATING:    0x0808,
  BUSY_COMMITTING:  0x0810,
  BUSY_REFRESH:     0x0820,
  BUSY_REFRESH_CLEAN:  0x0821,
  BUSY_REFRESH_DIRTY:  0x0822,
  BUSY_DESTROYING:  0x0840,

  // exceptions that can be raised when processing records
  BAD_STATE_ERROR:     new Error("Internal Inconsistency"),
  RECORD_EXISTS_ERROR: new Error("Record Exists"),
  NOT_FOUND_ERROR:     new Error("Not found "),
  BUSY_ERROR:          new Error("Busy"),

  /**
    Helper method returns a new SC.RecordAttribute instance to map a simple
    value or to-one relationship.  At the very least, you should pass the
    type class you expect the attribute to have.  You may pass any additional
    options as well.

    Use this helper when you define SC.Record subclasses.

    h4. Example

    {{{
      MyApp.Contact = SC.Record.extend({
        firstName: SC.Record.attr(String, { isRequired: YES })
      });
    }}}

    @param {Class} type the attribute type
    @param {Hash} opts the options for the attribute
    @returns {SC.RecordAttribute} created instance
  */
  attr: function(type, opts) {
    return SC.RecordAttribute.attr(type, opts);
  },

  /**
    Returns an SC.RecordAttribute that describes a fetched attribute.  When
    you reference this attribute, it will return an SC.RecordArray that uses
    the type as the fetch key and passes the attribute value as a param.

    Use this helper when you define SC.Record subclasses.

    h4. Example

    {{{
      MyApp.Group = SC.Record.extend({
        contacts: SC.Record.fetch('MyApp.Contact')
      });
    }}}

    @param {SC.Record|String} recordType The type of records to load
    @param {Hash} opts the options for the attribute
    @returns {SC.RecordAttribute} created instance
  */
  fetch: function(recordType, opts) {
    return SC.FetchedAttribute.attr(recordType, opts) ;
  },

  /**
    Returns an SC.ManyAttribute that describes a record array backed by an
    array of guids stored in the underlying JSON.  You can edit the contents
    of this relationship.

    If you set the inverse and isMaster: NO key, then editing this array will
    modify the underlying data, but the inverse key on the matching record
    will also be edited and that record will be marked as needing a change.

    @param {SC.Reocrd|String} recordType The type of record to create
    @param {Hash} opts the options for the attribute
    @returns {SC.ManyAttribute} created instance
  */
  toMany: function(recordType, opts) {
    return SC.ManyAttribute.attr(recordType, opts);
  },

  /**
    Returns all storeKeys mapped by Id for this record type.  This method is
    used mostly by the SC.Store and the Record to coordinate.  You will rarely
    need to call this method yourself.

    @returns {Hash}
  */
  storeKeysById: function() {
    var key = SC.keyFor('storeKey', SC.guidFor(this)),
        ret = this[key];
    if (!ret) ret = this[key] = {};
    return ret;
  },

  /**
    Given a primaryKey value for the record, returns the associated
    storeKey.  If the primaryKey has not been assigned a storeKey yet, it
    will be added.

    For the inverse of this method see SC.Store.idFor() and
    SC.Store.recordTypeFor().

    @param {String} id a record id
    @returns {Number} a storeKey.
  */
  storeKeyFor: function(id) {
    var storeKeys = this.storeKeysById(),
        ret       = storeKeys[id];

    if (!ret) {
      ret = SC.Store.generateStoreKey();
      SC.Store.idsByStoreKey[ret] = id ;
      SC.Store.recordTypesByStoreKey[ret] = this ;
      storeKeys[id] = ret ;
    }

    return ret ;
  },

  /**
    Given a primaryKey value for the record, returns the associated
    storeKey.  As opposed to storeKeyFor() however, this method
    will NOT generate a new storeKey but returned undefined.

    @param {String} id a record id
    @returns {Number} a storeKey.
  */
  storeKeyExists: function(id) {
    var storeKeys = this.storeKeysById(),
        ret       = storeKeys[id];

    return ret ;
  },

  /**
    Returns a record with the named ID in store.

    @param {SC.Store} store the store
    @param {String} id the record id
    @returns {SC.Record} record instance
  */
  find: function(store, id) {
    return store.find(this, id);
  },

  /**
    Finds all records in the store with this record type matching the named
    parameters.  This is the same as calling store.findAll(recordType, params)

    @param {SC.Store} store the store
    @param {Hash} params optional params
    @returns {SC.RecordArray} result set or null
  */
  findAll: function(store, params) {
    return store.findAll(this, params);
  }

}) ;

/**
  Alias for SC.Record.attr.  Using this version to describe to-one
  relationships can sometimes make your code more understandable.
*/
SC.Record.toOne = SC.Record.attr ;

<commit_msg>Added statusToString() method and cleaned up normalize()<commit_after>// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple, Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/**
  @class

  A Record is the core model class in SproutCore. It is analogous to
  NSManagedObject in Core Data and EOEnterpriseObject in the Enterprise
  Objects Framework (aka WebObjects), or ActiveRecord::Base in Rails.

  To create a new model class, in your SproutCore workspace, do:

  {{{
    $ sc-gen model MyApp.MyModel
  }}}

  This will create MyApp.MyModel in clients/my_app/models/my_model.js.

  The core attributes hash is used to store the values of a record in a
  format that can be easily passed to/from the server.  The values should
  generally be stored in their raw string form.  References to external
  records should be stored as primary keys.

  Normally you do not need to work with the attributes hash directly.
  Instead you should use get/set on normal record properties.  If the
  property is not defined on the object, then the record will check the
  attributes hash instead.

  You can bulk update attributes from the server using the
  updateAttributes() method.

  @extends SC.Object
  @since SproutCore 1.0
*/
SC.Record = SC.Object.extend(
/** @scope SC.Record.prototype */ {

  // ...............................
  // PROPERTIES
  //

  /**
    This is the primary key used to distinguish records.  If the keys
    match, the records are assumed to be identical.

    @property
    @type {String}
  */
  primaryKey: 'guid',

  /**
    Returns the id for the record instance.  The id is used to uniquely
    identify this record instance from all others of the same type.  If you
    have a primaryKey set on this class, then the id will be the value of the
    primaryKey property on the underlying JSON hash.
  */
  id: function() {
    return SC.Store.idFor(this.storeKey);
  }.property('storeKey').cacheable(),

  /**
    The record's status changes as it is loaded from the server.

    @property {Number}
  */
  status: function() {
    return this.store.readStatus(this.storeKey);
  }.property('storeKey').cacheable(),

  /**
    The store that owns this record.  All changes will be buffered into this
    store and committed to the rest of the store chain through here.

    This property is set when the record instance is created and should not be
    changed or else it will break the record behavior.

    @property {SC.Store}
  */
  store: null,

  /**
    This is the store key for the record, it is used to link it back to the
    dataHash. If a record is reused, this value will be replaced.

    You should not edit this store key but you may sometimes need to refer to
    this store key when implementing a Server object.

    @property {Integer}
  */
  storeKey: null,

  // ...............................
  // CRUD OPERATIONS
  //

  /**
    Refresh the record from the persistent store.  If the record was loaded
    from a persistent store, then the store will be asked to reload the
    record data from the server.  If the record is new and exists only in
    memory then this call will have no effect.

    @returns {SC.Record} receiver
  */
  refresh: function() {
    this.get('store').refreshRecord(null, null, this.get('storeKey'));
    return this ;
  },

  /**
    Deletes the record along with any dependent records.  This will mark the
    records destroyed in the store as well as changing the isDestroyed
    property on the record to YES.  If this is a new record, this will avoid
    creating the record in the first place.

    @returns {SC.Record} receiver
  */
  destroy: function() {
    this.get('store').destroyRecord(null, null, this.get('storeKey'));
    return this ;
  },

  /**
    You can invoke this method anytime you need to make the record as dirty.
    This will cause the record to be commited when you commitChanges()
    on the underlying store.

    If you use the writeAttribute() primitive, this method will be called for
    you.

    @returns {SC.Record} reciever
  */
  recordDidChange: function() {
    this.get('store').recordDidChange(null, null, this.get('storeKey'));
    return this ;
  },

  // ...............................
  // ATTRIBUTES
  //

  /** @private
    Current edit level.  Used to defer editing changes.
  */
  _editLevel: 0 ,

  /**
    Defers notification of record changes until you call a matching
    endEditing() method.  This method is called automatically whenever you
    set an attribute, but you can call it yourself to group multiple changes.

    Calls to beginEditing() and endEditing() can be nested.

    @returns {SC.Record} receiver
  */
  beginEditing: function() {
    this._editLevel++;
    return this ;
  },

  /**
    Notifies the store of record changes if this matches a top level call to
    beginEditing().  This method is called automatically whenever you set an
    attribute, but you can call it yourself to group multiple changes.

    Calls to beginEditing() and endEditing() can be nested.

    @returns {SC.Record} receiver
  */
  endEditing: function() {
    if(--this._editLevel <= 0) {
      this._editLevel = 0;
      this.recordDidChange();
    }
    return this ;
  },

  /**
    Reads the raw attribute from the underlying data hash.  This method does
    not transform the underlying attribute at all.

    @param {String} key the attribute you want to read
    @returns {Object} the value of the key, or null if it doesn't exist
  */
  readAttribute: function(key) {
    var store = this.get('store'), storeKey = this.storeKey;
    var attrs = store.readDataHash(storeKey);
    return attrs ? attrs[key] : undefined ;
  },

  /**
    Updates the passed attribute with the new value.  This method does not
    transform the value at all.  If instead you want to modify an array or
    hash already defined on the underlying json, you should instead get
    an editable version of the attribute using editableAttribute()

    @param {String} key the attribute you want to read
    @param {Object} value the attribute you want to read
    @returns {SC.Record} receiver
  **/
  writeAttribute: function(key, value) {
    this.beginEditing();
    var store = this.get('store'), storeKey = this.storeKey;
    var attrs = store.readEditableDataHash(storeKey);
    if (!attrs) throw SC.Record.BAD_STATE_ERROR;
    attrs[key] = value;
    this.endEditing();
    return this ;
  },

  /**
    This will return the raw attributes that you can edit directly.  If you
    make changes to this hash, be sure to call beginEditing() before you get
    the attributes and endEditing() aftwards.

    @returns {Object} the current attributes of the receiver
  **/
  attributes: function() {
    var store = this.get('store'), storeKey = this.storeKey;
    return store.readEditableDataHash(storeKey);
  }.property(),

  /**
    Called by the store whenever the underlying data hash has changed.  This
    will notify any observers interested in data hash properties that they
    have changed.

    @returns {SC.Record} receiver
  */
  storeDidChangeProperties: function(statusOnly) {
    if (statusOnly) {
      this.notifyPropertyChange('status');
    } else {
      this.allPropertiesDidChange();
    }
  },

  /**
    Normalizing a record will ensure that the underlying hash conforms
    to the record attributes such as their types (transforms) and default
    values.

    This method will write the conforming hash to the store and return
    the materialized record.

    By normalizing the record, you can use .attributes() and be
    assured that it will conform to the defined model. For example, this
    can be useful in the case where you need to send a JSON representation
    to some server after you have used .createRecord(), since this method
    will enforce the 'rules' in the model such as their types and default
    values. You can also include null values in the hash with the
    includeNull argument.

    @param {Boolean} includeNull will write empty (null) attributes
    @returns {SC.Record} the normalized record
  */

  normalize: function(includeNull) {

    var primaryKey = this.primaryKey, dataHash = {}, recordId = this.get('id'), recHash;
    var store = this.get('store'), storeKey = this.get('storeKey'), attrValue, isRecord;

    dataHash[primaryKey] = recordId;

    for(var key in this) {
      // make sure property is a record attribute.
      if(this[key] && this[key]['typeClass']) {
        isRecord = SC.typeOf(this[key].typeClass())==='class';

        if (!isRecord) {
          attrValue = this.get(key);
          if(attrValue || includeNull) dataHash[key] = attrValue;
        }
        else if(isRecord) {
          recHash = store.readDataHash(storeKey);

          if(recHash[key]) {
            // write value already there
            dataHash[key] = recHash[key];
          }
          else {
            // or write default
            dataHash[key] = this[key].get('defaultValue');
          }

        }
        else if(includeNull) {
          dataHash[key] = null;
        }

      }
    }

    store.writeDataHash(storeKey, dataHash);
    return store.materializeRecord(storeKey);
  },

  /**
    If you try to get/set a property not defined by the record, then this
    method will be called. It will try to get the value from the set of
    attributes.

    @param {String} key the attribute being get/set
    @param {Object} value the value to set the key to, if present
    @returns {Object} the value
  */
  unknownProperty: function(key, value) {
    if (value !== undefined) {

      // if we're modifying the PKEY, then SC.Store needs to relocate where
      // this record is cached. store the old key, update the value, then let
      // the store do the housekeeping...
      var primaryKey = this.get('primaryKey');
      this.writeAttribute(key,value);

      // update ID if needed
      if (key === primaryKey) {
        SC.Store.replaceIdFor(this.get('storeKey'), value);
      }

    }
    return this.readAttribute(key);
  },

  // ...............................
  // PRIVATE
  //

  toString: function() {
    var attrs = this.get('attributes');
    return "%@(%@)".fmt(this.constructor.toString(), SC.inspect(attrs));
  }

}) ;

// Class Methods
SC.Record.mixin( /** @scope SC.Record */ {

  // Record States
  CLEAN:            0x0001,
  DIRTY:            0x0002,

  EMPTY:            0x0100,
  ERROR:            0x1000,

  READY:            0x0200,
  READY_CLEAN:      0x0201,
  READY_DIRTY:      0x0202,
  READY_NEW:        0x0203,

  DESTROYED:        0x0400,
  DESTROYED_CLEAN:  0x0401,
  DESTROYED_DIRTY:  0x0402,

  BUSY:             0x0800,
  BUSY_LOADING:     0x0804,
  BUSY_CREATING:    0x0808,
  BUSY_COMMITTING:  0x0810,
  BUSY_REFRESH:     0x0820,
  BUSY_REFRESH_CLEAN:  0x0821,
  BUSY_REFRESH_DIRTY:  0x0822,
  BUSY_DESTROYING:  0x0840,

  // exceptions that can be raised when processing records
  BAD_STATE_ERROR:     new Error("Internal Inconsistency"),
  RECORD_EXISTS_ERROR: new Error("Record Exists"),
  NOT_FOUND_ERROR:     new Error("Not found "),
  BUSY_ERROR:          new Error("Busy"),

  /**
    Helper method returns a new SC.RecordAttribute instance to map a simple
    value or to-one relationship.  At the very least, you should pass the
    type class you expect the attribute to have.  You may pass any additional
    options as well.

    Use this helper when you define SC.Record subclasses.

    h4. Example

    {{{
      MyApp.Contact = SC.Record.extend({
        firstName: SC.Record.attr(String, { isRequired: YES })
      });
    }}}

    @param {Class} type the attribute type
    @param {Hash} opts the options for the attribute
    @returns {SC.RecordAttribute} created instance
  */
  attr: function(type, opts) {
    return SC.RecordAttribute.attr(type, opts);
  },

  /**
    Returns an SC.RecordAttribute that describes a fetched attribute.  When
    you reference this attribute, it will return an SC.RecordArray that uses
    the type as the fetch key and passes the attribute value as a param.

    Use this helper when you define SC.Record subclasses.

    h4. Example

    {{{
      MyApp.Group = SC.Record.extend({
        contacts: SC.Record.fetch('MyApp.Contact')
      });
    }}}

    @param {SC.Record|String} recordType The type of records to load
    @param {Hash} opts the options for the attribute
    @returns {SC.RecordAttribute} created instance
  */
  fetch: function(recordType, opts) {
    return SC.FetchedAttribute.attr(recordType, opts) ;
  },

  /**
    Returns an SC.ManyAttribute that describes a record array backed by an
    array of guids stored in the underlying JSON.  You can edit the contents
    of this relationship.

    If you set the inverse and isMaster: NO key, then editing this array will
    modify the underlying data, but the inverse key on the matching record
    will also be edited and that record will be marked as needing a change.

    @param {SC.Reocrd|String} recordType The type of record to create
    @param {Hash} opts the options for the attribute
    @returns {SC.ManyAttribute} created instance
  */
  toMany: function(recordType, opts) {
    return SC.ManyAttribute.attr(recordType, opts);
  },

  /**
    Returns all storeKeys mapped by Id for this record type.  This method is
    used mostly by the SC.Store and the Record to coordinate.  You will rarely
    need to call this method yourself.

    @returns {Hash}
  */
  storeKeysById: function() {
    var key = SC.keyFor('storeKey', SC.guidFor(this)),
        ret = this[key];
    if (!ret) ret = this[key] = {};
    return ret;
  },

  /**
    Given a primaryKey value for the record, returns the associated
    storeKey.  If the primaryKey has not been assigned a storeKey yet, it
    will be added.

    For the inverse of this method see SC.Store.idFor() and
    SC.Store.recordTypeFor().

    @param {String} id a record id
    @returns {Number} a storeKey.
  */
  storeKeyFor: function(id) {
    var storeKeys = this.storeKeysById(),
        ret       = storeKeys[id];

    if (!ret) {
      ret = SC.Store.generateStoreKey();
      SC.Store.idsByStoreKey[ret] = id ;
      SC.Store.recordTypesByStoreKey[ret] = this ;
      storeKeys[id] = ret ;
    }

    return ret ;
  },

  /**
    Given a primaryKey value for the record, returns the associated
    storeKey.  As opposed to storeKeyFor() however, this method
    will NOT generate a new storeKey but returned undefined.

    @param {String} id a record id
    @returns {Number} a storeKey.
  */
  storeKeyExists: function(id) {
    var storeKeys = this.storeKeysById(),
        ret       = storeKeys[id];

    return ret ;
  },

  /**
    Returns a record with the named ID in store.

    @param {SC.Store} store the store
    @param {String} id the record id
    @returns {SC.Record} record instance
  */
  find: function(store, id) {
    return store.find(this, id);
  },

  /**
    Finds all records in the store with this record type matching the named
    parameters.  This is the same as calling store.findAll(recordType, params)

    @param {SC.Store} store the store
    @param {Hash} params optional params
    @returns {SC.RecordArray} result set or null
  */
  findAll: function(store, params) {
    return store.findAll(this, params);
  },

  /**
    Creates a string representation of the status. This could be one or
    multiple statuses. This is a helper method and is meant for debug/output
    purposes only.

    @param {Number} status (as returned from store.readStatus() )
    @returns {String}
  */

  statusToString: function(status) {
    var ret = [];

    for(prop in SC.Record) {
      if(prop.match(/[A-Z_]$/) && SC.Record[prop]===status) {
        ret.push(prop);
      }
    }

    return ret.join(" ");
  }

}) ;

/**
  Alias for SC.Record.attr.  Using this version to describe to-one
  relationships can sometimes make your code more understandable.
*/
SC.Record.toOne = SC.Record.attr ;

<|endoftext|>


<commit_before>// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/**
  @class

  Drop Down Menu has a functionality similar to that of SelectField

  Clicking the DropDownMenu button displays a menu pane with a
  list of items. The selected item will be displayed on the button.
  User has the option of enabling checkbox for the selected menu item.

  @extends SC.ButtonView
  @version 1.0
  @author Mohammed Ashik
*/
sc_require('views/button');

SC.DropDownMenu = SC.ButtonView.extend(
/** @scope SC.DropDownMenu.prototype */ {

  /**
    An array of items that will be form the menu you want to show.

    @property
    @type {Array}
  */
  objects: [],

  /**
    Binding default for an array of objects

    @property
    @default SC.Binding.multiple()
  */
  objectsBindingDefault: SC.Binding.multiple(),

  /**
    If you set this to a non-null value, then the name shown for each
    menu item will be pulled from the object using the named property.
    if this is null, the collection objects themselves will be used.

    @property
    @type {String}
    @default: null
  */
  nameKey: null,

  /**
    If you set this to a non-null value, then the value of this key will
    be used to sort the objects.  If this is not set, then nameKey will
    be used.

    @property
    @type: {String}
    @default: null
  */
  sortKey: null,

  /**
     Set this to a non-null value to use a key from the passed set of objects
     as the value for the options popup.  If you don't set this, then the
     objects themselves will be used as the value.

     @property
     @type {String}
     @default null
  */
  valueKey: null,

  /**
    If true, the empty name will be localized.

    @property
    @type {Boolean}
    @default YES
  */
  localize: YES,

  /**
    if true, it means that no sorting will occur, objects will appear
    in the same order as in the array

    @property
    @type {Boolean}
    @default YES
  */
  disableSort: YES,

  /**

    @property
    @default ['drop-down-menu']
  */
  classNames: ['drop-down-menu'],

  /**
    Menu item list

    @property
    @type:{Array}
  */
  itemList: [],

  /**
    Current selected menu item

    @property
    @default null
  */
  currentSelItem: null,

  /**
    Property to set the index of the selected menu item. This in turn
    is used to calculate the preferMatrix.

    @property
    @type {Number}
    @default null
  */
  itemIdx: null,

  /**
     Current Value of the dropDown

     @property
     @default null
  */
  value: null ,

  /**
    if this property is set to 'YES', a checbox is shown next to the
    selected menu item.

    @private
    @default NO
  */
  checkboxEnabled: NO,

  /**
    Default value of the drop down.
     This will be the first item from the menu item list.

    @private
  */
  _defaultVal: null,

  /**
    Default value of the drop down.
     This will be the title corresponding to the _defaultVal.

    @private
  */
  _defaultTitle: null,

  /**
    Default value of the drop down.
     This will be the icon corresponding to the _defaultVal.

    @private
  */
  _defaultIcon: null,

  /**
    @private

    The button theme will be popup
  */
  theme: 'popup',

  /**
    Render method gets triggered when these properties change

    @property
    @type{SC.Array}
  */
  displayProperties: ['icon', 'value','controlSize'],

  /**
    Prefer matrix to position the drop down menu such that the
    selected item for the menu item will appear aligned to the
    the button. The value at the second index(0) changes based on the
    postion(index) of the menu item in the menu pane.

    @property
    @type {Array}
    @default [ 0, 0, 2]

  */
  preferMatrix: null,

  /**
    Width of the sprite image that gets applied due to the theme.
     This has to be accounted for while calculating the actual
     width of the button

    @property
    @type {Number}
    @default 32
  */
  DROP_DOWN_SPRITE_WIDTH: 32,

  /**
    Property to set the menu item height. This in turn is used for
    the calculation of prefMatrix.

    @property
    @type {Number}
    @default 20
  */
  CUSTOM_MENU_ITEM_HEIGHT: 20,

  /**
    Binds the button's selection state to the menu's visibility.

    @private
  */
  isSelectedBinding: '*menu.isVisibleInWindow',

  /**
    If this property is set to 'YES', the menu pane will be positioned
    below the anchor.

    @private
    @default NO
  */
  isDefaultPosition: NO,

  /**
    Left Alignment based on the size of the button

    @private
  */
  leftAlign: function() {
    var val = 0 ;
    var controlSize = this.get('controlSize') ;
    if(controlSize === SC.SMALL_CONTROL_SIZE) val = -14 ;
    if(controlSize === SC.REGULAR_CONTROL_SIZE) val = -11 ;
    return val;
  }.property('controlSize'),

  /**
    override this method to implement your own sorting of the menu. By
    default, menu items are sorted using the value shown or the sortKey

    @param{SC.Array} objects the unsorted array of objects to display.
    @returns sorted array of objects
  */
  sortObjects: function(objects) {
    if(!this.get('disableSort')){
      var nameKey = this.get('sortKey') || this.get('nameKey') ;
      objects = objects.sort(function(a,b) {
        if (nameKey) {
          a = a.get ? a.get(nameKey) : a[nameKey] ;
          b = b.get ? b.get(nameKey) : b[nameKey] ;
        }
        return (a<b) ? -1 : ((a>b) ? 1 : 0) ;
      }) ;
    }
    return objects ;
  },

  /**
    render method

    @private
  */
  render: function(context,firstTime) {

    var layoutWidth = this.layout.width ;
    if(firstTime && layoutWidth) {
      this.adjust({ width: layoutWidth - this.DROP_DOWN_SPRITE_WIDTH }) ;
    }

    var objects = this.get('objects') ;
    objects = this.sortObjects(objects) ;

    //Get the namekey, iconKey and valueKey set by the user
    var nameKey = this.get('nameKey') ;
    var iconKey = this.get('iconKey') ;
    var valueKey = this.get('valueKey') ;
    var checkboxEnabled = this.get('checkboxEnabled') ;

    //get the current selected value
    var currentSelectedVal = this.get('value') ;

    // get the localization flag.
    var shouldLocalize = this.get('localize') ;

    //itemList array to set the menu items
    var itemList = [] ;

    //to set the 'checkbox' property of menu items
    var isChecked = YES ;

    //index for finding the first item in the list
    var idx = 0 ;

    objects.forEach(function(object) {
    if (object) {

      //Get the name value. If value key is not specified convert obj
      //to string
      var name = nameKey ? (object.get ?
        object.get(nameKey) : object[nameKey]) : object.toString() ;

      // localize name if specified.
      name = shouldLocalize? name.loc() : name ;

      // get the value using the valueKey or the object if no valueKey.
      // then convert to a string or use _guid if one of available.
      var value = (valueKey) ? (object.get ?
        object.get(valueKey) : object[valueKey]) : object ;

      if (currentSelectedVal && value){
        if( currentSelectedVal === value ) {
          this.set('title', name) ;
        }
      }

      //Check if the item is currentSelectedItem or not
      if(name === this.title) {

        //set the itemIdx - To change the prefMatrix accordingly.
        this.set('itemIdx', idx) ;
        isChecked = checkboxEnabled ? YES : NO ;
      }
      else {
        isChecked = NO ;
      }

      //Get the icon value
      var icon = iconKey ? (object.get ?
        object.get(iconKey) : object[iconKey]) : null ;
      if (SC.none(object[iconKey])) icon = null ;

      //Set the first item from the list as default selected item
      if (idx === 0) {
        this._defaultSelVal = name ;
        this.set('icon', icon) ;
      }

      //Set the items in the itemList array
      itemList.push({
        title: name,
        icon: icon,
        newVal:value,
        isEnabled: YES,
        checkbox: isChecked,
        action: this.displaySelectedItem
      });
    }

    idx += 1 ;

    this.set('itemList', itemList) ;
    }, this ) ;

    if(firstTime) {
      var selectionValue = this.get('selectionValue') ;
      this.title =
        selectionValue? selectionValue : this.get('_defaultSelVal') ;
    }

    //Set the preference matrix for the menu pane
    this.changeDropDownPreferMatrix(this.itemIdx) ;

    sc_super() ;
  },

  /**
    Button action handler

    @private
    @param {DOMMouseEvent} evt mouseup event that triggered the action
  */
  _action: function( evt )
  {
    var width = this.get('layer').offsetWidth ;
    var currSel = this.get('currentSelItem') ;
    var itemList = this.get('itemList') ;
    var menuControlSize = this.get('controlSize') ;
    var menu = SC.MenuPane.create({

      /**
        Class name - drop-down-menu-item

      */
      classNames: ['drop-down-menu-item'],

      /**
        The menu items are set from the itemList property of DropDownMenu

        @property
      */
      items: itemList,

      /**
        This property enables all the items and makes them selectable.

        @property
      */
      isEnabled: YES,

      preferType: SC.PICKER_MENU,
      itemHeightKey: 'height',
      layout: { width: width },
      controlSize: menuControlSize,
      contentView: SC.View.extend({
      })
    }) ;

    // no menu to toggle... bail...
    if (!menu) return NO ;
    menu.popup(this, this.preferMatrix) ;
    menu.set('currentSelectedMenuItem', currSel) ;
    return YES ;
  },

  /**
     Action method for the drop down menu items

  */
  displaySelectedItem: function() {

    //Get MenuPane, currentSelectedMenuItem & menuItemView
    var menuView = this.get('parentView') ;
    var currSel = menuView.get('currentSelectedMenuItem') ;
    var itemViews = menuView.menuItemViews ;
    var title,newVal ;

    //  Fetch the index of the current selected item
    var itemIdx = 0 ;
    if (currSel && itemViews) {
      itemIdx = itemViews.indexOf(currSel) ;
    }

    // Get the drop down View
    var button = this.getPath('parentView.anchor') ;

    // set the value and title
    var object = menuView.get('items') ;
    var len = object.length ;
    var found = null ;

    while (!found && (--len >= 0)) {
      title = object[len].title ? object[len].title: object.toString() ;
      newVal =  object[len].newVal ? object[len].newVal: title ;

      if (title === this.get('value')) {
        found = object ;
        button.set('value', newVal) ;
        button.set('title', title) ;
      }
    }

    // set the icon, currentSelectedItem and itemIdx
    button.set('icon', this.get('icon')).set('currentSelItem', currSel).
      set('itemIdx', itemIdx) ;
  },

  /**
     Set the "top" attribute in the prefer matrix property which will
     position menu such that the selected item in the menu will be
     place aligned to the item on the button when menu is opened.
  */
  changeDropDownPreferMatrix: function() {
    var preferMatrixAttributeTop = 0 ;
    var itemIdx = this.get('itemIdx') ;
    var leftAlign = this.get('leftAlign') ;
    var defPreferMatrix ;
    var tempPreferMatrix ;

    if(this.get('isDefaultPosition')) {
      defPreferMatrix = [leftAlign, 4, 3] ;
      this.set('preferMatrix', defPreferMatrix) ;
    }
    else {
      if(itemIdx)
        preferMatrixAttributeTop = itemIdx * this.CUSTOM_MENU_ITEM_HEIGHT ;
      tempPreferMatrix = [leftAlign, -preferMatrixAttributeTop, 2] ;
      this.set('preferMatrix', tempPreferMatrix) ;
    }
  }

}) ;

<commit_msg>deleted redundant code. selectionValue changed to value.<commit_after>// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/**
  @class

  Drop Down Menu has a functionality similar to that of SelectField

  Clicking the DropDownMenu button displays a menu pane with a
  list of items. The selected item will be displayed on the button.
  User has the option of enabling checkbox for the selected menu item.

  @extends SC.ButtonView
  @version 1.0
  @author Mohammed Ashik
*/
sc_require('views/button');

SC.DropDownMenu = SC.ButtonView.extend(
/** @scope SC.DropDownMenu.prototype */ {

  /**
    An array of items that will be form the menu you want to show.

    @property
    @type {Array}
  */
  objects: [],

  /**
    Binding default for an array of objects

    @property
    @default SC.Binding.multiple()
  */
  objectsBindingDefault: SC.Binding.multiple(),

  /**
    If you set this to a non-null value, then the name shown for each
    menu item will be pulled from the object using the named property.
    if this is null, the collection objects themselves will be used.

    @property
    @type {String}
    @default: null
  */
  nameKey: null,

  /**
    If you set this to a non-null value, then the value of this key will
    be used to sort the objects.  If this is not set, then nameKey will
    be used.

    @property
    @type: {String}
    @default: null
  */
  sortKey: null,

  /**
     Set this to a non-null value to use a key from the passed set of objects
     as the value for the options popup.  If you don't set this, then the
     objects themselves will be used as the value.

     @property
     @type {String}
     @default null
  */
  valueKey: null,

  /**
    If true, the empty name will be localized.

    @property
    @type {Boolean}
    @default YES
  */
  localize: YES,

  /**
    if true, it means that no sorting will occur, objects will appear
    in the same order as in the array

    @property
    @type {Boolean}
    @default YES
  */
  disableSort: YES,

  /**

    @property
    @default ['drop-down-menu']
  */
  classNames: ['drop-down-menu'],

  /**
    Menu item list

    @property
    @type:{Array}
  */
  itemList: [],

  /**
    Current selected menu item

    @property
    @default null
  */
  currentSelItem: null,

  /**
    Property to set the index of the selected menu item. This in turn
    is used to calculate the preferMatrix.

    @property
    @type {Number}
    @default null
  */
  itemIdx: null,

  /**
     Current Value of the dropDown

     @property
     @default null
  */
  value: null ,

  /**
    if this property is set to 'YES', a checbox is shown next to the
    selected menu item.

    @private
    @default NO
  */
  checkboxEnabled: NO,

  /**
    Default value of the drop down.
     This will be the first item from the menu item list.

    @private
  */
  _defaultVal: null,

  /**
    Default value of the drop down.
     This will be the title corresponding to the _defaultVal.

    @private
  */
  _defaultTitle: null,

  /**
    Default value of the drop down.
     This will be the icon corresponding to the _defaultVal.

    @private
  */
  _defaultIcon: null,

  /**
    @private

    The button theme will be popup
  */
  theme: 'popup',

  /**
    Render method gets triggered when these properties change

    @property
    @type{SC.Array}
  */
  displayProperties: ['icon', 'value','controlSize'],

  /**
    Prefer matrix to position the drop down menu such that the
    selected item for the menu item will appear aligned to the
    the button. The value at the second index(0) changes based on the
    postion(index) of the menu item in the menu pane.

    @property
    @type {Array}
    @default [ 0, 0, 2]

  */
  preferMatrix: null,

  /**
    Width of the sprite image that gets applied due to the theme.
     This has to be accounted for while calculating the actual
     width of the button

    @property
    @type {Number}
    @default 32
  */
  DROP_DOWN_SPRITE_WIDTH: 32,

  /**
    Property to set the menu item height. This in turn is used for
    the calculation of prefMatrix.

    @property
    @type {Number}
    @default 20
  */
  CUSTOM_MENU_ITEM_HEIGHT: 20,

  /**
    Binds the button's selection state to the menu's visibility.

    @private
  */
  isSelectedBinding: '*menu.isVisibleInWindow',

  /**
    If this property is set to 'YES', the menu pane will be positioned
    below the anchor.

    @private
    @default NO
  */
  isDefaultPosition: NO,

  /**
    Left Alignment based on the size of the button

    @private
  */
  leftAlign: function() {
    var val = 0 ;
    var controlSize = this.get('controlSize') ;
    if(controlSize === SC.SMALL_CONTROL_SIZE) val = -14 ;
    if(controlSize === SC.REGULAR_CONTROL_SIZE) val = -11 ;
    return val;
  }.property('controlSize'),

  /**
    override this method to implement your own sorting of the menu. By
    default, menu items are sorted using the value shown or the sortKey

    @param{SC.Array} objects the unsorted array of objects to display.
    @returns sorted array of objects
  */
  sortObjects: function(objects) {
    if(!this.get('disableSort')){
      var nameKey = this.get('sortKey') || this.get('nameKey') ;
      objects = objects.sort(function(a,b) {
        if (nameKey) {
          a = a.get ? a.get(nameKey) : a[nameKey] ;
          b = b.get ? b.get(nameKey) : b[nameKey] ;
        }
        return (a<b) ? -1 : ((a>b) ? 1 : 0) ;
      }) ;
    }
    return objects ;
  },

  /**
    render method

    @private
  */
  render: function(context,firstTime) {

    var layoutWidth = this.layout.width ;
    if(firstTime && layoutWidth) {
      this.adjust({ width: layoutWidth - this.DROP_DOWN_SPRITE_WIDTH }) ;
    }

    var objects = this.get('objects') ;
    objects = this.sortObjects(objects) ;

    //Get the namekey, iconKey and valueKey set by the user
    var nameKey = this.get('nameKey') ;
    var iconKey = this.get('iconKey') ;
    var valueKey = this.get('valueKey') ;
    var checkboxEnabled = this.get('checkboxEnabled') ;

    //get the current selected value
    var currentSelectedVal = this.get('value') ;

    // get the localization flag.
    var shouldLocalize = this.get('localize') ;

    //itemList array to set the menu items
    var itemList = [] ;

    //to set the 'checkbox' property of menu items
    var isChecked = YES ;

    //index for finding the first item in the list
    var idx = 0 ;

    objects.forEach(function(object) {
    if (object) {

      //Get the name value. If value key is not specified convert obj
      //to string
      var name = nameKey ? (object.get ?
        object.get(nameKey) : object[nameKey]) : object.toString() ;

      // localize name if specified.
      name = shouldLocalize? name.loc() : name ;

      // get the value using the valueKey or the object if no valueKey.
      // then convert to a string or use _guid if one of available.
      var value = (valueKey) ? (object.get ?
        object.get(valueKey) : object[valueKey]) : object ;

      if (currentSelectedVal && value){
        if( currentSelectedVal === value ) {
          this.set('title', name) ;
        }
      }

      //Check if the item is currentSelectedItem or not
      if(name === this.title) {

        //set the itemIdx - To change the prefMatrix accordingly.
        this.set('itemIdx', idx) ;
        isChecked = checkboxEnabled ? YES : NO ;
      }
      else {
        isChecked = NO ;
      }

      //Get the icon value
      var icon = iconKey ? (object.get ?
        object.get(iconKey) : object[iconKey]) : null ;
      if (SC.none(object[iconKey])) icon = null ;

      //Set the first item from the list as default selected item
      if (idx === 0) {
        this._defaultSelVal = name ;
        this.set('icon', icon) ;
      }

      //Set the items in the itemList array
      itemList.push({
        title: name,
        icon: icon,
        newVal:value,
        isEnabled: YES,
        checkbox: isChecked,
        action: this.displaySelectedItem
      });
    }

    idx += 1 ;

    this.set('itemList', itemList) ;
    }, this ) ;

    if(firstTime) {
      var value = this.get('value') ;
      if(SC.none(value)) {
        this.set('value', this._defaultVal) ;
        this.set('title', this._defaultTitle) ;
        this.set('icon', this._defaultIcon) ;
      }
    }

    //Set the preference matrix for the menu pane
    this.changeDropDownPreferMatrix(this.itemIdx) ;

    sc_super() ;
  },

  /**
    Button action handler

    @private
    @param {DOMMouseEvent} evt mouseup event that triggered the action
  */
  _action: function( evt )
  {
    var width = this.get('layer').offsetWidth ;
    var currSel = this.get('currentSelItem') ;
    var itemList = this.get('itemList') ;
    var menuControlSize = this.get('controlSize') ;
    var menu = SC.MenuPane.create({

      /**
        Class name - drop-down-menu-item

      */
      classNames: ['drop-down-menu-item'],

      /**
        The menu items are set from the itemList property of DropDownMenu

        @property
      */
      items: itemList,

      /**
        This property enables all the items and makes them selectable.

        @property
      */
      isEnabled: YES,

      preferType: SC.PICKER_MENU,
      itemHeightKey: 'height',
      layout: { width: width },
      controlSize: menuControlSize,
      contentView: SC.View.extend({
      })
    }) ;

    // no menu to toggle... bail...
    if (!menu) return NO ;
    menu.popup(this, this.preferMatrix) ;
    menu.set('currentSelectedMenuItem', currSel) ;
    return YES ;
  },

  /**
     Action method for the drop down menu items

  */
  displaySelectedItem: function() {

    //Get MenuPane, currentSelectedMenuItem & menuItemView
    var menuView = this.get('parentView') ;
    var currSel = menuView.get('currentSelectedMenuItem') ;
    var itemViews = menuView.menuItemViews ;
    var title,newVal ;

    //  Fetch the index of the current selected item
    var itemIdx = 0 ;
    if (currSel && itemViews) {
      itemIdx = itemViews.indexOf(currSel) ;
    }

    // Get the drop down View
    var button = this.getPath('parentView.anchor') ;

    // set the value and title
    var object = menuView.get('items') ;
    var len = object.length ;
    var found = null ;

    while (!found && (--len >= 0)) {
      title = object[len].title ? object[len].title: object.toString() ;
      newVal =  object[len].newVal ? object[len].newVal: title ;

      if (title === this.get('value')) {
        found = object ;
        button.set('value', newVal) ;
        button.set('title', title) ;
      }
    }

    // set the icon, currentSelectedItem and itemIdx
    button.set('icon', this.get('icon')).set('currentSelItem', currSel).
      set('itemIdx', itemIdx) ;
  },

  /**
     Set the "top" attribute in the prefer matrix property which will
     position menu such that the selected item in the menu will be
     place aligned to the item on the button when menu is opened.
  */
  changeDropDownPreferMatrix: function() {
    var preferMatrixAttributeTop = 0 ;
    var itemIdx = this.get('itemIdx') ;
    var leftAlign = this.get('leftAlign') ;
    var defPreferMatrix ;
    var tempPreferMatrix ;

    if(this.get('isDefaultPosition')) {
      defPreferMatrix = [leftAlign, 4, 3] ;
      this.set('preferMatrix', defPreferMatrix) ;
    }
    else {
      if(itemIdx)
        preferMatrixAttributeTop = itemIdx * this.CUSTOM_MENU_ITEM_HEIGHT ;
      tempPreferMatrix = [leftAlign, -preferMatrixAttributeTop, 2] ;
      this.set('preferMatrix', tempPreferMatrix) ;
    }
  }

}) ;

<|endoftext|>


<commit_before>function doIt() {

  var ruleOne = 'aeiou';
  var ruleTwo = 'repeated char';
  var ruleThree = ['ab', 'cd', 'pq', 'xy'];

  var input = $('#lol').text();

  $('#result').text('Result = ' + result);
}

function replaceAll(str, find, replace) {
  return str.replace(new RegExp(find, 'g'), replace);
}
<commit_msg>Update custom.js<commit_after>function doIt() {

  var ruleOne = 'aeiou';
  var ruleTwo = 'repeated char';
  var ruleThree = ['ab', 'cd', 'pq', 'xy'];

  var input = $('#lol').text();
  var inputArr = input.split('\n');

  for(var i = 0; i < inputArr.length; ++i) {
    //
    // DO TESTS!
    //
  }

  $('#result').text('Result = ' + result);
}

function replaceAll(str, find, replace) {
  return str.replace(new RegExp(find, 'g'), replace);
}
<|endoftext|>


<commit_before>//$( document ).ready(function() {

  function doIt() {

    	for (var a=[]; a.push([])<10;);

    	console.log(a);

    function addHouseIfNew() {
      for(var i = 0; i < houses.length; ++i) {
        if(houses[i].x == x && houses[i].y == y) {
          return;
        }
      }
      var house = {};
      house.x = x;
      house.y = y;
      houses.push(house);
    }

    var steps = $.trim($('#lol').text());
    var x = 0;
    var y = 0;
    var firstHouse = {'x' : x, 'y' : y};
    var houses = [firstHouse];
    for(var i = 0; i < steps.length;++i) {
      var move = steps[i];

      if(move == '>') {
        // RIGHT
        ++x;
      } else if(move == '<') {
        // LEFT
        --x;
      } else if(move == '^') {
        // UP
        ++y;
      } else {
        // DOWN
        --y;
      }
      addHouseIfNew();
      if(move != '^' && move != '<' && move != '>' && move != 'v') {
        alert(move);
      }
    }

    $('#result').text('Result = ' + houses.length);
  }
//});
<commit_msg>Update custom.js<commit_after>//$( document ).ready(function() {

  function doIt() {

    function success(data) {

      $('#lol').text(data);
      var steps = $.trim($('#lol').text());

      function addHouseIfNew() {
        for(var i = 0; i < houses.length; ++i) {
          if(houses[i].x == x && houses[i].y == y) {
            return;
          }
        }
        var house = {};
        house.x = x;
        house.y = y;
        houses.push(house);
      }

      var steps = $.trim($('#lol').text());
      var x = 0;
      var y = 0;
      var firstHouse = {'x' : x, 'y' : y};
      var houses = [firstHouse];
      for(var i = 0; i < steps.length;++i) {
        var move = steps[i];

        if(move == '>') {
          // RIGHT
          ++x;
        } else if(move == '<') {
          // LEFT
          --x;
        } else if(move == '^') {
          // UP
          ++y;
        } else {
          // DOWN
          --y;
        }
        addHouseIfNew();
        if(move != '^' && move != '<' && move != '>' && move != 'v') {
          alert(move);
        }
      }

      $('#result').text('Result = ' + houses.length);

    };

    $.ajax({
      url: 'http://adventofcode.com/day/3/input',
      type:'GET'
      data: data,
      success: success,
      dataType: 'jsonp'
    });
  }
//});
<|endoftext|>


<commit_before>// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt

frappe.provide("frappe.perm");

// backward compatibilty
var READ = "read", WRITE = "write", CREATE = "create", DELETE = "delete";
var SUBMIT = "submit", CANCEL = "cancel", AMEND = "amend";

$.extend(frappe.perm, {
	rights: ["read", "write", "create", "delete", "submit", "cancel", "amend",
		"report", "import", "export", "print", "email", "share", "set_user_permissions"],

	doctype_perm: {},

	has_perm: function(doctype, permlevel, ptype, doc) {
		if (!permlevel) permlevel = 0;
		if (!frappe.perm.doctype_perm[doctype]) {
			frappe.perm.doctype_perm[doctype] = frappe.perm.get_perm(doctype);
		}

		var perms = frappe.perm.doctype_perm[doctype];
		if (!perms)
			return false;

		if (!perms[permlevel])
			return false;

		var perm = !!perms[permlevel][ptype];

		if(permlevel===0 && perm && doc) {
			var docinfo = frappe.model.get_docinfo(doctype, doc.name);
			if(docinfo && !docinfo.permissions[ptype])
				perm = false;
		}

		return perm;
	},

	get_perm: function(doctype, doc) {
		var perm = [{ read: 0, apply_user_permissions: {} }];

		var meta = frappe.get_doc("DocType", doctype);
		if (!meta) {
			return perm;
		}

		if (user==="Administrator" || user_roles.indexOf("Administrator")!==-1) {
			perm[0].read = 1;
		}

		frappe.perm.build_role_permissions(perm, meta);

		if(doc) {
			// apply user permissions via docinfo (which is processed server-side)
			var docinfo = frappe.model.get_docinfo(doctype, doc.name);
			if(docinfo) {
				$.each(docinfo.permissions || [], function(ptype, val) {
					perm[0][ptype] = val;
				});
			}

			// apply permissions from shared
			if(docinfo.shared) {
				for(var i=0; i<docinfo.shared; i++) {
					var s = docinfo.shared[i];
					if(s.user===user) {
						perm[0]["read"] = s.read;
						perm[0]["write"] = s.write;
						perm[0]["share"] = s.share;

					}
				}
			}
		}

		if(frappe.model.can_read(doctype) && !perm[0].read) {
			// read via sharing
			perm[0].read = 1;
		}

		return perm;
	},

	build_role_permissions: function(perm, meta) {
		$.each(meta.permissions || [], function(i, p) {
			// if user has this role
			if(user_roles.indexOf(p.role)!==-1) {
				var permlevel = cint(p.permlevel);
				if(!perm[permlevel]) {
					perm[permlevel] = {};
				}

				$.each(frappe.perm.rights, function(i, key) {
					perm[permlevel][key] = perm[permlevel][key] || (p[key] || 0);

					if (permlevel===0) {
						var apply_user_permissions = perm[permlevel].apply_user_permissions;
						var current_value = (apply_user_permissions[key]===undefined ?
								1 : apply_user_permissions[key]);
						apply_user_permissions[key] = current_value && cint(p.apply_user_permissions);
					}
				});

				if (permlevel===0 && cint(p.apply_user_permissions) && p.user_permission_doctypes) {
					// set user_permission_doctypes in perms
					var user_permission_doctypes = JSON.parse(p.user_permission_doctypes);

					if (user_permission_doctypes && user_permission_doctypes.length) {
						if (!perm[permlevel]["user_permission_doctypes"]) {
							perm[permlevel]["user_permission_doctypes"] = {};
						}

						$.each(frappe.perm.rights, function(i, key) {
							if (!perm[permlevel]["user_permission_doctypes"][key]) {
								perm[permlevel]["user_permission_doctypes"][key] = [];
							}

							perm[permlevel]["user_permission_doctypes"][key].push(user_permission_doctypes);
						});
					}
				}
			}
		});

		// remove values with 0
		$.each(perm[0], function(key, val) {
			if (!val) {
				delete perm[0][key];
			}
		});

		$.each(perm, function(i, v) {
			if(v===undefined) {
				perm[i] = {};
			}
		});
	},

	get_match_rules: function(doctype, ptype) {
		var me = this;
		var match_rules = [];

		if (!ptype) ptype = "read";

		var perm = frappe.perm.get_perm(doctype);
		var apply_user_permissions = perm[0].apply_user_permissions;
		if (!apply_user_permissions[ptype]) {
			return match_rules;
		}

		var user_permissions = frappe.defaults.get_user_permissions();
		if(user_permissions && !$.isEmptyObject(user_permissions)) {
			if(perm[0].user_permission_doctypes) {
				var user_permission_doctypes = me.get_user_permission_doctypes(perm[0].user_permission_doctypes[ptype],
					user_permissions);
			} else {
				// json is not set, so give list of all doctypes
				var user_permission_doctypes = [[doctype].concat(frappe.meta.get_linked_fields(doctype))];
			}

			$.each(user_permission_doctypes, function(i, doctypes) {
				var rules = {};
				var fields_to_check = frappe.meta.get_fields_to_check_permissions(doctype, null, doctypes);
				$.each(fields_to_check, function(i, df) {
					rules[df.label] = user_permissions[df.options] || [];
				});
				if (!$.isEmptyObject(rules)) {
					match_rules.push(rules);
				}
			});
		}

		return match_rules;
	},

	get_user_permission_doctypes: function(user_permission_doctypes, user_permissions) {
		// returns a list of list like [["User", "Blog Post"], ["User"]]
		var out = [];

		if (user_permission_doctypes && user_permission_doctypes.length) {
			$.each(user_permission_doctypes, function(i, doctypes) {
				var valid_doctypes = [];
				$.each(doctypes, function(i, d) {
					if (user_permissions[d]) {
						valid_doctypes.push(d);
					}
				});
				if (valid_doctypes.length) {
					out.push(valid_doctypes);
				}
			});

		} else {
			out = [Object.keys(user_permissions)];
		}

		if (out.length > 1) {
			// OPTIMIZATION
			// if intersection exists, use that to reduce the amount of querying
			// for example, [["Blogger", "Blog Category"], ["Blogger"]], should only search in [["Blogger"]] as the first and condition becomes redundant
			var common = out[0];
			for (var i=1, l=out.length; i < l; i++) {
				common = frappe.utils.intersection(common, out[i]);
				if (!common.length) {
					break;
				}
			}

			if (common.length) {
				// is common one of the user_permission_doctypes set?
				common.sort();
				for (var i=0, l=out.length; i < l; i++) {
					var arr = [].concat(out).sort();
					// are arrays equal?
					if (JSON.stringify(common)===JSON.stringify(arr)) {
						out = [common];
						break;
					}
				}
			}
		}

		return out
	},

	get_field_display_status: function(df, doc, perm, explain) {
		if(!doc) {
			return (df && cint(df.hidden)) ? "None": "Write";
		}

		perm = perm || frappe.perm.get_perm(doc.doctype, doc);
		if(!df.permlevel) df.permlevel = 0;
		var p = perm[df.permlevel];
		var status = "None";

		// permission
		if(p) {
			if(p.write && !df.disabled) {
				status = "Write";
			} else if(p.read) {
				status = "Read";
			}
		}
		if(explain) console.log("By Permission:" + status);

		// hidden
		if(cint(df.hidden)) status = "None";
		if(explain) console.log("By Hidden:" + status);

		// hidden due to dependency
		if(cint(df.hidden_due_to_dependency)) status = "None";
		if(explain) console.log("By Hidden Due To Dependency:" + status);

		// submit
		if(status==="Write" && cint(doc.docstatus) > 0) status = "Read";
		if(explain) console.log("By Submit:" + status);

		// allow on submit
		var allow_on_submit = df.fieldtype==="Table" ? 0 : cint(df.allow_on_submit);
		if(status==="Read" && allow_on_submit && cint(doc.docstatus)===1 && p.write) {
			status = "Write";
		}
		if(explain) console.log("By Allow on Submit:" + status);

		// workflow state
		if(status==="Read" && cur_frm && cur_frm.state_fieldname) {
			// fields updated by workflow must be read-only
			if(cint(cur_frm.read_only) ||
				in_list(cur_frm.states.update_fields, df.fieldname) ||
				df.fieldname==cur_frm.state_fieldname) {
				status = "Read";
			}
		}
		if(explain) console.log("By Workflow:" + status);

		// read only field is checked
		if(status==="Write" && cint(df.read_only)) {
			status = "Read";
		}
		if(explain) console.log("By Read Only:" + status);

		if(status==="Write" && df.set_only_once && !doc.__islocal) {
			status = "Read";
		}
		if(explain) console.log("By Set Only Once:" + status);

		return status;
	},

	is_visible: function(df, doc, perm) {
		if (typeof df === 'string') {
			// df is fieldname
			df = frappe.meta.get_docfield(doc.doctype, df, doc.parent || doc.name);
		}

		var status = frappe.perm.get_field_display_status(df, doc, perm);

		return status==="None" ? false : true;
	},
});
<commit_msg>[perms] allow email/print if has write access via share<commit_after>// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt

frappe.provide("frappe.perm");

// backward compatibilty
var READ = "read", WRITE = "write", CREATE = "create", DELETE = "delete";
var SUBMIT = "submit", CANCEL = "cancel", AMEND = "amend";

$.extend(frappe.perm, {
	rights: ["read", "write", "create", "delete", "submit", "cancel", "amend",
		"report", "import", "export", "print", "email", "share", "set_user_permissions"],

	doctype_perm: {},

	has_perm: function(doctype, permlevel, ptype, doc) {
		if (!permlevel) permlevel = 0;
		if (!frappe.perm.doctype_perm[doctype]) {
			frappe.perm.doctype_perm[doctype] = frappe.perm.get_perm(doctype);
		}

		var perms = frappe.perm.doctype_perm[doctype];
		if (!perms)
			return false;

		if (!perms[permlevel])
			return false;

		var perm = !!perms[permlevel][ptype];

		if(permlevel===0 && perm && doc) {
			var docinfo = frappe.model.get_docinfo(doctype, doc.name);
			if(docinfo && !docinfo.permissions[ptype])
				perm = false;
		}

		return perm;
	},

	get_perm: function(doctype, doc) {
		var perm = [{ read: 0, apply_user_permissions: {} }];

		var meta = frappe.get_doc("DocType", doctype);
		if (!meta) {
			return perm;
		}

		if (user==="Administrator" || user_roles.indexOf("Administrator")!==-1) {
			perm[0].read = 1;
		}

		frappe.perm.build_role_permissions(perm, meta);

		if(doc) {
			// apply user permissions via docinfo (which is processed server-side)
			var docinfo = frappe.model.get_docinfo(doctype, doc.name);
			if(docinfo) {
				$.each(docinfo.permissions || [], function(ptype, val) {
					perm[0][ptype] = val;
				});
			}

			// apply permissions from shared
			if(docinfo.shared) {
				for(var i=0; i<docinfo.shared; i++) {
					var s = docinfo.shared[i];
					if(s.user===user) {
						perm[0]["read"] = s.read;
						perm[0]["write"] = s.write;
						perm[0]["share"] = s.share;

						if(s.write) {
							// also give print, email permissions if read
							// and these permissions exist at level [0]
							perm[0].email = meta.permissions[0].email;
							perm[0].print = meta.permissions[0].print;
						}
					}
				}
			}
		}

		if(frappe.model.can_read(doctype) && !perm[0].read) {
			// read via sharing
			perm[0].read = 1;
		}

		return perm;
	},

	build_role_permissions: function(perm, meta) {
		$.each(meta.permissions || [], function(i, p) {
			// if user has this role
			if(user_roles.indexOf(p.role)!==-1) {
				var permlevel = cint(p.permlevel);
				if(!perm[permlevel]) {
					perm[permlevel] = {};
				}

				$.each(frappe.perm.rights, function(i, key) {
					perm[permlevel][key] = perm[permlevel][key] || (p[key] || 0);

					if (permlevel===0) {
						var apply_user_permissions = perm[permlevel].apply_user_permissions;
						var current_value = (apply_user_permissions[key]===undefined ?
								1 : apply_user_permissions[key]);
						apply_user_permissions[key] = current_value && cint(p.apply_user_permissions);
					}
				});

				if (permlevel===0 && cint(p.apply_user_permissions) && p.user_permission_doctypes) {
					// set user_permission_doctypes in perms
					var user_permission_doctypes = JSON.parse(p.user_permission_doctypes);

					if (user_permission_doctypes && user_permission_doctypes.length) {
						if (!perm[permlevel]["user_permission_doctypes"]) {
							perm[permlevel]["user_permission_doctypes"] = {};
						}

						$.each(frappe.perm.rights, function(i, key) {
							if (!perm[permlevel]["user_permission_doctypes"][key]) {
								perm[permlevel]["user_permission_doctypes"][key] = [];
							}

							perm[permlevel]["user_permission_doctypes"][key].push(user_permission_doctypes);
						});
					}
				}
			}
		});

		// remove values with 0
		$.each(perm[0], function(key, val) {
			if (!val) {
				delete perm[0][key];
			}
		});

		$.each(perm, function(i, v) {
			if(v===undefined) {
				perm[i] = {};
			}
		});
	},

	get_match_rules: function(doctype, ptype) {
		var me = this;
		var match_rules = [];

		if (!ptype) ptype = "read";

		var perm = frappe.perm.get_perm(doctype);
		var apply_user_permissions = perm[0].apply_user_permissions;
		if (!apply_user_permissions[ptype]) {
			return match_rules;
		}

		var user_permissions = frappe.defaults.get_user_permissions();
		if(user_permissions && !$.isEmptyObject(user_permissions)) {
			if(perm[0].user_permission_doctypes) {
				var user_permission_doctypes = me.get_user_permission_doctypes(perm[0].user_permission_doctypes[ptype],
					user_permissions);
			} else {
				// json is not set, so give list of all doctypes
				var user_permission_doctypes = [[doctype].concat(frappe.meta.get_linked_fields(doctype))];
			}

			$.each(user_permission_doctypes, function(i, doctypes) {
				var rules = {};
				var fields_to_check = frappe.meta.get_fields_to_check_permissions(doctype, null, doctypes);
				$.each(fields_to_check, function(i, df) {
					rules[df.label] = user_permissions[df.options] || [];
				});
				if (!$.isEmptyObject(rules)) {
					match_rules.push(rules);
				}
			});
		}

		return match_rules;
	},

	get_user_permission_doctypes: function(user_permission_doctypes, user_permissions) {
		// returns a list of list like [["User", "Blog Post"], ["User"]]
		var out = [];

		if (user_permission_doctypes && user_permission_doctypes.length) {
			$.each(user_permission_doctypes, function(i, doctypes) {
				var valid_doctypes = [];
				$.each(doctypes, function(i, d) {
					if (user_permissions[d]) {
						valid_doctypes.push(d);
					}
				});
				if (valid_doctypes.length) {
					out.push(valid_doctypes);
				}
			});

		} else {
			out = [Object.keys(user_permissions)];
		}

		if (out.length > 1) {
			// OPTIMIZATION
			// if intersection exists, use that to reduce the amount of querying
			// for example, [["Blogger", "Blog Category"], ["Blogger"]], should only search in [["Blogger"]] as the first and condition becomes redundant
			var common = out[0];
			for (var i=1, l=out.length; i < l; i++) {
				common = frappe.utils.intersection(common, out[i]);
				if (!common.length) {
					break;
				}
			}

			if (common.length) {
				// is common one of the user_permission_doctypes set?
				common.sort();
				for (var i=0, l=out.length; i < l; i++) {
					var arr = [].concat(out).sort();
					// are arrays equal?
					if (JSON.stringify(common)===JSON.stringify(arr)) {
						out = [common];
						break;
					}
				}
			}
		}

		return out
	},

	get_field_display_status: function(df, doc, perm, explain) {
		if(!doc) {
			return (df && cint(df.hidden)) ? "None": "Write";
		}

		perm = perm || frappe.perm.get_perm(doc.doctype, doc);
		if(!df.permlevel) df.permlevel = 0;
		var p = perm[df.permlevel];
		var status = "None";

		// permission
		if(p) {
			if(p.write && !df.disabled) {
				status = "Write";
			} else if(p.read) {
				status = "Read";
			}
		}
		if(explain) console.log("By Permission:" + status);

		// hidden
		if(cint(df.hidden)) status = "None";
		if(explain) console.log("By Hidden:" + status);

		// hidden due to dependency
		if(cint(df.hidden_due_to_dependency)) status = "None";
		if(explain) console.log("By Hidden Due To Dependency:" + status);

		// submit
		if(status==="Write" && cint(doc.docstatus) > 0) status = "Read";
		if(explain) console.log("By Submit:" + status);

		// allow on submit
		var allow_on_submit = df.fieldtype==="Table" ? 0 : cint(df.allow_on_submit);
		if(status==="Read" && allow_on_submit && cint(doc.docstatus)===1 && p.write) {
			status = "Write";
		}
		if(explain) console.log("By Allow on Submit:" + status);

		// workflow state
		if(status==="Read" && cur_frm && cur_frm.state_fieldname) {
			// fields updated by workflow must be read-only
			if(cint(cur_frm.read_only) ||
				in_list(cur_frm.states.update_fields, df.fieldname) ||
				df.fieldname==cur_frm.state_fieldname) {
				status = "Read";
			}
		}
		if(explain) console.log("By Workflow:" + status);

		// read only field is checked
		if(status==="Write" && cint(df.read_only)) {
			status = "Read";
		}
		if(explain) console.log("By Read Only:" + status);

		if(status==="Write" && df.set_only_once && !doc.__islocal) {
			status = "Read";
		}
		if(explain) console.log("By Set Only Once:" + status);

		return status;
	},

	is_visible: function(df, doc, perm) {
		if (typeof df === 'string') {
			// df is fieldname
			df = frappe.meta.get_docfield(doc.doctype, df, doc.parent || doc.name);
		}

		var status = frappe.perm.get_field_display_status(df, doc, perm);

		return status==="None" ? false : true;
	},
});
<|endoftext|>


<commit_before>/**
 * @class Denkmal_Page_Events
 * @extends Denkmal_Page_Abstract
 */
var Denkmal_Page_Events = Denkmal_Page_Abstract.extend({

  /** @type String */
  _class: 'Denkmal_Page_Events',

  /** @type SwipeCarousel */
  _carousel: null,

  /** @type Boolean */
  _floatboxFullscreen: false,

  _stateParams: ['date', 'event'],

  events: {
    'swipeCarousel-change .swipeCarousel': function(event, data) {
      var delaySetUrl = !data.immediateSetUrl;
      this._onShowPane($(data.element), delaySetUrl);
    },

    'click .dismissBanner': function() {
      this._hideBanner();
    }
  },

  ready: function() {
    this._onShowPaneSetUrlDelayed = _.debounce(this._onShowPaneSetUrl, 2000);

    var $carousel = this.$('.swipeCarousel');
    this._carousel = new SwipeCarousel($carousel);
    this._carousel.init();

    var self = this;
    this.on('destruct', function() {
      self._carousel.destroy();
    });

    this._showBanner();

    this._initFloatboxMediaQuery();
  },

  _initFloatboxMediaQuery: function() {
    var self = this;
    var mediaQueryLarge = {
      match: function() {
        self._changeFloatboxFullscreen(false);
      }
    };
    var mediaQuerySmall = {
      match: function() {
        self._changeFloatboxFullscreen(true);
      }
    };
    enquire.register('(min-width: 600px)', mediaQueryLarge);
    enquire.register('(max-width: 599px)', mediaQuerySmall);
    this.on('destruct', function() {
      enquire.unregister('(min-width: 600px)', mediaQueryLarge);
      enquire.unregister('(max-width: 599px)', mediaQuerySmall);
    });
  },

  _changeFloatboxFullscreen: function(state) {
    this._floatboxFullscreen = state;
    $('.floatbox').toggleClass('fullscreen', state);
  },

  /**
   * @param {String} date
   */
  showPane: function(date) {
    var $element = this.$('.dateList > .dateList-item[data-date="' + date + '"]');
    if (!$element.length) {
      throw new Error('Cannot find date list pane for date `' + date + '`');
    }
    this._carousel.showPane($element.index(), {immediateSetUrl: true}, !Modernizr.touchevents);
    this._onShowPane($element);
  },

  /**
   * @param {String } date
   * @returns {boolean}
   * @private
   */
  _hasPane: function(date) {
    var $element = this.$('.dateList > .dateList-item[data-date="' + date + '"]');
    return $element.length > 0;
  },

  /**
   * @param {jQuery} $element
   * @param {Boolean} [delaySetUrl]
   */
  _onShowPane: function($element, delaySetUrl) {
    var title = $element.data('title');
    var url = $element.data('url');
    var date = $element.data('date');
    var menuEntryHash = $element.data('menu-hash');

    cm.getDocument()._updateTitle(title);
    cm.getDocument()._activateMenuEntries([menuEntryHash]);

    if (delaySetUrl) {
      this._onShowPaneSetUrlDelayed(url, date);
    } else {
      this._onShowPaneSetUrl(url, date);
    }

    this.trigger('swipe', $element);
  },

  /**
   * @param {String} url
   * @param {String} date
   */
  _onShowPaneSetUrl: function(url, date) {
    if (!$.contains(document, this.el)) {
      return; // View has been destroyed in the meantime
    }
    var nextState = {date: date};
    if (!_.isEqual(nextState, this.getState())) {
      cm.router.pushState(url);
      this.setState(nextState);
    }
  },

  /**
   * @param {String} eventId
   * @returns {Denkmal_Component_Event}
   * @private
   */
  _getEventComponent: function(eventId) {
    eventId = '' + eventId;
    var eventComponentList = {};
    _.each(this.getChildren('Denkmal_Component_EventList'), function(eventListCmp) {
      _.each(eventListCmp.getChildren('Denkmal_Component_Event'), function(eventCmp) {
        eventComponentList['' + eventCmp.getEvent().id] = eventCmp;
      });
    });
    var eventComponent = eventComponentList[eventId];
    if (!eventComponent) {
      throw new Error('Cannot find event component for id `' + eventId + '`');
    }
    return eventComponent;
  },

  /**
   * @param {String} eventId
   * @param {String} date
   */
  showEventDetails: function(eventId, date) {
    var eventComponent = this._getEventComponent(eventId);
    eventComponent.popOut({'fullscreen': this._floatboxFullscreen});
  },

  hideEventDetails: function() {
    $('.floatbox').floatbox('close')
  },

  /**
   * @param {Boolean} state
   */
  _changeState: function(state) {
    var date = state['date'];
    if (!date) {
      date = this.$('.dateList > .dateList-item:first').data('date');
      this.setState({date: date});
    }
    if (!this._hasPane(date)) {
      return false;
    }

    this.showPane(date);

    var event = state['event'];
    if (event) {
      this.showEventDetails(event, date);
    } else {
      this.hideEventDetails();
    }
  },

  /**
   * @private
   */
  _showBanner: function() {
    if (null === this.storageGet('bannerVisible')) {
      this.$('.banner').show();
    }
  },

  /**
   * @private
   */
  _hideBanner: function() {
    this.storageSet('bannerVisible', false);
    this.$('.banner').slideUp();
  }
});
<commit_msg>Immediately push history state on swipe<commit_after>/**
 * @class Denkmal_Page_Events
 * @extends Denkmal_Page_Abstract
 */
var Denkmal_Page_Events = Denkmal_Page_Abstract.extend({

  /** @type String */
  _class: 'Denkmal_Page_Events',

  /** @type SwipeCarousel */
  _carousel: null,

  /** @type Boolean */
  _floatboxFullscreen: false,

  _stateParams: ['date', 'event'],

  events: {
    'swipeCarousel-change .swipeCarousel': function(event, data) {
      var pushHistoryState = !data.skipPushHistoryState;
      this._onShowPane($(data.element), pushHistoryState);
    },

    'click .dismissBanner': function() {
      this._hideBanner();
    }
  },

  ready: function() {
    var $carousel = this.$('.swipeCarousel');
    this._carousel = new SwipeCarousel($carousel);
    this._carousel.init();

    var self = this;
    this.on('destruct', function() {
      self._carousel.destroy();
    });

    this._showBanner();

    this._initFloatboxMediaQuery();
  },

  _initFloatboxMediaQuery: function() {
    var self = this;
    var mediaQueryLarge = {
      match: function() {
        self._changeFloatboxFullscreen(false);
      }
    };
    var mediaQuerySmall = {
      match: function() {
        self._changeFloatboxFullscreen(true);
      }
    };
    enquire.register('(min-width: 600px)', mediaQueryLarge);
    enquire.register('(max-width: 599px)', mediaQuerySmall);
    this.on('destruct', function() {
      enquire.unregister('(min-width: 600px)', mediaQueryLarge);
      enquire.unregister('(max-width: 599px)', mediaQuerySmall);
    });
  },

  _changeFloatboxFullscreen: function(state) {
    this._floatboxFullscreen = state;
    $('.floatbox').toggleClass('fullscreen', state);
  },

  /**
   * @param {String} date
   */
  showPane: function(date) {
    var $element = this.$('.dateList > .dateList-item[data-date="' + date + '"]');
    if (!$element.length) {
      throw new Error('Cannot find date list pane for date `' + date + '`');
    }
    this._carousel.showPane($element.index(), {skipPushHistoryState: true}, !Modernizr.touchevents);
    this._onShowPane($element);
  },

  /**
   * @param {String } date
   * @returns {boolean}
   * @private
   */
  _hasPane: function(date) {
    var $element = this.$('.dateList > .dateList-item[data-date="' + date + '"]');
    return $element.length > 0;
  },

  /**
   * @param {jQuery} $element
   * @param {Boolean} [pushHistoryState]
   */
  _onShowPane: function($element, pushHistoryState) {
    var title = $element.data('title');
    var url = $element.data('url');
    var date = $element.data('date');
    var menuEntryHash = $element.data('menu-hash');

    cm.getDocument()._updateTitle(title);
    cm.getDocument()._activateMenuEntries([menuEntryHash]);

    if (pushHistoryState) {
      this._onShowPaneSetUrl(url, date);
    }

    this.trigger('swipe', $element);
  },

  /**
   * @param {String} url
   * @param {String} date
   */
  _onShowPaneSetUrl: function(url, date) {
    var nextState = {date: date};
    if (!_.isEqual(nextState, this.getState())) {
      cm.router.pushState(url);
      this.setState(nextState);
    }
  },

  /**
   * @param {String} eventId
   * @returns {Denkmal_Component_Event}
   * @private
   */
  _getEventComponent: function(eventId) {
    eventId = '' + eventId;
    var eventComponentList = {};
    _.each(this.getChildren('Denkmal_Component_EventList'), function(eventListCmp) {
      _.each(eventListCmp.getChildren('Denkmal_Component_Event'), function(eventCmp) {
        eventComponentList['' + eventCmp.getEvent().id] = eventCmp;
      });
    });
    var eventComponent = eventComponentList[eventId];
    if (!eventComponent) {
      throw new Error('Cannot find event component for id `' + eventId + '`');
    }
    return eventComponent;
  },

  /**
   * @param {String} eventId
   * @param {String} date
   */
  showEventDetails: function(eventId, date) {
    var eventComponent = this._getEventComponent(eventId);
    eventComponent.popOut({'fullscreen': this._floatboxFullscreen});
  },

  hideEventDetails: function() {
    $('.floatbox').floatbox('close')
  },

  /**
   * @param {Boolean} state
   */
  _changeState: function(state) {
    var date = state['date'];
    if (!date) {
      date = this.$('.dateList > .dateList-item:first').data('date');
      this.setState({date: date});
    }
    if (!this._hasPane(date)) {
      return false;
    }

    this.showPane(date);

    var event = state['event'];
    if (event) {
      this.showEventDetails(event, date);
    } else {
      this.hideEventDetails();
    }
  },

  /**
   * @private
   */
  _showBanner: function() {
    if (null === this.storageGet('bannerVisible')) {
      this.$('.banner').show();
    }
  },

  /**
   * @private
   */
  _hideBanner: function() {
    this.storageSet('bannerVisible', false);
    this.$('.banner').slideUp();
  }
});
<|endoftext|>