<commit_before>import _ from 'lodash';
// Import from "dist" because it would require and "global" would not be defined in Angular apps.
import dragula from 'dragula/dist/dragula';
import NestedArrayComponent from '../_classes/nestedarray/NestedArrayComponent';
import { fastCloneDeep } from '../../utils/utils';

export default class DataGridComponent extends NestedArrayComponent {
  static schema(...extend) {
    return NestedArrayComponent.schema({
      label: 'Data Grid',
      key: 'dataGrid',
      type: 'datagrid',
      clearOnHide: true,
      input: true,
      tree: true,
      components: []
    }, ...extend);
  }

  static get builderInfo() {
    return {
      title: 'Data Grid',
      icon: 'th',
      group: 'data',
      documentation: 'http://help.form.io/userguide/#datagrid',
      weight: 30,
      schema: DataGridComponent.schema()
    };
  }

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

  init() {
    this.components = this.components || [];

    // Add new values based on minLength.
    this.rows = [];
    this.createRows(true);
    this.visibleColumns = {};
    this.checkColumns();
  }

  get dataValue() {
    const dataValue = super.dataValue;
    if (!dataValue || !Array.isArray(dataValue)) {
      return this.emptyValue;
    }
    return dataValue;
  }

  set dataValue(value) {
    super.dataValue = value;
  }

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

  get emptyValue() {
    return [{}];
  }

  get addAnotherPosition() {
    return _.get(this.component, 'addAnotherPosition', 'bottom');
  }

  get minLength() {
    if (this.hasRowGroups()) {
      return _.sum(this.getGroupSizes());
    }
    else {
      return _.get(this.component, 'validate.minLength', 0);
    }
  }

  get defaultValue() {
    const value = super.defaultValue;
    let defaultValue;

    if (Array.isArray(value)) {
      defaultValue = value;
    }
    else if (value && (typeof value === 'object')) {
      defaultValue = [value];
    }
    else {
      defaultValue = this.emptyValue;
    }

    for (let dIndex = defaultValue.length; dIndex < this.minLength; dIndex++) {
      defaultValue.push({});
    }

    return defaultValue;
  }

  set disabled(disabled) {
    super.disabled = disabled;
    _.each(this.refs[`${this.datagridKey}-addRow`], (button) => {
      button.disabled = disabled;
    });
    _.each(this.refs[`${this.datagridKey}-removeRow`], (button) => {
      button.disabled = disabled;
    });
  }

  get disabled() {
    return super.disabled;
  }

  get datagridKey() {
    return `datagrid-${this.key}`;
  }

  get allowReorder() {
    return !this.options.readOnly && _.get(this.component, 'reorder', false);
  }

  get iteratableRows() {
    return this.rows.map((row, index) => ({
      components: row,
      data: this.dataValue[index],
    }));
  }

  /**
   * Split rows into chunks.
   * @param {Number[]} groups - array of numbers where each item is size of group
   * @param {Array<T>} rows - rows collection
   * @return {Array<T[]>}
   */
  getRowChunks(groups, rows) {
    const [, chunks] = groups.reduce(
      ([startIndex, acc], size) => {
        const endIndex = startIndex +  size;
        return [endIndex, [...acc, [startIndex, endIndex]]];
      }, [0, []]
    );
    return chunks.map(range => _.slice(rows, ...range));
  }

  /**
   * Create groups object.
   * Each key in object represents index of first row in group.
   * @return {Object}
   */
  getGroups() {
    const groups = _.get(this.component, 'rowGroups', []);
    const sizes = _.map(groups, 'numberOfRows').slice(0, -1);
    const indexes = sizes.reduce((groupIndexes, size) => {
      const last = groupIndexes[groupIndexes.length - 1];
      return groupIndexes.concat(last + size);
    }, [0]);

    return groups.reduce(
      (gidxs, group, idx) => {
        return {
          ...gidxs,
          [indexes[idx]]: group
        };
      },
      {}
    );
  }

  /**
   * Retrun group sizes.
   * @return {Number[]}
   */
  getGroupSizes() {
    return _.map(_.get(this.component, 'rowGroups', []), 'numberOfRows');
  }

  hasRowGroups() {
    return _.get(this, 'component.enableRowGroups', false) && !this.builderMode;
  }

  totalRowsNumber(groups) {
    return _.sum(_.map(groups, 'numberOfRows'));
  }

  setStaticValue(n) {
    this.dataValue = _.range(n).map(() => ({}));
  }

  hasExtraColumn() {
    return (this.hasRemoveButtons() || this.canAddColumn);
  }

  hasRemoveButtons() {
    return !this.component.disableAddingRemovingRows &&
      !this.options.readOnly &&
      !this.disabled &&
      this.fullMode &&
      (this.dataValue.length > _.get(this.component, 'validate.minLength', 0));
  }

  hasTopSubmit() {
    return this.hasAddButton() && ['top', 'both'].includes(this.addAnotherPosition);
  }

  hasBottomSubmit() {
    return this.hasAddButton() && ['bottom', 'both'].includes(this.addAnotherPosition);
  }

  get canAddColumn() {
    return this.builderMode;
  }

  render() {
    const columns = this.getColumns();
    return super.render(this.renderTemplate('datagrid', {
      rows: this.getRows(),
      columns: columns,
      groups: this.hasRowGroups() ? this.getGroups() : [],
      visibleColumns: this.visibleColumns,
      hasToggle: _.get(this, 'component.groupToggle', false),
      hasHeader: this.hasHeader(),
      hasExtraColumn: this.hasExtraColumn(),
      hasAddButton: this.hasAddButton(),
      hasRemoveButtons: this.hasRemoveButtons(),
      hasTopSubmit: this.hasTopSubmit(),
      hasBottomSubmit: this.hasBottomSubmit(),
      hasGroups: this.hasRowGroups(),
      numColumns: columns.length + (this.hasExtraColumn() ? 1 : 0),
      datagridKey: this.datagridKey,
      allowReorder: this.allowReorder,
      builder: this.builderMode,
      canAddColumn: this.canAddColumn,
      placeholder: this.renderTemplate('builderPlaceholder', {
        position: this.componentComponents.length,
      }),
    }));
  }

  getRows() {
    return this.rows.map(row => {
      const components = {};
      _.each(row, (col, key) => {
        components[key] = col.render();
      });
      return components;
    });
  }

  getColumns() {
    return this.component.components.filter((comp) => {
      return (!this.visibleColumns.hasOwnProperty(comp.key) || this.visibleColumns[comp.key]);
    });
  }

  hasHeader() {
    return this.component.components.reduce((hasHeader, col) => {
      // If any of the components has a title and it isn't hidden, display the header.
      return hasHeader || ((col.label || col.title) && !col.hideLabel);
    }, false);
  }

  attach(element) {
    this.loadRefs(element, {
      [`${this.datagridKey}-row`]: 'multiple',
      [`${this.datagridKey}-tbody`]: 'single',
      [`${this.datagridKey}-addRow`]: 'multiple',
      [`${this.datagridKey}-removeRow`]: 'multiple',
      [`${this.datagridKey}-group-header`]: 'multiple',
      [this.datagridKey]: 'multiple',
    });

    if (this.allowReorder) {
      this.refs[`${this.datagridKey}-row`].forEach((row, index) => {
        row.dragInfo = { index };
      });

      this.dragula = dragula([this.refs[`${this.datagridKey}-tbody`]], {
        moves: (_draggedElement, _oldParent, clickedElement) => clickedElement.classList.contains('formio-drag-button')
      }).on('drop', this.onReorder.bind(this));
    }

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

    this.refs[`${this.datagridKey}-removeRow`].forEach((removeButton, index) => {
      this.addEventListener(removeButton, 'click', this.removeRow.bind(this, index));
    });

    if (this.hasRowGroups()) {
      this.refs.chunks = this.getRowChunks(this.getGroupSizes(), this.refs[`${this.datagridKey}-row`]);
      this.refs[`${this.datagridKey}-group-header`].forEach((header, index) => {
        this.addEventListener(header, 'click', () => this.toggleGroup(header, index));
      });
     }

    const columns = this.getColumns();
    const rowLength = columns.length;
    this.rows.forEach((row, rowIndex) => {
      let columnIndex = 0;
      columns.forEach((col) => {
        this.attachComponents(
          this.refs[this.datagridKey][(rowIndex * rowLength) + columnIndex],
          [this.rows[rowIndex][col.key]],
          this.component.components
        );
        columnIndex++;
      });
    });
    return super.attach(element);
  }

  onReorder(element, _target, _source, sibling) {
    if (!element.dragInfo || (sibling && !sibling.dragInfo)) {
      console.warn('There is no Drag Info available for either dragged or sibling element');
      return;
    }

    const oldPosition = element.dragInfo.index;
    //should drop at next sibling position; no next sibling means drop to last position
    const newPosition = sibling ? sibling.dragInfo.index : this.dataValue.length;
    const movedBelow = newPosition > oldPosition;
    const dataValue = fastCloneDeep(this.dataValue);
    const draggedRowData = dataValue[oldPosition];

    //insert element at new position
    dataValue.splice(newPosition, 0, draggedRowData);
    //remove element from old position (if was moved above, after insertion it's at +1 index)
    dataValue.splice(movedBelow ? oldPosition : oldPosition + 1, 1);

    //need to re-build rows to re-calculate indexes and other indexed fields for component instance (like rows for ex.)
    this.setValue(dataValue);
    this.redraw();
  }

  addRow() {
    const index = this.rows.length;

    // Handle length mismatch between rows and dataValue
    if (this.dataValue.length === index) {
      this.dataValue.push({});
    }

    this.rows[index] = this.createRowComponents(this.dataValue[index], index);
    this.checkConditions();
    this.redraw();
  }

  removeRow(index) {
    this.splice(index);
    const [row] = this.rows.splice(index, 1);
    _.each(row, (component) => this.removeComponent(component));
    this.redraw();
  }

  getRowValues() {
    return this.dataValue;
  }

  setRowComponentsData(rowIndex, rowData) {
    _.each(this.rows[rowIndex], (component) => {
      component.data = rowData;
    });
  }

  createRows(init) {
    let added = false;
    const rowValues = this.getRowValues();
    // Create any missing rows.
    rowValues.forEach((row, index) => {
      if (this.rows[index]) {
        this.setRowComponentsData(index, row);
      }
      else {
        this.rows[index] = this.createRowComponents(row, index);
        added = true;
      }
    });
    // Delete any extra rows.
    const removed = !!this.rows.splice(rowValues.length).length;
    if (!init && (added || removed)) {
      this.redraw();
    }
    return added;
  }

  createRowComponents(row, rowIndex) {
    const components = {};
    this.component.components.map((col, colIndex) => {
      const options = _.clone(this.options);
      options.name += `[${rowIndex}]`;
      options.row = `${rowIndex}-${colIndex}`;
      const component = this.createComponent(col, options, row);
      component.parentDisabled = !!this.disabled;
      component.rowIndex = rowIndex;
      component.inDataGrid = true;
      components[col.key] = component;
    });
    return components;
  }

  /**
   * Checks the validity of this datagrid.
   *
   * @param data
   * @param dirty
   * @return {*}
   */
  checkValidity(data, dirty, row, silentCheck) {
    data = data || this.rootValue;
    row = row || this.data;

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

    if (!this.checkComponentValidity(data, dirty, row, { silentCheck })) {
      return false;
    }

    return this.checkRows('checkValidity', data, dirty, true, silentCheck);
  }

  checkColumns(data, flags = {}) {
    data = data || this.rootValue;
    let show = false;

    if (!this.rows || !this.rows.length) {
      return { rebuild: false, show: false };
    }

    if (this.builderMode) {
      return { rebuild: false, show: true };
    }

    const visibility = {};

    const dataValue = this.dataValue;
    this.rows.forEach((row, rowIndex) => {
      _.each(row, (col, key) => {
        if (col && (typeof col.checkConditions === 'function')) {
          visibility[key] = !!visibility[key] ||
            (col.checkConditions(data, flags, dataValue[rowIndex]) && col.type !== 'hidden');
        }
      });
    });
    const rebuild = !_.isEqual(visibility, this.visibleColumns);
    _.each(visibility, (col) => {
      show |= col;
    });

    this.visibleColumns = visibility;
    return { rebuild, show };
  }

