<commit_before>require('ember-handlebars-compiler');

var slice = Array.prototype.slice,
    originalTemplate = Ember.Handlebars.template;

/**
  @private

  If a path starts with a reserved keyword, returns the root
  that should be used.

  @method normalizePath
  @for Ember
  @param root {Object}
  @param path {String}
  @param data {Hash}
*/
var normalizePath = Ember.Handlebars.normalizePath = function(root, path, data) {
  var keywords = (data && data.keywords) || {},
      keyword, isKeyword;

  // Get the first segment of the path. For example, if the
  // path is "foo.bar.baz", returns "foo".
  keyword = path.split('.', 1)[0];

  // Test to see if the first path is a keyword that has been
  // passed along in the view's data hash. If so, we will treat
  // that object as the new root.
  if (keywords.hasOwnProperty(keyword)) {
    // Look up the value in the template's data hash.
    root = keywords[keyword];
    isKeyword = true;

    // Handle cases where the entire path is the reserved
    // word. In that case, return the object itself.
    if (path === keyword) {
      path = '';
    } else {
      // Strip the keyword from the path and look up
      // the remainder from the newly found root.
      path = path.substr(keyword.length+1);
    }
  }

  return { root: root, path: path, isKeyword: isKeyword };
};

/**
  Lookup both on root and on window. If the path starts with
  a keyword, the corresponding object will be looked up in the
  template's data hash and used to resolve the path.

  @method get
  @for Ember.Handlebars
  @param {Object} root The object to look up the property on
  @param {String} path The path to be lookedup
  @param {Object} options The template's option hash
*/
var handlebarsGet = Ember.Handlebars.get = function(root, path, options) {
  var data = options && options.data,
      normalizedPath = normalizePath(root, path, data),
      value;

  if (Ember.FEATURES.isEnabled("ember-handlebars-caps-lookup")) {

    // If the path starts with a capital letter, look it up on Ember.lookup,
    // which defaults to the `window` object in browsers.
    if (Ember.isGlobalPath(path)) {
      value = Ember.get(Ember.lookup, path);
    } else {

      // In cases where the path begins with a keyword, change the
      // root to the value represented by that keyword, and ensure
      // the path is relative to it.
      value = Ember.get(normalizedPath.root, normalizedPath.path);
    }

  } else {
    root = normalizedPath.root;
    path = normalizedPath.path;

    value = Ember.get(root, path);

    if (value === undefined && root !== Ember.lookup && Ember.isGlobalPath(path)) {
      value = Ember.get(Ember.lookup, path);
    }
  }

  return value;
};

Ember.Handlebars.resolveParams = function(context, params, options) {
  var resolvedParams = [], types = options.types, param, type;

  for (var i=0, l=params.length; i<l; i++) {
    param = params[i];
    type = types[i];

    if (type === 'ID') {
      resolvedParams.push(handlebarsGet(context, param, options));
    } else {
      resolvedParams.push(param);
    }
  }

  return resolvedParams;
};

Ember.Handlebars.resolveHash = function(context, hash, options) {
  var resolvedHash = {}, types = options.hashTypes, type;

  for (var key in hash) {
    if (!hash.hasOwnProperty(key)) { continue; }

    type = types[key];

    if (type === 'ID') {
      resolvedHash[key] = handlebarsGet(context, hash[key], options);
    } else {
      resolvedHash[key] = hash[key];
    }
  }

  return resolvedHash;
};

/**
  @private

  Registers a helper in Handlebars that will be called if no property with the
  given name can be found on the current context object, and no helper with
  that name is registered.

  This throws an exception with a more helpful error message so the user can
  track down where the problem is happening.

  @method helperMissing
  @for Ember.Handlebars.helpers
  @param {String} path
  @param {Hash} options
*/
Ember.Handlebars.registerHelper('helperMissing', function(path) {
  var error, view = "";

  var options = arguments[arguments.length - 1];

  if (Ember.FEATURES.isEnabled('container-renderables')) {

    var helper = Ember.Handlebars.resolveHelper(options.data.view.container, path);

    if (helper) {
      return helper.apply(this, slice.call(arguments, 1));
    }
  }

  error = "%@ Handlebars error: Could not find property '%@' on object %@.";
  if (options.data) {
    view = options.data.view;
  }
  throw new Ember.Error(Ember.String.fmt(error, [view, path, this]));
});

/**
  @private

  Registers a helper in Handlebars that will be called if no property with the
  given name can be found on the current context object, and no helper with
  that name is registered.

  This throws an exception with a more helpful error message so the user can
  track down where the problem is happening.

  @method helperMissing
  @for Ember.Handlebars.helpers
  @param {String} path
  @param {Hash} options
*/
Ember.Handlebars.registerHelper('blockHelperMissing', function(path) {

  var options = arguments[arguments.length - 1];

  if (Ember.FEATURES.isEnabled('container-renderables')) {

    var helper = Ember.Handlebars.resolveHelper(options.data.view.container, path);

    if (helper) {
      return helper.apply(this, slice.call(arguments, 1));
    }
  }

  return Handlebars.helpers.blockHelperMissing.apply(this, arguments);
});

/**
  Register a bound handlebars helper. Bound helpers behave similarly to regular
  handlebars helpers, with the added ability to re-render when the underlying data
  changes.

  ## Simple example

  ```javascript
  Ember.Handlebars.registerBoundHelper('capitalize', function(value) {
    return value.toUpperCase();
  });
  ```

  The above bound helper can be used inside of templates as follows:

  ```handlebars
  {{capitalize name}}
  ```

  In this case, when the `name` property of the template's context changes,
  the rendered value of the helper will update to reflect this change.

  ## Example with options

  Like normal handlebars helpers, bound helpers have access to the options
  passed into the helper call.

  ```javascript
  Ember.Handlebars.registerBoundHelper('repeat', function(value, options) {
    var count = options.hash.count;
    var a = [];
    while(a.length < count) {
        a.push(value);
    }
    return a.join('');
  });
  ```

  This helper could be used in a template as follows:

  ```handlebars
  {{repeat text count=3}}
  ```

  ## Example with bound options

  Bound hash options are also supported. Example:

  ```handlebars
  {{repeat text countBinding="numRepeats"}}
  ```

  In this example, count will be bound to the value of
  the `numRepeats` property on the context. If that property
  changes, the helper will be re-rendered.

  ## Example with extra dependencies

  The `Ember.Handlebars.registerBoundHelper` method takes a variable length
  third parameter which indicates extra dependencies on the passed in value.
  This allows the handlebars helper to update when these dependencies change.

  ```javascript
  Ember.Handlebars.registerBoundHelper('capitalizeName', function(value) {
    return value.get('name').toUpperCase();
  }, 'name');
  ```

  ## Example with multiple bound properties

  `Ember.Handlebars.registerBoundHelper` supports binding to
  multiple properties, e.g.:

  ```javascript
  Ember.Handlebars.registerBoundHelper('concatenate', function() {
    var values = Array.prototype.slice.call(arguments, 0, -1);
    return values.join('||');
  });
  ```

  Which allows for template syntax such as `{{concatenate prop1 prop2}}` or
  `{{concatenate prop1 prop2 prop3}}`. If any of the properties change,
  the helpr will re-render.  Note that dependency keys cannot be
  using in conjunction with multi-property helpers, since it is ambiguous
  which property the dependent keys would belong to.

  ## Use with unbound helper

  The `{{unbound}}` helper can be used with bound helper invocations
  to render them in their unbound form, e.g.

  ```handlebars
  {{unbound capitalize name}}
  ```

  In this example, if the name property changes, the helper
  will not re-render.

  ## Use with blocks not supported

  Bound helpers do not support use with Handlebars blocks or
  the addition of child views of any kind.

  @method registerBoundHelper
  @for Ember.Handlebars
  @param {String} name
  @param {Function} function
  @param {String} dependentKeys*
*/
Ember.Handlebars.registerBoundHelper = function(name, fn) {
  var boundHelperArgs = slice.call(arguments, 1),
      boundFn = Ember.Handlebars.makeBoundHelper.apply(this, boundHelperArgs);
  Ember.Handlebars.registerHelper(name, boundFn);
};

/**
  @private

  A (mostly) private helper function to `registerBoundHelper`. Takes the
  provided Handlebars helper function fn and returns it in wrapped
  bound helper form.

  The main use case for using this outside of `registerBoundHelper`
  is for registering helpers on the container:

  ```js
  var boundHelperFn = Ember.Handlebars.makeBoundHelper(function(word) {
    return word.toUpperCase();
  });

  container.register('helper:my-bound-helper', boundHelperFn);
  ```

  In the above example, if the helper function hadn't been wrapped in
  `makeBoundHelper`, the registered helper would be unbound.

  @method makeBoundHelper
  @for Ember.Handlebars
  @param {Function} function
  @param {String} dependentKeys*
*/
Ember.Handlebars.makeBoundHelper = function(fn) {
  var dependentKeys = slice.call(arguments, 1);

  function helper() {
    var properties = slice.call(arguments, 0, -1),
      numProperties = properties.length,
      options = arguments[arguments.length - 1],
      normalizedProperties = [],
      types = options.types,
      data = options.data,
      hash = options.hash,
      view = data.view,
      contexts = options.contexts,
      currentContext = (contexts && contexts.length) ? contexts[0] : this,
      prefixPathForDependentKeys = '',
      loc, len, hashOption,
      boundOption, property,
      normalizedValue = Ember._SimpleHandlebarsView.prototype.normalizedValue;

    Ember.assert("registerBoundHelper-generated helpers do not support use with Handlebars blocks.", !options.fn);

    // Detect bound options (e.g. countBinding="otherCount")
    var boundOptions = hash.boundOptions = {};
    for (hashOption in hash) {
      if (Ember.IS_BINDING.test(hashOption)) {
        // Lop off 'Binding' suffix.
        boundOptions[hashOption.slice(0, -7)] = hash[hashOption];
      }
    }

    // Expose property names on data.properties object.
    var watchedProperties = [];
    data.properties = [];
    for (loc = 0; loc < numProperties; ++loc) {
      data.properties.push(properties[loc]);
      if (types[loc] === 'ID') {
        var normalizedProp = normalizePath(currentContext, properties[loc], data);
        normalizedProperties.push(normalizedProp);
        watchedProperties.push(normalizedProp);
      } else {
        normalizedProperties.push(null);
      }
    }

    // Handle case when helper invocation is preceded by `unbound`, e.g.
    // {{unbound myHelper foo}}
    if (data.isUnbound) {
      return evaluateUnboundHelper(this, fn, normalizedProperties, options);
    }

    var bindView = new Ember._SimpleHandlebarsView(null, null, !options.hash.unescaped, options.data);

    // Override SimpleHandlebarsView's method for generating the view's content.
    bindView.normalizedValue = function() {
      var args = [], boundOption;

      // Copy over bound hash options.
      for (boundOption in boundOptions) {
        if (!boundOptions.hasOwnProperty(boundOption)) { continue; }
        property = normalizePath(currentContext, boundOptions[boundOption], data);
        bindView.path = property.path;
        bindView.pathRoot = property.root;
        hash[boundOption] = normalizedValue.call(bindView);
      }

      for (loc = 0; loc < numProperties; ++loc) {
        property = normalizedProperties[loc];
        if (property) {
          bindView.path = property.path;
          bindView.pathRoot = property.root;
          args.push(normalizedValue.call(bindView));
        } else {
          args.push(properties[loc]);
        }
      }
      args.push(options);

      // Run the supplied helper function.
      return fn.apply(currentContext, args);
    };

    view.appendChild(bindView);

    // Assemble list of watched properties that'll re-render this helper.
    for (boundOption in boundOptions) {
      if (boundOptions.hasOwnProperty(boundOption)) {
        watchedProperties.push(normalizePath(currentContext, boundOptions[boundOption], data));
      }
    }

    // Observe each property.
    for (loc = 0, len = watchedProperties.length; loc < len; ++loc) {
      property = watchedProperties[loc];
      view.registerObserver(property.root, property.path, bindView, bindView.rerender);
    }

    if (types[0] !== 'ID' || normalizedProperties.length === 0) {
      return;
    }

    // Add dependent key observers to the first param
    var normalized = normalizedProperties[0],
        pathRoot = normalized.root,
        path = normalized.path;

    if(!Ember.isEmpty(path)) {
      prefixPathForDependentKeys = path + '.';
    }
    for (var i=0, l=dependentKeys.length; i<l; i++) {
      view.registerObserver(pathRoot, prefixPathForDependentKeys + dependentKeys[i], bindView, bindView.rerender);
    }
  }

  helper._rawFunction = fn;
  return helper;
};

