<commit_before>/* global Exhibit */
/**
 * @fileOverview Slider facet functions and UI
 * @author SkyeWM
 * @author David Karger
 * @author Axel Hecht
 */

/**
 * @class
 * @constructor
 * @param {Element} containerElmt
 * @param {Exhibit.UIContext} uiContext
 */
Exhibit.SliderFacet = function(containerElmt, uiContext) {
    Exhibit.jQuery.extend(this, new Exhibit.Facet("cloud", containerElmt, uiContext));
    this.addSettingSpecs(Exhibit.SliderFacet._settingSpecs);

//  this._selection = {min: null, max: null};
    this._range = {min: null, max: null}; //currently selected range
    this._maxRange = {min: null, max: null}; //total range of slider
};

/**
 * @private
 * @constant
 */
Exhibit.SliderFacet._settingSpecs = {
    "precision":        { "type": "float", "defaultValue": 1 },
    "histogram":        { "type": "boolean", "defaultValue": true },
    "horizontal":       { "type": "boolean", "defaultValue": true },
    "showMissing":      { "type": "boolean", "defaultValue": true},
    "inputText":        { "type": "boolean", "defaultValue": true },
    "selection":        { "type": "float", "dimensions": 2},
    "height":           { "type": "text" },
    "width":            { "type": "text" }
};

/**
 * @static
 * @param {Object} configuration
 * @param {Element} containerElmt
 * @param {Exhibit.UIContext} uiContext
 * @returns {Exhibit.SliderFacet}
 */
Exhibit.SliderFacet.create = function(configuration, containerElmt, uiContext) {
    var facet, thisUIContext;
    thisUIContext = Exhibit.UIContext.create(configuration, uiContext);
    facet = new Exhibit.SliderFacet(containerElmt, thisUIContext);

    Exhibit.SliderFacet._configure(facet, configuration);

    facet._initializeUI();
    thisUIContext.getCollection().addFacet(facet);
    facet.register();

    return facet;
};

/**
 * @static
 * @param {Element} configElmt
 * @param {Element} containerElmt
 * @param {Exhibit.UIContext} uiContext
 * @returns {Exhibit.SliderFacet}
 */
Exhibit.SliderFacet.createFromDOM = function(configElmt, containerElmt, uiContext) {
    var configuration, thisUIContext, facet, expressionString, selection, showMissing;
    configuration = Exhibit.getConfigurationFromDOM(configElmt);
    thisUIContext = Exhibit.UIContext.createFromDOM(configElmt, uiContext);
    facet = new Exhibit.SliderFacet(
    (typeof containerElmt !== "undefined" && containerElmt !== null) ?
            containerElmt :
            configElmt,
    thisUIContext
    );

    //fills facet with configuration information
    Exhibit.SettingsUtilities.collectSettingsFromDOM(configElmt, facet.getSettingSpecs(), facet._settings);

    try {
        expressionString = Exhibit.getAttribute(configElmt, "expression");
        if (typeof expressionString !== "undefined" && expressionString !== null && expressionString.length > 0) {
            facet.setExpression(Exhibit.ExpressionParser.parse(expressionString));
        facet.setExpressionString(expressionString);
        }

        showMissing = Exhibit.getAttribute(configElmt, "showMissing");

        if (showMissing !== null && showMissing.length > 0) {
            facet._showMissing = (showMissing == "true");
        }
        else {
            facet._showMissing=true;
        }

    if ("selection" in facet._settings) {
        selection = facet._settings.selection;
        facet._range = {min: selection[0], max: selection[1]};
    }

    } catch (e) {
        Exhibit.Debug.exception(e, Exhibit._("%facets.error.configuration", "SliderFacet"));
    }

    Exhibit.SliderFacet._configure(facet, configuration);
    facet._initializeUI();
    thisUIContext.getCollection().addFacet(facet);
    facet.register();

    return facet;
};

/**
 * @static
 * @private
 * @param {Exhibit.SliderFacet} facet
 * @param {Object} configuration
 */
Exhibit.SliderFacet._configure = function(facet, configuration) {
    var selection, segment, property;
    Exhibit.SettingsUtilities.collectSettings(configuration, facet.getSettingSpecs(), facet._settings);

    if (typeof configuration.expression !== "undefined") {
        facet.setExpressionString(configuration.expression);
        facet.setExpression(Exhibit.ExpressionParser.parse(configuration.expression));
    }
    if (typeof configuration.selection !== "undefined") {
        selection = configuration.selection;
        facet._range = {min: selection[0], max: selection[1]};
    }
    if (typeof configuration.showMissing !== "undefined") {
        facet._showMissing = configuration.showMissing;
    }

    if (typeof facet._settings.facetLabel === "undefined") {
        if (facet.getExpression() !== null && facet.getExpression().isPath()) {
            segment = facet.getExpression().getPath().getLastSegment();
            property = facet.getUIContext().getDatabase().getProperty(segment.property);
            if (typeof property !== "undefined" && property !== null) {
                facet._settings.facetLabel = segment.forward ? property.getLabel() : property.getReverseLabel();
            }
        }
    }

    facet._cache = new Exhibit.FacetUtilities.Cache(
        facet.getUIContext().getDatabase(),
        facet.getUIContext().getCollection(),
        facet.getExpression()
    );

    facet._maxRange = facet._getMaxRange();
};

/**
 *
 */
Exhibit.SliderFacet.prototype._dispose = function() {
    this._cache.dispose();
    this._cache = null;
//  this._selection = null;
    this._range = null; //currently selected range
    this._maxRange = null; //total range of slider
};

/**
 * @returns {Boolean}
 */
Exhibit.SliderFacet.prototype.hasRestrictions = function() {
    return (this._range.min && this._range.min != this._maxRange.min) ||
           (this._range.max && this._range.max != this._maxRange.max);
};

/**
 *
 */
Exhibit.SliderFacet.prototype.clearAllRestrictions = function() {
    Exhibit.jQuery(this.getContainer()).trigger("onBeforeFacetReset.exhibit");
    this._range = this._maxRange;
    this._notifyCollection();
};

/**
 * @param {Exhibit.Set} items
 * @returns {Exhibit.Set}
 */
Exhibit.SliderFacet.prototype.restrict = function(items) {
    if (!this.hasRestrictions()) {
        return items;
    }

    var set = new Exhibit.Set();

    if (this.getExpression().isPath()){
        var path = this.getExpression().getPath();
        var database = this.getUIContext().getDatabase();
        set=path.rangeBackward(this._range.min, this._range.max, false, items, database).values;
    }
    else {
        this._buildRangeIndex();
        var rangeIndex=this._rangeIndex;
        set=rangeIndex.getSubjectsInRange(this._range.min, this._range.max, false, null, items);
    }

    if (this._showMissing){
        this._cache.getItemsMissingValue(items, set);
    }

    return set;
};

/**
 * @param {Exhibit.Set} items
 */
Exhibit.SliderFacet.prototype.update = function(items) {
    if (this._settings.histogram) {
        var data = [];
        var n = 75; //number of bars on histogram
        var range = (this._maxRange.max - this._maxRange.min)/n; //range represented by each bar
        var missingCount = 0;
        var database = this.getUIContext().getDatabase();

        if (this._selectMissing){
            missingCount = this._cache.getItemsMissingValue(items).size();
        }

        if (this.getExpression().isPath()){
            var path = this.getExpression().getPath();

            for (var i=0; i<n; i++) {
                data[i] = path.rangeBackward(this._maxRange.min+i*range, this._maxRange.min+(i+1)*range, (i+1) === n, items,database).values.size()+missingCount;
            }
        }

        else {
            this._buildRangeIndex();
            var rangeIndex=this._rangeIndex;

            for (var i=0; i<n; i++){
                data[i] = rangeIndex.getSubjectsInRange(this._maxRange.min+i*range, this._maxRange.min+(i+1)*range, (i+1) === n, null, items).size()+missingCount;
            }

        }
        this._slider.updateHistogram(data);
    }
    this._slider._setMin(this._range.min);
    this._slider._setMax(this._range.max);
};

/**
 * @private
 */
Exhibit.SliderFacet.prototype._initializeUI = function() {
    Exhibit.jQuery(this.getContainer()).empty();
    Exhibit.jQuery(this.getContainer()).addClass("exhibit-cloudFacet");

    var dom = Exhibit.jQuery.simileDOM(
        "string",
        this.getContainer(),
        ((typeof this._settings.facetLabel !== "undefined") ?
         (   "
" +
             "" + this.getLabel() + "" +
             "
"
         ) :
         ""
        ) +
            '

'
    );

    this._dom = dom;

    this._slider = new Exhibit.SliderFacet.slider(this._dom.slider, this, this._settings.precision, this._settings.horizontal);
};

/**
 * @private
 * @returns {Object}
 */
//gets maximum range? need to construct a range index for this...
Exhibit.SliderFacet.prototype._getMaxRange = function() {
    var rangeIndex;
    if (this.getExpression().isPath()){
        var path = this.getExpression().getPath();
        var database = this.getUIContext().getDatabase();
        var propertyID = path.getLastSegment().property;
        var property = database.getProperty(propertyID);
        rangeIndex = property.getRangeIndex();
    }
    else {
        this._buildRangeIndex();
        rangeIndex = this._rangeIndex;
    }

    return {min: rangeIndex.getMin(), max: rangeIndex.getMax()};
};

/**
 * @private
 */
Exhibit.SliderFacet.prototype._buildRangeIndex = function() {
    if (typeof this._rangeIndex === "undefined") {
        var expression = this.getExpression();
        var database = this.getUIContext().getDatabase();
        var getter = function(item, f) {
            expression.evaluateOnItem(item, database).values.visit(function(value) {
                if (typeof value != "number") {
                    value = parseFloat(value);
                }
                if (!isNaN(value)) {
                    f(value);
                }
            });
        };

        this._rangeIndex = new Exhibit.Database.RangeIndex(
            this.getUIContext().getCollection().getAllItems(),
            getter
        );
    }
};

/**
 * Used by the actual slider to set the facet values
 */
Exhibit.SliderFacet.prototype.changeRange = function(range) {
    Exhibit.History.pushComponentState(
        this,
        Exhibit.Facet.getRegistryKey(),
        range,
        Exhibit._("%facets.numeric.rangeShort", range.min, range.max),
        true
    );
};

//clear
Exhibit.SliderFacet.prototype._notifyCollection = function() {
    this.getUIContext().getCollection().onFacetUpdated(this);
};

/**
 * @returns {Object}
 */
Exhibit.SliderFacet.prototype.exportEmptyState = function() {
    return {};
};

/**
 * @returns {Object}
 */
Exhibit.SliderFacet.prototype.exportState = function() {
    return this._range;
};

/**
 * @param {Object} state
 */
Exhibit.SliderFacet.prototype.importState = function(state) {
    if (this.stateDiffers(state)) {
        if (typeof state.min === 'undefined' || state.min === null) {
            this._range.min = this._maxRange.min;
        }
        else {
            this._range.min = state.min;
        }
        this._slider._setMin(this._range.min);
        if (typeof state.max === 'undefined' || state.max === null) {
            this._range.max = this._maxRange.max;
        }
        else {
            this._range.max = state.max;
        }
        this._slider._setMax(this._range.max);
    }
    this._notifyCollection();
};

/**
 * @param {Object} state
 */
