<commit_before>/**
   Copyright 2015 Couchbase, Inc.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 **/

function createLogsSectionCells (ns, modeCell, stalenessCell, tasksProgressCell, logsSectionTabs, serversCell, isROAdminCell) {
  ns.activeTabCell = Cell.needing(modeCell).compute(function (v, mode) {
    return (mode === "log") || undefined;
  }).name('activeTabCell');

  ns.logsRawCell = Cell.needing(ns.activeTabCell).compute(function (v, active) {
    return future.get({url: "/logs"});
  }).name('logsRawCell');

  ns.logsRawCell.keepValueDuringAsync = true;

  ns.massagedLogsCell = Cell.compute(function (v) {
    var logsValue = v(ns.logsRawCell);
    var stale = v.need(stalenessCell);
    if (logsValue === undefined) {
      if (!stale){
        return;
      }
      logsValue = {list: []};
    }
    return _.extend({}, logsValue, {stale: stale});
  }).name('massagedLogsCell');

  ns.isCollectionInfoTabCell = Cell.compute(function (v) {
    return v.need(logsSectionTabs) === "collection_info" &&
           v.need(ns.activeTabCell) && !v.need(isROAdminCell);
  }).name("isClusterTabCell");
  ns.isCollectionInfoTabCell.equality = _.isEqual;

  ns.tasksCollectionInfoCell = Cell.computeEager(function (v) {
    if (!v.need(ns.isCollectionInfoTabCell)) {
      return null;
    }

    var tasks = v.need(tasksProgressCell);
    var task = _.detect(tasks, function (taskInfo) {
      return taskInfo.type === "clusterLogsCollection";
    });
    if (!task) {
      return {
        nodesByStatus: {},
        nodeErrors: [],
        status: 'idle',
        perNode: {}
      }
    }

    // this is cheap deep cloning of task. We're going to add some
    // attributes there and it's best to avoid mutating part of
    // tasksProgressCell value
    task = JSON.parse(JSON.stringify(task));

    var perNodeHash = task.perNode;
    var perNode = [];

    var cancallable = "starting started startingUpload startedUpload".split(" ");

    var nodes = v.need(serversCell).allNodes;

    _.each(perNodeHash, function (ni, nodeName) {
      var node = _.detect(nodes, function (n) {
        return n.otpNode === nodeName;
      });

      ni.nodeName = (node === undefined) ? nodeName.replace(/^.*?@/, '') :
        ViewHelpers.stripPortHTML(node.hostname, nodes);
      perNode.push(ni);
      // possible per-node statuses are:
      //      starting, started, failed, collected,
      //      startingUpload, startedUpload, failedUpload, uploaded

      if (task.status == 'cancelled' && cancallable.indexOf(ni.status) >= 0) {
        ni.status = 'cancelled';
      }
    });

    var nodesByStatus = _.groupBy(perNode, 'status');

    var nodeErrors = _.compact(_.map(perNode, function (ni) {
      if (ni.uploadOutput) {
        return {nodeName: ni.nodeName,
                error: ni.uploadOutput};
      }
    }));

    task.nodesByStatus = nodesByStatus;
    task.nodeErrors = nodeErrors;

    return task;
  }).name("tasksCollectionInfoCell");
  ns.tasksCollectionInfoCell.equality = _.isEqual;

  ns.prepareCollectionInfoNodesCell = Cell.computeEager(function (v) {
    if (!v.need(ns.isCollectionInfoTabCell)) {
      return null;
    }
    var nodes = v.need(serversCell).allNodes;

    return {
      nodes: _(nodes).map(function (node) {
        return {
          nodeClass: node.nodeClass,
          value: node.otpNode,
          isUnhealthy: node.status === 'unhealthy',
          hostname: ViewHelpers.stripPortHTML(node.hostname, nodes)
        };
      })
    }
  }).name("prepareCollectionInfoNodesCell");
  ns.prepareCollectionInfoNodesCell.equality = _.isEqual;
}
var LogsSection = {
  init: function () {
    var collectInfoStartNewView = $("#js_collect_info_start_new_view");
    var selectNodesListCont = $("#js_select_nodes_list_container");
    var uploadToCouchbase = $("#js_upload_to_cb");
    var uploadToHost = $("#js_uploadHost_input");
    var uploadToForm = $("#js_upload_conf");
    var collectForm = $("#js_collect_info_form");
    var collectFromRadios = $("input[name='from']", collectInfoStartNewView);
    var cancelCollectBtn = $("#js_cancel_collect_info");
    var startNewCollectBtn = $("#js_start_new_info");
    var collectInfoWrapper = $("#js_collect_information");
    var collectResultView = $("#js_collect_result_view");
    var collectResultSectionSpinner = $("#js_collect_info_spinner");
    var showResultViewBtn = $("#js_previous_result_btn");
    var cancelConfiramationDialog = $("#js_cancel_collection_confirmation_dialog");
    var saveButton = $(".js_save_button", collectInfoWrapper);

    var collectInfoViewNameCell = new StringHashFragmentCell("collectInfoViewName");

    var allActiveNodeBoxes;
    var allNodeBoxes;
    var overlayCollectInfoStartNewView;
    var self = this;

    collectResultSectionSpinner.show();
    collectResultView.hide();
    collectInfoStartNewView.hide();

    self.tabs = new TabsCell('logsTabs', '#js_logs .tabs', '#js_logs .panes > div', ['logs', 'collection_info']);

    createLogsSectionCells(
      self,
      DAL.cells.mode,
      IOCenter.staleness,
      DAL.cells.tasksProgressCell,
      LogsSection.tabs,
      DAL.cells.serversCell,
      DAL.cells.isROAdminCell
    );

    renderCellTemplate(self.massagedLogsCell, 'logs', {
      valueTransformer: function (value) {
        var list = value.list || [];
        return _.clone(list).reverse();
      }
    });

    cancelCollectBtn.click(function (e) {
      if (cancelCollectBtn.hasClass('dynamic_disabled')) {
        return;
      }
      e.preventDefault();

      showDialog(cancelConfiramationDialog, {
        eventBindings: [['.save_button', 'click', function (e) {
          e.preventDefault();
          hideDialog(cancelConfiramationDialog);

          $.ajax({
            url: '/controller/cancelLogsCollection',
            type: "POST",
            success: function () {
              recalculateTasksUri();
              cancelCollectBtn.addClass('dynamic_disabled');
            },
            error: recalculateTasksUri
          });
        }]]
      });

    });
    startNewCollectBtn.click(function (e) {
      e.preventDefault();
      collectInfoViewNameCell.setValue("startNew");
    });
    showResultViewBtn.click(function (e) {
      e.preventDefault();
      collectInfoViewNameCell.setValue("result");
    });
    collectForm.submit(function (e) {
      e.preventDefault();

      hideErrors();

      var formValues = getCollectFormValues();

      if (formValues["upload"]) {
        if (!formValues["customer"]) {
          showCollectInfoErrors("customer field must be given if upload is selected", "customer");
        }
        if (!formValues["uploadHost"]) {
          showCollectInfoErrors("upload host field must be given if upload is selected", "uploadHost");
        }
        if (!formValues["uploadHost"] || !formValues["customer"]) {
          return;
        }
      }

      saveButton.attr('disabled', true);

      delete formValues["upload"];

      var overlayCollectForm = overlayWithSpinner(collectForm);

      jsonPostWithErrors('/controller/startLogsCollection', $.param(formValues), function (commonError, status, perFieldErrors) {
        overlayCollectForm.remove();
        saveButton.attr('disabled', false);
        if (status === 'success') {
          overlayCollectInfoStartNewView = overlayWithSpinner(collectInfoStartNewView);
          showResultViewBtn.hide();
          self.tasksCollectionInfoCell.changedSlot.subscribeOnce(function () {
            collectInfoViewNameCell.setValue("result");
          });
          recalculateTasksUri();
        } else {
          handleCollectInfoServersError(commonError, perFieldErrors);
        }
      });
    });
    uploadToCouchbase.change(function (e) {
      $('input[type="text"]', uploadToForm).attr('disabled', !$(this).attr('checked'));
    });
    collectFromRadios.change(function (e) {
      if (!allActiveNodeBoxes) {
        return;
      }

      var isAllnodesChecked = $(this).val() == '*';
      if (isAllnodesChecked) {
        allNodeBoxes.attr({checked: true, disabled: true});
      } else {
        allNodeBoxes.attr({checked: false});
        allActiveNodeBoxes.attr({checked: true, disabled: false});
      }
      maybeDisableSaveBtn();
    });

    function getCollectFormValues() {
      var dataArray = collectForm.serializeArray();
      var params = {'selected-nodes': []};
      _.each(dataArray, function (pair) {
        var name = pair.name;
        var value = pair.value;
        if (name === 'js-selected-nodes') {
          params['selected-nodes'].push(value);
        } else {
          params[name] = value;
        }
      });
      if (params['from'] === '*') {
        delete params['from'];
        delete params['selected-nodes'];
        params['nodes'] = '*';
      } else {
        delete params['from'];
        params['nodes'] = params['selected-nodes'].join(',');
        delete params['selected-nodes'];
      }
      _.each(["uploadHost", "customer", "ticket"], function (k) {
        if (params[k] === '') {
          delete params[k];
        }
      });

      return params;
    }

    function handleCollectInfoServersError(commonError, perFieldErrors) {
      _.each((!!commonError.length ? commonError : perFieldErrors), showCollectInfoErrors);
    }
    function showCollectInfoErrors(value, key) {
      key = key || 'generalCollectInfo';
      $("#js_" + key + "_error").text(value).show();
      $("#js_" + key + "_input").addClass("dynamic_input_error");
    }

    function hideErrors() {
      $(".js_error_container", collectInfoStartNewView).hide();
      $(".dynamic_input_error", collectInfoStartNewView).removeClass("dynamic_input_error");
    }

    function recalculateTasksUri() {
      DAL.cells.tasksProgressURI.recalculate();
    }

    function hideResultButtons() {
      startNewCollectBtn.hide();
      cancelCollectBtn.hide();
    }

    function switchCollectionInfoView(isResultView, isCurrentlyRunning, isRunBefore) {
      if (isResultView) {
        collectInfoStartNewView.hide();
        collectResultView.show();
        showResultViewBtn.hide();
        cancelCollectBtn.toggle(isCurrentlyRunning);
        startNewCollectBtn.toggle(!isCurrentlyRunning);
        collectForm[0].reset();
      } else {
        collectInfoStartNewView.show();
        collectResultView.hide();
        hideResultButtons();
        hideErrors();
        showResultViewBtn.toggle(isRunBefore);
      }
    }

    function renderResultView(collectionInfo) {
      renderTemplate('js_collect_progress', collectionInfo);
    }

    function maybeDisableSaveBtn() {
      saveButton.attr('disabled', !jQuery('input:checked', selectNodesListCont).length);
    }

    function renderStartNewView() {
      self.prepareCollectionInfoNodesCell.getValue(function (selectNodesList) {
        renderTemplate('js_select_nodes_list', selectNodesList);
        allActiveNodeBoxes = $('input:not(:disabled)', selectNodesListCont);
        allNodeBoxes = $('input', selectNodesListCont);
        allActiveNodeBoxes.change(maybeDisableSaveBtn);
        collectFromRadios.eq(0).attr('checked', true).trigger('change');
        uploadToCouchbase.attr('checked', false).trigger('change');
        maybeDisableSaveBtn();
      });
    }

    self.isCollectionInfoTabCell.subscribeValue(function (isCollectionInfoTab) {
      if (!isCollectionInfoTab) {
        hideResultButtons();
        showResultViewBtn.hide();
      }
    });

    DAL.cells.isEnterpriseCell.subscribeValue(function (isEnterprise) {
      if (isEnterprise) {
        uploadToHost.val('s3.amazonaws.com/cb-customers');
      }
    });

    Cell.subscribeMultipleValues(function (collectionInfo, tabName) {
      if (!collectionInfo) {
        return;
      }
      var isCurrentlyRunning = collectionInfo.status === 'running';
      var isRunBefore = !!_.keys(collectionInfo.perNode).length;
      var isResultView = tabName === 'result';

      !isCurrentlyRunning && cancelCollectBtn.removeClass('dynamic_disabled');

      if (isCurrentlyRunning) {
        collectInfoViewNameCell.setValue("result");
      } else {
        if (tabName) {
          if (!isRunBefore) {
            collectInfoViewNameCell.setValue("startNew");
          }
        } else {
          var defaultTabName = isRunBefore ? "result": "startNew";
          collectInfoViewNameCell.setValue(defaultTabName);
        }
      }

      switchCollectionInfoView(isResultView, isCurrentlyRunning, isRunBefore);
      collectResultSectionSpinner.hide();

      if (overlayCollectInfoStartNewView) {
        overlayCollectInfoStartNewView.remove();
      }
      if (isResultView) {
        renderResultView(collectionInfo);
      } else {
        renderStartNewView();
      }
    }, self.tasksCollectionInfoCell, collectInfoViewNameCell);

    self.massagedLogsCell.subscribeValue(function (massagedLogs) {
      if (massagedLogs === undefined){
        return;
      }
      var stale = massagedLogs.stale;
      $('#js_logs .staleness-notice')[stale ? 'show' : 'hide']();
    });

    self.logsRawCell.subscribe(function (cell) {
      cell.recalculateAfterDelay(30000);
    });
  },
  onEnter: function () {
  },
  navClick: function () {
    if (DAL.cells.mode.value == 'log'){
      this.logsRawCell.recalculate();
    }
  },
  domId: function (sec) {
    return 'logs';
  }
}
<commit_msg>MB-14516: keep upload options between tab switching<commit_after>/**
   Copyright 2015 Couchbase, Inc.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 **/

