<commit_before>import {Validation} from 'aurelia-validation';
import {transient, inject} from 'aurelia-dependency-injection';
import {OrmMetadata} from './orm-metadata';

@transient()
@inject(Validation)
export class Entity {

  /**
   * Construct a new entity.
   *
   * @param {Validation} validator
   *
   * @return {Entity}
   */
  constructor(validator) {
    this
      .define('__meta', OrmMetadata.forTarget(this.constructor))
      .define('__cleanValues', {}, true);

    // No validation? No need to set the validator.
    if (!this.hasValidation()) {
      return this;
    }

    // Set the validator.
    return this.define('__validator', validator);
  }

  /**
   * Get the transport for the resource this repository represents.
   *
   * @return {Rest}
   */
  getTransport() {
    return this.getRepository().getTransport();
  }

  /**
   * Get reference to the repository.
   *
   * @return {Repository}
   */
  getRepository() {
    return this.__repository;
  }

  /**
   * @param {Repository} repository
   *
   * @return {Entity}
   */
  setRepository(repository) {
    return this.define('__repository', repository);
  }

  /**
   * Define a non-enumerable property on the entity.
   *
   * @param {string}  property
   * @param {*}       value
   * @param {boolean} [writable]
   *
   * @return {Entity}
   */
  define(property, value, writable) {
    Object.defineProperty(this, property, {
      value: value,
      writable: !!writable,
      enumerable: false
    });

    return this;
  }

  /**
   * Get the metadata for this entity.
   *
   * return {Metadata}
   */
  getMeta() {
    return this.__meta;
  }

  /**
   * Persist the entity's state to the server.
   * Either creates a new record (POST) or updates an existing one (PUT) based on the entity's state,
   *
   * @return {Promise}
   */
  save() {
    if (!this.isNew()) {
      return this.update();
    }

    let response;
    return this.getTransport()
      .create(this.getResource(), this.asObject(false))
      .then((created) => {
        this.id  = created.id;
        response = created;
      })
      .then(() => this.saveCollections())
      .then(() => this.markClean())
      .then(() => response);
  }

  /**
   * Persist the changes made to this entity to the server.
   *
   * @see .save()
   *
   * @return {Promise}
   *
   * @throws {Error}
   */
  update() {
    if (this.isNew()) {
      throw new Error('Required value "id" missing on entity.');
    }

    // We're clean, no need to update.
    if (this.isClean()) {
      // Always save collections (might have new).
      return this.saveCollections()
        .then(() => this.markClean())
        .then(() => null);
    }

    let requestBody = this.asObject(false);
    let response;

    delete requestBody.id;

    return this.getTransport()
      .update(this.getResource(), this.id, requestBody)
      .then((updated) => response = updated)
      .then(() => this.saveCollections())
      .then(() => this.markClean())
      .then(() => response);
  }

  /**
   * Add an entity to a collection (persist).
   *
   * When given entity has data, create the entity and set up the relation.
   *
   * @param {Entity|number} entity     Entity or id
   * @param {string}        [property] The name of the property
   *
   * @return {Promise}
   */
  addCollectionAssociation(entity, property) {
    property = property || getPropertyForAssociation(this, entity);
    let url  = [this.getResource(), this.id, property];

    if (this.isNew()) {
      throw new Error('Cannot add association to entity that does not have an id.');
    }

    if (!(entity instanceof Entity)) {
      url.push(entity);

      return this.getTransport().create(url.join('/'));
    }

    if (entity.isNew()) {
      let associationProperty = getPropertyForAssociation(entity, this);
      let relation            = entity.getMeta().fetch('association', associationProperty);

      if (!relation || relation.type !== 'entity') {
        // Many relation, create and then link.
        return entity.save().then(() => {
          return this.addCollectionAssociation(entity, property);
        });
      }

      // toOne relation, pass in ID to prevent extra request. Something something performance.
      entity[associationProperty] = this.id;

      return entity.save().then(() => {
        return entity;
      });
    }

    // Entity isn't new, just add id to url.
    url.push(entity.id);

    return this.getTransport().create(url.join('/')).then(() => {
      return entity;
    });
  }

  /**
   * Remove an entity from a collection.
   *
   * @param {Entity|number} entity     Entity or id
   * @param {string}        [property] The name of the property
   *
   * @return {Promise}
   */
  removeCollectionAssociation(entity, property) {
    property       = property || getPropertyForAssociation(this, entity);
    let idToRemove = entity;

    if (entity instanceof Entity) {
      if (!entity.id) {
        return Promise.resolve(null);
      }

      idToRemove = entity.id;
    }

    return this.getTransport().destroy([this.getResource(), this.id, property, idToRemove].join('/'));
  }

  /**
   * Persist the collections on the entity.
   *
   * @return {Promise}
   */
  saveCollections() {
    let tasks              = [];
    let currentCollections = getCollectionsCompact(this, true);
    let cleanCollections   = this.__cleanValues.data ? this.__cleanValues.data.collections : null;

    let addTasksForDifferences = (base, candidate, method) => {
      if (base === null) {
        return;
      }

      Object.getOwnPropertyNames(base).forEach(property => {
        base[property].forEach(id => {
          if (candidate === null || !Array.isArray(candidate[property]) || candidate[property].indexOf(id) === -1) {
            tasks.push(method.call(this, id, property));
          }
        });
      });
    };

    // Something to add?
    addTasksForDifferences(currentCollections, cleanCollections, this.addCollectionAssociation);

    // Something to remove?
    addTasksForDifferences(cleanCollections, currentCollections, this.removeCollectionAssociation);

    return Promise.all(tasks).then(results => this);
  }

  /**
   * Mark this entity as clean, in its current state.
   *
   * @return {Entity}
   */
  markClean() {
    let cleanValues    = getFlat(this);
    this.__cleanValues = {
      checksum: JSON.stringify(cleanValues),
      data: cleanValues
    };

    return this;
  }

  /**
   * Return if the entity is clean.
   *
   * @return {boolean}
   */
  isClean() {
    return getFlat(this, true) === this.__cleanValues.checksum;
  }

  /**
   * Return if the entity is dirty.
   *
   * @return {boolean}
   */
  isDirty() {
    return !this.isClean();
  }

  /**
   * Return if the entity is new (ergo, hasn't been persisted to the server).
   *
   * @return {boolean}
   */
  isNew() {
    return typeof this.id === 'undefined';
  }

  /**
   * Get the resource name of this entity's reference (static).
   *
   * @return {string|null}
   */
  static getResource() {
    return OrmMetadata.forTarget(this).fetch('resource');
  }

  /**
   * Get the resource name of this entity instance
   *
   * @return {string|null}
   */
  getResource() {
    return this.__resource || this.getMeta().fetch('resource');
  }

  /**
   * Set this instance's resource.
   *
   * @param {string} resource
   *
   * @return {Entity} Fluent interface
   */
  setResource(resource) {
    return this.define('__resource', resource);
  }

  /**
   * Destroy this entity (DELETE request to the server).
   *
   * @return {Promise}
   */
  destroy() {
    if (!this.id) {
      throw new Error('Required value "id" missing on entity.');
    }

    return this.getTransport().destroy(this.getResource(), this.id);
  }

  /**
   * Get the name of the entity. This is useful for labels in texts.
   *
   * @return {string}
   */
  getName() {
    let metaName = this.getMeta().fetch('name');

    if (metaName) {
      return metaName;
    }

    return this.getResource();
  }

  /**
   * Get the name of the entity (static). This is useful for labels in texts.
   *
   * @return {string}
   */
  static getName() {
    let metaName = OrmMetadata.forTarget(this).fetch('name');

    if (metaName) {
      return metaName;
    }

    return this.getResource();
  }

  /**
   * Set data on this entity.
   *
   * @param {{}} data
   * @return {Entity}
   */
  setData(data) {
    Object.assign(this, data);

    return this;
  }

  /**
   * Enable validation for this entity.
   *
   * @return {Entity}
   *
   * @throws {Error}
   */
  enableValidation() {
    if (!this.hasValidation()) {
      throw new Error('Entity not marked as validated. Did you forget the @validation() decorator?');
    }

    if (this.__validation) {
      return this;
    }

    return this.define('__validation', this.__validator.on(this));
  }

  /**
   * Get the validation instance.
   *
   * @return {Validation}
   */
  getValidation() {
    if (!this.hasValidation()) {
      return null;
    }

    if (!this.__validation) {
      this.enableValidation();
    }

    return this.__validation;
  }

  /**
   * Check if entity has validation enabled.
   *
   * @return {boolean}
   */
  hasValidation() {
    return !!this.getMeta().fetch('validation');
  }

  /**
   * Get the data in this entity as a POJO.
   *
   * @param {boolean} [shallow]
   *
   * @return {{}}
   */
  asObject(shallow) {
    return asObject(this, shallow);
  }

  /**
   * Get the data in this entity as a json string.
   *
   * @param {boolean} [shallow]
   *
   * @return {string}
   */
  asJson(shallow) {
    return asJson(this, shallow);
  }
}

/**
 * Entity representation as pojo.
 *
 * @param {Entity} entity
 * @param {boolean} [shallow]
 *
 * @return {{}}
 */
function asObject(entity, shallow) {
  let pojo     = {};
  let metadata = entity.getMeta();

  Object.keys(entity).forEach(propertyName => {
    let value       = entity[propertyName];
    let association = metadata.fetch('associations', propertyName);

    // No meta data, no value or no association property: simple assignment.
    if (!association || !value) {
      pojo[propertyName] = value;

      return;
    }

    // When shallow, we only assign toOne associations.
    if (shallow) {
      if (association.type === 'collection') {
        return;
      }

      if (value.id) {
        pojo[propertyName] = value.id;
      } else if (value instanceof Entity) {
        pojo[propertyName] = value.asObject();
      } else if (['string', 'number', 'boolean'].indexOf(typeof value) > -1 || value.constructor === Object) {
        pojo[propertyName] = value;
      }

      return;
    }

    // Array, treat children as potential entities.
    if (!Array.isArray(value)) {
      // Single value not an instance of entity? Simple assignment.
      pojo[propertyName] = !(value instanceof Entity) ? value : value.asObject(shallow);

      return;
    }

    let asObjects = [];

    value.forEach(childValue => {
      if (typeof childValue !== 'object') {
        return;
      }

      if (!(childValue instanceof Entity)) {
        asObjects.push(childValue);

        return;
      }

      // If shallow, we don't handle toMany.
      if (!shallow || (typeof childValue === 'object' && !childValue.id)) {
        asObjects.push(childValue.asObject(shallow));
      }
    });

    // We don't send along empty arrays.
    if (asObjects.length > 0) {
      pojo[propertyName] = asObjects;
    }
  });

  return pojo;
}

/**
 * Entity representation as json
 *
 * @param {Entity} entity
 * @param {boolean} [shallow]
 *
 * @return {string}
 */
