<commit_before>import _ from 'lodash';
import NestedComponent from '../_classes/nested/NestedComponent';
import Component from '../_classes/component/Component';
import Components from '../Components';

export default class EditGridComponent extends NestedComponent {
  static schema(...extend) {
    return NestedComponent.schema({
      type: 'editgrid',
      label: 'Edit Grid',
      key: 'editGrid',
      clearOnHide: true,
      input: true,
      tree: true,
      defaultOpen: false,
      components: [],
      inlineEdit: false,
      templates: {
        header: this.defaultHeaderTemplate,
        row: this.defaultRowTemplate,
        footer: ''
      }
    }, ...extend);
  }

  static get builderInfo() {
    return {
      title: 'Edit Grid',
      icon: 'tasks',
      group: 'data',
      documentation: 'http://help.form.io/userguide/#editgrid',
      weight: 30,
      schema: EditGridComponent.schema()
    };
  }

  static get defaultHeaderTemplate() {
    return  `

  {% util.eachComponent(components, function(component) { %}

{{ component.label }}

  {% }) %}

`;
  }

  static get defaultRowTemplate() {
    return `

  {% util.eachComponent(components, function(component) { %}

      {{ getView(component, row[component.key]) }}

  {% }) %}
  {% if (!instance.options.readOnly) { %}

        <button class="btn btn-default btn-sm editRow">Edit</button>
        <button class="btn btn-danger btn-sm removeRow">Delete</button>

  {% } %}

`;
  }

  get defaultSchema() {
    return EditGridComponent.schema();
  }

  get emptyValue() {
    return [];
  }

  get editgridKey() {
    return `editgrid-${this.key}`;
  }

  constructor(...args) {
    super(...args);
    this.type = 'editgrid';
  }

  init() {
    this.components = this.components || [];
    const dataValue = this.dataValue || [];
    this.editRows = dataValue.map((row, rowIndex) => ({
      isOpen: false,
      data: row,
      components: this.createRowComponents(row, rowIndex),
    }));
    // In builder we need one row so the components will show up.
    if (this.builderMode) {
      this.addRow();
    }
  }

  render(children) {
    const dataValue = this.dataValue || [];
    return super.render(children || this.renderTemplate('editgrid', {
      editgridKey: this.editgridKey,
      header: this.renderString(_.get(this.component, 'templates.header'), {
        components: this.component.components,
        value: dataValue
      }),
      footer: this.renderString(_.get(this.component, 'templates.footer'), {
        components: this.component.components,
        value: dataValue
      }),
      rows: this.editRows.map(this.renderRow.bind(this)),
      openRows: this.editRows.map(row => row.isOpen),
      errors: this.editRows.map(row => row.error)
    }));
  }

  attach(element) {
    this.loadRefs(element, {
      [`${this.editgridKey}-addRow`]: 'multiple',
      [`${this.editgridKey}-removeRow`]: 'multiple',
      [`${this.editgridKey}-saveRow`]: 'multiple',
      [`${this.editgridKey}-cancelRow`]: 'multiple',
      [this.editgridKey]: 'multiple',
    });

    this.refs[`${this.editgridKey}-addRow`].forEach((addButton) => {
      this.addEventListener(addButton, 'click', this.addRow.bind(this));
    });

    let openRowCount = 0;
    this.refs[this.editgridKey].forEach((row, rowIndex) => {
      if (this.editRows[rowIndex].isOpen) {
        this.attachComponents(row, this.editRows[rowIndex].components);
        this.addEventListener(this.refs[`${this.editgridKey}-saveRow`][openRowCount], 'click', this.saveRow.bind(this, rowIndex));
        this.addEventListener(this.refs[`${this.editgridKey}-cancelRow`][openRowCount], 'click', this.cancelRow.bind(this, rowIndex));
        openRowCount++;
      }
      else {
        // Attach edit and remove button events.
        [
          {
            class: 'removeRow',
            event: 'click',
            action: this.removeRow.bind(this, rowIndex)
          },
          {
            class: 'editRow',
            event: 'click',
            action: this.editRow.bind(this, rowIndex)
          },
        ].forEach(action => {
          const elements = row.getElementsByClassName(action.class);
          Array.prototype.forEach.call(elements, element => {
            element.addEventListener(action.event, action.action);
          });
        });
      }
    });

    // Add open class to the element if any edit grid row is open
    if (openRowCount) {
      this.addClass(this.refs.component, `formio-component-${this.component.type}-row-open`);
    }
    else {
      this.removeClass(this.refs.component, `formio-component-${this.component.type}-row-open`);
    }

    super.attach(element);
  }

  renderRow(row, rowIndex) {
    const dataValue = this.dataValue || [];
    if (row.isOpen) {
      return this.renderComponents(row.components);
    }
    else {
      return this.renderString(_.get(this.component, 'templates.row', EditGridComponent.defaultRowTemplate),
        {
          row: dataValue[rowIndex],
          rowIndex,
          components: this.component.components,
          flattenedComponents: this.flattenComponents(rowIndex),
          getView: (component, data) => {
            console.log('getView() method is depricated, consider usage of flattenedComponents[componentKey].getView(data)');
            const builtComponent = Components.create(component, this.options, data, true);
            const result = builtComponent.getView(data);

            builtComponent.destroy();

            return result;
          },

        });
    }
  }

  checkData(data, flags = {}) {
    return this.editRows.reduce((valid, editRow) => this.checkRow(data, editRow, flags) && valid, true);
  }

  checkRow(data, editRow, flags = {}) {
    let valid = true;
    if (flags.noCheck) {
      return;
    }

    // Update the value.
    let changed = this.updateValue({
      noUpdateEvent: true
    });

    // Iterate through all components and check conditions, and calculate values.
    editRow.components.forEach(comp => {
      if (comp.checkData) {
        valid &= comp.checkData(data, flags);
      }
      changed |= comp.calculateValue(data, {
        noUpdateEvent: true
      });
      comp.checkConditions(data);
      if (!flags.noValidate) {
        valid &= comp.checkValidity(data, this.component.inlineEdit || !editRow.isOpen);
      }
    });

    if (!flags.noValidate) {
      valid &= (this.validateRow(editRow) === true);
    }

    // Trigger the change if the values changed.
    if (changed) {
      this.triggerChange(flags);
    }

    // Return if the value is valid.
    return valid;
  }

  everyComponent(fn, rowIndex) {
    const components = this.getComponents(rowIndex);
    _.each(components, (component, index) => {
      if (fn(component, components, index) === false) {
        return false;
      }

      if (typeof component.everyComponent === 'function') {
        if (component.everyComponent(fn) === false) {
          return false;
        }
      }
    });
  }

  flattenComponents(rowIndex) {
    const result = {};

    this.everyComponent((component) => {
      result[component.key] = component;
    }, rowIndex);

    return result;
  }

  getComponents(rowIndex) {
    return this.options.builder
      ? super.getComponents()
      : _.isNumber(rowIndex)
        ? (this.editRows[rowIndex].components || [])
        : this.editRows.reduce((result, row) => result.concat(row.components || []), []);
  }

  destroyComponents(rowIndex) {
    const components = this.getComponents(rowIndex).slice();
    components.forEach((comp) => comp.destroy());
  }

  addRow() {
    if (this.options.readOnly) {
      return;
    }
    const dataObj = {};
    this.editRows.push({
      components: [],
      isOpen: true,
      data: dataObj
    });
    if (this.component.inlineEdit) {
      this.dataValue.push(dataObj);
    }
    const rowIndex = this.editRows.length - 1;
    const editRow = this.editRows[rowIndex];
    this.emit('editGridAddRow', {
      component: this.component,
      row: editRow
    });
    editRow.components = this.createRowComponents(editRow.data, rowIndex);
    this.redraw();
  }

  editRow(rowIndex) {
    const dataValue = this.dataValue || [];
    const editRow = this.editRows[rowIndex];
    editRow.dirty = false;
    editRow.isOpen = true;
    editRow.editing = true;
    const dataSnapshot = dataValue[rowIndex] ? _.cloneDeep(dataValue[rowIndex]) : {};
    if (this.component.inlineEdit) {
      editRow.backup = dataSnapshot;
    }
    else {
      editRow.data = dataSnapshot;
    }
    this.redraw();
  }