/**
  @private

  Renders the unbound form of an otherwise bound helper function.

  @method evaluateUnboundHelper
  @param {Function} fn
  @param {Object} context
  @param {Array} normalizedProperties
  @param {String} options
*/
function evaluateUnboundHelper(context, fn, normalizedProperties, options) {
  var args = [], hash = options.hash, boundOptions = hash.boundOptions, loc, len, property, boundOption;

  for (boundOption in boundOptions) {
    if (!boundOptions.hasOwnProperty(boundOption)) { continue; }
    hash[boundOption] = Ember.Handlebars.get(context, boundOptions[boundOption], options);
  }

  for(loc = 0, len = normalizedProperties.length; loc < len; ++loc) {
    property = normalizedProperties[loc];
    args.push(Ember.Handlebars.get(property.root, property.path, options));
  }
  args.push(options);
  return fn.apply(context, args);
}

/**
  @private

  Overrides Handlebars.template so that we can distinguish
  user-created, top-level templates from inner contexts.

  @method template
  @for Ember.Handlebars
  @param {String} spec
*/
Ember.Handlebars.template = function(spec) {
  var t = originalTemplate(spec);
  t.isTop = true;
  return t;
};
<commit_msg>[BUGFIX beta] Added assert mismatched template compiler version.<commit_after>require('ember-handlebars-compiler');

var slice = Array.prototype.slice,
    originalTemplate = Ember.Handlebars.template;

/**
  @private

  If a path starts with a reserved keyword, returns the root
  that should be used.

  @method normalizePath
  @for Ember
  @param root {Object}
  @param path {String}
  @param data {Hash}
*/
var normalizePath = Ember.Handlebars.normalizePath = function(root, path, data) {
  var keywords = (data && data.keywords) || {},
      keyword, isKeyword;

  // Get the first segment of the path. For example, if the
  // path is "foo.bar.baz", returns "foo".
  keyword = path.split('.', 1)[0];

  // Test to see if the first path is a keyword that has been
  // passed along in the view's data hash. If so, we will treat
  // that object as the new root.
  if (keywords.hasOwnProperty(keyword)) {
    // Look up the value in the template's data hash.
    root = keywords[keyword];
    isKeyword = true;

    // Handle cases where the entire path is the reserved
    // word. In that case, return the object itself.
    if (path === keyword) {
      path = '';
    } else {
      // Strip the keyword from the path and look up
      // the remainder from the newly found root.
      path = path.substr(keyword.length+1);
    }
  }

  return { root: root, path: path, isKeyword: isKeyword };
};

/**
  Lookup both on root and on window. If the path starts with
  a keyword, the corresponding object will be looked up in the
  template's data hash and used to resolve the path.

  @method get
  @for Ember.Handlebars
  @param {Object} root The object to look up the property on
  @param {String} path The path to be lookedup
  @param {Object} options The template's option hash
*/
var handlebarsGet = Ember.Handlebars.get = function(root, path, options) {
  var data = options && options.data,
      normalizedPath = normalizePath(root, path, data),
      value;

  if (Ember.FEATURES.isEnabled("ember-handlebars-caps-lookup")) {

    // If the path starts with a capital letter, look it up on Ember.lookup,
    // which defaults to the `window` object in browsers.
    if (Ember.isGlobalPath(path)) {
      value = Ember.get(Ember.lookup, path);
    } else {

      // In cases where the path begins with a keyword, change the
      // root to the value represented by that keyword, and ensure
      // the path is relative to it.
      value = Ember.get(normalizedPath.root, normalizedPath.path);
    }

  } else {
    root = normalizedPath.root;
    path = normalizedPath.path;

    value = Ember.get(root, path);

    if (value === undefined && root !== Ember.lookup && Ember.isGlobalPath(path)) {
      value = Ember.get(Ember.lookup, path);
    }
  }

  return value;
};

Ember.Handlebars.resolveParams = function(context, params, options) {
  var resolvedParams = [], types = options.types, param, type;

  for (var i=0, l=params.length; i<l; i++) {
    param = params[i];
    type = types[i];

    if (type === 'ID') {
      resolvedParams.push(handlebarsGet(context, param, options));
    } else {
      resolvedParams.push(param);
    }
  }

  return resolvedParams;
};

Ember.Handlebars.resolveHash = function(context, hash, options) {
  var resolvedHash = {}, types = options.hashTypes, type;

  for (var key in hash) {
    if (!hash.hasOwnProperty(key)) { continue; }

    type = types[key];

    if (type === 'ID') {
      resolvedHash[key] = handlebarsGet(context, hash[key], options);
    } else {
      resolvedHash[key] = hash[key];
    }
  }

  return resolvedHash;
};

/**
  @private

  Registers a helper in Handlebars that will be called if no property with the
  given name can be found on the current context object, and no helper with
  that name is registered.

  This throws an exception with a more helpful error message so the user can
  track down where the problem is happening.

  @method helperMissing
  @for Ember.Handlebars.helpers
  @param {String} path
  @param {Hash} options
*/
Ember.Handlebars.registerHelper('helperMissing', function(path) {
  var error, view = "";

  var options = arguments[arguments.length - 1];

  if (Ember.FEATURES.isEnabled('container-renderables')) {

    var helper = Ember.Handlebars.resolveHelper(options.data.view.container, path);

    if (helper) {
      return helper.apply(this, slice.call(arguments, 1));
    }
  }

  error = "%@ Handlebars error: Could not find property '%@' on object %@.";
  if (options.data) {
    view = options.data.view;
  }
  throw new Ember.Error(Ember.String.fmt(error, [view, path, this]));
});

/**
  @private

  Registers a helper in Handlebars that will be called if no property with the
  given name can be found on the current context object, and no helper with
  that name is registered.

  This throws an exception with a more helpful error message so the user can
  track down where the problem is happening.

  @method helperMissing
  @for Ember.Handlebars.helpers
  @param {String} path
  @param {Hash} options
*/
Ember.Handlebars.registerHelper('blockHelperMissing', function(path) {

  var options = arguments[arguments.length - 1];

  if (Ember.FEATURES.isEnabled('container-renderables')) {

    Ember.assert("`blockHelperMissing` was invoked without a helper name, which is most likely due to a mismatch between the version of Ember.js you're running now and the one used to precompile your templates. Please make sure the version of `ember-handlebars-compiler` you're using is up to date.", path);

    var helper = Ember.Handlebars.resolveHelper(options.data.view.container, path);

    if (helper) {
      return helper.apply(this, slice.call(arguments, 1));
    }
  }

  return Handlebars.helpers.blockHelperMissing.apply(this, arguments);
});

/**
  Register a bound handlebars helper. Bound helpers behave similarly to regular
  handlebars helpers, with the added ability to re-render when the underlying data
  changes.

  ## Simple example

  ```javascript
  Ember.Handlebars.registerBoundHelper('capitalize', function(value) {
    return value.toUpperCase();
  });
  ```

  The above bound helper can be used inside of templates as follows:

  ```handlebars
  {{capitalize name}}
  ```

  In this case, when the `name` property of the template's context changes,
  the rendered value of the helper will update to reflect this change.

  ## Example with options

  Like normal handlebars helpers, bound helpers have access to the options
  passed into the helper call.

  ```javascript
  Ember.Handlebars.registerBoundHelper('repeat', function(value, options) {
    var count = options.hash.count;
    var a = [];
    while(a.length < count) {
        a.push(value);
    }
    return a.join('');
  });
  ```

  This helper could be used in a template as follows:

  ```handlebars
  {{repeat text count=3}}
  ```

  ## Example with bound options

  Bound hash options are also supported. Example:

  ```handlebars
  {{repeat text countBinding="numRepeats"}}
  ```

  In this example, count will be bound to the value of
  the `numRepeats` property on the context. If that property
  changes, the helper will be re-rendered.

  ## Example with extra dependencies

  The `Ember.Handlebars.registerBoundHelper` method takes a variable length
  third parameter which indicates extra dependencies on the passed in value.
  This allows the handlebars helper to update when these dependencies change.

  ```javascript
  Ember.Handlebars.registerBoundHelper('capitalizeName', function(value) {
    return value.get('name').toUpperCase();
  }, 'name');
  ```

  ## Example with multiple bound properties

  `Ember.Handlebars.registerBoundHelper` supports binding to
  multiple properties, e.g.:

  ```javascript
  Ember.Handlebars.registerBoundHelper('concatenate', function() {
    var values = Array.prototype.slice.call(arguments, 0, -1);
    return values.join('||');
  });
  ```

  Which allows for template syntax such as `{{concatenate prop1 prop2}}` or
  `{{concatenate prop1 prop2 prop3}}`. If any of the properties change,
  the helpr will re-render.  Note that dependency keys cannot be
  using in conjunction with multi-property helpers, since it is ambiguous
  which property the dependent keys would belong to.

  ## Use with unbound helper

  The `{{unbound}}` helper can be used with bound helper invocations
  to render them in their unbound form, e.g.

  ```handlebars
  {{unbound capitalize name}}
  ```

  In this example, if the name property changes, the helper
  will not re-render.

  ## Use with blocks not supported

  Bound helpers do not support use with Handlebars blocks or
  the addition of child views of any kind.

  @method registerBoundHelper
  @for Ember.Handlebars
  @param {String} name
  @param {Function} function
  @param {String} dependentKeys*
*/
Ember.Handlebars.registerBoundHelper = function(name, fn) {
  var boundHelperArgs = slice.call(arguments, 1),
      boundFn = Ember.Handlebars.makeBoundHelper.apply(this, boundHelperArgs);
  Ember.Handlebars.registerHelper(name, boundFn);
};

