<commit_before>var Collection = require('ampersand-collection');
var lodashMixin = require('ampersand-collection-lodash-mixin');
var parentMixin = require('./collection-parent-mixin');
var assert = require('assert');
var _ = require('lodash');
var Type = require('./types/type');
var types = require('./types');

/**
 * Helper function to get the type name given a value.
 *
 * @example
 *   getTypeName(false)
 *   // 'Boolean'
 *   getTypeName({})
 *   // 'Object'
 *   getTypeName(BSON.ObjectID())
 *   // 'ObjectID'
 * @param  {Any} value -Value to get type for
 * @return {String}
 */
var getTypeName = function(value) {
  var T;
  if (_.has(value, '_bsontype')) {
    T = value._bsontype;
  } else {
    T = Object.prototype.toString.call(value).replace(/\[object (\w+)\]/, '$1');
  }
  // don't want to create naming conflict with javascript Object
  if (T === 'Object') {
    T = 'Document';
  }
  return T;
};

module.exports = Collection.extend(lodashMixin, parentMixin, {
  modelType: 'TypeCollection',
  mainIndex: 'name',
  /**
   * Sort by probability descending, with Undefined always last.
   * Note that the application has to call collection.sort() explicitly;
   * it will not be sorted automatically for performance reasons.
   * @param {ampersand.State} model - The model to get the sort value for.
   * @return {Number}
   */
  comparator: function(model) {
    if (model.getId() === 'Undefined') return 0;
    return model.probability * -1;
  },
  model: function(attrs, options) {
    var Klass = types[attrs.name];
    assert(Klass, 'No value type for ' + attrs.name);
    return new Klass(attrs, options);
  },
  isModel: function(model) {
    return model instanceof Type;
  },
  /**
   * adds a new value to the correct type, and creates the type first
   * if it doesn't exist yet.
   *
   * @param {Any} value - value to be added
   */
  addToType: function(value) {
    var newTypeAdded = false;
    var typeName = getTypeName(value);
    // get or create type
    var type = this.get(typeName);
    if (!type) {
      type = this.add({
        name: typeName
      });
      newTypeAdded = true;
    }
    // leave it to type to add the value
    type.parse(value);
    if (newTypeAdded && this.parent) {
      this.parent.trigger('change:types.length');
    }
  },
  serialize: function() {
    this.sort();
    return Collection.prototype.serialize.call(this);
  }
});
<commit_msg>INT-497: Make TypeCollection use sort API correctly.<commit_after>var Collection = require('ampersand-collection');
var lodashMixin = require('ampersand-collection-lodash-mixin');
var parentMixin = require('./collection-parent-mixin');
var assert = require('assert');
var _ = require('lodash');
var Type = require('./types/type');
var types = require('./types');

/**
 * Helper function to get the type name given a value.
 *
 * @example
 *   getTypeName(false)
 *   // 'Boolean'
 *   getTypeName({})
 *   // 'Object'
 *   getTypeName(BSON.ObjectID())
 *   // 'ObjectID'
 * @param  {Any} value -Value to get type for
 * @return {String}
 */
var getTypeName = function(value) {
  var T;
  if (_.has(value, '_bsontype')) {
    T = value._bsontype;
  } else {
    T = Object.prototype.toString.call(value).replace(/\[object (\w+)\]/, '$1');
  }
  // don't want to create naming conflict with javascript Object
  if (T === 'Object') {
    T = 'Document';
  }
  return T;
};

module.exports = Collection.extend(lodashMixin, parentMixin, {
  modelType: 'TypeCollection',
  mainIndex: 'name',
  /**
   * Sort by probability descending, with Undefined always last.
   * Note that the application has to call collection.sort() explicitly;
   * it will not be sorted automatically for performance reasons.
   * @param {ampersand.State} model - The model to get the sort value for.
   * @return {Number}
   */
  comparator: function(model) {
    if (model.getId() === 'Undefined') return 0;
    return model.probability * -1;
  },
  model: function(attrs, options) {
    var Klass = types[attrs.name];
    assert(Klass, 'No value type for ' + attrs.name);
    return new Klass(attrs, options);
  },
  isModel: function(model) {
    return model instanceof Type;
  },
  /**
   * adds a new value to the correct type, and creates the type first
   * if it doesn't exist yet.
   *
   * @param {Any} value - value to be added
   */
  addToType: function(value) {
    var field = this.parent;

    var typeName = getTypeName(value);
    var added = false;
    // get or create type
    var type = this.get(typeName);
    if (!type) {
      type = this.add({name: typeName}, {sort: true});
      added = true;
    }
    // leave it to type to add the value
    type.parse(value);

    if(added && field){
      field.trigger('change:types.length');
    }
  },
  serialize: function() {
    return Collection.prototype.serialize.call(this);
  }
});
<|endoftext|>


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

