<commit_before>/**
 * Copyright (c) Baidu Inc. All rights reserved.
 *
 * This source code is licensed under the MIT license.
 * See LICENSE file in the project root for license information.
 *
 * @file data types
 */

var bind = require('./bind');
var empty = require('./empty');
var extend = require('./extend');

// #[begin] error
var ANONYMOUS_CLASS_NAME = '<<anonymous>>';

/**
 * 获取精确的类型
 *
 * @NOTE 如果 obj 是一个 DOMElement，我们会返回 `element`；
 *
 * @param  {*} obj 目标
 * @return {string}
 */
function getDataType(obj) {

    if (obj && obj.nodeType === 1) {
        return 'element';
    }

    return Object.prototype.toString
        .call(obj)
        .slice(8, -1)
        .toLowerCase();
}
// #[end]

/**
 * 创建链式的数据类型校验器
 *
 * @param  {Function} validate 真正的校验器
 * @return {Function}
 */
function createChainableChecker(validate) {
    var chainedChecker = function () {};
    chainedChecker.isRequired = empty;

    // 只在 error 功能启用时才有实际上的 dataTypes 检测
    // #[begin] error
    var checkType = function (isRequired, data, dataName, componentName, fullDataName) {

        var dataValue = data[dataName];
        var dataType = getDataType(dataValue);

        componentName = componentName || ANONYMOUS_CLASS_NAME;

        // 如果是 null 或 undefined，那么要提前返回啦
        if (dataValue == null) {
            // 是 required 就报错
            if (isRequired) {
                throw new Error('[SAN ERROR] '
                    + 'The `' + dataName + '` '
                    + 'is marked as required in `' + componentName + '`, '
                    + 'but its value is ' + dataType
                );
            }
            // 不是 required，那就是 ok 的
            return;
        }

        validate(data, dataName, componentName, fullDataName);

    };

    chainedChecker = bind(checkType, null, false);
    chainedChecker.isRequired = bind(checkType, null, true);
    // #[end]

    return chainedChecker;

}

// #[begin] error
/**
 * 生成主要类型数据校验器
 *
 * @param  {string} type 主类型
 * @return {Function}
 */
function createPrimaryTypeChecker(type) {

    return createChainableChecker(function (data, dataName, componentName, fullDataName) {

        var dataValue = data[dataName];
        var dataType = getDataType(dataValue);

        if (dataType !== type) {
            throw new Error('[SAN ERROR] '
                + 'Invalid ' + componentName + ' data `' + fullDataName + '` of type'
                + '(' + dataType + ' supplied to ' + componentName + ', '
                + 'expected ' + type + ')'
            );
        }

    });

}

/**
 * 生成 arrayOf 校验器
 *
 * @param  {Function} arrayItemChecker 数组中每项数据的校验器
 * @return {Function}
 */
function createArrayOfChecker(arrayItemChecker) {

    return createChainableChecker(function (data, dataName, componentName, fullDataName) {

        if (typeof arrayItemChecker !== 'function') {
            throw new Error('[SAN ERROR] '
                + 'Data `' + dataName + '` of `' + componentName + '` has invalid '
                + 'DataType notation inside `arrayOf`, expected `function`'
            );
        }

        var dataValue = data[dataName];
        var dataType = getDataType(dataValue);

        if (dataType !== 'array') {
            throw new Error('[SAN ERROR] '
                + 'Invalid ' + componentName + ' data `' + fullDataName + '` of type'
                + '(' + dataType + ' supplied to ' + componentName + ', '
                + 'expected array)'
            );
        }

        for (var i = 0, len = dataValue.length; i < len; i++) {
            arrayItemChecker(dataValue, i, componentName, fullDataName + '[' + i + ']');
        }

    });

}

/**
 * 生成 instanceOf 检测器
 *
 * @param  {Function|Class} expectedClass 期待的类
 * @return {Function}
 */
function createInstanceOfChecker(expectedClass) {

    return createChainableChecker(function (data, dataName, componentName, fullDataName) {

        var dataValue = data[dataName];

        if (dataValue instanceof expectedClass) {
            return;
        }

        var dataValueClassName = dataValue.constructor && dataValue.constructor.name
            ? dataValue.constructor.name
            : ANONYMOUS_CLASS_NAME;

        var expectedClassName = expectedClass.name || ANONYMOUS_CLASS_NAME;

        throw new Error('[SAN ERROR] '
            + 'Invalid ' + componentName + ' data `' + fullDataName + '` of type'
            + '(' + dataValueClassName + ' supplied to ' + componentName + ', '
            + 'expected instance of ' + expectedClassName + ')'
        );

    });

}

/**
 * 生成 shape 校验器
 *
 * @param  {Object} shapeTypes shape 校验规则
 * @return {Function}
 */
function createShapeChecker(shapeTypes) {

    return createChainableChecker(function (data, dataName, componentName, fullDataName) {

        if (getDataType(shapeTypes) !== 'object') {
            throw new Error('[SAN ERROR] '
                + 'Data `' + fullDataName + '` of `' + componentName + '` has invalid '
                + 'DataType notation inside `shape`, expected `object`'
            );
        }

        var dataValue = data[dataName];
        var dataType = getDataType(dataValue);

        if (dataType !== 'object') {
            throw new Error('[SAN ERROR] '
                + 'Invalid ' + componentName + ' data `' + fullDataName + '` of type'
                + '(' + dataType + ' supplied to ' + componentName + ', '
                + 'expected object)'
            );
        }

        for (var shapeKeyName in shapeTypes) {
            /* istanbul ignore else  */
            if (shapeTypes.hasOwnProperty(shapeKeyName)) {
                var checker = shapeTypes[shapeKeyName];
                if (typeof checker === 'function') {
                    checker(dataValue, shapeKeyName, componentName, fullDataName + '.' + shapeKeyName);
                }
            }
        }

    });

}

/**
 * 生成 oneOf 校验器
 *
 * @param  {Array} expectedEnumValues 期待的枚举值
 * @return {Function}
 */
function createOneOfChecker(expectedEnumValues) {

    return createChainableChecker(function (data, dataName, componentName, fullDataName) {

        if (getDataType(expectedEnumValues) !== 'array') {
            throw new Error('[SAN ERROR] '
                + 'Data `' + fullDataName + '` of `' + componentName + '` has invalid '
                + 'DataType notation inside `oneOf`, array is expected.'
            );
        }

        var dataValue = data[dataName];

        for (var i = 0, len = expectedEnumValues.length; i < len; i++) {
            if (dataValue === expectedEnumValues[i]) {
                return;
            }
        }

        throw new Error('[SAN ERROR] '
            + 'Invalid ' + componentName + ' data `' + fullDataName + '` of value'
            + '(`' + dataValue + '` supplied to ' + componentName + ', '
            + 'expected one of ' + expectedEnumValues.join(',') + ')'
        );

    });

}

/**
 * 生成 oneOfType 校验器
 *
 * @param  {Array<Function>} expectedEnumOfTypeValues 期待的枚举类型
 * @return {Function}
 */
function createOneOfTypeChecker(expectedEnumOfTypeValues) {

    return createChainableChecker(function (data, dataName, componentName, fullDataName) {

        if (getDataType(expectedEnumOfTypeValues) !== 'array') {
            throw new Error('[SAN ERROR] '
                + 'Data `' + dataName + '` of `' + componentName + '` has invalid '
                + 'DataType notation inside `oneOf`, array is expected.'
            );
        }

        var dataValue = data[dataName];

        for (var i = 0, len = expectedEnumOfTypeValues.length; i < len; i++) {

            var checker = expectedEnumOfTypeValues[i];

            if (typeof checker !== 'function') {
                continue;
            }

            try {
                checker(data, dataName, componentName, fullDataName);
                // 如果 checker 完成校验没报错，那就返回了
                return;
            }
            catch (e) {
                // 如果有错误，那么应该把错误吞掉
            }

        }

        // 所有的可接受 type 都失败了，才丢一个异常
        throw new Error('[SAN ERROR] '
            + 'Invalid ' + componentName + ' data `' + dataName + '` of value'
            + '(`' + dataValue + '` supplied to ' + componentName + ')'
        );

    });

}

/**
 * 生成 objectOf 校验器
 *
 * @param  {Function} typeChecker 对象属性值校验器
 * @return {Function}
 */
function createObjectOfChecker(typeChecker) {

    return createChainableChecker(function (data, dataName, componentName, fullDataName) {

        if (typeof typeChecker !== 'function') {
            throw new Error('[SAN ERROR] '
                + 'Data `' + dataName + '` of `' + componentName + '` has invalid '
                + 'DataType notation inside `objectOf`, expected function'
            );
        }

        var dataValue = data[dataName];
        var dataType = getDataType(dataValue);

        if (dataType !== 'object') {
            throw new Error('[SAN ERROR] '
                + 'Invalid ' + componentName + ' data `' + dataName + '` of type'
                + '(' + dataType + ' supplied to ' + componentName + ', '
                + 'expected object)'
            );
        }

        for (var dataKeyName in dataValue) {
            /* istanbul ignore else  */
            if (dataValue.hasOwnProperty(dataKeyName)) {
                typeChecker(
                    dataValue,
                    dataKeyName,
                    componentName,
                    fullDataName + '.' + dataKeyName
                );
            }
        }

    });

}

/**
 * 生成 exact 校验器
 *
 * @param  {Object} shapeTypes object 形态定义
 * @return {Function}
 */