Exhibit.SliderFacet.prototype.stateDiffers = function(state) {
    if (typeof state.min === 'undefined') {
        if (this._range.min && this._range.min != this._maxRange.min) {
            return true;
        }
    }
    else {
        if (this._range.min !== state.min) {
            return true;
        }
    }
    if (typeof state.max === 'undefined') {
        if (this._range.max && this._range.max != this._maxRange.max) {
            return true;
        }
    }
    else {
        if (this._range.max !== state.min) {
            return true;
        }
    }
    return false;
};
<commit_msg>Only notify collection update if slider facet state has changed<commit_after>/* global Exhibit */
/**
 * @fileOverview Slider facet functions and UI
 * @author SkyeWM
 * @author David Karger
 * @author Axel Hecht
 */

/**
 * @class
 * @constructor
 * @param {Element} containerElmt
 * @param {Exhibit.UIContext} uiContext
 */
Exhibit.SliderFacet = function(containerElmt, uiContext) {
    Exhibit.jQuery.extend(this, new Exhibit.Facet("cloud", containerElmt, uiContext));
    this.addSettingSpecs(Exhibit.SliderFacet._settingSpecs);

//  this._selection = {min: null, max: null};
    this._range = {min: null, max: null}; //currently selected range
    this._maxRange = {min: null, max: null}; //total range of slider
};

/**
 * @private
 * @constant
 */
Exhibit.SliderFacet._settingSpecs = {
    "precision":        { "type": "float", "defaultValue": 1 },
    "histogram":        { "type": "boolean", "defaultValue": true },
    "horizontal":       { "type": "boolean", "defaultValue": true },
    "showMissing":      { "type": "boolean", "defaultValue": true},
    "inputText":        { "type": "boolean", "defaultValue": true },
    "selection":        { "type": "float", "dimensions": 2},
    "height":           { "type": "text" },
    "width":            { "type": "text" }
};

/**
 * @static
 * @param {Object} configuration
 * @param {Element} containerElmt
 * @param {Exhibit.UIContext} uiContext
 * @returns {Exhibit.SliderFacet}
 */
Exhibit.SliderFacet.create = function(configuration, containerElmt, uiContext) {
    var facet, thisUIContext;
    thisUIContext = Exhibit.UIContext.create(configuration, uiContext);
    facet = new Exhibit.SliderFacet(containerElmt, thisUIContext);

    Exhibit.SliderFacet._configure(facet, configuration);

    facet._initializeUI();
    thisUIContext.getCollection().addFacet(facet);
    facet.register();

    return facet;
};

/**
 * @static
 * @param {Element} configElmt
 * @param {Element} containerElmt
 * @param {Exhibit.UIContext} uiContext
 * @returns {Exhibit.SliderFacet}
 */
Exhibit.SliderFacet.createFromDOM = function(configElmt, containerElmt, uiContext) {
    var configuration, thisUIContext, facet, expressionString, selection, showMissing;
    configuration = Exhibit.getConfigurationFromDOM(configElmt);
    thisUIContext = Exhibit.UIContext.createFromDOM(configElmt, uiContext);
    facet = new Exhibit.SliderFacet(
    (typeof containerElmt !== "undefined" && containerElmt !== null) ?
            containerElmt :
            configElmt,
    thisUIContext
    );

    //fills facet with configuration information
    Exhibit.SettingsUtilities.collectSettingsFromDOM(configElmt, facet.getSettingSpecs(), facet._settings);

    try {
        expressionString = Exhibit.getAttribute(configElmt, "expression");
        if (typeof expressionString !== "undefined" && expressionString !== null && expressionString.length > 0) {
            facet.setExpression(Exhibit.ExpressionParser.parse(expressionString));
        facet.setExpressionString(expressionString);
        }

        showMissing = Exhibit.getAttribute(configElmt, "showMissing");

        if (showMissing !== null && showMissing.length > 0) {
            facet._showMissing = (showMissing == "true");
        }
        else {
            facet._showMissing=true;
        }

    if ("selection" in facet._settings) {
        selection = facet._settings.selection;
        facet._range = {min: selection[0], max: selection[1]};
    }

    } catch (e) {
        Exhibit.Debug.exception(e, Exhibit._("%facets.error.configuration", "SliderFacet"));
    }

    Exhibit.SliderFacet._configure(facet, configuration);
    facet._initializeUI();
    thisUIContext.getCollection().addFacet(facet);
    facet.register();

    return facet;
};

/**
 * @static
 * @private
 * @param {Exhibit.SliderFacet} facet
 * @param {Object} configuration
 */
Exhibit.SliderFacet._configure = function(facet, configuration) {
    var selection, segment, property;
    Exhibit.SettingsUtilities.collectSettings(configuration, facet.getSettingSpecs(), facet._settings);

    if (typeof configuration.expression !== "undefined") {
        facet.setExpressionString(configuration.expression);
        facet.setExpression(Exhibit.ExpressionParser.parse(configuration.expression));
    }
    if (typeof configuration.selection !== "undefined") {
        selection = configuration.selection;
        facet._range = {min: selection[0], max: selection[1]};
    }
    if (typeof configuration.showMissing !== "undefined") {
        facet._showMissing = configuration.showMissing;
    }

    if (typeof facet._settings.facetLabel === "undefined") {
        if (facet.getExpression() !== null && facet.getExpression().isPath()) {
            segment = facet.getExpression().getPath().getLastSegment();
            property = facet.getUIContext().getDatabase().getProperty(segment.property);
            if (typeof property !== "undefined" && property !== null) {
                facet._settings.facetLabel = segment.forward ? property.getLabel() : property.getReverseLabel();
            }
        }
    }

    facet._cache = new Exhibit.FacetUtilities.Cache(
        facet.getUIContext().getDatabase(),
        facet.getUIContext().getCollection(),
        facet.getExpression()
    );

    facet._maxRange = facet._getMaxRange();
};

/**
 *
 */
Exhibit.SliderFacet.prototype._dispose = function() {
    this._cache.dispose();
    this._cache = null;
//  this._selection = null;
    this._range = null; //currently selected range
    this._maxRange = null; //total range of slider
};

/**
 * @returns {Boolean}
 */
Exhibit.SliderFacet.prototype.hasRestrictions = function() {
    return (this._range.min && this._range.min != this._maxRange.min) ||
           (this._range.max && this._range.max != this._maxRange.max);
};

/**
 *
 */
Exhibit.SliderFacet.prototype.clearAllRestrictions = function() {
    Exhibit.jQuery(this.getContainer()).trigger("onBeforeFacetReset.exhibit");
    this._range = this._maxRange;
    this._notifyCollection();
};

/**
 * @param {Exhibit.Set} items
 * @returns {Exhibit.Set}
 */
Exhibit.SliderFacet.prototype.restrict = function(items) {
    if (!this.hasRestrictions()) {
        return items;
    }

    var set = new Exhibit.Set();

    if (this.getExpression().isPath()){
        var path = this.getExpression().getPath();
        var database = this.getUIContext().getDatabase();
        set=path.rangeBackward(this._range.min, this._range.max, false, items, database).values;
    }
    else {
        this._buildRangeIndex();
        var rangeIndex=this._rangeIndex;
        set=rangeIndex.getSubjectsInRange(this._range.min, this._range.max, false, null, items);
    }

    if (this._showMissing){
        this._cache.getItemsMissingValue(items, set);
    }

    return set;
};

/**
 * @param {Exhibit.Set} items
 */
Exhibit.SliderFacet.prototype.update = function(items) {
    if (this._settings.histogram) {
        var data = [];
        var n = 75; //number of bars on histogram
        var range = (this._maxRange.max - this._maxRange.min)/n; //range represented by each bar
        var missingCount = 0;
        var database = this.getUIContext().getDatabase();

        if (this._selectMissing){
            missingCount = this._cache.getItemsMissingValue(items).size();
        }

        if (this.getExpression().isPath()){
            var path = this.getExpression().getPath();

            for (var i=0; i<n; i++) {
                data[i] = path.rangeBackward(this._maxRange.min+i*range, this._maxRange.min+(i+1)*range, (i+1) === n, items,database).values.size()+missingCount;
            }
        }

        else {
            this._buildRangeIndex();
            var rangeIndex=this._rangeIndex;

            for (var i=0; i<n; i++){
                data[i] = rangeIndex.getSubjectsInRange(this._maxRange.min+i*range, this._maxRange.min+(i+1)*range, (i+1) === n, null, items).size()+missingCount;
            }

        }
        this._slider.updateHistogram(data);
    }
    this._slider._setMin(this._range.min);
    this._slider._setMax(this._range.max);
};

/**
 * @private
 */
Exhibit.SliderFacet.prototype._initializeUI = function() {
    Exhibit.jQuery(this.getContainer()).empty();
    Exhibit.jQuery(this.getContainer()).addClass("exhibit-cloudFacet");

    var dom = Exhibit.jQuery.simileDOM(
        "string",
        this.getContainer(),
        ((typeof this._settings.facetLabel !== "undefined") ?
         (   "
" +
             "" + this.getLabel() + "" +
             "
"
         ) :
         ""
        ) +
            '

'
    );

    this._dom = dom;

    this._slider = new Exhibit.SliderFacet.slider(this._dom.slider, this, this._settings.precision, this._settings.horizontal);
};

/**
 * @private
 * @returns {Object}
 */
//gets maximum range? need to construct a range index for this...
Exhibit.SliderFacet.prototype._getMaxRange = function() {
    var rangeIndex;
    if (this.getExpression().isPath()){
        var path = this.getExpression().getPath();
        var database = this.getUIContext().getDatabase();
        var propertyID = path.getLastSegment().property;
        var property = database.getProperty(propertyID);
        rangeIndex = property.getRangeIndex();
    }
    else {
        this._buildRangeIndex();
        rangeIndex = this._rangeIndex;
    }

    return {min: rangeIndex.getMin(), max: rangeIndex.getMax()};
};

/**
 * @private
 */
Exhibit.SliderFacet.prototype._buildRangeIndex = function() {
    if (typeof this._rangeIndex === "undefined") {
        var expression = this.getExpression();
        var database = this.getUIContext().getDatabase();
        var getter = function(item, f) {
            expression.evaluateOnItem(item, database).values.visit(function(value) {
                if (typeof value != "number") {
                    value = parseFloat(value);
                }
                if (!isNaN(value)) {
                    f(value);
                }
            });
        };

        this._rangeIndex = new Exhibit.Database.RangeIndex(
            this.getUIContext().getCollection().getAllItems(),
            getter
        );
    }
};

/**
 * Used by the actual slider to set the facet values
 */
Exhibit.SliderFacet.prototype.changeRange = function(range) {
    Exhibit.History.pushComponentState(
        this,
        Exhibit.Facet.getRegistryKey(),
        range,
        Exhibit._("%facets.numeric.rangeShort", range.min, range.max),
        true
    );
};

//clear
Exhibit.SliderFacet.prototype._notifyCollection = function() {
    this.getUIContext().getCollection().onFacetUpdated(this);
};

/**
 * @returns {Object}
 */
Exhibit.SliderFacet.prototype.exportEmptyState = function() {
    return {};
};

/**
 * @returns {Object}
 */
Exhibit.SliderFacet.prototype.exportState = function() {
    return this._range;
};

/**
 * @param {Object} state
 */