function createLogsSectionCells (ns, modeCell, stalenessCell, tasksProgressCell, logsSectionTabs, serversCell, isROAdminCell) {
  ns.activeTabCell = Cell.needing(modeCell).compute(function (v, mode) {
    return (mode === "log") || undefined;
  }).name('activeTabCell');

  ns.logsRawCell = Cell.needing(ns.activeTabCell).compute(function (v, active) {
    return future.get({url: "/logs"});
  }).name('logsRawCell');

  ns.logsRawCell.keepValueDuringAsync = true;

  ns.massagedLogsCell = Cell.compute(function (v) {
    var logsValue = v(ns.logsRawCell);
    var stale = v.need(stalenessCell);
    if (logsValue === undefined) {
      if (!stale){
        return;
      }
      logsValue = {list: []};
    }
    return _.extend({}, logsValue, {stale: stale});
  }).name('massagedLogsCell');

  ns.isCollectionInfoTabCell = Cell.compute(function (v) {
    return v.need(logsSectionTabs) === "collection_info" &&
           v.need(ns.activeTabCell) && !v.need(isROAdminCell);
  }).name("isClusterTabCell");
  ns.isCollectionInfoTabCell.equality = _.isEqual;

  ns.tasksCollectionInfoCell = Cell.computeEager(function (v) {
    if (!v.need(ns.isCollectionInfoTabCell)) {
      return null;
    }

    var tasks = v.need(tasksProgressCell);
    var task = _.detect(tasks, function (taskInfo) {
      return taskInfo.type === "clusterLogsCollection";
    });
    if (!task) {
      return {
        nodesByStatus: {},
        nodeErrors: [],
        status: 'idle',
        perNode: {}
      }
    }

    // this is cheap deep cloning of task. We're going to add some
    // attributes there and it's best to avoid mutating part of
    // tasksProgressCell value
    task = JSON.parse(JSON.stringify(task));

    var perNodeHash = task.perNode;
    var perNode = [];

    var cancallable = "starting started startingUpload startedUpload".split(" ");

    var nodes = v.need(serversCell).allNodes;

    _.each(perNodeHash, function (ni, nodeName) {
      var node = _.detect(nodes, function (n) {
        return n.otpNode === nodeName;
      });

      ni.nodeName = (node === undefined) ? nodeName.replace(/^.*?@/, '') :
        ViewHelpers.stripPortHTML(node.hostname, nodes);
      perNode.push(ni);
      // possible per-node statuses are:
      //      starting, started, failed, collected,
      //      startingUpload, startedUpload, failedUpload, uploaded

      if (task.status == 'cancelled' && cancallable.indexOf(ni.status) >= 0) {
        ni.status = 'cancelled';
      }
    });

    var nodesByStatus = _.groupBy(perNode, 'status');

    var nodeErrors = _.compact(_.map(perNode, function (ni) {
      if (ni.uploadOutput) {
        return {nodeName: ni.nodeName,
                error: ni.uploadOutput};
      }
    }));

    task.nodesByStatus = nodesByStatus;
    task.nodeErrors = nodeErrors;

    return task;
  }).name("tasksCollectionInfoCell");
  ns.tasksCollectionInfoCell.equality = _.isEqual;

  ns.prepareCollectionInfoNodesCell = Cell.computeEager(function (v) {
    if (!v.need(ns.isCollectionInfoTabCell)) {
      return null;
    }
    var nodes = v.need(serversCell).allNodes;

    return {
      nodes: _(nodes).map(function (node) {
        return {
          nodeClass: node.nodeClass,
          value: node.otpNode,
          isUnhealthy: node.status === 'unhealthy',
          hostname: ViewHelpers.stripPortHTML(node.hostname, nodes)
        };
      })
    }
  }).name("prepareCollectionInfoNodesCell");
  ns.prepareCollectionInfoNodesCell.equality = _.isEqual;
}
var LogsSection = {
  init: function () {
    var collectInfoStartNewView = $("#js_collect_info_start_new_view");
    var selectNodesListCont = $("#js_select_nodes_list_container");
    var uploadToCouchbase = $("#js_upload_to_cb");
    var uploadToHost = $("#js_uploadHost_input");
    var uploadToForm = $("#js_upload_conf");
    var collectForm = $("#js_collect_info_form");
    var collectFromRadios = $("input[name='from']", collectInfoStartNewView);
    var cancelCollectBtn = $("#js_cancel_collect_info");
    var startNewCollectBtn = $("#js_start_new_info");
    var collectInfoWrapper = $("#js_collect_information");
    var collectResultView = $("#js_collect_result_view");
    var collectResultSectionSpinner = $("#js_collect_info_spinner");
    var showResultViewBtn = $("#js_previous_result_btn");
    var cancelConfiramationDialog = $("#js_cancel_collection_confirmation_dialog");
    var saveButton = $(".js_save_button", collectInfoWrapper);

    var collectInfoViewNameCell = new StringHashFragmentCell("collectInfoViewName");

    var allActiveNodeBoxes;
    var allNodeBoxes;
    var overlayCollectInfoStartNewView;
    var self = this;

    collectResultSectionSpinner.show();
    collectResultView.hide();
    collectInfoStartNewView.hide();

    self.tabs = new TabsCell('logsTabs', '#js_logs .tabs', '#js_logs .panes > div', ['logs', 'collection_info']);

    createLogsSectionCells(
      self,
      DAL.cells.mode,
      IOCenter.staleness,
      DAL.cells.tasksProgressCell,
      LogsSection.tabs,
      DAL.cells.serversCell,
      DAL.cells.isROAdminCell
    );

    renderCellTemplate(self.massagedLogsCell, 'logs', {
      valueTransformer: function (value) {
        var list = value.list || [];
        return _.clone(list).reverse();
      }
    });

    cancelCollectBtn.click(function (e) {
      if (cancelCollectBtn.hasClass('dynamic_disabled')) {
        return;
      }
      e.preventDefault();

      showDialog(cancelConfiramationDialog, {
        eventBindings: [['.save_button', 'click', function (e) {
          e.preventDefault();
          hideDialog(cancelConfiramationDialog);

          $.ajax({
            url: '/controller/cancelLogsCollection',
            type: "POST",
            success: function () {
              recalculateTasksUri();
              cancelCollectBtn.addClass('dynamic_disabled');
            },
            error: recalculateTasksUri
          });
        }]]
      });

    });
    startNewCollectBtn.click(function (e) {
      e.preventDefault();
      collectInfoViewNameCell.setValue("startNew");
    });
    showResultViewBtn.click(function (e) {
      e.preventDefault();
      collectInfoViewNameCell.setValue("result");
    });
    collectForm.submit(function (e) {
      e.preventDefault();

      hideErrors();

      var formValues = getCollectFormValues();

      if (formValues["upload"]) {
        if (!formValues["customer"]) {
          showCollectInfoErrors("customer field must be given if upload is selected", "customer");
        }
        if (!formValues["uploadHost"]) {
          showCollectInfoErrors("upload host field must be given if upload is selected", "uploadHost");
        }
        if (!formValues["uploadHost"] || !formValues["customer"]) {
          return;
        }
      }

      saveButton.attr('disabled', true);

      delete formValues["upload"];

      var overlayCollectForm = overlayWithSpinner(collectForm);

      jsonPostWithErrors('/controller/startLogsCollection', $.param(formValues), function (commonError, status, perFieldErrors) {
        overlayCollectForm.remove();
        saveButton.attr('disabled', false);
        if (status === 'success') {
          overlayCollectInfoStartNewView = overlayWithSpinner(collectInfoStartNewView);
          showResultViewBtn.hide();
          self.tasksCollectionInfoCell.changedSlot.subscribeOnce(function () {
            collectInfoViewNameCell.setValue("result");
          });
          recalculateTasksUri();
        } else {
          handleCollectInfoServersError(commonError, perFieldErrors);
        }
      });
    });
    uploadToCouchbase.change(function (e) {
      $('input[type="text"]', uploadToForm).attr('disabled', !$(this).attr('checked'));
    });
    collectFromRadios.change(function (e) {
      if (!allActiveNodeBoxes) {
        return;
      }

      var isAllnodesChecked = $(this).val() == '*';
      if (isAllnodesChecked) {
        allNodeBoxes.attr({checked: true, disabled: true});
      } else {
        allNodeBoxes.attr({checked: false});
        allActiveNodeBoxes.attr({checked: true, disabled: false});
      }
      maybeDisableSaveBtn();
    });

    function getCollectFormValues() {
      var dataArray = collectForm.serializeArray();
      var params = {'selected-nodes': []};
      _.each(dataArray, function (pair) {
        var name = pair.name;
        var value = pair.value;
        if (name === 'js-selected-nodes') {
          params['selected-nodes'].push(value);
        } else {
          params[name] = value;
        }
      });
      if (params['from'] === '*') {
        delete params['from'];
        delete params['selected-nodes'];
        params['nodes'] = '*';
      } else {
        delete params['from'];
        params['nodes'] = params['selected-nodes'].join(',');
        delete params['selected-nodes'];
      }
      _.each(["uploadHost", "customer", "ticket"], function (k) {
        if (params[k] === '') {
          delete params[k];
        }
      });

      return params;
    }

    function handleCollectInfoServersError(commonError, perFieldErrors) {
      _.each((!!commonError.length ? commonError : perFieldErrors), showCollectInfoErrors);
    }
    function showCollectInfoErrors(value, key) {
      key = key || 'generalCollectInfo';
      $("#js_" + key + "_error").text(value).show();
      $("#js_" + key + "_input").addClass("dynamic_input_error");
    }

    function hideErrors() {
      $(".js_error_container", collectInfoStartNewView).hide();
      $(".dynamic_input_error", collectInfoStartNewView).removeClass("dynamic_input_error");
    }

    function recalculateTasksUri() {
      DAL.cells.tasksProgressURI.recalculate();
    }

    function hideResultButtons() {
      startNewCollectBtn.hide();
      cancelCollectBtn.hide();
    }

    function switchCollectionInfoView(isResultView, isCurrentlyRunning, isRunBefore) {
      if (isResultView) {
        collectInfoStartNewView.hide();
        collectResultView.show();
        showResultViewBtn.hide();
        cancelCollectBtn.toggle(isCurrentlyRunning);
        startNewCollectBtn.toggle(!isCurrentlyRunning);
      } else {
        collectInfoStartNewView.show();
        collectResultView.hide();
        hideResultButtons();
        hideErrors();
        showResultViewBtn.toggle(isRunBefore);
      }
    }

    function renderResultView(collectionInfo) {
      renderTemplate('js_collect_progress', collectionInfo);
    }

    function maybeDisableSaveBtn() {
      saveButton.attr('disabled', !jQuery('input:checked', selectNodesListCont).length);
    }

    function renderStartNewView() {
      self.prepareCollectionInfoNodesCell.getValue(function (selectNodesList) {
        renderTemplate('js_select_nodes_list', selectNodesList);
        allActiveNodeBoxes = $('input:not(:disabled)', selectNodesListCont);
        allNodeBoxes = $('input', selectNodesListCont);
        allActiveNodeBoxes.change(maybeDisableSaveBtn);
        collectFromRadios.eq(0).attr('checked', true).trigger('change');
        uploadToCouchbase.trigger('change');
        maybeDisableSaveBtn();
        DAL.cells.isEnterpriseCell.getValue(function (isEnterprise) {
          if (isEnterprise && !uploadToHost.val()) {
            uploadToHost.val('s3.amazonaws.com/cb-customers');
          }
        });
      });
    }

    self.isCollectionInfoTabCell.subscribeValue(function (isCollectionInfoTab) {
      if (!isCollectionInfoTab) {
        hideResultButtons();
        showResultViewBtn.hide();
      }
    });

    Cell.subscribeMultipleValues(function (collectionInfo, tabName) {
      if (!collectionInfo) {
        return;
      }
      var isCurrentlyRunning = collectionInfo.status === 'running';
      var isRunBefore = !!_.keys(collectionInfo.perNode).length;
      var isResultView = tabName === 'result';

      !isCurrentlyRunning && cancelCollectBtn.removeClass('dynamic_disabled');

      if (isCurrentlyRunning) {
        collectInfoViewNameCell.setValue("result");
      } else {
        if (tabName) {
          if (!isRunBefore) {
            collectInfoViewNameCell.setValue("startNew");
          }
        } else {
          var defaultTabName = isRunBefore ? "result": "startNew";
          collectInfoViewNameCell.setValue(defaultTabName);
        }
      }

      switchCollectionInfoView(isResultView, isCurrentlyRunning, isRunBefore);
      collectResultSectionSpinner.hide();

      if (overlayCollectInfoStartNewView) {
        overlayCollectInfoStartNewView.remove();
      }
      if (isResultView) {
        renderResultView(collectionInfo);
      } else {
        renderStartNewView();
      }
    }, self.tasksCollectionInfoCell, collectInfoViewNameCell);

    self.massagedLogsCell.subscribeValue(function (massagedLogs) {
      if (massagedLogs === undefined){
        return;
      }
      var stale = massagedLogs.stale;
      $('#js_logs .staleness-notice')[stale ? 'show' : 'hide']();
    });

    self.logsRawCell.subscribe(function (cell) {
      cell.recalculateAfterDelay(30000);
    });
  },
  onEnter: function () {
  },
  navClick: function () {
    if (DAL.cells.mode.value == 'log'){
      this.logsRawCell.recalculate();
    }
  },
  domId: function (sec) {
    return 'logs';
  }
}
<|endoftext|>