function asJson(entity, shallow) {
  let json;

  try {
    json = JSON.stringify(asObject(entity, shallow));
  } catch (error) {
    json = '';
  }

  return json;
}

/**
 * Get a compact object of collections (arrays of ids)
 *
 * @param {Entity}  forEntity
 * @param {boolean} [includeNew]
 *
 * @return {{}}
 */
function getCollectionsCompact(forEntity, includeNew) {
  let associations = forEntity.getMeta().fetch('associations');
  let collections  = {};

  Object.getOwnPropertyNames(associations).forEach(index => {
    let association = associations[index];

    if (association.type !== 'collection') {
      return;
    }

    collections[index] = [];

    if (!Array.isArray(forEntity[index])) {
      return;
    }

    forEntity[index].forEach(entity => {
      if (typeof entity === 'number') {
        collections[index].push(entity);

        return;
      }

      if (entity.id) {
        collections[index].push(entity.id);
      } else if (includeNew && entity instanceof Entity) {
        collections[index].push(entity);
      }
    });
  });

  return collections;
}

/**
 * Get a flat, plain representation of the entity and its associations.
 *
 * @param {Entity}  entity
 * @param {boolean} [json]
 *
 * @return {{entity, collections}}
 */
function getFlat(entity, json) {
  let flat = {
    entity: asObject(entity, false),
    collections: getCollectionsCompact(entity)
  };

  if (json) {
    flat = JSON.stringify(flat);
  }

  return flat;
}

/**
 * Get the property of the association on this entity.
 *
 * @param {Entity} forEntity
 * @param {Entity} entity
 *
 * @return {string}
 */
function getPropertyForAssociation(forEntity, entity) {
  let associations = forEntity.getMeta().fetch('associations');

  return Object.keys(associations).filter(key => {
    return associations[key].entity === entity.getResource();
  })[0];
}
<commit_msg>fix(entity): markClean should not affect children fix(entity): isClean/isDirty should take children into account<commit_after>import {Validation} from 'aurelia-validation';
import {transient, inject} from 'aurelia-dependency-injection';
import {OrmMetadata} from './orm-metadata';

@transient()
@inject(Validation)
export class Entity {

  /**
   * Construct a new entity.
   *
   * @param {Validation} validator
   *
   * @return {Entity}
   */
  constructor(validator) {
    this
      .define('__meta', OrmMetadata.forTarget(this.constructor))
      .define('__cleanValues', {}, true);

    // No validation? No need to set the validator.
    if (!this.hasValidation()) {
      return this;
    }

    // Set the validator.
    return this.define('__validator', validator);
  }

  /**
   * Get the transport for the resource this repository represents.
   *
   * @return {Rest}
   */
  getTransport() {
    return this.getRepository().getTransport();
  }

  /**
   * Get reference to the repository.
   *
   * @return {Repository}
   */
  getRepository() {
    return this.__repository;
  }

  /**
   * @param {Repository} repository
   *
   * @return {Entity}
   */
  setRepository(repository) {
    return this.define('__repository', repository);
  }

  /**
   * Define a non-enumerable property on the entity.
   *
   * @param {string}  property
   * @param {*}       value
   * @param {boolean} [writable]
   *
   * @return {Entity}
   */
  define(property, value, writable) {
    Object.defineProperty(this, property, {
      value: value,
      writable: !!writable,
      enumerable: false
    });

    return this;
  }

  /**
   * Get the metadata for this entity.
   *
   * return {Metadata}
   */
  getMeta() {
    return this.__meta;
  }

  /**
   * Persist the entity's state to the server.
   * Either creates a new record (POST) or updates an existing one (PUT) based on the entity's state,
   *
   * @return {Promise}
   */
  save() {
    if (!this.isNew()) {
      return this.update();
    }

    let response;
    return this.getTransport()
      .create(this.getResource(), this.asObject(false))
      .then((created) => {
        this.id  = created.id;
        response = created;
      })
      .then(() => this.saveCollections())
      .then(() => this.markClean())
      .then(() => response);
  }

  /**
   * Persist the changes made to this entity to the server.
   *
   * @see .save()
   *
   * @return {Promise}
   *
   * @throws {Error}
   */
  update() {
    if (this.isNew()) {
      throw new Error('Required value "id" missing on entity.');
    }

    // We're clean, no need to update.
    if (this.isClean()) {
      // Always save collections (might have new).
      return this.saveCollections()
        .then(() => this.markClean())
        .then(() => null);
    }

    let requestBody = this.asObject(false);
    let response;

    delete requestBody.id;

    return this.getTransport()
      .update(this.getResource(), this.id, requestBody)
      .then((updated) => response = updated)
      .then(() => this.saveCollections())
      .then(() => this.markClean())
      .then(() => response);
  }

  /**
   * Add an entity to a collection (persist).
   *
   * When given entity has data, create the entity and set up the relation.
   *
   * @param {Entity|number} entity     Entity or id
   * @param {string}        [property] The name of the property
   *
   * @return {Promise}
   */
  addCollectionAssociation(entity, property) {
    property = property || getPropertyForAssociation(this, entity);
    let url  = [this.getResource(), this.id, property];

    if (this.isNew()) {
      throw new Error('Cannot add association to entity that does not have an id.');
    }

    if (!(entity instanceof Entity)) {
      url.push(entity);

      return this.getTransport().create(url.join('/'));
    }

    if (entity.isNew()) {
      let associationProperty = getPropertyForAssociation(entity, this);
      let relation            = entity.getMeta().fetch('association', associationProperty);

      if (!relation || relation.type !== 'entity') {
        // Many relation, create and then link.
        return entity.save().then(() => {
          return this.addCollectionAssociation(entity, property);
        });
      }

      // toOne relation, pass in ID to prevent extra request. Something something performance.
      entity[associationProperty] = this.id;

      return entity.save().then(() => {
        return entity;
      });
    }

    // Entity isn't new, just add id to url.
    url.push(entity.id);

    return this.getTransport().create(url.join('/')).then(() => {
      return entity;
    });
  }

  /**
   * Remove an entity from a collection.
   *
   * @param {Entity|number} entity     Entity or id
   * @param {string}        [property] The name of the property
   *
   * @return {Promise}
   */
  removeCollectionAssociation(entity, property) {
    property       = property || getPropertyForAssociation(this, entity);
    let idToRemove = entity;

    if (entity instanceof Entity) {
      if (!entity.id) {
        return Promise.resolve(null);
      }

      idToRemove = entity.id;
    }

    return this.getTransport().destroy([this.getResource(), this.id, property, idToRemove].join('/'));
  }

  /**
   * Persist the collections on the entity.
   *
   * @return {Promise}
   */
  saveCollections() {
    let tasks              = [];
    let currentCollections = getCollectionsCompact(this, true);
    let cleanCollections   = this.__cleanValues.data ? this.__cleanValues.data.collections : null;

    let addTasksForDifferences = (base, candidate, method) => {
      if (base === null) {
        return;
      }

      Object.getOwnPropertyNames(base).forEach(property => {
        base[property].forEach(id => {
          if (candidate === null || !Array.isArray(candidate[property]) || candidate[property].indexOf(id) === -1) {
            tasks.push(method.call(this, id, property));
          }
        });
      });
    };

    // Something to add?
    addTasksForDifferences(currentCollections, cleanCollections, this.addCollectionAssociation);

    // Something to remove?
    addTasksForDifferences(cleanCollections, currentCollections, this.removeCollectionAssociation);

    return Promise.all(tasks).then(results => this);
  }

  /**
   * Mark this entity as clean, in its current state.
   *
   * @return {Entity}
   */
  markClean() {
    let cleanValues    = getFlat(this, false, true);
    this.__cleanValues = {
      checksum: JSON.stringify(cleanValues),
      data: cleanValues
    };

    return this;
  }

  /**
   * Return if the entity is clean.
   *
   * @return {boolean}
   */
  isClean() {
    return getFlat(this, true, false) === this.__cleanValues.checksum;
  }

  /**
   * Return if the entity is dirty.
   *
   * @return {boolean}
   */
  isDirty() {
    return !this.isClean();
  }

  /**
   * Return if the entity is new (ergo, hasn't been persisted to the server).
   *
   * @return {boolean}
   */
  isNew() {
    return typeof this.id === 'undefined';
  }

  /**
   * Get the resource name of this entity's reference (static).
   *
   * @return {string|null}
   */
  static getResource() {
    return OrmMetadata.forTarget(this).fetch('resource');
  }

  /**
   * Get the resource name of this entity instance
   *
   * @return {string|null}
   */
  getResource() {
    return this.__resource || this.getMeta().fetch('resource');
  }

  /**
   * Set this instance's resource.
   *
   * @param {string} resource
   *
   * @return {Entity} Fluent interface
   */
  setResource(resource) {
    return this.define('__resource', resource);
  }

  /**
   * Destroy this entity (DELETE request to the server).
   *
   * @return {Promise}
   */
  destroy() {
    if (!this.id) {
      throw new Error('Required value "id" missing on entity.');
    }

    return this.getTransport().destroy(this.getResource(), this.id);
  }

  /**
   * Get the name of the entity. This is useful for labels in texts.
   *
   * @return {string}
   */
  getName() {
    let metaName = this.getMeta().fetch('name');

    if (metaName) {
      return metaName;
    }

    return this.getResource();
  }

  /**
   * Get the name of the entity (static). This is useful for labels in texts.
   *
   * @return {string}
   */
  static getName() {
    let metaName = OrmMetadata.forTarget(this).fetch('name');

    if (metaName) {
      return metaName;
    }

    return this.getResource();
  }

  /**
   * Set data on this entity.
   *
   * @param {{}} data
   * @return {Entity}
   */
  setData(data) {
    Object.assign(this, data);

    return this;
  }

  /**
   * Enable validation for this entity.
   *
   * @return {Entity}
   *
   * @throws {Error}
   */
  enableValidation() {
    if (!this.hasValidation()) {
      throw new Error('Entity not marked as validated. Did you forget the @validation() decorator?');
    }

    if (this.__validation) {
      return this;
    }

    return this.define('__validation', this.__validator.on(this));
  }

  /**
   * Get the validation instance.
   *
   * @return {Validation}
   */
  getValidation() {
    if (!this.hasValidation()) {
      return null;
    }

    if (!this.__validation) {
      this.enableValidation();
    }

    return this.__validation;
  }

  /**
   * Check if entity has validation enabled.
   *
   * @return {boolean}
   */
  hasValidation() {
    return !!this.getMeta().fetch('validation');
  }

  /**
   * Get the data in this entity as a POJO.
   *
   * @param {boolean} [shallow]
   *
   * @return {{}}
   */
  asObject(shallow) {
    return asObject(this, shallow);
  }

