<commit_before>const UPDATE_TIMEOUT = 50
const MIN_FLUSH_DELAY = 100

class CentralScheduler {
  constructor(pm) {
    this.waiting = []
    this.timeout = null
    this.lastForce = 0
    this.pm = pm
    this.force = this.force.bind(this)
    pm.on("flush", this.onFlush.bind(this))
  }

  set(f) {
    if (this.waiting.length == 0)
      this.timeout = setTimeout(this.force, UPDATE_TIMEOUT)
    if (this.waiting.indexOf(f) == -1) this.waiting.push(f)
  }

  unset(f) {
    let index = this.waiting.indexOf(f)
    if (index > -1) this.waiting.splice(index, 1)
  }

  force() {
    clearTimeout(this.timeout)
    this.lastForce = Date.now()

    while (this.waiting.length) {
      for (let i = 0; i < this.waiting.length; i++) {
        let result = this.waiting[i]()
        if (result) this.waiting[i] = result
        else this.waiting.splice(i--, 1)
      }
    }
  }

  onFlush() {
    if (this.waiting.length && (Date.now() - this.lastForce) > MIN_FLUSH_DELAY)
      this.force()
  }

  static get(pm) {
    return pm.mod.centralScheduler || (pm.mod.centralScheduler = new this(pm))
  }
}

// :: (ProseMirror, () -> ?() -> ?())
// Schedule a DOM update function to be called either the next time
// the editor is [flushed](#ProseMirror.flush), or if no flush happens
// immediately, after 200 milliseconds. This is used to synchronize
// DOM updates and read to prevent [DOM layout
// thrashing](http://eloquentjavascript.net/13_dom.html#p_nnTb9RktUT).
//
// Often, your updates will need to both read and write from the DOM.
// To schedule such access in lockstep with other modules, the
// function you give can return another function, which may return
// another function, and so on. The first call should _write_ to the
// DOM, and _not read_. If a _read_ needs to happen, that should be
// done in the function returned from the first call. If that has to
// be followed by another _write_, that should be done in a function
// returned from the second function, and so on.
export function scheduleDOMUpdate(pm, f) { CentralScheduler.get(pm).set(f) }

// :: (ProseMirror, () -> ?() -> ?())
// Cancel an update scheduled with `scheduleDOMUpdate`. Calling this with
// a function that is not actually scheduled is harmless.
export function unscheduleDOMUpdate(pm, f) { CentralScheduler.get(pm).unset(f) }

// ;; Helper for scheduling updates whenever any of a series of events
// happen.
export class UpdateScheduler {
  // :: (ProseMirror, string, () -> ?())
  // Creates an update scheduler for the given editor. `events` should
  // be a space-separated list of event names (for example
  // `"selectionChange change"`). `start` should be a function as
  // expected by `scheduleDOMUpdate`.
  constructor(pm, events, start) {
    this.pm = pm
    this.start = start

    this.events = events.split(" ")
    this.onEvent = this.onEvent.bind(this)
    this.events.forEach(event => pm.on(event, this.onEvent))
  }

  // :: ()
  // Detach the event handlers registered by this scheduler.
  detach() {
    unscheduleDOMUpdate(this.pm, this.start)
    this.events.forEach(event => this.pm.off(event, this.onEvent))
  }

  onEvent() {
    scheduleDOMUpdate(this.pm, this.start)
  }

  // :: ()
  // Force an update. Note that if the editor has scheduled a flush,
  // the update is still delayed until the flush occurs.
  force() {
    if (this.pm.operation) {
      this.onEvent()
    } else {
      unscheduleDOMUpdate(this.pm, this.start)
      for (let run = this.start; run; run = run()) {}
    }
  }
}
<commit_msg>Fix race condition in ui/update.js<commit_after>const UPDATE_TIMEOUT = 50
const MIN_FLUSH_DELAY = 100

class CentralScheduler {
  constructor(pm) {
    this.waiting = []
    this.timeout = null
    this.lastForce = 0
    this.pm = pm
    this.timedOut = () => {
      if (this.pm.operation)
        this.timeout = setTimeout(this.timedOut, UPDATE_TIMEOUT)
      else
        this.force()
    }
    pm.on("flush", this.onFlush.bind(this))
  }

  set(f) {
    if (this.waiting.length == 0)
      this.timeout = setTimeout(this.timedOut, UPDATE_TIMEOUT)
    if (this.waiting.indexOf(f) == -1) this.waiting.push(f)
  }

  unset(f) {
    let index = this.waiting.indexOf(f)
    if (index > -1) this.waiting.splice(index, 1)
  }

  force() {
    clearTimeout(this.timeout)
    this.lastForce = Date.now()

    while (this.waiting.length) {
      for (let i = 0; i < this.waiting.length; i++) {
        let result = this.waiting[i]()
        if (result) this.waiting[i] = result
        else this.waiting.splice(i--, 1)
      }
    }
  }

  onFlush() {
    if (this.waiting.length && (Date.now() - this.lastForce) > MIN_FLUSH_DELAY)
      this.force()
  }

  static get(pm) {
    return pm.mod.centralScheduler || (pm.mod.centralScheduler = new this(pm))
  }
}

// :: (ProseMirror, () -> ?() -> ?())
// Schedule a DOM update function to be called either the next time
// the editor is [flushed](#ProseMirror.flush), or if no flush happens
// immediately, after 200 milliseconds. This is used to synchronize
// DOM updates and read to prevent [DOM layout
// thrashing](http://eloquentjavascript.net/13_dom.html#p_nnTb9RktUT).
//
// Often, your updates will need to both read and write from the DOM.
// To schedule such access in lockstep with other modules, the
// function you give can return another function, which may return
// another function, and so on. The first call should _write_ to the
// DOM, and _not read_. If a _read_ needs to happen, that should be
// done in the function returned from the first call. If that has to
// be followed by another _write_, that should be done in a function
// returned from the second function, and so on.
export function scheduleDOMUpdate(pm, f) { CentralScheduler.get(pm).set(f) }

// :: (ProseMirror, () -> ?() -> ?())
// Cancel an update scheduled with `scheduleDOMUpdate`. Calling this with
// a function that is not actually scheduled is harmless.
export function unscheduleDOMUpdate(pm, f) { CentralScheduler.get(pm).unset(f) }

// ;; Helper for scheduling updates whenever any of a series of events
// happen.
export class UpdateScheduler {
  // :: (ProseMirror, string, () -> ?())
  // Creates an update scheduler for the given editor. `events` should
  // be a space-separated list of event names (for example
  // `"selectionChange change"`). `start` should be a function as
  // expected by `scheduleDOMUpdate`.
  constructor(pm, events, start) {
    this.pm = pm
    this.start = start

    this.events = events.split(" ")
    this.onEvent = this.onEvent.bind(this)
    this.events.forEach(event => pm.on(event, this.onEvent))
  }

  // :: ()
  // Detach the event handlers registered by this scheduler.
  detach() {
    unscheduleDOMUpdate(this.pm, this.start)
    this.events.forEach(event => this.pm.off(event, this.onEvent))
  }

  onEvent() {
    scheduleDOMUpdate(this.pm, this.start)
  }

  // :: ()
  // Force an update. Note that if the editor has scheduled a flush,
  // the update is still delayed until the flush occurs.
  force() {
    if (this.pm.operation) {
      this.onEvent()
    } else {
      unscheduleDOMUpdate(this.pm, this.start)
      for (let run = this.start; run; run = run()) {}
    }
  }
}
<|endoftext|>


<commit_before>function runTest()
{
    FBTest.sysout("commandline.cd.START");
    FBTest.openNewTab(basePath + "commandLine/api/cd.html", function(win)
    {
        FBTest.openFirebug();
        FBTest.clearCache();
        FBTest.enableConsolePanel(function(win)
        {
            var tasks = new FBTest.TaskList();

            tasks.push(FBTest.executeCommandAndVerify, "cd(frames[0])",
                "[\"Current window:\", Window cdFrame.html]",
                "pre", "objectBox objectBox-array");

            tasks.push(FBTest.executeCommandAndVerify, "$(\"#test-iframe-1\")",
                /<div\s*id=\"test-iframe-1\">/,
                "a", "objectLink objectLink-element");

            tasks.push(FBTest.executeCommandAndVerify, "cd(top)",
                "[\"Current window:\", Window cd.html]",
                "pre", "objectBox objectBox-array");

            tasks.run(function() {
                FBTest.testDone("commandline.cd.DONE");
            });
        });
    });
}
<commit_msg>Update tests after 13b78e8<commit_after>function runTest()
{
    FBTest.sysout("commandline.cd.START");
    FBTest.openNewTab(basePath + "commandLine/api/cd.html", function(win)
    {
        FBTest.openFirebug();
        FBTest.clearCache();
        FBTest.enableConsolePanel(function(win)
        {
            var tasks = new FBTest.TaskList();
            tasks.push(FBTest.executeCommandAndVerify, "cd(frames[0])",
                "Current window: Window cdFrame.html",
                "div", "logRow-info");

            tasks.push(FBTest.executeCommandAndVerify, "$(\"#test-iframe-1\")",
                /<div\s*id=\"test-iframe-1\">/,
                "a", "objectLink objectLink-element");

            tasks.push(FBTest.executeCommandAndVerify, "cd(top)",
                "Current window: Window cd.html",
                "div", "logRow-info");

            tasks.run(function() {
                FBTest.testDone("commandline.cd.DONE");
            });
        });
    });
}
<|endoftext|>


<commit_before>import { SEARCH_OBJECT_LABELS, SEARCH_OBJECT_TYPES } from "actions"
import {
  LocationOverlayRow,
  OrganizationOverlayRow,
  PersonDetailedOverlayRow,
  PositionOverlayRow,
  ReportDetailedOverlayRow,
  TaskSimpleOverlayRow
} from "components/advancedSelectWidget/AdvancedSelectOverlayRow"
import AdvancedMultiSelect from "components/advancedSelectWidget/AdvancedMultiSelect"
import AdvancedSingleSelect from "components/advancedSelectWidget/AdvancedSingleSelect"
import ButtonToggleGroup from "components/ButtonToggleGroup"
import * as Models from "models"
import PropTypes from "prop-types"
import React, { useMemo, useState } from "react"
import { Button } from "react-bootstrap"
import LOCATIONS_ICON from "resources/locations.png"
import ORGANIZATIONS_ICON from "resources/organizations.png"
import PEOPLE_ICON from "resources/people.png"
import POSITIONS_ICON from "resources/positions.png"
import REPORTS_ICON from "resources/reports.png"
import TASKS_ICON from "resources/tasks.png"
import { ENTITY_TYPES } from "utils_links"

const entityFilters = {
  allEntities: {
    label: "All entities",
    queryVars: {}
  }
}