Exhibit.SliderFacet.prototype.importState = function(state) {
    if (this.stateDiffers(state)) {
        if (typeof state.min === 'undefined' || state.min === null) {
            this._range.min = this._maxRange.min;
        }
        else {
            this._range.min = state.min;
        }
        this._slider._setMin(this._range.min);
        if (typeof state.max === 'undefined' || state.max === null) {
            this._range.max = this._maxRange.max;
        }
        else {
            this._range.max = state.max;
        }
        this._slider._setMax(this._range.max);

        this._notifyCollection();
    }
};

/**
 * @param {Object} state
 */
Exhibit.SliderFacet.prototype.stateDiffers = function(state) {
    if (typeof state.min === 'undefined') {
        if (this._range.min && this._range.min != this._maxRange.min) {
            return true;
        }
    }
    else {
        if (this._range.min !== state.min) {
            return true;
        }
    }
    if (typeof state.max === 'undefined') {
        if (this._range.max && this._range.max != this._maxRange.max) {
            return true;
        }
    }
    else {
        if (this._range.max !== state.min) {
            return true;
        }
    }
    return false;
};
<|endoftext|>


<commit_before>import makeDebug from 'debug';
import fp from 'mostly-func';
import { getId, isSelected, selectNext } from '../helpers';

const debug = makeDebug('mostly:feathers-mongoose:hooks:assoc');

const defaultOptions = {
  idField: 'id'
};

function isPresent (obj, target) {
  return fp.reduce((result, val) => {
    return result && fp.has(target, val);
  }, true, [].concat(obj));
}

// associcate current data as a foreign key to another service
export default function assoc(target, opts) {
  opts = Object.assign({}, defaultOptions, opts);

  return function(hook) {
    let options = Object.assign({}, opts);

    if (hook.type !== 'after') {
      throw new Error(`The 'assoc' hook should only be used as a 'after' hook.`);
    }

    if (!options.service || !options.field) {
      throw new Error('You need to provide a service and a field');
    }

    const assocField = function (data, params, target) {
      const service = hook.app.service(options.service);

      // pass infomation of the $select and specified by options.fallThrough
      const selection = { $select: params.query.$select };
      params = options.fallThrough
        ? fp.pick(options.fallThrough, params)
        : {};
      params.query = selection;

      if (Array.isArray(data)) {
        params.query = fp.merge(params.query, {
          [options.field]: { $in: fp.map(fp.prop(options.idField), data) },
        });
      } else {
        if (options.elemMatch) {
          params.query = fp.merge(params.query, {
            [options.field]: { $elemMatch: { [options.elemMatch]: fp.prop(options.idField, data) } }
          });
        } else {
          params.query = fp.merge(params.query, {
            [options.field]: fp.prop(options.idField, data)
          });
        }
      }
      params.populate = false; // prevent recursive populate
      params.paginate = false; // disable paginate

      // filter
      if (options.filters && options.filters.length > 0) {
        for (const filter of options.filters) {
          if (filter.field && filter.value) {
            const value = typeof filter.value === 'function'
              ? filter.value.call(null, hook) : filter.value;
            params.query[filter.field] = Array.isArray(value)? { $in: value } : value;
          }
        }
      }
      // sort
      if (options.sort) {
        params.query.$sort = options.sort;
      }
      // limit
      if (options.limit) {
        params.query.$limit = options.limit;
      }

      debug('assoc =>', target, options.service, params.query);

      return service.find(params).then((results) => {
        const filterById = function (id) {
          return fp.filter(obj => {
            let prop = Array.concat([], obj[options.field] || []);
            prop = fp.map(getId(options.idField), prop); // convert ObjectId
            return prop.indexOf(id) >= 0;
          });
        };
        const assocResult = function (results) {
          return fp.map((item) => {
            let values = filterById(item[options.idField])(results);
            if (values) {
              if (options.sort) {
                values = fp.sortBy(fp.prop(options.sort), values);
              }
              return fp.assoc(target, values, item);
            }
            return item;
          });
        };
        results = results && results.data || results;
        if (Array.isArray(data)) {
          return assocResult(results)(data);
        } else {
          return fp.assoc(target, results, data);
        }
      });
    };

    let data = hook.result && hook.result.data || hook.result;

    if (fp.isNil(data) || fp.isEmpty(data)) return hook;

    // each assoc field should have its own params
    let params = fp.assign({ query: {} }, hook.params);

    // target must be specified by $select to assoc
    if (!isSelected(target, params.query.$select)) return hook;

    // already associcated
    if (isPresent(data, target)) return hook;

    // $select with * for next level
    if (params.query.$select) {
      params.query.$select = selectNext(target, params.query.$select);
    }

    return assocField(data, params, target, options).then(result => {
      if (hook.result.data) {
        hook.result.data = result;
      } else {
        hook.result = result;
      }
      return hook;
    });
  };
}

<commit_msg>Fix assoc result with array field using elemMatch<commit_after>import makeDebug from 'debug';
import fp from 'mostly-func';
import { getId, isSelected, selectNext } from '../helpers';

const debug = makeDebug('mostly:feathers-mongoose:hooks:assoc');

const defaultOptions = {
  idField: 'id'
};

function isPresent (obj, target) {
  return fp.reduce((result, val) => {
    return result && fp.has(target, val);
  }, true, [].concat(obj));
}

// associcate current data as a foreign key to another service
export default function assoc(target, opts) {
  opts = Object.assign({}, defaultOptions, opts);

  return function(hook) {
    let options = Object.assign({}, opts);

    if (hook.type !== 'after') {
      throw new Error(`The 'assoc' hook should only be used as a 'after' hook.`);
    }

    if (!options.service || !options.field) {
      throw new Error('You need to provide a service and a field');
    }

    const assocField = function (data, params, target) {
      const service = hook.app.service(options.service);

      // pass infomation of the $select and specified by options.fallThrough
      const selection = { $select: params.query.$select };
      params = options.fallThrough
        ? fp.pick(options.fallThrough, params)
        : {};
      params.query = selection;

      if (Array.isArray(data)) {
        // assoc with array field
        if (options.elemMatch) {
          params.query = fp.merge(params.query, {
            [options.field]: {
              $elemMatch: {
                [options.elemMatch]: { $in: fp.map(fp.prop(options.idField), data) }
              }
            }
          });
        } else {
          params.query = fp.merge(params.query, {
            [options.field]: { $in: fp.map(fp.prop(options.idField), data) },
          });
        }
      } else {
        // assoc with array field
        if (options.elemMatch) {
          params.query = fp.merge(params.query, {
            [options.field]: {
              $elemMatch: {
                [options.elemMatch]: fp.prop(options.idField, data)
              }
            }
          });
        } else {
          params.query = fp.merge(params.query, {
            [options.field]: fp.prop(options.idField, data)
          });
        }
      }
      params.populate = false; // prevent recursive populate
      params.paginate = false; // disable paginate

      // filter
      if (options.filters && options.filters.length > 0) {
        for (const filter of options.filters) {
          if (filter.field && filter.value) {
            const value = typeof filter.value === 'function'
              ? filter.value.call(null, hook) : filter.value;
            params.query[filter.field] = Array.isArray(value)? { $in: value } : value;
          }
        }
      }
      // sort
      if (options.sort) {
        params.query.$sort = options.sort;
      }
      // limit
      if (options.limit) {
        params.query.$limit = options.limit;
      }

      debug('assoc =>', target, options.service, params.query);

      return service.find(params).then((results) => {
        const filterById = function (id) {
          return fp.filter(obj => {
            let prop = Array.concat([], obj[options.field] || []);
            // assoc with array field
            if (options.elemMatch) {
              return fp.find(elem => getId(options.elemMatch, elem) === id, prop);
            } else {
              return fp.find(elem => getId(options.idField, elem) === id, prop);
            }
          });
        };
        const assocResult = function (results) {
          return fp.map((item) => {
            let values = filterById(item[options.idField])(results);
            if (values) {
              if (options.sort) {
                values = fp.sortBy(fp.prop(options.sort), values);
              }
              return fp.assoc(target, values, item);
            }
            return item;
          });
        };
        results = results && results.data || results;
        if (Array.isArray(data)) {
          return assocResult(results)(data);
        } else {
          return fp.assoc(target, results, data);
        }
      });
    };

    let data = hook.result && hook.result.data || hook.result;

    if (fp.isNil(data) || fp.isEmpty(data)) return hook;

    // each assoc field should have its own params
    let params = fp.assign({ query: {} }, hook.params);

    // target must be specified by $select to assoc
    if (!isSelected(target, params.query.$select)) return hook;

    // already associcated
    if (isPresent(data, target)) return hook;

    // $select with * for next level
    if (params.query.$select) {
      params.query.$select = selectNext(target, params.query.$select);
    }

    return assocField(data, params, target, options).then(result => {
      if (hook.result.data) {
        hook.result.data = result;
      } else {
        hook.result = result;
      }
      return hook;
    });
  };
}

<|endoftext|>


<commit_before>/*
 * OpenSeadragon - ImageLoader
 *
 * Copyright (C) 2009 CodePlex Foundation
 * Copyright (C) 2010-2013 OpenSeadragon contributors

 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * - Neither the name of CodePlex Foundation nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

(function( $ ){

// private class
function ImageJob ( options ) {

    $.extend( true, this, {
        timeout:        $.DEFAULT_SETTINGS.timeout,
        jobId:          null
    }, options );

    /**
     * Image object which will contain downloaded image.
     * @member {Image} image
     * @memberof OpenSeadragon.ImageJob#
     */
    this.image = null;
}

ImageJob.prototype = {
    start: function(){
        var _this = this;

        this.image = new Image();

        if ( this.crossOriginPolicy !== false ) {
            this.image.crossOrigin = this.crossOriginPolicy;
        }

        this.image.onload = function(){
            _this.finish( true );
        };
        this.image.onabort = this.image.onerror = function(){
            _this.finish( false );
        };

        this.jobId = window.setTimeout( function(){
            _this.finish( false );
        }, this.timeout);

        this.image.src = this.src;
    },

    finish: function( successful ) {
        this.image.onload = this.image.onerror = this.image.onabort = null;
        if (!successful) {
            this.image = null;
        }

        if ( this.jobId ) {
            window.clearTimeout( this.jobId );
        }

        this.callback( this );
    }

};

/**
 * @class ImageLoader
 * @memberof OpenSeadragon
 * @classdesc Handles downloading of a set of images using asynchronous queue pattern.
 * You generally won't have to interact with the ImageLoader directly.
 * @param {Object} options - Options for this ImageLoader.
 * @param {Number} [options.jobLimit] - The number of concurrent image requests. See imageLoaderLimit in {@link OpenSeadragon.Options} for details.
 */
$.ImageLoader = function( options ) {

    $.extend( true, this, {
        jobLimit:       $.DEFAULT_SETTINGS.imageLoaderLimit,
        jobQueue:       [],
        jobsInProgress: 0
    }, options );

};

$.ImageLoader.prototype = /** @lends OpenSeadragon.ImageLoader.prototype */{

    /**
     * Add an unloaded image to the loader queue.
     * @method
     * @param {String} src - URL of image to download.
     * @param {String} crossOriginPolicy - CORS policy to use for downloads
     * @param {Function} callback - Called once image has been downloaded.
     */
    addJob: function( options ) {
        var _this = this,
            complete = function( job ) {
                completeJob( _this, job, options.callback );
            },
            jobOptions = {
                src: options.src,
                crossOriginPolicy: options.crossOriginPolicy,
                callback: complete,
                abort: options.abort
            },
            newJob = new ImageJob( jobOptions );

        if ( !this.jobLimit || this.jobsInProgress < this.jobLimit ) {
            newJob.start();
            this.jobsInProgress++;
        }
        else {
            this.jobQueue.push( newJob );
        }
    },

    /**
     * Clear any unstarted image loading jobs from the queue.
     * @method
     */
    clear: function() {
        for( var i = 0; i < this.jobQueue.length; i++ ) {
            job = this.jobQueue[i];
            if ( typeof job.abort === "function" ) {
                job.abort();
            }
        }

        this.jobQueue = [];
    }
};

