<commit_before>/** This component represents a json table object. */
apogeeapp.app.JsonTableComponent = function(workspaceUI,table,componentJson) {
    //extend edit component
    apogeeapp.app.EditComponent.call(this,workspaceUI,table,apogeeapp.app.JsonTableComponent.generator,componentJson);

    //to do:
    //need to ave this and read if from component json, during create or read
    //need to refresh a display if we change it when data view active
    //need to refresh the view from the cache (whereit is happening...)
    this.dataView = (componentJson.dataView !== undefined ) ? componentJson.dataView : apogeeapp.app.JsonTableComponent.PLAIN_DATA_VEW;

    this.memberUpdated();

    //add a cleanup and save actions
    this.addSaveAction(apogeeapp.app.JsonTableComponent.writeToJson);
};

apogeeapp.app.JsonTableComponent.prototype = Object.create(apogeeapp.app.EditComponent.prototype);
apogeeapp.app.JsonTableComponent.prototype.constructor = apogeeapp.app.JsonTableComponent;

apogeeapp.app.JsonTableComponent.prototype.getDataView = function() {
    if(!this.dataView) this.dataView = "Plain";
    return this.dataView;
}

apogeeapp.app.JsonTableComponent.prototype.setDataView = function(dataView) {
    this.dataView = dataView;
    //update the window display if needed
    var windowDisplay = this.getWindowDisplay();
    if(windowDisplay) {
        windowDisplay.updateViewModeElement(apogeeapp.app.JsonTableComponent.VIEW_DATA);
    }
}

//==============================
// Protected and Private Instance Methods
//==============================

apogeeapp.app.JsonTableComponent.VIEW_DATA = "Data";
apogeeapp.app.JsonTableComponent.VIEW_CODE = "Formula";
apogeeapp.app.JsonTableComponent.VIEW_SUPPLEMENTAL_CODE = "Private";
apogeeapp.app.JsonTableComponent.VIEW_DESCRIPTION = "Notes";

apogeeapp.app.JsonTableComponent.VIEW_MODES = [
    apogeeapp.app.JsonTableComponent.VIEW_DATA,
    apogeeapp.app.JsonTableComponent.VIEW_CODE,
    apogeeapp.app.JsonTableComponent.VIEW_SUPPLEMENTAL_CODE,
    apogeeapp.app.JsonTableComponent.VIEW_DESCRIPTION
];

apogeeapp.app.JsonTableComponent.TABLE_EDIT_SETTINGS = {
    "viewModes": apogeeapp.app.JsonTableComponent.VIEW_MODES,
    "defaultView": apogeeapp.app.JsonTableComponent.VIEW_DATA,
    "clearFunctionMenuText": "Clear Formula",
    "emptyDataValue": ""
}

apogeeapp.app.JsonTableComponent.PLAIN_DATA_VEW = "Plain";
apogeeapp.app.JsonTableComponent.JSON_DATA_VEW = "JSON";
apogeeapp.app.JsonTableComponent.FORM_DATA_VIEW = "Form";

/**  This method retrieves the table edit settings for this component instance
 * @protected */
apogeeapp.app.JsonTableComponent.prototype.getTableEditSettings = function() {
    return apogeeapp.app.JsonTableComponent.TABLE_EDIT_SETTINGS;
}

/** This method should be implemented to retrieve a view mode of the give type.
 * @protected. */
apogeeapp.app.JsonTableComponent.prototype.getViewModeElement = function(editComponentDisplay,viewType) {

	//create the new view element;
	switch(viewType) {
        case apogeeapp.app.JsonTableComponent.VIEW_DATA:
            switch(this.dataView) {
                case apogeeapp.app.JsonTableComponent.JSON_DATA_VEW:
                    return new apogeeapp.app.AceDataMode(editComponentDisplay,true);

                case apogeeapp.app.JsonTableComponent.FORM_DATA_VIEW:
                    return new apogeeapp.app.FormDataMode(editComponentDisplay);

                case apogeeapp.app.JsonTableComponent.PLAIN_DATA_VEW:
                default:
                    return new apogeeapp.app.AceDataMode(editComponentDisplay,false);
            }

		case apogeeapp.app.JsonTableComponent.VIEW_CODE:
			return new apogeeapp.app.AceCodeMode(editComponentDisplay);

		case apogeeapp.app.JsonTableComponent.VIEW_SUPPLEMENTAL_CODE:
			return new apogeeapp.app.AceSupplementalMode(editComponentDisplay);

        case apogeeapp.app.JsonTableComponent.VIEW_DESCRIPTION:
			return new apogeeapp.app.AceDescriptionMode(editComponentDisplay);

		default:
//temporary error handling...
			alert("unrecognized view element!");
			return null;
	}
}

//======================================
// Static methods
//======================================

apogeeapp.app.JsonTableComponent.createComponent = function(workspaceUI,data,componentOptions) {

    var json = {};
    json.action = "createMember";
    json.owner = data.parent;
    json.workspace = data.parent.getWorkspace();
    json.name = data.name;
    json.type = apogee.JsonTable.generator.type;
    var actionResponse = apogee.action.doAction(json,true);

    var table = json.member;
    if(table) {

        //need to add data view to component options
        var componentJson;
        if(componentOptions) {
            componentJson = apogee.util.jsonCopy(componentOptions);
        }
        else {
            componentJson = {};
        }

        if(data.dataView) {
            componentJson.dataView = data.dataView;
        }

        var tableComponent = new apogeeapp.app.JsonTableComponent(workspaceUI,table,componentJson);
        actionResponse.component = tableComponent;
    }
    return actionResponse;
}

apogeeapp.app.JsonTableComponent.createComponentFromJson = function(workspaceUI,member,componentJson) {
    var tableComponent = new apogeeapp.app.JsonTableComponent(workspaceUI,member,componentJson);
    return tableComponent;
}

apogeeapp.app.JsonTableComponent.writeToJson = function(json) {
    json.dataView = this.dataView;
}

apogeeapp.app.JsonTableComponent.addPropFunction = function(component,values) {
    values.dataView = component.getDataView();
}

apogeeapp.app.JsonTableComponent.updateProperties = function(component,oldValues,newValues,actionResponse) {
    component.setDataView(newValues.dataView);
}

//======================================
// This is the component generator, to register the component
//======================================

apogeeapp.app.JsonTableComponent.generator = {};
apogeeapp.app.JsonTableComponent.generator.displayName = "Data Table";
apogeeapp.app.JsonTableComponent.generator.uniqueName = "apogeeapp.app.JsonTableComponent";
apogeeapp.app.JsonTableComponent.generator.createComponent = apogeeapp.app.JsonTableComponent.createComponent;
apogeeapp.app.JsonTableComponent.generator.createComponentFromJson = apogeeapp.app.JsonTableComponent.createComponentFromJson;
apogeeapp.app.JsonTableComponent.generator.DEFAULT_WIDTH = 300;
apogeeapp.app.JsonTableComponent.generator.DEFAULT_HEIGHT = 300;
apogeeapp.app.JsonTableComponent.generator.ICON_RES_PATH = "/dataIcon.png";

apogeeapp.app.JsonTableComponent.generator.propertyDialogLines = [
    {
        "type":"dropdown",
        "heading":"Data View: ",
        "entries":[
            "Plain",
            "JSON",
            "Form"
        ],
        "resultKey":"dataView"
    }
];

apogeeapp.app.JsonTableComponent.generator.addPropFunction = apogeeapp.app.JsonTableComponent.addPropFunction;
apogeeapp.app.JsonTableComponent.generator.updateProperties = apogeeapp.app.JsonTableComponent.updateProperties;

<commit_msg>set json as the defualt view for data table<commit_after>/** This component represents a json table object. */
apogeeapp.app.JsonTableComponent = function(workspaceUI,table,componentJson) {
    //extend edit component
    apogeeapp.app.EditComponent.call(this,workspaceUI,table,apogeeapp.app.JsonTableComponent.generator,componentJson);

    //to do:
    //need to ave this and read if from component json, during create or read
    //need to refresh a display if we change it when data view active
    //need to refresh the view from the cache (whereit is happening...)
    this.dataView = (componentJson.dataView !== undefined ) ? componentJson.dataView : apogeeapp.app.JsonTableComponent.PLAIN_DATA_VEW;

    this.memberUpdated();

    //add a cleanup and save actions
    this.addSaveAction(apogeeapp.app.JsonTableComponent.writeToJson);
};

apogeeapp.app.JsonTableComponent.prototype = Object.create(apogeeapp.app.EditComponent.prototype);
apogeeapp.app.JsonTableComponent.prototype.constructor = apogeeapp.app.JsonTableComponent;

apogeeapp.app.JsonTableComponent.prototype.getDataView = function() {
    if(!this.dataView) this.dataView = "JSON";
    return this.dataView;
}

apogeeapp.app.JsonTableComponent.prototype.setDataView = function(dataView) {
    this.dataView = dataView;
    //update the window display if needed
    var windowDisplay = this.getWindowDisplay();
    if(windowDisplay) {
        windowDisplay.updateViewModeElement(apogeeapp.app.JsonTableComponent.VIEW_DATA);
    }
}

//==============================
// Protected and Private Instance Methods
//==============================

apogeeapp.app.JsonTableComponent.VIEW_DATA = "Data";
apogeeapp.app.JsonTableComponent.VIEW_CODE = "Formula";
apogeeapp.app.JsonTableComponent.VIEW_SUPPLEMENTAL_CODE = "Private";
apogeeapp.app.JsonTableComponent.VIEW_DESCRIPTION = "Notes";

apogeeapp.app.JsonTableComponent.VIEW_MODES = [
    apogeeapp.app.JsonTableComponent.VIEW_DATA,
    apogeeapp.app.JsonTableComponent.VIEW_CODE,
    apogeeapp.app.JsonTableComponent.VIEW_SUPPLEMENTAL_CODE,
    apogeeapp.app.JsonTableComponent.VIEW_DESCRIPTION
];

apogeeapp.app.JsonTableComponent.TABLE_EDIT_SETTINGS = {
    "viewModes": apogeeapp.app.JsonTableComponent.VIEW_MODES,
    "defaultView": apogeeapp.app.JsonTableComponent.VIEW_DATA,
    "clearFunctionMenuText": "Clear Formula",
    "emptyDataValue": ""
}