<commit_before>#!/usr/bin/env node

var app = require('commander')
var async = require('async')
var api = require('./lib/api')
var auth = require('./lib/auth')

app
  .description('Export your data')
  .option('--pretty', 'Pretty print output')
  .parse(process.argv)

function showProgress () {
  process.stderr.write('.')
}

function getUser (callback) {
  api('/user', function (err, res, body) {
    if (err) process.exit(1)

    showProgress()
    callback(null, {user: body})
  })
}

function getLists (callback) {
  api('/lists', function (err, res, body) {
    if (err) process.exit(1)

    showProgress()
    callback(null, {lists: body})
  })
}

function embedLists (data, callback) {
  var lists = []
  async.each(data.lists, function (list, complete) {
    async.parallel([
      function getTasks (cb) {
        api({url: '/tasks', qs: {list_id: list.id}}, function (err, res, body) {
          showProgress()
          cb(err, body)
        })
      },
      function getSubtasks (cb) {
        api({url: '/subtasks', qs: {list_id: list.id}}, function (err, res, body) {
          showProgress()
          cb(err, body)
        })
      },
      function getNotes (cb) {
        api({url: '/notes', qs: {list_id: list.id}}, function (err, res, body) {
          if (err) process.exit(1)

          showProgress()
          cb(err, body)
        })
      },
      function getFiles (cb) {
        api({url: '/files', qs: {list_id: list.id}}, function (err, res, body) {
          showProgress()
          cb(err, body)
        })
      }
    ], function (err, results) {
      if (err) process.exit(1)

      var tasks = results[0]
      var subtasks = results[1]
      var notes = results[2]
      var files = results[3]
      tasks.forEach(function (task, index) {
        tasks[index].subtasks = []
        tasks[index].notes = []
        tasks[index].files = []
      })
      subtasks.forEach(function (subtask) {
        tasks.forEach(function (task, index) {
          if (task.id === subtask.task_id) {
            tasks[index].subtasks.push(subtask)
          }
        })
      })
      notes.forEach(function (note) {
        tasks.forEach(function (task, index) {
          if (task.id === note.task_id) {
            tasks[index].notes.push(note)
          }
        })
      })
      files.forEach(function (file) {
        tasks.forEach(function (task, index) {
          if (task.id === file.task_id) {
            tasks[index].files.push(file)
          }
        })
      })
      list.tasks = tasks
      lists.push(list)
      complete()
    })
  }, function (err) {
    if (err) process.exit(1)

    data.lists = lists
    callback(err, data)
  })
}

function getEmbeddedLists (callback) {
  async.waterfall([
    getLists,
    embedLists
  ], function (err, data) {
    if (err) process.exit(1)

    callback(err, data)
  })
}

function main () {
  async.parallel([
    getUser,
    getEmbeddedLists
  ], function (err, results) {
    if (err) process.exit(1)

    var data = {
      exported_at: new Date(),
      data: {
        user: results[0].user,
        lists: results[1].lists
      }
    }

    var output

    if (app.pretty) {
      output = JSON.stringify(data, null, 2)
    } else {
      output = JSON.stringify(data)
    }

    process.stdout.write(output)
  })
}

auth(main)
<commit_msg>export completed tasks as well<commit_after>#!/usr/bin/env node

var app = require('commander')
var async = require('async')
var api = require('./lib/api')
var auth = require('./lib/auth')

app
  .description('Export all your data')
  .option('--pretty', 'Pretty print output')
  .parse(process.argv)

function showProgress () {
  process.stderr.write('.')
}

function getUser (callback) {
  api('/user', function (err, res, body) {
    if (err) process.exit(1)

    showProgress()
    callback(null, {user: body})
  })
}

function getLists (callback) {
  api('/lists', function (err, res, body) {
    if (err) process.exit(1)

    showProgress()
    callback(null, {lists: body})
  })
}

function embedLists (data, callback) {
  var lists = []
  async.each(data.lists, function (list, complete) {
    async.parallel([
      function getTasks (cb) {
        api({url: '/tasks', qs: {list_id: list.id, completed: false}}, function (err, res, body) {
          showProgress()
          cb(err, body)
        })
      },
      function getCompletedTasks (cb) {
        api({url: '/tasks', qs: {list_id: list.id, completed: true}}, function (err, res, body) {
          showProgress()
          cb(err, body)
        })
      },
      function getSubtasks (cb) {
        api({url: '/subtasks', qs: {list_id: list.id}}, function (err, res, body) {
          showProgress()
          cb(err, body)
        })
      },
      function getNotes (cb) {
        api({url: '/notes', qs: {list_id: list.id}}, function (err, res, body) {
          if (err) process.exit(1)

          showProgress()
          cb(err, body)
        })
      },
      function getFiles (cb) {
        api({url: '/files', qs: {list_id: list.id}}, function (err, res, body) {
          showProgress()
          cb(err, body)
        })
      }
    ], function (err, results) {
      if (err) process.exit(1)

      var tasks = [].concat(results[0]).concat(results[1])
      var subtasks = results[2]
      var notes = results[3]
      var files = results[4]

      tasks.forEach(function (task, index) {
        tasks[index].subtasks = []
        tasks[index].notes = []
        tasks[index].files = []
      })

      subtasks.forEach(function (subtask) {
        tasks.forEach(function (task, index) {
          if (task.id === subtask.task_id) {
            tasks[index].subtasks.push(subtask)
          }
        })
      })

      notes.forEach(function (note) {
        tasks.forEach(function (task, index) {
          if (task.id === note.task_id) {
            tasks[index].notes.push(note)
          }
        })
      })
      files.forEach(function (file) {
        tasks.forEach(function (task, index) {
          if (task.id === file.task_id) {
            tasks[index].files.push(file)
          }
        })
      })
      list.tasks = tasks
      lists.push(list)
      complete()
    })
  }, function (err) {
    if (err) process.exit(1)

    data.lists = lists
    callback(err, data)
  })
}

function getEmbeddedLists (callback) {
  async.waterfall([
    getLists,
    embedLists
  ], function (err, data) {
    if (err) process.exit(1)

    callback(err, data)
  })
}

function main () {
  async.parallel([
    getUser,
    getEmbeddedLists
  ], function (err, results) {
    if (err) process.exit(1)

    var data = {
      exported_at: new Date(),
      data: {
        user: results[0].user,
        lists: results[1].lists
      }
    }

    var output

    if (app.pretty) {
      output = JSON.stringify(data, null, 2)
    } else {
      output = JSON.stringify(data)
    }

    process.stdout.write(output)
  })
}