  /**
   * Get the data in this entity as a json string.
   *
   * @param {boolean} [shallow]
   *
   * @return {string}
   */
  asJson(shallow) {
    return asJson(this, shallow);
  }
}

/**
 * Entity representation as pojo.
 *
 * @param {Entity} entity
 * @param {boolean} [shallow]
 *
 * @return {{}}
 */
function asObject(entity, shallow) {
  let pojo     = {};
  let metadata = entity.getMeta();

  Object.keys(entity).forEach(propertyName => {
    let value       = entity[propertyName];
    let association = metadata.fetch('associations', propertyName);

    // No meta data, no value or no association property: simple assignment.
    if (!association || !value) {
      pojo[propertyName] = value;

      return;
    }

    // When shallow, we only assign toOne associations.
    if (shallow) {
      if (association.type === 'collection') {
        return;
      }

      if (value.id) {
        pojo[propertyName] = value.id;
      } else if (value instanceof Entity) {
        pojo[propertyName] = value.asObject();
      } else if (['string', 'number', 'boolean'].indexOf(typeof value) > -1 || value.constructor === Object) {
        pojo[propertyName] = value;
      }

      return;
    }

    // Array, treat children as potential entities.
    if (!Array.isArray(value)) {
      // Single value not an instance of entity? Simple assignment.
      pojo[propertyName] = !(value instanceof Entity) ? value : value.asObject(shallow);

      return;
    }

    let asObjects = [];

    value.forEach(childValue => {
      if (typeof childValue !== 'object') {
        return;
      }

      if (!(childValue instanceof Entity)) {
        asObjects.push(childValue);

        return;
      }

      // If shallow, we don't handle toMany.
      if (!shallow || (typeof childValue === 'object' && !childValue.id)) {
        asObjects.push(childValue.asObject(shallow));
      }
    });

    // We don't send along empty arrays.
    if (asObjects.length > 0) {
      pojo[propertyName] = asObjects;
    }
  });

  return pojo;
}

/**
 * Entity representation as json
 *
 * @param {Entity} entity
 * @param {boolean} [shallow]
 *
 * @return {string}
 */
function asJson(entity, shallow) {
  let json;

  try {
    json = JSON.stringify(asObject(entity, shallow));
  } catch (error) {
    json = '';
  }

  return json;
}

/**
 * Get a compact object of collections (arrays of ids)
 *
 * @param {Entity}  forEntity
 * @param {boolean} [includeNew]
 *
 * @return {{}}
 */
function getCollectionsCompact(forEntity, includeNew) {
  let associations = forEntity.getMeta().fetch('associations');
  let collections  = {};

  Object.getOwnPropertyNames(associations).forEach(index => {
    let association = associations[index];

    if (association.type !== 'collection') {
      return;
    }

    collections[index] = [];

    if (!Array.isArray(forEntity[index])) {
      return;
    }

    forEntity[index].forEach(entity => {
      if (typeof entity === 'number') {
        collections[index].push(entity);

        return;
      }

      if (entity.id) {
        collections[index].push(entity.id);
      } else if (includeNew && entity instanceof Entity) {
        collections[index].push(entity);
      }
    });
  });

  return collections;
}

/**
 * Get a flat, plain representation of the entity and its associations.
 *
 * @param {Entity}  entity
 * @param {boolean} [json]
 *
 * @return {{entity, collections}}
 */
function getFlat(entity, json, shallow) {
  let flat = {
    entity: asObject(entity, !!shallow),
    collections: getCollectionsCompact(entity)
  };

  if (json) {
    flat = JSON.stringify(flat);
  }

  return flat;
}

/**
 * Get the property of the association on this entity.
 *
 * @param {Entity} forEntity
 * @param {Entity} entity
 *
 * @return {string}
 */
function getPropertyForAssociation(forEntity, entity) {
  let associations = forEntity.getMeta().fetch('associations');

  return Object.keys(associations).filter(key => {
    return associations[key].entity === entity.getResource();
  })[0];
}
<|endoftext|>


<commit_before>'use strict';
// Author: Oğuzhan TÜRK

/* global Connector */

function toTitleCase(str)
{
    return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
}

Connector.playerSelector = '.jwplayer';

Connector.getArtist = function() {
  return toTitleCase($('#besa_mp3_play_area')[0].getElementsByTagName('table')[0].getElementsByTagName('td')[3].innerHTML.trim());
}

//Connector.artistSelector = '#now_playing .title_artist .a:first';

Connector.getTrack = function() {
  return toTitleCase($('#besa_mp3_play_area')[0].getElementsByTagName('table')[0].getElementsByTagName('td')[5].innerHTML.trim());
}

//Connector.trackSelector = '#now_playing .title_artist .t:first';

//Connector.albumSelector = '#now_playing .album .detail:first';

Connector.playButtonSelector = '#player_play_button';
<commit_msg>Update trtturku.js<commit_after>'use strict';
// Author: Oğuzhan TÜRK

/* global Connector */

function toTitleCase(str)
{
    return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
}

Connector.playerSelector = '.jwplayer';

Connector.getArtist = function() {
  return toTitleCase($('#besa_mp3_play_area')[0].getElementsByTagName('table')[0].getElementsByTagName('td')[3].innerHTML.trim());
}

//Connector.artistSelector = '#now_playing .title_artist .a:first';

Connector.getTrack = function() {
  return toTitleCase($('#besa_mp3_play_area')[0].getElementsByTagName('table')[0].getElementsByTagName('td')[5].innerHTML.trim());
}

//Connector.trackSelector = '#now_playing .title_artist .t:first';

//Connector.albumSelector = '#now_playing .album .detail:first';

Connector.playButtonSelector = '.jwplay';
<|endoftext|>


