<commit_before>/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
(function () {
  'use strict';

  var module = angular.module('pnc.configuration-set-record', [
    'ui.router',
    'angularUtils.directives.uiBreadcrumbs',
    'pnc.common.restclient'
  ]);

  module.config([
    '$stateProvider',
    '$urlRouterProvider',
    function ($stateProvider, $urlRouterProvider) {

      $stateProvider.state('configuration-set-record', {
        abstract: true,
        url: '/configuration-set-record',
        views: {
          'content@': {
            templateUrl: 'common/templates/single-col.tmpl.html'
          }
        },
        data: {
          proxy: 'configuration-set-record.list'
        }
      });

      $stateProvider.state('configuration-set-record.list', {
        url: '',
        templateUrl: 'configuration-set-record/views/record.list.html',
        data: {
          displayName: 'Group Builds'
        },
        controller: 'CsRecordListController',
        controllerAs: 'ctrl'
      });

      $urlRouterProvider.when('/configuration-set-record/:recordId', '/configuration-set-record/:recordId/info');

      $stateProvider.state('configuration-set-record.detail', {
        url: '/{recordId:int}',
        templateUrl: 'configuration-set-record/views/record.detail.html',
        data: {
          displayName: '# {{ csRecordDetail.id }} \u2014 {{ csRecordDetail.configurationSet.name }} '
        },
        controller: 'CsRecordDetailController',
        controllerAs: 'ctrl',
        resolve: {
          // we cannot use csRecord.getConfigurationSet() from BuildConfigurationSetRecordDAO
          // we have to load it immediately for displayName to work
          csRecordDetail: function (BuildConfigurationSetRecordDAO, BuildConfigurationSetDAO, $stateParams) {
            return BuildConfigurationSetRecordDAO.get({recordId: $stateParams.recordId}).$promise
              .then(function (csRecord) {
                return BuildConfigurationSetDAO.get({configurationSetId: csRecord.buildConfigurationSetId}).$promise
                  .then(function (configurationSet) {
                    csRecord.configurationSet = configurationSet;
                    return csRecord;
                  });
              });
          },
          // only records that belong to the current csRecord
          records: function ($q, csRecordDetail, BuildRecordDAO) {
            return BuildRecordDAO.query().then(function (r) {
              return _(r).where({buildConfigSetRecordId: csRecordDetail.id});
            });
          }
        }
      });

      $stateProvider.state('configuration-set-record.detail.info', {
        url: '/info',
        templateUrl: 'configuration-set-record/views/record.detail.info.html',
        data: {
          displayName: 'Build Group Info'
        },
        controller: 'CsRecordInfoController',
        controllerAs: 'ctrl'
      });

      $stateProvider.state('configuration-set-record.detail.result', {
        url: '/result',
        controller: 'CsRecordResultController',
        controllerAs: 'ctrl',
        templateUrl: 'configuration-set-record/views/record.detail.result.html',
        data: {
          displayName: 'Build Group Result'
        },
        resolve: {
          // load log for each record
          recordsLog: function ($q, BuildRecordDAO, records) {
            var promises = _(records).map(function (record) {
              return BuildRecordDAO.getLog({recordId: record.id}).$promise
                .then(function (log) {
                  var recordCopy = _.clone(record);
                  recordCopy.log = log;
                  return recordCopy;
                });
            });
            return $q.all(promises);
          }
        }
      });

      $stateProvider.state('configuration-set-record.detail.output', {
        url: '/output',
        controller: 'CsRecordOutputController',
        controllerAs: 'ctrl',
        templateUrl: 'configuration-set-record/views/record.detail.output.html',
        data: {
          displayName: 'Build Group Output'
        },
        resolve: {
          // load artifacts for each record
          recordsArtifacts: function ($q, BuildRecordDAO, records) {
            var promises = _(records).map(function (record) {
              return BuildRecordDAO.getArtifacts({recordId: record.id})
                .then(function (artifacts) {
                  var recordCopy = _.clone(record);
                  recordCopy.artifacts = artifacts;
                  return recordCopy;
                });
            });
            return $q.all(promises).then(function(result) {
              // skip when not available
              return _(result).filter(function(e) { return e.artifacts.length; });
            });
          }
        }
      });

    }]);

})();
<commit_msg>NCL-3144 Remove where method in favor of filter<commit_after>/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
(function () {
  'use strict';

  var module = angular.module('pnc.configuration-set-record', [
    'ui.router',
    'angularUtils.directives.uiBreadcrumbs',
    'pnc.common.restclient'
  ]);

  module.config([
    '$stateProvider',
    '$urlRouterProvider',
    function ($stateProvider, $urlRouterProvider) {

      $stateProvider.state('configuration-set-record', {
        abstract: true,
        url: '/configuration-set-record',
        views: {
          'content@': {
            templateUrl: 'common/templates/single-col.tmpl.html'
          }
        },
        data: {
          proxy: 'configuration-set-record.list'
        }
      });

      $stateProvider.state('configuration-set-record.list', {
        url: '',
        templateUrl: 'configuration-set-record/views/record.list.html',
        data: {
          displayName: 'Group Builds'
        },
        controller: 'CsRecordListController',
        controllerAs: 'ctrl'
      });

      $urlRouterProvider.when('/configuration-set-record/:recordId', '/configuration-set-record/:recordId/info');

      $stateProvider.state('configuration-set-record.detail', {
        url: '/{recordId:int}',
        templateUrl: 'configuration-set-record/views/record.detail.html',
        data: {
          displayName: '# {{ csRecordDetail.id }} \u2014 {{ csRecordDetail.configurationSet.name }} '
        },
        controller: 'CsRecordDetailController',
        controllerAs: 'ctrl',
        resolve: {
          // we cannot use csRecord.getConfigurationSet() from BuildConfigurationSetRecordDAO
          // we have to load it immediately for displayName to work
          csRecordDetail: function (BuildConfigurationSetRecordDAO, BuildConfigurationSetDAO, $stateParams) {
            return BuildConfigurationSetRecordDAO.get({recordId: $stateParams.recordId}).$promise
              .then(function (csRecord) {
                return BuildConfigurationSetDAO.get({configurationSetId: csRecord.buildConfigurationSetId}).$promise
                  .then(function (configurationSet) {
                    csRecord.configurationSet = configurationSet;
                    return csRecord;
                  });
              });
          },
          // only records that belong to the current csRecord
          records: function ($q, csRecordDetail, BuildRecordDAO) {
            return BuildRecordDAO.query().then(function (r) {
              return _(r).filter({buildConfigSetRecordId: csRecordDetail.id});
            });
          }
        }
      });

      $stateProvider.state('configuration-set-record.detail.info', {
        url: '/info',
        templateUrl: 'configuration-set-record/views/record.detail.info.html',
        data: {
          displayName: 'Build Group Info'
        },
        controller: 'CsRecordInfoController',
        controllerAs: 'ctrl'
      });

      $stateProvider.state('configuration-set-record.detail.result', {
        url: '/result',
        controller: 'CsRecordResultController',
        controllerAs: 'ctrl',
        templateUrl: 'configuration-set-record/views/record.detail.result.html',
        data: {
          displayName: 'Build Group Result'
        },
        resolve: {
          // load log for each record
          recordsLog: function ($q, BuildRecordDAO, records) {
            var promises = _(records).map(function (record) {
              return BuildRecordDAO.getLog({recordId: record.id}).$promise
                .then(function (log) {
                  var recordCopy = _.clone(record);
                  recordCopy.log = log;
                  return recordCopy;
                });
            });
            return $q.all(promises);
          }
        }
      });

      $stateProvider.state('configuration-set-record.detail.output', {
        url: '/output',
        controller: 'CsRecordOutputController',
        controllerAs: 'ctrl',
        templateUrl: 'configuration-set-record/views/record.detail.output.html',
        data: {
          displayName: 'Build Group Output'
        },
        resolve: {
          // load artifacts for each record
          recordsArtifacts: function ($q, BuildRecordDAO, records) {
            var promises = _(records).map(function (record) {
              return BuildRecordDAO.getArtifacts({recordId: record.id})
                .then(function (artifacts) {
                  var recordCopy = _.clone(record);
                  recordCopy.artifacts = artifacts;
                  return recordCopy;
                });
            });
            return $q.all(promises).then(function(result) {
              // skip when not available
              return _(result).filter(function(e) { return e.artifacts.length; });
            });
          }
        }
      });

    }]);

})();
<|endoftext|>


<commit_before>import React from 'react';
import Dropdown from './../dropdown';
import I18n from 'i18n-js';
import classNames from 'classnames';
import escapeStringRegexp from 'escape-string-regexp';

/**
 * A dropdown filter widget.
 *
 * == How to use a dropdown in a component:
 *
 * In your file
 *
 *   import DropdownFilter from 'carbon/lib/components/dropdown-filter';
 *
 * To render a DropdownFilter:
 *
 *   <DropdownFilter name="foo" options={ foo } onChange={ myChangeHandler } />
 *
 * The developer should pass data to the store as JSON. e.g.
 *
 *   foo: [{ id: 1, name: "Foo" }, { id: 2, name: "Bar" }]
 *
 * You can also use the component in 'suggest' mode, which only shows the dropdown
 * once a filter term has been entered.
 *
 * You can also use the component in 'freetext' mode, which behaves like 'suggest',
 * but allows write-in text values in addition to list options.
 *
 * You can also define a function using the 'create' prop, this will allow you
 * to trigger events to create new items.
 *
 * @class DropdownFilter
 * @constructor
 */