auth(main)
<|endoftext|>


<commit_before>/*
 * References:
 * * http://luke.breuer.com/tutorial/javascript-drag-and-drop-tutorial.aspx
 * * http://stackoverflow.com/questions/1291325/drag-drop-problem-draggable-in-positionrelative-parent
 *
 * Note that default drag does not work with position: relative by default!
 * */
var drag = (function() {
    function drag(elem, cbs) {
        if(!elem) {
            console.warn('drag is missing elem!');
            return;
        }

        if(isTouch()) dragTemplate(elem, cbs, 'touchstart', 'touchmove', 'touchend');
        else dragTemplate(elem, cbs, 'mousedown', 'mousemove', 'mouseup');
    }

    function xyslider(o) {
        var twod = div(o['class'] || '', o.parent);
        var pointer = div('pointer', twod);
        div('shape shape1', pointer);
        div('shape shape2', pointer);
        div('bg bg1', twod);
        div('bg bg2', twod);

        drag(twod, attachPointer(o.cbs, pointer));

        return {
            background: twod,
            pointer: pointer
        };
    }

    function slider(o) {
        var oned = div(o['class'], o.parent);
        var pointer = div('pointer', oned);
        div('shape', pointer);
        div('bg', oned);

        drag(oned, attachPointer(o.cbs, pointer));

        return {
            background: oned,
            pointer: pointer
        };
    }

    drag.xyslider = xyslider;
    drag.slider = slider;

    return drag;

    function attachPointer(cbs, pointer) {
        var ret = {};

        for(var n in cbs) ret[n] = wrap(cbs[n]);

        function wrap(fn) {
            return function(p) {
                p.pointer = pointer;
                fn(p);
            };
        }

        return ret;
    }

    // move to elemutils lib?
    function div(klass, p) {
        return e('div', klass, p);
    }

    function e(type, klass, p) {
        var elem = document.createElement(type);
        if(klass) elem.className = klass;
        p.appendChild(elem);

        return elem;
    }

    // http://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript
    function isTouch() {
        return typeof(window.ontouchstart) != 'undefined';
    }

    function dragTemplate(elem, cbs, down, move, up) {
        var dragging = false;

        cbs = getCbs(cbs);

        var beginCb = cbs.begin;
        var changeCb = cbs.change;
        var endCb = cbs.end;

        on(elem, down, function(e) {
            dragging = true;

            var moveHandler = partial(callCb, changeCb, elem);
            function upHandler() {
                dragging = false;

                off(document, move, moveHandler);
                off(document, up, upHandler);

                callCb(endCb, elem, e);
            }

            on(document, move, moveHandler);
            on(document, up, upHandler);

            callCb(beginCb, elem, e);
        });
    }

    function on(elem, evt, handler) {
        if(elem.addEventListener)
            elem.addEventListener(evt, handler, false);
        else if(elem.attachEvent)
            elem.attachEvent('on' + evt, handler);
    }

    function off(elem, evt, handler) {
        if(elem.removeEventListener)
            elem.removeEventListener(evt, handler, false);
        else if(elem.detachEvent)
            elem.detachEvent('on' + evt, handler);
    }

    function getCbs(cbs) {
        if(!cbs) {
            var initialOffset;
            var initialPos;

            return {
                begin: function(c) {
                    initialOffset = {x: c.elem.offsetLeft, y: c.elem.offsetTop};
                    initialPos = c.cursor;
                },
                change: function(c) {
                    style(c.elem, 'left', (initialOffset.x + c.cursor.x - initialPos.x) + 'px');
                    style(c.elem, 'top', (initialOffset.y + c.cursor.y - initialPos.y) + 'px');
                },
                end: empty
            };
        }
        else {
            return {
                begin: cbs.begin || empty,
                change: cbs.change || empty,
                end: cbs.end || empty
            };
        }
    }

    // TODO: set draggable class (handy for fx)

    function style(e, prop, value) {
        e.style[prop] = value;
    }

    function empty() {}

    function callCb(cb, elem, e) {
        e.preventDefault();

        var offset = findPos(elem);
        var width = elem.clientWidth;
        var height = elem.clientHeight;
        var cursor = {
            x: cursorX(elem, e),
            y: cursorY(elem, e)
        };
        var x = (cursor.x - offset.x) / width;
        var y = (cursor.y - offset.y) / height;

        cb({
            x: isNaN(x)? 0: x,
            y: isNaN(y)? 0: y,
            cursor: cursor,
            elem: elem,
            e: e
        });
    }

    // http://stackoverflow.com/questions/4394747/javascript-curry-function
    function partial(fn) {
        var slice = Array.prototype.slice;
        var args = slice.apply(arguments, [1]);

        return function() {
            return fn.apply(null, args.concat(slice.apply(arguments)));
        };
    }

    // http://www.quirksmode.org/js/findpos.html
    function findPos(e) {
        var x = 0;
        var y = 0;

        if(e.offsetParent) {
            do {
                x += e.offsetLeft;
                y += e.offsetTop;
            } while (e = e.offsetParent);
        }

        return {x: x, y: y};
    }

    // http://javascript.about.com/library/blmousepos.htm
    function cursorX(elem, evt) {
        if(isFixed(elem)) {
            var bodyLeft = parseInt(getStyle(document.body, 'marginLeft'), 10) -
                calc(elem, 'scrollLeft') + window.pageXOffset +
                elem.style.marginLeft;

            return evt.clientX - bodyLeft;
        }
        if(evt.pageX) return evt.pageX;
        else if(evt.clientX)
            return evt.clientX + document.body.scrollLeft;
    }
    function cursorY(elem, evt) {
        if(isFixed(elem)) {
            var bodyTop = parseInt(getStyle(document.body, 'marginTop'), 10) -
                calc(elem, 'scrollTop') + window.pageYOffset +
                elem.style.marginTop;

            return evt.clientY - bodyTop;
        }
        if(evt.pageY) return evt.pageY;
        else if(evt.clientY)
            return evt.clientY + document.body.scrollTop;
    }

    function calc(element, prop) {
        var ret = 0;

        while (element.nodeName != "HTML") {
            ret += element[prop];
            element = element.parentNode;
        }

        return ret;
    }

    // http://www.velocityreviews.com/forums/t942580-mouse-position-in-both-fixed-and-relative-positioning.html
    function isFixed(element) {
        // While not at the top of the document tree, or not fixed, keep
        // searching upwards.
        while (element.nodeName != "HTML" && usedStyle(element,
                "position") != "fixed")
            element = element.parentNode;
            if(element.nodeName == "HTML") return false;
            else return true;
    }

    // http://www.javascriptkit.com/dhtmltutors/dhtmlcascade4.shtml
    function getStyle(el, cssprop){
        if (el.currentStyle) // IE
            return el.currentStyle[cssprop];

        if(document.defaultView && document.defaultView.getComputedStyle)
            return document.defaultView.getComputedStyle(el, "")[cssprop];

        //try and get inline style
        return el.style[cssprop];
    }

    // Used style is to get around browsers' different methods of getting
    // the currently used (e.g. inline, class, etc) style for an element
    function usedStyle(element, property) {
        var s;

        // getComputedStyle is the standard way but some ie versions don't
        // support it
        if(window.getComputedStyle)
            s = window.getComputedStyle(element, null);
        else s = element.currentStyle;

        return s[property];
    }
})();
<commit_msg>Register both touch and mouse events<commit_after>/*
 * References:
 * * http://luke.breuer.com/tutorial/javascript-drag-and-drop-tutorial.aspx
 * * http://stackoverflow.com/questions/1291325/drag-drop-problem-draggable-in-positionrelative-parent
 *
 * Note that default drag does not work with position: relative by default!
 * */