/**
 * Cleans up ImageJob once completed.
 * @method
 * @private
 * @param loader - ImageLoader used to start job.
 * @param job - The ImageJob that has completed.
 * @param callback - Called once cleanup is finished.
 */
function completeJob( loader, job, callback ) {
    var nextJob;

    loader.jobsInProgress--;

    if ( (!loader.jobLimit || loader.jobsInProgress < loader.jobLimit) && loader.jobQueue.length > 0) {
        nextJob = loader.jobQueue.shift();
        nextJob.start();
        loader.jobsInProgress++;
    }

    callback( job.image );
}

}( OpenSeadragon ));
<commit_msg>Forgot var<commit_after>/*
 * OpenSeadragon - ImageLoader
 *
 * Copyright (C) 2009 CodePlex Foundation
 * Copyright (C) 2010-2013 OpenSeadragon contributors

 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * - Neither the name of CodePlex Foundation nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

(function( $ ){

// private class
function ImageJob ( options ) {

    $.extend( true, this, {
        timeout:        $.DEFAULT_SETTINGS.timeout,
        jobId:          null
    }, options );

    /**
     * Image object which will contain downloaded image.
     * @member {Image} image
     * @memberof OpenSeadragon.ImageJob#
     */
    this.image = null;
}

ImageJob.prototype = {
    start: function(){
        var _this = this;

        this.image = new Image();

        if ( this.crossOriginPolicy !== false ) {
            this.image.crossOrigin = this.crossOriginPolicy;
        }

        this.image.onload = function(){
            _this.finish( true );
        };
        this.image.onabort = this.image.onerror = function(){
            _this.finish( false );
        };

        this.jobId = window.setTimeout( function(){
            _this.finish( false );
        }, this.timeout);

        this.image.src = this.src;
    },

    finish: function( successful ) {
        this.image.onload = this.image.onerror = this.image.onabort = null;
        if (!successful) {
            this.image = null;
        }

        if ( this.jobId ) {
            window.clearTimeout( this.jobId );
        }

        this.callback( this );
    }

};

/**
 * @class ImageLoader
 * @memberof OpenSeadragon
 * @classdesc Handles downloading of a set of images using asynchronous queue pattern.
 * You generally won't have to interact with the ImageLoader directly.
 * @param {Object} options - Options for this ImageLoader.
 * @param {Number} [options.jobLimit] - The number of concurrent image requests. See imageLoaderLimit in {@link OpenSeadragon.Options} for details.
 */
$.ImageLoader = function( options ) {

    $.extend( true, this, {
        jobLimit:       $.DEFAULT_SETTINGS.imageLoaderLimit,
        jobQueue:       [],
        jobsInProgress: 0
    }, options );

};

$.ImageLoader.prototype = /** @lends OpenSeadragon.ImageLoader.prototype */{

    /**
     * Add an unloaded image to the loader queue.
     * @method
     * @param {String} src - URL of image to download.
     * @param {String} crossOriginPolicy - CORS policy to use for downloads
     * @param {Function} callback - Called once image has been downloaded.
     */
    addJob: function( options ) {
        var _this = this,
            complete = function( job ) {
                completeJob( _this, job, options.callback );
            },
            jobOptions = {
                src: options.src,
                crossOriginPolicy: options.crossOriginPolicy,
                callback: complete,
                abort: options.abort
            },
            newJob = new ImageJob( jobOptions );

        if ( !this.jobLimit || this.jobsInProgress < this.jobLimit ) {
            newJob.start();
            this.jobsInProgress++;
        }
        else {
            this.jobQueue.push( newJob );
        }
    },

    /**
     * Clear any unstarted image loading jobs from the queue.
     * @method
     */
    clear: function() {
        for( var i = 0; i < this.jobQueue.length; i++ ) {
            var job = this.jobQueue[i];
            if ( typeof job.abort === "function" ) {
                job.abort();
            }
        }

        this.jobQueue = [];
    }
};

/**
 * Cleans up ImageJob once completed.
 * @method
 * @private
 * @param loader - ImageLoader used to start job.
 * @param job - The ImageJob that has completed.
 * @param callback - Called once cleanup is finished.
 */
function completeJob( loader, job, callback ) {
    var nextJob;

    loader.jobsInProgress--;

    if ( (!loader.jobLimit || loader.jobsInProgress < loader.jobLimit) && loader.jobQueue.length > 0) {
        nextJob = loader.jobQueue.shift();
        nextJob.start();
        loader.jobsInProgress++;
    }

    callback( job.image );
}

}( OpenSeadragon ));
<|endoftext|>


<commit_before>var N = 1000;

console.log('finished');

function test1() {
  var startTime = performance.now();
  var node = foam.u2.Element.create({nodeName: 'UL'});     // Create a
 node
  for ( var i = 0 ; i < N ; i++ )
    node.start('li').add("1text" + i).end();                // Append an

  //node.write(document);
  console.log('U2 (pre)', performance.now() - startTime);
}

function test2() {
  var startTime = performance.now();
  var node = foam.u2.Element.create({nodeName: 'UL'});     // Create a
 node
  node.write(document);
  for ( var i = 0 ; i < N ; i++ )
    node.start('li').add("2text" + i).end();                // Append an

  console.log('U2 (post)', performance.now() - startTime);
}

function test3() {
  var startTime = performance.now();
  var node = document.createElement("UL");               // Create a
 node
  for ( var i = 0 ; i < N ; i++ ) {
    var li = document.createElement("LI")
    li.appendChild(document.createTextNode("3text" + i)); // Append an

    node.appendChild(li);
  }
  document.body.appendChild(node);
  console.log('DOM (pre)', performance.now() - startTime);
}

function test4() {
  var startTime = performance.now();
  var node = document.createElement("UL");               // Create a
 node
  document.body.appendChild(node);
  for ( var i = 0 ; i < N ; i++ ) {
    var li = document.createElement("LI")
    li.appendChild(document.createTextNode("4text" + i)); // Append an

    node.appendChild(li);
  }
  console.log('DOM (post)', performance.now() - startTime);
}

function test5() {
  var startTime = performance.now();
  var node = '
';
  for ( var i = 0 ; i < N ; i++ ) {
    node = node + '
5text' + i + '
'
  }
  node = node + '
';
  document.body.outerHTML = node;
  console.log('outerHTML', performance.now() - startTime);
}

function allTests() {
  test1();
  //test2();
  test3();
  //test4();
  // test5();
  console.log('\n');
}

allTests();
allTests();
allTests();
<commit_msg>Update bench.js.<commit_after>var N = 1000;

function bench() {
console.log('finished');

function test1() {
  var startTime = Date.now();
  var node = foam.u2.Element.create({nodeName: 'UL'});     // Create a
 node
  for ( var i = 0 ; i < N ; i++ )
    node.start('li').add("1text" + i).end();                // Append an

  node.write(document);
  console.log('U2 (pre)', Date.now() - startTime);
}

function test2() {
  var startTime = Date.now();
  var node = foam.u2.Element.create({nodeName: 'UL'});     // Create a
 node
  node.write(document);
  for ( var i = 0 ; i < N ; i++ )
    node.start('li').add("2text" + i).end();                // Append an

  console.log('U2 (post)', Date.now() - startTime);
}

function test3() {
  var startTime = Date.now();
  var node = document.createElement("UL");               // Create a
 node
  for ( var i = 0 ; i < N ; i++ ) {
    var li = document.createElement("LI")
    li.appendChild(document.createTextNode("3text" + i)); // Append an

    node.appendChild(li);
  }
  document.body.appendChild(node);
  console.log('DOM (pre)', Date.now() - startTime);
}

function test4() {
  var startTime = Date.now();
  var node = document.createElement("UL");               // Create a
 node
  document.body.appendChild(node);
  for ( var i = 0 ; i < N ; i++ ) {
    var li = document.createElement("LI")
    li.appendChild(document.createTextNode("4text" + i)); // Append an

    node.appendChild(li);
  }
  console.log('DOM (post)', Date.now() - startTime);
}

function test5() {
  var startTime = Date.now();
  var node = '
';
  for ( var i = 0 ; i < N ; i++ ) {
    node = node + '
5text' + i + '
'
  }
  node = node + '
';
  document.body.outerHTML = node;
  console.log('outerHTML', Date.now() - startTime);
}

function test6() {
  var v = foam.u2.DefaultValidator.create();

  var startTime = Date.now();
  for ( var i = 0 ; i < N ; i++ ) {
    v.sanitizeText('6text' + i);
  }
  console.log('sanitize', Date.now() - startTime);
}

function allTests() {
  test1();
  //test2();
  test3();
  //test4();
  // test5();
  // test6();
  console.log('\n');
}

allTests();
allTests();
allTests();

console.profile('p1');
test1();
console.profileEnd();
}
<|endoftext|>


<commit_before>/*
	InputMagic v0.1.0
	https://github.com/ideasmashup/input-magic

	By William ANGER, ISC License, July 2014
*/

