<commit_before>'use strict';

import React              from 'react';
import ReduxUtil          from '../../util/ReduxUtil';

import autoBindAllMethods from '../../../shared/util/autoBindAllMethods';
import assert             from 'assert';
import {AC}               from '../../state/actions';
import {hashSelCrit}      from '../../../shared/util/selCritUtil';

import studentsMeta       from '../../../shared/model/studentsMeta';
import coursesMeta        from '../../../shared/model/coursesMeta';
const  metaXlate = {      // indexed via selCrit.target
         'Students': studentsMeta,
         'Courses':  coursesMeta,
       };

import Dialog         from 'material-ui/lib/dialog';
import TextField      from 'material-ui/lib/text-field';
import FlatButton     from 'material-ui/lib/flat-button';
import Toggle         from 'material-ui/lib/toggle';
import Select         from 'react-select'
import FieldValue     from './FieldValue'
import SortValue      from './SortValue'
import SelCritDetail  from './SelCritDetail'

import UserMsg from '../UserMsg';

/**
 * The EditSelCrit component edits a supplied selCrit object, through
 * an interactive modal dialog.  Because the selCrit is a generic
 * object, any selCrit.target is supported (for example: Students,
 * Courses, etc.).
 *
 * An edit session is initiated through the edit() static method.  The
 * incentive for this programmatic interface is an edit session can be
 * invoked from a variety of different places - programmatically
 * (i.e. the invoker does NOT need access to the dispatcher).
 *
 *
 *
 * USAGE:
 *
 *   To use the EditSelCrit component, you must first pre-instantiate
 *   it somewhere within the app's DOM.  Only one instance should ever
 *   be created within the app.  The EditSelCrit component is
 *   initially hidden. until an edit session is programatically
 *   initiated.
 *
 *       <EditSelCrit/>
 *
 *   An edit edit session may then be programatically initiated (in a
 *   variety of different logic points) by simply calling the static
 *   edit() method:
 *
 *       EditSelCrit.edit(selCrit, extraActionsOnCompletionCb): void
 *         ... see edit() JavaDoc below
 *
 *
 *
 * External Synchronization on Edit Complete:
 *
 *   selCrit objects may reside in a variety of different places
 *   ... for example:
 *        - in the LefNav menu [to choose], and
 *        - driving the dispay content in a view).
 *
 *   These instances must be synced to reflect the latest changes.
 *   Each selCrit object has a unique key (selCrit.key), that
 *   identifies that specific instance.  This key is maintained
 *   irrespective to whether the selCrit is persisted or not.  You may
 *   have a selCrit that is temporary (not saved at all), or you
 *   may decide to temporarly modify a persistant selCrit.
 *
 *   - ACTION: AT.selCrit.edit.changed ... action contains: selCrit
 *
 *     To facilate this external synchronization, the edit completion
 *     process will emit the AT.selCrit.edit.changed action, and
 *     should be PUBLICALLY monitored by various reducers.
 *
 *     This action is intelligently emitted ONLY if a change has
 *     actually occured (within the edit session).
 *
 *     This action indicates that the supplied selCrit has been
 *     modified within the app (in memory irrespective to it's DB save
 *     status).
 *
 *     This is a public trigger to sync the supplied selCrit where
 *     appropriate (matching selCrit.key).  As an example, the LeftNav
 *     contains a variety of selCrit objects that may be selected to
 *     view, and should be updated.
 *
 *
 *   - PARAM: extraActionsOnCompletionCb ... see edit() JavaDoc below
 *
 *     The syncing of active views should be handled through the
 *     extraActionsOnCompletionCb parameter, and NOT the
 *     AT.selCrit.edit.changed action.
 *
 *     The reason for this is additional work is required - over and
 *     above the in-memory syncing the selCrit ... namely an
 *     additional action to re-retrieve DB results from the newly
 *     modified selCrit.  This additional work CANNOT be acomplished
 *     by monnitoring the AT.selCrit.edit.changed action, because this
 *     would require a reducer to issue actions (an anti-pattern
 *     side-effect).
 *
 *   - AT.selCrit.save ... action contains: selCrit
 *
 *     In addition, the edit session may be completed through a save
 *     operation.  When this occurs, the AT.selCrit.save action is emitted.
 *
 *     This action is emitted in addition to the other
 *     complete-related actions (discussed here), and is NOT really of
 *     PUBLIC concern.  In other words, the app should sync changes by
 *     monioring the actions discussed above.
 *
 *     With that said, the selCrit should be synced when the save is
 *     complete, by monitoring the AT.selCrit.save.complete action.
 *     This is merely to accomidate the change to selCrit.dbHash -
 *     reflecting it's DB persistance status.
 */