class DropdownFilter extends Dropdown {
  constructor(...args) {
    super(...args);

    /**
     * The user input search text.
     *
     * @property filter
     * @type {String}
     * @default null
     */
    this.state.filter = null;

    /**
     * Determines if list is being opened on current render.
     *
     * @property openingList
     * @type {Boolean}
     * @default false
     */
    this.openingList = false;

    // bind scope to functions - allowing them to be overridden and
    // recalled with the use of super
    this.handleVisibleChange = this.handleVisibleChange.bind(this);
  }

  static propTypes = {
    /**
     * The ID value for the component
     *
     * @property value
     * @type {String}
     */
    value: React.PropTypes.oneOfType([
      React.PropTypes.string,
      React.PropTypes.number
    ]),

    /**
     * The options to be displayed in the dropdown. Should be set in the store and passed from the parent component.
     *
     * @property options
     * @type {object}
     */
    options: React.PropTypes.object.isRequired,

    /**
     * Enables create functionality for dropdown.
     *
     * @property create
     * @type {Function}
     */
    create: React.PropTypes.func,

    /**
     * Should the dropdown act and look like a suggestable input instead.
     *
     * @property suggest
     * @type {Boolean}
     */
    suggest: React.PropTypes.bool,

    /**
     * Should the dropdown accept free text as well as suggested options?
     *
     * @property freetext
     * @type {Boolean}
     */
    freetext: React.PropTypes.bool
  }

  /**
   * Lifecycle hook for when the component will update.
   *
   * @method componentWillUpdate
   * @param {Object} nextProps
   * @param {Object} nextState
   */
  componentWillUpdate(nextProps, nextState) {
    // if list is being opened, set boolean
    if (this.state.open != nextState.open) {
      this.openingList = true;
    }
  }

  /**
   * Selects the value for the component
   *
   * @method selectValue
   * @param {String} val
   */
  selectValue(val, visibleVal) {
    let filter = this.props.freetext ? visibleVal : null;
    super.selectValue(val, visibleVal);
    this.setState({ filter: filter });
  }

  /*
   * Handles changes to the visible input field. Updates filter and displayed value.
   *
   * @method handleVisibleChange
   * @param {Object} ev event
   */
  handleVisibleChange(ev) {
    let state = {
      filter: ev.target.value,
      highlighted: null
    };

    if ((this.props.suggest || this.props.freetext) && ev.target.value.length <= 0) {
      state.open = false;
    } else {
      state.open = true;
    }

    this.setState(state);

    this.openingList = false;

    if (this.props.create) {
      // if create is enabled then empty the selected value so the filter persists
      this.emitOnChangeCallback("", ev.target.value);
    }
  }

  /*
   * Handles what happens on blur of the input.
   *
   * @method handleBlur
   */
  handleBlur = () => {
    if (!this.blockBlur) {
      let filter = null;

      if (this.props.create || this.props.freetext) filter = this.state.filter;
      this.setState({ open: false, filter: filter });

      if (this.props.freetext) {
        let opt = this.props.options.find((opt) => { return opt.get('name') === this.state.filter; });

        if (opt) this.selectValue(opt.get('id'), opt.get('name'));
        else this.emitOnChangeCallback('', this.state.filter);
      }
    }
  }

  /**
   * Handles what happens on focus of the input.
   *
   * @method handleFocus
   */
  handleFocus = () => {
    if (!(this.props.suggest || this.props.freetext) && !this.blockFocus) {
      this.setState({ open: true });
    } else {
      this.blockFocus = false;
    }

    this._input.setSelectionRange(0, this._input.value.length);
  }

  /**
   * Handles what happens when create button is clicked.
   *
   * @method handleCreate
   */
  handleCreate = (ev) => {
    this.setState({ open: false });
    this.props.create(ev, this);
  }

  /**
   * Prepares list options by converting to JSON and formatting filtered options.
   *
   * @method prepareList
   * @param {Object} options Immutable map of list options
   */
  prepareList = (options) => {
    if ((this.props.suggest || this.props.freetext || !this.openingList) && typeof this.state.filter === 'string') {
      let filter = this.state.filter;
      let regex = new RegExp(escapeStringRegexp(filter), 'i');

      // if user has entered a search filter
      options = options.filter((option) => {
        if (option.name.search(regex) > -1) {
          option.name = this.highlightMatches(option.name, filter);
          return option;
        }
      });
    }

    return options;
  }

  /**
   * Function that returns search results. Builds each list item with relevant handlers and classes.
   *
   * @method results
   */
  results(options) {
    let items = super.results(options);

    if (!items.length) {
      items = (

          {
            I18n.t("dropdownlist.no_results", {
              defaultValue: "No results match \"%{term}\"",
              term: this.state.filter
            })
          }

      );
    }

    return items;
  }

  /**
   * Return the list item which should be highlighted by default.
   *
   * @method highlighted
   */
  highlighted = (options) => {
    let highlighted = null;

    if (this.state.highlighted) {
      highlighted = this.state.highlighted;
    } else {
      if (!this.state.filter && this.props.value) {
        highlighted = this.props.value;
      } else if (this.state.filter && options.length) {
        highlighted = options[0].id;
      }
    }

    return highlighted;
  }

  /**
   * Getter to return HTML for list to render method.
   *
   * @method listHTML
   */
  get listHTML() {
    let original = super.listHTML,
        html = [original];

    if (this.state.open && this.props.create) {
      let text = "Create ";

      if (this.state.filter) {
        text += '"' + this.state.filter + '"';
      } else {
        text += "New";
      }

      html.push(
        { text }
      );
    }

    return html;
  }

  /**
   * Extends the input content to include the input icon.
   *
   * @method additionalInputContent
   */
  get additionalInputContent() {
    let content = super.additionalInputContent;
    if (this.props.suggest || this.props.freetext) content.shift();
    return content;
  }

  /**
   * Returns the list options in the correct format
   *
   * @method options
   */
  get options() {
    return this.prepareList(this.props.options.toJS());
  }

  /**
   * Uses the mainClasses method provided by the decorator to add additional classes.
   *
   * @method mainClasses
   */
  get mainClasses() {
    return classNames(
      super.mainClasses,
      'carbon-dropdown-filter'
    );
  }

  /**
   * Uses the inputClasses method provided by the decorator to add additional classes.
   *
   * @method inputClasses
   */
  get inputClasses() {
    let filtered = !this.props.create && !this.props.freetext && typeof this.state.filter === 'string';
    return classNames( super.inputClasses, { 'carbon-dropdown__input--filtered': filtered });
  }

  /**
   * Input props for the dropdown, extended from the base dropdown component.
   *
   * @method inputProps
   */
  get inputProps() {
    let props = super.inputProps;

    let value = props.value;

    if (typeof this.state.filter === 'string') {
      // if filter has a value, use that instead
      value = this.state.filter;
    }

    props.readOnly = this.props.readOnly || false;
    props.onChange = this.handleVisibleChange;
    props.value = value;

    return props;
  }

  /**
   * Find and highlights search terms in text
   *
   * @method highlightMatches
   * @param {String} optionText - the text to search
   * @param {String} value - the search term
   */
  highlightMatches = (optionText, value) => {
    if (!value.length) { return optionText; }

    let beginning, end, middle, newValue, parsedOptionText, valIndex;

    parsedOptionText = optionText.toLowerCase();
    valIndex = parsedOptionText.indexOf(value);

    if (valIndex === -1) {
      return optionText;
    }

    beginning = optionText.substr(0, valIndex);
    middle = optionText.substr(valIndex, value.length);
    end = optionText.substr(valIndex + value.length, optionText.length);

    // find end of string recursively
    if (end.indexOf(value) !== -1) {
      end = this.highlightMatches(end, value);
    }

    // build JSX object
    newValue = [
      { beginning },
      { middle },
      { end }
    ];

    return newValue;
  }
}

export default DropdownFilter;
<commit_msg>Addressed style concerns<commit_after>import React from 'react';
import Dropdown from './../dropdown';
import I18n from 'i18n-js';
import classNames from 'classnames';
import escapeStringRegexp from 'escape-string-regexp';

/**
 * A dropdown filter widget.
 *
 * == How to use a dropdown in a component:
 *
 * In your file
 *
 *   import DropdownFilter from 'carbon/lib/components/dropdown-filter';
 *
 * To render a DropdownFilter:
 *
 *   <DropdownFilter name="foo" options={ foo } onChange={ myChangeHandler } />
 *
 * The developer should pass data to the store as JSON. e.g.
 *
 *   foo: [{ id: 1, name: "Foo" }, { id: 2, name: "Bar" }]
 *
 * You can also use the component in 'suggest' mode, which only shows the dropdown
 * once a filter term has been entered.
 *
 * You can also use the component in 'freetext' mode, which behaves like 'suggest',
 * but allows write-in text values in addition to list options.
 *
 * You can also define a function using the 'create' prop, this will allow you
 * to trigger events to create new items.
 *
 * @class DropdownFilter
 * @constructor
 */
class DropdownFilter extends Dropdown {
  constructor(...args) {
    super(...args);

    /**
     * The user input search text.
     *
     * @property filter
     * @type {String}
     * @default null
     */
    this.state.filter = null;

    /**
     * Determines if list is being opened on current render.
     *
     * @property openingList
     * @type {Boolean}
     * @default false
     */
    this.openingList = false;

    // bind scope to functions - allowing them to be overridden and
    // recalled with the use of super
    this.handleVisibleChange = this.handleVisibleChange.bind(this);
  }