function createExactChecker(shapeTypes) {

    return createChainableChecker(function (data, dataName, componentName, fullDataName, secret) {

        if (getDataType(shapeTypes) !== 'object') {
            throw new Error('[SAN ERROR] '
                + 'Data `' + dataName + '` of `' + componentName + '` has invalid '
                + 'DataType notation inside `exact`'
            );
        }

        var dataValue = data[dataName];
        var dataValueType = getDataType(dataValue);

        if (dataValueType !== 'object') {
            throw new Error('[SAN ERROR] '
                + 'Invalid data `' + fullDataName + '` of type `' + dataValueType + '`'
                + '(supplied to ' + componentName + ', expected `object`)'
            );
        }

        var allKeys = {};

        // 先合入 shapeTypes
        extend(allKeys, shapeTypes);
        // 再合入 dataValue
        extend(allKeys, dataValue);
        // 保证 allKeys 的类型正确

        for (var key in allKeys) {
            /* istanbul ignore else  */
            if (allKeys.hasOwnProperty(key)) {
                var checker = shapeTypes[key];

                // dataValue 中有一个多余的数据项
                if (!checker) {
                    throw new Error('[SAN ERROR] '
                        + 'Invalid data `' + fullDataName + '` key `' + key + '` '
                        + 'supplied to `' + componentName + '`. '
                        + '(`' + key + '` is not defined in `DataTypes.exact`)'
                    );
                }

                if (!(key in dataValue)) {
                    throw new Error('[SAN ERROR] '
                        + 'Invalid data `' + fullDataName + '` key `' + key + '` '
                        + 'supplied to `' + componentName + '`. '
                        + '(`' + key + '` is marked `required` in `DataTypes.exact`)'
                    );
                }

                checker(
                    dataValue,
                    key,
                    componentName,
                    fullDataName + '.' + key,
                    secret
                );

            }
        }

    });

}
// #[end]

/* eslint-disable fecs-valid-var-jsdoc */
var DataTypes = {
    array: createChainableChecker(empty),
    object: createChainableChecker(empty),
    func: createChainableChecker(empty),
    string: createChainableChecker(empty),
    number: createChainableChecker(empty),
    bool: createChainableChecker(empty),
    symbol: createChainableChecker(empty),
    any: createChainableChecker,
    arrayOf: createChainableChecker,
    instanceOf: createChainableChecker,
    shape: createChainableChecker,
    oneOf: createChainableChecker,
    oneOfType: createChainableChecker,
    objectOf: createChainableChecker,
    exact: createChainableChecker
};

// #[begin] error
DataTypes = {

    any: createChainableChecker(empty),

    // 类型检测
    array: createPrimaryTypeChecker('array'),
    object: createPrimaryTypeChecker('object'),
    func: createPrimaryTypeChecker('function'),
    string: createPrimaryTypeChecker('string'),
    number: createPrimaryTypeChecker('number'),
    bool: createPrimaryTypeChecker('boolean'),
    symbol: createPrimaryTypeChecker('symbol'),

    // 复合类型检测
    arrayOf: createArrayOfChecker,
    instanceOf: createInstanceOfChecker,
    shape: createShapeChecker,
    oneOf: createOneOfChecker,
    oneOfType: createOneOfTypeChecker,
    objectOf: createObjectOfChecker,
    exact: createExactChecker

};
/* eslint-enable fecs-valid-var-jsdoc */
// #[end]

module.exports = DataTypes;
<commit_msg>not support element<commit_after>/**
 * Copyright (c) Baidu Inc. All rights reserved.
 *
 * This source code is licensed under the MIT license.
 * See LICENSE file in the project root for license information.
 *
 * @file data types
 */

var bind = require('./bind');
var empty = require('./empty');
var extend = require('./extend');

// #[begin] error
var ANONYMOUS_CLASS_NAME = '<<anonymous>>';

/**
 * 获取精确的类型
 *
 * @NOTE 如果 obj 是一个 DOMElement，我们会返回 `element`；
 *
 * @param  {*} obj 目标
 * @return {string}
 */
function getDataType(obj) {
    // 不支持element了。data应该是纯数据
    // if (obj && obj.nodeType === 1) {
    //     return 'element';
    // }

    return Object.prototype.toString
        .call(obj)
        .slice(8, -1)
        .toLowerCase();
}
// #[end]

/**
 * 创建链式的数据类型校验器
 *
 * @param  {Function} validate 真正的校验器
 * @return {Function}
 */
function createChainableChecker(validate) {
    /* istanbul ignore next */
    var chainedChecker = function () {};
    chainedChecker.isRequired = empty;

    // 只在 error 功能启用时才有实际上的 dataTypes 检测
    // #[begin] error
    validate = validate || empty;
    var checkType = function (isRequired, data, dataName, componentName, fullDataName) {

        var dataValue = data[dataName];
        var dataType = getDataType(dataValue);

        /* istanbul ignore next */
        componentName = componentName || ANONYMOUS_CLASS_NAME;

        // 如果是 null 或 undefined，那么要提前返回啦
        if (dataValue == null) {
            // 是 required 就报错
            if (isRequired) {
                throw new Error('[SAN ERROR] '
                    + 'The `' + dataName + '` '
                    + 'is marked as required in `' + componentName + '`, '
                    + 'but its value is ' + dataType
                );
            }
            // 不是 required，那就是 ok 的
            return;
        }

        validate(data, dataName, componentName, fullDataName);

    };

    chainedChecker = bind(checkType, null, false);
    chainedChecker.isRequired = bind(checkType, null, true);
    // #[end]

    return chainedChecker;

}

// #[begin] error
/**
 * 生成主要类型数据校验器
 *
 * @param  {string} type 主类型
 * @return {Function}
 */
function createPrimaryTypeChecker(type) {

    return createChainableChecker(function (data, dataName, componentName, fullDataName) {

        var dataValue = data[dataName];
        var dataType = getDataType(dataValue);

        if (dataType !== type) {
            throw new Error('[SAN ERROR] '
                + 'Invalid ' + componentName + ' data `' + fullDataName + '` of type'
                + '(' + dataType + ' supplied to ' + componentName + ', '
                + 'expected ' + type + ')'
            );
        }

    });

}

/**
 * 生成 arrayOf 校验器
 *
 * @param  {Function} arrayItemChecker 数组中每项数据的校验器
 * @return {Function}
 */
function createArrayOfChecker(arrayItemChecker) {

    return createChainableChecker(function (data, dataName, componentName, fullDataName) {

        if (typeof arrayItemChecker !== 'function') {
            throw new Error('[SAN ERROR] '
                + 'Data `' + dataName + '` of `' + componentName + '` has invalid '
                + 'DataType notation inside `arrayOf`, expected `function`'
            );
        }

        var dataValue = data[dataName];
        var dataType = getDataType(dataValue);

        if (dataType !== 'array') {
            throw new Error('[SAN ERROR] '
                + 'Invalid ' + componentName + ' data `' + fullDataName + '` of type'
                + '(' + dataType + ' supplied to ' + componentName + ', '
                + 'expected array)'
            );
        }

        for (var i = 0, len = dataValue.length; i < len; i++) {
            arrayItemChecker(dataValue, i, componentName, fullDataName + '[' + i + ']');
        }

    });

}

/**
 * 生成 instanceOf 检测器
 *
 * @param  {Function|Class} expectedClass 期待的类
 * @return {Function}
 */
function createInstanceOfChecker(expectedClass) {

    return createChainableChecker(function (data, dataName, componentName, fullDataName) {

        var dataValue = data[dataName];

        if (dataValue instanceof expectedClass) {
            return;
        }

        var dataValueClassName = dataValue.constructor && dataValue.constructor.name
            ? dataValue.constructor.name
            : /* istanbul ignore next */ ANONYMOUS_CLASS_NAME;

        /* istanbul ignore next */
        var expectedClassName = expectedClass.name || ANONYMOUS_CLASS_NAME;

        throw new Error('[SAN ERROR] '
            + 'Invalid ' + componentName + ' data `' + fullDataName + '` of type'
            + '(' + dataValueClassName + ' supplied to ' + componentName + ', '
            + 'expected instance of ' + expectedClassName + ')'
        );

    });

}

/**
 * 生成 shape 校验器
 *
 * @param  {Object} shapeTypes shape 校验规则
 * @return {Function}
 */
function createShapeChecker(shapeTypes) {

    return createChainableChecker(function (data, dataName, componentName, fullDataName) {

        if (getDataType(shapeTypes) !== 'object') {
            throw new Error('[SAN ERROR] '
                + 'Data `' + fullDataName + '` of `' + componentName + '` has invalid '
                + 'DataType notation inside `shape`, expected `object`'
            );
        }

        var dataValue = data[dataName];
        var dataType = getDataType(dataValue);

        if (dataType !== 'object') {
            throw new Error('[SAN ERROR] '
                + 'Invalid ' + componentName + ' data `' + fullDataName + '` of type'
                + '(' + dataType + ' supplied to ' + componentName + ', '
                + 'expected object)'
            );
        }

        for (var shapeKeyName in shapeTypes) {
            /* istanbul ignore else  */
            if (shapeTypes.hasOwnProperty(shapeKeyName)) {
                var checker = shapeTypes[shapeKeyName];
                if (typeof checker === 'function') {
                    checker(dataValue, shapeKeyName, componentName, fullDataName + '.' + shapeKeyName);
                }
            }
        }

    });

}

/**
 * 生成 oneOf 校验器
 *
 * @param  {Array} expectedEnumValues 期待的枚举值
 * @return {Function}
 */
function createOneOfChecker(expectedEnumValues) {

    return createChainableChecker(function (data, dataName, componentName, fullDataName) {

        if (getDataType(expectedEnumValues) !== 'array') {
            throw new Error('[SAN ERROR] '
                + 'Data `' + fullDataName + '` of `' + componentName + '` has invalid '
                + 'DataType notation inside `oneOf`, array is expected.'
            );
        }

        var dataValue = data[dataName];

        for (var i = 0, len = expectedEnumValues.length; i < len; i++) {
            if (dataValue === expectedEnumValues[i]) {
                return;
            }
        }

        throw new Error('[SAN ERROR] '
            + 'Invalid ' + componentName + ' data `' + fullDataName + '` of value'
            + '(`' + dataValue + '` supplied to ' + componentName + ', '
            + 'expected one of ' + expectedEnumValues.join(',') + ')'
        );

    });

}

/**
 * 生成 oneOfType 校验器
 *
 * @param  {Array<Function>} expectedEnumOfTypeValues 期待的枚举类型
 * @return {Function}
 */
