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

foam.CLASS({
  package: 'foam.core',
  name: 'Relationship',
  implements: [ 'foam.mlang.Expressions' ],

  properties: [
    'name',
    'inverseName',
    {
      name: 'cardinality',
      value: '0:1' // '1:*', '*:1', '*:*'
    },
    {
      name: 'sourceModel'
    },
    {
      class: 'FObjectArray',
      name: 'sourceProperties',
      of: 'Property',
      adaptArrayElement: foam.core.Model.PROPERTIES.adaptArrayElement
    },
    {
      name: 'targetModel'
    },
    {
      class: 'FObjectArray',
      name: 'targetProperties',
      of: 'Property',
      adaptArrayElement: foam.core.Model.PROPERTIES.adaptArrayElement
    },
    {
      class: 'Boolean',
      name: 'oneWay'
    }
    /* FUTURE:
    {
      name: 'deleteStrategy'
      // prevent, cascade, orphan
    }
    */
  ],

  methods: [
    function init() {
      var sourceProps = this.sourceProperties || [];
      var targetProps = this.targetProperties || [];

      if ( ! sourceProps.length ) {
        sourceProps = [ foam.core.Property.create({name: this.name}) ];
      }

      if ( ! targetProps.length ) {
        targetProps = [ foam.core.Property.create({name: this.inverseName}) ];
      }

      this.assert(
          sourceProps.length === targetProps.length,
          'Relationship source/target property list length mismatch.');

      var source = this.lookup(this.sourceModel);
      var target = this.lookup(this.targetModel);

      this.assert(source, 'Unknown sourceModel: ', this.sourceModel);
      this.assert(target, 'Unknown targetModel: ', this.targetModel);

      for ( var i = 0 ; i < sourceProps.length ; i++ ) {
        var sp = sourceProps[i];
        var tp = targetProps[i];

        source.installAxiom(sp);
        if ( ! this.oneWay ) target.installAxiom(tp);
      }

      var relationship = this;

      // Install filtered target DAO in source Model
      var daoProp = foam.core.Property.create({
        name: this.name,
        transient: true,
        factory: function() {
          return foam.dao.RelationshipDAO.create({
            obj: this,
            relationship: relationship
          }, this);
        }
      });

      source.installAxiom(daoProp);

      /*
      if ( ! this.oneWay ) {
        sourceProperty.preSet = function(_, newValue) {
          if ( newValue ) {
            for ( var i = 0 ; i < sourceProps.length ; i++ ) {
              newValue[targetProps[i].name] = this[sourceProps[i]];
            }
          }
          return newValue;
        };
      }
      */
    },

    function targetQueryFromSource(obj) {
      var targetClass = this.lookup(this.targetModel);
      var targetProp  = targetClass[foam.String.constantize(this.inverseName)];
      return this.EQ(targetProp, obj.id);
    },

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

foam.CLASS({
  package: 'foam.core',
  name: 'Relationship',
  requires: [
    'foam.mlang.Expressions'
  ],

  properties: [
    'name',
    'inverseName',
    {
      name: 'cardinality',
      value: '0:1' // '1:*', '*:1', '*:*'
    },
    {
      name: 'sourceModel'
    },
    {
      class: 'FObjectArray',
      name: 'sourceProperties',
      of: 'Property',
      adaptArrayElement: foam.core.Model.PROPERTIES.adaptArrayElement
    },
    {
      name: 'targetModel'
    },
    {
      class: 'FObjectArray',
      name: 'targetProperties',
      of: 'Property',
      adaptArrayElement: foam.core.Model.PROPERTIES.adaptArrayElement
    },
    {
      class: 'Boolean',
      name: 'oneWay'
    },
    {
      name: 'E',
      factory: function() { return this.Expressions.create(); }
    },
    /* FUTURE:
    {
      name: 'deleteStrategy'
      // prevent, cascade, orphan
    }
    */
  ],

  methods: [
    function init() {
      var sourceProps = this.sourceProperties || [];
      var targetProps = this.targetProperties || [];

      if ( ! sourceProps.length ) {
        sourceProps = [ foam.core.Property.create({name: this.name}) ];
      }

      if ( ! targetProps.length ) {
        targetProps = [ foam.core.Property.create({name: this.inverseName}) ];
      }

      this.assert(
          sourceProps.length === targetProps.length,
          'Relationship source/target property list length mismatch.');

      var source = this.lookup(this.sourceModel);
      var target = this.lookup(this.targetModel);

      this.assert(source, 'Unknown sourceModel: ', this.sourceModel);
      this.assert(target, 'Unknown targetModel: ', this.targetModel);

      for ( var i = 0 ; i < sourceProps.length ; i++ ) {
        var sp = sourceProps[i];
        var tp = targetProps[i];

        source.installAxiom(sp);
        if ( ! this.oneWay ) target.installAxiom(tp);
      }

      var relationship = this;

      // Install filtered target DAO in source Model
      var daoProp = foam.core.Property.create({
        name: this.name,
        transient: true,
        factory: function() {
          return foam.dao.RelationshipDAO.create({
            obj: this,
            relationship: relationship
          }, this);
        }
      });

      source.installAxiom(daoProp);

      /*
      if ( ! this.oneWay ) {
        sourceProperty.preSet = function(_, newValue) {
          if ( newValue ) {
            for ( var i = 0 ; i < sourceProps.length ; i++ ) {
              newValue[targetProps[i].name] = this[sourceProps[i]];
            }
          }
          return newValue;
        };
      }
      */
    },

    function targetQueryFromSource(obj) {
      var targetClass = this.lookup(this.targetModel);
      var targetProp  = targetClass[foam.String.constantize(this.inverseName)];
      return this.E.EQ(targetProp, obj.id);
    },

    function adaptTarget(source, target) {
      target[this.inverseName] = source.id;
    }
  ]
});
<|endoftext|>


<commit_before>var oAssign = require( 'object-assign' );

/*eslint func-names: 0*/

function Value( val ) {
  this.value = val;
}

function Piece( frag ) {
  this.fragment = frag;
}
function GroupedPiece( frag ) {
  this.fragment = frag;
}
function NamedPiece( name, frag ) {
  this.fragment = frag;
  this.name = name;
}
function GroupedAltPiece( frag ) {
  this.fragment = frag;
}
function NamedAltPiece( name, frag ) {
  this.fragment = frag;
  this.name = name;
}
function Nothing() {}
function Empty() {}
function MaybeEnter() {}
function GroupName( name ) {
  this.name = name;
}

function getMatch( chain, walkFn, walkOpts ) {
  // console.log( JSON.stringify( chain, null, 2 ) );
  var { conform } = walkOpts;
  var { inputType } = chain;

  const valStack = [];
  var r;

  chain.forEach( function( curr ) {
    if ( curr.move ) {
      switch ( curr.move.dir ) {
      case 'maybe_enter' : {
        valStack.push( new MaybeEnter() );
      } break;
      case 'maybe_exit': {
        let c,
          acc = new Empty( );
        while ( !( ( c = valStack.pop() ) instanceof MaybeEnter ) ) {
          acc = _foldLeft( inputType, conform, acc, c );
        }
        valStack.push( acc );
      } break;
      case 'maybe_single_enter' : {
        valStack.push( new MaybeEnter() );
      } break;
      case 'maybe_single_exit': {
        let top = valStack.pop(),
          acc;
        if ( top instanceof MaybeEnter ) {
          acc = new Nothing();
        } else {
          acc = top;
          //get rid of MaybeEnter
          valStack.pop();
        }
        valStack.push( acc );
      } break;
      case 'enter' : {
      } break;
      case 'exit': {
        let c = valStack.pop();
        let acc;
        if ( conform && c instanceof NamedAltPiece ) {
          acc = new GroupedAltPiece(
            oAssign( {}, { [ c.name ]: c.fragment } )
          )
        } else {
          acc = c;
        }
        valStack.push( acc );
      } break;
      case 'maybe_in': {
        if ( conform && curr.move.name ) {
          valStack.push( new GroupName( curr.move.name ) );
        }
      } break;
      case 'loop': {
      } break;
      case 'maybe_out': {
        if ( conform && curr.move.name ) {
          let c = valStack.pop();
          let gn = valStack.pop();
          valStack.push( _giveName( gn, c ) );
        }
      } break;
      case 'in': {
        if ( conform && curr.move.name ) {
          valStack.push( new GroupName( curr.move.name ) );
        }
      } break;
      case 'out': {
        if ( conform && curr.move.name ) {
          let c = valStack.pop();
          let gn = valStack.pop();
          valStack.push( _giveAltName( gn, c ) );
        }
      } break;
      case 'clause': {
        let conformed = walkFn( curr.clause, curr.guide, walkOpts );
        valStack.push( new Value( conformed ) );
      } break;
      default: console.error( curr ); throw 'FUUU';
      }
    }
    // console.log( curr.move.dir, `(${curr.move.name || ''})`, curr, [].concat( valStack ) );

  } );
  if ( valStack.length !== 1 ) {
    console.error( 'valStack', valStack );
    throw '!';
  }
  r = valStack.pop();

  let retVal;

  if ( r instanceof Piece ) {
    retVal = r.fragment;
  } else if ( r instanceof GroupedPiece ) {
    retVal = r.fragment;
  } else if ( r instanceof GroupedAltPiece ) {
    retVal = r.fragment;
  } else if ( r instanceof Value ) {
    retVal = r.value;
  } else if ( r instanceof Empty ) {
    retVal = _coerceToProperType( inputType, [] );
  } else {
    retVal = r;
  }

  // console.log( 'r', r );
  return retVal;
}

function _giveName( groupName, c ) {
  if ( c instanceof Nothing ) {
    return new NamedPiece( groupName.name, undefined );
  } else if ( c instanceof GroupedPiece ) {
    return new NamedPiece( groupName.name, c.fragment );
  } else if ( c instanceof Piece || c instanceof GroupedAltPiece ) {
    return new NamedPiece( groupName.name, c.fragment );
  } else if ( c instanceof Value ) {
    return new NamedPiece( groupName.name, c.value );
  } else if ( c instanceof Empty ) {
    return new Value( {} );
  } else {
    console.error( c );
    throw 'c!!!';
  }
}

function _giveAltName( groupName, c ) {
  if ( c instanceof Nothing ) {
    return new NamedAltPiece( groupName.name, undefined );
  } else if ( c instanceof GroupedAltPiece ) {
    return new NamedAltPiece( groupName.name, c.fragment );
  } else if ( c instanceof Piece ) {
    return new NamedAltPiece( groupName.name, c.fragment );
  } else if ( c instanceof GroupedPiece ) {
    return new NamedAltPiece( groupName.name, c.fragment );
  } else if ( c instanceof Value ) {
    return new NamedAltPiece( groupName.name, c.value );
  } else if ( c instanceof Empty ) {
    return new Value( {} );
  } else {
    console.error( c );
    throw 'c!!!alt';
  }
}

function _foldLeft( inputType, conform, acc, c ) {

  if ( conform && c instanceof NamedPiece ) {
    let rightArr;

    if ( acc instanceof Nothing ) {
      rightArr = {};
    } else if ( !acc ) {
      rightArr = {};
    } else if ( acc instanceof Empty ) {
      rightArr = {};
    } else if ( acc instanceof GroupedPiece ) {
      rightArr = acc.fragment;
    } else if ( acc instanceof GroupedAltPiece ) {
      rightArr = acc.fragment;
    } else {
      console.error( acc, c );
      throw '!!acc_fl_np';
    }
    return new GroupedPiece(
      oAssign( {}, {
        [ c.name ]: c.fragment
      }, rightArr )
    );
  } else {
    let leftArr,
      rightArr;
    if ( acc instanceof Nothing ) {
      rightArr = [];
    } else if ( !acc ) {
      rightArr = [];
    } else if ( acc instanceof Piece ) {
      rightArr = acc.fragment;
    } else if ( acc instanceof Empty ) {
      rightArr = [];
    } else {
      console.error( acc );
      throw '!!acc_fl';
    }

    if ( c instanceof Value ) {
      leftArr = [ c.value ];
    } else if ( c instanceof Piece ) {
      leftArr = c.fragment;
    } else if ( c instanceof GroupedPiece ) {
      leftArr = [ c.fragment ];
    } else if ( c instanceof GroupedAltPiece ) {
      leftArr = [ c.fragment ];
    } else if ( c instanceof Nothing ) {
      leftArr = [];
    } else if ( c instanceof Empty ) {
      leftArr = [];
    } else {
      console.error( c );
      throw 'cc!!';
    }
    let p = new Piece( _coerceToProperType( inputType, leftArr.concat( rightArr ) ) );
    return p;
  }
}

function _coerceToProperType( t, arr ) {
  if ( t === 'string' && Array.isArray( arr ) ) {
    return arr.join( '' );
  } else {
    return arr;
  }
}

module.exports = getMatch;
<commit_msg>handle missing Piece case in recontructing nfa match<commit_after>var oAssign = require( 'object-assign' );

/*eslint func-names: 0*/

function Value( val ) {
  this.value = val;
}

function Piece( frag ) {
  this.fragment = frag;
}
function GroupedPiece( frag ) {
  this.fragment = frag;
}
function NamedPiece( name, frag ) {
  this.fragment = frag;
  this.name = name;
}
function GroupedAltPiece( frag ) {
  this.fragment = frag;
}
function NamedAltPiece( name, frag ) {
  this.fragment = frag;
  this.name = name;
}
function Nothing() {}
function Empty() {}
function MaybeEnter() {}
function GroupName( name ) {
  this.name = name;
}

function getMatch( chain, walkFn, walkOpts ) {
  // console.log( JSON.stringify( chain, null, 2 ) );
  var { conform } = walkOpts;
  var { inputType } = chain;

  const valStack = [];
  var r;

  chain.forEach( function( curr ) {
    if ( curr.move ) {
      switch ( curr.move.dir ) {
      case 'maybe_enter' : {
        valStack.push( new MaybeEnter() );
      } break;
      case 'maybe_exit': {
        let c,
          acc = new Empty( );
        while ( !( ( c = valStack.pop() ) instanceof MaybeEnter ) ) {
          acc = _foldLeft( inputType, conform, acc, c );
        }
        valStack.push( acc );
      } break;
      case 'maybe_single_enter' : {
        valStack.push( new MaybeEnter() );
      } break;
      case 'maybe_single_exit': {
        let top = valStack.pop(),
          acc;
        if ( top instanceof MaybeEnter ) {
          acc = new Nothing();
        } else {
          acc = top;
          //get rid of MaybeEnter
          valStack.pop();
        }
        valStack.push( acc );
      } break;
      case 'enter' : {
      } break;
      case 'exit': {
        let c = valStack.pop();
        let acc;
        if ( conform && c instanceof NamedAltPiece ) {
          acc = new GroupedAltPiece(
            oAssign( {}, { [ c.name ]: c.fragment } )
          )
        } else {
          acc = c;
        }
        valStack.push( acc );
      } break;
      case 'maybe_in': {
        if ( conform && curr.move.name ) {
          valStack.push( new GroupName( curr.move.name ) );
        }
      } break;
      case 'loop': {
      } break;
      case 'maybe_out': {
        if ( conform && curr.move.name ) {
          let c = valStack.pop();
          let gn = valStack.pop();
          valStack.push( _giveName( gn, c ) );
        }
      } break;
      case 'in': {
        if ( conform && curr.move.name ) {
          valStack.push( new GroupName( curr.move.name ) );
        }
      } break;
      case 'out': {
        if ( conform && curr.move.name ) {
          let c = valStack.pop();
          let gn = valStack.pop();
          valStack.push( _giveAltName( gn, c ) );
        }
      } break;
      case 'clause': {
        let conformed = walkFn( curr.clause, curr.guide, walkOpts );
        valStack.push( new Value( conformed ) );
      } break;
      default: console.error( curr ); throw 'FUUU';
      }
    }
    // console.log( curr.move.dir, `(${curr.move.name || ''})`, curr, [].concat( valStack ) );

  } );
  if ( valStack.length !== 1 ) {
    console.error( 'valStack', valStack );
    throw '!';
  }
  r = valStack.pop();

  let retVal;

  if ( r instanceof Piece ) {
    retVal = r.fragment;
  } else if ( r instanceof GroupedPiece ) {
    retVal = r.fragment;
  } else if ( r instanceof GroupedAltPiece ) {
    retVal = r.fragment;
  } else if ( r instanceof Value ) {
    retVal = r.value;
  } else if ( r instanceof Empty ) {
    retVal = _coerceToProperType( inputType, [] );
  } else {
    retVal = r;
  }

  // console.log( 'r', r );
  return retVal;
}

function _giveName( groupName, c ) {
  if ( c instanceof Nothing ) {
    return new NamedPiece( groupName.name, undefined );
  } else if ( c instanceof GroupedPiece ) {
    return new NamedPiece( groupName.name, c.fragment );
  } else if ( c instanceof Piece || c instanceof GroupedAltPiece ) {
    return new NamedPiece( groupName.name, c.fragment );
  } else if ( c instanceof Value ) {
    return new NamedPiece( groupName.name, c.value );
  } else if ( c instanceof Empty ) {
    return new Value( {} );
  } else {
    console.error( c );
    throw 'c!!!';
  }
}

function _giveAltName( groupName, c ) {
  if ( c instanceof Nothing ) {
    return new NamedAltPiece( groupName.name, undefined );
  } else if ( c instanceof GroupedAltPiece ) {
    return new NamedAltPiece( groupName.name, c.fragment );
  } else if ( c instanceof Piece ) {
    return new NamedAltPiece( groupName.name, c.fragment );
  } else if ( c instanceof GroupedPiece ) {
    return new NamedAltPiece( groupName.name, c.fragment );
  } else if ( c instanceof Value ) {
    return new NamedAltPiece( groupName.name, c.value );
  } else if ( c instanceof Empty ) {
    return new Value( {} );
  } else {
    console.error( c );
    throw 'c!!!alt';
  }
}

function _foldLeft( inputType, conform, acc, c ) {

  if ( conform && c instanceof NamedPiece ) {
    let rightArr;

    if ( acc instanceof Nothing ) {
      rightArr = {};
    } else if ( !acc ) {
      rightArr = {};
    } else if ( acc instanceof Empty ) {
      rightArr = {};
    } else if ( acc instanceof GroupedPiece ) {
      rightArr = acc.fragment;
    } else if ( acc instanceof Piece ) {
      rightArr = acc.fragment;
    } else if ( acc instanceof GroupedAltPiece ) {
      rightArr = acc.fragment;
    } else {
      console.error( acc, c );
      debugger;
      throw '!!acc_fl_np';
    }
    return new GroupedPiece(
      oAssign( {}, {
        [ c.name ]: c.fragment
      }, rightArr )
    );
  } else {
    let leftArr,
      rightArr;
    if ( acc instanceof Nothing ) {
      rightArr = [];
    } else if ( !acc ) {
      rightArr = [];
    } else if ( acc instanceof Piece ) {
      rightArr = acc.fragment;
    } else if ( acc instanceof Empty ) {
      rightArr = [];
    } else {
      console.error( acc );
      throw '!!acc_fl';
    }

    if ( c instanceof Value ) {
      leftArr = [ c.value ];
    } else if ( c instanceof Piece ) {
      leftArr = c.fragment;
    } else if ( c instanceof GroupedPiece ) {
      leftArr = [ c.fragment ];
    } else if ( c instanceof GroupedAltPiece ) {
      leftArr = [ c.fragment ];
    } else if ( c instanceof Nothing ) {
      leftArr = [];
    } else if ( c instanceof Empty ) {
      leftArr = [];
    } else {
      console.error( c );
      throw 'cc!!';
    }
    let p = new Piece( _coerceToProperType( inputType, leftArr.concat( rightArr ) ) );
    return p;
  }
}

function _coerceToProperType( t, arr ) {
  if ( t === 'string' && Array.isArray( arr ) ) {
    return arr.join( '' );
  } else {
    return arr;
  }
}

module.exports = getMatch;
<|endoftext|>


<commit_before>/* @flow */

import config from '../config'
import { warn } from './debug'
import { nativeWatch } from './env'
import { set } from '../observer/index'

import {
  ASSET_TYPES,
  LIFECYCLE_HOOKS
} from 'shared/constants'

import {
  extend,
  hasOwn,
  camelize,
  toRawType,
  capitalize,
  isBuiltInTag,
  isPlainObject
} from 'shared/util'

/**
 * Option overwriting strategies are functions that handle
 * how to merge a parent option value and a child option
 * value into the final value.
 */
const strats = config.optionMergeStrategies

/**
 * Options with restrictions
 */
if (process.env.NODE_ENV !== 'production') {
  strats.el = strats.propsData = function (parent, child, vm, key) {
    if (!vm) {
      warn(
        `option "${key}" can only be used during instance ` +
        'creation with the `new` keyword.'
      )
    }
    return defaultStrat(parent, child)
  }
}

/**
 * Helper that recursively merges two data objects together.
 */
function mergeData (to: Object, from: ?Object): Object {
  if (!from) return to
  let key, toVal, fromVal
  const keys = Object.keys(from)
  for (let i = 0; i < keys.length; i++) {
    key = keys[i]
    toVal = to[key]
    fromVal = from[key]
    if (!hasOwn(to, key)) {
      set(to, key, fromVal)
    } else if (isPlainObject(toVal) && isPlainObject(fromVal)) {
      mergeData(toVal, fromVal)
    }
  }
  return to
}

/**
 * Data
 */
export function mergeDataOrFn (
  parentVal: any,
  childVal: any,
  vm?: Component
): ?Function {
  if (!vm) {
    // in a Vue.extend merge, both should be functions
    if (!childVal) {
      return parentVal
    }
    if (!parentVal) {
      return childVal
    }
    // when parentVal & childVal are both present,
    // we need to return a function that returns the
    // merged result of both functions... no need to
    // check if parentVal is a function here because
    // it has to be a function to pass previous merges.
    return function mergedDataFn () {
      return mergeData(
        typeof childVal === 'function' ? childVal.call(this, this) : childVal,
        typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
      )
    }
  } else {
    return function mergedInstanceDataFn () {
      // instance merge
      const instanceData = typeof childVal === 'function'
        ? childVal.call(vm, vm)
        : childVal
      const defaultData = typeof parentVal === 'function'
        ? parentVal.call(vm, vm)
        : parentVal
      if (instanceData) {
        return mergeData(instanceData, defaultData)
      } else {
        return defaultData
      }
    }
  }
}

strats.data = function (
  parentVal: any,
  childVal: any,
  vm?: Component
): ?Function {
  if (!vm) {
    if (childVal && typeof childVal !== 'function') {
      process.env.NODE_ENV !== 'production' && warn(
        'The "data" option should be a function ' +
        'that returns a per-instance value in component ' +
        'definitions.',
        vm
      )

      return parentVal
    }
    return mergeDataOrFn(parentVal, childVal)
  }

  return mergeDataOrFn(parentVal, childVal, vm)
}

/**
 * Hooks and props are merged as arrays.
 */
function mergeHook (
  parentVal: ?Array<Function>,
  childVal: ?Function | ?Array<Function>
): ?Array<Function> {
  return childVal
    ? parentVal
      ? parentVal.concat(childVal)
      : Array.isArray(childVal)
        ? childVal
        : [childVal]
    : parentVal
}

LIFECYCLE_HOOKS.forEach(hook => {
  strats[hook] = mergeHook
})

/**
 * Assets
 *
 * When a vm is present (instance creation), we need to do
 * a three-way merge between constructor options, instance
 * options and parent options.
 */
function mergeAssets (
  parentVal: ?Object,
  childVal: ?Object,
  vm?: Component,
  key: string
): Object {
  const res = Object.create(parentVal || null)
  if (childVal) {
    process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm)
    return extend(res, childVal)
  } else {
    return res
  }
}

ASSET_TYPES.forEach(function (type) {
  strats[type + 's'] = mergeAssets
})

/**
 * Watchers.
 *
 * Watchers hashes should not overwrite one
 * another, so we merge them as arrays.
 */
strats.watch = function (
  parentVal: ?Object,
  childVal: ?Object,
  vm?: Component,
  key: string
): ?Object {
  // work around Firefox's Object.prototype.watch...
  if (parentVal === nativeWatch) parentVal = undefined
  if (childVal === nativeWatch) childVal = undefined
  /* istanbul ignore if */
  if (!childVal) return Object.create(parentVal || null)
  if (process.env.NODE_ENV !== 'production') {
    assertObjectType(key, childVal, vm)
  }
  if (!parentVal) return childVal
  const ret = {}
  extend(ret, parentVal)
  for (const key in childVal) {
    let parent = ret[key]
    const child = childVal[key]
    if (parent && !Array.isArray(parent)) {
      parent = [parent]
    }
    ret[key] = parent
      ? parent.concat(child)
      : Array.isArray(child) ? child : [child]
  }
  return ret
}

/**
 * Other object hashes.
 */
strats.props =
strats.methods =
strats.inject =
strats.computed = function (
  parentVal: ?Object,
  childVal: ?Object,
  vm?: Component,
  key: string
): ?Object {
  if (childVal && process.env.NODE_ENV !== 'production') {
    assertObjectType(key, childVal, vm)
  }
  if (!parentVal) return childVal
  const ret = Object.create(null)
  extend(ret, parentVal)
  if (childVal) extend(ret, childVal)
  return ret
}
strats.provide = mergeDataOrFn

/**
 * Default strategy.
 */
const defaultStrat = function (parentVal: any, childVal: any): any {
  return childVal === undefined
    ? parentVal
    : childVal
}

/**
 * Validate component names
 */
function checkComponents (options: Object) {
  for (const key in options.components) {
    validateComponentName(key)
  }
}

export function validateComponentName (name: string) {
  if (!/^[a-zA-Z][\w-]*$/.test(name)) {
    warn(
      'Invalid component name: "' + name + '". Component names ' +
      'can only contain alphanumeric characters and the hyphen, ' +
      'and must start with a letter.'
    )
  }
  if (isBuiltInTag(name) || config.isReservedTag(name)) {
    warn(
      'Do not use built-in or reserved HTML elements as component ' +
      'id: ' + name
    )
  }
}

/**
 * Ensure all props option syntax are normalized into the
 * Object-based format.
 */
function normalizeProps (options: Object, vm: ?Component) {
  const props = options.props
  if (!props) return
  const res = {}
  let i, val, name
  if (Array.isArray(props)) {
    i = props.length
    while (i--) {
      val = props[i]
      if (typeof val === 'string') {
        name = camelize(val)
        res[name] = { type: null }
      } else if (process.env.NODE_ENV !== 'production') {
        warn('props must be strings when using array syntax.')
      }
    }
  } else if (isPlainObject(props)) {
    for (const key in props) {
      val = props[key]
      name = camelize(key)
      if (process.env.NODE_ENV !== 'production' && isPlainObject(val)) {
        validatePropObject(name, val, vm)
      }
      res[name] = isPlainObject(val)
        ? val
        : { type: val }
    }
  } else if (process.env.NODE_ENV !== 'production') {
    warn(
      `Invalid value for option "props": expected an Array or an Object, ` +
      `but got ${toRawType(props)}.`,
      vm
    )
  }
  options.props = res
}

/**
 * Validate whether a prop object keys are valid.
 */
const propOptionsRE = /^(type|default|required|validator)$/

function validatePropObject (
  propName: string,
  prop: Object,
  vm: ?Component
) {
  for (const key in prop) {
    if (!propOptionsRE.test(key)) {
      warn(
        `Invalid key "${key}" in validation rules object for prop "${propName}".`,
        vm
      )
    }
  }
}

/**
 * Normalize all injections into Object-based format
 */
function normalizeInject (options: Object, vm: ?Component) {
  const inject = options.inject
  const normalized = options.inject = {}
  if (Array.isArray(inject)) {
    for (let i = 0; i < inject.length; i++) {
      normalized[inject[i]] = { from: inject[i] }
    }
  } else if (isPlainObject(inject)) {
    for (const key in inject) {
      const val = inject[key]
      normalized[key] = isPlainObject(val)
        ? extend({ from: key }, val)
        : { from: val }
    }
  } else if (process.env.NODE_ENV !== 'production' && inject) {
    warn(
      `Invalid value for option "inject": expected an Array or an Object, ` +
      `but got ${toRawType(inject)}.`,
      vm
    )
  }
}

/**
 * Normalize raw function directives into object format.
 */
function normalizeDirectives (options: Object) {
  const dirs = options.directives
  if (dirs) {
    for (const key in dirs) {
      const def = dirs[key]
      if (typeof def === 'function') {
        dirs[key] = { bind: def, update: def }
      }
    }
  }
}

function assertObjectType (name: string, value: any, vm: ?Component) {
  if (!isPlainObject(value)) {
    warn(
      `Invalid value for option "${name}": expected an Object, ` +
      `but got ${toRawType(value)}.`,
      vm
    )
  }
}

/**
 * Merge two option objects into a new one.
 * Core utility used in both instantiation and inheritance.
 */
export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  if (process.env.NODE_ENV !== 'production') {
    checkComponents(child)
  }

  if (typeof child === 'function') {
    child = child.options
  }

  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)
  const extendsFrom = child.extends
  if (extendsFrom) {
    parent = mergeOptions(parent, extendsFrom, vm)
  }
  if (child.mixins) {
    for (let i = 0, l = child.mixins.length; i < l; i++) {
      parent = mergeOptions(parent, child.mixins[i], vm)
    }
  }
  const options = {}
  let key
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

/**
 * Resolve an asset.
 * This function is used because child instances need access
 * to assets defined in its ancestor chain.
 */
export function resolveAsset (
  options: Object,
  type: string,
  id: string,
  warnMissing?: boolean
): any {
  /* istanbul ignore if */
  if (typeof id !== 'string') {
    return
  }
  const assets = options[type]
  // check local registration variations first
  if (hasOwn(assets, id)) return assets[id]
  const camelizedId = camelize(id)
  if (hasOwn(assets, camelizedId)) return assets[camelizedId]
  const PascalCaseId = capitalize(camelizedId)
  if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId]
  // fallback to prototype chain
  const res = assets[id] || assets[camelizedId] || assets[PascalCaseId]
  if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {
    warn(
      'Failed to resolve ' + type.slice(0, -1) + ': ' + id,
      options
    )
  }
  return res
}
<commit_msg>refactor: return is better when inject has no value (#7258)<commit_after>/* @flow */

import config from '../config'
import { warn } from './debug'
import { nativeWatch } from './env'
import { set } from '../observer/index'

import {
  ASSET_TYPES,
  LIFECYCLE_HOOKS
} from 'shared/constants'

import {
  extend,
  hasOwn,
  camelize,
  toRawType,
  capitalize,
  isBuiltInTag,
  isPlainObject
} from 'shared/util'

/**
 * Option overwriting strategies are functions that handle
 * how to merge a parent option value and a child option
 * value into the final value.
 */
const strats = config.optionMergeStrategies

/**
 * Options with restrictions
 */
if (process.env.NODE_ENV !== 'production') {
  strats.el = strats.propsData = function (parent, child, vm, key) {
    if (!vm) {
      warn(
        `option "${key}" can only be used during instance ` +
        'creation with the `new` keyword.'
      )
    }
    return defaultStrat(parent, child)
  }
}

/**
 * Helper that recursively merges two data objects together.
 */
function mergeData (to: Object, from: ?Object): Object {
  if (!from) return to
  let key, toVal, fromVal
  const keys = Object.keys(from)
  for (let i = 0; i < keys.length; i++) {
    key = keys[i]
    toVal = to[key]
    fromVal = from[key]
    if (!hasOwn(to, key)) {
      set(to, key, fromVal)
    } else if (isPlainObject(toVal) && isPlainObject(fromVal)) {
      mergeData(toVal, fromVal)
    }
  }
  return to
}

/**
 * Data
 */
export function mergeDataOrFn (
  parentVal: any,
  childVal: any,
  vm?: Component
): ?Function {
  if (!vm) {
    // in a Vue.extend merge, both should be functions
    if (!childVal) {
      return parentVal
    }
    if (!parentVal) {
      return childVal
    }
    // when parentVal & childVal are both present,
    // we need to return a function that returns the
    // merged result of both functions... no need to
    // check if parentVal is a function here because
    // it has to be a function to pass previous merges.
    return function mergedDataFn () {
      return mergeData(
        typeof childVal === 'function' ? childVal.call(this, this) : childVal,
        typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
      )
    }
  } else {
    return function mergedInstanceDataFn () {
      // instance merge
      const instanceData = typeof childVal === 'function'
        ? childVal.call(vm, vm)
        : childVal
      const defaultData = typeof parentVal === 'function'
        ? parentVal.call(vm, vm)
        : parentVal
      if (instanceData) {
        return mergeData(instanceData, defaultData)
      } else {
        return defaultData
      }
    }
  }
}

strats.data = function (
  parentVal: any,
  childVal: any,
  vm?: Component
): ?Function {
  if (!vm) {
    if (childVal && typeof childVal !== 'function') {
      process.env.NODE_ENV !== 'production' && warn(
        'The "data" option should be a function ' +
        'that returns a per-instance value in component ' +
        'definitions.',
        vm
      )

      return parentVal
    }
    return mergeDataOrFn(parentVal, childVal)
  }

  return mergeDataOrFn(parentVal, childVal, vm)
}

/**
 * Hooks and props are merged as arrays.
 */
function mergeHook (
  parentVal: ?Array<Function>,
  childVal: ?Function | ?Array<Function>
): ?Array<Function> {
  return childVal
    ? parentVal
      ? parentVal.concat(childVal)
      : Array.isArray(childVal)
        ? childVal
        : [childVal]
    : parentVal
}

LIFECYCLE_HOOKS.forEach(hook => {
  strats[hook] = mergeHook
})

/**
 * Assets
 *
 * When a vm is present (instance creation), we need to do
 * a three-way merge between constructor options, instance
 * options and parent options.
 */
function mergeAssets (
  parentVal: ?Object,
  childVal: ?Object,
  vm?: Component,
  key: string
): Object {
  const res = Object.create(parentVal || null)
  if (childVal) {
    process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm)
    return extend(res, childVal)
  } else {
    return res
  }
}