  static propTypes = {
    /**
     * The ID value for the component
     *
     * @property value
     * @type {String}
     */
    value: React.PropTypes.oneOfType([
      React.PropTypes.string,
      React.PropTypes.number
    ]),

    /**
     * The options to be displayed in the dropdown. Should be set in the store and passed from the parent component.
     *
     * @property options
     * @type {object}
     */
    options: React.PropTypes.object.isRequired,

    /**
     * Enables create functionality for dropdown.
     *
     * @property create
     * @type {Function}
     */
    create: React.PropTypes.func,

    /**
     * Should the dropdown act and look like a suggestable input instead.
     *
     * @property suggest
     * @type {Boolean}
     */
    suggest: React.PropTypes.bool,

    /**
     * Should the dropdown accept free text as well as suggested options?
     *
     * @property freetext
     * @type {Boolean}
     */
    freetext: React.PropTypes.bool
  }

  /**
   * Lifecycle hook for when the component will update.
   *
   * @method componentWillUpdate
   * @param {Object} nextProps
   * @param {Object} nextState
   */
  componentWillUpdate(nextProps, nextState) {
    // if list is being opened, set boolean
    if (this.state.open != nextState.open) {
      this.openingList = true;
    }
  }

  /**
   * Selects the value for the component
   *
   * @method selectValue
   * @param {String} val
   */
  selectValue(val, visibleVal) {
    let filter = this.props.freetext ? visibleVal : null;
    super.selectValue(val, visibleVal);
    this.setState({ filter: filter });
  }

  /*
   * Handles changes to the visible input field. Updates filter and displayed value.
   *
   * @method handleVisibleChange
   * @param {Object} ev event
   */
  handleVisibleChange(ev) {
    let state = {
      filter: ev.target.value,
      highlighted: null
    };

    if (this.writeable && ev.target.value.length <= 0) {
      state.open = false;
    } else {
      state.open = true;
    }

    this.setState(state);

    this.openingList = false;

    if (this.props.create) {
      // if create is enabled then empty the selected value so the filter persists
      this.emitOnChangeCallback("", ev.target.value);
    }
  }

  /*
   * Handles what happens on blur of the input.
   *
   * @method handleBlur
   */
  handleBlur = () => {
    if (!this.blockBlur) {
      let filter = null;

      if (this.props.create || this.props.freetext) { filter = this.state.filter; }
      this.setState({ open: false, filter: filter });

      if (this.props.freetext) {
        let opt = this.props.options.find((opt) => { return opt.get('name') === this.state.filter; });

        if (opt) {
          this.selectValue(opt.get('id'), opt.get('name'));
        } else {
          this.emitOnChangeCallback('', this.state.filter);
        }
      }
    }
  }

  /**
   * Handles what happens on focus of the input.
   *
   * @method handleFocus
   */
  handleFocus = () => {
    if (!this.writeable && !this.blockFocus) {
      this.setState({ open: true });
    } else {
      this.blockFocus = false;
    }

    this._input.setSelectionRange(0, this._input.value.length);
  }

  /**
   * Handles what happens when create button is clicked.
   *
   * @method handleCreate
   */
  handleCreate = (ev) => {
    this.setState({ open: false });
    this.props.create(ev, this);
  }

  /**
   * Prepares list options by converting to JSON and formatting filtered options.
   *
   * @method prepareList
   * @param {Object} options Immutable map of list options
   */
  prepareList = (options) => {
    if (this.writeable || !this.openingList) && typeof this.state.filter === 'string') {
      let filter = this.state.filter;
      let regex = new RegExp(escapeStringRegexp(filter), 'i');

      // if user has entered a search filter
      options = options.filter((option) => {
        if (option.name.search(regex) > -1) {
          option.name = this.highlightMatches(option.name, filter);
          return option;
        }
      });
    }

    return options;
  }

  /**
   * Function that returns search results. Builds each list item with relevant handlers and classes.
   *
   * @method results
   */
  results(options) {
    let items = super.results(options);

    if (!items.length) {
      items = (

          {
            I18n.t("dropdownlist.no_results", {
              defaultValue: "No results match \"%{term}\"",
              term: this.state.filter
            })
          }

      );
    }

    return items;
  }

  /**
   * Return the list item which should be highlighted by default.
   *
   * @method highlighted
   */
  highlighted = (options) => {
    let highlighted = null;

    if (this.state.highlighted) {
      highlighted = this.state.highlighted;
    } else {
      if (!this.state.filter && this.props.value) {
        highlighted = this.props.value;
      } else if (this.state.filter && options.length) {
        highlighted = options[0].id;
      }
    }

    return highlighted;
  }

  /**
   * Getter to return HTML for list to render method.
   *
   * @method listHTML
   */
  get listHTML() {
    let original = super.listHTML,
        html = [original];

    if (this.state.open && this.props.create) {
      let text = "Create ";

      if (this.state.filter) {
        text += '"' + this.state.filter + '"';
      } else {
        text += "New";
      }

      html.push(
        { text }
      );
    }

    return html;
  }

  /**
   * Extends the input content to include the input icon.
   *
   * @method additionalInputContent
   */
  get additionalInputContent() {
    let content = super.additionalInputContent;
    if (this.writeable) { content.shift(); }
    return content;
  }

  /**
   * Returns the list options in the correct format
   *
   * @method options
   */
  get options() {
    return this.prepareList(this.props.options.toJS());
  }

  /**
   * Uses the mainClasses method provided by the decorator to add additional classes.
   *
   * @method mainClasses
   */
  get mainClasses() {
    return classNames(
      super.mainClasses,
      'carbon-dropdown-filter'
    );
  }

  /**
   * Uses the inputClasses method provided by the decorator to add additional classes.
   *
   * @method inputClasses
   */
  get inputClasses() {
    let filtered = !this.props.create && !this.props.freetext && typeof this.state.filter === 'string';
    return classNames( super.inputClasses, { 'carbon-dropdown__input--filtered': filtered });
  }

  /**
   * Input props for the dropdown, extended from the base dropdown component.
   *
   * @method inputProps
   */
  get inputProps() {
    let props = super.inputProps;

    let value = props.value;

    if (typeof this.state.filter === 'string') {
      // if filter has a value, use that instead
      value = this.state.filter;
    }

    props.readOnly = this.props.readOnly || false;
    props.onChange = this.handleVisibleChange;
    props.value = value;

    return props;
  }

  /**
   * Find and highlights search terms in text
   *
   * @method highlightMatches
   * @param {String} optionText - the text to search
   * @param {String} value - the search term
   */
  highlightMatches = (optionText, value) => {
    if (!value.length) { return optionText; }

    let beginning, end, middle, newValue, parsedOptionText, valIndex;

    parsedOptionText = optionText.toLowerCase();
    valIndex = parsedOptionText.indexOf(value);

    if (valIndex === -1) {
      return optionText;
    }

    beginning = optionText.substr(0, valIndex);
    middle = optionText.substr(valIndex, value.length);
    end = optionText.substr(valIndex + value.length, optionText.length);

    // find end of string recursively
    if (end.indexOf(value) !== -1) {
      end = this.highlightMatches(end, value);
    }

    // build JSX object
    newValue = [
      { beginning },
      { middle },
      { end }
    ];

    return newValue;
  }

  /**
   * Returns whether input is writeable (for suggest or freetext modes)
   *
   * @method  writeable
   * @return  {Boolean}
   */
  get writeable() {
    return this.props.suggest || this.props.freetext;
  }
}

export default DropdownFilter;
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import _ from 'lodash';
import { browserHistory } from 'react-router';
import moment from 'moment';

import API from '../../../lib/API';
import capitalize from '../../../lib/capitalize';
import { ConfirmModal, InformationModal } from '../../general/modals';
import Member from '../../../lib/models/Member';
import MemberForm from './MemberForm';
import memberFormSchema from './memberFormSchema';

const formatData = (member) => {
  const data = member;

  data.firstName = capitalize(data.firstName);
  data.lastName = capitalize(data.lastName);

  if (data.noNo) {
    delete data.noNo;
    delete data.no;
  } else if (data.no.length === 7) {
    data.no = `${+data.no.substr(0, 2) <= +moment().format('YY') ? '20' : '19'}${data.no}`;
  }

  if (data.city.name) {
    data.city.name = capitalize(data.city.name);
    data.city.state.code = data.city.state.code || 'QC';
  } else {
    delete data.city;
  }

  if (data.zip) {
    data.zip = data.zip.replace(/\s/g, '').toUpperCase();
  }

  if (data.address) {
    data.address = capitalize(data.address);
  }

  data.phone = data.phone.filter(phone => phone.number);

  return data;
};

export default class MemberFormContainer extends Component {
  constructor(props) {
    super(props);
    this.state = {
      email: '',
      error: null,
      isAdmin: JSON.parse(sessionStorage.getItem('user')).isAdmin,
      member: new Member(),
      no: props.params && props.params.no,
      redirectTo: null,
      showModal: null,
      states: [],
    };

    this.schema = _.cloneDeep(memberFormSchema);
    this.schema.title = !props.params.no ? 'Ajouter un membre' : 'Modifier un membre';
    this.handleNo();
  }