const peopleFilters = {
  allEntities: {
    label: "All",
    queryVars: { matchPositionName: true }
  },
  activeAdvisors: {
    label: "All advisors",
    queryVars: { role: Models.Person.ROLE.ADVISOR, matchPositionName: true }
  },
  activePrincipals: {
    label: "All principals",
    queryVars: { role: Models.Person.ROLE.PRINCIPAL }
  }
}

const widgetPropsReport = {
  objectType: Models.Report,
  overlayRenderRow: ReportDetailedOverlayRow,
  overlayColumns: ["Goal", "Author", "Updated"],
  filterDefs: entityFilters,
  queryParams: {},
  fields: Models.Report.autocompleteQuery,
  addon: REPORTS_ICON
}

const widgetPropsPeople = {
  objectType: Models.Person,
  overlayRenderRow: PersonDetailedOverlayRow,
  overlayColumns: ["Name", "Position", "Location", "Organization"],
  filterDefs: peopleFilters,
  queryParams: {},
  fields: Models.Person.autocompleteQueryWithNotes,
  addon: PEOPLE_ICON
}

const widgetPropsOrganization = {
  objectType: Models.Organization,
  overlayRenderRow: OrganizationOverlayRow,
  overlayColumns: ["Name"],
  filterDefs: entityFilters,
  queryParams: {},
  fields: Models.Organization.autocompleteQuery,
  addon: ORGANIZATIONS_ICON
}

const widgetPropsPosition = {
  objectType: Models.Position,
  overlayRenderRow: PositionOverlayRow,
  overlayColumns: ["Position", "Organization", "Current Occupant"],
  filterDefs: entityFilters,
  queryParams: {},
  fields: Models.Position.autocompleteQuery,
  addon: POSITIONS_ICON
}

const widgetPropsLocation = {
  objectType: Models.Location,
  overlayRenderRow: LocationOverlayRow,
  overlayColumns: ["Name"],
  filterDefs: entityFilters,
  queryParams: { status: Models.Location.STATUS.ACTIVE },
  fields: Models.Location.autocompleteQuery,
  addon: LOCATIONS_ICON
}

const widgetPropsTask = {
  objectType: Models.Task,
  overlayRenderRow: TaskSimpleOverlayRow,
  overlayColumns: ["Name"],
  filterDefs: entityFilters,
  queryParams: { status: Models.Task.STATUS.ACTIVE },
  fields: Models.Task.autocompleteQueryWithNotes,
  addon: TASKS_ICON
}

const widgetTypeMapping = {
  [ENTITY_TYPES.REPORTS]: widgetPropsReport,
  [ENTITY_TYPES.PEOPLE]: widgetPropsPeople,
  [ENTITY_TYPES.ORGANIZATIONS]: widgetPropsOrganization,
  [ENTITY_TYPES.POSITIONS]: widgetPropsPosition,
  [ENTITY_TYPES.LOCATIONS]: widgetPropsLocation,
  [ENTITY_TYPES.TASKS]: widgetPropsTask
}

const MultiTypeAdvancedSelectComponent = ({
  fieldName,
  onConfirm,
  objectType,
  entityTypes,
  value,
  isMultiSelect
}) => {
  const [entityType, setEntityType] = useState(
    objectType ||
      Object.values(ENTITY_TYPES).find(et => entityTypes.includes(et)) ||
      ENTITY_TYPES.REPORTS
  )
  const [advancedSelectProps, setAdvancedSelectProps] = useState(
    widgetTypeMapping[entityType]
  )
  function changeEntityType(newEntityType) {
    setEntityType(newEntityType)
    setAdvancedSelectProps(widgetTypeMapping[newEntityType])
  }
  const searchPlaceholder = useMemo(() => {
    const [key] = Object.entries(ENTITY_TYPES).find(
      ([, et]) => et === entityType
    )
    const entityLabel = SEARCH_OBJECT_LABELS[SEARCH_OBJECT_TYPES[key]]
    return "Find " + entityLabel.toLowerCase()
  }, [entityType])
  return (
    <>
      {entityTypes.length > 1 && (
        <ButtonToggleGroup value={entityType} onChange={changeEntityType}>
          {Object.entries(ENTITY_TYPES)
            .filter(([, et]) => entityTypes.includes(et))
            .map(([key, entityName], value) => {
              const entityLabel = SEARCH_OBJECT_LABELS[SEARCH_OBJECT_TYPES[key]]
              return (
                <Button key={entityName} value={entityName}>
                  {entityLabel}
                </Button>
              )
            })}
        </ButtonToggleGroup>
      )}

      {isMultiSelect ? (
        <AdvancedMultiSelect
          autofocus="true"
          fieldName={fieldName}
          fieldLabel="Search in ANET:"
          placeholder={searchPlaceholder}
          value={value}
          showEmbedded
          overlayColumns={advancedSelectProps.overlayColumns}
          overlayRenderRow={advancedSelectProps.overlayRenderRow}
          filterDefs={advancedSelectProps.filterDefs}
          onChange={value => onConfirm(value, entityType)}
          objectType={advancedSelectProps.objectType}
          queryParams={advancedSelectProps.queryParams}
          fields={advancedSelectProps.fields}
          addon={advancedSelectProps.addon}
        />
      ) : (
        <AdvancedSingleSelect
          autofocus="true"
          fieldName={fieldName}
          fieldLabel="Search in ANET:"
          placeholder={searchPlaceholder}
          value={value}
          showEmbedded
          showRemoveButton={false}
          overlayColumns={advancedSelectProps.overlayColumns}
          overlayRenderRow={advancedSelectProps.overlayRenderRow}
          filterDefs={advancedSelectProps.filterDefs}
          onChange={value => onConfirm(value, entityType)}
          objectType={advancedSelectProps.objectType}
          queryParams={advancedSelectProps.queryParams}
          fields={advancedSelectProps.fields}
          addon={advancedSelectProps.addon}
        />
      )}
    </>
  )
}

MultiTypeAdvancedSelectComponent.propTypes = {
  fieldName: PropTypes.string,
  onConfirm: PropTypes.func,
  objectType: PropTypes.string,
  entityTypes: PropTypes.arrayOf(PropTypes.string).isRequired,
  value: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
  isMultiSelect: PropTypes.bool.isRequired
}
MultiTypeAdvancedSelectComponent.defaultProps = {
  fieldName: "entitySelect",
  entityTypes: Object.values(ENTITY_TYPES),
  isMultiSelect: false
}

export default MultiTypeAdvancedSelectComponent
<commit_msg>NCI-Agency/anet#3143: Show only 'active' objects<commit_after>import { SEARCH_OBJECT_LABELS, SEARCH_OBJECT_TYPES } from "actions"
import {
  LocationOverlayRow,
  OrganizationOverlayRow,
  PersonDetailedOverlayRow,
  PositionOverlayRow,
  ReportDetailedOverlayRow,
  TaskSimpleOverlayRow
} from "components/advancedSelectWidget/AdvancedSelectOverlayRow"
import AdvancedMultiSelect from "components/advancedSelectWidget/AdvancedMultiSelect"
import AdvancedSingleSelect from "components/advancedSelectWidget/AdvancedSingleSelect"
import ButtonToggleGroup from "components/ButtonToggleGroup"
import * as Models from "models"
import PropTypes from "prop-types"
import React, { useMemo, useState } from "react"
import { Button } from "react-bootstrap"
import LOCATIONS_ICON from "resources/locations.png"
import ORGANIZATIONS_ICON from "resources/organizations.png"
import PEOPLE_ICON from "resources/people.png"
import POSITIONS_ICON from "resources/positions.png"
import REPORTS_ICON from "resources/reports.png"
import TASKS_ICON from "resources/tasks.png"
import { ENTITY_TYPES } from "utils_links"

const entityFilters = {
  allEntities: {
    label: "All entities",
    queryVars: {}
  }
}

const peopleFilters = {
  allEntities: {
    label: "All",
    queryVars: { matchPositionName: true }
  },
  activeAdvisors: {
    label: "All advisors",
    queryVars: { role: Models.Person.ROLE.ADVISOR, matchPositionName: true }
  },
  activePrincipals: {
    label: "All principals",
    queryVars: { role: Models.Person.ROLE.PRINCIPAL }
  }
}

const widgetPropsReport = {
  objectType: Models.Report,
  overlayRenderRow: ReportDetailedOverlayRow,
  overlayColumns: ["Goal", "Author", "Updated"],
  filterDefs: entityFilters,
  queryParams: {}, // allow any report, incl. one's own drafts
  fields: Models.Report.autocompleteQuery,
  addon: REPORTS_ICON
}

const widgetPropsPeople = {
  objectType: Models.Person,
  overlayRenderRow: PersonDetailedOverlayRow,
  overlayColumns: ["Name", "Position", "Location", "Organization"],
  filterDefs: peopleFilters,
  queryParams: { status: Models.Person.STATUS.ACTIVE },
  fields: Models.Person.autocompleteQueryWithNotes,
  addon: PEOPLE_ICON
}

const widgetPropsOrganization = {
  objectType: Models.Organization,
  overlayRenderRow: OrganizationOverlayRow,
  overlayColumns: ["Name"],
  filterDefs: entityFilters,
  queryParams: { status: Models.Organization.STATUS.ACTIVE },
  fields: Models.Organization.autocompleteQuery,
  addon: ORGANIZATIONS_ICON
}

const widgetPropsPosition = {
  objectType: Models.Position,
  overlayRenderRow: PositionOverlayRow,
  overlayColumns: ["Position", "Organization", "Current Occupant"],
  filterDefs: entityFilters,
  queryParams: { status: Models.Position.STATUS.ACTIVE },
  fields: Models.Position.autocompleteQuery,
  addon: POSITIONS_ICON
}

const widgetPropsLocation = {
  objectType: Models.Location,
  overlayRenderRow: LocationOverlayRow,
  overlayColumns: ["Name"],
  filterDefs: entityFilters,
  queryParams: { status: Models.Location.STATUS.ACTIVE },
  fields: Models.Location.autocompleteQuery,
  addon: LOCATIONS_ICON
}

const widgetPropsTask = {
  objectType: Models.Task,
  overlayRenderRow: TaskSimpleOverlayRow,
  overlayColumns: ["Name"],
  filterDefs: entityFilters,
  queryParams: { status: Models.Task.STATUS.ACTIVE },
  fields: Models.Task.autocompleteQueryWithNotes,
  addon: TASKS_ICON
}

const widgetTypeMapping = {
  [ENTITY_TYPES.REPORTS]: widgetPropsReport,
  [ENTITY_TYPES.PEOPLE]: widgetPropsPeople,
  [ENTITY_TYPES.ORGANIZATIONS]: widgetPropsOrganization,
  [ENTITY_TYPES.POSITIONS]: widgetPropsPosition,
  [ENTITY_TYPES.LOCATIONS]: widgetPropsLocation,
  [ENTITY_TYPES.TASKS]: widgetPropsTask
}