var drag = (function() {
    function drag(elem, cbs) {
        if(!elem) {
            console.warn('drag is missing elem!');
            return;
        }

        dragTemplate(elem, cbs, 'touchstart', 'touchmove', 'touchend');
        dragTemplate(elem, cbs, 'mousedown', 'mousemove', 'mouseup');
    }

    function xyslider(o) {
        var twod = div(o['class'] || '', o.parent);
        var pointer = div('pointer', twod);
        div('shape shape1', pointer);
        div('shape shape2', pointer);
        div('bg bg1', twod);
        div('bg bg2', twod);

        drag(twod, attachPointer(o.cbs, pointer));

        return {
            background: twod,
            pointer: pointer
        };
    }

    function slider(o) {
        var oned = div(o['class'], o.parent);
        var pointer = div('pointer', oned);
        div('shape', pointer);
        div('bg', oned);

        drag(oned, attachPointer(o.cbs, pointer));

        return {
            background: oned,
            pointer: pointer
        };
    }

    drag.xyslider = xyslider;
    drag.slider = slider;

    return drag;

    function attachPointer(cbs, pointer) {
        var ret = {};

        for(var n in cbs) ret[n] = wrap(cbs[n]);

        function wrap(fn) {
            return function(p) {
                p.pointer = pointer;
                fn(p);
            };
        }

        return ret;
    }

    // move to elemutils lib?
    function div(klass, p) {
        return e('div', klass, p);
    }

    function e(type, klass, p) {
        var elem = document.createElement(type);
        if(klass) elem.className = klass;
        p.appendChild(elem);

        return elem;
    }

    // http://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript
    function isTouch() {
        return typeof(window.ontouchstart) != 'undefined';
    }

    function dragTemplate(elem, cbs, down, move, up) {
        var dragging = false;

        cbs = getCbs(cbs);

        var beginCb = cbs.begin;
        var changeCb = cbs.change;
        var endCb = cbs.end;

        on(elem, down, function(e) {
            dragging = true;

            var moveHandler = partial(callCb, changeCb, elem);
            function upHandler() {
                dragging = false;

                off(document, move, moveHandler);
                off(document, up, upHandler);

                callCb(endCb, elem, e);
            }

            on(document, move, moveHandler);
            on(document, up, upHandler);

            callCb(beginCb, elem, e);
        });
    }

    function on(elem, evt, handler) {
        if(elem.addEventListener)
            elem.addEventListener(evt, handler, false);
        else if(elem.attachEvent)
            elem.attachEvent('on' + evt, handler);
    }

    function off(elem, evt, handler) {
        if(elem.removeEventListener)
            elem.removeEventListener(evt, handler, false);
        else if(elem.detachEvent)
            elem.detachEvent('on' + evt, handler);
    }

    function getCbs(cbs) {
        if(!cbs) {
            var initialOffset;
            var initialPos;

            return {
                begin: function(c) {
                    initialOffset = {x: c.elem.offsetLeft, y: c.elem.offsetTop};
                    initialPos = c.cursor;
                },
                change: function(c) {
                    style(c.elem, 'left', (initialOffset.x + c.cursor.x - initialPos.x) + 'px');
                    style(c.elem, 'top', (initialOffset.y + c.cursor.y - initialPos.y) + 'px');
                },
                end: empty
            };
        }
        else {
            return {
                begin: cbs.begin || empty,
                change: cbs.change || empty,
                end: cbs.end || empty
            };
        }
    }

    // TODO: set draggable class (handy for fx)

    function style(e, prop, value) {
        e.style[prop] = value;
    }

    function empty() {}

    function callCb(cb, elem, e) {
        e.preventDefault();

        var offset = findPos(elem);
        var width = elem.clientWidth;
        var height = elem.clientHeight;
        var cursor = {
            x: cursorX(elem, e),
            y: cursorY(elem, e)
        };
        var x = (cursor.x - offset.x) / width;
        var y = (cursor.y - offset.y) / height;

        cb({
            x: isNaN(x)? 0: x,
            y: isNaN(y)? 0: y,
            cursor: cursor,
            elem: elem,
            e: e
        });
    }

    // http://stackoverflow.com/questions/4394747/javascript-curry-function
    function partial(fn) {
        var slice = Array.prototype.slice;
        var args = slice.apply(arguments, [1]);

        return function() {
            return fn.apply(null, args.concat(slice.apply(arguments)));
        };
    }

    // http://www.quirksmode.org/js/findpos.html
    function findPos(e) {
        var x = 0;
        var y = 0;

        if(e.offsetParent) {
            do {
                x += e.offsetLeft;
                y += e.offsetTop;
            } while (e = e.offsetParent);
        }

        return {x: x, y: y};
    }

    // http://javascript.about.com/library/blmousepos.htm
    function cursorX(elem, evt) {
        if(isFixed(elem)) {
            var bodyLeft = parseInt(getStyle(document.body, 'marginLeft'), 10) -
                calc(elem, 'scrollLeft') + window.pageXOffset +
                elem.style.marginLeft;

            return evt.clientX - bodyLeft;
        }
        if(evt.pageX) return evt.pageX;
        else if(evt.clientX)
            return evt.clientX + document.body.scrollLeft;
    }
    function cursorY(elem, evt) {
        if(isFixed(elem)) {
            var bodyTop = parseInt(getStyle(document.body, 'marginTop'), 10) -
                calc(elem, 'scrollTop') + window.pageYOffset +
                elem.style.marginTop;

            return evt.clientY - bodyTop;
        }
        if(evt.pageY) return evt.pageY;
        else if(evt.clientY)
            return evt.clientY + document.body.scrollTop;
    }

    function calc(element, prop) {
        var ret = 0;

        while (element.nodeName != "HTML") {
            ret += element[prop];
            element = element.parentNode;
        }

        return ret;
    }

    // http://www.velocityreviews.com/forums/t942580-mouse-position-in-both-fixed-and-relative-positioning.html
    function isFixed(element) {
        // While not at the top of the document tree, or not fixed, keep
        // searching upwards.
        while (element.nodeName != "HTML" && usedStyle(element,
                "position") != "fixed")
            element = element.parentNode;
            if(element.nodeName == "HTML") return false;
            else return true;
    }

    // http://www.javascriptkit.com/dhtmltutors/dhtmlcascade4.shtml
    function getStyle(el, cssprop){
        if (el.currentStyle) // IE
            return el.currentStyle[cssprop];

        if(document.defaultView && document.defaultView.getComputedStyle)
            return document.defaultView.getComputedStyle(el, "")[cssprop];

        //try and get inline style
        return el.style[cssprop];
    }

    // Used style is to get around browsers' different methods of getting
    // the currently used (e.g. inline, class, etc) style for an element
    function usedStyle(element, property) {
        var s;

        // getComputedStyle is the standard way but some ie versions don't
        // support it
        if(window.getComputedStyle)
            s = window.getComputedStyle(element, null);
        else s = element.currentStyle;

        return s[property];
    }
})();
<|endoftext|>


<commit_before>/*
 * charts.js
 * Copyright (c) 2017 thegrumpydictator@gmail.com
 *
 * This file is part of Firefly III.
 *
 * Firefly III is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Firefly III is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
 */
/** global: Chart, defaultChartOptions, accounting, defaultPieOptions, noDataForChart, todayText */
var allCharts = {};

/*
 Make some colours:
 */
var colourSet = [
    [53, 124, 165],
    [0, 141, 76],
    [219, 139, 11],
    [202, 25, 90],
    [85, 82, 153],
    [66, 133, 244],
    [219, 68, 55],
    [244, 180, 0],
    [15, 157, 88],
    [171, 71, 188],
    [0, 172, 193],
    [255, 112, 67],
    [158, 157, 36],
    [92, 107, 192],
    [240, 98, 146],
    [0, 121, 107],
    [194, 24, 91]
];

var fillColors = [];
var strokePointHighColors = [];

for (var i = 0; i < colourSet.length; i++) {
    fillColors.push("rgba(" + colourSet[i][0] + ", " + colourSet[i][1] + ", " + colourSet[i][2] + ", 0.5)");
    strokePointHighColors.push("rgba(" + colourSet[i][0] + ", " + colourSet[i][1] + ", " + colourSet[i][2] + ", 0.9)");
}

Chart.defaults.global.legend.display = false;
Chart.defaults.global.animation.duration = 0;
Chart.defaults.global.responsive = true;
Chart.defaults.global.maintainAspectRatio = false;

/**
 * Chart line thing
 */
const verticalLinePlugin = {
    getLinePosition: function (chart, pointIndex) {
        const meta = chart.getDatasetMeta(0); // first dataset is used to discover X coordinate of a point
        const data = meta.data;
        return data[pointIndex]._model.x;
    },
    renderVerticalLine: function (chartInstance, pointIndex) {
        const lineLeftOffset = this.getLinePosition(chartInstance, pointIndex);
        const scale = chartInstance.scales['y-axis-0'];
        const context = chartInstance.chart.ctx;

        // render vertical line
        context.beginPath();
        context.strokeStyle = fillColors[3];
        context.moveTo(lineLeftOffset, scale.top);
        context.lineTo(lineLeftOffset, scale.bottom);
        context.stroke();

        // write label
        context.fillStyle = "#444444";
        context.textAlign = 'left';
        context.fillText(todayText, lineLeftOffset, scale.top * 3); // (scale.bottom - scale.top) / 2 + scale.top
    },

    afterDatasetsDraw: function (chart, easing) {
        if (chart.config.lineAtIndex) {
            chart.config.lineAtIndex.forEach(pointIndex => this.renderVerticalLine(chart, pointIndex));
        }
    }
};

Chart.plugins.register(verticalLinePlugin);

/**
 *
 * @param data
 * @returns {{}}
 */
function colorizeData(data) {
    var newData = {};
    newData.datasets = [];

    for (var i = 0; i < data.count; i++) {
        newData.labels = data.labels;
        var dataset = data.datasets[i];
        dataset.fill = false;
        dataset.backgroundColor = dataset.borderColor = fillColors[i];
        newData.datasets.push(dataset);
    }
    return newData;
}

/**
 * Function to draw a line chart:
 * @param URI
 * @param container
 */
function lineChart(URI, container) {
    "use strict";

    var colorData = true;
    var options = $.extend(true, {}, defaultChartOptions);
    var chartType = 'line';

    drawAChart(URI, container, chartType, options, colorData, -1);
}

function lineChartWithDay(URI, container, today) {
    "use strict";
    var colorData = true;
    var options = $.extend(true, {}, defaultChartOptions);
    var chartType = 'line';

    drawAChart(URI, container, chartType, options, colorData, today);
}

/**
 * Function to draw a chart with double Y Axes and stacked columns.
 *
 * @param URI
 * @param container
 */
function doubleYChart(URI, container) {
    "use strict";

    var colorData = true;
    var options = $.extend(true, {}, defaultChartOptions);
    options.scales.yAxes = [
        // y axis 0:
        {
            display: true,
            ticks: {
                callback: function (tickValue) {
                    "use strict";
                    return accounting.formatMoney(tickValue);

                },
                beginAtZero: true
            },
            position: "left",
            "id": "y-axis-0"
        },
        // and y axis 1:
        {
            display: true,
            ticks: {
                callback: function (tickValue) {
                    "use strict";
                    return accounting.formatMoney(tickValue);

                },
                beginAtZero: true
            },
            position: "right",
            "id": "y-axis-1"
        }

    ];
    options.stacked = true;
    options.scales.xAxes[0].stacked = true;

    var chartType = 'bar';

    drawAChart(URI, container, chartType, options, colorData);
}

/**
 * Function to draw a chart with double Y Axes and non stacked columns.
 *
 * @param URI
 * @param container
 */
function doubleYNonStackedChart(URI, container) {
    "use strict";

    var colorData = true;
    var options = $.extend(true, {}, defaultChartOptions);
    options.scales.yAxes = [
        // y axis 0:
        {
            display: true,
            ticks: {
                callback: function (tickValue) {
                    "use strict";
                    return accounting.formatMoney(tickValue);

                },
                beginAtZero: true
            },
            position: "left",
            "id": "y-axis-0"
        },
        // and y axis 1:
        {
            display: true,
            ticks: {
                callback: function (tickValue) {
                    "use strict";
                    return accounting.formatMoney(tickValue);

                },
                beginAtZero: true
            },
            position: "right",
            "id": "y-axis-1"
        }

    ];
    var chartType = 'bar';

    drawAChart(URI, container, chartType, options, colorData);
}

/**
 *
 * @param URI
 * @param container
 */
function columnChart(URI, container) {
    "use strict";
    var colorData = true;
    var options = $.extend(true, {}, defaultChartOptions);
    var chartType = 'bar';

    drawAChart(URI, container, chartType, options, colorData);

}

/**
 *
 * @param URI
 * @param container
 */
function stackedColumnChart(URI, container) {
    "use strict";

    var colorData = true;
    var options = $.extend(true, {}, defaultChartOptions);

    options.stacked = true;
    options.scales.xAxes[0].stacked = true;
    options.scales.yAxes[0].stacked = true;

    var chartType = 'bar';

    drawAChart(URI, container, chartType, options, colorData);
}

/**
 *
 * @param URI
 * @param container
 */
function pieChart(URI, container) {
    "use strict";

    var colorData = false;
    var options = $.extend(true, {}, defaultPieOptions);
    var chartType = 'pie';

    drawAChart(URI, container, chartType, options, colorData);

}

/**
 * @param URI
 * @param container
 * @param chartType
 * @param options
 * @param colorData
 * @param today
 */
function drawAChart(URI, container, chartType, options, colorData, today) {
    var containerObj = $('#' + container);
    if (containerObj.length === 0) {
        return;
    }

    $.getJSON(URI).done(function (data) {
        containerObj.removeClass('general-chart-error');
        if (data.labels.length === 0) {
            // remove the chart container + parent
            var holder = $('#' + container).parent().parent();
            if (holder.hasClass('box') || holder.hasClass('box-body')) {
                // find box-body:
                var boxBody;
                if (!holder.hasClass('box-body')) {
                    boxBody = holder.find('.box-body');
                } else {
                    boxBody = holder;
                }
                boxBody.empty().append($('<p>').append($('<em>').text(noDataForChart)));
            }
            return;
        }

        if (colorData) {
            data = colorizeData(data);
        }

        if (allCharts.hasOwnProperty(container)) {
            allCharts[container].data.datasets = data.datasets;
            allCharts[container].data.labels = data.labels;
            allCharts[container].update();
        } else {
            // new chart!
            var ctx = document.getElementById(container).getContext("2d");
            var chartOpts = {
                type: chartType,
                data: data,
                options: options,
                lineAtIndex: []
            };
            if (today >= 0) {
                chartOpts.lineAtIndex.push(today - 1);
            }
            allCharts[container] = new Chart(ctx, chartOpts);
        }

    }).fail(function () {
        $('#' + container).addClass('general-chart-error');
    });
}
<commit_msg>Fix charts in IE<commit_after>/*
 * charts.js
 * Copyright (c) 2017 thegrumpydictator@gmail.com
 *
 * This file is part of Firefly III.
 *
 * Firefly III is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Firefly III is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
 */