  clearErrors(rowIndex) {
    const editRow = this.editRows[rowIndex];
    if (editRow && Array.isArray(editRow.components)) {
      editRow.components.forEach(comp => {
        comp.setPristine(true);
        comp.setCustomValidity('');
      });
    }
  }

  cancelRow(rowIndex) {
    const editRow = this.editRows[rowIndex];
    if (this.options.readOnly) {
      editRow.dirty = false;
      editRow.isOpen = false;
      this.redraw();
      return;
    }
    if (editRow.editing) {
      const dataValue = this.dataValue || [];
      editRow.dirty = false;
      editRow.isOpen = false;
      if (this.component.inlineEdit) {
        this.dataValue[rowIndex] = editRow.backup;
      }
      editRow.data = dataValue[rowIndex] || {};
      this.clearErrors(rowIndex);
    }
    else {
      this.clearErrors(rowIndex);
      this.destroyComponents(rowIndex);
      if (this.component.inlineEdit) {
        this.splice(rowIndex);
      }
      this.editRows.splice(rowIndex, 1);
    }

    this.checkValidity(this.data, true);
    this.redraw();
  }

  saveRow(rowIndex) {
    const editRow = this.editRows[rowIndex];
    if (this.options.readOnly) {
      editRow.dirty = false;
      editRow.isOpen = false;
      this.redraw();
      return;
    }
    editRow.dirty = true;
    if (!!this.validateRow(editRow) !== true) {
      return;
    }

    if (!this.component.inlineEdit) {
      const dataValue = this.dataValue || [];
      if (editRow.editing) {
        dataValue[rowIndex] = editRow.data;
      }
      else {
        // Insert this row into its proper place.
        const newIndex = dataValue.length;
        dataValue.push(editRow.data);
        this.editRows.splice(rowIndex, 1);
        this.editRows.splice(newIndex, 0, editRow);
        rowIndex = newIndex;
      }
    }
    editRow.dirty = false;
    editRow.isOpen = false;
    this.updateValue();
    this.triggerChange();
    this.checkValidity(this.data, true);
    this.redraw();
  }

  removeRow(rowIndex) {
    if (this.options.readOnly) {
      return;
    }
    this.destroyComponents(rowIndex);
    this.splice(rowIndex);
    this.editRows.splice(rowIndex, 1);
    this.updateValue();
    this.triggerChange();
    this.checkValidity(this.data, true);
    this.redraw();
  }

  createRowComponents(row, rowIndex) {
    const components = [];
    this.component.components.map((col, colIndex) => {
      const column = _.clone(col);
      const options = _.clone(this.options);
      options.name += `[${rowIndex}]`;
      options.row = `${rowIndex}-${colIndex}`;
      const comp = this.createComponent(_.assign({}, column, {
        row: options.row
      }), options, row);
      comp.rowIndex = rowIndex;
      // Don't bubble sub changes since they won't apply until pressing save.
      comp.triggerChange = () => {
        // Should we recalculate or something here?
        // TODO: Cause refreshOn to trigger.
        if (this.component.inlineEdit) {
          this.triggerChange();
        }
        // else {
        //   this.checkRow(this.data, rowIndex);
        // }
      };
      components.push(comp);
    });
    return components;
  }

  validateRow(editRow, dirty) {
    let valid = true;
    if (editRow.isOpen) {
      const isDirty = dirty || !!editRow.dirty;
      editRow.components.forEach(comp => {
        comp.setPristine(!isDirty);
        valid &= comp.checkValidity(null, isDirty, editRow.data);
      });
    }

    if (this.component.validate && this.component.validate.row) {
      valid = this.evaluate(this.component.validate.row, {
        valid,
        row: editRow.data
      }, 'valid', true);
      if (valid.toString() !== 'true') {
        editRow.error = valid;
      }
      else {
        delete editRow.error;
      }
      if (valid === null) {
        valid = `Invalid row validation for ${this.key}`;
      }
    }

    return !!valid;
  }

  checkValidity(data, dirty) {
    if (!this.checkCondition(null, data)) {
      this.setCustomValidity('');
      return true;
    }

    let rowsValid = true;
    let rowsClosed = true;
    this.editRows.forEach((editRow) => {
      // Trigger all errors on the row.
      const rowValid = this.validateRow(editRow, dirty);

      rowsValid &= rowValid;

      // Any open rows causes validation to fail.
      rowsClosed &= !editRow.isOpen;
    });

    if (!rowsValid) {
      this.setCustomValidity('Please correct rows before proceeding.', dirty);
      return false;
    }
    else if (!rowsClosed && !this.component.inlineEdit) {
      this.setCustomValidity('Please save all rows before proceeding.', dirty);
      return false;
    }

    const message = this.invalid || this.invalidMessage(data, dirty);
    this.setCustomValidity(message, dirty);
    return true;
  }

  get defaultValue() {
    const value = super.defaultValue;
    return Array.isArray(value) ? value : [];
  }

  updateValue(flags, value) {
    // Intentionally skip over nested component updateValue method to keep recursive update from occurring with sub components.
    return Component.prototype.updateValue.call(this, flags, value);
  }

  hasChanged(before, after) {
    if (
      ((before === undefined) || (before === null)) &&
      ((after === undefined) || (after === null))
    ) {
      return false;
    }
    return !_.isEqual(before, after);
  }

  setValue(value, flags) {
    if (!value) {
      return;
    }
    if (!Array.isArray(value)) {
      if (typeof value === 'object') {
        value = [value];
      }
      else {
        return;
      }
    }

    const changed = this.hasChanged(value, this.dataValue);
    this.dataValue = value;
    // Refresh editRow data when data changes.
    this.dataValue.forEach((row, rowIndex) => {
      const editRow = this.editRows[rowIndex];
      if (editRow) {
        editRow.data = row;
        if (editRow.isOpen) {
          editRow.components.forEach(col => {
            col.data = row;
            col.setValue(row[col.key], flags);
          });
        }
      }
      else {
        this.editRows[rowIndex] = {
          components: this.createRowComponents(row, rowIndex),
          isOpen: false,
          data: row,
        };
      }
    });
    if (changed) {
      this.checkValidity(this.data);
      this.redraw();
    }
    return changed;
  }

  /**
   * Get the value of this component.
   *
   * @returns {*}
   */
  getValue() {
    return this.dataValue;
  }

  restoreComponentsContext() {
    return;
  }
}
<commit_msg>Fixed issue with EditGrid rows context.<commit_after>import _ from 'lodash';
import NestedComponent from '../_classes/nested/NestedComponent';
import Component from '../_classes/component/Component';
import Components from '../Components';

export default class EditGridComponent extends NestedComponent {
  static schema(...extend) {
    return NestedComponent.schema({
      type: 'editgrid',
      label: 'Edit Grid',
      key: 'editGrid',
      clearOnHide: true,
      input: true,
      tree: true,
      defaultOpen: false,
      components: [],
      inlineEdit: false,
      templates: {
        header: this.defaultHeaderTemplate,
        row: this.defaultRowTemplate,
        footer: ''
      }
    }, ...extend);
  }

  static get builderInfo() {
    return {
      title: 'Edit Grid',
      icon: 'tasks',
      group: 'data',
      documentation: 'http://help.form.io/userguide/#editgrid',
      weight: 30,
      schema: EditGridComponent.schema()
    };
  }

  static get defaultHeaderTemplate() {
    return  `

  {% util.eachComponent(components, function(component) { %}

{{ component.label }}

  {% }) %}

`;
  }

  static get defaultRowTemplate() {
    return `

  {% util.eachComponent(components, function(component) { %}

      {{ getView(component, row[component.key]) }}

  {% }) %}
  {% if (!instance.options.readOnly) { %}

        <button class="btn btn-default btn-sm editRow">Edit</button>
        <button class="btn btn-danger btn-sm removeRow">Delete</button>

  {% } %}

`;
  }

  get defaultSchema() {
    return EditGridComponent.schema();
  }

  get emptyValue() {
    return [];
  }

  get editgridKey() {
    return `editgrid-${this.key}`;
  }

  constructor(...args) {
    super(...args);
    this.type = 'editgrid';
  }

