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

sc_require('components/graph/axes/axis_view');

/** @class  DG.CellAxisView - The view for a graph axis that displays cells.

 @extends DG.AxisView
 */
DG.CellAxisView = DG.AxisView.extend( (function() {
  var kTickLength = 4,
      kAxisGap = 2;

  return {
    /** @scope DG.CellAxisView.prototype */

    /**
     Number of pixels required from the axis line to the edge of the graph view
     @property { Number }
     */
    desiredExtent: function() {
      var tExtent = sc_super();
      if( !this.get('isNumeric')) { // Not yet handling numeric axis broken up by cells
        tExtent += kTickLength + kAxisGap + this.get('maxLabelExtent');
      }
      return tExtent;
    }.property('maxLabelExtent'),

    /**
     I'm not supposed to work with numbers.
     @property { Boolean }
     */
    isNumeric: false,

    /**
     * @property {Boolean}
     */
    centering: false,

    /**
     * @property { Object { cellBeingDragged: {Integer}, position: {Number}}}
     */
    dragInfo: null,

    /**
     @property{Number}
     */
    axisLineCoordinate: function() {
      var tCoord;
      switch( this.get('orientation')) {
        case 'vertical':
          tCoord = this.get('drawWidth');
          break;
        case 'vertical2':
        case 'horizontal':
          tCoord = 0;
          break;
      }
      return tCoord;
    }.property('drawWidth'),

    /**
     @property {Number} Each time we draw the axis, we set this property to the maximum width
     of the label strings. Note that this will change depending not only on the length of the
     strings but on the rotation of the labels.
     */
    maxLabelExtent: 0,

    /**
     This is the main backbone of the axis.
     @return {Raphael element}
     */
    renderAxisLine: function() {
      var tCoord = this.get('axisLineCoordinate'),
          tPixelMin = this.get('pixelMin'),
          tPixelMax = this.get('pixelMax'),
          tStart, tStop;
      switch( this.get('orientation')) {
        case 'vertical':
          tStart = { x: tCoord - 1, y: tPixelMin };
          tStop = { x: tCoord - 1, y: tPixelMax };
          break;
        case 'horizontal':
          tStart = { x: tPixelMin, y: tCoord + 1 };
          tStop = { x: tPixelMax, y: tCoord + 1 };
          break;
        case 'vertical2':
          tStart = { x: tCoord + 1, y: tPixelMin };
          tStop = { x: tCoord + 1, y: tPixelMax };
          break;
      }
      return this._paper.line( tStart.x, tStart.y, tStop.x, tStop.y)
        .attr( { stroke: DG.PlotUtilities.kAxisColor,
          strokeWidth: 2 });
    },

    /**
     The convention is that, unlike continuous axes, for a
     vertical axis, the low numbers are at the top.
     @param {Number} in screen coordinates
     @return {Number} the cell number corresponding to the given screen coordinate
     */
    whichCell: function( iCoord) {
      var tNumCells = this.getPath('model.numberOfCells'),
          tCell = 0;
      if( tNumCells > 1) {
        var tPixelMin = this.get('pixelMin'),
            tPixelMax = this.get('pixelMax');
        // In order to do the computation, we have to adjust for the coordinates of the ends
        // of the axes, knowing the thePixel is in plot view coordinates, not axis view coordinates.
        if( this.get('isVertical')) {
          iCoord = Math.min( Math.max( tPixelMax + 1, iCoord), tPixelMin - 1);
          tCell = Math.floor( (iCoord - tPixelMax) * tNumCells / ( tPixelMin - tPixelMax));
        }
        else {  // horizontal
          iCoord = Math.max( Math.min( tPixelMax - tPixelMin - 1, iCoord), 1.0);
          tCell = Math.floor( iCoord * tNumCells / ( tPixelMax - tPixelMin));
        }

        //      KCP_ASSERT( (tCell >= 0) && (tCell < tNumCells));
      }
      return tCell;
    },

    /**
     Override base class to account for drag in progress
     @return {Number} coordinate of the given cell.
     */
    cellToCoordinate: function (iCellNum, iIgnoreDragging) {
      var tDragInfo = this.get('dragInfo');
      if( !iIgnoreDragging && tDragInfo && tDragInfo.draggingInProgress && iCellNum === tDragInfo.cellBeingDragged) {
        return tDragInfo.position;
      }
      else {
        return sc_super();
      }
    },

    /**
     Since I'm a leaf class I implement doDraw.
     */
    doDraw: function doDraw() {
      var this_ = this,
          tModel = this.get('model'),
          tNumCells = tModel.get('numberOfCells'),
          tBaseline = this_.get('axisLineCoordinate'),
          tOrientation = this.get('orientation'),
          tRotation = (tOrientation === 'horizontal') ? 0 : -90, // default to parallel to axis
          tCursorClass = (tOrientation === 'horizontal') ? 'dg-axis-cell-label-x' : 'dg-axis-cell-label-y',
          tMaxHeight = DG.RenderingUtilities.kDefaultFontHeight,  // So there will be a default extent
          tCentering = this.get('centering'),
          tTickOffset = tCentering ? 0 : this.get('fullCellWidth') / 2,
          tAnchor = tCentering ? 'middle' : 'start',
          tMaxWidth = tMaxHeight,
          tLabelSpecs = this.get('labelSpecs') || [],
          tCollision = false,
          tPrevLabelEnd,
          tDragStartCoord, tCellBeingDragged, tOriginalCellIndex; // eslint-disable-line no-unused-vars

      var beginDrag = function ( iWindowX, iWindowY) {
            tOriginalCellIndex = tCellBeingDragged = this.cellNum;
            tDragStartCoord = DG.ViewUtilities.windowToViewCoordinates( { x: iWindowX, y: iWindowY }, this_);
            tDragStartCoord = (tOrientation === 'horizontal') ? tDragStartCoord.x : tDragStartCoord.y;
          },
          doDrag = function (iDeltaX, iDeltaY, iWindowX, iWindowY) {
            var tModel = this_.get('model'),
                tCurrentCoord = tDragStartCoord + ((tOrientation === 'horizontal') ? iDeltaX : iDeltaY),
                tCategoryInCurrentCell = this_.whichCell( tCurrentCoord);
            // Todo Touch is currently returning NaN for window coordinates. Fix this
            if( isNaN(tCurrentCoord))
                return;
            SC.run(function() {
              if( tCategoryInCurrentCell !== tCellBeingDragged) {
                tModel.swapCategoriesByIndex( tCellBeingDragged, tCategoryInCurrentCell);
                tCellBeingDragged = tCategoryInCurrentCell;
              }
              this_.set('dragInfo', {
                cellBeingDragged: tCellBeingDragged, position: tCurrentCoord,
                draggingInProgress: true
              });
              this_.displayDidChange();
              this_.propertyDidChange('categoriesDragged');
            });
          },
          endDrag = function ( iEvent) {
            SC.run(function() {
              this_.set('dragInfo', null);
              this_.displayDidChange();
              this_.propertyDidChange('categoriesDragged');
              this_.updateLayerIfNeeded();
            });
          };

      function measureOneCell( iCellNum, iCellName) {
        var tCoord = this_.cellToCoordinate(iCellNum);
        if( !isFinite( tCoord)) {
          return;
        }
        var tTextElement;
        if( !tLabelSpecs[iCellNum]) {
          tTextElement = this_._paper.text(0, 0, iCellName)
              .addClass('dg-axis-tick-label')
              .addClass(tCursorClass)
              .drag(doDrag, beginDrag, endDrag);
        }
        else {
          tTextElement = tLabelSpecs[ iCellNum].element;
          tTextElement.attr('text', iCellName);
        }
        var tTextExtent = DG.RenderingUtilities.getExtentForTextElement(
                          tTextElement, DG.RenderingUtilities.kDefaultFontHeight, true /* compute with no transform */);

        tTextElement.cellNum = iCellNum;
        tLabelSpecs[iCellNum] = { element: tTextElement, coord: tCoord,
                      height: tTextExtent.height, width: tTextExtent.width };

        tMaxHeight = Math.max( tMaxHeight, tTextExtent.height);
        tMaxWidth = Math.max( tMaxWidth, tTextExtent.width);
        if(SC.none( tPrevLabelEnd))
          tCollision = tTextExtent.width > this_.get('fullCellWidth');
        else
          tCollision = tCollision || (tCoord - tTextExtent.width / 2 < tPrevLabelEnd);
        tPrevLabelEnd = (tCoord + tTextExtent.width / 2);
      } // measureOneCell

      // iLabelSpec has form { element: {Raphael element}, coord: {Number}, height: {Number}, width: {Number} }
      function drawOneCell( iLabelSpec, iIndex) {
        var tCoord = iLabelSpec.coord,
            tLabelX, tLabelY;
        switch( this_.get('orientation')) {
          case 'vertical':
          case 'vertical2':
            this_._elementsToClear.push(
              this_._paper.line( tBaseline, tCoord + tTickOffset, tBaseline - kTickLength, tCoord + tTickOffset)
                .attr( { stroke: DG.PlotUtilities.kAxisColor }));
            tLabelX = tBaseline - kTickLength - kAxisGap - iLabelSpec.height / 3;
            tLabelY = tCoord + tTickOffset;
            if( tRotation === 0) {
              tAnchor = 'end';
            }
            break;

          case 'horizontal':
            this_._elementsToClear.push(
              this_._paper.line( tCoord - tTickOffset, tBaseline, tCoord - tTickOffset, tBaseline + kTickLength)
                .attr( { stroke: DG.PlotUtilities.kAxisColor }));
            tLabelX = tCoord - tTickOffset + 1;
            tLabelY = tBaseline + kTickLength + kAxisGap + iLabelSpec.height / 3;
            if( tRotation === -90) {
              tAnchor = 'end';
              if( iIndex === 0)
                tLabelX += iLabelSpec.height / 3;
            }
            break;
        }
        iLabelSpec.element.attr( { x: tLabelX, y: tLabelY, 'text-anchor': tAnchor });
        DG.RenderingUtilities.rotateText( iLabelSpec.element, tRotation, tLabelX, tLabelY);
      } // drawOneCell

      //==========Main body of doDraw==================
      // Special case for short labels
      if( this.getPath('model.maxCellNameLength') <= 3)
        tRotation = 0;

      this._elementsToClear.push( this.renderAxisLine());

      tModel.forEachCellDo( measureOneCell);
      while( tLabelSpecs.length > tNumCells) {
        var tSpec = tLabelSpecs.pop();
        tSpec.element.remove();
      }
      if( tCollision) // labels must be perpendicular to axis
        tRotation = (tOrientation === 'horizontal') ? -90 : 0;
      tLabelSpecs.forEach( drawOneCell);

      this.set('labelSpecs', tLabelSpecs);

      this.renderLabel();
      // By changing maxLabelExtent we can trigger notification that causes the graph to re-layout
      // axes and plot if needed.
      this.setIfChanged('maxLabelExtent',
            (tOrientation === 'horizontal') ?
              ((tRotation === 0) ? tMaxHeight : tMaxWidth) :  // horizontal
              ((tRotation === 0) ? tMaxWidth : tMaxHeight));  // vertical
    }

  };
}())
);