  componentWillMount() {
    API.state.select((error, res) => {
      if (error) {
        this.setState({ error });
        return;
      }

      const stateSelect = this.schema.sections[1].fields.find(field => field.key === 'state');
      stateSelect.options = res.map(state => ({ value: state, label: state }));

      this.setState({ states: res });
    });

    if (this.state.no) {
      API.member.select(this.state.no, (error, res) => {
        if (error) {
          this.setState({ error });
          return;
        }

        this.setState({ member: new Member(res), email: res.email });
      });
    } else if (this.props.location.query.no) {
      const { no } = this.props.location.query;
      this.setState({ member: new Member({ no }) });
    }
  }

  closeModal = () => this.setState({ error: null, isUpdate: null, showModal: null })

  cancel = (event) => {
    event.preventDefault();
    const { no } = this.state;
    browserHistory.push(no ? `/member/view/${no}` : '/search');
  }

  exists = async (no, data) => {
    const distinct = {
      no: `${no}` !== data.no ? data.no : undefined,
      email: this.state.email !== data.email ? data.email : undefined,
    };

    return new Promise((resolve, reject) => {
      if (distinct.no || distinct.email) {
        API.member.exists(distinct, (error, res) => {
          if (error) {
            return reject(error);
          }

          return resolve(res.no);
        });
      }

      return resolve();
    });
  }

  insert = (data) => {
    API.member.insert(data, (error, res) => {
      if (error) {
        this.setState({ error });
        return;
      }

      this.goToMember(res.no || data.no);
    });
  }

  handleMerge = () => {
    const duplicate = this.state.no;
    const no = this.state.redirectTo;

    API.member.merge({ duplicate, no }, (error) => {
      if (error) {
        this.setState({ error });
        return;
      }

      this.setState({ showModal: 'merged' });
    });
  }

  handleNo = () => {
    const { member, no } = this.state;
    const isEdit = !!no;
    const inlineNo = this.schema.sections[0].fields.find(field => field.key === 'no');

    if (isEdit) {
      inlineNo.inline = inlineNo.inline.filter(field => field.key === 'no');
    } else {
      const noInput = inlineNo.inline.find(field => field.key === 'no');

      inlineNo.inline.find(field => field.key === 'noNo').onChange = (event) => {
        member.no = null;
        noInput.disabled = event.target.checked;

        this.setState({ member });
      };
    }
  }

  save = async (member) => {
    const no = this.props.params && this.props.params.no;
    const data = formatData({ ...member });
    const existingUser = await this.exists(no, data);

    if (existingUser) {
      return this.setState({ isUpdate: !!no, showModal: 'exists', redirectTo: existingUser });
    }

    if (data.zip) {
      data.zip = data.zip.replace(/\s/g, '').toUpperCase();
    }

    return no ? this.update(no, data) : this.insert(data);
  }

  update = (no, data) => {
    API.member.update(no, data, (error) => {
      if (error) {
        this.setState({ error });
        return;
      }

      this.goToMember(data.no);
    });
  }

  goToMember = (no) => browserHistory.push(`/member/view/${no}`)

  handleGoToMember = () => this.goToMember(this.state.redirectTo)

  getModal = () => {
    const { error, isAdmin, isUpdate, showModal } = this.state;

    if (error) {
      return (
        <InformationModal
          message={error.message}
          onClick={this.closeModal}
          title={`Erreur ${error.code}`}
        />
      );
    }

    switch (showModal) {
      case 'exists':
        if (!isUpdate) {
          return (
            <ConfirmModal
              message={'Un membre avec les informations saisies existent déjà. Voulez-vous aller à sa fiche ?'}
              onCancel={this.closeModal}
              onConfirm={this.handleGoToMember}
              title={'Erreur = Membre existant'}
            />
          );
        }

        if (isAdmin) {
          // eslint-disable-next-line max-len
          const message = 'Les informations que vous avez inscrites correspondent à un compte déjà existant. Voulez-vous fusionner ce compte avec le compte correspondant ?';

          return (
            <ConfirmModal
              customActions={[
                {
                  label: 'Annuler',
                  onClick: this.closeModal,
                },
                {
                  bsStyle: 'danger',
                  label: 'Fusionner',
                  onClick: this.handleMerge,
                },
              ]}
              message={message}
              title={'Erreur - Compte existant'}
            />
          );
        }

        return (
          <InformationModal
            message={'Les informations que vous avez inscrites correspondent à un compte déjà existant.'}
            onClick={this.closeModal}
            title={'Compte déjà existant'}
          />
        );
      case 'merged':
        return (
          <InformationModal
            message={'Les comptes ont été fusionnés, vous serez redirigé au compte du membre.'}
            onClick={this.handleGoToMember}
            title={'Comptes fussionés'}
          />
        );
      default:
        return null;
    }
  }

  render() {
    const member = this.state.member;
    delete member.account;

    return (
      <MemberForm
        member={member}
        modal={this.getModal()}
        onCancel={this.cancel}
        onSave={this.save}
        schema={this.schema}
      />
    );
  }
}