/** global: Chart, defaultChartOptions, accounting, defaultPieOptions, noDataForChart, todayText */
var allCharts = {};

/*
 Make some colours:
 */
var colourSet = [
    [53, 124, 165],
    [0, 141, 76],
    [219, 139, 11],
    [202, 25, 90],
    [85, 82, 153],
    [66, 133, 244],
    [219, 68, 55],
    [244, 180, 0],
    [15, 157, 88],
    [171, 71, 188],
    [0, 172, 193],
    [255, 112, 67],
    [158, 157, 36],
    [92, 107, 192],
    [240, 98, 146],
    [0, 121, 107],
    [194, 24, 91]
];

var fillColors = [];
var strokePointHighColors = [];

for (var i = 0; i < colourSet.length; i++) {
    fillColors.push("rgba(" + colourSet[i][0] + ", " + colourSet[i][1] + ", " + colourSet[i][2] + ", 0.5)");
    strokePointHighColors.push("rgba(" + colourSet[i][0] + ", " + colourSet[i][1] + ", " + colourSet[i][2] + ", 0.9)");
}

Chart.defaults.global.legend.display = false;
Chart.defaults.global.animation.duration = 0;
Chart.defaults.global.responsive = true;
Chart.defaults.global.maintainAspectRatio = false;

/**
 * Chart line thing
 */
const verticalLinePlugin = {
    getLinePosition: function (chart, pointIndex) {
        const meta = chart.getDatasetMeta(0); // first dataset is used to discover X coordinate of a point
        const data = meta.data;
        return data[pointIndex]._model.x;
    },
    renderVerticalLine: function (chartInstance, pointIndex) {
        const lineLeftOffset = this.getLinePosition(chartInstance, pointIndex);
        const scale = chartInstance.scales['y-axis-0'];
        const context = chartInstance.chart.ctx;

        // render vertical line
        context.beginPath();
        context.strokeStyle = fillColors[3];
        context.moveTo(lineLeftOffset, scale.top);
        context.lineTo(lineLeftOffset, scale.bottom);
        context.stroke();

        // write label
        context.fillStyle = "#444444";
        context.textAlign = 'left';
        context.fillText(todayText, lineLeftOffset, scale.top * 3); // (scale.bottom - scale.top) / 2 + scale.top
    },

    afterDatasetsDraw: function (chart, easing) {
        if (chart.config.lineAtIndex) {
            chart.config.lineAtIndex.forEach(function(pointIndex) {
                this.renderVerticalLine(chart, pointIndex);
            }, this);
        }
    }
};

Chart.plugins.register(verticalLinePlugin);

/**
 *
 * @param data
 * @returns {{}}
 */
function colorizeData(data) {
    var newData = {};
    newData.datasets = [];

    for (var i = 0; i < data.count; i++) {
        newData.labels = data.labels;
        var dataset = data.datasets[i];
        dataset.fill = false;
        dataset.backgroundColor = dataset.borderColor = fillColors[i];
        newData.datasets.push(dataset);
    }
    return newData;
}

/**
 * Function to draw a line chart:
 * @param URI
 * @param container
 */
function lineChart(URI, container) {
    "use strict";

    var colorData = true;
    var options = $.extend(true, {}, defaultChartOptions);
    var chartType = 'line';

    drawAChart(URI, container, chartType, options, colorData, -1);
}

function lineChartWithDay(URI, container, today) {
    "use strict";
    var colorData = true;
    var options = $.extend(true, {}, defaultChartOptions);
    var chartType = 'line';

    drawAChart(URI, container, chartType, options, colorData, today);
}

/**
 * Function to draw a chart with double Y Axes and stacked columns.
 *
 * @param URI
 * @param container
 */
function doubleYChart(URI, container) {
    "use strict";

    var colorData = true;
    var options = $.extend(true, {}, defaultChartOptions);
    options.scales.yAxes = [
        // y axis 0:
        {
            display: true,
            ticks: {
                callback: function (tickValue) {
                    "use strict";
                    return accounting.formatMoney(tickValue);

                },
                beginAtZero: true
            },
            position: "left",
            "id": "y-axis-0"
        },
        // and y axis 1:
        {
            display: true,
            ticks: {
                callback: function (tickValue) {
                    "use strict";
                    return accounting.formatMoney(tickValue);

                },
                beginAtZero: true
            },
            position: "right",
            "id": "y-axis-1"
        }

    ];
    options.stacked = true;
    options.scales.xAxes[0].stacked = true;

    var chartType = 'bar';

    drawAChart(URI, container, chartType, options, colorData);
}

/**
 * Function to draw a chart with double Y Axes and non stacked columns.
 *
 * @param URI
 * @param container
 */
function doubleYNonStackedChart(URI, container) {
    "use strict";

    var colorData = true;
    var options = $.extend(true, {}, defaultChartOptions);
    options.scales.yAxes = [
        // y axis 0:
        {
            display: true,
            ticks: {
                callback: function (tickValue) {
                    "use strict";
                    return accounting.formatMoney(tickValue);

                },
                beginAtZero: true
            },
            position: "left",
            "id": "y-axis-0"
        },
        // and y axis 1:
        {
            display: true,
            ticks: {
                callback: function (tickValue) {
                    "use strict";
                    return accounting.formatMoney(tickValue);

                },
                beginAtZero: true
            },
            position: "right",
            "id": "y-axis-1"
        }

    ];
    var chartType = 'bar';

    drawAChart(URI, container, chartType, options, colorData);
}

/**
 *
 * @param URI
 * @param container
 */
function columnChart(URI, container) {
    "use strict";
    var colorData = true;
    var options = $.extend(true, {}, defaultChartOptions);
    var chartType = 'bar';

    drawAChart(URI, container, chartType, options, colorData);

}

/**
 *
 * @param URI
 * @param container
 */
function stackedColumnChart(URI, container) {
    "use strict";

    var colorData = true;
    var options = $.extend(true, {}, defaultChartOptions);

    options.stacked = true;
    options.scales.xAxes[0].stacked = true;
    options.scales.yAxes[0].stacked = true;

    var chartType = 'bar';

    drawAChart(URI, container, chartType, options, colorData);
}

/**
 *
 * @param URI
 * @param container
 */
function pieChart(URI, container) {
    "use strict";

    var colorData = false;
    var options = $.extend(true, {}, defaultPieOptions);
    var chartType = 'pie';

    drawAChart(URI, container, chartType, options, colorData);

}

/**
 * @param URI
 * @param container
 * @param chartType
 * @param options
 * @param colorData
 * @param today
 */
function drawAChart(URI, container, chartType, options, colorData, today) {
    var containerObj = $('#' + container);
    if (containerObj.length === 0) {
        return;
    }

    $.getJSON(URI).done(function (data) {
        containerObj.removeClass('general-chart-error');
        if (data.labels.length === 0) {
            // remove the chart container + parent
            var holder = $('#' + container).parent().parent();
            if (holder.hasClass('box') || holder.hasClass('box-body')) {
                // find box-body:
                var boxBody;
                if (!holder.hasClass('box-body')) {
                    boxBody = holder.find('.box-body');
                } else {
                    boxBody = holder;
                }
                boxBody.empty().append($('<p>').append($('<em>').text(noDataForChart)));
            }
            return;
        }

        if (colorData) {
            data = colorizeData(data);
        }

        if (allCharts.hasOwnProperty(container)) {
            allCharts[container].data.datasets = data.datasets;
            allCharts[container].data.labels = data.labels;
            allCharts[container].update();
        } else {
            // new chart!
            var ctx = document.getElementById(container).getContext("2d");
            var chartOpts = {
                type: chartType,
                data: data,
                options: options,
                lineAtIndex: []
            };
            if (today >= 0) {
                chartOpts.lineAtIndex.push(today - 1);
            }
            allCharts[container] = new Chart(ctx, chartOpts);
        }

    }).fail(function () {
        $('#' + container).addClass('general-chart-error');
    });
}
<|endoftext|>


