<commit_before>/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2022 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

var Defaults = require('../tween/Defaults');
var GetAdvancedValue = require('../../utils/object/GetAdvancedValue');
var GetBoolean = require('./GetBoolean');
var GetEaseFunction = require('./GetEaseFunction');
var GetInterpolationFunction = require('./GetInterpolationFunction');
var GetNewValue = require('./GetNewValue');
var GetProps = require('./GetProps');
var GetTargets = require('./GetTargets');
var GetValue = require('../../utils/object/GetValue');
var GetValueOp = require('./GetValueOp');
var Tween = require('../tween/Tween');

/**
 * Creates a new Tween.
 *
 * @function Phaser.Tweens.Builders.TweenBuilder
 * @since 3.0.0
 *
 * @param {Phaser.Tweens.TweenManager} parent - The owner of the new Tween.
 * @param {Phaser.Types.Tweens.TweenBuilderConfig|object} config - Configuration for the new Tween.
 * @param {Phaser.Types.Tweens.TweenConfigDefaults} defaults - Tween configuration defaults.
 *
 * @return {Phaser.Tweens.Tween} The new tween.
 */
var TweenBuilder = function (parent, config, defaults)
{
    if (defaults === undefined)
    {
        defaults = Defaults;
    }

    //  Create arrays of the Targets and the Properties
    var targets = (defaults.targets) ? defaults.targets : GetTargets(config);

    var props = GetProps(config);

    //  Default Tween values

    var delay = GetValue(config, 'delay', defaults.delay);
    var duration = GetValue(config, 'duration', defaults.duration);
    var easeParams = GetValue(config, 'easeParams', defaults.easeParams);
    var ease = GetValue(config, 'ease', defaults.ease);
    var hold = GetValue(config, 'hold', defaults.hold);
    var repeat = GetValue(config, 'repeat', defaults.repeat);
    var repeatDelay = GetValue(config, 'repeatDelay', defaults.repeatDelay);
    var yoyo = GetBoolean(config, 'yoyo', defaults.yoyo);
    var flipX = GetBoolean(config, 'flipX', defaults.flipX);
    var flipY = GetBoolean(config, 'flipY', defaults.flipY);
    var interpolation = GetValue(config, 'interpolation', defaults.interpolation);

    var addTarget = function (tween, targetIndex, key, value)
    {
        var ops = GetValueOp(key, value);

        var interpolationFunc = GetInterpolationFunction(GetValue(value, 'interpolation', interpolation));

        tween.add(
            targetIndex,
            key,
            ops.getEnd,
            ops.getStart,
            ops.getActive,
            GetEaseFunction(GetValue(value, 'ease', ease), GetValue(value, 'easeParams', easeParams)),
            GetNewValue(value, 'delay', delay),
            GetValue(value, 'duration', duration),
            GetBoolean(value, 'yoyo', yoyo),
            GetValue(value, 'hold', hold),
            GetValue(value, 'repeat', repeat),
            GetValue(value, 'repeatDelay', repeatDelay),
            GetBoolean(value, 'flipX', flipX),
            GetBoolean(value, 'flipY', flipY),
            interpolationFunc,
            (interpolationFunc) ? value : null
        );
    };

    var tween = new Tween(parent, targets);

    //  Loop through every property defined in the Tween, i.e.: props { x, y, alpha }
    for (var p = 0; p < props.length; p++)
    {
        var key = props[p].key;
        var value = props[p].value;

        //  Create 1 TweenData per target, per property
        for (var targetIndex = 0; targetIndex < targets.length; targetIndex++)
        {
            //  Special-case for scale short-cut:
            if (key === 'scale' && !targets[targetIndex].hasOwnProperty('scale'))
            {
                addTarget(tween, targetIndex, 'scaleX', value);
                addTarget(tween, targetIndex, 'scaleY', value);
            }
            else
            {
                addTarget(tween, targetIndex, key, value);
            }
        }
    }

    tween.completeDelay = GetAdvancedValue(config, 'completeDelay', 0);
    tween.loop = Math.round(GetAdvancedValue(config, 'loop', 0));
    tween.loopDelay = Math.round(GetAdvancedValue(config, 'loopDelay', 0));
    tween.paused = GetBoolean(config, 'paused', false);
    tween.persist = GetBoolean(config, 'persist', false);

    //  Set the Callbacks
    tween.callbackScope = GetValue(config, 'callbackScope', tween);

    var callbacks = Tween.TYPES;

    for (var i = 0; i < callbacks.length; i++)
    {
        var type = callbacks[i];

        var callback = GetValue(config, type, false);

        if (callback)
        {
            var callbackParams = GetValue(config, type + 'Params', []);

            tween.setCallback(type, callback, callbackParams);
        }
    }

    return tween;
};

module.exports = TweenBuilder;
<commit_msg>If you pass in a tween, return it. Also use merged defaults and base type<commit_after>/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2022 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

var BaseTween = require('../tween/BaseTween');
var Defaults = require('../tween/Defaults');
var GetAdvancedValue = require('../../utils/object/GetAdvancedValue');
var GetBoolean = require('./GetBoolean');
var GetEaseFunction = require('./GetEaseFunction');
var GetInterpolationFunction = require('./GetInterpolationFunction');
var GetNewValue = require('./GetNewValue');
var GetProps = require('./GetProps');
var GetTargets = require('./GetTargets');
var GetValue = require('../../utils/object/GetValue');
var GetValueOp = require('./GetValueOp');
var MergeRight = require('../../utils/object/MergeRight');
var Tween = require('../tween/Tween');

/**
 * Creates a new Tween.
 *
 * @function Phaser.Tweens.Builders.TweenBuilder
 * @since 3.0.0
 *
 * @param {Phaser.Tweens.TweenManager} parent - The owner of the new Tween.
 * @param {Phaser.Types.Tweens.TweenBuilderConfig|object} config - Configuration for the new Tween.
 * @param {Phaser.Types.Tweens.TweenConfigDefaults} defaults - Tween configuration defaults.
 *
 * @return {Phaser.Tweens.Tween} The new tween.
 */
var TweenBuilder = function (parent, config, defaults)
{
    if (config instanceof Tween)
    {
        config.parent = parent;

        return config;
    }

    if (defaults === undefined)
    {
        defaults = Defaults;
    }
    else
    {
        defaults = MergeRight(Defaults, defaults);
    }

    //  Create arrays of the Targets and the Properties
    var targets = (defaults.targets) ? defaults.targets : GetTargets(config);

    var props = GetProps(config);

    //  Default Tween values

    var delay = GetValue(config, 'delay', defaults.delay);
    var duration = GetValue(config, 'duration', defaults.duration);
    var easeParams = GetValue(config, 'easeParams', defaults.easeParams);
    var ease = GetValue(config, 'ease', defaults.ease);
    var hold = GetValue(config, 'hold', defaults.hold);
    var repeat = GetValue(config, 'repeat', defaults.repeat);
    var repeatDelay = GetValue(config, 'repeatDelay', defaults.repeatDelay);
    var yoyo = GetBoolean(config, 'yoyo', defaults.yoyo);
    var flipX = GetBoolean(config, 'flipX', defaults.flipX);
    var flipY = GetBoolean(config, 'flipY', defaults.flipY);
    var interpolation = GetValue(config, 'interpolation', defaults.interpolation);

    var addTarget = function (tween, targetIndex, key, value)
    {
        var ops = GetValueOp(key, value);

        var interpolationFunc = GetInterpolationFunction(GetValue(value, 'interpolation', interpolation));

        tween.add(
            targetIndex,
            key,
            ops.getEnd,
            ops.getStart,
            ops.getActive,
            GetEaseFunction(GetValue(value, 'ease', ease), GetValue(value, 'easeParams', easeParams)),
            GetNewValue(value, 'delay', delay),
            GetValue(value, 'duration', duration),
            GetBoolean(value, 'yoyo', yoyo),
            GetValue(value, 'hold', hold),
            GetValue(value, 'repeat', repeat),
            GetValue(value, 'repeatDelay', repeatDelay),
            GetBoolean(value, 'flipX', flipX),
            GetBoolean(value, 'flipY', flipY),
            interpolationFunc,
            (interpolationFunc) ? value : null
        );
    };

    var tween = new Tween(parent, targets);

    //  Loop through every property defined in the Tween, i.e.: props { x, y, alpha }
    for (var p = 0; p < props.length; p++)
    {
        var key = props[p].key;
        var value = props[p].value;

        //  Create 1 TweenData per target, per property
        for (var targetIndex = 0; targetIndex < targets.length; targetIndex++)
        {
            //  Special-case for scale short-cut:
            if (key === 'scale' && !targets[targetIndex].hasOwnProperty('scale'))
            {
                addTarget(tween, targetIndex, 'scaleX', value);
                addTarget(tween, targetIndex, 'scaleY', value);
            }
            else
            {
                addTarget(tween, targetIndex, key, value);
            }
        }
    }

    tween.completeDelay = GetAdvancedValue(config, 'completeDelay', 0);
    tween.loop = Math.round(GetAdvancedValue(config, 'loop', 0));
    tween.loopDelay = Math.round(GetAdvancedValue(config, 'loopDelay', 0));
    tween.paused = GetBoolean(config, 'paused', false);
    tween.persist = GetBoolean(config, 'persist', false);

    //  Set the Callbacks
    tween.callbackScope = GetValue(config, 'callbackScope', tween);

    var callbacks = BaseTween.TYPES;

    for (var i = 0; i < callbacks.length; i++)
    {
        var type = callbacks[i];

        var callback = GetValue(config, type, false);

        if (callback)
        {
            var callbackParams = GetValue(config, type + 'Params', []);

            tween.setCallback(type, callback, callbackParams);
        }
    }

    return tween;
};

module.exports = TweenBuilder;
<|endoftext|>


<commit_before>export function getCheapestSelectedSuburb (state) {
  const selected = getSelectedSuburbs(state)
  const prices = selected.map(suburb => {
    return suburb.price
  })
  const min = Math.min(...prices)
  return selected.find(suburb => {
    return suburb.price === min
  })
}