ASSET_TYPES.forEach(function (type) {
  strats[type + 's'] = mergeAssets
})

/**
 * Watchers.
 *
 * Watchers hashes should not overwrite one
 * another, so we merge them as arrays.
 */
strats.watch = function (
  parentVal: ?Object,
  childVal: ?Object,
  vm?: Component,
  key: string
): ?Object {
  // work around Firefox's Object.prototype.watch...
  if (parentVal === nativeWatch) parentVal = undefined
  if (childVal === nativeWatch) childVal = undefined
  /* istanbul ignore if */
  if (!childVal) return Object.create(parentVal || null)
  if (process.env.NODE_ENV !== 'production') {
    assertObjectType(key, childVal, vm)
  }
  if (!parentVal) return childVal
  const ret = {}
  extend(ret, parentVal)
  for (const key in childVal) {
    let parent = ret[key]
    const child = childVal[key]
    if (parent && !Array.isArray(parent)) {
      parent = [parent]
    }
    ret[key] = parent
      ? parent.concat(child)
      : Array.isArray(child) ? child : [child]
  }
  return ret
}

/**
 * Other object hashes.
 */
strats.props =
strats.methods =
strats.inject =
strats.computed = function (
  parentVal: ?Object,
  childVal: ?Object,
  vm?: Component,
  key: string
): ?Object {
  if (childVal && process.env.NODE_ENV !== 'production') {
    assertObjectType(key, childVal, vm)
  }
  if (!parentVal) return childVal
  const ret = Object.create(null)
  extend(ret, parentVal)
  if (childVal) extend(ret, childVal)
  return ret
}
strats.provide = mergeDataOrFn