(function(){

	var $input = null;
	var firstpress = -1, lastpress = +new Date(), typespeed = -1, count = 0;
	var enable_autocomplete = false;

	// @Bobince: http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript/3561711#3561711
	function RegExp_escape(str) {
		return (str !== undefined)? str.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') : '';
	}

	if (!String.prototype.trim) {
		String.prototype.trim = function (pattern) {
			if (pattern == undefined) pattern = /^\s+|\s+$/g;
			return this.replace(pattern, ''); //"\t\n\r\0\x0B."
		};
	}

	if (!String.prototype.remove) {
		String.prototype.remove = function(str) {
			var re = (typeof str == 'string')? str : new RegExp(str, 'g');
			return this.replace(re, '');
		};
	}

	if (!String.prototype.localeCompare) {
		String.prototype.localeCompare = function(str, locale, options) {
			return ((this == str) ? 0 : ((this > str) ? 1 : -1));
		};
	}

	//special post-keypress behaviors
	function after_keypress() {

		// do nothing until user types more stuff
		//if (lastpress == firstpress) return;

		var pausedelay = +new Date() - lastpress;
		//console.log('paused for '+ pausedelay);

		if (pausedelay < 500) {
			// last keypress very close : do nothing
			// because user is probably still typing stuff...

			// interrupt tasks
			//$('.reveal.quartz .loader').hide();
		}
		else if (pausedelay > 1000) {
			// last keypress was long ago
			// check email harder with a server validation request

			// indicate task in progress
			//$('.reveal.quartz .loader').show();

			// animate avatar
			state = 'hey';
			loops = 0;
		}
		else {
			// last keypress moderately close
			// user not typing but may type again soon...
		}

		// do some basic autocompletion and checking
		email_range_autocomplete($input);
	}
	/*
		Validation state
	 */

	function state_change(state) {
		console.log('state_change = '+ state);
	}

	function input_state(name, state, completeness) {
		var $input = $('input{name='+name+']');

	}

	/*
		Form handling
	*/

	function ajax_forms(callback){
		$('form').on('submit', function(e) {
			e.preventDefault();

			var $form = $(this);

			$.ajax({
				url: $form.attr('action'),
				type: $form.attr('method'),
				data: $form.serialize(),
				success: function(resp) {
					callback($form, resp);
				}
			});
		});
	}

	function caret($input, value) {
		var input = $input.get();
		var pos = 0;

		$input.focus();

		if (value === undefined) {
			// fetch caret position

			if (input.createTextRange) {
				var range = document.selection.createRange();
				range.moveStart('character', -$input.val().length);
				pos = range.text.length;
			}
			else if (input.selectionStart !== undefined) {
				pos = input.selectionStart;
			}

			return pos;
		}
		else {
			// set caret position
			range_create($input, value, value);
		}
	}

	function range_create($input, start, end) {
		var input = $input[0];

		if (input.createTextRange) {
			var range = input.createTextRange();
			range.collapse(true);
			range.moveStart('character', start);
			range.moveEnd('character', end);
			range.select();
		}
		else if (input.setSelectionRange) {
			input.setSelectionRange(start, end);
		}
	}

	function range_append_hint($input, hint) {
		var input = $input.get();
		var text = $input.val().trim(RegExp_escape(hint));

		$input.val(text + hint);
		$input.data('hint', hint);
		range_create($input, text.length, text.length + hint.length);
	}

	function range_autocomplete($input) {
		// complete only when caret is at value end

	}

	/*
		Email processing
	*/

	function cmp_str(a, b) {
		return a.localCompare(b);
	}

	var ADJS = ['amazing','blithesome','charismatic','decisive','excellent',
	            'fantastical','great','heroic','incredible','jolly','kickstarter',
	            'light','magical','nice','outstanding','perfect','quality',
	            'remarkable','smart','thrilling','ultimate','vibrant','wondrous','xylophone','yes_we_can','zippy'];
	var DOMS = ['hotmail','live','yahoo','gmail','orange','aol','free','numericable'];
	var EXTS = ['com','fr','eu','org','us','net','io'];

	function complete(str, suggest_arr, max_results, offset, suffix) {
		var results = [];
		if (max_results === undefined) max_results = 1;
		if (suffix === undefined) suffix = '';

		for (var i=0; i<suggest_arr.length; i++) {
			if (results < max_results) {
				if (suggest_arr[i].substring(0, str.length) == str) {
					results.push(offset? suggest_arr[i].substr(str.length) + suffix : suggest_arr[i] + suffix);
				}
			}
			else break;
		}

		// return array or single value
		if (results.length == 0) {
			// no match found
			return (max_results == 1) ? suffix : [suffix];
		}
		else {
			// found a match (or more)
			return (max_results == 1) ? results[0] : results;
		}
	}

	function email_range_autocomplete($input) {
		var value = $input.val();
		var text = value.trim(RegExp_escape($input.data('hint')));
		var len = text.length;
		var hint = '';
		var completeness = Math.min(text.length / 14, 1);

		console.log('input='+ text +' ( '+ (completeness*100) +'% )');

		if (text.length < 2) {
			// no completion yet...
			state_change('incomplete');
		}

		// complete with "best guesses" (or "motivationnal" words)
		if (text.indexOf('@') == -1) {
			if (text.length > 0) {
				hint = complete(text, ADJS, 1, true, '@');
			}
		}
		else {
			if (text.charAt(text.length - 1) != '@') {
				var parts = text.split('@', 2);
				var bef = parts[0];
				var aft = parts[1];

				if (aft.indexOf('.') == -1) {
					hint = complete(aft, DOMS, 1, true, '.');
					state_change('incomplete');
				}
				else {
					var tmp = aft.split('.', 2);
					var host = tmp[0];
					var ext = tmp[1];

					if (aft.charAt(aft.length - 1) !== '.') {
						if (ext.length > 2) {
							state_change('complete');
						}
						else {
							hint = complete(ext, EXTS, 1, true);
							state_change('partial');
						}
					}
					else {
						// no hint
					}

					// no hint
				}
			}
			else {
				// no hint
			}
		}

		if (hint.length > 0) {
			// only suggest non-empty strings
			range_append_hint($input, hint);
		}
		else {
			$input.data('hint', '');
		}
	}

	function is_email(email) {
		// Test for the minimum length the email can be
		if (email.length < 3) {
			console.log('email_too_short');
			return false;
		}

		// Test for an @ character after the first position
		if (email.indexOf('@', 1) === -1) {
			console.log('email_no_at');
			return false;
		}

		// Split out the local and domain parts
		var parts = email.split('@', 2);
		var local = parts[0];
		var domain = parts[1];

		// LOCAL PART
		// Test for invalid characters
		if (!new RegExp('^[a-zA-Z0-9!#$%&\'*+\/=?^_`{|}~\.-]+$').test(local)) {
			console.log('local_invalid_chars');
			return false;
		}

		// DOMAIN PART
		// Test for sequences of periods
		if (new RegExp('\\.{2,}').exec(domain) !== null) {
			console.log('domain_period_sequence '+ new RegExp('\\.{2,}').exec(domain).toSource());
			return false;
		}

		// Test for leading and trailing periods and whitespace
		if (domain.trim() !== domain) {
			console.log('domain_period_limits');
			return false;
		}

		// Split the domain into subs
		var subs = domain.split('.');

		// Assume the domain will have at least two subs
		if (subs.length < 2) {
			console.log('domain_no_periods');
			return false;
		}

		// Loop through each sub
		for (sub in subs) {
			// Test for leading and trailing hyphens and whitespace
			if (sub.trim() !== sub) {
				console.log('sub_hyphen_limits');
				return false;
			}

			// Test for invalid characters
			if (!new RegExp('^[a-z0-9-]+$', 'i').test(sub)) {
				console.log('sub_invalid_chars');
				return false;
			}
		}

		// Congratulations your email made it!
		console.log('valid_email');
		return true;
	}

	function email_deep_check(str) {
		// validate and verify on server
	}

	function input_magic(name) {
		$input = $('input[name='+name+']');

		$input.keypress(function(e){
			var keycode = (e.keyCode ? e.keyCode : e.which);
			var now = +new Date();

			// typing text
			if (count++ == 0) {
				firstpress = now;
				typespeed = 3000;
			}
			else {
				lastpress = now;
				typespeed = (typespeed + (now - lastpress)) / 2;
			}

			// codes ref : http://www.quirksmode.org/js/keys.html

			if (keycode == 13) {
				// enter pressed : submit value (?)
				log.console('enter pressed... submit?');

				$input.data('hint', '');
				enable_autocomplete = false;
			}
			else if (keycode < 32) {
				// non-printable character : clear hint
				$input.data('hint', '');
				enable_autocomplete = false;
			}
			else if (keycode >= 33 && keycode <= 40) {
				// arrows keys (home, end, pgup, pgdwn, etc) : clear hint
				$input.data('hint', '');
				enable_autocomplete = true;
			}
			else {
				// printable characters : typing text so stop checking
				enable_autocomplete = true;
				$('.reveal.quartz .loader').hide();
			}

			if (enable_autocomplete) {
				// do autocomplete
				setTimeout(function(){email_range_autocomplete($input)}, 10);
				enable_autocomplete = false;
			}
		});
	}

	window.input_magic = input_magic;
})();
<commit_msg>replaced keypress by keydown to allow capture of non-printable keys<commit_after>/*
	InputMagic v0.1.0
	https://github.com/ideasmashup/input-magic

	By William ANGER, ISC License, July 2014
*/