export function getSelectedSuburbs (state) {
  const values = state.form.location.values || {}
  const selected = values.suburbs || []
  return getSuburbs(state).filter(suburb => selected.includes(suburb.name))
}

export function getSuburbs (state) {
  return state.suburbs
}
<commit_msg>Find cheapest suburb by land cost<commit_after>export function getCheapestSelectedSuburb (state) {
  const selected = getSelectedSuburbs(state)
  const min = Math.min(...selected.map(suburb => {
    return suburb.landCostPerSqmFloorspace
  }))
  return selected.find(suburb => {
    return suburb.landCostPerSqmFloorspace === min
  })
}

export function getSelectedSuburbs (state) {
  const values = state.form.location.values || {}
  const selected = values.suburbs || []
  return getSuburbs(state).filter(suburb => selected.includes(suburb.name))
}

export function getSuburbs (state) {
  return state.suburbs
}
<|endoftext|>


<commit_before>var m = require('mithril');
var treeBuild = require('tree').build;
var treeOps = require('tree').ops;
var treePath = require('tree').path;
var cevalCtrl = require('ceval').ctrl;
var readDests = require('chess').readDests;
var k = Mousetrap;
var chessground = require('chessground');
var keyboard = require('./keyboard');
var opposite = chessground.util.opposite;
var groundBuild = require('./ground');
var socketBuild = require('./socket');
var moveTestBuild = require('./moveTest');
var mergeSolution = require('./solution');
var throttle = require('common').throttle;
var xhr = require('./xhr');
var sound = require('./sound');