const EditSelCrit = ReduxUtil.wrapCompWithInjectedProps(

  class EditSelCrit extends React.Component { // component definition

    constructor(props, context) {
      super(props, context);

      autoBindAllMethods(this);

      // keep track of our one-and-only instance
      assert(!_singleton, "<EditSelCrit> only ONE EditSelCrit is needed and therefore may be instantiated within the app.");
      _singleton = this;
    }

    /**
     * Edit the supplied selCrit by activating a modal dialog.
     *
     * NOTE: This method is very close to an AC, except that it
     *       automatically dispatches the appropriate action.  As a
     *       result it can be programmatically invoked at any logic
     *       point.  In addition, it insures an <EditSelCrit> has been
     *       instantiated.
     *
     * @parm {SelCrit} selCrit the selCrit to edit.
     * @parm {function} extraActionsOnCompletionCb an optional client
     * callback, executed on edit completion, supporting additional
     * logic over-and-above the normal synchronization (ex: refresh a
     * retrieval based on this selCrit).
     *   API: extraActionsOnCompletionCb(selCrit): Action -or- Action[]
     *
     * @public
     */
    static edit(selCrit, extraActionsOnCompletionCb) {
      // validate that an <EditSelCrit> has been instantiated
      assert(_singleton, "EditSelCrit.edit() ... ERROR: NO <EditSelCrit> has been instantiated within the app.");

      // pass-through to our instance method
      _singleton.edit(selCrit, extraActionsOnCompletionCb);
    }

    /**
     * edit() - internal instance method that initiates the selCrit edit session
     */
    edit(selCrit, extraActionsOnCompletionCb) {
      const p = this.props;

      // determine the DB meta definition we are working for
      this.meta = metaXlate[selCrit.target];

      // define the total set of fieldOptions promoted to the user
      // ... derived from meta.validFields
      this.fieldOptions = [];
      for (const fieldName in this.meta.validFields) {
        const fieldLabel = this.meta.validFields[fieldName];
        if (fieldLabel) { // missing field label is a control indication to NOT promote field to user
          this.fieldOptions.push( { value: fieldName, label: fieldLabel } );
        }
      }

      // define the total set of sortOptions promoted to the user
      // ... derived from meta.validFields
      this.sortOptions = [];
      for (const fieldName in this.meta.validFields) {
        const fieldLabel = this.meta.validFields[fieldName];
        if (fieldLabel) { // missing field label is a control indication to NOT promote field to user
          this.sortOptions.push( { value: fieldName, label: fieldLabel, ascDec: 1 } );
        }
      }

      // retain extraActionsOnCompletionCb in self for future reference (on close)
      this.extraActionsOnCompletionCb = extraActionsOnCompletionCb;

      // retain the starting selCrit.curHash, providing the ability to determine when self has changed the selCrit
      // ... NOTE: we have to calculate it, because the appState selCrit.curHash is not defined till after
      //           the following dispatch cycle
      this.starting_curHash = hashSelCrit(selCrit);
      // console.log(`xx <EditSelCrit>.edit() initial selCrit: ${JSON.stringify(selCrit, null, 2)}`); // our hash check appears to be working!

      // dispatch the appropriate action
      p.dispatch( AC.selCrit.edit(selCrit, this.meta) );
    }

    /**
     * handleEditComplete()
     */
    handleEditComplete(completionType) { // completionType: 'Use'/'Save'/'Cancel'
      const p = this.props;

      // apply validation - prematurely returning on errors
      // ... validation errors dynamically are shown in dialog
      if (completionType !== 'Cancel') {
        let valid = true;
        if (!p.selCrit.name.trim()) // name is required
          valid = false;
        if (!p.selCrit.desc.trim()) // desc is required
          valid = false;
        for (const extraFilterObj of p.extraFilter) { // filter is missing some components
          if (!extraFilterObj.operator ||
              !extraFilterObj.value ||
              extraFilterObj.value.length === 0)
            valid = false;
        }
        if (!valid) {
          UserMsg.display('Please resolve the highlighted validation errors.');
          return;
        }
      }

      //***
      //*** emit the appropriate action(s)
      //***

      // we always issue a close (to take down our dialog)
      const actions = [AC.selCrit.edit.close()];

      // issue change actions (when appropriate) ...
      if (this.starting_curHash !== p.selCrit.curHash &&  // when selCrit has actually changed -AND-
          completionType !== 'Cancel') {                  // the Cancel button was NOT used

        // publish our standard PUBLIC sync action
        actions.push( AC.selCrit.edit.changed(p.selCrit) );

            // apply invoker-based 'extra' synchronization (ex: refresh a retrieval based on this selCrit)
        if (this.extraActionsOnCompletionCb) {
          const extraActions = this.extraActionsOnCompletionCb(p.selCrit);
          if (extraActions) {
            if (Array.isArray(extraActions))
              actions.push(...extraActions);
            else
              actions.push(extraActions);
          }
        }
      }

      // issue save (when appropriate) ...
      if (this.starting_curHash !== p.selCrit.dbHash && // when selCrit needs to be saved -AND-
          completionType === 'Save') {                  // the Save button was used

        actions.push( AC.selCrit.save(p.selCrit) );
      }

      // publish the appropriate actions
      p.dispatch( actions );

    }

    /**
     * handleNameChange()
     */
    handleNameChange(event) {
      const p = this.props;
      p.dispatch( AC.selCrit.edit.nameChange(event.target.value) );
    }

    /**
     * handleDescChange()
     */
    handleDescChange(event) {
      const p = this.props;
      p.dispatch( AC.selCrit.edit.descChange(event.target.value) );
    }

    /**
     * handleFieldsChange()
     */
    handleFieldsChange(selectedFieldOptions) { // selectedFieldOptions: Option[ {value, label}, ...]
      const p = this.props;
      p.dispatch( AC.selCrit.edit.fieldsChange(selectedFieldOptions) );
    }

    /**
     * handleSortChange()
     */
    handleSortChange(selectedSortOptions) { // selectedSortOptions: Option[ {value, label, ascDec}, ...]
      const p = this.props;
      p.dispatch( AC.selCrit.edit.sortChange(selectedSortOptions) );
    }

    /**
     * handleDistinguishMajorSortFieldChange()
     */
    handleDistinguishMajorSortFieldChange(event, value) {
      const p = this.props;
      p.dispatch( AC.selCrit.edit.distinguishMajorSortFieldChange(value) );
    }

    /**
     * render()
     */
    render() {
      const p = this.props;

      // no-op, when we are not in an edit session
      if (!p.selCrit)
        return null;

      // NOTE: non-modal dialog: outer click same as 'Use' button
      return <Dialog modal={false}
                     title={`${p.selCrit.target} Filter`}
                     open={true}
                     autoScrollBodyContent={true}
                     onRequestClose={ (buttonClicked) => this.handleEditComplete('Use')}
                     actions={[
                       <FlatButton label="Use"
                                   primary={true}
                                   title="use this filter (without saving)"
                                   onTouchTap={ (e) => this.handleEditComplete('Use') }/>,
                       <FlatButton label="Save"
                                   primary={true}
                                   title={p.selCrit.dbHash===this.starting_curHash ? 'there are NO changes to save' : 'save and use changes'}
                                   disabled={p.selCrit.dbHash===this.starting_curHash}
                                   onTouchTap={ (e) => this.handleEditComplete('Save') }/>,
                       <FlatButton label="Cancel"
                                   primary={true}
                                   title={p.selCrit.curHash===this.starting_curHash ? 'there are NO changes to cancel' : 'cancel changes'}
                                   disabled={p.selCrit.curHash===this.starting_curHash}
                                   onTouchTap={ (e) => this.handleEditComplete('Cancel') }/>,
                     ]}
                     contentStyle={{
                         width:         '90%',
                         maxWidth:      'none',
                         verticalAlign: 'top',
                       }}>

        <TextField floatingLabelText="Name"
                   style={{ width: '10em' }}
                   errorText={p.selCrit.name.trim() ? null : 'Name is required'}
                   value={p.selCrit.name}
                   onChange={this.handleNameChange}/>

        <TextField floatingLabelText="Description"
                   style={{ width: '40em' }}
                   errorText={p.selCrit.desc.trim() ? null : 'Description is required'}
                   value={p.selCrit.desc}
                   onChange={this.handleDescChange}/>

          Display Fields (note that Students Gender/Name/StudentNum are always grouped together):

          <Select multi={true}
                  options={this.fieldOptions}
                  matchProp="label"
                  placeholder="... using default fields"
                  valueComponent={FieldValue}
                  value={p.selectedFieldOptions}
                  onChange={this.handleFieldsChange}
                  resetValue={[]}/>

          Order By:

          <Select multi={true}
                  options={this.sortOptions}
                  matchProp="label"
                  placeholder="... no sort in effect"
                  valueComponent={SortValue}
                  value={p.selectedSortOptions}
                  onChange={this.handleSortChange}
                  resetValue={[]}/>
          <Toggle label="Distinguish Major Sort Field"
                  labelPosition="right"
                  style={{paddingLeft: 20}}
                  toggled={p.selCrit.distinguishMajorSortField}
                  onToggle={this.handleDistinguishMajorSortFieldChange}/>

        <SelCritDetail meta={this.meta}/>

      </Dialog>;
    }
  }, // end of ... component definition

  { // component property injection
    mapStateToProps(appState, ownProps) {
      return {
        selCrit:              appState.editSelCrit.selCrit,
        selectedFieldOptions: appState.editSelCrit.extra ? appState.editSelCrit.extra.selectedFieldOptions : null,
        selectedSortOptions:  appState.editSelCrit.extra ? appState.editSelCrit.extra.selectedSortOptions : null,
        extraFilter:          appState.editSelCrit.extra ? appState.editSelCrit.extra.filter : null,
      };
    }
  }); // end of ... component property injection

// define expected props
EditSelCrit.propTypes = {
}

// keep track of our one-and-only instance
let _singleton = null;

export default EditSelCrit;
<commit_msg>minor doc change<commit_after>'use strict';

import React              from 'react';
import ReduxUtil          from '../../util/ReduxUtil';

import autoBindAllMethods from '../../../shared/util/autoBindAllMethods';
import assert             from 'assert';
import {AC}               from '../../state/actions';
import {hashSelCrit}      from '../../../shared/util/selCritUtil';

import studentsMeta       from '../../../shared/model/studentsMeta';
import coursesMeta        from '../../../shared/model/coursesMeta';
const  metaXlate = {      // indexed via selCrit.target
         'Students': studentsMeta,
         'Courses':  coursesMeta,
       };

import Dialog         from 'material-ui/lib/dialog';
import TextField      from 'material-ui/lib/text-field';
import FlatButton     from 'material-ui/lib/flat-button';
import Toggle         from 'material-ui/lib/toggle';
import Select         from 'react-select'
import FieldValue     from './FieldValue'
import SortValue      from './SortValue'
import SelCritDetail  from './SelCritDetail'

import UserMsg from '../UserMsg';

/**
 * The EditSelCrit component edits a supplied selCrit object, through
 * an interactive modal dialog.  Because the selCrit is a generic
 * object, any selCrit.target is supported (for example: Students,
 * Courses, etc.).
 *
 * An edit session is initiated through the edit() static method.  The
 * incentive for this programmatic interface is an edit session can be
 * invoked from a variety of different places - programmatically
 * (i.e. the invoker does NOT need access to the dispatcher).
 *
 *
 *
 * USAGE:
 *
 *   To use the EditSelCrit component, you must first pre-instantiate
 *   it somewhere within the app's DOM.  Only one instance should ever
 *   be created within the app.  The EditSelCrit component is
 *   initially hidden. until an edit session is programatically
 *   initiated.
 *
 *       <EditSelCrit/>
 *
 *   An edit edit session may then be programatically initiated (in a
 *   variety of different logic points) by simply calling the static
 *   edit() method:
 *
 *       EditSelCrit.edit(selCrit, extraActionsOnCompletionCb): void
 *         ... see edit() JavaDoc below
 *
 *
 *
 * External Synchronization on Edit Complete:
 *
 *   selCrit objects may reside in a variety of different places
 *   ... for example:
 *        - in the LefNav menu [to choose], and
 *        - driving the dispay content in a view).
 *
 *   These instances must be synced to reflect the latest changes.
 *   Each selCrit object has a unique key (selCrit.key), that
 *   identifies that specific instance.  This key is maintained
 *   irrespective to whether the selCrit is persisted or not.  You may
 *   have a selCrit that is temporary (not saved at all), or you
 *   may decide to temporarly modify a persistant selCrit.
 *
 *   - ACTION: AT.selCrit.edit.changed ... action contains: selCrit
 *
 *     To facilate this external synchronization, the edit completion
 *     process will emit the AT.selCrit.edit.changed action, and
 *     should be PUBLICALLY monitored by various reducers.
 *
 *     This action is intelligently emitted ONLY if a change has
 *     actually occured (within the edit session).
 *
 *     This action indicates that the supplied selCrit has been
 *     modified within the app (in memory irrespective to it's DB save
 *     status).
 *
 *     This is a public trigger to sync the supplied selCrit where
 *     appropriate (matching selCrit.key).  As an example, the LeftNav
 *     contains a variety of selCrit objects that may be selected to
 *     view, and should be updated.
 *
 *
 *   - PARAM: extraActionsOnCompletionCb ... see edit() JavaDoc below
 *
 *     The syncing of active views should be handled through the
 *     extraActionsOnCompletionCb parameter, and NOT the
 *     AT.selCrit.edit.changed action.
 *
 *     The reason for this is additional work is required - over and
 *     above the in-memory syncing the selCrit ... namely an
 *     additional action to re-retrieve DB results from the newly
 *     modified selCrit.  This additional work CANNOT be acomplished
 *     by monnitoring the AT.selCrit.edit.changed action, because this
 *     would require a reducer to issue actions (an anti-pattern
 *     side-effect).
 *
 *   - AT.selCrit.save ... action contains: selCrit
 *
 *     In addition, the edit session may be completed through a save
 *     operation.  When this occurs, the AT.selCrit.save action is emitted.
 *
 *     This action is emitted in addition to the other
 *     complete-related actions (discussed here), and is NOT really of
 *     PUBLIC concern.  In other words, the app should sync changes by
 *     monioring the actions discussed above.
 *
 *     With that said, the selCrit should be synced when the save is
 *     complete, by monitoring the AT.selCrit.save.complete action.
 *     This is merely to accomidate the change to selCrit (_id,
 *     lastDbModDate, and dbHash) - reflecting it's DB persistance status.
 */