/**
 * Default strategy.
 */
const defaultStrat = function (parentVal: any, childVal: any): any {
  return childVal === undefined
    ? parentVal
    : childVal
}

/**
 * Validate component names
 */
function checkComponents (options: Object) {
  for (const key in options.components) {
    validateComponentName(key)
  }
}

export function validateComponentName (name: string) {
  if (!/^[a-zA-Z][\w-]*$/.test(name)) {
    warn(
      'Invalid component name: "' + name + '". Component names ' +
      'can only contain alphanumeric characters and the hyphen, ' +
      'and must start with a letter.'
    )
  }
  if (isBuiltInTag(name) || config.isReservedTag(name)) {
    warn(
      'Do not use built-in or reserved HTML elements as component ' +
      'id: ' + name
    )
  }
}

/**
 * Ensure all props option syntax are normalized into the
 * Object-based format.
 */
function normalizeProps (options: Object, vm: ?Component) {
  const props = options.props
  if (!props) return
  const res = {}
  let i, val, name
  if (Array.isArray(props)) {
    i = props.length
    while (i--) {
      val = props[i]
      if (typeof val === 'string') {
        name = camelize(val)
        res[name] = { type: null }
      } else if (process.env.NODE_ENV !== 'production') {
        warn('props must be strings when using array syntax.')
      }
    }
  } else if (isPlainObject(props)) {
    for (const key in props) {
      val = props[key]
      name = camelize(key)
      if (process.env.NODE_ENV !== 'production' && isPlainObject(val)) {
        validatePropObject(name, val, vm)
      }
      res[name] = isPlainObject(val)
        ? val
        : { type: val }
    }
  } else if (process.env.NODE_ENV !== 'production') {
    warn(
      `Invalid value for option "props": expected an Array or an Object, ` +
      `but got ${toRawType(props)}.`,
      vm
    )
  }
  options.props = res
}