CLASS({
  package: 'foam.graphics',
  name:  'CView',
  label: 'CView',

  requires: [
    'foam.graphics.PositionedCViewView',
    'foam.graphics.CViewView'
  ],

  traits: [ 'foam.patterns.ChildTreeTrait' ],

  documentation: function() {/*
      The base class for a canvas item. A $$DOC{ref:'.'} can be directly inserted
      into the DOM with $$DOC{ref:'.write'}, and will generate a $$DOC{ref:'CViewView'}
      wrapper.

$$DOC{ref:'.'} submodels directly nest inside each other, with a single
      root $$DOC{ref:'.'} attached to the canvas. Use $$DOC{ref:'.addChild'} to attach a new
      $$DOC{ref:'.'} to the scene graph:

            var rootNode = this.X.CView.create({width:300, height:200});

            rootNode.write(this.X); // a CViewView wrapper is created for us

            rootNode.addChild(this.X.Circle.create({x:30, y:50, radius: 30, color: 'blue'});

            rootNode.addChild(this.X.Label.create({x: 50, y: 30, text: "Hello", color: 'black'});

When modeling your own $$DOC{ref:'foam.graphics.CView'} submodel, override $$DOC{ref:'.paintSelf'}
      to render your content. Children will automatically be painted for you. For more direct
      control over child rendering, override $$DOC{ref:'.paint'}.
    */},

  properties: [
    {
      name:  'view',
      type:  'Canvas2',
      postSet: function(_, view) {
        for ( var key in this.children ) {
          var child = this.children[key];
          child.view = view;
          if ( view ) child.addListener(view.paint);
        }
      },
      transient: true,
      hidden: true,
      documentation: function() {/* The canvas view this scene draws into */ }
    },
    {
      name: 'children',
      hidden: true
    },
    {
      name: 'canvas',
      getter: function() { debugger; },
      transient: true,
      hidden: true,
      documentation: function() {/* Safe getter for the canvas view this scene draws into */ }
    },
    {
      name: '$',
      defaultValueFn: function() { return this.view && this.view.$; },
      transient: true,
      hidden: true,
      documentation: function() {/* Safe getter for the canvas DOM element this scene draws into */ }
    },
    {
      name: 'state',
      defaultValue: 'initial',
      documentation: function() {/* Indicates if canvas setup is in progress ('initial'),
                                  or ready to paint ('active'). */}
    },
    {
      name: 'suspended',
      model_: 'BooleanProperty',
      defaultValue: false,
      documentation: function() {/*
          Suspend painting. While this property is true, this
          $$DOC{ref:'foam.graphics.CView'} will not paint itself or its
          children.
        */},
    },
    {
      name: 'className',
      help: 'CSS class name(s), space separated. Used if adapted with a CViewView.',
      defaultValue: '',
      documentation: function() {/* CSS class name(s), space separated.
          Only used if this is the root node adapted with a $$DOC{ref:'CViewView'}. */},
      postSet: function() {
        if ( ! this.$ ) return;
        this.$.className = this.className;
      }
    },
    {
      model_: 'FloatProperty',
      name: 'x',
      defaultValue: 0,
      documentation: function() {/*
          The X offset of this view relative to its parent. */}
    },
    {
      model_: 'FloatProperty',
      name: 'y',
      defaultValue: 0,
      documentation: function() {/*
          The Y offset of this view relative to its parent. */}
    },
    {
      model_: 'FloatProperty',
      name: 'a',
      label: 'Rotation',
      defaultValue: 0
    },
    {
      model_: 'FloatProperty',
      name: 'scaleX',
      defaultValue: 1
    },
    {
      model_: 'FloatProperty',
      name: 'scaleY',
      defaultValue: 1
    },
    {
      name: 'canvasX',
      hidden: true,
      getter: function() { return this.x + ( this.parent ? this.parent.canvasX : 0 ); }
    },
    {
      name: 'canvasY',
      hidden: true,
      getter: function() { return this.y + ( this.parent ? this.parent.canvasY : 0 ); }
    },
    {
      model_: 'IntProperty',
      name:  'width',
      defaultValue: 10,
      documentation: function() {/*
          The width of this view. Painting is not automatically clipped, so a view
          may render outside of its apparent rectangle. */}
    },
    {
      model_: 'IntProperty',
      name:  'height',
      defaultValue: 10,
      documentation: function() {/*
          The height of this view. Painting is not automatically clipped, so a view
          may render outside of its apparent rectangle. */}
    },
    {
      model_: 'FloatProperty',
      name:  'alpha',
      defaultValue: 1,
      documentation: function() {/*
          The desired opacity of the content, from 0:transparent to 1:opaque.
          Child views do not inherit and are not limited by this value. */}
    },
    {
      name:  'color',
      label: 'Foreground Color',
      type:  'String',
      defaultValue: 'black',
      documentation: function() {/*
          The foreground color for rendering primary content. */}
    },
    {
      name:  'background',
      label: 'Background Color',
      type:  'String',
      defaultValue: 'white',
      documentation: function() {/*
          The optional background color for opaque items that $$DOC{ref:'.erase'}
          their background. */}
    },
    {
      name: 'font',
      documentation: function() {/*
          The font to use for rendering text, in CSS string format: '24px Roboto'. */}
    },
    {
      name: 'clipped',
      model_: 'BooleanProperty',
      defaultValue: false
    }
  ],

  methods: {
    toView_: function() { /* Internal. Creates a CViewView wrapper. */
      if ( ! this.view ) {
        var params = {cview: this};
        if ( this.className )   params.className   = this.className;
        if ( this.tooltip )     params.tooltip     = this.tooltip;
        if ( this.speechLabel ) params.speechLabel = this.speechLabel;
        if ( this.tabIndex )    params.tabIndex    = this.tabIndex;
        if ( this.role )        params.role        = this.role;
        if ( this.data$ )       params.data$       = this.data$;
        this.view = this.CViewView.create(params);
      }
      return this.view;
    },
    toGLView_: function() { /* internal, creates a CViewGLView wrapper for 3d canvases */
      var model = this.X.lookup('foam.graphics.webgl.CViewGLView')
      if ( model ) return model.create({ sourceView: this });
      return '';
    },
    toPositionedView_: function() { /* Internal. Creates a PositionedCViewView wrapper. */
      if ( ! this.view ) {
        var params = {cview: this};
        if ( this.className ) params.className = this.className;
        this.view = this.PositionedCViewView.create(params);
      }
      return this.view;
    },
    initCView: function() { /* Override in submodels for initialization. Callled
          once on first $$DOC{ref:'.paint'} when transitioning from 'initial'
          to 'active' '$$DOC{ref:'.state'}. */ },

    write: function(opt_X) { /* Inserts this $$DOC{ref:'foam.graphics.CView'} into the DOM
                                   with an $$DOC{ref:'foam.graphics.AbstractCViewView'} wrapper. */
      var X = opt_X || this.X;
      X.writeView(this.toView_(), X);
    },
    addChild: function(child) { /* Adds a child $$DOC{ref:'foam.graphics.CView'} to the scene
                                   under this. */
      this.SUPER(child);

      if ( child === this ) debugger;

      if ( this.view ) {
        child.view = this.view;
        child.addListener(this.view.paint);
        try { this.view.paint(); } catch (x) { }
      }
      return this;
    },

    removeChild: function(child) { /* Removes a child from the scene. */
      this.SUPER(child);
      child.view = undefined;
      if ( this.view ) {
        child.removeListener(this.view.paint);
        this.view.paint();
      }
      return this;
    },

    removeAllChildren: function(child) { /* Removes all children from the scene. */
      for ( var i = this.children.length-1 ; i >= 0 ; i-- )
        this.removeChild(this.children[i]);
      return this;
    },

    findChildAt: function(x, y) {
      var c2 = { x: x, y: y, r: 1 };

      var cs = this.children;
      // Start from the end to find the child in the foreground
      for ( var i = cs.length-1 ; i >= 0 ; i-- ) {
        var c1 = cs[i];
        if ( c1.intersects && c1.intersects(c2) ) return c1;
      }
    },

    erase: function(canvas) { /* Wipes the canvas area of this $$DOC{ref:'.'}. Primarily used
                          by the root node to clear the entire canvas, but an opaque child
                          may choose to erase its own area, if required. */
// Why do we do a clearRect()?  It causes problems when opacity isn't 1.
//      canvas.clearRect(0, 0, this.width, this.height);
      canvas.fillStyle = this.background;
      canvas.fillRect(0, 0, this.width, this.height);
    },

    paintChildren: function(c) { /* Paints each child. */
      for ( var i = 0 ; i < this.children.length ; i++ ) {
        var child = this.children[i];
        c.save();
        c.beginPath(); // reset any existing path (canvas.restore() does not affect path)
        child.paint(c);
        c.restore();
      }
    },

    paintSelf: function(canvas) { /* Implement this in sub-models to do your painting. */ },

    paint: function(canvas) { /* Translates the canvas to our ($$DOC{ref:'.x'}, $$DOC{ref:'.y'}),
                          does a $$DOC{ref:'.paintSelf'} then paints all the children. */
      if ( ! this.$ ) return; // no canvas element, so do nothing
      if ( ! this.width || ! this.height ) return;
      if ( this.state === 'initial' ) {
        this.state = 'active';
        this.initCView();
      }
      if ( this.suspended ) return; // we allowed initialization, but if suspended don't paint

      var c = canvas || this.view.canvas;
      c.save();
      c.globalAlpha *= this.alpha;
      this.transform(c);
      if ( this.clipped ) {
        c.rect(0,0,this.width,this.height);
        c.clip();
      }
      this.paintSelf(c);
      this.paintChildren(c);
      c.restore();
    },

    transform: function(canvas) {
      canvas.translate(this.x, this.y);
      canvas.scale(this.scaleX, this.scaleY);
      if ( this.a ) canvas.rotate(this.a);
    },

    scale: function(s) {
      this.scaleX = this.scaleY = s;
    },

    mapToParent: function(point) { /* Maps a coordinate from this to our parents'. */
      point.x += this.x;
      point.y += this.y;
      return point;
    },

    mapToCanvas: function(point) { /* Maps a coordinate from this to the canvas.
                    Useful for sharing a point between sibling or cousin items. */
      this.mapToParent(point);
      if ( this.parent && this.parent.mapToCanvas )
        this.parent.mapToCanvas(point);
      return point;
    },

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

CLASS({
  package: 'foam.graphics',
  name:  'CView',
  label: 'CView',

  requires: [
    'foam.graphics.PositionedCViewView',
    'foam.graphics.CViewView'
  ],

  traits: [ 'foam.patterns.ChildTreeTrait' ],

  documentation: function() {/*
      The base class for a canvas item. A $$DOC{ref:'.'} can be directly inserted
      into the DOM with $$DOC{ref:'.write'}, and will generate a $$DOC{ref:'CViewView'}
      wrapper.

$$DOC{ref:'.'} submodels directly nest inside each other, with a single
      root $$DOC{ref:'.'} attached to the canvas. Use $$DOC{ref:'.addChild'} to attach a new
      $$DOC{ref:'.'} to the scene graph:

            var rootNode = this.X.CView.create({width:300, height:200});

            rootNode.write(this.X); // a CViewView wrapper is created for us

            rootNode.addChild(this.X.Circle.create({x:30, y:50, radius: 30, color: 'blue'});

            rootNode.addChild(this.X.Label.create({x: 50, y: 30, text: "Hello", color: 'black'});

When modeling your own $$DOC{ref:'foam.graphics.CView'} submodel, override $$DOC{ref:'.paintSelf'}
      to render your content. Children will automatically be painted for you. For more direct
      control over child rendering, override $$DOC{ref:'.paint'}.
    */},

  properties: [
    {
      name:  'view',
      type:  'Canvas2',
      postSet: function(_, view) {
        for ( var key in this.children ) {
          var child = this.children[key];
          child.view = view;
          if ( view ) child.addListener(view.paint);
        }
      },
      transient: true,
      hidden: true,
      documentation: function() {/* The canvas view this scene draws into */ }
    },
    {
      name: 'children',
      hidden: true
    },
    {
      name: 'canvas',
      getter: function() { debugger; },
      transient: true,
      hidden: true,
      documentation: function() {/* Safe getter for the canvas view this scene draws into */ }
    },
    {
      name: '$',
      defaultValueFn: function() { return this.view && this.view.$; },
      transient: true,
      hidden: true,
      documentation: function() {/* Safe getter for the canvas DOM element this scene draws into */ }
    },
    {
      name: 'state',
      defaultValue: 'initial',
      documentation: function() {/* Indicates if canvas setup is in progress ('initial'),
                                  or ready to paint ('active'). */}
    },
    {
      name: 'suspended',
      model_: 'BooleanProperty',
      defaultValue: false,
      documentation: function() {/*
          Suspend painting. While this property is true, this
          $$DOC{ref:'foam.graphics.CView'} will not paint itself or its
          children.
        */},
    },
    {
      name: 'className',
      help: 'CSS class name(s), space separated. Used if adapted with a CViewView.',
      defaultValue: '',
      documentation: function() {/* CSS class name(s), space separated.
          Only used if this is the root node adapted with a $$DOC{ref:'CViewView'}. */},
      postSet: function() {
        if ( ! this.$ ) return;
        this.$.className = this.className;
      }
    },
    {
      model_: 'FloatProperty',
      name: 'x',
      defaultValue: 0,
      documentation: function() {/*
          The X offset of this view relative to its parent. */}
    },
    {
      model_: 'FloatProperty',
      name: 'y',
      defaultValue: 0,
      documentation: function() {/*
          The Y offset of this view relative to its parent. */}
    },
    {
      model_: 'FloatProperty',
      name: 'a',
      label: 'Rotation',
      defaultValue: 0
    },
    {
      model_: 'FloatProperty',
      name: 'scaleX',
      defaultValue: 1
    },
    {
      model_: 'FloatProperty',
      name: 'scaleY',
      defaultValue: 1
    },
    {
      name: 'canvasX',
      hidden: true,
      getter: function() { return this.x + ( this.parent ? this.parent.canvasX : 0 ); }
    },
    {
      name: 'canvasY',
      hidden: true,
      getter: function() { return this.y + ( this.parent ? this.parent.canvasY : 0 ); }
    },
    {
      model_: 'IntProperty',
      name:  'width',
      defaultValue: 10,
      documentation: function() {/*
          The width of this view. Painting is not automatically clipped, so a view
          may render outside of its apparent rectangle. */}
    },
    {
      model_: 'IntProperty',
      name:  'height',
      defaultValue: 10,
      documentation: function() {/*
          The height of this view. Painting is not automatically clipped, so a view
          may render outside of its apparent rectangle. */}
    },
    {
      model_: 'FloatProperty',
      name:  'alpha',
      defaultValue: 1,
      documentation: function() {/*
          The desired opacity of the content, from 0:transparent to 1:opaque.
          Child views do not inherit and are not limited by this value. */}
    },
    {
      name:  'color',
      label: 'Foreground Color',
      type:  'String',
      defaultValue: 'black',
      documentation: function() {/*
          The foreground color for rendering primary content. */}
    },
    {
      name:  'background',
      label: 'Background Color',
      type:  'String',
      defaultValue: 'white',
      documentation: function() {/*
          The optional background color for opaque items that $$DOC{ref:'.erase'}
          their background. */}
    },
    {
      name: 'font',
      documentation: function() {/*
          The font to use for rendering text, in CSS string format: '24px Roboto'. */}
    },
    {
      name: 'clipped',
      model_: 'BooleanProperty',
      defaultValue: false
    }
  ],

  methods: {
    toView_: function() { /* Internal. Creates a CViewView wrapper. */
      if ( ! this.view ) {
        var params = {cview: this};
        if ( this.className )   params.className   = this.className;
        if ( this.tooltip )     params.tooltip     = this.tooltip;
        if ( this.speechLabel ) params.speechLabel = this.speechLabel;
        if ( this.tabIndex )    params.tabIndex    = this.tabIndex;
        if ( this.role )        params.role        = this.role;
        if ( this.data$ )       params.data$       = this.data$;
        this.view = this.CViewView.create(params);
      }
      return this.view;
    },
    toGLView_: function() { /* internal, creates a CViewGLView wrapper for 3d canvases */
      var model = this.X.lookup('foam.graphics.webgl.CViewGLView')
      if ( model ) return model.create({ sourceView: this });
      return '';
    },
    toPositionedView_: function() { /* Internal. Creates a PositionedCViewView wrapper. */
      if ( ! this.view ) {
        var params = {cview: this};
        if ( this.className ) params.className = this.className;
        this.view = this.PositionedCViewView.create(params);
      }
      return this.view;
    },
    initCView: function() { /* Override in submodels for initialization. Callled
          once on first $$DOC{ref:'.paint'} when transitioning from 'initial'
          to 'active' '$$DOC{ref:'.state'}. */ },

    write: function(opt_X) { /* Inserts this $$DOC{ref:'foam.graphics.CView'} into the DOM
                                   with an $$DOC{ref:'foam.graphics.AbstractCViewView'} wrapper. */
      var X = opt_X || this.X;
      X.writeView(this.toView_(), X);
    },
    addChild: function(child) { /* Adds a child $$DOC{ref:'foam.graphics.CView'} to the scene
                                   under this. */
      this.SUPER(child);

      if ( child === this ) debugger;

      if ( this.view ) {
        child.view = this.view;
        child.addListener(this.view.paint);
        try { this.view.paint(); } catch (x) { }
      }
      return this;
    },

    removeChild: function(child) { /* Removes a child from the scene. */
      this.SUPER(child);
      child.view = undefined;
      if ( this.view ) {
        child.removeListener(this.view.paint);
        this.view.paint();
      }
      return this;
    },

    removeAllChildren: function(child) { /* Removes all children from the scene. */
      for ( var i = this.children.length-1 ; i >= 0 ; i-- )
        this.removeChild(this.children[i]);
      return this;
    },

    findChildAt: function(x, y) {
      var c2 = { x: x, y: y, r: 1 };

      var cs = this.children;
      // Start from the end to find the child in the foreground
      for ( var i = cs.length-1 ; i >= 0 ; i-- ) {
        var c1 = cs[i];
        if ( c1.intersects && c1.intersects(c2) ) return c1;
      }
    },

    erase: function(canvas) { /* Wipes the canvas area of this $$DOC{ref:'.'}. Primarily used
                          by the root node to clear the entire canvas, but an opaque child
                          may choose to erase its own area, if required. */
// Why do we do a clearRect()?  It causes problems when opacity isn't 1.
//      canvas.clearRect(0, 0, this.width, this.height);
      canvas.fillStyle = this.background;
      canvas.fillRect(0, 0, this.width, this.height);
    },

    paintChildren: function(c) { /* Paints each child. */
      for ( var i = 0 ; i < this.children.length ; i++ ) {
        var child = this.children[i];
        c.save();
        c.beginPath(); // reset any existing path (canvas.restore() does not affect path)
        child.paint(c);
        c.restore();
      }
    },

    paintSelf: function(canvas) { /* Implement this in sub-models to do your painting. */ },

    paint: function(canvas) { /* Translates the canvas to our ($$DOC{ref:'.x'}, $$DOC{ref:'.y'}),
                          does a $$DOC{ref:'.paintSelf'} then paints all the children. */
      if ( ! this.width || ! this.height ) return;
      if ( this.state === 'initial' ) {
        this.state = 'active';
        this.initCView();
      }
      if ( this.suspended ) return; // we allowed initialization, but if suspended don't paint

      var c = canvas || this.view.canvas;
      c.save();
      c.globalAlpha *= this.alpha;
      this.transform(c);
      if ( this.clipped ) {
        c.rect(0,0,this.width,this.height);
        c.clip();
      }
      this.paintSelf(c);
      this.paintChildren(c);
      c.restore();
    },

    transform: function(canvas) {
      canvas.translate(this.x, this.y);
      canvas.scale(this.scaleX, this.scaleY);
      if ( this.a ) canvas.rotate(this.a);
    },

    scale: function(s) {
      this.scaleX = this.scaleY = s;
    },

    mapToParent: function(point) { /* Maps a coordinate from this to our parents'. */
      point.x += this.x;
      point.y += this.y;
      return point;
    },

    mapToCanvas: function(point) { /* Maps a coordinate from this to the canvas.
                    Useful for sharing a point between sibling or cousin items. */
      this.mapToParent(point);
      if ( this.parent && this.parent.mapToCanvas )
        this.parent.mapToCanvas(point);
      return point;
    },

    destroy: function() {}
  }
});
<|endoftext|>


<commit_before>var _ = require('underscore');
var Mosaic = require('mosaic-commons');
var Lunr = require('lunr');
var ResourceFieldsAccessor = require('./ResourceFieldsAccessor');
Lunr.utils.warn = function(msg) {
    // Silently ignore messages
};
// Change the default tokenizer
Lunr.tokenizer = function(obj) {
    var result = [];
    if (!arguments.length || obj == null || obj == undefined)
        return result;
    if (Array.isArray(obj))
        return result = obj.map(function(t) {
            return t.toLowerCase();
        });
    return result = obj.toString().trim().toLowerCase().split(
            /[\(\)\s\-\.\/\/\'\’\"]+/);
}

// Filtering of empty words
function emptyStopWordFilter(token) {
    if (emptyStopWordFilter.stopWords.indexOf(token) === -1)
        return token;
}
emptyStopWordFilter.stopWords = new Lunr.SortedSet();
emptyStopWordFilter.stopWords.length = 1;
emptyStopWordFilter.stopWords.elements = [ "" ];

// Filter numbers
function numberFilters(token) {
    if (!token.match(/^\d+$/gim))
        return token;
}
// Filter/transform french words
function frenchWordsFilter(token) {
    token = token.toLowerCase();
    if (token === 'd' || token === 'de' || token === 'des' || token === 'la'
            || token === 'le' || token === 'à' || token === 'au'
            || token === 'aux')
        return;
    return token;
}

var LunrWrapper = Mosaic.Class.extend(Mosaic.Events.prototype, {

    /** Normalizes texts - remove all accented characters */
    normalizeText : getNormalizationFunction({
        '[ùûü]' : 'u',
        '[ÿ]' : 'y',
        '[àâ]' : 'a',
        '[æ]' : 'ae',
        '[ç]' : 'c',
        '[éèêë]' : 'e',
        '[ïî]' : 'i',
        '[ô]' : 'o',
        '[œ]' : 'oe',
    }),

    /**
     */
    initialize : function(options) {
        this.setOptions(options);
        this.index(this.options.resources);
    },

    index : function(resources, reset) {
        var that = this;
        if (reset) {
            delete that._indexPromise;
        }
        return that._getLunrIndex().then(function(index) {
            return that._reindexResources(index, resources).then(function() {
                return that._runSearch();
            });
        });
    },

    getQuery : function() {
        return this._query || {};
    },

    search : function(query) {
        var that = this;
        return Mosaic.P.then(function() {
            that._query = query || '';
            return that._runSearch();
        }).then(function(results) {
            return results;
        });
    },

    _runSearch : function() {
        var that = this;
        var event = {
            query : that._query,
        };
        var promise;
        if (that._searchPromise) {
            promise = that._searchPromise.then(function(result) {
            }, function(err) {
            });
        } else {
            promise = Mosaic.P();
        }
        return that._searchPromise = promise.then(function() {
            that.emit('search:begin', event);
            return that._getLunrIndex();
        }).then(function(index) {
            var q = that._query;
            var result = {
                resources : [],
                scores : []
            };
            if (q) {
                var entries = result.scores = index.lunr.search(q);
                _.each(entries, function(entry) {
                    var r = index.refs[entry.ref];
                    if (r) {
                        result.resources.push(r);
                    }
                });
            } else {
                result.resources = _.values(index.refs);
            }
            result = that._filterSearchResult(result);
            event.result = result;
            that.emit('search:end', event);
            return result;
        }, function(err) {
            event.error = err;
            that.emit('search:end', event);
        });
    },

    _getLunrIndex : function() {
        if (!this._indexPromise) {
            var that = this;
            this._indexPromise = this._buildLunrIndex();
        }
        return this._indexPromise;
    },

    /** Builds and returns a full-text search index. */
    _buildLunrIndex : function() {
        var that = this;
        var start = Date.now();
        return Mosaic.P.then(function() {
            that.emit('configuration:begin');
            var index = {};
            index.refs = {};
            index.lunr = Lunr(function(lunr) {
                var fields = that._getIndexFields();
                _.each(fields, function(info, field) {
                    info = info || {};
                    var boost = info.boost || 1;
                    var type = info.type || 'field';
                    lunr[type](field, {
                        boost : boost
                    });
                });
            });
            that.emit('configuration:end');
            return index;
        });
    },

    _setIndexingPipeline : function(index) {
        index.lunr.pipeline.reset();
        index.lunr.pipeline.add(this._filterToken.bind(this, [ //
        numberFilters, //
        this.normalizeText, //
        Lunr.stemmer, //
        frenchWordsFilter, //
        // Lunr.trimmer, //
        // Lunr.stopWordFilter, //
        emptyStopWordFilter, //
        function(entry) {
            // console.log(' o ', arguments);
            return entry;
        } ]));
    },
    _setSearchPipeline : function(index) {
        // Disable token registration
        index.lunr.pipeline.reset();
        index.lunr.pipeline.add(this._filterToken.bind(this, [ //
        this.normalizeText, //
        Lunr.stemmer, //
        frenchWordsFilter, //
        emptyStopWordFilter ]));
    },
    _filterToken : function(filters, token) {
        for (var i = 0; i < filters.length; i++) {
            token = filters[i](token);
            if (!token)
                return;
        }
        return token;
    },

    _reindexResources : function(index, resources) {
        var that = this;
        return Mosaic.P.then(function() {
            that._setIndexingPipeline(index);
            var indexingInfo = {
                resources : resources,
                position : 0
            };
            that.emit('indexing:begin', indexingInfo);
            var fields = that._getIndexFields();
            _.each(resources, function(resource, key) {
                var indexEntry = {};
                _.each(fields, function(fieldInfo, field) {
                    var value = that._getResourceFieldValue(resource, field);
                    indexEntry[field] = that._mergeValues(value, ' ');
                    if (fieldInfo.type === 'ref') {
                        index.refs[value] = resource;
                    }
                });
                index.lunr.add(indexEntry);
                indexingInfo.position++;
                that.emit('indexing:progress', indexingInfo);
            });
            that._setSearchPipeline(index);
            that.emit('indexing:end', indexingInfo);
        });
    },

    setResourceFilter : function(filter) {
        this._resourceFilter = filter;
    },

    getResourceFilter : function() {
        return this._resourceFilter;
    },

    _filterSearchResult : function(result) {
        var filter = this.getResourceFilter();
        if (filter) {
            result.resources = _.filter(result.resources, filter);
        }
        return result;
    },

    _mergeValues : function(val, separator) {
        var s;
        if (_.isArray(val)) {
            s = _.map(val, this._filterText, this).join(separator);
        } else {
            s = this._filterText(val);
        }
        return s;
    },
    _filterText : function(val) {
        return val ? '' + val : '';
    },

    _getResourceFieldValue : ResourceFieldsAccessor.getValue,

    /**
     * Returns all fields to index with their respective type and boost factors.
     */
    _getIndexFields : function() {
        this._fields = this._fields || this.options.fields;
        if (!this._fields) {
            this._fields = {
                "id" : {
                    "type" : "ref",
                    "boost" : 1
                },
                "properties.name" : {
                    "type" : "field",
                    "boost" : 10
                },
                "properties.description" : {
                    "type" : "field",
                    "boost" : 5
                },
                "properties.tags" : {
                    "type" : "field",
                    "boost" : 15,
                    "filter" : true
                },
                "properties.address" : {
                    "type" : "field",
                    "boost" : 1
                },
                "properties.postcode" : {
                    "type" : "field",
                    "boost" : 1,
                    "filter" : "prefix"
                },
                "properties.city" : {
                    "type" : "field",
                    "boost" : 2
                },
                "properties.url" : {
                    "type" : "field",
                    "boost" : 0.5
                }
            }
        }
        return this._fields;
    },

});
LunrWrapper.getNormalizationFunction = getNormalizationFunction;
module.exports = LunrWrapper;

/** Returns a function normalizing strings */
function getNormalizationFunction() {
    var repl = [];
    for (var i = 0; i < arguments.length; i++) {
        var mapping = arguments[i];
        for ( var key in mapping) {
            repl.push({
                regexp : new RegExp(key, 'gim'),
                value : mapping[key]
            });
        }
    }
    return function(str) {
        if (!str || str == '')
            return '';
        str = str + '';
        for (var i = 0; i < repl.length; i++) {
            var slot = repl[i];
            str = str.replace(slot.regexp, slot.value);
        }
        return str;
    }
}
<commit_msg>Remove numbers filter from the lunr indexer<commit_after>var _ = require('underscore');
var Mosaic = require('mosaic-commons');
var Lunr = require('lunr');
var ResourceFieldsAccessor = require('./ResourceFieldsAccessor');
Lunr.utils.warn = function(msg) {
    // Silently ignore messages
};
// Change the default tokenizer
Lunr.tokenizer = function(obj) {
    var result = [];
    if (!arguments.length || obj == null || obj == undefined)
        return result;
    if (Array.isArray(obj))
        return result = obj.map(function(t) {
            return t.toLowerCase();
        });
    return result = obj.toString().trim().toLowerCase().split(
            /[\(\)\s\-\.\/\/\'\’\"]+/);
}

// Filtering of empty words
function emptyStopWordFilter(token) {
    if (emptyStopWordFilter.stopWords.indexOf(token) === -1)
        return token;
}
emptyStopWordFilter.stopWords = new Lunr.SortedSet();
emptyStopWordFilter.stopWords.length = 1;
emptyStopWordFilter.stopWords.elements = [ "" ];

// Filter numbers
function numberFilters(token) {
    if (!token.match(/^\d+$/gim))
        return token;
}
// Filter/transform french words
function frenchWordsFilter(token) {
    token = token.toLowerCase();
    if (token === 'd' || token === 'de' || token === 'des' || token === 'la'
            || token === 'le' || token === 'à' || token === 'au'
            || token === 'aux')
        return;
    return token;
}

var LunrWrapper = Mosaic.Class.extend(Mosaic.Events.prototype, {

    /** Normalizes texts - remove all accented characters */
    normalizeText : getNormalizationFunction({
        '[ùûü]' : 'u',
        '[ÿ]' : 'y',
        '[àâ]' : 'a',
        '[æ]' : 'ae',
        '[ç]' : 'c',
        '[éèêë]' : 'e',
        '[ïî]' : 'i',
        '[ô]' : 'o',
        '[œ]' : 'oe',
    }),

    /**
     */
    initialize : function(options) {
        this.setOptions(options);
        this.index(this.options.resources);
    },

    index : function(resources, reset) {
        var that = this;
        if (reset) {
            delete that._indexPromise;
        }
        return that._getLunrIndex().then(function(index) {
            return that._reindexResources(index, resources).then(function() {
                return that._runSearch();
            });
        });
    },

    getQuery : function() {
        return this._query || {};
    },

    search : function(query) {
        var that = this;
        return Mosaic.P.then(function() {
            that._query = query || '';
            return that._runSearch();
        }).then(function(results) {
            return results;
        });
    },

    _runSearch : function() {
        var that = this;
        var event = {
            query : that._query,
        };
        var promise;
        if (that._searchPromise) {
            promise = that._searchPromise.then(function(result) {
            }, function(err) {
            });
        } else {
            promise = Mosaic.P();
        }
        return that._searchPromise = promise.then(function() {
            that.emit('search:begin', event);
            return that._getLunrIndex();
        }).then(function(index) {
            var q = that._query;
            var result = {
                resources : [],
                scores : []
            };
            if (q) {
                var entries = result.scores = index.lunr.search(q);
                _.each(entries, function(entry) {
                    var r = index.refs[entry.ref];
                    if (r) {
                        result.resources.push(r);
                    }
                });
            } else {
                result.resources = _.values(index.refs);
            }
            result = that._filterSearchResult(result);
            event.result = result;
            that.emit('search:end', event);
            return result;
        }, function(err) {
            event.error = err;
            that.emit('search:end', event);
        });
    },

    _getLunrIndex : function() {
        if (!this._indexPromise) {
            var that = this;
            this._indexPromise = this._buildLunrIndex();
        }
        return this._indexPromise;
    },

    /** Builds and returns a full-text search index. */
    _buildLunrIndex : function() {
        var that = this;
        var start = Date.now();
        return Mosaic.P.then(function() {
            that.emit('configuration:begin');
            var index = {};
            index.refs = {};
            index.lunr = Lunr(function(lunr) {
                var fields = that._getIndexFields();
                _.each(fields, function(info, field) {
                    info = info || {};
                    var boost = info.boost || 1;
                    var type = info.type || 'field';
                    lunr[type](field, {
                        boost : boost
                    });
                });
            });
            that.emit('configuration:end');
            return index;
        });
    },

    _setIndexingPipeline : function(index) {
        index.lunr.pipeline.reset();
        index.lunr.pipeline.add(this._filterToken.bind(this, [ //
        // numberFilters, //
        this.normalizeText, //
        Lunr.stemmer, //
        frenchWordsFilter, //
        // Lunr.trimmer, //
        // Lunr.stopWordFilter, //
        emptyStopWordFilter, //
        function(entry) {
            // console.log(' o ', arguments);
            return entry;
        } ]));
    },
    _setSearchPipeline : function(index) {
        // Disable token registration
        index.lunr.pipeline.reset();
        index.lunr.pipeline.add(this._filterToken.bind(this, [ //
        this.normalizeText, //
        Lunr.stemmer, //
        frenchWordsFilter, //
        emptyStopWordFilter ]));
    },
    _filterToken : function(filters, token) {
        for (var i = 0; i < filters.length; i++) {
            token = filters[i](token);
            if (!token)
                return;
        }
        return token;
    },

    _reindexResources : function(index, resources) {
        var that = this;
        return Mosaic.P.then(function() {
            that._setIndexingPipeline(index);
            var indexingInfo = {
                resources : resources,
                position : 0
            };
            that.emit('indexing:begin', indexingInfo);
            var fields = that._getIndexFields();
            _.each(resources, function(resource, key) {
                var indexEntry = {};
                _.each(fields, function(fieldInfo, field) {
                    var value = that._getResourceFieldValue(resource, field);
                    indexEntry[field] = that._mergeValues(value, ' ');
                    if (fieldInfo.type === 'ref') {
                        index.refs[value] = resource;
                    }
                });
                index.lunr.add(indexEntry);
                indexingInfo.position++;
                that.emit('indexing:progress', indexingInfo);
            });
            that._setSearchPipeline(index);
            that.emit('indexing:end', indexingInfo);
        });
    },

    setResourceFilter : function(filter) {
        this._resourceFilter = filter;
    },

    getResourceFilter : function() {
        return this._resourceFilter;
    },

    _filterSearchResult : function(result) {
        var filter = this.getResourceFilter();
        if (filter) {
            result.resources = _.filter(result.resources, filter);
        }
        return result;
    },

    _mergeValues : function(val, separator) {
        var s;
        if (_.isArray(val)) {
            s = _.map(val, this._filterText, this).join(separator);
        } else {
            s = this._filterText(val);
        }
        return s;
    },
    _filterText : function(val) {
        return val ? '' + val : '';
    },

    _getResourceFieldValue : ResourceFieldsAccessor.getValue,

    /**
     * Returns all fields to index with their respective type and boost factors.
     */
    _getIndexFields : function() {
        this._fields = this._fields || this.options.fields;
        if (!this._fields) {
            this._fields = {
                "id" : {
                    "type" : "ref",
                    "boost" : 1
                },
                "properties.name" : {
                    "type" : "field",
                    "boost" : 10
                },
                "properties.description" : {
                    "type" : "field",
                    "boost" : 5
                },
                "properties.tags" : {
                    "type" : "field",
                    "boost" : 15,
                    "filter" : true
                },
                "properties.address" : {
                    "type" : "field",
                    "boost" : 1
                },
                "properties.postcode" : {
                    "type" : "field",
                    "boost" : 1,
                    "filter" : "prefix"
                },
                "properties.city" : {
                    "type" : "field",
                    "boost" : 2
                },
                "properties.url" : {
                    "type" : "field",
                    "boost" : 0.5
                }
            }
        }
        return this._fields;
    },

});
LunrWrapper.getNormalizationFunction = getNormalizationFunction;
module.exports = LunrWrapper;

/** Returns a function normalizing strings */
function getNormalizationFunction() {
    var repl = [];
    for (var i = 0; i < arguments.length; i++) {
        var mapping = arguments[i];
        for ( var key in mapping) {
            repl.push({
                regexp : new RegExp(key, 'gim'),
                value : mapping[key]
            });
        }
    }
    return function(str) {
        if (!str || str == '')
            return '';
        str = str + '';
        for (var i = 0; i < repl.length; i++) {
            var slot = repl[i];
            str = str.replace(slot.regexp, slot.value);
        }
        return str;
    }
}
<|endoftext|>


<commit_before>'use strict';

import $ from 'jquery';
import { Keyboard } from './foundation.util.keyboard';
import { GetYoDigits } from './foundation.util.core';
import { Positionable } from './foundation.positionable';

import { Triggers } from './foundation.util.triggers';

/**
 * Dropdown module.
 * @module foundation.dropdown
 * @requires foundation.util.keyboard
 * @requires foundation.util.box
 * @requires foundation.util.triggers
 */
class Dropdown extends Positionable {
  /**
   * Creates a new instance of a dropdown.
   * @class
   * @name Dropdown
   * @param {jQuery} element - jQuery object to make into a dropdown.
   *        Object should be of the dropdown panel, rather than its anchor.
   * @param {Object} options - Overrides to the default plugin settings.
   */
  _setup(element, options) {
    this.$element = element;
    this.options = $.extend({}, Dropdown.defaults, this.$element.data(), options);
    this.className = 'Dropdown'; // ie9 back compat

    // Triggers init is idempotent, just need to make sure it is initialized
    Triggers.init($);

    this._init();

    Keyboard.register('Dropdown', {
      'ENTER': 'open',
      'SPACE': 'open',
      'ESCAPE': 'close'
    });
  }

  /**
   * Initializes the plugin by setting/checking options and attributes, adding helper variables, and saving the anchor.
   * @function
   * @private
   */
  _init() {
    var $id = this.$element.attr('id');

    this.$anchors = $(`[data-toggle="${$id}"]`).length ? $(`[data-toggle="${$id}"]`) : $(`[data-open="${$id}"]`);
    this.$anchors.attr({
      'aria-controls': $id,
      'data-is-focus': false,
      'data-yeti-box': $id,
      'aria-haspopup': true,
      'aria-expanded': false
    });

    this._setCurrentAnchor(this.$anchors.first());

    if(this.options.parentClass){
      this.$parent = this.$element.parents('.' + this.options.parentClass);
    }else{
      this.$parent = null;
    }

    this.$element.attr({
      'aria-hidden': 'true',
      'data-yeti-box': $id,
      'data-resize': $id,
      'aria-labelledby': this.$currentAnchor.id || GetYoDigits(6, 'dd-anchor')
    });
    super._init();
    this._events();
  }

  _getDefaultPosition() {
    // handle legacy classnames
    var position = this.$element[0].className.match(/(top|left|right|bottom)/g);
    if(position) {
      return position[0];
    } else {
      return 'bottom'
    }
  }

  _getDefaultAlignment() {
    // handle legacy float approach
    var horizontalPosition = /float-(\S+)/.exec(this.$currentAnchor.className);
    if(horizontalPosition) {
      return horizontalPosition[1];
    }

    return super._getDefaultAlignment();
  }

  /**
   * Sets the position and orientation of the dropdown pane, checks for collisions if allow-overlap is not true.
   * Recursively calls itself if a collision is detected, with a new position class.
   * @function
   * @private
   */
  _setPosition() {
    this.$element.removeClass(`has-position-${this.position} has-alignment-${this.alignment}`);
    super._setPosition(this.$currentAnchor, this.$element, this.$parent);
    this.$element.addClass(`has-position-${this.position} has-alignment-${this.alignment}`);
  }

  /**
   * Make it a current anchor.
   * Current anchor as the reference for the position of Dropdown panes.
   * @param {HTML} el - DOM element of the anchor.
   * @function
   * @private
   */
  _setCurrentAnchor(el) {
    this.$currentAnchor = $(el);
  }

  /**
   * Adds event listeners to the element utilizing the triggers utility library.
   * @function
   * @private
   */
  _events() {
    var _this = this;
    this.$element.on({
      'open.zf.trigger': this.open.bind(this),
      'close.zf.trigger': this.close.bind(this),
      'toggle.zf.trigger': this.toggle.bind(this),
      'resizeme.zf.trigger': this._setPosition.bind(this)
    });

    this.$anchors.off('click.zf.trigger')
      .on('click.zf.trigger', function() { _this._setCurrentAnchor(this); });

    if(this.options.hover){
      this.$anchors.off('mouseenter.zf.dropdown mouseleave.zf.dropdown')
      .on('mouseenter.zf.dropdown', function(){
        _this._setCurrentAnchor(this);

        var bodyData = $('body').data();
        if(typeof(bodyData.whatinput) === 'undefined' || bodyData.whatinput === 'mouse') {
          clearTimeout(_this.timeout);
          _this.timeout = setTimeout(function(){
            _this.open();
            _this.$anchors.data('hover', true);
          }, _this.options.hoverDelay);
        }
      }).on('mouseleave.zf.dropdown', function(){
        clearTimeout(_this.timeout);
        _this.timeout = setTimeout(function(){
          _this.close();
          _this.$anchors.data('hover', false);
        }, _this.options.hoverDelay);
      });
      if(this.options.hoverPane){
        this.$element.off('mouseenter.zf.dropdown mouseleave.zf.dropdown')
            .on('mouseenter.zf.dropdown', function(){
              clearTimeout(_this.timeout);
            }).on('mouseleave.zf.dropdown', function(){
              clearTimeout(_this.timeout);
              _this.timeout = setTimeout(function(){
                _this.close();
                _this.$anchors.data('hover', false);
              }, _this.options.hoverDelay);
            });
      }
    }
    this.$anchors.add(this.$element).on('keydown.zf.dropdown', function(e) {

      var $target = $(this),
        visibleFocusableElements = Keyboard.findFocusable(_this.$element);

      Keyboard.handleKey(e, 'Dropdown', {
        open: function() {
          if ($target.is(_this.$anchors)) {
            _this.open();
            _this.$element.attr('tabindex', -1).focus();
            e.preventDefault();
          }
        },
        close: function() {
          _this.close();
          _this.$anchors.focus();
        }
      });
    });
  }

  /**
   * Adds an event handler to the body to close any dropdowns on a click.
   * @function
   * @private
   */
  _addBodyHandler() {
     var $body = $(document.body).not(this.$element),
         _this = this;
     $body.off('click.zf.dropdown')
          .on('click.zf.dropdown', function(e){
            if(_this.$anchors.is(e.target) || _this.$anchors.find(e.target).length) {
              return;
            }
            if(_this.$element.is(e.target) || _this.$element.find(e.target).length) {
              return;
            }
            _this.close();
            $body.off('click.zf.dropdown');
          });
  }

  /**
   * Opens the dropdown pane, and fires a bubbling event to close other dropdowns.
   * @function
   * @fires Dropdown#closeme
   * @fires Dropdown#show
   */
  open() {
    // var _this = this;
    /**
     * Fires to close other open dropdowns, typically when dropdown is opening
     * @event Dropdown#closeme
     */
    this.$element.trigger('closeme.zf.dropdown', this.$element.attr('id'));
    this.$anchors.addClass('hover')
        .attr({'aria-expanded': true});
    // this.$element/*.show()*/;

    this.$element.addClass('is-opening');
    this._setPosition();
    this.$element.removeClass('is-opening').addClass('is-open')
        .attr({'aria-hidden': false});

    if(this.options.autoFocus){
      var $focusable = Keyboard.findFocusable(this.$element);
      if($focusable.length){
        $focusable.eq(0).focus();
      }
    }

    if(this.options.closeOnClick){ this._addBodyHandler(); }

    if (this.options.trapFocus) {
      Keyboard.trapFocus(this.$element);
    }

    /**
     * Fires once the dropdown is visible.
     * @event Dropdown#show
     */
    this.$element.trigger('show.zf.dropdown', [this.$element]);
  }

  /**
   * Closes the open dropdown pane.
   * @function
   * @fires Dropdown#hide
   */
  close() {
    if(!this.$element.hasClass('is-open')){
      return false;
    }
    this.$element.removeClass('is-open')
        .attr({'aria-hidden': true});

    this.$anchors.removeClass('hover')
        .attr('aria-expanded', false);

    /**
     * Fires once the dropdown is no longer visible.
     * @event Dropdown#hide
     */
    this.$element.trigger('hide.zf.dropdown', [this.$element]);

    if (this.options.trapFocus) {
      Keyboard.releaseFocus(this.$element);
    }
  }

  /**
   * Toggles the dropdown pane's visibility.
   * @function
   */
  toggle() {
    if(this.$element.hasClass('is-open')){
      if(this.$anchors.data('hover')) return;
      this.close();
    }else{
      this.open();
    }
  }

  /**
   * Destroys the dropdown.
   * @function
   */
  _destroy() {
    this.$element.off('.zf.trigger').hide();
    this.$anchors.off('.zf.dropdown');
    $(document.body).off('click.zf.dropdown');

  }
}

Dropdown.defaults = {
  /**
   * Class that designates bounding container of Dropdown (default: window)
   * @option
   * @type {?string}
   * @default null
   */
  parentClass: null,
  /**
   * Amount of time to delay opening a submenu on hover event.
   * @option
   * @type {number}
   * @default 250
   */
  hoverDelay: 250,
  /**
   * Allow submenus to open on hover events
   * @option
   * @type {boolean}
   * @default false
   */
  hover: false,
  /**
   * Don't close dropdown when hovering over dropdown pane
   * @option
   * @type {boolean}
   * @default false
   */
  hoverPane: false,
  /**
   * Number of pixels between the dropdown pane and the triggering element on open.
   * @option
   * @type {number}
   * @default 0
   */
  vOffset: 0,
  /**
   * Number of pixels between the dropdown pane and the triggering element on open.
   * @option
   * @type {number}
   * @default 0
   */
  hOffset: 0,
  /**
   * DEPRECATED: Class applied to adjust open position.
   * @option
   * @type {string}
   * @default ''
   */
  positionClass: '',

  /**
   * Position of dropdown. Can be left, right, bottom, top, or auto.
   * @option
   * @type {string}
   * @default 'auto'
   */
  position: 'auto',
  /**
   * Alignment of dropdown relative to anchor. Can be left, right, bottom, top, center, or auto.
   * @option
   * @type {string}
   * @default 'auto'
   */
  alignment: 'auto',
  /**
   * Allow overlap of container/window. If false, dropdown will first try to position as defined by data-position and data-alignment, but reposition if it would cause an overflow.
   * @option
   * @type {boolean}
   * @default false
   */
  allowOverlap: false,
  /**
   * Allow overlap of only the bottom of the container. This is the most common
   * behavior for dropdowns, allowing the dropdown to extend the bottom of the
   * screen but not otherwise influence or break out of the container.
   * @option
   * @type {boolean}
   * @default true
   */
  allowBottomOverlap: true,
  /**
   * Allow the plugin to trap focus to the dropdown pane if opened with keyboard commands.
   * @option
   * @type {boolean}
   * @default false
   */
  trapFocus: false,
  /**
   * Allow the plugin to set focus to the first focusable element within the pane, regardless of method of opening.
   * @option
   * @type {boolean}
   * @default false
   */
  autoFocus: false,
  /**
   * Allows a click on the body to close the dropdown.
   * @option
   * @type {boolean}
   * @default false
   */
  closeOnClick: false
}

export {Dropdown};
<commit_msg>Prevent SPACE from setting focus to the Dropdown if anchor is an input element.<commit_after>'use strict';

import $ from 'jquery';
import { Keyboard } from './foundation.util.keyboard';
import { GetYoDigits } from './foundation.util.core';
import { Positionable } from './foundation.positionable';

import { Triggers } from './foundation.util.triggers';

/**
 * Dropdown module.
 * @module foundation.dropdown
 * @requires foundation.util.keyboard
 * @requires foundation.util.box
 * @requires foundation.util.triggers
 */
class Dropdown extends Positionable {
  /**
   * Creates a new instance of a dropdown.
   * @class
   * @name Dropdown
   * @param {jQuery} element - jQuery object to make into a dropdown.
   *        Object should be of the dropdown panel, rather than its anchor.
   * @param {Object} options - Overrides to the default plugin settings.
   */
  _setup(element, options) {
    this.$element = element;
    this.options = $.extend({}, Dropdown.defaults, this.$element.data(), options);
    this.className = 'Dropdown'; // ie9 back compat

    // Triggers init is idempotent, just need to make sure it is initialized
    Triggers.init($);

    this._init();

    Keyboard.register('Dropdown', {
      'ENTER': 'open',
      'SPACE': 'open',
      'ESCAPE': 'close'
    });
  }

  /**
   * Initializes the plugin by setting/checking options and attributes, adding helper variables, and saving the anchor.
   * @function
   * @private
   */
  _init() {
    var $id = this.$element.attr('id');

    this.$anchors = $(`[data-toggle="${$id}"]`).length ? $(`[data-toggle="${$id}"]`) : $(`[data-open="${$id}"]`);
    this.$anchors.attr({
      'aria-controls': $id,
      'data-is-focus': false,
      'data-yeti-box': $id,
      'aria-haspopup': true,
      'aria-expanded': false
    });

    this._setCurrentAnchor(this.$anchors.first());

    if(this.options.parentClass){
      this.$parent = this.$element.parents('.' + this.options.parentClass);
    }else{
      this.$parent = null;
    }

    this.$element.attr({
      'aria-hidden': 'true',
      'data-yeti-box': $id,
      'data-resize': $id,
      'aria-labelledby': this.$currentAnchor.id || GetYoDigits(6, 'dd-anchor')
    });
    super._init();
    this._events();
  }

  _getDefaultPosition() {
    // handle legacy classnames
    var position = this.$element[0].className.match(/(top|left|right|bottom)/g);
    if(position) {
      return position[0];
    } else {
      return 'bottom'
    }
  }

  _getDefaultAlignment() {
    // handle legacy float approach
    var horizontalPosition = /float-(\S+)/.exec(this.$currentAnchor.className);
    if(horizontalPosition) {
      return horizontalPosition[1];
    }

    return super._getDefaultAlignment();
  }

  /**
   * Sets the position and orientation of the dropdown pane, checks for collisions if allow-overlap is not true.
   * Recursively calls itself if a collision is detected, with a new position class.
   * @function
   * @private
   */
  _setPosition() {
    this.$element.removeClass(`has-position-${this.position} has-alignment-${this.alignment}`);
    super._setPosition(this.$currentAnchor, this.$element, this.$parent);
    this.$element.addClass(`has-position-${this.position} has-alignment-${this.alignment}`);
  }

  /**
   * Make it a current anchor.
   * Current anchor as the reference for the position of Dropdown panes.
   * @param {HTML} el - DOM element of the anchor.
   * @function
   * @private
   */
  _setCurrentAnchor(el) {
    this.$currentAnchor = $(el);
  }

  /**
   * Adds event listeners to the element utilizing the triggers utility library.
   * @function
   * @private
   */
  _events() {
    var _this = this;
    this.$element.on({
      'open.zf.trigger': this.open.bind(this),
      'close.zf.trigger': this.close.bind(this),
      'toggle.zf.trigger': this.toggle.bind(this),
      'resizeme.zf.trigger': this._setPosition.bind(this)
    });

    this.$anchors.off('click.zf.trigger')
      .on('click.zf.trigger', function() { _this._setCurrentAnchor(this); });

    if(this.options.hover){
      this.$anchors.off('mouseenter.zf.dropdown mouseleave.zf.dropdown')
      .on('mouseenter.zf.dropdown', function(){
        _this._setCurrentAnchor(this);

        var bodyData = $('body').data();
        if(typeof(bodyData.whatinput) === 'undefined' || bodyData.whatinput === 'mouse') {
          clearTimeout(_this.timeout);
          _this.timeout = setTimeout(function(){
            _this.open();
            _this.$anchors.data('hover', true);
          }, _this.options.hoverDelay);
        }
      }).on('mouseleave.zf.dropdown', function(){
        clearTimeout(_this.timeout);
        _this.timeout = setTimeout(function(){
          _this.close();
          _this.$anchors.data('hover', false);
        }, _this.options.hoverDelay);
      });
      if(this.options.hoverPane){
        this.$element.off('mouseenter.zf.dropdown mouseleave.zf.dropdown')
            .on('mouseenter.zf.dropdown', function(){
              clearTimeout(_this.timeout);
            }).on('mouseleave.zf.dropdown', function(){
              clearTimeout(_this.timeout);
              _this.timeout = setTimeout(function(){
                _this.close();
                _this.$anchors.data('hover', false);
              }, _this.options.hoverDelay);
            });
      }
    }
    this.$anchors.add(this.$element).on('keydown.zf.dropdown', function(e) {

      var $target = $(this),
        visibleFocusableElements = Keyboard.findFocusable(_this.$element);

      Keyboard.handleKey(e, 'Dropdown', {
        open: function() {
          if ($target.is(_this.$anchors) && !$target.is('input')) {
            _this.open();
            _this.$element.attr('tabindex', -1).focus();
            e.preventDefault();
          }
        },
        close: function() {
          _this.close();
          _this.$anchors.focus();
        }
      });
    });
  }

  /**
   * Adds an event handler to the body to close any dropdowns on a click.
   * @function
   * @private
   */
  _addBodyHandler() {
     var $body = $(document.body).not(this.$element),
         _this = this;
     $body.off('click.zf.dropdown')
          .on('click.zf.dropdown', function(e){
            if(_this.$anchors.is(e.target) || _this.$anchors.find(e.target).length) {
              return;
            }
            if(_this.$element.is(e.target) || _this.$element.find(e.target).length) {
              return;
            }
            _this.close();
            $body.off('click.zf.dropdown');
          });
  }

  /**
   * Opens the dropdown pane, and fires a bubbling event to close other dropdowns.
   * @function
   * @fires Dropdown#closeme
   * @fires Dropdown#show
   */
  open() {
    // var _this = this;
    /**
     * Fires to close other open dropdowns, typically when dropdown is opening
     * @event Dropdown#closeme
     */
    this.$element.trigger('closeme.zf.dropdown', this.$element.attr('id'));
    this.$anchors.addClass('hover')
        .attr({'aria-expanded': true});
    // this.$element/*.show()*/;

    this.$element.addClass('is-opening');
    this._setPosition();
    this.$element.removeClass('is-opening').addClass('is-open')
        .attr({'aria-hidden': false});

    if(this.options.autoFocus){
      var $focusable = Keyboard.findFocusable(this.$element);
      if($focusable.length){
        $focusable.eq(0).focus();
      }
    }

    if(this.options.closeOnClick){ this._addBodyHandler(); }

    if (this.options.trapFocus) {
      Keyboard.trapFocus(this.$element);
    }

    /**
     * Fires once the dropdown is visible.
     * @event Dropdown#show
     */
    this.$element.trigger('show.zf.dropdown', [this.$element]);
  }

  /**
   * Closes the open dropdown pane.
   * @function
   * @fires Dropdown#hide
   */
  close() {
    if(!this.$element.hasClass('is-open')){
      return false;
    }
    this.$element.removeClass('is-open')
        .attr({'aria-hidden': true});

    this.$anchors.removeClass('hover')
        .attr('aria-expanded', false);

    /**
     * Fires once the dropdown is no longer visible.
     * @event Dropdown#hide
     */
    this.$element.trigger('hide.zf.dropdown', [this.$element]);

    if (this.options.trapFocus) {
      Keyboard.releaseFocus(this.$element);
    }
  }

  /**
   * Toggles the dropdown pane's visibility.
   * @function
   */
  toggle() {
    if(this.$element.hasClass('is-open')){
      if(this.$anchors.data('hover')) return;
      this.close();
    }else{
      this.open();
    }
  }

  /**
   * Destroys the dropdown.
   * @function
   */
  _destroy() {
    this.$element.off('.zf.trigger').hide();
    this.$anchors.off('.zf.dropdown');
    $(document.body).off('click.zf.dropdown');

  }
}

Dropdown.defaults = {
  /**
   * Class that designates bounding container of Dropdown (default: window)
   * @option
   * @type {?string}
   * @default null
   */
  parentClass: null,
  /**
   * Amount of time to delay opening a submenu on hover event.
   * @option
   * @type {number}
   * @default 250
   */
  hoverDelay: 250,
  /**
   * Allow submenus to open on hover events
   * @option
   * @type {boolean}
   * @default false
   */
  hover: false,
  /**
   * Don't close dropdown when hovering over dropdown pane
   * @option
   * @type {boolean}
   * @default false
   */
  hoverPane: false,
  /**
   * Number of pixels between the dropdown pane and the triggering element on open.
   * @option
   * @type {number}
   * @default 0
   */
  vOffset: 0,
  /**
   * Number of pixels between the dropdown pane and the triggering element on open.
   * @option
   * @type {number}
   * @default 0
   */
  hOffset: 0,
  /**
   * DEPRECATED: Class applied to adjust open position.
   * @option
   * @type {string}
   * @default ''
   */
  positionClass: '',

  /**
   * Position of dropdown. Can be left, right, bottom, top, or auto.
   * @option
   * @type {string}
   * @default 'auto'
   */
  position: 'auto',
  /**
   * Alignment of dropdown relative to anchor. Can be left, right, bottom, top, center, or auto.
   * @option
   * @type {string}
   * @default 'auto'
   */
  alignment: 'auto',
  /**
   * Allow overlap of container/window. If false, dropdown will first try to position as defined by data-position and data-alignment, but reposition if it would cause an overflow.
   * @option
   * @type {boolean}
   * @default false
   */
  allowOverlap: false,
  /**
   * Allow overlap of only the bottom of the container. This is the most common
   * behavior for dropdowns, allowing the dropdown to extend the bottom of the
   * screen but not otherwise influence or break out of the container.
   * @option
   * @type {boolean}
   * @default true
   */
  allowBottomOverlap: true,
  /**
   * Allow the plugin to trap focus to the dropdown pane if opened with keyboard commands.
   * @option
   * @type {boolean}
   * @default false
   */
  trapFocus: false,
  /**
   * Allow the plugin to set focus to the first focusable element within the pane, regardless of method of opening.
   * @option
   * @type {boolean}
   * @default false
   */
  autoFocus: false,
  /**
   * Allows a click on the body to close the dropdown.
   * @option
   * @type {boolean}
   * @default false
   */
  closeOnClick: false
}

export {Dropdown};
<|endoftext|>


<commit_before>import React from 'react'
import '../styles/video.css'

const Tutorial = props =>

          If you have any problems using the MarCom Resource Center, please
          contact Jesse Weigel at{' '}
          jweigel@franciscan.edu or{' '}
           740-284-5305.

export default Tutorial
<commit_msg>Add title to iframe<commit_after>import React from 'react'
import '../styles/video.css'

const Tutorial = props =>

            If you have any problems using the MarCom Resource Center, please
            contact Jesse Weigel at{' '}

              jweigel@franciscan.edu
            {' '}
            or  740-284-5305.

export default Tutorial
<|endoftext|>


<commit_before>/**
 * @fileoverview Utility class and functions for React components detection
 * @author Yannick Croissant
 */
'use strict';

var util = require('util');
var variableUtil = require('./variable');
var pragmaUtil = require('./pragma');

/**
 * Components
 * @class
 */
function Components() {
  this._list = {};
  this._getId = function(node) {
    return node && node.range.join(':');
  };
}

/**
 * Add a node to the components list, or update it if it's already in the list
 *
 * @param {ASTNode} node The AST node being added.
 * @param {Number} confidence Confidence in the component detection (0=banned, 1=maybe, 2=yes)
 */
Components.prototype.add = function(node, confidence) {
  var id = this._getId(node);
  if (this._list[id]) {
    if (confidence === 0 || this._list[id].confidence === 0) {
      this._list[id].confidence = 0;
    } else {
      this._list[id].confidence = Math.max(this._list[id].confidence, confidence);
    }
    return;
  }
  this._list[id] = {
    node: node,
    confidence: confidence
  };
};

/**
 * Find a component in the list using its node
 *
 * @param {ASTNode} node The AST node being searched.
 * @returns {Object} Component object, undefined if the component is not found
 */
Components.prototype.get = function(node) {
  var id = this._getId(node);
  return this._list[id];
};

/**
 * Update a component in the list
 *
 * @param {ASTNode} node The AST node being updated.
 * @param {Object} props Additional properties to add to the component.
 */
Components.prototype.set = function(node, props) {
  while (node && !this._list[this._getId(node)]) {
    node = node.parent;
  }
  if (!node) {
    return;
  }
  var id = this._getId(node);
  this._list[id] = util._extend(this._list[id], props);
};

/**
 * Return the components list
 * Components for which we are not confident are not returned
 *
 * @returns {Object} Components list
 */
Components.prototype.list = function() {
  var list = {};
  var usedPropTypes = {};
  // Find props used in components for which we are not confident
  for (var i in this._list) {
    if (!this._list.hasOwnProperty(i) || this._list[i].confidence >= 2) {
      continue;
    }
    var component;
    var node;
    node = this._list[i].node;
    while (!component && node.parent) {
      node = node.parent;
      component = this.get(node);
    }
    if (component) {
      usedPropTypes[this._getId(component.node)] = this._list[i].usedPropTypes;
    }
  }
  // Assign used props in not confident components to the parent component
  for (var j in this._list) {
    if (!this._list.hasOwnProperty(j) || this._list[j].confidence < 2) {
      continue;
    }
    var id = this._getId(this._list[j].node);
    list[j] = this._list[j];
    if (usedPropTypes[id]) {
      list[j].usedPropTypes = (list[j].usedPropTypes || []).concat(usedPropTypes[id]);
    }
  }
  return list;
};

/**
 * Return the length of the components list
 * Components for which we are not confident are not counted
 *
 * @returns {Number} Components list length
 */
Components.prototype.length = function() {
  var length = 0;
  for (var i in this._list) {
    if (!this._list.hasOwnProperty(i) || this._list[i].confidence < 2) {
      continue;
    }
    length++;
  }
  return length;
};

function componentRule(rule, context) {

  var pragma = pragmaUtil.getFromContext(context);
  var sourceCode = context.getSourceCode();
  var components = new Components();

  // Utilities for component detection
  var utils = {

    /**
     * Check if the node is a React ES5 component
     *
     * @param {ASTNode} node The AST node being checked.
     * @returns {Boolean} True if the node is a React ES5 component, false if not
     */
    isES5Component: function(node) {
      if (!node.parent) {
        return false;
      }
      return new RegExp('^(' + pragma + '\\.)?createClass$').test(sourceCode.getText(node.parent.callee));
    },

    /**
     * Check if the node is a React ES6 component
     *
     * @param {ASTNode} node The AST node being checked.
     * @returns {Boolean} True if the node is a React ES6 component, false if not
     */
    isES6Component: function(node) {
      if (!node.superClass) {
        return false;
      }
      return new RegExp('^(' + pragma + '\\.)?Component$').test(sourceCode.getText(node.superClass));
    },

    /**
     * Check if the node is returning JSX
     *
     * @param {ASTNode} node The AST node being checked (must be a ReturnStatement).
     * @returns {Boolean} True if the node is returning JSX, false if not
     */
    isReturningJSX: function(node) {
      var property;
      switch (node.type) {
        case 'ReturnStatement':
          property = 'argument';
          break;
        case 'ArrowFunctionExpression':
          property = 'body';
          break;
        default:
          return false;
      }

      var returnsJSX =
        node[property] &&
        node[property].type === 'JSXElement'
      ;
      var returnsReactCreateElement =
        node[property] &&
        node[property].callee &&
        node[property].callee.property &&
        node[property].callee.property.name === 'createElement'
      ;

      return Boolean(returnsJSX || returnsReactCreateElement);
    },

    /**
     * Get the parent component node from the current scope
     *
     * @returns {ASTNode} component node, null if we are not in a component
     */
    getParentComponent: function() {
      return (
        utils.getParentES6Component() ||
        utils.getParentES5Component() ||
        utils.getParentStatelessComponent()
      );
    },

    /**
     * Get the parent ES5 component node from the current scope
     *
     * @returns {ASTNode} component node, null if we are not in a component
     */
    getParentES5Component: function() {
      var scope = context.getScope();
      while (scope) {
        var node = scope.block && scope.block.parent && scope.block.parent.parent;
        if (node && utils.isES5Component(node)) {
          return node;
        }
        scope = scope.upper;
      }
      return null;
    },

    /**
     * Get the parent ES6 component node from the current scope
     *
     * @returns {ASTNode} component node, null if we are not in a component
     */
    getParentES6Component: function() {
      var scope = context.getScope();
      while (scope && scope.type !== 'class') {
        scope = scope.upper;
      }
      var node = scope && scope.block;
      if (!node || !utils.isES6Component(node)) {
        return null;
      }
      return node;
    },

    /**
     * Get the parent stateless component node from the current scope
     *
     * @returns {ASTNode} component node, null if we are not in a component
     */
    getParentStatelessComponent: function() {
      var scope = context.getScope();
      while (scope) {
        var node = scope.block;
        var isFunction = /Function/.test(node.type); // Ignore non functions
        var isNotMethod = !node.parent || node.parent.type !== 'MethodDefinition'; // Ignore classes methods
        var isNotArgument = !node.parent || node.parent.type !== 'CallExpression'; // Ignore arguments (callback, etc.)
        if (isFunction && isNotMethod && isNotArgument) {
          return node;
        }
        scope = scope.upper;
      }
      return null;
    },

    /**
     * Get the related component from a node
     *
     * @param {ASTNode} node The AST node being checked (must be a MemberExpression).
     * @returns {ASTNode} component node, null if we cannot find the component
     */
    getRelatedComponent: function(node) {
      var i;
      var j;
      var k;
      var l;
      // Get the component path
      var componentPath = [];
      while (node) {
        if (node.property && node.property.type === 'Identifier') {
          componentPath.push(node.property.name);
        }
        if (node.object && node.object.type === 'Identifier') {
          componentPath.push(node.object.name);
        }
        node = node.object;
      }
      componentPath.reverse();

      // Find the variable in the current scope
      var variableName = componentPath.shift();
      if (!variableName) {
        return null;
      }
      var variableInScope;
      var variables = variableUtil.variablesInScope(context);
      for (i = 0, j = variables.length; i < j; i++) {
        if (variables[i].name === variableName) {
          variableInScope = variables[i];
          break;
        }
      }
      if (!variableInScope) {
        return null;
      }

      // Find the variable declaration
      var defInScope;
      var defs = variableInScope.defs;
      for (i = 0, j = defs.length; i < j; i++) {
        if (defs[i].type === 'ClassName' || defs[i].type === 'FunctionName' || defs[i].type === 'Variable') {
          defInScope = defs[i];
          break;
        }
      }
      if (!defInScope || !defInScope.node) {
        return null;
      }
      node = defInScope.node.init || defInScope.node;

      // Traverse the node properties to the component declaration
      for (i = 0, j = componentPath.length; i < j; i++) {
        if (!node.properties) {
          continue;
        }
        for (k = 0, l = node.properties.length; k < l; k++) {
          if (node.properties[k].key.name === componentPath[i]) {
            node = node.properties[k];
            break;
          }
        }
        if (!node || !node.value) {
          return null;
        }
        node = node.value;
      }

      // Return the component
      return components.get(node);
    }
  };

  // Component detection instructions
  var detectionInstructions = {
    ClassDeclaration: function(node) {
      if (!utils.isES6Component(node)) {
        return;
      }
      components.add(node, 2);
    },

    ClassProperty: function(node) {
      node = utils.getParentComponent();
      if (!node) {
        return;
      }
      components.add(node, 2);
    },

    ObjectExpression: function(node) {
      if (!utils.isES5Component(node)) {
        return;
      }
      components.add(node, 2);
    },

    FunctionExpression: function(node) {
      node = utils.getParentComponent();
      if (!node) {
        return;
      }
      components.add(node, 1);
    },

    FunctionDeclaration: function(node) {
      node = utils.getParentComponent();
      if (!node) {
        return;
      }
      components.add(node, 1);
    },

    ArrowFunctionExpression: function(node) {
      node = utils.getParentComponent();
      if (!node) {
        return;
      }
      if (node.expression && utils.isReturningJSX(node)) {
        components.add(node, 2);
      } else {
        components.add(node, 1);
      }
    },

    ThisExpression: function(node) {
      node = utils.getParentComponent();
      if (!node || !/Function/.test(node.type)) {
        return;
      }
      // Ban functions with a ThisExpression
      components.add(node, 0);
    },

    BlockComment: function(node) {
      pragma = pragmaUtil.getFromNode(node) || pragma;
    },

    ReturnStatement: function(node) {
      if (!utils.isReturningJSX(node)) {
        return;
      }
      node = utils.getParentComponent();
      if (!node) {
        return;
      }
      components.add(node, 2);
    }
  };

  // Update the provided rule instructions to add the component detection
  var ruleInstructions = rule(context, components, utils);
  var updatedRuleInstructions = util._extend({}, ruleInstructions);
  Object.keys(detectionInstructions).forEach(function(instruction) {
    updatedRuleInstructions[instruction] = function(node) {
      detectionInstructions[instruction](node);
      return ruleInstructions[instruction] ? ruleInstructions[instruction](node) : void 0;
    };
  });
  // Return the updated rule instructions
  return updatedRuleInstructions;
}

Components.detect = function(rule) {
  return componentRule.bind(this, rule);
};

module.exports = Components;
<commit_msg>Add component detection for class expressions<commit_after>/**
 * @fileoverview Utility class and functions for React components detection
 * @author Yannick Croissant
 */
'use strict';

var util = require('util');
var variableUtil = require('./variable');
var pragmaUtil = require('./pragma');

/**
 * Components
 * @class
 */
function Components() {
  this._list = {};
  this._getId = function(node) {
    return node && node.range.join(':');
  };
}

/**
 * Add a node to the components list, or update it if it's already in the list
 *
 * @param {ASTNode} node The AST node being added.
 * @param {Number} confidence Confidence in the component detection (0=banned, 1=maybe, 2=yes)
 */
Components.prototype.add = function(node, confidence) {
  var id = this._getId(node);
  if (this._list[id]) {
    if (confidence === 0 || this._list[id].confidence === 0) {
      this._list[id].confidence = 0;
    } else {
      this._list[id].confidence = Math.max(this._list[id].confidence, confidence);
    }
    return;
  }
  this._list[id] = {
    node: node,
    confidence: confidence
  };
};

/**
 * Find a component in the list using its node
 *
 * @param {ASTNode} node The AST node being searched.
 * @returns {Object} Component object, undefined if the component is not found
 */
Components.prototype.get = function(node) {
  var id = this._getId(node);
  return this._list[id];
};

/**
 * Update a component in the list
 *
 * @param {ASTNode} node The AST node being updated.
 * @param {Object} props Additional properties to add to the component.
 */
Components.prototype.set = function(node, props) {
  while (node && !this._list[this._getId(node)]) {
    node = node.parent;
  }
  if (!node) {
    return;
  }
  var id = this._getId(node);
  this._list[id] = util._extend(this._list[id], props);
};

/**
 * Return the components list
 * Components for which we are not confident are not returned
 *
 * @returns {Object} Components list
 */
Components.prototype.list = function() {
  var list = {};
  var usedPropTypes = {};
  // Find props used in components for which we are not confident
  for (var i in this._list) {
    if (!this._list.hasOwnProperty(i) || this._list[i].confidence >= 2) {
      continue;
    }
    var component;
    var node;
    node = this._list[i].node;
    while (!component && node.parent) {
      node = node.parent;
      component = this.get(node);
    }
    if (component) {
      usedPropTypes[this._getId(component.node)] = this._list[i].usedPropTypes;
    }
  }
  // Assign used props in not confident components to the parent component
  for (var j in this._list) {
    if (!this._list.hasOwnProperty(j) || this._list[j].confidence < 2) {
      continue;
    }
    var id = this._getId(this._list[j].node);
    list[j] = this._list[j];
    if (usedPropTypes[id]) {
      list[j].usedPropTypes = (list[j].usedPropTypes || []).concat(usedPropTypes[id]);
    }
  }
  return list;
};

/**
 * Return the length of the components list
 * Components for which we are not confident are not counted
 *
 * @returns {Number} Components list length
 */
Components.prototype.length = function() {
  var length = 0;
  for (var i in this._list) {
    if (!this._list.hasOwnProperty(i) || this._list[i].confidence < 2) {
      continue;
    }
    length++;
  }
  return length;
};

function componentRule(rule, context) {

  var pragma = pragmaUtil.getFromContext(context);
  var sourceCode = context.getSourceCode();
  var components = new Components();

  // Utilities for component detection
  var utils = {

    /**
     * Check if the node is a React ES5 component
     *
     * @param {ASTNode} node The AST node being checked.
     * @returns {Boolean} True if the node is a React ES5 component, false if not
     */
    isES5Component: function(node) {
      if (!node.parent) {
        return false;
      }
      return new RegExp('^(' + pragma + '\\.)?createClass$').test(sourceCode.getText(node.parent.callee));
    },

    /**
     * Check if the node is a React ES6 component
     *
     * @param {ASTNode} node The AST node being checked.
     * @returns {Boolean} True if the node is a React ES6 component, false if not
     */
    isES6Component: function(node) {
      if (!node.superClass) {
        return false;
      }
      return new RegExp('^(' + pragma + '\\.)?Component$').test(sourceCode.getText(node.superClass));
    },

    /**
     * Check if the node is returning JSX
     *
     * @param {ASTNode} node The AST node being checked (must be a ReturnStatement).
     * @returns {Boolean} True if the node is returning JSX, false if not
     */
    isReturningJSX: function(node) {
      var property;
      switch (node.type) {
        case 'ReturnStatement':
          property = 'argument';
          break;
        case 'ArrowFunctionExpression':
          property = 'body';
          break;
        default:
          return false;
      }

      var returnsJSX =
        node[property] &&
        node[property].type === 'JSXElement'
      ;
      var returnsReactCreateElement =
        node[property] &&
        node[property].callee &&
        node[property].callee.property &&
        node[property].callee.property.name === 'createElement'
      ;

      return Boolean(returnsJSX || returnsReactCreateElement);
    },

    /**
     * Get the parent component node from the current scope
     *
     * @returns {ASTNode} component node, null if we are not in a component
     */
    getParentComponent: function() {
      return (
        utils.getParentES6Component() ||
        utils.getParentES5Component() ||
        utils.getParentStatelessComponent()
      );
    },

    /**
     * Get the parent ES5 component node from the current scope
     *
     * @returns {ASTNode} component node, null if we are not in a component
     */
    getParentES5Component: function() {
      var scope = context.getScope();
      while (scope) {
        var node = scope.block && scope.block.parent && scope.block.parent.parent;
        if (node && utils.isES5Component(node)) {
          return node;
        }
        scope = scope.upper;
      }
      return null;
    },

    /**
     * Get the parent ES6 component node from the current scope
     *
     * @returns {ASTNode} component node, null if we are not in a component
     */
    getParentES6Component: function() {
      var scope = context.getScope();
      while (scope && scope.type !== 'class') {
        scope = scope.upper;
      }
      var node = scope && scope.block;
      if (!node || !utils.isES6Component(node)) {
        return null;
      }
      return node;
    },

    /**
     * Get the parent stateless component node from the current scope
     *
     * @returns {ASTNode} component node, null if we are not in a component
     */
    getParentStatelessComponent: function() {
      var scope = context.getScope();
      while (scope) {
        var node = scope.block;
        var isFunction = /Function/.test(node.type); // Ignore non functions
        var isNotMethod = !node.parent || node.parent.type !== 'MethodDefinition'; // Ignore classes methods
        var isNotArgument = !node.parent || node.parent.type !== 'CallExpression'; // Ignore arguments (callback, etc.)
        if (isFunction && isNotMethod && isNotArgument) {
          return node;
        }
        scope = scope.upper;
      }
      return null;
    },

    /**
     * Get the related component from a node
     *
     * @param {ASTNode} node The AST node being checked (must be a MemberExpression).
     * @returns {ASTNode} component node, null if we cannot find the component
     */
    getRelatedComponent: function(node) {
      var i;
      var j;
      var k;
      var l;
      // Get the component path
      var componentPath = [];
      while (node) {
        if (node.property && node.property.type === 'Identifier') {
          componentPath.push(node.property.name);
        }
        if (node.object && node.object.type === 'Identifier') {
          componentPath.push(node.object.name);
        }
        node = node.object;
      }
      componentPath.reverse();

      // Find the variable in the current scope
      var variableName = componentPath.shift();
      if (!variableName) {
        return null;
      }
      var variableInScope;
      var variables = variableUtil.variablesInScope(context);
      for (i = 0, j = variables.length; i < j; i++) {
        if (variables[i].name === variableName) {
          variableInScope = variables[i];
          break;
        }
      }
      if (!variableInScope) {
        return null;
      }

      // Find the variable declaration
      var defInScope;
      var defs = variableInScope.defs;
      for (i = 0, j = defs.length; i < j; i++) {
        if (defs[i].type === 'ClassName' || defs[i].type === 'FunctionName' || defs[i].type === 'Variable') {
          defInScope = defs[i];
          break;
        }
      }
      if (!defInScope || !defInScope.node) {
        return null;
      }
      node = defInScope.node.init || defInScope.node;

      // Traverse the node properties to the component declaration
      for (i = 0, j = componentPath.length; i < j; i++) {
        if (!node.properties) {
          continue;
        }
        for (k = 0, l = node.properties.length; k < l; k++) {
          if (node.properties[k].key.name === componentPath[i]) {
            node = node.properties[k];
            break;
          }
        }
        if (!node || !node.value) {
          return null;
        }
        node = node.value;
      }

      // Return the component
      return components.get(node);
    }
  };

  // Component detection instructions
  var detectionInstructions = {
    ClassExpression: function(node) {
      if (!utils.isES6Component(node)) {
        return;
      }
      components.add(node, 2);
    },

    ClassDeclaration: function(node) {
      if (!utils.isES6Component(node)) {
        return;
      }
      components.add(node, 2);
    },

    ClassProperty: function(node) {
      node = utils.getParentComponent();
      if (!node) {
        return;
      }
      components.add(node, 2);
    },

    ObjectExpression: function(node) {
      if (!utils.isES5Component(node)) {
        return;
      }
      components.add(node, 2);
    },

    FunctionExpression: function(node) {
      node = utils.getParentComponent();
      if (!node) {
        return;
      }
      components.add(node, 1);
    },

    FunctionDeclaration: function(node) {
      node = utils.getParentComponent();
      if (!node) {
        return;
      }
      components.add(node, 1);
    },

    ArrowFunctionExpression: function(node) {
      node = utils.getParentComponent();
      if (!node) {
        return;
      }
      if (node.expression && utils.isReturningJSX(node)) {
        components.add(node, 2);
      } else {
        components.add(node, 1);
      }
    },

    ThisExpression: function(node) {
      node = utils.getParentComponent();
      if (!node || !/Function/.test(node.type)) {
        return;
      }
      // Ban functions with a ThisExpression
      components.add(node, 0);
    },

    BlockComment: function(node) {
      pragma = pragmaUtil.getFromNode(node) || pragma;
    },

    ReturnStatement: function(node) {
      if (!utils.isReturningJSX(node)) {
        return;
      }
      node = utils.getParentComponent();
      if (!node) {
        return;
      }
      components.add(node, 2);
    }
  };

  // Update the provided rule instructions to add the component detection
  var ruleInstructions = rule(context, components, utils);
  var updatedRuleInstructions = util._extend({}, ruleInstructions);
  Object.keys(detectionInstructions).forEach(function(instruction) {
    updatedRuleInstructions[instruction] = function(node) {
      detectionInstructions[instruction](node);
      return ruleInstructions[instruction] ? ruleInstructions[instruction](node) : void 0;
    };
  });
  // Return the updated rule instructions
  return updatedRuleInstructions;
}

Components.detect = function(rule) {
  return componentRule.bind(this, rule);
};

module.exports = Components;
<|endoftext|>


<commit_before>

'use strict';

// Detect if run by NPM and fail
if (process.env.npm_lifecycle_script) {
  console.log(`

    This project uses yarn, so you shouldn't use npm install but use yan.

    Basics:
      $ see yarnpkg.com for installation of yarn
      $ yarn install

    To install a new package (also adds to package.json):
      $ yarn add [packagename]

    To upgrade a package:
      $ yarn upgrade [packagename]

    For more help, see https://yarnpkg.com/en/docs/usage.

    Reasons for this change:
    - yarn uses a dependency lockfile by default, allowing for a fully reproducible build
    - yarn is faster to install dependencies from scratch
    - yarn has more secure checksum matching, ensuring package contents are consistent
  `);
  process.exit(1);
}

<commit_msg>NPM: Remove killer script<commit_after><|endoftext|>