const EditSelCrit = ReduxUtil.wrapCompWithInjectedProps(

  class EditSelCrit extends React.Component { // component definition

    constructor(props, context) {
      super(props, context);

      autoBindAllMethods(this);

      // keep track of our one-and-only instance
      assert(!_singleton, "<EditSelCrit> only ONE EditSelCrit is needed and therefore may be instantiated within the app.");
      _singleton = this;
    }

    /**
     * Edit the supplied selCrit by activating a modal dialog.
     *
     * NOTE: This method is very close to an AC, except that it
     *       automatically dispatches the appropriate action.  As a
     *       result it can be programmatically invoked at any logic
     *       point.  In addition, it insures an <EditSelCrit> has been
     *       instantiated.
     *
     * @parm {SelCrit} selCrit the selCrit to edit.
     * @parm {function} extraActionsOnCompletionCb an optional client
     * callback, executed on edit completion, supporting additional
     * logic over-and-above the normal synchronization (ex: refresh a
     * retrieval based on this selCrit).
     *   API: extraActionsOnCompletionCb(selCrit): Action -or- Action[]
     *
     * @public
     */
    static edit(selCrit, extraActionsOnCompletionCb) {
      // validate that an <EditSelCrit> has been instantiated
      assert(_singleton, "EditSelCrit.edit() ... ERROR: NO <EditSelCrit> has been instantiated within the app.");

      // pass-through to our instance method
      _singleton.edit(selCrit, extraActionsOnCompletionCb);
    }

    /**
     * edit() - internal instance method that initiates the selCrit edit session
     */
    edit(selCrit, extraActionsOnCompletionCb) {
      const p = this.props;

      // determine the DB meta definition we are working for
      this.meta = metaXlate[selCrit.target];

      // define the total set of fieldOptions promoted to the user
      // ... derived from meta.validFields
      this.fieldOptions = [];
      for (const fieldName in this.meta.validFields) {
        const fieldLabel = this.meta.validFields[fieldName];
        if (fieldLabel) { // missing field label is a control indication to NOT promote field to user
          this.fieldOptions.push( { value: fieldName, label: fieldLabel } );
        }
      }

      // define the total set of sortOptions promoted to the user
      // ... derived from meta.validFields
      this.sortOptions = [];
      for (const fieldName in this.meta.validFields) {
        const fieldLabel = this.meta.validFields[fieldName];
        if (fieldLabel) { // missing field label is a control indication to NOT promote field to user
          this.sortOptions.push( { value: fieldName, label: fieldLabel, ascDec: 1 } );
        }
      }

      // retain extraActionsOnCompletionCb in self for future reference (on close)
      this.extraActionsOnCompletionCb = extraActionsOnCompletionCb;

      // retain the starting selCrit.curHash, providing the ability to determine when self has changed the selCrit
      // ... NOTE: we have to calculate it, because the appState selCrit.curHash is not defined till after
      //           the following dispatch cycle
      this.starting_curHash = hashSelCrit(selCrit);
      // console.log(`xx <EditSelCrit>.edit() initial selCrit: ${JSON.stringify(selCrit, null, 2)}`); // our hash check appears to be working!

      // dispatch the appropriate action
      p.dispatch( AC.selCrit.edit(selCrit, this.meta) );
    }

    /**
     * handleEditComplete()
     */
    handleEditComplete(completionType) { // completionType: 'Use'/'Save'/'Cancel'
      const p = this.props;

      // apply validation - prematurely returning on errors
      // ... validation errors dynamically are shown in dialog
      if (completionType !== 'Cancel') {
        let valid = true;
        if (!p.selCrit.name.trim()) // name is required
          valid = false;
        if (!p.selCrit.desc.trim()) // desc is required
          valid = false;
        for (const extraFilterObj of p.extraFilter) { // filter is missing some components
          if (!extraFilterObj.operator ||
              !extraFilterObj.value ||
              extraFilterObj.value.length === 0)
            valid = false;
        }
        if (!valid) {
          UserMsg.display('Please resolve the highlighted validation errors.');
          return;
        }
      }

      //***
      //*** emit the appropriate action(s)
      //***

      // we always issue a close (to take down our dialog)
      const actions = [AC.selCrit.edit.close()];

      // issue change actions (when appropriate) ...
      if (this.starting_curHash !== p.selCrit.curHash &&  // when selCrit has actually changed -AND-
          completionType !== 'Cancel') {                  // the Cancel button was NOT used

        // publish our standard PUBLIC sync action
        actions.push( AC.selCrit.edit.changed(p.selCrit) );

            // apply invoker-based 'extra' synchronization (ex: refresh a retrieval based on this selCrit)
        if (this.extraActionsOnCompletionCb) {
          const extraActions = this.extraActionsOnCompletionCb(p.selCrit);
          if (extraActions) {
            if (Array.isArray(extraActions))
              actions.push(...extraActions);
            else
              actions.push(extraActions);
          }
        }
      }

      // issue save (when appropriate) ...
      if (this.starting_curHash !== p.selCrit.dbHash && // when selCrit needs to be saved -AND-
          completionType === 'Save') {                  // the Save button was used

        actions.push( AC.selCrit.save(p.selCrit) );
      }

      // publish the appropriate actions
      p.dispatch( actions );

    }

    /**
     * handleNameChange()
     */
    handleNameChange(event) {
      const p = this.props;
      p.dispatch( AC.selCrit.edit.nameChange(event.target.value) );
    }

    /**
     * handleDescChange()
     */
    handleDescChange(event) {
      const p = this.props;
      p.dispatch( AC.selCrit.edit.descChange(event.target.value) );
    }

    /**
     * handleFieldsChange()
     */
    handleFieldsChange(selectedFieldOptions) { // selectedFieldOptions: Option[ {value, label}, ...]
      const p = this.props;
      p.dispatch( AC.selCrit.edit.fieldsChange(selectedFieldOptions) );
    }

    /**
     * handleSortChange()
     */
    handleSortChange(selectedSortOptions) { // selectedSortOptions: Option[ {value, label, ascDec}, ...]
      const p = this.props;
      p.dispatch( AC.selCrit.edit.sortChange(selectedSortOptions) );
    }

    /**
     * handleDistinguishMajorSortFieldChange()
     */
    handleDistinguishMajorSortFieldChange(event, value) {
      const p = this.props;
      p.dispatch( AC.selCrit.edit.distinguishMajorSortFieldChange(value) );
    }

    /**
     * render()
     */
    render() {
      const p = this.props;

      // no-op, when we are not in an edit session
      if (!p.selCrit)
        return null;

      // NOTE: non-modal dialog: outer click same as 'Use' button
      return <Dialog modal={false}
                     title={`${p.selCrit.target} Filter`}
                     open={true}
                     autoScrollBodyContent={true}
                     onRequestClose={ (buttonClicked) => this.handleEditComplete('Use')}
                     actions={[
                       <FlatButton label="Use"
                                   primary={true}
                                   title="use this filter (without saving)"
                                   onTouchTap={ (e) => this.handleEditComplete('Use') }/>,
                       <FlatButton label="Save"
                                   primary={true}
                                   title={p.selCrit.dbHash===this.starting_curHash ? 'there are NO changes to save' : 'save and use changes'}
                                   disabled={p.selCrit.dbHash===this.starting_curHash}
                                   onTouchTap={ (e) => this.handleEditComplete('Save') }/>,
                       <FlatButton label="Cancel"
                                   primary={true}
                                   title={p.selCrit.curHash===this.starting_curHash ? 'there are NO changes to cancel' : 'cancel changes'}
                                   disabled={p.selCrit.curHash===this.starting_curHash}
                                   onTouchTap={ (e) => this.handleEditComplete('Cancel') }/>,
                     ]}
                     contentStyle={{
                         width:         '90%',
                         maxWidth:      'none',
                         verticalAlign: 'top',
                       }}>

        <TextField floatingLabelText="Name"
                   style={{ width: '10em' }}
                   errorText={p.selCrit.name.trim() ? null : 'Name is required'}
                   value={p.selCrit.name}
                   onChange={this.handleNameChange}/>

        <TextField floatingLabelText="Description"
                   style={{ width: '40em' }}
                   errorText={p.selCrit.desc.trim() ? null : 'Description is required'}
                   value={p.selCrit.desc}
                   onChange={this.handleDescChange}/>

          Display Fields (note that Students Gender/Name/StudentNum are always grouped together):

          <Select multi={true}
                  options={this.fieldOptions}
                  matchProp="label"
                  placeholder="... using default fields"
                  valueComponent={FieldValue}
                  value={p.selectedFieldOptions}
                  onChange={this.handleFieldsChange}
                  resetValue={[]}/>

          Order By:

          <Select multi={true}
                  options={this.sortOptions}
                  matchProp="label"
                  placeholder="... no sort in effect"
                  valueComponent={SortValue}
                  value={p.selectedSortOptions}
                  onChange={this.handleSortChange}
                  resetValue={[]}/>
          <Toggle label="Distinguish Major Sort Field"
                  labelPosition="right"
                  style={{paddingLeft: 20}}
                  toggled={p.selCrit.distinguishMajorSortField}
                  onToggle={this.handleDistinguishMajorSortFieldChange}/>

        <SelCritDetail meta={this.meta}/>

      </Dialog>;
    }
  }, // end of ... component definition

  { // component property injection
    mapStateToProps(appState, ownProps) {
      return {
        selCrit:              appState.editSelCrit.selCrit,
        selectedFieldOptions: appState.editSelCrit.extra ? appState.editSelCrit.extra.selectedFieldOptions : null,
        selectedSortOptions:  appState.editSelCrit.extra ? appState.editSelCrit.extra.selectedSortOptions : null,
        extraFilter:          appState.editSelCrit.extra ? appState.editSelCrit.extra.filter : null,
      };
    }
  }); // end of ... component property injection