function createOneOfTypeChecker(expectedEnumOfTypeValues) {

    return createChainableChecker(function (data, dataName, componentName, fullDataName) {

        if (getDataType(expectedEnumOfTypeValues) !== 'array') {
            throw new Error('[SAN ERROR] '
                + 'Data `' + dataName + '` of `' + componentName + '` has invalid '
                + 'DataType notation inside `oneOf`, array is expected.'
            );
        }

        var dataValue = data[dataName];

        for (var i = 0, len = expectedEnumOfTypeValues.length; i < len; i++) {

            var checker = expectedEnumOfTypeValues[i];

            if (typeof checker !== 'function') {
                continue;
            }

            try {
                checker(data, dataName, componentName, fullDataName);
                // 如果 checker 完成校验没报错，那就返回了
                return;
            }
            catch (e) {
                // 如果有错误，那么应该把错误吞掉
            }

        }

        // 所有的可接受 type 都失败了，才丢一个异常
        throw new Error('[SAN ERROR] '
            + 'Invalid ' + componentName + ' data `' + dataName + '` of value'
            + '(`' + dataValue + '` supplied to ' + componentName + ')'
        );

    });

}

/**
 * 生成 objectOf 校验器
 *
 * @param  {Function} typeChecker 对象属性值校验器
 * @return {Function}
 */
function createObjectOfChecker(typeChecker) {

    return createChainableChecker(function (data, dataName, componentName, fullDataName) {

        if (typeof typeChecker !== 'function') {
            throw new Error('[SAN ERROR] '
                + 'Data `' + dataName + '` of `' + componentName + '` has invalid '
                + 'DataType notation inside `objectOf`, expected function'
            );
        }

        var dataValue = data[dataName];
        var dataType = getDataType(dataValue);

        if (dataType !== 'object') {
            throw new Error('[SAN ERROR] '
                + 'Invalid ' + componentName + ' data `' + dataName + '` of type'
                + '(' + dataType + ' supplied to ' + componentName + ', '
                + 'expected object)'
            );
        }

        for (var dataKeyName in dataValue) {
            /* istanbul ignore else  */
            if (dataValue.hasOwnProperty(dataKeyName)) {
                typeChecker(
                    dataValue,
                    dataKeyName,
                    componentName,
                    fullDataName + '.' + dataKeyName
                );
            }
        }

    });

}

/**
 * 生成 exact 校验器
 *
 * @param  {Object} shapeTypes object 形态定义
 * @return {Function}
 */
function createExactChecker(shapeTypes) {

    return createChainableChecker(function (data, dataName, componentName, fullDataName, secret) {

        if (getDataType(shapeTypes) !== 'object') {
            throw new Error('[SAN ERROR] '
                + 'Data `' + dataName + '` of `' + componentName + '` has invalid '
                + 'DataType notation inside `exact`'
            );
        }

        var dataValue = data[dataName];
        var dataValueType = getDataType(dataValue);

        if (dataValueType !== 'object') {
            throw new Error('[SAN ERROR] '
                + 'Invalid data `' + fullDataName + '` of type `' + dataValueType + '`'
                + '(supplied to ' + componentName + ', expected `object`)'
            );
        }

        var allKeys = {};

        // 先合入 shapeTypes
        extend(allKeys, shapeTypes);
        // 再合入 dataValue
        extend(allKeys, dataValue);
        // 保证 allKeys 的类型正确

        for (var key in allKeys) {
            /* istanbul ignore else  */
            if (allKeys.hasOwnProperty(key)) {
                var checker = shapeTypes[key];

                // dataValue 中有一个多余的数据项
                if (!checker) {
                    throw new Error('[SAN ERROR] '
                        + 'Invalid data `' + fullDataName + '` key `' + key + '` '
                        + 'supplied to `' + componentName + '`. '
                        + '(`' + key + '` is not defined in `DataTypes.exact`)'
                    );
                }

                if (!(key in dataValue)) {
                    throw new Error('[SAN ERROR] '
                        + 'Invalid data `' + fullDataName + '` key `' + key + '` '
                        + 'supplied to `' + componentName + '`. '
                        + '(`' + key + '` is marked `required` in `DataTypes.exact`)'
                    );
                }

                checker(
                    dataValue,
                    key,
                    componentName,
                    fullDataName + '.' + key,
                    secret
                );

            }
        }

    });

}
// #[end]

/* eslint-disable fecs-valid-var-jsdoc */
var DataTypes = {
    array: createChainableChecker(),
    object: createChainableChecker(),
    func: createChainableChecker(),
    string: createChainableChecker(),
    number: createChainableChecker(),
    bool: createChainableChecker(),
    symbol: createChainableChecker(),
    any: createChainableChecker,
    arrayOf: createChainableChecker,
    instanceOf: createChainableChecker,
    shape: createChainableChecker,
    oneOf: createChainableChecker,
    oneOfType: createChainableChecker,
    objectOf: createChainableChecker,
    exact: createChainableChecker
};

// #[begin] error
DataTypes = {

    any: createChainableChecker(),

    // 类型检测
    array: createPrimaryTypeChecker('array'),
    object: createPrimaryTypeChecker('object'),
    func: createPrimaryTypeChecker('function'),
    string: createPrimaryTypeChecker('string'),
    number: createPrimaryTypeChecker('number'),
    bool: createPrimaryTypeChecker('boolean'),
    symbol: createPrimaryTypeChecker('symbol'),

    // 复合类型检测
    arrayOf: createArrayOfChecker,
    instanceOf: createInstanceOfChecker,
    shape: createShapeChecker,
    oneOf: createOneOfChecker,
    oneOfType: createOneOfTypeChecker,
    objectOf: createObjectOfChecker,
    exact: createExactChecker

};
/* eslint-enable fecs-valid-var-jsdoc */
// #[end]

module.exports = DataTypes;
<|endoftext|>


<commit_before>import _ from 'lodash'
import React, { Component } from 'react'

export const props = (deprecated) => (Decorated) => class Deprecate extends Component {
  static _meta = Decorated._meta;

  componentDidMount() {
    this.warnAboutDeprecated()
  }

  componentDidUpdate(prevProps, prevState) {
    this.warnAboutDeprecated()
  }

  warnAboutDeprecated() {
    const warnings = _.pick(deprecated, _.keys(this.props))
    _.each(warnings, (val, key) => {
      /* eslint-disable no-console */
      console.warn(`${Decorated.name} prop "${key}" is deprecated. ${val}`)
      /* eslint-enable no-console */
    })
  }

  render() {
    return <Decorated {...this.props} />
  }
}
<commit_msg>limit deprecate check and warnings to non-production<commit_after>import _ from 'lodash'
import React, { Component } from 'react'

export const props = (deprecated) => (Decorated) => {
  return process.env.NODE_ENV === 'production' ? Decorated : class Deprecate extends Component {
    static _meta = Decorated._meta;

    componentDidMount() {
      this.warnAboutDeprecated()
    }

    componentDidUpdate(prevProps, prevState) {
      this.warnAboutDeprecated()
    }

    warnAboutDeprecated() {
      const warnings = _.pick(deprecated, _.keys(this.props))
      _.each(warnings, (val, key) => {
        /* eslint-disable no-console */
        console.warn(`${Decorated.name} prop "${key}" is deprecated. ${val}`)
        /* eslint-enable no-console */
      })
    }

    render() {
      return <Decorated {...this.props} />
    }
  }
}
<|endoftext|>


<commit_before>/**
 * Copyright 2017-present, Callstack.
 * All rights reserved.
 *
 * @flow
 */
import type { Platform, Logger } from '../types';

const path = require('path');
const { createWebpackConfig } = require('../index');

const { makeReactNativeConfig } = require('./makeReactNativeConfig');