<commit_before>(function(){

	var mi2 = mi2JS;
	var mi2Proto = mi2.prototype;

	function Base(){}

	mi2.comp.def.Base = Base;
	mi2.comp.tpl.Base = '';
	Base.compName = 'Base';
	// extend mi2JS to get addClass and other html utility functions (html.js must be included in the bundle to have them)
	mi2.extend(Base, mi2);

	var proto = Base.prototype; //

	/* called when the component is initially constructed */
	proto.construct = function(el, parent){
		this.el = el;
	};

	proto.__init = function(){
		if(!this.__initialized){
			this.initTemplate();
			this.fireEvent('init',{ });
		}
		this.__initialized = true;
	};

	proto.initTemplate = function(){
		if(this.__template){
			this.el.innerHTML = this.__template;
			this.parseChildren();
			this.initChildren();
		}
		delete this.__template;
	};

	proto.parseChildren = function(){
		this.children = [];
		mi2.parseChildren(this.el,this);
	};

	proto.initChildren = function(){
		var c;
		for(var i=0; i<this.children.length; i++){
			c = this.children[i];
			if(!c.lazyInit) c.__init();
		}
	};

	proto.on_init = function(evt){ };

	proto.getCompName = function(){
		if(this.el && this.el.getAttribute){
			return this.el.getAttribute('as');
		}
	};

	/** listener shortcut that by default binds callback function to current object/component
	so you can use this in callback without extra bloat otherwise needed
	*/
	proto.listen = function(el,evt,listener){
		listener = listener || this['on_'+evt];

		if(el.addListener){
			el.addListener( evt, listener, this);
		}else //dom node
			mi2.listen( el, evt, listener, this);
	};

	//setTimeout and setInterval shortcut that by default binds callback function to current object
	//so you can use this in callback without extra bloat otherwise needed
	proto.setTimeout = function(fc,delay){
		return setTimeout( fc.bind(this), delay );
	};

	proto.setInterval = function(fc,delay){
		return setInterval( fc.bind(this), delay );
	};

	proto.addListener = function(evtName,callback, scope){
		if(!this.__listeners) this.__listeners = {};
		var l = this.__listeners[evtName];
		if(!l) l = this.__listeners[evtName] = [];

		// bind a scope to the callback function
		if(scope) callback = mi2.bind( scope, callback );

		l.push({callback:callback, scope:scope });
	};

	proto.isTransitive = function(){ return false; };

	proto.fireEvent = function(evtName, ex){

		if(evtName == 'show'){
			// if not initialized yet, fire init event first
			this.__init();
		}

		if(!ex) ex = {};
		ex.name = evtName;
		ex.target = this;

		if(ex.eventFor == 'parent' && this.isTransitive && this.isTransitive()){
			if(this.parent) this.parent.fireEvent(evtName, ex);
			return;
		}

		if(typeof(this['on_'+evtName]) == 'function'){
			try{
				this['on_'+evtName](ex);
			}catch(e){
				console.log('Error calling event handler function ','on_'+evtName, ex, 'component', this , 'error', e);
				console.error(e.message);
			}

		}

		var l;
		if(this.__listeners) l=this.__listeners[evtName];
		if(l) for(var i=0; i<l.length; i++){
			try{
				l[i].callback(ex);
			}catch(e){
				console.log('Error firing event ',evtName, ex, 'listener', l[i].scope, 'error', e);
				console.error(e.message);
			}
		}

		var child;
		if(ex.eventFor == 'children' && this.children){
			for(var i=0; i<this.children.length; i++){
				child = this.children[i];
				// if hidden no need for hide/show event to propagate
				if(!child.isVisible() && (evtName == 'hide' || evtName == 'show' )) continue;

				child.fireEvent(evtName, ex);
			}
		}
	};

	proto.addRef = function(r){
		if(!this.__refs) this.__refs = [];
		this.__refs.push(r);
	};

	proto.addChild = function(c){
		if(!this.el) {
			var msg = 'Component not propery initialized. Must call Base constructor before parseChildren';
			console.log(msg, this, this.el);
			throw new Error(msg);
		}
		var cnt = this.children.length >>> 0;
		for(var i =0; i<cnt; i++) if(c == this.children[i]) return;
		this.children.push(c);
	};

	proto.removeChild = function(){
		var found = -1, cnt = this.children.length >>> 0;
		for(var i =0; i<cnt; i++) if(c == this.children[i]) return;
		if(found != -1) this.children.splice(found,1);
	};

	proto.setParent = function(p){
		var old = this.parent;
		if(old && old.removeChild)
			old.removeChild(this);
		this.parent = p;
		if(p  && p.addChild) p.addChild(this);
	};

	/* find component that is holding the DOM node */
	proto.findRef = function(toFind, from){
		var testFunc = function(comp){
			return comp.el == toFind;
		}
		if(typeof toFind == 'string'){
			if('+' == toFind)
				toFind = from.el.nextElementSibling;
			else if('-' == toFind)
				toFind = from.el.previousElementSibling;
			else{
				testFunc = function(comp){
					return comp.attr('p') == toFind;
				}
			}
		}
		var i,cd = this.children;
		if(cd) for(i=0; i<cd.length; i++){
			if(testFunc(cd[i])) return cd[i];
		}
		cd = this.__refs;
		if(cd) for(i=0; i<cd.length; i++){
			if(testFunc(cd[i])) return cd[i];
		}
		// if it is a domNode and we have no reference to it, just return a NodeWrapper
		if(toFind.tagName)
			return new mi2(toFind);
	}

	proto.find    = function(search){ return mi2.find   (this.el, search); }
	proto.findAll = function(search){ return mi2.findAll(this.el, search); }

	/** Use this when the tag on which the component is defined*/
	proto.replaceTag = function(el,tag){
		var ret = document.createElement(tag);
		var arr = el.attributes;
		for(var i=0; i<arr.length; i++){
			ret.setAttribute(arr[i].name, arr[i].value);
		}
		el.parentNode.replaceChild(ret,el);
		return ret;
	};

	proto.isVisibleTruly = function(){
		var c = this;
		while(c){
			if(!c.isVisible()) return false;
			c = c.parent;
		}
		return true;
	};

	proto.setVisible = function(visible){
		if(visible == this.isVisible()) return;

		mi2Proto.setVisible.call(this, visible);

		// when hidden by parent, there is no point in firing the event
		// correct event will be fired when parent becomes visible
		if(!this.parent || this.parent.isVisibleTruly())
			this.fireEvent(visible ? 'show':'hide',{eventFor:'children'});
	};

}(mi2JS));
<commit_msg>small bugfix<commit_after>(function(){

	var mi2 = mi2JS;
	var mi2Proto = mi2.prototype;

	function Base(){}

	mi2.comp.def.Base = Base;
	mi2.comp.tpl.Base = '';
	Base.compName = 'Base';
	// extend mi2JS to get addClass and other html utility functions (html.js must be included in the bundle to have them)
	mi2.extend(Base, mi2);

	var proto = Base.prototype; //

	/* called when the component is initially constructed */
	proto.construct = function(el, parent){
		this.children = [];
		this.el = el;
	};

	proto.__init = function(){
		if(!this.__initialized){
			this.initTemplate();
			this.fireEvent('init',{ });
		}
		this.__initialized = true;
	};

	proto.initTemplate = function(){
		if(this.__template){
			this.el.innerHTML = this.__template;
			this.parseChildren();
			this.initChildren();
		}
		delete this.__template;
	};

	proto.parseChildren = function(){
		mi2.parseChildren(this.el,this);
	};

	proto.initChildren = function(){
		var c;
		for(var i=0; i<this.children.length; i++){
			c = this.children[i];
			if(!c.lazyInit) c.__init();
		}
	};

	proto.on_init = function(evt){ };

	proto.getCompName = function(){
		if(this.el && this.el.getAttribute){
			return this.el.getAttribute('as');
		}
	};

	/** listener shortcut that by default binds callback function to current object/component
	so you can use this in callback without extra bloat otherwise needed
	*/
	proto.listen = function(el,evt,listener){
		listener = listener || this['on_'+evt];

		if(el.addListener){
			el.addListener( evt, listener, this);
		}else //dom node
			mi2.listen( el, evt, listener, this);
	};

	//setTimeout and setInterval shortcut that by default binds callback function to current object
	//so you can use this in callback without extra bloat otherwise needed
	proto.setTimeout = function(fc,delay){
		return setTimeout( fc.bind(this), delay );
	};

	proto.setInterval = function(fc,delay){
		return setInterval( fc.bind(this), delay );
	};

	proto.addListener = function(evtName,callback, scope){
		if(!this.__listeners) this.__listeners = {};
		var l = this.__listeners[evtName];
		if(!l) l = this.__listeners[evtName] = [];

		// bind a scope to the callback function
		if(scope) callback = mi2.bind( scope, callback );

		l.push({callback:callback, scope:scope });
	};

	proto.isTransitive = function(){ return false; };

	proto.fireEvent = function(evtName, ex){

		if(evtName == 'show'){
			// if not initialized yet, fire init event first
			this.__init();
		}

		if(!ex) ex = {};
		ex.name = evtName;
		ex.target = this;

		if(ex.eventFor == 'parent' && this.isTransitive && this.isTransitive()){
			if(this.parent) this.parent.fireEvent(evtName, ex);
			return;
		}

		if(typeof(this['on_'+evtName]) == 'function'){
			try{
				this['on_'+evtName](ex);
			}catch(e){
				console.log('Error calling event handler function ','on_'+evtName, ex, 'component', this , 'error', e);
				console.error(e.message);
			}

		}

		var l;
		if(this.__listeners) l=this.__listeners[evtName];
		if(l) for(var i=0; i<l.length; i++){
			try{
				l[i].callback(ex);
			}catch(e){
				console.log('Error firing event ',evtName, ex, 'listener', l[i].scope, 'error', e);
				console.error(e.message);
			}
		}

		var child;
		if(ex.eventFor == 'children' && this.children){
			for(var i=0; i<this.children.length; i++){
				child = this.children[i];
				// if hidden no need for hide/show event to propagate
				if(!child.isVisible() && (evtName == 'hide' || evtName == 'show' )) continue;

				child.fireEvent(evtName, ex);
			}
		}
	};

	proto.addRef = function(r){
		if(!this.__refs) this.__refs = [];
		this.__refs.push(r);
	};

	proto.addChild = function(c){
		if(!this.el) {
			var msg = 'Component not propery initialized. Must call Base constructor before parseChildren';
			console.log(msg, this, this.el);
			throw new Error(msg);
		}
		var cnt = this.children.length >>> 0;
		for(var i =0; i<cnt; i++) if(c == this.children[i]) return;
		this.children.push(c);
	};

	proto.removeChild = function(){
		var found = -1, cnt = this.children.length >>> 0;
		for(var i =0; i<cnt; i++) if(c == this.children[i]) return;
		if(found != -1) this.children.splice(found,1);
	};

	proto.setParent = function(p){
		var old = this.parent;
		if(old && old.removeChild)
			old.removeChild(this);
		this.parent = p;
		if(p  && p.addChild) p.addChild(this);
	};

	/* find component that is holding the DOM node */
	proto.findRef = function(toFind, from){
		var testFunc = function(comp){
			return comp.el == toFind;
		}
		if(typeof toFind == 'string'){
			if('+' == toFind)
				toFind = from.el.nextElementSibling;
			else if('-' == toFind)
				toFind = from.el.previousElementSibling;
			else{
				testFunc = function(comp){
					return comp.attr('p') == toFind;
				}
			}
		}
		var i,cd = this.children;
		if(cd) for(i=0; i<cd.length; i++){
			if(testFunc(cd[i])) return cd[i];
		}
		cd = this.__refs;
		if(cd) for(i=0; i<cd.length; i++){
			if(testFunc(cd[i])) return cd[i];
		}
		// if it is a domNode and we have no reference to it, just return a NodeWrapper
		if(toFind.tagName)
			return new mi2(toFind);
	}

	proto.find    = function(search){ return mi2.find   (this.el, search); }
	proto.findAll = function(search){ return mi2.findAll(this.el, search); }

	/** Use this when the tag on which the component is defined*/
	proto.replaceTag = function(el,tag){
		var ret = document.createElement(tag);
		var arr = el.attributes;
		for(var i=0; i<arr.length; i++){
			ret.setAttribute(arr[i].name, arr[i].value);
		}
		el.parentNode.replaceChild(ret,el);
		return ret;
	};

	proto.isVisibleTruly = function(){
		var c = this;
		while(c){
			if(!c.isVisible()) return false;
			c = c.parent;
		}
		return true;
	};

	proto.setVisible = function(visible){
		if(visible == this.isVisible()) return;

		mi2Proto.setVisible.call(this, visible);

		// when hidden by parent, there is no point in firing the event
		// correct event will be fired when parent becomes visible
		if(!this.parent || this.parent.isVisibleTruly())
			this.fireEvent(visible ? 'show':'hide',{eventFor:'children'});
	};

}(mi2JS));
<|endoftext|>


<commit_before>/**
 * Copyright 2013-2015, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule reactComponentExpect
 * @nolint
 */

'use strict';

var ReactInstanceMap = require('ReactInstanceMap');
var ReactTestUtils = require('ReactTestUtils');

var assign = require('Object.assign');

function reactComponentExpect(instance) {
  if (instance instanceof reactComponentExpectInternal) {
    return instance;
  }

  if (!(this instanceof reactComponentExpect)) {
    return new reactComponentExpect(instance);
  }

  expect(instance).not.toBeNull();

  var internalInstance = ReactInstanceMap.get(instance);

  expect(typeof internalInstance).toBe('object');
  expect(typeof internalInstance.constructor).toBe('function');
  expect(ReactTestUtils.isElement(internalInstance)).toBe(false);

  return new reactComponentExpectInternal(internalInstance);
}

function reactComponentExpectInternal(internalInstance) {
  this._instance = internalInstance;
}