// define expected props
EditSelCrit.propTypes = {
}

// keep track of our one-and-only instance
let _singleton = null;

export default EditSelCrit;
<|endoftext|>


<commit_before>ko.bindingHandlers['value'] = {
    'after': ['options', 'foreach'],
    'init': function (element, valueAccessor, allBindings) {
        // Always catch "change" event; possibly other events too if asked
        var eventsToCatch = ["change"];
        var requestedEventsToCatch = allBindings.get("valueUpdate");
        var propertyChangedFired = false;
        if (requestedEventsToCatch) {
            if (typeof requestedEventsToCatch == "string") // Allow both individual event names, and arrays of event names
                requestedEventsToCatch = [requestedEventsToCatch];
            ko.utils.arrayPushAll(eventsToCatch, requestedEventsToCatch);
            eventsToCatch = ko.utils.arrayGetDistinctValues(eventsToCatch);
        }

        var valueUpdateHandler = function() {
            propertyChangedFired = false;
            var modelValue = valueAccessor();
            var elementValue = ko.selectExtensions.readValue(element);
            ko.expressionRewriting.writeValueToProperty(modelValue, allBindings, 'value', elementValue);
        }

        // Workaround for https://github.com/SteveSanderson/knockout/issues/122
        // IE doesn't fire "change" events on textboxes if the user selects a value from its autocomplete list
        var ieAutoCompleteHackNeeded = ko.utils.ieVersion && element.tagName.toLowerCase() == "input" && element.type == "text"
                                       && element.autocomplete != "off" && (!element.form || element.form.autocomplete != "off");
        if (ieAutoCompleteHackNeeded && ko.utils.arrayIndexOf(eventsToCatch, "propertychange") == -1) {
            ko.utils.registerEventHandler(element, "propertychange", function () { propertyChangedFired = true });
            ko.utils.registerEventHandler(element, "blur", function() {
                if (propertyChangedFired) {
                    valueUpdateHandler();
                }
            });
        }

        ko.utils.arrayForEach(eventsToCatch, function(eventName) {
            // The syntax "after<eventname>" means "run the handler asynchronously after the event"
            // This is useful, for example, to catch "keydown" events after the browser has updated the control
            // (otherwise, ko.selectExtensions.readValue(this) will receive the control's value *before* the key event)
            var handler = valueUpdateHandler;
            if (ko.utils.stringStartsWith(eventName, "after")) {
                handler = function() { setTimeout(valueUpdateHandler, 0) };
                eventName = eventName.substring("after".length);
            }
            ko.utils.registerEventHandler(element, eventName, handler);
        });
    },
    'update': function (element, valueAccessor, allBindings) {
        var newValue = ko.utils.unwrapObservable(valueAccessor());
        var elementValue = ko.selectExtensions.readValue(element);
        var valueHasChanged = (newValue !== elementValue);

        if (valueHasChanged) {
            if (ko.utils.tagNameLower(element) === "select") {
                var allowUnset = allBindings.get('valueAllowUnset');
                var applyValueAction = function () {
                    ko.selectExtensions.writeValue(element, newValue, allowUnset);
                };
                applyValueAction();

                if (!allowUnset && newValue !== ko.selectExtensions.readValue(element)) {
                    // If you try to set a model value that can't be represented in an already-populated dropdown, reject that change,
                    // because you're not allowed to have a model value that disagrees with a visible UI selection.
                    ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, "change"]);
                } else {
                    // Workaround for IE6 bug: It won't reliably apply values to SELECT nodes during the same execution thread
                    // right after you've changed the set of OPTION nodes on it. So for that node type, we'll schedule a second thread
                    // to apply the value as well.
                    setTimeout(applyValueAction, 0);
                }
            } else {
                ko.selectExtensions.writeValue(element, newValue);
            }
        }
    }
};
ko.expressionRewriting.twoWayBindings['value'] = true;
<commit_msg>Add propertyChangedFired=false to focus handler.<commit_after>ko.bindingHandlers['value'] = {
    'after': ['options', 'foreach'],
    'init': function (element, valueAccessor, allBindings) {
        // Always catch "change" event; possibly other events too if asked
        var eventsToCatch = ["change"];
        var requestedEventsToCatch = allBindings.get("valueUpdate");
        var propertyChangedFired = false;
        if (requestedEventsToCatch) {
            if (typeof requestedEventsToCatch == "string") // Allow both individual event names, and arrays of event names
                requestedEventsToCatch = [requestedEventsToCatch];
            ko.utils.arrayPushAll(eventsToCatch, requestedEventsToCatch);
            eventsToCatch = ko.utils.arrayGetDistinctValues(eventsToCatch);
        }

        var valueUpdateHandler = function() {
            propertyChangedFired = false;
            var modelValue = valueAccessor();
            var elementValue = ko.selectExtensions.readValue(element);
            ko.expressionRewriting.writeValueToProperty(modelValue, allBindings, 'value', elementValue);
        }

        // Workaround for https://github.com/SteveSanderson/knockout/issues/122
        // IE doesn't fire "change" events on textboxes if the user selects a value from its autocomplete list
        var ieAutoCompleteHackNeeded = ko.utils.ieVersion && element.tagName.toLowerCase() == "input" && element.type == "text"
                                       && element.autocomplete != "off" && (!element.form || element.form.autocomplete != "off");
        if (ieAutoCompleteHackNeeded && ko.utils.arrayIndexOf(eventsToCatch, "propertychange") == -1) {
            ko.utils.registerEventHandler(element, "propertychange", function () { propertyChangedFired = true });
            ko.utils.registerEventHandler(element, "focus", function () { propertyChangedFired = false });
            ko.utils.registerEventHandler(element, "blur", function() {
                if (propertyChangedFired) {
                    valueUpdateHandler();
                }
            });
        }

        ko.utils.arrayForEach(eventsToCatch, function(eventName) {
            // The syntax "after<eventname>" means "run the handler asynchronously after the event"
            // This is useful, for example, to catch "keydown" events after the browser has updated the control
            // (otherwise, ko.selectExtensions.readValue(this) will receive the control's value *before* the key event)
            var handler = valueUpdateHandler;
            if (ko.utils.stringStartsWith(eventName, "after")) {
                handler = function() { setTimeout(valueUpdateHandler, 0) };
                eventName = eventName.substring("after".length);
            }
            ko.utils.registerEventHandler(element, eventName, handler);
        });
    },
    'update': function (element, valueAccessor, allBindings) {
        var newValue = ko.utils.unwrapObservable(valueAccessor());
        var elementValue = ko.selectExtensions.readValue(element);
        var valueHasChanged = (newValue !== elementValue);

        if (valueHasChanged) {
            if (ko.utils.tagNameLower(element) === "select") {
                var allowUnset = allBindings.get('valueAllowUnset');
                var applyValueAction = function () {
                    ko.selectExtensions.writeValue(element, newValue, allowUnset);
                };
                applyValueAction();

                if (!allowUnset && newValue !== ko.selectExtensions.readValue(element)) {
                    // If you try to set a model value that can't be represented in an already-populated dropdown, reject that change,
                    // because you're not allowed to have a model value that disagrees with a visible UI selection.
                    ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, "change"]);
                } else {
                    // Workaround for IE6 bug: It won't reliably apply values to SELECT nodes during the same execution thread
                    // right after you've changed the set of OPTION nodes on it. So for that node type, we'll schedule a second thread
                    // to apply the value as well.
                    setTimeout(applyValueAction, 0);
                }
            } else {
                ko.selectExtensions.writeValue(element, newValue);
            }
        }
    }
};
ko.expressionRewriting.twoWayBindings['value'] = true;
<|endoftext|>


<commit_before>'use strict'

require('./style.css')
require('./themes/mac.css')
require('./grid.css')