/**
 * Validate whether a prop object keys are valid.
 */
const propOptionsRE = /^(type|default|required|validator)$/

function validatePropObject (
  propName: string,
  prop: Object,
  vm: ?Component
) {
  for (const key in prop) {
    if (!propOptionsRE.test(key)) {
      warn(
        `Invalid key "${key}" in validation rules object for prop "${propName}".`,
        vm
      )
    }
  }
}

/**
 * Normalize all injections into Object-based format
 */
function normalizeInject (options: Object, vm: ?Component) {
  const inject = options.inject
  if (!inject) return
  const normalized = options.inject = {}
  if (Array.isArray(inject)) {
    for (let i = 0; i < inject.length; i++) {
      normalized[inject[i]] = { from: inject[i] }
    }
  } else if (isPlainObject(inject)) {
    for (const key in inject) {
      const val = inject[key]
      normalized[key] = isPlainObject(val)
        ? extend({ from: key }, val)
        : { from: val }
    }
  } else if (process.env.NODE_ENV !== 'production') {
    warn(
      `Invalid value for option "inject": expected an Array or an Object, ` +
      `but got ${toRawType(inject)}.`,
      vm
    )
  }
}

/**
 * Normalize raw function directives into object format.
 */
function normalizeDirectives (options: Object) {
  const dirs = options.directives
  if (dirs) {
    for (const key in dirs) {
      const def = dirs[key]
      if (typeof def === 'function') {
        dirs[key] = { bind: def, update: def }
      }
    }
  }
}

function assertObjectType (name: string, value: any, vm: ?Component) {
  if (!isPlainObject(value)) {
    warn(
      `Invalid value for option "${name}": expected an Object, ` +
      `but got ${toRawType(value)}.`,
      vm
    )
  }
}

/**
 * Merge two option objects into a new one.
 * Core utility used in both instantiation and inheritance.
 */
export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  if (process.env.NODE_ENV !== 'production') {
    checkComponents(child)
  }

  if (typeof child === 'function') {
    child = child.options
  }

  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)
  const extendsFrom = child.extends
  if (extendsFrom) {
    parent = mergeOptions(parent, extendsFrom, vm)
  }
  if (child.mixins) {
    for (let i = 0, l = child.mixins.length; i < l; i++) {
      parent = mergeOptions(parent, child.mixins[i], vm)
    }
  }
  const options = {}
  let key
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

/**
 * Resolve an asset.
 * This function is used because child instances need access
 * to assets defined in its ancestor chain.
 */
export function resolveAsset (
  options: Object,
  type: string,
  id: string,
  warnMissing?: boolean
): any {
  /* istanbul ignore if */
  if (typeof id !== 'string') {
    return
  }
  const assets = options[type]
  // check local registration variations first
  if (hasOwn(assets, id)) return assets[id]
  const camelizedId = camelize(id)
  if (hasOwn(assets, camelizedId)) return assets[camelizedId]
  const PascalCaseId = capitalize(camelizedId)
  if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId]
  // fallback to prototype chain
  const res = assets[id] || assets[camelizedId] || assets[PascalCaseId]
  if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {
    warn(
      'Failed to resolve ' + type.slice(0, -1) + ': ' + id,
      options
    )
  }
  return res
}
<|endoftext|>