(function(){

	var $input = null;
	var firstpress = -1, lastpress = +new Date(), typespeed = -1, count = 0;
	var enable_autocomplete = false;

	// @Bobince: http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript/3561711#3561711
	function RegExp_escape(str) {
		return (str !== undefined)? str.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') : '';
	}

	if (!String.prototype.trim) {
		String.prototype.trim = function (pattern) {
			if (pattern == undefined) pattern = /^\s+|\s+$/g;
			return this.replace(pattern, ''); //"\t\n\r\0\x0B."
		};
	}

	if (!String.prototype.remove) {
		String.prototype.remove = function(str) {
			var re = (typeof str == 'string')? str : new RegExp(str, 'g');
			return this.replace(re, '');
		};
	}

	if (!String.prototype.localeCompare) {
		String.prototype.localeCompare = function(str, locale, options) {
			return ((this == str) ? 0 : ((this > str) ? 1 : -1));
		};
	}

	//special post-keypress behaviors
	function after_keypress() {

		// do nothing until user types more stuff
		//if (lastpress == firstpress) return;

		var pausedelay = +new Date() - lastpress;
		//console.log('paused for '+ pausedelay);

		if (pausedelay < 500) {
			// last keypress very close : do nothing
			// because user is probably still typing stuff...

			// interrupt tasks
			//$('.reveal.quartz .loader').hide();
		}
		else if (pausedelay > 1000) {
			// last keypress was long ago
			// check email harder with a server validation request

			// indicate task in progress
			//$('.reveal.quartz .loader').show();

			// animate avatar
			state = 'hey';
			loops = 0;
		}
		else {
			// last keypress moderately close
			// user not typing but may type again soon...
		}

		// do some basic autocompletion and checking
		email_range_autocomplete($input);
	}
	/*
		Validation state
	 */

	function state_change(state) {
		console.log('state_change = '+ state);
	}

	function input_state(name, state, completeness) {
		var $input = $('input{name='+name+']');

	}

	/*
		Form handling
	*/

	function ajax_forms(callback){
		$('form').on('submit', function(e) {
			e.preventDefault();

			var $form = $(this);

			$.ajax({
				url: $form.attr('action'),
				type: $form.attr('method'),
				data: $form.serialize(),
				success: function(resp) {
					callback($form, resp);
				}
			});
		});
	}

	function caret($input, value) {
		var input = $input.get();
		var pos = 0;

		$input.focus();

		if (value === undefined) {
			// fetch caret position

			if (input.createTextRange) {
				var range = document.selection.createRange();
				range.moveStart('character', -$input.val().length);
				pos = range.text.length;
			}
			else if (input.selectionStart !== undefined) {
				pos = input.selectionStart;
			}

			return pos;
		}
		else {
			// set caret position
			range_create($input, value, value);
		}
	}

	function range_create($input, start, end) {
		var input = $input[0];

		if (input.createTextRange) {
			var range = input.createTextRange();
			range.collapse(true);
			range.moveStart('character', start);
			range.moveEnd('character', end);
			range.select();
		}
		else if (input.setSelectionRange) {
			input.setSelectionRange(start, end);
		}
	}

	function range_append_hint($input, hint) {
		var input = $input.get();
		var text = $input.val().trim(RegExp_escape(hint));

		$input.val(text + hint);
		$input.data('hint', hint);
		range_create($input, text.length, text.length + hint.length);
	}

	function range_autocomplete($input) {
		// complete only when caret is at value end

	}

	/*
		Email processing
	*/

	function cmp_str(a, b) {
		return a.localCompare(b);
	}

	var ADJS = ['amazing','blithesome','charismatic','decisive','excellent',
	            'fantastical','great','heroic','incredible','jolly','kickstarter',
	            'light','magical','nice','outstanding','perfect','quality',
	            'remarkable','smart','thrilling','ultimate','vibrant','wondrous','xylophone','yes_we_can','zippy'];
	var DOMS = ['hotmail','live','yahoo','gmail','orange','aol','free','numericable'];
	var EXTS = ['com','fr','eu','org','us','net','io'];

	function complete(str, suggest_arr, max_results, offset, suffix) {
		var results = [];
		if (max_results === undefined) max_results = 1;
		if (suffix === undefined) suffix = '';

		for (var i=0; i<suggest_arr.length; i++) {
			if (results < max_results) {
				if (suggest_arr[i].substring(0, str.length) == str) {
					results.push(offset? suggest_arr[i].substr(str.length) + suffix : suggest_arr[i] + suffix);
				}
			}
			else break;
		}

		// return array or single value
		if (results.length == 0) {
			// no match found
			return (max_results == 1) ? suffix : [suffix];
		}
		else {
			// found a match (or more)
			return (max_results == 1) ? results[0] : results;
		}
	}

	function email_range_autocomplete($input) {
		var value = $input.val();
		var text = value.trim(RegExp_escape($input.data('hint')));
		var len = text.length;
		var hint = '';
		var completeness = Math.min(text.length / 14, 1);

		console.log('input='+ text +' ( '+ (completeness*100) +'% )');

		if (text.length < 2) {
			// no completion yet...
			state_change('incomplete');
		}

		// complete with "best guesses" (or "motivationnal" words)
		if (text.indexOf('@') == -1) {
			if (text.length > 0) {
				hint = complete(text, ADJS, 1, true, '@');
			}
		}
		else {
			if (text.charAt(text.length - 1) != '@') {
				var parts = text.split('@', 2);
				var bef = parts[0];
				var aft = parts[1];

				if (aft.indexOf('.') == -1) {
					hint = complete(aft, DOMS, 1, true, '.');
					state_change('incomplete');
				}
				else {
					var tmp = aft.split('.', 2);
					var host = tmp[0];
					var ext = tmp[1];

					if (aft.charAt(aft.length - 1) !== '.') {
						if (ext.length > 2) {
							state_change('complete');
						}
						else {
							hint = complete(ext, EXTS, 1, true);
							state_change('partial');
						}
					}
					else {
						// no hint
					}

					// no hint
				}
			}
			else {
				// no hint
			}
		}

		if (hint.length > 0) {
			// only suggest non-empty strings
			range_append_hint($input, hint);
		}
		else {
			$input.data('hint', '');
		}
	}

	function is_email(email) {
		// Test for the minimum length the email can be
		if (email.length < 3) {
			console.log('email_too_short');
			return false;
		}

		// Test for an @ character after the first position
		if (email.indexOf('@', 1) === -1) {
			console.log('email_no_at');
			return false;
		}

		// Split out the local and domain parts
		var parts = email.split('@', 2);
		var local = parts[0];
		var domain = parts[1];

		// LOCAL PART
		// Test for invalid characters
		if (!new RegExp('^[a-zA-Z0-9!#$%&\'*+\/=?^_`{|}~\.-]+$').test(local)) {
			console.log('local_invalid_chars');
			return false;
		}

		// DOMAIN PART
		// Test for sequences of periods
		if (new RegExp('\\.{2,}').exec(domain) !== null) {
			console.log('domain_period_sequence '+ new RegExp('\\.{2,}').exec(domain).toSource());
			return false;
		}

		// Test for leading and trailing periods and whitespace
		if (domain.trim() !== domain) {
			console.log('domain_period_limits');
			return false;
		}

		// Split the domain into subs
		var subs = domain.split('.');

		// Assume the domain will have at least two subs
		if (subs.length < 2) {
			console.log('domain_no_periods');
			return false;
		}

		// Loop through each sub
		for (sub in subs) {
			// Test for leading and trailing hyphens and whitespace
			if (sub.trim() !== sub) {
				console.log('sub_hyphen_limits');
				return false;
			}

			// Test for invalid characters
			if (!new RegExp('^[a-z0-9-]+$', 'i').test(sub)) {
				console.log('sub_invalid_chars');
				return false;
			}
		}

		// Congratulations your email made it!
		console.log('valid_email');
		return true;
	}

	function email_deep_check(str) {
		// validate and verify on server
	}

	function input_magic(name) {
		$input = $('input[name='+name+']');

		$input.keydown(function(e){
			var keycode = (e.keyCode ? e.keyCode : e.which);
			var now = +new Date();

			// typing text
			if (count++ == 0) {
				firstpress = now;
				typespeed = 3000;
			}
			else {
				lastpress = now;
				typespeed = (typespeed + (now - lastpress)) / 2;
			}

			// codes ref : http://www.quirksmode.org/js/keys.html

			if (keycode == 13) {
				// enter pressed : submit value (?)
				log.console('enter pressed... submit?');

				$input.data('hint', '');
				enable_autocomplete = false;
			}
			else if (keycode < 32) {
				// non-printable character : clear hint
				$input.data('hint', '');
				enable_autocomplete = false;
			}
			else if (keycode >= 33 && keycode <= 40) {
				// arrows keys (home, end, pgup, pgdwn, etc) : clear hint
				$input.data('hint', '');
				enable_autocomplete = true;
			}
			else {
				// printable characters : typing text so stop checking
				enable_autocomplete = true;
				$('.reveal.quartz .loader').hide();
			}

			if (enable_autocomplete) {
				// do autocomplete
				setTimeout(function(){email_range_autocomplete($input)}, 10);
				enable_autocomplete = false;
			}
		});
	}

	window.input_magic = input_magic;
})();
<|endoftext|>


<commit_before>/**
* Small Group Controller
*/

'use strict';

import { handleError, validationError, generateCode, uniqueDayCode } from '../lib/helpers'

var SmallGroup = require('./smallgroup.model');
var ClassYear = require('../classyear/classyear.model');
var Attendance = require('../attendance/attendance.model');
var User = require('../user/user.model');
var Project = require('../project/project.model');
var util = require('../../components/utilities')

// Get all smallgroups
// Restricted to authenticated users
// router.get('/', auth.isAuthenticated(), controller.index);
// TODO: only return smallgroups for this year
/**
* @api {GET} /api/smallgroup Index
* @APIname index
* @APIgroup SmallGroup Controller
* @apidescription Get list of smallgroups
* @apiSuccess {Collection} index List of smallgroups
* @apiError (Error) 500 Unable to get list
*/
exports.index = function(req, res) {
    SmallGroup.find({}, function(err, smallgroups){
        if (err) { return handleError(res, err); }
        return res.status(200).json(smallgroups);
    });
};

// Create a new smallgroup for the current class year
// Restricted to mentors
// router.post('/', auth.hasRole('mentor'), controller.create);
/**
* @api {POST} /api/project Create
* @APIname create
* @APIgroup SmallGroup Controller
* @apidescription Create a new smallgroup
* @apiSuccess {HTTP} 200 Successfully created the smallgroup
* @apiError (Error) 500 Unable to create smallgroup
*/
// TODO - we may want to make this an admin-only controller action
exports.create = (req, res) => {

    const user = req.user
    const memberId = req.user._id
    let { name } = req.body

    return ClassYear.getCurrent((err, classYear) => {

        const smallgroup = new SmallGroup({
            name: name, // TODO - pass name in req.body
            classYear: classYear._id,
            enabled: true,
            students: [user._id],
            dayCodes: []
        })

        return smallgroup.save()
        .then((smallgroup) => {
          res.status(200).json({ smallgroup }).end()
        })
        .catch((err) => {
          return handleError(res, err)
        })

    })
}

/**
* @api {POST} /api/project Modify
* @APIname modify
* @APIgroup SmallGroup Controller
* @apidescription Modify the smallgroup
* @apiPermission Mentors
* @apiSuccess {HTTP} 200 Successfully modified the smallgroup
* @apiError (Error) 500 Unable to modify smallgroup
*/

// Modify the smallgroup
// Restricted to mentors
// router.put('/:id', auth.hasRole('mentor'), controller.modify);
exports.modify = function(req, res){
    var id = req.params.id;
    SmallGroup.update({'_id': id}, req.body.smallgroup, function(err){
        if (err) {return handleError(res, err);}
        res.sendStatus(200);
    });
};

/**
* @api {DELETE} /api/project Delete
* @APIname delete
* @APIgroup SmallGroup Controller
* @apidescription Delete the smallgroup
* @apiPermission Mentors
* @apiSuccess {HTTP} 200 Successfully deleted the smallgroup
* @apiError (Error) 500 Unable to delete smallgroup
*/
// Delete the smallgroup
// Restricted to mentors
// router.put('/:id', auth.hasRole('mentor'), controller.modify);
exports.delete = function(req, res){
    var id = req.params.id;
    SmallGroup.findById(id, function(err, smallgroup){
        if (err) return handleError(res, err);
        smallgroup.remove();
        res.sendStatus(200);
    });
};

/**
* @api {GET} /api/project getSmallGroup
* @APIname getSmallGroup
* @APIgroup SmallGroup Controller
* @apidescription Get the smallgroup by its id
* @apiPermission Authenticated users
* @apiSuccess {json} Model Returns single smallgroup, returns daycode if mentor
* @apiError (Error) 500 Unable to find smallgroup
*/
// Get the smallgroup by id
// Restricted to authenticated users
// Only return daycode is the user is a mentor
// router.get('/:id', auth.isAuthenticated(), controller.getSmallGroup);
exports.getSmallGroup = function(req, res){
    var id = req.params.id;
    var query = SmallGroup.findById(id)
    if (req.user.isMentor){
        query.select('+dayCodes.code')
    }
    return query.exec(function(err, smallgroup){
        if (err) return handleError(res, err);
        if (!smallgroup) return handleError(res, err);
        var responseObject = smallgroup.toObject();
        if (req.user && req.user.isMentor){
            // Mentors should get a day code
            // Generate a day code if one does not already exist
            if (smallgroup.dayCode){
                responseObject.dayCode = smallgroup.dayCode;
            }
            if (smallgroup.bonusDayCode){
                responseObject.bonusDayCode = smallgroup.bonusDayCode;
            }
        }
        res.status(200).json(responseObject);
    });
};

// Generate a daycode or return the current day code for the smallgroups
// Restricted to mentors
// router.post('/daycode', auth.hasRole('mentor'), controller.daycode);

/**
* @api {GET} /api/project Daycode
* @APIname daycode
* @APIgroup SmallGroup Controller
* @apidescription Get the daycode
* @apiPermission Mentors
* @apiSuccess {json} Model Returns the daycode
* @apiError (Error) 500 Unable to find the daycode
*/
exports.daycode = function(req, res){
  var userId = req.user.id;

  return ClassYear.getCurrent(function(err, classYear){
    var classYearId = classYear._id;
    return SmallGroup.findOne({"students":userId, "classYear":classYearId})
    .select('+dayCodes.code')
    .exec(function(err, smallgroup){

      if (err) { return handleError(res, err); }

      var today = new Date();
      today.setHours(0,0,0,0);
      for (var i = 0;i < smallgroup.dayCodes.length;i++){
        if (today.getTime() === smallgroup.dayCodes[i].date.getTime()){
          return res.status(200).json(smallgroup.dayCodes[i].code)
        }
      }

      //unique code generator, imported from api/lib/helpers.js
      uniqueDayCode(6, function(err, dayCode) {
        if (err) { return handleError(res, err); }
        var code = dayCode;

        smallgroup.dayCodes.push({
          date: today,
          code: code,
          bonusDay: req.body.bonusDay ? true : false
        });

        return smallgroup.save( function (err, classYear) {
          if (err) { return handleError(res, err); }
          return res.status(200).json({ code: code })
        });
      });

    });
  });
};