const buttons = document.querySelectorAll('button')
const textarea = document.querySelector('.textarea')
const eventName = 'letterReceiver'
const event = new CustomEvent(eventName)

textarea.addEventListener('keydown', (e) => {
  if (isPrintable(e)) {
    e.preventDefault()
  } else {
    e.target.focus()
  }
})

// send event
buttons.forEach((b) => b.addEventListener('click', clickEmitter))

function clickEmitter (e) {
  const button = buttonDOMChecker(e)
  const code = checkButtonCode(button)
  e.code = code
  letterSender(e)
}

function buttonDOMChecker (e) {
  const target = e.target
  const button = target.nodeName === 'BUTTON'
    ? target
    : target.parentNode.nodeName === 'BUTTON'
      ? target.parentNode
      : new Error ('we cant find button in', target)
  return button
}

function checkButtonCode (target) {
  return target.dataset && target.dataset.code
}

document.addEventListener('keydown', letterSender)
document.addEventListener('keyup', buttonDisactivator)

function letterSender (e) {
  buttonActivator(e)

  const code = e.code
  setMode(code)

  if (isPrintable(e)) {
    e.preventDefault()
    event.code = code
    textarea.dispatchEvent(event)
  } else {
    textarea.focus()
  }
}

function buttonQuerySelector (e) {
  const code = e.code
  return document.querySelector(`[data-code=${code}]`)
}

function buttonActivator (e) {
  const button = buttonQuerySelector(e)
  button.classList.add('active')
}

function buttonDisactivator (e) {
  const button = buttonQuerySelector(e)
  button.classList.remove('active')
  clearMode(e.code)
}

// receive event
textarea.addEventListener(eventName, letterListener)

function letterListener (e) {
  if (false) // if selection is at the bottom
    window.scroll(0, 10e3)

  if (isPrintable(e)) {
    changeText(e)
  } else {
    e.target.focus()
  }
}

function changeText (e) {
  const selection = window.getSelection()
  const node = selection.baseNode
  const textContent = node.textContent
  const offset = selection.baseOffset
  node.textContent = textContent.substring(0, offset) + getCodeInMode(e.code, getMode()) + textContent.substring(offset)

  var range = document.createRange()
  range.selectNode(textarea)
  selection.addRange(range)
  console.log(selection)
  console.log(range)

  // var selection = window.getSelection()
  // var content = textarea
  // var html = content.innerHTML
  // console.log(selection, content)
  // content.innerHTML = html.substring(0, selection.focusOffset) +
  //   getCodeInMode(e.code, getMode()) +
  //   html.substring(selection.focusOffset + selection.anchorOffset)
  // e.target.innerHTML += getCodeInMode(e.code, getMode())
}

// data
const codes = require('../data')

function isPrintable (e) {
  const code = getCodeProperty(e.code)
  return code.printable === true
}

function getCodeProperty (property) {
  return codes[property]
}

function getCodeInMode (code, mode = 'Default') {
  if (!getCodeProperty(code))
    new Error('code is undefined')
  return getCodeProperty(code)[mode] || ''
}

let mode
let modes = {
  Alt: false,
  Shift: false,
  CapsLock: false
}

function setMode (code) {
  if (code === 'AltLeft' || code === 'AltRight')
    modes['Alt'] = true
  if (code === 'ShiftLeft' || code === 'ShiftRight')
     modes['Shift'] = true
  if (code === 'CapsLock')
    modes['CapsLock'] = true
  if (code === 'OSLeft' || code === 'OSRight')
    modes['CMD'] = true
}

function clearMode (code) {
  if (code === 'AltLeft' || code === 'AltRight')
    modes['Alt'] = false
  if (code === 'ShiftLeft' || code === 'ShiftRight')
    modes['Shift'] = false
  if (code === 'CapsLock')
    modes['CapsLock'] = false
  if (code === 'OSLeft' || code === 'OSRight')
    modes['CMD'] = false
}

function getMode () {
  let arr = []
  for (let i in modes) {
    modes[i] && arr.push(i)
  }
  return arr.length > 0 ? arr.join('_') : 'Default'
}

function isActive () {
  return false
}<commit_msg>little bit fixed editor<commit_after>'use strict'

require('./style.css')
require('./themes/mac.css')
require('./grid.css')

const buttons = document.querySelectorAll('button')
const textarea = document.querySelector('.textarea')
const eventName = 'letterReceiver'
const event = new CustomEvent(eventName)

textarea.addEventListener('keydown', (e) => {
  if (isPrintable(e)) {
    e.preventDefault()
  } else {
    e.target.focus()
  }
})

// send event
buttons.forEach((b) => b.addEventListener('click', clickEmitter))

function clickEmitter (e) {
  const button = buttonDOMChecker(e)
  const code = checkButtonCode(button)
  e.code = code
  letterSender(e)
}

function buttonDOMChecker (e) {
  const target = e.target
  const button = target.nodeName === 'BUTTON'
    ? target
    : target.parentNode.nodeName === 'BUTTON'
      ? target.parentNode
      : new Error ('we cant find button in', target)
  return button
}

function checkButtonCode (target) {
  return target.dataset && target.dataset.code
}

document.addEventListener('keydown', letterSender)
document.addEventListener('keyup', buttonDisactivator)

function letterSender (e) {
  buttonActivator(e)

  const code = e.code
  setMode(code)

  if (isPrintable(e)) {
    e.preventDefault()
    event.code = code
    textarea.dispatchEvent(event)
  } else {
    textarea.focus()
  }
}

function buttonQuerySelector (e) {
  const code = e.code
  return document.querySelector(`[data-code=${code}]`)
}

function buttonActivator (e) {
  const button = buttonQuerySelector(e)
  button.classList.add('active')
}

function buttonDisactivator (e) {
  const button = buttonQuerySelector(e)
  button.classList.remove('active')
  clearMode(e.code)
}

// receive event
textarea.addEventListener(eventName, letterListener)

function letterListener (e) {
  if (false) // if selection is at the bottom
    window.scroll(0, 10e3)

  if (isPrintable(e)) {
    changeText(e)
  } else {
    e.target.focus()
  }
}

function changeText (e) {
  const selection = window.getSelection()
  const node = selection.baseNode
  const textContent = node.textContent
  const offset = selection.focusOffset
  const anchorOffset = selection.anchorOffset
  node.textContent = textContent.substring(0, anchorOffset) + getCodeInMode(e.code, getMode()) + textContent.substring(offset)

  // var range = document.createRange()
  // range.selectNode(textarea)
  // selection.addRange(range)
  // console.log(selection)
  // console.log(range)

  var range = document.createRange();
  range.setStart(node, anchorOffset + 1)
  range.setEnd(node, anchorOffset + 1)
  selection.removeAllRanges();
  selection.addRange(range);

  // var selection = window.getSelection()
  // var content = textarea
  // var html = content.innerHTML
  // console.log(selection, content)
  // content.innerHTML = html.substring(0, selection.focusOffset) +
  //   getCodeInMode(e.code, getMode()) +
  //   html.substring(selection.focusOffset + selection.anchorOffset)
  // e.target.innerHTML += getCodeInMode(e.code, getMode())
}

// data
const codes = require('../data')

function isPrintable (e) {
  const code = getCodeProperty(e.code)
  return code.printable === true
}

function getCodeProperty (property) {
  return codes[property]
}

function getCodeInMode (code, mode = 'Default') {
  if (!getCodeProperty(code))
    new Error('code is undefined')
  return getCodeProperty(code)[mode] || ''
}

let mode
let modes = {
  Alt: false,
  Shift: false,
  CapsLock: false
}

function setMode (code) {
  if (code === 'AltLeft' || code === 'AltRight')
    modes['Alt'] = true
  if (code === 'ShiftLeft' || code === 'ShiftRight')
     modes['Shift'] = true
  if (code === 'CapsLock')
    modes['CapsLock'] = true
  if (code === 'OSLeft' || code === 'OSRight')
    modes['CMD'] = true
}

function clearMode (code) {
  if (code === 'AltLeft' || code === 'AltRight')
    modes['Alt'] = false
  if (code === 'ShiftLeft' || code === 'ShiftRight')
    modes['Shift'] = false
  if (code === 'CapsLock')
    modes['CapsLock'] = false
  if (code === 'OSLeft' || code === 'OSRight')
    modes['CMD'] = false
}

function getMode () {
  let arr = []
  for (let i in modes) {
    modes[i] && arr.push(i)
  }
  return arr.length > 0 ? arr.join('_') : 'Default'
}

function isActive () {
  return false
}<|endoftext|>


<commit_before>var request = require('request'),
    async = require('async');

module.exports = {

    load: function (config, callback) {

        var self = this,
            calls = {};

        for (var v in config) {
            calls[v] = (function (varname) {
                var fn = function (cb) {
                    if(cb) {
                        console.log('Loading "persistent.' + varname + '" from ' + config[varname].url);
                    } else {
                        console.log('Reloading "persistent.' + varname + '" from ' + config[varname].url);
                    }
                    request(config[varname].url, function (error, response, body) {
                        if (error || response.statusCode != 200) {
                            throw new Error("Could not load static file for '" + varname + "' from: " + config[varname].url + (error ? error : " (status code: " + response.statusCode + ")"));
                        } else {
                            self[varname] = JSON.parse(body);
                            if(config[varname].transform && typeof config[varname].transform == "function") {
                                self[varname] = config[varname].transform(self[varname]);
                            }
                            if (cb) cb();
                        }
                    });
                };
                if (config[varname].maxAge && config[varname].maxAge > 0) {
                    setInterval(function () {
                        fn();
                    }, config[varname].maxAge * 1000);
                }
                return fn;
            })(v);
        }

        async.parallel(calls, function (err, results) {
            callback();
        });

    }
};
<commit_msg>new error management<commit_after>var request = require('request'),
    async = require('async');