/**
  @private

  A (mostly) private helper function to `registerBoundHelper`. Takes the
  provided Handlebars helper function fn and returns it in wrapped
  bound helper form.

  The main use case for using this outside of `registerBoundHelper`
  is for registering helpers on the container:

  ```js
  var boundHelperFn = Ember.Handlebars.makeBoundHelper(function(word) {
    return word.toUpperCase();
  });

  container.register('helper:my-bound-helper', boundHelperFn);
  ```

  In the above example, if the helper function hadn't been wrapped in
  `makeBoundHelper`, the registered helper would be unbound.

  @method makeBoundHelper
  @for Ember.Handlebars
  @param {Function} function
  @param {String} dependentKeys*
*/
Ember.Handlebars.makeBoundHelper = function(fn) {
  var dependentKeys = slice.call(arguments, 1);

  function helper() {
    var properties = slice.call(arguments, 0, -1),
      numProperties = properties.length,
      options = arguments[arguments.length - 1],
      normalizedProperties = [],
      types = options.types,
      data = options.data,
      hash = options.hash,
      view = data.view,
      contexts = options.contexts,
      currentContext = (contexts && contexts.length) ? contexts[0] : this,
      prefixPathForDependentKeys = '',
      loc, len, hashOption,
      boundOption, property,
      normalizedValue = Ember._SimpleHandlebarsView.prototype.normalizedValue;

    Ember.assert("registerBoundHelper-generated helpers do not support use with Handlebars blocks.", !options.fn);

    // Detect bound options (e.g. countBinding="otherCount")
    var boundOptions = hash.boundOptions = {};
    for (hashOption in hash) {
      if (Ember.IS_BINDING.test(hashOption)) {
        // Lop off 'Binding' suffix.
        boundOptions[hashOption.slice(0, -7)] = hash[hashOption];
      }
    }

    // Expose property names on data.properties object.
    var watchedProperties = [];
    data.properties = [];
    for (loc = 0; loc < numProperties; ++loc) {
      data.properties.push(properties[loc]);
      if (types[loc] === 'ID') {
        var normalizedProp = normalizePath(currentContext, properties[loc], data);
        normalizedProperties.push(normalizedProp);
        watchedProperties.push(normalizedProp);
      } else {
        normalizedProperties.push(null);
      }
    }

    // Handle case when helper invocation is preceded by `unbound`, e.g.
    // {{unbound myHelper foo}}
    if (data.isUnbound) {
      return evaluateUnboundHelper(this, fn, normalizedProperties, options);
    }

    var bindView = new Ember._SimpleHandlebarsView(null, null, !options.hash.unescaped, options.data);

    // Override SimpleHandlebarsView's method for generating the view's content.
    bindView.normalizedValue = function() {
      var args = [], boundOption;

      // Copy over bound hash options.
      for (boundOption in boundOptions) {
        if (!boundOptions.hasOwnProperty(boundOption)) { continue; }
        property = normalizePath(currentContext, boundOptions[boundOption], data);
        bindView.path = property.path;
        bindView.pathRoot = property.root;
        hash[boundOption] = normalizedValue.call(bindView);
      }

      for (loc = 0; loc < numProperties; ++loc) {
        property = normalizedProperties[loc];
        if (property) {
          bindView.path = property.path;
          bindView.pathRoot = property.root;
          args.push(normalizedValue.call(bindView));
        } else {
          args.push(properties[loc]);
        }
      }
      args.push(options);

      // Run the supplied helper function.
      return fn.apply(currentContext, args);
    };

    view.appendChild(bindView);

    // Assemble list of watched properties that'll re-render this helper.
    for (boundOption in boundOptions) {
      if (boundOptions.hasOwnProperty(boundOption)) {
        watchedProperties.push(normalizePath(currentContext, boundOptions[boundOption], data));
      }
    }

    // Observe each property.
    for (loc = 0, len = watchedProperties.length; loc < len; ++loc) {
      property = watchedProperties[loc];
      view.registerObserver(property.root, property.path, bindView, bindView.rerender);
    }

    if (types[0] !== 'ID' || normalizedProperties.length === 0) {
      return;
    }

    // Add dependent key observers to the first param
    var normalized = normalizedProperties[0],
        pathRoot = normalized.root,
        path = normalized.path;

    if(!Ember.isEmpty(path)) {
      prefixPathForDependentKeys = path + '.';
    }
    for (var i=0, l=dependentKeys.length; i<l; i++) {
      view.registerObserver(pathRoot, prefixPathForDependentKeys + dependentKeys[i], bindView, bindView.rerender);
    }
  }

  helper._rawFunction = fn;
  return helper;
};

/**
  @private

  Renders the unbound form of an otherwise bound helper function.

  @method evaluateUnboundHelper
  @param {Function} fn
  @param {Object} context
  @param {Array} normalizedProperties
  @param {String} options
*/
function evaluateUnboundHelper(context, fn, normalizedProperties, options) {
  var args = [], hash = options.hash, boundOptions = hash.boundOptions, loc, len, property, boundOption;

  for (boundOption in boundOptions) {
    if (!boundOptions.hasOwnProperty(boundOption)) { continue; }
    hash[boundOption] = Ember.Handlebars.get(context, boundOptions[boundOption], options);
  }

  for(loc = 0, len = normalizedProperties.length; loc < len; ++loc) {
    property = normalizedProperties[loc];
    args.push(Ember.Handlebars.get(property.root, property.path, options));
  }
  args.push(options);
  return fn.apply(context, args);
}

/**
  @private

  Overrides Handlebars.template so that we can distinguish
  user-created, top-level templates from inner contexts.

  @method template
  @for Ember.Handlebars
  @param {String} spec
*/
Ember.Handlebars.template = function(spec) {
  var t = originalTemplate(spec);
  t.isTop = true;
  return t;
};
<|endoftext|>


<commit_before>steal('funcunit/qunit','./attributes', function(){

module("can/observe/attributes");

test("literal converters and serializes", function(){
	can.Observe("Task1",{
		attributes: {
			createdAt: "date"
		},
		convert: {
			date: function(d){
				var months = ["jan", "feb", "mar"]
				return months[d.getMonth()]
			}
		},
		serialize: {
			date: function(d){
				var months = {"jan":0, "feb":1, "mar":2}
				return months[d]
			}
		}
	},{});
	can.Observe("Task2",{
		attributes: {
			createdAt: "date"
		},
		convert: {
			date: function(d){
				var months = ["apr", "may", "jun"]
				return months[d.getMonth()]
			}
		},
		serialize: {
			date: function(d){
				var months = {"apr":0, "may":1, "jun":2}
				return months[d]
			}
		}
	},{});
	var d = new Date();
	d.setMonth(1)
	var task1=new Task1({
		createdAt: d,
		name:"Task1"
	});
	d.setMonth(2)
	var task2=new Task2({
		createdAt: d,
		name:"Task2"
	});
	equals(task1.createdAt, "feb", "Task1 model convert");
	equals(task2.createdAt, "jun", "Task2 model convert");
	equals(task1.serialize().createdAt, 1, "Task1 model serialize");
	equals(task2.serialize().createdAt, 2, "Task2 model serialize");
	equals(task1.serialize().name, "Task1", "Task1 model default serialized");
	equals(task2.serialize().name, "Task2", "Task2 model default serialized");
});

var makeClasses= function(){
	can.Observe("AttrTest.Person", {
		serialize: function() {
			return "My name is " + this.name;
		}
	});
	can.Observe("AttrTest.Loan");
	can.Observe("AttrTest.Issue");

	AttrTest.Person.model = function(data){
		return new this(data);
	}
	AttrTest.Loan.models = function(data){
		return can.map(data, function(l){
			return new AttrTest.Loan(l)
		});
	}
	AttrTest.Issue.models = function(data){
		return can.map(data, function(l){
			return new AttrTest.Issue(l)
		});
	}
	can.Observe("AttrTest.Customer",
	{
		attributes : {
			person : "AttrTest.Person.model",
			loans : "AttrTest.Loan.models",
			issues : "AttrTest.Issue.models"
		}
	},
	{});
}

test("default converters", function(){
	var num = 1318541064012;
	equals( can.Observe.convert.date(num).getTime(), num, "converted to a date with a number" );
})

test("basic observe associations", function(){
	makeClasses();

	var c = new AttrTest.Customer({
		person : {
			id: 1,
			name: "Justin"
		},
		issues : [],
		loans : [
			{
				amount : 1000,
				id: 2
			},
			{
				amount : 19999,
				id: 3
			}
		]
	});

	equals(c.person.name, "Justin", "association present");

	equals(c.person.constructor, AttrTest.Person, "belongs to association typed");

	equals(c.issues.length, 0);

	equals(c.loans.length, 2);

	equals(c.loans[0].constructor, AttrTest.Loan);

});

});<commit_msg>fix for attributes tests breaking on the 30th + of each month<commit_after>steal('funcunit/qunit','./attributes', function(){

module("can/observe/attributes");

test("literal converters and serializes", function(){
	can.Observe("Task1",{
		attributes: {
			createdAt: "date"
		},
		convert: {
			date: function(d){
				var months = ["jan", "feb", "mar"]
				return months[d.getMonth()]
			}
		},
		serialize: {
			date: function(d){
				var months = {"jan":0, "feb":1, "mar":2}
				return months[d]
			}
		}
	},{});
	can.Observe("Task2",{
		attributes: {
			createdAt: "date"
		},
		convert: {
			date: function(d){
				var months = ["apr", "may", "jun"]
				return months[d.getMonth()]
			}
		},
		serialize: {
			date: function(d){
				var months = {"apr":0, "may":1, "jun":2}
				return months[d]
			}
		}
	},{});
	var d = new Date();
	d.setMonth(1, 1)
	var task1=new Task1({
		createdAt: d,
		name:"Task1"
	});
	d.setMonth(2, 1)
	var task2=new Task2({
		createdAt: d,
		name:"Task2"
	});
	equals(task1.createdAt, "feb", "Task1 model convert");
	equals(task2.createdAt, "jun", "Task2 model convert");
	equals(task1.serialize().createdAt, 1, "Task1 model serialize");
	equals(task2.serialize().createdAt, 2, "Task2 model serialize");
	equals(task1.serialize().name, "Task1", "Task1 model default serialized");
	equals(task2.serialize().name, "Task2", "Task2 model default serialized");
});

var makeClasses= function(){
	can.Observe("AttrTest.Person", {
		serialize: function() {
			return "My name is " + this.name;
		}
	});
	can.Observe("AttrTest.Loan");
	can.Observe("AttrTest.Issue");

	AttrTest.Person.model = function(data){
		return new this(data);
	}
	AttrTest.Loan.models = function(data){
		return can.map(data, function(l){
			return new AttrTest.Loan(l)
		});
	}
	AttrTest.Issue.models = function(data){
		return can.map(data, function(l){
			return new AttrTest.Issue(l)
		});
	}
	can.Observe("AttrTest.Customer",
	{
		attributes : {
			person : "AttrTest.Person.model",
			loans : "AttrTest.Loan.models",
			issues : "AttrTest.Issue.models"
		}
	},
	{});
}

test("default converters", function(){
	var num = 1318541064012;
	equals( can.Observe.convert.date(num).getTime(), num, "converted to a date with a number" );
})

test("basic observe associations", function(){
	makeClasses();

	var c = new AttrTest.Customer({
		person : {
			id: 1,
			name: "Justin"
		},
		issues : [],
		loans : [
			{
				amount : 1000,
				id: 2
			},
			{
				amount : 19999,
				id: 3
			}
		]
	});

	equals(c.person.name, "Justin", "association present");

	equals(c.person.constructor, AttrTest.Person, "belongs to association typed");

	equals(c.issues.length, 0);

	equals(c.loans.length, 2);

	equals(c.loans[0].constructor, AttrTest.Loan);

});

});<|endoftext|>