apogeeapp.app.JsonTableComponent.PLAIN_DATA_VEW = "Plain";
apogeeapp.app.JsonTableComponent.JSON_DATA_VEW = "JSON";
apogeeapp.app.JsonTableComponent.FORM_DATA_VIEW = "Form";

/**  This method retrieves the table edit settings for this component instance
 * @protected */
apogeeapp.app.JsonTableComponent.prototype.getTableEditSettings = function() {
    return apogeeapp.app.JsonTableComponent.TABLE_EDIT_SETTINGS;
}

/** This method should be implemented to retrieve a view mode of the give type.
 * @protected. */
apogeeapp.app.JsonTableComponent.prototype.getViewModeElement = function(editComponentDisplay,viewType) {

	//create the new view element;
	switch(viewType) {
        case apogeeapp.app.JsonTableComponent.VIEW_DATA:
            switch(this.dataView) {
                case apogeeapp.app.JsonTableComponent.JSON_DATA_VEW:
                    return new apogeeapp.app.AceDataMode(editComponentDisplay,true);

                case apogeeapp.app.JsonTableComponent.FORM_DATA_VIEW:
                    return new apogeeapp.app.FormDataMode(editComponentDisplay);

                case apogeeapp.app.JsonTableComponent.PLAIN_DATA_VEW:
                default:
                    return new apogeeapp.app.AceDataMode(editComponentDisplay,false);
            }

		case apogeeapp.app.JsonTableComponent.VIEW_CODE:
			return new apogeeapp.app.AceCodeMode(editComponentDisplay);

		case apogeeapp.app.JsonTableComponent.VIEW_SUPPLEMENTAL_CODE:
			return new apogeeapp.app.AceSupplementalMode(editComponentDisplay);

        case apogeeapp.app.JsonTableComponent.VIEW_DESCRIPTION:
			return new apogeeapp.app.AceDescriptionMode(editComponentDisplay);

		default:
//temporary error handling...
			alert("unrecognized view element!");
			return null;
	}
}

//======================================
// Static methods
//======================================

apogeeapp.app.JsonTableComponent.createComponent = function(workspaceUI,data,componentOptions) {

    var json = {};
    json.action = "createMember";
    json.owner = data.parent;
    json.workspace = data.parent.getWorkspace();
    json.name = data.name;
    json.type = apogee.JsonTable.generator.type;
    var actionResponse = apogee.action.doAction(json,true);

    var table = json.member;
    if(table) {

        //need to add data view to component options
        var componentJson;
        if(componentOptions) {
            componentJson = apogee.util.jsonCopy(componentOptions);
        }
        else {
            componentJson = {};
        }

        if(data.dataView) {
            componentJson.dataView = data.dataView;
        }

        var tableComponent = new apogeeapp.app.JsonTableComponent(workspaceUI,table,componentJson);
        actionResponse.component = tableComponent;
    }
    return actionResponse;
}

apogeeapp.app.JsonTableComponent.createComponentFromJson = function(workspaceUI,member,componentJson) {
    var tableComponent = new apogeeapp.app.JsonTableComponent(workspaceUI,member,componentJson);
    return tableComponent;
}

apogeeapp.app.JsonTableComponent.writeToJson = function(json) {
    json.dataView = this.dataView;
}

apogeeapp.app.JsonTableComponent.addPropFunction = function(component,values) {
    values.dataView = component.getDataView();
}

apogeeapp.app.JsonTableComponent.updateProperties = function(component,oldValues,newValues,actionResponse) {
    component.setDataView(newValues.dataView);
}

//======================================
// This is the component generator, to register the component
//======================================

apogeeapp.app.JsonTableComponent.generator = {};
apogeeapp.app.JsonTableComponent.generator.displayName = "Data Table";
apogeeapp.app.JsonTableComponent.generator.uniqueName = "apogeeapp.app.JsonTableComponent";
apogeeapp.app.JsonTableComponent.generator.createComponent = apogeeapp.app.JsonTableComponent.createComponent;
apogeeapp.app.JsonTableComponent.generator.createComponentFromJson = apogeeapp.app.JsonTableComponent.createComponentFromJson;
apogeeapp.app.JsonTableComponent.generator.DEFAULT_WIDTH = 300;
apogeeapp.app.JsonTableComponent.generator.DEFAULT_HEIGHT = 300;
apogeeapp.app.JsonTableComponent.generator.ICON_RES_PATH = "/dataIcon.png";

apogeeapp.app.JsonTableComponent.generator.propertyDialogLines = [
    {
        "type":"dropdown",
        "heading":"Data View: ",
        "entries":[
            "JSON",
            "Plain",
            "Form"
        ],
        "resultKey":"dataView"
    }
];

apogeeapp.app.JsonTableComponent.generator.addPropFunction = apogeeapp.app.JsonTableComponent.addPropFunction;
apogeeapp.app.JsonTableComponent.generator.updateProperties = apogeeapp.app.JsonTableComponent.updateProperties;

<|endoftext|>