module.exports = {

    load: function (config, callback) {

        var self = this,
            calls = {};

        for (var v in config) {
            calls[v] = (function (varname) {
                var fn = function (asyncCallback) {
                    if (asyncCallback) {
                        console.log('Loading "persistent.' + varname + '" from ' + config[varname].url);
                    } else {
                        console.log('Reloading "persistent.' + varname + '" from ' + config[varname].url);
                    }
                    request(config[varname].url, function (error, response, body) {
                        if (error || response.statusCode != 200) {
                            if (asyncCallback) {
                                asyncCallback(new Error("Could not load static file for '" + varname + "' from: " + config[varname].url + (error ? error : " (status code: " + response.statusCode + ")")));
                            }
                        } else {
                            self[varname] = JSON.parse(body);
                            if (config[varname].transform && typeof config[varname].transform == "function") {
                                self[varname] = config[varname].transform(self[varname]);
                            }
                            if (asyncCallback) {
                                asyncCallback();
                            }
                        }
                    });
                };
                if (config[varname].maxAge && config[varname].maxAge > 0) {
                    setInterval(function () {
                        fn();
                    }, config[varname].maxAge * 1000);
                }
                return fn;
            })(v);
        }

        async.parallel(calls, function (err) {
            callback(err);
        });

    }
};
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2014 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  package: 'com.google.foam.demos.lifestar',
  name: 'Disk',
  extends: 'foam.graphics.Box',

  requires: [ 'foam.graphics.Arc' ],

  classes: [
    {
      name: 'Point',
      extends: 'foam.graphics.Circle',
      properties: [
        { name: 'r' },
        { name: 't' },
        { name: 'g' },
        { class: 'Float', name: 'glowRadius' }
      ],
      methods: [
        function polar3D(r, t, g) {
          this.x = 250 + Math.sin(t) * Math.cos(g) * r;
          this.y = 250 + Math.cos(t) * r;
        },
        function paintSelf(x) {
          this.polar3D(this.r, this.t, this.g);
          this.SUPER(x);
          if ( this.glowRadius ) {
            x.globalAlpha = 0.2;
            var oldR = this.radius;
            this.radius = this.glowRadius;
            this.SUPER(x);
            this.radius = oldR;
          }
        }
      ]
    }
  ],

  properties: [
    [ 'n',  227 ],
    [ 'width', 500 ],
    [ 'height', 500 ],
    [ 'time',   0 ],
    [ 'color', 'black' ]
  ],

  methods: [
    function initCView() {
      this.SUPER();
      for ( var i = 0 ; i < this.n ; i++ ) this.addPoint(i);
      this.tick();
    },
    function addPoint(i) {
      var p = this.Point.create({
        r: i*200/this.n,
        t: i*Math.PI*20/this.n,
        g: 0, //i*Math.PI/5/this.n,
        radius: 3,
        border: null,
        arcWidth: 0
      });

      this.time$.sub(function() {
        p.t -= 0.01;
        p.g += 0.01;
        var on = Math.abs((this.time % this.n - i + this.n)%this.n) < 20
        p.glowRadius = on ? 8 : 0;
        var s = on ? 100 : 70;
        var l = on ? 70 : 30;
        p.color = this.hsl(i*365/this.n, s, l);
      }.bind(this));
      this.add(p);
    }
  ],
  listeners: [
    {
      name: 'tick',
      isFramed: true,
      code: function() { this.time++; this.tick(); this.invalidated.pub(); }
    }
  ]
});
<commit_msg>Copyright and formatting.<commit_after>/**
 * @license
 * Copyright 2014 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'com.google.foam.demos.lifestar',
  name: 'Disk',
  extends: 'foam.graphics.Box',

  requires: [ 'foam.graphics.Arc' ],

  classes: [
    {
      name: 'Point',
      extends: 'foam.graphics.Circle',
      properties: [
        { name: 'r' },
        { name: 't' },
        { name: 'g' },
        { class: 'Float', name: 'glowRadius' }
      ],
      methods: [
        function polar3D(r, t, g) {
          this.x = 250 + Math.sin(t) * Math.cos(g) * r;
          this.y = 250 + Math.cos(t) * r;
        },
        function paintSelf(x) {
          this.polar3D(this.r, this.t, this.g);
          this.SUPER(x);
          if ( this.glowRadius ) {
            x.globalAlpha = 0.2;
            var oldR = this.radius;
            this.radius = this.glowRadius;
            this.SUPER(x);
            this.radius = oldR;
          }
        }
      ]
    }
  ],

  properties: [
    [ 'n',      227 ],
    [ 'width',  500 ],
    [ 'height', 500 ],
    [ 'time',   0 ],
    [ 'color', 'black' ]
  ],

  methods: [
    function initCView() {
      this.SUPER();
      for ( var i = 0 ; i < this.n ; i++ ) this.addPoint(i);
      this.tick();
    },
    function addPoint(i) {
      var p = this.Point.create({
        r: i*200/this.n,
        t: i*Math.PI*20/this.n,
        g: 0, //i*Math.PI/5/this.n,
        radius: 3,
        border: null,
        arcWidth: 0
      });

      this.time$.sub(function() {
        p.t -= 0.01;
        p.g += 0.01;
        var on = Math.abs((this.time % this.n - i + this.n)%this.n) < 20
        p.glowRadius = on ? 8 : 0;
        var s = on ? 100 : 70;
        var l = on ? 70 : 30;
        p.color = this.hsl(i*365/this.n, s, l);
      }.bind(this));
      this.add(p);
    }
  ],
  listeners: [
    {
      name: 'tick',
      isFramed: true,
      code: function() { this.time++; this.tick(); this.invalidated.pub(); }
    }
  ]
});
<|endoftext|>


<commit_before>var config = require("./e2e.auth.json");

// e2e.conf.js
exports.config = {
  framework: 'jasmine',
  seleniumAddress: 'http://localhost:4444/wd/hub',
  chromeOnly: true,
  params: config,
  jasmineNodeOpts: {
    // defaultTimeoutInterval: 120000,
    realtimeFailure: true
  },

  /*
   * Specify the parameters of the browsers to test
   */
  multiCapabilities:[
    // Working on Windows and OSX
    { 'browserName': 'chrome' },

    // Firefox: 46.0.1 and below supposedly work, latest does not
    // See http://stackoverflow.com/questions/38644703/org-openqa-selenium-firefox-notconnectedexception-when-running-selenium-from-com
    // { 'browserName': 'firefox' },

    // Safari: has not been tested
    // NOTE: OSX Only
    //{ 'browserName': 'safari' },

    // IE11: fails to connect to browser
    // NOTE: Windows Only
    // See https://github.com/seleniumhq/selenium-google-code-issue-archive/issues/6511
    // { 'browserName': 'internet explorer', 'version': '11' },
  ],

  // Disable animations for testing
  onPrepare: function() {
    /* global angular: false, browser: false, jasmine: false */
    'use strict';
    // Disable animations
    var disableNgAnimate = function() {
      angular.module('disableNgAnimate', []).run(['$animate', function($animate) {
        $animate.enabled(false);
      }]);
    };

    var disableCssAnimate = function() {
      angular
        .module('disableCssAnimate', [])
        .run(function() {
            var style = document.createElement('style');
            style.type = 'text/css';
            style.innerHTML = '* {' +
                // See http://stackoverflow.com/questions/26584451/how-to-disable-animations-in-protractor-for-angular-js-application
                // Disable CSS animation for e2e tests
                '-webkit-transition: none !important;' +
                '-moz-transition: none !important' +
                '-o-transition: none !important' +
                '-ms-transition: none !important' +
                'transition: none !important' +
                '}' +

                // See https://github.com/angular/protractor/issues/319
                // Disable sticky footer for e2e tests
                '#footer {' +
                'position: static;' +
                '}';
            document.getElementsByTagName('head')[0].appendChild(style);
        });
    };

    browser.addMockModule('disableNgAnimate', disableNgAnimate);
    browser.addMockModule('disableCssAnimate', disableCssAnimate);
  },

  /*
   * Specify which test spec(s) to run
   */
  suites: {
    landing: 'tests/e2e/landing.e2e.js',
    navbar: 'tests/e2e/navbar.e2e.js',
    swagger: 'tests/e2e/swagger.e2e.js',
    help: 'tests/e2e/help.e2e.js',

    signup: 'tests/e2e/signup.e2e.js',
    login: 'tests/e2e/login.e2e.js',
    reset: 'tests/e2e/reset.e2e.js',

    catalog: 'tests/e2e/catalog.e2e.js',
    addSpec: 'tests/e2e/addSpec.e2e.js',
    editSpec: 'tests/e2e/editSpec.e2e.js',
    dashboard: 'tests/e2e/dashboard.e2e.js',
    addService: 'tests/e2e/addService.e2e.js',
    editService: 'tests/e2e/editService.e2e.js',
    console: 'tests/e2e/console.e2e.js',
  }
}
<commit_msg>Fixed stale file path<commit_after>var config = require("./protractor.auth.json");