// Delete a day code from a smallgroup and the corresponding daycode submission from attendance
// Restricted to mentors
// router.delete('/:id/day/:dayCode', auth.hasRole('mentor'), controller.deleteDay);

/**
* @api {DELETE} /api/project deleteDay
* @APIname deleteDay
* @APIgroup SmallGroup Controller
* @apidescription Deletes a day/daycode
* @apiPermission Mentors
* @apiSuccess {HTTP} 200 Successfully deleted daycode
* @apiError (Error) 500 Unable to find daycode
*/
exports.deleteDay = function(req, res){
    var dayCode = req.params.dayCode;
    var smallgroupId = req.params.id;
    SmallGroup.findOneAndUpdate({"_id":smallgroupId}, {
      $pull: { dayCodes: {code : dayCode }}
    })
      .select('+dayCodes.code')
      .exec(function(err, smallgroup){
        if (err) return handleError(res, err);

        return Attendance.remove({code : dayCode}, function (err){
          if (err) return handleError(res, err);
          return res.status(200).json(smallgroup);
        });
  });
};

// Returns the user profile for a userId
// Only returns private information if the user is a mentor
function getFullUserProfile(userId, mentor, callback){
    User.findById(userId)
    .populate('projects')
    .exec(function(err, user){
        if (err) return callback("Could not find user", null);
        if (!user) return callback("Could not find user", null);
            // Add the user's attendance
        var profile = {};
        if (mentor){
            profile = user.privateProfile;

            ClassYear.getCurrent(function(err, classYear){
                var classYearId = classYear._id;
                var date = util.convertToMidnight(new Date());

                Attendance.find({classYear:classYearId, date:date, smallgroup:true, user:userId})
                .exec(function(err, attendance){
                    profile.attendance = attendance;
                    callback(null, profile);

                })

            });
        }
        else{
            profile = user.profile;
            callback(null, profile);

        }
    });
}

// Get the members of a smallgroup
// Only returns private information if the user is a mentor
// Restricted to mentors
// router.get('/:id/members', auth.isAuthenticated(), controller.getSmallGroupMembers);
/**
* @api {GET} /api/project getSmallGroupMembers
* @APIname getSmallGroupMembers
* @APIgroup SmallGroup Controller
* @apidescription Get the daycode
* @apiPermission Mentors
* @apiSuccess {Collection} root Returns list of smallgroup members
* @apiError (Error) 500 Unable to find the smallgroup
*/
exports.getSmallGroupMembers = function(req, res){
    var id = req.params.id;
    SmallGroup.findById(id, function(err, smallgroup){
        if (err) return handleError(res, err);
        if (!smallgroup) return handleError(res, err);

        var members = [];
        var loadedMembers = 0;

        // Load each group member's full profile

        smallgroup.students.forEach(function(studentId){

            getFullUserProfile(studentId, req.user && req.user.isMentor, function(err, member){
                loadedMembers ++;
                if (member){
                    members.push(member);
                }

                // Check if we're done loading members
                if (loadedMembers === smallgroup.students.length){
                    return res.status(200).json(members);
                }
            })
        });
    });
};

// Add a member to a smallgroup
// Restricted to mentors
// router.put('/:id/member', auth.hasRole('mentor'), controller.addMember);
/**
* @api {POST} /api/project addMember
* @APIname addMember
* @APIgroup SmallGroup Controller
* @apidescription Add a member to the smallgroup
* @apiPermission Mentors
* @apiSuccess {HTTP} 200 Successfully added the member
* @apiError (Error) 500 Unable to add the member
*/
exports.addMember = function(req, res){
    var memberId = req.body.memberId;
    var smallGroupId = req.params.id;
    return ClassYear.getCurrent(function(err, classYear){
        var classYearId = classYear._id;
        return SmallGroup.findOneAndUpdate({"students": memberId, "classYear":classYearId}, {
            $pull: { students : memberId }
        }, function(err, smallgroup){
            if (err) return handleError(res, err);
            return SmallGroup.findOneAndUpdate({_id: smallGroupId}, {
                $addToSet: { students : memberId }
            }, function(err, smallgroup){
                if (err) return handleError(res, err);
                return res.sendStatus(200);
            });
        });
    });
};

// Delete a member from a smallgroup
// Restricted to mentors
// router.delete('/:id/member/:memberId', auth.isAuthenticated(), controller.deleteMember);
/**
* @api {DELETE} /api/project deleteMember
* @APIname deleteMember
* @APIgroup SmallGroup Controller
* @apidescription Delete a member from the smallgroup
* @apiPermission Mentors
* @apiSuccess {HTTP} 200 Successfully removed the member
* @apiError (Error) 500 Unable to remove the member
* @apiError (Error) 403 Member not deleted
*/
exports.deleteMember = function(req, res){
    var memberId = req.params.memberId;
    var smallGroupId = req.params.id;
    if(req.user.id === memberId || req.user.role !== 'user' ){
    return SmallGroup.findOneAndUpdate({_id: smallGroupId}, {
        $pull: { students : memberId }
    }, function(err, smallgroup){
        if (err) return handleError(res, err);
        return res.sendStatus(200);
    }); }
    return res.sendStatus(403);
};

// Change ths name of a smallgroup
// Restricted to mentors
// router.put('/:id/name', auth.isAuthenticated(), controller.changeName);
/**
* @api {GET} /api/project changeName
* @APIname changeName
* @APIgroup SmallGroup Controller
* @apidescription Change the name of a smallgroup
* @apiPermission Mentors
* @apiSuccess {Text} Returns the new name of the smallgroup
* @apiError (Error) 500 Unable to find the smallgroup
*/
exports.changeName = function(req,res){
  var id = req.params.id;
  var newName = String(req.body.smallGroupName);
  return SmallGroup.findById(id, function(err,smallgroup){
    if (err) return handleError(res, err);
    smallgroup.name = newName;
    return smallgroup.save(function(err){
      if (err) return validationError(res,err);
      return res.status(200).json({name:smallgroup.name});
    })
  });
};
<commit_msg>Code Quality Improvement in smallgroup.controller.js<commit_after>/**
* Small Group Controller
*/

'use strict';

import { handleError, validationError, generateCode, uniqueDayCode } from '../lib/helpers'

const SmallGroup = require('./smallgroup.model');
const ClassYear = require('../classyear/classyear.model');
const Attendance = require('../attendance/attendance.model');
const User = require('../user/user.model');
const Project = require('../project/project.model');
const util = require('../../components/utilities')

// Get all smallgroups
// Restricted to authenticated users
// router.get('/', auth.isAuthenticated(), controller.index);
// TODO: only return smallgroups for this year
/**
* @api {GET} /api/smallgroup Index
* @APIname index
* @APIgroup SmallGroup Controller
* @apidescription Get list of smallgroups
* @apiSuccess {Collection} index List of smallgroups
* @apiError (Error) 500 Unable to get list
*/
exports.index = (req, res) => {
    SmallGroup.find({}, (err, smallgroups) => {
        if (err) { return handleError(res, err); }
        return res.status(200).json(smallgroups);
    });
};

// Create a new smallgroup for the current class year
// Restricted to mentors
// router.post('/', auth.hasRole('mentor'), controller.create);
/**
* @api {POST} /api/project Create
* @APIname create
* @APIgroup SmallGroup Controller
* @apidescription Create a new smallgroup
* @apiSuccess {HTTP} 200 Successfully created the smallgroup
* @apiError (Error) 500 Unable to create smallgroup
*/
// TODO - we may want to make this an admin-only controller action
exports.create = (req, res) => {

    const user = req.user
    const memberId = req.user._id
    let { name } = req.body

    return ClassYear.getCurrent((err, classYear) => {

        const smallgroup = new SmallGroup({
            name: name, // TODO - pass name in req.body
            classYear: classYear._id,
            enabled: true,
            students: [user._id],
            dayCodes: []
        })

        return smallgroup.save()
        .then((smallgroup) => {
          res.status(200).json({ smallgroup }).end()
        })
        .catch((err) => {
          return handleError(res, err)
        })

    })
}

/**
* @api {POST} /api/project Modify
* @APIname modify
* @APIgroup SmallGroup Controller
* @apidescription Modify the smallgroup
* @apiPermission Mentors
* @apiSuccess {HTTP} 200 Successfully modified the smallgroup
* @apiError (Error) 500 Unable to modify smallgroup
*/

// Modify the smallgroup
// Restricted to mentors
// router.put('/:id', auth.hasRole('mentor'), controller.modify);
exports.modify = (req, res) => {
    const id = req.params.id;
    SmallGroup.update({'_id': id}, req.body.smallgroup, (err) => {
        if (err) {return handleError(res, err);}
        res.sendStatus(200);
    });
};

/**
* @api {DELETE} /api/project Delete
* @APIname delete
* @APIgroup SmallGroup Controller
* @apidescription Delete the smallgroup
* @apiPermission Mentors
* @apiSuccess {HTTP} 200 Successfully deleted the smallgroup
* @apiError (Error) 500 Unable to delete smallgroup
*/
// Delete the smallgroup
// Restricted to mentors
// router.put('/:id', auth.hasRole('mentor'), controller.modify);
exports.delete = (req, res) => {
    var id = req.params.id;
    SmallGroup.findById(id, (err, smallgroup) => {
        if (err) return handleError(res, err);
        smallgroup.remove();
        res.sendStatus(200);
    });
};

/**
* @api {GET} /api/project getSmallGroup
* @APIname getSmallGroup
* @APIgroup SmallGroup Controller
* @apidescription Get the smallgroup by its id
* @apiPermission Authenticated users
* @apiSuccess {json} Model Returns single smallgroup, returns daycode if mentor
* @apiError (Error) 500 Unable to find smallgroup
*/
// Get the smallgroup by id
// Restricted to authenticated users
// Only return daycode is the user is a mentor
// router.get('/:id', auth.isAuthenticated(), controller.getSmallGroup);
exports.getSmallGroup = (req, res) => {
    const id = req.params.id;
    const query = SmallGroup.findById(id)
    if (req.user.isMentor){
        query.select('+dayCodes.code')
    }
    return query.exec((err, smallgroup) => {
        if (err) return handleError(res, err);
        if (!smallgroup) return handleError(res, err);
        const responseObject = smallgroup.toObject();
        if (req.user && req.user.isMentor){
            // Mentors should get a day code
            // Generate a day code if one does not already exist
            if (smallgroup.dayCode){
                responseObject.dayCode = smallgroup.dayCode;
            }
            if (smallgroup.bonusDayCode){
                responseObject.bonusDayCode = smallgroup.bonusDayCode;
            }
        }
        res.status(200).json(responseObject);
    });
};

// Generate a daycode or return the current day code for the smallgroups
// Restricted to mentors
// router.post('/daycode', auth.hasRole('mentor'), controller.daycode);