<commit_before>(function() {
  'use strict';

  function sequoiaTreeDirective(Tree){

    return {
      restrict: 'AE',
      replace: true,
      templateUrl: 'angular-sequoia.html',
      scope: {
        'treeNodes': '=sequoiaTree',
        'model': '=ngModel',
        'template': '=nodeTemplate',
        'options': '='
      },
      link: function(scope) {
        scope.canEdit = scope.options.canEdit ? true : false;

        scope.model = _.isArray(scope.model) ? scope.model : [];
        scope.breadcrumbs = [];

        var tree = new Tree(scope.treeNodes, scope.template);

        scope.load = function(node) {
          scope.onlySelected = false;
          if(tree.isValidNode(node)) {
            tree.setCurrentNodes(node[tree.template.nodes]);
            scope.breadcrumbs = tree.breadcrumbs(node[tree.template.id]);
          } else {
            tree.setCurrentNodes();
            scope.breadcrumbs = [];
          }
        };

        scope.select = function(node) {
          if(node[tree.template.id]) {
            scope.model.push(node[tree.template.id]);
          }
        };

        scope.deselect = function(node) {
          var index = node[tree.template.id] ? _.indexOf(scope.model,node[tree.template.id]) : -1;
          if(index !== -1) {
            scope.model.splice(index, 1);
          }
        };

        scope.isSelected = function(node) {
          return _.indexOf(scope.model, node[tree.template.id]) !== -1 ? true : false;
        };

        scope.toggleSelected = function() {
          if(scope.onlySelected) {
            scope.onlySelected = false;
            tree.setCurrentNodes(tree.getNodesInPath());
          } else {
            scope.onlySelected = true;
            var selected = tree.findSelected(scope.model);
            tree.setNodesInPath(tree.nodes);
            tree.setCurrentNodes(selected);
          }
        };

        scope.tree = tree;

        scope.load();
      }
    };

  }

  sequoiaTreeDirective.$inject = ['SequoiaTree'];

  angular.module('ngSequoia')
    .directive('sequoiaTree', sequoiaTreeDirective);

})();<commit_msg>Add handling of adding, removing and editing nodes<commit_after>(function() {
  'use strict';

  function sequoiaTreeDirective(Tree){

    return {
      restrict: 'AE',
      replace: true,
      templateUrl: 'angular-sequoia.html',
      scope: {
        'treeNodes': '=sequoiaTree',
        'model': '=ngModel',
        'template': '=nodeTemplate',
        'options': '='
      },
      link: function(scope) {
        scope.canEdit = scope.options.canEdit ? true : false;

        scope.model = _.isArray(scope.model) ? scope.model : [];
        scope.breadcrumbs = [];

        var tree = new Tree(scope.treeNodes, scope.template);

        scope.load = function(node) {
          scope.onlySelected = false;
          if(tree.isValidNode(node)) {
            tree.setCurrentNodes(node[tree.template.nodes]);
            scope.breadcrumbs = tree.breadcrumbs(node[tree.template.id]);
          } else {
            tree.setCurrentNodes();
            scope.breadcrumbs = [];
          }
        };

        scope.select = function(node) {
          if(node[tree.template.id]) {
            scope.model.push(node[tree.template.id]);
          }
        };

        scope.deselect = function(node) {
          var index = node[tree.template.id] ? _.indexOf(scope.model,node[tree.template.id]) : -1;
          if(index !== -1) {
            scope.model.splice(index, 1);
          }
        };

        scope.isSelected = function(node) {
          return _.indexOf(scope.model, node[tree.template.id]) !== -1 ? true : false;
        };

        scope.toggleSelected = function() {
          if(scope.onlySelected) {
            scope.onlySelected = false;
            tree.setCurrentNodes(tree.getNodesInPath());
          } else {
            scope.onlySelected = true;
            var selected = tree.findSelected(scope.model);
            tree.setNodesInPath(tree.nodes);
            tree.setCurrentNodes(selected);
          }
        };

        scope.tree = tree;

        scope.load();

        /* Handle adding and editing nodes */
        scope.toggleEditing = function() {
          scope.isEditing = !scope.isEditing;
        };

        scope.addNode = function(node) {
          if(tree.isValidNode(node)) {
            scope.load(node);
          }
          scope.options.addNode.call(scope,tree.nodes);
        };

        scope.remove = function(node) {
          var index = node ? _.indexOf(tree.nodes, node) : -1;
          if(index !== -1) {
            tree.nodes.splice(index, 1);
          }
        };
      }
    };

  }

  sequoiaTreeDirective.$inject = ['SequoiaTree'];

  angular.module('ngSequoia')
    .directive('sequoiaTree', sequoiaTreeDirective);

})();<|endoftext|>


<commit_before>(function() {
  'use strict';

  function sequoiaTreeDirective(Tree){

    return {
      restrict: 'AE',
      replace: true,
      templateUrl: 'angular-sequoia.html',
      scope: {
        'treeNodes': '=sequoiaTree',
        'model': '=ngModel',
        'template': '=nodeTemplate',
        'options': '='
      },
      link: function(scope) {
        scope.canEdit = scope.options.canEdit ? true : false;

        scope.model = _.isArray(scope.model) ? scope.model : [];
        scope.breadcrumbs = [];

        var tree = new Tree(scope.treeNodes, scope.template);

        scope.load = function(node) {
          scope.onlySelected = false;
          if(tree.isValidNode(node)) {
            tree.setCurrentNodes(node[tree.template.nodes]);
            scope.breadcrumbs = tree.breadcrumbs(node[tree.template.id]);
          } else {
            tree.setCurrentNodes();
            scope.breadcrumbs = [];
          }
        };

        scope.select = function(node) {
          if(node[tree.template.id]) {
            scope.model.push(node[tree.template.id]);
          }
        };

        scope.deselect = function(node) {
          var index = node[tree.template.id] ? _.indexOf(scope.model,node[tree.template.id]) : -1;
          if(index !== -1) {
            scope.model.splice(index, 1);
          }
        };

        scope.isSelected = function(node) {
          return _.indexOf(scope.model, node[tree.template.id]) !== -1 ? true : false;
        };

        scope.toggleSelected = function() {
          if(scope.onlySelected) {
            scope.onlySelected = false;
            tree.setCurrentNodes(tree.getNodesInPath());
          } else {
            scope.onlySelected = true;
            var selected = tree.findSelected(scope.model);
            tree.setNodesInPath(tree.nodes);
            tree.setCurrentNodes(selected);
          }
        };

        scope.tree = tree;

        scope.load();

        /* Handle adding and editing nodes */
        scope.toggleEditing = function() {
          scope.isEditing = !scope.isEditing;
        };

        scope.addNode = function(node) {
          if(tree.isValidNode(node)) {
            scope.load(node);
          }
          scope.options.addNode.call(scope,tree.nodes);
        };

        scope.remove = function(node) {
          var index = node ? _.indexOf(tree.nodes, node) : -1;
          if(index !== -1) {
            tree.nodes.splice(index, 1);
          }
        };
      }
    };

  }

  sequoiaTreeDirective.$inject = ['SequoiaTree'];

  angular.module('ngSequoia')
    .directive('sequoiaTree', sequoiaTreeDirective);

})();<commit_msg>Add BUTTONS constant<commit_after>(function() {
  'use strict';

  function sequoiaTreeDirective(Tree, BUTTONS){

    return {
      restrict: 'AE',
      replace: true,
      templateUrl: 'angular-sequoia.html',
      scope: {
        'treeNodes': '=sequoiaTree',
        'model': '=ngModel',
        'template': '=nodeTemplate',
        'options': '='
      },
      link: function(scope) {
        scope.canEdit = scope.options.canEdit ? true : false;

        scope.model = _.isArray(scope.model) ? scope.model : [];
        scope.breadcrumbs = [];

        var tree = new Tree(scope.treeNodes, scope.template);

        scope.load = function(node) {
          scope.onlySelected = false;
          if(tree.isValidNode(node)) {
            tree.setCurrentNodes(node[tree.template.nodes]);
            scope.breadcrumbs = tree.breadcrumbs(node[tree.template.id]);
          } else {
            tree.setCurrentNodes();
            scope.breadcrumbs = [];
          }
        };

        scope.select = function(node) {
          if(node[tree.template.id]) {
            scope.model.push(node[tree.template.id]);
          }
        };

        scope.deselect = function(node) {
          var index = node[tree.template.id] ? _.indexOf(scope.model,node[tree.template.id]) : -1;
          if(index !== -1) {
            scope.model.splice(index, 1);
          }
        };

        scope.isSelected = function(node) {
          return _.indexOf(scope.model, node[tree.template.id]) !== -1 ? true : false;
        };

        scope.toggleSelected = function() {
          if(scope.onlySelected) {
            scope.onlySelected = false;
            tree.setCurrentNodes(tree.getNodesInPath());
          } else {
            scope.onlySelected = true;
            var selected = tree.findSelected(scope.model);
            tree.setNodesInPath(tree.nodes);
            tree.setCurrentNodes(selected);
          }
        };

        scope.tree = tree;

        scope.load();

        /* Handle adding and editing nodes */
        scope.toggleEditing = function() {
          scope.isEditing = !scope.isEditing;
        };

        scope.addNode = function(node) {
          if(tree.isValidNode(node)) {
            scope.load(node);
          }
          scope.options.addNode.call(scope,tree.nodes);
        };

        scope.remove = function(node) {
          var index = node ? _.indexOf(tree.nodes, node) : -1;
          if(index !== -1) {
            tree.nodes.splice(index, 1);
          }
        };
      }
    };

  }

  sequoiaTreeDirective.$inject = ['SequoiaTree', 'BUTTONS'];

  angular.module('ngSequoia')
    .directive('sequoiaTree', sequoiaTreeDirective);

})();<|endoftext|>


<commit_before>'use strict'
var ButtonNavigator = require('./ButtonNavigator')
var Slider = require('./Slider')
var EventManager = require('../lib/eventManager')
var yo = require('yo-yo')

function StepManager (_parent, _traceManager) {
  this.event = new EventManager()
  this.parent = _parent
  this.traceManager = _traceManager

  var self = this
  this.parent.event.register('newTraceLoaded', this, function () {
    self.traceManager.getLength(function (error, length) {
      if (error) {
        console.log(error)
      } else {
        self.slider.init(length)
        self.init()
      }
    })
  })

  this.slider = new Slider(this.traceManager)
  this.slider.event.register('moved', this, function (step) {
    self.sliderMoved(step)
  })

  this.buttonNavigator = new ButtonNavigator(_parent, this.traceManager)
  this.buttonNavigator.event.register('stepIntoBack', this, function () {
    self.stepIntoBack()
  })
  this.buttonNavigator.event.register('stepIntoForward', this, function () {
    self.stepIntoForward()
  })
  this.buttonNavigator.event.register('stepOverBack', this, function () {
    self.stepOverBack()
  })
  this.buttonNavigator.event.register('stepOverForward', this, function () {
    self.stepOverForward()
  })
  this.buttonNavigator.event.register('jumpNextCall', this, function () {
    self.jumpNextCall()
  })
  this.buttonNavigator.event.register('jumpOut', this, function () {
    self.jumpOut()
  })
  this.buttonNavigator.event.register('jumpToException', this, function (exceptionIndex) {
    self.jumpTo(exceptionIndex)
  })
}

StepManager.prototype.render = function () {
  return (
  yo`

        ${this.slider.render()}
        ${this.buttonNavigator.render()}

`
  )
}

StepManager.prototype.reset = function () {
  this.slider.setValue(0)
  this.currentStepIndex = 0
  this.buttonNavigator.reset()
}

StepManager.prototype.init = function () {
  this.slider.setValue(0)
  this.changeState(0)
}

StepManager.prototype.newTraceAvailable = function () {
  this.init()
}