<commit_before>define([
  'backbone',
  'extensions/mixins/date-functions',
  'modernizr',
  'jquery',
  'lodash'
],
function (Backbone, DateFunctions, Modernizr, $, _) {
  var View = Backbone.View.extend({

    modernizr: Modernizr,

    initialize: function (options) {
      options = options || {};
      this.viewInstances = {};
      Backbone.View.prototype.initialize.apply(this, arguments);
      var el = options.el;
      delete options.el;
      _.extend(this, options);
      options.el = el;
    },

    render: function (options) {
      options = options || {};
      this.removeSubviews(options);
      if (this.template) {
        var context = _.extend(
          this.templateContext(), options.context
        );
        this.$el.html(this.template(context));
      }
      this.renderSubviews(options);
    },

    templateContext: function () {
      var context = {
        model: this.model,
        collection: this.collection
      };
      if (this.model) {
        _.extend(context, this.model.toJSON());
      }
      return context;
    },

    defaultSubviewOptions: function () {
      return {
        renderOnInit: true,
        model: this.model,
        collection: this.collection
      };
    },

    removeSubviews: function () {
      _.invoke(this.viewInstances, 'remove');
      this.viewInstances = {};
    },

    renderSubviews: function () {

      var viewsDefinition = this.views;
      if (_.isFunction(viewsDefinition)) {
        viewsDefinition = viewsDefinition.call(this);
      }
      var instances = this.viewInstances;
      _.each(viewsDefinition, function (definition, selector) {
        var $el = this.$el.find(selector);
        if (!$el.length) {
          console.warn('No element found for ' + selector);
          return;
        }

        var View,
            options = this.defaultSubviewOptions();

        $el.empty();
        options.$el = $el;

        if (typeof definition === 'function') {
          View = definition.call(this);
        } else if (_.isObject(definition)) {
          View = definition.view;
          if (_.isFunction(definition.options)) {
            _.extend(options, definition.options.call(this));
          } else {
            _.extend(options, definition.options);
          }
        } else {
          console.warn('Invalid view definition for ' + selector);
          return;
        }

        var instance = instances[selector] = new View(options);
        instance.render({
          context: this.templateContext()
        });
      }, this);
    },

    /*
     *  Subviews definition. Override in subclasses.
     *
     *  Syntax:
     *  views: {
     *      '.selector1': SubView,
     *      // default, provides subview with result of this.defaultSubviewOptions()
     *
     *      '#selector2': {
     *        view: AnotherView,
     *        options: {
     *          // overrides to this.defaultSubviewOptions
     *          model: this.collection.selectedItem,
     *          customOption: 'foo'
     *        }
     *      },
     *      '.selector3': {
     *        view: YetAnotherView,
     *        options: function () {
     *          // `this` refers to parent view
     *          return {
     *            // overrides to this.defaultSubviewOptions
     *            model: this.collection.selectedItem,
     *            customOption: 'foo'
     *          }
     *        }
     *      }
     *  },
     *
     *  N.B.: Selectors need to refer to elements created by the parent view.
     *  No wrapper element is created for subviews.
     */
    views: {},

    keys: {
      escape: 27
    },

    magnitudes: {
      billion:  {value: 1e9, suffix: 'b' },
      million:  {value: 1e6, suffix: 'm' },
      thousand: {value: 1e3, suffix: 'k' },
      unit:     {value: 1,   suffix: ''  }
    },

    currencies: {
      gbp: { prefix: '£', suffix: '' }
    },

    magnitudeFor: function (value) {
      if (value >= 1e9) return this.magnitudes.billion;
      if (value >= 1e6) return this.magnitudes.million;
      if (value >= 1e3) return this.magnitudes.thousand;
      return this.magnitudes.unit;
    },

    formatNumber: function (value, magnitude, decimalPlaces, currency) {
      var val = (value / magnitude.value).toFixed(decimalPlaces || 0).toString() + magnitude.suffix;
      if (currency) {
        val = currency.prefix + val + currency.suffix;
      }
      return val;
    },

    /**
     * Returns a number formatting function whose actual format depends on the values passed as argument.
     * The formatter can then be used to format all the number in the series applying the same format, regardless of the
     * individual values. This is especially useful for graph axes, where a homogeneous formatting of the labels is
     * required.
     *
     * @param values
     * @return {Function}
     */
    numberListFormatter: function (values, currency) {
      function isAnExactMultipleOf(magnitude) {
        return function (n) { return n % magnitude === 0; };
      }

      var max = values.reduce(function (a, b) {return a > b ? a : b; });
      var magnitude = this.magnitudeFor(max);
      var decimalPlaces;
      if (max === magnitude.value) {
        if ((values.length === 2) && (values[0] === 0) && (values[1] === 1)) {
          decimalPlaces = 0;
        } else {
          decimalPlaces = 1;
        }
      } else {
        decimalPlaces = values.every(isAnExactMultipleOf(magnitude.value)) ? 0 : 1;
      }

      var format = this.formatNumber;
      var currencyEntry = (currency) ? this.currencies[currency] : null;
      return function (value) {
        if (value === 0) return '0';
        return format(value, magnitude, decimalPlaces, currencyEntry);
      };
    },

    /**
     * Returns an object describing evenly spaced, nice tick values given an extent and a minimum tick count.
     * The returned object will include the values, extent and step of the ticks.
     * The extent may be extended to include the next nice tick value.
     *
     * @param extent
     * @param minimumTickCount
     * @return {Object}
     */
    calculateLinearTicks: function (extent, minimumTickCount) {

      if (extent[0] >= extent[1]) {
        throw new Error('Upper bound must be larger than lower.');
      }
      var targetTickCount = (minimumTickCount === 1) ? minimumTickCount : minimumTickCount - 1,
          span = extent[1] - extent[0],
          step = Math.pow(10, Math.floor(Math.log(span / targetTickCount) / Math.LN10)),
          err = targetTickCount / span * step;

      // Filter ticks to get closer to the desired count.
      if (err <= 0.15) step *= 10;
      else if (err <= 0.35) step *= 5;
      else if (err <= 0.75) step *= 2;

      // Round start and stop values to step interval.
      var first = Math.floor(extent[0] / step) * step,
          last = Math.ceil(extent[1] / step) * step,
          lastInclusive = last + step / 2;

      return {
        values: _.range(first, lastInclusive, step),
        extent: [first, last],
        step: step
      };
    },

    /**
     * Format a number according to its magnitude.
     *
     * Numbers are rendered with a suffix indicating the magnitude
     * and with at least 3 total digits.
     *
     * Examples:
     *
     * formatNumericLabel(    123) -> 123
     * formatNumericLabel(   1234) -> 1.23k
     * formatNumericLabel(  12345) -> 12.3k
     * formatNumericLabel( 123456) -> 123k
     * formatNumericLabel(1234567) -> 1.23m
     * formatNumericLabel(1234567890) -> 1.23b
     *
     * This function is more complicated than one would think it need be for
     * two reasons:
     * - numbers in javascript are represented as IEEE 745 floating point, and
     *   therefore they have approximation issues that make unpredictable the
     *   rounding of limit numbers; this could be ignored, making the algorithm
     *   simpler, if that level of accuracy is not required
     * - numbers below 1000 show only meaningful decimal digits, while numbers
     *   above 1000 always show the decimal digits; ex: 1 -> 1; 1000 -> 1.00k
     *
     * If we can relax these two reasons, the algorithm can become much simpler.
     * See for example View.prototype.format for a simpler alternative.
     */
    formatNumericLabel: function (value, currency) {
      if (value === null) return null;
      if (value === 0) return '0';

      /*
       * Return the appropriate magnitude (m, k, unit) for rounding a number.
       *
       * Thresholds are picked so that number are formatted with the closest
       * magnitude. So for example magnitudeOf(500,000) returns magnitudes.million,
       * and not magnitudes.thousand, so that it is formatted as 0.50m rather
       * than 500k. The actual threshold is 499,500, because digits after the
       * 3rd most significant are rounded; this means that 499,499 will be
       * rounded to 499,000 and formatted as 499k; 499,500 will be rounded
       * as 500,000 and formatted as 0.50m.
       */
      var magnitudeOf = function (number) {
        if (Math.abs(number) >= 999500000) return View.prototype.magnitudes.billion;
        if (Math.abs(number) >= 999500) return View.prototype.magnitudes.million;
        if (Math.abs(number) >= 999.5) return View.prototype.magnitudes.thousand;
        return View.prototype.magnitudes.unit;
      };

      /*
       * Numbers less than  10 times the magnitude -> 2 decimal digits: N.NNx
       * Numbers less than 100 times the magnitude -> 1 decimal digits: NN.Nx
       * Numbers 100 times the magnitude or more   -> no decimal digits: NNNx
       */
      var decimalDigits = function (number, magnitude) {
        if (Math.abs(number) < magnitude.value * 10) return (currency) ? 1 : 2;
        if (Math.abs(number) < magnitude.value * 100) return (currency) ? 0 : 1;
        return 0;
      };

      var magnitude = magnitudeOf(value);
      var digits = decimalDigits(value, magnitude, currency);
      var roundingFactor = Math.pow(10, digits);

      var roundedValue = Math.round(value * roundingFactor / magnitude.value) / roundingFactor;

      if (magnitude === View.prototype.magnitudes.unit) {
        // Render only significant decimal digits: 1.5 -> 1.5
        // NOTE: Why are we formatting decimal digits differently for numbers below 1000?
        var val = roundedValue.toString() + magnitude.suffix;
        if (currency) {
          var c = View.prototype.currencies[currency];
          val = c.prefix + val + c.suffix;
        }
        return val;
      }
      // Render a fixed number of decimal digits: 1,500 -> 1.50k
      var v = roundedValue.toFixed(digits) + magnitude.suffix;
      if (currency) {
        var curr = View.prototype.currencies[currency];
        v = curr.prefix + v + curr.suffix;
      }
      return v;
    },

    formatPercentage: function (fraction, numDecimals, showSigns) {
      if (isNaN(fraction) || !_.isNumber(fraction)) {
        return fraction;
      }
      numDecimals = numDecimals || 0;
      showSigns = showSigns || false;
      var value = Math.abs(100 * fraction).toFixed(numDecimals);
      if (showSigns) {
        if (fraction > 0) {
          value = '+' + value;
        } else if (fraction < 0) {
          value = '−' + value;
        }
      }
      value += '%';
      return value;
    },

    formatPeriod: function (model, period) {
      var start = model.get('_start_at') || model.get('start_at');
      var end = model.get('_end_at') || model.get('end_at');

      if (model.get('_original_start_at')) {
        start = this.getMoment(model.get('_original_start_at'));
        end = this.getMoment(model.get('_original_end_at'));
      }

      switch (period) {
        case 'week': // fall through; we're formatting weeks same as days
        case 'day':
          if (end) {
            end = this.getMoment(end).subtract(1, 'days');
            if (start.diff(end)) {
              if (start.month() !== end.month()) {
                return start.format('D MMM') + ' to ' + end.format('D MMM YYYY');
              } else {
                return start.format('D') + ' to ' + end.format('D MMM YYYY');
              }
            }
          }
          return start.format('D MMM YYYY');
        case 'month':
        case 'quarter':
          if (end) {
            end = this.getMoment(end).subtract(1, 'months');
            if (start.diff(end)) {
              if (start.year() !== end.year()) {
                return start.format('MMM YYYY') + ' to ' + end.format('MMM YYYY');
              } else if (start.month() !== end.month()) {
                return start.format('MMM') + ' to ' + end.format('MMM YYYY');
              }
            }
          }
          return start.format('MMMM YYYY');
      }
    },

    pluralise: function (singular, quantity, plural) {
      if (quantity === 1) {
        return singular;
      } else if (plural) {
        return plural;
      } else {
        return singular + 's';
      }
    },

    /**
     * Formats a number of milliseconds as a given unit of time
     * @param {Number} milliseconds Duration in milliseconds
     * @param {String} unit Unit to format as (either ms or s)
     * @param {Number} precision Number of significant figures to format to
     */
    formatDuration: function (milliseconds, unit, precision) {

      var formattedNumber, formatString;

      var millisecondsToSeconds = function (t) {
        return t / 1000;
      };

      milliseconds = Math.round(milliseconds);

      if (unit === 's') {
        formattedNumber = View.prototype.numberToSignificantFigures(millisecondsToSeconds(milliseconds), precision);
        formatString = 's';
      } else {
        formattedNumber = View.prototype.numberToSignificantFigures(milliseconds, precision);
        formatString = 'ms';
      }

      return formattedNumber + formatString;

    },

    /**
     * Rounds a number to a given number of significant figures
     * @param {Number} value Number to round
     * @param {Number} sigFigs Number of significant figures to round to
     */
    numberToSignificantFigures: function (value, sigFigs) {
      if (value === 0) {
        return 0;
      } else {
        var exponent = sigFigs - Math.floor(Math.log(Math.abs(value)) / Math.LN10) - 1;
        var magnitude = Math.pow(10, exponent);
        return Math.round(value * magnitude) / magnitude;
      }
    },

    /**
     * Convenience method, gets object property or method result. The method
     * is passed no arguments and is executed in the object context.
     * @param {String} prop Name of object property or method.
     * @param {Object} [obj=this] Object to inspect.
     */
    prop: function (prop, obj) {
      obj = obj || this;
      return _.isFunction(obj[prop]) ? obj[prop].call(obj) : obj[prop];
    }
  });

  _.extend(View.prototype, DateFunctions);

  return View;
});
<commit_msg>Extend base view with formatter methods<commit_after>define([
  'backbone',
  'extensions/mixins/date-functions',
  'extensions/mixins/formatters',
  'modernizr',
  'jquery',
  'lodash'
],
function (Backbone, DateFunctions, Formatters, Modernizr, $, _) {
  var View = Backbone.View.extend({

    modernizr: Modernizr,

    initialize: function (options) {
      options = options || {};
      this.viewInstances = {};
      Backbone.View.prototype.initialize.apply(this, arguments);
      var el = options.el;
      delete options.el;
      _.extend(this, options);
      options.el = el;
    },

    render: function (options) {
      options = options || {};
      this.removeSubviews(options);
      if (this.template) {
        var context = _.extend(
          this.templateContext(), options.context
        );
        this.$el.html(this.template(context));
      }
      this.renderSubviews(options);
    },

    templateContext: function () {
      var context = {
        model: this.model,
        collection: this.collection
      };
      if (this.model) {
        _.extend(context, this.model.toJSON());
      }
      return context;
    },

    defaultSubviewOptions: function () {
      return {
        renderOnInit: true,
        model: this.model,
        collection: this.collection
      };
    },

    removeSubviews: function () {
      _.invoke(this.viewInstances, 'remove');
      this.viewInstances = {};
    },

    renderSubviews: function () {

      var viewsDefinition = this.views;
      if (_.isFunction(viewsDefinition)) {
        viewsDefinition = viewsDefinition.call(this);
      }
      var instances = this.viewInstances;
      _.each(viewsDefinition, function (definition, selector) {
        var $el = this.$el.find(selector);
        if (!$el.length) {
          console.warn('No element found for ' + selector);
          return;
        }

        var View,
            options = this.defaultSubviewOptions();

        $el.empty();
        options.$el = $el;

        if (typeof definition === 'function') {
          View = definition.call(this);
        } else if (_.isObject(definition)) {
          View = definition.view;
          if (_.isFunction(definition.options)) {
            _.extend(options, definition.options.call(this));
          } else {
            _.extend(options, definition.options);
          }
        } else {
          console.warn('Invalid view definition for ' + selector);
          return;
        }

        var instance = instances[selector] = new View(options);
        instance.render({
          context: this.templateContext()
        });
      }, this);
    },

    /*
     *  Subviews definition. Override in subclasses.
     *
     *  Syntax:
     *  views: {
     *      '.selector1': SubView,
     *      // default, provides subview with result of this.defaultSubviewOptions()
     *
     *      '#selector2': {
     *        view: AnotherView,
     *        options: {
     *          // overrides to this.defaultSubviewOptions
     *          model: this.collection.selectedItem,
     *          customOption: 'foo'
     *        }
     *      },
     *      '.selector3': {
     *        view: YetAnotherView,
     *        options: function () {
     *          // `this` refers to parent view
     *          return {
     *            // overrides to this.defaultSubviewOptions
     *            model: this.collection.selectedItem,
     *            customOption: 'foo'
     *          }
     *        }
     *      }
     *  },
     *
     *  N.B.: Selectors need to refer to elements created by the parent view.
     *  No wrapper element is created for subviews.
     */
    views: {},

    keys: {
      escape: 27
    },

    magnitudes: {
      billion:  {value: 1e9, suffix: 'b' },
      million:  {value: 1e6, suffix: 'm' },
      thousand: {value: 1e3, suffix: 'k' },
      unit:     {value: 1,   suffix: ''  }
    },

    currencies: {
      gbp: { prefix: '£', suffix: '' }
    },

    magnitudeFor: function (value) {
      if (value >= 1e9) return this.magnitudes.billion;
      if (value >= 1e6) return this.magnitudes.million;
      if (value >= 1e3) return this.magnitudes.thousand;
      return this.magnitudes.unit;
    },

    formatNumber: function (value, magnitude, decimalPlaces, currency) {
      var val = (value / magnitude.value).toFixed(decimalPlaces || 0).toString() + magnitude.suffix;
      if (currency) {
        val = currency.prefix + val + currency.suffix;
      }
      return val;
    },

    /**
     * Returns a number formatting function whose actual format depends on the values passed as argument.
     * The formatter can then be used to format all the number in the series applying the same format, regardless of the
     * individual values. This is especially useful for graph axes, where a homogeneous formatting of the labels is
     * required.
     *
     * @param values
     * @return {Function}
     */
    numberListFormatter: function (values, currency) {
      function isAnExactMultipleOf(magnitude) {
        return function (n) { return n % magnitude === 0; };
      }

      var max = values.reduce(function (a, b) {return a > b ? a : b; });
      var magnitude = this.magnitudeFor(max);
      var decimalPlaces;
      if (max === magnitude.value) {
        if ((values.length === 2) && (values[0] === 0) && (values[1] === 1)) {
          decimalPlaces = 0;
        } else {
          decimalPlaces = 1;
        }
      } else {
        decimalPlaces = values.every(isAnExactMultipleOf(magnitude.value)) ? 0 : 1;
      }

      var format = this.formatNumber;
      var currencyEntry = (currency) ? this.currencies[currency] : null;
      return function (value) {
        if (value === 0) return '0';
        return format(value, magnitude, decimalPlaces, currencyEntry);
      };
    },

    /**
     * Returns an object describing evenly spaced, nice tick values given an extent and a minimum tick count.
     * The returned object will include the values, extent and step of the ticks.
     * The extent may be extended to include the next nice tick value.
     *
     * @param extent
     * @param minimumTickCount
     * @return {Object}
     */
    calculateLinearTicks: function (extent, minimumTickCount) {

      if (extent[0] >= extent[1]) {
        throw new Error('Upper bound must be larger than lower.');
      }
      var targetTickCount = (minimumTickCount === 1) ? minimumTickCount : minimumTickCount - 1,
          span = extent[1] - extent[0],
          step = Math.pow(10, Math.floor(Math.log(span / targetTickCount) / Math.LN10)),
          err = targetTickCount / span * step;

      // Filter ticks to get closer to the desired count.
      if (err <= 0.15) step *= 10;
      else if (err <= 0.35) step *= 5;
      else if (err <= 0.75) step *= 2;

      // Round start and stop values to step interval.
      var first = Math.floor(extent[0] / step) * step,
          last = Math.ceil(extent[1] / step) * step,
          lastInclusive = last + step / 2;

      return {
        values: _.range(first, lastInclusive, step),
        extent: [first, last],
        step: step
      };
    },

    /**
     * Format a number according to its magnitude.
     *
     * Numbers are rendered with a suffix indicating the magnitude
     * and with at least 3 total digits.
     *
     * Examples:
     *
     * formatNumericLabel(    123) -> 123
     * formatNumericLabel(   1234) -> 1.23k
     * formatNumericLabel(  12345) -> 12.3k
     * formatNumericLabel( 123456) -> 123k
     * formatNumericLabel(1234567) -> 1.23m
     * formatNumericLabel(1234567890) -> 1.23b
     *
     * This function is more complicated than one would think it need be for
     * two reasons:
     * - numbers in javascript are represented as IEEE 745 floating point, and
     *   therefore they have approximation issues that make unpredictable the
     *   rounding of limit numbers; this could be ignored, making the algorithm
     *   simpler, if that level of accuracy is not required
     * - numbers below 1000 show only meaningful decimal digits, while numbers
     *   above 1000 always show the decimal digits; ex: 1 -> 1; 1000 -> 1.00k
     *
     * If we can relax these two reasons, the algorithm can become much simpler.
     * See for example View.prototype.format for a simpler alternative.
     */
    formatNumericLabel: function (value, currency) {
      if (value === null) return null;
      if (value === 0) return '0';

      /*
       * Return the appropriate magnitude (m, k, unit) for rounding a number.
       *
       * Thresholds are picked so that number are formatted with the closest
       * magnitude. So for example magnitudeOf(500,000) returns magnitudes.million,
       * and not magnitudes.thousand, so that it is formatted as 0.50m rather
       * than 500k. The actual threshold is 499,500, because digits after the
       * 3rd most significant are rounded; this means that 499,499 will be
       * rounded to 499,000 and formatted as 499k; 499,500 will be rounded
       * as 500,000 and formatted as 0.50m.
       */
      var magnitudeOf = function (number) {
        if (Math.abs(number) >= 999500000) return View.prototype.magnitudes.billion;
        if (Math.abs(number) >= 999500) return View.prototype.magnitudes.million;
        if (Math.abs(number) >= 999.5) return View.prototype.magnitudes.thousand;
        return View.prototype.magnitudes.unit;
      };

      /*
       * Numbers less than  10 times the magnitude -> 2 decimal digits: N.NNx
       * Numbers less than 100 times the magnitude -> 1 decimal digits: NN.Nx
       * Numbers 100 times the magnitude or more   -> no decimal digits: NNNx
       */
      var decimalDigits = function (number, magnitude) {
        if (Math.abs(number) < magnitude.value * 10) return (currency) ? 1 : 2;
        if (Math.abs(number) < magnitude.value * 100) return (currency) ? 0 : 1;
        return 0;
      };

      var magnitude = magnitudeOf(value);
      var digits = decimalDigits(value, magnitude, currency);
      var roundingFactor = Math.pow(10, digits);

      var roundedValue = Math.round(value * roundingFactor / magnitude.value) / roundingFactor;

      if (magnitude === View.prototype.magnitudes.unit) {
        // Render only significant decimal digits: 1.5 -> 1.5
        // NOTE: Why are we formatting decimal digits differently for numbers below 1000?
        var val = roundedValue.toString() + magnitude.suffix;
        if (currency) {
          var c = View.prototype.currencies[currency];
          val = c.prefix + val + c.suffix;
        }
        return val;
      }
      // Render a fixed number of decimal digits: 1,500 -> 1.50k
      var v = roundedValue.toFixed(digits) + magnitude.suffix;
      if (currency) {
        var curr = View.prototype.currencies[currency];
        v = curr.prefix + v + curr.suffix;
      }
      return v;
    },

    formatPercentage: function (fraction, numDecimals, showSigns) {
      if (isNaN(fraction) || !_.isNumber(fraction)) {
        return fraction;
      }
      numDecimals = numDecimals || 0;
      showSigns = showSigns || false;
      var value = Math.abs(100 * fraction).toFixed(numDecimals);
      if (showSigns) {
        if (fraction > 0) {
          value = '+' + value;
        } else if (fraction < 0) {
          value = '−' + value;
        }
      }
      value += '%';
      return value;
    },

    formatPeriod: function (model, period) {
      var start = model.get('_start_at') || model.get('start_at');
      var end = model.get('_end_at') || model.get('end_at');

      if (model.get('_original_start_at')) {
        start = this.getMoment(model.get('_original_start_at'));
        end = this.getMoment(model.get('_original_end_at'));
      }

      switch (period) {
        case 'week': // fall through; we're formatting weeks same as days
        case 'day':
          if (end) {
            end = this.getMoment(end).subtract(1, 'days');
            if (start.diff(end)) {
              if (start.month() !== end.month()) {
                return start.format('D MMM') + ' to ' + end.format('D MMM YYYY');
              } else {
                return start.format('D') + ' to ' + end.format('D MMM YYYY');
              }
            }
          }
          return start.format('D MMM YYYY');
        case 'month':
        case 'quarter':
          if (end) {
            end = this.getMoment(end).subtract(1, 'months');
            if (start.diff(end)) {
              if (start.year() !== end.year()) {
                return start.format('MMM YYYY') + ' to ' + end.format('MMM YYYY');
              } else if (start.month() !== end.month()) {
                return start.format('MMM') + ' to ' + end.format('MMM YYYY');
              }
            }
          }
          return start.format('MMMM YYYY');
      }
    },

    pluralise: function (singular, quantity, plural) {
      if (quantity === 1) {
        return singular;
      } else if (plural) {
        return plural;
      } else {
        return singular + 's';
      }
    },

    /**
     * Formats a number of milliseconds as a given unit of time
     * @param {Number} milliseconds Duration in milliseconds
     * @param {String} unit Unit to format as (either ms or s)
     * @param {Number} precision Number of significant figures to format to
     */
    formatDuration: function (milliseconds, unit, precision) {

      var formattedNumber, formatString;

      var millisecondsToSeconds = function (t) {
        return t / 1000;
      };

      milliseconds = Math.round(milliseconds);

      if (unit === 's') {
        formattedNumber = View.prototype.numberToSignificantFigures(millisecondsToSeconds(milliseconds), precision);
        formatString = 's';
      } else {
        formattedNumber = View.prototype.numberToSignificantFigures(milliseconds, precision);
        formatString = 'ms';
      }

      return formattedNumber + formatString;

    },

    /**
     * Rounds a number to a given number of significant figures
     * @param {Number} value Number to round
     * @param {Number} sigFigs Number of significant figures to round to
     */
    numberToSignificantFigures: function (value, sigFigs) {
      if (value === 0) {
        return 0;
      } else {
        var exponent = sigFigs - Math.floor(Math.log(Math.abs(value)) / Math.LN10) - 1;
        var magnitude = Math.pow(10, exponent);
        return Math.round(value * magnitude) / magnitude;
      }
    },

    /**
     * Convenience method, gets object property or method result. The method
     * is passed no arguments and is executed in the object context.
     * @param {String} prop Name of object property or method.
     * @param {Object} [obj=this] Object to inspect.
     */
    prop: function (prop, obj) {
      obj = obj || this;
      return _.isFunction(obj[prop]) ? obj[prop].call(obj) : obj[prop];
    }
  });

  _.extend(View.prototype, DateFunctions);
  _.extend(View.prototype, Formatters);

  return View;
});
<|endoftext|>