  init() {
    this.components = this.components || [];
    const dataValue = this.dataValue || [];
    this.editRows = dataValue.map((row, rowIndex) => ({
      isOpen: false,
      data: row,
      components: this.createRowComponents(row, rowIndex),
    }));
    // In builder we need one row so the components will show up.
    if (this.builderMode) {
      this.addRow();
    }
  }

  render(children) {
    const dataValue = this.dataValue || [];
    return super.render(children || this.renderTemplate('editgrid', {
      editgridKey: this.editgridKey,
      header: this.renderString(_.get(this.component, 'templates.header'), {
        components: this.component.components,
        value: dataValue
      }),
      footer: this.renderString(_.get(this.component, 'templates.footer'), {
        components: this.component.components,
        value: dataValue
      }),
      rows: this.editRows.map(this.renderRow.bind(this)),
      openRows: this.editRows.map(row => row.isOpen),
      errors: this.editRows.map(row => row.error)
    }));
  }

  attach(element) {
    this.loadRefs(element, {
      [`${this.editgridKey}-addRow`]: 'multiple',
      [`${this.editgridKey}-removeRow`]: 'multiple',
      [`${this.editgridKey}-saveRow`]: 'multiple',
      [`${this.editgridKey}-cancelRow`]: 'multiple',
      [this.editgridKey]: 'multiple',
    });

    this.refs[`${this.editgridKey}-addRow`].forEach((addButton) => {
      this.addEventListener(addButton, 'click', this.addRow.bind(this));
    });

    let openRowCount = 0;
    this.refs[this.editgridKey].forEach((row, rowIndex) => {
      if (this.editRows[rowIndex].isOpen) {
        this.attachComponents(row, this.editRows[rowIndex].components);
        this.addEventListener(this.refs[`${this.editgridKey}-saveRow`][openRowCount], 'click', this.saveRow.bind(this, rowIndex));
        this.addEventListener(this.refs[`${this.editgridKey}-cancelRow`][openRowCount], 'click', this.cancelRow.bind(this, rowIndex));
        openRowCount++;
      }
      else {
        // Attach edit and remove button events.
        [
          {
            class: 'removeRow',
            event: 'click',
            action: this.removeRow.bind(this, rowIndex)
          },
          {
            class: 'editRow',
            event: 'click',
            action: this.editRow.bind(this, rowIndex)
          },
        ].forEach(action => {
          const elements = row.getElementsByClassName(action.class);
          Array.prototype.forEach.call(elements, element => {
            element.addEventListener(action.event, action.action);
          });
        });
      }
    });

    // Add open class to the element if any edit grid row is open
    if (openRowCount) {
      this.addClass(this.refs.component, `formio-component-${this.component.type}-row-open`);
    }
    else {
      this.removeClass(this.refs.component, `formio-component-${this.component.type}-row-open`);
    }

    super.attach(element);
  }

  renderRow(row, rowIndex) {
    const dataValue = this.dataValue || [];
    if (row.isOpen) {
      return this.renderComponents(row.components);
    }
    else {
      return this.renderString(_.get(this.component, 'templates.row', EditGridComponent.defaultRowTemplate),
        {
          row: dataValue[rowIndex],
          rowIndex,
          components: this.component.components,
          flattenedComponents: this.flattenComponents(rowIndex),
          getView: (component, data) => {
            console.log('getView() method is depricated, consider usage of flattenedComponents[componentKey].getView(data)');
            const builtComponent = Components.create(component, this.options, data, true);
            const result = builtComponent.getView(data);

            builtComponent.destroy();

            return result;
          },
        });
    }
  }

  checkData(data, flags = {}) {
    return this.editRows.reduce((valid, editRow) => this.checkRow(data, editRow, flags) && valid, true);
  }

  checkRow(data, editRow, flags = {}) {
    let valid = true;
    if (flags.noCheck) {
      return;
    }

    // Update the value.
    let changed = this.updateValue({
      noUpdateEvent: true
    });

    // Iterate through all components and check conditions, and calculate values.
    editRow.components.forEach(comp => {
      if (comp.checkData) {
        valid &= comp.checkData(data, flags);
      }
      changed |= comp.calculateValue(data, {
        noUpdateEvent: true
      });
      comp.checkConditions(data);
      if (!flags.noValidate) {
        valid &= comp.checkValidity(data, this.component.inlineEdit || !editRow.isOpen);
      }
    });

    if (!flags.noValidate) {
      valid &= (this.validateRow(editRow) === true);
    }

    // Trigger the change if the values changed.
    if (changed) {
      this.triggerChange(flags);
    }

    // Return if the value is valid.
    return valid;
  }

  everyComponent(fn, rowIndex) {
    const components = this.getComponents(rowIndex);
    _.each(components, (component, index) => {
      if (fn(component, components, index) === false) {
        return false;
      }

      if (typeof component.everyComponent === 'function') {
        if (component.everyComponent(fn) === false) {
          return false;
        }
      }
    });
  }

  flattenComponents(rowIndex) {
    const result = {};

    this.everyComponent((component) => {
      result[component.key] = component;
    }, rowIndex);

    return result;
  }

  getComponents(rowIndex) {
    return this.options.builder
      ? super.getComponents()
      : _.isNumber(rowIndex)
        ? (this.editRows[rowIndex].components || [])
        : this.editRows.reduce((result, row) => result.concat(row.components || []), []);
  }

  destroyComponents(rowIndex) {
    const components = this.getComponents(rowIndex).slice();
    components.forEach((comp) => comp.destroy());
  }

  addRow() {
    if (this.options.readOnly) {
      return;
    }
    const dataObj = {};
    this.editRows.push({
      components: [],
      isOpen: true,
      data: dataObj
    });
    if (this.component.inlineEdit) {
      this.dataValue.push(dataObj);
    }
    const rowIndex = this.editRows.length - 1;
    const editRow = this.editRows[rowIndex];
    this.emit('editGridAddRow', {
      component: this.component,
      row: editRow
    });
    editRow.components = this.createRowComponents(editRow.data, rowIndex);
    this.redraw();
  }

  editRow(rowIndex) {
    const dataValue = this.dataValue || [];
    const editRow = this.editRows[rowIndex];
    editRow.dirty = false;
    editRow.isOpen = true;
    editRow.editing = true;
    const dataSnapshot = dataValue[rowIndex] ? _.cloneDeep(dataValue[rowIndex]) : {};
    if (this.component.inlineEdit) {
      editRow.backup = dataSnapshot;
    }
    else {
      editRow.data = dataSnapshot;
      this.restoreRowContext(editRow);
    }
    this.redraw();
  }

  clearErrors(rowIndex) {
    const editRow = this.editRows[rowIndex];
    if (editRow && Array.isArray(editRow.components)) {
      editRow.components.forEach(comp => {
        comp.setPristine(true);
        comp.setCustomValidity('');
      });
    }
  }

  cancelRow(rowIndex) {
    const editRow = this.editRows[rowIndex];
    if (this.options.readOnly) {
      editRow.dirty = false;
      editRow.isOpen = false;
      this.redraw();
      return;
    }
    if (editRow.editing) {
      const dataValue = this.dataValue || [];
      editRow.dirty = false;
      editRow.isOpen = false;
      if (this.component.inlineEdit) {
        this.dataValue[rowIndex] = editRow.backup;
        editRow.data = editRow.backup;
        this.restoreRowContext(editRow);
      }
      editRow.data = dataValue[rowIndex] || {};
      this.clearErrors(rowIndex);
    }
    else {
      this.clearErrors(rowIndex);
      this.destroyComponents(rowIndex);
      if (this.component.inlineEdit) {
        this.splice(rowIndex);
      }
      this.editRows.splice(rowIndex, 1);
    }

    this.checkValidity(this.data, true);
    this.redraw();
  }