StepManager.prototype.jumpTo = function (step) {
  if (!this.traceManager.inRange(step)) {
    return
  }
  this.slider.setValue(step)
  this.changeState(step)
}

StepManager.prototype.sliderMoved = function (step) {
  if (!this.traceManager.inRange(step)) {
    return
  }
  this.changeState(step)
}

StepManager.prototype.stepIntoForward = function () {
  if (!this.traceManager.isLoaded()) {
    return
  }
  var step = this.currentStepIndex + 1
  if (!this.traceManager.inRange(step)) {
    return
  }
  this.slider.setValue(step)
  this.changeState(step)
}

StepManager.prototype.stepIntoBack = function () {
  if (!this.traceManager.isLoaded()) {
    return
  }
  var step = this.currentStepIndex - 1
  if (!this.traceManager.inRange(step)) {
    return
  }
  this.slider.setValue(step)
  this.changeState(step)
}

StepManager.prototype.stepOverForward = function () {
  if (!this.traceManager.isLoaded()) {
    return
  }
  var step = this.traceManager.findStepOverForward(this.currentStepIndex)
  this.slider.setValue(step)
  this.changeState(step)
}

StepManager.prototype.stepOverBack = function () {
  if (!this.traceManager.isLoaded()) {
    return
  }
  var step = this.traceManager.findStepOverBack(this.currentStepIndex)
  this.slider.setValue(step)
  this.changeState(step)
}

StepManager.prototype.jumpNextCall = function () {
  if (!this.traceManager.isLoaded()) {
    return
  }
  var step = this.traceManager.findNextCall(this.currentStepIndex)
  this.slider.setValue(step)
  this.changeState(step)
}

StepManager.prototype.jumpOut = function () {
  if (!this.traceManager.isLoaded()) {
    return
  }
  var step = this.traceManager.findStepOut(this.currentStepIndex)
  this.slider.setValue(step)
  this.changeState(step)
}

StepManager.prototype.changeState = function (step) {
  this.currentStepIndex = step
  this.buttonNavigator.stepChanged(step)
  this.event.trigger('stepChanged', [step])
}

module.exports = StepManager
<commit_msg>use soliditySlider<commit_after>'use strict'
var ButtonNavigator = require('./ButtonNavigator')
var Slider = require('./Slider')
var SoliditySlider = require('./SoliditySlider')
var EventManager = require('../lib/eventManager')
var SourceMappingDecoder = require('../util/sourceMappingDecoder')
var yo = require('yo-yo')

function StepManager (_parent, _traceManager) {
  this.event = new EventManager()
  this.sourceMappingDecoder = new SourceMappingDecoder()
  this.parent = _parent
  this.traceManager = _traceManager
  this.sourceMapByAddress = {}

  var self = this
  this.parent.event.register('newTraceLoaded', this, function () {
    self.traceManager.getLength(function (error, length) {
      if (error) {
        console.log(error)
      } else {
        self.slider.init(length)
        self.soliditySlider.init(self.parent.callTree.reducedTraceBySourceLocation.length)
        self.init()
      }
    })
  })

  this.slider = new Slider(this.traceManager)
  this.slider.event.register('moved', this, function (step) {
    self.sliderMoved(step)
    self.soliditySlider.setValue(step)
  })

  this.soliditySlider = new SoliditySlider(this.traceManager, self.parent.callTree.reducedTraceBySourceLocation)
  this.soliditySlider.event.register('moved', this, function (srcLocationStep) {
    var step = self.parent.callTree.reducedTraceBySourceLocation[srcLocationStep]
    self.sliderMoved(step)
    self.slider.setValue(step)
  })

  this.buttonNavigator = new ButtonNavigator(_parent, this.traceManager)
  this.buttonNavigator.event.register('stepIntoBack', this, function () {
    self.stepIntoBack()
  })
  this.buttonNavigator.event.register('stepIntoForward', this, function () {
    self.stepIntoForward()
  })
  this.buttonNavigator.event.register('stepOverBack', this, function () {
    self.stepOverBack()
  })
  this.buttonNavigator.event.register('stepOverForward', this, function () {
    self.stepOverForward()
  })
  this.buttonNavigator.event.register('jumpNextCall', this, function () {
    self.jumpNextCall()
  })
  this.buttonNavigator.event.register('jumpOut', this, function () {
    self.jumpOut()
  })
  this.buttonNavigator.event.register('jumpToException', this, function (exceptionIndex) {
    self.jumpTo(exceptionIndex)
  })

  if (this.parent.solidityProxy.loaded()) {
    this.parent.vmDebugger.asmCode.register('hide', () => {
      this.soliditySlider.show()
      this.slider.hide()
    })
    this.parent.vmDebugger.asmCode.register('show', () => {
      this.soliditySlider.hide()
      this.slider.show()
    })
  }
}

StepManager.prototype.render = function () {
  return (
  yo`

        ${this.slider.render()}
        ${this.soliditySlider.render()}
        ${this.buttonNavigator.render()}

`
  )
}

StepManager.prototype.reset = function () {
  this.slider.setValue(0)
  this.soliditySlider.setValue(0)
  this.currentStepIndex = 0
  this.buttonNavigator.reset()
}

StepManager.prototype.init = function () {
  this.slider.setValue(0)
  this.soliditySlider.setValue(0)
  this.changeState(0)
}

StepManager.prototype.newTraceAvailable = function () {
  this.init()
}

StepManager.prototype.jumpTo = function (step) {
  if (!this.traceManager.inRange(step)) {
    return
  }
  this.slider.setValue(step)
  this.soliditySlider.setValue(step)
  this.changeState(step)
}

StepManager.prototype.sliderMoved = function (step) {
  if (!this.traceManager.inRange(step)) {
    return
  }
  this.changeState(step)
}

StepManager.prototype.stepIntoForward = function () {
  if (!this.traceManager.isLoaded()) {
    return
  }
  var step = this.currentStepIndex + 1
  if (!this.traceManager.inRange(step)) {
    return
  }
  this.slider.setValue(step)
  this.soliditySlider.setValue(step)
  this.changeState(step)
}

StepManager.prototype.stepIntoBack = function () {
  if (!this.traceManager.isLoaded()) {
    return
  }
  var step = this.currentStepIndex - 1
  if (!this.traceManager.inRange(step)) {
    return
  }
  this.slider.setValue(step)
  this.soliditySlider.setValue(step)
  this.changeState(step)
}

StepManager.prototype.stepOverForward = function () {
  if (!this.traceManager.isLoaded()) {
    return
  }
  var step = this.traceManager.findStepOverForward(this.currentStepIndex)
  this.slider.setValue(step)
  this.soliditySlider.setValue(step)
  this.changeState(step)
}

StepManager.prototype.stepOverBack = function () {
  if (!this.traceManager.isLoaded()) {
    return
  }
  var step = this.traceManager.findStepOverBack(this.currentStepIndex)
  this.slider.setValue(step)
  this.soliditySlider.setValue(step)
  this.changeState(step)
}

StepManager.prototype.jumpNextCall = function () {
  if (!this.traceManager.isLoaded()) {
    return
  }
  var step = this.traceManager.findNextCall(this.currentStepIndex)
  this.slider.setValue(step)
  this.soliditySlider.setValue(step)
  this.changeState(step)
}

StepManager.prototype.jumpOut = function () {
  if (!this.traceManager.isLoaded()) {
    return
  }
  var step = this.traceManager.findStepOut(this.currentStepIndex)
  this.slider.setValue(step)
  this.soliditySlider.setValue(step)
  this.changeState(step)
}

StepManager.prototype.changeState = function (step) {
  this.currentStepIndex = step
  this.buttonNavigator.stepChanged(step)
  this.event.trigger('stepChanged', [step])
}

module.exports = StepManager
<|endoftext|>


<commit_before>// If the scheduler is not customized via `etch.setScheduler`, an instance of
// this class will be used to schedule updates to the document. The
// `updateDocument` method accepts functions to be run at some point in the
// future, then runs them on the next animation frame.
export default class DefaultScheduler {
  constructor () {
    this.updateRequests = []
    this.updateRequested = false
    this.performUpdates = this.performUpdates.bind(this)
  }

  updateDocument (fn) {
    this.updateRequests.push(fn)
    if (!this.updateRequested) {
      this.updateRequested = true
      window.requestAnimationFrame(this.performUpdates)
    }
  }

  updateDocumentSync (fn) {
    this.updateRequests.push(fn)
    this.performUpdates()
  }

  getNextUpdatePromise () {
    if (!this.nextUpdatePromise) {
      this.nextUpdatePromise = new Promise(resolve => {
        this.resolveNextUpdatePromise = resolve
      })
    }
    return this.nextUpdatePromise
  }

  performUpdates () {
    while (this.updateRequests.length > 0) {
      this.updateRequests.shift()()
    }
    this.updateRequested = false
    if (this.nextUpdatePromise) {
      let resolveNextUpdatePromise = this.resolveNextUpdatePromise
      this.nextUpdatePromise = null
      this.resolveNextUpdatePromise = null
      resolveNextUpdatePromise()
    }
  }
}
<commit_msg>Cancel the pending animation frame on updateDocumentSync<commit_after>// If the scheduler is not customized via `etch.setScheduler`, an instance of
// this class will be used to schedule updates to the document. The
// `updateDocument` method accepts functions to be run at some point in the
// future, then runs them on the next animation frame.
export default class DefaultScheduler {
  constructor () {
    this.updateRequests = []
    this.pendingAnimationFrame = null
    this.performUpdates = this.performUpdates.bind(this)
  }

  updateDocument (fn) {
    this.updateRequests.push(fn)
    if (!this.pendingAnimationFrame) {
      this.pendingAnimationFrame = window.requestAnimationFrame(this.performUpdates)
    }
  }

  updateDocumentSync (fn) {
    if (this.pendingAnimationFrame) window.cancelAnimationFrame(this.pendingAnimationFrame)
    this.updateRequests.push(fn)
    this.performUpdates()
  }

  getNextUpdatePromise () {
    if (!this.nextUpdatePromise) {
      this.nextUpdatePromise = new Promise(resolve => {
        this.resolveNextUpdatePromise = resolve
      })
    }
    return this.nextUpdatePromise
  }

  performUpdates () {
    while (this.updateRequests.length > 0) {
      this.updateRequests.shift()()
    }
    this.pendingAnimationFrame = null
    if (this.nextUpdatePromise) {
      let resolveNextUpdatePromise = this.resolveNextUpdatePromise
      this.nextUpdatePromise = null
      this.resolveNextUpdatePromise = null
      resolveNextUpdatePromise()
    }
  }
}
<|endoftext|>