  checkComponentConditions(data, flags, row) {
    // If table isn't visible, don't bother calculating columns.
    if (!super.checkComponentConditions(data, flags, row)) {
      return false;
    }

    const { rebuild, show } = this.checkColumns(data, flags);
    // If a rebuild is needed, then rebuild the table.
    if (rebuild) {
      this.redraw();
    }

    // Return if this table should show.
    return show;
  }

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

    // Make sure we always have at least one row.
    // NOTE: Removing this will break "Public Configurations" in portal. ;)
    if (value && !value.length) {
      value.push({});
    }

    const changed = this.hasChanged(value, this.dataValue);
    this.dataValue = value;
    this.createRows();
    this.rows.forEach((row, rowIndex) => {
      if (value.length <= rowIndex) {
        return;
      }
      _.each(row, (col) => {
        col.rowIndex = rowIndex;
        this.setNestedValue(col, value[rowIndex], flags);
      });
    });

    this.updateOnChange(flags, changed);
    return changed;
  }

  restoreComponentsContext() {
    this.rows.forEach((row, index) => _.forIn(row, (component) => component.data = this.dataValue[index]));
  }

  getComponent(path, fn) {
    path = Array.isArray(path) ? path : [path];
    const [key, ...remainingPath] = path;
    let result = [];
    if (_.isNumber(key) && remainingPath.length) {
      const compKey = remainingPath.pop();
      result = this.rows[key][compKey];
      if (result && _.isFunction(fn)) {
        fn(result, this.getComponents());
      }
      if (remainingPath.length && 'getComponent' in result) {
        return result.getComponent(remainingPath, fn);
      }
      return result;
    }
    if (!_.isString(key)) {
      return result;
    }

    this.everyComponent((component, components) => {
      if (component.component.key === key) {
        let comp = component;
        if (remainingPath.length > 0 && 'getComponent' in component) {
          comp = component.getComponent(remainingPath, fn);
        }
        else if (fn) {
          fn(component, components);
        }

        result = result.concat(comp);
      }
    });

    return result.length > 0 ? result : null;
  }

  toggleGroup(element, index) {
    element.classList.toggle('collapsed');
    _.each(this.refs.chunks[index], row => {
      row.classList.toggle('hidden');
    });
  }
}
<commit_msg>Fix datagrid rows on editmode when default is set and allow override of forcing first row.<commit_after>import _ from 'lodash';
// Import from "dist" because it would require and "global" would not be defined in Angular apps.
import dragula from 'dragula/dist/dragula';
import NestedArrayComponent from '../_classes/nestedarray/NestedArrayComponent';
import { fastCloneDeep } from '../../utils/utils';

export default class DataGridComponent extends NestedArrayComponent {
  static schema(...extend) {
    return NestedArrayComponent.schema({
      label: 'Data Grid',
      key: 'dataGrid',
      type: 'datagrid',
      clearOnHide: true,
      input: true,
      tree: true,
      components: []
    }, ...extend);
  }

  static get builderInfo() {
    return {
      title: 'Data Grid',
      icon: 'th',
      group: 'data',
      documentation: 'http://help.form.io/userguide/#datagrid',
      weight: 30,
      schema: DataGridComponent.schema()
    };
  }

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

  init() {
    this.components = this.components || [];

    // Add new values based on minLength.
    this.rows = [];
    this.createRows(true);
    this.visibleColumns = {};
    this.checkColumns();
  }

  get dataValue() {
    const dataValue = super.dataValue;
    if (!dataValue || !Array.isArray(dataValue)) {
      return this.emptyValue;
    }
    return dataValue;
  }

  set dataValue(value) {
    super.dataValue = value;
  }

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

  get emptyValue() {
    return [{}];
  }

  get addAnotherPosition() {
    return _.get(this.component, 'addAnotherPosition', 'bottom');
  }

  get minLength() {
    if (this.hasRowGroups()) {
      return _.sum(this.getGroupSizes());
    }
    else {
      return _.get(this.component, 'validate.minLength', 0);
    }
  }

  get defaultValue() {
    // Ensure we have one and only one row in builder mode.
    if (this.builderMode) {
      return [{}];
    }
    const value = super.defaultValue;
    let defaultValue;

    if (Array.isArray(value)) {
      defaultValue = value;
    }
    else if (value && (typeof value === 'object')) {
      defaultValue = [value];
    }
    else {
      defaultValue = this.emptyValue;
    }

    for (let dIndex = defaultValue.length; dIndex < this.minLength; dIndex++) {
      defaultValue.push({});
    }

    return defaultValue;
  }

  set disabled(disabled) {
    super.disabled = disabled;
    _.each(this.refs[`${this.datagridKey}-addRow`], (button) => {
      button.disabled = disabled;
    });
    _.each(this.refs[`${this.datagridKey}-removeRow`], (button) => {
      button.disabled = disabled;
    });
  }

  get disabled() {
    return super.disabled;
  }

  get datagridKey() {
    return `datagrid-${this.key}`;
  }

  get allowReorder() {
    return !this.options.readOnly && _.get(this.component, 'reorder', false);
  }

  get iteratableRows() {
    return this.rows.map((row, index) => ({
      components: row,
      data: this.dataValue[index],
    }));
  }

  /**
   * Split rows into chunks.
   * @param {Number[]} groups - array of numbers where each item is size of group
   * @param {Array<T>} rows - rows collection
   * @return {Array<T[]>}
   */
  getRowChunks(groups, rows) {
    const [, chunks] = groups.reduce(
      ([startIndex, acc], size) => {
        const endIndex = startIndex +  size;
        return [endIndex, [...acc, [startIndex, endIndex]]];
      }, [0, []]
    );
    return chunks.map(range => _.slice(rows, ...range));
  }

  /**
   * Create groups object.
   * Each key in object represents index of first row in group.
   * @return {Object}
   */
  getGroups() {
    const groups = _.get(this.component, 'rowGroups', []);
    const sizes = _.map(groups, 'numberOfRows').slice(0, -1);
    const indexes = sizes.reduce((groupIndexes, size) => {
      const last = groupIndexes[groupIndexes.length - 1];
      return groupIndexes.concat(last + size);
    }, [0]);

    return groups.reduce(
      (gidxs, group, idx) => {
        return {
          ...gidxs,
          [indexes[idx]]: group
        };
      },
      {}
    );
  }

  /**
   * Retrun group sizes.
   * @return {Number[]}
   */
  getGroupSizes() {
    return _.map(_.get(this.component, 'rowGroups', []), 'numberOfRows');
  }

  hasRowGroups() {
    return _.get(this, 'component.enableRowGroups', false) && !this.builderMode;
  }

  totalRowsNumber(groups) {
    return _.sum(_.map(groups, 'numberOfRows'));
  }

  setStaticValue(n) {
    this.dataValue = _.range(n).map(() => ({}));
  }

  hasExtraColumn() {
    return (this.hasRemoveButtons() || this.canAddColumn);
  }

  hasRemoveButtons() {
    return !this.component.disableAddingRemovingRows &&
      !this.options.readOnly &&
      !this.disabled &&
      this.fullMode &&
      (this.dataValue.length > _.get(this.component, 'validate.minLength', 0));
  }

  hasTopSubmit() {
    return this.hasAddButton() && ['top', 'both'].includes(this.addAnotherPosition);
  }

  hasBottomSubmit() {
    return this.hasAddButton() && ['bottom', 'both'].includes(this.addAnotherPosition);
  }

  get canAddColumn() {
    return this.builderMode;
  }

  render() {
    const columns = this.getColumns();
    return super.render(this.renderTemplate('datagrid', {
      rows: this.getRows(),
      columns: columns,
      groups: this.hasRowGroups() ? this.getGroups() : [],
      visibleColumns: this.visibleColumns,
      hasToggle: _.get(this, 'component.groupToggle', false),
      hasHeader: this.hasHeader(),
      hasExtraColumn: this.hasExtraColumn(),
      hasAddButton: this.hasAddButton(),
      hasRemoveButtons: this.hasRemoveButtons(),
      hasTopSubmit: this.hasTopSubmit(),
      hasBottomSubmit: this.hasBottomSubmit(),
      hasGroups: this.hasRowGroups(),
      numColumns: columns.length + (this.hasExtraColumn() ? 1 : 0),
      datagridKey: this.datagridKey,
      allowReorder: this.allowReorder,
      builder: this.builderMode,
      canAddColumn: this.canAddColumn,
      placeholder: this.renderTemplate('builderPlaceholder', {
        position: this.componentComponents.length,
      }),
    }));
  }

  getRows() {
    return this.rows.map(row => {
      const components = {};
      _.each(row, (col, key) => {
        components[key] = col.render();
      });
      return components;
    });
  }

  getColumns() {
    return this.component.components.filter((comp) => {
      return (!this.visibleColumns.hasOwnProperty(comp.key) || this.visibleColumns[comp.key]);
    });
  }

  hasHeader() {
    return this.component.components.reduce((hasHeader, col) => {
      // If any of the components has a title and it isn't hidden, display the header.
      return hasHeader || ((col.label || col.title) && !col.hideLabel);
    }, false);
  }

  attach(element) {
    this.loadRefs(element, {
      [`${this.datagridKey}-row`]: 'multiple',
      [`${this.datagridKey}-tbody`]: 'single',
      [`${this.datagridKey}-addRow`]: 'multiple',
      [`${this.datagridKey}-removeRow`]: 'multiple',
      [`${this.datagridKey}-group-header`]: 'multiple',
      [this.datagridKey]: 'multiple',
    });

    if (this.allowReorder) {
      this.refs[`${this.datagridKey}-row`].forEach((row, index) => {
        row.dragInfo = { index };
      });

      this.dragula = dragula([this.refs[`${this.datagridKey}-tbody`]], {
        moves: (_draggedElement, _oldParent, clickedElement) => clickedElement.classList.contains('formio-drag-button')
      }).on('drop', this.onReorder.bind(this));
    }

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

    this.refs[`${this.datagridKey}-removeRow`].forEach((removeButton, index) => {
      this.addEventListener(removeButton, 'click', this.removeRow.bind(this, index));
    });

    if (this.hasRowGroups()) {
      this.refs.chunks = this.getRowChunks(this.getGroupSizes(), this.refs[`${this.datagridKey}-row`]);
      this.refs[`${this.datagridKey}-group-header`].forEach((header, index) => {
        this.addEventListener(header, 'click', () => this.toggleGroup(header, index));
      });
     }

    const columns = this.getColumns();
    const rowLength = columns.length;
    this.rows.forEach((row, rowIndex) => {
      let columnIndex = 0;
      columns.forEach((col) => {
        this.attachComponents(
          this.refs[this.datagridKey][(rowIndex * rowLength) + columnIndex],
          [this.rows[rowIndex][col.key]],
          this.component.components
        );
        columnIndex++;
      });
    });
    return super.attach(element);
  }

  onReorder(element, _target, _source, sibling) {
    if (!element.dragInfo || (sibling && !sibling.dragInfo)) {
      console.warn('There is no Drag Info available for either dragged or sibling element');
      return;
    }

    const oldPosition = element.dragInfo.index;
    //should drop at next sibling position; no next sibling means drop to last position
    const newPosition = sibling ? sibling.dragInfo.index : this.dataValue.length;
    const movedBelow = newPosition > oldPosition;
    const dataValue = fastCloneDeep(this.dataValue);
    const draggedRowData = dataValue[oldPosition];

    //insert element at new position
    dataValue.splice(newPosition, 0, draggedRowData);
    //remove element from old position (if was moved above, after insertion it's at +1 index)
    dataValue.splice(movedBelow ? oldPosition : oldPosition + 1, 1);

    //need to re-build rows to re-calculate indexes and other indexed fields for component instance (like rows for ex.)
    this.setValue(dataValue);
    this.redraw();
  }