<commit_before>(function ($, fluid) {

    "use strict";

    var environment = flock.init();
    environment.start();

    fluid.defaults("gpii.sensorPlayer.sensor", {
        gradeNames: ["fluid.modelComponent"],
        model: {
            sensorValue: 50,
            simulateChanges: false,
            simulateChangesInterval: 2000,
            sensorMax: 100,
            sensorMin: 0,
            description: "A sensor"
        },
        members: {
            simulateChangesIntervalId: null
        },
        modelListeners: {
            sensorValue: {
                "this": "console",
                "method": "log",
                "args": "{that}.model.sensorValue"
            },
            simulateChanges: {
                "funcName": "gpii.sensorPlayer.sensor.simulateChanges",
                "args": ["{that}", "{that}.model.simulateChanges"]
            }
        }
    });

    gpii.sensorPlayer.sensor.randomInt = function(min, max) {
        min = Math.ceil(min);
        max = Math.floor(max);
        return Math.floor(Math.random() * (max - min)) + min;
    };

    fluid.defaults("gpii.sensorPlayer.sensor.pHSensor", {
        gradeNames: ["gpii.sensorPlayer.sensor"],
        model: {
            sensorValue: 7,
            sensorMax: 14,
            sensorMin: 1,
            description: "A simulated pH sensor."
        }
    });

    fluid.defaults("gpii.sensorPlayer.sensor.temperatureSensor", {
        gradeNames: ["gpii.sensorPlayer.sensor"],
        model: {
            sensorValue: 18,
            sensorMax: 26,
            sensorMin: 18,
            description: "A simulated temperature sensor (in celcius); constrained from 18 to 26."
        }
    });

    gpii.sensorPlayer.sensor.simulateChanges = function(that, simulateChanges) {
        if(simulateChanges) {
            // Turn on the interval changes to the sensorValue
            that.simulateChangesIntervalId = setInterval(function() {
                that.applier.change("sensorValue", gpii.sensorPlayer.sensor.randomInt(that.model.sensorMax, that.model.sensorMin));
            }, that.model.simulateChangesInterval);
        } else {
            // Turn off the interval changes to the sensorValue
            clearInterval(that.simulateChangesIntervalId);
        }
    };

    // A sensor sonifier that uses the scaling synth
    fluid.defaults("gpii.sensorPlayer.sensorSonifier", {
        gradeNames: ["fluid.modelComponent"],
        model: {
            sensorMax: 100,
            sensorMin: 0,
            sensorValue: 50
        },
        components: {
            scalingSynth: {
                type: "gpii.sensorPlayer.scalingSynth",
                options: {
                    model: {
                        valueInformation: {
                            max: "{sensorSonifier}.model.sensorMax",
                            min: "{sensorSonifier}.model.sensorMin",
                            current: "{sensorSonifier}.model.sensorValue"
                        }
                    }
                }
            }
        }
    });

    // A model-driven synth that scales model values
    // with specified min / max to a min/max frequency range
    fluid.defaults("gpii.sensorPlayer.scalingSynth", {
        gradeNames: ["flock.modelSynth"],
        modelRelay: [{
            target: "inputs.carrier.freq",
            singleTransform: {
                type: "gpii.sensorPlayer.transforms.minMaxScale",
                input: "{that}.model.valueInformation.current",
                inputScaleMax: "{that}.model.valueInformation.max",
                inputScaleMin: "{that}.model.valueInformation.min",
                outputScaleMax: "{that}.model.freqMax",
                outputScaleMin: "{that}.model.freqMin"
            }
        },
        {
            target: "inputs.midpoint.freq",
            singleTransform: {
                type: "fluid.transforms.binaryOp",
                left: {
                    transform: {
                        type: "fluid.transforms.binaryOp",
                        left: "{that}.model.freqMax",
                        right: "{that}.model.freqMin",
                        operator: "+"
                    }
                },
                right: "2",
                operator: "/"
            }
        }
        ],
        model: {
            // In real-world usage, these will be bound
            // to models from other components
            valueInformation: {
                max: 100,
                min: 0,
                current: 50
            },
            inputs: {
                carrier: {
                    freq: 440
                }
            },
            freqMax: 680,
            freqMin: 200
        },
        synthDef: {
            ugen: "flock.ugen.out",
            sources: [
                {
                    ugen: "flock.ugen.sum",
                    sources: [
                        {
                        id: "carrier",
                        ugen: "flock.ugen.sin",
                        inputs: {
                            freq: 440,
                            mul: {
                                id: "mod",
                                ugen: "flock.ugen.sinOsc",
                                freq: 0.1,
                                mul: 0.25
                                }
                            }
                        },
                        {
                        id: "midpoint",
                        ugen: "flock.ugen.sin",
                        inputs: {
                            freq: 440,
                            mul: 0
                            }
                        }
                    ]
                }
            ]
        },
        addToEnvironment: true
    });

    fluid.registerNamespace("gpii.sensorPlayer.transforms");

    fluid.defaults("gpii.sensorPlayer.transforms.minMaxScale", {
        "gradeNames": [ "fluid.standardTransformFunction", "fluid.multiInputTransformFunction" ],
        "inputVariables": {
            "inputScaleMax": 100,
            "inputScaleMin": 0,
            "outputScaleMax": 680,
            "outputScaleMin": 200
        }
    });

    gpii.sensorPlayer.transforms.minMaxScale = function (input, extraInputs) {
        var scaledValue = ((extraInputs.outputScaleMax() - extraInputs.outputScaleMin()) * (input - extraInputs.inputScaleMin()) / (extraInputs.inputScaleMax() - extraInputs.inputScaleMin())) + extraInputs.outputScaleMin();
        return scaledValue;
    };

    fluid.defaults("gpii.sensorPlayer.valueDisplay", {
        gradeNames: ["fluid.viewComponent"],
        model: {
            value: "Hello, World!"
        },
        members: {
            template: "

"
        },
        selectors: {
            value: ".flc-valueDisplay-value"
        },
        listeners: {
            "onCreate.appendTemplate": {
                "this": "{that}.container",
                "method": "html",
                "args": "{that}.template"
            },
            "onCreate.setInitialValue": {
                "this": "{that}.dom.value",
                "method": "html",
                "args": ["{that}.model.value"]
            }
        },
        modelListeners: {
            value: {
                "this": "{that}.dom.value",
                "method": "html",
                "args": ["{that}.model.value"]
            }
        }
    });

    fluid.defaults("gpii.sensorPlayer.sensorDisplayDebug", {
        gradeNames: ["fluid.viewComponent"],
        events: {
            displayTemplateReady: null
        },
        selectors: {
            sensorMaxDisplay: ".flc-sensorMaxValue",
            sensorMinDisplay: ".flc-sensorMinValue",
            sensorValueDisplay: ".flc-sensorValue",
            synthFreqDisplay: ".flc-freqValue",
            descriptionDisplay: ".flc-descriptionDisplay",
            midpointToneControl: ".flc-midpointToneControl",
            muteControl: ".flc-muteControl"
        },
        members: {
            template: "

<form> Play Sensor Midpoint Tone<input class=\"flc-midpointToneControl\" type=\"checkbox\"/>
 Mute Synthesizer<input class=\"flc-muteControl\" type=\"checkbox\"/> </form>"
        },
        listeners: {
            "onCreate.appendDisplayTemplate": {
                "this": "{that}.container",
                "method": "html",
                "args": "{that}.template"
            },
            "onCreate.fireDisplayTemplateReady": {
                func: "{that}.events.displayTemplateReady.fire"
            },
            "onCreate.bindSynthControls": {
                func: "gpii.sensorPlayer.sensorDisplayDebug.bindSynthControls",
                args: ["{that}", "{sensorSynthesizer}"]
            }
        },
        components: {
            descriptionDisplay: {
                createOnEvent: "{sensorDisplayDebug}.events.displayTemplateReady",
                type: "gpii.sensorPlayer.valueDisplay",
                container: "{sensorDisplayDebug}.dom.descriptionDisplay",
                options: {
                    model: {
                        value: "{sensor}.model.description"
                    },
                    members: {
                        template: "Sensor Description: "
                    }
                }
            },
            sensorMinDisplay: {
                createOnEvent: "{sensorDisplayDebug}.events.displayTemplateReady",
                type: "gpii.sensorPlayer.valueDisplay",
                container: "{sensorDisplayDebug}.dom.sensorMinDisplay",
                options: {
                    model: {
                        value: "{sensor}.model.sensorMin"
                    },
                    members: {
                        template: "Sensor Min Value: "
                    }
                }
            },
            sensorMaxDisplay: {
                createOnEvent: "{sensorDisplayDebug}.events.displayTemplateReady",
                type: "gpii.sensorPlayer.valueDisplay",
                container: "{sensorDisplayDebug}.dom.sensorMaxDisplay",
                options: {
                    model: {
                        value: "{sensor}.model.sensorMax"
                    },
                    members: {
                        template: "Sensor Max Value: "
                    }
                }
            },
            sensorDisplayDebug: {
                createOnEvent: "{sensorDisplayDebug}.events.displayTemplateReady",
                type: "gpii.sensorPlayer.valueDisplay",
                container: "{sensorDisplayDebug}.dom.sensorValueDisplay",
                options: {
                    model: {
                        value: "{sensor}.model.sensorValue"
                    },
                    members: {
                        template: "Sensor Current Value: "
                    }
                }
            },
            synthFreqDisplay: {
                createOnEvent: "{sensorDisplayDebug}.events.displayTemplateReady",
                type: "gpii.sensorPlayer.valueDisplay",
                container: "{sensorDisplayDebug}.dom.synthFreqDisplay",
                options: {
                    model: {
                        value: "{sensorSynthesizer}.model.inputs.carrier.freq"
                    },
                    members: {
                        template: "Synthesizer frequency: "
                    }
                }
            }
        }
    });

    gpii.sensorPlayer.sensorDisplayDebug.bindSynthControls = function (that, sensorSynthesizer) {
        var muteControl = that.locate("muteControl");
        var midpointToneControl = that.locate("midpointToneControl");

        muteControl.click(function () {
            var checked = muteControl.is(":checked");
            if(checked) {
                sensorSynthesizer.scalingSynth.set("mod.mul", {
                    id: "fader",
                   ugen: "flock.ugen.line",
                   rate: "control",
                   start: 0.25,
                   end: 0,
                   duration: 1
                });
            }
            else {
                sensorSynthesizer.scalingSynth.set("mod.mul", {
                    id: "fader",
                   ugen: "flock.ugen.line",
                   rate: "control",
                   start: 0,
                   end: 0.25,
                   duration: 1
                });

            }

        });

        midpointToneControl.click(function () {
            var checked = midpointToneControl.is(":checked");
            if(checked) {
                sensorSynthesizer.scalingSynth.applier.change("inputs.midpoint.mul", 0.12);
            }
            else {
                sensorSynthesizer.scalingSynth.applier.change("inputs.midpoint.mul", 0);
            }

        });
    };

    fluid.defaults("gpii.sensorPlayer", {
        gradeNames: ["fluid.modelComponent"],
        components: {
            sensor: {
                type: "gpii.sensorPlayer.sensor",
                options: {
                    model: {
                        simulateChanges: true
                    }
                }
            },
            sensorSynthesizer: {
                type: "gpii.sensorPlayer.sensorSonifier",
                options: {
                    model: {
                        sensorValue: "{sensor}.model.sensorValue",
                        sensorMax: "{sensor}.model.sensorMax",
                        sensorMin: "{sensor}.model.sensorMin"
                    }
                }
            }
        }
    });

    fluid.defaults("gpii.sensorPlayer.pHSensorPlayer", {
        gradeNames: ["gpii.sensorPlayer"],
        components: {
            sensor: {
                type: "gpii.sensorPlayer.sensor.pHSensor"
            }
        }
    });

    fluid.defaults("gpii.sensorPlayer.temperatureSensorPlayer", {
        gradeNames: ["gpii.sensorPlayer"],
        components: {
            sensor: {
                type: "gpii.sensorPlayer.sensor.temperatureSensor"
            }
        }
    });

})(jQuery, fluid);
<commit_msg>GPII-2190: apply mute effect to sum unit generator.<commit_after>(function ($, fluid) {

    "use strict";

    var environment = flock.init();
    environment.start();

    fluid.defaults("gpii.sensorPlayer.sensor", {
        gradeNames: ["fluid.modelComponent"],
        model: {
            sensorValue: 50,
            simulateChanges: false,
            simulateChangesInterval: 2000,
            sensorMax: 100,
            sensorMin: 0,
            description: "A sensor"
        },
        members: {
            simulateChangesIntervalId: null
        },
        modelListeners: {
            sensorValue: {
                "this": "console",
                "method": "log",
                "args": "{that}.model.sensorValue"
            },
            simulateChanges: {
                "funcName": "gpii.sensorPlayer.sensor.simulateChanges",
                "args": ["{that}", "{that}.model.simulateChanges"]
            }
        }
    });

    gpii.sensorPlayer.sensor.randomInt = function(min, max) {
        min = Math.ceil(min);
        max = Math.floor(max);
        return Math.floor(Math.random() * (max - min)) + min;
    };

    fluid.defaults("gpii.sensorPlayer.sensor.pHSensor", {
        gradeNames: ["gpii.sensorPlayer.sensor"],
        model: {
            sensorValue: 7,
            sensorMax: 14,
            sensorMin: 1,
            description: "A simulated pH sensor."
        }
    });

    fluid.defaults("gpii.sensorPlayer.sensor.temperatureSensor", {
        gradeNames: ["gpii.sensorPlayer.sensor"],
        model: {
            sensorValue: 18,
            sensorMax: 26,
            sensorMin: 18,
            description: "A simulated temperature sensor (in celcius); constrained from 18 to 26."
        }
    });

    gpii.sensorPlayer.sensor.simulateChanges = function(that, simulateChanges) {
        if(simulateChanges) {
            // Turn on the interval changes to the sensorValue
            that.simulateChangesIntervalId = setInterval(function() {
                that.applier.change("sensorValue", gpii.sensorPlayer.sensor.randomInt(that.model.sensorMax, that.model.sensorMin));
            }, that.model.simulateChangesInterval);
        } else {
            // Turn off the interval changes to the sensorValue
            clearInterval(that.simulateChangesIntervalId);
        }
    };

    // A sensor sonifier that uses the scaling synth
    fluid.defaults("gpii.sensorPlayer.sensorSonifier", {
        gradeNames: ["fluid.modelComponent"],
        model: {
            sensorMax: 100,
            sensorMin: 0,
            sensorValue: 50
        },
        components: {
            scalingSynth: {
                type: "gpii.sensorPlayer.scalingSynth",
                options: {
                    model: {
                        valueInformation: {
                            max: "{sensorSonifier}.model.sensorMax",
                            min: "{sensorSonifier}.model.sensorMin",
                            current: "{sensorSonifier}.model.sensorValue"
                        }
                    }
                }
            }
        }
    });

    // A model-driven synth that scales model values
    // with specified min / max to a min/max frequency range
    fluid.defaults("gpii.sensorPlayer.scalingSynth", {
        gradeNames: ["flock.modelSynth"],
        modelRelay: [{
            target: "inputs.carrier.freq",
            singleTransform: {
                type: "gpii.sensorPlayer.transforms.minMaxScale",
                input: "{that}.model.valueInformation.current",
                inputScaleMax: "{that}.model.valueInformation.max",
                inputScaleMin: "{that}.model.valueInformation.min",
                outputScaleMax: "{that}.model.freqMax",
                outputScaleMin: "{that}.model.freqMin"
            }
        },
        {
            target: "inputs.midpoint.freq",
            singleTransform: {
                type: "fluid.transforms.binaryOp",
                left: {
                    transform: {
                        type: "fluid.transforms.binaryOp",
                        left: "{that}.model.freqMax",
                        right: "{that}.model.freqMin",
                        operator: "+"
                    }
                },
                right: "2",
                operator: "/"
            }
        }
        ],
        model: {
            // In real-world usage, these will be bound
            // to models from other components
            valueInformation: {
                max: 100,
                min: 0,
                current: 50
            },
            inputs: {
                carrier: {
                    freq: 440
                }
            },
            freqMax: 680,
            freqMin: 200
        },
        synthDef: {
            ugen: "flock.ugen.out",
            sources: [
                {
                    id: "sum",
                    mul: 1,
                    ugen: "flock.ugen.sum",
                    sources: [
                        {
                        id: "carrier",
                        ugen: "flock.ugen.sin",
                        inputs: {
                            freq: 440,
                            mul: {
                                id: "mod",
                                ugen: "flock.ugen.sinOsc",
                                freq: 0.1,
                                mul: 0.25
                                }
                            }
                        },
                        {
                        id: "midpoint",
                        ugen: "flock.ugen.sin",
                        inputs: {
                            freq: 440,
                            mul: 0
                            }
                        }
                    ]
                }
            ]
        },
        addToEnvironment: true
    });

    fluid.registerNamespace("gpii.sensorPlayer.transforms");

    fluid.defaults("gpii.sensorPlayer.transforms.minMaxScale", {
        "gradeNames": [ "fluid.standardTransformFunction", "fluid.multiInputTransformFunction" ],
        "inputVariables": {
            "inputScaleMax": 100,
            "inputScaleMin": 0,
            "outputScaleMax": 680,
            "outputScaleMin": 200
        }
    });

    gpii.sensorPlayer.transforms.minMaxScale = function (input, extraInputs) {
        var scaledValue = ((extraInputs.outputScaleMax() - extraInputs.outputScaleMin()) * (input - extraInputs.inputScaleMin()) / (extraInputs.inputScaleMax() - extraInputs.inputScaleMin())) + extraInputs.outputScaleMin();
        return scaledValue;
    };

    fluid.defaults("gpii.sensorPlayer.valueDisplay", {
        gradeNames: ["fluid.viewComponent"],
        model: {
            value: "Hello, World!"
        },
        members: {
            template: "

"
        },
        selectors: {
            value: ".flc-valueDisplay-value"
        },
        listeners: {
            "onCreate.appendTemplate": {
                "this": "{that}.container",
                "method": "html",
                "args": "{that}.template"
            },
            "onCreate.setInitialValue": {
                "this": "{that}.dom.value",
                "method": "html",
                "args": ["{that}.model.value"]
            }
        },
        modelListeners: {
            value: {
                "this": "{that}.dom.value",
                "method": "html",
                "args": ["{that}.model.value"]
            }
        }
    });

    fluid.defaults("gpii.sensorPlayer.sensorDisplayDebug", {
        gradeNames: ["fluid.viewComponent"],
        events: {
            displayTemplateReady: null
        },
        selectors: {
            sensorMaxDisplay: ".flc-sensorMaxValue",
            sensorMinDisplay: ".flc-sensorMinValue",
            sensorValueDisplay: ".flc-sensorValue",
            synthFreqDisplay: ".flc-freqValue",
            descriptionDisplay: ".flc-descriptionDisplay",
            midpointToneControl: ".flc-midpointToneControl",
            muteControl: ".flc-muteControl"
        },
        members: {
            template: "

<form> Play Sensor Midpoint Tone<input class=\"flc-midpointToneControl\" type=\"checkbox\"/>
 Mute Synthesizer<input class=\"flc-muteControl\" type=\"checkbox\"/> </form>"
        },
        listeners: {
            "onCreate.appendDisplayTemplate": {
                "this": "{that}.container",
                "method": "html",
                "args": "{that}.template"
            },
            "onCreate.fireDisplayTemplateReady": {
                func: "{that}.events.displayTemplateReady.fire"
            },
            "onCreate.bindSynthControls": {
                func: "gpii.sensorPlayer.sensorDisplayDebug.bindSynthControls",
                args: ["{that}", "{sensorSynthesizer}"]
            }
        },
        components: {
            descriptionDisplay: {
                createOnEvent: "{sensorDisplayDebug}.events.displayTemplateReady",
                type: "gpii.sensorPlayer.valueDisplay",
                container: "{sensorDisplayDebug}.dom.descriptionDisplay",
                options: {
                    model: {
                        value: "{sensor}.model.description"
                    },
                    members: {
                        template: "Sensor Description: "
                    }
                }
            },
            sensorMinDisplay: {
                createOnEvent: "{sensorDisplayDebug}.events.displayTemplateReady",
                type: "gpii.sensorPlayer.valueDisplay",
                container: "{sensorDisplayDebug}.dom.sensorMinDisplay",
                options: {
                    model: {
                        value: "{sensor}.model.sensorMin"
                    },
                    members: {
                        template: "Sensor Min Value: "
                    }
                }
            },
            sensorMaxDisplay: {
                createOnEvent: "{sensorDisplayDebug}.events.displayTemplateReady",
                type: "gpii.sensorPlayer.valueDisplay",
                container: "{sensorDisplayDebug}.dom.sensorMaxDisplay",
                options: {
                    model: {
                        value: "{sensor}.model.sensorMax"
                    },
                    members: {
                        template: "Sensor Max Value: "
                    }
                }
            },
            sensorDisplayDebug: {
                createOnEvent: "{sensorDisplayDebug}.events.displayTemplateReady",
                type: "gpii.sensorPlayer.valueDisplay",
                container: "{sensorDisplayDebug}.dom.sensorValueDisplay",
                options: {
                    model: {
                        value: "{sensor}.model.sensorValue"
                    },
                    members: {
                        template: "Sensor Current Value: "
                    }
                }
            },
            synthFreqDisplay: {
                createOnEvent: "{sensorDisplayDebug}.events.displayTemplateReady",
                type: "gpii.sensorPlayer.valueDisplay",
                container: "{sensorDisplayDebug}.dom.synthFreqDisplay",
                options: {
                    model: {
                        value: "{sensorSynthesizer}.model.inputs.carrier.freq"
                    },
                    members: {
                        template: "Synthesizer frequency: "
                    }
                }
            }
        }
    });

    gpii.sensorPlayer.sensorDisplayDebug.bindSynthControls = function (that, sensorSynthesizer) {
        var muteControl = that.locate("muteControl");
        var midpointToneControl = that.locate("midpointToneControl");

        muteControl.click(function () {
            var checked = muteControl.is(":checked");
            if(checked) {
                sensorSynthesizer.scalingSynth.set("sum.mul", {
                    id: "fader",
                   ugen: "flock.ugen.line",
                   rate: "control",
                   start: 1,
                   end: 0,
                   duration: 1
                });
            }
            else {
                sensorSynthesizer.scalingSynth.set("sum.mul", {
                    id: "fader",
                   ugen: "flock.ugen.line",
                   rate: "control",
                   start: 0,
                   end: 1,
                   duration: 1
                });

            }

        });

        midpointToneControl.click(function () {
            var checked = midpointToneControl.is(":checked");
            if(checked) {
                sensorSynthesizer.scalingSynth.applier.change("inputs.midpoint.mul", 0.12);
            }
            else {
                sensorSynthesizer.scalingSynth.applier.change("inputs.midpoint.mul", 0);
            }

        });
    };

    fluid.defaults("gpii.sensorPlayer", {
        gradeNames: ["fluid.modelComponent"],
        components: {
            sensor: {
                type: "gpii.sensorPlayer.sensor",
                options: {
                    model: {
                        simulateChanges: true
                    }
                }
            },
            sensorSynthesizer: {
                type: "gpii.sensorPlayer.sensorSonifier",
                options: {
                    model: {
                        sensorValue: "{sensor}.model.sensorValue",
                        sensorMax: "{sensor}.model.sensorMax",
                        sensorMin: "{sensor}.model.sensorMin"
                    }
                }
            }
        }
    });

    fluid.defaults("gpii.sensorPlayer.pHSensorPlayer", {
        gradeNames: ["gpii.sensorPlayer"],
        components: {
            sensor: {
                type: "gpii.sensorPlayer.sensor.pHSensor"
            }
        }
    });

    fluid.defaults("gpii.sensorPlayer.temperatureSensorPlayer", {
        gradeNames: ["gpii.sensorPlayer"],
        components: {
            sensor: {
                type: "gpii.sensorPlayer.sensor.temperatureSensor"
            }
        }
    });

})(jQuery, fluid);
<|endoftext|>