const MultiTypeAdvancedSelectComponent = ({
  fieldName,
  onConfirm,
  objectType,
  entityTypes,
  value,
  isMultiSelect
}) => {
  const [entityType, setEntityType] = useState(
    objectType ||
      Object.values(ENTITY_TYPES).find(et => entityTypes.includes(et)) ||
      ENTITY_TYPES.REPORTS
  )
  const [advancedSelectProps, setAdvancedSelectProps] = useState(
    widgetTypeMapping[entityType]
  )
  function changeEntityType(newEntityType) {
    setEntityType(newEntityType)
    setAdvancedSelectProps(widgetTypeMapping[newEntityType])
  }
  const searchPlaceholder = useMemo(() => {
    const [key] = Object.entries(ENTITY_TYPES).find(
      ([, et]) => et === entityType
    )
    const entityLabel = SEARCH_OBJECT_LABELS[SEARCH_OBJECT_TYPES[key]]
    return "Find " + entityLabel.toLowerCase()
  }, [entityType])
  return (
    <>
      {entityTypes.length > 1 && (
        <ButtonToggleGroup value={entityType} onChange={changeEntityType}>
          {Object.entries(ENTITY_TYPES)
            .filter(([, et]) => entityTypes.includes(et))
            .map(([key, entityName], value) => {
              const entityLabel = SEARCH_OBJECT_LABELS[SEARCH_OBJECT_TYPES[key]]
              return (
                <Button key={entityName} value={entityName}>
                  {entityLabel}
                </Button>
              )
            })}
        </ButtonToggleGroup>
      )}

      {isMultiSelect ? (
        <AdvancedMultiSelect
          autofocus="true"
          fieldName={fieldName}
          fieldLabel="Search in ANET:"
          placeholder={searchPlaceholder}
          value={value}
          showEmbedded
          overlayColumns={advancedSelectProps.overlayColumns}
          overlayRenderRow={advancedSelectProps.overlayRenderRow}
          filterDefs={advancedSelectProps.filterDefs}
          onChange={value => onConfirm(value, entityType)}
          objectType={advancedSelectProps.objectType}
          queryParams={advancedSelectProps.queryParams}
          fields={advancedSelectProps.fields}
          addon={advancedSelectProps.addon}
        />
      ) : (
        <AdvancedSingleSelect
          autofocus="true"
          fieldName={fieldName}
          fieldLabel="Search in ANET:"
          placeholder={searchPlaceholder}
          value={value}
          showEmbedded
          showRemoveButton={false}
          overlayColumns={advancedSelectProps.overlayColumns}
          overlayRenderRow={advancedSelectProps.overlayRenderRow}
          filterDefs={advancedSelectProps.filterDefs}
          onChange={value => onConfirm(value, entityType)}
          objectType={advancedSelectProps.objectType}
          queryParams={advancedSelectProps.queryParams}
          fields={advancedSelectProps.fields}
          addon={advancedSelectProps.addon}
        />
      )}
    </>
  )
}

MultiTypeAdvancedSelectComponent.propTypes = {
  fieldName: PropTypes.string,
  onConfirm: PropTypes.func,
  objectType: PropTypes.string,
  entityTypes: PropTypes.arrayOf(PropTypes.string).isRequired,
  value: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
  isMultiSelect: PropTypes.bool.isRequired
}
MultiTypeAdvancedSelectComponent.defaultProps = {
  fieldName: "entitySelect",
  entityTypes: Object.values(ENTITY_TYPES),
  isMultiSelect: false
}

export default MultiTypeAdvancedSelectComponent
<|endoftext|>


<commit_before>/* globals __DEV__ */
import Phaser from 'phaser';
import Collectible from '../sprites/Collectible';

import config from '../config';
import collectibles from '../config/collectibles';
import faces from '../config/faces';

// TODO Move to a file
const gameConfig = {
    drinkDecrementPerTimeUnit: 1,
    drinkDecrementTimeMilliSeconds: 1000,
    drinkMaximumAmount: 250,
    foodDecrementPerMove: 2,
    foodMaximumAmount: 250,
    playerInitialMoveSpeed: 400,
    playerMaxDragMultiplier: 0.4,
    playerYPosition: 960,
    minimumSpawnTime: 1800,
    maximumSpawnTime: 3300,
    ui: {
        face: {
            position: [800, 1600],
            scale: 1,
        },
    },
};

function randomIntegerBetween(min, max) {
    return Math.floor((Math.random() * (max - min)) + min);
}

export default class extends Phaser.State {
    init() {
        this._player = null;
        this._collectibleGroup = null;
        this._spawnTimer = 0;
        this._nextSpawnTime = 0;
        this._drinkTimer = 0;
        this._fontStyle = null;
        this._face = null;

        this._scoreText = null;
        this._score = 0;
        this._health = 0;
        this._cursors = null;
        this._facing = 'idle';
        this._drinkAmount = 0;
        this._drinkAmountText = null;
        this._foodAmount = 0;
        this._foodAmountText = null;
    }

    create() {
        this.physics.startSystem(Phaser.Physics.ARCADE);

        this.physics.arcade.gravity.y = 200;

        this.add.sprite(0, 0, 'background-game');

        this.add.button(config.gameWidth * 0.86, config.gameHeight * 0.03, 'pause-button', this.managePause, this, 0, 1);

        this._fontStyle = {
            font: '40px Arial',
            fill: '#FFCC00',
            stroke: '#333',
            strokeThickness: 5,
            align: 'center',
        };

        this._spawnTimer = 0;
        this._drinkTimer = 0;

        this._drinkAmountText = this.add.text(120, 80, '0', this._fontStyle);
        this._foodAmountText = this.add.text(120, 160, '0', this._fontStyle);
        this._health = 10;

        this._collectibleGroup = this.add.group();

        this.initialisePlayer();

        this.add.sprite(0, 0, 'frame');
        this.initialiseUi();

        this.setDrinkText();
        this.setFoodText();

//        this.createBannerText();
    }

    managePause() {
        this.game.paused = true;
        const pausedText = this.add.text(
                300,
                400,
                'Game paused.\nTap anywhere to continue.',
                this._fontStyle
        );

        this.input.onDown.add(() => {
            pausedText.destroy();
            this.game.paused = false;
        }, this);
    }

    update() {
        this._spawnTimer += this.time.elapsed;

        this.updatePlayer();
        this.updateCollectibles();
        this.updateDrinkAmount();

        this.updateFace();

        if (this.isGameOver()) {
            this.showGameOver();
        }
    }

    // TODO - needs working out properly once rules are clear
    updateFace() {
        const foodDifference = this._foodAmount - this._drinkAmount;
        const drinkDifference = this._drinkAmount - this._foodAmount;
        let newFace = faces.FACE_NEUTRAL;

        if (foodDifference > 200) {
            newFace = faces.FACE_SICK;
        } else if (foodDifference > 100) {
            newFace = faces.FACE_GLUTTONOUS;
        } else if (foodDifference > 50) {
            newFace = faces.FACE_GOURMAND;
        }

        if (drinkDifference > 200) {
            newFace = faces.FACE_HAMMERED;
        } else if (drinkDifference > 100) {
            newFace = faces.FACE_DRUNK;
        } else if (drinkDifference > 50) {
            newFace = faces.FACE_MERRY;
        }

        this.setFace(newFace);
    }

    setFace(faceFrame) {
        if (faceFrame !== this._face.frame) {
            this._face.frame = faceFrame;
        }
    }

    isGameOver() {
        // TODO This should be based on drink/food amount
        return false;
    }

    showGameOver() {
        this.add.sprite((config.gameWidth - 594) / 2, (config.gameHeight - 271) / 2, 'game-over');
        this.game.paused = true;
    }

    updateCollectibles() {
        if (this._spawnTimer > this._nextSpawnTime) {
            this._spawnTimer = 0;
            this.spawnCollectible();
        }

        this._collectibleGroup.forEach((collectible) => {
            collectible.update();
        });

        this.physics.arcade.overlap(
            this._player,
            this._collectibleGroup,
            this.collideWithPlayer,
            null,
            this
        );
    }

    getDrunkSpeedMultiplier() {
        const drunkPercentage = this._drinkAmount / gameConfig.drinkMaximumAmount;
        const speedMultiplier = 1 - (drunkPercentage * gameConfig.playerMaxDragMultiplier);
        return speedMultiplier;
    }

    updatePlayer() {
        const speedMultiplier = this.getDrunkSpeedMultiplier();
        const moveSpeed = gameConfig.playerInitialMoveSpeed * speedMultiplier;
        this._player.body.velocity.x = 0;

        if (this.isLeftPressed()) {
            this._player.body.velocity.x = -moveSpeed;

            if (this._facing !== 'left') {
                this._facing = 'left';
                this._player.animations.play('move-left');
                this.updateFoodAmount();
            }
        } else if (this.isRightPressed()) {
            this._player.body.velocity.x = moveSpeed;

            if (this._facing !== 'right') {
                this._facing = 'right';
                this._player.animations.play('move-right');
                this.updateFoodAmount();
            }
        } else if (this._facing !== 'idle') {
            this._facing = 'idle';
            this._player.animations.play('idle');
        }
    }

    isLeftPressed() {
        return this._cursors.left.isDown || this.hasTouchedLeft();
    }

    isRightPressed() {
        return this._cursors.right.isDown || this.hasTouchedRight();
    }

    hasTouchedLeft() {
        if (this.input.activePointer.isDown) {
            return (this.input.activePointer.worldX < (config.gameWidth / 2));
        }

        return false;
    }

    hasTouchedRight() {
        if (this.input.activePointer.isDown) {
            return (this.input.activePointer.worldX > (config.gameWidth / 2));
        }

        return false;
    }

    initialisePlayer() {
        this._player = this.add.sprite(config.gameWidth * 0.5, gameConfig.playerYPosition, 'player');
        this._player.scale = {x: 1.5, y: 1.5};
        this._player.animations.add('idle', [0, 1, 2, 3], 10, true);
        this._player.animations.add('move-left', [4, 5, 6, 7], 10, true);
        this._player.animations.add('move-right', [8, 9, 10, 11], 10, true);
        this._player.animations.play('idle');

        this._cursors = this.game.input.keyboard.createCursorKeys();
        this.game.physics.enable(this._player, Phaser.Physics.ARCADE);
        this._player.body.allowGravity = false;
        this._player.body.collideWorldBounds = true;

        this._player.anchor.setTo(0.5, 0);
    }