  addRow() {
    const index = this.rows.length;

    // Handle length mismatch between rows and dataValue
    if (this.dataValue.length === index) {
      this.dataValue.push({});
    }

    this.rows[index] = this.createRowComponents(this.dataValue[index], index);
    this.checkConditions();
    this.redraw();
  }

  removeRow(index) {
    this.splice(index);
    const [row] = this.rows.splice(index, 1);
    _.each(row, (component) => this.removeComponent(component));
    this.redraw();
  }

  getRowValues() {
    return this.dataValue;
  }

  setRowComponentsData(rowIndex, rowData) {
    _.each(this.rows[rowIndex], (component) => {
      component.data = rowData;
    });
  }

  createRows(init) {
    let added = false;
    const rowValues = this.getRowValues();
    // Create any missing rows.
    rowValues.forEach((row, index) => {
      if (this.rows[index]) {
        this.setRowComponentsData(index, row);
      }
      else {
        this.rows[index] = this.createRowComponents(row, index);
        added = true;
      }
    });
    // Delete any extra rows.
    const removed = !!this.rows.splice(rowValues.length).length;
    if (!init && (added || removed)) {
      this.redraw();
    }
    return added;
  }

  createRowComponents(row, rowIndex) {
    const components = {};
    this.component.components.map((col, colIndex) => {
      const options = _.clone(this.options);
      options.name += `[${rowIndex}]`;
      options.row = `${rowIndex}-${colIndex}`;
      const component = this.createComponent(col, options, row);
      component.parentDisabled = !!this.disabled;
      component.rowIndex = rowIndex;
      component.inDataGrid = true;
      components[col.key] = component;
    });
    return components;
  }

  /**
   * Checks the validity of this datagrid.
   *
   * @param data
   * @param dirty
   * @return {*}
   */
  checkValidity(data, dirty, row, silentCheck) {
    data = data || this.rootValue;
    row = row || this.data;

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

    if (!this.checkComponentValidity(data, dirty, row, { silentCheck })) {
      return false;
    }

    return this.checkRows('checkValidity', data, dirty, true, silentCheck);
  }

  checkColumns(data, flags = {}) {
    data = data || this.rootValue;
    let show = false;

    if (!this.rows || !this.rows.length) {
      return { rebuild: false, show: false };
    }

    if (this.builderMode) {
      return { rebuild: false, show: true };
    }

    const visibility = {};

    const dataValue = this.dataValue;
    this.rows.forEach((row, rowIndex) => {
      _.each(row, (col, key) => {
        if (col && (typeof col.checkConditions === 'function')) {
          visibility[key] = !!visibility[key] ||
            (col.checkConditions(data, flags, dataValue[rowIndex]) && col.type !== 'hidden');
        }
      });
    });
    const rebuild = !_.isEqual(visibility, this.visibleColumns);
    _.each(visibility, (col) => {
      show |= col;
    });

    this.visibleColumns = visibility;
    return { rebuild, show };
  }

  checkComponentConditions(data, flags, row) {
    // If table isn't visible, don't bother calculating columns.
    if (!super.checkComponentConditions(data, flags, row)) {
      return false;
    }

    const { rebuild, show } = this.checkColumns(data, flags);
    // If a rebuild is needed, then rebuild the table.
    if (rebuild) {
      this.redraw();
    }

    // Return if this table should show.
    return show;
  }

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

    // Make sure we always have at least one row.
    // NOTE: Removing this will break "Public Configurations" in portal. ;)
    if (value && !value.length && !this.component.noFirstRow) {
      value.push({});
    }

    const changed = this.hasChanged(value, this.dataValue);
    this.dataValue = value;
    this.createRows();
    this.rows.forEach((row, rowIndex) => {
      if (value.length <= rowIndex) {
        return;
      }
      _.each(row, (col) => {
        col.rowIndex = rowIndex;
        this.setNestedValue(col, value[rowIndex], flags);
      });
    });

    this.updateOnChange(flags, changed);
    return changed;
  }

  restoreComponentsContext() {
    this.rows.forEach((row, index) => _.forIn(row, (component) => component.data = this.dataValue[index]));
  }

  getComponent(path, fn) {
    path = Array.isArray(path) ? path : [path];
    const [key, ...remainingPath] = path;
    let result = [];
    if (_.isNumber(key) && remainingPath.length) {
      const compKey = remainingPath.pop();
      result = this.rows[key][compKey];
      if (result && _.isFunction(fn)) {
        fn(result, this.getComponents());
      }
      if (remainingPath.length && 'getComponent' in result) {
        return result.getComponent(remainingPath, fn);
      }
      return result;
    }
    if (!_.isString(key)) {
      return result;
    }

    this.everyComponent((component, components) => {
      if (component.component.key === key) {
        let comp = component;
        if (remainingPath.length > 0 && 'getComponent' in component) {
          comp = component.getComponent(remainingPath, fn);
        }
        else if (fn) {
          fn(component, components);
        }

        result = result.concat(comp);
      }
    });

    return result.length > 0 ? result : null;
  }

  toggleGroup(element, index) {
    element.classList.toggle('collapsed');
    _.each(this.refs.chunks[index], row => {
      row.classList.toggle('hidden');
    });
  }
}
<|endoftext|>


<commit_before>// Variables

var tag = "";
var mouseIsDown = false;
var draggedItem = null;
var road = null;
var roadImg, carImg;
var items = [];
var ctx,diffx,diffy;
var roadMode = false;
var roadModeEnd = false;

var roadPos1 = new Object();
var roadPos2 = new Object();

// Setup

$(document).ready(function () {
	console.log("testing");

	ctx = document.getElementById('canvas').getContext('2d');
	var cvs = document.getElementById('canvas');
	cvs.width  = $(window).width();
	cvs.height  = $(window).height()-50;
	//canvas.addEventListener("pointerdown", mouseDown, false);
	//canvas.addEventListener("pointermove", mouseMove, false);
	//canvas.addEventListener("pointerup", mouseUp, false);

	canvas.addEventListener("touchstart", touchDown, false);
	//canvas.addEventListener("touchmove", touchMove, false);
	canvas.addEventListener("touchend", touchUp, false);
	canvas.addEventListener("touchcancel", touchCancel, false);

	// Load items
	carImg = new Image();
	carImg.src = "/assets/redcartop.png"
	setPos(carImg,0,0);
	carImg.shouldDragSimple = true;

	roadImg = new Image();
	roadImg.src = "/assets/road.png"
	setPos(roadImg,300,0);
	roadImg.shouldDragComplete = true;

	items.push(roadImg);
	items.push(carImg);

	drawStatics();
	items.forEach(function (item) {
		item.onload = function () {
			ctx.drawImage(item,item.posx,item.posy);
		}
	});
});

// Mouse Events

function touchDown(event) {
	event.preventDefault();
	$('#info').text("T-DOWN "+event.targetTouches[0].pageX+","+event.targetTouches[0].pageY);
	if (!roadMode) {
		roadPos1.x = event.targetTouches[0].pageX;
		roadPos1.y = event.targetTouches[0].pageY;
		roadMode = true;
	} else if (roadMode) {
		roadPos2.x = event.targetTouches[0].pageX;
		roadPos2.y = event.targetTouches[0].pageY;

		road = new Object();
		road.isRoad = true;
		road.pos1 = roadPos1;
		road.pos2 = roadPos2;
		items.push(road);
		roadMode = false;
		drawItems();
	}
}

function touchMove (event) {
	event.preventDefault();
	$('#info').text("T-MOVING "+event.targetTouches[0].pageX+","+event.targetTouches[0].pageY);
}

function touchUp (event) {
	$('#info').text("T-UP "+event.targetTouches[0].pageX+","+event.targetTouches[0].pageY);
}

function touchCancel (event) {
	$('#info').text("T-CANCEL "+event.targetTouches[0].pageX+","+event.targetTouches[0].pageY);
}

// Helper Methods

function isIn (event,element) {
	if (event.x < element.posx+element.width && event.x > element.posx && event.y < element.posy+element.height && event.y > element.posy) {
		return true;
	}
	return false;
}

function setPos (element,x,y) {
	element.posx = x;
	element.posy = y;
}

function drawStatics() {
	ctx.strokeRect(0,0,$(window).width(),150);
	ctx.strokeRect(0,150,$(window).width(),$(window).height()-50);
}

function drawItems() {
	items.forEach(function (item) {
		if (item.isRoad) {
			ctx.moveTo(item.pos1.x, item.pos1.y);
			ctx.lineTo(item.pos2.x, item.pos2.y);
			ctx.stroke();
		} else {
			ctx.drawImage(item,item.posx,item.posy);
		}
	});
}

Array.prototype.remove = function (from, to) { // Remove element code snippet by John Resig, creator of jQuery
  var rest = this.slice((to || from) + 1 || this.length);
  this.length = from < 0 ? this.length + from : from;
  return this.push.apply(this, rest);
};

function startDragging (event,item) {
	draggedItem = item;
	items.remove(items.indexOf(item));
	items.push(item);
	diffx = event.x - draggedItem.posx;
	diffy = event.y - draggedItem.posy;
}

function startDraggingTouch (event,item) {
	draggedItem = item;
	items.remove(items.indexOf(item));
	items.push(item);
	diffx = event.targetTouches[0].pageX - draggedItem.posx;
	diffy = event.targetTouches[0].pageY - draggedItem.posy;
}

function drag (event,selected) {
	setPos(selected,event.x-diffx,event.y-diffy);
	ctx.clearRect(0, 0, canvas.width, canvas.height);
	drawStatics();
	drawItems();
}

function dragTouch (event,selected) {
	setPos(selected,event.targetTouches[0].pageX-diffx,event.targetTouches[0].pageY-diffy);
	ctx.clearRect(0, 0, canvas.width, canvas.height);
	drawStatics();
	drawItems();
}<commit_msg>Added touch support.<commit_after>// Variables

var tag = "";
var mouseIsDown = false;
var draggedItem = null;
var road = null;
var roadImg, carImg;
var items = [];
var ctx,diffx,diffy;
var roadMode = false;
var roadModeEnd = false;

var roadPos1 = new Object();
var roadPos2 = new Object();

// Setup

$(document).ready(function () {
	console.log("testing");

	ctx = document.getElementById('canvas').getContext('2d');
	var cvs = document.getElementById('canvas');
	cvs.width  = $(window).width();
	cvs.height  = $(window).height()-50;

	canvas.addEventListener("touchstart", touchDown, false);

	// Load items
	carImg = new Image();
	carImg.src = "/assets/redcartop.png"
	setPos(carImg,0,0);
	carImg.shouldDragSimple = true;

	roadImg = new Image();
	roadImg.src = "/assets/road.png"
	setPos(roadImg,300,0);
	roadImg.shouldDragComplete = true;

	items.push(roadImg);
	items.push(carImg);

	drawStatics();
	items.forEach(function (item) {
		item.onload = function () {
			ctx.drawImage(item,item.posx,item.posy);
		}
	});
});

// Mouse Events

function touchDown(event) {
	event.preventDefault();
	$('#info').text("T-DOWN "+event.targetTouches[0].pageX+","+event.targetTouches[0].pageY);
	if (!roadMode) {
		roadPos1.x = event.targetTouches[0].pageX;
		roadPos1.y = event.targetTouches[0].pageY;
		roadMode = true;
	} else if (roadMode) {
		roadPos2.x = event.targetTouches[0].pageX;
		roadPos2.y = event.targetTouches[0].pageY;

		road = new Object();
		road.isRoad = true;
		slope = (roadPos2.y-roadPos1.y)/(roadPos2.x-roadPos1.x);
		if (slope < .57) {
			roadPos1.y = (roadPos2.y+roadPos1.y)/2;
			roadPos2.y = (roadPos2.y+roadPos1.y)/2;
		} else if (slope < 1.73) {
			ydiff = roadPos2.y-roadPos1.y;
			roadPos2.x = roadPos1.x+ydiff;
		} else {
			roadPos1.x = (roadPos2.x+roadPos1.x)/2;
			roadPos2.x = (roadPos2.x+roadPos1.x)/2;
		}
		road.pos1 = roadPos1;
		road.pos2 = roadPos2;
		items.push(road);
		roadMode = false;
		drawItems();
	}
}