module.exports = function getConfig(
  configPath: ?string,
  configOptions: any,
  platform: Platform,
  logger?: Logger
) {
  let config;

  /**
   * When it doesn't have DEFAULT_CONFIG_FILE_PATH and it's not specified another file
   * we will use default configuration based on main file from package.json
   */
  if (configPath === null) {
    // $FlowFixMe
    let entry = require(path.resolve(process.cwd(), 'package.json')).main;

    if (!entry) entry = 'index.js';

    /**
     * Make it relative for Webpack
     */
    entry = `./${entry}`;

    config = {
      webpack: createWebpackConfig({ entry }),
    };
  } else {
    // $FlowFixMe
    config = require(configPath);
    config = config.__esModule ? config.default : config;
  }

  // $FlowFixMe
  return makeReactNativeConfig(config, configOptions, platform, logger);
};
<commit_msg>Inform user that compiler uses ZERO-CONFIGURATION 🚀mode (#407)<commit_after>/**
 * Copyright 2017-present, Callstack.
 * All rights reserved.
 *
 * @flow
 */
import type { Platform, Logger } from '../types';

const path = require('path');
const loggerUtil = require('../logger');
const { createWebpackConfig } = require('../index');
const { makeReactNativeConfig } = require('./makeReactNativeConfig');

module.exports = function getConfig(
  configPath: ?string,
  configOptions: any,
  platform: Platform,
  logger?: Logger = loggerUtil
) {
  let config;

  /**
   * When it doesn't have DEFAULT_CONFIG_FILE_PATH and it's not specified another file
   * we will use default configuration based on main file from package.json
   */
  if (configPath === null) {
    // $FlowFixMe
    let entry = require(path.resolve(process.cwd(), 'package.json')).main;

    if (!entry) entry = 'index.js';

    /**
     * Make it relative for Webpack
     */
    entry = `./${entry}`;

    config = {
      webpack: createWebpackConfig({ entry }),
    };
    logger.info(
      `Couldn't find "haul.config.js". Using default configuration.\nFound entry file at ${entry}.`
    );
  } else {
    // $FlowFixMe
    config = require(configPath);
    config = config.__esModule ? config.default : config;
  }

  // $FlowFixMe
  return makeReactNativeConfig(config, configOptions, platform, logger);
};
<|endoftext|>


<commit_before>// Word of ⚠
// If you are under age or don't like seeing ugly things, please don't read this file.
// thanks, ❤

// but really though, there's no API for that right now so I decided to go the dirty way.

async function getIt () {
	// 😂
	const API = "https://instances.mastodon.xyz/"
	return fetch(API)
		.then(response => response.text())
}

	// This is where the 💩 happens
export async function getInstances() {
	// Can you see it coming?
	const node = document.createElement('div')
	// oh yeah 💃
	node.innerHTML = await getIt()
	// I just did that 😎
	const links = [...node.querySelectorAll('a')]

	// sometimes, dirty way is the good way 🍆
	return links.reduce((accumulator, link) => {
		if (!/localhost/.test(link)) {
			accumulator.push(link.href)
		}

		return accumulator
	}, [])
}<commit_msg>newlines are the new black<commit_after>// Word of ⚠
// If you are under age or don't like seeing ugly things, please don't read this file.
// thanks, ❤

// but really though, there's no API for that right now so I decided to go the dirty way.

async function getIt () {
	// 😂
	const API = "https://instances.mastodon.xyz/"
	return fetch(API)
		.then(response => response.text())
}

	// This is where the 💩 happens
export async function getInstances() {
	// Can you see it coming?
	const node = document.createElement('div')
	// oh yeah 💃
	node.innerHTML = await getIt()
	// I just did that 😎
	const links = [...node.querySelectorAll('a')]

	// sometimes, dirty way is the good way 🍆
	return links.reduce((accumulator, link) => {
		if (!/localhost/.test(link)) {
			accumulator.push(link.href)
		}

		return accumulator
	}, [])
}
<|endoftext|>


<commit_before>/*
Name:         tk102
Description:  TK102 GPS server for Node.js
Source:       https://github.com/fvdm/nodejs-tk102
Feedback:     https://github.com/fvdm/nodejs-tk102/issues
License:      Unlicense / Public Domain

This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to <http://unlicense.org>
*/

// INIT
var net = require('net')
var EventEmitter = require('events').EventEmitter

var tk102 = new EventEmitter()

// defaults
tk102.settings = {
	ip:          '0.0.0.0', // default listen on all IPs
	port:        0,         // 0 = random, 'listening' event reports port
	connections: 10,        // 10 simultaneous connections
	timeout:     10         // 10 seconds idle timeout
}

// Create server
tk102.createServer = function( vars ) {

	// override settings
	if( typeof vars == 'object' && Object.keys(vars).length >= 1 ) {
		for( var key in vars ) {
			tk102.settings[ key ] = vars[ key ]
		}
	}

	// start server
	tk102.server = net.createServer( function( socket ) {

		// socket idle timeout
		if( tk102.settings.timeout > 0 ) {
			socket.setTimeout( tk102.settings.timeout * 1000, function() {
				tk102.emit( 'timeout', socket )
				socket.end()
			})
		}

	}).listen( tk102.settings.port, tk102.settings.ip, function() {

		// server ready
		tk102.emit( 'listening', tk102.server.address() )

	})

	// maximum number of slots
	tk102.server.maxConnections = tk102.settings.connections

	// inbound connection
	tk102.server.on( 'connection', function( socket ) {

		tk102.emit( 'connection', socket )
		socket.setEncoding('utf8')
		var data = ''

		// receiving data
		socket.on( 'data', function( chunk ) {
			tk102.emit( 'data', chunk )
			data += chunk
		})

		// complete
		socket.on( 'close', function() {

			var gps = {}
			gps = tk102.parse( data )
			if( data != '' ) {
				var gps = tk102.parse( data )
				if( gps ) {
					tk102.emit( 'track', gps )
				} else {
					var err = new Error('Connot parse GPS data from device')
					err.reason = err.message
					err.socket = socket
					err.input = data

					tk102.emit( 'fail', err )
				}
			}

		})

		// error
		socket.on( 'error', function( error ) {
			var err = new Error('Socket error')

			err.reason = err.message
			err.socket = socket
			err.input = tk102.settings

			tk102.emit( 'error', err )
		})

	})
}

// Parse GPRMC string
tk102.parse = function( raw ) {

	// 1203292316,0031698765432,GPRMC,211657.000,A,5213.0247,N,00516.7757,E,0.00,273.30,290312,,,A*62,F,imei:123456789012345,123
	var raw = raw.trim()
	var str = raw.split(',')
	var data = false

	// only continue with correct input, else the server may quit...
	if( str.length == 18 && str[2] == 'GPRMC' ) {

		// parse
		var datetime = str[0].replace( /([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})/, function( match, year, month, day, hour, minute ) {
			return '20'+ year +'-'+ month +'-'+ day +' '+ hour +':'+ minute
		})

		var gpsdate = str[11].replace( /([0-9]{2})([0-9]{2})([0-9]{2})/, function( match, day, month, year ) {
			return '20'+ year +'-'+ month +'-'+ day
		})

		var gpstime = str[3].replace( /([0-9]{2})([0-9]{2})([0-9]{2})\.([0-9]{3})/, function( match, hour, minute, second, ms ) {
			return hour +':'+ minute +':'+ second +'.'+ ms
		})

		data = {
			'raw': raw,
			'datetime': datetime,
			'phone': str[1],
			'gps': {
				'date': gpsdate,
				'time': gpstime,
				'signal': str[15] == 'F' ? 'full' : 'low',
				'fix': str[4] == 'A' ? 'active' : 'invalid'
			},
			'geo': {
				'latitude':	tk102.fixGeo( str[5], str[6] ),
				'longitude': tk102.fixGeo( str[7], str[8] ),
				'bearing': parseInt( str[10] )
			},
			'speed': {
				'knots': Math.round( str[9] * 1000 ) / 1000,
				'kmh': Math.round( str[9] * 1.852 * 1000 ) / 1000,
				'mph': Math.round( str[9] * 1.151 * 1000 ) / 1000
			},
			'imei': str[16].replace( 'imei:', '' )
		}
	}

	// done
	return data
}

// Clean geo positions, with 6 decimals
tk102.fixGeo = function( one, two ) {
	var minutes = one.substr(-7, 7)
	var degrees = parseInt( one.replace( minutes, '' ), 10 )
	var one = degrees + (minutes / 60)

	var one = parseFloat( (two == 'S' || two == 'W' ? '-' : '') + one )
	return Math.round( one * 1000000 ) / 1000000
}

// ready
module.exports = tk102
<commit_msg>Added 'Server error' to 'error' event<commit_after>/*
Name:         tk102
Description:  TK102 GPS server for Node.js
Source:       https://github.com/fvdm/nodejs-tk102
Feedback:     https://github.com/fvdm/nodejs-tk102/issues
License:      Unlicense / Public Domain

This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to <http://unlicense.org>
*/

// INIT
var net = require('net')
var EventEmitter = require('events').EventEmitter

var tk102 = new EventEmitter()

// defaults
tk102.settings = {
	ip:          '0.0.0.0', // default listen on all IPs
	port:        0,         // 0 = random, 'listening' event reports port
	connections: 10,        // 10 simultaneous connections
	timeout:     10         // 10 seconds idle timeout
}

// Create server
tk102.createServer = function( vars ) {

	// override settings
	if( typeof vars == 'object' && Object.keys(vars).length >= 1 ) {
		for( var key in vars ) {
			tk102.settings[ key ] = vars[ key ]
		}
	}

	// start server
	tk102.server = net.createServer( function( socket ) {

		// socket idle timeout
		if( tk102.settings.timeout > 0 ) {
			socket.setTimeout( tk102.settings.timeout * 1000, function() {
				tk102.emit( 'timeout', socket )
				socket.end()
			})
		}

	}).listen( tk102.settings.port, tk102.settings.ip, function() {

		// server ready
		tk102.emit( 'listening', tk102.server.address() )

	})

	// maximum number of slots
	tk102.server.maxConnections = tk102.settings.connections

	// inbound connection
	tk102.server.on( 'connection', function( socket ) {

		tk102.emit( 'connection', socket )
		socket.setEncoding('utf8')
		var data = ''

		// receiving data
		socket.on( 'data', function( chunk ) {
			tk102.emit( 'data', chunk )
			data += chunk
		})

		// complete
		socket.on( 'close', function() {

			var gps = {}
			gps = tk102.parse( data )
			if( data != '' ) {
				var gps = tk102.parse( data )
				if( gps ) {
					tk102.emit( 'track', gps )
				} else {
					var err = new Error('Connot parse GPS data from device')
					err.reason = err.message
					err.socket = socket
					err.input = data

					tk102.emit( 'fail', err )
				}
			}

		})

		// error
		socket.on( 'error', function( error ) {
			var err = new Error('Socket error')

			err.reason = err.message
			err.socket = socket
			err.input = tk102.settings

			tk102.emit( 'error', err )
		})

	})

	tk102.server.on( 'error', function( error ) {
		var err = new Error('Server error')

		err.reason = err.message
		err.input = tk102.settings

		tk102.emit( 'error', err )
	})
}

// Parse GPRMC string
tk102.parse = function( raw ) {

	// 1203292316,0031698765432,GPRMC,211657.000,A,5213.0247,N,00516.7757,E,0.00,273.30,290312,,,A*62,F,imei:123456789012345,123
	var raw = raw.trim()
	var str = raw.split(',')
	var data = false

	// only continue with correct input, else the server may quit...
	if( str.length == 18 && str[2] == 'GPRMC' ) {

		// parse
		var datetime = str[0].replace( /([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})/, function( match, year, month, day, hour, minute ) {
			return '20'+ year +'-'+ month +'-'+ day +' '+ hour +':'+ minute
		})

		var gpsdate = str[11].replace( /([0-9]{2})([0-9]{2})([0-9]{2})/, function( match, day, month, year ) {
			return '20'+ year +'-'+ month +'-'+ day
		})

		var gpstime = str[3].replace( /([0-9]{2})([0-9]{2})([0-9]{2})\.([0-9]{3})/, function( match, hour, minute, second, ms ) {
			return hour +':'+ minute +':'+ second +'.'+ ms
		})

		data = {
			'raw': raw,
			'datetime': datetime,
			'phone': str[1],
			'gps': {
				'date': gpsdate,
				'time': gpstime,
				'signal': str[15] == 'F' ? 'full' : 'low',
				'fix': str[4] == 'A' ? 'active' : 'invalid'
			},
			'geo': {
				'latitude':	tk102.fixGeo( str[5], str[6] ),
				'longitude': tk102.fixGeo( str[7], str[8] ),
				'bearing': parseInt( str[10] )
			},
			'speed': {
				'knots': Math.round( str[9] * 1000 ) / 1000,
				'kmh': Math.round( str[9] * 1.852 * 1000 ) / 1000,
				'mph': Math.round( str[9] * 1.151 * 1000 ) / 1000
			},
			'imei': str[16].replace( 'imei:', '' )
		}
	}

	// done
	return data
}

// Clean geo positions, with 6 decimals
tk102.fixGeo = function( one, two ) {
	var minutes = one.substr(-7, 7)
	var degrees = parseInt( one.replace( minutes, '' ), 10 )
	var one = degrees + (minutes / 60)

	var one = parseFloat( (two == 'S' || two == 'W' ? '-' : '') + one )
	return Math.round( one * 1000000 ) / 1000000
}

// ready
module.exports = tk102
<|endoftext|>


<commit_before>require.config({
  baseUrl: '/',
  paths: {
    jquery: 'lib/jquery/jquery',
    text: 'lib/requirejs-text/text',
    base64: 'lib/base64/base64',
    hogan: 'lib/hogan/web/builds/2.0.0/hogan-2.0.0.amd',
    hgn: 'lib/requirejs-hogan-plugin/hgn',
    jasmine: 'lib/jasmine/lib/jasmine-core/jasmine',
    'jasmine-html': 'lib/jasmine/lib/jasmine-core/jasmine-html',
    'jasmine-jquery': 'lib/jasmine-jquery/lib/jasmine-jquery',
  },
  packages: [{
     name: "streamhub-wall",
     location: "src/"
  },{
     name: "streamhub-sdk",
     location: "lib/streamhub-sdk/src/"
  }],
  shim: {
    jquery: {
        exports: '$'
    },
    'jquery-isotope': {
        deps: ['jquery']
    },
    'jquery-imagesloaded': {
        deps: ['jquery']
    },
    jasmine: {
        exports: 'jasmine'
    },
    'jasmine-html': {
        deps: ['jasmine'],
        exports: 'jasmine'
    },
    'jasmine-jquery': {
        deps: ['jquery', 'jasmine']
    }
  },
  urlArgs: "_=" +  (new Date()).getTime()
});
<commit_msg>updated requirejs conf<commit_after>require.config({
  paths: {
    jquery: 'lib/jquery/jquery',
    text: 'lib/requirejs-text/text',
    base64: 'lib/base64/base64',
    hogan: 'lib/hogan/web/builds/2.0.0/hogan-2.0.0.amd',
    hgn: 'lib/requirejs-hogan-plugin/hgn',
    jasmine: 'lib/jasmine/lib/jasmine-core/jasmine',
    'jasmine-html': 'lib/jasmine/lib/jasmine-core/jasmine-html',
    'jasmine-jquery': 'lib/jasmine-jquery/lib/jasmine-jquery',
  },
  packages: [{
     name: "streamhub-wall",
     location: "./src"
  },{
     name: "streamhub-sdk",
     location: "lib/streamhub-sdk/src/"
  }],
  shim: {
    jquery: {
        exports: '$'
    },
    jasmine: {
        exports: 'jasmine'
    },
    'jasmine-html': {
        deps: ['jasmine'],
        exports: 'jasmine'
    },
    'jasmine-jquery': {
        deps: ['jquery', 'jasmine']
    }
  },
  urlArgs: "_=" +  (new Date()).getTime()
});
<|endoftext|>


<commit_before>var path = require('path');

var LocationUtils = require('../../utils/location');
var editHTMLElement = require('./editHTMLElement');

/**
    Resolve all HTML links:
        - /test.md in hello -> ../test.html

    @param {String} currentFile
    @param {Function(String) -> String} resolveFile
    @param {HTMLDom} $
*/
function resolveLinks(currentFile, resolveFile, $) {
    var currentDirectory = path.dirname(currentFile);

    return editHTMLElement($, 'a', function($a) {
        var href = $a.attr('href');

        if (LocationUtils.isExternal(href)) {
            $a.attr('_target', 'blank');
            return;
        }

        // Calcul absolute path for this
        href = LocationUtils.toAbsolute(href, currentDirectory, '.');

        // Resolve file
        href = resolveFile(href);

        // Convert back to relative
        href = LocationUtils.relative(currentDirectory, href);

        $a.attr('href', href);
    });
}

module.exports = resolveLinks;
<commit_msg>Fix #1266: don't fail on "a" tags without "href"<commit_after>var path = require('path');

var LocationUtils = require('../../utils/location');
var editHTMLElement = require('./editHTMLElement');

/**
    Resolve all HTML links:
        - /test.md in hello -> ../test.html

    @param {String} currentFile
    @param {Function(String) -> String} resolveFile
    @param {HTMLDom} $
*/
function resolveLinks(currentFile, resolveFile, $) {
    var currentDirectory = path.dirname(currentFile);

    return editHTMLElement($, 'a', function($a) {
        var href = $a.attr('href');

        // Don't change a tag without href
        if (!href) {
            return;
        }

        if (LocationUtils.isExternal(href)) {
            $a.attr('_target', 'blank');
            return;
        }

        // Calcul absolute path for this
        href = LocationUtils.toAbsolute(href, currentDirectory, '.');

        // Resolve file
        href = resolveFile(href);

        // Convert back to relative
        href = LocationUtils.relative(currentDirectory, href);

        $a.attr('href', href);
    });
}

module.exports = resolveLinks;
<|endoftext|>


<commit_before>Submissions = new Mongo.Collection("submissions");

Submissions.allow({
  insert: function(userId, submission) {
    var requiredPrompts = Prompts.find({$and: [{required: true}, {deleted: {$not: {$eq: true}}}]}).fetch();

    var areAnyRequiredPromptsNotRespondedTo = requiredPrompts.some(function(prompt) {
      return !submission.responses.some(function(response) {
          return ((response.promptId === prompt._id) && (response.text != ''));
        })
    });

    return !areAnyRequiredPromptsNotRespondedTo;
  },

  update: function(userId) {
    return User.isOwnersUserId(userId);
  },

  remove: function(userId) {
    return User.isOwnersUserId(userId);
  }
});

if (Meteor.isServer) {
  Meteor.publish("submissions", function() {
    if (this.userId === null) return [];
    var isOwner = User.isOwnersUserId(this.userId);

    if (isOwner) {
      return Submissions.find({});
    } else {
      return [];
    }
  });
}

Submissions.inTableFormat = function(prompts) {
  return Submissions.find().map(function(submission) {
    var responses = prompts.map(function(prompt) {
      var responseForPrompt = _(submission.responses).find(function(response) {
        return response.promptId === prompt._id;
      });

      if (responseForPrompt === undefined) {
        return '';
      } else {
        return responseForPrompt.response;
      }
    });

    return responses;
  });
}

Submissions.exportCsvFormattedString = function() {
  var csvFormattedResponses = '',
    headerText = [],
    prompts = Prompts.inOrder();

  prompts.forEach(function(prompt) {
    headerText.push(prompt.text);
  });

  Submissions.inTableFormat(prompts).forEach(function(submission) {
    csvFormattedResponses += submission.join(',') + '\r\n';
  });

  return headerText.join(',') + '\r\n' + csvFormattedResponses;
};<commit_msg>Modify Submissions required-prompts test not to use too-new Mongo EQ operator (which is not available in vagrant-spk mongo)<commit_after>Submissions = new Mongo.Collection("submissions");

Submissions.allow({
  insert: function(userId, submission) {
    var requiredPrompts = Prompts.find({$and: [{required: true}, {deleted: {$ne: true}}]}).fetch();

    var areAnyRequiredPromptsNotRespondedTo = requiredPrompts.some(function(prompt) {
      return !submission.responses.some(function(response) {
          return ((response.promptId === prompt._id) && (response.text != ''));
        })
    });

    return !areAnyRequiredPromptsNotRespondedTo;
  },

  update: function(userId) {
    return User.isOwnersUserId(userId);
  },

  remove: function(userId) {
    return User.isOwnersUserId(userId);
  }
});

if (Meteor.isServer) {
  Meteor.publish("submissions", function() {
    if (this.userId === null) return [];
    var isOwner = User.isOwnersUserId(this.userId);

    if (isOwner) {
      return Submissions.find({});
    } else {
      return [];
    }
  });
}

Submissions.inTableFormat = function(prompts) {
  return Submissions.find().map(function(submission) {
    var responses = prompts.map(function(prompt) {
      var responseForPrompt = _(submission.responses).find(function(response) {
        return response.promptId === prompt._id;
      });

      if (responseForPrompt === undefined) {
        return '';
      } else {
        return responseForPrompt.response;
      }
    });

    return responses;
  });
}

Submissions.exportCsvFormattedString = function() {
  var csvFormattedResponses = '',
    headerText = [],
    prompts = Prompts.inOrder();

  prompts.forEach(function(prompt) {
    headerText.push(prompt.text);
  });

  Submissions.inTableFormat(prompts).forEach(function(submission) {
    csvFormattedResponses += submission.join(',') + '\r\n';
  });

  return headerText.join(',') + '\r\n' + csvFormattedResponses;
};<|endoftext|>


<commit_before>/* @flow strict-local */
import { DeviceEventEmitter, NativeModules, Platform, PushNotificationIOS } from 'react-native';
import NotificationsIOS from 'react-native-notifications';

import type { Auth, Dispatch, Identity, Narrow, User } from '../types';
import { topicNarrow, privateNarrow, groupNarrow } from '../utils/narrow';
import * as api from '../api';
import * as logging from '../utils/logging';
import {
  unackPushToken,
  gotPushToken,
  sendAllPushToken,
  narrowToNotification,
} from './notificationActions';
import { identityOfAuth } from '../account/accountMisc';

/**
 * The data we need in JS/React code for acting on a notification.
 *
 * The actual objects we describe with these types may have a bunch more
 * fields.  So, properly, these object types aren't really exact.  But
 * pretending they are seems to be the least unpleasant way to get Flow to
 * recognize the effect of refinements like `data.pm_users === undefined`.
 *
 * Currently:
 *
 *  * On iOS, these objects are translated directly, field by field, from
 *    the blobs of key/value pairs sent by the Zulip server in the
 *    "payload".  The set of fields therefore varies by server version.
 *
 *  * On Android, our platform-native code constructs the exact data to
 *    send; see `MessageFcmMessage#dataForOpen` in `FcmMessage.kt`.
 *    That should be kept in sync with this type definition, in which case
 *    these types really are exact.
 */
export type Notification =
  | {| recipient_type: 'stream', stream: string, topic: string, realm_uri?: string |}
  // Group PM messages have `pm_users`, which is comma-separated IDs.
  | {| recipient_type: 'private', pm_users: string, realm_uri?: string |}
  // 1:1 PM messages lack `pm_users`.
  | {| recipient_type: 'private', sender_email: string, realm_uri?: string |};

/**
 * Identify the account the notification is for, if possible.
 *
 * Returns an index into `identities`, or `null` if we can't tell.
 *
 * @param identities Identities corresponding to the accounts state in Redux.
 */
export const getAccountFromNotificationData = (
  data: Notification,
  identities: $ReadOnlyArray<Identity>,
): number | null => {
  const { realm_uri } = data;
  if (realm_uri == null) {
    // Old server, no realm info included.  If needed to cater to 1.8.x
    // servers, could try to guess using serverHost; for now, don't.
    logging.warn('notification missing field: realm_uri');
    return null;
  }

  const urlMatches = [];
  identities.forEach((account, i) => {
    if (account.realm === realm_uri) {
      urlMatches.push(i);
    }
  });
  if (urlMatches.length === 0) {
    // No match.  Either we logged out of this account and didn't
    // successfully tell the server to stop sending notifications (possibly
    // just a race -- this notification was sent before the logout); or
    // there's some confusion where the realm_uri we have is different from
    // the one the server sends in notifications.
    const knownUrls = identities.map(({ realm }) => realm);
    logging.warn(
      `notif realm_uri not found: ${realm_uri}, known values are ${JSON.stringify(knownUrls)}`,
    );
    return null;
  }

  if (urlMatches.length > 1) {
    // The user has several accounts in the notification's realm.  We should
    // be able to tell the right one using the notification's `user_id`...
    // except we don't store user IDs in `accounts`, only emails.  Until we
    // fix that, just ignore the information.
    logging.warn(`notif realm_uri ambiguous: ${urlMatches.length} matches for ${realm_uri}`);
    // TODO get user_id into accounts data, and use that
    return null;
  }

  return urlMatches[0];
};

export const getNarrowFromNotificationData = (
  data: Notification,
  usersById: Map<number, User>,
): Narrow | null => {
  if (!data.recipient_type) {
    // This condition is impossible if the value is rightly-typed; but in
    // the iOS case it comes more or less unfiltered from the Zulip server,
    // so we check here.
    //
    // TODO check further upstream instead, at a "crunchy shell".
    return null;
  }

  if (data.recipient_type === 'stream') {
    return topicNarrow(data.stream, data.topic);
  }

  if (data.pm_users === undefined) {
    return privateNarrow(data.sender_email);
  }

  const emails = [];
  const idStrs = data.pm_users.split(',');
  for (let i = 0; i < idStrs.length; ++i) {
    const user = usersById.get(+idStrs[i]);
    if (!user) {
      return null;
    }
    emails.push(user.email);
  }
  return groupNarrow(emails);
};

type NotificationMuddle =
  | Notification
  | null
  | void
  | { getData: () => Notification | { zulip: Notification } };

/** Extract the actual notification data from the wix library's wrapping (iOS only). */
// exported for tests
export const extractNotificationData = (notification: NotificationMuddle): Notification | null => {
  if (!notification || !notification.getData) {
    return null;
  }
  const data = notification.getData();
  return data && data.zulip ? data.zulip : data;
};

const getInitialNotification = async (): Promise<Notification | null> => {
  if (Platform.OS === 'android') {
    const { Notifications } = NativeModules;
    return Notifications.getInitialNotification();
  }
  const notification = await PushNotificationIOS.getInitialNotification();
  // $FlowFixMe Upstream's libdef for getInitialNotification has `Object` types.
  return extractNotificationData(notification);
};

export const handleInitialNotification = async (dispatch: Dispatch) => {
  const data = await getInitialNotification();
  dispatch(narrowToNotification(data));
};

/**
 * Listens for notification-related events.
 *
 * An instance of this doesn't affect the subscriptions of any other
 * instance, or anything else.
 */
export class NotificationListener {
  dispatch: Dispatch;
  unsubs: Array<() => void> = [];

  constructor(dispatch: Dispatch) {
    this.dispatch = dispatch;
  }

  /** Private. */
  listen(name: string, handler: (...empty) => void | Promise<void>) {
    if (Platform.OS === 'ios') {
      NotificationsIOS.addEventListener(name, handler);
      this.unsubs.push(() => NotificationsIOS.removeEventListener(name, handler));
    }
    const subscription = DeviceEventEmitter.addListener(name, handler);
    this.unsubs.push(() => subscription.remove());
  }

  /** Private. */
  unlistenAll() {
    while (this.unsubs.length > 0) {
      this.unsubs.pop()();
    }
  }

  /** Private. */
  handleNotificationOpen = (notification: NotificationMuddle) => {
    const data: Notification =
      // $FlowFixMe clarify notification types
      Platform.OS === 'ios' ? extractNotificationData(notification) : notification;
    this.dispatch(narrowToNotification(data));
  };

  /** Private. */
  handleDeviceToken = async (deviceToken: string) => {
    this.dispatch(gotPushToken(deviceToken));
    await this.dispatch(sendAllPushToken());
  };

  /** Start listening.  Don't call twice without intervening `stop`. */
  start() {
    this.listen('notificationOpened', this.handleNotificationOpen);
    this.listen('remoteNotificationsRegistered', this.handleDeviceToken);
    if (Platform.OS === 'ios') {
      this.listen('remoteNotificationsRegistrationFailed', (errMsg: string) => {
        logging.warn(`Failed to register iOS push token: ${errMsg}`);
      });
    }
  }

  /** Stop listening. */
  stop() {
    this.unlistenAll();
  }
}

/** Try to cause a `remoteNotificationsRegistered` event. */
export const getNotificationToken = () => {
  if (Platform.OS === 'ios') {
    // This leads to a call (in wix's, or RN upstream's, NotificationsIOS) to this:
    //   https://developer.apple.com/documentation/uikit/uiapplication/1622932-registerusernotificationsettings
    // (deprecated after iOS 10, yikes!); which after possibly prompting the
    // user causes "the app" (i.e. the platform part) to call this, I think,
    // though I haven't successfully traced all the steps there:
    //   https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623022-application?language=objc
    // which certainly in the wix case leads to a call to this:
    //   https://developer.apple.com/documentation/uikit/uiapplication/1623078-registerforremotenotifications
    // which "initiate[s] the registration process with [APNs]".  Then the
    // methods that calls on success/failure in turn are implemented to send
    // an event with name `remoteNotificationsRegistered` etc., in both the
    // wix and RN-upstream case.  (Though NB in the *failure* case, the
    // event names differ!  wix has s/Error/Failed/ vs. upstream; also
    // upstream has singular for failure although plural for success, ouch.)
    //
    // In short, this kicks off a sequence: permissions -> "register" ->
    // send event we already have a global listener for.  And the first two
    // steps satisfy the stern warnings in Apple's docs (at the above links)
    // to request permissions first, then "register".
    NotificationsIOS.requestPermissions();
  } else {
    // On Android, we do this at application startup.
  }
};

export const tryStopNotifications = async (
  auth: Auth,
  token: string | null,
  dispatch: Dispatch,
) => {
  if (token !== null) {
    dispatch(unackPushToken(identityOfAuth(auth)));
    try {
      await api.forgetPushToken(auth, Platform.OS, token);
    } catch (e) {
      logging.warn(e);
    }
  }
};
<commit_msg>notifications: fix type of registration failure event<commit_after>/* @flow strict-local */
import { DeviceEventEmitter, NativeModules, Platform, PushNotificationIOS } from 'react-native';
import NotificationsIOS from 'react-native-notifications';

import type { Auth, Dispatch, Identity, Narrow, User } from '../types';
import { topicNarrow, privateNarrow, groupNarrow } from '../utils/narrow';
import * as api from '../api';
import * as logging from '../utils/logging';
import {
  unackPushToken,
  gotPushToken,
  sendAllPushToken,
  narrowToNotification,
} from './notificationActions';
import { identityOfAuth } from '../account/accountMisc';

/**
 * The data we need in JS/React code for acting on a notification.
 *
 * The actual objects we describe with these types may have a bunch more
 * fields.  So, properly, these object types aren't really exact.  But
 * pretending they are seems to be the least unpleasant way to get Flow to
 * recognize the effect of refinements like `data.pm_users === undefined`.
 *
 * Currently:
 *
 *  * On iOS, these objects are translated directly, field by field, from
 *    the blobs of key/value pairs sent by the Zulip server in the
 *    "payload".  The set of fields therefore varies by server version.
 *
 *  * On Android, our platform-native code constructs the exact data to
 *    send; see `MessageFcmMessage#dataForOpen` in `FcmMessage.kt`.
 *    That should be kept in sync with this type definition, in which case
 *    these types really are exact.
 */
export type Notification =
  | {| recipient_type: 'stream', stream: string, topic: string, realm_uri?: string |}
  // Group PM messages have `pm_users`, which is comma-separated IDs.
  | {| recipient_type: 'private', pm_users: string, realm_uri?: string |}
  // 1:1 PM messages lack `pm_users`.
  | {| recipient_type: 'private', sender_email: string, realm_uri?: string |};

/**
 * Identify the account the notification is for, if possible.
 *
 * Returns an index into `identities`, or `null` if we can't tell.
 *
 * @param identities Identities corresponding to the accounts state in Redux.
 */
export const getAccountFromNotificationData = (
  data: Notification,
  identities: $ReadOnlyArray<Identity>,
): number | null => {
  const { realm_uri } = data;
  if (realm_uri == null) {
    // Old server, no realm info included.  If needed to cater to 1.8.x
    // servers, could try to guess using serverHost; for now, don't.
    logging.warn('notification missing field: realm_uri');
    return null;
  }

  const urlMatches = [];
  identities.forEach((account, i) => {
    if (account.realm === realm_uri) {
      urlMatches.push(i);
    }
  });
  if (urlMatches.length === 0) {
    // No match.  Either we logged out of this account and didn't
    // successfully tell the server to stop sending notifications (possibly
    // just a race -- this notification was sent before the logout); or
    // there's some confusion where the realm_uri we have is different from
    // the one the server sends in notifications.
    const knownUrls = identities.map(({ realm }) => realm);
    logging.warn(
      `notif realm_uri not found: ${realm_uri}, known values are ${JSON.stringify(knownUrls)}`,
    );
    return null;
  }

  if (urlMatches.length > 1) {
    // The user has several accounts in the notification's realm.  We should
    // be able to tell the right one using the notification's `user_id`...
    // except we don't store user IDs in `accounts`, only emails.  Until we
    // fix that, just ignore the information.
    logging.warn(`notif realm_uri ambiguous: ${urlMatches.length} matches for ${realm_uri}`);
    // TODO get user_id into accounts data, and use that
    return null;
  }

  return urlMatches[0];
};

export const getNarrowFromNotificationData = (
  data: Notification,
  usersById: Map<number, User>,
): Narrow | null => {
  if (!data.recipient_type) {
    // This condition is impossible if the value is rightly-typed; but in
    // the iOS case it comes more or less unfiltered from the Zulip server,
    // so we check here.
    //
    // TODO check further upstream instead, at a "crunchy shell".
    return null;
  }

  if (data.recipient_type === 'stream') {
    return topicNarrow(data.stream, data.topic);
  }

  if (data.pm_users === undefined) {
    return privateNarrow(data.sender_email);
  }

  const emails = [];
  const idStrs = data.pm_users.split(',');
  for (let i = 0; i < idStrs.length; ++i) {
    const user = usersById.get(+idStrs[i]);
    if (!user) {
      return null;
    }
    emails.push(user.email);
  }
  return groupNarrow(emails);
};

type NotificationMuddle =
  | Notification
  | null
  | void
  | { getData: () => Notification | { zulip: Notification } };

/** Extract the actual notification data from the wix library's wrapping (iOS only). */
// exported for tests
export const extractNotificationData = (notification: NotificationMuddle): Notification | null => {
  if (!notification || !notification.getData) {
    return null;
  }
  const data = notification.getData();
  return data && data.zulip ? data.zulip : data;
};

const getInitialNotification = async (): Promise<Notification | null> => {
  if (Platform.OS === 'android') {
    const { Notifications } = NativeModules;
    return Notifications.getInitialNotification();
  }
  const notification = await PushNotificationIOS.getInitialNotification();
  // $FlowFixMe Upstream's libdef for getInitialNotification has `Object` types.
  return extractNotificationData(notification);
};

export const handleInitialNotification = async (dispatch: Dispatch) => {
  const data = await getInitialNotification();
  dispatch(narrowToNotification(data));
};

/**
 * From rn-notifications@1.5.0's RNNotifications.m.
 */
type NotificationRegistrationFailedEvent = {|
  domain: string, // e.g. 'NSCocoaErrorDomain'
  code: number,
  localizedDescription: string,
|};

/**
 * Listens for notification-related events.
 *
 * An instance of this doesn't affect the subscriptions of any other
 * instance, or anything else.
 */
export class NotificationListener {
  dispatch: Dispatch;
  unsubs: Array<() => void> = [];

  constructor(dispatch: Dispatch) {
    this.dispatch = dispatch;
  }

  /** Private. */
  listen(name: string, handler: (...empty) => void | Promise<void>) {
    if (Platform.OS === 'ios') {
      NotificationsIOS.addEventListener(name, handler);
      this.unsubs.push(() => NotificationsIOS.removeEventListener(name, handler));
    }
    const subscription = DeviceEventEmitter.addListener(name, handler);
    this.unsubs.push(() => subscription.remove());
  }

  /** Private. */
  unlistenAll() {
    while (this.unsubs.length > 0) {
      this.unsubs.pop()();
    }
  }

  /** Private. */
  handleNotificationOpen = (notification: NotificationMuddle) => {
    const data: Notification =
      // $FlowFixMe clarify notification types
      Platform.OS === 'ios' ? extractNotificationData(notification) : notification;
    this.dispatch(narrowToNotification(data));
  };

  /** Private. */
  handleDeviceToken = async (deviceToken: string) => {
    this.dispatch(gotPushToken(deviceToken));
    await this.dispatch(sendAllPushToken());
  };

  /** Private. */
  handleRegistrationFailure = (err: NotificationRegistrationFailedEvent) => {
    logging.warn(`Failed to register iOS push token: ${JSON.stringify(err)}`);
  };

  /** Start listening.  Don't call twice without intervening `stop`. */
  start() {
    this.listen('notificationOpened', this.handleNotificationOpen);
    this.listen('remoteNotificationsRegistered', this.handleDeviceToken);
    if (Platform.OS === 'ios') {
      this.listen('remoteNotificationsRegistrationFailed', this.handleRegistrationFailure);
    }
  }

  /** Stop listening. */
  stop() {
    this.unlistenAll();
  }
}

/** Try to cause a `remoteNotificationsRegistered` event. */
export const getNotificationToken = () => {
  if (Platform.OS === 'ios') {
    // This leads to a call (in wix's, or RN upstream's, NotificationsIOS) to this:
    //   https://developer.apple.com/documentation/uikit/uiapplication/1622932-registerusernotificationsettings
    // (deprecated after iOS 10, yikes!); which after possibly prompting the
    // user causes "the app" (i.e. the platform part) to call this, I think,
    // though I haven't successfully traced all the steps there:
    //   https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623022-application?language=objc
    // which certainly in the wix case leads to a call to this:
    //   https://developer.apple.com/documentation/uikit/uiapplication/1623078-registerforremotenotifications
    // which "initiate[s] the registration process with [APNs]".  Then the
    // methods that calls on success/failure in turn are implemented to send
    // an event with name `remoteNotificationsRegistered` etc., in both the
    // wix and RN-upstream case.  (Though NB in the *failure* case, the
    // event names differ!  wix has s/Error/Failed/ vs. upstream; also
    // upstream has singular for failure although plural for success, ouch.)
    //
    // In short, this kicks off a sequence: permissions -> "register" ->
    // send event we already have a global listener for.  And the first two
    // steps satisfy the stern warnings in Apple's docs (at the above links)
    // to request permissions first, then "register".
    NotificationsIOS.requestPermissions();
  } else {
    // On Android, we do this at application startup.
  }
};

export const tryStopNotifications = async (
  auth: Auth,
  token: string | null,
  dispatch: Dispatch,
) => {
  if (token !== null) {
    dispatch(unackPushToken(identityOfAuth(auth)));
    try {
      await api.forgetPushToken(auth, Platform.OS, token);
    } catch (e) {
      logging.warn(e);
    }
  }
};
<|endoftext|>


<commit_before>(function() {

/**
 * Models collection
 * @namespace
 * @constructor
 * @augments ns.Model
 */
ns.ModelCollection = function() {};

no.inherit(ns.ModelCollection, ns.Model);

ns.ModelCollection.prototype.getData = function() {
    // это составная модель —
    // нужно склеить все данные
    // из моделей её состовляющих
    if ( this.isValid() ) {
        var items = [];
        if (this.info.split) {
            // массив с хранилищем данных моделей
            items = no.jpath(this.info.split.items, this.data);
            // удаляем все старые данные, но оставляем массив, чтобы сохранить ссылку
            items.splice(0, items.length);
        }

        // пишем новые
        this.models.forEach(function(model) {
            items.push( model.getData() );
        });
    }

    return this.data;
};

ns.ModelCollection.prototype._reset = function() {
    ns.Model.prototype._reset.apply(this, arguments);
    this.clear();
};

/**
 * Регистрирует обработчики событий.
 * @private
 */
ns.ModelCollection.prototype._bindEvents = function() {
    ns.Model.prototype._bindEvents.apply(this, arguments);

    // При уничтожении вложенной модели коллекция выносит останки.
    this.on('ns-model-destroyed', function(e, data) {
        if (data && data.model) {
            this.remove(data.model);
        }
    });
};

/**
 * Разбивает данные через jpath описанный в info.split
 * на составные модели
 */
ns.ModelCollection.prototype._setData = function(data) {
    this.clear();

    var info = this.info.split;
    var models = data;
    if (info) {
        var items = no.jpath(info.items, data);
        models = this._splitModels(items);
    }

    // если модели не удалось засплитить попробуем вставить данные,
    // может в них есть модели
    this.insert(models);

    return data;
};

/**
 * Создает модели из разбитых данных
 *
 * @param { Array <JSON>} items – массив данных для будущих подмоделей
 * @return { Array <ns.Model> } – массив полученных подмоделей
 */
ns.ModelCollection.prototype._splitModels = function(items) {
    var info = this.info.split;
    var that = this;

    var models = [];

    items.forEach(function(item) {

        var params = {};
        for (var key in info.params) {
            params[key] = no.jpath(info.params[key], item);
        }

        // идентификатор подмодели берется из info.model_id
        // он коллецкия может содержать модели только одного вида
        var model = ns.Model.get(info.model_id, params).setData(item);

        // при обновлении timestamp внутренней модели обновим и у внешней
        model.on('ns-model-touched', function() {
            that.timestamp = this.timestamp;
        });

        models.push(model);
    });

    return models;
};

/**
 * Подписывает коллекию на события из подмоделей
 *
 * @param {ns.Model} model
 */
ns.ModelCollection.prototype._subscribeSplit = function(model) {

    // добавим нашу коллекцию с список слушающих коллекций для подмодели
    model.eventListeners[this.key] = this;

    // если ранее мы не переопределяли прототипный тригер
    // то переопеделим его
    if (ns.Model.prototype.trigger === model.trigger) {
        model.trigger = function(evt, data) {
            ns.Model.prototype.trigger.call(this, evt, data);

            var collData = {
                data: data,
                model: this
            };

            for (var key in this.eventListeners) {
                var collection = this.eventListeners[key];
                if (collection) {
                    collection.trigger(evt, collData);
                }
            }
        };
    }
};

/**
 * Обновляет timestamp'ы модели
 */
ns.ModelCollection.prototype.touch = function() {
    ns.Model.prototype.touch.apply(this, arguments);
    // timestampSelf показывает последнее время изменения внешней модели
    // в то время, как timestamp - последнее время изменения внешней или внутренней модели
    this.timestampSelf = this.timestamp;
};

/**
 * Удаляет подписку коллекции на подмодель
 * Обязательно при удалении подмодели из коллекции
 *
 * @param {ns.Model} model
 */
ns.ModelCollection.prototype._unsubscribeSplit = function(model) {
    delete model.eventListeners[this.key];
};

/**
 * Удаляет все подмодели из коллекции
 * С каждой модели происходит снятие подписки
 */
ns.ModelCollection.prototype.clear = function() {

    this.models = this.models || [];

    this.models.forEach(
        this._unsubscribeSplit.bind(this)
    );

    this.models = [];
};

/**
 * Вставляет подмодели в коллекцию
 *
 * @param {Array <ns.Model> | ns.Model} models – одна или несколько подмоделей
 *                                                 для вставки
 * @param {Number} [index] – индекс позиции, на которую вставить подмодели
 *
 * @return {Boolean} – признак успешности вставки
 */
ns.ModelCollection.prototype.insert = function(models, index) {

    index = isNaN(index) ? this.models.length : index;

    // если переданная модель не массив
    if (!(models instanceof Array)) {
        // обернем ее в массив
        models = [models];
    }

    models.forEach(function(model) {
        // вставить можем только модель
        if (ns.Model.info(model.id)) {
            this.models.splice(index, 0, model);
            index++;

            // не забудем подписаться на события
            this._subscribeSplit(model);
        }
    }.bind(this));

    // оповестим всех, что вставили подмодели
    if (models.length) {
        this.trigger('ns-model-insert', models);
    }

    return true;
};

/**
 * Удаляет подмодель из коллекции
 *
 * @param {ns.Model | Number | Array<ns.Model | Number>} models – подмодели или индексы подмодели, которую надо удалить
 * @return {Boolean} – признак успешности удаления
 */
ns.ModelCollection.prototype.remove = function(models) {

    var modelsRemoved = [];

    if (!(models instanceof Array)) {
        models = [models];
    }

    models.forEach(function(modelOrIndex) {
        var index;

        if (isNaN(modelOrIndex)) {
            index = this.models.indexOf(modelOrIndex);
        } else {
            index = modelOrIndex;
        }

        if (index >= 0) {
            var model = this.models[index];

            // не забудем отписаться от событий подмодели
            this._unsubscribeSplit(model);
            modelsRemoved.push(model);
            this.models.splice(index, 1);
        }

    }.bind(this));

    if (modelsRemoved.length) {
        this.trigger('ns-model-remove', modelsRemoved);
        return true;
    }

    return false;
};

})();
<commit_msg>важный комментарий<commit_after>(function() {

/**
 * Models collection
 * @namespace
 * @constructor
 * @augments ns.Model
 */
ns.ModelCollection = function() {};

no.inherit(ns.ModelCollection, ns.Model);

ns.ModelCollection.prototype.getData = function() {
    // это составная модель —
    // нужно склеить все данные
    // из моделей её состовляющих
    if ( this.isValid() ) {
        var items = [];
        if (this.info.split) {
            // массив с хранилищем данных моделей
            items = no.jpath(this.info.split.items, this.data);
            // удаляем все старые данные, но оставляем массив, чтобы сохранить ссылку
            items.splice(0, items.length);
        }

        // пишем новые
        this.models.forEach(function(model) {
            items.push( model.getData() );
        });
    }

    return this.data;
};

ns.ModelCollection.prototype._reset = function() {
    ns.Model.prototype._reset.apply(this, arguments);
    this.clear();
};

/**
 * Регистрирует обработчики событий.
 * @private
 */
ns.ModelCollection.prototype._bindEvents = function() {
    ns.Model.prototype._bindEvents.apply(this, arguments);

    // При уничтожении вложенной модели коллекция выносит останки.
    this.on('ns-model-destroyed', function(e, data) {
        // Убедимся, что удалился именно элемент коллекции, а не сама коллекция.
        // Пока это возможно только по наличию data.model в аргументах
        if (data && data.model) {
            this.remove(data.model);
        }
    });
};

/**
 * Разбивает данные через jpath описанный в info.split
 * на составные модели
 */
ns.ModelCollection.prototype._setData = function(data) {
    this.clear();

    var info = this.info.split;
    var models = data;
    if (info) {
        var items = no.jpath(info.items, data);
        models = this._splitModels(items);
    }

    // если модели не удалось засплитить попробуем вставить данные,
    // может в них есть модели
    this.insert(models);

    return data;
};

/**
 * Создает модели из разбитых данных
 *
 * @param { Array <JSON>} items – массив данных для будущих подмоделей
 * @return { Array <ns.Model> } – массив полученных подмоделей
 */
ns.ModelCollection.prototype._splitModels = function(items) {
    var info = this.info.split;
    var that = this;

    var models = [];

    items.forEach(function(item) {

        var params = {};
        for (var key in info.params) {
            params[key] = no.jpath(info.params[key], item);
        }

        // идентификатор подмодели берется из info.model_id
        // он коллецкия может содержать модели только одного вида
        var model = ns.Model.get(info.model_id, params).setData(item);

        // при обновлении timestamp внутренней модели обновим и у внешней
        model.on('ns-model-touched', function() {
            that.timestamp = this.timestamp;
        });

        models.push(model);
    });

    return models;
};

/**
 * Подписывает коллекию на события из подмоделей
 *
 * @param {ns.Model} model
 */
ns.ModelCollection.prototype._subscribeSplit = function(model) {

    // добавим нашу коллекцию с список слушающих коллекций для подмодели
    model.eventListeners[this.key] = this;

    // если ранее мы не переопределяли прототипный тригер
    // то переопеделим его
    if (ns.Model.prototype.trigger === model.trigger) {
        model.trigger = function(evt, data) {
            ns.Model.prototype.trigger.call(this, evt, data);

            var collData = {
                data: data,
                model: this
            };

            for (var key in this.eventListeners) {
                var collection = this.eventListeners[key];
                if (collection) {
                    collection.trigger(evt, collData);
                }
            }
        };
    }
};

/**
 * Обновляет timestamp'ы модели
 */
ns.ModelCollection.prototype.touch = function() {
    ns.Model.prototype.touch.apply(this, arguments);
    // timestampSelf показывает последнее время изменения внешней модели
    // в то время, как timestamp - последнее время изменения внешней или внутренней модели
    this.timestampSelf = this.timestamp;
};

/**
 * Удаляет подписку коллекции на подмодель
 * Обязательно при удалении подмодели из коллекции
 *
 * @param {ns.Model} model
 */
ns.ModelCollection.prototype._unsubscribeSplit = function(model) {
    delete model.eventListeners[this.key];
};

/**
 * Удаляет все подмодели из коллекции
 * С каждой модели происходит снятие подписки
 */
ns.ModelCollection.prototype.clear = function() {

    this.models = this.models || [];

    this.models.forEach(
        this._unsubscribeSplit.bind(this)
    );

    this.models = [];
};

/**
 * Вставляет подмодели в коллекцию
 *
 * @param {Array <ns.Model> | ns.Model} models – одна или несколько подмоделей
 *                                                 для вставки
 * @param {Number} [index] – индекс позиции, на которую вставить подмодели
 *
 * @return {Boolean} – признак успешности вставки
 */
ns.ModelCollection.prototype.insert = function(models, index) {

    index = isNaN(index) ? this.models.length : index;

    // если переданная модель не массив
    if (!(models instanceof Array)) {
        // обернем ее в массив
        models = [models];
    }

    models.forEach(function(model) {
        // вставить можем только модель
        if (ns.Model.info(model.id)) {
            this.models.splice(index, 0, model);
            index++;

            // не забудем подписаться на события
            this._subscribeSplit(model);
        }
    }.bind(this));

    // оповестим всех, что вставили подмодели
    if (models.length) {
        this.trigger('ns-model-insert', models);
    }

    return true;
};

/**
 * Удаляет подмодель из коллекции
 *
 * @param {ns.Model | Number | Array<ns.Model | Number>} models – подмодели или индексы подмодели, которую надо удалить
 * @return {Boolean} – признак успешности удаления
 */
ns.ModelCollection.prototype.remove = function(models) {

    var modelsRemoved = [];

    if (!(models instanceof Array)) {
        models = [models];
    }

    models.forEach(function(modelOrIndex) {
        var index;

        if (isNaN(modelOrIndex)) {
            index = this.models.indexOf(modelOrIndex);
        } else {
            index = modelOrIndex;
        }

        if (index >= 0) {
            var model = this.models[index];

            // не забудем отписаться от событий подмодели
            this._unsubscribeSplit(model);
            modelsRemoved.push(model);
            this.models.splice(index, 1);
        }

    }.bind(this));

    if (modelsRemoved.length) {
        this.trigger('ns-model-remove', modelsRemoved);
        return true;
    }

    return false;
};

})();
<|endoftext|>