<commit_before>app.controller("AppointmentsCtrl", function($scope, $state, $ionicPopup, $rootScope, aptListener, barberInfo, appointmentData) {

  // Initailize Appointments
  // Feilds
  $scope.appointments = aptListener.appointments = appointmentData.getApts();
  $scope.barber = barberInfo.getBarber();
  $scope.schedule_info = {};
  $scope.schedule_info.alarm = true;
  $scope.today = new moment();
  $scope.data = {};
  $scope.data.date = moment().format('YYYY-MM-DD');

  $scope.eqTime = function(atime) {
    return atime === $scope.data.date;
  };

  //Functions
  $scope.back = function() {
    $state.go('account');
  };

  $scope.schedule = function(num) {
    // An elaborate, custom popup
    var myPopup = $ionicPopup.show({
      template: '<input type="text" ng-model="data.name" placeholder="Full Name">
 <input type="tel" ng-model="data.phone" placeholder="phone number">',
      title: 'Choose new date and time.',
      scope: $scope,
      buttons: [{
        text: 'Cancel',
        onTap: function(e) {
          return 'cancel button';
        }
      }, {
        text: 'Confirm',
        type: 'button-assertive',
        onTap: function(e) {
          //  alert($scope.newDate.date);
          if (!$scope.data.name || !$scope.data.phone) {
            //don't allow the user to close unless he enters wifi password
            e.preventDefault();
          } else {
            return 'submit';
          }
        }
      }, ]
    });
    myPopup.then(function(res) {
      //  alert($scope.newDate.date);
      console.log(res);
      if (res === 'submit') {
        localAptDB.put({
          _id: moment().format() + '-' + $scope.barber,
          slot_num: num,
          client_name: $scope.data.name,
          client_phone: $scope.data.phone,
          barber: $scope.barber,
          date: $scope.data.date,
          alarm: $scope.data.alarm,
          sms_0: false,
          sms_1: false,
          done: false
        }).then(function(response) {
          console.log('Complete!');
          // Show some pops up fancy stuffs here, also go back to login.
          console.log(response);
          $state.go('appointments');
        }).catch(function(err) {
          console.log(err);
        });
      }
    });
  };

  $scope.delete = function(id) {
    var confirmPopup = $ionicPopup.confirm({
      title: 'Are you sure you want to cancel?',
      //  template: 'Are you sure you want to cancel this appointment?',
      buttons: [{
        text: 'Cancel',
        type: 'button-stable'
      }, {
        text: 'Yes',
        type: 'button-assertive',
        onTap: function(e) {
          return true;
        }
      }, ]
    });
    confirmPopup.then(function(res) {
      console.log(res);
      if (res) {
        console.log('about to delete');
        remoteAptDB.get(id).then(function(doc) {
          return remoteAptDB.remove(doc);
        }).then(function(result) {
          // handle result
          console.log('Deleted Document!');
        }).catch(function(err) {
          console.log(err);
        });
      } else {
        //canceled
        console.log('canceled!');
      }
    });
  };

  var processData = function ProcessData (apt) {
    for (var i = 0; i < apt.rows.length; i++) {
      console.log('Loop Date:');
      console.log(apt.rows[i].doc.date);
      // if (new moment(apt.rows[i].doc.date).format('YYYY-MM-DD') === $scope.data.date.format('YYYY-MM-DD')) {
      appointments[apt.rows[i].doc.slot_num].client_name = apt.rows[i].doc.client_name;
      appointments[apt.rows[i].doc.slot_num].client_phone = apt.rows[i].doc.client_phone;
      appointments[apt.rows[i].doc.slot_num].barber = apt.rows[i].doc.barber;
      appointments[apt.rows[i].doc.slot_num].date = new moment(apt.rows[i].doc.date).format('YYYY-MM-DD');
      appointments[apt.rows[i].doc.slot_num].alarm = apt.rows[i].doc.alarm;
      appointments[apt.rows[i].doc.slot_num].sms_0 = apt.rows[i].doc.sms_0;
      appointments[apt.rows[i].doc.slot_num].sms_1 = apt.rows[i].doc.sms_1;
      appointments[apt.rows[i].doc.slot_num].done = apt.rows[i].doc.done;
      // }
    }
  };

  // Flex Calendar Shit -------------------------------------------------------

  var today = new Date();
  var dd = today.getDate();
  var mm = today.getMonth(); //January is 0!
  var yyyy = today.getFullYear();
  if (dd < 10) {
    dd = '0' + dd;
  }
  if (mm < 10) {
    mm = '0' + mm;
  }

  $scope.options = {
    // defaultDate: new Date(yyyy, mm, dd),
    // minDate: new Date(2015, 06, 12),
    // maxDate: new Date(2015, 12, 31),
    disabledDates: [
      new Date(2015, 07, 24),
      new Date(2015, 07, 25),
      new Date(2015, 07, 26),
    ],
    dayNamesLength: 3, // 1 for "M", 2 for "Mo", 3 for "Mon"; 9 will show full day names. Default is 1.
    mondayIsFirstDay: true, //set monday as first day of week. Default is false
    eventClick: function(date_obj) {
      console.log(date_obj);
      $scope.data.date = moment(date_obj.date).format('YYYY-MM-DD');
      // processData(appointmentData.getDBApts($scope.data.date));
      appointmentData.getDBApts($scope.data.date,ProcessData);

      // console.log($scope.appointments);
    },
    dateClick: function(date_obj) {
      console.log('Date Clicked');
      console.log(date_obj);
      $scope.data.date = new moment(date_obj.date).format('YYYY-MM-DD');
      $scope.appointments = appointments = appointmentData.getApts();
      console.log('Apts Before:');
      console.log(appointments);
      console.log('Returned:');

      console.log(appointmentData.getDBApts($scope.data.date));
      appointmentData.getDBApts($scope.data.date,ProcessData);

      console.log('Apts After:');
      console.log(appointments);
    },
    changeMonth: function(month, year) {
      console.log(month, year);
    },
  };

  $scope.events = [{
    foo: 'bar',
    date: new Date(2015, 7, 10)
  }, {
    foo: 'bar',
    date: new Date(2015, 7, 11)
  }];

});
<commit_msg>starting to re write code<commit_after>app.controller("AppointmentsCtrl", function($scope, $state, $ionicPopup, $rootScope, barberInfo, appointmentData) {

  // Initailize Appointments
  // Feilds
  $scope.data = {};
  $scope.data.date = new moment();
  var apts = process($scope.data.date);
  $scope.appointments = apts;

  // Old Feilds
  $scope.barber = barberInfo.getBarber();

  localAptDB.changes({
    since: 'now',
    live: true,
    include_docs: true
  }).on('change', function(change) {
    // handle change
    if (change === 'deleted') {
      //Handle Deleted Doc
      docChange('delete', change.doc);
    } else {
      // Handle Created Doc
      docChange('create', change.doc);
    }
  }).on('complete', function(info) {
    // changes() was canceled
  }).on('error', function(err) {
    console.log(err);
  });

  $scope.eqTime = function(atime) {
    return atime === $scope.data.date;
  };

  //Functions
  $scope.back = function() {
    $state.go('account');
  };

  $scope.schedule = function(num) {
    // An elaborate, custom popup
    var myPopup = $ionicPopup.show({
      template: '<input type="text" ng-model="data.name" placeholder="Full Name">
 <input type="tel" ng-model="data.phone" placeholder="phone number">',
      title: 'Choose new date and time.',
      scope: $scope,
      buttons: [{
        text: 'Cancel',
        onTap: function(e) {
          return 'cancel button';
        }
      }, {
        text: 'Confirm',
        type: 'button-assertive',
        onTap: function(e) {
          //  alert($scope.newDate.date);
          if (!$scope.data.name || !$scope.data.phone) {
            //don't allow the user to close unless he enters wifi password
            e.preventDefault();
          } else {
            return 'submit';
          }
        }
      }, ]
    });
    myPopup.then(function(res) {
      //  alert($scope.newDate.date);
      console.log(res);
      if (res === 'submit') {
        localAptDB.put({
          _id: moment().format() + '-' + $scope.barber,
          slot_num: num,
          client_name: $scope.data.name,
          client_phone: $scope.data.phone,
          barber: $scope.barber,
          date: $scope.data.date,
          alarm: $scope.data.alarm,
          sms_0: false,
          sms_1: false,
          done: false
        }).then(function(response) {
          console.log('Complete!');
          // Show some pops up fancy stuffs here, also go back to login.
          console.log(response);
          $state.go('appointments');
        }).catch(function(err) {
          console.log(err);
        });
      }
    });
  };

  $scope.delete = function(id) {
    var confirmPopup = $ionicPopup.confirm({
      title: 'Are you sure you want to cancel?',
      //  template: 'Are you sure you want to cancel this appointment?',
      buttons: [{
        text: 'Cancel',
        type: 'button-stable'
      }, {
        text: 'Yes',
        type: 'button-assertive',
        onTap: function(e) {
          return true;
        }
      }, ]
    });
    confirmPopup.then(function(res) {
      console.log(res);
      if (res) {
        console.log('about to delete');
        remoteAptDB.get(id).then(function(doc) {
          return remoteAptDB.remove(doc);
        }).then(function(result) {
          // handle result
          console.log('Deleted Document!');
        }).catch(function(err) {
          console.log(err);
        });
      } else {
        //canceled
        console.log('canceled!');
      }
    });
  };

  // Process results and return completed appointments array***
  function process(date_obj) {
    console.log('in process');
    localAptDB.allDocs({
      include_docs: true,
      attachments: true,
      startkey: date_obj.format('YYYY-MM-DD'),
      endkey: barberInfo.getBarber()
    }).then(function(res) {
      console.log('res: ' + res.rows);
      var appointments = [];
      for (var i = 0; i < 14; i++) {
        if (res.rows[i] !== null) {
          appointments[i] = res.rows[i];
        } else {
          appointments[i] = {
            slot_num: i
          };
        }
      }
      return appointments;
    }).catch(function(err) {
      console.log(err);
      return null;
    });
  }

  // Modify $scope.appointments based on changes to DB ***
  function docChange(type, doc) {
    if (type === 'delete') {
      // Handle Deleting Doc
      for (var i = 0; i < $scope.appointments.length; i++) {
        if ($scope.appointments[i]._id === doc._id) {
          $scope.appointments[i] = {
            slot_num: i
          };
        }
      }
    } else if (type === 'create') {
      // Handle Adding Doc
      for (var x = 0; x < $scope.appointments.length; x++) {
        if ($scope.appointments[x]._id === doc._id) {
          $scope.appointments[x] = doc;
        }
      }
    }
  }

  // Flex Calendar Shit -------------------------------------------------------

  var today = new Date();
  var dd = today.getDate();
  var mm = today.getMonth(); //January is 0!
  var yyyy = today.getFullYear();
  if (dd < 10) {
    dd = '0' + dd;
  }
  if (mm < 10) {
    mm = '0' + mm;
  }

  $scope.options = {
    // defaultDate: new Date(yyyy, mm, dd),
    // minDate: new Date(2015, 06, 12),
    // maxDate: new Date(2015, 12, 31),
    disabledDates: [
      new Date(2015, 07, 24),
      new Date(2015, 07, 25),
      new Date(2015, 07, 26),
    ],
    dayNamesLength: 3, // 1 for "M", 2 for "Mo", 3 for "Mon"; 9 will show full day names. Default is 1.
    mondayIsFirstDay: true, //set monday as first day of week. Default is false
    eventClick: function(date_obj) {
      console.log(date_obj);
      apts = process(new moment(date_obj));
    },
    dateClick: function(date_obj) {
      apts = process(new moment(date_obj));
    },
    changeMonth: function(month, year) {
      console.log(month, year);
    },
  };

  $scope.events = [{
    foo: 'bar',
    date: new Date(2015, 7, 10)
  }, {
    foo: 'bar',
    date: new Date(2015, 7, 11)
  }];

});
<|endoftext|>


