<commit_before>// TODO Add key controls
//      * Enter: Shoudl select happen if branch is deepest descendent?
//      * Shift+Enter: multiple select

// TODO add third state to checkbox to show children selected

angular
  .module('angular-material-tree')
  .directive('mdBranch', branchDirective);

// checkbox html
var CHECKBOX_SELECTION_INDICATOR = angular.element('

');
// branch arrow icon svg
var BRANCH_ARROW_TEMPLATE = angular.element('
'+
  '
'+
    '<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">'+
      '<path d="M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z"/>'+
      '<path d="M0-.25h24v24H0z" fill="none"/>'+
    '</svg>'+
  '
'+
'
');

/*@ngInject*/
function branchDirective($parse, $document, $mdUtil, $filter, $$mdTree, $mdConstant) {
  var treeElement;

  return {
    restrict: 'E',
    priority: 1000,
    terminal: true,
    transclude: 'element',
    compile: compile
  };

  function compile(tElement, tAttrs) {
    var expression = tAttrs.branchRepeat;
    var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)\s*$/);
    var repeatName = match[1];
    var repeatListExpression = $parse(match[2]);
    var parentNode = tElement[0].parentNode;
    var isRoot = parentNode.nodeName === 'MD-TREE';
    var hasParentBranch = parentNode.nodeName === 'MD-BRANCH';
    var isOpen = isRoot || (hasParentBranch && parentNode.classList.contains('md-open'));

    return function postLink(scope, element, attrs, ctrls, transclude) {
      var dataWatcher;
      var items;
      var keyCodes = $mdConstant.KEY_CODE;
      var blocks = [];
      var pooledBlocks = [];
      var itemsLength = 0;
      var isUpdating = false;
      var isFilterOpen = false;
      var filter = $filter('filter');
      if (isOpen) { startWatching(); }

      scope.$mdBranchFilter = function (arr, filterValue) {
        var filtered = filter(arr, filterValue);
        // open branches if filter string is greater then 2 and items have been found
        if (filtered.length < arr.length && filtered.length > 0) {
          isFilterOpen = true;
          blocks.forEach(function (block) {
            $$mdTree.filterOpen(block);
          });

        // close branches if filter is less than 3 characters or no items have been found
        } else if (isFilterOpen && filtered.length === 0) {
          isFilterOpen = false;
          blocks.forEach(function (block) {
            $$mdTree.filterClose(block);
          });
        }

        return filtered;
      };

      // watch model data
      function startWatching() {
        if (dataWatcher) { return; }
        dataWatcher = scope.$watchCollection(repeatListExpression, updateBranch);
      }
      // kill watcher
      function killWatching() {
        if (typeof dataWatcher === 'function') {
          dataWatcher();
          dataWatcher = undefined;
        }
      }

      // expose methods to scope
      scope.startWatching = startWatching;
      scope.killWatching = killWatching;

      function updateBranch(newItems, oldItems) {
        if (isUpdating) { return; }
        isUpdating = true;

        var i;
        var _block;
        var keys;
        var index;
        var length;
        var maxIndex;
        var newBlocks = [];
        var _itemsLength = newItems && newItems.length || 0;

        if (_itemsLength !== itemsLength) {
          itemsLength = _itemsLength;
        }
        items = newItems;

        // Detach and pool any blocks that are no longer in the viewport.
        keys = Object.keys(blocks);
        i = 0;
        length = keys.length;
        while (i < length) {
          index = parseInt(keys[i]);
          if (index >= itemsLength) {
            poolBlock(index);
          }
          i += 1;
        }

        // Update blocks that are already rendered.
        i = 0;
        while ((blocks[i] !== null && blocks[i] !== undefined)) {
          updateBlock(blocks[i], i);
          i += 1;
        }
        maxIndex = i - 1;

        // Collect blocks at the end.
        while (i < itemsLength) {
          _block = getBlock(i);
          updateBlock(_block, i);
          newBlocks.push(_block);
          i += 1;
        }

        // Attach collected blocks to the document.
        if (newBlocks.length) {
          element[0].parentNode.insertBefore(
            domFragmentFromBlocks(newBlocks),
            blocks[maxIndex] && blocks[maxIndex].element[0].nextSibling);
        }

        isUpdating = false;
      }

      // store block in memory and remove it from the dom.
      function poolBlock(index) {
        blocks[index].element
          .off('blur', onBlur)
          .off('focus', onFocus);
        pooledBlocks.unshift(blocks[index]);
        blocks[index].element[0].parentNode.removeChild(blocks[index].element[0]);
        delete blocks[index];
      }

      // update block scope and state
      function updateBlock(block, index) {
        blocks[index] = block;

        if (block.new) { updateNewBlock(block); } // configure template for new blocks
        if (!block.new && (block.scope.$index === index && block.scope[repeatName] === items[index])) {
          updateState(block.scope,  index); // update state if a block is nore or changes
          return;
        }
        block.new = false;

        // Update and digest the block's scope.
        updateScope(block.scope, index);
        updateState(block.scope,  index);

        if (!scope.$root.$$phase) {
          block.scope.$digest();
        }
      }

      // NOTE Might cause problems when applying a new scope
      // place contents into containers to display items correctly
      // this is only done once
      function updateNewBlock(block) {
        var isSelectable = block.element.attr('select') !== undefined;
        var hideCheckbox = block.element.attr('hide-checkbox') !== undefined;
        var innerContainer = angular.element('
'); // branch contents
        var branchContainer = angular.element('
'); // nested branched

        if (isSelectable) {
          if (!hideCheckbox) {
            block.element.addClass('md-checkbox-enabled');
            innerContainer.append(CHECKBOX_SELECTION_INDICATOR.clone());
          } else {
            block.element.addClass('md-select-highlight-enabled');
          }
        }
        Array.prototype.slice.call(block.element[0].childNodes).forEach(function (node) {
          if (node.nodeType === 8 && node.nodeValue.trim() === 'mdBranch:') {
            branchContainer.append(node);
          } else {
            innerContainer.append(node);
          }
        });
        block.element.append(innerContainer);

        // add branches
        if (branchContainer[0].childNodes.length) {
          innerContainer.prepend(BRANCH_ARROW_TEMPLATE.clone());
          block.element.append(branchContainer);

        // if no more branches then mark as tip
        } else {
          block.element.addClass('md-tip no-arrow');
        }
      }

      // Change the model value attached to the scope
      function updateScope($scope, index) {
        $scope.$index = index; // data index
        $scope.repeatName = repeatName; // data property
        $scope[repeatName] = items && items[index]; // data
        $scope.$odd = !($scope.$even = (index & 1) === 0);
        $scope.$depth = ($scope.$parent.$depth + 1) || 0;
        items[index].$$depth = $scope.$depth;
      }

      // upate open state
      // disconnect/reconnect scopes
      // start watching for open items
      function updateState($scope, index) {
        var item = items ? items[index] : undefined;
        var element = $scope.$element && $scope.$element[0] ? $scope.$element : undefined;

        // if the data is replaced and we loose the protected state properties then we need to copy them across
        if (item.$$mdBranchId === undefined) {
          var oldItem = oldItems[index];
          if (oldItem && oldItem.id === item.id ) {
            item.$$mdBranchId = oldItem.$$mdBranchId;
            item.$$selected = oldItem.$$selected;
            Object.defineProperty(item, '$$isOpen', {
              value: oldItem.$$isOpen,
              enumerable: false, writable: true, configurable: false
            });
          }
        }

        // reconnect all scopes
        $mdUtil.reconnectScope($scope);
        element.toggleClass('md-open', item.$$isOpen);

        // wait till next digest to change state so we do not get into an ifinite loop
        $mdUtil.nextTick(function () {
          // if open then watch the data
          if (item.$$isOpen) {
            $scope.startWatching();

          // sconnect scopes that are closed
          } else {
            $mdUtil.disconnectScope($scope);
          }
        });
      }

      // set initial state on data
      function initState(item) {
        if (item.$$isOpen === undefined) {
          Object.defineProperty(item, '$$isOpen', {
            value: false,
            configurable: false,
            enumerable: false,
            writable: true
          });
        }
      }

      // check pool for block
      // otherwise create a new block
      function getBlock(index) {
        if (pooledBlocks.length) {
          return pooledBlocks.pop();
        }

        // create new bloc
        var block;
        transclude(function(clone, scope) {
          block = {
            element: clone,
            new: true,
            scope: scope
          };

          updateScope(scope, index);
          initState(items[index]);
          scope.$element = clone; // attach element to scope so it can be accessed in controller
          parentNode.appendChild(clone[0]);
          scope.$on('$destroy', function () {
            clone
              .off('blur', onBlur)
              .off('focus', onFocus);
          });
        });
        return block;
      }

      // add blocks to one fragment for better performance
      function domFragmentFromBlocks(blocks) {
        var fragment = $document[0].createDocumentFragment();
        blocks.forEach(function(block) {
          fragment.appendChild(block.element[0]);
          block.element.attr('tabindex', '0');
          block.element
            .on('blur', onBlur)
            .on('focus', onFocus);
        });
        return fragment;
      }

      function onBlur(e) {
        angular.element(e.target)
          .removeClass('md-focused')
          .off('keydown', onKeydown);
      }

      function onFocus(e) {
        angular.element(e.target)
          .addClass('md-focused')
          .on('keydown', onKeydown);
      }

      function onKeydown(e) {
        switch (e.keyCode) {
          case keyCodes.UP_ARROW:
            return focusPrevious(e.target);
          case keyCodes.DOWN_ARROW:
            return focusNext(e.target);
          case keyCodes.RIGHT_ARROW:
            return openNextBranch(e.target);
          case keyCodes.LEFT_ARROW:
            return closePrevBranch(e.target);
          case $$mdTree.isShiftPressed() && keyCodes.SPACE:
          case $$mdTree.isShiftPressed() && keyCodes.ENTER:
            return selectBranch(e.target, false);
          case keyCodes.SPACE:
          case keyCodes.ENTER:
            return handleEnter(e.target);
        }
      }

      // recusivly find next branch
      function focusNext(branchElement) {
        branchElement = angular.element(branchElement);
        var next;
        var branchContainer = branchElement[0].querySelector('.md-branch-container');
        if (branchElement.hasClass('md-open') && branchContainer) {
          // find nearest child branch
          Array.prototype.slice.call(branchContainer.children).every(function (el) {
            if (el.nodeName === 'MD-BRANCH') { next = angular.element(el); }
            return !next;
          });

          // if no child branches are found try to get next branch
          if (!next) { next = branchElement.next(); }
        } else {
          next = branchElement.next();
        }

        // recursively find next branch
        if (!next || !next.length) { next = findNext(branchElement); }
        if (next && next.length) { next.focus(); }
      }

      // recusivly find previous branch
      function focusPrevious(branchElement) {
        branchElement = angular.element(branchElement);
        var previous = branchElement[0].previousElementSibling;

        // if no previous branch exists then step out to next highest layer
        if (!previous) {
          previous = $$mdTree.getBranch(branchElement[0].parentNode);

        // if found then find the deepest and lowest sub branch
        } else {
          previous = findDeepest(previous);
        }

        // focus on element
        if (previous) { angular.element(previous).focus(); }
      }

      // keep stepping out and look for next branch that we can focus on
      function findNext(el) {
        var branch = $$mdTree.getBranch(el[0].parentNode);
        if (!branch) { return null; }
        var next = angular.element(branch).next();
        if (next && next.length) { return next; }
        return findNext(angular.element(branch));
      }

      function findDeepest(el) {
        var next;
        if (!el || el.nodeName !== 'MD-BRANCH') { return null; }
        if ($$mdTree.isOpen(el)) {
          var branchContainer = el.querySelector('.md-branch-container');
          if (branchContainer) {
            Array.prototype.slice.call(branchContainer.children).reverse().every(function (el) {
              if (el.nodeName === 'MD-BRANCH') { next = el; }
              return !next;
            });
            if (next) { return findDeepest(next); }
          }
        }
        return el;
      }

      // open branch or select next
      function openNextBranch(branchElement) {
        if (!$$mdTree.isOpen(branchElement)) {
          var arrow = $$mdTree.getArrow(branchElement);
          if (arrow && !$$mdTree.isTip(branchElement)) {
            // open branch by simulating click
            getTreeElement(branchElement).triggerHandler({
              type: 'click',
              target: arrow
            });
          } else {
            focusNext(branchElement);
          }
        } else {
          focusNext(branchElement);
        }
      }

      // close branch or select previous
      function closePrevBranch(branchElement) {
        if ($$mdTree.isOpen(branchElement)) {
          var arrow = $$mdTree.getArrow(branchElement);
          if (arrow) {
            // close branch by simulating click
            getTreeElement(branchElement).triggerHandler({
              type: 'click',
              target: arrow
            });
          } else {
            focusPrevious(branchElement);
          }
        } else {
          focusPrevious(branchElement);
        }
      }

      // open/close branch
      function handleEnter(branchElement) {
        // single select branch
        if ($$mdTree.hasCheckbox(branchElement)) {
          selectBranch(branchElement, true);
          // TODO invoke single select callback

        // toggle open/close branch
        } else {
          toggleOpen(branchElement);
        }
      }

      function toggleOpen(branchElement) {
        if ($$mdTree.canOpen(branchElement) && !$$mdTree.isTip(branchElement)) {
          // open branch by simulating click
          getTreeElement(branchElement).triggerHandler({
            type: 'click',
            target: $$mdTree.getArrow(branchElement)
          });
        }
      }

      function selectBranch(branchElement, single) {
        var el;
        if (single === true) {
          el = branchElement.querySelector('.md-branch-inner');
        } else {
          el = $$mdTree.getCheckbox(branchElement);
        }
        if (el) {
          getTreeElement(branchElement).triggerHandler({
            type: 'click',
            target: el
          });
        }
      }

      function getTreeElement(branchElement) {
        if (treeElement) { return treeElement; }
        treeElement = walkForTree(branchElement);
        return treeElement;
      }

      function walkForTree(el) {
        var parent = el.parentNode;
        while (parent && parent !== document.body) {
          if (parent.nodeName === 'MD-TREE') { return angular.element(parent); }
          parent = parent.parentNode;
        }
        return null;
      }

    };
  }

}
<commit_msg>add missing old items<commit_after>// TODO Add key controls
//      * Enter: Shoudl select happen if branch is deepest descendent?
//      * Shift+Enter: multiple select

// TODO add third state to checkbox to show children selected

angular
  .module('angular-material-tree')
  .directive('mdBranch', branchDirective);

// checkbox html
var CHECKBOX_SELECTION_INDICATOR = angular.element('

');
// branch arrow icon svg
var BRANCH_ARROW_TEMPLATE = angular.element('
'+
  '
'+
    '<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">'+
      '<path d="M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z"/>'+
      '<path d="M0-.25h24v24H0z" fill="none"/>'+
    '</svg>'+
  '
'+
'
');

/*@ngInject*/
function branchDirective($parse, $document, $mdUtil, $filter, $$mdTree, $mdConstant) {
  var treeElement;

  return {
    restrict: 'E',
    priority: 1000,
    terminal: true,
    transclude: 'element',
    compile: compile
  };

  function compile(tElement, tAttrs) {
    var expression = tAttrs.branchRepeat;
    var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)\s*$/);
    var repeatName = match[1];
    var repeatListExpression = $parse(match[2]);
    var parentNode = tElement[0].parentNode;
    var isRoot = parentNode.nodeName === 'MD-TREE';
    var hasParentBranch = parentNode.nodeName === 'MD-BRANCH';
    var isOpen = isRoot || (hasParentBranch && parentNode.classList.contains('md-open'));

    return function postLink(scope, element, attrs, ctrls, transclude) {
      var dataWatcher;
      var items;
      var oldItems;
      var keyCodes = $mdConstant.KEY_CODE;
      var blocks = [];
      var pooledBlocks = [];
      var itemsLength = 0;
      var isUpdating = false;
      var isFilterOpen = false;
      var filter = $filter('filter');
      if (isOpen) { startWatching(); }

      scope.$mdBranchFilter = function (arr, filterValue) {
        var filtered = filter(arr, filterValue);
        // open branches if filter string is greater then 2 and items have been found
        if (filtered.length < arr.length && filtered.length > 0) {
          isFilterOpen = true;
          blocks.forEach(function (block) {
            $$mdTree.filterOpen(block);
          });

        // close branches if filter is less than 3 characters or no items have been found
        } else if (isFilterOpen && filtered.length === 0) {
          isFilterOpen = false;
          blocks.forEach(function (block) {
            $$mdTree.filterClose(block);
          });
        }

        return filtered;
      };

      // watch model data
      function startWatching() {
        if (dataWatcher) { return; }
        dataWatcher = scope.$watchCollection(repeatListExpression, updateBranch);
      }
      // kill watcher
      function killWatching() {
        if (typeof dataWatcher === 'function') {
          dataWatcher();
          dataWatcher = undefined;
        }
      }

      // expose methods to scope
      scope.startWatching = startWatching;
      scope.killWatching = killWatching;

      function updateBranch(newItems, old) {
        if (isUpdating) { return; }
        oldItems = old;
        isUpdating = true;

        var i;
        var _block;
        var keys;
        var index;
        var length;
        var maxIndex;
        var newBlocks = [];
        var _itemsLength = newItems && newItems.length || 0;

        if (_itemsLength !== itemsLength) {
          itemsLength = _itemsLength;
        }
        items = newItems;

        // Detach and pool any blocks that are no longer in the viewport.
        keys = Object.keys(blocks);
        i = 0;
        length = keys.length;
        while (i < length) {
          index = parseInt(keys[i]);
          if (index >= itemsLength) {
            poolBlock(index);
          }
          i += 1;
        }

        // Update blocks that are already rendered.
        i = 0;
        while ((blocks[i] !== null && blocks[i] !== undefined)) {
          updateBlock(blocks[i], i);
          i += 1;
        }
        maxIndex = i - 1;

        // Collect blocks at the end.
        while (i < itemsLength) {
          _block = getBlock(i);
          updateBlock(_block, i);
          newBlocks.push(_block);
          i += 1;
        }

        // Attach collected blocks to the document.
        if (newBlocks.length) {
          element[0].parentNode.insertBefore(
            domFragmentFromBlocks(newBlocks),
            blocks[maxIndex] && blocks[maxIndex].element[0].nextSibling);
        }

        isUpdating = false;
      }

      // store block in memory and remove it from the dom.
      function poolBlock(index) {
        blocks[index].element
          .off('blur', onBlur)
          .off('focus', onFocus);
        pooledBlocks.unshift(blocks[index]);
        blocks[index].element[0].parentNode.removeChild(blocks[index].element[0]);
        delete blocks[index];
      }

      // update block scope and state
      function updateBlock(block, index) {
        blocks[index] = block;

        if (block.new) { updateNewBlock(block); } // configure template for new blocks
        if (!block.new && (block.scope.$index === index && block.scope[repeatName] === items[index])) {
          updateState(block.scope,  index); // update state if a block is nore or changes
          return;
        }
        block.new = false;

        // Update and digest the block's scope.
        updateScope(block.scope, index);
        updateState(block.scope,  index);

        if (!scope.$root.$$phase) {
          block.scope.$digest();
        }
      }

      // NOTE Might cause problems when applying a new scope
      // place contents into containers to display items correctly
      // this is only done once
      function updateNewBlock(block) {
        var isSelectable = block.element.attr('select') !== undefined;
        var hideCheckbox = block.element.attr('hide-checkbox') !== undefined;
        var innerContainer = angular.element('
'); // branch contents
        var branchContainer = angular.element('
'); // nested branched

        if (isSelectable) {
          if (!hideCheckbox) {
            block.element.addClass('md-checkbox-enabled');
            innerContainer.append(CHECKBOX_SELECTION_INDICATOR.clone());
          } else {
            block.element.addClass('md-select-highlight-enabled');
          }
        }
        Array.prototype.slice.call(block.element[0].childNodes).forEach(function (node) {
          if (node.nodeType === 8 && node.nodeValue.trim() === 'mdBranch:') {
            branchContainer.append(node);
          } else {
            innerContainer.append(node);
          }
        });
        block.element.append(innerContainer);

        // add branches
        if (branchContainer[0].childNodes.length) {
          innerContainer.prepend(BRANCH_ARROW_TEMPLATE.clone());
          block.element.append(branchContainer);

        // if no more branches then mark as tip
        } else {
          block.element.addClass('md-tip no-arrow');
        }
      }

      // Change the model value attached to the scope
      function updateScope($scope, index) {
        $scope.$index = index; // data index
        $scope.repeatName = repeatName; // data property
        $scope[repeatName] = items && items[index]; // data
        $scope.$odd = !($scope.$even = (index & 1) === 0);
        $scope.$depth = ($scope.$parent.$depth + 1) || 0;
        items[index].$$depth = $scope.$depth;
      }

      // upate open state
      // disconnect/reconnect scopes
      // start watching for open items
      function updateState($scope, index) {
        var item = items ? items[index] : undefined;
        var element = $scope.$element && $scope.$element[0] ? $scope.$element : undefined;

        // if the data is replaced and we loose the protected state properties then we need to copy them across
        if (item.$$mdBranchId === undefined) {
          var oldItem = oldItems[index];
          if (oldItem && oldItem.id === item.id ) {
            item.$$mdBranchId = oldItem.$$mdBranchId;
            item.$$selected = oldItem.$$selected;
            Object.defineProperty(item, '$$isOpen', {
              value: oldItem.$$isOpen,
              enumerable: false, writable: true, configurable: false
            });
          }
        }

        // reconnect all scopes
        $mdUtil.reconnectScope($scope);
        element.toggleClass('md-open', item.$$isOpen);

        // wait till next digest to change state so we do not get into an ifinite loop
        $mdUtil.nextTick(function () {
          // if open then watch the data
          if (item.$$isOpen) {
            $scope.startWatching();

          // sconnect scopes that are closed
          } else {
            $mdUtil.disconnectScope($scope);
          }
        });
      }

      // set initial state on data
      function initState(item) {
        if (item.$$isOpen === undefined) {
          Object.defineProperty(item, '$$isOpen', {
            value: false,
            configurable: false,
            enumerable: false,
            writable: true
          });
        }
      }

      // check pool for block
      // otherwise create a new block
      function getBlock(index) {
        if (pooledBlocks.length) {
          return pooledBlocks.pop();
        }

        // create new bloc
        var block;
        transclude(function(clone, scope) {
          block = {
            element: clone,
            new: true,
            scope: scope
          };

          updateScope(scope, index);
          initState(items[index]);
          scope.$element = clone; // attach element to scope so it can be accessed in controller
          parentNode.appendChild(clone[0]);
          scope.$on('$destroy', function () {
            clone
              .off('blur', onBlur)
              .off('focus', onFocus);
          });
        });
        return block;
      }

      // add blocks to one fragment for better performance
      function domFragmentFromBlocks(blocks) {
        var fragment = $document[0].createDocumentFragment();
        blocks.forEach(function(block) {
          fragment.appendChild(block.element[0]);
          block.element.attr('tabindex', '0');
          block.element
            .on('blur', onBlur)
            .on('focus', onFocus);
        });
        return fragment;
      }

      function onBlur(e) {
        angular.element(e.target)
          .removeClass('md-focused')
          .off('keydown', onKeydown);
      }

      function onFocus(e) {
        angular.element(e.target)
          .addClass('md-focused')
          .on('keydown', onKeydown);
      }

      function onKeydown(e) {
        switch (e.keyCode) {
          case keyCodes.UP_ARROW:
            return focusPrevious(e.target);
          case keyCodes.DOWN_ARROW:
            return focusNext(e.target);
          case keyCodes.RIGHT_ARROW:
            return openNextBranch(e.target);
          case keyCodes.LEFT_ARROW:
            return closePrevBranch(e.target);
          case $$mdTree.isShiftPressed() && keyCodes.SPACE:
          case $$mdTree.isShiftPressed() && keyCodes.ENTER:
            return selectBranch(e.target, false);
          case keyCodes.SPACE:
          case keyCodes.ENTER:
            return handleEnter(e.target);
        }
      }

      // recusivly find next branch
      function focusNext(branchElement) {
        branchElement = angular.element(branchElement);
        var next;
        var branchContainer = branchElement[0].querySelector('.md-branch-container');
        if (branchElement.hasClass('md-open') && branchContainer) {
          // find nearest child branch
          Array.prototype.slice.call(branchContainer.children).every(function (el) {
            if (el.nodeName === 'MD-BRANCH') { next = angular.element(el); }
            return !next;
          });

          // if no child branches are found try to get next branch
          if (!next) { next = branchElement.next(); }
        } else {
          next = branchElement.next();
        }

        // recursively find next branch
        if (!next || !next.length) { next = findNext(branchElement); }
        if (next && next.length) { next.focus(); }
      }

      // recusivly find previous branch
      function focusPrevious(branchElement) {
        branchElement = angular.element(branchElement);
        var previous = branchElement[0].previousElementSibling;

        // if no previous branch exists then step out to next highest layer
        if (!previous) {
          previous = $$mdTree.getBranch(branchElement[0].parentNode);

        // if found then find the deepest and lowest sub branch
        } else {
          previous = findDeepest(previous);
        }

        // focus on element
        if (previous) { angular.element(previous).focus(); }
      }

      // keep stepping out and look for next branch that we can focus on
      function findNext(el) {
        var branch = $$mdTree.getBranch(el[0].parentNode);
        if (!branch) { return null; }
        var next = angular.element(branch).next();
        if (next && next.length) { return next; }
        return findNext(angular.element(branch));
      }

      function findDeepest(el) {
        var next;
        if (!el || el.nodeName !== 'MD-BRANCH') { return null; }
        if ($$mdTree.isOpen(el)) {
          var branchContainer = el.querySelector('.md-branch-container');
          if (branchContainer) {
            Array.prototype.slice.call(branchContainer.children).reverse().every(function (el) {
              if (el.nodeName === 'MD-BRANCH') { next = el; }
              return !next;
            });
            if (next) { return findDeepest(next); }
          }
        }
        return el;
      }

      // open branch or select next
      function openNextBranch(branchElement) {
        if (!$$mdTree.isOpen(branchElement)) {
          var arrow = $$mdTree.getArrow(branchElement);
          if (arrow && !$$mdTree.isTip(branchElement)) {
            // open branch by simulating click
            getTreeElement(branchElement).triggerHandler({
              type: 'click',
              target: arrow
            });
          } else {
            focusNext(branchElement);
          }
        } else {
          focusNext(branchElement);
        }
      }

      // close branch or select previous
      function closePrevBranch(branchElement) {
        if ($$mdTree.isOpen(branchElement)) {
          var arrow = $$mdTree.getArrow(branchElement);
          if (arrow) {
            // close branch by simulating click
            getTreeElement(branchElement).triggerHandler({
              type: 'click',
              target: arrow
            });
          } else {
            focusPrevious(branchElement);
          }
        } else {
          focusPrevious(branchElement);
        }
      }

      // open/close branch
      function handleEnter(branchElement) {
        // single select branch
        if ($$mdTree.hasCheckbox(branchElement)) {
          selectBranch(branchElement, true);
          // TODO invoke single select callback

        // toggle open/close branch
        } else {
          toggleOpen(branchElement);
        }
      }

      function toggleOpen(branchElement) {
        if ($$mdTree.canOpen(branchElement) && !$$mdTree.isTip(branchElement)) {
          // open branch by simulating click
          getTreeElement(branchElement).triggerHandler({
            type: 'click',
            target: $$mdTree.getArrow(branchElement)
          });
        }
      }

      function selectBranch(branchElement, single) {
        var el;
        if (single === true) {
          el = branchElement.querySelector('.md-branch-inner');
        } else {
          el = $$mdTree.getCheckbox(branchElement);
        }
        if (el) {
          getTreeElement(branchElement).triggerHandler({
            type: 'click',
            target: el
          });
        }
      }

      function getTreeElement(branchElement) {
        if (treeElement) { return treeElement; }
        treeElement = walkForTree(branchElement);
        return treeElement;
      }

      function walkForTree(el) {
        var parent = el.parentNode;
        while (parent && parent !== document.body) {
          if (parent.nodeName === 'MD-TREE') { return angular.element(parent); }
          parent = parent.parentNode;
        }
        return null;
      }

    };
  }

}
<|endoftext|>


<commit_before>  $.extend(prototype, {
    change: function (shiftKey, originalEvent) {
      var options = this.options;
      var aspectRatio = options.aspectRatio;
      var action = this.action;
      var container = this.container;
      var canvas = this.canvas;
      var cropBox = this.cropBox;
      var width = cropBox.width;
      var height = cropBox.height;
      var left = cropBox.left;
      var top = cropBox.top;
      var right = left + width;
      var bottom = top + height;
      var minLeft = 0;
      var minTop = 0;
      var maxWidth = container.width;
      var maxHeight = container.height;
      var renderable = true;
      var offset;
      var range;

      // Locking aspect ratio in "free mode" by holding shift key (#259)
      if (!aspectRatio && shiftKey) {
        aspectRatio = width && height ? width / height : 1;
      }

      if (options.strict) {
        minLeft = cropBox.minLeft;
        minTop = cropBox.minTop;
        maxWidth = minLeft + min(container.width, canvas.width);
        maxHeight = minTop + min(container.height, canvas.height);
      }

      range = {
        x: this.endX - this.startX,
        y: this.endY - this.startY
      };

      if (aspectRatio) {
        range.X = range.y * aspectRatio;
        range.Y = range.x / aspectRatio;
      }

      switch (action) {
        // Move crop box
        case ACTION_ALL:
          left += range.x;
          top += range.y;
          break;

        // Resize crop box
        case ACTION_EAST:
          if (range.x >= 0 && (right >= maxWidth || aspectRatio &&
            (top <= minTop || bottom >= maxHeight))) {

            renderable = false;
            break;
          }

          width += range.x;

          if (aspectRatio) {
            height = width / aspectRatio;
            top -= range.Y / 2;
          }

          if (width < 0) {
            action = ACTION_WEST;
            width = 0;
          }

          break;

        case ACTION_NORTH:
          if (range.y <= 0 && (top <= minTop || aspectRatio &&
            (left <= minLeft || right >= maxWidth))) {

            renderable = false;
            break;
          }

          height -= range.y;
          top += range.y;

          if (aspectRatio) {
            width = height * aspectRatio;
            left += range.X / 2;
          }

          if (height < 0) {
            action = ACTION_SOUTH;
            height = 0;
          }

          break;

        case ACTION_WEST:
          if (range.x <= 0 && (left <= minLeft || aspectRatio &&
            (top <= minTop || bottom >= maxHeight))) {

            renderable = false;
            break;
          }

          width -= range.x;
          left += range.x;

          if (aspectRatio) {
            height = width / aspectRatio;
            top += range.Y / 2;
          }

          if (width < 0) {
            action = ACTION_EAST;
            width = 0;
          }

          break;

        case ACTION_SOUTH:
          if (range.y >= 0 && (bottom >= maxHeight || aspectRatio &&
            (left <= minLeft || right >= maxWidth))) {

            renderable = false;
            break;
          }

          height += range.y;

          if (aspectRatio) {
            width = height * aspectRatio;
            left -= range.X / 2;
          }

          if (height < 0) {
            action = ACTION_NORTH;
            height = 0;
          }

          break;

        case ACTION_NORTH_EAST:
          if (aspectRatio) {
            if (range.y <= 0 && (top <= minTop || right >= maxWidth)) {
              renderable = false;
              break;
            }

            height -= range.y;
            top += range.y;
            width = height * aspectRatio;
          } else {
            if (range.x >= 0) {
              if (right < maxWidth) {
                width += range.x;
              } else if (range.y <= 0 && top <= minTop) {
                renderable = false;
              }
            } else {
              width += range.x;
            }

            if (range.y <= 0) {
              if (top > minTop) {
                height -= range.y;
                top += range.y;
              }
            } else {
              height -= range.y;
              top += range.y;
            }
          }

          if (width < 0 && height < 0) {
            action = ACTION_SOUTH_WEST;
            height = 0;
            width = 0;
          } else if (width < 0) {
            action = ACTION_NORTH_WEST;
            width = 0;
          } else if (height < 0) {
            action = ACTION_SOUTH_EAST;
            height = 0;
          }

          break;

        case ACTION_NORTH_WEST:
          if (aspectRatio) {
            if (range.y <= 0 && (top <= minTop || left <= minLeft)) {
              renderable = false;
              break;
            }

            height -= range.y;
            top += range.y;
            width = height * aspectRatio;
            left += range.X;
          } else {
            if (range.x <= 0) {
              if (left > minLeft) {
                width -= range.x;
                left += range.x;
              } else if (range.y <= 0 && top <= minTop) {
                renderable = false;
              }
            } else {
              width -= range.x;
              left += range.x;
            }

            if (range.y <= 0) {
              if (top > minTop) {
                height -= range.y;
                top += range.y;
              }
            } else {
              height -= range.y;
              top += range.y;
            }
          }

          if (width < 0 && height < 0) {
            action = ACTION_SOUTH_EAST;
            height = 0;
            width = 0;
          } else if (width < 0) {
            action = ACTION_NORTH_EAST;
            width = 0;
          } else if (height < 0) {
            action = ACTION_SOUTH_WEST;
            height = 0;
          }

          break;

        case ACTION_SOUTH_WEST:
          if (aspectRatio) {
            if (range.x <= 0 && (left <= minLeft || bottom >= maxHeight)) {
              renderable = false;
              break;
            }

            width -= range.x;
            left += range.x;
            height = width / aspectRatio;
          } else {
            if (range.x <= 0) {
              if (left > minLeft) {
                width -= range.x;
                left += range.x;
              } else if (range.y >= 0 && bottom >= maxHeight) {
                renderable = false;
              }
            } else {
              width -= range.x;
              left += range.x;
            }

            if (range.y >= 0) {
              if (bottom < maxHeight) {
                height += range.y;
              }
            } else {
              height += range.y;
            }
          }

          if (width < 0 && height < 0) {
            action = ACTION_NORTH_EAST;
            height = 0;
            width = 0;
          } else if (width < 0) {
            action = ACTION_SOUTH_EAST;
            width = 0;
          } else if (height < 0) {
            action = ACTION_NORTH_WEST;
            height = 0;
          }

          break;

        case ACTION_SOUTH_EAST:
          if (aspectRatio) {
            if (range.x >= 0 && (right >= maxWidth || bottom >= maxHeight)) {
              renderable = false;
              break;
            }

            width += range.x;
            height = width / aspectRatio;
          } else {
            if (range.x >= 0) {
              if (right < maxWidth) {
                width += range.x;
              } else if (range.y >= 0 && bottom >= maxHeight) {
                renderable = false;
              }
            } else {
              width += range.x;
            }

            if (range.y >= 0) {
              if (bottom < maxHeight) {
                height += range.y;
              }
            } else {
              height += range.y;
            }
          }

          if (width < 0 && height < 0) {
            action = ACTION_NORTH_WEST;
            height = 0;
            width = 0;
          } else if (width < 0) {
            action = ACTION_SOUTH_WEST;
            width = 0;
          } else if (height < 0) {
            action = ACTION_NORTH_EAST;
            height = 0;
          }

          break;

        // Move canvas
        case ACTION_MOVE:
          this.move(range.x, range.y);
          renderable = false;
          break;

        // Zoom canvas
        case ACTION_ZOOM:
          this.zoom((function (x1, y1, x2, y2) {
            var z1 = sqrt(x1 * x1 + y1 * y1);
            var z2 = sqrt(x2 * x2 + y2 * y2);

            return (z2 - z1) / z1;
          })(
            abs(this.startX - this.startX2),
            abs(this.startY - this.startY2),
            abs(this.endX - this.endX2),
            abs(this.endY - this.endY2)
          ), originalEvent);
          this.startX2 = this.endX2;
          this.startY2 = this.endY2;
          renderable = false;
          break;

        // Create crop box
        case ACTION_CROP:
          if (range.x && range.y) {
            offset = this.$cropper.offset();
            left = this.startX - offset.left;
            top = this.startY - offset.top;
            width = cropBox.minWidth;
            height = cropBox.minHeight;

            if (range.x > 0) {
              if (range.y > 0) {
                action = ACTION_SOUTH_EAST;
              } else {
                action = ACTION_NORTH_EAST;
                top -= height;
              }
            } else {
              if (range.y > 0) {
                action = ACTION_SOUTH_WEST;
                left -= width;
              } else {
                action = ACTION_NORTH_WEST;
                left -= width;
                top -= height;
              }
            }

            // Show the crop box if is hidden
            if (!this.cropped) {
              this.cropped = true;
              this.$cropBox.removeClass(CLASS_HIDDEN);
            }
          }

          break;

        // No default
      }

      if (renderable) {
        cropBox.width = width;
        cropBox.height = height;
        cropBox.left = left;
        cropBox.top = top;
        this.action = action;

        this.renderCropBox();
      }

      // Override
      this.startX = this.endX;
      this.startY = this.endY;
    }
  });
<commit_msg>Improved the experience of cropping<commit_after>  $.extend(prototype, {
    change: function (shiftKey, originalEvent) {
      var options = this.options;
      var aspectRatio = options.aspectRatio;
      var action = this.action;
      var container = this.container;
      var canvas = this.canvas;
      var cropBox = this.cropBox;
      var width = cropBox.width;
      var height = cropBox.height;
      var left = cropBox.left;
      var top = cropBox.top;
      var right = left + width;
      var bottom = top + height;
      var minLeft = 0;
      var minTop = 0;
      var maxWidth = container.width;
      var maxHeight = container.height;
      var renderable = true;
      var offset;
      var range;

      // Locking aspect ratio in "free mode" by holding shift key (#259)
      if (!aspectRatio && shiftKey) {
        aspectRatio = width && height ? width / height : 1;
      }

      if (options.strict) {
        minLeft = cropBox.minLeft;
        minTop = cropBox.minTop;
        maxWidth = minLeft + min(container.width, canvas.width);
        maxHeight = minTop + min(container.height, canvas.height);
      }

      range = {
        x: this.endX - this.startX,
        y: this.endY - this.startY
      };

      if (aspectRatio) {
        range.X = range.y * aspectRatio;
        range.Y = range.x / aspectRatio;
      }

      switch (action) {
        // Move crop box
        case ACTION_ALL:
          left += range.x;
          top += range.y;
          break;

        // Resize crop box
        case ACTION_EAST:
          if (range.x >= 0 && (right >= maxWidth || aspectRatio &&
            (top <= minTop || bottom >= maxHeight))) {

            renderable = false;
            break;
          }

          width += range.x;

          if (aspectRatio) {
            height = width / aspectRatio;
            top -= range.Y / 2;
          }

          if (width < 0) {
            action = ACTION_WEST;
            width = 0;
          }

          break;

        case ACTION_NORTH:
          if (range.y <= 0 && (top <= minTop || aspectRatio &&
            (left <= minLeft || right >= maxWidth))) {

            renderable = false;
            break;
          }

          height -= range.y;
          top += range.y;

          if (aspectRatio) {
            width = height * aspectRatio;
            left += range.X / 2;
          }

          if (height < 0) {
            action = ACTION_SOUTH;
            height = 0;
          }

          break;

        case ACTION_WEST:
          if (range.x <= 0 && (left <= minLeft || aspectRatio &&
            (top <= minTop || bottom >= maxHeight))) {

            renderable = false;
            break;
          }

          width -= range.x;
          left += range.x;

          if (aspectRatio) {
            height = width / aspectRatio;
            top += range.Y / 2;
          }

          if (width < 0) {
            action = ACTION_EAST;
            width = 0;
          }

          break;

        case ACTION_SOUTH:
          if (range.y >= 0 && (bottom >= maxHeight || aspectRatio &&
            (left <= minLeft || right >= maxWidth))) {

            renderable = false;
            break;
          }

          height += range.y;

          if (aspectRatio) {
            width = height * aspectRatio;
            left -= range.X / 2;
          }

          if (height < 0) {
            action = ACTION_NORTH;
            height = 0;
          }

          break;

        case ACTION_NORTH_EAST:
          if (aspectRatio) {
            if (range.y <= 0 && (top <= minTop || right >= maxWidth)) {
              renderable = false;
              break;
            }

            height -= range.y;
            top += range.y;
            width = height * aspectRatio;
          } else {
            if (range.x >= 0) {
              if (right < maxWidth) {
                width += range.x;
              } else if (range.y <= 0 && top <= minTop) {
                renderable = false;
              }
            } else {
              width += range.x;
            }

            if (range.y <= 0) {
              if (top > minTop) {
                height -= range.y;
                top += range.y;
              }
            } else {
              height -= range.y;
              top += range.y;
            }
          }

          if (width < 0 && height < 0) {
            action = ACTION_SOUTH_WEST;
            height = 0;
            width = 0;
          } else if (width < 0) {
            action = ACTION_NORTH_WEST;
            width = 0;
          } else if (height < 0) {
            action = ACTION_SOUTH_EAST;
            height = 0;
          }

          break;

        case ACTION_NORTH_WEST:
          if (aspectRatio) {
            if (range.y <= 0 && (top <= minTop || left <= minLeft)) {
              renderable = false;
              break;
            }

            height -= range.y;
            top += range.y;
            width = height * aspectRatio;
            left += range.X;
          } else {
            if (range.x <= 0) {
              if (left > minLeft) {
                width -= range.x;
                left += range.x;
              } else if (range.y <= 0 && top <= minTop) {
                renderable = false;
              }
            } else {
              width -= range.x;
              left += range.x;
            }

            if (range.y <= 0) {
              if (top > minTop) {
                height -= range.y;
                top += range.y;
              }
            } else {
              height -= range.y;
              top += range.y;
            }
          }

          if (width < 0 && height < 0) {
            action = ACTION_SOUTH_EAST;
            height = 0;
            width = 0;
          } else if (width < 0) {
            action = ACTION_NORTH_EAST;
            width = 0;
          } else if (height < 0) {
            action = ACTION_SOUTH_WEST;
            height = 0;
          }

          break;

        case ACTION_SOUTH_WEST:
          if (aspectRatio) {
            if (range.x <= 0 && (left <= minLeft || bottom >= maxHeight)) {
              renderable = false;
              break;
            }

            width -= range.x;
            left += range.x;
            height = width / aspectRatio;
          } else {
            if (range.x <= 0) {
              if (left > minLeft) {
                width -= range.x;
                left += range.x;
              } else if (range.y >= 0 && bottom >= maxHeight) {
                renderable = false;
              }
            } else {
              width -= range.x;
              left += range.x;
            }

            if (range.y >= 0) {
              if (bottom < maxHeight) {
                height += range.y;
              }
            } else {
              height += range.y;
            }
          }

          if (width < 0 && height < 0) {
            action = ACTION_NORTH_EAST;
            height = 0;
            width = 0;
          } else if (width < 0) {
            action = ACTION_SOUTH_EAST;
            width = 0;
          } else if (height < 0) {
            action = ACTION_NORTH_WEST;
            height = 0;
          }

          break;

        case ACTION_SOUTH_EAST:
          if (aspectRatio) {
            if (range.x >= 0 && (right >= maxWidth || bottom >= maxHeight)) {
              renderable = false;
              break;
            }

            width += range.x;
            height = width / aspectRatio;
          } else {
            if (range.x >= 0) {
              if (right < maxWidth) {
                width += range.x;
              } else if (range.y >= 0 && bottom >= maxHeight) {
                renderable = false;
              }
            } else {
              width += range.x;
            }

            if (range.y >= 0) {
              if (bottom < maxHeight) {
                height += range.y;
              }
            } else {
              height += range.y;
            }
          }

          if (width < 0 && height < 0) {
            action = ACTION_NORTH_WEST;
            height = 0;
            width = 0;
          } else if (width < 0) {
            action = ACTION_SOUTH_WEST;
            width = 0;
          } else if (height < 0) {
            action = ACTION_NORTH_EAST;
            height = 0;
          }

          break;

        // Move canvas
        case ACTION_MOVE:
          this.move(range.x, range.y);
          renderable = false;
          break;

        // Zoom canvas
        case ACTION_ZOOM:
          this.zoom((function (x1, y1, x2, y2) {
            var z1 = sqrt(x1 * x1 + y1 * y1);
            var z2 = sqrt(x2 * x2 + y2 * y2);

            return (z2 - z1) / z1;
          })(
            abs(this.startX - this.startX2),
            abs(this.startY - this.startY2),
            abs(this.endX - this.endX2),
            abs(this.endY - this.endY2)
          ), originalEvent);
          this.startX2 = this.endX2;
          this.startY2 = this.endY2;
          renderable = false;
          break;

        // Create crop box
        case ACTION_CROP:
          if (range.x || range.y) {
            offset = this.$cropper.offset();
            left = this.startX - offset.left;
            top = this.startY - offset.top;
            width = cropBox.minWidth;
            height = cropBox.minHeight;

            if (range.x > 0) {
              action = range.y > 0 ? ACTION_SOUTH_EAST :
                (range.y < 0 ? ACTION_NORTH_EAST : ACTION_EAST);
            } else if (range.x < 0) {
              left -= width;
              action = range.y > 0 ? ACTION_SOUTH_WEST :
                (range.y < 0 ? ACTION_NORTH_WEST : ACTION_WEST);
            } else {
              action = range.y > 0 ? ACTION_SOUTH : ACTION_NORTH;
            }

            if (range.y < 0) {
              top -= height;
            }

            // Show the crop box if is hidden
            if (!this.cropped) {
              this.cropped = true;
              this.$cropBox.removeClass(CLASS_HIDDEN);
            }
          }

          break;

        // No default
      }

      if (renderable) {
        cropBox.width = width;
        cropBox.height = height;
        cropBox.left = left;
        cropBox.top = top;
        this.action = action;

        this.renderCropBox();
      }

      // Override
      this.startX = this.endX;
      this.startY = this.endY;
    }
  });
<|endoftext|>


<commit_before>//todo, document functions
/**
 * declaration
 */
var _chorus = window.chorus = {
    /**
     * core
     * (in order of include via grunt compile)
     */
    init: function(){return undefined;},
    data: {},
    events: {},
    logic: {
        //core
        helpers:{},
        notes:{},
        //modules,
        scales:{},
        chords:{}
    },
    layout: {},
    /**
     * config
     */
    //default search result holder
    searchResult: {},
    //default config object
    defaultConfig: {},
    //any user set config options will be saved here with defaults for any unset options
    config: {}
};

/**
 * search result
 */
(function(){
    this.scales = {};
    this.chords = {};
    this.containers = [];
}).apply(_chorus.searchResult);

/**
 * default config
 */
(function(){
    //logic
    this.searchCallback = '';
    this.flattenSearchResults = false;
    //layout
    this.layoutInstrument ='main';
    this.layoutInstrumentTitles = false;
    this.layoutInstrumentTitleElement ='h2';
    this.layoutInstrumentStringOrder ='desc';
    this.layoutContainerId = '';
    this.layoutContainerClass = '';
    this.searchButton = true;
    this.searchText = 'Search Chorus';
    //developer
    this.chorusDebug ='none';
}).apply(_chorus.defaultConfig);

(function(){
    //variables
    this.currentConfig = '';

    //functions
    this.resetCurrentConfig = function(){
        this.currentConfig = '';
    };

}).apply(_chorus.config);

/**
 * sets config values and creates instrument grid
 * @type {Function|*}
 */
_chorus.init = function(element, userConfig) {
    var events = _chorus.events,
        layout = _chorus.layout,
        dictionary = _chorus.data.dictionary,
        customEvents = _chorus.data.customEvents,
        configId = layout.getRandomId(),
        config = _chorus.config,
        helpers = _chorus.logic.helpers,
        domContainers;
    events.listeners.init();
    config.currentConfig = configId;
    config[configId] = _chorus.logic.helpers.cloneObject(_chorus.defaultConfig);
    if (userConfig !== undefined) {
        for (var key in userConfig) {
            if (userConfig.hasOwnProperty(key) && config[configId].hasOwnProperty(key)) {
                config[configId][key] = userConfig[key];
            }
        }
    }
    if (element !== undefined) {
        domContainers = helpers.getDomRepresentationFromStringName(element);
        if (domContainers && domContainers.length > 0) {
            domContainers.forEach(function(entry){
                layout.init(entry);
            });
            config.resetCurrentConfig();
            events.dispatchEvent(customEvents.chorusInitComplete, 'chorusJS has finished initialization');
        }
        else {
            events.sendMessage(dictionary.errorNotFound + 'no elements found using init value');
            return false;
        }
    } else {
        events.sendMessage(dictionary.errorUndefined + 'no element defined in init');
        return false;
    }
};

/**
 * top level methods exposing lower level functions
 */
_chorus.search = function(tones, container, callback){
    _chorus.logic.helpers.searchScalesAndChords(tones,container,callback);
};
_chorus.searchScales = function(tones, container, callback){
    _chorus.logic.scales.searchScales(tones, container, false, callback);
};
_chorus.searchChords = function(tones, container, callback){
    _chorus.logic.chords.searchChords(tones, container, false, callback);
};
_chorus.applyResults = function(tones, container){
    _chorus.layout.html.applyResults(tones, container);
};
_chorus.populateLists = function(scaleResultContainer,chordResultContainer){
    _chorus.layout.html.populateLists(scaleResultContainer, chordResultContainer);
};<commit_msg>started documentation<commit_after>/**
 * declaration
 */
var _chorus = window.chorus = {
    /**
     * core
     * (in order of include via grunt compile)
     */
    init: function(){return undefined;},
    data: {},
    events: {},
    logic: {
        //core
        helpers:{},
        notes:{},
        //modules,
        scales:{},
        chords:{}
    },
    layout: {},
    /**
     * config
     */
    //default search result holder
    searchResult: {},
    //default config object
    defaultConfig: {},
    //any user set config options will be saved here with defaults for any unset options
    config: {}
};

/**
 * search result
 */
(function(){
    this.scales = {};
    this.chords = {};
    this.containers = [];
}).apply(_chorus.searchResult);

/**
 * default config
 */
(function(){
    //logic
    this.searchCallback = '';
    this.flattenSearchResults = false;
    //layout
    this.layoutInstrument ='main';
    this.layoutInstrumentTitles = false;
    this.layoutInstrumentTitleElement ='h2';
    this.layoutInstrumentStringOrder ='desc';
    this.layoutContainerId = '';
    this.layoutContainerClass = '';
    this.searchButton = true;
    this.searchText = 'Search Chorus';
    //developer
    this.chorusDebug ='none';
}).apply(_chorus.defaultConfig);

(function(){
    //variables
    this.currentConfig = '';

    //functions
    /**
     * reset current config object (used in search functions)
     */
    this.resetCurrentConfig = function(){
        this.currentConfig = '';
    };

}).apply(_chorus.config);

/**
 * sets config values and creates instrument grid
 * @param {string} element id or classname for html dom element to initialize chorus in
 * @param {Object} userConfig object with key:value pairs to replace default config
 */
_chorus.init = function(element, userConfig) {
    var events = _chorus.events,
        layout = _chorus.layout,
        dictionary = _chorus.data.dictionary,
        customEvents = _chorus.data.customEvents,
        configId = layout.getRandomId(),
        config = _chorus.config,
        helpers = _chorus.logic.helpers,
        domContainers;
    events.listeners.init();
    config.currentConfig = configId;
    config[configId] = _chorus.logic.helpers.cloneObject(_chorus.defaultConfig);
    if (userConfig !== undefined) {
        for (var key in userConfig) {
            if (userConfig.hasOwnProperty(key) && config[configId].hasOwnProperty(key)) {
                config[configId][key] = userConfig[key];
            }
        }
    }
    if (element !== undefined) {
        domContainers = helpers.getDomRepresentationFromStringName(element);
        if (domContainers && domContainers.length > 0) {
            domContainers.forEach(function(entry){
                layout.init(entry);
            });
            config.resetCurrentConfig();
            events.dispatchEvent(customEvents.chorusInitComplete, 'chorusJS has finished initialization');
        }
        else {
            events.sendMessage(dictionary.errorNotFound + 'no elements found using init value');
        }
    } else {
        events.sendMessage(dictionary.errorUndefined + 'no element defined in init');
    }
};

/**
 * top level methods exposing lower level functions
 */

/**
 * search all modules based on provided tones or tones found in the container
 * @param {Array} [tones] array of tone ints
 * @param {string} [container] id or classname for html dom element to search in
 * @param {function} [callback] function to call after search is complete
 */
_chorus.search = function(tones, container, callback){
    _chorus.logic.helpers.searchScalesAndChords(tones,container,callback);
};

/**
 * search the scales module based on provided tones or tones found in the container
 * @param {Array} [tones] array of tone ints
 * @param {string} [container] id or classname for html dom element to search in
 * @param {function} [callback] function to call after search is complete
 */
_chorus.searchScales = function(tones, container, callback){
    _chorus.logic.scales.searchScales(tones, container, false, callback);
};

/**
 * search the chords module based on provided tones or tones found in the container
 * @param {Array} [tones] array of tone ints
 * @param {string} [container] id or classname for html dom element to search in
 * @param {function} [callback] function to call after search is complete
 */
_chorus.searchChords = function(tones, container, callback){
    _chorus.logic.chords.searchChords(tones, container, false, callback);
};

/**
 * apply tone results to selected instrument container
 * @param {Array} [tones] array of tone ints
 * @param {string} container id or classname for html dom element to apply tones to
 */
_chorus.applyResults = function(tones, container){
    _chorus.layout.html.applyResults(tones, container);
};

/**
 * insert results into containers
 * @param {string} [scaleResultContainer] container id or classname for html dom element to insert results in
 * @param {string} [chordResultContainer] container id or classname for html dom element to insert results in
 */
_chorus.populateLists = function(scaleResultContainer,chordResultContainer){
    _chorus.layout.html.populateLists(scaleResultContainer, chordResultContainer);
};<|endoftext|>


<commit_before>/*
 * Auto-attach ajax for forms
 *
 * <form action="..." method="post" data-ajaxsend data-options="...">
 * ...
 * <input type="submit" name="submit" value="Send" />
 * </form>
 *
 */

$(function(){

    // using jQuery
    function getCookie(name) {
        var cookieValue = null;
        if (document.cookie && document.cookie != '') {
            var cookies = document.cookie.split(';');
            for (var i = 0; i < cookies.length; i++) {
                var cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) == (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }
    var csrftoken = getCookie('csrftoken');

    function csrfSafeMethod(method) {
        // these HTTP methods do not require CSRF protection
        return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
    }
    function sameOrigin(url) {
        // test that a given url is a same-origin URL
        // url could be relative or scheme relative or absolute
        var host = document.location.host; // host + port
        var protocol = document.location.protocol;
        var sr_origin = '//' + host;
        var origin = protocol + sr_origin;
        // Allow absolute or scheme relative URLs to same origin
        return (url == origin || url.slice(0, origin.length + 1) == origin + '/') ||
            (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') ||
            // or any other URL that isn't scheme relative or absolute i.e relative.
            !(/^(\/\/|http:|https:).*/.test(url));
    }

    $("form[data-ajaxsend]").each(function(){

        var options = $(this).attr('data-ajaxsend-options') || "";
        if(options){
            options = options.split(';');
            var r = {};
            options.forEach(function(a){
                var b = a.trim().split(':');
                r[b[0]] = (b[1]||'').trim();
                return r
            });
        }

        options = $.extend({
            done_element: this,
            done_event: 'done',
            fail_element: this,
            fail_event: 'fail',
            //complete_element: this,
            //complete_event: 'complete',
            sending_element: this,
            sending_event: 'sending',
            beforesend_element: this,
            beforesend_event: 'beforesend',
            loading_img: 'http://s11.postimg.org/lb6hylp9r/loading.gif'
        }, r);

        var i = new Image();
        i.src = decodeURIComponent(options.loading_img);

        $(this).append($($("
").append(i)).hide());

        function disabled(form){
            form.find('input,textarea,select').attr('disabled', true);
        }

        function enabled(form){
            form.find('input,textarea,select').attr('disabled', false);
        }

        // capture submit
        $(this).on('submit', function(event){
            event.preventDefault();
            var $form = $(this);
            var formData = $form.serialize();

            /* si hay un file entre los campos usa el otro metodo */
            var filesInput = $form.find('input[type=file]');

            if(filesInput.length){

                // Create a new FormData object.
                formData = new FormData();

                for (var f = 0; f < filesInput.length; f++) {

                    // Loop through each of the selected files.
                    for (var i = 0; i < filesInput[f].files.length; i++) {
                      var file = filesInput[f].files[i];

                      // Add the file to the request.
                      formData.append(filesInput[f].name, file, file.name);
                    }
                }

                var formDataString = $form.serializeArray();

                for (var i = 0; i < formDataString.length; i++) {
                    formData.append(formDataString[i].name, formDataString[i].value);
                }
            }

            var xhr = $.ajax({
                type: 'POST',
                url: $form.attr('action'),
                data: formData,
                dataType: 'json',
                global: false,
                cache: false,
                //contentType: false, /* bug? no termina de procesar los datos la vista de django y POST viaja vacio */
                processData: false,
                beforeSend: function(jqXHR, settings){
                    $form.find('.loading').show();
                    disabled($form);
                    console.log('loading...');
                    $(options.beforesend_element).trigger(options.beforesend_event, jqXHR);

                    // CSRFToken protect
                    if (csrftoken && !csrfSafeMethod(settings.type) && sameOrigin(settings.url)) {
                        jqXHR.setRequestHeader("X-CSRFToken", csrftoken);
                    }
                }
            });

            xhr.always(function(response){
                $form.find('.loading').hide();
                console.log('stop');
                enabled($form);
                $(options.sending_element).trigger(options.sending_event, response);
            });

            xhr.done(function(data, textStatus, jqXHR){
                $(options.done_element).trigger(options.done_event, {data:data, textStatus:textStatus, jqXHR:jqXHR});
                $form.trigger('reset'); // clean
            });

            xhr.fail(function(jqXHR, textStatus, errorThrown){
                $(options.fail_element, $form).trigger(options.fail_event, {jqXHR:jqXHR, textStatus:textStatus, errorThrown:errorThrown});
                try {
                    $.each(jqXHR.responseJSON.errors, function(key, val){
                        $form
                            .find("*[name="+key+"]")
                            .addClass('error')
                            .next('.error')
                            .css('display', 'block')
                            .text(val[0]);
                    });
                } catch(E){};
            });

            return false;
        });

        // tracking para google analytics
        // on submit
        $(this).on('done', function(event){
            var $form = $(this);
            var action = $form.attr('action') + '/submited/';
            var custom_tarck_pageview = $form.data('track_pageview');
            var track_pageview = action || custom_tarck_pageview;

            if(window._gaq){
                _gaq.push(['_trackPageview', track_pageview]);
            } else if(window.ga) {
                ga('send', 'pageview', track_pageview);
            }
        });
    });

    if (window.ga || window._gaq) {
      $("form").each(function() {
        var $form = $(this);

        var formName = $(this).attr('name') || $(this).attr('id');

        $('input, textarea, select', $form).on('blur', function() {
          var fieldName = $(this).attr('name');
          if(window._gaq){
            _gaq.push(['_trackEvent', formName, 'completed', fieldName]);
          } else if(window.ga) {
            ga('send', 'event', formName, 'completed', fieldName);
          }
        });
      });
    }
});
<commit_msg>deprecate form events<commit_after>/*
 * Auto-attach ajax for forms
 *
 * <form action="..." method="post" data-ajaxsend data-options="...">
 * ...
 * <input type="submit" name="submit" value="Send" />
 * </form>
 *
 */

$(function(){

    // using jQuery
    function getCookie(name) {
        var cookieValue = null;
        if (document.cookie && document.cookie != '') {
            var cookies = document.cookie.split(';');
            for (var i = 0; i < cookies.length; i++) {
                var cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) == (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }
    var csrftoken = getCookie('csrftoken');

    function csrfSafeMethod(method) {
        // these HTTP methods do not require CSRF protection
        return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
    }
    function sameOrigin(url) {
        // test that a given url is a same-origin URL
        // url could be relative or scheme relative or absolute
        var host = document.location.host; // host + port
        var protocol = document.location.protocol;
        var sr_origin = '//' + host;
        var origin = protocol + sr_origin;
        // Allow absolute or scheme relative URLs to same origin
        return (url == origin || url.slice(0, origin.length + 1) == origin + '/') ||
            (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') ||
            // or any other URL that isn't scheme relative or absolute i.e relative.
            !(/^(\/\/|http:|https:).*/.test(url));
    }

    $("form[data-ajaxsend]").each(function(){

        var options = $(this).attr('data-ajaxsend-options') || "";
        if(options){
            options = options.split(';');
            var r = {};
            options.forEach(function(a){
                var b = a.trim().split(':');
                r[b[0]] = (b[1]||'').trim();
                return r
            });
        }

        options = $.extend({
            done_element: this,
            done_event: 'done',
            fail_element: this,
            fail_event: 'fail',
            //complete_element: this,
            //complete_event: 'complete',
            sending_element: this,
            sending_event: 'sending',
            beforesend_element: this,
            beforesend_event: 'beforesend',
            loading_img: 'http://s11.postimg.org/lb6hylp9r/loading.gif'
        }, r);

        var i = new Image();
        i.src = decodeURIComponent(options.loading_img);

        $(this).append($($("
").append(i)).hide());

        function disabled(form){
            form.find('input,textarea,select').attr('disabled', true);
        }

        function enabled(form){
            form.find('input,textarea,select').attr('disabled', false);
        }

        // capture submit
        $(this).on('submit', function(event){
            event.preventDefault();
            var $form = $(this);
            var formData = $form.serialize();

            /* si hay un file entre los campos usa el otro metodo */
            var filesInput = $form.find('input[type=file]');

            if(filesInput.length){

                // Create a new FormData object.
                formData = new FormData();

                for (var f = 0; f < filesInput.length; f++) {

                    // Loop through each of the selected files.
                    for (var i = 0; i < filesInput[f].files.length; i++) {
                      var file = filesInput[f].files[i];

                      // Add the file to the request.
                      formData.append(filesInput[f].name, file, file.name);
                    }
                }

                var formDataString = $form.serializeArray();

                for (var i = 0; i < formDataString.length; i++) {
                    formData.append(formDataString[i].name, formDataString[i].value);
                }
            }

            var xhr = $.ajax({
                type: 'POST',
                url: $form.attr('action'),
                data: formData,
                dataType: 'json',
                global: false,
                cache: false,
                //contentType: false, /* bug? no termina de procesar los datos la vista de django y POST viaja vacio */
                processData: false,
                beforeSend: function(jqXHR, settings){
                    $form.find('.loading').show();
                    disabled($form);
                    console.log('loading...');
                    $(options.beforesend_element).trigger(options.beforesend_event, jqXHR);

                    // CSRFToken protect
                    if (csrftoken && !csrfSafeMethod(settings.type) && sameOrigin(settings.url)) {
                        jqXHR.setRequestHeader("X-CSRFToken", csrftoken);
                    }
                }
            });

            xhr.always(function(response){
                $form.find('.loading').hide();
                console.log('stop');
                enabled($form);
                $(options.sending_element).trigger(options.sending_event, response);
            });

            xhr.done(function(data, textStatus, jqXHR){
                $(options.done_element).trigger(options.done_event, {data:data, textStatus:textStatus, jqXHR:jqXHR});
                $form.trigger('reset'); // clean
            });

            xhr.fail(function(jqXHR, textStatus, errorThrown){
                $(options.fail_element, $form).trigger(options.fail_event, {jqXHR:jqXHR, textStatus:textStatus, errorThrown:errorThrown});
                try {
                    $.each(jqXHR.responseJSON.errors, function(key, val){
                        $form
                            .find("*[name="+key+"]")
                            .addClass('error')
                            .next('.error')
                            .css('display', 'block')
                            .text(val[0]);
                    });
                } catch(E){};
            });

            return false;
        });

        // tracking para google analytics
        // on submit
        $(this).on('done', function(event){
            var $form = $(this);
            var action = $form.attr('action') + '/submited/';
            var custom_tarck_pageview = $form.data('track_pageview');
            var track_pageview = action || custom_tarck_pageview;

            if(window._gaq){
                _gaq.push(['_trackPageview', track_pageview]);
            } else if(window.ga) {
                ga('send', 'pageview', track_pageview);
            }
        });
    });

    /* Tracking para campos de formularios DEPRECADO -> hotjar */
    /*
    if (window.ga || window._gaq) {
      $("form").each(function() {
        var $form = $(this);

        var formName = $(this).attr('name') || $(this).attr('id');

        $('input, textarea, select', $form).on('blur', function() {
          var fieldName = $(this).attr('name');
          if(window._gaq){
            _gaq.push(['_trackEvent', formName, 'completed', fieldName]);
          } else if(window.ga) {
            ga('send', 'event', formName, 'completed', fieldName);
          }
        });
      });
    }
    */
});
<|endoftext|>


<commit_before>// TODO Enter config as JSON
// TODO Maybe merge general and rules
// TODO Use immediate object initialization pattern
// Full config
TRIPLET.config = (function() {

  var random = TRIPLET.utilities.random;

  function Randomizer(type, arg) {
    if (type in random) return random[type].bind(null, arg);
    else throw new Error('Wrong type of random function: ' + type);
  }

  var config = {

    general: {
      rows: 5, columns: 5, defaultRowsCols: 3, size: 420,
      top: 20, right: 20, bottom: 20, left: 20
    },

    rules: {
      emptyVal: 9, signsPerRound: 1, winLength: 4
    },

    players: [
      { name: 'Ann', ai: 'hard', signID: 0, color: 'ff0000' },
      { name: 'Bob', ai: 'none', signID: 1, color: '0000ff' }
    ],

    element: {
      line: {
        random: Object.defineProperties({}, {
          'imgID': { get: Randomizer('item', [0, 1, 2, 3]) },
          'move': { get: Randomizer('error', 8) },
          'rotate': { get: Randomizer('error', 0.1) },
          'scale': { get: Randomizer('error', 0.1) }
        }),
        frames: { inRow: 1, total: 6, delay: 25 },
        pause: 40
      },
      sign: {
        random: Object.defineProperties({}, {
          'imgID': {
            value: Object.defineProperties([], {
              '0': { get: Randomizer('item', [4]) },
              '1': { get: Randomizer('item', [5]) }
            })
          },
          'move': { get: Randomizer('error', 8) },
          'rotate': { get: Randomizer('error', 0.1) },
          'scale': { get: Randomizer('error', 0.1) }
        }),
        frames: { inRow: 1, total: 1, delay: 0 },
        pause: 200
      }
    },

    assets: {
      images: [
        'https://dl.dropboxusercontent.com/s/wzy9flotdl1ba74/line_0an.png',
        'https://dl.dropboxusercontent.com/s/gfe69iq6uwi4m4j/line_1an.png',
        'https://dl.dropboxusercontent.com/s/2m2w25o8l9hqguv/line_2an.png',
        'https://dl.dropboxusercontent.com/s/gzkhrchd9vmfcqk/line_3an.png',
        'https://dl.dropboxusercontent.com/s/wecnycckk5c3rij/signx_0.png',
        'https://dl.dropboxusercontent.com/s/jfzgiqfaxe8q9al/signo_0.png'
      ]
    },

    ai: {
      none: {
        score: {
          sign: { own: 0, enemy: 0, mainEnemy: 0 }, win: 0, tie: 0
        }, depth: 0, infelicity: 0
      },
      hard: {
        score: {
          sign: { own: 6, enemy: 4, mainEnemy: 5 }, win: 100000, tie: 100
        }, depth: 4, infelicity: 2
      },
      normal: {
        score: {
          sign: { own: 5, enemy: 5, mainEnemy: 5 }, win: 10000, tie: 50
        }, depth: 1, infelicity: 5
      },
      easy: {
        score: {
          sign: { own: 5, enemy: 5, mainEnemy: 5 }, win: 100, tie: 10
        }, depth: 0, infelicity: 30
      }
    }

  };

  (function(cfg) {
    cfg.maxLineLength = Math.min(cfg.rows, cfg.columns);
    cfg.maxTurns = cfg.rows * cfg.columns;
  })(config.general);

  (function(cfg) {
    cfg.turnsPerRound = config.players.length * cfg.signsPerRound;
  })(config.rules);

  return config;

})();
<commit_msg>Editor test<commit_after>// TODO Enter config as JSON
// TODO Maybe merge general and rules
// TODO Use immediate object initialization pattern here
// Full config
TRIPLET.config = (function() {

  var random = TRIPLET.utilities.random;

  function Randomizer(type, arg) {
    if (type in random) return random[type].bind(null, arg);
    else throw new Error('Wrong type of random function: ' + type);
  }

  var config = {

    general: {
      rows: 5, columns: 5, defaultRowsCols: 3, size: 420,
      top: 20, right: 20, bottom: 20, left: 20
    },

    rules: {
      emptyVal: 9, signsPerRound: 1, winLength: 4
    },

    players: [
      { name: 'Ann', ai: 'hard', signID: 0, color: 'ff0000' },
      { name: 'Bob', ai: 'none', signID: 1, color: '0000ff' }
    ],

    element: {
      line: {
        random: Object.defineProperties({}, {
          'imgID': { get: Randomizer('item', [0, 1, 2, 3]) },
          'move': { get: Randomizer('error', 8) },
          'rotate': { get: Randomizer('error', 0.1) },
          'scale': { get: Randomizer('error', 0.1) }
        }),
        frames: { inRow: 1, total: 6, delay: 25 },
        pause: 40
      },
      sign: {
        random: Object.defineProperties({}, {
          'imgID': {
            value: Object.defineProperties([], {
              '0': { get: Randomizer('item', [4]) },
              '1': { get: Randomizer('item', [5]) }
            })
          },
          'move': { get: Randomizer('error', 8) },
          'rotate': { get: Randomizer('error', 0.1) },
          'scale': { get: Randomizer('error', 0.1) }
        }),
        frames: { inRow: 1, total: 1, delay: 0 },
        pause: 200
      }
    },

    assets: {
      images: [
        'https://dl.dropboxusercontent.com/s/wzy9flotdl1ba74/line_0an.png',
        'https://dl.dropboxusercontent.com/s/gfe69iq6uwi4m4j/line_1an.png',
        'https://dl.dropboxusercontent.com/s/2m2w25o8l9hqguv/line_2an.png',
        'https://dl.dropboxusercontent.com/s/gzkhrchd9vmfcqk/line_3an.png',
        'https://dl.dropboxusercontent.com/s/wecnycckk5c3rij/signx_0.png',
        'https://dl.dropboxusercontent.com/s/jfzgiqfaxe8q9al/signo_0.png'
      ]
    },

    ai: {
      none: {
        score: {
          sign: { own: 0, enemy: 0, mainEnemy: 0 }, win: 0, tie: 0
        }, depth: 0, infelicity: 0
      },
      hard: {
        score: {
          sign: { own: 6, enemy: 4, mainEnemy: 5 }, win: 100000, tie: 100
        }, depth: 4, infelicity: 2
      },
      normal: {
        score: {
          sign: { own: 5, enemy: 5, mainEnemy: 5 }, win: 10000, tie: 50
        }, depth: 1, infelicity: 5
      },
      easy: {
        score: {
          sign: { own: 5, enemy: 5, mainEnemy: 5 }, win: 100, tie: 10
        }, depth: 0, infelicity: 30
      }
    }

  };

  (function(cfg) {
    cfg.maxLineLength = Math.min(cfg.rows, cfg.columns);
    cfg.maxTurns = cfg.rows * cfg.columns;
  })(config.general);

  (function(cfg) {
    cfg.turnsPerRound = config.players.length * cfg.signsPerRound;
  })(config.rules);

  return config;

})();
<|endoftext|>


<commit_before>/**
 * Handle config and automatically sync to other Chrome profiles
 *
 * Listens for changes to chrome.storage.sync coming from other envirements/computers,
 * overrides the changes with the blacklist. Automatically saves changes to chrome.storage.sync
 * after being overridden with the blacklist.
 */
window.Config = Backbone.Model.extend({
  /**
   * Blacklist of values that should never be user changeable
   */
  blacklist: {
    clientId: 'UnSbSEb6EFHUZt3ygTwPSTdcdGd8Lvey',
    googleAnalyticsAccount: 'UA-2706568-45',
    authorizeUrl: 'https://account.app.net/oauth/authenticate',
    accessTokenUrl: 'https://account.app.net/oauth/access_token',
    apiBaseUrl: 'https://alpha-api.app.net',
    baseUrl: 'https://alpha.app.net',
    apiScope: 'stream,write_post,follow,messages',
  },

  defaults: {
    apiRequestFrequency: 15 * 1000,
    apiFollowersRequestFrequency: 15 * 60 * 1000,
    mentionNotifications: true,
    defaultMentionFrequency: 15 * 1000,
    followerNotifications: true,
    defaultFollowerFrequency: 15 * 60 * 1000,
    autoDismissNotifications: false,
  },

  initialize: function() {
    _.bindAll(this);
    this.on('change', this.override);
    this.override();
    chrome.storage.onChanged.addListener(this.setChomeChangesToModel);
    this.getFromChrome();
    this.on('change', this.setModelChangesToChrome);
  },

  /**
   * Override config with values that should not change
   */
  override: function() {
    this.set(this.blacklist);
  },

  /**
   * Get config values from chrome.storage.sync
   */
  getFromChrome: function() {
    console.log('config.getFromChrome');
    chrome.storage.sync.get('config', this.getFromChromeCallback);
  },

  /**
   * Set changes of chrome.storage.sync to config model
   */
  getFromChromeCallback: function(items){
    console.log('config.getFromChromeCallback', items);
    this.set(items['config']);
    this.trigger('ready');
  },

  /**
   * Set changes of chrome.storage.sync to config model
   */
  setChomeChangesToModel: function(changes, ns) {
    console.log('config.setChomeChangesToModel', changes, ns);
    // Ignore local changes
    if (ns != 'sync') {
      return this;
    }
    if (changes['config']) {
      console.log('config.setChomeChangesToModel:set', changes['config']);
      this.set(changes['config'].newValue);
    }
  },

  /**
   * Set changes of config model to chrome.storage.sync
   *
   * Trigger at most once every five seconds
   */
   setModelChangesToChrome: _.debounce(function(model, options) {
    console.log('config.setModelChangesToChrome', model, options);
    chrome.storage.sync.set({'config': this.attributes});
  }, 5000),
});

window.config = new Config();
<commit_msg>Documented config initialize function<commit_after>/**
 * Handle config and automatically sync to other Chrome profiles
 *
 * Listens for changes to chrome.storage.sync coming from other envirements/computers,
 * overrides the changes with the blacklist. Automatically saves changes to chrome.storage.sync
 * after being overridden with the blacklist.
 */
window.Config = Backbone.Model.extend({

  /**
   * Blacklist of values that should never be user changeable
   */
  blacklist: {
    clientId: 'UnSbSEb6EFHUZt3ygTwPSTdcdGd8Lvey',
    googleAnalyticsAccount: 'UA-2706568-45',
    authorizeUrl: 'https://account.app.net/oauth/authenticate',
    accessTokenUrl: 'https://account.app.net/oauth/access_token',
    apiBaseUrl: 'https://alpha-api.app.net',
    baseUrl: 'https://alpha.app.net',
    apiScope: 'stream,write_post,follow,messages',
  },

  defaults: {
    apiRequestFrequency: 15 * 1000,
    apiFollowersRequestFrequency: 15 * 60 * 1000,
    mentionNotifications: true,
    defaultMentionFrequency: 15 * 1000,
    followerNotifications: true,
    defaultFollowerFrequency: 15 * 60 * 1000,
    autoDismissNotifications: false,
  },

  initialize: function() {
    // Set up `this` to work in every method
    _.bindAll(this);
    // When the model changes override blacklist values
    this.on('change', this.override);
    this.override();
    // Listen for changes to chrome.storage and propigate to model
    chrome.storage.onChanged.addListener(this.setChomeChangesToModel);
    // Get current values from chrome.storage
    this.getFromChrome();
    // On change save new values to chrome.storage
    this.on('change', this.setModelChangesToChrome);
  },

  /**
   * Override config with values that should not change
   */
  override: function() {
    this.set(this.blacklist);
  },

  /**
   * Get config values from chrome.storage.sync
   */
  getFromChrome: function() {
    console.log('config.getFromChrome');
    chrome.storage.sync.get('config', this.getFromChromeCallback);
  },

  /**
   * Set changes of chrome.storage.sync to config model
   */
  getFromChromeCallback: function(items){
    console.log('config.getFromChromeCallback', items);
    this.set(items['config']);
    this.trigger('ready');
  },

  /**
   * Set changes of chrome.storage.sync to config model
   */
  setChomeChangesToModel: function(changes, ns) {
    console.log('config.setChomeChangesToModel', changes, ns);
    // Ignore local changes
    if (ns != 'sync') {
      return this;
    }
    if (changes['config']) {
      console.log('config.setChomeChangesToModel:set', changes['config']);
      this.set(changes['config'].newValue);
    }
  },

  /**
   * Set changes of config model to chrome.storage.sync
   *
   * Trigger at most once every five seconds
   */
   setModelChangesToChrome: _.debounce(function(model, options) {
    console.log('config.setModelChangesToChrome', model, options);
    chrome.storage.sync.set({'config': this.attributes});
  }, 5000),
});

window.config = new Config();
<|endoftext|>


<commit_before>/*
	Typographie, v1.0
	https://github.com/asleepwalker/typographie

	by Artyom "Sleepwalker" Fedosov, 2014
	http://me.asleepwalker.ru/
	mail@asleepwalker.ru
*/

var App = {};
App.in = 'plain';
App.out = 'plain';
App.editor = 'enabled';
App.actions = {'list':['inquot','dash','dblspace','special','math','crrctpunc','crrctspecial','nbsp','hellip','safehtml']};

App.send = function() {
	request = new XMLHttpRequest();
	request.open('POST', 'engine/main.php', true);

	request.onload = function() {
		if (request.status >= 200 && request.status < 400) window.App.show(JSON.parse(request.responseText));
		else window.App.error();
	};
	request.onerror = window.App.error;

	var data = {};
	data.in = window.App.in;
	data.out = window.App.out;
	data.editor = window.App.editor;
	data.raw = document.getElementById('raw_input').value;
	request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
	request.send('in='+data.in+'&out='+data.out+'&editor='+data.editor+'&raw='+encodeURIComponent(data.raw));
	window.App.state('loading');
};

App.show = function(data) {
	window.App.state('free');
	document.getElementById('display').innerHTML = data.response;
	//enabling editor if need
};

App.error = function() {
	window.App.state('free');
	//error message
};

App.state = function(state) {
	window.App.now_state = state;
	//visual
}; App.state('free');

App.actions.add = function(action) {
	if (App.actions.list.indexOf(action) == -1)
		App.actions.list.push(action);
};

App.actions.remove = function(action) {
	if (App.actions.list.indexOf(action) != -1)
		App.actions.list.splice(App.actions.list.indexOf(action), 1);
};

document.getElementById('submit').onclick = App.send;
var input_modes = document.getElementById('input_mode').getElementsByTagName('li');
for (var i = 0; i < input_modes.length; i++) {
	input_modes[i].onclick = function() {
		if (this.className != 'active') {
			for (var i = 0; i < input_modes.length; i++) { input_modes[i].className = ''; }
			this.className = 'active';
			window.App.in = this.getAttribute('data-mode');
		}
	};
}
var output_modes = document.getElementById('output_mode').getElementsByTagName('li');
for (var i = 0; i < output_modes.length; i++) {
	output_modes[i].onclick = function() {
		if (this.className != 'active') {
			for (var i = 0; i < output_modes.length; i++) { output_modes[i].className = ''; }
			this.className = 'active';
			window.App.out = this.getAttribute('data-mode');
		}
	};
}
document.getElementById('editor_checkbox').onclick = function() {
	if (window.App.editor == 'enabled') {
		document.getElementById('ec_input').className = 'checkbox';
		window.App.editor = 'disabled';
	} else {
		document.getElementById('ec_input').className = 'checkbox checked';
		window.App.editor = 'enabled';
	}
};<commit_msg>Sending actionlist<commit_after>/*
	Typographie, v1.0
	https://github.com/asleepwalker/typographie

	by Artyom "Sleepwalker" Fedosov, 2014
	http://me.asleepwalker.ru/
	mail@asleepwalker.ru
*/

var App = {};
App.in = 'plain';
App.out = 'plain';
App.editor = 'enabled';
App.actions = {'list':['inquot','dash','dblspace','special','math','crrctpunc','crrctspecial','nbsp','hellip','safehtml']};

App.send = function() {
	request = new XMLHttpRequest();
	request.open('POST', 'engine/main.php', true);

	request.onload = function() {
		if (request.status >= 200 && request.status < 400) window.App.show(JSON.parse(request.responseText));
		else window.App.error();
	};
	request.onerror = window.App.error;

	var data = {};
	data.in = window.App.in;
	data.out = window.App.out;
	data.editor = window.App.editor;
	data.actions = window.App.actions.list.join(',');
	data.raw = document.getElementById('raw_input').value;
	request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
	request.send('in='+data.in+'&out='+data.out+'&editor='+data.editor+'&actions='+data.actions+'&raw='+encodeURIComponent(data.raw));
	window.App.state('loading');
};

App.show = function(data) {
	window.App.state('free');
	document.getElementById('display').innerHTML = data.response;
	//enabling editor if need
};

App.error = function() {
	window.App.state('free');
	//error message
};

App.state = function(state) {
	window.App.now_state = state;
	//visual
}; App.state('free');

App.actions.add = function(action) {
	if (App.actions.list.indexOf(action) == -1)
		App.actions.list.push(action);
};

App.actions.remove = function(action) {
	if (App.actions.list.indexOf(action) != -1)
		App.actions.list.splice(App.actions.list.indexOf(action), 1);
};

document.getElementById('submit').onclick = App.send;
var input_modes = document.getElementById('input_mode').getElementsByTagName('li');
for (var i = 0; i < input_modes.length; i++) {
	input_modes[i].onclick = function() {
		if (this.className != 'active') {
			for (var i = 0; i < input_modes.length; i++) { input_modes[i].className = ''; }
			this.className = 'active';
			window.App.in = this.getAttribute('data-mode');
		}
	};
}
var output_modes = document.getElementById('output_mode').getElementsByTagName('li');
for (var i = 0; i < output_modes.length; i++) {
	output_modes[i].onclick = function() {
		if (this.className != 'active') {
			for (var i = 0; i < output_modes.length; i++) { output_modes[i].className = ''; }
			this.className = 'active';
			window.App.out = this.getAttribute('data-mode');
		}
	};
}
document.getElementById('editor_checkbox').onclick = function() {
	if (window.App.editor == 'enabled') {
		document.getElementById('ec_input').className = 'checkbox';
		window.App.editor = 'disabled';
	} else {
		document.getElementById('ec_input').className = 'checkbox checked';
		window.App.editor = 'enabled';
	}
};<|endoftext|>