module.exports = function(opts, i18n) {

  var vm = {};
  var data, tree, ground, ceval, moveTest;

  var setPath = function(path) {
    vm.path = path;
    vm.nodeList = tree.getNodeList(path);
    vm.node = treeOps.last(vm.nodeList);
    vm.mainline = treeOps.mainlineNodeList(tree.root);
  };

  var initiate = function(fromData) {
    data = fromData;
    tree = treeBuild(treeOps.reconstruct(data.game.treeParts));
    var initialPath = treePath.fromNodeList(treeOps.mainlineNodeList(tree.root));
    vm.mode = 'play'; // play | try | view
    vm.loading = false;
    vm.round = null;
    vm.voted = null;
    vm.justPlayed = null;
    vm.resultSent = false;
    vm.lastFeedback = 'init';
    vm.initialPath = initialPath;
    vm.initialNode = tree.nodeAtPath(initialPath);

    setPath(treePath.init(initialPath));
    setTimeout(function() {
      jump(initialPath);
      m.redraw();
    }, 500);

    vm.canViewSolution = false; // just to delay button display
    setTimeout(function() {
      vm.canViewSolution = true;
      m.redraw();
      // }, 5000);
    }, 50);

    socket.setDestsCache(data.game.destsCache);
    moveTest = moveTestBuild(vm, data.puzzle);

    showGround();
    m.redraw();

    if (window.history.pushState)
      window.history.replaceState(null, null, '/training/' + data.puzzle.id);
  };

  var showGround = function() {
    var node = vm.node;
    var color = node.ply % 2 === 0 ? 'white' : 'black';
    var dests = readDests(node.dests);
    var movable = (vm.mode === 'view' || color === data.puzzle.color) ? {
      color: (dests && Object.keys(dests).length > 0) ? color : null,
      dests: dests || {}
    } : {
      color: null,
      dests: {}
    };
    var config = {
      fen: node.fen,
      orientation: data.puzzle.color,
      turnColor: color,
      movable: movable,
      premovable: {
        enabled: false
      },
      check: node.check,
      lastMove: uciToLastMove(node.uci)
    };
    if (!dests && !node.check) {
      // premove while dests are loading from server
      // can't use when in check because it highlights the wrong king
      config.turnColor = opposite(color);
      config.movable.color = color;
      config.premovable.enabled = true;
    } else if (vm.mode !== 'view' && color !== data.puzzle.color) { //  && !node.check) {
      config.turnColor = color;
      config.movable.color = data.puzzle.color;
      config.premovable.enabled = true;
    }
    if (!ground) ground = groundBuild(data, config, userMove);
    ground.set(config);
    if (!dests) getDests();
  };

  var userMove = function(orig, dest, capture) {
    vm.justPlayed = orig;
    sound[capture ? 'capture' : 'move']();
    sendMove(orig, dest);
  };

  var sendMove = function(orig, dest, prom) {
    var move = {
      orig: orig,
      dest: dest,
      fen: vm.node.fen,
      path: vm.path
    };
    if (prom) move.promotion = prom;
    socket.sendAnaMove(move);
  };

  // var preparePremoving = function() {
  //   ground.set({
  //     turnColor: ground.data.movable.color,
  //     movable: {
  //       color: opposite(ground.data.movable.color)
  //     }
  //   });
  // };

  var getDests = throttle(800, false, function() {
    if (!vm.node.dests && treePath.contains(vm.path, vm.initialPath))
      socket.sendAnaDests({
        fen: vm.node.fen,
        path: vm.path
      });
  });

  var uciToLastMove = function(uci) {
    if (!uci) return;
    return [uci.substr(0, 2), uci.substr(2, 2)]; // assuming standard chess
  };

  var addNode = function(node, path) {
    var newPath = tree.addNode(node, path);
    jump(newPath);
    reorderChildren(path);
    m.redraw();
    ground.playPremove();

    var progress = moveTest();
    if (progress) applyProgress(progress);
    m.redraw();
  };

  var reorderChildren = function(path, recursive) {
    var node = tree.nodeAtPath(path);
    node.children.sort(function(c1, c2) {
      if (c1.puzzle === 'fail') return 1;
      if (c1.puzzle === 'retry') return 1;
      if (c1.puzzle === 'good') return -1;
      return 0;
    });
    if (recursive) node.children.forEach(function(child) {
      reorderChildren(path + child.id, true);
    });
  };

  var revertUserMove = function() {
    setTimeout(function() {
      ground.cancelPremove();
      userJump(treePath.init(vm.path));
      m.redraw();
    }, 500);
  };

  var applyProgress = function(progress) {
    if (progress === 'fail') {
      vm.lastFeedback = 'fail';
      revertUserMove();
      if (vm.mode === 'play') {
        vm.canViewSolution = true;
        vm.mode = 'try';
        sendResult(false);
      }
    } else if (progress === 'retry') {
      vm.lastFeedback = 'retry';
      revertUserMove();
    } else if (progress === 'win') {
      if (vm.mode !== 'view') {
        vm.lastFeedback = 'win';
        vm.mode = 'view';
        showGround(); // to disable premoves
        if (vm.mode === 'play') sendResult(true);
      }
    } else if (progress && progress.orig) {
      vm.lastFeedback = 'good';
      setTimeout(function() {
        socket.sendAnaMove(progress);
      }, 500);
    }
  };

  var sendResult = function(win) {
    if (vm.resultSent) return;
    vm.resultSent = true;
    vm.loading = true;
    xhr.round(data.puzzle.id, win).then(function(res) {
      data.user = res.user;
      vm.round = res.round;
      vm.voted = res.voted;
      vm.loading = false;
      m.redraw();
    });
  };

  var nextPuzzle = function() {
    vm.loading = true;
    xhr.nextPuzzle().then(function(d) {
      // pushState(cfg);
      vm.round = null;
      vm.loading = false;
      initiate(d);
    });
  };

  var addDests = function(dests, path, opening) {
    tree.addDests(dests, path, opening);
    if (path === vm.path) {
      showGround();
      m.redraw();
      if (gameOver()) ceval.stop();
    }
    ground.playPremove();
  };

  var instanciateCeval = function(failsafe) {
    ceval = cevalCtrl({
      variant: data.game.variant,
      possible: true,
      emit: function(res) {
        tree.updateAt(res.work.path, function(node) {
          if (node.ceval && node.ceval.depth >= res.eval.depth) return;
          node.ceval = res.eval;
          // if (res.work.path === vm.path) {
          //   setAutoShapes();
          //   m.redraw();
          // }
        });
      },
      setAutoShapes: $.noop,
      failsafe: failsafe,
      onCrash: function(e) {
        console.log('Local eval failed!', e);
        if (ceval.pnaclSupported) {
          console.log('Retrying in failsafe mode');
          instanciateCeval(true);
          startCeval();
        }
      }
    });
  };
  // instanciateCeval();

  var gameOver = function() {
    if (vm.node.dests !== '') return false;
    if (vm.node.check) {
      var san = vm.node.san;
      var checkmate = san && san[san.length - 1] === '#';
      return checkmate;
    }
    return true;
  };

  var jump = function(path) {
    var pathChanged = path !== vm.path;
    setPath(path);
    showGround();
    if (pathChanged) {
      if (!vm.node.uci) sound.move(); // initial position
      else if (vm.node.uci.indexOf(vm.justPlayed) !== 0) {
        if (vm.node.san.indexOf('x') !== -1) sound.capture();
        else sound.move();
      }
      if (/\+|\#/.test(vm.node.san)) sound.check();
      // this.vm.threatMode = false;
      // this.ceval.stop();
      // this.startCeval();
    }
    vm.justPlayed = null;
    vm.autoScrollRequested = true;
  };

  var userJump = function(path) {
    ground.selectSquare(null);
    jump(path);
  };

  var viewSolution = function() {
    sendResult(false);
    vm.mode = 'view';
    mergeSolution(tree, vm.initialNode, data.puzzle.branch, data.puzzle.color);
    reorderChildren(vm.initialPath, true);

    // try and play the solution next move
    var next = vm.node.children[0];
    if (next) userJump(vm.path + next.id);

    vm.autoScrollRequested = true;
    m.redraw();
  };

  var socket = socketBuild({
    send: opts.socketSend,
    addNode: addNode,
    addDests: addDests,
    reset: function() {
      showGround();
      m.redraw();
    }
  });

  var recentHash = function() {
    return data.user ? data.user.recent.reduce(function(h, r) {
      return h + r[0];
    }, '') : '';
  };

  var hasEverVoted = lichess.storage.make('puzzle-ever-voted');

  var vote = function(v) {
    hasEverVoted.set(1);
    vm.voted = v;
    xhr.vote(data.puzzle.id, v).then(function(res) {
      data.puzzle.vote = res[1];
      m.redraw();
    });
  };

  initiate(opts.data);

  keyboard.bind({
    vm: vm,
    userJump: userJump
  });

  return {
    vm: vm,
    getData: function() {
      return data;
    },
    getTree: function() {
      return tree;
    },
    ground: ground,
    userJump: userJump,
    viewSolution: viewSolution,
    nextPuzzle: nextPuzzle,
    recentHash: recentHash,
    hasEverVoted: hasEverVoted,
    vote: vote,
    trans: lichess.trans(opts.i18n),
    socketReceive: socket.receive
  };
}
<commit_msg>better jump to puzzle solution<commit_after>var m = require('mithril');
var treeBuild = require('tree').build;
var treeOps = require('tree').ops;
var treePath = require('tree').path;
var cevalCtrl = require('ceval').ctrl;
var readDests = require('chess').readDests;
var k = Mousetrap;
var chessground = require('chessground');
var keyboard = require('./keyboard');
var opposite = chessground.util.opposite;
var groundBuild = require('./ground');
var socketBuild = require('./socket');
var moveTestBuild = require('./moveTest');
var mergeSolution = require('./solution');
var throttle = require('common').throttle;
var xhr = require('./xhr');
var sound = require('./sound');

module.exports = function(opts, i18n) {

  var vm = {};
  var data, tree, ground, ceval, moveTest;

  var setPath = function(path) {
    vm.path = path;
    vm.nodeList = tree.getNodeList(path);
    vm.node = treeOps.last(vm.nodeList);
    vm.mainline = treeOps.mainlineNodeList(tree.root);
  };

  var initiate = function(fromData) {
    data = fromData;
    tree = treeBuild(treeOps.reconstruct(data.game.treeParts));
    var initialPath = treePath.fromNodeList(treeOps.mainlineNodeList(tree.root));
    vm.mode = 'play'; // play | try | view
    vm.loading = false;
    vm.round = null;
    vm.voted = null;
    vm.justPlayed = null;
    vm.resultSent = false;
    vm.lastFeedback = 'init';
    vm.initialPath = initialPath;
    vm.initialNode = tree.nodeAtPath(initialPath);

    setPath(treePath.init(initialPath));
    setTimeout(function() {
      jump(initialPath);
      m.redraw();
    }, 500);

    vm.canViewSolution = false; // just to delay button display
    setTimeout(function() {
      vm.canViewSolution = true;
      m.redraw();
      // }, 5000);
    }, 50);

    socket.setDestsCache(data.game.destsCache);
    moveTest = moveTestBuild(vm, data.puzzle);

    showGround();
    m.redraw();

    if (window.history.pushState)
      window.history.replaceState(null, null, '/training/' + data.puzzle.id);
  };

  var showGround = function() {
    var node = vm.node;
    var color = node.ply % 2 === 0 ? 'white' : 'black';
    var dests = readDests(node.dests);
    var movable = (vm.mode === 'view' || color === data.puzzle.color) ? {
      color: (dests && Object.keys(dests).length > 0) ? color : null,
      dests: dests || {}
    } : {
      color: null,
      dests: {}
    };
    var config = {
      fen: node.fen,
      orientation: data.puzzle.color,
      turnColor: color,
      movable: movable,
      premovable: {
        enabled: false
      },
      check: node.check,
      lastMove: uciToLastMove(node.uci)
    };
    if (!dests && !node.check) {
      // premove while dests are loading from server
      // can't use when in check because it highlights the wrong king
      config.turnColor = opposite(color);
      config.movable.color = color;
      config.premovable.enabled = true;
    } else if (vm.mode !== 'view' && color !== data.puzzle.color) { //  && !node.check) {
      config.turnColor = color;
      config.movable.color = data.puzzle.color;
      config.premovable.enabled = true;
    }
    if (!ground) ground = groundBuild(data, config, userMove);
    ground.set(config);
    if (!dests) getDests();
  };

  var userMove = function(orig, dest, capture) {
    vm.justPlayed = orig;
    sound[capture ? 'capture' : 'move']();
    sendMove(orig, dest);
  };

  var sendMove = function(orig, dest, prom) {
    var move = {
      orig: orig,
      dest: dest,
      fen: vm.node.fen,
      path: vm.path
    };
    if (prom) move.promotion = prom;
    socket.sendAnaMove(move);
  };

  // var preparePremoving = function() {
  //   ground.set({
  //     turnColor: ground.data.movable.color,
  //     movable: {
  //       color: opposite(ground.data.movable.color)
  //     }
  //   });
  // };

  var getDests = throttle(800, false, function() {
    if (!vm.node.dests && treePath.contains(vm.path, vm.initialPath))
      socket.sendAnaDests({
        fen: vm.node.fen,
        path: vm.path
      });
  });

  var uciToLastMove = function(uci) {
    if (!uci) return;
    return [uci.substr(0, 2), uci.substr(2, 2)]; // assuming standard chess
  };

  var addNode = function(node, path) {
    var newPath = tree.addNode(node, path);
    jump(newPath);
    reorderChildren(path);
    m.redraw();
    ground.playPremove();

    var progress = moveTest();
    if (progress) applyProgress(progress);
    m.redraw();
  };

  var reorderChildren = function(path, recursive) {
    var node = tree.nodeAtPath(path);
    node.children.sort(function(c1, c2) {
      if (c1.puzzle === 'fail') return 1;
      if (c1.puzzle === 'retry') return 1;
      if (c1.puzzle === 'good') return -1;
      return 0;
    });
    if (recursive) node.children.forEach(function(child) {
      reorderChildren(path + child.id, true);
    });
  };

  var revertUserMove = function() {
    setTimeout(function() {
      ground.cancelPremove();
      userJump(treePath.init(vm.path));
      m.redraw();
    }, 500);
  };

  var applyProgress = function(progress) {
    if (progress === 'fail') {
      vm.lastFeedback = 'fail';
      revertUserMove();
      if (vm.mode === 'play') {
        vm.canViewSolution = true;
        vm.mode = 'try';
        sendResult(false);
      }
    } else if (progress === 'retry') {
      vm.lastFeedback = 'retry';
      revertUserMove();
    } else if (progress === 'win') {
      if (vm.mode !== 'view') {
        vm.lastFeedback = 'win';
        vm.mode = 'view';
        showGround(); // to disable premoves
        if (vm.mode === 'play') sendResult(true);
      }
    } else if (progress && progress.orig) {
      vm.lastFeedback = 'good';
      setTimeout(function() {
        socket.sendAnaMove(progress);
      }, 500);
    }
  };

  var sendResult = function(win) {
    if (vm.resultSent) return;
    vm.resultSent = true;
    vm.loading = true;
    xhr.round(data.puzzle.id, win).then(function(res) {
      data.user = res.user;
      vm.round = res.round;
      vm.voted = res.voted;
      vm.loading = false;
      m.redraw();
    });
  };

  var nextPuzzle = function() {
    vm.loading = true;
    xhr.nextPuzzle().then(function(d) {
      // pushState(cfg);
      vm.round = null;
      vm.loading = false;
      initiate(d);
    });
  };

  var addDests = function(dests, path, opening) {
    tree.addDests(dests, path, opening);
    if (path === vm.path) {
      showGround();
      m.redraw();
      if (gameOver()) ceval.stop();
    }
    ground.playPremove();
  };

  var instanciateCeval = function(failsafe) {
    ceval = cevalCtrl({
      variant: data.game.variant,
      possible: true,
      emit: function(res) {
        tree.updateAt(res.work.path, function(node) {
          if (node.ceval && node.ceval.depth >= res.eval.depth) return;
          node.ceval = res.eval;
          // if (res.work.path === vm.path) {
          //   setAutoShapes();
          //   m.redraw();
          // }
        });
      },
      setAutoShapes: $.noop,
      failsafe: failsafe,
      onCrash: function(e) {
        console.log('Local eval failed!', e);
        if (ceval.pnaclSupported) {
          console.log('Retrying in failsafe mode');
          instanciateCeval(true);
          startCeval();
        }
      }
    });
  };
  // instanciateCeval();

  var gameOver = function() {
    if (vm.node.dests !== '') return false;
    if (vm.node.check) {
      var san = vm.node.san;
      var checkmate = san && san[san.length - 1] === '#';
      return checkmate;
    }
    return true;
  };

  var jump = function(path) {
    var pathChanged = path !== vm.path;
    setPath(path);
    showGround();
    if (pathChanged) {
      if (!vm.node.uci) sound.move(); // initial position
      else if (vm.node.uci.indexOf(vm.justPlayed) !== 0) {
        if (vm.node.san.indexOf('x') !== -1) sound.capture();
        else sound.move();
      }
      if (/\+|\#/.test(vm.node.san)) sound.check();
      // this.vm.threatMode = false;
      // this.ceval.stop();
      // this.startCeval();
    }
    vm.justPlayed = null;
    vm.autoScrollRequested = true;
  };

  var userJump = function(path) {
    ground.selectSquare(null);
    jump(path);
  };

  var viewSolution = function() {
    sendResult(false);
    vm.mode = 'view';
    mergeSolution(tree, vm.initialNode, data.puzzle.branch, data.puzzle.color);
    reorderChildren(vm.initialPath, true);

    // try and play the solution next move
    var next = vm.node.children[0];
    if (next && next.puzzle === 'good') userJump(vm.path + next.id);
    else {
      var firstGoodPath = treeOps.takePathWhile(vm.mainline, function(node) {
        return node.puzzle !== 'good';
      });
      if (firstGoodPath) userJump(firstGoodPath + tree.nodeAtPath(firstGoodPath).children[0].id);
    }

    vm.autoScrollRequested = true;
    m.redraw();
  };

  var socket = socketBuild({
    send: opts.socketSend,
    addNode: addNode,
    addDests: addDests,
    reset: function() {
      showGround();
      m.redraw();
    }
  });

  var recentHash = function() {
    return data.user ? data.user.recent.reduce(function(h, r) {
      return h + r[0];
    }, '') : '';
  };

  var hasEverVoted = lichess.storage.make('puzzle-ever-voted');

  var vote = function(v) {
    hasEverVoted.set(1);
    vm.voted = v;
    xhr.vote(data.puzzle.id, v).then(function(res) {
      data.puzzle.vote = res[1];
      m.redraw();
    });
  };

  initiate(opts.data);

  keyboard.bind({
    vm: vm,
    userJump: userJump
  });

  return {
    vm: vm,
    getData: function() {
      return data;
    },
    getTree: function() {
      return tree;
    },
    ground: ground,
    userJump: userJump,
    viewSolution: viewSolution,
    nextPuzzle: nextPuzzle,
    recentHash: recentHash,
    hasEverVoted: hasEverVoted,
    vote: vote,
    trans: lichess.trans(opts.i18n),
    socketReceive: socket.receive
  };
}
<|endoftext|>


<commit_before>(function(root) {

var _,
    errors,
    assert,
    Chronicle,
    ChronicleTest,
    registerTest;

if (typeof exports !== 'undefined') {
  _    = require('underscore');
  errors   = require('substance-util/errors');
  assert   = require('substance-test/assert');
  Chronicle = require('..');
  ChronicleTest = require('./chronicle_test');
  var test = require('substance-test');
  registerTest = test.registerTest;
} else {
  _ = root._;
  errors = root.Substance.errors;
  assert = root.Substance.assert;
  Chronicle = root.Substance.Chronicle;
  ChronicleTest = root.Substance.Chronicle.AbstractTest;
  registerTest = root.Substance.registerTest;
}

var ROOT = Chronicle.ROOT;

// Index structure:
//
// ROOT - 01 - 02 - 03 - 04
//    |              |
//    |                - 05 - 06
//      - 07 - 08

var C1 = new Chronicle.Change("01", ROOT, "bla");
var OP = function(op, val, id, parent) {
  var data = { op: op, val: val };
  return new Chronicle.Change(id, parent, data);
};
var PLUS = function(id, parents) {
  return OP("plus", 1, id, parents);
};

var Basics = function() {
  ChronicleTest.call(this);

  // deactivate the default fixture
  // for testing basic behavior
  this.default_fixture = this.fixture;

  this.fixture = function() {};

  this.actions = [

    "Index: add", function() {
      this.index.add(C1);
      assert.isTrue(this.index.contains(C1.id));

      // should reject
      assert.exception(errors.ChronicleError, function() {
        this.index.add(PLUS("bla", "doesnotexist"));
      }, this);
    },

    "Index: get", function() {
      var c = this.index.get(C1.id);
      assert.isEqual(C1.id, c.id);
      assert.isEqual(C1.data, c.data);
    },

    "Index: list", function() {
      assert.isArrayEqual([ROOT, C1.id], this.index.list());
    },

    "Every Ref should reference an existing change", function() {
      assert.exception(errors.ChronicleError, function() {
        this.index.setRef("FAIL", "balla");
      }, this);
    },

    "Record", function() {
      var id = this.next_uuid();
      this.chronicle.record({op: "plus", val: 1});
      assert.isTrue(this.index.contains(id));
    },

    "Should not record failing changes", function() {
      var id = this.next_uuid();
      assert.exception(function() {
        // this fails due to division by 0
        this.chronicle.record({op: "div", val: 0});
      }, this);
      assert.isFalse(this.index.contains(id));
    },

    "Import", function() {
      var other = Chronicle.Index.create();
      other.add(PLUS("1", ROOT));
      other.add(PLUS("2", "1"));
      other.add(PLUS("3", "2"));
      other.add(PLUS("4", ROOT));
      this.chronicle.import(other);

      for (var idx=1; idx<5; idx++) {
        assert.isTrue(this.index.contains(""+idx));
      }
    },

    "Reject Imports with Failing changes", function() {
      this.setup();

      var other = Chronicle.Index.create();
      other.add(PLUS("1", ROOT));
      other.add(OP("div", 0, "2", "1"));
      other.add(PLUS("3", "2"));

      assert.exception(errors.ChronicleError, function() {
        this.chronicle.import(other);
      }, this);
    },

    "Load default fixture", function() {
      this.setup();
      this.default_fixture();
    },

    "Reset", function() {
      this.comp.reset();

      var seq = ["07", "05", "04", ROOT, "06", "08", "03", "01", "02"];
      _.each(seq, function(id) {
        this.chronicle.open(id);
        assert.isEqual(this.RESULTS[id], this.comp.result);
        assert.isEqual(id, this.comp.getState());
      }, this);
    },

    "Transition: simple forward", function() {

      this.chronicle.open(ROOT);
      this.chronicle.step("01", "02", "03");
      assert.isEqual("03", this.comp.getState());
      assert.isEqual(this.RESULTS["03"], this.comp.result);

    },

    "Transition: simple revert", function() {

      this.chronicle.open("02");
      this.chronicle.step("01");
      assert.isEqual("01", this.comp.getState());
      assert.isEqual(this.RESULTS["01"], this.comp.result);

    },

    "Transition: revert and apply", function() {

      this.chronicle.open("04");
      this.chronicle.step("03", "05");
      assert.isEqual("05", this.comp.getState());
      assert.isEqual(this.RESULTS["05"], this.comp.result);

    },

    "Transition: across ROOT", function() {

      this.chronicle.open("01");
      this.chronicle.step(ROOT, "07");
      assert.isEqual("07", this.comp.getState());
      assert.isEqual(this.RESULTS["07"], this.comp.result);

    },

    // TODO: add some smoke tests to check robustness against wrong usage

  ];
};

Basics.prototype = ChronicleTest.prototype;

registerTest(['Chronicle', 'Basics'], new Basics());

})(this);
<commit_msg>Nothing real.<commit_after>(function(root) { "use strict";

var _,
    errors,
    assert,
    Chronicle,
    ChronicleTest,
    registerTest;

if (typeof exports !== 'undefined') {
  _    = require('underscore');
  errors   = require('substance-util/errors');
  assert   = require('substance-test/assert');
  Chronicle = require('..');
  ChronicleTest = require('./chronicle_test');
  var test = require('substance-test');
  registerTest = test.registerTest;
} else {
  _ = root._;
  errors = root.Substance.errors;
  assert = root.Substance.assert;
  Chronicle = root.Substance.Chronicle;
  ChronicleTest = root.Substance.Chronicle.AbstractTest;
  registerTest = root.Substance.registerTest;
}

var ROOT = Chronicle.ROOT;

// Index structure:
//
// ROOT - 01 - 02 - 03 - 04
//    |              |
//    |                - 05 - 06
//      - 07 - 08

var C1 = new Chronicle.Change("01", ROOT, "bla");
var OP = function(op, val, id, parent) {
  var data = { op: op, val: val };
  return new Chronicle.Change(id, parent, data);
};
var PLUS = function(id, parents) {
  return OP("plus", 1, id, parents);
};

var Basics = function() {
  ChronicleTest.call(this);

  // deactivate the default fixture
  // for testing basic behavior
  this.default_fixture = this.fixture;

  this.fixture = function() {};

  this.actions = [

    "Index: add", function() {
      this.index.add(C1);
      assert.isTrue(this.index.contains(C1.id));

      // should reject
      assert.exception(errors.ChronicleError, function() {
        this.index.add(PLUS("bla", "doesnotexist"));
      }, this);
    },

    "Index: get", function() {
      var c = this.index.get(C1.id);
      assert.isEqual(C1.id, c.id);
      assert.isEqual(C1.data, c.data);
    },

    "Index: list", function() {
      assert.isArrayEqual([ROOT, C1.id], this.index.list());
    },

    "Every Ref should reference an existing change", function() {
      assert.exception(errors.ChronicleError, function() {
        this.index.setRef("FAIL", "balla");
      }, this);
    },

    "Record", function() {
      var id = this.next_uuid();
      this.chronicle.record({op: "plus", val: 1});
      assert.isTrue(this.index.contains(id));
    },

    "Should not record failing changes", function() {
      var id = this.next_uuid();
      assert.exception(function() {
        // this fails due to division by 0
        this.chronicle.record({op: "div", val: 0});
      }, this);
      assert.isFalse(this.index.contains(id));
    },

    "Import", function() {
      var other = Chronicle.Index.create();
      other.add(PLUS("1", ROOT));
      other.add(PLUS("2", "1"));
      other.add(PLUS("3", "2"));
      other.add(PLUS("4", ROOT));
      this.chronicle.import(other);

      for (var idx=1; idx<5; idx++) {
        assert.isTrue(this.index.contains(""+idx));
      }
    },

    "Reject Imports with Failing changes", function() {
      this.setup();

      var other = Chronicle.Index.create();
      other.add(PLUS("1", ROOT));
      other.add(OP("div", 0, "2", "1"));
      other.add(PLUS("3", "2"));

      assert.exception(errors.ChronicleError, function() {
        this.chronicle.import(other);
      }, this);
    },

    "Load default fixture", function() {
      this.setup();
      this.default_fixture();
    },

    "Reset", function() {
      this.comp.reset();

      var seq = ["07", "05", "04", ROOT, "06", "08", "03", "01", "02"];
      _.each(seq, function(id) {
        this.chronicle.open(id);
        assert.isEqual(this.RESULTS[id], this.comp.result);
        assert.isEqual(id, this.comp.getState());
      }, this);
    },

    "Transition: simple forward", function() {

      this.chronicle.open(ROOT);
      this.chronicle.step("01", "02", "03");
      assert.isEqual("03", this.comp.getState());
      assert.isEqual(this.RESULTS["03"], this.comp.result);

    },

    "Transition: simple revert", function() {

      this.chronicle.open("02");
      this.chronicle.step("01");
      assert.isEqual("01", this.comp.getState());
      assert.isEqual(this.RESULTS["01"], this.comp.result);

    },

    "Transition: revert and apply", function() {

      this.chronicle.open("04");
      this.chronicle.step("03", "05");
      assert.isEqual("05", this.comp.getState());
      assert.isEqual(this.RESULTS["05"], this.comp.result);

    },

    "Transition: across ROOT", function() {

      this.chronicle.open("01");
      this.chronicle.step(ROOT, "07");
      assert.isEqual("07", this.comp.getState());
      assert.isEqual(this.RESULTS["07"], this.comp.result);

    },

    // TODO: add some smoke tests to check robustness against wrong usage

  ];
};

Basics.prototype = ChronicleTest.prototype;

registerTest(['Chronicle', 'Basics'], new Basics());

})(this);
<|endoftext|>


<commit_before>// # can/map/map_hepers
// Helper functions that are primarily used to serialize
// a map, or track the maps created from plain JavaScript objects.
// `can.Map` handles cycles in objects nicely!

steal('can/util', 'util/object/isplain', function(can){

	var mapHelpers = {
		// ### mapHelpers.attrParts
		// Parses attribute name into its parts.
		attrParts: function (attr, keepKey) {
			//Keep key intact
			if (keepKey ) {
				return [attr];
			}
			// Split key on '.'
			return typeof attr === "object" ? attr : ("" + attr)
				.split(".");
		},

		// ### can.mapHelpers.canMakeObserve
		// Determines if an object can be made into an observable.
		canMakeObserve: function (obj) {
			return obj && !can.isDeferred(obj) && (can.isArray(obj) || can.isPlainObject(obj) );
		},

		// ### mapHelpers.serialize
		// Serializes a Map or Map.List by recursively calling the `how`
		// method on any child objects. This is able to handle
		// cycles.
		// `map` - the map or list to serialize.
		// `how` - the method to call recursively.
		// `where` - the target Object or Array that becomes the serialized result.
		serialize: (function(){

			// A temporary mapping of map cids to the serialized result.
			var serializeMap = null;

			return function (map, how, where) {
				var cid = can.cid(map),
					firstSerialize = false;

				// If there isn't an existing serializeMap, this means
				// this is the initial non-recursive call to this function.
				// We mark this  as the first call, and then setup the serializeMap.
				// The serialize map is further devided into `how` because
				// `.serialize` might call `.attr`.
				if(!serializeMap) {
					firstSerialize = true;
					serializeMap = {
						attr: {},
						serialize: {}
					};
				}

				serializeMap[how][cid] = where;
				// Go through each property.
				map.each(function (val, name) {
					// If the value is an `object`, and has an `attr` or `serialize` function.
					var result,
						isObservable =  can.isMapLike(val),
						serialized = isObservable && serializeMap[how][can.cid(val)];

					if( serialized ) {
						result = serialized;
					} else {
						// special attr or serializer
						if(map["___"+how]) {
							result =  map["___"+how](name, val);
						} else {
							result = mapHelpers.getValue(map, name, val, how);
						}
					}
					// this is probably removable
					if(result !== undefined){
						where[name] = result;
					}
				});

				if(firstSerialize) {
					serializeMap = null;
				}
				return where;
			};
		})(),

		// ## getValue
		// If `val` is an observable, calls `how` on it; otherwise
		// returns the value of `val`.
		getValue: function(map, name, val, how){
			if( can.isMapLike(val) ) {
				return val[how]();
			} else {
				return val;
			}
		},

		// ## define
		// A hook to call whenever a Map is defined.
		// We need a better place for this.
		define: null,

		// ## addComputedAttr
		// Adds a compute so it will control the behavior of an
		// attribute.  Each computedAttrs object has:
		// - `compute` - the compute that will be read and updated.
		// - `count` - the number of bindings to this individual property.
		//   This is used to know when to bind `handler` to the compute.
		// - `handler` - a function that when bound to `compute` forwards all
		//   events to `map`.
		addComputedAttr: function(map, attrName, compute){
			map._computedAttrs[attrName] = {
				compute: compute,
				count: 0,
				handler: function (ev, newVal, oldVal) {
					can.batch.trigger(map, {
						type: attrName,
						batchNum: ev.batchNum,
						target: map
					}, [newVal, oldVal]);
				}
			};
		},

		// ### can.mapHelpers.addToMap
		// Tracks map instances created from JS objects.
		// This should be called whenever an instance is created for a particular object.
		// This may return a `teardown` function that should be called after all instances
		// might be created.
		//
		// While creating map instances from plain ole JS objects (POJOs), it's
		// possible that the same JS object exists as two different properties and
		// we want only one map instance created for one JS object.
		//
		// ```
		// var obj = {name: "I am everywhere"}
		// var map = new can.Map({obj1: obj, obj2: obj});
		// ok( map.attr("obj1") === map.attr("obj2") )
		// ```
		//
		// This works by temporarily adding a `cid` to any found POJO object
		// and storing it in a temporary Object that maps those `cid`s to
		// the POJO and the instance created for it.
		// The `teardown` function removes those temporary `cid`s and
		// clears the map for memory safety.
		addToMap: function addToMap(obj, instance) {
			var teardown;

			// Setup a fresh mapping if `madeMap` is missing.
			if (!madeMap) {
				teardown = teardownMap;
				madeMap = {};
			}

			// Record if Object has a `_cid` before adding one.
			var hasCid = obj._cid;
			var cid = can.cid(obj);

			// Only update if there already isn't one already.
			if (!madeMap[cid]) {

				madeMap[cid] = {
					obj: obj,
					instance: instance,
					added: !hasCid
				};
			}
			return teardown;
		},

		// ### getMapFromObject
		// Returns the map instance already created for this object `obj` or
		// `undefined` if nothing has been already created.
		getMapFromObject: function (obj) {
			return madeMap && madeMap[obj._cid] && madeMap[obj._cid].instance;
		}
	};

	// ## POJOs to Map instance helpers

	// ### madeMap
	// A temporary map of Maps that have been made from plain JS objects.
	// `{POJO_CID: {obj: POJO, instance: MAP, added: Boolean}}`
	var madeMap = null;

	// ### teardownMap
	// Clears out map of converted objects and removes temporary `cids`.
	var teardownMap = function () {
		for (var cid in madeMap) {
			if (madeMap[cid].added) {
				delete madeMap[cid].obj._cid;
			}
		}
		madeMap = null;
	};

	return mapHelpers;
});

<commit_msg>maybe I'll get it right one of these commits<commit_after>// # can/map/map_hepers
// Helper functions that are primarily used to serialize
// a map, or track the maps created from plain JavaScript objects.
// `can.Map` handles cycles in objects nicely!

steal('can/util', 'can/util/object/isplain', function(can){

	var mapHelpers = {
		// ### mapHelpers.attrParts
		// Parses attribute name into its parts.
		attrParts: function (attr, keepKey) {
			//Keep key intact
			if (keepKey ) {
				return [attr];
			}
			// Split key on '.'
			return typeof attr === "object" ? attr : ("" + attr)
				.split(".");
		},

		// ### can.mapHelpers.canMakeObserve
		// Determines if an object can be made into an observable.
		canMakeObserve: function (obj) {
			return obj && !can.isDeferred(obj) && (can.isArray(obj) || can.isPlainObject(obj) );
		},

		// ### mapHelpers.serialize
		// Serializes a Map or Map.List by recursively calling the `how`
		// method on any child objects. This is able to handle
		// cycles.
		// `map` - the map or list to serialize.
		// `how` - the method to call recursively.
		// `where` - the target Object or Array that becomes the serialized result.
		serialize: (function(){

			// A temporary mapping of map cids to the serialized result.
			var serializeMap = null;

			return function (map, how, where) {
				var cid = can.cid(map),
					firstSerialize = false;

				// If there isn't an existing serializeMap, this means
				// this is the initial non-recursive call to this function.
				// We mark this  as the first call, and then setup the serializeMap.
				// The serialize map is further devided into `how` because
				// `.serialize` might call `.attr`.
				if(!serializeMap) {
					firstSerialize = true;
					serializeMap = {
						attr: {},
						serialize: {}
					};
				}

				serializeMap[how][cid] = where;
				// Go through each property.
				map.each(function (val, name) {
					// If the value is an `object`, and has an `attr` or `serialize` function.
					var result,
						isObservable =  can.isMapLike(val),
						serialized = isObservable && serializeMap[how][can.cid(val)];

					if( serialized ) {
						result = serialized;
					} else {
						// special attr or serializer
						if(map["___"+how]) {
							result =  map["___"+how](name, val);
						} else {
							result = mapHelpers.getValue(map, name, val, how);
						}
					}
					// this is probably removable
					if(result !== undefined){
						where[name] = result;
					}
				});

				if(firstSerialize) {
					serializeMap = null;
				}
				return where;
			};
		})(),

		// ## getValue
		// If `val` is an observable, calls `how` on it; otherwise
		// returns the value of `val`.
		getValue: function(map, name, val, how){
			if( can.isMapLike(val) ) {
				return val[how]();
			} else {
				return val;
			}
		},

		// ## define
		// A hook to call whenever a Map is defined.
		// We need a better place for this.
		define: null,

		// ## addComputedAttr
		// Adds a compute so it will control the behavior of an
		// attribute.  Each computedAttrs object has:
		// - `compute` - the compute that will be read and updated.
		// - `count` - the number of bindings to this individual property.
		//   This is used to know when to bind `handler` to the compute.
		// - `handler` - a function that when bound to `compute` forwards all
		//   events to `map`.
		addComputedAttr: function(map, attrName, compute){
			map._computedAttrs[attrName] = {
				compute: compute,
				count: 0,
				handler: function (ev, newVal, oldVal) {
					can.batch.trigger(map, {
						type: attrName,
						batchNum: ev.batchNum,
						target: map
					}, [newVal, oldVal]);
				}
			};
		},

		// ### can.mapHelpers.addToMap
		// Tracks map instances created from JS objects.
		// This should be called whenever an instance is created for a particular object.
		// This may return a `teardown` function that should be called after all instances
		// might be created.
		//
		// While creating map instances from plain ole JS objects (POJOs), it's
		// possible that the same JS object exists as two different properties and
		// we want only one map instance created for one JS object.
		//
		// ```
		// var obj = {name: "I am everywhere"}
		// var map = new can.Map({obj1: obj, obj2: obj});
		// ok( map.attr("obj1") === map.attr("obj2") )
		// ```
		//
		// This works by temporarily adding a `cid` to any found POJO object
		// and storing it in a temporary Object that maps those `cid`s to
		// the POJO and the instance created for it.
		// The `teardown` function removes those temporary `cid`s and
		// clears the map for memory safety.
		addToMap: function addToMap(obj, instance) {
			var teardown;

			// Setup a fresh mapping if `madeMap` is missing.
			if (!madeMap) {
				teardown = teardownMap;
				madeMap = {};
			}

			// Record if Object has a `_cid` before adding one.
			var hasCid = obj._cid;
			var cid = can.cid(obj);

			// Only update if there already isn't one already.
			if (!madeMap[cid]) {

				madeMap[cid] = {
					obj: obj,
					instance: instance,
					added: !hasCid
				};
			}
			return teardown;
		},

		// ### getMapFromObject
		// Returns the map instance already created for this object `obj` or
		// `undefined` if nothing has been already created.
		getMapFromObject: function (obj) {
			return madeMap && madeMap[obj._cid] && madeMap[obj._cid].instance;
		}
	};

	// ## POJOs to Map instance helpers

	// ### madeMap
	// A temporary map of Maps that have been made from plain JS objects.
	// `{POJO_CID: {obj: POJO, instance: MAP, added: Boolean}}`
	var madeMap = null;

	// ### teardownMap
	// Clears out map of converted objects and removes temporary `cids`.
	var teardownMap = function () {
		for (var cid in madeMap) {
			if (madeMap[cid].added) {
				delete madeMap[cid].obj._cid;
			}
		}
		madeMap = null;
	};

	return mapHelpers;
});

<|endoftext|>


<commit_before>/* global angular, console */

angular.module('umc-angular-google-analytics', [])
    .provider('Analytics', function() {
        'use strict';

        var created = false;
        var trackRoutes = true;
        var trackPrefix = '';
        var domainName;
        var filename = 'analytics.js';
        var pageEvent = '$routeChangeSuccess';

        this.trackers = [];
        this._logs = [];

        // config methods
        this.setAccount = function(id) {
            var primary = {
                code: id,
                name: '',
                trackEcommerce: false,
                ecommerceLoaded: false,
                trackDisplayfeatures: false,
                displayfeaturesLoaded: false,
                trackEnhancedEcommerce: false,
                enhancedEcommerceLoaded: false
            };

            // first tracker in array is always the "account" value
            if (this.trackers.length === 0) {
                this.trackers.push(primary);
            } else {
                this.trackers[0] = primary;
            }
            return true;
        };

        this.trackPages = function(doTrack) {
            trackRoutes = doTrack;
            return true;
        };

        this.trackPrefix = function(prefix) {
            trackPrefix = prefix;
            return true;
        };

        this.setDomainName = function(domain) {
            domainName = domain;
            return true;
        };

        this.setFilename = function(name) {
            filename = name;
            return true;
        };

        this.setPageEvent = function(name) {
            pageEvent = name;
            return true;
        };

        this.trackEcommerce = function (doTrack, name) {
            if (name === null || name === '') {
                this.trackers[0].trackEcommerce = doTrack;
                return true;
            }

            for (var i = 1; i < this.trackers.length; i++) {
                if (this.trackers[i].name === name) {
                    this.trackers[i].trackEcommerce = doTrack;
                    return true;
                }
            }

            return true;
        };

        this.trackEnhancedEcommerce = function (doTrack, name) {
            if (name === null || name === '') {
                this.trackers[0].trackEnhancedEcommerce = doTrack;
                return true;
            }

            for (var i = 1; i < this.trackers.length; i++) {
                if (this.trackers[i].name === name) {
                    this.trackers[i].trackEnhancedEcommerce = doTrack;
                    return true;
                }
            }

            return true;
        };

        this.trackDisplayFeatures = function (doTrack, name) {
            if (name === null || name === '') {
                this.trackers[0].trackDisplayfeatures = doTrack;
                return true;
            }

            for (var i = 1; i < this.trackers.length; i++) {
                if (this.trackers[i].name === name) {
                    this.trackers[i].trackDisplayfeatures = doTrack;
                    return true;
                }
            }
            return true;
        };

        this.addTracker = function(code, name) {
            // handle special case of primary tracker
            if (name === null || name === '') {
                this.trackers[0].code = code;
                return;
            }

            for (var i = 1; i < this.trackers.length; i++) {
                if (this.trackers[i].name === name) {
                    this.trackers[i].code = code;
                    return;
                }
            }

            this.trackers.push({
                code: code,
                name: name,
                trackEcommerce: false,
                ecommerceLoaded: false,
                trackDisplayfeatures: false,
                displayfeaturesLoaded: false,
                trackEnhancedEcommerce: false,
                enhancedEcommerceLoaded: false
            });
        };

        this.getTrackers = function() {
            return this.trackers;
        };

        // public service
        this.$get = ['$document', '$rootScope', '$location', '$window', function($document, $rootScope, $location, $window) {
          // private methods
          this._createScriptTag = function() {
            //require a tracking id
            if (this.trackers.length === 0)
                return;

            //initialize the window object __gaTracker
            $window.GoogleAnalyticsObject = '__gaTracker';
            if (angular.isUndefined($window.__gaTracker)) {
                $window.__gaTracker = function() {
                    if (angular.isUndefined($window.__gaTracker.q)) {
                        $window.__gaTracker.q = [];
                    }
                    $window.__gaTracker.q.push(arguments);
                };
                $window.__gaTracker.l=1*new Date();
            }
            var opts = {};
            if (domainName) {
                opts.cookieDomain = domainName;
            }

              // create the primary tracker
              $window.__gaTracker('create', this.trackers[0].code);

              // create secondary trackers if present
              for (var i = 1; i < this.trackers.length; i++) {
                  $window.__gaTracker('create', this.trackers[i].code, {'name': this.trackers[i].name});
              }

              // load ecommerce for each tracker if requested
              // Only enable ecommerce if enhanced ecommerce is disabled per Google documentation.
              if (this.trackers[0].trackEcommerce && !this.trackers[0].ecommerceLoaded && !this.trackers[0].trackEnhancedEcommerce) {
                  this.trackers[0].ecommerceLoaded = true;
                  $window.__gaTracker('require', 'ecommerce', 'ecommerce.js');
                  this._log('loadGA', 'ecommerce');
              }
              for (var e = 1; i < this.trackers.length; e++) {
                  if (this.trackers[e].ecommerceLoaded )
                      continue;
                  this.trackers[e].ecommerceLoaded = true;
                  $window.__gaTracker(this.trackers[e].name + '.require', 'ecommerce', 'ecommerce.js');
                  this._log('loadGA', 'ecommerce ' + this.trackers[e].name);
              }

              // load enhanced ecommerce for each tracker if requested
              // Only enable ecommerce if enhanced ecommerce is disabled per Google documentation.
              if (this.trackers[0].trackEnhancedEcommerce && !this.trackers[0].enhancedEcommerceLoaded) {
                  this.trackers[0].enhancedEcommerceLoaded = true;
                  $window.__gaTracker('require', 'ec', 'ec.js');
                  this._log('loadGA', 'ec');
              }
              for (var ee = 1; i < this.trackers.length; ee++) {
                  if (this.trackers[e].enhancedEcommerceLoaded)
                      continue;
                  this.trackers[ee].enhancedEcommerceLoaded = true;
                  $window.__gaTracker(this.trackers[ee].name + '.require', 'ec', 'ec.js');
                  this._log('loadGA', 'ec ' + this.trackers[ee].name);
              }

              // load display features
              if (this.trackers[0].trackDisplayfeatures && !this.trackers[0].displayfeaturesLoaded) {
                  this.trackers[0].displayfeaturesLoaded = true;
                  $window.__gaTracker('require', 'displayfeatures', 'displayfeatures.js');
                  this._log('loadGA', 'displayfeatures');
              }
              for (var d = 1; i < this.trackers.length; d++) {
                  if (this.trackers[e].displayfeaturesLoaded)
                      continue;
                  this.trackers[d].displayfeaturesLoaded = true;
                  $window.__gaTracker(this.trackers[d].name + '.require', 'displayfeatures', 'displayfeatures.js');
                  this._log('loadGA', 'displayfeatures ' + this.trackers[d].name);
              }

            if (trackRoutes) {
              this._trackPage($location.path(), $rootScope.pageTitle); // TODO: this is too specific to our apps
            }

            // inject the google analytics tag
            (function() {
              var gaTag = $document[0].createElement('script');
              gaTag.type = 'text/javascript';
              gaTag.async = true;
              gaTag.src = '//www.google-analytics.com/' + filename;
              var s = $document[0].getElementsByTagName('script')[0];
              s.parentNode.insertBefore(gaTag, s);
            })();
            created = true;
          };
          // for testing
          this._log = function() {
              this._logs.push(arguments);
          };

          /**
            * Track Pageview
            * https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#hit
            * @param url (optional, if not specified $location.path() will be used, and this gets the trackPrefix prefixed onto it)
            * @param title (optional, if not specified $rootScope.pageTitle will be used if defined, else let GA use the document.title property)
            * @private
            */
          this._trackPage = function(url,title) {
              if (angular.isUndefined($window.__gaTracker)) {
                  return;
              }

              // TODO: we had the require calls here for displayfeatures. do we actually need it here?

              if (angular.isUndefined(url)) {
                 url = $location.path();
              }

              var fullUrl = trackPrefix + url;
              // page should always start with a /
              if (fullUrl !== '' && fullUrl.charAt(0) !== '/') {
                 fullUrl = '/' + fullUrl;
              }

              var opts = {
                'page': fullUrl
              };

              if (angular.isUndefined(title) && angular.isDefined($rootScope.pageTitle)) {
                  title = $rootScope.pageTitle;
              }
              if (angular.isDefined(title) && title !== '') {
                  opts.title = title;
              }

              // primary
              $window.__gaTracker('send','pageview', opts);
              // secondary trackers
              for (var x = 1; x < this.trackers.length; x++) {
                  $window.__gaTracker(this.trackers[x].name + '.send','pageview', opts);
              }
              this._log('pageview', arguments);
          };

          /**
            * Track Event
            * https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#events
            * @param category
            * @param action
            * @param label
            * @param value
            * @private
            */
          this._trackEvent = function(category, action, label, value) {
              if (angular.isUndefined($window.__gaTracker)) {
                  return;
              }

              // these two are required by Google's API
              var evt = {
                 'eventCategory': category,
                 'eventAction': action
              };

              // label is optional and a string
              if (typeof label !== 'undefined') {
                  evt.eventLabel = label;
              }

              // value is optional and numeric
              if (typeof value !== 'undefined') {
                 // if value is not numeric, just default it to zero
                 if (!isNaN(parseFloat(value)) && isFinite(value)) {
                     value = 0;
                  }
                  evt.eventValue = value;
              }

              // primary
              $window.__gaTracker('send','event', evt);

              // secondary trackers
              for (var x = 1; x < this.trackers.length; x++) {
                  $window.__gaTracker(this.trackers[x].name + '.send','event', evt);
              }
              this._log('event', arguments);
          };

          /**
           * Add transaction
           * https://developers.google.com/analytics/devguides/collection/analyticsjs/ecommerce#addTrans
           * @param transactionId
           * @param affiliation
           * @param total
           * @param tax
           * @param shipping
           * @private
           * temporarily removing city, state, country as I don't see these in the newer documentation
           */
          this._addTrans = function (transactionId, affiliation, total, tax, shipping) {
            if (angular.isUndefined($window.__gaTracker)) { return; }

            //TODO: guard in case ecommerce hasn't been loaded, shouldn't really happen

            $window.__gaTracker('ecommerce:addTransaction', {
                'id': transactionId,
                'affiliation': affiliation,
                'revenue': total,
                'shipping': shipping,
                'tax': tax
                //'city': city,
                //'state': state,
                //'country': country
            });
            this._log('ecommerce:addTransaction', arguments);
          };

          /**
           * Add item to transaction
           * https://developers.google.com/analytics/devguides/collection/analyticsjs/ecommerce#addItem
           * @param transactionId
           * @param name
           * @param sku
           * @param category
           * @param price
           * @param quantity
           * @private
           */
          this._addItem = function (transactionId, name, sku, category, price, quantity) {
              if (angular.isUndefined($window.__gaTracker)) { return; }

              $window.__gaTracker('ecommerce:addItem', {
                  'id': transactionId,
                  'name': name,
                  'sku': sku,
                  'category': category,
                  'price': price,
                  'quantity': quantity
              });
              this._log('ecommerce:addItem', arguments);
          };

          /**
           * Track transaction
           * https://developers.google.com/analytics/devguides/collection/analyticsjs/ecommerce#sendingData
           * @private
           */
          this._trackTrans = function () {
              if (angular.isUndefined($window.__gaTracker)) { return; }

              $window.__gaTracker('ecommerce:send');
              this._log('ecommerce:send', arguments);
          };

          /**
           * Clear transaction
           * https://developers.google.com/analytics/devguides/collection/analyticsjs/ecommerce#clearingData
           * @private
           */
          this._clearTrans = function () {
              if (angular.isUndefined($window.__gaTracker)) { return; }

              $window.__gaTracker('ecommerce:clear');
              this._log('ecommerce:clear', arguments);
          };

          this._trackSocial = function(network, action, target) {
              if (angular.isUndefined($window.__gaTracker)) {
                 return;
              }

              var social = {
                 'socialNetwork': network,
                 'socialAction': action,
                 'socialTarget': target
              };

              // primary
              $window.__gaTracker('send','social', social);

              // secondary trackers
              for (var x = 1; x < this.trackers.length; x++) {
                  $window.__gaTracker(this.trackers[x].name + '.send','social', social);
              }

              this._log('trackSocial', arguments);
          };

          // --------- initialization steps -----------------------
          // creates the ganalytics tracker
          this._createScriptTag();

          var me = this;

          // activates page tracking
          if (trackRoutes) {
              $rootScope.$on(pageEvent, function() {
                  me._trackPage($location.path(), $rootScope.pageTitle);
              });
          }
          // --------- end initialization steps -----------------------

          // the rest of the public interface
          return {
                _logs: me._logs,
                trackers: me.trackers,
                trackPage: function(url, title) {
                    // add a page event
                    me._trackPage(url, title);
                },
                trackEvent: function(category, action, label, value) {
                    // add an action event
                    me._trackEvent(category, action, label, value);
                },
                addTrans: function (transactionId, affiliation, total, tax, shipping) {
                    me._addTrans(transactionId, affiliation, total, tax, shipping);
                },
                addItem: function (transactionId, sku, name, category, price, quantity) {
                    me._addItem(transactionId, name, sku, category, price, quantity);
                },
                trackTrans: function () {
                    me._trackTrans();
                },
                clearTrans: function () {
                    me._clearTrans();
                },
                trackSocial: function(network, action, target) {
                    me._trackSocial(network, action, target);
                },
                ga: function() {
                    if (angular.isDefined($window.__gaTracker)) {
                        $window.__gaTracker(arguments);
                    }
                }
            };
        }];

    });
<commit_msg>Handle ecommerce events for each property<commit_after>/* global angular, console */

angular.module('umc-angular-google-analytics', [])
    .provider('Analytics', function() {
        'use strict';

        var created = false;
        var trackRoutes = true;
        var trackPrefix = '';
        var domainName;
        var filename = 'analytics.js';
        var pageEvent = '$routeChangeSuccess';

        this.trackers = [];
        this._logs = [];

        // config methods
        this.setAccount = function(id) {
            var primary = {
                code: id,
                name: '',
                trackEcommerce: false,
                ecommerceLoaded: false,
                trackDisplayfeatures: false,
                displayfeaturesLoaded: false,
                trackEnhancedEcommerce: false,
                enhancedEcommerceLoaded: false
            };

            // first tracker in array is always the "account" value
            if (this.trackers.length === 0) {
                this.trackers.push(primary);
            } else {
                this.trackers[0] = primary;
            }
            return true;
        };

        this.trackPages = function(doTrack) {
            trackRoutes = doTrack;
            return true;
        };

        this.trackPrefix = function(prefix) {
            trackPrefix = prefix;
            return true;
        };

        this.setDomainName = function(domain) {
            domainName = domain;
            return true;
        };

        this.setFilename = function(name) {
            filename = name;
            return true;
        };

        this.setPageEvent = function(name) {
            pageEvent = name;
            return true;
        };

        this.trackEcommerce = function (doTrack, name) {
            if (name === null || name === '') {
                this.trackers[0].trackEcommerce = doTrack;
                return true;
            }

            for (var i = 1; i < this.trackers.length; i++) {
                if (this.trackers[i].name === name) {
                    this.trackers[i].trackEcommerce = doTrack;
                    return true;
                }
            }

            return true;
        };

        this.trackEnhancedEcommerce = function (doTrack, name) {
            if (name === null || name === '') {
                this.trackers[0].trackEnhancedEcommerce = doTrack;
                return true;
            }

            for (var i = 1; i < this.trackers.length; i++) {
                if (this.trackers[i].name === name) {
                    this.trackers[i].trackEnhancedEcommerce = doTrack;
                    return true;
                }
            }

            return true;
        };

        this.trackDisplayFeatures = function (doTrack, name) {
            if (name === null || name === '') {
                this.trackers[0].trackDisplayfeatures = doTrack;
                return true;
            }

            for (var i = 1; i < this.trackers.length; i++) {
                if (this.trackers[i].name === name) {
                    this.trackers[i].trackDisplayfeatures = doTrack;
                    return true;
                }
            }
            return true;
        };

        this.addTracker = function(code, name) {
            // handle special case of primary tracker
            if (name === null || name === '') {
                this.trackers[0].code = code;
                return;
            }

            for (var i = 1; i < this.trackers.length; i++) {
                if (this.trackers[i].name === name) {
                    this.trackers[i].code = code;
                    return;
                }
            }

            this.trackers.push({
                code: code,
                name: name,
                trackEcommerce: false,
                ecommerceLoaded: false,
                trackDisplayfeatures: false,
                displayfeaturesLoaded: false,
                trackEnhancedEcommerce: false,
                enhancedEcommerceLoaded: false
            });
        };

        this.getTrackers = function() {
            return this.trackers;
        };

        // public service
        this.$get = ['$document', '$rootScope', '$location', '$window', function($document, $rootScope, $location, $window) {
          // private methods
          this._createScriptTag = function() {
            //require a tracking id
            if (this.trackers.length === 0)
                return;

            //initialize the window object __gaTracker
            $window.GoogleAnalyticsObject = '__gaTracker';
            if (angular.isUndefined($window.__gaTracker)) {
                $window.__gaTracker = function() {
                    if (angular.isUndefined($window.__gaTracker.q)) {
                        $window.__gaTracker.q = [];
                    }
                    $window.__gaTracker.q.push(arguments);
                };
                $window.__gaTracker.l=1*new Date();
            }
            var opts = {};
            if (domainName) {
                opts.cookieDomain = domainName;
            }

              // create the primary tracker
              $window.__gaTracker('create', this.trackers[0].code);

              // create secondary trackers if present
              for (var i = 1; i < this.trackers.length; i++) {
                  $window.__gaTracker('create', this.trackers[i].code, {'name': this.trackers[i].name});
              }

              // load ecommerce for each tracker if requested
              // Only enable ecommerce if enhanced ecommerce is disabled per Google documentation.
              if (this.trackers[0].trackEcommerce && !this.trackers[0].ecommerceLoaded && !this.trackers[0].trackEnhancedEcommerce) {
                  this.trackers[0].ecommerceLoaded = true;
                  $window.__gaTracker('require', 'ecommerce', 'ecommerce.js');
                  this._log('loadGA', 'ecommerce');
              }
              for (var e = 1; i < this.trackers.length; e++) {
                  if (this.trackers[e].ecommerceLoaded )
                      continue;
                  this.trackers[e].ecommerceLoaded = true;
                  $window.__gaTracker(this.trackers[e].name + '.require', 'ecommerce', 'ecommerce.js');
                  this._log('loadGA', 'ecommerce ' + this.trackers[e].name);
              }

              // load enhanced ecommerce for each tracker if requested
              // Only enable ecommerce if enhanced ecommerce is disabled per Google documentation.
              if (this.trackers[0].trackEnhancedEcommerce && !this.trackers[0].enhancedEcommerceLoaded) {
                  this.trackers[0].enhancedEcommerceLoaded = true;
                  $window.__gaTracker('require', 'ec', 'ec.js');
                  this._log('loadGA', 'ec');
              }
              for (var ee = 1; i < this.trackers.length; ee++) {
                  if (this.trackers[e].enhancedEcommerceLoaded)
                      continue;
                  this.trackers[ee].enhancedEcommerceLoaded = true;
                  $window.__gaTracker(this.trackers[ee].name + '.require', 'ec', 'ec.js');
                  this._log('loadGA', 'ec ' + this.trackers[ee].name);
              }

              // load display features
              if (this.trackers[0].trackDisplayfeatures && !this.trackers[0].displayfeaturesLoaded) {
                  this.trackers[0].displayfeaturesLoaded = true;
                  $window.__gaTracker('require', 'displayfeatures', 'displayfeatures.js');
                  this._log('loadGA', 'displayfeatures');
              }
              for (var d = 1; i < this.trackers.length; d++) {
                  if (this.trackers[e].displayfeaturesLoaded)
                      continue;
                  this.trackers[d].displayfeaturesLoaded = true;
                  $window.__gaTracker(this.trackers[d].name + '.require', 'displayfeatures', 'displayfeatures.js');
                  this._log('loadGA', 'displayfeatures ' + this.trackers[d].name);
              }

            if (trackRoutes) {
              this._trackPage($location.path(), $rootScope.pageTitle); // TODO: this is too specific to our apps
            }

            // inject the google analytics tag
            (function() {
              var gaTag = $document[0].createElement('script');
              gaTag.type = 'text/javascript';
              gaTag.async = true;
              gaTag.src = '//www.google-analytics.com/' + filename;
              var s = $document[0].getElementsByTagName('script')[0];
              s.parentNode.insertBefore(gaTag, s);
            })();
            created = true;
          };
          // for testing
          this._log = function() {
              this._logs.push(arguments);
          };

          /**
            * Track Pageview
            * https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#hit
            * @param url (optional, if not specified $location.path() will be used, and this gets the trackPrefix prefixed onto it)
            * @param title (optional, if not specified $rootScope.pageTitle will be used if defined, else let GA use the document.title property)
            * @private
            */
          this._trackPage = function(url,title) {
              if (angular.isUndefined($window.__gaTracker)) {
                  return;
              }

              // TODO: we had the require calls here for displayfeatures. do we actually need it here?

              if (angular.isUndefined(url)) {
                 url = $location.path();
              }

              var fullUrl = trackPrefix + url;
              // page should always start with a /
              if (fullUrl !== '' && fullUrl.charAt(0) !== '/') {
                 fullUrl = '/' + fullUrl;
              }

              var opts = {
                'page': fullUrl
              };

              if (angular.isUndefined(title) && angular.isDefined($rootScope.pageTitle)) {
                  title = $rootScope.pageTitle;
              }
              if (angular.isDefined(title) && title !== '') {
                  opts.title = title;
              }

              // primary
              $window.__gaTracker('send','pageview', opts);
              // secondary trackers
              for (var x = 1; x < this.trackers.length; x++) {
                  $window.__gaTracker(this.trackers[x].name + '.send','pageview', opts);
              }
              this._log('pageview', arguments);
          };

          /**
            * Track Event
            * https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#events
            * @param category
            * @param action
            * @param label
            * @param value
            * @private
            */
          this._trackEvent = function(category, action, label, value) {
              if (angular.isUndefined($window.__gaTracker)) {
                  return;
              }

              // these two are required by Google's API
              var evt = {
                 'eventCategory': category,
                 'eventAction': action
              };

              // label is optional and a string
              if (typeof label !== 'undefined') {
                  evt.eventLabel = label;
              }

              // value is optional and numeric
              if (typeof value !== 'undefined') {
                 // if value is not numeric, just default it to zero
                 if (!isNaN(parseFloat(value)) && isFinite(value)) {
                     value = 0;
                  }
                  evt.eventValue = value;
              }

              // primary
              $window.__gaTracker('send','event', evt);

              // secondary trackers
              for (var x = 1; x < this.trackers.length; x++) {
                  $window.__gaTracker(this.trackers[x].name + '.send','event', evt);
              }
              this._log('event', arguments);
          };

          /**
           * Add transaction
           * https://developers.google.com/analytics/devguides/collection/analyticsjs/ecommerce#addTrans
           * @param transactionId
           * @param affiliation
           * @param total
           * @param tax
           * @param shipping
           * @param currency (defaults to USD if not set)  ISO 4217 codes for this.
           * @private
           */
          this._addTrans = function (transactionId, affiliation, total, tax, shipping, currency) {
              if (angular.isUndefined($window.__gaTracker)) {
                  return;
              }

              if (currency == null)
                  currency = 'USD';

              //TODO: guard in case ecommerce hasn't been loaded, shouldn't really happen
              var tran = {
                  'id': transactionId,
                  'affiliation': affiliation,
                  'revenue': total,
                  'shipping': shipping,
                  'tax': tax,
                  'currency': currency
              };

              if (this.trackers[0].trackEcommerce) {
                  $window.__gaTracker('ecommerce:addTransaction', tran);
                  this._log('ecommerce:addTransaction', arguments);
              }

              for (var x = 1; x < this.trackers.length; x++) {
                  if (this.trackers[x].trackEcommerce) {
                      $window.__gaTracker(this.trackers[x].name + '.ecommerce:addTransaction', tran);
                      this._log('ecommerce:addTransaction', arguments);
                  }
              }
          };

          /**
           * Add item to transaction
           * https://developers.google.com/analytics/devguides/collection/analyticsjs/ecommerce#addItem
           * @param transactionId
           * @param name
           * @param sku
           * @param category
           * @param price
           * @param quantity
           * @param currency (defaults to USD if not set)  ISO 4217 codes for this.
           * @private
           */
          this._addItem = function (transactionId, name, sku, category, price, quantity, currency) {
              if (angular.isUndefined($window.__gaTracker)) {
                  return;
              }

              if (currency == null)
                  currency = 'USD';

              var item = {
                  'id': transactionId,
                  'name': name,
                  'sku': sku,
                  'category': category,
                  'price': price,
                  'quantity': quantity,
                  'currency': currency
              };

              if (this.trackers[0].trackEcommerce) {
                  $window.__gaTracker('ecommerce:addItem', item);
                  this._log('ecommerce:addItem', arguments);
              }

              for (var x = 1; x < this.trackers.length; x++) {
                  if (this.trackers[x].trackEcommerce) {
                      $window.__gaTracker(this.trackers[x].name + '.ecommerce:addItem', item);
                      this._log('ecommerce:addItem', arguments);
                  }
              }
          };

          /**
           * Track transaction
           * https://developers.google.com/analytics/devguides/collection/analyticsjs/ecommerce#sendingData
           * @private
           */
          this._trackTrans = function () {
              if (angular.isUndefined($window.__gaTracker)) {
                  return;
              }

              if (this.trackers[0].trackEcommerce) {
                  $window.__gaTracker('ecommerce:send');
                  this._log('ecommerce:send', arguments);
              }

              for (var x = 1; x < this.trackers.length; x++) {
                  if (this.trackers[x].trackEcommerce) {
                      $window.__gaTracker(this.trackers[x].name + '.ecommerce:send');
                      this._log('ecommerce:send', arguments);
                  }
              }
          };

          /**
           * Clear transaction
           * https://developers.google.com/analytics/devguides/collection/analyticsjs/ecommerce#clearingData
           * @private
           */
          this._clearTrans = function () {
              if (angular.isUndefined($window.__gaTracker)) {
                  return;
              }

              if (this.trackers[0].trackEcommerce) {
                  $window.__gaTracker('ecommerce:clear');
                  this._log('ecommerce:clear', arguments);
              }

              for (var x = 1; x < this.trackers.length; x++) {
                  if (this.trackers[x].trackEcommerce) {
                      $window.__gaTracker(this.trackers[x].name + '.ecommerce:clear');
                      this._log('ecommerce:clear', arguments);
                  }
              }
          };

          this._trackSocial = function(network, action, target) {
              if (angular.isUndefined($window.__gaTracker)) {
                 return;
              }

              var social = {
                 'socialNetwork': network,
                 'socialAction': action,
                 'socialTarget': target
              };

              // primary
              $window.__gaTracker('send','social', social);

              // secondary trackers
              for (var x = 1; x < this.trackers.length; x++) {
                  $window.__gaTracker(this.trackers[x].name + '.send','social', social);
              }

              this._log('trackSocial', arguments);
          };

          // --------- initialization steps -----------------------
          // creates the ganalytics tracker
          this._createScriptTag();

          var me = this;

          // activates page tracking
          if (trackRoutes) {
              $rootScope.$on(pageEvent, function() {
                  me._trackPage($location.path(), $rootScope.pageTitle);
              });
          }
          // --------- end initialization steps -----------------------

          // the rest of the public interface
          return {
                _logs: me._logs,
                trackers: me.trackers,
                trackPage: function(url, title) {
                    // add a page event
                    me._trackPage(url, title);
                },
                trackEvent: function(category, action, label, value) {
                    // add an action event
                    me._trackEvent(category, action, label, value);
                },
                addTrans: function (transactionId, affiliation, total, tax, shipping) {
                    me._addTrans(transactionId, affiliation, total, tax, shipping);
                },
                addItem: function (transactionId, sku, name, category, price, quantity) {
                    me._addItem(transactionId, name, sku, category, price, quantity);
                },
                trackTrans: function () {
                    me._trackTrans();
                },
                clearTrans: function () {
                    me._clearTrans();
                },
                trackSocial: function(network, action, target) {
                    me._trackSocial(network, action, target);
                },
                ga: function() {
                    if (angular.isDefined($window.__gaTracker)) {
                        $window.__gaTracker(arguments);
                    }
                }
            };
        }];

    });
<|endoftext|>