assign(reactComponentExpectInternal.prototype, {
  // Getters -------------------------------------------------------------------

  /**
   * @instance: Retrieves the backing instance.
   */
  instance: function() {
    return this._instance.getPublicInstance();
  },

  /**
   * There are two types of components in the world.
   * - A component created via React.createClass() - Has a single child
   *   subComponent - the return value from the .render() function. This
   *   function @subComponent expects that this._instance is component created
   *   with React.createClass().
   * - A primitive DOM component - which has many renderedChildren, each of
   *   which may have a name that is unique with respect to its siblings. This
   *   method will fail if this._instance is a primitive component.
   *
   * TL;DR: An instance may have a subComponent (this._renderedComponent) or
   * renderedChildren, but never both. Neither will actually show up until you
   * render the component (simply instantiating is not enough).
   */
  expectRenderedChild: function() {
    this.toBeCompositeComponent();
    var child = this._instance._renderedComponent;
    // TODO: Hide ReactEmptyComponent instances here?
    return new reactComponentExpectInternal(child);
  },

  /**
   * The nth child of a DOMish component instance that is not falsy.
   */
  expectRenderedChildAt: function(childIndex) {
    // Currently only dom components have arrays of children, but that will
    // change soon.
    this.toBeDOMComponent();
    var renderedChildren =
      this._instance._renderedComponent._renderedChildren || {};
    for (var name in renderedChildren) {
      if (!renderedChildren.hasOwnProperty(name)) {
        continue;
      }
      if (renderedChildren[name]) {
        if (renderedChildren[name]._mountIndex === childIndex) {
          return new reactComponentExpectInternal(renderedChildren[name]);
        }
      }
    }
    throw new Error('Child:' + childIndex + ' is not found');
  },

  toBeDOMComponentWithChildCount: function(n) {
    this.toBeDOMComponent();
    expect(this._instance._renderedComponent._renderedChildren).toBeTruthy();
    var len = Object.keys(this._instance._renderedComponent._renderedChildren)
              .length;
    expect(len).toBe(n);
    return this;
  },

  toBeDOMComponentWithNoChildren: function() {
    this.toBeDOMComponent();
    expect(this._instance._renderedComponent._renderedChildren).toBeFalsy();
    return this;
  },

  // Matchers ------------------------------------------------------------------

  toBeComponentOfType: function(constructor) {
    expect(
      this._instance._currentElement.type === constructor
    ).toBe(true);
    return this;
  },

  /**
   * A component that is created with React.createClass. Just duck typing
   * here.
   */
  toBeCompositeComponent: function() {
    expect(
      typeof this.instance() === 'object' &&
      typeof this.instance().render === 'function'
    ).toBe(true);
    return this;
  },

  toBeCompositeComponentWithType: function(constructor) {
    this.toBeCompositeComponent();
    expect(
      this._instance._currentElement.type === constructor
    ).toBe(true);
    return this;
  },

  toBeTextComponentWithValue: function(val) {
    var elementType = typeof this._instance._currentElement;
    expect(elementType === 'string' || elementType === 'number').toBe(true);
    expect(this._instance._stringText).toBe(val);
    return this;
  },

  toBePresent: function() {
    expect(this.instance()).toBeTruthy();
    return this;
  },

  /**
   * A terminal type of component representing some virtual dom node. Just duck
   * typing here.
   */
  toBeDOMComponent: function() {
    expect(ReactTestUtils.isDOMComponent(this.instance())).toBe(true);
    return this;
  },

  /**
   * @deprecated
   * @see toBeComponentOfType
   */
  toBeDOMComponentWithTag: function(tag) {
    this.toBeDOMComponent();
    expect(this.instance().tagName).toBe(tag.toUpperCase());
    return this;
  },

  /**
   * Check that internal state values are equal to a state of expected values.
   */
  scalarStateEqual: function(stateNameToExpectedValue) {
    expect(this.instance()).toBeTruthy();
    for (var stateName in stateNameToExpectedValue) {
      if (!stateNameToExpectedValue.hasOwnProperty(stateName)) {
        continue;
      }
      expect(this.instance().state[stateName])
        .toEqual(stateNameToExpectedValue[stateName]);
    }
    return this;
  },

  /**
   * Check a set of props are equal to a set of expected values - only works
   * with scalars.
   */
  scalarPropsEqual: function(propNameToExpectedValue) {
    expect(this.instance()).toBeTruthy();
    for (var propName in propNameToExpectedValue) {
      if (!propNameToExpectedValue.hasOwnProperty(propName)) {
        continue;
      }
      expect(this.instance().props[propName])
        .toEqual(propNameToExpectedValue[propName]);
    }
    return this;
  },

  /**
   * Check a set of props are equal to a set of expected values - only works
   * with scalars.
   */
  scalarContextEqual: function(contextNameToExpectedValue) {
    expect(this.instance()).toBeTruthy();
    for (var contextName in contextNameToExpectedValue) {
      if (!contextNameToExpectedValue.hasOwnProperty(contextName)) {
        continue;
      }
      expect(this.instance().context[contextName])
        .toEqual(contextNameToExpectedValue[contextName]);
    }
    return this;
  }
});

module.exports = reactComponentExpect;
<commit_msg>toBeDOMComponentWithChildCount readability<commit_after>/**
 * Copyright 2013-2015, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule reactComponentExpect
 * @nolint
 */

'use strict';

var ReactInstanceMap = require('ReactInstanceMap');
var ReactTestUtils = require('ReactTestUtils');

var assign = require('Object.assign');

function reactComponentExpect(instance) {
  if (instance instanceof reactComponentExpectInternal) {
    return instance;
  }

  if (!(this instanceof reactComponentExpect)) {
    return new reactComponentExpect(instance);
  }

  expect(instance).not.toBeNull();

  var internalInstance = ReactInstanceMap.get(instance);

  expect(typeof internalInstance).toBe('object');
  expect(typeof internalInstance.constructor).toBe('function');
  expect(ReactTestUtils.isElement(internalInstance)).toBe(false);

  return new reactComponentExpectInternal(internalInstance);
}

function reactComponentExpectInternal(internalInstance) {
  this._instance = internalInstance;
}

assign(reactComponentExpectInternal.prototype, {
  // Getters -------------------------------------------------------------------

  /**
   * @instance: Retrieves the backing instance.
   */
  instance: function() {
    return this._instance.getPublicInstance();
  },

  /**
   * There are two types of components in the world.
   * - A component created via React.createClass() - Has a single child
   *   subComponent - the return value from the .render() function. This
   *   function @subComponent expects that this._instance is component created
   *   with React.createClass().
   * - A primitive DOM component - which has many renderedChildren, each of
   *   which may have a name that is unique with respect to its siblings. This
   *   method will fail if this._instance is a primitive component.
   *
   * TL;DR: An instance may have a subComponent (this._renderedComponent) or
   * renderedChildren, but never both. Neither will actually show up until you
   * render the component (simply instantiating is not enough).
   */
  expectRenderedChild: function() {
    this.toBeCompositeComponent();
    var child = this._instance._renderedComponent;
    // TODO: Hide ReactEmptyComponent instances here?
    return new reactComponentExpectInternal(child);
  },

  /**
   * The nth child of a DOMish component instance that is not falsy.
   */
  expectRenderedChildAt: function(childIndex) {
    // Currently only dom components have arrays of children, but that will
    // change soon.
    this.toBeDOMComponent();
    var renderedChildren =
      this._instance._renderedComponent._renderedChildren || {};
    for (var name in renderedChildren) {
      if (!renderedChildren.hasOwnProperty(name)) {
        continue;
      }
      if (renderedChildren[name]) {
        if (renderedChildren[name]._mountIndex === childIndex) {
          return new reactComponentExpectInternal(renderedChildren[name]);
        }
      }
    }
    throw new Error('Child:' + childIndex + ' is not found');
  },

  toBeDOMComponentWithChildCount: function(count) {
    this.toBeDOMComponent();
    var renderedChildren = this._instance._renderedComponent._renderedChildren;
    expect(renderedChildren).toBeTruthy();
    expect(Object.keys(renderedChildren).length).toBe(count);
    return this;
  },

  toBeDOMComponentWithNoChildren: function() {
    this.toBeDOMComponent();
    expect(this._instance._renderedComponent._renderedChildren).toBeFalsy();
    return this;
  },

  // Matchers ------------------------------------------------------------------

  toBeComponentOfType: function(constructor) {
    expect(
      this._instance._currentElement.type === constructor
    ).toBe(true);
    return this;
  },

  /**
   * A component that is created with React.createClass. Just duck typing
   * here.
   */
  toBeCompositeComponent: function() {
    expect(
      typeof this.instance() === 'object' &&
      typeof this.instance().render === 'function'
    ).toBe(true);
    return this;
  },

  toBeCompositeComponentWithType: function(constructor) {
    this.toBeCompositeComponent();
    expect(
      this._instance._currentElement.type === constructor
    ).toBe(true);
    return this;
  },

  toBeTextComponentWithValue: function(val) {
    var elementType = typeof this._instance._currentElement;
    expect(elementType === 'string' || elementType === 'number').toBe(true);
    expect(this._instance._stringText).toBe(val);
    return this;
  },

  toBePresent: function() {
    expect(this.instance()).toBeTruthy();
    return this;
  },

  /**
   * A terminal type of component representing some virtual dom node. Just duck
   * typing here.
   */
  toBeDOMComponent: function() {
    expect(ReactTestUtils.isDOMComponent(this.instance())).toBe(true);
    return this;
  },

  /**
   * @deprecated
   * @see toBeComponentOfType
   */
  toBeDOMComponentWithTag: function(tag) {
    this.toBeDOMComponent();
    expect(this.instance().tagName).toBe(tag.toUpperCase());
    return this;
  },

  /**
   * Check that internal state values are equal to a state of expected values.
   */
  scalarStateEqual: function(stateNameToExpectedValue) {
    expect(this.instance()).toBeTruthy();
    for (var stateName in stateNameToExpectedValue) {
      if (!stateNameToExpectedValue.hasOwnProperty(stateName)) {
        continue;
      }
      expect(this.instance().state[stateName])
        .toEqual(stateNameToExpectedValue[stateName]);
    }
    return this;
  },

  /**
   * Check a set of props are equal to a set of expected values - only works
   * with scalars.
   */
  scalarPropsEqual: function(propNameToExpectedValue) {
    expect(this.instance()).toBeTruthy();
    for (var propName in propNameToExpectedValue) {
      if (!propNameToExpectedValue.hasOwnProperty(propName)) {
        continue;
      }
      expect(this.instance().props[propName])
        .toEqual(propNameToExpectedValue[propName]);
    }
    return this;
  },

  /**
   * Check a set of props are equal to a set of expected values - only works
   * with scalars.
   */
  scalarContextEqual: function(contextNameToExpectedValue) {
    expect(this.instance()).toBeTruthy();
    for (var contextName in contextNameToExpectedValue) {
      if (!contextNameToExpectedValue.hasOwnProperty(contextName)) {
        continue;
      }
      expect(this.instance().context[contextName])
        .toEqual(contextNameToExpectedValue[contextName]);
    }
    return this;
  }
});

module.exports = reactComponentExpect;
<|endoftext|>