<commit_before>var marked = require("marked");

mojo.application.paperclip.modifier("markdown", marked);

var MarkedView = mojo.views.Base.extend({
    paper: require("./template.pc")
});

module.exports = function (element) {
    var view = new MarkedView({
        content: "Hello *world*, I'm **Markdown**!"
    });
    element.appendChild(view.render());
}<commit_msg>check for string<commit_after>var marked = require("marked");

mojo.application.paperclip.modifier("markdown", function (v) {
  return marked(v || "");
});

var MarkedView = mojo.views.Base.extend({
    paper: require("./template.pc")
});

module.exports = function (element) {
    var view = new MarkedView({
        content: "Hello *world*, I'm **Markdown**!"
    });
    element.appendChild(view.render());
}<|endoftext|>


<commit_before>require('../../stylus/components/_toolbar.styl')

import Applicationable from '../../mixins/applicationable'
import Colorable from '../../mixins/colorable'
import Themeable from '../../mixins/themeable'
import SSRBootable from '../../mixins/ssr-bootable'

export default {
  name: 'v-toolbar',

  mixins: [
    Applicationable,
    Colorable,
    SSRBootable,
    Themeable
  ],

  data: () => ({
    activeTimeout: null,
    currentScroll: 0,
    heights: {
      mobileLandscape: 48,
      mobile: 56,
      desktop: 64,
      dense: 48
    },
    isActiveProxy: false,
    isExtended: false,
    isScrollingUp: false,
    previousScroll: null,
    previousScrollDirection: null,
    target: null
  }),

  props: {
    absolute: Boolean,
    card: Boolean,
    clippedLeft: Boolean,
    clippedRight: Boolean,
    dense: Boolean,
    extended: Boolean,
    extensionHeight: [Number, String],
    fixed: Boolean,
    flat: Boolean,
    floating: Boolean,
    height: [Number, String],
    invertedScroll: Boolean,
    manualScroll: {
      type: Boolean,
      default: null
    },
    prominent: Boolean,
    scrollOffScreen: Boolean,
    scrollTarget: String,
    scrollThreshold: {
      type: Number,
      default: 300
    },
    tabs: Boolean
  },

  computed: {
    computedContentHeight () {
      if (this.height) return parseInt(this.height)
      if (this.dense) return this.heights.dense

      if (this.prominent ||
        this.$vuetify.breakpoint.mdAndUp
      ) return this.heights.desktop

      if (this.$vuetify.breakpoint.width >
        this.$vuetify.breakpoint.height
      ) return this.heights.mobileLandscape

      return this.heights.mobile
    },
    computedExtensionHeight () {
      if (this.extensionHeight) return parseInt(this.extensionHeight)

      return this.computedContentHeight
    },
    computedHeight () {
      if (!this.isExtended) return this.computedContentHeight

      return this.computedContentHeight + this.computedExtensionHeight
    },
    classes () {
      return this.addBackgroundColorClassChecks({
        'toolbar': true,
        'elevation-0': this.flat || (!this.isActiveProxy && !this.tabs),
        'toolbar--absolute': this.absolute,
        'toolbar--card': this.card,
        'toolbar--clipped': this.clippedLeft || this.clippedRight,
        'toolbar--dense': this.dense,
        'toolbar--extended': this.isExtended,
        'toolbar--fixed': this.fixed,
        'toolbar--floating': this.floating,
        'toolbar--is-booted': this.isBooted,
        'toolbar--prominent': this.prominent,
        'theme--dark': this.dark,
        'theme--light': this.light
      })
    },
    paddingLeft () {
      if (!this.app || this.clippedLeft) return 0

      return this.$vuetify.application.left
    },
    paddingRight () {
      if (!this.app || this.clippedRight) return 0

      return this.$vuetify.application.right
    },
    isActive () {
      if (!this.scrollOffScreen) return true
      if (this.manualScroll) return this.manualScroll

      return this.invertedScroll
        ? this.currentScroll > this.scrollThreshold
        : this.currentScroll < this.scrollThreshold ||
          this.isScrollingUp
    },
    styles () {
      const style = {}

      if (!this.isActiveProxy) {
        style.transform = `translateY(-${this.computedHeight}px)`
      }

      if (this.app) {
        style.paddingRight = `${this.paddingRight}px`
        style.paddingLeft = `${this.paddingLeft}px`
      }

      return style
    }
  },

  watch: {
    // This is to avoid an accidental
    // false positive when scrolling.
    // sometimes for 1 frame it appears
    // as if the direction has changed
    // but it actually has not
    isActive: {
      immediate: true,
      handler (val) {
        clearTimeout(this.activeTimeout)

        this.activeTimeout = setTimeout(() => {
          this.isActiveProxy = val
        }, 20)
      }
    },
    clippedLeft (val) {
      this.updateApplication()
    },
    clippedRight (val) {
      this.updateApplication()
    },
    isScrolling (val) {
      this.updateApplication()
    }
  },

  destroyed () {
    if (this.app) this.$vuetify.application.top -= this.computedHeight
  },

  methods: {
    onScroll () {
      if (typeof window === 'undefined') return

      if (!this.target) {
        this.target = this.scrollTarget
          ? document.querySelector(this.scrollTarget)
          : window
      }

      this.currentScroll = this.scrollTarget
        ? this.target.scrollTop
        : this.target.pageYOffset || document.documentElement.scrollTop

      this.isScrollingUp = this.currentScroll < this.previousScroll

      this.previousScroll = this.currentScroll
    },
    updateApplication () {
      if (!this.app) return

      this.$vuetify.application.top = !this.fixed &&
        !this.absolute ||
        this.invertedScroll
        ? 0
        : this.computedHeight
    }
  },

  render (h) {
    this.isExtended = this.extended || !!this.$slots.extension

    const children = []
    const data = {
      'class': this.classes,
      style: this.styles,
      on: this.$listeners
    }

    if (this.scrollOffScreen) {
      data.directives = [{
        name: 'scroll',
        value: {
          callback: this.onScroll,
          target: this.scrollTarget
        }
      }]
    }

    children.push(h('div', {
      staticClass: 'toolbar__content',
      style: { height: `${this.computedContentHeight}px` },
      ref: 'content'
    }, this.$slots.default))

    if (this.isExtended) {
      children.push(h('div', {
        staticClass: 'toolbar__extension',
        style: { height: `${this.computedExtensionHeight}px` }
      }, this.$slots.extension))
    }

    return h('nav', data, children)
  }
}
<commit_msg>fix(v-toolbar): re-evaluate application when **inverted-scroll** changes<commit_after>require('../../stylus/components/_toolbar.styl')