  saveRow(rowIndex) {
    const editRow = this.editRows[rowIndex];
    if (this.options.readOnly) {
      editRow.dirty = false;
      editRow.isOpen = false;
      this.redraw();
      return;
    }
    editRow.dirty = true;
    if (!!this.validateRow(editRow) !== true) {
      return;
    }

    if (!this.component.inlineEdit) {
      const dataValue = this.dataValue || [];
      if (editRow.editing) {
        dataValue[rowIndex] = editRow.data;
      }
      else {
        // Insert this row into its proper place.
        const newIndex = dataValue.length;
        dataValue.push(editRow.data);
        this.editRows.splice(rowIndex, 1);
        this.editRows.splice(newIndex, 0, editRow);
        rowIndex = newIndex;
      }
    }
    editRow.dirty = false;
    editRow.isOpen = false;
    this.updateValue();
    this.triggerChange();
    this.checkValidity(this.data, true);
    this.redraw();
  }

  removeRow(rowIndex) {
    if (this.options.readOnly) {
      return;
    }
    this.destroyComponents(rowIndex);
    this.splice(rowIndex);
    this.editRows.splice(rowIndex, 1);
    this.updateValue();
    this.triggerChange();
    this.checkValidity(this.data, true);
    this.redraw();
  }

  createRowComponents(row, rowIndex) {
    const components = [];
    this.component.components.map((col, colIndex) => {
      const column = _.clone(col);
      const options = _.clone(this.options);
      options.name += `[${rowIndex}]`;
      options.row = `${rowIndex}-${colIndex}`;
      const comp = this.createComponent(_.assign({}, column, {
        row: options.row
      }), options, row);
      comp.rowIndex = rowIndex;
      // Don't bubble sub changes since they won't apply until pressing save.
      comp.triggerChange = () => {
        // Should we recalculate or something here?
        // TODO: Cause refreshOn to trigger.
        if (this.component.inlineEdit) {
          this.triggerChange();
        }
        // else {
        //   this.checkRow(this.data, rowIndex);
        // }
      };
      components.push(comp);
    });
    return components;
  }

  validateRow(editRow, dirty) {
    let valid = true;
    if (editRow.isOpen) {
      const isDirty = dirty || !!editRow.dirty;
      editRow.components.forEach(comp => {
        comp.setPristine(!isDirty);
        valid &= comp.checkValidity(null, isDirty, editRow.data);
      });
    }

    if (this.component.validate && this.component.validate.row) {
      valid = this.evaluate(this.component.validate.row, {
        valid,
        row: editRow.data
      }, 'valid', true);
      if (valid.toString() !== 'true') {
        editRow.error = valid;
      }
      else {
        delete editRow.error;
      }
      if (valid === null) {
        valid = `Invalid row validation for ${this.key}`;
      }
    }

    return !!valid;
  }

  checkValidity(data, dirty) {
    if (!this.checkCondition(null, data)) {
      this.setCustomValidity('');
      return true;
    }

    let rowsValid = true;
    let rowsClosed = true;
    this.editRows.forEach((editRow) => {
      // Trigger all errors on the row.
      const rowValid = this.validateRow(editRow, dirty);

      rowsValid &= rowValid;

      // Any open rows causes validation to fail.
      rowsClosed &= !editRow.isOpen;
    });

    if (!rowsValid) {
      this.setCustomValidity('Please correct rows before proceeding.', dirty);
      return false;
    }
    else if (!rowsClosed && !this.component.inlineEdit) {
      this.setCustomValidity('Please save all rows before proceeding.', dirty);
      return false;
    }

    const message = this.invalid || this.invalidMessage(data, dirty);
    this.setCustomValidity(message, dirty);
    return true;
  }

  get defaultValue() {
    const value = super.defaultValue;
    return Array.isArray(value) ? value : [];
  }

  updateValue(flags, value) {
    // Intentionally skip over nested component updateValue method to keep recursive update from occurring with sub components.
    return Component.prototype.updateValue.call(this, flags, value);
  }

  hasChanged(before, after) {
    if (
      ((before === undefined) || (before === null)) &&
      ((after === undefined) || (after === null))
    ) {
      return false;
    }
    return !_.isEqual(before, after);
  }

  setValue(value, flags) {
    if (!value) {
      return;
    }
    if (!Array.isArray(value)) {
      if (typeof value === 'object') {
        value = [value];
      }
      else {
        return;
      }
    }

    const changed = this.hasChanged(value, this.dataValue);
    this.dataValue = value;
    // Refresh editRow data when data changes.
    this.dataValue.forEach((row, rowIndex) => {
      const editRow = this.editRows[rowIndex];
      if (editRow) {
        editRow.data = row;
        if (editRow.isOpen) {
          editRow.components.forEach(col => {
            col.data = row;
            col.setValue(row[col.key], flags);
          });
        }
      }
      else {
        this.editRows[rowIndex] = {
          components: this.createRowComponents(row, rowIndex),
          isOpen: false,
          data: row,
        };
      }
    });
    if (changed) {
      this.checkValidity(this.data);
      this.redraw();
    }
    return changed;
  }

  /**
   * Get the value of this component.
   *
   * @returns {*}
   */
  getValue() {
    return this.dataValue;
  }

  restoreComponentsContext() {
    return;
  }

  restoreRowContext(editRow) {
    editRow.components.forEach((component) => component.data = editRow.data);
  }
}
<|endoftext|>


<commit_before>// Get stories from JSON
//
$(document).ready(getstories);
function getstories(){

    $.getJSON('stories.json', function(data) {
		$.each(data.list, function(key, val) {
        var output="
";
		    output+="
" + val.resolved_title +"
";
		    output+="
" + "val.resolved_title"+"
";
		    output+="
" + val.excerpt +"
";
		    output+="
";
			output+="
";

			$("#news").append(output);
      	});
    });
}<commit_msg>Last again<commit_after>// Get stories from JSON
//
$(document).ready(getstories);
function getstories(){

    $.getJSON('stories.json', function(data) {
		$.each(data.list, function(key, val) {
        var output="
";
		    output+="
" + val.resolved_title +"
";
		    output+=""+"
"+val.resolved_title+"
"+"";
		    output+="
" + val.excerpt +"
";
		    output+="
";
			output+="
";

			$("#news").append(output);
      	});
    });
}<|endoftext|>


<commit_before>(function (factory) {
  'use strict';

  if (typeof define === 'function' && define.amd) {
    define(['jquery'], factory)
  } else if (typeof exports === 'object') {
    module.exports = factory(require('jquery'))
  } else {
    factory(jQuery)
  }
}(function ($) {
  'use strict';

  var Gist = function (element) {
    this.$gist = $(element)
    this.request()
  }

  Gist.prototype.request = function () {
    var id   = this.$gist.data('gist')
    var file = this.$gist.data('file') || false
    var url  = 'https://gist.github.com/' + id + '.json'

    if (file) {
      url += '?file=' + file
    }

    var self = this

    $.ajax({
      url: url,
      dataType: 'jsonp',
      cache: true,
      success: function (data, textStatus, jqXHR) {
        self.onAjaxSuccess(self, data, textStatus, jqXHR)
      },
      error: function (jqXHR, textStatus, errorThrown) {
        self.onAjaxError(self, jqXHR, textStatus, errorThrown)
      }
    })
  }

  Gist.prototype.onAjaxSuccess = function (self, data, textStatus, jqXHR) {
    if (! data || ! 'div' in data) {
      self.onAjaxError()
      return
    }

    // Append the stylesheet if it doesn't exists.
    // Trying to minimize requests.
    if (! $('link[href="' + data.stylesheet + '"]').length) {
      $(document.head).append('<link href="' + data.stylesheet + '" rel="stylesheet">')
    }

    // When loading different files from the same gist
    // on one page, there will be duplicate ids.
    //
    // That should be fixed.
    self.$gist.html(data.div)
  }

  Gist.prototype.onAjaxError = function (self, jqXHR, textStatus, errorThrown) {
    self.$gist.html(':(')
  }

  function Plugin(option) {
    return this.each(function () {
      var $this = $(this)
      var data  = $this.data('gist-initialized')

      if (! data) {
        $this.data('gist-initialized', (data = new Gist(this)))
      }
    })
  }

  var old = $.fn.gist

  $.fn.gist             = Plugin
  $.fn.gist.Constructor = Gist

  $.fn.gist.noConflict = function () {
    $.fn.gist = old
    return this
  }
}))<commit_msg>Pass in options<commit_after>(function (factory) {
  'use strict';

  if (typeof define === 'function' && define.amd) {
    define(['jquery'], factory)
  } else if (typeof exports === 'object') {
    module.exports = factory(require('jquery'))
  } else {
    factory(jQuery)
  }
}(function ($) {
  'use strict';

  var Gist = function (element, options) {
    this.options = options
    this.$gist   = $(element)
    this.request()
  }

  Gist.DEFAULTS = {
    timeout: 1000,
    success: onAjaxSuccess,
    error: onAjaxError
  }

  Gist.prototype.request = function () {
    var id   = this.$gist.data('gist')
    var file = this.$gist.data('file') || false
    var url  = 'https://gist.github.com/' + id + '.json'

    if (file) {
      url += '?file=' + file
    }

    var self = this

    $.ajax({
      url: url,
      dataType: 'jsonp',
      cache: true,
      success: function (data, textStatus, jqXHR) {
        self.onAjaxSuccess(self, data, textStatus, jqXHR)
      },
      error: function (jqXHR, textStatus, errorThrown) {
        self.onAjaxError(self, jqXHR, textStatus, errorThrown)
      }
    })
  }

  Gist.prototype.onAjaxSuccess = function (self, data, textStatus, jqXHR) {
    if (! data || ! 'div' in data) {
      self.onAjaxError()
      return
    }

    // Append the stylesheet if it doesn't exists.
    // Trying to minimize requests.
    if (! $('link[href="' + data.stylesheet + '"]').length) {
      $(document.head).append('<link href="' + data.stylesheet + '" rel="stylesheet">')
    }

    // When loading different files from the same gist
    // on one page, there will be duplicate ids.
    //
    // That should be fixed.
    self.$gist.html(data.div)
  }

  Gist.prototype.onAjaxError = function (self, jqXHR, textStatus, errorThrown) {
    self.$gist.html(':(')
  }

  function Plugin(option) {
    return this.each(function () {
      var $this   = $(this)
      var data    = $this.data('gist-initialized')
      var options = $.extend({}, Gist.DEFAULTS, $this.data(), typeof option == 'object' && option)

      if (! data) {
        $this.data('gist-initialized', (data = new Gist(this, options)))
      }
    })
  }

  var old = $.fn.gist

  $.fn.gist             = Plugin
  $.fn.gist.Constructor = Gist

  $.fn.gist.noConflict = function () {
    $.fn.gist = old
    return this
  }
}))<|endoftext|>