// e2e.conf.js
exports.config = {
  framework: 'jasmine',
  seleniumAddress: 'http://localhost:4444/wd/hub',
  chromeOnly: true,
  params: config,
  jasmineNodeOpts: {
    // defaultTimeoutInterval: 120000,
    realtimeFailure: true
  },

  /*
   * Specify the parameters of the browsers to test
   */
  multiCapabilities:[
    // Working on Windows and OSX
    { 'browserName': 'chrome' },

    // Firefox: 46.0.1 and below supposedly work, latest does not
    // See http://stackoverflow.com/questions/38644703/org-openqa-selenium-firefox-notconnectedexception-when-running-selenium-from-com
    // { 'browserName': 'firefox' },

    // Safari: has not been tested
    // NOTE: OSX Only
    //{ 'browserName': 'safari' },

    // IE11: fails to connect to browser
    // NOTE: Windows Only
    // See https://github.com/seleniumhq/selenium-google-code-issue-archive/issues/6511
    // { 'browserName': 'internet explorer', 'version': '11' },
  ],

  // Disable animations for testing
  onPrepare: function() {
    /* global angular: false, browser: false, jasmine: false */
    'use strict';
    // Disable animations
    var disableNgAnimate = function() {
      angular.module('disableNgAnimate', []).run(['$animate', function($animate) {
        $animate.enabled(false);
      }]);
    };

    var disableCssAnimate = function() {
      angular
        .module('disableCssAnimate', [])
        .run(function() {
            var style = document.createElement('style');
            style.type = 'text/css';
            style.innerHTML = '* {' +
                // See http://stackoverflow.com/questions/26584451/how-to-disable-animations-in-protractor-for-angular-js-application
                // Disable CSS animation for e2e tests
                '-webkit-transition: none !important;' +
                '-moz-transition: none !important' +
                '-o-transition: none !important' +
                '-ms-transition: none !important' +
                'transition: none !important' +
                '}' +

                // See https://github.com/angular/protractor/issues/319
                // Disable sticky footer for e2e tests
                '#footer {' +
                'position: static;' +
                '}';
            document.getElementsByTagName('head')[0].appendChild(style);
        });
    };

    browser.addMockModule('disableNgAnimate', disableNgAnimate);
    browser.addMockModule('disableCssAnimate', disableCssAnimate);
  },

  /*
   * Specify which test spec(s) to run
   */
  suites: {
    landing: 'tests/e2e/landing.e2e.js',
    navbar: 'tests/e2e/navbar.e2e.js',
    swagger: 'tests/e2e/swagger.e2e.js',
    help: 'tests/e2e/help.e2e.js',

    signup: 'tests/e2e/signup.e2e.js',
    login: 'tests/e2e/login.e2e.js',
    reset: 'tests/e2e/reset.e2e.js',

    catalog: 'tests/e2e/catalog.e2e.js',
    addSpec: 'tests/e2e/addSpec.e2e.js',
    editSpec: 'tests/e2e/editSpec.e2e.js',
    dashboard: 'tests/e2e/dashboard.e2e.js',
    addService: 'tests/e2e/addService.e2e.js',
    editService: 'tests/e2e/editService.e2e.js',
    console: 'tests/e2e/console.e2e.js',
  }
}
<|endoftext|>