<commit_before>import {
  CLASS_TRANSITION,
  EVENT_LOAD,
  EVENT_TRANSITION_END,
  EVENT_VIEWED,
} from './constants';
import {
  addClass,
  addListener,
  each,
  empty,
  extend,
  getImageNameFromURL,
  getImageNaturalSizes,
  getTransforms,
  isFunction,
  isString,
  proxy,
  removeClass,
  removeListener,
  setData,
  setStyle,
} from './utilities';

export default {
  render() {
    this.initContainer();
    this.initViewer();
    this.initList();
    this.renderViewer();
  },

  initContainer() {
    this.containerData = {
      width: window.innerWidth,
      height: window.innerHeight,
    };
  },

  initViewer() {
    const { options, parent } = this;
    let viewerData;

    if (options.inline) {
      viewerData = {
        width: Math.max(parent.offsetWidth, options.minWidth),
        height: Math.max(parent.offsetHeight, options.minHeight),
      };

      this.parentData = viewerData;
    }

    if (this.fulled || !viewerData) {
      viewerData = this.containerData;
    }

    this.viewerData = extend({}, viewerData);
  },

  renderViewer() {
    if (this.options.inline && !this.fulled) {
      setStyle(this.viewer, this.viewerData);
    }
  },

  initList() {
    const { element, options, list } = this;
    const items = [];

    each(this.images, (image, i) => {
      const { src } = image;
      const alt = image.alt || getImageNameFromURL(src);
      let { url } = options;

      if (isString(url)) {
        url = image.getAttribute(url);
      } else if (isFunction(url)) {
        url = url.call(image, image);
      }

      if (src || url) {
        items.push('
' +
          '<img' +
            ` src="${src || url}"` +
            ' role="button"' +
            ' data-action="view"' +
            ` data-index="${i}"` +
            ` data-original-url="${url || src}"` +
            ` alt="${alt}"` +
          '>' +
        '
');
      }
    });

    list.innerHTML = items.join('');

    each(list.getElementsByTagName('img'), (image) => {
      setData(image, 'filled', true);
      addListener(image, EVENT_LOAD, proxy(this.loadImage, this), {
        once: true,
      });
    });

    this.items = list.getElementsByTagName('li');

    if (options.transition) {
      addListener(element, EVENT_VIEWED, () => {
        addClass(list, CLASS_TRANSITION);
      }, {
        once: true,
      });
    }
  },

  renderList(index) {
    const i = index || this.index;
    const width = this.items[i].offsetWidth || 30;
    const outerWidth = width + 1; // 1 pixel of `margin-left` width

    // Place the active item in the center of the screen
    setStyle(this.list, {
      width: outerWidth * this.length,
      marginLeft: ((this.viewerData.width - width) / 2) - (outerWidth * i),
    });
  },

  resetList() {
    const { list } = this;

    empty(list);
    removeClass(list, CLASS_TRANSITION);
    setStyle(list, {
      marginLeft: 0,
    });
  },

  initImage(callback) {
    if (!this.image) {
      return;
    }

    const { options, image, viewerData } = this;
    const footerHeight = this.footer.offsetHeight;
    const viewerWidth = viewerData.width;
    const viewerHeight = Math.max(viewerData.height - footerHeight, footerHeight);
    const oldImageData = this.ImageData || {};

    getImageNaturalSizes(image, (naturalWidth, naturalHeight) => {
      const aspectRatio = naturalWidth / naturalHeight;
      let width = viewerWidth;
      let height = viewerHeight;

      if (viewerHeight * aspectRatio > viewerWidth) {
        height = viewerWidth / aspectRatio;
      } else {
        width = viewerHeight * aspectRatio;
      }

      width = Math.min(width * 0.9, naturalWidth);
      height = Math.min(height * 0.9, naturalHeight);

      const imageData = {
        naturalWidth,
        naturalHeight,
        aspectRatio,
        ratio: width / naturalWidth,
        width,
        height,
        left: (viewerWidth - width) / 2,
        top: (viewerHeight - height) / 2,
      };
      const initialImageData = extend({}, imageData);

      if (options.rotatable) {
        imageData.rotate = oldImageData.rotate || 0;
        initialImageData.rotate = 0;
      }

      if (options.scalable) {
        imageData.scaleX = oldImageData.scaleX || 1;
        imageData.scaleY = oldImageData.scaleY || 1;
        initialImageData.scaleX = 1;
        initialImageData.scaleY = 1;
      }

      this.imageData = imageData;
      this.initialImageData = initialImageData;

      if (isFunction(callback)) {
        callback();
      }
    });
  },

  renderImage(callback) {
    if (!this.image) {
      return;
    }

    const { image, imageData } = this;

    setStyle(image, extend({
      width: imageData.width,
      height: imageData.height,
      marginLeft: imageData.left,
      marginTop: imageData.top,
    }, getTransforms(imageData)));

    if (isFunction(callback)) {
      if (this.transitioning) {
        addListener(image, EVENT_TRANSITION_END, callback, {
          once: true,
        });
      } else {
        callback();
      }
    }
  },

  resetImage() {
    const { image } = this;

    // this.image only defined after viewed
    if (image) {
      removeListener(image, EVENT_LOAD, this.onLoad);
      image.parentNode.removeChild(image);
      this.image = null;
    }
  },
};
<commit_msg>refactor: make the `url` function call to `this`<commit_after>import {
  CLASS_TRANSITION,
  EVENT_LOAD,
  EVENT_TRANSITION_END,
  EVENT_VIEWED,
} from './constants';
import {
  addClass,
  addListener,
  each,
  empty,
  extend,
  getImageNameFromURL,
  getImageNaturalSizes,
  getTransforms,
  isFunction,
  isString,
  proxy,
  removeClass,
  removeListener,
  setData,
  setStyle,
} from './utilities';

export default {
  render() {
    this.initContainer();
    this.initViewer();
    this.initList();
    this.renderViewer();
  },

  initContainer() {
    this.containerData = {
      width: window.innerWidth,
      height: window.innerHeight,
    };
  },

  initViewer() {
    const { options, parent } = this;
    let viewerData;

    if (options.inline) {
      viewerData = {
        width: Math.max(parent.offsetWidth, options.minWidth),
        height: Math.max(parent.offsetHeight, options.minHeight),
      };

      this.parentData = viewerData;
    }

    if (this.fulled || !viewerData) {
      viewerData = this.containerData;
    }

    this.viewerData = extend({}, viewerData);
  },

  renderViewer() {
    if (this.options.inline && !this.fulled) {
      setStyle(this.viewer, this.viewerData);
    }
  },

  initList() {
    const { element, options, list } = this;
    const items = [];

    each(this.images, (image, i) => {
      const { src } = image;
      const alt = image.alt || getImageNameFromURL(src);
      let { url } = options;

      if (isString(url)) {
        url = image.getAttribute(url);
      } else if (isFunction(url)) {
        url = url.call(this, image);
      }

      if (src || url) {
        items.push('
' +
          '<img' +
            ` src="${src || url}"` +
            ' role="button"' +
            ' data-action="view"' +
            ` data-index="${i}"` +
            ` data-original-url="${url || src}"` +
            ` alt="${alt}"` +
          '>' +
        '
');
      }
    });

    list.innerHTML = items.join('');

    each(list.getElementsByTagName('img'), (image) => {
      setData(image, 'filled', true);
      addListener(image, EVENT_LOAD, proxy(this.loadImage, this), {
        once: true,
      });
    });

    this.items = list.getElementsByTagName('li');

    if (options.transition) {
      addListener(element, EVENT_VIEWED, () => {
        addClass(list, CLASS_TRANSITION);
      }, {
        once: true,
      });
    }
  },

  renderList(index) {
    const i = index || this.index;
    const width = this.items[i].offsetWidth || 30;
    const outerWidth = width + 1; // 1 pixel of `margin-left` width

    // Place the active item in the center of the screen
    setStyle(this.list, {
      width: outerWidth * this.length,
      marginLeft: ((this.viewerData.width - width) / 2) - (outerWidth * i),
    });
  },

  resetList() {
    const { list } = this;

    empty(list);
    removeClass(list, CLASS_TRANSITION);
    setStyle(list, {
      marginLeft: 0,
    });
  },

  initImage(callback) {
    if (!this.image) {
      return;
    }

    const { options, image, viewerData } = this;
    const footerHeight = this.footer.offsetHeight;
    const viewerWidth = viewerData.width;
    const viewerHeight = Math.max(viewerData.height - footerHeight, footerHeight);
    const oldImageData = this.ImageData || {};

    getImageNaturalSizes(image, (naturalWidth, naturalHeight) => {
      const aspectRatio = naturalWidth / naturalHeight;
      let width = viewerWidth;
      let height = viewerHeight;

      if (viewerHeight * aspectRatio > viewerWidth) {
        height = viewerWidth / aspectRatio;
      } else {
        width = viewerHeight * aspectRatio;
      }

      width = Math.min(width * 0.9, naturalWidth);
      height = Math.min(height * 0.9, naturalHeight);

      const imageData = {
        naturalWidth,
        naturalHeight,
        aspectRatio,
        ratio: width / naturalWidth,
        width,
        height,
        left: (viewerWidth - width) / 2,
        top: (viewerHeight - height) / 2,
      };
      const initialImageData = extend({}, imageData);

      if (options.rotatable) {
        imageData.rotate = oldImageData.rotate || 0;
        initialImageData.rotate = 0;
      }

      if (options.scalable) {
        imageData.scaleX = oldImageData.scaleX || 1;
        imageData.scaleY = oldImageData.scaleY || 1;
        initialImageData.scaleX = 1;
        initialImageData.scaleY = 1;
      }

      this.imageData = imageData;
      this.initialImageData = initialImageData;

      if (isFunction(callback)) {
        callback();
      }
    });
  },

  renderImage(callback) {
    if (!this.image) {
      return;
    }

    const { image, imageData } = this;

    setStyle(image, extend({
      width: imageData.width,
      height: imageData.height,
      marginLeft: imageData.left,
      marginTop: imageData.top,
    }, getTransforms(imageData)));

    if (isFunction(callback)) {
      if (this.transitioning) {
        addListener(image, EVENT_TRANSITION_END, callback, {
          once: true,
        });
      } else {
        callback();
      }
    }
  },

  resetImage() {
    const { image } = this;

    // this.image only defined after viewed
    if (image) {
      removeListener(image, EVENT_LOAD, this.onLoad);
      image.parentNode.removeChild(image);
      this.image = null;
    }
  },
};
<|endoftext|>


<commit_before>async function startProgram() {
        // Write code here

        speak('throw the ball in the air to hear that scrambled word, collide with the ball to hear the solution');
        await delay(8);
        var word = "JAMES";
        var hint = 'A first name for a man';
        var ans = word;
        var wordLength = word.length;
        var scrambled = "";

        for (var i = 0; i < wordLength; i++) {
                var charIndex = Math.floor(Math.random() * word.length);
                scrambled += word.charAt(charIndex) + ' , ';
                word = word.substr(0, charIndex) + word.substr(charIndex + 1);
        }
        speak('The scrambled word is:' + scrambled);
        await delay(wordLength);
        speak('The answer is:' + ans);
        await delay(wordLength);

        //varFirstLetter =
        speak('Here is a hint, The word starts with the letter: ' + ans.substring(0, 1) + hint)
        // Put the other hand here
}
<commit_msg>test from chromebox local username xxx<commit_after>async function startProgram() {
        // Write code here

        speak('throw the ball in the air to hear that scrambled word, collide with the ball to hear the solution');
        await delay(8);
        var word = "JAMES";
        var hint = 'A first name for a man';
        var ans = word;
        var wordLength = word.length;
        var scrambled = "";

        for (var i = 0; i < wordLength; i++) {
                var charIndex = Math.floor(Math.random() * word.length);
                scrambled += word.charAt(charIndex) + ' , ';
                word = word.substr(0, charIndex) + word.substr(charIndex + 1);
        }
        speak('The scrambled word is:' + scrambled);
        await delay(wordLength);
        speak('The answer is:' + ans);
        await delay(wordLength);

        //varFirstLetter =
        speak('Here is a hint, The word starts with the letter: ' + ans.substring(0, 1) + hint)
        // Put the other hand here
}
<|endoftext|>


<commit_before>/*
*
*	Headless abilities
*
*/
G.abilities[39] =[

// 	First Ability: Larva Infest
{
	//	Type : Can be "onQuery","onStartPhase","onDamage"
	trigger : "onStartPhase",

	_targetTeam: "enemy",
	_getHexes: function() {
		return this.creature.getHexMap(inlineback2hex);
	},

	require: function() {
		if (!this.atLeastOneTarget(this._getHexes(), this._targetTeam)) {
			return false;
		}
		return this.testRequirements();
	},

	//	activate() :
	activate : function() {
		var ability = this;
		var creature = this.creature;

		if (this.atLeastOneTarget(this._getHexes(), this._targetTeam)) {
			this.end();
			this.setUsed(false); // Infinite triggering
		}else{
			return false;
		}

		var targets = this.getTargets(this._getHexes());

		targets.each(function() {
			if( !(this.target instanceof Creature) ) return;

			var trg = this.target;

			if (ability.isUpgraded()) {
				// Upgraded ability causes fatigue - endurance set to 0
				trg.addFatigue(trg.endurance);
			}

			// Add an effect that triggers on the target's start phase and adds the
			// debuff
			var effect = new Effect(
				ability.title, // Name
				creature, // Caster
				trg, // Target
				"onStartPhase", // Trigger
				{
					effectFn: function() {
						// Activate debuff
						trg.addEffect(new Effect(
							"", // No name to prevent logging
							creature,
							trg,
							"",
							{
								deleteTrigger: "",
								stackable: true,
								alterations: { endurance: -5 }
							}
						));
						// Note: effect activate by default adds the effect on the target,
						// but target already has this effect, so remove the trigger to
						// prevent infinite addition of this effect.
						this.trigger = "";
						this.deleteEffect();
					}
				}
			);

			trg.addEffect(effect, "%CreatureName" + trg.id + "% has been infested");
		});
	},
},

// 	Second Ability: Cartilage Dagger
{
	//	Type : Can be "onQuery", "onStartPhase", "onDamage"
	trigger : "onQuery",

	// 	require() :
	require : function() {
		var crea = this.creature;

		if( !this.testRequirements() ) return false;

		//At least one target
		if( !this.atLeastOneTarget(crea.getHexMap(frontnback2hex), "enemy") ) {
			this.message = G.msg.abilities.notarget;
			return false;
		}
		return true;
	},

	// 	query() :
	query : function() {
		var ability = this;
		var crea = this.creature;

		G.grid.queryCreature( {
			fnOnConfirm : function() { ability.animation.apply(ability, arguments); },
			team : 0, // Team, 0 = enemies
			id : crea.id,
			flipped : crea.flipped,
			hexs : crea.getHexMap(frontnback2hex),
		});
	},

	//	activate() :
	activate : function(target,args) {
		var ability = this;
		ability.end();

		var d = { pierce: 11 };
		//Bonus for fatigued foe
		d.pierce = target.endurance <= 0 ? d.pierce * 2 : d.pierce;
		// Extra pierce damage if upgraded
		if (this.isUpgraded()) {
			var bonus = this.creature.stats.endurance - target.stats.endurance;
			if (bonus > 0) {
				d.pierce += bonus;
			}
		}

		var damage = new Damage(
			ability.creature, //Attacker
			d, //Damage Type
			1, //Area
			[]	//Effects
		);

		var dmg = target.takeDamage(damage);
	},
},

// 	Third Ability: Whip Move
{
	//	Type : Can be "onQuery", "onStartPhase", "onDamage"
	trigger : "onQuery",

	directions : [0,1,0,0,1,0],

	_minDistance: 2,
	_getMaxDistance: function() {
		if (this.isUpgraded()) {
			return 8;
		}
		return 6;
	},
	_targetTeam: "both",
	_getValidDirections: function() {
		// Get all directions where there are no targets within min distance,
		// and a target within max distance
		var crea = this.creature;
		var x = crea.player.flipped ? crea.x - crea.size + 1 : crea.x;
		var validDirections = [0, 0, 0, 0, 0, 0];
		for (var i = 0; i < this.directions.length; i++) {
			if (this.directions[i] === 0) {
				continue;
			}
			var directions = [0, 0, 0, 0, 0, 0];
			directions[i] = 1;
			var testMin = this.testDirection({
				team: this._targetTeam,
				x: x,
				directions: directions,
				distance: this._minDistance,
				sourceCreature: crea
			});
			var testMax = this.testDirection({
				team: this._targetTeam,
				x: x,
				directions: directions,
				distance: this._getMaxDistance(),
				sourceCreature: crea
			});
			if (!testMin && testMax) {
				// Target needs to be moveable
				var fx = 0;
				if ((!this.creature.player.flipped && i>2) ||
						(this.creature.player.flipped && i<3)) {
					fx =  -1*(this.creature.size-1);
				}
				var dir = G.grid.getHexLine(
					this.creature.x+fx, this.creature.y, i, this.creature.player.flipped);
				if (this._getMaxDistance() > 0) {
					dir = dir.slice(0,this._getMaxDistance()+1);
				}
				dir = dir.filterCreature(true, true, this.creature.id);
				var target = dir.last().creature;
				if (target.stats.moveable) {
					validDirections[i] = 1;
				}
			}
		}
		return validDirections;
	},

	require : function(){
		if( !this.testRequirements() ) return false;

		// Creature must be moveable
		if (!this.creature.stats.moveable) {
			this.message = G.msg.abilities.notmoveable;
			return false;
		}

		// There must be at least one direction where there is a target within
		// min/max range
		var validDirections = this._getValidDirections();
		if (!validDirections.some(function(e) { return e === 1; })) {
			this.message = G.msg.abilities.notarget;
			return false;
		}
		return true;
	},

	// 	query() :
	query : function(){
		var ability = this;
		var crea = this.creature;

		G.grid.queryDirection({
			fnOnConfirm : function(){ ability.animation.apply(ability,arguments); },
			team : this._targetTeam,
			id : crea.id,
			requireCreature : true,
			sourceCreature : crea,
			x : crea.x,
			y : crea.y,
			directions : this._getValidDirections(),
			distance: this._getMaxDistance()
		});
	},

	//	activate() :
	activate : function(path,args) {
		var ability = this;
		var crea = this.creature;
		var target = path.last().creature;
		path = path.filter( function(){	return !this.creature; }); //remove creatures
		ability.end();

		//Movement
		var creature = (args.direction==4) ? crea.hexagons[crea.size-1] : crea.hexagons[0] ;
		path.filterCreature(false, false);
		var destination = null;
		var destinationTarget = null;
		if (target.size === 1) {
			// Small creature, pull target towards self
			destinationTarget = path.first();
		} else if (target.size === 2) {
			// Medium creature, pull self and target towards each other half way,
			// rounding upwards for self (self move one extra hex if required)
			var midpoint = Math.floor((path.length - 1) / 2);
			destination = path[midpoint];
			if (midpoint < path.length - 1) {
				destinationTarget = path[midpoint + 1];
			}
		} else {
			// Large creature, pull self towards target
			destination = path.last();
		}

		var x;
		var hex;
		if (destination !== null) {
			x = (args.direction === 4) ? destination.x + crea.size - 1 : destination.x;
			hex = G.grid.hexs[destination.y][x];
			crea.moveTo(hex, {
				ignoreMovementPoint: true,
				ignorePath: true,
				callback: function() {
					var interval = setInterval(function() {
						if (!G.freezedInput) {
							clearInterval(interval);
							G.activeCreature.queryMove();
						}
					}, 100);
				},
			});
		}
		if (destinationTarget !== null) {
			x = (args.direction === 1) ? destinationTarget.x + target.size - 1 : destinationTarget.x;
			hex = G.grid.hexs[destinationTarget.y][x];
			target.moveTo(hex, {
				ignoreMovementPoint: true,
				ignorePath: true,
				callback: function() {
					var interval = setInterval(function() {
						if (!G.freezedInput) {
							clearInterval(interval);
							G.activeCreature.queryMove();
						}
					}, 100);
				},
			});
		}
	},
},

// 	Fourth Ability: Boomerang Tool
{
	//	Type : Can be "onQuery","onStartPhase","onDamage"
	trigger : "onQuery",

	damages : {
		slash : 10,
		crush : 5,
	},

	_getHexes: function() {
		// extra range if upgraded
		var hexes;
		if (this.isUpgraded()) {
			hexes = [
				  [0,0,0,0,0,0],
				   [0,1,1,1,1,1],
					[0,1,1,1,1,1],//origin line
				   [0,1,1,1,1,1]];
		} else {
			hexes = [
				  [0,0,0,0,0],
				   [0,1,1,1,1],
					[0,1,1,1,1],//origin line
				   [0,1,1,1,1]];
		}
		hexes.origin = [0, 2];
		return hexes;
	},

	// 	require() :
	require : function(){
		if( !this.testRequirements() ) return false;
		return true;
	},

	// 	query() :
	query : function(){
		var ability = this;
		var crea = this.creature;

		var hexes = this._getHexes();

		G.grid.queryChoice({
			fnOnConfirm : function(){ ability.animation.apply(ability,arguments); },
			team : "both",
			requireCreature : 0,
			id : crea.id,
			flipped : crea.player.flipped,
			choices: [
				crea.getHexMap(hexes),
				crea.getHexMap(hexes, true),
			],
		});
	},

	activate: function(hexs) {
		var damages = { slash : 10 };

		var ability = this;
		ability.end();

		ability.areaDamage(
			ability.creature, //Attacker
			damages, //Damage Type
			[],	//Effects
			ability.getTargets(hexs), //Targets
			true //Notriggers avoid double retailiation
		);

		ability.areaDamage(
			ability.creature, //Attacker
			damages, //Damage Type
			[],	//Effects
			ability.getTargets(hexs) //Targets
		);
	},
}
];
<commit_msg>Larva Infest occurs both at start and end of turn (#1018)<commit_after>/*
*
*	Headless abilities
*
*/
G.abilities[39] =[

// 	First Ability: Larva Infest
{
	trigger: "onStartPhase onEndPhase",

	_targetTeam: "enemy",
	_getHexes: function() {
		return this.creature.getHexMap(inlineback2hex);
	},

	require: function() {
		if (!this.atLeastOneTarget(this._getHexes(), this._targetTeam)) {
			return false;
		}
		return this.testRequirements();
	},

	//	activate() :
	activate : function() {
		var ability = this;
		var creature = this.creature;

		if (this.atLeastOneTarget(this._getHexes(), this._targetTeam)) {
			this.end();
			this.setUsed(false); // Infinite triggering
		}else{
			return false;
		}

		var targets = this.getTargets(this._getHexes());

		targets.each(function() {
			if( !(this.target instanceof Creature) ) return;

			var trg = this.target;

			if (ability.isUpgraded()) {
				// Upgraded ability causes fatigue - endurance set to 0
				trg.addFatigue(trg.endurance);
			}

			// Add an effect that triggers on the target's start phase and adds the
			// debuff
			var effect = new Effect(
				ability.title, // Name
				creature, // Caster
				trg, // Target
				"onStartPhase", // Trigger
				{
					effectFn: function() {
						// Activate debuff
						trg.addEffect(new Effect(
							"", // No name to prevent logging
							creature,
							trg,
							"",
							{
								deleteTrigger: "",
								stackable: true,
								alterations: { endurance: -5 }
							}
						));
						// Note: effect activate by default adds the effect on the target,
						// but target already has this effect, so remove the trigger to
						// prevent infinite addition of this effect.
						this.trigger = "";
						this.deleteEffect();
					}
				}
			);

			trg.addEffect(effect, "%CreatureName" + trg.id + "% has been infested");
		});
	},
},

// 	Second Ability: Cartilage Dagger
{
	//	Type : Can be "onQuery", "onStartPhase", "onDamage"
	trigger : "onQuery",

	// 	require() :
	require : function() {
		var crea = this.creature;

		if( !this.testRequirements() ) return false;

		//At least one target
		if( !this.atLeastOneTarget(crea.getHexMap(frontnback2hex), "enemy") ) {
			this.message = G.msg.abilities.notarget;
			return false;
		}
		return true;
	},

	// 	query() :
	query : function() {
		var ability = this;
		var crea = this.creature;

		G.grid.queryCreature( {
			fnOnConfirm : function() { ability.animation.apply(ability, arguments); },
			team : 0, // Team, 0 = enemies
			id : crea.id,
			flipped : crea.flipped,
			hexs : crea.getHexMap(frontnback2hex),
		});
	},

	//	activate() :
	activate : function(target,args) {
		var ability = this;
		ability.end();

		var d = { pierce: 11 };
		//Bonus for fatigued foe
		d.pierce = target.endurance <= 0 ? d.pierce * 2 : d.pierce;
		// Extra pierce damage if upgraded
		if (this.isUpgraded()) {
			var bonus = this.creature.stats.endurance - target.stats.endurance;
			if (bonus > 0) {
				d.pierce += bonus;
			}
		}

		var damage = new Damage(
			ability.creature, //Attacker
			d, //Damage Type
			1, //Area
			[]	//Effects
		);

		var dmg = target.takeDamage(damage);
	},
},

// 	Third Ability: Whip Move
{
	//	Type : Can be "onQuery", "onStartPhase", "onDamage"
	trigger : "onQuery",

	directions : [0,1,0,0,1,0],

	_minDistance: 2,
	_getMaxDistance: function() {
		if (this.isUpgraded()) {
			return 8;
		}
		return 6;
	},
	_targetTeam: "both",
	_getValidDirections: function() {
		// Get all directions where there are no targets within min distance,
		// and a target within max distance
		var crea = this.creature;
		var x = crea.player.flipped ? crea.x - crea.size + 1 : crea.x;
		var validDirections = [0, 0, 0, 0, 0, 0];
		for (var i = 0; i < this.directions.length; i++) {
			if (this.directions[i] === 0) {
				continue;
			}
			var directions = [0, 0, 0, 0, 0, 0];
			directions[i] = 1;
			var testMin = this.testDirection({
				team: this._targetTeam,
				x: x,
				directions: directions,
				distance: this._minDistance,
				sourceCreature: crea
			});
			var testMax = this.testDirection({
				team: this._targetTeam,
				x: x,
				directions: directions,
				distance: this._getMaxDistance(),
				sourceCreature: crea
			});
			if (!testMin && testMax) {
				// Target needs to be moveable
				var fx = 0;
				if ((!this.creature.player.flipped && i>2) ||
						(this.creature.player.flipped && i<3)) {
					fx =  -1*(this.creature.size-1);
				}
				var dir = G.grid.getHexLine(
					this.creature.x+fx, this.creature.y, i, this.creature.player.flipped);
				if (this._getMaxDistance() > 0) {
					dir = dir.slice(0,this._getMaxDistance()+1);
				}
				dir = dir.filterCreature(true, true, this.creature.id);
				var target = dir.last().creature;
				if (target.stats.moveable) {
					validDirections[i] = 1;
				}
			}
		}
		return validDirections;
	},

	require : function(){
		if( !this.testRequirements() ) return false;

		// Creature must be moveable
		if (!this.creature.stats.moveable) {
			this.message = G.msg.abilities.notmoveable;
			return false;
		}

		// There must be at least one direction where there is a target within
		// min/max range
		var validDirections = this._getValidDirections();
		if (!validDirections.some(function(e) { return e === 1; })) {
			this.message = G.msg.abilities.notarget;
			return false;
		}
		return true;
	},

	// 	query() :
	query : function(){
		var ability = this;
		var crea = this.creature;

		G.grid.queryDirection({
			fnOnConfirm : function(){ ability.animation.apply(ability,arguments); },
			team : this._targetTeam,
			id : crea.id,
			requireCreature : true,
			sourceCreature : crea,
			x : crea.x,
			y : crea.y,
			directions : this._getValidDirections(),
			distance: this._getMaxDistance()
		});
	},

	//	activate() :
	activate : function(path,args) {
		var ability = this;
		var crea = this.creature;
		var target = path.last().creature;
		path = path.filter( function(){	return !this.creature; }); //remove creatures
		ability.end();

		//Movement
		var creature = (args.direction==4) ? crea.hexagons[crea.size-1] : crea.hexagons[0] ;
		path.filterCreature(false, false);
		var destination = null;
		var destinationTarget = null;
		if (target.size === 1) {
			// Small creature, pull target towards self
			destinationTarget = path.first();
		} else if (target.size === 2) {
			// Medium creature, pull self and target towards each other half way,
			// rounding upwards for self (self move one extra hex if required)
			var midpoint = Math.floor((path.length - 1) / 2);
			destination = path[midpoint];
			if (midpoint < path.length - 1) {
				destinationTarget = path[midpoint + 1];
			}
		} else {
			// Large creature, pull self towards target
			destination = path.last();
		}

		var x;
		var hex;
		if (destination !== null) {
			x = (args.direction === 4) ? destination.x + crea.size - 1 : destination.x;
			hex = G.grid.hexs[destination.y][x];
			crea.moveTo(hex, {
				ignoreMovementPoint: true,
				ignorePath: true,
				callback: function() {
					var interval = setInterval(function() {
						if (!G.freezedInput) {
							clearInterval(interval);
							G.activeCreature.queryMove();
						}
					}, 100);
				},
			});
		}
		if (destinationTarget !== null) {
			x = (args.direction === 1) ? destinationTarget.x + target.size - 1 : destinationTarget.x;
			hex = G.grid.hexs[destinationTarget.y][x];
			target.moveTo(hex, {
				ignoreMovementPoint: true,
				ignorePath: true,
				callback: function() {
					var interval = setInterval(function() {
						if (!G.freezedInput) {
							clearInterval(interval);
							G.activeCreature.queryMove();
						}
					}, 100);
				},
			});
		}
	},
},

// 	Fourth Ability: Boomerang Tool
{
	//	Type : Can be "onQuery","onStartPhase","onDamage"
	trigger : "onQuery",

	damages : {
		slash : 10,
		crush : 5,
	},

	_getHexes: function() {
		// extra range if upgraded
		var hexes;
		if (this.isUpgraded()) {
			hexes = [
				  [0,0,0,0,0,0],
				   [0,1,1,1,1,1],
					[0,1,1,1,1,1],//origin line
				   [0,1,1,1,1,1]];
		} else {
			hexes = [
				  [0,0,0,0,0],
				   [0,1,1,1,1],
					[0,1,1,1,1],//origin line
				   [0,1,1,1,1]];
		}
		hexes.origin = [0, 2];
		return hexes;
	},

	// 	require() :
	require : function(){
		if( !this.testRequirements() ) return false;
		return true;
	},

	// 	query() :
	query : function(){
		var ability = this;
		var crea = this.creature;

		var hexes = this._getHexes();

		G.grid.queryChoice({
			fnOnConfirm : function(){ ability.animation.apply(ability,arguments); },
			team : "both",
			requireCreature : 0,
			id : crea.id,
			flipped : crea.player.flipped,
			choices: [
				crea.getHexMap(hexes),
				crea.getHexMap(hexes, true),
			],
		});
	},

	activate: function(hexs) {
		var damages = { slash : 10 };

		var ability = this;
		ability.end();

		ability.areaDamage(
			ability.creature, //Attacker
			damages, //Damage Type
			[],	//Effects
			ability.getTargets(hexs), //Targets
			true //Notriggers avoid double retailiation
		);

		ability.areaDamage(
			ability.creature, //Attacker
			damages, //Damage Type
			[],	//Effects
			ability.getTargets(hexs) //Targets
		);
	},
}
];
<|endoftext|>


<commit_before>//
// Handle drag and drop page conversions
//

// only in global scope for dubugging purposes
var uploader = null;
var formatter = null;
var nodetree = null;
var sdviewer = null;
var hocrviewer = null;
var reshandler = null;
var presetmanager = null;
var guimanager = null;

function saveState() {
    presetmanager.saveState();
    nodetree.saveState();
}

function loadState() {
    presetmanager.loadState();
    nodetree.loadState();
}

$(function() {

    // script builder buttons
    $("#stop_refresh").button({
        text: false,
        icons: {
            primary: "ui-icon-refresh",
        }
    });
    $("#focus_script").button({
        text: false,
        icons: {
            primary: "ui-icon-home",
        }
    });
    $("#layout_nodes").button({
        text: false,
        icons: {
            primary: "ui-icon-grip-dotted-vertical",
        }
    });
    $("#clear_cache").button({
        text: false,
        icons: {
            primary: "ui-icon-suitcase",
        }
    });
    $("#save_task_preset").button({
        icons: {
            primary: "ui-icon-arrowreturnthick-1-w",
        }
    });
    $("#cancel_task_preset").button({
        icons: {
            primary: "ui-icon-cancel",
        }
    });

    // viewer toolsbar
    // style toolbar
    $(".tbbutton").button({
        disabled: false,
    });
    $("#format").buttonset();
    $("#hocr_zoomin").click(function(event) {
        hocrviewer.increaseFontSize();
    }).button({
        text: false,
        icons: {
            primary: "ui-icon-zoomin",
        }
    });
    $("#hocr_zoomout").click(function(event) {
        hocrviewer.reduceFontSize();
    }).button({
        text: false,
        icons: {
            primary: "ui-icon-zoomout",
        }
    });
    $("#format_block").click(function(event) {
        formatter.blockLayout(hocrviewer.container());
    });
    $("#format_column").click(function(event) {
        formatter.columnLayout(hocrviewer.container());
    });

    $("#image_zoomin").click(function(event) {
        sdviewer.zoomBy(2);
    }).button({
        text: false,
        icons: {
            primary: "ui-icon-zoomin",
        }
    });
    $("#image_zoomout").click(function(event) {
        sdviewer.zoomBy(0.5);
    }).button({
        text: false,
        icons: {
            primary: "ui-icon-zoomout",
        }
    });
    $("#centre").click(function(event) {
        sdviewer.goHome();
    }).button({
        text: false,
        icons: {
            primary: "ui-icon-home",
        }
    });
    $("#fullscreen").click(function(event) {
        sdviewer.setFullPage(true);
    }).button({
        text: false,
        icons: {
            primary: "ui-icon-arrow-4-diag",
        }
    });
    $("#text_zoomin").click(function(event) {
        textviewer.increaseFontSize();
    }).button({
        text: false,
        icons: {
            primary: "ui-icon-zoomin",
        }
    });
    $("#text_zoomout").click(function(event) {
        textviewer.reduceFontSize();
    }).button({
        text: false,
        icons: {
            primary: "ui-icon-zoomout",
        }
    });

    $("#refresh").click(function(event) {
        var active = sdviewer.activeBuffer();
        sdviewer.setBufferPath(active, sdviewer.bufferPath(active));
    }).button({
        text: false,
        icons: {
            primary: "ui-icon-refresh",
        }
    });

    $("#focus_script").click(function(event) {
        nodetree.centreTree();
        event.preventDefault();
        event.stopPropagation();
    });
    $("#layout_nodes").click(function(event) {
        nodetree.layoutNodes();
        event.preventDefault();
        event.stopPropagation();
    });
    $("#clear_cache").click(function(event) {
        resultcache = {};
        $.getJSON("/presets/clear_cache/", function(data) {
            if (data && data.ok)
                alert("Cache cleared");
            else
                console.error("Unexpect result on cache clear", data);
        });
        event.preventDefault();
        event.stopPropagation();
    });
    $("#save_task_preset").click(function(event) {
        $("#task_update_script").val(
           JSON.stringify(nodetree.buildScript(), null, '\t'));
        $("#task_update_form").submit();
    });
    $("#cancel_task_preset").click(function(event) {
        $("#task_update_script").val("");
        $("#task_update_form").submit();
    });

    // initialise the uploader...
    uploader  = new OCRJS.AjaxUploader(
        null,
        "/presets/upload/",
        { multi: false, errorhandler: OCRJS.ajaxErrorHandler, }
    );
    // save state on leaving the page... at least try to...
    window.onbeforeunload = function(event) {
        try {
            saveState();
        } catch (msg) {
            alert(msg);
        }
    }

    $(".nodefilein").live("change", function(event) {
        console.log("Change:", $(this).val());
    });

    $("#viewertabs").tabs({
        select: function(event, ui) {
            // ensure we refresh the buffer when switching
            // back to an image tab, otherwise the viewer
            // loses its images...
            sdviewer.setBufferPath(sdviewer.activeBuffer(),
                sdviewer.activeBufferPath());
        },
    });

    $("#stop_refresh").click(function() {
        if (reshandler.isPending()) {
            reshandler.abort();
        } else {
            nodetree.scriptChanged("Refresh");
        }
        event.stopPropagation();
        event.preventDefault();
    });

    $("#optionsform").submit(function(event) {
        nodetree.scriptChanged();
        event.stopPropagation();
        event.preventDefault();
    });

    var resultcache = {};
    function handleResult(nodename, data, cached) {
        console.log("Data:", data);
        if (data.result.type == "error") {
            console.log("NODE ERROR: ", data.result.node, data.result.error);
            nodetree.setNodeErrored(data.result.node, data.result.error);
            return;
        }

        // otherwise cache the result and handle it
        if (!cached) {
            var node = nodetree.getNode(nodename);
            if (node) {
                var hash = hex_md5(bencode(node.hashValue()));
                resultcache[hash] = data;
            }
        }

        if (data.result.type == "image" || data.result.type == "pseg") {
            // this magic hides the buffer loading transition by putting the
            // new data in the back buffer and switching them after a delay
            // TODO: Find if we can subscript to an event to tell us exactly
            // when it's safe to switch.  ATM just using a 200ms delay.
            var active = sdviewer.activeBuffer();
            sdviewer.setBufferPath(active^1, data.result.dzi);
            setTimeout(function() {
                sdviewer.setActiveBuffer(active^1);
                guimanager.refreshGui();
            }, 150);

            var overlays = {};
            if (data.result.type == "pseg") {
                console.log("Result:", data.result);
                var overlays = {};
                $.each(["lines", "paragraphs", "columns"], function(i, klass) {
                    if (data.result[klass]) {
                        overlays[klass] = data.result[klass];
                    }
                });
            }
            sdviewer.setBufferOverlays(overlays, 0);
            $("#viewertabs").tabs("select", 0);
        } else if (data.result.type == "hocr") {
            hocrviewer.setData(data.result.data);
            $("#viewertabs").tabs("select", 1);
            $("input[name='format']:checked").click();
        } else if (data.result.type == "text") {
            textviewer.setData(data.result.data);
            $("#viewertabs").tabs("select", 2);
        } else {
            throw "Unknown result type: " + data.result.type;
        }
    }

    function runScript() {
        var nodename = nodetree.getEvalNode();
        var node = nodetree.getNode(nodename);
        if (node) {
            var hash = hex_md5(bencode(node.hashValue()));
            console.log("Hash for node", node.name, hash);
            if (resultcache[hash]) {
                console.log("Found cached result for:", nodename);
                handleResult(nodename, resultcache[hash], true);
            } else
                reshandler.runScript(nodename, nodetree.buildScript());
        }
    }

    // Initialise objects
    sdviewer = new OCRJS.ImageViewer($("#imageviewer_1").get(0), {
        numBuffers: 2,
        dashboard: false,
    });
    guimanager = new OCRJS.Nodetree.GuiManager(sdviewer);
    hocrviewer = new OCRJS.HocrViewer($("#hocrviewer_1").get(0));
    textviewer = new OCRJS.TextViewer($("#textviewer_1").get(0));
    reshandler = new OCRJS.ResultHandler();
    formatter = new OCRJS.LineFormatter();
    nodetree = new OCRJS.Nodetree.NodeTree($("#node_canvas"));
    presetmanager = new OCRJS.PresetManager($("#script_toolbar").get(0), nodetree);

    // Set up events
    nodetree.addListeners({
        scriptChanged: function(what) {
            console.log("Script changed:", what);
            presetmanager.checkForChanges();
            runScript();
        },
        nodeFocussed: function(node) {
            if (!node)
                guimanager.tearDownGui();
            else {
                if (sdviewer.activeViewer()) {
                    console.log("Setting GUI for", node.name);
                    guimanager.setupGui(node);
                }
            }
        },
        registerUploader: function(name, elem) {
            uploader.removeListeners("onXHRLoad.setfilepath");
            uploader.setTarget(elem);
            // FIXME: No error handling
            uploader.addListener("onXHRLoad.setfilepath", function(data) {
                nodetree.setFileInPath(name, JSON.parse(data.target.response).file);
            });
        },
        ready: function() {
            // load state stored from last time
            loadState();
        },
    });

    //var iconstates = ["ui-icon-arrowrefresh-1-w", "ui-icon-arrowrefresh-1-n",
    //        "ui-icon-arrowrefresh-1-e", "ui-icon-arrowrefresh-1-s"];
    //var refindex = 0;
    //var reftimer = null;
    //function showPending() {
    //    clearTimeout(reftimer);
    //    reftimer = setTimeout(function() {
    //        $("#stop_refresh").button("option", "icons", {
    //            primary: iconstates[refindex],
    //        });
    //        refindex++;
    //        if (refindex >= iconstates.length)
    //            refindex = 0;
    //        showPending();
    //    }, 100);
    //}

    reshandler.addListeners({
        resultPending: function() {
            $("#stop_refresh").button({
                text: false,
                icons: {
                    primary: "ui-icon-cancel",
                }
            });
            //showPending();
            nodetree.clearErrors();
        },
        validationErrors: function(nodeerrors) {
            //clearTimeout(reftimer);
            $("#stop_refresh").button({
                text: false,
                icons: {
                    primary: "ui-icon-refresh",
                }
            });
            nodetree.clearErrors();
            $.each(nodeerrors, function(node, error) {
                nodetree.setNodeErrored(node, error);
            });
            // clear the client-size cache
            resultcache = {};
        },
        resultDone: function(node, data) {
            //clearTimeout(reftimer);
            $("#stop_refresh").button({
                text: false,
                icons: {
                    primary: "ui-icon-refresh",
                }
            });
            if (data.status != "ABORT" && data.status != "FAILURE")
               handleResult(node, data, false);
        }
    });

    var hsplit = $("#sidebar").layout({
        applyDefaultStyles: true,
        north: {
            resizable: false,
            closable: false,
            slidable: false,
            spacing_open: 0,
        },
        south: {
            size: 200,
            onresize_end: function() {
                setTimeout(function() {
                    nodetree.resetSize();
                });
            },
            onclose_end: function() {
                setTimeout(function() {
                    nodetree.resetSize();
                });
            },
        },
    });

    vsplit.options.east.onresize_end = function() {
        setTimeout(function() {
            nodetree.resetSize();
            sdviewer.resetSize();
        });
    };
    vsplit.options.center.onresize_end = function() {
        // FIXME: grotesque hack to resize the image viewer
        // to fit the full page height.  The viewers overflow
        // is also hidden in CSS.
        var iv = $("#imageviewer_1");
            mc = $("#maincontent");
        iv.height((mc.height() + mc.offset().top) - (iv.offset().top));
        sdviewer.resetSize();
    };

    $(window).resize();

    // Initialise nodetree!
    nodetree.init();

    // the run script on first load
    runScript();
});

<commit_msg>Add error handling<commit_after>//
// Handle drag and drop page conversions
//

// only in global scope for dubugging purposes
var uploader = null;
var formatter = null;
var nodetree = null;
var sdviewer = null;
var hocrviewer = null;
var reshandler = null;
var presetmanager = null;
var guimanager = null;

function saveState() {
    presetmanager.saveState();
    nodetree.saveState();
}

function loadState() {
    presetmanager.loadState();
    nodetree.loadState();
}

$(function() {

    // script builder buttons
    $("#stop_refresh").button({
        text: false,
        icons: {
            primary: "ui-icon-refresh",
        }
    });
    $("#focus_script").button({
        text: false,
        icons: {
            primary: "ui-icon-home",
        }
    });
    $("#layout_nodes").button({
        text: false,
        icons: {
            primary: "ui-icon-grip-dotted-vertical",
        }
    });
    $("#clear_cache").button({
        text: false,
        icons: {
            primary: "ui-icon-suitcase",
        }
    });
    $("#save_task_preset").button({
        icons: {
            primary: "ui-icon-arrowreturnthick-1-w",
        }
    });
    $("#cancel_task_preset").button({
        icons: {
            primary: "ui-icon-cancel",
        }
    });

    // viewer toolsbar
    // style toolbar
    $(".tbbutton").button({
        disabled: false,
    });
    $("#format").buttonset();
    $("#hocr_zoomin").click(function(event) {
        hocrviewer.increaseFontSize();
    }).button({
        text: false,
        icons: {
            primary: "ui-icon-zoomin",
        }
    });
    $("#hocr_zoomout").click(function(event) {
        hocrviewer.reduceFontSize();
    }).button({
        text: false,
        icons: {
            primary: "ui-icon-zoomout",
        }
    });
    $("#format_block").click(function(event) {
        formatter.blockLayout(hocrviewer.container());
    });
    $("#format_column").click(function(event) {
        formatter.columnLayout(hocrviewer.container());
    });

    $("#image_zoomin").click(function(event) {
        sdviewer.zoomBy(2);
    }).button({
        text: false,
        icons: {
            primary: "ui-icon-zoomin",
        }
    });
    $("#image_zoomout").click(function(event) {
        sdviewer.zoomBy(0.5);
    }).button({
        text: false,
        icons: {
            primary: "ui-icon-zoomout",
        }
    });
    $("#centre").click(function(event) {
        sdviewer.goHome();
    }).button({
        text: false,
        icons: {
            primary: "ui-icon-home",
        }
    });
    $("#fullscreen").click(function(event) {
        sdviewer.setFullPage(true);
    }).button({
        text: false,
        icons: {
            primary: "ui-icon-arrow-4-diag",
        }
    });
    $("#text_zoomin").click(function(event) {
        textviewer.increaseFontSize();
    }).button({
        text: false,
        icons: {
            primary: "ui-icon-zoomin",
        }
    });
    $("#text_zoomout").click(function(event) {
        textviewer.reduceFontSize();
    }).button({
        text: false,
        icons: {
            primary: "ui-icon-zoomout",
        }
    });

    $("#refresh").click(function(event) {
        var active = sdviewer.activeBuffer();
        sdviewer.setBufferPath(active, sdviewer.bufferPath(active));
    }).button({
        text: false,
        icons: {
            primary: "ui-icon-refresh",
        }
    });

    $("#focus_script").click(function(event) {
        nodetree.centreTree();
        event.preventDefault();
        event.stopPropagation();
    });
    $("#layout_nodes").click(function(event) {
        nodetree.layoutNodes();
        event.preventDefault();
        event.stopPropagation();
    });
    $("#clear_cache").click(function(event) {
        resultcache = {};
        $.ajax({
            url: "/presets/clear_cache/",
            type: "POST",
            dataType: "JSON",
            error: OCRJS.ajaxErrorHandler,
            success: function(data) {
                if (data && data.ok)
                    alert("Cache cleared");
                else
                    console.error("Unexpect result on cache clear", data);
            },
        });
        event.preventDefault();
        event.stopPropagation();
    });
    $("#save_task_preset").click(function(event) {
        $("#task_update_script").val(
           JSON.stringify(nodetree.buildScript(), null, '\t'));
        $("#task_update_form").submit();
    });
    $("#cancel_task_preset").click(function(event) {
        $("#task_update_script").val("");
        $("#task_update_form").submit();
    });

    // initialise the uploader...
    uploader  = new OCRJS.AjaxUploader(
        null,
        "/presets/upload/",
        { multi: false, errorhandler: OCRJS.ajaxErrorHandler, }
    );
    // save state on leaving the page... at least try to...
    window.onbeforeunload = function(event) {
        try {
            saveState();
        } catch (msg) {
            alert(msg);
        }
    }

    $(".nodefilein").live("change", function(event) {
        console.log("Change:", $(this).val());
    });

    $("#viewertabs").tabs({
        select: function(event, ui) {
            // ensure we refresh the buffer when switching
            // back to an image tab, otherwise the viewer
            // loses its images...
            sdviewer.setBufferPath(sdviewer.activeBuffer(),
                sdviewer.activeBufferPath());
        },
    });

    $("#stop_refresh").click(function() {
        if (reshandler.isPending()) {
            reshandler.abort();
        } else {
            nodetree.scriptChanged("Refresh");
        }
        event.stopPropagation();
        event.preventDefault();
    });

    $("#optionsform").submit(function(event) {
        nodetree.scriptChanged();
        event.stopPropagation();
        event.preventDefault();
    });

    var resultcache = {};
    function handleResult(nodename, data, cached) {
        console.log("Data:", data);
        if (data.result.type == "error") {
            console.log("NODE ERROR: ", data.result.node, data.result.error);
            nodetree.setNodeErrored(data.result.node, data.result.error);
            return;
        }

        // otherwise cache the result and handle it
        if (!cached) {
            var node = nodetree.getNode(nodename);
            if (node) {
                var hash = hex_md5(bencode(node.hashValue()));
                resultcache[hash] = data;
            }
        }

        if (data.result.type == "image" || data.result.type == "pseg") {
            // this magic hides the buffer loading transition by putting the
            // new data in the back buffer and switching them after a delay
            // TODO: Find if we can subscript to an event to tell us exactly
            // when it's safe to switch.  ATM just using a 200ms delay.
            var active = sdviewer.activeBuffer();
            sdviewer.setBufferPath(active^1, data.result.dzi);
            setTimeout(function() {
                sdviewer.setActiveBuffer(active^1);
                guimanager.refreshGui();
            }, 150);

            var overlays = {};
            if (data.result.type == "pseg") {
                console.log("Result:", data.result);
                var overlays = {};
                $.each(["lines", "paragraphs", "columns"], function(i, klass) {
                    if (data.result[klass]) {
                        overlays[klass] = data.result[klass];
                    }
                });
            }
            sdviewer.setBufferOverlays(overlays, 0);
            $("#viewertabs").tabs("select", 0);
        } else if (data.result.type == "hocr") {
            hocrviewer.setData(data.result.data);
            $("#viewertabs").tabs("select", 1);
            $("input[name='format']:checked").click();
        } else if (data.result.type == "text") {
            textviewer.setData(data.result.data);
            $("#viewertabs").tabs("select", 2);
        } else {
            throw "Unknown result type: " + data.result.type;
        }
    }

    function runScript() {
        var nodename = nodetree.getEvalNode();
        var node = nodetree.getNode(nodename);
        if (node) {
            var hash = hex_md5(bencode(node.hashValue()));
            console.log("Hash for node", node.name, hash);
            if (resultcache[hash]) {
                console.log("Found cached result for:", nodename);
                handleResult(nodename, resultcache[hash], true);
            } else
                reshandler.runScript(nodename, nodetree.buildScript());
        }
    }

    // Initialise objects
    sdviewer = new OCRJS.ImageViewer($("#imageviewer_1").get(0), {
        numBuffers: 2,
        dashboard: false,
    });
    guimanager = new OCRJS.Nodetree.GuiManager(sdviewer);
    hocrviewer = new OCRJS.HocrViewer($("#hocrviewer_1").get(0));
    textviewer = new OCRJS.TextViewer($("#textviewer_1").get(0));
    reshandler = new OCRJS.ResultHandler();
    formatter = new OCRJS.LineFormatter();
    nodetree = new OCRJS.Nodetree.NodeTree($("#node_canvas"));
    presetmanager = new OCRJS.PresetManager($("#script_toolbar").get(0), nodetree);

    // Set up events
    nodetree.addListeners({
        scriptChanged: function(what) {
            console.log("Script changed:", what);
            presetmanager.checkForChanges();
            runScript();
        },
        nodeFocussed: function(node) {
            if (!node)
                guimanager.tearDownGui();
            else {
                if (sdviewer.activeViewer()) {
                    console.log("Setting GUI for", node.name);
                    guimanager.setupGui(node);
                }
            }
        },
        registerUploader: function(name, elem) {
            uploader.removeListeners("onXHRLoad.setfilepath");
            uploader.setTarget(elem);
            // FIXME: No error handling
            uploader.addListener("onXHRLoad.setfilepath", function(data) {
                nodetree.setFileInPath(name, JSON.parse(data.target.response).file);
            });
        },
        ready: function() {
            // load state stored from last time
            loadState();
        },
    });

    //var iconstates = ["ui-icon-arrowrefresh-1-w", "ui-icon-arrowrefresh-1-n",
    //        "ui-icon-arrowrefresh-1-e", "ui-icon-arrowrefresh-1-s"];
    //var refindex = 0;
    //var reftimer = null;
    //function showPending() {
    //    clearTimeout(reftimer);
    //    reftimer = setTimeout(function() {
    //        $("#stop_refresh").button("option", "icons", {
    //            primary: iconstates[refindex],
    //        });
    //        refindex++;
    //        if (refindex >= iconstates.length)
    //            refindex = 0;
    //        showPending();
    //    }, 100);
    //}

    reshandler.addListeners({
        resultPending: function() {
            $("#stop_refresh").button({
                text: false,
                icons: {
                    primary: "ui-icon-cancel",
                }
            });
            //showPending();
            nodetree.clearErrors();
        },
        validationErrors: function(nodeerrors) {
            //clearTimeout(reftimer);
            $("#stop_refresh").button({
                text: false,
                icons: {
                    primary: "ui-icon-refresh",
                }
            });
            nodetree.clearErrors();
            $.each(nodeerrors, function(node, error) {
                nodetree.setNodeErrored(node, error);
            });
            // clear the client-size cache
            resultcache = {};
        },
        resultDone: function(node, data) {
            //clearTimeout(reftimer);
            $("#stop_refresh").button({
                text: false,
                icons: {
                    primary: "ui-icon-refresh",
                }
            });
            if (data.status != "ABORT" && data.status != "FAILURE")
               handleResult(node, data, false);
        }
    });

    var hsplit = $("#sidebar").layout({
        applyDefaultStyles: true,
        north: {
            resizable: false,
            closable: false,
            slidable: false,
            spacing_open: 0,
        },
        south: {
            size: 200,
            onresize_end: function() {
                setTimeout(function() {
                    nodetree.resetSize();
                });
            },
            onclose_end: function() {
                setTimeout(function() {
                    nodetree.resetSize();
                });
            },
        },
    });

    vsplit.options.east.onresize_end = function() {
        setTimeout(function() {
            nodetree.resetSize();
            sdviewer.resetSize();
        });
    };
    vsplit.options.center.onresize_end = function() {
        // FIXME: grotesque hack to resize the image viewer
        // to fit the full page height.  The viewers overflow
        // is also hidden in CSS.
        var iv = $("#imageviewer_1");
            mc = $("#maincontent");
        iv.height((mc.height() + mc.offset().top) - (iv.offset().top));
        sdviewer.resetSize();
    };

    $(window).resize();

    // Initialise nodetree!
    nodetree.init();

    // the run script on first load
    runScript();
});

<|endoftext|>