// Helper Methods

function isIn (event,element) {
	if (event.x < element.posx+element.width && event.x > element.posx && event.y < element.posy+element.height && event.y > element.posy) {
		return true;
	}
	return false;
}

function setPos (element,x,y) {
	element.posx = x;
	element.posy = y;
}

function drawStatics() {
	ctx.strokeRect(0,0,$(window).width(),150);
	ctx.strokeRect(0,150,$(window).width(),$(window).height()-50);
}

function drawItems() {
	items.forEach(function (item) {
		if (item.isRoad) {
			ctx.moveTo(item.pos1.x, item.pos1.y);
			ctx.lineTo(item.pos2.x, item.pos2.y);
			ctx.stroke();
		} else {
			ctx.drawImage(item,item.posx,item.posy);
		}
	});
}

Array.prototype.remove = function (from, to) { // Remove element code snippet by John Resig, creator of jQuery
  var rest = this.slice((to || from) + 1 || this.length);
  this.length = from < 0 ? this.length + from : from;
  return this.push.apply(this, rest);
};

function startDragging (event,item) {
	draggedItem = item;
	items.remove(items.indexOf(item));
	items.push(item);
	diffx = event.x - draggedItem.posx;
	diffy = event.y - draggedItem.posy;
}

function startDraggingTouch (event,item) {
	draggedItem = item;
	items.remove(items.indexOf(item));
	items.push(item);
	diffx = event.targetTouches[0].pageX - draggedItem.posx;
	diffy = event.targetTouches[0].pageY - draggedItem.posy;
}

function drag (event,selected) {
	setPos(selected,event.x-diffx,event.y-diffy);
	ctx.clearRect(0, 0, canvas.width, canvas.height);
	drawStatics();
	drawItems();
}

function dragTouch (event,selected) {
	setPos(selected,event.targetTouches[0].pageX-diffx,event.targetTouches[0].pageY-diffy);
	ctx.clearRect(0, 0, canvas.width, canvas.height);
	drawStatics();
	drawItems();
}<|endoftext|>


<commit_before>

qx.Class.define("gosa.ui.form.WindowListItem", {
  extend: qx.ui.form.ListItem,

  /*
  *****************************************************************************
     PROPERTIES
  *****************************************************************************
  */
  properties : {
    appearance :
    {
      refine : true,
      init : "gosa-listitem-window"
    },

    window: {
      check: "qx.ui.window.Window",
      nullable: true
    },

    object: {
      check: "gosa.proxy.Object",
      async: true,
      nullable: true,
      apply: "_applyObject"
    }
  },

  /*
  *****************************************************************************
     MEMBERS
  *****************************************************************************
  */
  members : {
    _applyObject: function(object) {
      if (object) {
        // try to get the search result for this dn, to get the mapped title/icon values
        return gosa.io.Rpc.getInstance().cA("getObjectSearchItem", object.dn)
        .then(function(result) {
          this.setLabel(result.title);
          this.setIcon(result.icon ? result.icon : gosa.util.Icons.getIconByType(result.tag, 22));
        }, this)
        .catch(function(error) {
          this.error(error);
          // fallback
          var dnPart = qx.util.StringSplit.split(qx.util.StringSplit.split(object.dn, "\,")[0], "=")[1];
          this.setLabel(dnPart);
        }, this);

      }
    }
  }
});<commit_msg>fix the unrejected promis error<commit_after>

qx.Class.define("gosa.ui.form.WindowListItem", {
  extend: qx.ui.form.ListItem,

  /*
  *****************************************************************************
     PROPERTIES
  *****************************************************************************
  */
  properties : {
    appearance :
    {
      refine : true,
      init : "gosa-listitem-window"
    },

    window: {
      check: "qx.ui.window.Window",
      nullable: true
    },

    object: {
      check: "gosa.proxy.Object",
      nullable: true,
      apply: "_applyObject"
    }
  },

  /*
  *****************************************************************************
     MEMBERS
  *****************************************************************************
  */
  members : {
    _applyObject: function(object) {
      if (object) {
        // we need to break out of the property apply chain to allow promises to be used
        // otherwise we get a warning about a created but not returned promise
        new qx.util.DeferredCall(function() {
          // try to get the search result for this dn, to get the mapped title/icon values
          gosa.io.Rpc.getInstance().cA("getObjectSearchItem", object.dn)
          .then(function(result) {
            this.setLabel(result.title);
            this.setIcon(result.icon ? result.icon : gosa.util.Icons.getIconByType(result.tag, 22));
          }, this)
          .catch(function(error) {
            this.error(error);
            // fallback
            var dnPart = qx.util.StringSplit.split(qx.util.StringSplit.split(object.dn, "\,")[0], "=")[1];
            this.setLabel(dnPart);
          }, this);
        }, this).schedule();
      }
    }
  }
});<|endoftext|>


<commit_before>// Variables

var tag = "";
var mouseIsDown = false;
var draggedItem = null;
var road = null;
var roadImg, carImg;
var items = [];
var ctx,diffx,diffy;
var roadMode = false;
var roadModeEnd = false;

var roadPos1 = new Object();
var roadPos2 = new Object();

// Setup

$(document).ready(function () {
	console.log("testing");

	ctx = document.getElementById('canvas').getContext('2d');
	var cvs = document.getElementById('canvas');
	cvs.width  = $(window).width();
	cvs.height  = $(window).height()-50;
	//canvas.addEventListener("pointerdown", mouseDown, false);
	//canvas.addEventListener("pointermove", mouseMove, false);
	//canvas.addEventListener("pointerup", mouseUp, false);

	canvas.addEventListener("touchstart", touchDown, false);
	canvas.addEventListener("touchmove", touchMove, false);
	canvas.addEventListener("touchend", touchUp, false);
	canvas.addEventListener("touchcancel", touchCancel, false);

	canvas.addEventListener("mousedown", mouseDown, false);
	canvas.addEventListener("mousemove", mouseMove, false);
	canvas.addEventListener("mouseup", mouseUp, false);

	// Load items
	carImg = new Image();
	carImg.src = "/assets/redcartop.png"
	setPos(carImg,0,0);
	carImg.shouldDragSimple = true;

	roadImg = new Image();
	roadImg.src = "/assets/road.png"
	setPos(roadImg,300,0);
	roadImg.shouldDragComplete = true;

	items.push(roadImg);
	items.push(carImg);

	drawStatics();
	items.forEach(function (item) {
		item.onload = function () {
			ctx.drawImage(item,item.posx,item.posy);
		}
	});
});

// Mouse Events

function mouseDown(event) {
	$('#info').text("DOWN "+event.x+","+event.y);
}

function mouseMove (event) {
	$('#info').text("MOVING "+event.x+","+event.y);
}

function mouseUp (event) {
	$('#info').text("UP "+event.x+","+event.y);
}

function touchDown(event) {
	$('#info').text("T-DOWN "+event.targetTouches[0].pageX+","+event.targetTouches[0].pageY);
}

function touchMove (event) {
	$('#info').text("T-MOVING "+event.targetTouches[0].pageX+","+event.targetTouches[0].pageY);
}

function touchUp (event) {
	$('#info').text("T-UP "+event.targetTouches[0].pageX+","+event.targetTouches[0].pageY);
}

function touchCancel (event) {
	$('#info').text("T-CANCEL "+event.targetTouches[0].pageX+","+event.targetTouches[0].pageY);
}

// Helper Methods

function isIn (event,element) {
	if (event.x < element.posx+element.width && event.x > element.posx && event.y < element.posy+element.height && event.y > element.posy) {
		return true;
	}
	return false;
}

function setPos (element,x,y) {
	element.posx = x;
	element.posy = y;
}

function drawStatics() {
	ctx.strokeRect(0,0,$(window).width(),150);
	ctx.strokeRect(0,150,$(window).width(),$(window).height()-50);
}

function drawItems() {
	items.forEach(function (item) {
		if (item.isRoad) {
			ctx.moveTo(item.pos1.x, item.pos1.y);
			ctx.lineTo(item.pos2.x, item.pos2.y);
			ctx.stroke();
		} else {
			ctx.drawImage(item,item.posx,item.posy);
		}
	});
}

Array.prototype.remove = function (from, to) { // Remove element code snippet by John Resig, creator of jQuery
  var rest = this.slice((to || from) + 1 || this.length);
  this.length = from < 0 ? this.length + from : from;
  return this.push.apply(this, rest);
};

function startDragging (event,item) {
	draggedItem = item;
	items.remove(items.indexOf(item));
	items.push(item);
	diffx = event.x - draggedItem.posx;
	diffy = event.y - draggedItem.posy;
}

function startDraggingTouch (event,item) {
	draggedItem = item;
	items.remove(items.indexOf(item));
	items.push(item);
	diffx = event.targetTouches[0].pageX - draggedItem.posx;
	diffy = event.targetTouches[0].pageY - draggedItem.posy;
}

function drag (event,selected) {
	setPos(selected,event.x-diffx,event.y-diffy);
	ctx.clearRect(0, 0, canvas.width, canvas.height);
	drawStatics();
	drawItems();
}

function dragTouch (event,selected) {
	setPos(selected,event.targetTouches[0].pageX-diffx,event.targetTouches[0].pageY-diffy);
	ctx.clearRect(0, 0, canvas.width, canvas.height);
	drawStatics();
	drawItems();
}<commit_msg>Added touch support.<commit_after>// Variables

var tag = "";
var mouseIsDown = false;
var draggedItem = null;
var road = null;
var roadImg, carImg;
var items = [];
var ctx,diffx,diffy;
var roadMode = false;
var roadModeEnd = false;

var roadPos1 = new Object();
var roadPos2 = new Object();

// Setup

$(document).ready(function () {
	console.log("testing");

	ctx = document.getElementById('canvas').getContext('2d');
	var cvs = document.getElementById('canvas');
	cvs.width  = $(window).width();
	cvs.height  = $(window).height()-50;
	//canvas.addEventListener("pointerdown", mouseDown, false);
	//canvas.addEventListener("pointermove", mouseMove, false);
	//canvas.addEventListener("pointerup", mouseUp, false);

	canvas.addEventListener("touchstart", touchDown, false);
	canvas.addEventListener("touchmove", touchMove, false);
	canvas.addEventListener("touchend", touchUp, false);
	canvas.addEventListener("touchcancel", touchCancel, false);

	canvas.addEventListener("mousedown", mouseDown, false);
	canvas.addEventListener("mousemove", mouseMove, false);
	canvas.addEventListener("mouseup", mouseUp, false);

	// Load items
	carImg = new Image();
	carImg.src = "/assets/redcartop.png"
	setPos(carImg,0,0);
	carImg.shouldDragSimple = true;

	roadImg = new Image();
	roadImg.src = "/assets/road.png"
	setPos(roadImg,300,0);
	roadImg.shouldDragComplete = true;

	items.push(roadImg);
	items.push(carImg);

	drawStatics();
	items.forEach(function (item) {
		item.onload = function () {
			ctx.drawImage(item,item.posx,item.posy);
		}
	});
});

// Mouse Events

function mouseDown(event) {
	$('#info').text("DOWN "+event.x+","+event.y);
}

function mouseMove (event) {
	$('#info').text("MOVING "+event.x+","+event.y);
}

function mouseUp (event) {
	$('#info').text("UP "+event.x+","+event.y);
}

function touchDown(event) {
	event.preventDefault();
	$('#info').text("T-DOWN "+event.targetTouches[0].pageX+","+event.targetTouches[0].pageY);
}

function touchMove (event) {
	event.preventDefault();
	$('#info').text("T-MOVING "+event.targetTouches[0].pageX+","+event.targetTouches[0].pageY);
}

function touchUp (event) {
	event.preventDefault();
	$('#info').text("T-UP "+event.targetTouches[0].pageX+","+event.targetTouches[0].pageY);
}

function touchCancel (event) {
	event.preventDefault();
	$('#info').text("T-CANCEL "+event.targetTouches[0].pageX+","+event.targetTouches[0].pageY);
}

// Helper Methods