MemberFormContainer.propTypes = {
  params: React.PropTypes.shape(),
  location: React.PropTypes.shape(),
  router: React.PropTypes.shape(),
};
<commit_msg>Fix race condition (#113)<commit_after>import React, { Component } from 'react';
import _ from 'lodash';
import { browserHistory } from 'react-router';
import moment from 'moment';

import API from '../../../lib/API';
import capitalize from '../../../lib/capitalize';
import { ConfirmModal, InformationModal } from '../../general/modals';
import Member from '../../../lib/models/Member';
import MemberForm from './MemberForm';
import memberFormSchema from './memberFormSchema';

const formatData = (member) => {
  const data = member;

  data.firstName = capitalize(data.firstName);
  data.lastName = capitalize(data.lastName);

  if (data.noNo) {
    delete data.noNo;
    delete data.no;
  } else if (data.no.length === 7) {
    data.no = `${+data.no.substr(0, 2) <= +moment().format('YY') ? '20' : '19'}${data.no}`;
  }

  if (data.city.name) {
    data.city.name = capitalize(data.city.name);
    data.city.state.code = data.city.state.code || 'QC';
  } else {
    delete data.city;
  }

  if (data.zip) {
    data.zip = data.zip.replace(/\s/g, '').toUpperCase();
  }

  if (data.address) {
    data.address = capitalize(data.address);
  }

  data.phone = data.phone.filter(phone => phone.number);

  return data;
};

export default class MemberFormContainer extends Component {
  constructor(props) {
    super(props);
    this.state = {
      email: '',
      error: null,
      isAdmin: JSON.parse(sessionStorage.getItem('user')).isAdmin,
      member: new Member(),
      no: props.params && props.params.no,
      redirectTo: null,
      showModal: null,
      states: [],
    };

    this.schema = _.cloneDeep(memberFormSchema);
    this.schema.title = !props.params.no ? 'Ajouter un membre' : 'Modifier un membre';
    this.handleNo();
  }

  componentWillMount() {
    API.state.select((error, res) => {
      if (error) {
        this.setState({ error });
        return;
      }

      const stateSelect = this.schema.sections[1].fields.find(field => field.key === 'state');
      stateSelect.options = res.map(state => ({ value: state, label: state }));

      this.setState({ states: res });
    });

    if (this.state.no) {
      API.member.select(this.state.no, (error, res) => {
        if (error) {
          this.setState({ error });
          return;
        }

        this.setState({ member: new Member(res), email: res.email });
      });
    } else if (this.props.location.query.no) {
      const { no } = this.props.location.query;
      this.setState({ member: new Member({ no }) });
    }
  }

  closeModal = () => this.setState({ error: null, isUpdate: null, showModal: null })

  cancel = (event) => {
    event.preventDefault();
    const { no } = this.state;
    browserHistory.push(no ? `/member/view/${no}` : '/search');
  }

  exists = async (no, data) => {
    const distinct = {
      no: `${no}` !== data.no ? data.no : undefined,
      email: this.state.email !== data.email ? data.email : undefined,
    };

    if (!distinct.no && !distinct.email) {
      return false;
    }

    return new Promise((resolve, reject) => {
      API.member.exists(distinct, (error, res) => {
        if (error) {
          return reject(error);
        }

        return resolve(res.no);
      });
    });
  }

  insert = (data) => {
    API.member.insert(data, (error, res) => {
      if (error) {
        this.setState({ error });
        return;
      }

      this.goToMember(res.no || data.no);
    });
  }

  handleMerge = () => {
    const duplicate = this.state.no;
    const no = this.state.redirectTo;

    API.member.merge({ duplicate, no }, (error) => {
      if (error) {
        this.setState({ error });
        return;
      }

      this.setState({ showModal: 'merged' });
    });
  }

  handleNo = () => {
    const { member, no } = this.state;
    const isEdit = !!no;
    const inlineNo = this.schema.sections[0].fields.find(field => field.key === 'no');

    if (isEdit) {
      inlineNo.inline = inlineNo.inline.filter(field => field.key === 'no');
    } else {
      const noInput = inlineNo.inline.find(field => field.key === 'no');

      inlineNo.inline.find(field => field.key === 'noNo').onChange = (event) => {
        member.no = null;
        noInput.disabled = event.target.checked;

        this.setState({ member });
      };
    }
  }

  save = async (member) => {
    const no = this.props.params && this.props.params.no;
    const data = formatData({ ...member });
    const existingUser = await this.exists(no, data);

    if (existingUser) {
      return this.setState({ isUpdate: !!no, showModal: 'exists', redirectTo: existingUser });
    }

    if (data.zip) {
      data.zip = data.zip.replace(/\s/g, '').toUpperCase();
    }

    return no ? this.update(no, data) : this.insert(data);
  }

  update = (no, data) => {
    API.member.update(no, data, (error) => {
      if (error) {
        this.setState({ error });
        return;
      }

      this.goToMember(data.no);
    });
  }

  goToMember = (no) => browserHistory.push(`/member/view/${no}`)

  handleGoToMember = () => this.goToMember(this.state.redirectTo)

  getModal = () => {
    const { error, isAdmin, isUpdate, showModal } = this.state;

    if (error) {
      return (
        <InformationModal
          message={error.message}
          onClick={this.closeModal}
          title={`Erreur ${error.code}`}
        />
      );
    }

    switch (showModal) {
      case 'exists':
        if (!isUpdate) {
          return (
            <ConfirmModal
              message={'Un membre avec les informations saisies existent déjà. Voulez-vous aller à sa fiche ?'}
              onCancel={this.closeModal}
              onConfirm={this.handleGoToMember}
              title={'Erreur = Membre existant'}
            />
          );
        }

        if (isAdmin) {
          // eslint-disable-next-line max-len
          const message = 'Les informations que vous avez inscrites correspondent à un compte déjà existant. Voulez-vous fusionner ce compte avec le compte correspondant ?';

          return (
            <ConfirmModal
              customActions={[
                {
                  label: 'Annuler',
                  onClick: this.closeModal,
                },
                {
                  bsStyle: 'danger',
                  label: 'Fusionner',
                  onClick: this.handleMerge,
                },
              ]}
              message={message}
              title={'Erreur - Compte existant'}
            />
          );
        }

        return (
          <InformationModal
            message={'Les informations que vous avez inscrites correspondent à un compte déjà existant.'}
            onClick={this.closeModal}
            title={'Compte déjà existant'}
          />
        );
      case 'merged':
        return (
          <InformationModal
            message={'Les comptes ont été fusionnés, vous serez redirigé au compte du membre.'}
            onClick={this.handleGoToMember}
            title={'Comptes fussionés'}
          />
        );
      default:
        return null;
    }
  }

  render() {
    const member = this.state.member;
    delete member.account;

    return (
      <MemberForm
        member={member}
        modal={this.getModal()}
        onCancel={this.cancel}
        onSave={this.save}
        schema={this.schema}
      />
    );
  }
}

MemberFormContainer.propTypes = {
  params: React.PropTypes.shape(),
  location: React.PropTypes.shape(),
  router: React.PropTypes.shape(),
};
<|endoftext|>


<commit_before>/**
* Copyright 2012-2016, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var d3 = require('d3');

var Lib = require('../../lib');

module.exports = function getUpdateObject(axisLayout, buttonLayout) {
    var axName = axisLayout._name;
    var update = {};

    if(buttonLayout.step === 'all') {
        update[axName + '.autorange'] = true;
    }
    else {
        var xrange = getXRange(axisLayout, buttonLayout);

        update[axName + '.range[0]'] = xrange[0];
        update[axName + '.range[1]'] = xrange[1];
    }

    return update;
};

function getXRange(axisLayout, buttonLayout) {
    var currentRange = axisLayout.range;
    var base = new Date(Lib.dateTime2ms(currentRange[1]));

    var step = buttonLayout.step,
        count = buttonLayout.count;

    var range0;

    switch(buttonLayout.stepmode) {
        case 'backward':
            range0 = Lib.ms2DateTime(+d3.time[step].utc.offset(base, -count));
            break;

        case 'todate':
            var base2 = d3.time[step].utc.offset(base, -count);

            range0 = Lib.ms2DateTime(+d3.time[step].utc.ceil(base2));
            break;
    }

    var range1 = currentRange[1];

    return [range0, range1];
}
<commit_msg>partial support for range selectors on world calendars<commit_after>/**
* Copyright 2012-2016, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var d3 = require('d3');

module.exports = function getUpdateObject(axisLayout, buttonLayout) {
    var axName = axisLayout._name;
    var update = {};

    if(buttonLayout.step === 'all') {
        update[axName + '.autorange'] = true;
    }
    else {
        var xrange = getXRange(axisLayout, buttonLayout);

        update[axName + '.range[0]'] = xrange[0];
        update[axName + '.range[1]'] = xrange[1];
    }

    return update;
};

function getXRange(axisLayout, buttonLayout) {
    var currentRange = axisLayout.range;
    var base = new Date(axisLayout.r2l(currentRange[1]));

    var step = buttonLayout.step,
        count = buttonLayout.count;

    var range0;

    switch(buttonLayout.stepmode) {
        case 'backward':
            range0 = axisLayout.l2r(+d3.time[step].utc.offset(base, -count));
            break;

        case 'todate':
            var base2 = d3.time[step].utc.offset(base, -count);

            range0 = axisLayout.l2r(+d3.time[step].utc.ceil(base2));
            break;
    }

    var range1 = currentRange[1];

    return [range0, range1];
}
<|endoftext|>


<commit_before>import { rgb } from "d3-color";
import { interpolateRgb } from "d3-interpolate";
import { updateVisibleTipsAndBranchThicknesses} from "../../../actions/tree";
import { mediumTransitionDuration } from "../../../util/globals";
import { branchOpacityFunction } from "../../../util/colorHelpers";

/* Callbacks used by the tips / branches when hovered / selected */

export const onTipHover = function onTipHover(d) {
  const phylotree = d.that.params.orientation[0] === 1 ?
    this.state.tree :
    this.state.treeToo;
  phylotree.svg.select("#tip_" + d.n.clade)
    .attr("r", (e) => e["r"] + 4);
  this.setState({
    hovered: {d, type: ".tip"}
  });
};

export const onTipClick = function onTipClick(d) {
  // console.log("tip click", d)
  this.setState({
    hovered: null,
    selectedTip: d
  });
  /* are we clicking from tree1 or tree2? */
  const tipSelected = d.that.params.orientation[0] === 1 ?
    {treeIdx: d.n.arrayIdx} :
    {treeTooIdx: d.n.arrayIdx};
  this.props.dispatch(updateVisibleTipsAndBranchThicknesses({tipSelected}));
};

export const onBranchHover = function onBranchHover(d) {
  /* emphasize the color of the branch */
  for (const id of ["#branch_S_" + d.n.clade, "#branch_T_" + d.n.clade]) {
    if (this.props.colorByConfidence) {
      this.state.tree.svg.select(id)
        .style("stroke", (el) => { // eslint-disable-line no-loop-func
          const ramp = branchOpacityFunction(this.props.tree.nodes[el.n.arrayIdx].attr[this.props.colorBy + "_entropy"]);
          const raw = this.props.tree.nodeColors[el.n.arrayIdx];
          const base = el.branchStroke;
          return rgb(interpolateRgb(raw, base)(ramp)).toString();
        });
    } else {
      this.state.tree.svg.select(id)
        .style("stroke", (el) => this.props.tree.nodeColors[el.n.arrayIdx]);
    }
  }
  if (this.props.temporalConfidence.exists && this.props.temporalConfidence.display && !this.props.temporalConfidence.on) {
    const tree = d.that.params.orientation[0] === 1 ? this.state.tree : this.state.treeToo;
    if (!("confidenceIntervals" in tree.groups)) {
      tree.groups.confidenceIntervals = tree.svg.append("g").attr("id", "confidenceIntervals");
    }
    tree.groups.confidenceIntervals
      .selectAll(".conf")
      .data([d])
      .enter()
        .call((sel) => this.state.tree.drawSingleCI(sel, 0.5));
  }
  this.setState({
    hovered: {d, type: ".branch"}
  });
};

export const onBranchClick = function onBranchClick(d) {
  const root = [undefined, undefined];
  if (d.that.params.orientation[0] === 1) root[0] = d.n.arrayIdx;
  else root[1] = d.n.arrayIdx;
  this.props.dispatch(updateVisibleTipsAndBranchThicknesses({root}));
};

/* onBranchLeave called when mouse-off, i.e. anti-hover */
export const onBranchLeave = function onBranchLeave(d) {
  for (const id of ["#branch_T_" + d.n.clade, "#branch_S_" + d.n.clade]) {
    this.state.tree.svg.select(id)
      .style("stroke", (el) => el.branchStroke);
  }
  if (this.props.temporalConfidence.exists && this.props.temporalConfidence.display && !this.props.temporalConfidence.on) {
    const tree = d.that.params.orientation[0] === 1 ? this.state.tree : this.state.treeToo;
    tree.removeConfidence(mediumTransitionDuration);
  }
  if (this.state.hovered) {
    this.setState({hovered: null});
  }
};

export const onTipLeave = function onTipLeave(d) {
  const phylotree = d.that.params.orientation[0] === 1 ?
    this.state.tree :
    this.state.treeToo;
  if (!this.state.selectedTip) {
    phylotree.svg.select("#tip_" + d.n.clade)
      .attr("r", (dd) => dd["r"]);
  }
  if (this.state.hovered) {
    this.setState({hovered: null});
  }
};

/* clearSelectedTip when clicking to go away */
export const clearSelectedTip = function clearSelectedTip(d) {
  const phylotree = d.that.params.orientation[0] === 1 ?
    this.state.tree :
    this.state.treeToo;
  phylotree.svg.select("#tip_" + d.n.clade)
    .attr("r", (dd) => dd["r"]);
  this.setState({selectedTip: null, hovered: null});
  /* restore the tip visibility! */
  this.props.dispatch(updateVisibleTipsAndBranchThicknesses(
    {tipSelected: {clear: true}}
  ));
};

/**
 * @param  {node} d tree node object
 * @param  {int} n total number of nodes in current view
 * @return {int} font size of the tip label
 */
export const tipLabelSize = (d, n) => {
  if (n > 70) {
    return 0;
  } else if (n < 20) {
    return 14;
  }
  const fs = 6 + 8 * (70 - n) / (70 - 20);
  return fs;
};
<commit_msg>immediately remove CIs onBranchLeave to prevent d3 transition bugs<commit_after>import { rgb } from "d3-color";
import { interpolateRgb } from "d3-interpolate";
import { updateVisibleTipsAndBranchThicknesses} from "../../../actions/tree";
import { mediumTransitionDuration } from "../../../util/globals";
import { branchOpacityFunction } from "../../../util/colorHelpers";

/* Callbacks used by the tips / branches when hovered / selected */

export const onTipHover = function onTipHover(d) {
  const phylotree = d.that.params.orientation[0] === 1 ?
    this.state.tree :
    this.state.treeToo;
  phylotree.svg.select("#tip_" + d.n.clade)
    .attr("r", (e) => e["r"] + 4);
  this.setState({
    hovered: {d, type: ".tip"}
  });
};

export const onTipClick = function onTipClick(d) {
  // console.log("tip click", d)
  this.setState({
    hovered: null,
    selectedTip: d
  });
  /* are we clicking from tree1 or tree2? */
  const tipSelected = d.that.params.orientation[0] === 1 ?
    {treeIdx: d.n.arrayIdx} :
    {treeTooIdx: d.n.arrayIdx};
  this.props.dispatch(updateVisibleTipsAndBranchThicknesses({tipSelected}));
};

export const onBranchHover = function onBranchHover(d) {
  /* emphasize the color of the branch */
  for (const id of ["#branch_S_" + d.n.clade, "#branch_T_" + d.n.clade]) {
    if (this.props.colorByConfidence) {
      this.state.tree.svg.select(id)
        .style("stroke", (el) => { // eslint-disable-line no-loop-func
          const ramp = branchOpacityFunction(this.props.tree.nodes[el.n.arrayIdx].attr[this.props.colorBy + "_entropy"]);
          const raw = this.props.tree.nodeColors[el.n.arrayIdx];
          const base = el.branchStroke;
          return rgb(interpolateRgb(raw, base)(ramp)).toString();
        });
    } else {
      this.state.tree.svg.select(id)
        .style("stroke", (el) => this.props.tree.nodeColors[el.n.arrayIdx]);
    }
  }
  if (this.props.temporalConfidence.exists && this.props.temporalConfidence.display && !this.props.temporalConfidence.on) {
    const tree = d.that.params.orientation[0] === 1 ? this.state.tree : this.state.treeToo;
    if (!("confidenceIntervals" in tree.groups)) {
      tree.groups.confidenceIntervals = tree.svg.append("g").attr("id", "confidenceIntervals");
    }
    tree.groups.confidenceIntervals
      .selectAll(".conf")
      .data([d])
      .enter()
        .call((sel) => tree.drawSingleCI(sel, 0.5));
  }
  this.setState({
    hovered: {d, type: ".branch"}
  });
};

export const onBranchClick = function onBranchClick(d) {
  const root = [undefined, undefined];
  if (d.that.params.orientation[0] === 1) root[0] = d.n.arrayIdx;
  else root[1] = d.n.arrayIdx;
  this.props.dispatch(updateVisibleTipsAndBranchThicknesses({root}));
};

/* onBranchLeave called when mouse-off, i.e. anti-hover */
export const onBranchLeave = function onBranchLeave(d) {
  for (const id of ["#branch_T_" + d.n.clade, "#branch_S_" + d.n.clade]) {
    this.state.tree.svg.select(id)
      .style("stroke", (el) => el.branchStroke);
  }
  if (this.props.temporalConfidence.exists && this.props.temporalConfidence.display && !this.props.temporalConfidence.on) {
    const tree = d.that.params.orientation[0] === 1 ? this.state.tree : this.state.treeToo;
    tree.removeConfidence();
  }
  if (this.state.hovered) {
    this.setState({hovered: null});
  }
};

export const onTipLeave = function onTipLeave(d) {
  const phylotree = d.that.params.orientation[0] === 1 ?
    this.state.tree :
    this.state.treeToo;
  if (!this.state.selectedTip) {
    phylotree.svg.select("#tip_" + d.n.clade)
      .attr("r", (dd) => dd["r"]);
  }
  if (this.state.hovered) {
    this.setState({hovered: null});
  }
};

/* clearSelectedTip when clicking to go away */
export const clearSelectedTip = function clearSelectedTip(d) {
  const phylotree = d.that.params.orientation[0] === 1 ?
    this.state.tree :
    this.state.treeToo;
  phylotree.svg.select("#tip_" + d.n.clade)
    .attr("r", (dd) => dd["r"]);
  this.setState({selectedTip: null, hovered: null});
  /* restore the tip visibility! */
  this.props.dispatch(updateVisibleTipsAndBranchThicknesses(
    {tipSelected: {clear: true}}
  ));
};

/**
 * @param  {node} d tree node object
 * @param  {int} n total number of nodes in current view
 * @return {int} font size of the tip label
 */
export const tipLabelSize = (d, n) => {
  if (n > 70) {
    return 0;
  } else if (n < 20) {
    return 14;
  }
  const fs = 6 + 8 * (70 - n) / (70 - 20);
  return fs;
};
<|endoftext|>


<commit_before>/**
 * conjoon
 * (c) 2002-2009 siteartwork.de/conjoon.org
 * licensing@conjoon.org
 *
 * $Author$
 * $Id$
 * $Date$
 * $Revision$
 * $LastChangedDate$
 * $LastChangedBy$
 * $URL$
 */

Ext.namespace('com.conjoon.groupware.feeds');

com.conjoon.groupware.feeds.FeedGrid = Ext.extend(Ext.grid.GridPanel, {

    clkRow          : null,
    clkRecord       : null,
    cellClickActive : false,

    initComponent : function()
    {
        this.store = com.conjoon.groupware.feeds.FeedStore.getInstance();

        this.store.setDefaultSort('pubDate', "DESC");

        this.columns = [{
            id:'name',
            header: com.conjoon.Gettext.gettext("Feed"),
            hidden:true,
            width: 120,
            sortable: true,
            dataIndex: 'name'
          },{
            id:'title',
            header: com.conjoon.Gettext.gettext("Title"),
            width: 220,
            sortable: true,
            dataIndex: 'title',
            renderer : function(value, metadata, record){
                metadata.attr = 'qtip="'+value.replace(/"/g, '"')+'"';
                return value;
            }
          },{
            header: com.conjoon.Gettext.gettext("Description"),
            hidden:true,
            width: 180,
            sortable: true,
            dataIndex: 'description'
          },{
            header: com.conjoon.Gettext.gettext("Date"),
            width: 120,
            hidden:true,
            sortable: true,
            renderer: Ext.util.Format.dateRenderer('d.m.Y H:i'),
            dataIndex: 'pubDate'
          }
        ];

        var groupTextTpl  = '{text} ({[values.rs.length]}/{[function(){var b = 0;for (var i = 0, rs = values.rs, max_i = rs.length; i < max_i; i++) {if (!rs[i].data.isRead) {b++;}}return b;}()]})';
        this.groupTextTpl = new Ext.XTemplate(groupTextTpl);
        this.groupTextTpl.compile();

        this.view = new Ext.grid.GroupingView({
            forceFit      : false,
            showGroupName : false,
            groupTextTpl  : groupTextTpl,
            getRowClass   : this.applyRowClass
        });

        var displayOptionsMenu = new Ext.menu.Menu({
            items: [{
                id : 'groupFeeds',
                text: com.conjoon.Gettext.gettext("group after feeds"),
                checked: true,
                //group: 'com.conjoon.groupware.FeedGrid.display',
                checkHandler: this.toggleGroupView,
                scope: this
              },
              "-",{
                iconCls : 'com-conjoon-groupware-feeds-FeedGrid-optionsMenu-configureItem-icon',
                text    : com.conjoon.Gettext.gettext("Settings..."),
                scope   : this,
                handler : function() {
                    var optDialog = new com.conjoon.groupware.feeds.FeedOptionsDialog();
                    optDialog.show();
                }

              }]
        });

        this.tbar = new Ext.Toolbar([{
            iconCls : 'com-conjoon-groupware-feeds-FeedGrid-toolbar-addFeedButton-icon',
            handler : function(){
                var dialog = new com.conjoon.groupware.feeds.AddFeedDialog({
                    animateTarget : this.getTopToolbar().items.get(0).el.dom.id
                });
                dialog.show();
            },
            scope : this
          },{
            iconCls : 'com-conjoon-groupware-feeds-FeedGrid-toolbar-refreshFeedsButton-icon',
            handler : function(){
                this.clkRow    = null;
                this.clkRecord = null;
                com.conjoon.groupware.feeds.AccountStore.getInstance().reload();
                this.store.reload();},
            scope: this
          },{
            iconCls : 'com-conjoon-groupware-feeds-FeedGrid-toolbar-configureItem-icon',
            handler : function() {
                var optDialog = new com.conjoon.groupware.feeds.FeedOptionsDialog();
                optDialog.show();
            }
          }
        ]);

        Ext.apply(this, {
            loadMask    : {msg: com.conjoon.Gettext.gettext("Loading feeds...")},
            autoScroll  : true,
            style       : 'cursor:default',
            title       : com.conjoon.Gettext.gettext("Feeds"),
            iconCls     : 'com-conjoon-groupware-feeds-Icon',
            hideBorders : true,
            plugins     : [
                new Ext.ux.grid.GridViewMenuPlugin()
            ]
        });

        com.conjoon.groupware.feeds.FeedGrid.superclass.initComponent.call(this);
    },

    initEvents : function()
    {
        Ext.ux.util.MessageBus.subscribe(
           'com.conjoon.groupware.feeds.FeedViewBaton.onFeedLoadSuccess',
           this.onFeedItemLoaded,
           this
        );

        Ext.ux.util.MessageBus.subscribe(
           'com.conjoon.groupware.feeds.FeedPreview.onLoadSuccess',
           this.onFeedItemLoaded,
           this
        );

        // install listeners
        this.on('cellclick',    this.onCellClick, this, {buffer : 200});
        this.on('celldblclick', this.onCellDblClick, this);

        var preview = com.conjoon.groupware.feeds.FeedPreview;

        this.on('resize',         preview.hide.createDelegate(preview, [true]));
        this.on('beforecollapse', preview.hide.createDelegate(preview, [true, false]));
        this.on('contextmenu',    preview.hide.createDelegate(preview, [false]));

        this.on('contextmenu', this.onContextClick, this);
        this.on('rowcontextmenu', this.onRowContextClick, this);

        com.conjoon.groupware.feeds.FeedGrid.superclass.initEvents.call(this);
    },

    // within this function "this" is actually the GridView
    applyRowClass: function(record, rowIndex, p, ds)
    {
        if (record.data.isRead) {
            return 'com-conjoon-groupware-feeds-FeedGrid-itemRead';
        } else {
            return 'com-conjoon-groupware-feeds-FeedGrid-itemUnread';
        }
    },

    // private
    commitRecords : function()
    {
        var m = this.store.getModifiedRecords();

        var l = m.length;

        if (l == 0) {
            return;
        }

        var requestArray = new Array();

        for (var i = 0; i < l; i++) {
            requestArray.push({
                'id'     : m[i].id,
                'isRead' : m[i].get('isRead')
            });
        }

        Ext.Ajax.request({
            url: './groupware/feeds/set.item.read/format/json',
            params: {
                json: Ext.encode(requestArray)
            }
        });

        this.store.commitChanges();
        this._updateGroupTemplates();
    },

    onFeedItemLoaded : function(subject, message)
    {
        if (message.id) {
            this.markItemsRead(true, message.id)
        }
    },

    _updateGroupTemplates : function()
    {
        if (!this.store.groupField) {
            return;
        }

        var view = this.view;
        var rs   = this.store.getRange();
        var len  = rs.length;
        if (len < 1) {
            return [];
        }

        var groupField = view.getGroupField();

        var r;
        var gvalue;
        var indexes = {};
        var groups = [];

        for (var i = 0; i < len; i++) {
            r      = rs[i];
            gvalue = r.data[groupField];

            if (!indexes[gvalue]) {
                indexes[gvalue] = [];
            }

            indexes[gvalue].push(r);
        }

        var a  = 0;
        var gr = view.getGroups();
        for (var i in indexes) {
            gr[a++].firstChild.firstChild.innerHTML = this.groupTextTpl.apply({
                text : i,
                rs : indexes[i]
            });
        }
    },

    markItemsRead : function(bRead, id)
    {
        var feedIds = new Array();

        if (!id) {
            var selection = this.selModel.getSelections();
            for (var i = 0; i < selection.length; i++) {
                selection[i].set('isRead', bRead);
            }
        } else {
            var rec = this.store.getById(id);
            if (rec) {
                rec.set('isRead', bRead);
            }
        }

        this.commitRecords();
    },

    onCellClick : function(grid, rowIndex, columnIndex, e)
    {
        if (this.cellClickActive) {
            this.cellClickActive = false;
            return;
        }

        if (e.shiftKey || e.ctrlKey) {
            return;
        }

        this.clkRow    = this.view.getRow(rowIndex);
        this.clkRecord = this.store.getAt(rowIndex);
        com.conjoon.groupware.feeds.FeedPreview.show(grid, rowIndex, columnIndex, e);
    },

    onCellDblClick : function(grid, rowIndex, columnIndex, eventObject)
    {
        this.cellClickActive = true;
        var feedItem = grid.getStore().getAt(rowIndex);
        com.conjoon.groupware.feeds.FeedPreview.hide(true, false);
        com.conjoon.groupware.feeds.FeedViewBaton.showFeed(feedItem, true);
    },

    // private
    /**
     *
     * @access private
     */
    createContextMenu : function()
    {
        if(!this.menu){ // create context menu on first right click
            this.menu = new Ext.menu.Menu({
                items: [{
                    text: com.conjoon.Gettext.gettext("Refresh"),
                    scope:this,
                    handler: function(){
                        this.clkRow    = null;
                        this.clkRecord = null;
                        com.conjoon.groupware.feeds.AccountStore.getInstance().reload();
                        this.store.reload();}
                  },
                 '-',{
                    text: com.conjoon.Gettext.gettext("mark as read"),
                    scope:this,
                    handler: function(){
                        this.markItemsRead(true);}
                  },{
                    text: com.conjoon.Gettext.gettext("mark as unread"),
                    scope:this,
                    handler: function(){
                        this.markItemsRead(false);}
                  },
                  '-',{
                    text : com.conjoon.Gettext.gettext("Settings..."),
                    scope:this,
                    handler: function(){
                        var dialog = new com.conjoon.groupware.feeds.FeedOptionsDialog();
                        dialog.show();
                    }
                }]
            });
            this.menu.on('hide', this.onContextHide, this);
        }
    },

    onContextClick : function(e)
    {
        e.stopEvent();

        // row was clicked, we wont handle this
        if (e.getTarget().id == "") {
            return;
        }

        this.createContextMenu();

        this.selModel.clearSelections();

        this.clkRow    = null;
        this.clkRecord = null;

        this.menu.items.get(2).setDisabled(true);
        this.menu.items.get(3).setDisabled(true);

        this.menu.showAt(e.getXY());
    },

    onRowContextClick : function(grid, index, e)
    {
        var selModel = this.selModel;

        this.createContextMenu();

        e.stopEvent();
        if(this.ctxRow){
            //Ext.fly(this.ctxRow).removeClass('x-node-ctx');
            this.ctxRow = null;
        }

        this.ctxRow    = this.view.getRow(index);
        this.ctxRecord = this.store.getAt(index);

        if (!selModel.isSelected(index)) {
            selModel.selectRow(index, false);
        }

        if (selModel.getCount() == 1) {
            this.menu.items.get(2).setDisabled((this.ctxRecord.data.isRead == true));
            this.menu.items.get(3).setDisabled(!(this.ctxRecord.data.isRead == true));
        } else {
            this.menu.items.get(2).setDisabled(false);
            this.menu.items.get(3).setDisabled(false);
        }

        //Ext.fly(this.ctxRow).addClass('x-node-ctx');
        this.menu.showAt(e.getXY());
    },

    onContextHide : function()
    {
        if(this.ctxRow){
           // Ext.fly(this.ctxRow).removeClass('x-node-ctx');
            this.ctxRow = null;
        }
    }

});<commit_msg> - enhancement: changed parameters for "setItemRead()" action - request params now consists of arrays of integers with the indexes "read"/"unread"<commit_after>/**
 * conjoon
 * (c) 2002-2009 siteartwork.de/conjoon.org
 * licensing@conjoon.org
 *
 * $Author$
 * $Id$
 * $Date$
 * $Revision$
 * $LastChangedDate$
 * $LastChangedBy$
 * $URL$
 */

Ext.namespace('com.conjoon.groupware.feeds');

com.conjoon.groupware.feeds.FeedGrid = Ext.extend(Ext.grid.GridPanel, {

    clkRow          : null,
    clkRecord       : null,
    cellClickActive : false,

    initComponent : function()
    {
        this.store = com.conjoon.groupware.feeds.FeedStore.getInstance();

        this.store.setDefaultSort('pubDate', "DESC");

        this.columns = [{
            id:'name',
            header: com.conjoon.Gettext.gettext("Feed"),
            hidden:true,
            width: 120,
            sortable: true,
            dataIndex: 'name'
          },{
            id:'title',
            header: com.conjoon.Gettext.gettext("Title"),
            width: 220,
            sortable: true,
            dataIndex: 'title',
            renderer : function(value, metadata, record){
                metadata.attr = 'qtip="'+value.replace(/"/g, '"')+'"';
                return value;
            }
          },{
            header: com.conjoon.Gettext.gettext("Description"),
            hidden:true,
            width: 180,
            sortable: true,
            dataIndex: 'description'
          },{
            header: com.conjoon.Gettext.gettext("Date"),
            width: 120,
            hidden:true,
            sortable: true,
            renderer: Ext.util.Format.dateRenderer('d.m.Y H:i'),
            dataIndex: 'pubDate'
          }
        ];

        var groupTextTpl  = '{text} ({[values.rs.length]}/{[function(){var b = 0;for (var i = 0, rs = values.rs, max_i = rs.length; i < max_i; i++) {if (!rs[i].data.isRead) {b++;}}return b;}()]})';
        this.groupTextTpl = new Ext.XTemplate(groupTextTpl);
        this.groupTextTpl.compile();

        this.view = new Ext.grid.GroupingView({
            forceFit      : false,
            showGroupName : false,
            groupTextTpl  : groupTextTpl,
            getRowClass   : this.applyRowClass
        });

        var displayOptionsMenu = new Ext.menu.Menu({
            items: [{
                id : 'groupFeeds',
                text: com.conjoon.Gettext.gettext("group after feeds"),
                checked: true,
                //group: 'com.conjoon.groupware.FeedGrid.display',
                checkHandler: this.toggleGroupView,
                scope: this
              },
              "-",{
                iconCls : 'com-conjoon-groupware-feeds-FeedGrid-optionsMenu-configureItem-icon',
                text    : com.conjoon.Gettext.gettext("Settings..."),
                scope   : this,
                handler : function() {
                    var optDialog = new com.conjoon.groupware.feeds.FeedOptionsDialog();
                    optDialog.show();
                }

              }]
        });

        this.tbar = new Ext.Toolbar([{
            iconCls : 'com-conjoon-groupware-feeds-FeedGrid-toolbar-addFeedButton-icon',
            handler : function(){
                var dialog = new com.conjoon.groupware.feeds.AddFeedDialog({
                    animateTarget : this.getTopToolbar().items.get(0).el.dom.id
                });
                dialog.show();
            },
            scope : this
          },{
            iconCls : 'com-conjoon-groupware-feeds-FeedGrid-toolbar-refreshFeedsButton-icon',
            handler : function(){
                this.clkRow    = null;
                this.clkRecord = null;
                com.conjoon.groupware.feeds.AccountStore.getInstance().reload();
                this.store.reload();},
            scope: this
          },{
            iconCls : 'com-conjoon-groupware-feeds-FeedGrid-toolbar-configureItem-icon',
            handler : function() {
                var optDialog = new com.conjoon.groupware.feeds.FeedOptionsDialog();
                optDialog.show();
            }
          }
        ]);

        Ext.apply(this, {
            loadMask    : {msg: com.conjoon.Gettext.gettext("Loading feeds...")},
            autoScroll  : true,
            style       : 'cursor:default',
            title       : com.conjoon.Gettext.gettext("Feeds"),
            iconCls     : 'com-conjoon-groupware-feeds-Icon',
            hideBorders : true,
            plugins     : [
                new Ext.ux.grid.GridViewMenuPlugin()
            ]
        });

        com.conjoon.groupware.feeds.FeedGrid.superclass.initComponent.call(this);
    },

    initEvents : function()
    {
        Ext.ux.util.MessageBus.subscribe(
           'com.conjoon.groupware.feeds.FeedViewBaton.onFeedLoadSuccess',
           this.onFeedItemLoaded,
           this
        );

        Ext.ux.util.MessageBus.subscribe(
           'com.conjoon.groupware.feeds.FeedPreview.onLoadSuccess',
           this.onFeedItemLoaded,
           this
        );

        // install listeners
        this.on('cellclick',    this.onCellClick, this, {buffer : 200});
        this.on('celldblclick', this.onCellDblClick, this);

        var preview = com.conjoon.groupware.feeds.FeedPreview;

        this.on('resize',         preview.hide.createDelegate(preview, [true]));
        this.on('beforecollapse', preview.hide.createDelegate(preview, [true, false]));
        this.on('contextmenu',    preview.hide.createDelegate(preview, [false]));

        this.on('contextmenu', this.onContextClick, this);
        this.on('rowcontextmenu', this.onRowContextClick, this);

        com.conjoon.groupware.feeds.FeedGrid.superclass.initEvents.call(this);
    },

    // within this function "this" is actually the GridView
    applyRowClass: function(record, rowIndex, p, ds)
    {
        if (record.data.isRead) {
            return 'com-conjoon-groupware-feeds-FeedGrid-itemRead';
        } else {
            return 'com-conjoon-groupware-feeds-FeedGrid-itemUnread';
        }
    },

    // private
    commitRecords : function()
    {
        var m = this.store.getModifiedRecords();

        var l = m.length;

        if (l == 0) {
            return;
        }

        var requestArray = {
            read   : [],
            unread : []
        };

        for (var i = 0; i < l; i++) {
            if (m[i].get('isRead')) {
                requestArray['read'].push(m[i].id);
            } else {
                requestArray['unread'].push(m[i].id);
            }
        }

        Ext.Ajax.request({
            url: './groupware/feeds/set.item.read/format/json',
            params: {
                read   : Ext.encode(requestArray['read']),
                unread : Ext.encode(requestArray['unread'])
            }
        });

        this.store.commitChanges();
        this._updateGroupTemplates();
    },

    onFeedItemLoaded : function(subject, message)
    {
        if (message.id) {
            this.markItemsRead(true, message.id)
        }
    },

    _updateGroupTemplates : function()
    {
        if (!this.store.groupField) {
            return;
        }

        var view = this.view;
        var rs   = this.store.getRange();
        var len  = rs.length;
        if (len < 1) {
            return [];
        }

        var groupField = view.getGroupField();

        var r;
        var gvalue;
        var indexes = {};
        var groups = [];

        for (var i = 0; i < len; i++) {
            r      = rs[i];
            gvalue = r.data[groupField];

            if (!indexes[gvalue]) {
                indexes[gvalue] = [];
            }

            indexes[gvalue].push(r);
        }

        var a  = 0;
        var gr = view.getGroups();
        for (var i in indexes) {
            gr[a++].firstChild.firstChild.innerHTML = this.groupTextTpl.apply({
                text : i,
                rs : indexes[i]
            });
        }
    },

    markItemsRead : function(bRead, id)
    {
        var feedIds = new Array();

        if (!id) {
            var selection = this.selModel.getSelections();
            for (var i = 0; i < selection.length; i++) {
                selection[i].set('isRead', bRead);
            }
        } else {
            var rec = this.store.getById(id);
            if (rec) {
                rec.set('isRead', bRead);
            }
        }

        this.commitRecords();
    },

    onCellClick : function(grid, rowIndex, columnIndex, e)
    {
        if (this.cellClickActive) {
            this.cellClickActive = false;
            return;
        }

        if (e.shiftKey || e.ctrlKey) {
            return;
        }

        this.clkRow    = this.view.getRow(rowIndex);
        this.clkRecord = this.store.getAt(rowIndex);
        com.conjoon.groupware.feeds.FeedPreview.show(grid, rowIndex, columnIndex, e);
    },

    onCellDblClick : function(grid, rowIndex, columnIndex, eventObject)
    {
        this.cellClickActive = true;
        var feedItem = grid.getStore().getAt(rowIndex);
        com.conjoon.groupware.feeds.FeedPreview.hide(true, false);
        com.conjoon.groupware.feeds.FeedViewBaton.showFeed(feedItem, true);
    },

    // private
    /**
     *
     * @access private
     */
    createContextMenu : function()
    {
        if(!this.menu){ // create context menu on first right click
            this.menu = new Ext.menu.Menu({
                items: [{
                    text: com.conjoon.Gettext.gettext("Refresh"),
                    scope:this,
                    handler: function(){
                        this.clkRow    = null;
                        this.clkRecord = null;
                        com.conjoon.groupware.feeds.AccountStore.getInstance().reload();
                        this.store.reload();}
                  },
                 '-',{
                    text: com.conjoon.Gettext.gettext("mark as read"),
                    scope:this,
                    handler: function(){
                        this.markItemsRead(true);}
                  },{
                    text: com.conjoon.Gettext.gettext("mark as unread"),
                    scope:this,
                    handler: function(){
                        this.markItemsRead(false);}
                  },
                  '-',{
                    text : com.conjoon.Gettext.gettext("Settings..."),
                    scope:this,
                    handler: function(){
                        var dialog = new com.conjoon.groupware.feeds.FeedOptionsDialog();
                        dialog.show();
                    }
                }]
            });
            this.menu.on('hide', this.onContextHide, this);
        }
    },

    onContextClick : function(e)
    {
        e.stopEvent();

        // row was clicked, we wont handle this
        if (e.getTarget().id == "") {
            return;
        }

        this.createContextMenu();

        this.selModel.clearSelections();

        this.clkRow    = null;
        this.clkRecord = null;

        this.menu.items.get(2).setDisabled(true);
        this.menu.items.get(3).setDisabled(true);

        this.menu.showAt(e.getXY());
    },

    onRowContextClick : function(grid, index, e)
    {
        var selModel = this.selModel;

        this.createContextMenu();

        e.stopEvent();
        if(this.ctxRow){
            //Ext.fly(this.ctxRow).removeClass('x-node-ctx');
            this.ctxRow = null;
        }

        this.ctxRow    = this.view.getRow(index);
        this.ctxRecord = this.store.getAt(index);

        if (!selModel.isSelected(index)) {
            selModel.selectRow(index, false);
        }

        if (selModel.getCount() == 1) {
            this.menu.items.get(2).setDisabled((this.ctxRecord.data.isRead == true));
            this.menu.items.get(3).setDisabled(!(this.ctxRecord.data.isRead == true));
        } else {
            this.menu.items.get(2).setDisabled(false);
            this.menu.items.get(3).setDisabled(false);
        }

        //Ext.fly(this.ctxRow).addClass('x-node-ctx');
        this.menu.showAt(e.getXY());
    },

    onContextHide : function()
    {
        if(this.ctxRow){
           // Ext.fly(this.ctxRow).removeClass('x-node-ctx');
            this.ctxRow = null;
        }
    }

});<|endoftext|>