import Applicationable from '../../mixins/applicationable'
import Colorable from '../../mixins/colorable'
import Themeable from '../../mixins/themeable'
import SSRBootable from '../../mixins/ssr-bootable'

export default {
  name: 'v-toolbar',

  mixins: [
    Applicationable,
    Colorable,
    SSRBootable,
    Themeable
  ],

  data: () => ({
    activeTimeout: null,
    currentScroll: 0,
    heights: {
      mobileLandscape: 48,
      mobile: 56,
      desktop: 64,
      dense: 48
    },
    isActiveProxy: false,
    isExtended: false,
    isScrollingUp: false,
    previousScroll: null,
    previousScrollDirection: null,
    target: null
  }),

  props: {
    absolute: Boolean,
    card: Boolean,
    clippedLeft: Boolean,
    clippedRight: Boolean,
    dense: Boolean,
    extended: Boolean,
    extensionHeight: [Number, String],
    fixed: Boolean,
    flat: Boolean,
    floating: Boolean,
    height: [Number, String],
    invertedScroll: Boolean,
    manualScroll: {
      type: Boolean,
      default: null
    },
    prominent: Boolean,
    scrollOffScreen: Boolean,
    scrollTarget: String,
    scrollThreshold: {
      type: Number,
      default: 300
    },
    tabs: Boolean
  },

  computed: {
    computedContentHeight () {
      if (this.height) return parseInt(this.height)
      if (this.dense) return this.heights.dense

      if (this.prominent ||
        this.$vuetify.breakpoint.mdAndUp
      ) return this.heights.desktop

      if (this.$vuetify.breakpoint.width >
        this.$vuetify.breakpoint.height
      ) return this.heights.mobileLandscape

      return this.heights.mobile
    },
    computedExtensionHeight () {
      if (this.extensionHeight) return parseInt(this.extensionHeight)

      return this.computedContentHeight
    },
    computedHeight () {
      if (!this.isExtended) return this.computedContentHeight

      return this.computedContentHeight + this.computedExtensionHeight
    },
    classes () {
      return this.addBackgroundColorClassChecks({
        'toolbar': true,
        'elevation-0': this.flat || (!this.isActiveProxy && !this.tabs),
        'toolbar--absolute': this.absolute,
        'toolbar--card': this.card,
        'toolbar--clipped': this.clippedLeft || this.clippedRight,
        'toolbar--dense': this.dense,
        'toolbar--extended': this.isExtended,
        'toolbar--fixed': this.fixed,
        'toolbar--floating': this.floating,
        'toolbar--is-booted': this.isBooted,
        'toolbar--prominent': this.prominent,
        'theme--dark': this.dark,
        'theme--light': this.light
      })
    },
    paddingLeft () {
      if (!this.app || this.clippedLeft) return 0

      return this.$vuetify.application.left
    },
    paddingRight () {
      if (!this.app || this.clippedRight) return 0

      return this.$vuetify.application.right
    },
    isActive () {
      if (!this.scrollOffScreen) return true
      if (this.manualScroll) return this.manualScroll

      return this.invertedScroll
        ? this.currentScroll > this.scrollThreshold
        : this.currentScroll < this.scrollThreshold ||
          this.isScrollingUp
    },
    styles () {
      const style = {}

      if (!this.isActiveProxy) {
        style.transform = `translateY(-${this.computedHeight}px)`
      }

      if (this.app) {
        style.paddingRight = `${this.paddingRight}px`
        style.paddingLeft = `${this.paddingLeft}px`
      }

      return style
    }
  },

  watch: {
    // This is to avoid an accidental
    // false positive when scrolling.
    // sometimes for 1 frame it appears
    // as if the direction has changed
    // but it actually has not
    isActive: {
      immediate: true,
      handler (val) {
        clearTimeout(this.activeTimeout)

        this.activeTimeout = setTimeout(() => {
          this.isActiveProxy = val
        }, 20)
      }
    },
    clippedLeft (val) {
      this.updateApplication()
    },
    clippedRight (val) {
      this.updateApplication()
    },
    invertedScroll () {
      this.updateApplication()
    },
    isScrolling (val) {
      this.updateApplication()
    }
  },

  destroyed () {
    if (this.app) this.$vuetify.application.top -= this.computedHeight
  },

  methods: {
    onScroll () {
      if (typeof window === 'undefined') return

      if (!this.target) {
        this.target = this.scrollTarget
          ? document.querySelector(this.scrollTarget)
          : window
      }

      this.currentScroll = this.scrollTarget
        ? this.target.scrollTop
        : this.target.pageYOffset || document.documentElement.scrollTop

      this.isScrollingUp = this.currentScroll < this.previousScroll

      this.previousScroll = this.currentScroll
    },
    updateApplication () {
      if (!this.app) return

      this.$vuetify.application.top = (!this.fixed &&
        !this.absolute) ||
        this.invertedScroll
        ? 0
        : this.computedHeight
    }
  },

  render (h) {
    this.isExtended = this.extended || !!this.$slots.extension

    const children = []
    const data = {
      'class': this.classes,
      style: this.styles,
      on: this.$listeners
    }

    if (this.scrollOffScreen) {
      data.directives = [{
        name: 'scroll',
        value: {
          callback: this.onScroll,
          target: this.scrollTarget
        }
      }]
    }

    children.push(h('div', {
      staticClass: 'toolbar__content',
      style: { height: `${this.computedContentHeight}px` },
      ref: 'content'
    }, this.$slots.default))

    if (this.isExtended) {
      children.push(h('div', {
        staticClass: 'toolbar__extension',
        style: { height: `${this.computedExtensionHeight}px` }
      }, this.$slots.extension))
    }

    return h('nav', data, children)
  }
}
<|endoftext|>