<commit_before>define(["dojo", ".."], function(dojo, dijit){

// module:
//		dijit/form/_TextBoxMixin
// summary:
//		A mixin for textbox form input widgets

dojo.declare( "dijit.form._TextBoxMixin", null, {
	// summary:
	//		A mixin for textbox form input widgets

	// trim: Boolean
	//		Removes leading and trailing whitespace if true.  Default is false.
	trim: false,

	// uppercase: Boolean
	//		Converts all characters to uppercase if true.  Default is false.
	uppercase: false,

	// lowercase: Boolean
	//		Converts all characters to lowercase if true.  Default is false.
	lowercase: false,

	// propercase: Boolean
	//		Converts the first character of each word to uppercase if true.
	propercase: false,

	// maxLength: String
	//		HTML INPUT tag maxLength declaration.
	maxLength: "",

	// selectOnClick: [const] Boolean
	//		If true, all text will be selected when focused with mouse
	selectOnClick: false,

	// placeHolder: String
	//		Defines a hint to help users fill out the input field (as defined in HTML 5).
	//		This should only contain plain text (no html markup).
	placeHolder: "",

	_getValueAttr: function(){
		// summary:
		//		Hook so get('value') works as we like.
		// description:
		//		For `dijit.form.TextBox` this basically returns the value of the <input>.
		//
		//		For `dijit.form.MappedTextBox` subclasses, which have both
		//		a "displayed value" and a separate "submit value",
		//		This treats the "displayed value" as the master value, computing the
		//		submit value from it via this.parse().
		return this.parse(this.get('displayedValue'), this.constraints);
	},

	_setValueAttr: function(value, /*Boolean?*/ priorityChange, /*String?*/ formattedValue){
		// summary:
		//		Hook so set('value', ...) works.
		//
		// description:
		//		Sets the value of the widget to "value" which can be of
		//		any type as determined by the widget.
		//
		// value:
		//		The visual element value is also set to a corresponding,
		//		but not necessarily the same, value.
		//
		// formattedValue:
		//		If specified, used to set the visual element value,
		//		otherwise a computed visual value is used.
		//
		// priorityChange:
		//		If true, an onChange event is fired immediately instead of
		//		waiting for the next blur event.

		var filteredValue;
		if(value !== undefined){
			// TODO: this is calling filter() on both the display value and the actual value.
			// I added a comment to the filter() definition about this, but it should be changed.
			filteredValue = this.filter(value);
			if(typeof formattedValue != "string"){
				if(filteredValue !== null && ((typeof filteredValue != "number") || !isNaN(filteredValue))){
					formattedValue = this.filter(this.format(filteredValue, this.constraints));
				}else{ formattedValue = ''; }
			}
		}
		if(formattedValue != null && formattedValue != undefined && ((typeof formattedValue) != "number" || !isNaN(formattedValue)) && this.textbox.value != formattedValue){
			this.textbox.value = formattedValue;
			this._set("displayedValue", this.get("displayedValue"));
		}

		if(this.textDir == "auto"){
			this.applyTextDir(this.focusNode, formattedValue);
		}

		this.inherited(arguments, [filteredValue, priorityChange]);
	},

	// displayedValue: String
	//		For subclasses like ComboBox where the displayed value
	//		(ex: Kentucky) and the serialized value (ex: KY) are different,
	//		this represents the displayed value.
	//
	//		Setting 'displayedValue' through set('displayedValue', ...)
	//		updates 'value', and vice-versa.  Otherwise 'value' is updated
	//		from 'displayedValue' periodically, like onBlur etc.
	//
	//		TODO: move declaration to MappedTextBox?
	//		Problem is that ComboBox references displayedValue,
	//		for benefit of FilteringSelect.
	displayedValue: "",

	_getDisplayedValueAttr: function(){
		// summary:
		//		Hook so get('displayedValue') works.
		// description:
		//		Returns the displayed value (what the user sees on the screen),
		// 		after filtering (ie, trimming spaces etc.).
		//
		//		For some subclasses of TextBox (like ComboBox), the displayed value
		//		is different from the serialized value that's actually
		//		sent to the server (see dijit.form.ValidationTextBox.serialize)

		// TODO: maybe we should update this.displayedValue on every keystroke so that we don't need
		// this method
		// TODO: this isn't really the displayed value when the user is typing
		return this.filter(this.textbox.value);
	},

	_setDisplayedValueAttr: function(/*String*/ value){
		// summary:
		//		Hook so set('displayedValue', ...) works.
		// description:
		//		Sets the value of the visual element to the string "value".
		//		The widget value is also set to a corresponding,
		//		but not necessarily the same, value.

		if(value === null || value === undefined){ value = '' }
		else if(typeof value != "string"){ value = String(value) }

		this.textbox.value = value;

		// sets the serialized value to something corresponding to specified displayedValue
		// (if possible), and also updates the textbox.value, for example converting "123"
		// to "123.00"
		this._setValueAttr(this.get('value'), undefined);

		this._set("displayedValue", this.get('displayedValue'));

		// textDir support
		if(this.textDir == "auto"){
			this.applyTextDir(this.focusNode, value);
		}
	},

	format: function(/*String*/ value, /*Object*/ constraints){
		// summary:
		//		Replacable function to convert a value to a properly formatted string.
		// tags:
		//		protected extension
		return ((value == null || value == undefined) ? "" : (value.toString ? value.toString() : value));
	},

	parse: function(/*String*/ value, /*Object*/ constraints){
		// summary:
		//		Replacable function to convert a formatted string to a value
		// tags:
		//		protected extension

		return value;	// String
	},

	_refreshState: function(){
		// summary:
		//		After the user types some characters, etc., this method is
		//		called to check the field for validity etc.  The base method
		//		in `dijit.form.TextBox` does nothing, but subclasses override.
		// tags:
		//		protected
	},

	/*=====
	onInput: function(event){
		// summary:
		//		Connect to this function to receive notifications of various user data-input events.
		//		Return false to cancel the event and prevent it from being processed.
		// event:
		//		keydown | keypress | cut | paste | input
		// tags:
		//		callback
	},
	=====*/
	onInput: function(){},

	__skipInputEvent: false,
	_onInput: function(e){
		// summary:
		//		Called AFTER the input event has happened
		// set text direction according to textDir that was defined in creation
		if(this.textDir == "auto"){
			this.applyTextDir(this.focusNode, this.focusNode.value);
		}

		this._refreshState();

		// In case someone is watch()'ing for changes to displayedValue
		this._set("displayedValue", this.get("displayedValue"));
	},

	postCreate: function(){
		// setting the value here is needed since value="" in the template causes "undefined"
		// and setting in the DOM (instead of the JS object) helps with form reset actions
		this.textbox.setAttribute("value", this.textbox.value); // DOM and JS values should be the same

		this.inherited(arguments);

		// normalize input events to reduce spurious event processing
		//	onkeydown: do not forward modifier keys
		//	           set charOrCode to numeric keycode
		//	onkeypress: do not forward numeric charOrCode keys (already sent thru onkeydown)
		//	onpaste & oncut: set charOrCode to 229 (IME)
		//	oninput: if primary event not already processed, set charOrCode to 229 (IME), else do not forward
		var handleEvent = function(e){
			var charCode = e.charOrCode || e.keyCode || 229;
			if(e.type == "keydown"){
				switch(charCode){ // ignore "state" keys
					case dojo.keys.SHIFT:
					case dojo.keys.ALT:
					case dojo.keys.CTRL:
					case dojo.keys.META:
					case dojo.keys.CAPSLOCK:
						return;
					default:
						if(charCode >= 65 && charCode <= 90){ return; } // keydown for A-Z can be processed with keypress
				}
			}
			if(e.type == "keypress" && typeof charCode != "string"){ return; }
			if(e.type == "input"){
				if(this.__skipInputEvent){ // duplicate event
					this.__skipInputEvent = false;
					return;
				}
			}else{
				this.__skipInputEvent = true;
			}
			// create fake event to set charOrCode and to know if preventDefault() was called
			var faux = dojo.mixin({}, e, {
				charOrCode: charCode,
				wasConsumed: false,
				preventDefault: function(){
					faux.wasConsumed = true;
					e.preventDefault();
				},
				stopPropagation: function(){ e.stopPropagation(); }
			});
			// give web page author a chance to consume the event
			if(this.onInput(faux) === false){
				dojo.stopEvent(faux); // return false means stop
			}
			if(faux.wasConsumed){ return; } // if preventDefault was called
			setTimeout(dojo.hitch(this, "_onInput", faux), 0); // widget notification after key has posted
		};
		dojo.forEach([ "onkeydown", "onkeypress", "onpaste", "oncut", "oninput" ], function(event){
			this.connect(this.textbox, event, handleEvent);
		}, this);
	},

	_blankValue: '', // if the textbox is blank, what value should be reported
	filter: function(val){
		// summary:
		//		Auto-corrections (such as trimming) that are applied to textbox
		//		value on blur or form submit.
		// description:
		//		For MappedTextBox subclasses, this is called twice
		// 			- once with the display value
		//			- once the value as set/returned by set('value', ...)
		//		and get('value'), ex: a Number for NumberTextBox.
		//
		//		In the latter case it does corrections like converting null to NaN.  In
		//		the former case the NumberTextBox.filter() method calls this.inherited()
		//		to execute standard trimming code in TextBox.filter().
		//
		//		TODO: break this into two methods in 2.0
		//
		// tags:
		//		protected extension
		if(val === null){ return this._blankValue; }
		if(typeof val != "string"){ return val; }
		if(this.trim){
			val = dojo.trim(val);
		}
		if(this.uppercase){
			val = val.toUpperCase();
		}
		if(this.lowercase){
			val = val.toLowerCase();
		}
		if(this.propercase){
			val = val.replace(/[^\s]+/g, function(word){
				return word.substring(0,1).toUpperCase() + word.substring(1);
			});
		}
		return val;
	},

	_setBlurValue: function(){
		this._setValueAttr(this.get('value'), true);
	},

	_onBlur: function(e){
		if(this.disabled){ return; }
		this._setBlurValue();
		this.inherited(arguments);

		if(this._selectOnClickHandle){
			this.disconnect(this._selectOnClickHandle);
		}
	},

	_isTextSelected: function(){
		return this.textbox.selectionStart == this.textbox.selectionEnd;
	},

	_onFocus: function(/*String*/ by){
		if(this.disabled || this.readOnly){ return; }

		// Select all text on focus via click if nothing already selected.
		// Since mouse-up will clear the selection need to defer selection until after mouse-up.
		// Don't do anything on focus by tabbing into the widget since there's no associated mouse-up event.
		if(this.selectOnClick && by == "mouse"){
			this._selectOnClickHandle = this.connect(this.domNode, "onmouseup", function(){
				// Only select all text on first click; otherwise users would have no way to clear
				// the selection.
				this.disconnect(this._selectOnClickHandle);

				// Check if the user selected some text manually (mouse-down, mouse-move, mouse-up)
				// and if not, then select all the text
				if(this._isTextSelected()){
					dijit.selectInputText(this.textbox);
				}
			});
		}
		// call this.inherited() before refreshState(), since this.inherited() will possibly scroll the viewport
		// (to scroll the TextBox into view), which will affect how _refreshState() positions the tooltip
		this.inherited(arguments);

		this._refreshState();
	},

	reset: function(){
		// Overrides dijit._FormWidget.reset().
		// Additionally resets the displayed textbox value to ''
		this.textbox.value = '';
		this.inherited(arguments);
	},
	_setTextDirAttr: function(/*String*/ textDir){
		// summary:
		//		Setter for textDir.
		// description:
		//		Users shouldn't call this function; they should be calling
		//		set('textDir', value)
		// tags:
		//		private

		// only if new textDir is different from the old one
		// and on widgets creation.
		if(!this._created
			|| this.textDir != textDir){
				this._set("textDir", textDir);
				// so the change of the textDir will take place immediately.
				this.applyTextDir(this.focusNode, this.focusNode.value);
		}
	}
});

dijit._setSelectionRange = function(/*DomNode*/ element, /*Number?*/ start, /*Number?*/ stop){
	if(element.setSelectionRange){
		element.setSelectionRange(start, stop);
	}
};

dijit.selectInputText = function(/*DomNode*/ element, /*Number?*/ start, /*Number?*/ stop){
	// summary:
	//		Select text in the input element argument, from start (default 0), to stop (default end).

	// TODO: use functions in _editor/selection.js?
	var _window = dojo.global;
	var _document = dojo.doc;
	element = dojo.byId(element);
	if(isNaN(start)){ start = 0; }
	if(isNaN(stop)){ stop = element.value ? element.value.length : 0; }
	try{
		element.focus();
		dijit._setSelectionRange(element, start, stop);
	}catch(e){ /* squelch random errors (esp. on IE) from unexpected focus changes or DOM nodes being hidden */ }
};

return dijit.form._TextBoxMixin;
});
<commit_msg>Fixes #13002.  CAPS_LOCK typo.<commit_after>define(["dojo", ".."], function(dojo, dijit){

// module:
//		dijit/form/_TextBoxMixin
// summary:
//		A mixin for textbox form input widgets