<commit_before>(function() {
  var module = angular.module('loom_table_view_directive', []);

  module.filter('table', function() {
    return function(input, filter) {
      var out = '';

      if (input) {
        //converting the input and filter strings to lower case for a case insensitive filtering
        var inputLower = input.toLowerCase(),
            filterLower = filter.toLowerCase();
        if (inputLower.indexOf(filterLower) !== -1) {
          out = input;
        }
      }
      return out;
    };
  });

  function clone(obj) {
    // Handle the 3 simple types, and null or undefined
    if (obj == null || typeof obj != 'object') {
      return obj;
    }

    var copy;

    // Handle Array
    if (obj instanceof Array) {
      copy = [];
      for (var i = 0, len = obj.length; i < len; i++) {
        copy[i] = clone(obj[i]);
      }
      return copy;
    }

    // Handle Object
    if (obj instanceof Object) {
      copy = {};
      for (var attr in obj) {
        if (obj.hasOwnProperty(attr)) {
          copy[attr] = clone(obj[attr]);
        }
      }
      return copy;
    }
  }

  module.directive('loomTableView',
      function(tableFilter, mapService, $http, tableViewService, featureManagerService, dialogService, $translate) {
        return {
          restrict: 'C',
          templateUrl: 'tableview/partial/tableview.tpl.html',
          link: function(scope, element) {

            function resizeModal() {
              var containerHeight = angular.element('#table-view-window .modal-content')[0].clientHeight;
              var headerHeight = angular.element('#table-view-window .modal-header')[0].clientHeight;

              var contentHeight = containerHeight - headerHeight;
              //if conatainerHeight is 0 then the modal is closed so we shouldn't bother resizing
              if (containerHeight === 0) {
                return;
              }

              element[0].parentElement.style.height = contentHeight + 'px';

              var bodyHeight = contentHeight;// - angular.element('#table-view-window .modal-footer')[0].clientHeight;
              angular.element('#table-view-window .modal-body')[0].style.height = bodyHeight + 'px';

              //resize the panel to account for the filter text box and padding
              angular.element('#table-view-window .panel')[0].style.height = bodyHeight - 134 + 'px';
            }

            angular.element('#table-view-window').on('shown.bs.modal', function() {
              resizeModal();
              $.bootstrapSortable();
            });

            $(window).resize(resizeModal);

            scope.toggleWordWrap = function() {
              var tableElement = angular.element('#table-view-window .table-hover')[0];

              if (tableElement.style.whiteSpace !== 'normal') {
                tableElement.style.whiteSpace = 'normal';
              } else {
                tableElement.style.whiteSpace = 'nowrap';
              }
            };

            scope.rows = clone(tableViewService.rows);
            scope.attributes = tableViewService.attributeNameList;
            scope.restrictions = tableViewService.restrictionList;

            scope.filterText = '';
            scope.filteringTable = false;

            scope.filterTable = function() {
              if (scope.filteringTable) {
                scope.filterText = '';
              }

              if (scope.filterText === '') {
                scope.clearFilter();
                return;
              }

              scope.filteringTable = true;
              for (var feat in scope.rows) {
                scope.rows[feat].visible = false;

                for (var prop in scope.rows[feat].feature.properties) {
                  if (tableFilter(scope.rows[feat].feature.properties[prop], scope.filterText) !== '') {
                    scope.rows[feat].visible = true;
                    break;
                  }
                }
              }
            };

            scope.clearFilter = function() {
              for (var feat in scope.rows) {
                scope.rows[feat].visible = true;
              }

              scope.filteringTable = false;
            };

            scope.selectedRow = null;
            scope.selectFeature = function(feature) {
              if (scope.selectedRow) {
                scope.selectedRow.selected = false;
              }
              if (feature) {
                feature.selected = true;
              }
              scope.selectedRow = feature;
            };

            scope.goToMap = function() {
              var projectedgeom = transformGeometry(scope.selectedRow.feature.geometry,
                  tableViewService.selectedLayer.get('metadata').projection,
                  mapService.map.getView().getView2D().getProjection());

              mapService.zoomToExtent(projectedgeom.getExtent());

              var item = {layer: tableViewService.selectedLayer, features: [scope.selectedRow.feature]};
              $('#table-view-window').modal('hide');
              featureManagerService.show(item);

            };

            $('#table-view-window').on('hidden.bs.modal', function(e) {
              tableViewService.rows = [];
              tableViewService.attributeNameList = [];
              scope.restrictions = {};

              scope.filterText = '';
              scope.filteringTable = false;
              scope.selectedRow = null;
            });
            $('#table-view-window').on('show.bs.modal', function(e) {
              scope.attributes = tableViewService.attributeNameList;

              //this needs to be deep copied so the original values will be available to pass to the feature manager
              scope.rows = clone(tableViewService.rows);
              for (var row in scope.rows) {
                if (scope.rows[row].selected === true) {
                  scope.selectedRow = scope.rows[row];
                }
              }

              scope.restrictions = tableViewService.restrictionList;

              featureManagerService.hide();
            });

            function hasValidationErrors() {
              var numErrors = 0;
              var validationNumbers;
              for (var row in scope.rows) {
                var feature = scope.rows[row].feature;
                for (var prop in feature.properties) {
                  if (feature.properties[prop] !== '' && feature.properties[prop] !== null &&
                      scope.restrictions[prop] === 'int') {
                    validationNumbers = /^[-+]?[0-9]*$/;
                    if (!validationNumbers.test(feature.properties[prop])) {
                      numErrors++;
                    }
                  } else if (feature.properties[prop] !== '' && feature.properties[prop] !== null &&
                      scope.restrictions[prop] === 'double') {
                    validationNumbers = /^[-+]?[0-9]*\.?[0-9]+$/;
                    if (!validationNumbers.test(feature.properties[prop])) {
                      numErrors++;
                    }
                  }
                }
              }
              if (numErrors > 0) {
                dialogService.warn($translate('save_attributes'), $translate('invalid_fields', {value: numErrors}),
                    [$translate('btn_ok')], false);
                return true;
              } else {
                return false;
              }
            }

            scope.saveTable = function() {
              if (hasValidationErrors() === true) {
                //returning a string, even an empty one, will tell xeditable to not close the table form
                return '';
              }

              for (var featureIndex = 0; featureIndex < scope.rows.length; ++featureIndex) {
                var originalPropertyArray = [];
                var propertyArray = [];
                var originalFeature = tableViewService.rows[featureIndex].feature;
                var feature = scope.rows[featureIndex].feature;

                for (var prop in feature.properties) {
                  var validationNumbers;
                  if (feature.properties[prop] !== '' && feature.properties[prop] !== null &&
                      scope.restrictions[prop] === 'int') {
                    validationNumbers = /^[-+]?[0-9]*$/;
                    if (!validationNumbers.test(feature.properties[prop])) {
                      dialogService.warn($translate('save_attributes'), $translate('invalid_fields', {value: 7}),
                          [$translate('btn_ok')], false);
                    }
                  } else if (feature.properties[prop] !== '' && feature.properties[prop] !== null &&
                      scope.restrictions[prop] === 'double') {
                    validationNumbers = /^[-+]?[0-9]*\.?[0-9]+$/;
                    if (!validationNumbers.test(feature.properties[prop])) {
                      alert('invalid double');
                    }
                  }
                  propertyArray.push({0: prop, 1: feature.properties[prop]});
                  originalPropertyArray.push({0: prop, 1: originalFeature.properties[prop]});
                }

                featureManagerService.setSelectedItem({type: 'feature', id: originalFeature.id,
                  properties: originalPropertyArray});
                featureManagerService.setSelectedItemProperties(originalPropertyArray);
                featureManagerService.setSelectedLayer(tableViewService.selectedLayer);
                featureManagerService.endAttributeEditing(true, false, propertyArray);
              }
              $.bootstrapSortable();
            };

          }
        };
      });
}());
<commit_msg>Removed some errant code and changed the number validation to account for the refactoring of their global functions.<commit_after>(function() {
  var module = angular.module('loom_table_view_directive', []);

  module.filter('table', function() {
    return function(input, filter) {
      var out = '';

      if (input) {
        //converting the input and filter strings to lower case for a case insensitive filtering
        var inputLower = input.toLowerCase(),
            filterLower = filter.toLowerCase();
        if (inputLower.indexOf(filterLower) !== -1) {
          out = input;
        }
      }
      return out;
    };
  });

  function clone(obj) {
    // Handle the 3 simple types, and null or undefined
    if (obj == null || typeof obj != 'object') {
      return obj;
    }

    var copy;

    // Handle Array
    if (obj instanceof Array) {
      copy = [];
      for (var i = 0, len = obj.length; i < len; i++) {
        copy[i] = clone(obj[i]);
      }
      return copy;
    }

    // Handle Object
    if (obj instanceof Object) {
      copy = {};
      for (var attr in obj) {
        if (obj.hasOwnProperty(attr)) {
          copy[attr] = clone(obj[attr]);
        }
      }
      return copy;
    }
  }

  module.directive('loomTableView',
      function(tableFilter, mapService, $http, tableViewService, featureManagerService, dialogService, $translate) {
        return {
          restrict: 'C',
          templateUrl: 'tableview/partial/tableview.tpl.html',
          link: function(scope, element) {

            function resizeModal() {
              var containerHeight = angular.element('#table-view-window .modal-content')[0].clientHeight;
              var headerHeight = angular.element('#table-view-window .modal-header')[0].clientHeight;

              var contentHeight = containerHeight - headerHeight;
              //if conatainerHeight is 0 then the modal is closed so we shouldn't bother resizing
              if (containerHeight === 0) {
                return;
              }

              element[0].parentElement.style.height = contentHeight + 'px';

              var bodyHeight = contentHeight;// - angular.element('#table-view-window .modal-footer')[0].clientHeight;
              angular.element('#table-view-window .modal-body')[0].style.height = bodyHeight + 'px';

              //resize the panel to account for the filter text box and padding
              angular.element('#table-view-window .panel')[0].style.height = bodyHeight - 134 + 'px';
            }

            angular.element('#table-view-window').on('shown.bs.modal', function() {
              resizeModal();
              $.bootstrapSortable();
            });

            $(window).resize(resizeModal);

            scope.toggleWordWrap = function() {
              var tableElement = angular.element('#table-view-window .table-hover')[0];

              if (tableElement.style.whiteSpace !== 'normal') {
                tableElement.style.whiteSpace = 'normal';
              } else {
                tableElement.style.whiteSpace = 'nowrap';
              }
            };

            scope.rows = clone(tableViewService.rows);
            scope.attributes = tableViewService.attributeNameList;
            scope.restrictions = tableViewService.restrictionList;

            scope.filterText = '';
            scope.filteringTable = false;

            scope.filterTable = function() {
              if (scope.filteringTable) {
                scope.filterText = '';
              }

              if (scope.filterText === '') {
                scope.clearFilter();
                return;
              }

              scope.filteringTable = true;
              for (var feat in scope.rows) {
                scope.rows[feat].visible = false;

                for (var prop in scope.rows[feat].feature.properties) {
                  if (tableFilter(scope.rows[feat].feature.properties[prop], scope.filterText) !== '') {
                    scope.rows[feat].visible = true;
                    break;
                  }
                }
              }
            };

            scope.clearFilter = function() {
              for (var feat in scope.rows) {
                scope.rows[feat].visible = true;
              }

              scope.filteringTable = false;
            };

            scope.selectedRow = null;
            scope.selectFeature = function(feature) {
              if (scope.selectedRow) {
                scope.selectedRow.selected = false;
              }
              if (feature) {
                feature.selected = true;
              }
              scope.selectedRow = feature;
            };

            scope.goToMap = function() {
              var projectedgeom = transformGeometry(scope.selectedRow.feature.geometry,
                  tableViewService.selectedLayer.get('metadata').projection,
                  mapService.map.getView().getView2D().getProjection());

              mapService.zoomToExtent(projectedgeom.getExtent());

              var item = {layer: tableViewService.selectedLayer, features: [scope.selectedRow.feature]};
              $('#table-view-window').modal('hide');
              featureManagerService.show(item);

            };

            $('#table-view-window').on('hidden.bs.modal', function(e) {
              tableViewService.rows = [];
              tableViewService.attributeNameList = [];
              scope.restrictions = {};

              scope.filterText = '';
              scope.filteringTable = false;
              scope.selectedRow = null;
            });
            $('#table-view-window').on('show.bs.modal', function(e) {
              scope.attributes = tableViewService.attributeNameList;

              //this needs to be deep copied so the original values will be available to pass to the feature manager
              scope.rows = clone(tableViewService.rows);
              for (var row in scope.rows) {
                if (scope.rows[row].selected === true) {
                  scope.selectedRow = scope.rows[row];
                }
              }

              scope.restrictions = tableViewService.restrictionList;

              featureManagerService.hide();
            });

            function hasValidationErrors() {
              var numErrors = 0;
              for (var row in scope.rows) {
                var feature = scope.rows[row].feature;
                for (var prop in feature.properties) {
                  if (feature.properties[prop] !== '' && feature.properties[prop] !== null &&
                      scope.restrictions[prop] === 'int') {
                    if (!validateInteger(feature.properties[prop])) {
                      numErrors++;
                    }
                  } else if (feature.properties[prop] !== '' && feature.properties[prop] !== null &&
                      scope.restrictions[prop] === 'double') {
                    if (!validateDouble(feature.properties[prop])) {
                      numErrors++;
                    }
                  }
                }
              }
              if (numErrors > 0) {
                dialogService.warn($translate('save_attributes'), $translate('invalid_fields', {value: numErrors}),
                    [$translate('btn_ok')], false);
                return true;
              } else {
                return false;
              }
            }

            scope.saveTable = function() {
              if (hasValidationErrors() === true) {
                //returning a string, even an empty one, will stop xeditable from closing the table form
                return 'Invalid fields detected';
              }

              for (var featureIndex = 0; featureIndex < scope.rows.length; ++featureIndex) {
                var originalPropertyArray = [];
                var propertyArray = [];
                var originalFeature = tableViewService.rows[featureIndex].feature;
                var feature = scope.rows[featureIndex].feature;

                for (var prop in feature.properties) {
                  propertyArray.push({0: prop, 1: feature.properties[prop]});
                  originalPropertyArray.push({0: prop, 1: originalFeature.properties[prop]});
                }

                featureManagerService.setSelectedItem({type: 'feature', id: originalFeature.id,
                  properties: originalPropertyArray});
                featureManagerService.setSelectedItemProperties(originalPropertyArray);
                featureManagerService.setSelectedLayer(tableViewService.selectedLayer);
                featureManagerService.endAttributeEditing(true, false, propertyArray);
              }
              $.bootstrapSortable();
            };

          }
        };
      });
}());
<|endoftext|>