<commit_before>// Definition of the nodes collection

import {Mongo} from 'meteor/mongo';
import {Class} from 'meteor/jagi:astronomy';
import Link from '../links/links';

export const Nodes = new Mongo.Collection('tags');

const Node = Class.create({
    name: 'Node',
    collection: Nodes,
    fields: {
        title: {
            type: String
        },
        description: {
            type: String,
            default: "No description"
        },
        links: {
            type: [String], // TODO: check why Mongo.ObjectID doesn't work
            default() {
                return [];
            },
        },
        tags: {
            type: [String], // TODO: check why Mongo.ObjectID doesn't work
            default() {
                return [];
            },
        },
        createdAt: {
            type: Date,
            default() {
                return new Date();
            },
        },
    },
    helpers: {
        getLinks() {
            return Link.find({
                _id: {
                    $in: this.links
                }
            });
        },
        getTags() {
            return Node.find({
                _id: {
                    $in: this.tags
                }
            });
        },
    },
    meteorMethods: {
        create() {
            return this.save();
        },
        update(fields) {
            this.set(fields);
            return this.save();
        },
        delete() {
            return this.remove();
        },
        addTag(nodeId) {
            this.tags.push(nodeId);
            return this.save();
        },
        addLink(linkId) {
            this.links.push(linkId);
            return this.save();
        },
    },
});