    // TODO - Unify UI positions
    initialiseUi() {
        const faceBg = {
            x: config.gameWidth * 0.83,
            y: config.gameHeight * 0.89
        };
        const faceBackground = this.add.sprite(faceBg.x, faceBg.y, 'background-face');
        faceBackground.anchor.setTo(0.5, 0.5);

        this._face = this.add.sprite(faceBg.x + 4, faceBg.y + 10, 'faces');
        this._face.anchor.setTo(0.5, 0.5);
        this.setFace(faces.FACE_NEUTRAL);

        const scorePos = {
            x: faceBg.x,
            y: faceBg.y + 130
        };
        const scoreBar = this.add.sprite(scorePos.x, scorePos.y, 'score-bar');
        scoreBar.anchor.setTo(0.5, 0.5);

        this.createScoreText(scorePos);

        this.add.sprite(config.gameWidth * 0.02, config.gameHeight * 0.82, 'food-bar');
        this.add.sprite(config.gameWidth * 0.03, config.gameHeight * 0.9, 'drinks-bar');

        this.setDrinkText();
        this.setFoodText();
    }

    collideWithPlayer(player, collectible) {
        this.playerCollectedItem(collectible);
    }

    updateDrinkAmount() {
        this._drinkTimer += this.time.elapsed;
        if (this._drinkTimer > gameConfig.drinkDecrementTimeMilliSeconds) {
            this._drinkTimer = 0;

            this._drinkAmount -= gameConfig.drinkDecrementPerTimeUnit;
            if (this._drinkAmount < 0) {
                this._drinkAmount = 0;
            }

            this.setDrinkText();
        }
    }

    updateFoodAmount() {
        this._foodAmount -= gameConfig.foodDecrementPerMove;
        if (this._foodAmount < 0) {
            this._foodAmount = 0;
        }
        this.setFoodText();
    }

    setDrinkText() {
        this._drinkAmountText.setText(`Drink: ${this._drinkAmount}`);
    }

    setFoodText() {
        this._foodAmountText.setText(`Food: ${this._foodAmount}`);
    }

    spawnCollectible() {
        this._nextSpawnTime = randomIntegerBetween(
            gameConfig.minimumSpawnTime,
            gameConfig.maximumSpawnTime
        );

        const dropPos = randomIntegerBetween(config.gameWidth * 0.05, config.gameWidth * 0.95);
        const dropOffset = 0;

        // Choose collectible type
        const foodOrDrink = randomIntegerBetween(0, 100);
        const isFood = (foodOrDrink >= 50);
        const collectibleTypeList = isFood ? collectibles.food : collectibles.drinks;

        const randomTypeIndex = randomIntegerBetween(0, collectibleTypeList.length - 1);
        const collectibleType = collectibleTypeList[randomTypeIndex];

        const collectible = new Collectible({
            game: this,
            x: dropPos,
            y: dropOffset,
            asset: 'collectibles',
            collectibleType,
            onOutOfBounds: this.onCollectibleOutOfBounds,
            isFood
        });

        this._collectibleGroup.add(collectible);
    }

    playerCollectedItem(collectible) {
        collectible.kill();

        const points = collectible.collectibleType.points;
        this._score += points;

        this._scoreText.setText(this._score);

        if (collectible.isFood) {
            this._foodAmount += points;

            if (this._foodAmount > gameConfig.foodMaximumAmount) {
                this._foodAmount = gameConfig.foodMaximumAmount;
            }

            this.setFoodText();
        } else {
            this._drinkAmount += points;

            if (this._drinkAmount > gameConfig.drinkMaximumAmount) {
                this._drinkAmount = gameConfig.drinkMaximumAmount;
            }

            this.setDrinkText();
        }
    }

    onCollectibleOutOfBounds(collectible) {
        collectible.kill();
    }

    createBannerText() {
        const bannerText = 'See It Off';
        const banner = this.add.text(this.world.centerX, this.game.height - 80, bannerText);
        banner.font = 'eraserregular';
        banner.padding.set(10, 16);
        banner.fontSize = 80;
        banner.fill = '#fff';
        banner.smoothed = false;
        banner.anchor.setTo(0.5);
    }

    createScoreText(position) {
        this._scoreText = this.add.text(position.x, position.y, '0', this._fontStyle);
        this._scoreText.font = 'dinregular';
        this._scoreText.fontSize = 40;
        this._scoreText.fill = '#111';
        this._scoreText.smoothed = false;
        this._scoreText.anchor.setTo(0.5);
    }
}
<commit_msg>Remove unused health variable<commit_after>/* globals __DEV__ */
import Phaser from 'phaser';
import Collectible from '../sprites/Collectible';

import config from '../config';
import collectibles from '../config/collectibles';
import faces from '../config/faces';

// TODO Move to a file
const gameConfig = {
    drinkDecrementPerTimeUnit: 1,
    drinkDecrementTimeMilliSeconds: 1000,
    drinkMaximumAmount: 250,
    foodDecrementPerMove: 2,
    foodMaximumAmount: 250,
    playerInitialMoveSpeed: 400,
    playerMaxDragMultiplier: 0.4,
    playerYPosition: 960,
    minimumSpawnTime: 1800,
    maximumSpawnTime: 3300,
    ui: {
        face: {
            position: [800, 1600],
            scale: 1,
        },
    },
};

function randomIntegerBetween(min, max) {
    return Math.floor((Math.random() * (max - min)) + min);
}

export default class extends Phaser.State {
    init() {
        this._player = null;
        this._collectibleGroup = null;
        this._spawnTimer = 0;
        this._nextSpawnTime = 0;
        this._drinkTimer = 0;
        this._fontStyle = null;
        this._face = null;

        this._scoreText = null;
        this._score = 0;
        this._cursors = null;
        this._facing = 'idle';
        this._drinkAmount = 0;
        this._drinkAmountText = null;
        this._foodAmount = 0;
        this._foodAmountText = null;
    }

    create() {
        this.physics.startSystem(Phaser.Physics.ARCADE);

        this.physics.arcade.gravity.y = 200;

        this.add.sprite(0, 0, 'background-game');

        this.add.button(config.gameWidth * 0.86, config.gameHeight * 0.03, 'pause-button', this.managePause, this, 0, 1);

        this._fontStyle = {
            font: '40px Arial',
            fill: '#FFCC00',
            stroke: '#333',
            strokeThickness: 5,
            align: 'center',
        };

        this._spawnTimer = 0;
        this._drinkTimer = 0;

        this._drinkAmountText = this.add.text(120, 80, '0', this._fontStyle);
        this._foodAmountText = this.add.text(120, 160, '0', this._fontStyle);

        this._collectibleGroup = this.add.group();

        this.initialisePlayer();

        this.add.sprite(0, 0, 'frame');
        this.initialiseUi();

        this.setDrinkText();
        this.setFoodText();

//        this.createBannerText();
    }

    managePause() {
        this.game.paused = true;
        const pausedText = this.add.text(
                300,
                400,
                'Game paused.\nTap anywhere to continue.',
                this._fontStyle
        );

        this.input.onDown.add(() => {
            pausedText.destroy();
            this.game.paused = false;
        }, this);
    }

    update() {
        this._spawnTimer += this.time.elapsed;

        this.updatePlayer();
        this.updateCollectibles();
        this.updateDrinkAmount();

        this.updateFace();

        if (this.isGameOver()) {
            this.showGameOver();
        }
    }

    // TODO - needs working out properly once rules are clear
    updateFace() {
        const foodDifference = this._foodAmount - this._drinkAmount;
        const drinkDifference = this._drinkAmount - this._foodAmount;
        let newFace = faces.FACE_NEUTRAL;

        if (foodDifference > 200) {
            newFace = faces.FACE_SICK;
        } else if (foodDifference > 100) {
            newFace = faces.FACE_GLUTTONOUS;
        } else if (foodDifference > 50) {
            newFace = faces.FACE_GOURMAND;
        }

        if (drinkDifference > 200) {
            newFace = faces.FACE_HAMMERED;
        } else if (drinkDifference > 100) {
            newFace = faces.FACE_DRUNK;
        } else if (drinkDifference > 50) {
            newFace = faces.FACE_MERRY;
        }

        this.setFace(newFace);
    }

    setFace(faceFrame) {
        if (faceFrame !== this._face.frame) {
            this._face.frame = faceFrame;
        }
    }

    isGameOver() {
        // TODO This should be based on drink/food amount
        return false;
    }

    showGameOver() {
        this.add.sprite((config.gameWidth - 594) / 2, (config.gameHeight - 271) / 2, 'game-over');
        this.game.paused = true;
    }

    updateCollectibles() {
        if (this._spawnTimer > this._nextSpawnTime) {
            this._spawnTimer = 0;
            this.spawnCollectible();
        }

        this._collectibleGroup.forEach((collectible) => {
            collectible.update();
        });

        this.physics.arcade.overlap(
            this._player,
            this._collectibleGroup,
            this.collideWithPlayer,
            null,
            this
        );
    }

    getDrunkSpeedMultiplier() {
        const drunkPercentage = this._drinkAmount / gameConfig.drinkMaximumAmount;
        const speedMultiplier = 1 - (drunkPercentage * gameConfig.playerMaxDragMultiplier);
        return speedMultiplier;
    }

    updatePlayer() {
        const speedMultiplier = this.getDrunkSpeedMultiplier();
        const moveSpeed = gameConfig.playerInitialMoveSpeed * speedMultiplier;
        this._player.body.velocity.x = 0;

        if (this.isLeftPressed()) {
            this._player.body.velocity.x = -moveSpeed;

            if (this._facing !== 'left') {
                this._facing = 'left';
                this._player.animations.play('move-left');
                this.updateFoodAmount();
            }
        } else if (this.isRightPressed()) {
            this._player.body.velocity.x = moveSpeed;

            if (this._facing !== 'right') {
                this._facing = 'right';
                this._player.animations.play('move-right');
                this.updateFoodAmount();
            }
        } else if (this._facing !== 'idle') {
            this._facing = 'idle';
            this._player.animations.play('idle');
        }
    }

    isLeftPressed() {
        return this._cursors.left.isDown || this.hasTouchedLeft();
    }

    isRightPressed() {
        return this._cursors.right.isDown || this.hasTouchedRight();
    }

    hasTouchedLeft() {
        if (this.input.activePointer.isDown) {
            return (this.input.activePointer.worldX < (config.gameWidth / 2));
        }

        return false;
    }

    hasTouchedRight() {
        if (this.input.activePointer.isDown) {
            return (this.input.activePointer.worldX > (config.gameWidth / 2));
        }

        return false;
    }

    initialisePlayer() {
        this._player = this.add.sprite(config.gameWidth * 0.5, gameConfig.playerYPosition, 'player');
        this._player.scale = {x: 1.5, y: 1.5};
        this._player.animations.add('idle', [0, 1, 2, 3], 10, true);
        this._player.animations.add('move-left', [4, 5, 6, 7], 10, true);
        this._player.animations.add('move-right', [8, 9, 10, 11], 10, true);
        this._player.animations.play('idle');

        this._cursors = this.game.input.keyboard.createCursorKeys();
        this.game.physics.enable(this._player, Phaser.Physics.ARCADE);
        this._player.body.allowGravity = false;
        this._player.body.collideWorldBounds = true;

        this._player.anchor.setTo(0.5, 0);
    }