<commit_before>// Copyright 2017, University of Colorado Boulder

/**
 * Tests for styling accessibility siblings. Siblings should be positioned on top of other elements in the PDOM.
 *
 * @author Jesse Greenberg (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  // const AccessibilityUtil = require( 'SCENERY/accessibility/AccessibilityUtil' );
  const Bounds2 = require( 'DOT/Bounds2' );
  const Display = require( 'SCENERY/display/Display' );
  const Node = require( 'SCENERY/nodes/Node' );
  const Rectangle = require( 'SCENERY/nodes/Rectangle' );
  const Vector2 = require( 'DOT/Vector2' );

  // constants
  const PIXEL_PADDING = 3;

  QUnit.module( 'AccessibleSiblingTests' );

  /**
   * Returns true if the node's first accessible peer has a primary sibling with bounds thar are correctly positioned
   * in the viewport. Some padding is required for the HTML elements to have defined bounds, so we allow a few pixels
   * of error.
   * @param  {Node} node
   * @returns {boolean}
   */
  const siblingBoundsCorrect = node => {
    const nodeBounds = node.globalBounds;

    const siblingRect = node.accessibleInstances[ 0 ].peer.primarySibling.getBoundingClientRect();
    const siblingBounds = new Bounds2( siblingRect.x, siblingRect.y, siblingRect.x + siblingRect.width, siblingRect.y + siblingRect.height );

    return nodeBounds.equalsEpsilon( siblingBounds, PIXEL_PADDING );
  };

  // tests
  QUnit.test( 'sibling positioning', function( assert ) {

    const rootNode = new Node( { tagName: 'div' } );
    const display = new Display( rootNode ); // eslint-disable-line
    document.body.appendChild( display.domElement );

    // test bounds are set for basic input elements
    const buttonElement = new Rectangle( 5, 5, 5, 5, { tagName: 'button' } );
    const divElement = new Rectangle( 0, 0, 20, 20, { tagName: 'div', focusable: true } );
    const inputElement = new Rectangle( 10, 3, 25, 5, { tagName: 'input', inputType: 'range' } );

    rootNode.addChild( buttonElement );
    rootNode.addChild( divElement );
    rootNode.addChild( inputElement );

    // udpdate so the display to position elements
    display.updateDisplay();

    assert.ok( siblingBoundsCorrect( buttonElement ), 'button element child of root correctly positioned' );
    assert.ok( siblingBoundsCorrect( divElement ), 'div element child of root correctly positioned' );
    assert.ok( siblingBoundsCorrect( inputElement ), 'input element child of root correctly positioned' );

    // test that bounds are set correctly once we have a hierarchy and add transformations
    rootNode.removeChild( buttonElement );
    rootNode.removeChild( divElement );
    rootNode.removeChild( inputElement );

    rootNode.addChild( divElement );
    divElement.addChild( buttonElement );
    buttonElement.addChild( inputElement );

    // arbitrary transformations down the tree (should be propagated to input element)
    divElement.setCenter( new Vector2( 50, 50 ) );
    buttonElement.setScaleMagnitude( 0.89 );
    inputElement.setRotation( Math.PI / 4 );

    // udpdate so the display to position elements
    display.updateDisplay();
    assert.ok( siblingBoundsCorrect( inputElement ), 'input element descendant incorrectly positioned' );

    // remove the display element so it doesn't interfere with qunit api
    document.body.removeChild( display.domElement );
  } );
} );<commit_msg>test for inner content change, see #852<commit_after>// Copyright 2017, University of Colorado Boulder

/**
 * Tests for styling accessibility siblings. Siblings should be positioned on top of other elements in the PDOM.
 *
 * @author Jesse Greenberg (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  // const AccessibilityUtil = require( 'SCENERY/accessibility/AccessibilityUtil' );
  const Bounds2 = require( 'DOT/Bounds2' );
  const Display = require( 'SCENERY/display/Display' );
  const Node = require( 'SCENERY/nodes/Node' );
  const Rectangle = require( 'SCENERY/nodes/Rectangle' );
  const Vector2 = require( 'DOT/Vector2' );

  // constants
  const PIXEL_PADDING = 3;

  QUnit.module( 'AccessibleSiblingTests' );

  /**
   * Returns true if the node's first accessible peer has a primary sibling with bounds thar are correctly positioned
   * in the viewport. Some padding is required for the HTML elements to have defined bounds, so we allow a few pixels
   * of error.
   * @param  {Node} node
   * @returns {boolean}
   */
  const siblingBoundsCorrect = node => {
    const nodeBounds = node.globalBounds;

    const siblingRect = node.accessibleInstances[ 0 ].peer.primarySibling.getBoundingClientRect();
    const siblingBounds = new Bounds2( siblingRect.x, siblingRect.y, siblingRect.x + siblingRect.width, siblingRect.y + siblingRect.height );

    return nodeBounds.equalsEpsilon( siblingBounds, PIXEL_PADDING );
  };

  // tests
  QUnit.test( 'sibling positioning', function( assert ) {

    const rootNode = new Node( { tagName: 'div' } );
    const display = new Display( rootNode ); // eslint-disable-line
    document.body.appendChild( display.domElement );

    // test bounds are set for basic input elements
    const buttonElement = new Rectangle( 5, 5, 5, 5, { tagName: 'button' } );
    const divElement = new Rectangle( 0, 0, 20, 20, { tagName: 'div', focusable: true } );
    const inputElement = new Rectangle( 10, 3, 25, 5, { tagName: 'input', inputType: 'range' } );

    rootNode.addChild( buttonElement );
    rootNode.addChild( divElement );
    rootNode.addChild( inputElement );

    // udpdate so the display to position elements
    display.updateDisplay();

    assert.ok( siblingBoundsCorrect( buttonElement ), 'button element child of root correctly positioned' );
    assert.ok( siblingBoundsCorrect( divElement ), 'div element child of root correctly positioned' );
    assert.ok( siblingBoundsCorrect( inputElement ), 'input element child of root correctly positioned' );

    // test that bounds are set correctly once we have a hierarchy and add transformations
    rootNode.removeChild( buttonElement );
    rootNode.removeChild( divElement );
    rootNode.removeChild( inputElement );

    rootNode.addChild( divElement );
    divElement.addChild( buttonElement );
    buttonElement.addChild( inputElement );

    // arbitrary transformations down the tree (should be propagated to input element)
    divElement.setCenter( new Vector2( 50, 50 ) );
    buttonElement.setScaleMagnitude( 0.89 );
    inputElement.setRotation( Math.PI / 4 );

    // udpdate so the display to position elements
    display.updateDisplay();
    assert.ok( siblingBoundsCorrect( buttonElement ), 'button element descendant correctly positioned' );
    assert.ok( siblingBoundsCorrect( inputElement ), 'input element descendant correctly positioned' );

    // when inner content of an element changes, its client bounds change - make sure that the element still matches
    // the Node
    buttonElement.innerHTML = 'Some Test';
    display.updateDisplay();
    assert.ok( siblingBoundsCorrect( buttonElement ), 'button element descendant correclty positioned after inner content changed' );

    // remove the display element so it doesn't interfere with qunit api
    document.body.removeChild( display.domElement );
  } );
} );<|endoftext|>


<commit_before>/**
 * Quink, Copyright (c) 2013-2014 IMD - International Institute for Management Development, Switzerland.
 *
 * This file is part of Quink.
 *
 * Quink is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Quink is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Quink.  If not, see <http://www.gnu.org/licenses/>.
 */