/**
* @api {GET} /api/project Daycode
* @APIname daycode
* @APIgroup SmallGroup Controller
* @apidescription Get the daycode
* @apiPermission Mentors
* @apiSuccess {json} Model Returns the daycode
* @apiError (Error) 500 Unable to find the daycode
*/
exports.daycode = (req, res) => {
  const userId = req.user.id;

  return ClassYear.getCurrent((err, classYear) => {
    const classYearId = classYear._id;
      return SmallGroup.findOne({'students':userId, 'classYear':classYearId})
    .select('+dayCodes.code')
	  .exec((err, smallgroup) => {

      if (err) { return handleError(res, err); }

      const today = new Date();
      today.setHours(0,0,0,0);
      for (let i = 0;i < smallgroup.dayCodes.length;i++){
        if (today.getTime() === smallgroup.dayCodes[i].date.getTime()){
          return res.status(200).json(smallgroup.dayCodes[i].code)
        }
      }

      //unique code generator, imported from api/lib/helpers.js
      uniqueDayCode(6, (err, dayCode) => {
        if (err) { return handleError(res, err); }
        const code = dayCode;

        smallgroup.dayCodes.push({
          date: today,
          code: code,
          bonusDay: req.body.bonusDay ? true : false
        });

          return smallgroup.save((err, classYear) => {
          if (err) { return handleError(res, err); }
          return res.status(200).json({ code: code })
        });
      });

    });
  });
};

// Delete a day code from a smallgroup and the corresponding daycode submission from attendance
// Restricted to mentors
// router.delete('/:id/day/:dayCode', auth.hasRole('mentor'), controller.deleteDay);

/**
* @api {DELETE} /api/project deleteDay
* @APIname deleteDay
* @APIgroup SmallGroup Controller
* @apidescription Deletes a day/daycode
* @apiPermission Mentors
* @apiSuccess {HTTP} 200 Successfully deleted daycode
* @apiError (Error) 500 Unable to find daycode
*/
exports.deleteDay = (req, res) => {
    const dayCode = req.params.dayCode;
    const smallgroupId = req.params.id;
    SmallGroup.findOneAndUpdate({"_id":smallgroupId}, {
      $pull: { dayCodes: {code : dayCode }}
    })
      .select('+dayCodes.code')
      .exec((err, smallgroup) => {
        if (err) return handleError(res, err);

        return Attendance.remove({code : dayCode}, (err) => {
          if (err) return handleError(res, err);
          return res.status(200).json(smallgroup);
        });
  });
};

// Returns the user profile for a userId
// Only returns private information if the user is a mentor
function getFullUserProfile(userId, mentor, callback){
    User.findById(userId)
    .populate('projects')
    .exec((err, user) => {
        if (err) return callback('Could not find user', null);
        if (!user) return callback('Could not find user', null);
            // Add the user's attendance
        let profile = {};
        if (mentor) {
            profile = user.privateProfile;

            ClassYear.getCurrent((err, classYear) => {
                const classYearId = classYear._id;
                const date = util.convertToMidnight(new Date());

                Attendance.find({classYear:classYearId, date:date, smallgroup:true, user:userId})
                .exec((err, attendance) => {
                    profile.attendance = attendance;
                    callback(null, profile);

                })

            });
        }
        else {
            profile = user.profile;
            callback(null, profile);

        }
    });
}

// Get the members of a smallgroup
// Only returns private information if the user is a mentor
// Restricted to mentors
// router.get('/:id/members', auth.isAuthenticated(), controller.getSmallGroupMembers);
/**
* @api {GET} /api/project getSmallGroupMembers
* @APIname getSmallGroupMembers
* @APIgroup SmallGroup Controller
* @apidescription Get the daycode
* @apiPermission Mentors
* @apiSuccess {Collection} root Returns list of smallgroup members
* @apiError (Error) 500 Unable to find the smallgroup
*/
exports.getSmallGroupMembers = (req, res) => {
    const id = req.params.id;
    SmallGroup.findById(id, (err, smallgroup) => {
        if (err) return handleError(res, err);
        if (!smallgroup) return handleError(res, err);

        const members = [];
        let loadedMembers = 0;

        // Load each group member's full profile

        smallgroup.students.forEach((studentId) => {

            getFullUserProfile(studentId, req.user && req.user.isMentor, (err, member) => {
                loadedMembers ++;
                if (member) {
                    members.push(member);
                }

                // Check if we're done loading members
                if (loadedMembers === smallgroup.students.length) {
                    return res.status(200).json(members);
                }
            })
        });
    });
};

// Add a member to a smallgroup
// Restricted to mentors
// router.put('/:id/member', auth.hasRole('mentor'), controller.addMember);
/**
* @api {POST} /api/project addMember
* @APIname addMember
* @APIgroup SmallGroup Controller
* @apidescription Add a member to the smallgroup
* @apiPermission Mentors
* @apiSuccess {HTTP} 200 Successfully added the member
* @apiError (Error) 500 Unable to add the member
*/
exports.addMember = (req, res) => {
    const memberId = req.body.memberId;
    const smallGroupId = req.params.id;
    return ClassYear.getCurrent((err, classYear) => {
        const classYearId = classYear._id;
        return SmallGroup.findOneAndUpdate({'students': memberId, 'classYear':classYearId}, {
            $pull: { students : memberId }
        }, (err, smallgroup) => {
            if (err) return handleError(res, err);
            return SmallGroup.findOneAndUpdate({_id: smallGroupId}, {
                $addToSet: { students : memberId }
            }, (err, smallgroup) => {
                if (err) return handleError(res, err);
                return res.sendStatus(200);
            });
        });
    });
};

// Delete a member from a smallgroup
// Restricted to mentors
// router.delete('/:id/member/:memberId', auth.isAuthenticated(), controller.deleteMember);
/**
* @api {DELETE} /api/project deleteMember
* @APIname deleteMember
* @APIgroup SmallGroup Controller
* @apidescription Delete a member from the smallgroup
* @apiPermission Mentors
* @apiSuccess {HTTP} 200 Successfully removed the member
* @apiError (Error) 500 Unable to remove the member
* @apiError (Error) 403 Member not deleted
*/
exports.deleteMember = (req, res) => {
    const memberId = req.params.memberId;
    const smallGroupId = req.params.id;
    if (req.user.id === memberId || req.user.role !== 'user' ){
    return SmallGroup.findOneAndUpdate({_id: smallGroupId}, {
        $pull: { students : memberId }
    }, (err, smallgroup) => {
        if (err) return handleError(res, err);
        return res.sendStatus(200);
    }); }
    return res.sendStatus(403);
};

// Change ths name of a smallgroup
// Restricted to mentors
// router.put('/:id/name', auth.isAuthenticated(), controller.changeName);
/**
* @api {GET} /api/project changeName
* @APIname changeName
* @APIgroup SmallGroup Controller
* @apidescription Change the name of a smallgroup
* @apiPermission Mentors
* @apiSuccess {Text} Returns the new name of the smallgroup
* @apiError (Error) 500 Unable to find the smallgroup
*/
exports.changeName = (req,res) => {
  const id = req.params.id;
  const newName = String(req.body.smallGroupName);
    return SmallGroup.findById(id, (err,smallgroup) => {
    if (err) return handleError(res, err);
    smallgroup.name = newName;
    return smallgroup.save((err) => {
      if (err) return validationError(res,err);
      return res.status(200).json({name:smallgroup.name});
    })
  });
};
<|endoftext|>


<commit_before>app.factory('AuthFactory', ['$http', '$firebaseAuth', 'BioFactory', '$location', function($http, $firebaseAuth, BioFactory, $location){
  console.log('AuthFactory running');

  var auth = $firebaseAuth();
  var currentUser = {};
  var loggedIn = false;
  var userStatus = {};

  function logIn(userType) {
    return auth.$signInWithPopup("google").then(function(firebaseUser) {
      currentUser = firebaseUser.user;
      console.log('USER TYPE:', userType);
      if(currentUser) {
        getUser(currentUser, userType);
      }
    });
  }

  function getUser(currentUser, userType){
    currentUser.getToken().then(function(idToken){
      // console.log('ID TOKEN:', idToken);
      $http({
        method: 'GET',
        url: '/users.route',
        headers: {
          id_token: idToken,
          type: userType
        }
      })
      .then(function(response) {
        console.log(response.data);
        userStatus.userType = response.data.userStatus.userType;
        userStatus.userId = response.data.userStatus.userId;

        // if they are a new mentor go straight to profile

        if(userStatus.userType === 'mentor'){
          BioFactory.setMentorId(userStatus.userId);
          var newUser = response.data.userStatus.newUser;
          if(newUser === true){
              userStatus.newUser = true;
              $location.path("profile");
          }
          console.log("USER STATUS:", userStatus);
        }

      });
      userStatus.isLoggedIn = true;
    });

  }

  auth.$onAuthStateChanged(function(firebaseUser){

    // firebaseUser will be null if not logged in
    currentUser = firebaseUser;
    if(currentUser) {
      getUser(currentUser);
    } else {
      userStatus.isLoggedIn = false;
    }

  });

  function logOut() {
    return auth.$signOut().then(function() {
      currentUser = undefined;
      userStatus.isLoggedIn = false;
      userStatus.userType = "None";
      $location.path("home");
      userStatus.newUser = false;
      console.log('logged out');
      console.log('currentUser: ', currentUser);
    });
  }

  // TODO: Have Oliver explain what these variables are
  var publicApi = {
    auth: auth,
    userStatus: userStatus,
    currentUser: currentUser.user,
    logIn: function(userType) {
      return logIn(userType);
    },
    logOut: function() {
      return logOut();
    }
  };

  return publicApi;

}]);
<commit_msg>Remove BioFactory injection. Need to find solution<commit_after>app.factory('AuthFactory', ['$http', '$firebaseAuth', '$location', function($http, $firebaseAuth, $location){
  console.log('AuthFactory running');

  var auth = $firebaseAuth();
  var currentUser = {};
  var loggedIn = false;
  var userStatus = {};

  function logIn(userType) {
    return auth.$signInWithPopup("google").then(function(firebaseUser) {
      currentUser = firebaseUser.user;
      console.log('USER TYPE:', userType);
      if(currentUser) {
        getUser(currentUser, userType);
      }
    });
  }

  function getUser(currentUser, userType){
    currentUser.getToken().then(function(idToken){
      // console.log('ID TOKEN:', idToken);
      $http({
        method: 'GET',
        url: '/users.route',
        headers: {
          id_token: idToken,
          type: userType
        }
      })
      .then(function(response) {
        console.log(response.data);
        userStatus.userType = response.data.userStatus.userType;
        userStatus.userId = response.data.userStatus.userId;

        // if they are a new mentor go straight to profile

        if(userStatus.userType === 'mentor'){
          // BioFactory.setMentorId(userStatus.userId);
          var newUser = response.data.userStatus.newUser;
          if(newUser === true){
              userStatus.newUser = true;
              $location.path("profile");
          }
          console.log("USER STATUS:", userStatus);
        }

      });
      userStatus.isLoggedIn = true;
    });

  }

  auth.$onAuthStateChanged(function(firebaseUser){

    // firebaseUser will be null if not logged in
    currentUser = firebaseUser;
    if(currentUser) {
      getUser(currentUser);
    } else {
      userStatus.isLoggedIn = false;
    }

  });

  function logOut() {
    return auth.$signOut().then(function() {
      currentUser = undefined;
      userStatus.isLoggedIn = false;
      userStatus.userType = "None";
      $location.path("home");
      userStatus.newUser = false;
      console.log('logged out');
      console.log('currentUser: ', currentUser);
    });
  }

  // TODO: Have Oliver explain what these variables are
  var publicApi = {
    auth: auth,
    userStatus: userStatus,
    currentUser: currentUser.user,
    logIn: function(userType) {
      return logIn(userType);
    },
    logOut: function() {
      return logOut();
    }
  };

  return publicApi;

}]);
<|endoftext|>