    // TODO - Unify UI positions
    initialiseUi() {
        const faceBg = {
            x: config.gameWidth * 0.83,
            y: config.gameHeight * 0.89
        };
        const faceBackground = this.add.sprite(faceBg.x, faceBg.y, 'background-face');
        faceBackground.anchor.setTo(0.5, 0.5);

        this._face = this.add.sprite(faceBg.x + 4, faceBg.y + 10, 'faces');
        this._face.anchor.setTo(0.5, 0.5);
        this.setFace(faces.FACE_NEUTRAL);

        const scorePos = {
            x: faceBg.x,
            y: faceBg.y + 130
        };
        const scoreBar = this.add.sprite(scorePos.x, scorePos.y, 'score-bar');
        scoreBar.anchor.setTo(0.5, 0.5);

        this.createScoreText(scorePos);

        this.add.sprite(config.gameWidth * 0.02, config.gameHeight * 0.82, 'food-bar');
        this.add.sprite(config.gameWidth * 0.03, config.gameHeight * 0.9, 'drinks-bar');

        this.setDrinkText();
        this.setFoodText();
    }

    collideWithPlayer(player, collectible) {
        this.playerCollectedItem(collectible);
    }

    updateDrinkAmount() {
        this._drinkTimer += this.time.elapsed;
        if (this._drinkTimer > gameConfig.drinkDecrementTimeMilliSeconds) {
            this._drinkTimer = 0;

            this._drinkAmount -= gameConfig.drinkDecrementPerTimeUnit;
            if (this._drinkAmount < 0) {
                this._drinkAmount = 0;
            }

            this.setDrinkText();
        }
    }

    updateFoodAmount() {
        this._foodAmount -= gameConfig.foodDecrementPerMove;
        if (this._foodAmount < 0) {
            this._foodAmount = 0;
        }
        this.setFoodText();
    }

    setDrinkText() {
        this._drinkAmountText.setText(`Drink: ${this._drinkAmount}`);
    }

    setFoodText() {
        this._foodAmountText.setText(`Food: ${this._foodAmount}`);
    }

    spawnCollectible() {
        this._nextSpawnTime = randomIntegerBetween(
            gameConfig.minimumSpawnTime,
            gameConfig.maximumSpawnTime
        );

        const dropPos = randomIntegerBetween(config.gameWidth * 0.05, config.gameWidth * 0.95);
        const dropOffset = 0;

        // Choose collectible type
        const foodOrDrink = randomIntegerBetween(0, 100);
        const isFood = (foodOrDrink >= 50);
        const collectibleTypeList = isFood ? collectibles.food : collectibles.drinks;

        const randomTypeIndex = randomIntegerBetween(0, collectibleTypeList.length - 1);
        const collectibleType = collectibleTypeList[randomTypeIndex];

        const collectible = new Collectible({
            game: this,
            x: dropPos,
            y: dropOffset,
            asset: 'collectibles',
            collectibleType,
            onOutOfBounds: this.onCollectibleOutOfBounds,
            isFood
        });

        this._collectibleGroup.add(collectible);
    }

    playerCollectedItem(collectible) {
        collectible.kill();

        const points = collectible.collectibleType.points;
        this._score += points;

        this._scoreText.setText(this._score);

        if (collectible.isFood) {
            this._foodAmount += points;

            if (this._foodAmount > gameConfig.foodMaximumAmount) {
                this._foodAmount = gameConfig.foodMaximumAmount;
            }

            this.setFoodText();
        } else {
            this._drinkAmount += points;

            if (this._drinkAmount > gameConfig.drinkMaximumAmount) {
                this._drinkAmount = gameConfig.drinkMaximumAmount;
            }

            this.setDrinkText();
        }
    }

    onCollectibleOutOfBounds(collectible) {
        collectible.kill();
    }

    createBannerText() {
        const bannerText = 'See It Off';
        const banner = this.add.text(this.world.centerX, this.game.height - 80, bannerText);
        banner.font = 'eraserregular';
        banner.padding.set(10, 16);
        banner.fontSize = 80;
        banner.fill = '#fff';
        banner.smoothed = false;
        banner.anchor.setTo(0.5);
    }

    createScoreText(position) {
        this._scoreText = this.add.text(position.x, position.y, '0', this._fontStyle);
        this._scoreText.font = 'dinregular';
        this._scoreText.fontSize = 40;
        this._scoreText.fill = '#111';
        this._scoreText.smoothed = false;
        this._scoreText.anchor.setTo(0.5);
    }
}
<|endoftext|>