dojo.declare( "dijit.form._TextBoxMixin", null, {
	// summary:
	//		A mixin for textbox form input widgets

	// trim: Boolean
	//		Removes leading and trailing whitespace if true.  Default is false.
	trim: false,

	// uppercase: Boolean
	//		Converts all characters to uppercase if true.  Default is false.
	uppercase: false,

	// lowercase: Boolean
	//		Converts all characters to lowercase if true.  Default is false.
	lowercase: false,

	// propercase: Boolean
	//		Converts the first character of each word to uppercase if true.
	propercase: false,

	// maxLength: String
	//		HTML INPUT tag maxLength declaration.
	maxLength: "",

	// selectOnClick: [const] Boolean
	//		If true, all text will be selected when focused with mouse
	selectOnClick: false,

	// placeHolder: String
	//		Defines a hint to help users fill out the input field (as defined in HTML 5).
	//		This should only contain plain text (no html markup).
	placeHolder: "",

	_getValueAttr: function(){
		// summary:
		//		Hook so get('value') works as we like.
		// description:
		//		For `dijit.form.TextBox` this basically returns the value of the <input>.
		//
		//		For `dijit.form.MappedTextBox` subclasses, which have both
		//		a "displayed value" and a separate "submit value",
		//		This treats the "displayed value" as the master value, computing the
		//		submit value from it via this.parse().
		return this.parse(this.get('displayedValue'), this.constraints);
	},

	_setValueAttr: function(value, /*Boolean?*/ priorityChange, /*String?*/ formattedValue){
		// summary:
		//		Hook so set('value', ...) works.
		//
		// description:
		//		Sets the value of the widget to "value" which can be of
		//		any type as determined by the widget.
		//
		// value:
		//		The visual element value is also set to a corresponding,
		//		but not necessarily the same, value.
		//
		// formattedValue:
		//		If specified, used to set the visual element value,
		//		otherwise a computed visual value is used.
		//
		// priorityChange:
		//		If true, an onChange event is fired immediately instead of
		//		waiting for the next blur event.

		var filteredValue;
		if(value !== undefined){
			// TODO: this is calling filter() on both the display value and the actual value.
			// I added a comment to the filter() definition about this, but it should be changed.
			filteredValue = this.filter(value);
			if(typeof formattedValue != "string"){
				if(filteredValue !== null && ((typeof filteredValue != "number") || !isNaN(filteredValue))){
					formattedValue = this.filter(this.format(filteredValue, this.constraints));
				}else{ formattedValue = ''; }
			}
		}
		if(formattedValue != null && formattedValue != undefined && ((typeof formattedValue) != "number" || !isNaN(formattedValue)) && this.textbox.value != formattedValue){
			this.textbox.value = formattedValue;
			this._set("displayedValue", this.get("displayedValue"));
		}

		if(this.textDir == "auto"){
			this.applyTextDir(this.focusNode, formattedValue);
		}

		this.inherited(arguments, [filteredValue, priorityChange]);
	},

	// displayedValue: String
	//		For subclasses like ComboBox where the displayed value
	//		(ex: Kentucky) and the serialized value (ex: KY) are different,
	//		this represents the displayed value.
	//
	//		Setting 'displayedValue' through set('displayedValue', ...)
	//		updates 'value', and vice-versa.  Otherwise 'value' is updated
	//		from 'displayedValue' periodically, like onBlur etc.
	//
	//		TODO: move declaration to MappedTextBox?
	//		Problem is that ComboBox references displayedValue,
	//		for benefit of FilteringSelect.
	displayedValue: "",

	_getDisplayedValueAttr: function(){
		// summary:
		//		Hook so get('displayedValue') works.
		// description:
		//		Returns the displayed value (what the user sees on the screen),
		// 		after filtering (ie, trimming spaces etc.).
		//
		//		For some subclasses of TextBox (like ComboBox), the displayed value
		//		is different from the serialized value that's actually
		//		sent to the server (see dijit.form.ValidationTextBox.serialize)

		// TODO: maybe we should update this.displayedValue on every keystroke so that we don't need
		// this method
		// TODO: this isn't really the displayed value when the user is typing
		return this.filter(this.textbox.value);
	},

	_setDisplayedValueAttr: function(/*String*/ value){
		// summary:
		//		Hook so set('displayedValue', ...) works.
		// description:
		//		Sets the value of the visual element to the string "value".
		//		The widget value is also set to a corresponding,
		//		but not necessarily the same, value.

		if(value === null || value === undefined){ value = '' }
		else if(typeof value != "string"){ value = String(value) }

		this.textbox.value = value;

		// sets the serialized value to something corresponding to specified displayedValue
		// (if possible), and also updates the textbox.value, for example converting "123"
		// to "123.00"
		this._setValueAttr(this.get('value'), undefined);

		this._set("displayedValue", this.get('displayedValue'));

		// textDir support
		if(this.textDir == "auto"){
			this.applyTextDir(this.focusNode, value);
		}
	},

	format: function(/*String*/ value, /*Object*/ constraints){
		// summary:
		//		Replacable function to convert a value to a properly formatted string.
		// tags:
		//		protected extension
		return ((value == null || value == undefined) ? "" : (value.toString ? value.toString() : value));
	},

	parse: function(/*String*/ value, /*Object*/ constraints){
		// summary:
		//		Replacable function to convert a formatted string to a value
		// tags:
		//		protected extension

		return value;	// String
	},

	_refreshState: function(){
		// summary:
		//		After the user types some characters, etc., this method is
		//		called to check the field for validity etc.  The base method
		//		in `dijit.form.TextBox` does nothing, but subclasses override.
		// tags:
		//		protected
	},

	/*=====
	onInput: function(event){
		// summary:
		//		Connect to this function to receive notifications of various user data-input events.
		//		Return false to cancel the event and prevent it from being processed.
		// event:
		//		keydown | keypress | cut | paste | input
		// tags:
		//		callback
	},
	=====*/
	onInput: function(){},

	__skipInputEvent: false,
	_onInput: function(e){
		// summary:
		//		Called AFTER the input event has happened
		// set text direction according to textDir that was defined in creation
		if(this.textDir == "auto"){
			this.applyTextDir(this.focusNode, this.focusNode.value);
		}

		this._refreshState();

		// In case someone is watch()'ing for changes to displayedValue
		this._set("displayedValue", this.get("displayedValue"));
	},

	postCreate: function(){
		// setting the value here is needed since value="" in the template causes "undefined"
		// and setting in the DOM (instead of the JS object) helps with form reset actions
		this.textbox.setAttribute("value", this.textbox.value); // DOM and JS values should be the same

		this.inherited(arguments);

		// normalize input events to reduce spurious event processing
		//	onkeydown: do not forward modifier keys
		//	           set charOrCode to numeric keycode
		//	onkeypress: do not forward numeric charOrCode keys (already sent thru onkeydown)
		//	onpaste & oncut: set charOrCode to 229 (IME)
		//	oninput: if primary event not already processed, set charOrCode to 229 (IME), else do not forward
		var handleEvent = function(e){
			var charCode = e.charOrCode || e.keyCode || 229;
			if(e.type == "keydown"){
				switch(charCode){ // ignore "state" keys
					case dojo.keys.SHIFT:
					case dojo.keys.ALT:
					case dojo.keys.CTRL:
					case dojo.keys.META:
					case dojo.keys.CAPS_LOCK:
						return;
					default:
						if(charCode >= 65 && charCode <= 90){ return; } // keydown for A-Z can be processed with keypress
				}
			}
			if(e.type == "keypress" && typeof charCode != "string"){ return; }
			if(e.type == "input"){
				if(this.__skipInputEvent){ // duplicate event
					this.__skipInputEvent = false;
					return;
				}
			}else{
				this.__skipInputEvent = true;
			}
			// create fake event to set charOrCode and to know if preventDefault() was called
			var faux = dojo.mixin({}, e, {
				charOrCode: charCode,
				wasConsumed: false,
				preventDefault: function(){
					faux.wasConsumed = true;
					e.preventDefault();
				},
				stopPropagation: function(){ e.stopPropagation(); }
			});
			// give web page author a chance to consume the event
			if(this.onInput(faux) === false){
				dojo.stopEvent(faux); // return false means stop
			}
			if(faux.wasConsumed){ return; } // if preventDefault was called
			setTimeout(dojo.hitch(this, "_onInput", faux), 0); // widget notification after key has posted
		};
		dojo.forEach([ "onkeydown", "onkeypress", "onpaste", "oncut", "oninput" ], function(event){
			this.connect(this.textbox, event, handleEvent);
		}, this);
	},

	_blankValue: '', // if the textbox is blank, what value should be reported
	filter: function(val){
		// summary:
		//		Auto-corrections (such as trimming) that are applied to textbox
		//		value on blur or form submit.
		// description:
		//		For MappedTextBox subclasses, this is called twice
		// 			- once with the display value
		//			- once the value as set/returned by set('value', ...)
		//		and get('value'), ex: a Number for NumberTextBox.
		//
		//		In the latter case it does corrections like converting null to NaN.  In
		//		the former case the NumberTextBox.filter() method calls this.inherited()
		//		to execute standard trimming code in TextBox.filter().
		//
		//		TODO: break this into two methods in 2.0
		//
		// tags:
		//		protected extension
		if(val === null){ return this._blankValue; }
		if(typeof val != "string"){ return val; }
		if(this.trim){
			val = dojo.trim(val);
		}
		if(this.uppercase){
			val = val.toUpperCase();
		}
		if(this.lowercase){
			val = val.toLowerCase();
		}
		if(this.propercase){
			val = val.replace(/[^\s]+/g, function(word){
				return word.substring(0,1).toUpperCase() + word.substring(1);
			});
		}
		return val;
	},

	_setBlurValue: function(){
		this._setValueAttr(this.get('value'), true);
	},

	_onBlur: function(e){
		if(this.disabled){ return; }
		this._setBlurValue();
		this.inherited(arguments);

		if(this._selectOnClickHandle){
			this.disconnect(this._selectOnClickHandle);
		}
	},

	_isTextSelected: function(){
		return this.textbox.selectionStart == this.textbox.selectionEnd;
	},

	_onFocus: function(/*String*/ by){
		if(this.disabled || this.readOnly){ return; }

		// Select all text on focus via click if nothing already selected.
		// Since mouse-up will clear the selection need to defer selection until after mouse-up.
		// Don't do anything on focus by tabbing into the widget since there's no associated mouse-up event.
		if(this.selectOnClick && by == "mouse"){
			this._selectOnClickHandle = this.connect(this.domNode, "onmouseup", function(){
				// Only select all text on first click; otherwise users would have no way to clear
				// the selection.
				this.disconnect(this._selectOnClickHandle);

				// Check if the user selected some text manually (mouse-down, mouse-move, mouse-up)
				// and if not, then select all the text
				if(this._isTextSelected()){
					dijit.selectInputText(this.textbox);
				}
			});
		}
		// call this.inherited() before refreshState(), since this.inherited() will possibly scroll the viewport
		// (to scroll the TextBox into view), which will affect how _refreshState() positions the tooltip
		this.inherited(arguments);

		this._refreshState();
	},

	reset: function(){
		// Overrides dijit._FormWidget.reset().
		// Additionally resets the displayed textbox value to ''
		this.textbox.value = '';
		this.inherited(arguments);
	},
	_setTextDirAttr: function(/*String*/ textDir){
		// summary:
		//		Setter for textDir.
		// description:
		//		Users shouldn't call this function; they should be calling
		//		set('textDir', value)
		// tags:
		//		private

		// only if new textDir is different from the old one
		// and on widgets creation.
		if(!this._created
			|| this.textDir != textDir){
				this._set("textDir", textDir);
				// so the change of the textDir will take place immediately.
				this.applyTextDir(this.focusNode, this.focusNode.value);
		}
	}
});

dijit._setSelectionRange = function(/*DomNode*/ element, /*Number?*/ start, /*Number?*/ stop){
	if(element.setSelectionRange){
		element.setSelectionRange(start, stop);
	}
};

dijit.selectInputText = function(/*DomNode*/ element, /*Number?*/ start, /*Number?*/ stop){
	// summary:
	//		Select text in the input element argument, from start (default 0), to stop (default end).

	// TODO: use functions in _editor/selection.js?
	var _window = dojo.global;
	var _document = dojo.doc;
	element = dojo.byId(element);
	if(isNaN(start)){ start = 0; }
	if(isNaN(stop)){ stop = element.value ? element.value.length : 0; }
	try{
		element.focus();
		dijit._setSelectionRange(element, start, stop);
	}catch(e){ /* squelch random errors (esp. on IE) from unexpected focus changes or DOM nodes being hidden */ }
};