define([
    'Underscore',
    'jquery',
    'rangy',
    'hithandler/HitHandler',
    'locrange/LocRangeUtil',
    'util/PubSub'
], function (_, $, rangy, HitHandler, LocRangeUtil, PubSub) {
    'use strict';

    var FocusTracker = function () {
        this.states = [];
        this.onSelectionChangeBound = this.onSelectionChange.bind(this);
    };

    FocusTracker.prototype.init = function (selector) {
        var onTextInsert = this.onTextInsert.bind(this);
        $('body').on('orientationchange', this.onOrientationChange);
        $(window).on('scroll', this.onScroll());
        HitHandler.register(this);
        $(selector)
            .on('blur', this.onBlur.bind(this))
            .on('focus', this.onFocus.bind(this))
            .on('scroll', this.onEditableScroll.bind(this));
        // Ensure there's always an editable.
        this.editable = $(selector)[0];
        this.firstFocus = true;
        this.bindSelectionEvents();
        PubSub.subscribe('insert.char', onTextInsert);
        PubSub.subscribe('plugin.saved', onTextInsert);
        PubSub.subscribe('insert.text', onTextInsert);
        PubSub.subscribe('insert.html', onTextInsert);
    };

    /**
     * If the browser supports selectionchange events use them. Otherwise do the best that we can.
     */
    FocusTracker.prototype.bindSelectionEvents = function () {
        var onSelectionChange = this.onSelectionChange.bind(this);
        if (document.onselectionchange === undefined) {
            PubSub.subscribe('command.executed', onSelectionChange);
            PubSub.subscribe('nav.executed', onSelectionChange);
            PubSub.subscribe('plugin.exited', onSelectionChange);
            PubSub.subscribe('editable.range', onSelectionChange);
            PubSub.subscribe('insert.char', function () {
                setTimeout(onSelectionChange, 20);
            });
            PubSub.subscribe('plugin.saved', onSelectionChange);
            PubSub.subscribe('insert.text', onSelectionChange);
            PubSub.subscribe('insert.html', onSelectionChange);
        }
    };

    /**
     * The event is generated by the browser before the orientation change is complete.
     * Wait until after completion. Would be better to detect this rather than go on a timer.
     */
    FocusTracker.prototype.onOrientationChange = function (event) {
        setTimeout(function () {
            PubSub.publish('event.orientationchange', event);
        }, 500);
    };

    /**
     * Only fire scroll events if there has actually been scrolling. No idea why the browser seems to be firing
     * scroll events when no scrolling has taken place.
     */
    FocusTracker.prototype.onScroll = function () {
        var lastScrollTop = 0;
        return function (event) {
            var scrollTop = $('body').scrollTop();
            if (scrollTop !== lastScrollTop) {
                PubSub.publish('window.scroll', event);
                lastScrollTop = scrollTop;
            }
        };
    };

    FocusTracker.prototype.onEditableScroll = function (event) {
        PubSub.publish('editable.scroll', event);
    };

    /**
     * Switch off the selection change handler to avoid an empty selection being saved.
     */
    FocusTracker.prototype.onBlur = function (event) {
        var editable = event.delegateTarget;
        $(document).off('selectionchange.focustracker');
        this.lastEditable = editable;
        PubSub.publish('editable.blur', editable);
    };

    FocusTracker.prototype.onFocus = function (event) {
        var editable = event.delegateTarget,
            state = this.findState(editable);
        $(document).on('selectionchange.focustracker', this.onSelectionChangeBound);
        this.editable = editable;
        this.lastEditable = editable;
        if (state.range) {
            state.range.refresh();
            rangy.getSelection().setSingleRange(state.range);
        }
        PubSub.publish('editable.focus', editable);
    };

    /**
     * Sets the focus to the last editable that had the focus. Ensures that there is always
     * a range is the focused editable.
     */
    FocusTracker.prototype.restoreFocus = function () {
        var editable = this.lastEditable,
            state = this.findState(editable),
            range = state.range;
        editable.focus();
        if (!range) {
            range = rangy.createRange();
            range.setStart(editable, 0);
            range.collapse(true);
            state.range = range;
        }
        $(document).on('selectionchange.focustracker', this.onSelectionChange.bind(this));
        rangy.getSelection().setSingleRange(range);
        return range;
    };

    /**
     * Removing focus from the current editable, but don't want that change in selection
     * to be reflected in the saved selection state for the editable.
     */
    FocusTracker.prototype.removeFocus = function () {
        this.storeState(this.editable);
        $(document).off('selectionchange.focustracker');
        this.editable.blur();
    };

    FocusTracker.prototype.onSelectionChange = function () {
        this.storeState(this.editable);
        PubSub.publish('selection.change', LocRangeUtil.getSelectionLoc);
    };

    /**
     * Allow time for the DOM to be updated before saving the state.
     */
    FocusTracker.prototype.onTextInsert = function () {
        setTimeout(function () {
            this.storeState(this.editable);
        }.bind(this), 20);
    };

    FocusTracker.prototype.findState = function (editable) {
        var state = _.find(this.states, function (state) {
                return state.editable === editable;
            });
        if (!state) {
            state = {
                editable: editable
            };
            this.states.push(state);
        }
        return state;
    };

    FocusTracker.prototype.storeState = function (editable) {
        var state = this.findState(editable);
        state.range = this.getRange(editable);
        state.bodyScrollTop = this.bodyScrollTop;
        state.scrollTop = this.scrollTop;
        return state;
    };

    /**
     * Returns the current range if it's in the editable.
    */
    FocusTracker.prototype.getRange = function (editable) {
        var sel = rangy.getSelection(),
            range = sel.rangeCount && sel.getRangeAt(0),
            result;
        if (range && $(range.startContainer).closest(editable).length) {
            result = range;
        }
        return result;
    };

    /**
     * Executes func every delay interval until func returns true.
     */
    FocusTracker.prototype.until = function until(func, delay) {
        if (!func()) {
            _.delay(until, delay, func, delay);
        }
    };

    /**
     * Allow time for the range to be set within the document. It seems to take ages on iOS.
     * Returns false o allow other hit handlers to access the same event.
     */
    FocusTracker.prototype.handle = function (event) {
        var storeState = function () {
                var editable = event.event.delegateTarget,
                    executed;
                if (this.getRange(editable)) {
                    executed = true;
                    this.storeState(editable);
                    if (document.onselectionchange === undefined) {
                        this.onSelectionChange();
                    }
                }
                return executed;
            }.bind(this);
        this.until(storeState, 10);
        return false;
    };

    var theInstance = new FocusTracker();

    return {
        init: theInstance.init.bind(theInstance),
        restoreFocus: theInstance.restoreFocus.bind(theInstance),
        removeFocus: theInstance.removeFocus.bind(theInstance)
    };
});
<commit_msg>Bug fix: makes toolbar showing on page load work again.<commit_after>/**
 * Quink, Copyright (c) 2013-2014 IMD - International Institute for Management Development, Switzerland.
 *
 * This file is part of Quink.
 *
 * Quink is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Quink is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Quink.  If not, see <http://www.gnu.org/licenses/>.
 */

define([
    'Underscore',
    'jquery',
    'rangy',
    'hithandler/HitHandler',
    'locrange/LocRangeUtil',
    'util/PubSub'
], function (_, $, rangy, HitHandler, LocRangeUtil, PubSub) {
    'use strict';

    var FocusTracker = function () {
        this.states = [];
        this.onSelectionChangeBound = this.onSelectionChange.bind(this);
    };

    FocusTracker.prototype.init = function (selector) {
        var onTextInsert = this.onTextInsert.bind(this);
        $('body').on('orientationchange', this.onOrientationChange);
        $(window).on('scroll', this.onScroll());
        HitHandler.register(this);
        $(selector)
            .on('blur', this.onBlur.bind(this))
            .on('focus', this.onFocus.bind(this))
            .on('scroll', this.onEditableScroll.bind(this));
        // Ensure there's always an editable.
        this.editable = $(selector)[0];
        this.lastEditable = this.editable;
        this.firstFocus = true;
        this.bindSelectionEvents();
        PubSub.subscribe('insert.char', onTextInsert);
        PubSub.subscribe('plugin.saved', onTextInsert);
        PubSub.subscribe('insert.text', onTextInsert);
        PubSub.subscribe('insert.html', onTextInsert);
    };

    /**
     * If the browser supports selectionchange events use them. Otherwise do the best that we can.
     */
    FocusTracker.prototype.bindSelectionEvents = function () {
        var onSelectionChange = this.onSelectionChange.bind(this);
        if (document.onselectionchange === undefined) {
            PubSub.subscribe('command.executed', onSelectionChange);
            PubSub.subscribe('nav.executed', onSelectionChange);
            PubSub.subscribe('plugin.exited', onSelectionChange);
            PubSub.subscribe('editable.range', onSelectionChange);
            PubSub.subscribe('insert.char', function () {
                setTimeout(onSelectionChange, 20);
            });
            PubSub.subscribe('plugin.saved', onSelectionChange);
            PubSub.subscribe('insert.text', onSelectionChange);
            PubSub.subscribe('insert.html', onSelectionChange);
        }
    };

    /**
     * The event is generated by the browser before the orientation change is complete.
     * Wait until after completion. Would be better to detect this rather than go on a timer.
     */
    FocusTracker.prototype.onOrientationChange = function (event) {
        setTimeout(function () {
            PubSub.publish('event.orientationchange', event);
        }, 500);
    };

    /**
     * Only fire scroll events if there has actually been scrolling. No idea why the browser seems to be firing
     * scroll events when no scrolling has taken place.
     */
    FocusTracker.prototype.onScroll = function () {
        var lastScrollTop = 0;
        return function (event) {
            var scrollTop = $('body').scrollTop();
            if (scrollTop !== lastScrollTop) {
                PubSub.publish('window.scroll', event);
                lastScrollTop = scrollTop;
            }
        };
    };

    FocusTracker.prototype.onEditableScroll = function (event) {
        PubSub.publish('editable.scroll', event);
    };

    /**
     * Switch off the selection change handler to avoid an empty selection being saved.
     */
    FocusTracker.prototype.onBlur = function (event) {
        var editable = event.delegateTarget;
        $(document).off('selectionchange.focustracker');
        this.lastEditable = editable;
        PubSub.publish('editable.blur', editable);
    };

    FocusTracker.prototype.onFocus = function (event) {
        var editable = event.delegateTarget,
            state = this.findState(editable);
        $(document).on('selectionchange.focustracker', this.onSelectionChangeBound);
        this.editable = editable;
        this.lastEditable = editable;
        if (state.range) {
            state.range.refresh();
            rangy.getSelection().setSingleRange(state.range);
        }
        PubSub.publish('editable.focus', editable);
    };

    /**
     * Sets the focus to the last editable that had the focus. Ensures that there is always
     * a range is the focused editable.
     */
    FocusTracker.prototype.restoreFocus = function () {
        var editable = this.lastEditable,
            state = this.findState(editable),
            range = state.range;
        editable.focus();
        if (!range) {
            range = rangy.createRange();
            range.setStart(editable, 0);
            range.collapse(true);
            state.range = range;
        }
        $(document).on('selectionchange.focustracker', this.onSelectionChange.bind(this));
        rangy.getSelection().setSingleRange(range);
        return range;
    };

    /**
     * Removing focus from the current editable, but don't want that change in selection
     * to be reflected in the saved selection state for the editable.
     */
    FocusTracker.prototype.removeFocus = function () {
        this.storeState(this.editable);
        $(document).off('selectionchange.focustracker');
        this.editable.blur();
    };

    FocusTracker.prototype.onSelectionChange = function () {
        this.storeState(this.editable);
        PubSub.publish('selection.change', LocRangeUtil.getSelectionLoc);
    };

    /**
     * Allow time for the DOM to be updated before saving the state.
     */
    FocusTracker.prototype.onTextInsert = function () {
        setTimeout(function () {
            this.storeState(this.editable);
        }.bind(this), 20);
    };

    FocusTracker.prototype.findState = function (editable) {
        var state = _.find(this.states, function (state) {
                return state.editable === editable;
            });
        if (!state) {
            state = {
                editable: editable
            };
            this.states.push(state);
        }
        return state;
    };

    FocusTracker.prototype.storeState = function (editable) {
        var state = this.findState(editable);
        state.range = this.getRange(editable);
        state.bodyScrollTop = this.bodyScrollTop;
        state.scrollTop = this.scrollTop;
        return state;
    };

    /**
     * Returns the current range if it's in the editable.
    */
    FocusTracker.prototype.getRange = function (editable) {
        var sel = rangy.getSelection(),
            range = sel.rangeCount && sel.getRangeAt(0),
            result;
        if (range && $(range.startContainer).closest(editable).length) {
            result = range;
        }
        return result;
    };

    /**
     * Executes func every delay interval until func returns true.
     */
    FocusTracker.prototype.until = function until(func, delay) {
        if (!func()) {
            _.delay(until, delay, func, delay);
        }
    };

    /**
     * Allow time for the range to be set within the document. It seems to take ages on iOS.
     * Returns false o allow other hit handlers to access the same event.
     */
    FocusTracker.prototype.handle = function (event) {
        var storeState = function () {
                var editable = event.event.delegateTarget,
                    executed;
                if (this.getRange(editable)) {
                    executed = true;
                    this.storeState(editable);
                    if (document.onselectionchange === undefined) {
                        this.onSelectionChange();
                    }
                }
                return executed;
            }.bind(this);
        this.until(storeState, 10);
        return false;
    };

    var theInstance = new FocusTracker();

    return {
        init: theInstance.init.bind(theInstance),
        restoreFocus: theInstance.restoreFocus.bind(theInstance),
        removeFocus: theInstance.removeFocus.bind(theInstance)
    };
});
<|endoftext|>