<commit_before>define('Mobile/SalesLogix/Views/Activity/Complete', [
    'dojo/_base/declare',
    'dojo/_base/array',
    'dojo/_base/connect',
    'dojo/string',
    'Mobile/SalesLogix/Validator',
    'Mobile/SalesLogix/Template',
    'Sage/Platform/Mobile/Utility',
    'Sage/Platform/Mobile/Edit'
], function(
    declare,
    array,
    connect,
    string,
    validator,
    template,
    utility,
    Edit
) {

    return declare('Mobile.SalesLogix.Views.Activity.Complete', [Edit], {
        //Localization
        activityInfoText: 'Activity Info',
        accountText: 'account',
        contactText: 'contact',
        opportunityText: 'opportunity',
        ticketNumberText: 'ticket',
        companyText: 'company',
        leadText: 'lead',
        asScheduledText: 'as scheduled',
        categoryText: 'category',
        categoryTitleText: 'Activity Category',
        completedText: 'completed date',
		completedFormatText: 'M/d/yyyy h:mm tt',
        completionText: 'Completion',
        durationText: 'duration',
		durationInvalidText: "The field '${2}' must have a value.",
        carryOverNotesText: 'carry over notes',
        followUpText: 'follow-up',
        followUpTitleText: 'Follow-up type',
        leaderText: 'leader',
        longNotesText: 'notes',
        longNotesTitleText: 'Notes',
        otherInfoText: 'Other Info',
        priorityText: 'priority',
        priorityTitleText: 'Priority',
        regardingText: 'regarding',
        regardingTitleText: 'Activity Regarding',
        resultText: 'result',
        resultTitleText: 'Result',
        startingText: 'start date',
		startingFormatText: 'M/d/yyyy h:mm tt',
        timelessText: 'timeless',
        durationValueText: {
            0: 'none',
            15: '15 minutes',
            30: '30 minutes',
            60: '1 hour',
            90: '1.5 hours',
            120: '2 hours'
        },
        followupValueText: {
            'none': 'None',
            'atPhoneCall': 'Phone Call',
            'atAppointment': 'Meeting',
            'atToDo': 'To-Do'
        },

        //View Properties
        id: 'activity_complete',
        followupView: 'activity_edit',
        fieldsForLeads: ['AccountName', 'Lead'],
        fieldsForStandard: ['Account', 'Contact', 'Opportunity', 'Ticket'],
        picklistsByType: {
            'atAppointment': {
                'Category': 'Meeting Category Codes',
                'Description': 'Meeting Regarding',
                'Result': 'Meeting Result Codes'
            },
            'atLiterature': {
                'Description': 'Lit Request Regarding'
            },
            'atPersonal': {
                'Category': 'Meeting Category Codes',
                'Description': 'Personal Activity Regarding',
                'Result': 'Personal Activity Result Codes'
            },
            'atPhoneCall': {
                'Category': 'Phone Call Category Codes',
                'Description': 'Phone Call Regarding',
                'Result': 'Phone Call Result Codes'
            },
            'atToDo': {
                'Category': 'To Do Category Codes',
                'Description': 'To Do Regarding',
                'Result': 'To Do Result Codes'
            },
            'atEMail': {
                'Category': 'E-mail Category Codes',
                'Description': 'E-mail Regarding'
            }
        },

        entityName: 'Activity',
        querySelect: [
            'AccountId',
            'AccountName',
            'Alarm',
            'AlarmTime',
            'Category',
            'ContactId',
            'ContactName',
            'CompletedDate',
            'Description',
            'Duration',
            'Leader/$key',
            'LeadId',
            'LeadName',
            'LongNotes',
            'OpportunityId',
            'OpportunityName',
            'Priority',
            'Regarding',
            'Result',
            'Rollover',
            'StartDate',
            'TicketId',
            'TicketNumber',
            'Timeless',
            'Type',
            'Recurring',
            'RecurrenceState'
        ],
        resourceKind: 'activities',
        contractName: 'system',

        init: function() {
            this.inherited(arguments);

            this.connect(this.fields['Leader'], 'onChange', this.onLeaderChange);
            this.connect(this.fields['Timeless'], 'onChange', this.onTimelessChange);
            this.connect(this.fields['AsScheduled'], 'onChange', this.onAsScheduledChange);
            this.connect(this.fields['Followup'], 'onChange', this.onFollowupChange);
            this.connect(this.fields['Lead'], 'onChange', this.onLeadChange);
        },
        isActivityForLead: function(entry) {
            return entry && /^[\w]{12}$/.test(entry['LeadId']);
        },
        beforeTransitionTo: function() {
            this.inherited(arguments);

            array.forEach(this.fieldsForStandard.concat(this.fieldsForLeads), function(item) {
                if (this.fields[item])
                    this.fields[item].hide();
            }, this);

            var entry = this.options && this.options.entry;
            if (this.isActivityForLead(entry))
            {
                array.forEach(this.fieldsForLeads, function(item) {
                    if (this.fields[item])
                        this.fields[item].show();
                }, this);
            }
            else
            {
                array.forEach(this.fieldsForStandard, function(item) {
                    if (this.fields[item])
                        this.fields[item].show();
                }, this);
            }
        },
        toggleSelectField: function(field, disable) {
            disable === true ? field.disable() : field.enable();
        },
        onTimelessChange: function(value, field) {
            this.toggleSelectField(this.fields['Duration'], value);
        },
        onAsScheduledChange: function(scheduled, field) {
            if (scheduled)
            {
                this.toggleSelectField(this.fields['CompletedDate'], true);
                this.fields['CompletedDate'].setValue(this.fields['StartDate'].getValue());
            }
            else
            {
                this.toggleSelectField(this.fields['CompletedDate'], false);
                this.fields['CompletedDate'].setValue(new Date());
            }
        },
        onFollowupChange: function(value, field) {
            var disable = (value === 'none' || (value && value.key === 'none'));
            this.toggleSelectField(this.fields['CarryOverNotes'], disable);
        },
        onLeadChange: function(value, field) {
            var selection = field.getSelection();

            if (selection && this.insert)
            {
                this.fields['Company'].setValue(utility.getValue(selection, 'Company'));
            }
        },
        formatPicklistForType: function(type, which) {
            return this.picklistsByType[type] && this.picklistsByType[type][which];
        },
        setValues: function(values) {
            this.inherited(arguments);
            this.fields['CarryOverNotes'].setValue(true);
            this.fields['CompletedDate'].setValue(new Date());
            this.fields['Followup'].setValue('none');
            this.fields['Result'].setValue('Complete');

            this.toggleSelectField(this.fields['CarryOverNotes'], true);
            this.toggleSelectField(this.fields['CompletedDate'], false);
        },
        onLeaderChange: function(value, field) {
            var userId = field.getValue();
            this.fields['UserId'].setValue(userId && userId['$key']);
        },
        formatFollowupText: function(val, key, text) {
            return this.followupValueText[key] || text;
        },
        createDurationData: function() {
            var list = [];

            for (var duration in this.durationValueText)
            {
                list.push({
                    '$key': duration,
                    '$descriptor': this.durationValueText[duration]
                });
            }

            return {'$resources': list};
        },
        createFollowupData: function() {
            var list = [];

            for (var followup in this.followupValueText)
            {
                list.push({
                    '$key': followup,
                    '$descriptor': this.followupValueText[followup]
                });
            }

            return {'$resources': list};
        },
        navigateToFollowUpView: function(entry) {
            var view = App.getView(this.followupView),
                followupEntry = {
                    'Type': this.fields['Followup'].getValue(),
                    'Description': entry.Description,
                    'AccountId': entry.AccountId,
                    'AccountName': entry.AccountName,
                    'ContactId': entry.ContactId,
                    'ContactName': entry.ContactName,
                    'LeadId': entry.LeadId,
                    'LeadName': entry.LeadName,
                    'LongNotes': (this.fields['CarryOverNotes'].getValue() && entry['LongNotes']) || '',
                    'OpportunityId': entry.OpportunityId,
                    'OpportunityName': entry.OpportunityName,
                    'StartDate': Date.now(),
                    'TicketId': entry.TicketId,
                    'TicketNumber': entry.TicketNumber
                };

            //Return to activity list view after follow up.
            view.show({
                entry: followupEntry,
                insert: true,
                title: this.followupValueText[this.fields['Followup'].getValue()]
            }, {
                returnTo: -1
            });
        },
        completeActivity: function(entry, callback) {
            var completeActivityEntry = {
                "$name": "ActivityComplete",
                "request": {
                    "entity": { '$key': entry['$key'] },
                    "ActivityId": entry['$key'],
                    "userId": entry['Leader']['$key'],
                    "result": this.fields['Result'].getValue(),
                    "completeDate":  this.fields['CompletedDate'].getValue()
                }
            };

            var success = (function(scope, callback, entry) {
                return function() {
                    connect.publish('/app/refresh',[{
                        resourceKind: 'history'
                    }]);

                    callback.apply(scope, [entry]);
                };
            })(this, callback, entry);

            var request = new Sage.SData.Client.SDataServiceOperationRequest(this.getService())
                .setResourceKind('activities')
                .setContractName('system')
                .setOperationName('Complete');

            request.execute(completeActivityEntry, {
                success: success,
                failure: this.onRequestFailure,
                scope: this
            });
        },
        onUpdateCompleted: function(entry) {
            var followup = this.fields['Followup'].getValue() !== 'none'
                    ? this.navigateToFollowUpView
                    : this.getInherited(arguments);

            this.completeActivity(entry, followup);
        },
        formatDependentQuery: function(dependentValue, format, property) {
            property = property || '$key';

            return string.substitute(format, [utility.getValue(dependentValue, property)]);
        },
        createLayout: function() {
            return this.layout || (this.layout = [{
                title: this.activityInfoText,
                name: 'ActivityInfoSection',
                collapsed: false,
                children: [{
                    name: 'Type',
                    property: 'Type',
                    type: 'hidden'
                },{
                    dependsOn: 'Type',
                    label: this.regardingText,
                    name: 'Description',
                    property: 'Description',
                    picklist: this.formatPicklistForType.bindDelegate(this, 'Description'),
                    title: this.regardingTitleText,
                    orderBy: 'text asc',
                    type: 'picklist',
                    maxTextLength: 64,
                    validator: validator.exceedsMaxTextLength
                },{
                    label: this.startingText,
                    name: 'StartDate',
                    property: 'StartDate',
                    type: 'date',
                    showTimePicker: true,
                    formatString: this.startingFormatText,
                    minValue: (new Date(1900, 0, 1)),
                    validator: [
                        validator.exists,
                        validator.isDateInRange
                    ]
                },{
                    label: this.durationText,
                    title: this.durationTitleText,
                    name: 'Duration',
                    property: 'Duration',
                    type: 'duration',
                    view: 'select_list',
                    data: this.createDurationData(),
                    validator: {
                        fn: function(val, field) {
                            if (field.isDisabled()) return false;
                            if (!/^\d+$/.test(val)) return true;
                        },
                        message: this.durationInvalidText
                    }
                },{
                    label: this.timelessText,
                    name: 'Timeless',
                    property: 'Timeless',
                    type: 'boolean'
                }]
            },{
                title: this.completionText,
                name: 'CompletionSection',
                collapsed: false,
                children: [{
                    label: this.asScheduledText,
                    include: false,
                    name: 'AsScheduled',
                    property: 'AsScheduled',
                    type: 'boolean'
                },{
                    label: this.completedText,
                    name: 'CompletedDate',
                    property: 'CompletedDate',
                    type: 'date',
                    showTimePicker: true,
                    formatString: this.completedFormatText,
                    minValue: (new Date(1900, 0, 1)),
                    validator: [
                        validator.exists,
                        validator.isDateInRange
                    ]
                },{
                    dependsOn: 'Type',
                    label: this.resultText,
                    name: 'Result',
                    property: 'Result',
                    picklist: this.formatPicklistForType.bindDelegate(this, 'Result'),
                    title: this.resultTitleText,
                    orderBy: 'text asc',
                    type: 'picklist',
                    maxTextLength: 64,
                    validator: validator.exceedsMaxTextLength
                },{
                    label: this.followUpText,
                    title: this.followUpTitleText,
                    name: 'Followup',
                    property: 'Followup',
                    type: 'select',
                    view: 'select_list',
                    textRenderer: this.formatFollowupText.bindDelegate(this),
                    requireSelection: true,
                    valueKeyProperty: false,
                    valueTextProperty: false,
                    data: this.createFollowupData(),
                    include: false
                },{
                    label: this.carryOverNotesText,
                    include: false,
                    name: 'CarryOverNotes',
                    property: 'CarryOverNotes',
                    type: 'boolean'
                },{
                    label: this.longNotesText,
                    noteProperty: false,
                    name: 'LongNotes',
                    property: 'LongNotes',
                    title: this.longNotesTitleText,
                    type: 'note',
                    view: 'text_edit'
                },{
                    label: this.accountText,
                    name: 'Account',
                    property: 'Account',
                    type: 'lookup',
                    emptyText: '',
                    applyTo: '.',
                    valueKeyProperty: 'AccountId',
                    valueTextProperty: 'AccountName',
                    view: 'account_related'
                },{
                    dependsOn: 'Account',
                    label: this.contactText,
                    name: 'Contact',
                    property: 'Contact',
                    type: 'lookup',
                    emptyText: '',
                    applyTo: '.',
                    valueKeyProperty: 'ContactId',
                    valueTextProperty: 'ContactName',
                    view: 'contact_related',
                    where: this.formatDependentQuery.bindDelegate(this, 'Account.Id eq "${0}"', 'AccountId')
                },{
                    dependsOn: 'Account',
                    label: this.opportunityText,
                    name: 'Opportunity',
                    property: 'Opportunity',
                    type: 'lookup',
                    emptyText: '',
                    applyTo: '.',
                    valueKeyProperty: 'OpportunityId',
                    valueTextProperty: 'OpportunityName',
                    view: 'opportunity_related',
                    where: this.formatDependentQuery.bindDelegate(this, 'Account.Id eq "${0}"', 'AccountId')
                },{
                    dependsOn: 'Account',
                    label: this.ticketNumberText,
                    name: 'Ticket',
                    property: 'Ticket',
                    type: 'lookup',
                    emptyText: '',
                    applyTo: '.',
                    valueKeyProperty: 'TicketId',
                    valueTextProperty: 'TicketNumber',
                    view: 'ticket_related',
                    where: this.formatDependentQuery.bindDelegate(this, 'Account.Id eq "${0}"', 'AccountId')
                },{
                    label: this.leadText,
                    name: 'Lead',
                    property: 'Lead',
                    type: 'lookup',
                    emptyText: '',
                    applyTo: '.',
                    valueKeyProperty: 'LeadId',
                    valueTextProperty: 'LeadName',
                    view: 'lead_related'
                },{
                    label: this.companyText,
                    name: 'AccountName',
                    property: 'AccountName',
                    type: 'text'
                }]
            },{
                title: this.otherInfoText,
                name: 'OtherInfoSection',
                collapsed: false,
                children: [{
                    label: this.priorityText,
                    name: 'Priority',
                    property: 'Priority',
                    picklist: 'Priorities',
                    title: this.priorityTitleText,
                    type: 'picklist',
                    maxTextLength: 64,
                    validator: validator.exceedsMaxTextLength
                },{
                    dependsOn: 'Type',
                    label: this.categoryText,
                    name: 'Category',
                    property: 'Category',
                    picklist: this.formatPicklistForType.bindDelegate(this, 'Category'),
                    orderBy: 'text asc',
                    title: this.categoryTitleText,
                    type: 'picklist',
                    maxTextLength: 64,
                    validator: validator.exceedsMaxTextLength
                },{
                    type: 'hidden',
                    name: 'UserId',
                    property: 'UserId'
                },{
                    label: this.leaderText,
                    name: 'Leader',
                    property: 'Leader',
                    include: false,
                    type: 'lookup',
                    textProperty: 'UserInfo',
                    textTemplate: template.nameLF,
                    requireSelection: true,
                    view: 'user_list'
                }]
            }]);
        }
    });
});
<commit_msg>[Issue #36] Added Personal Activity as a followup activity option<commit_after>define('Mobile/SalesLogix/Views/Activity/Complete', [
    'dojo/_base/declare',
    'dojo/_base/array',
    'dojo/_base/connect',
    'dojo/string',
    'Mobile/SalesLogix/Validator',
    'Mobile/SalesLogix/Template',
    'Sage/Platform/Mobile/Utility',
    'Sage/Platform/Mobile/Edit'
], function(
    declare,
    array,
    connect,
    string,
    validator,
    template,
    utility,
    Edit
) {

    return declare('Mobile.SalesLogix.Views.Activity.Complete', [Edit], {
        //Localization
        activityInfoText: 'Activity Info',
        accountText: 'account',
        contactText: 'contact',
        opportunityText: 'opportunity',
        ticketNumberText: 'ticket',
        companyText: 'company',
        leadText: 'lead',
        asScheduledText: 'as scheduled',
        categoryText: 'category',
        categoryTitleText: 'Activity Category',
        completedText: 'completed date',
		completedFormatText: 'M/d/yyyy h:mm tt',
        completionText: 'Completion',
        durationText: 'duration',
		durationInvalidText: "The field '${2}' must have a value.",
        carryOverNotesText: 'carry over notes',
        followUpText: 'follow-up',
        followUpTitleText: 'Follow-up type',
        leaderText: 'leader',
        longNotesText: 'notes',
        longNotesTitleText: 'Notes',
        otherInfoText: 'Other Info',
        priorityText: 'priority',
        priorityTitleText: 'Priority',
        regardingText: 'regarding',
        regardingTitleText: 'Activity Regarding',
        resultText: 'result',
        resultTitleText: 'Result',
        startingText: 'start date',
		startingFormatText: 'M/d/yyyy h:mm tt',
        timelessText: 'timeless',
        durationValueText: {
            0: 'none',
            15: '15 minutes',
            30: '30 minutes',
            60: '1 hour',
            90: '1.5 hours',
            120: '2 hours'
        },
        followupValueText: {
            'none': 'None',
            'atPhoneCall': 'Phone Call',
            'atAppointment': 'Meeting',
            'atToDo': 'To-Do',
            'atPersonal': 'Personal Activity'
        },

        //View Properties
        id: 'activity_complete',
        followupView: 'activity_edit',
        fieldsForLeads: ['AccountName', 'Lead'],
        fieldsForStandard: ['Account', 'Contact', 'Opportunity', 'Ticket'],
        picklistsByType: {
            'atAppointment': {
                'Category': 'Meeting Category Codes',
                'Description': 'Meeting Regarding',
                'Result': 'Meeting Result Codes'
            },
            'atLiterature': {
                'Description': 'Lit Request Regarding'
            },
            'atPersonal': {
                'Category': 'Meeting Category Codes',
                'Description': 'Personal Activity Regarding',
                'Result': 'Personal Activity Result Codes'
            },
            'atPhoneCall': {
                'Category': 'Phone Call Category Codes',
                'Description': 'Phone Call Regarding',
                'Result': 'Phone Call Result Codes'
            },
            'atToDo': {
                'Category': 'To Do Category Codes',
                'Description': 'To Do Regarding',
                'Result': 'To Do Result Codes'
            },
            'atEMail': {
                'Category': 'E-mail Category Codes',
                'Description': 'E-mail Regarding'
            }
        },

        entityName: 'Activity',
        querySelect: [
            'AccountId',
            'AccountName',
            'Alarm',
            'AlarmTime',
            'Category',
            'ContactId',
            'ContactName',
            'CompletedDate',
            'Description',
            'Duration',
            'Leader/$key',
            'LeadId',
            'LeadName',
            'LongNotes',
            'OpportunityId',
            'OpportunityName',
            'Priority',
            'Regarding',
            'Result',
            'Rollover',
            'StartDate',
            'TicketId',
            'TicketNumber',
            'Timeless',
            'Type',
            'Recurring',
            'RecurrenceState'
        ],
        resourceKind: 'activities',
        contractName: 'system',

        init: function() {
            this.inherited(arguments);

            this.connect(this.fields['Leader'], 'onChange', this.onLeaderChange);
            this.connect(this.fields['Timeless'], 'onChange', this.onTimelessChange);
            this.connect(this.fields['AsScheduled'], 'onChange', this.onAsScheduledChange);
            this.connect(this.fields['Followup'], 'onChange', this.onFollowupChange);
            this.connect(this.fields['Lead'], 'onChange', this.onLeadChange);
        },
        isActivityForLead: function(entry) {
            return entry && /^[\w]{12}$/.test(entry['LeadId']);
        },
        beforeTransitionTo: function() {
            this.inherited(arguments);

            array.forEach(this.fieldsForStandard.concat(this.fieldsForLeads), function(item) {
                if (this.fields[item])
                    this.fields[item].hide();
            }, this);

            var entry = this.options && this.options.entry;
            if (this.isActivityForLead(entry))
            {
                array.forEach(this.fieldsForLeads, function(item) {
                    if (this.fields[item])
                        this.fields[item].show();
                }, this);
            }
            else
            {
                array.forEach(this.fieldsForStandard, function(item) {
                    if (this.fields[item])
                        this.fields[item].show();
                }, this);
            }
        },
        toggleSelectField: function(field, disable) {
            disable === true ? field.disable() : field.enable();
        },
        onTimelessChange: function(value, field) {
            this.toggleSelectField(this.fields['Duration'], value);
        },
        onAsScheduledChange: function(scheduled, field) {
            if (scheduled)
            {
                this.toggleSelectField(this.fields['CompletedDate'], true);
                this.fields['CompletedDate'].setValue(this.fields['StartDate'].getValue());
            }
            else
            {
                this.toggleSelectField(this.fields['CompletedDate'], false);
                this.fields['CompletedDate'].setValue(new Date());
            }
        },
        onFollowupChange: function(value, field) {
            var disable = (value === 'none' || (value && value.key === 'none'));
            this.toggleSelectField(this.fields['CarryOverNotes'], disable);
        },
        onLeadChange: function(value, field) {
            var selection = field.getSelection();

            if (selection && this.insert)
            {
                this.fields['Company'].setValue(utility.getValue(selection, 'Company'));
            }
        },
        formatPicklistForType: function(type, which) {
            return this.picklistsByType[type] && this.picklistsByType[type][which];
        },
        setValues: function(values) {
            this.inherited(arguments);
            this.fields['CarryOverNotes'].setValue(true);
            this.fields['CompletedDate'].setValue(new Date());
            this.fields['Followup'].setValue('none');
            this.fields['Result'].setValue('Complete');

            this.toggleSelectField(this.fields['CarryOverNotes'], true);
            this.toggleSelectField(this.fields['CompletedDate'], false);
        },
        onLeaderChange: function(value, field) {
            var userId = field.getValue();
            this.fields['UserId'].setValue(userId && userId['$key']);
        },
        formatFollowupText: function(val, key, text) {
            return this.followupValueText[key] || text;
        },
        createDurationData: function() {
            var list = [];

            for (var duration in this.durationValueText)
            {
                list.push({
                    '$key': duration,
                    '$descriptor': this.durationValueText[duration]
                });
            }

            return {'$resources': list};
        },
        createFollowupData: function() {
            var list = [];

            for (var followup in this.followupValueText)
            {
                list.push({
                    '$key': followup,
                    '$descriptor': this.followupValueText[followup]
                });
            }

            return {'$resources': list};
        },
        navigateToFollowUpView: function(entry) {
            var view = App.getView(this.followupView),
                followupEntry = {
                    'Type': this.fields['Followup'].getValue(),
                    'Description': entry.Description,
                    'AccountId': entry.AccountId,
                    'AccountName': entry.AccountName,
                    'ContactId': entry.ContactId,
                    'ContactName': entry.ContactName,
                    'LeadId': entry.LeadId,
                    'LeadName': entry.LeadName,
                    'LongNotes': (this.fields['CarryOverNotes'].getValue() && entry['LongNotes']) || '',
                    'OpportunityId': entry.OpportunityId,
                    'OpportunityName': entry.OpportunityName,
                    'StartDate': Date.now(),
                    'TicketId': entry.TicketId,
                    'TicketNumber': entry.TicketNumber
                };

            //Return to activity list view after follow up.
            view.show({
                entry: followupEntry,
                insert: true,
                title: this.followupValueText[this.fields['Followup'].getValue()]
            }, {
                returnTo: -1
            });
        },
        completeActivity: function(entry, callback) {
            var completeActivityEntry = {
                "$name": "ActivityComplete",
                "request": {
                    "entity": { '$key': entry['$key'] },
                    "ActivityId": entry['$key'],
                    "userId": entry['Leader']['$key'],
                    "result": this.fields['Result'].getValue(),
                    "completeDate":  this.fields['CompletedDate'].getValue()
                }
            };

            var success = (function(scope, callback, entry) {
                return function() {
                    connect.publish('/app/refresh',[{
                        resourceKind: 'history'
                    }]);

                    callback.apply(scope, [entry]);
                };
            })(this, callback, entry);

            var request = new Sage.SData.Client.SDataServiceOperationRequest(this.getService())
                .setResourceKind('activities')
                .setContractName('system')
                .setOperationName('Complete');

            request.execute(completeActivityEntry, {
                success: success,
                failure: this.onRequestFailure,
                scope: this
            });
        },
        onUpdateCompleted: function(entry) {
            var followup = this.fields['Followup'].getValue() !== 'none'
                    ? this.navigateToFollowUpView
                    : this.getInherited(arguments);

            this.completeActivity(entry, followup);
        },
        formatDependentQuery: function(dependentValue, format, property) {
            property = property || '$key';

            return string.substitute(format, [utility.getValue(dependentValue, property)]);
        },
        createLayout: function() {
            return this.layout || (this.layout = [{
                title: this.activityInfoText,
                name: 'ActivityInfoSection',
                collapsed: false,
                children: [{
                    name: 'Type',
                    property: 'Type',
                    type: 'hidden'
                },{
                    dependsOn: 'Type',
                    label: this.regardingText,
                    name: 'Description',
                    property: 'Description',
                    picklist: this.formatPicklistForType.bindDelegate(this, 'Description'),
                    title: this.regardingTitleText,
                    orderBy: 'text asc',
                    type: 'picklist',
                    maxTextLength: 64,
                    validator: validator.exceedsMaxTextLength
                },{
                    label: this.startingText,
                    name: 'StartDate',
                    property: 'StartDate',
                    type: 'date',
                    showTimePicker: true,
                    formatString: this.startingFormatText,
                    minValue: (new Date(1900, 0, 1)),
                    validator: [
                        validator.exists,
                        validator.isDateInRange
                    ]
                },{
                    label: this.durationText,
                    title: this.durationTitleText,
                    name: 'Duration',
                    property: 'Duration',
                    type: 'duration',
                    view: 'select_list',
                    data: this.createDurationData(),
                    validator: {
                        fn: function(val, field) {
                            if (field.isDisabled()) return false;
                            if (!/^\d+$/.test(val)) return true;
                        },
                        message: this.durationInvalidText
                    }
                },{
                    label: this.timelessText,
                    name: 'Timeless',
                    property: 'Timeless',
                    type: 'boolean'
                }]
            },{
                title: this.completionText,
                name: 'CompletionSection',
                collapsed: false,
                children: [{
                    label: this.asScheduledText,
                    include: false,
                    name: 'AsScheduled',
                    property: 'AsScheduled',
                    type: 'boolean'
                },{
                    label: this.completedText,
                    name: 'CompletedDate',
                    property: 'CompletedDate',
                    type: 'date',
                    showTimePicker: true,
                    formatString: this.completedFormatText,
                    minValue: (new Date(1900, 0, 1)),
                    validator: [
                        validator.exists,
                        validator.isDateInRange
                    ]
                },{
                    dependsOn: 'Type',
                    label: this.resultText,
                    name: 'Result',
                    property: 'Result',
                    picklist: this.formatPicklistForType.bindDelegate(this, 'Result'),
                    title: this.resultTitleText,
                    orderBy: 'text asc',
                    type: 'picklist',
                    maxTextLength: 64,
                    validator: validator.exceedsMaxTextLength
                },{
                    label: this.followUpText,
                    title: this.followUpTitleText,
                    name: 'Followup',
                    property: 'Followup',
                    type: 'select',
                    view: 'select_list',
                    textRenderer: this.formatFollowupText.bindDelegate(this),
                    requireSelection: true,
                    valueKeyProperty: false,
                    valueTextProperty: false,
                    data: this.createFollowupData(),
                    include: false
                },{
                    label: this.carryOverNotesText,
                    include: false,
                    name: 'CarryOverNotes',
                    property: 'CarryOverNotes',
                    type: 'boolean'
                },{
                    label: this.longNotesText,
                    noteProperty: false,
                    name: 'LongNotes',
                    property: 'LongNotes',
                    title: this.longNotesTitleText,
                    type: 'note',
                    view: 'text_edit'
                },{
                    label: this.accountText,
                    name: 'Account',
                    property: 'Account',
                    type: 'lookup',
                    emptyText: '',
                    applyTo: '.',
                    valueKeyProperty: 'AccountId',
                    valueTextProperty: 'AccountName',
                    view: 'account_related'
                },{
                    dependsOn: 'Account',
                    label: this.contactText,
                    name: 'Contact',
                    property: 'Contact',
                    type: 'lookup',
                    emptyText: '',
                    applyTo: '.',
                    valueKeyProperty: 'ContactId',
                    valueTextProperty: 'ContactName',
                    view: 'contact_related',
                    where: this.formatDependentQuery.bindDelegate(this, 'Account.Id eq "${0}"', 'AccountId')
                },{
                    dependsOn: 'Account',
                    label: this.opportunityText,
                    name: 'Opportunity',
                    property: 'Opportunity',
                    type: 'lookup',
                    emptyText: '',
                    applyTo: '.',
                    valueKeyProperty: 'OpportunityId',
                    valueTextProperty: 'OpportunityName',
                    view: 'opportunity_related',
                    where: this.formatDependentQuery.bindDelegate(this, 'Account.Id eq "${0}"', 'AccountId')
                },{
                    dependsOn: 'Account',
                    label: this.ticketNumberText,
                    name: 'Ticket',
                    property: 'Ticket',
                    type: 'lookup',
                    emptyText: '',
                    applyTo: '.',
                    valueKeyProperty: 'TicketId',
                    valueTextProperty: 'TicketNumber',
                    view: 'ticket_related',
                    where: this.formatDependentQuery.bindDelegate(this, 'Account.Id eq "${0}"', 'AccountId')
                },{
                    label: this.leadText,
                    name: 'Lead',
                    property: 'Lead',
                    type: 'lookup',
                    emptyText: '',
                    applyTo: '.',
                    valueKeyProperty: 'LeadId',
                    valueTextProperty: 'LeadName',
                    view: 'lead_related'
                },{
                    label: this.companyText,
                    name: 'AccountName',
                    property: 'AccountName',
                    type: 'text'
                }]
            },{
                title: this.otherInfoText,
                name: 'OtherInfoSection',
                collapsed: false,
                children: [{
                    label: this.priorityText,
                    name: 'Priority',
                    property: 'Priority',
                    picklist: 'Priorities',
                    title: this.priorityTitleText,
                    type: 'picklist',
                    maxTextLength: 64,
                    validator: validator.exceedsMaxTextLength
                },{
                    dependsOn: 'Type',
                    label: this.categoryText,
                    name: 'Category',
                    property: 'Category',
                    picklist: this.formatPicklistForType.bindDelegate(this, 'Category'),
                    orderBy: 'text asc',
                    title: this.categoryTitleText,
                    type: 'picklist',
                    maxTextLength: 64,
                    validator: validator.exceedsMaxTextLength
                },{
                    type: 'hidden',
                    name: 'UserId',
                    property: 'UserId'
                },{
                    label: this.leaderText,
                    name: 'Leader',
                    property: 'Leader',
                    include: false,
                    type: 'lookup',
                    textProperty: 'UserInfo',
                    textTemplate: template.nameLF,
                    requireSelection: true,
                    view: 'user_list'
                }]
            }]);
        }
    });
});
<|endoftext|>