function isIn (event,element) {
	if (event.x < element.posx+element.width && event.x > element.posx && event.y < element.posy+element.height && event.y > element.posy) {
		return true;
	}
	return false;
}

function setPos (element,x,y) {
	element.posx = x;
	element.posy = y;
}

function drawStatics() {
	ctx.strokeRect(0,0,$(window).width(),150);
	ctx.strokeRect(0,150,$(window).width(),$(window).height()-50);
}

function drawItems() {
	items.forEach(function (item) {
		if (item.isRoad) {
			ctx.moveTo(item.pos1.x, item.pos1.y);
			ctx.lineTo(item.pos2.x, item.pos2.y);
			ctx.stroke();
		} else {
			ctx.drawImage(item,item.posx,item.posy);
		}
	});
}

Array.prototype.remove = function (from, to) { // Remove element code snippet by John Resig, creator of jQuery
  var rest = this.slice((to || from) + 1 || this.length);
  this.length = from < 0 ? this.length + from : from;
  return this.push.apply(this, rest);
};

function startDragging (event,item) {
	draggedItem = item;
	items.remove(items.indexOf(item));
	items.push(item);
	diffx = event.x - draggedItem.posx;
	diffy = event.y - draggedItem.posy;
}

function startDraggingTouch (event,item) {
	draggedItem = item;
	items.remove(items.indexOf(item));
	items.push(item);
	diffx = event.targetTouches[0].pageX - draggedItem.posx;
	diffy = event.targetTouches[0].pageY - draggedItem.posy;
}

function drag (event,selected) {
	setPos(selected,event.x-diffx,event.y-diffy);
	ctx.clearRect(0, 0, canvas.width, canvas.height);
	drawStatics();
	drawItems();
}

function dragTouch (event,selected) {
	setPos(selected,event.targetTouches[0].pageX-diffx,event.targetTouches[0].pageY-diffy);
	ctx.clearRect(0, 0, canvas.width, canvas.height);
	drawStatics();
	drawItems();
}<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import Codemirror from 'react-codemirror';

import 'codemirror/addon/mode/simple';
import 'codemirror/addon/selection/active-line';

export default React.createClass({
  getInitialState() {
    return {
      code: 'lamp = aan\n\nals lamp = aan dan radio = uit',
      mode: 'ct-3000',
    };
  },
  updateCode(newCode) {
    this.setState({
      code: newCode
    });
  },
  render () {
    var options = {
      lineNumbers: true,
      lineWrapping: true,
      indentUnit: 2,
      tabSize: 2,
      theme: 'monokai',
      styleActiveLine: true,
      mode: this.state.mode
    };
    return (

        <Codemirror ref="editor" value={this.state.code} onChange={this.updateCode} options={options} />

    );
  }
});