<commit_msg>[#151064593] Bug fix: Categorical attributes do not reorder<commit_after>// ==========================================================================
//                            DG.CellAxisView
//
//  Author:   William Finzer
//
//  Copyright (c) 2014 by The Concord Consortium, Inc. All rights reserved.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// ==========================================================================

sc_require('components/graph/axes/axis_view');

/** @class  DG.CellAxisView - The view for a graph axis that displays cells.

 @extends DG.AxisView
 */
DG.CellAxisView = DG.AxisView.extend( (function() {
  var kTickLength = 4,
      kAxisGap = 2;

  return {
    /** @scope DG.CellAxisView.prototype */

    /**
     Number of pixels required from the axis line to the edge of the graph view
     @property { Number }
     */
    desiredExtent: function() {
      var tExtent = sc_super();
      if( !this.get('isNumeric')) { // Not yet handling numeric axis broken up by cells
        tExtent += kTickLength + kAxisGap + this.get('maxLabelExtent');
      }
      return tExtent;
    }.property('maxLabelExtent'),

    /**
     I'm not supposed to work with numbers.
     @property { Boolean }
     */
    isNumeric: false,

    /**
     * @property {Boolean}
     */
    centering: false,

    /**
     * @property { Object { cellBeingDragged: {Integer}, position: {Number}}}
     */
    dragInfo: null,

    /**
     @property{Number}
     */
    axisLineCoordinate: function() {
      var tCoord;
      switch( this.get('orientation')) {
        case 'vertical':
          tCoord = this.get('drawWidth');
          break;
        case 'vertical2':
        case 'horizontal':
          tCoord = 0;
          break;
      }
      return tCoord;
    }.property('drawWidth'),

    /**
     @property {Number} Each time we draw the axis, we set this property to the maximum width
     of the label strings. Note that this will change depending not only on the length of the
     strings but on the rotation of the labels.
     */
    maxLabelExtent: 0,

    /**
     This is the main backbone of the axis.
     @return {Raphael element}
     */
    renderAxisLine: function() {
      var tCoord = this.get('axisLineCoordinate'),
          tPixelMin = this.get('pixelMin'),
          tPixelMax = this.get('pixelMax'),
          tStart, tStop;
      switch( this.get('orientation')) {
        case 'vertical':
          tStart = { x: tCoord - 1, y: tPixelMin };
          tStop = { x: tCoord - 1, y: tPixelMax };
          break;
        case 'horizontal':
          tStart = { x: tPixelMin, y: tCoord + 1 };
          tStop = { x: tPixelMax, y: tCoord + 1 };
          break;
        case 'vertical2':
          tStart = { x: tCoord + 1, y: tPixelMin };
          tStop = { x: tCoord + 1, y: tPixelMax };
          break;
      }
      return this._paper.line( tStart.x, tStart.y, tStop.x, tStop.y)
        .attr( { stroke: DG.PlotUtilities.kAxisColor,
          strokeWidth: 2 });
    },

    /**
     The convention is that, unlike continuous axes, for a
     vertical axis, the low numbers are at the top.
     @param {Number} in screen coordinates
     @return {Number} the cell number corresponding to the given screen coordinate
     */
    whichCell: function( iCoord) {
      var tNumCells = this.getPath('model.numberOfCells'),
          tCell = 0;
      if( tNumCells > 1) {
        var tPixelMin = this.get('pixelMin'),
            tPixelMax = this.get('pixelMax');
        // In order to do the computation, we have to adjust for the coordinates of the ends
        // of the axes, knowing the thePixel is in plot view coordinates, not axis view coordinates.
        if( this.get('isVertical')) {
          iCoord = Math.min( Math.max( tPixelMax + 1, iCoord), tPixelMin - 1);
          tCell = Math.floor( (iCoord - tPixelMax) * tNumCells / ( tPixelMin - tPixelMax));
        }
        else {  // horizontal
          iCoord = Math.max( Math.min( tPixelMax - tPixelMin - 1, iCoord), 1.0);
          tCell = Math.floor( iCoord * tNumCells / ( tPixelMax - tPixelMin));
        }

        //      KCP_ASSERT( (tCell >= 0) && (tCell < tNumCells));
      }
      return tCell;
    },

    /**
     Override base class to account for drag in progress
     @return {Number} coordinate of the given cell.
     */
    cellToCoordinate: function (iCellNum, iIgnoreDragging) {
      var tDragInfo = this.get('dragInfo');
      if( !iIgnoreDragging && tDragInfo && tDragInfo.draggingInProgress && iCellNum === tDragInfo.cellBeingDragged) {
        return tDragInfo.position;
      }
      else {
        return sc_super();
      }
    },

    /**
     Since I'm a leaf class I implement doDraw.
     */
    doDraw: function doDraw() {
      var this_ = this,
          tModel = this.get('model'),
          tNumCells = tModel.get('numberOfCells'),
          tBaseline = this_.get('axisLineCoordinate'),
          tOrientation = this.get('orientation'),
          tRotation = (tOrientation === 'horizontal') ? 0 : -90, // default to parallel to axis
          tCursorClass = (tOrientation === 'horizontal') ? 'dg-axis-cell-label-x' : 'dg-axis-cell-label-y',
          tMaxHeight = DG.RenderingUtilities.kDefaultFontHeight,  // So there will be a default extent
          tCentering = this.get('centering'),
          tTickOffset = tCentering ? 0 : this.get('fullCellWidth') / 2,
          tAnchor = tCentering ? 'middle' : 'start',
          tMaxWidth = tMaxHeight,
          tLabelSpecs = this.get('labelSpecs') || [],
          tCollision = false,
          tPrevLabelEnd,
          tDragStartCoord, tCellBeingDragged, tOriginalCellIndex; // eslint-disable-line no-unused-vars

      var beginDrag = function ( iWindowX, iWindowY) {
            tOriginalCellIndex = tCellBeingDragged = this.cellNum;
            tDragStartCoord = DG.ViewUtilities.windowToViewCoordinates( { x: iWindowX, y: iWindowY }, this_);
            tDragStartCoord = (tOrientation === 'horizontal') ? tDragStartCoord.x : tDragStartCoord.y;
          },
          doDrag = function (iDeltaX, iDeltaY, iWindowX, iWindowY) {
            var tModel = this_.get('model'),
                tCurrentCoord = tDragStartCoord + ((tOrientation === 'horizontal') ? iDeltaX : iDeltaY),
                tCategoryInCurrentCell = this_.whichCell( tCurrentCoord);
            // Todo Touch is currently returning NaN for window coordinates. Fix this
            if( isNaN(tCurrentCoord))
                return;
            SC.run(function() {
              if( tCategoryInCurrentCell !== tCellBeingDragged) {
                var tSign = Math.sign( tCategoryInCurrentCell - tCellBeingDragged),
                    tCellToSwap = tCellBeingDragged + tSign;
                // Insist on pairwise swaps until we get one beyond tCategoryInCurrentCell
                while( tCellToSwap !== tCategoryInCurrentCell +tSign) {
                  tModel.swapCategoriesByIndex(tCellBeingDragged, tCellToSwap);
                  tCellToSwap += tSign;
                  tCellBeingDragged += tSign;
                }
                tCellBeingDragged = tCategoryInCurrentCell;
              }
              this_.set('dragInfo', {
                cellBeingDragged: tCellBeingDragged, position: tCurrentCoord,
                draggingInProgress: true
              });
              this_.displayDidChange();
              this_.propertyDidChange('categoriesDragged');
            });
          },
          endDrag = function ( iEvent) {
            SC.run(function() {
              this_.set('dragInfo', null);
              this_.displayDidChange();
              this_.propertyDidChange('categoriesDragged');
              this_.updateLayerIfNeeded();
            });
          };

      function measureOneCell( iCellNum, iCellName) {
        var tCoord = this_.cellToCoordinate(iCellNum);
        if( !isFinite( tCoord)) {
          return;
        }
        var tTextElement;
        if( !tLabelSpecs[iCellNum]) {
          tTextElement = this_._paper.text(0, 0, iCellName)
              .addClass('dg-axis-tick-label')
              .addClass(tCursorClass)
              .drag(doDrag, beginDrag, endDrag);
        }
        else {
          tTextElement = tLabelSpecs[ iCellNum].element;
          tTextElement.attr('text', iCellName);
        }
        var tTextExtent = DG.RenderingUtilities.getExtentForTextElement(
                          tTextElement, DG.RenderingUtilities.kDefaultFontHeight, true /* compute with no transform */);

        tTextElement.cellNum = iCellNum;
        tLabelSpecs[iCellNum] = { element: tTextElement, coord: tCoord,
                      height: tTextExtent.height, width: tTextExtent.width };

        tMaxHeight = Math.max( tMaxHeight, tTextExtent.height);
        tMaxWidth = Math.max( tMaxWidth, tTextExtent.width);
        if(SC.none( tPrevLabelEnd))
          tCollision = tTextExtent.width > this_.get('fullCellWidth');
        else
          tCollision = tCollision || (tCoord - tTextExtent.width / 2 < tPrevLabelEnd);
        tPrevLabelEnd = (tCoord + tTextExtent.width / 2);
      } // measureOneCell

      // iLabelSpec has form { element: {Raphael element}, coord: {Number}, height: {Number}, width: {Number} }
      function drawOneCell( iLabelSpec, iIndex) {
        var tCoord = iLabelSpec.coord,
            tLabelX, tLabelY;
        switch( this_.get('orientation')) {
          case 'vertical':
          case 'vertical2':
            this_._elementsToClear.push(
              this_._paper.line( tBaseline, tCoord + tTickOffset, tBaseline - kTickLength, tCoord + tTickOffset)
                .attr( { stroke: DG.PlotUtilities.kAxisColor }));
            tLabelX = tBaseline - kTickLength - kAxisGap - iLabelSpec.height / 3;
            tLabelY = tCoord + tTickOffset;
            if( tRotation === 0) {
              tAnchor = 'end';
            }
            break;

          case 'horizontal':
            this_._elementsToClear.push(
              this_._paper.line( tCoord - tTickOffset, tBaseline, tCoord - tTickOffset, tBaseline + kTickLength)
                .attr( { stroke: DG.PlotUtilities.kAxisColor }));
            tLabelX = tCoord - tTickOffset + 1;
            tLabelY = tBaseline + kTickLength + kAxisGap + iLabelSpec.height / 3;
            if( tRotation === -90) {
              tAnchor = 'end';
              if( iIndex === 0)
                tLabelX += iLabelSpec.height / 3;
            }
            break;
        }
        iLabelSpec.element.attr( { x: tLabelX, y: tLabelY, 'text-anchor': tAnchor });
        DG.RenderingUtilities.rotateText( iLabelSpec.element, tRotation, tLabelX, tLabelY);
      } // drawOneCell

      //==========Main body of doDraw==================
      // Special case for short labels
      if( this.getPath('model.maxCellNameLength') <= 3)
        tRotation = 0;

      this._elementsToClear.push( this.renderAxisLine());

      tModel.forEachCellDo( measureOneCell);
      while( tLabelSpecs.length > tNumCells) {
        var tSpec = tLabelSpecs.pop();
        tSpec.element.remove();
      }
      if( tCollision) // labels must be perpendicular to axis
        tRotation = (tOrientation === 'horizontal') ? -90 : 0;
      tLabelSpecs.forEach( drawOneCell);

      this.set('labelSpecs', tLabelSpecs);

      this.renderLabel();
      // By changing maxLabelExtent we can trigger notification that causes the graph to re-layout
      // axes and plot if needed.
      this.setIfChanged('maxLabelExtent',
            (tOrientation === 'horizontal') ?
              ((tRotation === 0) ? tMaxHeight : tMaxWidth) :  // horizontal
              ((tRotation === 0) ? tMaxWidth : tMaxHeight));  // vertical
    }

  };
}())
);

<|endoftext|>


<commit_before>"use strict";

define(['logManager',
    'clientUtil',
    'js/NodePropertyNames',
    'js/Decorators/DecoratorDB',
    'js/Constants',
    'js/Panels/MetaEditor/MetaEditorConstants',
    'js/Panels/ManualAspect/ManualAspectConstants'], function (logManager,
                                        util,
                                        nodePropertyNames,
                                        DecoratorDB,
                                        CONSTANTS,
                                        MetaEditorConstants,
                                        ManualAspectConstants) {

    var PropertyEditorController;

    PropertyEditorController = function (client, propertyGrid) {
        this._client = client;
        this._propertyGrid = propertyGrid;

        this._initEventHandlers();

        this._logger = logManager.create("PropertyEditorController");
        this._logger.debug("Created");
    };

    PropertyEditorController.prototype._initEventHandlers = function () {
        var self = this;

        if (this._client) {
            this._client.addEventListener(this._client.events.PROPERTY_EDITOR_SELECTION_CHANGED, function (__project, idList) {
                self._selectedObjectsChanged(idList);
            });
        }

        this._propertyGrid.onFinishChange(function (args) {
            self._onPropertyChanged(args);
        });
    };

    PropertyEditorController.prototype._selectedObjectsChanged = function (idList) {
        var patterns = {},
            i,
            self = this;

        this._idList = idList;

        if (this._territoryId) {
            this._client.removeUI(this._territoryId);
        }

        if (idList.length > 0) {
            i = idList.length;
            while (i--) {
                patterns[idList[i]] = { "children": 0 };
            }

            this._territoryId = this._client.addUI(this, function (/*events*/) {
                self._refreshPropertyList();
            });
            this._client.updateTerritory(this._territoryId, patterns);
        }
    };

    PropertyEditorController.prototype._refreshPropertyList = function () {
        var propList = this._getCommonPropertiesForSelection(this._idList);
        this._propertyGrid.setPropertyList(propList);
    };

    PropertyEditorController.prototype._getCommonPropertiesForSelection = function (selectedObjIDs) {
        var propList = {},
            selectionLength = selectedObjIDs.length,
            cNode,
            i,
            flattenedAttrs,
            flattenedRegs,
            flattenedPointers,
            commonAttrs = {},
            commonRegs = {},
            commonPointers = {},
            noCommonValueColor = "#f89406",
            _getNodePropertyValues, //fn
            _filterCommon, //fn
            _addItemsToResultList,  //fn
            _getPointerInfo,
            commonAttrMeta = {},
            buildCommonAttrMeta,     //fn
            _client = this._client;

        _getNodePropertyValues = function (node, propNameFn, propValueFn) {
            var result =  {},
                attrNames = node[propNameFn](),
                len = attrNames.length;

            while (--len >= 0) {
                result[attrNames[len]] = node[propValueFn](attrNames[len]);
            }

            return util.flattenObject(result);
        };

        _filterCommon = function (resultList, otherList, initPhase) {
            var it;

            if (initPhase === true) {
                for (it in otherList) {
                    if (otherList.hasOwnProperty(it)) {
                        resultList[it] = { "value": otherList[it],
                            "valueType": typeof otherList[it],
                            "isCommon": true };
                    }
                }
            } else {
                for (it in resultList) {
                    if (resultList.hasOwnProperty(it)) {
                        if (otherList.hasOwnProperty(it)) {
                            if (resultList[it].isCommon) {
                                resultList[it].isCommon = resultList[it].value === otherList[it];
                            }
                        } else {
                            delete resultList[it];
                        }
                    }
                }
            }
        };

        _getPointerInfo = function (node) {
            var result = {},
                availablePointers = node.getPointerNames(),
                len = availablePointers.length;

            while (len--) {
                result[availablePointers[len]] = node.getPointer(availablePointers[len]).to || '';
                if (availablePointers[len] === CONSTANTS.POINTER_BASE) {
                    var baseNode = _client.getNode(result[availablePointers[len]]);
                    if (baseNode) {
                        result[availablePointers[len]] = baseNode.getAttribute(nodePropertyNames.Attributes.name) + ' (' + result[availablePointers[len]] + ')';
                    }
                }
            }

            return util.flattenObject(result);
        };

        buildCommonAttrMeta = function (node, initPhase) {
            var nodeId = node.getId(),
                nodeAttributeNames = _client.getValidAttributeNames(nodeId) || [],
                len = nodeAttributeNames.length,
                attrMetaDescriptor,
                attrName,
                isCommon,
                commonEnumValues,
                isEnumCommon,
                isEnumAttrMeta;

            //first delete the ones from the common that does not exist in this node
            for (attrName in commonAttrMeta) {
                if (commonAttrMeta.hasOwnProperty(attrName)) {
                    if (nodeAttributeNames.indexOf(attrName) === -1) {
                        delete commonAttrMeta[attrName];
                    }
                }
            }

            //for the remaining list check if still common
            //common: type is the same
            //if type is enum, the common types should be the intersection of the individual enum types
            while (len--) {
                attrName = nodeAttributeNames[len];
                attrMetaDescriptor = _client.getAttributeSchema(nodeId,attrName);
                if (commonAttrMeta.hasOwnProperty(attrName)) {
                    isCommon = true;
                    //this attribute already exist in the attribute meta map
                    //let's see if it is still common
                    if (attrMetaDescriptor) {
                        if (commonAttrMeta[attrName].type === attrMetaDescriptor.type) {
                            isEnumCommon = commonAttrMeta[attrName].enum && commonAttrMeta[attrName].enum.length > 0;
                            isEnumAttrMeta = attrMetaDescriptor.enum && attrMetaDescriptor.enum.length > 0;
                            if (isEnumCommon && isEnumAttrMeta) {
                                //same type, both enum
                                //get the intersection of the enum values
                                commonEnumValues = _.intersection(commonAttrMeta[attrName].enum, attrMetaDescriptor.enum);

                                if (commonEnumValues.length !== commonAttrMeta[attrName].enum.length) {
                                    if (commonEnumValues.length === 0) {
                                        //0 common enum values, can not consider common attribute anymore
                                        isCommon = false;
                                    } else {
                                        //has common values but less than before
                                        //store the new common values
                                        commonAttrMeta[attrName].enum = commonEnumValues.slice(0);
                                    }
                                }
                            } else {
                                //not both are enum
                                //if only one is enum --> not common anymore
                                //if both are not enum --> still common
                                if (!isEnumCommon && !isEnumAttrMeta) {

                                } else {
                                    isCommon = false;
                                }
                            }
                        } else {
                            //different types, for sure it's not common anymore
                            isCommon = false;
                        }
                    } else {
                        //node meta descriptor in this node
                        //it's not common then
                        //NOTE: it should never happen probably
                        isCommon = false;
                    }

                    //if not common, delete it from attribute map
                    if (!isCommon) {
                        delete commonAttrMeta[attrName];
                    }
                } else {
                    //no entry for this attribute
                    //in init phase, create entry
                    if (initPhase) {
                        if (attrMetaDescriptor) {
                            commonAttrMeta[attrName] = {};
                            _.extend(commonAttrMeta[attrName], attrMetaDescriptor);
                        }
                    }
                }
            }
        };

        if (selectionLength > 0) {
            //get all attributes
            //get all registry elements
            i = selectionLength;
            while (--i >= 0) {
                cNode = this._client.getNode(selectedObjIDs[i]);

                if (cNode) {
                    flattenedAttrs = _getNodePropertyValues(cNode, "getAttributeNames", "getAttribute");
                    buildCommonAttrMeta(cNode, i === selectionLength - 1);

                    _filterCommon(commonAttrs, flattenedAttrs, i === selectionLength - 1);

                    flattenedRegs = _getNodePropertyValues(cNode, "getRegistryNames", "getRegistry");

                    _filterCommon(commonRegs, flattenedRegs, i === selectionLength - 1);

                    flattenedPointers = _getPointerInfo(cNode);

                    _filterCommon(commonPointers, flattenedPointers, i === selectionLength - 1);
                }
            }

            _addItemsToResultList = function (srcList, prefix, dstList, isAttribute) {
                var i,
                    extKey,
                    keyParts,
                    doDisplay;

                if (prefix !== "") {
                    prefix += ".";
                }

                for (i in srcList) {
                    if (srcList.hasOwnProperty(i)) {
                        doDisplay = true;

                        if (isAttribute && !commonAttrMeta.hasOwnProperty(i)) {
                            doDisplay = false;
                        }

                        if (doDisplay) {
                            extKey = prefix + i;
                            keyParts = i.split(".");

                            dstList[extKey] = { "name": keyParts[keyParts.length - 1],
                                "value": srcList[i].value,
                                "valueType": srcList[i].valueType};

                            if (i === "position.x" || i === "position.y") {
                                dstList[extKey].minValue = 0;
                                dstList[extKey].stepValue = 10;
                            }

                            if (srcList[i].readOnly === false || srcList[i].readOnly === true) {
                                dstList[extKey].readOnly = srcList[i].readOnly;
                            }

                            if (srcList[i].isCommon === false) {
                                dstList[extKey].value = "";
                                dstList[extKey].options = {"textColor": noCommonValueColor};
                            }

                            if (extKey.indexOf(".x") > -1) {
                                //let's say its inherited, make it italic
                                dstList[extKey].options = dstList[extKey].options || {};
                                dstList[extKey].options.textItalic = true;
                                dstList[extKey].options.textBold = true;
                            }

                            //decorator value should be rendered as an option list
                            if (i === nodePropertyNames.Registry.decorator) {
                                //dstList[extKey].valueType = "option";
                                //TODO: only the decorators for DiagramDesigner are listed so far, needs to be fixed...
                                dstList[extKey].valueItems = DecoratorDB.getDecoratorsByWidget('DiagramDesigner');
                            }

                            //if the attribute value is an enum, display the enum values
                            if (isAttribute && commonAttrMeta[i].enum && commonAttrMeta[i].enum.length > 0) {
                                dstList[extKey].valueItems = commonAttrMeta[i].enum.slice(0);
                                dstList[extKey].valueItems.sort();
                            }
                        }
                    }
                }
            };

            if (selectedObjIDs.length === 1) {
                propList[" ID"] = { "name": 'ID',
                    "value": selectedObjIDs[0],
                    "valueType": typeof selectedObjIDs[0],
                    "isCommon": true,
                    "readOnly": true};
            }

            propList["Attributes"] = { "name": 'Attributes',
                "text": "Attributes",
                "value": undefined};

            propList["Registry"] = { "name": 'Registry',
                "text": "Registry",
                "value": undefined};

            propList["Pointers"] = { "name": 'Pointers',
                "text": "Pointers",
                "value": undefined};

            _addItemsToResultList(commonAttrs, "Attributes", propList, true);

            //modify registry
            for (var it in commonRegs) {
                if (commonRegs.hasOwnProperty(it)) {
                    if (commonRegs.hasOwnProperty(it)) {
                        //#1: filter out rows of 'MetaEditor.MemberCoord' from Registry
                        if (it.indexOf( nodePropertyNames.Registry.ProjectRegistry + '.') === 0) { //#3: make ProjectRegistry entries readonly
                            commonRegs[it].readOnly = true;
                        } else if (it.indexOf(MetaEditorConstants.META_SHEET_REGISTRY_KEY) === 0 ) { //filter out MetaAspectRegistry
                            delete commonRegs[it];
                        } else if (it.indexOf(ManualAspectConstants.MANUAL_ASPECTS_REGISTRY_KEY) === 0) { //filter out ManualAspectRegistry
                            delete commonRegs[it];
                        }
                    }
                }
            }

            _addItemsToResultList(commonRegs, "Registry", propList);

            //filter out ros from Pointers
            for (var it in commonPointers) {
                if (commonPointers.hasOwnProperty(it)) {
                    if (commonPointers.hasOwnProperty(it)) {
                        commonPointers[it].readOnly = true;
                    }
                }
            }
            _addItemsToResultList(commonPointers, "Pointers", propList);
        }

        return propList;
    };

    PropertyEditorController.prototype._onPropertyChanged = function (args) {
        var selectedObjIDs = this._idList,
            i = selectedObjIDs.length,
            keyArr,
            setterFn,
            getterFn,
            propObject,
            propPointer,
            gmeID,
            path;

        this._client.startTransaction();
        while (--i >= 0) {
            gmeID = selectedObjIDs[i];

            keyArr = args.id.split(".");
            if (keyArr[0] === "Attributes") {
                setterFn = "setAttributes";
                getterFn = "getEditableAttribute";
            } else {
                setterFn = "setRegistry";
                getterFn = "getEditableRegistry";
            }

            keyArr.splice(0, 1);

            //get property object from node
            path = keyArr[0];
            propObject = this._client.getNode(gmeID)[getterFn](path);

            //get root object
            propPointer = propObject;
            keyArr.splice(0, 1);

            if(keyArr.length < 1){
                //simple value so just set it
                propObject = args.newValue;
            } else {
                //dig down to leaf property
                while (keyArr.length > 1) {
                    propPointer = propPointer[keyArr[0]];
                    keyArr.splice(0, 1);
                }

                //set value
                propPointer[keyArr[0]] = args.newValue;
            }

            //save back object
            this._client[setterFn](gmeID, path, propObject);
        }
        this._client.completeTransaction();
    };

    return PropertyEditorController;
});<commit_msg>remove unnecessary formatting<commit_after>"use strict";

define(['logManager',
    'clientUtil',
    'js/NodePropertyNames',
    'js/Decorators/DecoratorDB',
    'js/Constants',
    'js/Panels/MetaEditor/MetaEditorConstants',
    'js/Panels/ManualAspect/ManualAspectConstants'], function (logManager,
                                        util,
                                        nodePropertyNames,
                                        DecoratorDB,
                                        CONSTANTS,
                                        MetaEditorConstants,
                                        ManualAspectConstants) {

    var PropertyEditorController;

    PropertyEditorController = function (client, propertyGrid) {
        this._client = client;
        this._propertyGrid = propertyGrid;

        this._initEventHandlers();

        this._logger = logManager.create("PropertyEditorController");
        this._logger.debug("Created");
    };

    PropertyEditorController.prototype._initEventHandlers = function () {
        var self = this;

        if (this._client) {
            this._client.addEventListener(this._client.events.PROPERTY_EDITOR_SELECTION_CHANGED, function (__project, idList) {
                self._selectedObjectsChanged(idList);
            });
        }

        this._propertyGrid.onFinishChange(function (args) {
            self._onPropertyChanged(args);
        });
    };

    PropertyEditorController.prototype._selectedObjectsChanged = function (idList) {
        var patterns = {},
            i,
            self = this;

        this._idList = idList;

        if (this._territoryId) {
            this._client.removeUI(this._territoryId);
        }

        if (idList.length > 0) {
            i = idList.length;
            while (i--) {
                patterns[idList[i]] = { "children": 0 };
            }

            this._territoryId = this._client.addUI(this, function (/*events*/) {
                self._refreshPropertyList();
            });
            this._client.updateTerritory(this._territoryId, patterns);
        }
    };

    PropertyEditorController.prototype._refreshPropertyList = function () {
        var propList = this._getCommonPropertiesForSelection(this._idList);
        this._propertyGrid.setPropertyList(propList);
    };

    PropertyEditorController.prototype._getCommonPropertiesForSelection = function (selectedObjIDs) {
        var propList = {},
            selectionLength = selectedObjIDs.length,
            cNode,
            i,
            flattenedAttrs,
            flattenedRegs,
            flattenedPointers,
            commonAttrs = {},
            commonRegs = {},
            commonPointers = {},
            noCommonValueColor = "#f89406",
            _getNodePropertyValues, //fn
            _filterCommon, //fn
            _addItemsToResultList,  //fn
            _getPointerInfo,
            commonAttrMeta = {},
            buildCommonAttrMeta,     //fn
            _client = this._client;

        _getNodePropertyValues = function (node, propNameFn, propValueFn) {
            var result =  {},
                attrNames = node[propNameFn](),
                len = attrNames.length;

            while (--len >= 0) {
                result[attrNames[len]] = node[propValueFn](attrNames[len]);
            }

            return util.flattenObject(result);
        };

        _filterCommon = function (resultList, otherList, initPhase) {
            var it;

            if (initPhase === true) {
                for (it in otherList) {
                    if (otherList.hasOwnProperty(it)) {
                        resultList[it] = { "value": otherList[it],
                            "valueType": typeof otherList[it],
                            "isCommon": true };
                    }
                }
            } else {
                for (it in resultList) {
                    if (resultList.hasOwnProperty(it)) {
                        if (otherList.hasOwnProperty(it)) {
                            if (resultList[it].isCommon) {
                                resultList[it].isCommon = resultList[it].value === otherList[it];
                            }
                        } else {
                            delete resultList[it];
                        }
                    }
                }
            }
        };

        _getPointerInfo = function (node) {
            var result = {},
                availablePointers = node.getPointerNames(),
                len = availablePointers.length;

            while (len--) {
                result[availablePointers[len]] = node.getPointer(availablePointers[len]).to || '';
                if (availablePointers[len] === CONSTANTS.POINTER_BASE) {
                    var baseNode = _client.getNode(result[availablePointers[len]]);
                    if (baseNode) {
                        result[availablePointers[len]] = baseNode.getAttribute(nodePropertyNames.Attributes.name) + ' (' + result[availablePointers[len]] + ')';
                    }
                }
            }

            return util.flattenObject(result);
        };

        buildCommonAttrMeta = function (node, initPhase) {
            var nodeId = node.getId(),
                nodeAttributeNames = _client.getValidAttributeNames(nodeId) || [],
                len = nodeAttributeNames.length,
                attrMetaDescriptor,
                attrName,
                isCommon,
                commonEnumValues,
                isEnumCommon,
                isEnumAttrMeta;

            //first delete the ones from the common that does not exist in this node
            for (attrName in commonAttrMeta) {
                if (commonAttrMeta.hasOwnProperty(attrName)) {
                    if (nodeAttributeNames.indexOf(attrName) === -1) {
                        delete commonAttrMeta[attrName];
                    }
                }
            }

            //for the remaining list check if still common
            //common: type is the same
            //if type is enum, the common types should be the intersection of the individual enum types
            while (len--) {
                attrName = nodeAttributeNames[len];
                attrMetaDescriptor = _client.getAttributeSchema(nodeId,attrName);
                if (commonAttrMeta.hasOwnProperty(attrName)) {
                    isCommon = true;
                    //this attribute already exist in the attribute meta map
                    //let's see if it is still common
                    if (attrMetaDescriptor) {
                        if (commonAttrMeta[attrName].type === attrMetaDescriptor.type) {
                            isEnumCommon = commonAttrMeta[attrName].enum && commonAttrMeta[attrName].enum.length > 0;
                            isEnumAttrMeta = attrMetaDescriptor.enum && attrMetaDescriptor.enum.length > 0;
                            if (isEnumCommon && isEnumAttrMeta) {
                                //same type, both enum
                                //get the intersection of the enum values
                                commonEnumValues = _.intersection(commonAttrMeta[attrName].enum, attrMetaDescriptor.enum);

                                if (commonEnumValues.length !== commonAttrMeta[attrName].enum.length) {
                                    if (commonEnumValues.length === 0) {
                                        //0 common enum values, can not consider common attribute anymore
                                        isCommon = false;
                                    } else {
                                        //has common values but less than before
                                        //store the new common values
                                        commonAttrMeta[attrName].enum = commonEnumValues.slice(0);
                                    }
                                }
                            } else {
                                //not both are enum
                                //if only one is enum --> not common anymore
                                //if both are not enum --> still common
                                if (!isEnumCommon && !isEnumAttrMeta) {

                                } else {
                                    isCommon = false;
                                }
                            }
                        } else {
                            //different types, for sure it's not common anymore
                            isCommon = false;
                        }
                    } else {
                        //node meta descriptor in this node
                        //it's not common then
                        //NOTE: it should never happen probably
                        isCommon = false;
                    }

                    //if not common, delete it from attribute map
                    if (!isCommon) {
                        delete commonAttrMeta[attrName];
                    }
                } else {
                    //no entry for this attribute
                    //in init phase, create entry
                    if (initPhase) {
                        if (attrMetaDescriptor) {
                            commonAttrMeta[attrName] = {};
                            _.extend(commonAttrMeta[attrName], attrMetaDescriptor);
                        }
                    }
                }
            }
        };

        if (selectionLength > 0) {
            //get all attributes
            //get all registry elements
            i = selectionLength;
            while (--i >= 0) {
                cNode = this._client.getNode(selectedObjIDs[i]);

                if (cNode) {
                    flattenedAttrs = _getNodePropertyValues(cNode, "getAttributeNames", "getAttribute");
                    buildCommonAttrMeta(cNode, i === selectionLength - 1);

                    _filterCommon(commonAttrs, flattenedAttrs, i === selectionLength - 1);

                    flattenedRegs = _getNodePropertyValues(cNode, "getRegistryNames", "getRegistry");

                    _filterCommon(commonRegs, flattenedRegs, i === selectionLength - 1);

                    flattenedPointers = _getPointerInfo(cNode);

                    _filterCommon(commonPointers, flattenedPointers, i === selectionLength - 1);
                }
            }

            _addItemsToResultList = function (srcList, prefix, dstList, isAttribute) {
                var i,
                    extKey,
                    keyParts,
                    doDisplay;

                if (prefix !== "") {
                    prefix += ".";
                }

                for (i in srcList) {
                    if (srcList.hasOwnProperty(i)) {
                        doDisplay = true;

                        if (isAttribute && !commonAttrMeta.hasOwnProperty(i)) {
                            doDisplay = false;
                        }

                        if (doDisplay) {
                            extKey = prefix + i;
                            keyParts = i.split(".");

                            dstList[extKey] = { "name": keyParts[keyParts.length - 1],
                                "value": srcList[i].value,
                                "valueType": srcList[i].valueType};

                            if (i === "position.x" || i === "position.y") {
                                dstList[extKey].minValue = 0;
                                dstList[extKey].stepValue = 10;
                            }

                            if (srcList[i].readOnly === false || srcList[i].readOnly === true) {
                                dstList[extKey].readOnly = srcList[i].readOnly;
                            }

                            if (srcList[i].isCommon === false) {
                                dstList[extKey].value = "";
                                dstList[extKey].options = {"textColor": noCommonValueColor};
                            }

                            //possible inherited style --> italic
                            /*if (extKey.indexOf(".x") > -1) {
                                //let's say its inherited, make it italic
                                dstList[extKey].options = dstList[extKey].options || {};
                                dstList[extKey].options.textItalic = true;
                                dstList[extKey].options.textBold = true;
                            }*/

                            //decorator value should be rendered as an option list
                            if (i === nodePropertyNames.Registry.decorator) {
                                //dstList[extKey].valueType = "option";
                                //TODO: only the decorators for DiagramDesigner are listed so far, needs to be fixed...
                                dstList[extKey].valueItems = DecoratorDB.getDecoratorsByWidget('DiagramDesigner');
                            }

                            //if the attribute value is an enum, display the enum values
                            if (isAttribute && commonAttrMeta[i].enum && commonAttrMeta[i].enum.length > 0) {
                                dstList[extKey].valueItems = commonAttrMeta[i].enum.slice(0);
                                dstList[extKey].valueItems.sort();
                            }
                        }
                    }
                }
            };

            if (selectedObjIDs.length === 1) {
                propList[" ID"] = { "name": 'ID',
                    "value": selectedObjIDs[0],
                    "valueType": typeof selectedObjIDs[0],
                    "isCommon": true,
                    "readOnly": true};
            }

            propList["Attributes"] = { "name": 'Attributes',
                "text": "Attributes",
                "value": undefined};

            propList["Registry"] = { "name": 'Registry',
                "text": "Registry",
                "value": undefined};

            propList["Pointers"] = { "name": 'Pointers',
                "text": "Pointers",
                "value": undefined};

            _addItemsToResultList(commonAttrs, "Attributes", propList, true);

            //modify registry
            for (var it in commonRegs) {
                if (commonRegs.hasOwnProperty(it)) {
                    if (commonRegs.hasOwnProperty(it)) {
                        //#1: filter out rows of 'MetaEditor.MemberCoord' from Registry
                        if (it.indexOf( nodePropertyNames.Registry.ProjectRegistry + '.') === 0) { //#3: make ProjectRegistry entries readonly
                            commonRegs[it].readOnly = true;
                        } else if (it.indexOf(MetaEditorConstants.META_SHEET_REGISTRY_KEY) === 0 ) { //filter out MetaAspectRegistry
                            delete commonRegs[it];
                        } else if (it.indexOf(ManualAspectConstants.MANUAL_ASPECTS_REGISTRY_KEY) === 0) { //filter out ManualAspectRegistry
                            delete commonRegs[it];
                        }
                    }
                }
            }

            _addItemsToResultList(commonRegs, "Registry", propList);

            //filter out ros from Pointers
            for (var it in commonPointers) {
                if (commonPointers.hasOwnProperty(it)) {
                    if (commonPointers.hasOwnProperty(it)) {
                        commonPointers[it].readOnly = true;
                    }
                }
            }
            _addItemsToResultList(commonPointers, "Pointers", propList);
        }

        return propList;
    };

    PropertyEditorController.prototype._onPropertyChanged = function (args) {
        var selectedObjIDs = this._idList,
            i = selectedObjIDs.length,
            keyArr,
            setterFn,
            getterFn,
            propObject,
            propPointer,
            gmeID,
            path;

        this._client.startTransaction();
        while (--i >= 0) {
            gmeID = selectedObjIDs[i];

            keyArr = args.id.split(".");
            if (keyArr[0] === "Attributes") {
                setterFn = "setAttributes";
                getterFn = "getEditableAttribute";
            } else {
                setterFn = "setRegistry";
                getterFn = "getEditableRegistry";
            }

            keyArr.splice(0, 1);

            //get property object from node
            path = keyArr[0];
            propObject = this._client.getNode(gmeID)[getterFn](path);

            //get root object
            propPointer = propObject;
            keyArr.splice(0, 1);

            if(keyArr.length < 1){
                //simple value so just set it
                propObject = args.newValue;
            } else {
                //dig down to leaf property
                while (keyArr.length > 1) {
                    propPointer = propPointer[keyArr[0]];
                    keyArr.splice(0, 1);
                }

                //set value
                propPointer[keyArr[0]] = args.newValue;
            }

            //save back object
            this._client[setterFn](gmeID, path, propObject);
        }
        this._client.completeTransaction();
    };

    return PropertyEditorController;
});<|endoftext|>


<commit_before>
var MergeTemplateContainer = require('./data/merge-template-container.js');
var MergeTemplateService = require('./services/merge-template-service.js');
var EmailService = require('./services/email-service.js');
var MergeTemplatesView = require('./views/merge-template/merge-templates-list-view.js');
var CardsView = require('./views/merge-setup/cards-view.js');
var SettingsView = require('./views/settings/settings-view.js');
var ActionBar = require('./views/action-bar/action-bar.js');
var Snackbar = require('./views/snackbar/snackbar.js');
var Dialog = require('./views/dialog/dialog.js');
var LoadingScreen = require('./views/loading/loading-screen.js');
var baseHTML = require('./main.html');
var PubSub = require('pubsub-js');

// Handlers
var StandardMailHandler = require('./controllers/standard-mail-handler.js');

var MailMan = function(appendTo) {

  // ***** CONSTANTS ***** //

  //***** LOCAL VARIABLES *****//

  var self = this;
  var actionBar = ActionBar;
  var snackbar = Snackbar;
  var ls = LoadingScreen;

  var mtService = new MergeTemplateService();
  var emailService = new EmailService();
  var templatesContainer;

  var mtListView;
  var cardsView;
  var settingsView;
  var runDialog;
  var deleteDialog;
  var repeatDialog;

  // jquery objects
  var base = $(baseHTML);
  var header = base.find('[data-id="header"]');

  //***** PUBLIC *****//

  window.runAllTemplates = mtService.runMerges;

  /**
   * Performs basic set up of the Mailman environment.
   *
   * @constructor
   */
  this.init = function(appendTo) {

    appendTo.append(base);

    actionBar.init(header);
    snackbar.init(base);
    mtListView = new MergeTemplatesView(base);

    settingsView = new SettingsView(base);
    runDialog = new Dialog(appendTo, 'Run this merge?', 'This will run your merge template. ' +
      'Emails will be sent to everyone in your specified sheet. Are you sure you want to merge?');

    deleteDialog = new Dialog(appendTo, 'Delete this merge template?', 'This will remove this merge template. ' +
      'You won\'t be able to send emails using it anymore. Are you sure you want to delete this merge template?');

    repeatDialog = new Dialog(appendTo, 'Repeatedly run this merge template?', 'This will cause this merge template ' +
      'to run regularly. This is an advanced feature and requires training. Please see IT Knowledge Management for ' +
      'tips on training and use. Are you sure you want to repeatedly run this merge template?');

    // PubSub
    PubSub.subscribe('Rules.add', function(msg, data) {
      snackbar.show('Merge template created.');

      mtListView.show();

      if (cardsView != null) {
        cardsView.cleanup();
      }
    });
    PubSub.subscribe('Rules.update', function(msg, data) {
      snackbar.show('Merge template updated.');
    });
    PubSub.subscribe('Rules.run', function(msg, data) {
      snackbar.show('Running merge "' + data.mergeData.title + '".');
    });

    PubSub.subscribe('Rules.add', function(msg, data) {
      mtListView.show();
    });

    PubSub.subscribe('Rules.update', function(msg, data) {
      mtListView.show();

      if (cardsView != null) {
        cardsView.cleanup();
      }
    });

    actionBar.setSettingsHandler(function() {
      settingsView.show();
      mtListView.hide();

      if (cardsView != null) {
        cardsView.hide();
      }
    });

    mtListView.setInstantHandler(function(e) {
      cardsView = createCardsView(base, StandardMailHandler);

      mtListView.hide();
      cardsView.show();
    });

    mtListView.setDeleteDialog(deleteDialog);
    mtListView.setDeleteHandler(function(template) {
      snackbar.show('Deleting merge template...');
      templatesContainer.remove(template);
    });

    mtListView.setEditHandler(function(template) {
      cardsView = createCardsView(base, StandardMailHandler, template);
      mtListView.hide();
      cardsView.show();
    });

    mtListView.setRunDialog(runDialog);
    mtListView.setRunHandler(function(template) {
      // This only occurs when the user clicks OK.
      emailService.send(template);
      PubSub.publish('Rules.run', template.toConfig());
    });

    mtListView.setRepeatDialog(repeatDialog);
    mtListView.setRepeatHandlers(
      function(template) {
        snackbar.show('Turning ON repeated merge...');
        templatesContainer.toggleRepeat(template);
      },
      function(template) {
        snackbar.show('Turning OFF repeated merge...');
        templatesContainer.toggleRepeat(template);
      });

    mtService.getAll().then(
      function(result) {
        templatesContainer = new MergeTemplateContainer(result);
        mtListView.setContainer(templatesContainer);
        ls.hide();
      },
      function(err) {
        console.error(err);
      }
    ).done();

    mtListView.show();
  };

  //***** PRIVATE *****//

  var createCardsView = function(base, handler, data) {

    var view = new CardsView(base, handler, data);

    view.done().then(
      function(template) {
        if (templatesContainer.indexOf(template.toConfig().id) !== -1) {
          templatesContainer.update(template);
        }
        else {
          templatesContainer.add(template.toConfig());
        }
      },
      function() {
        view.cleanup();

        mtListView.show();
        view.hide();
      }
    ).done();

    return view;
  };

  this.init(appendTo);
};

/**  */
module.exports = MailMan;
<commit_msg>Transitioning views is slightly more robust now.<commit_after>
var MergeTemplateContainer = require('./data/merge-template-container.js');
var MergeTemplateService = require('./services/merge-template-service.js');
var EmailService = require('./services/email-service.js');
var MergeTemplatesView = require('./views/merge-template/merge-templates-list-view.js');
var CardsView = require('./views/merge-setup/cards-view.js');
var SettingsView = require('./views/settings/settings-view.js');
var ActionBar = require('./views/action-bar/action-bar.js');
var Snackbar = require('./views/snackbar/snackbar.js');
var Dialog = require('./views/dialog/dialog.js');
var LoadingScreen = require('./views/loading/loading-screen.js');
var baseHTML = require('./main.html');
var PubSub = require('pubsub-js');

// Handlers
var StandardMailHandler = require('./controllers/standard-mail-handler.js');

var MailMan = function(appendTo) {

  // ***** CONSTANTS ***** //

  //***** LOCAL VARIABLES *****//

  var self = this;
  var actionBar = ActionBar;
  var snackbar = Snackbar;
  var ls = LoadingScreen;

  var mtService = new MergeTemplateService();
  var emailService = new EmailService();
  var templatesContainer;

  var mtListView;
  var cardsView;
  var settingsView;
  var runDialog;
  var deleteDialog;
  var repeatDialog;

  // jquery objects
  var base = $(baseHTML);
  var header = base.find('[data-id="header"]');

  //***** PUBLIC *****//

  window.runAllTemplates = mtService.runMerges;

  /**
   * Performs basic set up of the Mailman environment.
   *
   * @constructor
   */
  this.init = function(appendTo) {

    appendTo.append(base);

    actionBar.init(header);
    snackbar.init(base);
    mtListView = new MergeTemplatesView(base);

    settingsView = new SettingsView(base);
    runDialog = new Dialog(appendTo, 'Run this merge?', 'This will run your merge template. ' +
      'Emails will be sent to everyone in your specified sheet. Are you sure you want to merge?');

    deleteDialog = new Dialog(appendTo, 'Delete this merge template?', 'This will remove this merge template. ' +
      'You won\'t be able to send emails using it anymore. Are you sure you want to delete this merge template?');

    repeatDialog = new Dialog(appendTo, 'Repeatedly run this merge template?', 'This will cause this merge template ' +
      'to run regularly. This is an advanced feature and requires training. Please see IT Knowledge Management for ' +
      'tips on training and use. Are you sure you want to repeatedly run this merge template?');

    // PubSub
    PubSub.subscribe('Rules.add', function(msg, data) {
      snackbar.show('Merge template created.');

      showListView();

      if (cardsView != null) {
        cardsView.cleanup();
      }
    });
    PubSub.subscribe('Rules.run', function(msg, data) {
      snackbar.show('Running merge "' + data.mergeData.title + '".');
    });
    PubSub.subscribe('Rules.update', function(msg, data) {
      snackbar.show('Merge template updated.');

      showListView();

      if (cardsView != null) {
        cardsView.cleanup();
      }
    });

    actionBar.setSettingsHandler(showSettingsView);

    mtListView.setInstantHandler(function(e) {
      cardsView = createCardsView(base, StandardMailHandler);
      showCardsView();
    });

    mtListView.setDeleteDialog(deleteDialog);
    mtListView.setDeleteHandler(function(template) {
      snackbar.show('Deleting merge template...');
      templatesContainer.remove(template);
    });

    mtListView.setEditHandler(function(template) {
      cardsView = createCardsView(base, StandardMailHandler, template);
      showCardsView();
    });

    mtListView.setRunDialog(runDialog);
    mtListView.setRunHandler(function(template) {
      // This only occurs when the user clicks OK.
      emailService.send(template);
      PubSub.publish('Rules.run', template.toConfig());
    });

    mtListView.setRepeatDialog(repeatDialog);
    mtListView.setRepeatHandlers(
      function(template) {
        snackbar.show('Turning ON repeated merge...');
        templatesContainer.toggleRepeat(template);
      },
      function(template) {
        snackbar.show('Turning OFF repeated merge...');
        templatesContainer.toggleRepeat(template);
      });

    mtService.getAll().then(
      function(result) {
        templatesContainer = new MergeTemplateContainer(result);
        mtListView.setContainer(templatesContainer);
        ls.hide();
      },
      function(err) {
        console.error(err);
      }
    ).done();

    showListView();
  };

  //***** PRIVATE *****//

  var showListView = function() {
    mtListView.show();
    settingsView.hide();

    if (cardsView != null) {
      cardsView.hide();
    }
  };

  var showSettingsView = function() {
    settingsView.show();
    mtListView.hide();

    if (cardsView != null) {
      cardsView.hide();
    }
  };

  var showCardsView = function() {
    mtListView.hide();
    settingsView.hide();

    if (cardsView != null) {
      cardsView.show();
    }
  };

  var createCardsView = function(base, handler, data) {

    var view = new CardsView(base, handler, data);

    view.done().then(
      function(template) {
        if (templatesContainer.indexOf(template.toConfig().id) !== -1) {
          templatesContainer.update(template);
        }
        else {
          templatesContainer.add(template.toConfig());
        }
      },
      function() {
        view.cleanup();

        mtListView.show();
        view.hide();
      }
    ).done();

    return view;
  };

  this.init(appendTo);
};

/**  */
module.exports = MailMan;
<|endoftext|>


<commit_before>var express = require('express');
var async = require('async');
var fs = require('fs');
var _ = require('underscore');
var app = module.exports = express();

app.get('/', function(req, res){
  if (!req.query.lat || !req.query.lng){
    return res.status(400).json({
      msg: 'latlng is required'
    });
  }

  var lat = parseFloat(req.query.lat);
  var lng = parseFloat(req.query.lng);

  var num = req.query.num | 10;
  var url;
  Checkin.geoNear([lat, lng], {
    num: num,
    maxDistance: 1000,
    spherical: true
  }, function(err, checkins){
    if (err){
      return res.status(400).json({
        msg: err
      });
    }
    var ee = [];
    checkins.forEach(function(e){
      ee.push(e.obj.stats.checkinsCount);
    });

    ee.

    checkins =  _.sortBy(checkins, 'obj.stats.checkinsCount');
    console.log(checkins);
    //console.log(checkins.sort('obj.stats.checkinsCount'));
    //console.log(checkins);
    async.map(checkins, function(checkin, next){
      Fscategory.find({
          $text: {
            $search: checkin.obj.categories[0].name
          }
        },{
          score: {
            $meta: 'textScore'
          }
      }).sort({
        score: {
          $meta: 'textScore'
        }
      }).exec(function(err, fsc){
        if (!fsc){
          url = 'https://ss3.4sqi.net/img/categories_v2/arts_entertainment/default_bg_32.png';
          checkin.icon = url;
          next(null, checkin);
        } else {
          fsc = fsc[0];
          if (!fsc){
            console.log('category icon not found, replaced by some default icons...');
            console.log(checkin.obj.categories[0].name);
            url = 'https://ss3.4sqi.net/img/categories_v2/arts_entertainment/default_bg_32.png';
            checkin.icon = url;
            next(null,checkin);
          } else {
            url = fsc.icon.prefix + 'bg_32' + fsc.icon.suffix;
            checkin.icon = url;
            next(null,checkin);
          }
        }

      });
    }, function(err, checkinArray){
      return res.status(200).json({
        msgcode: 0,
        data: checkinArray
      });
    });
  });
});
<commit_msg>before pulling at Seoul airport<commit_after>var express = require('express');
var async = require('async');
var _ = require('underscore');
var app = module.exports = express();

app.get('/', function(req, res){
  if (!req.query.lat || !req.query.lng){
    return res.status(400).json({
      msg: 'latlng is required'
    });
  }

  var lat = parseFloat(req.query.lat);
  var lng = parseFloat(req.query.lng);

  var num = req.query.num | 10;
  var url;
  Checkin.geoNear([lat, lng], {
    num: num,
    maxDistance: 1000,
    spherical: true
  }, function(err, checkins){
    if (err){
      return res.status(400).json({
        msg: err
      });
    }
    var ee = [];
    checkins.forEach(function(e){
      ee.push(e.obj.stats.checkinsCount);
    });

    ee.

    checkins =  _.sortBy(checkins, 'obj.stats.checkinsCount');
    console.log(checkins);
    //console.log(checkins.sort('obj.stats.checkinsCount'));
    //console.log(checkins);
    async.map(checkins, function(checkin, next){
      Fscategory.find({
          $text: {
            $search: checkin.obj.categories[0].name
          }
        },{
          score: {
            $meta: 'textScore'
          }
      }).sort({
        score: {
          $meta: 'textScore'
        }
      }).exec(function(err, fsc){
        if (!fsc){
          url = 'https://ss3.4sqi.net/img/categories_v2/arts_entertainment/default_bg_32.png';
          checkin.icon = url;
          next(null, checkin);
        } else {
          fsc = fsc[0];
          if (!fsc){
            console.log('category icon not found, replaced by some default icons...');
            console.log(checkin.obj.categories[0].name);
            url = 'https://ss3.4sqi.net/img/categories_v2/arts_entertainment/default_bg_32.png';
            checkin.icon = url;
            next(null,checkin);
          } else {
            url = fsc.icon.prefix + 'bg_32' + fsc.icon.suffix;
            checkin.icon = url;
            next(null,checkin);
          }
        }

      });
    }, function(err, checkinArray){
      return res.status(200).json({
        msgcode: 0,
        data: checkinArray
      });
    });
  });
});
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// @flow

import React, { PureComponent } from 'react';
import explicitConnect from '../../utils/connect';

import { ProfileRootMessage } from './ProfileRootMessage';
import { getView } from '../../selectors/app';
import { getDataSource } from '../../selectors/url-state';

import type { AppViewState, State, DataSource } from 'firefox-profiler/types';

import type { ConnectedProps } from '../../utils/connect';

const LOADING_MESSAGES: { [string]: string } = Object.freeze({
  'from-addon': 'Importing the profile directly from Firefox...',
  'from-file': 'Reading the file and processing the profile...',
  local: 'Not implemented yet.',
  public: 'Downloading and processing the profile...',
  'from-url': 'Downloading and processing the profile...',
  compare: 'Reading and processing profiles...',
});

// TODO Switch to a proper i18n library
function fewTimes(count: number) {
  switch (count) {
    case 1:
      return 'once';
    case 2:
      return 'twice';
    default:
      return `${count} times`;
  }
}

type ProfileLoaderAnimationStateProps = {|
  +view: AppViewState,
  +dataSource: DataSource,
|};

type ProfileLoaderAnimationProps = ConnectedProps<
  {||},
  ProfileLoaderAnimationStateProps,
  {||}
>;

class ProfileLoaderAnimationImpl extends PureComponent<ProfileLoaderAnimationProps> {
  render() {
    const { view, dataSource } = this.props;
    const loadingMessage = LOADING_MESSAGES[dataSource];
    const message = loadingMessage ? loadingMessage : 'View not found';
    const showLoader = Boolean(loadingMessage);

    let additionalMessage = '';
    if (view.additionalData) {
      if (view.additionalData.message) {
        additionalMessage = view.additionalData.message;
      }

      if (view.additionalData.attempt) {
        const attempt = view.additionalData.attempt;
        additionalMessage += `\nTried ${fewTimes(attempt.count)} out of ${
          attempt.total
        }.`;
      }
    }

    return (
      <ProfileRootMessage
        message={message}
        additionalMessage={additionalMessage}
        showLoader={showLoader}
      />
    );
  }
}

export const ProfileLoaderAnimation = explicitConnect<
  {||},
  ProfileLoaderAnimationStateProps,
  {||}
>({
  mapStateToProps: (state: State) => ({
    view: getView(state),
    dataSource: getDataSource(state),
  }),
  component: ProfileLoaderAnimationImpl,
});
<commit_msg>migrated app/ProfileLoaderAnimation to using absolute path<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// @flow

import React, { PureComponent } from 'react';
import explicitConnect from 'firefox-profiler/utils/connect';

import { ProfileRootMessage } from 'firefox-profiler/components/app/ProfileRootMessage';
import { getView } from 'firefox-profiler/selectors/app';
import { getDataSource } from 'firefox-profiler/selectors/url-state';

import type { AppViewState, State, DataSource } from 'firefox-profiler/types';

import type { ConnectedProps } from 'firefox-profiler/utils/connect';

const LOADING_MESSAGES: { [string]: string } = Object.freeze({
  'from-addon': 'Importing the profile directly from Firefox...',
  'from-file': 'Reading the file and processing the profile...',
  local: 'Not implemented yet.',
  public: 'Downloading and processing the profile...',
  'from-url': 'Downloading and processing the profile...',
  compare: 'Reading and processing profiles...',
});

// TODO Switch to a proper i18n library
function fewTimes(count: number) {
  switch (count) {
    case 1:
      return 'once';
    case 2:
      return 'twice';
    default:
      return `${count} times`;
  }
}

type ProfileLoaderAnimationStateProps = {|
  +view: AppViewState,
  +dataSource: DataSource,
|};

type ProfileLoaderAnimationProps = ConnectedProps<
  {||},
  ProfileLoaderAnimationStateProps,
  {||}
>;

class ProfileLoaderAnimationImpl extends PureComponent<ProfileLoaderAnimationProps> {
  render() {
    const { view, dataSource } = this.props;
    const loadingMessage = LOADING_MESSAGES[dataSource];
    const message = loadingMessage ? loadingMessage : 'View not found';
    const showLoader = Boolean(loadingMessage);

    let additionalMessage = '';
    if (view.additionalData) {
      if (view.additionalData.message) {
        additionalMessage = view.additionalData.message;
      }

      if (view.additionalData.attempt) {
        const attempt = view.additionalData.attempt;
        additionalMessage += `\nTried ${fewTimes(attempt.count)} out of ${
          attempt.total
        }.`;
      }
    }

    return (
      <ProfileRootMessage
        message={message}
        additionalMessage={additionalMessage}
        showLoader={showLoader}
      />
    );
  }
}

export const ProfileLoaderAnimation = explicitConnect<
  {||},
  ProfileLoaderAnimationStateProps,
  {||}
>({
  mapStateToProps: (state: State) => ({
    view: getView(state),
    dataSource: getDataSource(state),
  }),
  component: ProfileLoaderAnimationImpl,
});
<|endoftext|>


<commit_before>var Module = require('module');
var join = require('path').join;
var debug = require('debug')('injective');

module.exports = exports = Injective;

var symbol = 'injective';
var injectiveProp = '@@injective';
var typeProp = 'type';
var depsProp = 'deps';
var singletonProp = 'singleton';
var injectProp = 'inject';
var injectDepsProp = 'injectDeps';

function Injective(config, cache) {
    this._config = config || {};
    this._cache = cache || {};
}

Injective.injective = null;

Injective.isInjective = function(module) {
    return injectiveProp in module.exports || (typeof Symbol !== 'undefined' ? Symbol.for(symbol) in module.exports : false);
};

Injective.isInjectable = function(module) {
    return Injective.isInjective(module) && injectProp in Injective.getInjectiveConfig(module) && typeof Injective.getInjectiveConfig(module)[injectProp] === 'function';
};

Injective.isInstantiable = function(module) {
    return Injective.isInjective(module) && typeProp in Injective.getInjectiveConfig(module);
};

Injective.getInjectiveConfig = function(module) {
    return module.exports[injectiveProp] || (typeof Symbol !== 'undefined' ? module.exports[Symbol.for(symbol)] : undefined);
};

Injective.install = function(config) {
    var extension = '.js';
    Module._extensions[extension] = function(func, module, filename) {
        module.injective = Injective.injective;
        func(module, filename);
        if (Injective.isInjectable(module)) {
            Injective.injective._inject(module, filename);
        }
    }.bind(Module, require.extensions[extension]);
    Injective.injective = new Injective(config);
    return Injective.injective;
};

Injective.prototype.create = function() {
    debug('Creating new context');
    Injective.injective = new Injective(this._config, Object.create(this._cache));
    return Injective.injective;
};

Injective.prototype.define = function(id, obj) {
    debug('Defining instance for ' + id);
    this._cache[id] = obj;
    return this;
};

/**
 * (Module, Array)
 * (Module, String)
 */
Injective.prototype.require = function(module, deps) {
    var self = this;
    Injective.injective = this;

    if (Array.isArray(deps)) {
        return deps.map(function(dep) {
            return self._resolveDep(module, dep);
        });
    }

    return this._resolveDep(module, deps);
};

Injective.prototype.requireNative = function(module, path) {
    var self = this;
    Injective.injective = this;
    var paths = this._resolvePath(path);

    if (Array.isArray(paths)) {
        return paths.map(function(path) {
            return module.require(self._resolvePath(path));
        });
    }

    return module.require(paths);
};

/**
 * Turn require id and its dependencies into instance
 * path path of dep
 * module the module that requiring the dep
 */
Injective.prototype._load = function(module, path) {
    debug('Loading ' + path + ' from module ' + module.id);

    // user-defined object
    if (path in this._cache) {
        debug('Pre-defined instance exists, using cache: ' + path);
        return this._cache[path];
    }

    var filename = Module._resolveFilename(this._resolvePath(path), module);

    debug('Resolved ' + path + ' -> ' + filename);

    // singleton
    if (filename in this._cache) {
        debug('Instance exists, using cache: ' + filename);
        return this._cache[filename];
    }

    // Make sure it is loaded by node and got injected
    var mod = module.require(filename);
    var cachedModule = Module._cache[filename];

    // non-singleton
    if (Injective.isInstantiable(cachedModule)) {
        return this._instantiate(cachedModule, filename);
    }

    debug('Rely on native require() to load: ' + filename);

    return mod;
};

// upgrade path if necessary
Injective.prototype._resolvePath = function(path) {
    if ('bundles' in this._config && path in this._config.bundles) {
        path = this._config.bundles[path];
    }

    if ('paths' in this._config && path in this._config.paths) {
        path = this._config.paths[path];
    }

    if (path[0] === '/') {
        path = join(process.cwd(), path);
    }

    return path;
};

Injective.prototype._resolveDep = function(module, dep) {
    var self = this;
    var resolvedPath = self._resolvePath(dep);

    // Bundles
    if (Array.isArray(resolvedPath)) {
        return resolvedPath.map(function(dep) {
            return self._load(module, dep);
        });
    }

    return this._load(module, dep);
};

Injective.prototype._inject = function(module, filename) {
    var self = this;
    debug('Injecting dependency into ' + filename);
    var injectiveCfg = Injective.getInjectiveConfig(module);
    var resolvedDeps = injectDepsProp in injectiveCfg ? injectiveCfg[injectDepsProp].map(function(dep) {
        return self._resolveDep(module, dep);
    }) : [];
    injectiveCfg[injectProp].apply(null, resolvedDeps);
    return module.exports;
};

/**
 * Inject dependency and/or create (singleton) instance
 */
Injective.prototype._instantiate = function(module, filename) {
    debug('Creating new instance for ' + filename);
    var self = this;
    var injectiveCfg = Injective.getInjectiveConfig(module);
    var resolvedDeps = depsProp in injectiveCfg ? injectiveCfg[depsProp].map(function(dep) {
        return self._resolveDep(module, dep);
    }) : [];
    var instance;
    var type = injectiveCfg[typeProp];

    switch (type) {
        case 'factory':
            instance = module.exports.apply(module.exports, resolvedDeps);
            break;
        case 'constructor':
            instance = new(Function.prototype.bind.apply(module.exports, [null].concat(resolvedDeps)));
            break;
        default:
            throw new Error('Unknown type ' + type + ' defined in ' + filename);
    }

    if (injectiveCfg[singletonProp]) {
        this.define(filename, instance);
    }

    return instance;
};<commit_msg>no longer define a per module injective instance<commit_after>var Module = require('module');
var join = require('path').join;
var debug = require('debug')('injective');

module.exports = exports = Injective;

var symbol = 'injective';
var injectiveProp = '@@injective';
var typeProp = 'type';
var depsProp = 'deps';
var singletonProp = 'singleton';
var injectProp = 'inject';
var injectDepsProp = 'injectDeps';

function Injective(config, cache) {
    this._config = config || {};
    this._cache = cache || {};

    this.define('injective', this);
}

Injective.isInjective = function(module) {
    return injectiveProp in module.exports || (typeof Symbol !== 'undefined' ? Symbol.for(symbol) in module.exports : false);
};

Injective.isInjectable = function(module) {
    return Injective.isInjective(module) && injectProp in Injective.getInjectiveConfig(module) && typeof Injective.getInjectiveConfig(module)[injectProp] === 'function';
};

Injective.isInstantiable = function(module) {
    return Injective.isInjective(module) && typeProp in Injective.getInjectiveConfig(module);
};

Injective.getInjectiveConfig = function(module) {
    return module.exports[injectiveProp] || (typeof Symbol !== 'undefined' ? module.exports[Symbol.for(symbol)] : undefined);
};

Injective.install = function(config) {
    var injective = new Injective(config);

    if (depsProp in config) {
        injective.require(require.main, config[depsProp]);
    }

    var extension = '.js';
    Module._extensions[extension] = function(func, module, filename) {
        func(module, filename);
        if (Injective.isInjectable(module)) {
            injective._inject(module, filename);
        }
    }.bind(Module, require.extensions[extension]);

    return injective;
};

Injective.prototype.create = function() {
    debug('Creating new context');
    return new Injective(this._config, Object.create(this._cache));
};

Injective.prototype.define = function(id, obj) {
    debug('Defining instance for ' + id);
    this._cache[id] = obj;
    return this;
};

/**
 * (Module, Array)
 * (Module, String)
 */
Injective.prototype.require = function(module, deps) {
    var self = this;

    if (Array.isArray(deps)) {
        return deps.map(function(dep) {
            return self._resolveDep(module, dep);
        });
    }

    return this._resolveDep(module, deps);
};

Injective.prototype.requireNative = function(module, path) {
    var self = this;
    Injective.injective = this;
    var paths = this._resolvePath(path);

    if (Array.isArray(paths)) {
        return paths.map(function(path) {
            return module.require(self._resolvePath(path));
        });
    }

    return module.require(paths);
};

/**
 * Turn require id and its dependencies into instance
 * path path of dep
 * module the module that requiring the dep
 */
Injective.prototype._load = function(module, path) {
    debug('Loading ' + path + ' from module ' + module.id);

    // user-defined object
    if (path in this._cache) {
        debug('Pre-defined instance exists, using cache: ' + path);
        return this._cache[path];
    }

    var filename = Module._resolveFilename(this._resolvePath(path), module);

    debug('Resolved ' + path + ' -> ' + filename);

    // singleton
    if (filename in this._cache) {
        debug('Instance exists, using cache: ' + filename);
        return this._cache[filename];
    }

    // Make sure it is loaded by node and got injected
    var mod = module.require(filename);
    var cachedModule = Module._cache[filename];

    // non-singleton
    if (Injective.isInstantiable(cachedModule)) {
        return this._instantiate(cachedModule, filename);
    }

    debug('Rely on native require() to load: ' + filename);

    return mod;
};

// upgrade path if necessary
Injective.prototype._resolvePath = function(path) {
    if ('bundles' in this._config && path in this._config.bundles) {
        path = this._config.bundles[path];
    }

    if ('paths' in this._config && path in this._config.paths) {
        path = this._config.paths[path];
    }

    if (path[0] === '/') {
        path = join(process.cwd(), path);
    }

    return path;
};

Injective.prototype._resolveDep = function(module, dep) {
    var self = this;
    var resolvedPath = self._resolvePath(dep);

    // Bundles
    if (Array.isArray(resolvedPath)) {
        return resolvedPath.map(function(dep) {
            return self._load(module, dep);
        });
    }

    return this._load(module, dep);
};

Injective.prototype._inject = function(module, filename) {
    var self = this;
    debug('Injecting dependency into ' + filename);
    var injectiveCfg = Injective.getInjectiveConfig(module);
    var resolvedDeps = injectDepsProp in injectiveCfg ? injectiveCfg[injectDepsProp].map(function(dep) {
        return self._resolveDep(module, dep);
    }) : [];
    injectiveCfg[injectProp].apply(null, resolvedDeps);
    return module.exports;
};

/**
 * Inject dependency and/or create (singleton) instance
 */
Injective.prototype._instantiate = function(module, filename) {
    debug('Creating new instance for ' + filename);
    var self = this;
    var injectiveCfg = Injective.getInjectiveConfig(module);
    var resolvedDeps = depsProp in injectiveCfg ? injectiveCfg[depsProp].map(function(dep) {
        return self._resolveDep(module, dep);
    }) : [];
    var instance;
    var type = injectiveCfg[typeProp];

    switch (type) {
        case 'factory':
            instance = module.exports.apply(module.exports, resolvedDeps);
            break;
        case 'constructor':
            instance = new(Function.prototype.bind.apply(module.exports, [null].concat(resolvedDeps)));
            break;
        default:
            throw new Error('Unknown type ' + type + ' defined in ' + filename);
    }

    if (injectiveCfg[singletonProp]) {
        this.define(filename, instance);
    }

    return instance;
};<|endoftext|>