export default Node;
<commit_msg>- rename tag collection<commit_after>// Definition of the nodes collection

import {Mongo} from 'meteor/mongo';
import {Class} from 'meteor/jagi:astronomy';
import Link from '../links/links';

export const Nodes = new Mongo.Collection('nodes');

const Node = Class.create({
    name: 'Node',
    collection: Nodes,
    fields: {
        title: {
            type: String
        },
        description: {
            type: String,
            default: "No description"
        },
        links: {
            type: [String], // TODO: check why Mongo.ObjectID doesn't work
            default() {
                return [];
            },
        },
        tags: {
            type: [String], // TODO: check why Mongo.ObjectID doesn't work
            default() {
                return [];
            },
        },
        createdAt: {
            type: Date,
            default() {
                return new Date();
            },
        },
    },
    helpers: {
        getLinks() {
            return Link.find({
                _id: {
                    $in: this.links
                }
            });
        },
        getTags() {
            return Node.find({
                _id: {
                    $in: this.tags
                }
            });
        },
    },
    meteorMethods: {
        create() {
            return this.save();
        },
        update(fields) {
            this.set(fields);
            return this.save();
        },
        delete() {
            return this.remove();
        },
        addTag(nodeId) {
            this.tags.push(nodeId);
            return this.save();
        },
        addLink(linkId) {
            this.links.push(linkId);
            return this.save();
        },
    },
});

export default Node;
<|endoftext|>