<commit_before>import React, { PropTypes } from 'react';
import Remarkable from 'remarkable';
import { HotKeys } from 'react-hotkeys';
import { throttle } from 'lodash';
import { Form, Input, Select, Tabs } from 'antd';
import EditorToolbar from './EditorToolbar';
import Action from '../Button/Action';
import Body from '../Story/Body';
import './Editor.less';

const remarkable = new Remarkable();

const Option = Select.Option;
const OptionGroup = Select.OptGroup;
const TabPane = Tabs.TabPane;

class Editor extends React.Component {
  static propTypes = {
    recentTopics: PropTypes.arrayOf(PropTypes.string),
    popularTopics: PropTypes.arrayOf(PropTypes.string),
    onSubmit: PropTypes.func,
    onError: PropTypes.func,
    onImagePasted: PropTypes.func,
  }

  static defaultProps = {
    recentTopics: [],
    popularTopics: [],
    onSubmit: () => {},
    onError: () => {},
    onImagePasted: () => {},
  }

  state = {
    contentHtml: '',
  }

  static hotkeys = {
    h1: 'ctrl+shift+1',
    h2: 'ctrl+shift+2',
    h3: 'ctrl+shift+3',
    h4: 'ctrl+shift+4',
    h5: 'ctrl+shift+5',
    h6: 'ctrl+shift+6',
    bold: 'ctrl+b',
    italic: 'ctrl+i',
    quote: 'ctrl+q',
    link: 'ctrl+k',
    image: 'ctrl+m',
  };

  componentDidMount() {
    this.input.addEventListener('input', throttle(e => this.renderMarkdown(e.target.value), 500));
    this.input.addEventListener('paste', this.handlePastedImage);
  }

  setInput = (input) => {
    this.input = input && input.refs && input.refs.input;
  };

  //
  // Form validation and handling
  //

  handleSubmit = (e) => {
    e.preventDefault();
    this.props.form.validateFieldsAndScroll((err, values) => {
      if (!err) {
        this.props.onSubmit({
          ...values,
          body: this.input.value,
        });
      } else {
        this.props.onError(err);
      }
    });
  }

  checkTopics = (rule, value, callback) => {
    if (value && value.length >= 1 && value.length <= 5) {
      callback();
    } else {
      callback('You have to add 1 to 5 topics.');
    }
  }

  //
  // Editor methods
  //

  handlePastedImage = (e) => {
    if (e.clipboardData && e.clipboardData.items) {
      const items = e.clipboardData.items;
      Array.from(items).forEach((item) => {
        if (item.kind === 'file') {
          const blob = item.getAsFile();
          const reader = new FileReader();
          reader.onload = (event) => {
            this.props.onImagePasted(event.target.result, this.insertImage);
          };
          reader.readAsDataURL(blob);
        }
      });
    }
  };

  insertAtCursor = (before, after, deltaStart = 0, deltaEnd = 0) => {
    if (!this.input) return;

    const startPos = this.input.selectionStart;
    const endPos = this.input.selectionEnd;
    this.input.value = this.input.value.substring(0, startPos)
      + before
      + this.input.value.substring(startPos, endPos)
      + after
      + this.input.value.substring(endPos, this.input.value.length);

    this.input.selectionStart = startPos + deltaStart;
    this.input.selectionEnd = endPos + deltaEnd;
  }

  insertImage = (image) => {
    if (!this.input) return;

    const startPos = this.input.selectionStart;
    const endPos = this.input.selectionEnd;
    this.input.value = `${this.input.value.substring(0, startPos)}![image](${image})${this.input.value.substring(endPos, this.input.value.length)}`;

    this.renderMarkdown(this.input.value);
  }

  insertCode = (type) => {
    if (!this.input) return;
    this.input.focus();

    switch (type) {
      case 'h1':
        this.insertAtCursor('# ', '', 2, 2);
        break;
      case 'h2':
        this.insertAtCursor('## ', '', 3, 3);
        break;
      case 'h3':
        this.insertAtCursor('### ', '', 4, 4);
        break;
      case 'h4':
        this.insertAtCursor('#### ', '', 5, 5);
        break;
      case 'h5':
        this.insertAtCursor('##### ', '', 6, 6);
        break;
      case 'h6':
        this.insertAtCursor('###### ', '', 7, 7);
        break;
      case 'b':
        this.insertAtCursor('**', '**', 2, 2);
        break;
      case 'i':
        this.insertAtCursor('*', '*', 1, 1);
        break;
      case 'q':
        this.insertAtCursor('> ', '', 2, 2);
        break;
      case 'link':
        this.insertAtCursor('[', '](url)', 1, 1);
        break;
      case 'image':
        this.insertAtCursor('![', '](url)', 2, 2);
        break;
      default:
        break;
    }

    this.renderMarkdown(this.input.value);
  }

  renderMarkdown = (value) => {
    this.setState({
      contentHtml: remarkable.render(value),
    });
  }

  handlers = {
    h1: () => this.insertCode('h1'),
    h2: () => this.insertCode('h2'),
    h3: () => this.insertCode('h3'),
    h4: () => this.insertCode('h4'),
    h5: () => this.insertCode('h5'),
    h6: () => this.insertCode('h6'),
    bold: () => this.insertCode('b'),
    italic: () => this.insertCode('i'),
    quote: () => this.insertCode('q'),
    link: (e) => {
      e.preventDefault();
      this.insertCode('link');
    },
    image: () => this.insertCode('image'),
  }