<commit_before>import { useEffect, useRef, useState } from 'react';
import getOption from './lib/getOption';
import updateOption from './lib/updateOption';
import getDisplayValue from './lib/getDisplayValue';
import getValue from './lib/getValue';
import groupOptions from './lib/groupOptions';
import fuzzySearch from './lib/fuzzySearch';
import reduce from './lib/reduce';
import useOptions from './useOptions';
import useHighlight from './useHighlight';

const nullCb = () => {};

export default function useSelect({
    options: defaultOptions,
    defaultValue,
    value,
    multiple,
    search,
    onChange = nullCb,
    onFocus = nullCb,
    onBlur = nullCb,
    closeOnSelect = true,
    placeholder,
    getOptions,
    filterOptions,
    useFuzzySearch = true,
    debounce,
}) {
    const ref = useRef();
    const [state, update] = useState(() => ({
        option: null,
        search: '',
        focus: false,
    }));
    const setState = (u) => update({ ...state, ...u });
    const [options, fetching] = useOptions(
        defaultOptions,
        getOptions,
        debounce,
        state.search,
    );

    const onSelect = (v) => {
        const newOption = updateOption(
            getOption(decodeURIComponent(v), options),
            state.option,
            multiple,
        );

        if (value === undefined) {
            setState({
                option: newOption,
            });
        }

        onChange(getValue(newOption), newOption);

        setTimeout(() => {
            if (ref.current && closeOnSelect) {
                ref.current.blur();
            }
        }, 0);
    };

    const [keyHandlers, highlighted, setHighlighted] = useHighlight(
        options,
        onSelect,
        ref,
    );
    const middleware = [
        useFuzzySearch ? fuzzySearch : null,
        ...(filterOptions ? filterOptions : []),
    ];

    const snapshot = {
        ...state,
        value: getValue(state.option),
        fetching,
        highlighted,
        options: groupOptions(reduce(middleware, options, state.search)),
        displayValue: getDisplayValue(state.option, options, placeholder),
    };

    const valueProps = {
        tabIndex: '0',
        readOnly: !search,
        placeholder,
        value: state.focus && search ? state.search : snapshot.displayValue,
        ref,
        ...keyHandlers,
        onFocus: (e) => {
            setState({ focus: true });
            onFocus(e);
        },
        onBlur: (e) => {
            setState({ focus: false, search: '' });
            setHighlighted(-1);
            onBlur(e);
        },
        onMouseDown: (e) => {
            if (state.focus) {
                e.preventDefault();
                ref.current.blur();
            }
        },
        onChange: search
            ? ({ target }) => setState({ search: target.value })
            : null,
    };

    const optionProps = {
        tabIndex: '-1',
        onMouseDown(e) {
            e.preventDefault();
            onSelect(e.currentTarget.value);
        },
    };

    useEffect(() => {
        setState({
            option: getOption(
                value === undefined ? defaultValue : value,
                options,
            ),
        });
    }, [value, options]);

    return [snapshot, valueProps, optionProps];
}
<commit_msg>Prevent state overrides<commit_after>import { useEffect, useRef, useState } from 'react';
import getOption from './lib/getOption';
import updateOption from './lib/updateOption';
import getDisplayValue from './lib/getDisplayValue';
import getValue from './lib/getValue';
import groupOptions from './lib/groupOptions';
import fuzzySearch from './lib/fuzzySearch';
import reduce from './lib/reduce';
import useOptions from './useOptions';
import useHighlight from './useHighlight';