import CMInstance from 'codemirror';
CMInstance.defineSimpleMode('ct-3000', {
  // The start state contains the rules that are intially used
  start: [
    // The regex matches the token, the token property contains the type
    {regex: /"(?:[^\\]|\\.)*?"/, token: 'string'},
    // You can match multiple tokens at once. Note that the captured
    // groups must span the whole string in this case
    {regex: /(function)(\s+)([a-z$][\w$]*)/,
     token: ['keyword', null, 'variable-2']},
    // Rules are matched in the order in which they appear, so there is
    // no ambiguity between this one and the one above
    {regex: /(?:function|var|return|if|for|while|else|do|this|als|dan)\b/,
     token: 'keyword'},
    {regex: /true|false|null|undefined|aan|uit/, token: 'atom'},
    {regex: /0x[a-f\d]+|[-+]?(?:\.\d+|\d+\.?\d*)(?:e[-+]?\d+)?/i,
     token: 'number'},
    {regex: /\/\/.*/, token: 'comment'},
    {regex: /\/(?:[^\\]|\\.)*?\//, token: 'variable-3'},
    // A next property will cause the mode to move to a different state
    {regex: /\/\*/, token: 'comment', next: 'comment'},
    {regex: /[-+\/*=<>!]+/, token: 'operator'},
    // indent and dedent properties guide autoindentation
    {regex: /[\{\[\(]/, indent: true},
    {regex: /[\}\]\)]/, dedent: true},
    {regex: /[a-z$][\w$]*/, token: 'variable'},
    // You can embed other modes with the mode property. This rule
    // causes all code between << and >> to be highlighted with the XML
    // mode.
    {regex: /<</, token: 'meta', mode: {spec: 'xml', end: />>/}}
  ],
  // The multi-line comment state.
  comment: [
    {regex: /.*?\*\//, token: 'comment', next: 'start'},
    {regex: /.*/, token: 'comment'}
  ],
  // The meta property contains global information about the mode. It
  // can contain properties like lineComment, which are supported by
  // all modes, and also directives like dontIndentStates, which are
  // specific to simple modes.
  meta: {
    dontIndentStates: ['comment'],
    lineComment: '//'
  }
});
<commit_msg>Add more demo code & define more language ... things.<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import Codemirror from 'react-codemirror';

import 'codemirror/addon/mode/simple';
import 'codemirror/addon/selection/active-line';

export default React.createClass({
  getInitialState() {
    return {
      code: `// Toekenningen
lamp = aan
bericht = "test"

// Vergelijkingen
als lamp = aan dan radio = uit
als deur = open en lamp = uit dan bericht = "ALARM!"`,
      mode: 'ct-3000',
    };
  },
  updateCode(newCode) {
    this.setState({
      code: newCode
    });
  },
  render () {
    var options = {
      lineNumbers: true,
      lineWrapping: true,
      indentUnit: 2,
      tabSize: 2,
      theme: 'monokai',
      styleActiveLine: true,
      mode: this.state.mode
    };
    return (

        <Codemirror ref="editor" value={this.state.code} onChange={this.updateCode} options={options} />

    );
  }
});

import CMInstance from 'codemirror';
CMInstance.defineSimpleMode('ct-3000', {
  // The start state contains the rules that are intially used
  start: [
    // Prop names
    {regex: /(?:lamp|radio|deur|bericht)\b/, token: 'variable-3'},
    // Keywords
    {regex: /(?:als|dan|en)\b/, token: 'keyword'},
    // Value enums
    {regex: /(?:aan|uit|open|dicht)\b/, token: 'atom'},

    // The regex matches the token, the token property contains the type
    {regex: /"(?:[^\\]|\\.)*?"/, token: 'string'},
    // You can match multiple tokens at once. Note that the captured
    // groups must span the whole string in this case
    {regex: /(function)(\s+)([a-z$][\w$]*)/,
     token: ['keyword', null, 'variable-2']},
    // Rules are matched in the order in which they appear, so there is
    // no ambiguity between this one and the one above
    {regex: /(?:function|var|return|if|for|while|else|do|this)\b/,
     token: 'keyword'},
    {regex: /true|false|null|undefined/, token: 'atom'},
    {regex: /0x[a-f\d]+|[-+]?(?:\.\d+|\d+\.?\d*)(?:e[-+]?\d+)?/i,
     token: 'number'},
    {regex: /\/\/.*/, token: 'comment'},
    {regex: /\/(?:[^\\]|\\.)*?\//, token: 'variable-3'},
    // A next property will cause the mode to move to a different state
    {regex: /\/\*/, token: 'comment', next: 'comment'},
    {regex: /[-+\/*=<>!]+/, token: 'operator'},
    // indent and dedent properties guide autoindentation
    {regex: /[\{\[\(]/, indent: true},
    {regex: /[\}\]\)]/, dedent: true},
    {regex: /[a-z$][\w$]*/, token: 'variable'},
    // You can embed other modes with the mode property. This rule
    // causes all code between << and >> to be highlighted with the XML
    // mode.
    {regex: /<</, token: 'meta', mode: {spec: 'xml', end: />>/}}
  ],
  // The multi-line comment state.
  comment: [
    {regex: /.*?\*\//, token: 'comment', next: 'start'},
    {regex: /.*/, token: 'comment'}
  ],
  // The meta property contains global information about the mode. It
  // can contain properties like lineComment, which are supported by
  // all modes, and also directives like dontIndentStates, which are
  // specific to simple modes.
  meta: {
    dontIndentStates: ['comment'],
    lineComment: '//'
  }
});
<|endoftext|>


<commit_before>"use strict";

const $ = require("jquery");
const escape = require("css.escape");
const viewport = $("#viewport");

var serverHash = -1; // eslint-disable-line no-var
var lastMessageId = -1; // eslint-disable-line no-var

module.exports = {
	// Same value as media query in CSS that forces sidebars to become overlays
	mobileViewportPixels: 768,
	inputCommands: {collapse, expand, join},
	findCurrentNetworkChan,
	serverHash,
	lastMessageId,
	confirmExit,
	scrollIntoViewNicely,
	hasRoleInChannel,
	move,
	closeChan,
	resetHeight,
	toggleNotificationMarkers,
	updateTitle,
	togglePasswordField,
	requestIdleCallback,
	togglePreviewMoreButtonsIfNeeded,
};

function findCurrentNetworkChan(name) {
	name = name.toLowerCase();

	return $(".network .chan.active")
		.parent(".network")
		.find(".chan")
		.filter(function() {
			return $(this).attr("aria-label").toLowerCase() === name;
		})
		.first();
}

function resetHeight(element) {
	element.style.height = element.style.minHeight;
}

// Given a channel element will determine if the lounge user or a given nick is one of the supplied roles.
function hasRoleInChannel(channel, roles, nick) {
	if (!channel || !roles) {
		return false;
	}

	const channelID = channel.data("id");
	const network = $("#sidebar .network").has(`.chan[data-id="${channelID}"]`);
	const target = nick || network.attr("data-nick");
	const user = channel.find(`.names-original .user[data-name="${escape(target)}"]`).first();
	return user.parent().is("." + roles.join(", ."));
}

// Reusable scrollIntoView parameters for channel list / user list
function scrollIntoViewNicely(el) {
	// Ideally this would use behavior: "smooth", but that does not consistently work in e.g. Chrome
	// https://github.com/iamdustan/smoothscroll/issues/28#issuecomment-364061459
	el.scrollIntoView({block: "nearest", inline: "nearest"});
}

function collapse() {
	$(".chan.active .toggle-button.toggle-preview.opened").click();
	return true;
}

function expand() {
	$(".chan.active .toggle-button.toggle-preview:not(.opened)").click();
	return true;
}

function join(args) {
	const channel = args[0];

	if (channel) {
		const chan = findCurrentNetworkChan(channel);

		if (chan.length) {
			chan.trigger("click");
		}
	}
}

const favicon = $("#favicon");

function toggleNotificationMarkers(newState) {
	// Toggles the favicon to red when there are unread notifications
	if (favicon.data("toggled") !== newState) {
		const old = favicon.prop("href");
		favicon.prop("href", favicon.data("other"));
		favicon.data("other", old);
		favicon.data("toggled", newState);
	}

	// Toggles a dot on the menu icon when there are unread notifications
	viewport.toggleClass("notified", newState);
}

function updateTitle() {
	let title = $(document.body).data("app-name");
	const chanTitle = $("#sidebar").find(".chan.active").attr("aria-label");

	if (chanTitle && chanTitle.length > 0) {
		title = `${chanTitle} — ${title}`;
	}

	// add highlight count to title
	let alertEventCount = 0;
	$(".badge.highlight").each(function() {
		alertEventCount += parseInt($(this).attr("data-highlight"));
	});

	if (alertEventCount > 0) {
		title = `(${alertEventCount}) ${title}`;
	}

	document.title = title;
}

function togglePasswordField(elem) {
	$(elem).on("click", function() {
		const $this = $(this);
		const input = $this.closest("div").find("input");

		input.attr("type", input.attr("type") === "password" ? "text" : "password");

		swapLabel($this);
		swapLabel($this.find("span"));
		$this.toggleClass("visible");
	});
}

// Given a element, swap its aria-label with the content of `data-alt-label`
function swapLabel(element) {
	const altText = element.data("alt-label");
	element.data("alt-label", element.attr("aria-label")).attr("aria-label", altText);
}

function confirmExit() {
	if ($(document.body).hasClass("public")) {
		window.onbeforeunload = function() {
			return "Are you sure you want to navigate away from this page?";
		};
	}
}

function move(array, old_index, new_index) {
	if (new_index >= array.length) {
		let k = new_index - array.length;

		while ((k--) + 1) {
			this.push(undefined);
		}
	}

	array.splice(new_index, 0, array.splice(old_index, 1)[0]);
	return array;
}

function closeChan(chan) {
	const socket = require("./socket");
	let cmd = "/close";

	if (chan.hasClass("lobby")) {
		cmd = "/quit";
		const server = chan.find(".name").html();

		if (!confirm("Disconnect from " + server + "?")) { // eslint-disable-line no-alert
			return false;
		}
	}

	socket.emit("input", {
		target: chan.data("id"),
		text: cmd,
	});
	chan.css({
		transition: "none",
		opacity: 0.4,
	});
	return false;
}

function requestIdleCallback(callback, timeout) {
	if (window.requestIdleCallback) {
		// During an idle period the user agent will run idle callbacks in FIFO order
		// until either the idle period ends or there are no more idle callbacks eligible to be run.
		window.requestIdleCallback(callback, {timeout});
	} else {
		callback();
	}
}

// Force handling preview display
function togglePreviewMoreButtonsIfNeeded() {
	$("#chat .chan.active .toggle-content.toggle-type-link.show")
		.trigger("showMoreIfNeeded");
}
<commit_msg>Center scroll lists when navigating with keyboard<commit_after>"use strict";

const $ = require("jquery");
const escape = require("css.escape");
const viewport = $("#viewport");

var serverHash = -1; // eslint-disable-line no-var
var lastMessageId = -1; // eslint-disable-line no-var

module.exports = {
	// Same value as media query in CSS that forces sidebars to become overlays
	mobileViewportPixels: 768,
	inputCommands: {collapse, expand, join},
	findCurrentNetworkChan,
	serverHash,
	lastMessageId,
	confirmExit,
	scrollIntoViewNicely,
	hasRoleInChannel,
	move,
	closeChan,
	resetHeight,
	toggleNotificationMarkers,
	updateTitle,
	togglePasswordField,
	requestIdleCallback,
	togglePreviewMoreButtonsIfNeeded,
};

function findCurrentNetworkChan(name) {
	name = name.toLowerCase();

	return $(".network .chan.active")
		.parent(".network")
		.find(".chan")
		.filter(function() {
			return $(this).attr("aria-label").toLowerCase() === name;
		})
		.first();
}

function resetHeight(element) {
	element.style.height = element.style.minHeight;
}

// Given a channel element will determine if the lounge user or a given nick is one of the supplied roles.
function hasRoleInChannel(channel, roles, nick) {
	if (!channel || !roles) {
		return false;
	}

	const channelID = channel.data("id");
	const network = $("#sidebar .network").has(`.chan[data-id="${channelID}"]`);
	const target = nick || network.attr("data-nick");
	const user = channel.find(`.names-original .user[data-name="${escape(target)}"]`).first();
	return user.parent().is("." + roles.join(", ."));
}

// Reusable scrollIntoView parameters for channel list / user list
function scrollIntoViewNicely(el) {
	// Ideally this would use behavior: "smooth", but that does not consistently work in e.g. Chrome
	// https://github.com/iamdustan/smoothscroll/issues/28#issuecomment-364061459
	el.scrollIntoView({block: "center", inline: "nearest"});
}

function collapse() {
	$(".chan.active .toggle-button.toggle-preview.opened").click();
	return true;
}

function expand() {
	$(".chan.active .toggle-button.toggle-preview:not(.opened)").click();
	return true;
}

function join(args) {
	const channel = args[0];

	if (channel) {
		const chan = findCurrentNetworkChan(channel);

		if (chan.length) {
			chan.trigger("click");
		}
	}
}

const favicon = $("#favicon");

function toggleNotificationMarkers(newState) {
	// Toggles the favicon to red when there are unread notifications
	if (favicon.data("toggled") !== newState) {
		const old = favicon.prop("href");
		favicon.prop("href", favicon.data("other"));
		favicon.data("other", old);
		favicon.data("toggled", newState);
	}

	// Toggles a dot on the menu icon when there are unread notifications
	viewport.toggleClass("notified", newState);
}

function updateTitle() {
	let title = $(document.body).data("app-name");
	const chanTitle = $("#sidebar").find(".chan.active").attr("aria-label");

	if (chanTitle && chanTitle.length > 0) {
		title = `${chanTitle} — ${title}`;
	}

	// add highlight count to title
	let alertEventCount = 0;
	$(".badge.highlight").each(function() {
		alertEventCount += parseInt($(this).attr("data-highlight"));
	});

	if (alertEventCount > 0) {
		title = `(${alertEventCount}) ${title}`;
	}

	document.title = title;
}

function togglePasswordField(elem) {
	$(elem).on("click", function() {
		const $this = $(this);
		const input = $this.closest("div").find("input");

		input.attr("type", input.attr("type") === "password" ? "text" : "password");

		swapLabel($this);
		swapLabel($this.find("span"));
		$this.toggleClass("visible");
	});
}

// Given a element, swap its aria-label with the content of `data-alt-label`
function swapLabel(element) {
	const altText = element.data("alt-label");
	element.data("alt-label", element.attr("aria-label")).attr("aria-label", altText);
}

function confirmExit() {
	if ($(document.body).hasClass("public")) {
		window.onbeforeunload = function() {
			return "Are you sure you want to navigate away from this page?";
		};
	}
}

function move(array, old_index, new_index) {
	if (new_index >= array.length) {
		let k = new_index - array.length;

		while ((k--) + 1) {
			this.push(undefined);
		}
	}

	array.splice(new_index, 0, array.splice(old_index, 1)[0]);
	return array;
}

function closeChan(chan) {
	const socket = require("./socket");
	let cmd = "/close";

	if (chan.hasClass("lobby")) {
		cmd = "/quit";
		const server = chan.find(".name").html();

		if (!confirm("Disconnect from " + server + "?")) { // eslint-disable-line no-alert
			return false;
		}
	}

	socket.emit("input", {
		target: chan.data("id"),
		text: cmd,
	});
	chan.css({
		transition: "none",
		opacity: 0.4,
	});
	return false;
}

function requestIdleCallback(callback, timeout) {
	if (window.requestIdleCallback) {
		// During an idle period the user agent will run idle callbacks in FIFO order
		// until either the idle period ends or there are no more idle callbacks eligible to be run.
		window.requestIdleCallback(callback, {timeout});
	} else {
		callback();
	}
}

// Force handling preview display
function togglePreviewMoreButtonsIfNeeded() {
	$("#chat .chan.active .toggle-content.toggle-type-link.show")
		.trigger("showMoreIfNeeded");
}
<|endoftext|>


<commit_before>import React, { useRef } from 'react';

import { Dialog, Fab, withStyles, Button, Input, Typography } from '@material-ui/core';
import { Publish, Cancel, CloudUpload, InsertDriveFile } from '@material-ui/icons';

import { isEmpty } from 'lodash-es';

const style = () => (
  {
    add: {
      display: 'flex',
      justifyContent: 'flex-end',
      margin: '10px'
    },
    icon: {
      marginLeft: '5px'
    },
    upload: {
      display: 'flex',
      justifyContent: 'flex-end'
    },
    button: {
      margin: '5px'
    },
    text: {
      display: 'flex',
      justifyContent: 'center',
      padding: '10px'
    },
    input: {
      display: 'none'
    },
    fileSelect: {
      display: 'flex',
      justifyContent: 'space-around',
      alignItems: 'center',
      marginTop: '10px'
    },
    fab: {
      alignItems: 'flex-end'
    },
    file: {
      display: 'flex',
      alignItems: 'flex-end'
    }
  }
);

function FileDialog(props) {
  const [fileName, setFileName] = React.useState('');
  const { onClose, open, onCalculationUpload, classes } = props;
  const inputRef = useRef(null);

  const handleClose = () => {
    setFileName('');
    onClose();
  };

  const handleGetFiles = () => {
    inputRef.current.click();
  }

  const fileSelected = () => {
    setFileName(inputRef.current.files[0].name)
  }

  const onCalcUpload = () => {
    onCalculationUpload(inputRef.current.files[0]);
    handleClose();
  }

  return (
    <Dialog fullWidth maxWidth='xs' onClose={handleClose} open={open} className={classes.dialog}>

        <Button color='primary' className={classes.button} variant="contained" onClick={handleGetFiles}>
          Select File...
        </Button>
        <Input type='file' className={classes.input} inputRef={inputRef} onChange={fileSelected}></Input>

<Typography className={classes.text}>
          {isEmpty(fileName)
          ? 'No File Selected'
          :
<InsertDriveFile/> {fileName}
}
        </Typography>

        <Button className={classes.fab} onClick={onCalcUpload} disabled={isEmpty(fileName)}>
          Upload
          <CloudUpload color={isEmpty(fileName) ? 'disabled' : 'primary'} className={classes.icon}/>
        </Button>
        <Button className={classes.fab} onClick={handleClose}>
          Cancel
          <Cancel color='secondary' className={classes.icon}/>
        </Button>

    </Dialog>
  );
}

function FileUpload(props) {
  const [open, setOpen] = React.useState(false);
  const { classes } = props;

  const handleClickOpen = () => {
    setOpen(true);
  };

  const handleClose = () => {
    setOpen(false);
  };

  return (

      <Fab component='a' variant='extended' size='small' onClick={handleClickOpen}>
        Upload Calculation
        <Publish className={classes.icon}/>
      </Fab>
      <FileDialog open={open} onClose={handleClose} {...props}/>

  );
}

export default withStyles(style)(FileUpload)
<commit_msg>Fix console warning<commit_after>import React, { useRef } from 'react';

import { Dialog, Fab, withStyles, Button, Input, Typography } from '@material-ui/core';
import { Publish, Cancel, CloudUpload, InsertDriveFile } from '@material-ui/icons';

import { isEmpty } from 'lodash-es';

const style = () => (
  {
    add: {
      display: 'flex',
      justifyContent: 'flex-end',
      margin: '10px'
    },
    icon: {
      marginLeft: '5px'
    },
    upload: {
      display: 'flex',
      justifyContent: 'flex-end'
    },
    button: {
      margin: '5px'
    },
    text: {
      display: 'flex',
      justifyContent: 'center',
      padding: '10px'
    },
    input: {
      display: 'none'
    },
    fileSelect: {
      display: 'flex',
      justifyContent: 'space-around',
      alignItems: 'center',
      marginTop: '10px'
    },
    fab: {
      alignItems: 'flex-end'
    },
    file: {
      display: 'flex',
      alignItems: 'flex-end'
    }
  }
);

function FileDialog(props) {
  const [fileName, setFileName] = React.useState('');
  const { onClose, open, onCalculationUpload, classes } = props;
  const inputRef = useRef(null);

  const handleClose = () => {
    setFileName('');
    onClose();
  };

  const handleGetFiles = () => {
    inputRef.current.click();
  }

  const fileSelected = () => {
    setFileName(inputRef.current.files[0].name)
  }

  const onCalcUpload = () => {
    onCalculationUpload(inputRef.current.files[0]);
    handleClose();
  }

  return (
    <Dialog fullWidth maxWidth='xs' onClose={handleClose} open={open} className={classes.dialog}>

        <Button color='primary' className={classes.button} variant="contained" onClick={handleGetFiles}>
          Select File...
        </Button>
        <Input type='file' className={classes.input} inputRef={inputRef} onChange={fileSelected}></Input>

<Typography className={classes.text}>
          {isEmpty(fileName)
          ? 'No File Selected'
          : <InsertDriveFile/> {fileName} }
        </Typography>

        <Button className={classes.fab} onClick={onCalcUpload} disabled={isEmpty(fileName)}>
          Upload
          <CloudUpload color={isEmpty(fileName) ? 'disabled' : 'primary'} className={classes.icon}/>
        </Button>
        <Button className={classes.fab} onClick={handleClose}>
          Cancel
          <Cancel color='secondary' className={classes.icon}/>
        </Button>

    </Dialog>
  );
}

function FileUpload(props) {
  const [open, setOpen] = React.useState(false);
  const { classes } = props;

  const handleClickOpen = () => {
    setOpen(true);
  };

  const handleClose = () => {
    setOpen(false);
  };

  return (

      <Fab component='a' variant='extended' size='small' onClick={handleClickOpen}>
        Upload Calculation
        <Publish className={classes.icon}/>
      </Fab>
      <FileDialog open={open} onClose={handleClose} {...props}/>

  );
}

export default withStyles(style)(FileUpload)
<|endoftext|>


<commit_before>
var ItemsPerPage= 50;
var jobUpdateTimeout= 10000; // 10s

var injectVar= SpongeTools.injectVar;

var jobId= SpongeTools.jobId;

var invalidateJob= SpongeTools.invalidateJob;

var _saveJobTitle= SpongeTools.postData('setJobTitle');
var saveJobTitle= function( title ) {
    _saveJobTitle({ jobId: jobId(), title: title });
};

var _saveJobDescription= SpongeTools.postData('setJobDescription');
var saveJobDescription= function( description ) {
    _saveJobDescription({ jobId: jobId(), description: description });
};

var editorContext= SpongeTools.editorContext(function( context, property ) {

    switch ( property ) {
        case 'title': saveJobTitle(context.title); break;
        case 'text': saveJobDescription(context.text); break;
    }

    injectVar(context, 'changed')(true);
});

var _getJob= SpongeTools.getCachedData('getJob', 2000);
var getJob= function( _jobId ) {
    if ( !_jobId ) _jobId= jobId();
    invalidateJob(_jobId);
    return _getJob(_jobId);
};

// var getJobLog= SpongeTools.getCachedData('getJobLog', 2000);
var getJobLog= SpongeTools.postData('getJobLog');
var deleteJobLog= SpongeTools.postData('deleteJobLog');

var _getJobResult= SpongeTools.getCachedData('getJobResult', 2000);
var getJobResult= function( jobId, path ) {
    invalidateJob(jobId);
    return _getJobResult(jobId, path);
};

var _getJobResultMapArgs= SpongeTools.getCachedData('getJobResultMapArgs', 2000);
var getJobResultMapArgs= function( jobId, path ) {
    return _getJobResultMapArgs(jobId, path);
};

var _getJobResultMap= SpongeTools.getCachedData('getJobResultMap', 2000);
var getJobResultMap= function( jobId, path, data, format ) {
    return _getJobResultMap(jobId, path, data, format);
};

// object to hold map args reactively
var resultMapArgsCtxt= {};

var _restartJob= SpongeTools.getCachedData('restartJob', 2000);
var restartJob= function() {
    _restartJob(jobId(), function() {
        SpongeTools.invalidateJobList(true);
        invalidateJob(jobId(), true);
    });
};

var _deleteJob= SpongeTools.getCachedData('deleteJob', 2000);
var deleteJob= function() {
    var _jobId= jobId();
    _deleteJob(_jobId, function() {
        SpongeTools.invalidateJobList(true);
        jobId(undefined);
        SpongeTools.localSession('main-navigation')('view', undefined);
    });
    jobId(undefined);
};

var _getModel= SpongeTools.getCachedData('getModel');
var getModel= function() {
    var job= getJob();
    if ( !job ) return;

    return SpongeTools.cleanObject(_getModel(job.modelId));
};

var T= SpongeTools.Template;

/**
 * Template job
 */
T.select('job');

T.helper('statusClass', function() {
    if ( this.status.error )     return 'error';
    if ( this.status.success )   return 'success';
    if ( !this.status.finished ) return 'running';
    return '';
});

T.helper('status', function() {
    if ( this.status.error )     return new Spacebars.SafeString('Error:
' + this.status.error + '');
    if ( this.status.success )   return 'Successfully finished';
    if ( !this.status.finished ) return 'Running';
    return '';
});

T.helper('startTime', function() {
    if ( this.status && this.status.started ) {
        return SpongeTools.dateToString(this.status.started) + ' ' + SpongeTools.timeToString(this.status.started);
    }
});

T.helper('finishTime', function() {
    if ( this.status && this.status.finished ) {
        return SpongeTools.dateToString(this.status.finished) + ' ' + SpongeTools.timeToString(this.status.finished);
    }
});

T.helper('model', function() {
    var model= getModel();
    if ( !model ) return SpongeTools.valueToString(SpongeTools.modelId());

    return model.name;
});

T.helper('descriptionTitleContext', function() {
    return editorContext(this.description, 'title');
});

T.helper('descriptionTextContext', function() {
    return editorContext(this.description, 'text');
});

var setLogResult= function( content ) {
    $('#jobLog pre').html(content);
};

T.events({
    'click button.show-log': function( event ) {
        setLogResult('');
        SpongeTools.Modal.show($('#jobLog'));

        var jobId= SpongeTools.jobId();
        if ( !jobId ) return;

        var log= getJobLog(jobId, function(err, result) {
            if ( err ) console.error(err);
            setLogResult((result || {}).content);
        });

    },
    'click button.delete-log': function( event ) {
        var jobId= SpongeTools.jobId();
        if ( !jobId ) return;

        var log= deleteJobLog(jobId, function(err, result) {});

    },
    'click button.rerun-job': restartJob,
    'click button.delete-job': deleteJob,
    'click button.share-job': function( event ) {
        // FIXME: do we have the job object already?
        var id= (getJob() || {})._id;
        SpongeTools.shareObject({ type: 'Job', id: id });
        SpongeTools.Modal.show($('#shareObject'));
    },
});

T.select('jobArgs');

var onLocation= function( value, options, defaultFn ) {
    var job= getJob();

    if ( !job ) return defaultFn(value, options);

    var $result= $('
' + defaultFn(value, options) + '
');
    $result.find('a')
        .attr('info-title', 'Job: "' + job.description.title + '"')
        .attr('info-text', job.description.text)
    ;
    return $result.html();
};

var onObject= function( value, options, defaultFn ) {
};

T.helper('args', function() {
    var args= this.args;

    if ( !args ) return;

    return Object.keys(args).map(function( argName ) {
        return {
            name: argName,
            value: SpongeTools.valueToString(args[argName], { onLocation: onLocation }),
        };
    });
});

var showArgs= function( context ) {
    return injectVar(context, 'showHide', false);
};

T.helper('showHideArgsText', function() {
    return new Spacebars.SafeString('');
});

T.helper('showArgs', function() {
    return showArgs(this)();
});

T.events({
    'click a.show-hide-args': function( event ) {
        showArgs(this)(!showArgs(this)());
    },
});

T.select('jobResult');

var filterTables= function( obj, tables, path ) {
    if ( obj.tables ) {
        tables.push({
            path: path.join('.').split('.'),
            tables: obj.tables,
        });
        delete obj.tables;
    }
    for ( var name in obj ) {
        if ( obj[name] && typeof obj[name] === 'object' && obj[name].constructor === Object ) {
            path.push(name);
            filterTables(obj[name], tables, path);
            path.pop();
        }
    }
};

T.helper('result', function() {
    var jobId= SpongeTools.jobId();
    if ( !jobId ) return;

    var jobResult= getJobResult(jobId, '');

    if ( !jobResult ) return;

    return Object.keys(jobResult).map(function( id ) {
        var result= jobResult[id].result;
        var tables= [];
        if ( result ) filterTables(result, tables, [id, 'result']);
        return {
            id: id,
            args: (jobResult[id].args || {}).args,
            result: result,
            tables: tables,
        };
    });
});

var onObject= function( value, options, defaultFn ) {
    var out= defaultFn(value, options);
    return '
' + out + '
';
//    return '

	' + out + '

';
};

T.helper('resultMap', function() {
    var result= this.result || {};

    var model= getModel();
    var info= (((model || {}).definition || {}).info || {}).result || {};

    return Object.keys(result).filter(function( key ) {
        return key !== 'tables';
    }).map(function( key ) {
        var value= SpongeTools.valueToString(result[key], { onLocation: onLocation, onObject: onObject, onArray: onObject });
        if ( ! value ) return;

        return {
            name: key,
            value: value,
            info: info[key] || {},
        }
    }).filter(function( result ) {
        return result;
    }).sort(SpongeTools.indexSortFn);
});

T.helper('resultTables', function() {

    // empty resultMapArgsCtxt to free unneeded reactive values
    resultMapArgsCtxt= {};
    var tables= this.tables;
    if ( !tables || !tables.length ) return;

    var model= getModel();
    var info= ((((model || {}).definition || {}).info || {}).result || {}).tables || {};

    var result= [];
    tables.forEach(function( tableList ) {
        for ( var id in tableList.tables ) {
            var tablePath= tableList.path.join('.') + '.tables.' + id;

            // remove resultId and 'result' from path
            var path= tableList.path.slice(2);
            path.push(id);
            var index= path.join('.');
            result.push({
                index: index,
                tablePath: tablePath,
                jobId: jobId(),
                info: info[index] || {},
                hrefXml: SpongeTools.buildApiUrl('/Job/getResultTable/' + jobId() + '/' + tablePath + '?format=xml'),
                hrefCsv: SpongeTools.buildApiUrl('/Job/getResultTable/' + jobId() + '/' + tablePath + '?format=csv'),
            });
        }
    });
    return result;
});

T.helper('keys', function() {
    var self= this;
    return Object.keys(self).map(function( key ) {
        var objectVal= {};
        var simpleValue= SpongeTools.valueToString(self[key], { returnOnObject: objectVal });
        return {
            key: key,
            objectValue: simpleValue === objectVal && self[key],
            simpleValue: simpleValue,
        };
    });
});

T.events({
    'click .result.toggle': function( event ) {
        $(event.currentTarget)
            .toggleClass('icon-chevron-right')
            .toggleClass('icon-chevron-down')
            .parent().find('div:first').toggleClass('hidden')
        ;
        return false;
    },
    'click a.resultMapKml': function( event ) {
        injectVar(this, 'clicked')('kml');
        return false;
    },
    'click a.resultMapXml': function( event ) {
        injectVar(this, 'clicked')('xml');
        return false;
    },
    'click a.resultMapCsv': function( event ) {
        injectVar(this, 'clicked')('csv');
        return false;
    },
});

T.select('jobResultMapLink');

/**
 * helper does not return anything
 * it's simply a handler to show a dialog for map arguments
 */
T.helper('clicked', function() {
    var clicked= injectVar(this, 'clicked', false);

    var format= clicked();

    if ( !format ) return;

    if ( typeof format !== 'string' ) format= kml;

    var _jobId= jobId();
    var path= this.tablePath;

    // if clicked, get map args
    var args= getJobResultMapArgs(_jobId, path);
    if ( args === undefined ) return;

    // on result reset 'clicked'-status
    clicked(false);

    var _args= SpongeTools.buildValues(args, 'args', this);

    SpongeTools.valuesInput(
        _args, {
            title: 'Arguments for map "' + this.index + '"',
            simple: true,
        }, function( newArgs ) {
            var result= { args: newArgs, format: format };
            if ( 'transient' in args ) result.transient= args.transient;

            injectVar(resultMapArgsCtxt, path)(result);
        }
    );
});

T.select('jobResultMapResult');

/**
 * helper does not return anything
 * it's simply a handler to show a dialog for map arguments
 */
T.helper('templateHelper', function() {
    var path= this.tablePath;
    var args= injectVar(resultMapArgsCtxt, path, undefined);

    var _args= args();

    if ( !_args ) return null;

    format= _args.format;
    delete _args._format;

    var data= getJobResultMap(jobId(), this.tablePath, _args, format);
    if ( !data ) return Template.loadingImage;

    // unset arguments to prevent running into this function again
    // (was a bug in windows-browsers, calling this function repeatedly)
    args(undefined);

    if ( !data.url ) return null;

    var url= SpongeTools.buildApiUrl(data.url);

    console.log('Map Link', url);

    var contentType, target;
    switch ( format ) {
        case 'csv': contentType= 'text/comma-separated-values'; break;
        case 'xml': contentType= 'text/xml'; target= '_new'; break;
        default: contentType= 'application/vnd.google-earth.kml+xml'; break;
    }

console.log(url);
    SpongeTools.downloadLink(url, {
        query: {
            contentType: contentType,
            fileName: this.tablePath + '.' + format,
        },
        target: target,
    });
    return null;
});

<commit_msg>additional log<commit_after>
var ItemsPerPage= 50;
var jobUpdateTimeout= 10000; // 10s

var injectVar= SpongeTools.injectVar;

var jobId= SpongeTools.jobId;

var invalidateJob= SpongeTools.invalidateJob;

var _saveJobTitle= SpongeTools.postData('setJobTitle');
var saveJobTitle= function( title ) {
    _saveJobTitle({ jobId: jobId(), title: title });
};

var _saveJobDescription= SpongeTools.postData('setJobDescription');
var saveJobDescription= function( description ) {
    _saveJobDescription({ jobId: jobId(), description: description });
};

var editorContext= SpongeTools.editorContext(function( context, property ) {

    switch ( property ) {
        case 'title': saveJobTitle(context.title); break;
        case 'text': saveJobDescription(context.text); break;
    }

    injectVar(context, 'changed')(true);
});

var _getJob= SpongeTools.getCachedData('getJob', 2000);
var getJob= function( _jobId ) {
    if ( !_jobId ) _jobId= jobId();
    invalidateJob(_jobId);
    return _getJob(_jobId);
};

// var getJobLog= SpongeTools.getCachedData('getJobLog', 2000);
var getJobLog= SpongeTools.postData('getJobLog');
var deleteJobLog= SpongeTools.postData('deleteJobLog');

var _getJobResult= SpongeTools.getCachedData('getJobResult', 2000);
var getJobResult= function( jobId, path ) {
    invalidateJob(jobId);
    return _getJobResult(jobId, path);
};

var _getJobResultMapArgs= SpongeTools.getCachedData('getJobResultMapArgs', 2000);
var getJobResultMapArgs= function( jobId, path ) {
    return _getJobResultMapArgs(jobId, path);
};

var _getJobResultMap= SpongeTools.getCachedData('getJobResultMap', 2000);
var getJobResultMap= function( jobId, path, data, format ) {
    return _getJobResultMap(jobId, path, data, format);
};

// object to hold map args reactively
var resultMapArgsCtxt= {};

var _restartJob= SpongeTools.getCachedData('restartJob', 2000);
var restartJob= function() {
    _restartJob(jobId(), function() {
        SpongeTools.invalidateJobList(true);
        invalidateJob(jobId(), true);
    });
};

var _deleteJob= SpongeTools.getCachedData('deleteJob', 2000);
var deleteJob= function() {
    var _jobId= jobId();
    _deleteJob(_jobId, function() {
        SpongeTools.invalidateJobList(true);
        jobId(undefined);
        SpongeTools.localSession('main-navigation')('view', undefined);
    });
    jobId(undefined);
};

var _getModel= SpongeTools.getCachedData('getModel');
var getModel= function() {
    var job= getJob();
    if ( !job ) return;

    return SpongeTools.cleanObject(_getModel(job.modelId));
};

var T= SpongeTools.Template;

/**
 * Template job
 */
T.select('job');

T.helper('statusClass', function() {
    if ( this.status.error )     return 'error';
    if ( this.status.success )   return 'success';
    if ( !this.status.finished ) return 'running';
    return '';
});

T.helper('status', function() {
    if ( this.status.error )     return new Spacebars.SafeString('Error:
' + this.status.error + '');
    if ( this.status.success )   return 'Successfully finished';
    if ( !this.status.finished ) return 'Running';
    return '';
});

T.helper('startTime', function() {
    if ( this.status && this.status.started ) {
        return SpongeTools.dateToString(this.status.started) + ' ' + SpongeTools.timeToString(this.status.started);
    }
});

T.helper('finishTime', function() {
    if ( this.status && this.status.finished ) {
        return SpongeTools.dateToString(this.status.finished) + ' ' + SpongeTools.timeToString(this.status.finished);
    }
});

T.helper('model', function() {
    var model= getModel();
    if ( !model ) return SpongeTools.valueToString(SpongeTools.modelId());

    return model.name;
});

T.helper('descriptionTitleContext', function() {
    return editorContext(this.description, 'title');
});

T.helper('descriptionTextContext', function() {
    return editorContext(this.description, 'text');
});

var setLogResult= function( content ) {
    $('#jobLog pre').html(content);
};

T.events({
    'click button.show-log': function( event ) {
        setLogResult('');
        SpongeTools.Modal.show($('#jobLog'));

        var jobId= SpongeTools.jobId();
        if ( !jobId ) return;

        var log= getJobLog(jobId, function(err, result) {
            if ( err ) console.error(err);
            setLogResult((result || {}).content);
        });

    },
    'click button.delete-log': function( event ) {
        var jobId= SpongeTools.jobId();
        if ( !jobId ) return;

        var log= deleteJobLog(jobId, function(err, result) {});

    },
    'click button.rerun-job': restartJob,
    'click button.delete-job': deleteJob,
    'click button.share-job': function( event ) {
        // FIXME: do we have the job object already?
        var id= (getJob() || {})._id;
        SpongeTools.shareObject({ type: 'Job', id: id });
        SpongeTools.Modal.show($('#shareObject'));
    },
});

T.select('jobArgs');

var onLocation= function( value, options, defaultFn ) {
    var job= getJob();

    if ( !job ) return defaultFn(value, options);

    var $result= $('
' + defaultFn(value, options) + '
');
    $result.find('a')
        .attr('info-title', 'Job: "' + job.description.title + '"')
        .attr('info-text', job.description.text)
    ;
    return $result.html();
};

var onObject= function( value, options, defaultFn ) {
};

T.helper('args', function() {
    var args= this.args;

    if ( !args ) return;

    return Object.keys(args).map(function( argName ) {
        return {
            name: argName,
            value: SpongeTools.valueToString(args[argName], { onLocation: onLocation }),
        };
    });
});

var showArgs= function( context ) {
    return injectVar(context, 'showHide', false);
};

T.helper('showHideArgsText', function() {
    return new Spacebars.SafeString('');
});

T.helper('showArgs', function() {
    return showArgs(this)();
});

T.events({
    'click a.show-hide-args': function( event ) {
        showArgs(this)(!showArgs(this)());
    },
});

T.select('jobResult');

var filterTables= function( obj, tables, path ) {
    if ( obj.tables ) {
        tables.push({
            path: path.join('.').split('.'),
            tables: obj.tables,
        });
        delete obj.tables;
    }
    for ( var name in obj ) {
        if ( obj[name] && typeof obj[name] === 'object' && obj[name].constructor === Object ) {
            path.push(name);
            filterTables(obj[name], tables, path);
            path.pop();
        }
    }
};

T.helper('result', function() {
    var jobId= SpongeTools.jobId();
    if ( !jobId ) return;

    var jobResult= getJobResult(jobId, '');

    if ( !jobResult ) return;

    return Object.keys(jobResult).map(function( id ) {
        var result= jobResult[id].result;
        var tables= [];
        if ( result ) filterTables(result, tables, [id, 'result']);
        return {
            id: id,
            args: (jobResult[id].args || {}).args,
            result: result,
            tables: tables,
        };
    });
});

var onObject= function( value, options, defaultFn ) {
    var out= defaultFn(value, options);
    return '
' + out + '
';
//    return '

	' + out + '

';
};

T.helper('resultMap', function() {
    var result= this.result || {};

    var model= getModel();
    var info= (((model || {}).definition || {}).info || {}).result || {};

    return Object.keys(result).filter(function( key ) {
        return key !== 'tables';
    }).map(function( key ) {
        var value= SpongeTools.valueToString(result[key], { onLocation: onLocation, onObject: onObject, onArray: onObject });
        if ( ! value ) return;

        return {
            name: key,
            value: value,
            info: info[key] || {},
        }
    }).filter(function( result ) {
        return result;
    }).sort(SpongeTools.indexSortFn);
});

T.helper('resultTables', function() {

    // empty resultMapArgsCtxt to free unneeded reactive values
    resultMapArgsCtxt= {};
    var tables= this.tables;
    if ( !tables || !tables.length ) return;

    var model= getModel();
    var info= ((((model || {}).definition || {}).info || {}).result || {}).tables || {};

    var result= [];
    tables.forEach(function( tableList ) {
        for ( var id in tableList.tables ) {
            var tablePath= tableList.path.join('.') + '.tables.' + id;

            // remove resultId and 'result' from path
            var path= tableList.path.slice(2);
            path.push(id);
            var index= path.join('.');
            result.push({
                index: index,
                tablePath: tablePath,
                jobId: jobId(),
                info: info[index] || {},
                hrefXml: SpongeTools.buildApiUrl('/Job/getResultTable/' + jobId() + '/' + tablePath + '?format=xml'),
                hrefCsv: SpongeTools.buildApiUrl('/Job/getResultTable/' + jobId() + '/' + tablePath + '?format=csv'),
            });
        }
    });
    return result;
});

T.helper('keys', function() {
    var self= this;
    return Object.keys(self).map(function( key ) {
        var objectVal= {};
        var simpleValue= SpongeTools.valueToString(self[key], { returnOnObject: objectVal });
        return {
            key: key,
            objectValue: simpleValue === objectVal && self[key],
            simpleValue: simpleValue,
        };
    });
});

T.events({
    'click .result.toggle': function( event ) {
        $(event.currentTarget)
            .toggleClass('icon-chevron-right')
            .toggleClass('icon-chevron-down')
            .parent().find('div:first').toggleClass('hidden')
        ;
        return false;
    },
    'click a.resultMapKml': function( event ) {
        injectVar(this, 'clicked')('kml');
        return false;
    },
    'click a.resultMapXml': function( event ) {
        injectVar(this, 'clicked')('xml');
        return false;
    },
    'click a.resultMapCsv': function( event ) {
        injectVar(this, 'clicked')('csv');
        return false;
    },
});

T.select('jobResultMapLink');

/**
 * helper does not return anything
 * it's simply a handler to show a dialog for map arguments
 */
T.helper('clicked', function() {
    var clicked= injectVar(this, 'clicked', false);

    var format= clicked();

    if ( !format ) return;

    if ( typeof format !== 'string' ) format= kml;

    var _jobId= jobId();
    var path= this.tablePath;

    // if clicked, get map args
    var args= getJobResultMapArgs(_jobId, path);
    if ( args === undefined ) return;

    // on result reset 'clicked'-status
    clicked(false);

    var _args= SpongeTools.buildValues(args, 'args', this);

    SpongeTools.valuesInput(
        _args, {
            title: 'Arguments for map "' + this.index + '"',
            simple: true,
        }, function( newArgs ) {
            var result= { args: newArgs, format: format };
            if ( 'transient' in args ) result.transient= args.transient;

            injectVar(resultMapArgsCtxt, path)(result);
        }
    );
});

T.select('jobResultMapResult');

/**
 * helper does not return anything
 * it's simply a handler to show a dialog for map arguments
 */
T.helper('templateHelper', function() {
    var path= this.tablePath;
    var args= injectVar(resultMapArgsCtxt, path, undefined);

    var _args= args();

    if ( !_args ) return null;

    format= _args.format;
    delete _args._format;

    var data= getJobResultMap(jobId(), this.tablePath, _args, format);
    if ( !data ) return Template.loadingImage;

    // unset arguments to prevent running into this function again
    // (was a bug in windows-browsers, calling this function repeatedly)
    args(undefined);

    if ( !data.url ) {
console.log('!data.url', data);
        return null;
    }

    var url= SpongeTools.buildApiUrl(data.url);

    console.log('Map Link', url);

    var contentType, target;
    switch ( format ) {
        case 'csv': contentType= 'text/comma-separated-values'; break;
        case 'xml': contentType= 'text/xml'; target= '_new'; break;
        default: contentType= 'application/vnd.google-earth.kml+xml'; break;
    }

console.log(url);
    SpongeTools.downloadLink(url, {
        query: {
            contentType: contentType,
            fileName: this.tablePath + '.' + format,
        },
        target: target,
    });
    return null;
});

<|endoftext|>