return dijit.form._TextBoxMixin;
});
<|endoftext|>


<commit_before>(function (window, $, undefined) {
  'use strict';
  var TankControl;
  TankControl = function Example(cockpit) {
    console.log('Loading TankControl plugin in the browser.');
    // Instance variables
    this.cockpit = cockpit;
    this.tankControlActive = false;
    this.originalsettings = {};
    this.leftx = 0;
    this.lefty = 0;
    this.rightx = 0;
    this.righty = 0;
    this.lift = 0;

    //inputControl names
    this.controlNames = {
      leftLift: "tankControl.leftLift",
      portThrottle: "tankControl.portThrottle",
      rightLift: "tankControl.rightLift",
      starboardThrottle: "tankControl.starboardThrottle"
    };

    // Add required UI elements
    $('#keyboardInstructions').append('
t to toggle tank control
');
  };
  TankControl.prototype.listen = function listen() {
    var rov = this;

    // Toggle tank control
    rov.cockpit.emit('inputController.register',
      {
        name: "tankcontrol.toggleTankControl",
        description: "Toggles the tank control mode on/off",
        defaults: { keyboard: 't' },
        down: function() { rov.toggleControl(); }
      });

    // regirster controls
    rov.cockpit.emit('inputController.register',
      [
        {
          name: rov.controlNames.leftLift,
          description: "Tankcontrol: Lift control control for the left hand gamepad.",
          defaults: { gamepad: 'LEFT_STICK_X' },
          active: false,
          axis: function (v) {
            var direction;
            rov.leftx = -v;
            if (rov.leftx + rov.rightx >= 0) {
              direction = 1;
            } else {
              direction = -1;
            }
            rov.lift = direction * Math.max(Math.abs(rov.leftx), Math.abs(rov.rightx));
            rov.cockpit.emit('rovpilot.manualMotorThrottle', rov.lefty, rov.lift, rov.righty);
          }
        },
        {
          name: rov.controlNames.portThrottle,
          description: "Tankcontrol: Throttle control for the port prop.",
          defaults: { gamepad: 'LEFT_STICK_Y' },
          active: false,
          axis: function (v) {
            rov.lefty = v;
            rov.cockpit.emit('rovpilot.manualMotorThrottle', rov.lefty, rov.lift, rov.righty);
          }
        },
        {
          name: rov.controlNames.rightLift,
          description: "Tankcontrol: Lift control control for the right hand gamepad.",
          defaults: { gamepad: 'RIGHT_STICK_X' },
          active: false,
          axis: function (v) {
            var direction;
            rov.rightx = v;
            if (rov.leftx + rov.rightx >= 0) {
              direction = 1;
            } else {
              direction = -1;
            }
            rov.lift = direction * Math.max(Math.abs(rov.leftx), Math.abs(rov.rightx));
            rov.cockpit.emit('rovpilot.manualMotorThrottle', rov.lefty, rov.lift, rov.righty);
            console.log('rov.lift:' + rov.lift);
          }
        },
        {
          name: rov.controlNames.starboardThrottle,
          description: "Tankcontrol: Throttle control for the starboard prop.",
          defaults: { gamepad: 'RIGHT_STICK_Y' },
          active: false,
          axis: function (v) {
            rov.righty = v;
            rov.cockpit.emit('rovpilot.manualMotorThrottle', rov.lefty, rov.lift, rov.righty);
          }
        }
      ]);
  };
  TankControl.prototype.toggleControl = function toggleControl() {
    var rov = this;
    var controls = [];
    for (var name in rov.controlNames) {
      controls.push('tankControl.' + name);
    }

    if (!this.tankControlActive) {
      rov.cockpit.emit('inputController.activate', controls);
      rov.tankControlActive = true;
      console.log('Tank Control Active');
    }
    else {
      rov.cockpit.emit('inputController.deactivate', controls);
      rov.tankControlActive = false;
      console.log('Tank Control Deactivated');
    }
  };
  window.Cockpit.plugins.push(TankControl);
}(window, jQuery));<commit_msg>deactivated console.log('rov.lift:' + rov.lift)<commit_after>(function (window, $, undefined) {
  'use strict';
  var TankControl;
  TankControl = function Example(cockpit) {
    console.log('Loading TankControl plugin in the browser.');
    // Instance variables
    this.cockpit = cockpit;
    this.tankControlActive = false;
    this.originalsettings = {};
    this.leftx = 0;
    this.lefty = 0;
    this.rightx = 0;
    this.righty = 0;
    this.lift = 0;

    //inputControl names
    this.controlNames = {
      leftLift: "tankControl.leftLift",
      portThrottle: "tankControl.portThrottle",
      rightLift: "tankControl.rightLift",
      starboardThrottle: "tankControl.starboardThrottle"
    };

    // Add required UI elements
    $('#keyboardInstructions').append('
t to toggle tank control
');
  };
  TankControl.prototype.listen = function listen() {
    var rov = this;

    // Toggle tank control
    rov.cockpit.emit('inputController.register',
      {
        name: "tankcontrol.toggleTankControl",
        description: "Toggles the tank control mode on/off",
        defaults: { keyboard: 't' },
        down: function() { rov.toggleControl(); }
      });

    // regirster controls
    rov.cockpit.emit('inputController.register',
      [
        {
          name: rov.controlNames.leftLift,
          description: "Tankcontrol: Lift control control for the left hand gamepad.",
          defaults: { gamepad: 'LEFT_STICK_X' },
          active: false,
          axis: function (v) {
            var direction;
            rov.leftx = -v;
            if (rov.leftx + rov.rightx >= 0) {
              direction = 1;
            } else {
              direction = -1;
            }
            rov.lift = direction * Math.max(Math.abs(rov.leftx), Math.abs(rov.rightx));
            rov.cockpit.emit('rovpilot.manualMotorThrottle', rov.lefty, rov.lift, rov.righty);
          }
        },
        {
          name: rov.controlNames.portThrottle,
          description: "Tankcontrol: Throttle control for the port prop.",
          defaults: { gamepad: 'LEFT_STICK_Y' },
          active: false,
          axis: function (v) {
            rov.lefty = v;
            rov.cockpit.emit('rovpilot.manualMotorThrottle', rov.lefty, rov.lift, rov.righty);
          }
        },
        {
          name: rov.controlNames.rightLift,
          description: "Tankcontrol: Lift control control for the right hand gamepad.",
          defaults: { gamepad: 'RIGHT_STICK_X' },
          active: false,
          axis: function (v) {
            var direction;
            rov.rightx = v;
            if (rov.leftx + rov.rightx >= 0) {
              direction = 1;
            } else {
              direction = -1;
            }
            rov.lift = direction * Math.max(Math.abs(rov.leftx), Math.abs(rov.rightx));
            rov.cockpit.emit('rovpilot.manualMotorThrottle', rov.lefty, rov.lift, rov.righty);
            //console.log('rov.lift:' + rov.lift);
          }
        },
        {
          name: rov.controlNames.starboardThrottle,
          description: "Tankcontrol: Throttle control for the starboard prop.",
          defaults: { gamepad: 'RIGHT_STICK_Y' },
          active: false,
          axis: function (v) {
            rov.righty = v;
            rov.cockpit.emit('rovpilot.manualMotorThrottle', rov.lefty, rov.lift, rov.righty);
          }
        }
      ]);
  };
  TankControl.prototype.toggleControl = function toggleControl() {
    var rov = this;
    var controls = [];
    for (var name in rov.controlNames) {
      controls.push('tankControl.' + name);
    }

    if (!this.tankControlActive) {
      rov.cockpit.emit('inputController.activate', controls);
      rov.tankControlActive = true;
      console.log('Tank Control Active');
    }
    else {
      rov.cockpit.emit('inputController.deactivate', controls);
      rov.tankControlActive = false;
      console.log('Tank Control Deactivated');
    }
  };
  window.Cockpit.plugins.push(TankControl);
}(window, jQuery));
<|endoftext|>


<commit_before>import Em from 'ember';
import InFormMixin from 'ember-idx-forms/mixins/in_form';
import HasPropertyMixin from 'ember-idx-forms/mixins/has_property';
import HasPropertyValidationMixin from 'ember-idx-forms/mixins/has_property_validation';

/*
Form Group

Wraps labels, controls and help message for optimum spacing and validation styles.
A wrapper for a single input with its assistances views such as label, help message.

A form group can yield the control's view after or within a label, this is dependent on the control
    required layout and is defined byt he yieldInLabel property

Syntax:
{{em-form-group
    //The state of the form group
    status="none|error|warning|success"
    //If true the control view is yieled within the label
    yieldInLabel=true|false
    //If true validation icons will be rendered, by default inherited from the form
    v_icons: true
    //Label of the form group, default is a human friendly form of the property name
    label="Some label"
}}
 */
export default Em.Component.extend(InFormMixin, HasPropertyMixin, HasPropertyValidationMixin, {
  tagName: 'div',
  "class": 'form-group',
  layoutName: 'components/em-form-group',
  classNameBindings: ['class', 'hasSuccess', 'hasWarning', 'hasError', 'v_icons:has-feedback'],
  attributeBindings: ['disabled'],
  canShowErrors: false,
  canShowErrorsObserver: Em.observer('form', 'form.model', function() {
    this.set('canShowErrors', false);
  }),
  hasSuccess: Em.computed('status', 'canShowErrors', function() {
    var success;
    success = this.get('validations') && this.get('status') === 'success' && this.get('canShowErrors');
    this.set('success', success);
    return success;
  }),
  hasWarning: Em.computed('status', 'canShowErrors', function() {
    var warning;
    warning = this.get('validations') && this.get('status') === 'warning' && this.get('canShowErrors');
    this.set('warning', warning);
    return warning;
  }),
  hasError: Em.computed('status', 'canShowErrors', function() {
    var error;
    error = this.get('validations') && this.get('status') === 'error' && this.get('canShowErrors');
    this.set('error', error);
    return error;
  }),
  v_icons: Em.computed.alias('form.v_icons'),
  v_success_icon: 'fa fa-check',
  v_warn_icon: 'fa fa-exclamation-triangle',
  v_error_icon: 'fa fa-times',
  validations: true,
  yieldInLabel: false,
  v_icon: Em.computed('status', 'canShowErrors', function() {
    if (!this.get('canShowErrors')) {
      return;
    }
    switch (this.get('status')) {
      case 'success':
        return this.get('v_success_icon');
      case 'warning':
      case 'warn':
        return this.get('v_warn_icon');
      case 'error':
        return this.get('v_error_icon');
      default:
        return null;
    }
  }),
  init: function() {
    return this._super();
  },

  /*
  Observes the helpHasErrors of the help control and modify the 'status' property accordingly.
   */

  /*
  Listen to the focus out of the form group and display the errors
   */
  focusOut: function() {
    return this.set('canShowErrors', true);
  }
});<commit_msg>Only shows errors validations when the model has validation support<commit_after>import Em from 'ember';
import InFormMixin from 'ember-idx-forms/mixins/in_form';
import HasPropertyMixin from 'ember-idx-forms/mixins/has_property';
import HasPropertyValidationMixin from 'ember-idx-forms/mixins/has_property_validation';

/*
Form Group

Wraps labels, controls and help message for optimum spacing and validation styles.
A wrapper for a single input with its assistances views such as label, help message.

A form group can yield the control's view after or within a label, this is dependent on the control
    required layout and is defined byt he yieldInLabel property

Syntax:
{{em-form-group
    //The state of the form group
    status="none|error|warning|success"
    //If true the control view is yieled within the label
    yieldInLabel=true|false
    //If true validation icons will be rendered, by default inherited from the form
    v_icons: true
    //Label of the form group, default is a human friendly form of the property name
    label="Some label"
}}
 */
export default Em.Component.extend(InFormMixin, HasPropertyMixin, HasPropertyValidationMixin, {
  tagName: 'div',
  "class": 'form-group',
  layoutName: 'components/em-form-group',
  classNameBindings: ['class', 'hasSuccess', 'hasWarning', 'hasError', 'v_icons:has-feedback'],
  attributeBindings: ['disabled'],
  canShowErrors: false,
  canShowErrorsObserver: Em.observer('form', 'form.model', function() {
    this.set('canShowErrors', false);
  }),
  hasSuccess: Em.computed('status', 'canShowErrors', function() {
    var success;
    success = this.get('validations') && this.get('status') === 'success' && this.get('canShowErrors');
    this.set('success', success);
    return success;
  }),
  hasWarning: Em.computed('status', 'canShowErrors', function() {
    var warning;
    warning = this.get('validations') && this.get('status') === 'warning' && this.get('canShowErrors');
    this.set('warning', warning);
    return warning;
  }),
  hasError: Em.computed('status', 'canShowErrors', function() {
    var error;
    error = this.get('validations') && this.get('status') === 'error' && this.get('canShowErrors');
    this.set('error', error);
    return error;
  }),
  v_icons: Em.computed.alias('form.v_icons'),
  v_success_icon: 'fa fa-check',
  v_warn_icon: 'fa fa-exclamation-triangle',
  v_error_icon: 'fa fa-times',
  validations: true,
  yieldInLabel: false,
  v_icon: Em.computed('status', 'canShowErrors', function() {
    if (!this.get('canShowErrors')) {
      return;
    }
    switch (this.get('status')) {
      case 'success':
        return this.get('v_success_icon');
      case 'warning':
      case 'warn':
        return this.get('v_warn_icon');
      case 'error':
        return this.get('v_error_icon');
      default:
        return null;
    }
  }),
  init: function() {
    return this._super();
  },

  /*
  Observes the helpHasErrors of the help control and modify the 'status' property accordingly.
   */

  /*
  Listen to the focus out of the form group and display the errors
   */
  focusOut: function() {
    return this.set('canShowErrors', !!this.get('validations'));
  }
});<|endoftext|>


<commit_before>const _ = require('underscore');
const Phase = require('./phase.js');
const SimpleStep = require('./simplestep.js');
const ActionWindow = require('./actionwindow.js');
const EndRoundPrompt = require('./regroup/endroundprompt.js');

/*
V Regroup Phase
5.1 Regroup phase begins.
    ACTION WINDOW
5.2 Ready cards.
5.3 Discard from provinces.
5.4 Return rings.
5.5 Pass first player token.
5.6 Regroup phase ends.
 */

class RegroupPhase extends Phase {
    constructor(game) {
        super(game, 'regroup');
        this.initialise([
            new ActionWindow(this.game, 'After regroup phase begins', 'beginning'),
            new SimpleStep(game, () => this.readyCards()),
            new SimpleStep(game, () => this.discardFromProvinces()),
            new SimpleStep(game, () => this.returnRings()),
            new SimpleStep(game, () => this.passFirstPlayer()),
            new EndRoundPrompt(game),
            new SimpleStep(game, () => this.roundEnded())
        ]);
    }

    readyCards() {
        this.game.raiseEvent('onReadyAllCards', this, () => {
            _.each(this.game.getPlayers(), player => {
                player.readyCards();
            });
        });
    }

    discardFromProvinces() {
        _.each(this.game.getPlayersInFirstPlayerOrder(), player => player.discardFromBrokenProvinces());
        _.each(this.game.getPlayersInFirstPlayerOrder(), player => this.game.promptForSelect(player, {
            numCards: 0,
            multiSelect: true,
            activePromptTitle: 'Select dynasty cards to discard',
            waitingPromptTitle: 'Waiting for opponent to discard dynasty cards',
            cardCondition: card => {
                return (['province 1', 'province 2', 'province 3', 'province 4'].includes(card.location) &&
                        player === card.owner && !card.facedown);
            },
            onSelect: (player, cards) => {
                _.each(cards, card => {
                    let location = card.location;
                    player.moveCard(card, 'dynasty discard pile');
                    player.moveCard(player.dynastyDeck.first(), location);
                });
                this.complete();
                return true;
            }
        }));
    }

    returnRings() {
        this.game.raiseEvent('onReturnRings', this.game, () => {
            this.game.returnRings();
        });
    }

    passFirstPlayer() {
        this.allPlayers = this.game.getPlayersInFirstPlayerOrder();
        let firstPlayer = this.allPlayers.shift();
        let secondPlayer = this.allPlayers.shift();

        firstPlayer.firstPlayer = false;
        secondPlayer.firstPlayer = true;
    }

    roundEnded() {
        this.game.raiseEvent('onRoundEnded');
    }

}

module.exports = RegroupPhase;
<commit_msg>bug fix<commit_after>const _ = require('underscore');
const Phase = require('./phase.js');
const SimpleStep = require('./simplestep.js');
const ActionWindow = require('./actionwindow.js');
const EndRoundPrompt = require('./regroup/endroundprompt.js');

/*
V Regroup Phase
5.1 Regroup phase begins.
    ACTION WINDOW
5.2 Ready cards.
5.3 Discard from provinces.
5.4 Return rings.
5.5 Pass first player token.
5.6 Regroup phase ends.
 */

class RegroupPhase extends Phase {
    constructor(game) {
        super(game, 'regroup');
        this.initialise([
            new ActionWindow(this.game, 'After regroup phase begins', 'beginning'),
            new SimpleStep(game, () => this.readyCards()),
            new SimpleStep(game, () => this.discardFromProvinces()),
            new SimpleStep(game, () => this.returnRings()),
            new SimpleStep(game, () => this.passFirstPlayer()),
            new EndRoundPrompt(game),
            new SimpleStep(game, () => this.roundEnded())
        ]);
    }

    readyCards() {
        this.game.raiseEvent('onReadyAllCards', this, () => {
            _.each(this.game.getPlayers(), player => {
                player.readyCards();
            });
        });
    }

    discardFromProvinces() {
        _.each(this.game.getPlayersInFirstPlayerOrder(), player => player.discardFromBrokenProvinces());
        _.each(this.game.getPlayersInFirstPlayerOrder(), player => this.game.promptForSelect(player, {
            numCards: 0,
            multiSelect: true,
            activePromptTitle: 'Select dynasty cards to discard',
            waitingPromptTitle: 'Waiting for opponent to discard dynasty cards',
            cardCondition: card => {
                return (['province 1', 'province 2', 'province 3', 'province 4'].includes(card.location) &&
                        player === card.owner && !card.facedown);
            },
            onSelect: (player, cards) => {
                _.each(cards, card => {
                    let location = card.location;
                    player.moveCard(card, 'dynasty discard pile');
                    player.moveCard(player.dynastyDeck.first(), location);
                });
                return true;
            }
        }));
    }

    returnRings() {
        this.game.raiseEvent('onReturnRings', this.game, () => {
            this.game.returnRings();
        });
    }

    passFirstPlayer() {
        this.allPlayers = this.game.getPlayersInFirstPlayerOrder();
        let firstPlayer = this.allPlayers.shift();
        let secondPlayer = this.allPlayers.shift();

        firstPlayer.firstPlayer = false;
        secondPlayer.firstPlayer = true;
    }

    roundEnded() {
        this.game.raiseEvent('onRoundEnded');
    }

}

module.exports = RegroupPhase;
<|endoftext|>


<commit_before>import * as React from "react";

class BinderUI extends React.Component {
  render() {
    const {
      onFormSubmit,
      onGitrefChange,
      onRepoChange,
      repo,
      gitref
    } = this.props;
    return (

        <form onSubmit={onFormSubmit} className="form">
          <fieldset>
            GitHub Repo:
            <input
              id="repoInput"
              onChange={onRepoChange}
              type="text"
              name="repo"
              value={repo}
            />
          </fieldset>
          <fieldset>
            Branch/commit:
            <input
              id="gitrefInput"
              onChange={onGitrefChange}
              type="text"
              name="gitref"
              value={gitref}
            />
          </fieldset>
          <fieldset>
            <button type="submit"> Submit</button>
          </fieldset>
        </form>

    );
  }
}
// TODO: Make a generic little console for some of the styled container pieces,
//       then make this component inject the binder specific bits
export class BinderConsole extends React.Component {
  render() {
    const { logs, ...otherprops } = this.props;
    return (

        <BinderUI {...otherprops} />
        {logs.length > 0
          ? logs.map((log, index) => {
              return (

                  {log.phase}

                    {log.message}

              );
            })
          : null}

    );
  }
}
<commit_msg>stick inputs inside of their labels<commit_after>import * as React from "react";

class BinderUI extends React.Component {
  render() {
    const {
      onFormSubmit,
      onGitrefChange,
      onRepoChange,
      repo,
      gitref
    } = this.props;
    return (

        <form onSubmit={onFormSubmit} className="form">
          <fieldset>

              GitHub Repo:
              <input
                id="repoInput"
                onChange={onRepoChange}
                type="text"
                name="repo"
                value={repo}
              />

          </fieldset>
          <fieldset>

              Branch/commit:
              <input
                id="gitrefInput"
                onChange={onGitrefChange}
                type="text"
                name="gitref"
                value={gitref}
              />

          </fieldset>
          <fieldset>
            <button type="submit"> Submit</button>
          </fieldset>
        </form>

    );
  }
}
// TODO: Make a generic little console for some of the styled container pieces,
//       then make this component inject the binder specific bits
export class BinderConsole extends React.Component {
  render() {
    const { logs, ...otherprops } = this.props;
    return (

        <BinderUI {...otherprops} />
        {logs.length > 0
          ? logs.map((log, index) => {
              return (

                  {log.phase}

                    {log.message}

              );
            })
          : null}

    );
  }
}
<|endoftext|>