const nullCb = () => {};

export default function useSelect({
    options: defaultOptions,
    defaultValue,
    value,
    multiple,
    search,
    onChange = nullCb,
    onFocus = nullCb,
    onBlur = nullCb,
    closeOnSelect = true,
    placeholder,
    getOptions,
    filterOptions,
    useFuzzySearch = true,
    debounce,
}) {
    const ref = useRef();
    const [option, setOption] = useState(null);
    const [q, setSearch] = useState('');
    const [focus, setFocus] = useState(false);
    const [options, fetching] = useOptions(
        defaultOptions,
        getOptions,
        debounce,
        q,
    );

    const onSelect = (v) => {
        const newOption = updateOption(
            getOption(decodeURIComponent(v), options),
            option,
            multiple,
        );

        if (value === undefined) {
            setOption(newOption);
        }

        onChange(getValue(newOption), newOption);

        setTimeout(() => {
            if (ref.current && closeOnSelect) {
                ref.current.blur();
            }
        }, 0);
    };

    const [keyHandlers, highlighted, setHighlighted] = useHighlight(
        options,
        onSelect,
        ref,
    );
    const middleware = [
        useFuzzySearch ? fuzzySearch : null,
        ...(filterOptions ? filterOptions : []),
    ];

    const snapshot = {
        search: q,
        focus,
        option,
        value: getValue(option),
        fetching,
        highlighted,
        options: groupOptions(reduce(middleware, options, q)),
        displayValue: getDisplayValue(option, options, placeholder),
    };

    const valueProps = {
        tabIndex: '0',
        readOnly: !search,
        placeholder,
        value: focus && search ? q : snapshot.displayValue,
        ref,
        ...keyHandlers,
        onFocus: (e) => {
            setFocus(true);
            onFocus(e);
        },
        onBlur: (e) => {
            setFocus(false);
            setSearch('');
            setHighlighted(-1);
            onBlur(e);
        },
        onMouseDown: (e) => {
            if (focus) {
                e.preventDefault();
                ref.current.blur();
            }
        },
        onChange: search
            ? ({ target }) => setSearch(target.value)
            : null,
    };

    const optionProps = {
        tabIndex: '-1',
        onMouseDown(e) {
            e.preventDefault();
            onSelect(e.currentTarget.value);
        },
    };

    useEffect(() => {
        setOption(getOption(
            value === undefined ? defaultValue : value,
            options,
        ));
    }, [value, options]);

    return [snapshot, valueProps, optionProps];
}
<|endoftext|>