  render() {
    const { getFieldDecorator } = this.props.form;
    const { recentTopics, popularTopics } = this.props;

    return (
      <Form className="Editor" layout="vertical" onSubmit={this.handleSubmit}>
        <Form.Item label={Title}>
          {getFieldDecorator('title', {
            rules: [{ required: true, message: 'Please enter a title.' }],
          })(
            <Input className="Editor__title" placeholder="Add title" />
            )}
        </Form.Item>
        <Form.Item
          label={Topics}
          extra="Separate topics with commas."
        >
          {getFieldDecorator('topics', {
            rules: [
              { required: true, message: 'Please enter topics.', type: 'array' },
              { validator: this.checkTopics },
            ],
          })(
            <Select
              mode="tags"
              placeholder="Add story topics here"
              notFoundContent="No such topic found. Just type your topics and separate them with commas."
              tokenSeparators={[' ', ',']}
            >
              <OptionGroup key="recent" label={Recent}>
                {recentTopics && recentTopics.map(topic => <Option key={topic}>{topic}</Option>)}
              </OptionGroup>
              <OptionGroup key="popular" label={Popular}>
                {popularTopics && popularTopics.map(topic => <Option key={topic}>{topic}</Option>)}
              </OptionGroup>
            </Select>
            )}
        </Form.Item>
        <Form.Item
          label={Write your story}
        >
          <Tabs defaultActiveKey="1">
            <TabPane tab="Editor" key="1">
              <EditorToolbar onSelect={this.insertCode} />
              <HotKeys keyMap={Editor.hotkeys} handlers={this.handlers}>
                <Input ref={ref => this.setInput(ref)} type="textarea" placeholder="Write your story..." autosize={{ minRows: 3, maxRows: 10 }} />
              </HotKeys>

You can upload images just by pasting them.

            </TabPane>
            <TabPane tab="Preview" key="2">

            </TabPane>
          </Tabs>
        </Form.Item>
        <Form.Item className="Editor__submit">
          <Action text="Submit" />
        </Form.Item>
      </Form>
    );
  }
}

export default Form.create()(Editor);
<commit_msg>Add body validation<commit_after>import React, { PropTypes } from 'react';
import Remarkable from 'remarkable';
import { HotKeys } from 'react-hotkeys';
import { throttle } from 'lodash';
import { Form, Input, Select, Tabs } from 'antd';
import EditorToolbar from './EditorToolbar';
import Action from '../Button/Action';
import Body from '../Story/Body';
import './Editor.less';

const remarkable = new Remarkable();

const Option = Select.Option;
const OptionGroup = Select.OptGroup;
const TabPane = Tabs.TabPane;

class Editor extends React.Component {
  static propTypes = {
    recentTopics: PropTypes.arrayOf(PropTypes.string),
    popularTopics: PropTypes.arrayOf(PropTypes.string),
    onSubmit: PropTypes.func,
    onError: PropTypes.func,
    onImagePasted: PropTypes.func,
  }

  static defaultProps = {
    recentTopics: [],
    popularTopics: [],
    onSubmit: () => {},
    onError: () => {},
    onImagePasted: () => {},
  }

  state = {
    contentHtml: '',
    noContent: false,
  }

  static hotkeys = {
    h1: 'ctrl+shift+1',
    h2: 'ctrl+shift+2',
    h3: 'ctrl+shift+3',
    h4: 'ctrl+shift+4',
    h5: 'ctrl+shift+5',
    h6: 'ctrl+shift+6',
    bold: 'ctrl+b',
    italic: 'ctrl+i',
    quote: 'ctrl+q',
    link: 'ctrl+k',
    image: 'ctrl+m',
  };

  componentDidMount() {
    this.input.addEventListener('input', throttle(e => this.renderMarkdown(e.target.value), 500));
    this.input.addEventListener('paste', this.handlePastedImage);
  }

  setInput = (input) => {
    this.input = input && input.refs && input.refs.input;
  };

  //
  // Form validation and handling
  //

  handleSubmit = (e) => {
    // NOTE: Wrapping textarea in getFormDecorator makes it impossible to control its selection what is needed for markdown formatting.
    // NOTE: This code adds requirement for body input to not be empty.
    e.preventDefault();
    this.setState({ noContent: false });
    this.props.form.validateFieldsAndScroll((err, values) => {
      if (!err && this.input.value !== '') {
        this.props.onSubmit({
          ...values,
          body: this.input.value,
        });
      } else {
        if (this.input.value === '') {
          const errors = {
            ...err,
            body: {
              errors: [{
                field: 'body',
                message: "Content can't be empty.",
              }],
            },
          };
          this.setState({ noContent: true });
          this.props.onError(errors);
        } else {
          this.props.onError(err);
        }
      }
    });
  }

  checkTopics = (rule, value, callback) => {
    if (value && value.length >= 1 && value.length <= 5) {
      callback();
    } else {
      callback('You have to add 1 to 5 topics.');
    }
  }

  //
  // Editor methods
  //

  handlePastedImage = (e) => {
    if (e.clipboardData && e.clipboardData.items) {
      const items = e.clipboardData.items;
      Array.from(items).forEach((item) => {
        if (item.kind === 'file') {
          const blob = item.getAsFile();
          const reader = new FileReader();
          reader.onload = (event) => {
            this.props.onImagePasted(event.target.result, this.insertImage);
          };
          reader.readAsDataURL(blob);
        }
      });
    }
  };

  insertAtCursor = (before, after, deltaStart = 0, deltaEnd = 0) => {
    if (!this.input) return;

    const startPos = this.input.selectionStart;
    const endPos = this.input.selectionEnd;
    this.input.value = this.input.value.substring(0, startPos)
      + before
      + this.input.value.substring(startPos, endPos)
      + after
      + this.input.value.substring(endPos, this.input.value.length);

    this.input.selectionStart = startPos + deltaStart;
    this.input.selectionEnd = endPos + deltaEnd;
  }

  insertImage = (image) => {
    if (!this.input) return;

    const startPos = this.input.selectionStart;
    const endPos = this.input.selectionEnd;
    this.input.value = `${this.input.value.substring(0, startPos)}![image](${image})${this.input.value.substring(endPos, this.input.value.length)}`;

    this.renderMarkdown(this.input.value);
  }

  insertCode = (type) => {
    if (!this.input) return;
    this.input.focus();

    switch (type) {
      case 'h1':
        this.insertAtCursor('# ', '', 2, 2);
        break;
      case 'h2':
        this.insertAtCursor('## ', '', 3, 3);
        break;
      case 'h3':
        this.insertAtCursor('### ', '', 4, 4);
        break;
      case 'h4':
        this.insertAtCursor('#### ', '', 5, 5);
        break;
      case 'h5':
        this.insertAtCursor('##### ', '', 6, 6);
        break;
      case 'h6':
        this.insertAtCursor('###### ', '', 7, 7);
        break;
      case 'b':
        this.insertAtCursor('**', '**', 2, 2);
        break;
      case 'i':
        this.insertAtCursor('*', '*', 1, 1);
        break;
      case 'q':
        this.insertAtCursor('> ', '', 2, 2);
        break;
      case 'link':
        this.insertAtCursor('[', '](url)', 1, 1);
        break;
      case 'image':
        this.insertAtCursor('![', '](url)', 2, 2);
        break;
      default:
        break;
    }

    this.renderMarkdown(this.input.value);
  }

  renderMarkdown = (value) => {
    this.setState({
      contentHtml: remarkable.render(value),
    });
  }

  handlers = {
    h1: () => this.insertCode('h1'),
    h2: () => this.insertCode('h2'),
    h3: () => this.insertCode('h3'),
    h4: () => this.insertCode('h4'),
    h5: () => this.insertCode('h5'),
    h6: () => this.insertCode('h6'),
    bold: () => this.insertCode('b'),
    italic: () => this.insertCode('i'),
    quote: () => this.insertCode('q'),
    link: (e) => {
      e.preventDefault();
      this.insertCode('link');
    },
    image: () => this.insertCode('image'),
  }

  render() {
    const { getFieldDecorator } = this.props.form;
    const { recentTopics, popularTopics } = this.props;

    return (
      <Form className="Editor" layout="vertical" onSubmit={this.handleSubmit}>
        <Form.Item label={Title}>
          {getFieldDecorator('title', {
            rules: [{ required: true, message: 'Please enter a title.' }],
          })(
            <Input className="Editor__title" placeholder="Add title" />
            )}
        </Form.Item>
        <Form.Item
          label={Topics}
          extra="Separate topics with commas."
        >
          {getFieldDecorator('topics', {
            rules: [
              { required: true, message: 'Please enter topics.', type: 'array' },
              { validator: this.checkTopics },
            ],
          })(
            <Select
              mode="tags"
              placeholder="Add story topics here"
              notFoundContent="No such topic found. Just type your topics and separate them with commas."
              tokenSeparators={[' ', ',']}
            >
              <OptionGroup key="recent" label={Recent}>
                {recentTopics && recentTopics.map(topic => <Option key={topic}>{topic}</Option>)}
              </OptionGroup>
              <OptionGroup key="popular" label={Popular}>
                {popularTopics && popularTopics.map(topic => <Option key={topic}>{topic}</Option>)}
              </OptionGroup>
            </Select>
            )}
        </Form.Item>
        <Form.Item
          label={Write your story}
          validateStatus={this.state.noContent ? 'error' : ''}
          help={this.state.noContent ? "Story content can't be empty." : ''}
        >
          <Tabs defaultActiveKey="1">
            <TabPane tab="Editor" key="1">
              <EditorToolbar onSelect={this.insertCode} />
              <HotKeys keyMap={Editor.hotkeys} handlers={this.handlers}>
                <Input ref={ref => this.setInput(ref)} type="textarea" placeholder="Write your story..." autosize={{ minRows: 3, maxRows: 10 }} />
              </HotKeys>

You can upload images just by pasting them.

            </TabPane>
            <TabPane tab="Preview" key="2">

            </TabPane>
          </Tabs>
        </Form.Item>
        <Form.Item className="Editor__submit">
          <Action text="Submit" />
        </Form.Item>
      </Form>
    );
  }
}

export default Form.create()(Editor);
<|endoftext|>


<commit_before>"use strict";

var React         = require('react');
var PropTypes     = require('prop-types');
var invariant     = require('./util/invariant');
var assign        = Object.assign || require('object-assign');
var matchRoutes   = require('./matchRoutes');
var Environment   = require('./environment');
var shallowEqual  = require('is-equal-shallow');

var RouterMixin = {
  mixins: [Environment.Mixin],

  propTypes: {
    // Added by matches
    _: PropTypes.array,
    _query: PropTypes.object,
    hash: PropTypes.bool,
    // User props
    path: PropTypes.string,
    contextual: PropTypes.bool,
    onBeforeNavigation: PropTypes.func,
    onNavigation: PropTypes.func,
    urlPatternOptions: PropTypes.oneOfType([
      PropTypes.arrayOf(PropTypes.string),
      PropTypes.object
    ])
  },

  childContextTypes: {
    router: PropTypes.any
  },

  getChildContext: function() {
    return {
      router: this
    };
  },

  contextTypes: {
    router: PropTypes.any
  },

  getInitialState: function() {
    return this.getRouterState(this.props);
  },

  componentWillReceiveProps: function(nextProps) {
    if (!shallowEqual(nextProps, this.props)) {
      var nextState = this.getRouterState(nextProps);
      this.delegateSetRoutingState(nextState);
    }
  },

  getRouterState: function(props) {
    var path;
    var prefix;
    var query;

    var parent = props.contextual && this.getParentRouter();

    if (parent) {
      // Build our new path based off the parent. A navigation may be in progress, in which case
      // we as well want the newest data so we use the pending match.
      var parentMatch = parent._pendingMatch || parent.getMatch();

      invariant(
        props.path ||
        isString(parentMatch.unmatchedPath) ||
        parentMatch.matchedPath === parentMatch.path,
        "contextual router has nothing to match on: %s", parentMatch.unmatchedPath
      );

      path = props.path || parentMatch.unmatchedPath || '/';
      prefix = parent.state.prefix + parentMatch.matchedPath;
      query = parentMatch.query;
    } else {

      path = props.path || this.getEnvironment().getPath();

      invariant(
        isString(path),
        ("router operate in environment which cannot provide path, " +
         "pass it a path prop; or probably you want to make it contextual")
      );

      prefix = '';
    }

    if (path[0] !== '/') {
      path = '/' + path;
    }

    var match = matchRoutes(this.getRoutes(props), path, query, this.getURLPatternOptions());

    return {
      match: match,
      matchProps: match.getProps(),
      handler: match.getHandler(),
      prefix: prefix,
      navigation: {},
      path: path
    };
  },

  getEnvironment: function() {
    if (this.props.environment) {
      return this.props.environment;
    }
    if (this.props.hash) {
      return Environment.hashEnvironment;
    }
    if (this.props.contextual && this.context.router) {
      return this.context.router.getEnvironment();
    }
    return Environment.defaultEnvironment;
  },

  /**
   * Return parent router or undefined.
   */
  getParentRouter: function() {
    var current = this.context.router;
    var environment = this.getEnvironment();

    if (current) {
      if (current.getEnvironment() === environment) {
        return current;
      }
    }
  },

  /**
   * Return current match.
   */
  getMatch: function() {
    return this.state.match;
  },

  getURLPatternOptions: function() {
    var parent = this.getParentRouter();
    var parentOptions = parent && parent.getURLPatternOptions();
    // Check existence so we don't return an empty object if there are no options.
    if (parentOptions) {
      return assign({}, this.props.urlPatternOptions, parentOptions);
    }
    return this.props.urlPatternOptions;
  },

  /**
   * Make href scoped for the current router.
   */
  makeHref: function(href) {
    return join(this.state.prefix, href);
  },

  /**
   * Navigate to a path
   *
   * @param {String} path
   * @param {Function} navigation
   * @param {Callback} cb
   */
  navigate: function(path, navigation, cb) {
    path = join(this.state.prefix, path);
    this.getEnvironment().setPath(path, navigation, cb);
  },

  /**
   * Set new path.
   *
   * This function is called by environment.
   *
   * @private
   *
   * @param {String} path
   * @param {Function} navigation
   * @param {Callback} cb
   */
  setPath: function(path, navigation, cb) {
    var state = this.getRouterState(this.props);
    state.navigation = navigation;

    if (this.props.onBeforeNavigation &&
        this.props.onBeforeNavigation(state.path, navigation, state.match) === false) {
      return;
    }

    if (navigation.onBeforeNavigation &&
        navigation.onBeforeNavigation(state.path, navigation, state.match) === false) {
      return;
    }

    this.delegateSetRoutingState(state, function() {
      if (this.props.onNavigation) {
        this.props.onNavigation(state.path, navigation, state.match);
      }
      cb();
    }.bind(this));
  },

  /**
   * Return the current path
   */
  getPath: function () {
    return this.state.match.path;
  },

  /**
   * Try to delegate state update to a setRoutingState method (might be provided
   * by router itself) or use replaceState.
   */
  delegateSetRoutingState: function(state, cb) {
    // Store this here so it can be accessed by child contextual routers in onBeforeNavigation.
    this._pendingMatch = state.match;

    if (this.setRoutingState) {
      this.setRoutingState(state, cb);
    } else {
      this.setState(state, cb);
    }
  }

};

function join(a, b) {
  return (a + '/' + b).replace(/\/+/g, '/');
}

function isString(o) {
  return Object.prototype.toString.call(o) === '[object String]';
}

module.exports = RouterMixin;
<commit_msg>fix(React): mark cwrp as UNSAFE<commit_after>"use strict";

var React         = require('react');
var PropTypes     = require('prop-types');
var invariant     = require('./util/invariant');
var assign        = Object.assign || require('object-assign');
var matchRoutes   = require('./matchRoutes');
var Environment   = require('./environment');
var shallowEqual  = require('is-equal-shallow');

var RouterMixin = {
  mixins: [Environment.Mixin],

  propTypes: {
    // Added by matches
    _: PropTypes.array,
    _query: PropTypes.object,
    hash: PropTypes.bool,
    // User props
    path: PropTypes.string,
    contextual: PropTypes.bool,
    onBeforeNavigation: PropTypes.func,
    onNavigation: PropTypes.func,
    urlPatternOptions: PropTypes.oneOfType([
      PropTypes.arrayOf(PropTypes.string),
      PropTypes.object
    ])
  },

  childContextTypes: {
    router: PropTypes.any
  },

  getChildContext: function() {
    return {
      router: this
    };
  },

  contextTypes: {
    router: PropTypes.any
  },

  getInitialState: function() {
    return this.getRouterState(this.props);
  },

  UNSAFE_componentWillReceiveProps: function(nextProps) {
    if (!shallowEqual(nextProps, this.props)) {
      var nextState = this.getRouterState(nextProps);
      this.delegateSetRoutingState(nextState);
    }
  },

  getRouterState: function(props) {
    var path;
    var prefix;
    var query;

    var parent = props.contextual && this.getParentRouter();

    if (parent) {
      // Build our new path based off the parent. A navigation may be in progress, in which case
      // we as well want the newest data so we use the pending match.
      var parentMatch = parent._pendingMatch || parent.getMatch();

      invariant(
        props.path ||
        isString(parentMatch.unmatchedPath) ||
        parentMatch.matchedPath === parentMatch.path,
        "contextual router has nothing to match on: %s", parentMatch.unmatchedPath
      );

      path = props.path || parentMatch.unmatchedPath || '/';
      prefix = parent.state.prefix + parentMatch.matchedPath;
      query = parentMatch.query;
    } else {

      path = props.path || this.getEnvironment().getPath();

      invariant(
        isString(path),
        ("router operate in environment which cannot provide path, " +
         "pass it a path prop; or probably you want to make it contextual")
      );

      prefix = '';
    }

    if (path[0] !== '/') {
      path = '/' + path;
    }

    var match = matchRoutes(this.getRoutes(props), path, query, this.getURLPatternOptions());

    return {
      match: match,
      matchProps: match.getProps(),
      handler: match.getHandler(),
      prefix: prefix,
      navigation: {},
      path: path
    };
  },

  getEnvironment: function() {
    if (this.props.environment) {
      return this.props.environment;
    }
    if (this.props.hash) {
      return Environment.hashEnvironment;
    }
    if (this.props.contextual && this.context.router) {
      return this.context.router.getEnvironment();
    }
    return Environment.defaultEnvironment;
  },

  /**
   * Return parent router or undefined.
   */
  getParentRouter: function() {
    var current = this.context.router;
    var environment = this.getEnvironment();

    if (current) {
      if (current.getEnvironment() === environment) {
        return current;
      }
    }
  },

  /**
   * Return current match.
   */
  getMatch: function() {
    return this.state.match;
  },

  getURLPatternOptions: function() {
    var parent = this.getParentRouter();
    var parentOptions = parent && parent.getURLPatternOptions();
    // Check existence so we don't return an empty object if there are no options.
    if (parentOptions) {
      return assign({}, this.props.urlPatternOptions, parentOptions);
    }
    return this.props.urlPatternOptions;
  },

  /**
   * Make href scoped for the current router.
   */
  makeHref: function(href) {
    return join(this.state.prefix, href);
  },

  /**
   * Navigate to a path
   *
   * @param {String} path
   * @param {Function} navigation
   * @param {Callback} cb
   */
  navigate: function(path, navigation, cb) {
    path = join(this.state.prefix, path);
    this.getEnvironment().setPath(path, navigation, cb);
  },

  /**
   * Set new path.
   *
   * This function is called by environment.
   *
   * @private
   *
   * @param {String} path
   * @param {Function} navigation
   * @param {Callback} cb
   */
  setPath: function(path, navigation, cb) {
    var state = this.getRouterState(this.props);
    state.navigation = navigation;

    if (this.props.onBeforeNavigation &&
        this.props.onBeforeNavigation(state.path, navigation, state.match) === false) {
      return;
    }

    if (navigation.onBeforeNavigation &&
        navigation.onBeforeNavigation(state.path, navigation, state.match) === false) {
      return;
    }

    this.delegateSetRoutingState(state, function() {
      if (this.props.onNavigation) {
        this.props.onNavigation(state.path, navigation, state.match);
      }
      cb();
    }.bind(this));
  },

  /**
   * Return the current path
   */
  getPath: function () {
    return this.state.match.path;
  },

  /**
   * Try to delegate state update to a setRoutingState method (might be provided
   * by router itself) or use replaceState.
   */
  delegateSetRoutingState: function(state, cb) {
    // Store this here so it can be accessed by child contextual routers in onBeforeNavigation.
    this._pendingMatch = state.match;

    if (this.setRoutingState) {
      this.setRoutingState(state, cb);
    } else {
      this.setState(state, cb);
    }
  }

};

function join(a, b) {
  return (a + '/' + b).replace(/\/+/g, '/');
}

function isString(o) {
  return Object.prototype.toString.call(o) === '[object String]';
}

module.exports = RouterMixin;
<|endoftext|>


<commit_before>// dependencies
var fs = require('fs');
var http = require('http');
var express = require('express');
var routes = require('./routes');
var path = require('path');
var request = require('request');
var cheerio = require('cheerio');
var mongodb = require('mongodb');
var mongoose = require('mongoose');
var Answers = require('./models/answers.js'); // mongo model

// global config
var app = express();
app.set('port', process.env.PORT || 1337);
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));

// env config
app.configure('development', function(){
    app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

app.configure('production', function(){
    app.use(express.errorHandler());
});

// routes
app.get('/', function(req, res){
  Answers.find(function(error, answers){
    res.render('index', {
      title: 'RealizeChangeDotOrg',
      answerOne:answers[4]["answer"],
      answerTwo:answers[3]["answer"]
    });
  });
});
app.get('/ping', routes.ping);
app.get('/about', routes.about);
app.get('/answers', routes.answers);

app.get('/searching', function(req, res){
  var url = 'https://github.com/mjhea0/realize-change-dot-org/blob/master/answers.md'
  var request_options = {
    url: url
  };
  request(request_options, function(err, resp, body) {
    $ = cheerio.load(body)
  	var projects = $('.markdown-body p');
    var randNum = Math.floor(Math.random() * projects.length);
    var project = $(projects)[randNum];
    var title = $(project).find('strong').text();
    var description = $(project).text().slice(title.length + 3);
    res.send('
'+title+'

'+description+'
');
  });
});

// mongo config
var mongo = process.env.MONGOLAB_URI || 'mongodb://localhost/realize-change-dot-org'
mongoose.connect(mongo);

// run server
app.listen(app.get('port'), function(){
  console.log('\nExpress server listening on port ' + app.get('port'));
});

mongoose.connection.on('open', function() {
  console.log('You are connected to mongodb');
});
<commit_msg>mongo heroku support<commit_after>// dependencies
var fs = require('fs');
var http = require('http');
var express = require('express');
var routes = require('./routes');
var path = require('path');
var request = require('request');
var cheerio = require('cheerio');
var mongodb = require('mongodb');
var mongoose = require('mongoose');
var Answers = require('./models/answers.js'); // mongo model

// global config
var app = express();
app.set('port', process.env.PORT || 1337);
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));

// env config
app.configure('development', function(){
    app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

app.configure('production', function(){
    app.use(express.errorHandler());
});

// routes
app.get('/', function(req, res){
  Answers.find(function(error, answers){
    res.render('index', {
      title: 'RealizeChangeDotOrg',
      answerOne:answers[4]["answer"],
      answerTwo:answers[3]["answer"]
    });
  });
});
app.get('/ping', routes.ping);
app.get('/about', routes.about);
app.get('/answers', routes.answers);

app.get('/searching', function(req, res){
  var url = 'https://github.com/mjhea0/realize-change-dot-org/blob/master/answers.md'
  var request_options = {
    url: url
  };
  request(request_options, function(err, resp, body) {
    $ = cheerio.load(body)
  	var projects = $('.markdown-body p');
    var randNum = Math.floor(Math.random() * projects.length);
    var project = $(projects)[randNum];
    var title = $(project).find('strong').text();
    var description = $(project).text().slice(title.length + 3);
    res.send('
'+title+'

'+description+'
');
  });
});

// mongo config
var mongo = process.env.MONGOLAB_URI || 'mongodb://localhost/realize-change-dot-org'
mongoose.connect(mongo, function (err, res) {
  if (err) {
  console.log ('ERROR connecting to: ' + uristring + '. ' + err);
  } else {
  console.log ('Succeeded connected to: ' + uristring);
  }
});

// run server
app.listen(app.get('port'), function(){
  console.log('\nExpress server listening on port ' + app.get('port'));
});

mongoose.connection.on('open', function() {
  console.log('You are connected to mongodb');
});
<|endoftext|>