<commit_before>define(function(require) {

    var formatUtil = require('./format');
    var nubmerUtil = require('./number');
    var Model = require('../model/Model');
    var zrUtil = require('zrender/core/util');

    var modelUtil = {};

    /**
     * If value is not array, then translate it to array.
     * @param  {*} value
     * @return {Array} [value] or value
     */
    modelUtil.normalizeToArray = function (value) {
        return value instanceof Array
            ? value
            : value == null
            ? []
            : [value];
    };

    /**
     * Sync default option between normal and emphasis like `position` and `show`
     * In case some one will write code like
     *     label: {
     *         normal: {
     *             show: false,
     *             position: 'outside',
     *             textStyle: {
     *                 fontSize: 18
     *             }
     *         },
     *         emphasis: {
     *             show: true
     *         }
     *     }
     * @param {Object} opt
     * @param {Array.<string>} subOpts
     */
     modelUtil.defaultEmphasis = function (opt, subOpts) {
        if (opt) {
            var emphasisOpt = opt.emphasis = opt.emphasis || {};
            var normalOpt = opt.normal = opt.normal || {};

            // Default emphasis option from normal
            zrUtil.each(subOpts, function (subOptName) {
                var val = zrUtil.retrieve(emphasisOpt[subOptName], normalOpt[subOptName]);
                if (val != null) {
                    emphasisOpt[subOptName] = val;
                }
            });
        }
    };

    modelUtil.LABEL_OPTIONS = ['position', 'show', 'textStyle', 'distance', 'formatter'];

    /**
     * data could be [12, 2323, {value: 223}, [1221, 23], {value: [2, 23]}]
     * This helper method retieves value from data.
     * @param {string|number|Date|Array|Object} dataItem
     * @return {number|string|Date|Array.<number|string|Date>}
     */
    modelUtil.getDataItemValue = function (dataItem) {
        // Performance sensitive.
        return dataItem && (dataItem.value == null ? dataItem : dataItem.value);
    };

    /**
     * data could be [12, 2323, {value: 223}, [1221, 23], {value: [2, 23]}]
     * This helper method determine if dataItem has extra option besides value
     * @param {string|number|Date|Array|Object} dataItem
     */
    modelUtil.isDataItemOption = function (dataItem) {
        return zrUtil.isObject(dataItem)
            && !(dataItem instanceof Array);
            // // markLine data can be array
            // && !(dataItem[0] && zrUtil.isObject(dataItem[0]) && !(dataItem[0] instanceof Array));
    };

    /**
     * This helper method convert value in data.
     * @param {string|number|Date} value
     * @param {Object|string} [dimInfo] If string (like 'x'), dimType defaults 'number'.
     */
    modelUtil.converDataValue = function (value, dimInfo) {
        // Performance sensitive.
        var dimType = dimInfo && dimInfo.type;
        if (dimType === 'ordinal') {
            return value;
        }

        if (dimType === 'time' && !isFinite(value) && value != null && value !== '-') {
            value = +nubmerUtil.parseDate(value);
        }

        // dimType defaults 'number'.
        // If dimType is not ordinal and value is null or undefined or NaN or '-',
        // parse to NaN.
        return (value == null || value === '')
            ? NaN : +value; // If string (like '-'), using '+' parse to NaN
    };

    /**
     * Create a model proxy to be used in tooltip for edge data, markLine data, markPoint data.
     * @param {module:echarts/data/List} data
     * @param {Object} opt
     * @param {string} [opt.seriesIndex]
     * @param {Object} [opt.name]
     * @param {Object} [opt.mainType]
     * @param {Object} [opt.subType]
     */
    modelUtil.createDataFormatModel = function (data, opt) {
        var model = new Model();
        zrUtil.mixin(model, modelUtil.dataFormatMixin);
        model.seriesIndex = opt.seriesIndex;
        model.name = opt.name || '';
        model.mainType = opt.mainType;
        model.subType = opt.subType;

        model.getData = function () {
            return data;
        };
        return model;
    };

    // PENDING A little ugly
    modelUtil.dataFormatMixin = {
        /**
         * Get params for formatter
         * @param {number} dataIndex
         * @param {string} [dataType]
         * @return {Object}
         */
        getDataParams: function (dataIndex, dataType) {
            var data = this.getData(dataType);

            var seriesIndex = this.seriesIndex;
            var seriesName = this.name;

            var rawValue = this.getRawValue(dataIndex, dataType);
            var rawDataIndex = data.getRawIndex(dataIndex);
            var name = data.getName(dataIndex, true);
            var itemOpt = data.getRawDataItem(dataIndex);

            return {
                componentType: this.mainType,
                componentSubType: this.subType,
                seriesType: this.mainType === 'series' ? this.subType : null,
                seriesIndex: seriesIndex,
                seriesName: seriesName,
                name: name,
                dataIndex: rawDataIndex,
                data: itemOpt,
                dataType: dataType,
                value: rawValue,
                color: data.getItemVisual(dataIndex, 'color'),

                // Param name list for mapping `a`, `b`, `c`, `d`, `e`
                $vars: ['seriesName', 'name', 'value']
            };
        },

        /**
         * Format label
         * @param {number} dataIndex
         * @param {string} [status='normal'] 'normal' or 'emphasis'
         * @param {string} [dataType]
         * @param {number} [dimIndex]
         * @return {string}
         */
        getFormattedLabel: function (dataIndex, status, dataType, dimIndex) {
            status = status || 'normal';
            var data = this.getData(dataType);
            var itemModel = data.getItemModel(dataIndex);

            var params = this.getDataParams(dataIndex, dataType);
            if (dimIndex != null && (params.value instanceof Array)) {
                params.value = params.value[dimIndex];
            }

            var formatter = itemModel.get(['label', status, 'formatter']);

            if (typeof formatter === 'function') {
                params.status = status;
                return formatter(params);
            }
            else if (typeof formatter === 'string') {
                return formatUtil.formatTpl(formatter, params);
            }
        },

        /**
         * Get raw value in option
         * @param {number} idx
         * @param {string} [dataType]
         * @return {Object}
         */
        getRawValue: function (idx, dataType) {
            var data = this.getData(dataType);
            var dataItem = data.getRawDataItem(idx);
            if (dataItem != null) {
                return (zrUtil.isObject(dataItem) && !(dataItem instanceof Array))
                    ? dataItem.value : dataItem;
            }
        },

        /**
         * Should be implemented.
         * @param {number} dataIndex
         * @param {boolean} [multipleSeries=false]
         * @param {number} [dataType]
         * @return {string} tooltip string
         */
        formatTooltip: zrUtil.noop
    };

    /**
     * Mapping to exists for merge.
     *
     * @public
     * @param {Array.<Object>|Array.<module:echarts/model/Component>} exists
     * @param {Object|Array.<Object>} newCptOptions
     * @return {Array.<Object>} Result, like [{exist: ..., option: ...}, {}],
     *                          which order is the same as exists.
     */
    modelUtil.mappingToExists = function (exists, newCptOptions) {
        // Mapping by the order by original option (but not order of
        // new option) in merge mode. Because we should ensure
        // some specified index (like xAxisIndex) is consistent with
        // original option, which is easy to understand, espatially in
        // media query. And in most case, merge option is used to
        // update partial option but not be expected to change order.
        newCptOptions = (newCptOptions || []).slice();

        var result = zrUtil.map(exists || [], function (obj, index) {
            return {exist: obj};
        });

        // Mapping by id or name if specified.
        zrUtil.each(newCptOptions, function (cptOption, index) {
            if (!zrUtil.isObject(cptOption)) {
                return;
            }

            // id has highest priority.
            for (var i = 0; i < result.length; i++) {
                if (!result[i].option // Consider name: two map to one.
                    && cptOption.id != null
                    && result[i].exist.id === cptOption.id + ''
                ) {
                    result[i].option = cptOption;
                    newCptOptions[index] = null;
                    return;
                }
            }

            for (var i = 0; i < result.length; i++) {
                var exist = result[i].exist;
                if (!result[i].option // Consider name: two map to one.
                    // Can not match when both ids exist but different.
                    && (exist.id == null || cptOption.id == null)
                    && cptOption.name != null
                    && !modelUtil.isIdInner(cptOption)
                    && !modelUtil.isIdInner(exist)
                    && exist.name === cptOption.name + ''
                ) {
                    result[i].option = cptOption;
                    newCptOptions[index] = null;
                    return;
                }
            }
        });

        // Otherwise mapping by index.
        zrUtil.each(newCptOptions, function (cptOption, index) {
            if (!zrUtil.isObject(cptOption)) {
                return;
            }

            var i = 0;
            for (; i < result.length; i++) {
                var exist = result[i].exist;
                if (!result[i].option
                    && !modelUtil.isIdInner(exist)
                    // Caution:
                    // Do not overwrite id. But name can be overwritten,
                    // because axis use name as 'show label text'.
                    // 'exist' always has id and name and we dont
                    // need to check it.
                    && cptOption.id == null
                ) {
                    result[i].option = cptOption;
                    break;
                }
            }

            if (i >= result.length) {
                result.push({option: cptOption});
            }
        });

        return result;
    };

    /**
     * @public
     * @param {Object} cptOption
     * @return {boolean}
     */
    modelUtil.isIdInner = function (cptOption) {
        return zrUtil.isObject(cptOption)
            && cptOption.id
            && (cptOption.id + '').indexOf('\0_ec_\0') === 0;
    };

    /**
     * A helper for removing duplicate items between batchA and batchB,
     * and in themselves, and categorize by series.
     *
     * @param {Array.<Object>} batchA Like: [{seriesId: 2, dataIndex: [32, 4, 5]}, ...]
     * @param {Array.<Object>} batchB Like: [{seriesId: 2, dataIndex: [32, 4, 5]}, ...]
     * @return {Array.<Array.<Object>, Array.<Object>>} result: [resultBatchA, resultBatchB]
     */
    modelUtil.compressBatches = function (batchA, batchB) {
        var mapA = {};
        var mapB = {};

        makeMap(batchA || [], mapA);
        makeMap(batchB || [], mapB, mapA);

        return [mapToArray(mapA), mapToArray(mapB)];

        function makeMap(sourceBatch, map, otherMap) {
            for (var i = 0, len = sourceBatch.length; i < len; i++) {
                var seriesId = sourceBatch[i].seriesId;
                var dataIndices = modelUtil.normalizeToArray(sourceBatch[i].dataIndex);
                var otherDataIndices = otherMap && otherMap[seriesId];

                for (var j = 0, lenj = dataIndices.length; j < lenj; j++) {
                    var dataIndex = dataIndices[j];

                    if (otherDataIndices && otherDataIndices[dataIndex]) {
                        otherDataIndices[dataIndex] = null;
                    }
                    else {
                        (map[seriesId] || (map[seriesId] = {}))[dataIndex] = 1;
                    }
                }
            }
        }

        function mapToArray(map, isData) {
            var result = [];
            for (var i in map) {
                if (map.hasOwnProperty(i) && map[i] != null) {
                    if (isData) {
                        result.push(+i);
                    }
                    else {
                        var dataIndices = mapToArray(map[i], true);
                        dataIndices.length && result.push({seriesId: i, dataIndex: dataIndices});
                    }
                }
            }
            return result;
        }
    };

    /**
     * @param {module:echarts/model/Global} ecModel
     * @param {string|Object} finder
     *        If string, e.g., 'geo', means {geoIndex: 0}.
     *        If Object, could contain some of these properties below:
     *        {
     *            seriesIndex, seriesId,
     *            geoIndex, geoId,
     *            bmapIndex, bmapId,
     *            xAxisIndex, xAxisId,
     *            yAxisIndex, yAxisId,
     *            gridIndex, gridId,
     *            ... (can be extended)
     *        }
     * @param {Object} [opt]
     * @param {boolean} [opt.singleResult=false]
     * @return {Object} result like:
     *        {
     *            seriesModel: xxx,
     *            geoModel: xxx
     *            ...
     *        }
     *        If opt.singleResult, result in the first found model.
     */
    modelUtil.parseFinder = function (ecModel, finder, opt) {
        if (zrUtil.isString(finder)) {
            var obj = {};
            obj[finder + 'Index'] = 0;
            finder = obj;
        }

        var result = {};

        for (var key in finder) {
            if (finder.hasOwnProperty(key)) {
                var value = finder[key];
                key = key.match(/^(\w+)(Index|Id|Name)$/);
                var queryParam = {mainType: key[1]};
                queryParam[key[2].toLowerCase()] = value;
                var model = ecModel.queryComponents(queryParam)[0];
                result[key[1] + 'Model'] = model;

                if (opt && opt.singleResult) {
                    return model;
                }
            }
        }

        return result;
    };

    return modelUtil;
});<commit_msg>tweak<commit_after>define(function(require) {

    var formatUtil = require('./format');
    var nubmerUtil = require('./number');
    var Model = require('../model/Model');
    var zrUtil = require('zrender/core/util');

    var modelUtil = {};

    /**
     * If value is not array, then translate it to array.
     * @param  {*} value
     * @return {Array} [value] or value
     */
    modelUtil.normalizeToArray = function (value) {
        return value instanceof Array
            ? value
            : value == null
            ? []
            : [value];
    };

    /**
     * Sync default option between normal and emphasis like `position` and `show`
     * In case some one will write code like
     *     label: {
     *         normal: {
     *             show: false,
     *             position: 'outside',
     *             textStyle: {
     *                 fontSize: 18
     *             }
     *         },
     *         emphasis: {
     *             show: true
     *         }
     *     }
     * @param {Object} opt
     * @param {Array.<string>} subOpts
     */
     modelUtil.defaultEmphasis = function (opt, subOpts) {
        if (opt) {
            var emphasisOpt = opt.emphasis = opt.emphasis || {};
            var normalOpt = opt.normal = opt.normal || {};

            // Default emphasis option from normal
            zrUtil.each(subOpts, function (subOptName) {
                var val = zrUtil.retrieve(emphasisOpt[subOptName], normalOpt[subOptName]);
                if (val != null) {
                    emphasisOpt[subOptName] = val;
                }
            });
        }
    };

    modelUtil.LABEL_OPTIONS = ['position', 'show', 'textStyle', 'distance', 'formatter'];

    /**
     * data could be [12, 2323, {value: 223}, [1221, 23], {value: [2, 23]}]
     * This helper method retieves value from data.
     * @param {string|number|Date|Array|Object} dataItem
     * @return {number|string|Date|Array.<number|string|Date>}
     */
    modelUtil.getDataItemValue = function (dataItem) {
        // Performance sensitive.
        return dataItem && (dataItem.value == null ? dataItem : dataItem.value);
    };

    /**
     * data could be [12, 2323, {value: 223}, [1221, 23], {value: [2, 23]}]
     * This helper method determine if dataItem has extra option besides value
     * @param {string|number|Date|Array|Object} dataItem
     */
    modelUtil.isDataItemOption = function (dataItem) {
        return zrUtil.isObject(dataItem)
            && !(dataItem instanceof Array);
            // // markLine data can be array
            // && !(dataItem[0] && zrUtil.isObject(dataItem[0]) && !(dataItem[0] instanceof Array));
    };

    /**
     * This helper method convert value in data.
     * @param {string|number|Date} value
     * @param {Object|string} [dimInfo] If string (like 'x'), dimType defaults 'number'.
     */
    modelUtil.converDataValue = function (value, dimInfo) {
        // Performance sensitive.
        var dimType = dimInfo && dimInfo.type;
        if (dimType === 'ordinal') {
            return value;
        }

        if (dimType === 'time' && !isFinite(value) && value != null && value !== '-') {
            value = +nubmerUtil.parseDate(value);
        }

        // dimType defaults 'number'.
        // If dimType is not ordinal and value is null or undefined or NaN or '-',
        // parse to NaN.
        return (value == null || value === '')
            ? NaN : +value; // If string (like '-'), using '+' parse to NaN
    };

    /**
     * Create a model proxy to be used in tooltip for edge data, markLine data, markPoint data.
     * @param {module:echarts/data/List} data
     * @param {Object} opt
     * @param {string} [opt.seriesIndex]
     * @param {Object} [opt.name]
     * @param {Object} [opt.mainType]
     * @param {Object} [opt.subType]
     */
    modelUtil.createDataFormatModel = function (data, opt) {
        var model = new Model();
        zrUtil.mixin(model, modelUtil.dataFormatMixin);
        model.seriesIndex = opt.seriesIndex;
        model.name = opt.name || '';
        model.mainType = opt.mainType;
        model.subType = opt.subType;

        model.getData = function () {
            return data;
        };
        return model;
    };

    // PENDING A little ugly
    modelUtil.dataFormatMixin = {
        /**
         * Get params for formatter
         * @param {number} dataIndex
         * @param {string} [dataType]
         * @return {Object}
         */
        getDataParams: function (dataIndex, dataType) {
            var data = this.getData(dataType);

            var seriesIndex = this.seriesIndex;
            var seriesName = this.name;

            var rawValue = this.getRawValue(dataIndex, dataType);
            var rawDataIndex = data.getRawIndex(dataIndex);
            var name = data.getName(dataIndex, true);
            var itemOpt = data.getRawDataItem(dataIndex);

            return {
                componentType: this.mainType,
                componentSubType: this.subType,
                seriesType: this.mainType === 'series' ? this.subType : null,
                seriesIndex: seriesIndex,
                seriesName: seriesName,
                name: name,
                dataIndex: rawDataIndex,
                data: itemOpt,
                dataType: dataType,
                value: rawValue,
                color: data.getItemVisual(dataIndex, 'color'),

                // Param name list for mapping `a`, `b`, `c`, `d`, `e`
                $vars: ['seriesName', 'name', 'value']
            };
        },

        /**
         * Format label
         * @param {number} dataIndex
         * @param {string} [status='normal'] 'normal' or 'emphasis'
         * @param {string} [dataType]
         * @param {number} [dimIndex]
         * @return {string}
         */
        getFormattedLabel: function (dataIndex, status, dataType, dimIndex) {
            status = status || 'normal';
            var data = this.getData(dataType);
            var itemModel = data.getItemModel(dataIndex);

            var params = this.getDataParams(dataIndex, dataType);
            if (dimIndex != null && (params.value instanceof Array)) {
                params.value = params.value[dimIndex];
            }

            var formatter = itemModel.get(['label', status, 'formatter']);

            if (typeof formatter === 'function') {
                params.status = status;
                return formatter(params);
            }
            else if (typeof formatter === 'string') {
                return formatUtil.formatTpl(formatter, params);
            }
        },

        /**
         * Get raw value in option
         * @param {number} idx
         * @param {string} [dataType]
         * @return {Object}
         */
        getRawValue: function (idx, dataType) {
            var data = this.getData(dataType);
            var dataItem = data.getRawDataItem(idx);
            if (dataItem != null) {
                return (zrUtil.isObject(dataItem) && !(dataItem instanceof Array))
                    ? dataItem.value : dataItem;
            }
        },

        /**
         * Should be implemented.
         * @param {number} dataIndex
         * @param {boolean} [multipleSeries=false]
         * @param {number} [dataType]
         * @return {string} tooltip string
         */
        formatTooltip: zrUtil.noop
    };

    /**
     * Mapping to exists for merge.
     *
     * @public
     * @param {Array.<Object>|Array.<module:echarts/model/Component>} exists
     * @param {Object|Array.<Object>} newCptOptions
     * @return {Array.<Object>} Result, like [{exist: ..., option: ...}, {}],
     *                          which order is the same as exists.
     */
    modelUtil.mappingToExists = function (exists, newCptOptions) {
        // Mapping by the order by original option (but not order of
        // new option) in merge mode. Because we should ensure
        // some specified index (like xAxisIndex) is consistent with
        // original option, which is easy to understand, espatially in
        // media query. And in most case, merge option is used to
        // update partial option but not be expected to change order.
        newCptOptions = (newCptOptions || []).slice();

        var result = zrUtil.map(exists || [], function (obj, index) {
            return {exist: obj};
        });

        // Mapping by id or name if specified.
        zrUtil.each(newCptOptions, function (cptOption, index) {
            if (!zrUtil.isObject(cptOption)) {
                return;
            }

            // id has highest priority.
            for (var i = 0; i < result.length; i++) {
                if (!result[i].option // Consider name: two map to one.
                    && cptOption.id != null
                    && result[i].exist.id === cptOption.id + ''
                ) {
                    result[i].option = cptOption;
                    newCptOptions[index] = null;
                    return;
                }
            }

            for (var i = 0; i < result.length; i++) {
                var exist = result[i].exist;
                if (!result[i].option // Consider name: two map to one.
                    // Can not match when both ids exist but different.
                    && (exist.id == null || cptOption.id == null)
                    && cptOption.name != null
                    && !modelUtil.isIdInner(cptOption)
                    && !modelUtil.isIdInner(exist)
                    && exist.name === cptOption.name + ''
                ) {
                    result[i].option = cptOption;
                    newCptOptions[index] = null;
                    return;
                }
            }
        });

        // Otherwise mapping by index.
        zrUtil.each(newCptOptions, function (cptOption, index) {
            if (!zrUtil.isObject(cptOption)) {
                return;
            }

            var i = 0;
            for (; i < result.length; i++) {
                var exist = result[i].exist;
                if (!result[i].option
                    && !modelUtil.isIdInner(exist)
                    // Caution:
                    // Do not overwrite id. But name can be overwritten,
                    // because axis use name as 'show label text'.
                    // 'exist' always has id and name and we dont
                    // need to check it.
                    && cptOption.id == null
                ) {
                    result[i].option = cptOption;
                    break;
                }
            }

            if (i >= result.length) {
                result.push({option: cptOption});
            }
        });

        return result;
    };

    /**
     * @public
     * @param {Object} cptOption
     * @return {boolean}
     */
    modelUtil.isIdInner = function (cptOption) {
        return zrUtil.isObject(cptOption)
            && cptOption.id
            && (cptOption.id + '').indexOf('\0_ec_\0') === 0;
    };

    /**
     * A helper for removing duplicate items between batchA and batchB,
     * and in themselves, and categorize by series.
     *
     * @param {Array.<Object>} batchA Like: [{seriesId: 2, dataIndex: [32, 4, 5]}, ...]
     * @param {Array.<Object>} batchB Like: [{seriesId: 2, dataIndex: [32, 4, 5]}, ...]
     * @return {Array.<Array.<Object>, Array.<Object>>} result: [resultBatchA, resultBatchB]
     */
    modelUtil.compressBatches = function (batchA, batchB) {
        var mapA = {};
        var mapB = {};

        makeMap(batchA || [], mapA);
        makeMap(batchB || [], mapB, mapA);

        return [mapToArray(mapA), mapToArray(mapB)];

        function makeMap(sourceBatch, map, otherMap) {
            for (var i = 0, len = sourceBatch.length; i < len; i++) {
                var seriesId = sourceBatch[i].seriesId;
                var dataIndices = modelUtil.normalizeToArray(sourceBatch[i].dataIndex);
                var otherDataIndices = otherMap && otherMap[seriesId];

                for (var j = 0, lenj = dataIndices.length; j < lenj; j++) {
                    var dataIndex = dataIndices[j];

                    if (otherDataIndices && otherDataIndices[dataIndex]) {
                        otherDataIndices[dataIndex] = null;
                    }
                    else {
                        (map[seriesId] || (map[seriesId] = {}))[dataIndex] = 1;
                    }
                }
            }
        }

        function mapToArray(map, isData) {
            var result = [];
            for (var i in map) {
                if (map.hasOwnProperty(i) && map[i] != null) {
                    if (isData) {
                        result.push(+i);
                    }
                    else {
                        var dataIndices = mapToArray(map[i], true);
                        dataIndices.length && result.push({seriesId: i, dataIndex: dataIndices});
                    }
                }
            }
            return result;
        }
    };

    /**
     * @param {module:echarts/model/Global} ecModel
     * @param {string|Object} finder
     *        If string, e.g., 'geo', means {geoIndex: 0}.
     *        If Object, could contain some of these properties below:
     *        {
     *            seriesIndex, seriesId, seriesName,
     *            geoIndex, geoId, goeName,
     *            bmapIndex, bmapId, bmapName,
     *            xAxisIndex, xAxisId, xAxisName,
     *            yAxisIndex, yAxisId, yAxisName,
     *            gridIndex, gridId, gridName,
     *            ... (can be extended)
     *        }
     * @param {Object} [opt]
     * @param {boolean} [opt.singleResult=false]
     * @return {Object} result like:
     *        {
     *            seriesModel: xxx,
     *            geoModel: xxx
     *            ...
     *        }
     *        If opt.singleResult, result in the first found model.
     */
    modelUtil.parseFinder = function (ecModel, finder, opt) {
        if (zrUtil.isString(finder)) {
            var obj = {};
            obj[finder + 'Index'] = 0;
            finder = obj;
        }

        var result = {};

        for (var key in finder) {
            if (finder.hasOwnProperty(key)) {
                var value = finder[key];
                key = key.match(/^(\w+)(Index|Id|Name)$/);
                var queryParam = {mainType: key[1]};
                queryParam[key[2].toLowerCase()] = value;
                var model = ecModel.queryComponents(queryParam)[0];
                result[key[1] + 'Model'] = model;

                if (opt && opt.singleResult) {
                    return model;
                }
            }
        }

        return result;
    };

    return modelUtil;
});<|endoftext|>


<commit_before>import {extend} from "lodash";
import SeqCollection from "../model/SeqCollection";

const TreeHelper =  function(msa) {
  this.msa = msa;
  return this;
};

var tf =

    {loadTree: function(cb) {
      return this.msa.g.package.loadPackages(["msa-tnt", "biojs-io-newick"], cb);
    },

    showTree: function(newickStr) {
      var newick = window.require("biojs-io-newick");
      var mt = window.require("msa-tnt");

      if (typeof newickStr === "string") {
        var newickObj = newick.parse_newick(newickStr);
      } else {
        newickObj = newickStr;
      }

      var sel = new mt.selections();
      var treeDiv;
      if(this.msa.el.childNodes.length === 1){
        treeDiv = document.createElement("div");
        this.msa.el.appendChild(treeDiv);
      } else {
        console.log('A tree already exists. It will be overridden.');
        treeDiv = this.msa.el.childNodes[1];
        treeDiv.innerHTML = '';
      }

      var nodes = mt.app({
        seqs: this.msa.seqs.toJSON(),
        tree: newickObj
      });

      console.log("nodes", nodes.seqs);

      var t = new mt.adapters.tree({
        model: nodes,
        el: treeDiv,
        sel: sel
      });

      //treeDiv.style.width = "500px"

      // construct msa in a virtual dom
      var m = new mt.adapters.msa({
          model: nodes,
          sel: sel,
          msa: this.msa
      });

      // remove top collection
      nodes.models.forEach((e) => {
        delete e.collection;
        return Object.setPrototypeOf(e, require("backbone-thin").Model.prototype);
      });

      this.msa.seqs.reset(nodes.models);
      //@msa.draw()
      //@msa.render()
      return console.log(this.msa.seqs);
    },

    // workaround against browserify's static analysis
    require(pkg) {
      return require(pkg);
    }
    };

extend(TreeHelper.prototype , tf);
export default TreeHelper;
<commit_msg>Added a bit of logic to tree.js which tries to match tree leaves with MSA sequences. This allows users to upload tree files that have the same names OR IDs as the MSA sequences, whereas before only IDs were considered. This should make tree file uploads much more intuitive.<commit_after>import {extend} from "lodash";
import SeqCollection from "../model/SeqCollection";

const TreeHelper =  function(msa) {
  this.msa = msa;
  return this;
};

var tf =

    {loadTree: function(cb) {
      return this.msa.g.package.loadPackages(["msa-tnt", "biojs-io-newick"], cb);
    },

    showTree: function(newickStr) {
      var newick = window.require("biojs-io-newick");
      var mt = window.require("msa-tnt");

      if (typeof newickStr === "string") {
        var newickObj = newick.parse_newick(newickStr);
      } else {
        newickObj = newickStr;
      }

      var sel = new mt.selections();
      var treeDiv;
      if(this.msa.el.childNodes.length === 1){
        treeDiv = document.createElement("div");
        this.msa.el.appendChild(treeDiv);
      } else {
        console.log('A tree already exists. It will be overridden.');
        treeDiv = this.msa.el.childNodes[1];
        treeDiv.innerHTML = '';
      }

      const seqs = this.msa.seqs.toJSON();
      //adapt tree ids to sequence ids
      function iterateTree(nwck){
        if(nwck.children != null){
          nwck.children.forEach(x => iterateTree(x));
        } else {
          //found a leave
          let seq = seqs.filter(s => s.name === nwck.name)[0];
          if(seq != null){
            seq.ids = [seq.id];
            nwck.name = seq.id;
          }
        }
      }
      iterateTree(newickObj);

      var nodes = mt.app({
        seqs: seqs,
        tree: newickObj
      });

      console.log("nodes", nodes.seqs);

      var t = new mt.adapters.tree({
        model: nodes,
        el: treeDiv,
        sel: sel
      });

      //treeDiv.style.width = "500px"

      // construct msa in a virtual dom
      var m = new mt.adapters.msa({
          model: nodes,
          sel: sel,
          msa: this.msa
      });

      // remove top collection
      nodes.models.forEach((e) => {
        delete e.collection;
        return Object.setPrototypeOf(e, require("backbone-thin").Model.prototype);
      });

      this.msa.seqs.reset(nodes.models);
      //@msa.draw()
      //@msa.render()
      return console.log(this.msa.seqs);
    },

    // workaround against browserify's static analysis
    require(pkg) {
      return require(pkg);
    }
    };

extend(TreeHelper.prototype , tf);
export default TreeHelper;
<|endoftext|>


<commit_before>
// Ensure the global scope objects are created if they don't already exist.
if (!window.rhubarb){
    window.rhubarb = {};
}

if (!window.rhubarb.validation){
    window.rhubarb.validation = {};
}

if (!window.rhubarb.validation.common){
    window.rhubarb.validation.common = {};
}

if (!window.rhubarb.validation.sources){
    window.rhubarb.validation.sources = {};
}

if (!window.rhubarb.validation.triggers){
    window.rhubarb.validation.triggers = {};
}

if (!window.validation){
    // Provide a quick global alias to validation if that is possible
    window.validation = window.rhubarb.validation;
}

window.rhubarb.validation.states = {
    "notTested": 0,
    'valid': 1,
    'checking': 2,
    'invalid': 3
};

window.rhubarb.validation.validator = function(){

    this._checks = [];
    this._triggers = [];
    this._source = false;
    this._targetElement = false;
    this._isRequired = false;
    this._hasValue = false;
    this._messageFormatter = function (errors) {
        return "
" + errors.join(". ") + "
";
    };

    this.requiredMessage = "A value is required here.";
    this.state = window.rhubarb.validation.states.nottested;
    this.errorMessages = [];

    var self = this;

    this.require = function(message){
        self._isRequired = true;

        if (message){
            self.requiredMessage = message;
        }

        return self;
    };

    this.check = function(callback){
        self._checks.push(callback);

        return self;
    };

    this.setMessageFormatter = function(formatterCallback){
        self._messageFormatter = formatterCallback;
    };

    this.addTrigger = function(broker){

        // Try and upscale any strings passed to an HTML element
        try {
            broker = this.makeHtmlElement(broker);
        } catch (error) {
            throw new Error("A string '" + broker + "' passed to addTrigger was not a valid HTML Element ID.");
        }

        if (broker instanceof HTMLElement){
            broker = window.rhubarb.validation.triggers.onHtmlElementValueChanged(broker);
        }

        self._triggers.push(broker);

        broker.trigger = function(value){
            self.validate();
        };

        return self;
    };

    this.setSource = function(source){
        try {
            self._source = this.makeHtmlElementValueCallback(source);
        } catch (error){
            if (error.message == "string" ){
                throw new Error("A string '" + source + "' passed to setSource was not a valid HTML Element ID." );
            } else {
                throw new Error("An element passed to setSource was not a valid HTML input element." );
            }
        }

        return self;
    };

    this.makeHtmlElement = function(nameOrElement){
        if (typeof nameOrElement == "string"){
            nameOrElement = document.getElementById(nameOrElement);

            if (!nameOrElement){
                throw new Error(nameOrElement + " is not an HTML element" );
            }
        }

        return nameOrElement;
    };

    this.makeHtmlElementValueCallback = function(elementOrCallback){

        try {
            elementOrCallback = this.makeHtmlElement(elementOrCallback);
        } catch(error){
            throw new Error("string");
        }

        if (elementOrCallback instanceof HTMLElement){
            if ( typeof elementOrCallback.value == undefined ) {
                throw new Error("element");
            }

            var element = elementOrCallback;
            elementOrCallback = function () {
                return element.value;
            };
        }

        return elementOrCallback;
    };

    this.setTargetElement = function(targetElement){

        // Try and upscale any strings passed to an HTML element
        try {
            targetElement = this.makeHtmlElement(targetElement);
        } catch (error) {
            throw new Error("A string '" + targetElement + "' passed to setTargetElement was not a valid HTML Element ID.");
        }

        self._targetElement = targetElement;

        return self;
    };

    this.checkHasValue = function (value) {
        if (value == "0") {
            value = false;
        }

        self._hasValue = !!value;
    };

    this.validate = function(successCallback, failureCallback){

        var value = (self._source) ? self._source() : false;

        self.checkHasValue(value);

        // Update our status to checking (this might be used for slow validations)
        self.state = window.rhubarb.validation.states.checking;
        self.updateClasses();

        // Reset our error messages in case this is a second attempt
        self.errorMessages = [];

        var validationCompleted = function() {
            for (var i = 0; i < self._checks.length; i++) {
                var check = self._checks[i];

                if (self._hasValue || check.alwaysCheck) {
                    if (!check.checked) {
                        // Not all the checks are complete - a later callback will come back here
                        // when that happens. For now we can't update the status yet.
                        return;
                    }
                }
            }

            if (self._isRequired && !self._hasValue) {
                self.errorMessages.push(self.requiredMessage);
            }

            if (self.errorMessages.length > 0){
                self.state = window.rhubarb.validation.states.invalid;
                self.updateClasses();

                if (failureCallback){
                    failureCallback(self.errorMessages);
                }
            } else {
                self.state = window.rhubarb.validation.states.valid;
                self.updateClasses();

                if (successCallback){
                    successCallback();
                }
            }
        };

        if ( self._checks.length > 0 ) {
            // Do the checks only if we have checks to do
            for (var i = 0; i < self._checks.length; i++) {
                var check = self._checks[i];

                if (!self._hasValue && !check.alwaysCheck){
                    validationCompleted();
                    continue;
                }

                check.checked = false;

                check(
                    value,
                    function () {
                        // On success
                        this.checked = true;
                        validationCompleted();
                    }.bind(check),
                    function (error) {
                        // The validation failed - an Error exception should have been thrown.
                        this.checked = true;
                        if (Array.isArray(error)) {
                            for (var m = 0; m < error.length; m++) {
                                self.errorMessages.push(error[m]);
                            }
                        } else {
                            self.errorMessages.push(error);
                        }
                        validationCompleted();
                    }.bind(check)
                );
            }
        } else {
            // Otherwise just evaluation completion (essentially allowing for required status only)
            validationCompleted();
        }

        return self.state == window.rhubarb.validation.states.valid;
    };

    this.updateClasses = function(){
        if (!self._targetElement){
            // If we don't have a target element we can't update any CSS classes.
            return;
        }

        self._targetElement.classList.remove("is-valid", "is-checking", "is-invalid", "is-not-tested", "is-required", "is-missing");

        switch(self.state){
            case window.rhubarb.validation.states.valid:
                self._targetElement.classList.add("is-valid");
                break;
            case window.rhubarb.validation.states.checking:
                self._targetElement.classList.add("is-checking");
                break;
            case window.rhubarb.validation.states.invalid:
                self._targetElement.classList.add("is-invalid");
                break;
            case window.rhubarb.validation.states.notTested:
                self._targetElement.classList.add("is-not-tested");
                break;
        }

        if (self._isRequired){
            self._targetElement.classList.add("is-required");

            if (!self._hasValue){
                self._targetElement.classList.add("is-missing");
            }
        }

        // Try and find an error container and update the message with our error messages.
        var errorMessage = self._targetElement.querySelector(".js-validation-message");

        if (errorMessage){
            if( self.errorMessages.length > 0){
                errorMessage.innerHTML = self._messageFormatter(self.errorMessages);
            } else {
                errorMessage.innerHTML = "";
            }
        }
    }
};

window.rhubarb.validation.sources.fromHtmlElement = function(htmlElement){
    return function(){
        return htmlElement.value;
    };
};

window.rhubarb.validation.triggers.onHtmlElementValueChanged = function(htmlElement){
    var broker = {};
    broker.trigger = function(){};

    htmlElement.addEventListener('change', function(){
        broker.trigger();
    });

    return broker;
};

// Define our standard validation routines. You can of course create your own. The validation framework requires
// a callback for each validation routine. Our pattern here is to define a function that **returns a callback**.
// This allows our outer function to define arguments that are then presented to the programmer in any good IDE.
window.rhubarb.validation.common.lengthGreaterThan = function(greaterThan, orEqual){
  return function (value, successCallback, failedCallback){
      var compareTo = (orEqual) ? greaterThan - 1 : greaterThan;
      if (value.length > greaterThan){
          failedCallback("The length must less than " + (orEqual ? " (or equal to) " : "" ) + greaterThan);
          return;
      }

      successCallback();
  };
};

window.rhubarb.validation.common.regex = function(regex, failureMessage){
    return function(value, successCallback, failedCallback){
        if (!value.match(regex)){
            failedCallback(failureMessage);
            return;
        }

        successCallback();
    }
};

window.rhubarb.validation.common.isEmailAddress = function(){
    return function(value, successCallback, failedCallback){
        if (value.indexOf("@") == -1){
            failedCallback("Email addresses must contain a '@' character");
            return;
        }

        var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
        if (!re.test(value)){
            failedCallback(value + " does not look like an email address");
            return;
        }

        successCallback();
    }
};

window.rhubarb.validation.common.matches = function(matchSourceCallback){
  return function(value, successCallback, failedCallback){
      var compareTo = matchSourceCallback();

      if (compareTo != value){
          failedCallback("The values must match");
          return;
      }

      successCallback();
  };
};

window.rhubarb.validation.common.allValid = function(validations) {
    var check = function (value, successCallback, failedCallback) {
        var errors = [];
        var validationsToCheck = validations;

        for (var i = 0; i < validationsToCheck.length; i++) {
            var validationToCheck = validationsToCheck[i];

            validationToCheck.validate(
                function () {
                },
                function (errorMessages) {
                    for (var m = 0; m < errorMessages.length; m++) {
                        errors.push(errorMessages[m]);
                    }
                }.bind(validationToCheck)
            );
        }

        var checkAll = function () {
            for (var i = 0; i < validationsToCheck.length; i++) {
                var validationToCheckOn = validationsToCheck[i];
                if (validationToCheckOn.state == window.rhubarb.validation.states.checking) {
                    setTimeout(checkAll, 200);
                    return;
                }
            }

            if (errors.length > 1) {
                failedCallback(errors);
                return;
            }

            if (errors.length > 0) {
                failedCallback(errors);
                return;
            }

            successCallback();
        };

        checkAll();
    };

    check.alwaysCheck = true;
    return check;
};
<commit_msg>Required checks can no be callbacks for conditional behaviour<commit_after>
// Ensure the global scope objects are created if they don't already exist.
if (!window.rhubarb){
    window.rhubarb = {};
}

if (!window.rhubarb.validation){
    window.rhubarb.validation = {};
}

if (!window.rhubarb.validation.common){
    window.rhubarb.validation.common = {};
}

if (!window.rhubarb.validation.sources){
    window.rhubarb.validation.sources = {};
}

if (!window.rhubarb.validation.triggers){
    window.rhubarb.validation.triggers = {};
}

if (!window.validation){
    // Provide a quick global alias to validation if that is possible
    window.validation = window.rhubarb.validation;
}

window.rhubarb.validation.states = {
    "notTested": 0,
    'valid': 1,
    'checking': 2,
    'invalid': 3
};

window.rhubarb.validation.validator = function(){

    this._checks = [];
    this._triggers = [];
    this._source = false;
    this._targetElement = false;
    this._isRequiredChecks = [];
    this._hasValue = false;
    this._messageFormatter = function (errors) {
        return "
" + errors.join(". ") + "
";
    };

    this.defaultRequiredMessage = "A value is required here.";
    this.state = window.rhubarb.validation.states.nottested;
    this.errorMessages = [];

    var self = this;

    this.require = function(message){
        this.requireWhen(function(){ return true; }, message);

        return self;
    };

    this.requireWhen = function(callback, message){
        self._isRequiredChecks.push([callback, (message) ? message : self.defaultRequiredMessage]);

        return self;
    };

    this.check = function(callback){
        self._checks.push(callback);

        return self;
    };

    this.setMessageFormatter = function(formatterCallback){
        self._messageFormatter = formatterCallback;
    };

    this.addTrigger = function(broker){

        // Try and upscale any strings passed to an HTML element
        try {
            broker = this.makeHtmlElement(broker);
        } catch (error) {
            throw new Error("A string '" + broker + "' passed to addTrigger was not a valid HTML Element ID.");
        }

        if (broker instanceof HTMLElement){
            broker = window.rhubarb.validation.triggers.onHtmlElementValueChanged(broker);
        }

        self._triggers.push(broker);

        broker.trigger = function(value){
            self.validate();
        };

        return self;
    };

    this.setSource = function(source){
        try {
            self._source = this.makeHtmlElementValueCallback(source);
        } catch (error){
            if (error.message == "string" ){
                throw new Error("A string '" + source + "' passed to setSource was not a valid HTML Element ID." );
            } else {
                throw new Error("An element passed to setSource was not a valid HTML input element." );
            }
        }

        return self;
    };

    this.makeHtmlElement = function(nameOrElement){
        if (typeof nameOrElement == "string"){
            nameOrElement = document.getElementById(nameOrElement);

            if (!nameOrElement){
                throw new Error(nameOrElement + " is not an HTML element" );
            }
        }

        return nameOrElement;
    };

    this.makeHtmlElementValueCallback = function(elementOrCallback){

        try {
            elementOrCallback = this.makeHtmlElement(elementOrCallback);
        } catch(error){
            throw new Error("string");
        }

        if (elementOrCallback instanceof HTMLElement){
            if ( typeof elementOrCallback.value == undefined ) {
                throw new Error("element");
            }

            var element = elementOrCallback;
            elementOrCallback = function () {
                return element.value;
            };
        }

        return elementOrCallback;
    };

    this.setTargetElement = function(targetElement){

        // Try and upscale any strings passed to an HTML element
        try {
            targetElement = this.makeHtmlElement(targetElement);
        } catch (error) {
            throw new Error("A string '" + targetElement + "' passed to setTargetElement was not a valid HTML Element ID.");
        }

        self._targetElement = targetElement;

        return self;
    };

    this.checkHasValue = function (value) {
        if (value == "0") {
            value = false;
        }

        self._hasValue = !!value;
    };

    this.validate = function(successCallback, failureCallback){

        var value = (self._source) ? self._source() : false;

        self.checkHasValue(value);

        // Update our status to checking (this might be used for slow validations)
        self.state = window.rhubarb.validation.states.checking;
        self.updateClasses();

        // Reset our error messages in case this is a second attempt
        self.errorMessages = [];

        var validationCompleted = function() {
            for (var i = 0; i < self._checks.length; i++) {
                var check = self._checks[i];

                if (self._hasValue || check.alwaysCheck) {
                    if (!check.checked) {
                        // Not all the checks are complete - a later callback will come back here
                        // when that happens. For now we can't update the status yet.
                        return;
                    }
                }
            }

            for(var j = 0; j < self._isRequiredChecks.length; j++){
                var requiredCheck = self._isRequiredChecks[j];

                if (requiredCheck[0](value) && !self._hasValue){
                  self.errorMessages.push(requiredCheck[1]);
                }
            }

            if (self.errorMessages.length > 0){
                self.state = window.rhubarb.validation.states.invalid;
                self.updateClasses();

                if (failureCallback){
                    failureCallback(self.errorMessages);
                }
            } else {
                self.state = window.rhubarb.validation.states.valid;
                self.updateClasses();

                if (successCallback){
                    successCallback();
                }
            }
        };

        if ( self._checks.length > 0 ) {
            // Do the checks only if we have checks to do
            for (var i = 0; i < self._checks.length; i++) {
                var check = self._checks[i];

                if (!self._hasValue && !check.alwaysCheck){
                    validationCompleted();
                    continue;
                }

                check.checked = false;

                check(
                    value,
                    function () {
                        // On success
                        this.checked = true;
                        validationCompleted();
                    }.bind(check),
                    function (error) {
                        // The validation failed - an Error exception should have been thrown.
                        this.checked = true;
                        if (Array.isArray(error)) {
                            for (var m = 0; m < error.length; m++) {
                                self.errorMessages.push(error[m]);
                            }
                        } else {
                            self.errorMessages.push(error);
                        }
                        validationCompleted();
                    }.bind(check)
                );
            }
        } else {
            // Otherwise just evaluation completion (essentially allowing for required status only)
            validationCompleted();
        }

        return self.state == window.rhubarb.validation.states.valid;
    };

    this.updateClasses = function(){
        if (!self._targetElement){
            // If we don't have a target element we can't update any CSS classes.
            return;
        }

        self._targetElement.classList.remove("is-valid", "is-checking", "is-invalid", "is-not-tested", "is-required", "is-missing");

        switch(self.state){
            case window.rhubarb.validation.states.valid:
                self._targetElement.classList.add("is-valid");
                break;
            case window.rhubarb.validation.states.checking:
                self._targetElement.classList.add("is-checking");
                break;
            case window.rhubarb.validation.states.invalid:
                self._targetElement.classList.add("is-invalid");
                break;
            case window.rhubarb.validation.states.notTested:
                self._targetElement.classList.add("is-not-tested");
                break;
        }

        if (self._isRequiredChecks){
            self._targetElement.classList.add("is-required");

            if (!self._hasValue){
                self._targetElement.classList.add("is-missing");
            }
        }

        // Try and find an error container and update the message with our error messages.
        var errorMessage = self._targetElement.querySelector(".js-validation-message");

        if (errorMessage){
            if( self.errorMessages.length > 0){
                errorMessage.innerHTML = self._messageFormatter(self.errorMessages);
            } else {
                errorMessage.innerHTML = "";
            }
        }
    }
};

window.rhubarb.validation.sources.fromHtmlElement = function(htmlElement){
    return function(){
        return htmlElement.value;
    };
};

window.rhubarb.validation.triggers.onHtmlElementValueChanged = function(htmlElement){
    var broker = {};
    broker.trigger = function(){};

    htmlElement.addEventListener('change', function(){
        broker.trigger();
    });

    return broker;
};

// Define our standard validation routines. You can of course create your own. The validation framework requires
// a callback for each validation routine. Our pattern here is to define a function that **returns a callback**.
// This allows our outer function to define arguments that are then presented to the programmer in any good IDE.
window.rhubarb.validation.common.lengthGreaterThan = function(greaterThan, orEqual){
  return function (value, successCallback, failedCallback){
      var compareTo = (orEqual) ? greaterThan - 1 : greaterThan;
      if (value.length > greaterThan){
          failedCallback("The length must less than " + (orEqual ? " (or equal to) " : "" ) + greaterThan);
          return;
      }

      successCallback();
  };
};

window.rhubarb.validation.common.regex = function(regex, failureMessage){
    return function(value, successCallback, failedCallback){
        if (!value.match(regex)){
            failedCallback(failureMessage);
            return;
        }

        successCallback();
    }
};

window.rhubarb.validation.common.isEmailAddress = function(){
    return function(value, successCallback, failedCallback){
        if (value.indexOf("@") == -1){
            failedCallback("Email addresses must contain a '@' character");
            return;
        }

        var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
        if (!re.test(value)){
            failedCallback(value + " does not look like an email address");
            return;
        }

        successCallback();
    }
};

window.rhubarb.validation.common.matches = function(matchSourceCallback){
  return function(value, successCallback, failedCallback){
      var compareTo = matchSourceCallback();

      if (compareTo != value){
          failedCallback("The values must match");
          return;
      }

      successCallback();
  };
};

window.rhubarb.validation.common.allValid = function(validations) {
    var check = function (value, successCallback, failedCallback) {
        var errors = [];
        var validationsToCheck = validations;

        for (var i = 0; i < validationsToCheck.length; i++) {
            var validationToCheck = validationsToCheck[i];

            validationToCheck.validate(
                function () {
                },
                function (errorMessages) {
                    for (var m = 0; m < errorMessages.length; m++) {
                        errors.push(errorMessages[m]);
                    }
                }.bind(validationToCheck)
            );
        }

        var checkAll = function () {
            for (var i = 0; i < validationsToCheck.length; i++) {
                var validationToCheckOn = validationsToCheck[i];
                if (validationToCheckOn.state == window.rhubarb.validation.states.checking) {
                    setTimeout(checkAll, 200);
                    return;
                }
            }

            if (errors.length > 1) {
                failedCallback(errors);
                return;
            }

            if (errors.length > 0) {
                failedCallback(errors);
                return;
            }

            successCallback();
        };

        checkAll();
    };

    check.alwaysCheck = true;
    return check;
};
<|endoftext|>


<commit_before>/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ***************************************************************************

    This file contains methods that ensure that the blocks in the Blockly workspace are attached to each other in a valid sequence.
 */

function validateBlocklyGraph(){
    var blocks = blocklyWorkspace.getTopBlocks();

	document.getElementById("XMLOutput").value = "";
    if(blocks.length == 0){
        document.getElementById('validation-error-p').innerHTML = "Workspace is empty";
        return false;
    } else if(blocks.length > 1){
        document.getElementById('validation-error-p').innerHTML = "Only the start block is allowed to be placed directly in the workspace";
        return false;
	} else if(blocks[0].type != "block_0"){
        document.getElementById('validation-error-p').innerHTML = "It is compulsory to use the start block (block_0:start)";
        return false;
    } else {
        var allBlocks = blocklyWorkspace.getAllBlocks();
        var blocklyValidationResult = true;
        for(var i=0; i<allBlocks.length; i++) {
            blocklyValidationResult = validateBlock(allBlocks[i]) && blocklyValidationResult;
        }

        if(blocklyValidationResult) {
            generateXML();
            document.getElementById('validation-error-p').innerHTML = "";
        } else {
            document.getElementById('validation-error-p').innerHTML = "not valid";
        }
        return blocklyValidationResult;
    }
}

var parentConnection = {};

function validateBlockIfNecessary(block, event) {
    var hasBlocks = blocklyWorkspace.getTopBlocks().length > 0;
    console.log(event.toJson(), block.id, blocklyWorkspace.getTopBlocks().length, block.type, (block.getParent() ? block.getParent().type : null));

    if (!hasBlocks) {
        // This happens when the toolbox is created
        return;
    }

    if (event.type == Blockly.Events.MOVE) {
        if (event.newParentId) {
            // A block is moved and attached to another one
            if (event.newParentId == block.id) {
                // Here we check the parent's slot
                validateBlock(block);
                parentConnection[event.blockId] = block;
            }
        } else {
            // The block is supposed to have a parent, but the parent is gone
            if (parentConnection.hasOwnProperty(block.id) && !block.getParent()) {
                // The parent must be checked again
                console.log("connection lost");
                validateBlock(parentConnection[block.id]);
                delete parentConnection[block.id];
            }
        }
    } else if (event.type == Blockly.Events.CREATE && block.id == event.blockId) {
        for(var i=0; i<event.ids.length; i++) {
            var block = blocklyWorkspace.getBlockById( event.ids[i] );
            validateBlock(block);
        }
    }
}

//get all blocks. Send each block's slots for validation. Send each child block of each slot for validation
function validateBlock(block){
    var availableNotchNumbers   = block.getStatementInputNames();
    var thisBlockErrors         = [];

    for(var i=0; i<availableNotchNumbers.length; i++) {
        var notchNumber         = availableNotchNumbers[i];
        var slotContents        = block.getSlotContentsList(notchNumber);
        var errorContext        = block.type+", slot #"+(i+1);  // not a unique context, but will do for now; the slot# makes sense as a local base-1 number

        var actualChildren = getBlockTypesOfSlotContents(slotContents);
        var thisNotchIsValid = validatorDict[block.type][notchNumber].validate(actualChildren)
        console.log("notch", block.type, notchNumber, actualChildren, validatorDict[block.type][notchNumber], thisNotchIsValid );

        if (!thisNotchIsValid) {
            var fields = block.getInput(notchNumber).fieldRow
            thisBlockErrors.push(fields[fields.length-1].getText());
        }
    }

    if (thisBlockErrors.length > 0) {
        block.setWarningText("These connections are not valid: '" + thisBlockErrors.join("'\n'") + "'" );
        return false;
    } else {
        block.setWarningText(null);
        return true;
    }
}

function getBlockTypesOfSlotContents(blockArray){
	var ans = [];
	for(var i=0; i<blockArray.length; i++){
		ans.push( blockArray[i].type );
	}
	return ans;
}
<commit_msg>Less stuff in the log<commit_after>/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ***************************************************************************

    This file contains methods that ensure that the blocks in the Blockly workspace are attached to each other in a valid sequence.
 */

function validateBlocklyGraph(){
    var blocks = blocklyWorkspace.getTopBlocks();

	document.getElementById("XMLOutput").value = "";
    if(blocks.length == 0){
        document.getElementById('validation-error-p').innerHTML = "Workspace is empty";
        return false;
    } else if(blocks.length > 1){
        document.getElementById('validation-error-p').innerHTML = "Only the start block is allowed to be placed directly in the workspace";
        return false;
	} else if(blocks[0].type != "block_0"){
        document.getElementById('validation-error-p').innerHTML = "It is compulsory to use the start block (block_0:start)";
        return false;
    } else {
        var allBlocks = blocklyWorkspace.getAllBlocks();
        var blocklyValidationResult = true;
        for(var i=0; i<allBlocks.length; i++) {
            blocklyValidationResult = validateBlock(allBlocks[i]) && blocklyValidationResult;
        }

        if(blocklyValidationResult) {
            generateXML();
            document.getElementById('validation-error-p').innerHTML = "";
        } else {
            document.getElementById('validation-error-p').innerHTML = "not valid";
        }
        return blocklyValidationResult;
    }
}

var parentConnection = {};

function validateBlockIfNecessary(block, event) {
    var hasBlocks = blocklyWorkspace.getTopBlocks().length > 0;
    console.log(event.toJson(), block.id, blocklyWorkspace.getTopBlocks().length, block.type, (block.getParent() ? block.getParent().type : null));

    if (!hasBlocks) {
        // This happens when the toolbox is created
        return;
    }

    if (event.type == Blockly.Events.MOVE) {
        if (event.newParentId) {
            // A block is moved and attached to another one
            if (event.newParentId == block.id) {
                // Here we check the parent's slot
                validateBlock(block);
                parentConnection[event.blockId] = block;
            }
        } else {
            // The block is supposed to have a parent, but the parent is gone
            if (parentConnection.hasOwnProperty(block.id) && !block.getParent()) {
                // The parent must be checked again
                console.log("connection lost");
                validateBlock(parentConnection[block.id]);
                delete parentConnection[block.id];
            }
        }
    } else if (event.type == Blockly.Events.CREATE && block.id == event.blockId) {
        for(var i=0; i<event.ids.length; i++) {
            var block = blocklyWorkspace.getBlockById( event.ids[i] );
            validateBlock(block);
        }
    }
}

//get all blocks. Send each block's slots for validation. Send each child block of each slot for validation
function validateBlock(block){
    //console.log("validateBlock(", block.type, block.id, ")");
    var availableNotchNumbers   = block.getStatementInputNames();
    var thisBlockErrors         = [];

    for(var i=0; i<availableNotchNumbers.length; i++) {
        var notchNumber         = availableNotchNumbers[i];
        var slotContents        = block.getSlotContentsList(notchNumber);
        var errorContext        = block.type+", slot #"+(i+1);  // not a unique context, but will do for now; the slot# makes sense as a local base-1 number

        var actualChildren = getBlockTypesOfSlotContents(slotContents);
        var thisNotchIsValid = validatorDict[block.type][notchNumber].validate(actualChildren)
        //console.log("notch", notchNumber, actualChildren, validatorDict[block.type][notchNumber], thisNotchIsValid );

        if (!thisNotchIsValid) {
            var fields = block.getInput(notchNumber).fieldRow
            thisBlockErrors.push(fields[fields.length-1].getText());
        }
    }

    if (thisBlockErrors.length > 0) {
        block.setWarningText("These connections are not valid: '" + thisBlockErrors.join("'\n'") + "'" );
        return false;
    } else {
        block.setWarningText(null);
        return true;
    }
}

function getBlockTypesOfSlotContents(blockArray){
	var ans = [];
	for(var i=0; i<blockArray.length; i++){
		ans.push( blockArray[i].type );
	}
	return ans;
}
<|endoftext|>


<commit_before>/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ***************************************************************************

    This file contains methods that ensure that the blocks in the Blockly workspace are attached to each other in a valid sequence.
 */

function validateBlocklyGraph(){
    var blocks = blocklyWorkspace.getTopBlocks();

	document.getElementById("XMLOutput").value = "";
    if(blocks.length == 0){
        document.getElementById('validation-error-p').innerHTML = "Workspace is empty";
        return false;
    } else if(blocks.length > 1){
        document.getElementById('validation-error-p').innerHTML = "Only the start block is allowed to be placed directly in the workspace";
        return false;
	} else if(blocks[0].type != "block_0"){
        document.getElementById('validation-error-p').innerHTML = "It is compulsory to use the start block (block_0:start)";
        return false;
    } else {
        var allBlocks = blocklyWorkspace.getAllBlocks();
        var blocklyValidationResult = true;
        for(var i=0; i<allBlocks.length; i++) {
            blocklyValidationResult = validateBlock(allBlocks[i]) && blocklyValidationResult;
        }

        if(blocklyValidationResult) {
            generateXML();
            document.getElementById('validation-error-p').innerHTML = "";
        } else {
            document.getElementById('validation-error-p').innerHTML = "not valid";
        }
        return blocklyValidationResult;
    }
}

var parentConnection = {};

function validateBlockIfNecessary(block, event) {
    var hasBlocks = blocklyWorkspace.getTopBlocks().length > 0;
    console.log(event.toJson(), block.id, blocklyWorkspace.getTopBlocks().length, block.type, (block.getParent() ? block.getParent().type : null));

    if (!hasBlocks) {
        // This happens when the toolbox is created
        return;
    }

    if (event.type == Blockly.Events.MOVE) {
        if (event.newParentId) {
            // A block is moved and attached to another one
            if (event.newParentId == block.id) {
                // Here we check the parent's slot
                validateBlock(block);
                parentConnection[event.blockId] = block;
            }
        } else {
            // The block is supposed to have a parent, but the parent is gone
            if (parentConnection.hasOwnProperty(block.id) && !block.getParent()) {
                // The parent must be checked again
                console.log("connection lost");
                validateBlock(parentConnection[block.id]);
                delete parentConnection[block.id];
            }
        }
    } else if (event.type == Blockly.Events.CREATE && block.id == event.blockId) {
        for(var i=0; i<event.ids.length; i++) {
            var block = blocklyWorkspace.getBlockById( event.ids[i] );
            validateBlock(block);
        }
    }
}

//get all blocks. Send each block's slots for validation. Send each child block of each slot for validation
function validateBlock(block){
    //console.log("validateBlock(", block.type, block.id, ")");
    var availableNotchNumbers   = block.getStatementInputNames();
    var thisBlockErrors         = [];

    for(var i=0; i<availableNotchNumbers.length; i++) {
        var notchNumber         = availableNotchNumbers[i];
        var slotContents        = block.getSlotContentsList(notchNumber);
        var errorContext        = block.type+", slot #"+(i+1);  // not a unique context, but will do for now; the slot# makes sense as a local base-1 number

        var actualChildren = getBlockTypesOfSlotContents(slotContents);
        var thisNotchIsValid = validatorDict[block.type][notchNumber].validate(actualChildren)
        //console.log("notch", notchNumber, actualChildren, validatorDict[block.type][notchNumber], thisNotchIsValid );

        if (!thisNotchIsValid) {
            var fields = block.getInput(notchNumber).fieldRow
            thisBlockErrors.push(fields[fields.length-1].getText());
        }
    }

    if (thisBlockErrors.length > 0) {
        block.setWarningText("These connections are not valid: '" + thisBlockErrors.join("'\n'") + "'" );
        return false;
    } else {
        block.setWarningText(null);
        return true;
    }
}

function getBlockTypesOfSlotContents(blockArray){
	var ans = [];
	for(var i=0; i<blockArray.length; i++){
		ans.push( blockArray[i].type );
	}
	return ans;
}
<commit_msg>Need to register the connections of the new blocks too<commit_after>/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ***************************************************************************

    This file contains methods that ensure that the blocks in the Blockly workspace are attached to each other in a valid sequence.
 */

function validateBlocklyGraph(){
    var blocks = blocklyWorkspace.getTopBlocks();

	document.getElementById("XMLOutput").value = "";
    if(blocks.length == 0){
        document.getElementById('validation-error-p').innerHTML = "Workspace is empty";
        return false;
    } else if(blocks.length > 1){
        document.getElementById('validation-error-p').innerHTML = "Only the start block is allowed to be placed directly in the workspace";
        return false;
	} else if(blocks[0].type != "block_0"){
        document.getElementById('validation-error-p').innerHTML = "It is compulsory to use the start block (block_0:start)";
        return false;
    } else {
        var allBlocks = blocklyWorkspace.getAllBlocks();
        var blocklyValidationResult = true;
        for(var i=0; i<allBlocks.length; i++) {
            blocklyValidationResult = validateBlock(allBlocks[i]) && blocklyValidationResult;
        }

        if(blocklyValidationResult) {
            generateXML();
            document.getElementById('validation-error-p').innerHTML = "";
        } else {
            document.getElementById('validation-error-p').innerHTML = "not valid";
        }
        return blocklyValidationResult;
    }
}

var parentConnection = {};

function validateBlockIfNecessary(block, event) {
    var hasBlocks = blocklyWorkspace.getTopBlocks().length > 0;
    console.log(event.toJson(), block.id, blocklyWorkspace.getTopBlocks().length, block.type, (block.getParent() ? block.getParent().type : null));

    if (!hasBlocks) {
        // This happens when the toolbox is created
        return;
    }

    if (event.type == Blockly.Events.MOVE) {
        if (event.newParentId) {
            // A block is moved and attached to another one
            if (event.newParentId == block.id) {
                // Here we check the parent's slot
                validateBlock(block);
                parentConnection[event.blockId] = block;
            }
        } else {
            // The block is supposed to have a parent, but the parent is gone
            if (parentConnection.hasOwnProperty(block.id) && !block.getParent()) {
                // The parent must be checked again
                console.log("connection lost");
                validateBlock(parentConnection[block.id]);
                delete parentConnection[block.id];
            }
        }
    } else if (event.type == Blockly.Events.CREATE && block.id == event.blockId) {
        for(var i=0; i<event.ids.length; i++) {
            var block = blocklyWorkspace.getBlockById( event.ids[i] );
            validateBlock(block);
            if (block.getParent()) {
                parentConnection[block.id] = block.getParent();
            }
        }
    }
}

//get all blocks. Send each block's slots for validation. Send each child block of each slot for validation
function validateBlock(block){
    //console.log("validateBlock(", block.type, block.id, ")");
    var availableNotchNumbers   = block.getStatementInputNames();
    var thisBlockErrors         = [];

    for(var i=0; i<availableNotchNumbers.length; i++) {
        var notchNumber         = availableNotchNumbers[i];
        var slotContents        = block.getSlotContentsList(notchNumber);
        var errorContext        = block.type+", slot #"+(i+1);  // not a unique context, but will do for now; the slot# makes sense as a local base-1 number

        var actualChildren = getBlockTypesOfSlotContents(slotContents);
        var thisNotchIsValid = validatorDict[block.type][notchNumber].validate(actualChildren)
        //console.log("notch", notchNumber, actualChildren, validatorDict[block.type][notchNumber], thisNotchIsValid );

        if (!thisNotchIsValid) {
            var fields = block.getInput(notchNumber).fieldRow
            thisBlockErrors.push(fields[fields.length-1].getText());
        }
    }

    if (thisBlockErrors.length > 0) {
        block.setWarningText("These connections are not valid: '" + thisBlockErrors.join("'\n'") + "'" );
        return false;
    } else {
        block.setWarningText(null);
        return true;
    }
}

function getBlockTypesOfSlotContents(blockArray){
	var ans = [];
	for(var i=0; i<blockArray.length; i++){
		ans.push( blockArray[i].type );
	}
	return ans;
}
<|endoftext|>


<commit_before>import { $$, Component } from '../dom'
import { Form, FormRow, Input, Modal, MultiInput, MultiSelect } from '../ui'
import { cloneDeep } from '../util'

export default class AuthorModal extends Component {
  getActionHandlers () {
    return {
      addMultiInputItem: this._addMultiInputItem,
      updateMultiInputItem: this._updateMultiInputItem,
      removeMultiInputItem: this._removeMultiInputItem
    }
  }

  getInitialState () {
    const { node } = this.props
    let data
    if (node) {
      data = {
        firstName: node.firstName || '',
        middleNames: node.middleNames && node.middleNames.length > 0 ? node.middleNames.slice() : [''],
        lastName: node.lastName || '',
        prefix: node.prefix || '',
        suffix: node.suffix || '',
        affiliations: node.affiliations ? node.affiliations.slice() : []
      }
    } else {
      data = {
        firstName: '',
        middleNames: [''],
        lastName: '',
        prefix: '',
        suffix: '',
        affiliations: []
      }
    }

    return {
      data
    }
  }

  render () {
    const { document, mode } = this.props
    const { data } = this.state
    const title = mode === 'create' ? 'Create Author' : 'Edit Author'
    const confirmLabel = mode === 'edit' ? 'Update Author' : 'Create Author'

    const root = document.root
    const allAffiliations = root.resolve('affiliations')

    const el = $$(Modal, { class: 'sc-author-modal', title, cancelLabel: 'Cancel', confirmLabel, size: 'large' })
    const form = $$(Form).append(
      // first name (required)
      $$(FormRow, { label: 'First Name' },
        $$(Input, { autofocus: true, value: data.firstName, oninput: this._updateFirstName }).ref('firstName')
      ),
      // last name (required)
      $$(FormRow, { label: 'Last Name' },
        $$(Input, { value: data.lastName || '', oninput: this._updateLastName }).ref('lastName')
      ),
      $$(FormRow, { label: 'Middle Names', class: 'se-middle-names' },
        $$(MultiInput, { name: 'middleNames', value: data.middleNames, addLabel: 'Add Middlename' })
      ),
      // prefix (optional)
      $$(FormRow, { label: 'Prefix' },
        $$(Input, { value: data.prefix, oninput: this._updatePrefix }).ref('prefix')
      ),
      // suffix (optional)
      $$(FormRow, { label: 'Suffix' },
        $$(Input, { value: data.suffix, oninput: this._updateSuffix }).ref('suffix')
      )
    )

    // only show this if there are any affiliations available
    if (allAffiliations.length > 0) {
      form.append(
        $$(FormRow, { label: 'Affiliations' },
          $$(MultiSelect, {
            options: allAffiliations.map(aff => {
              return { value: aff.id, label: aff.name }
            }),
            selected: data.affiliations,
            placeholder: 'Add Affiliation',
            onchange: this._updateAffiliations
          }).ref('affiliations')
        )
      )
    }

    el.append(form)

    return el
  }

  _addMultiInputItem (name) {
    const data = cloneDeep(this.state.data)
    data[name].push('')
    this.extendState({
      data
    })
  }

  _updateMultiInputItem (name, idx, value) {
    this.state.data[name][idx] = value
  }

  _removeMultiInputItem (name, idx) {
    this.state.data[name].splice(idx, 1)
    this.rerender()
  }

  _updatePrefix () {
    this.state.data.prefix = this.refs.prefix.val()
  }

  _updateFirstName () {
    this.state.data.firstName = this.refs.firstName.val()
  }

  _updateLastName () {
    this.state.data.lastName = this.refs.lastName.val()
  }

  _updateSuffix () {
    this.state.data.suffix = this.refs.suffix.val()
  }

  _updateAffiliations () {
    this.state.data.affiliations = this.refs.affiliations.getSelectedValues()
  }
}
<commit_msg>Streamline AuthorModal using new MultiInput.<commit_after>import { $$, Component } from '../dom'
import { Form, FormRow, Input, Modal, MultiInput, MultiSelect } from '../ui'

export default class AuthorModal extends Component {
  getInitialState () {
    const { node } = this.props
    let data
    if (node) {
      data = {
        firstName: node.firstName || '',
        middleNames: node.middleNames && node.middleNames.length > 0 ? node.middleNames.slice() : [''],
        lastName: node.lastName || '',
        prefix: node.prefix || '',
        suffix: node.suffix || '',
        affiliations: node.affiliations ? node.affiliations.slice() : []
      }
    } else {
      data = {
        firstName: '',
        middleNames: [''],
        lastName: '',
        prefix: '',
        suffix: '',
        affiliations: []
      }
    }

    return {
      data
    }
  }

  render () {
    const { document, mode } = this.props
    const { data } = this.state
    const title = mode === 'create' ? 'Create Author' : 'Edit Author'
    const confirmLabel = mode === 'edit' ? 'Update Author' : 'Create Author'

    const root = document.root
    const allAffiliations = root.resolve('affiliations')

    const el = $$(Modal, { class: 'sc-author-modal', title, cancelLabel: 'Cancel', confirmLabel, size: 'large' })
    const form = $$(Form).append(
      // first name (required)
      $$(FormRow, { label: 'First Name' },
        $$(Input, { autofocus: true, value: data.firstName, oninput: this._updateFirstName }).ref('firstName')
      ),
      // last name (required)
      $$(FormRow, { label: 'Last Name' },
        $$(Input, { value: data.lastName || '', oninput: this._updateLastName }).ref('lastName')
      ),
      $$(FormRow, { label: 'Middle Names', class: 'se-middle-names' },
        $$(MultiInput, {
          value: data.middleNames,
          addLabel: 'Add Middlename',
          onchange: this._updateMiddleNames.bind(this)
        })
      ),
      // prefix (optional)
      $$(FormRow, { label: 'Prefix' },
        $$(Input, { value: data.prefix, oninput: this._updatePrefix }).ref('prefix')
      ),
      // suffix (optional)
      $$(FormRow, { label: 'Suffix' },
        $$(Input, { value: data.suffix, oninput: this._updateSuffix }).ref('suffix')
      )
    )

    // only show this if there are any affiliations available
    if (allAffiliations.length > 0) {
      form.append(
        $$(FormRow, { label: 'Affiliations' },
          $$(MultiSelect, {
            options: allAffiliations.map(aff => {
              return { value: aff.id, label: aff.name }
            }),
            selected: data.affiliations,
            placeholder: 'Add Affiliation',
            onchange: this._updateAffiliations
          }).ref('affiliations')
        )
      )
    }

    el.append(form)

    return el
  }

  _updateMiddleNames (event) {
    const { value } = event.detail
    this.state.data.middleNames = value
  }

  _updatePrefix () {
    this.state.data.prefix = this.refs.prefix.val()
  }

  _updateFirstName () {
    this.state.data.firstName = this.refs.firstName.val()
  }

  _updateLastName () {
    this.state.data.lastName = this.refs.lastName.val()
  }

  _updateSuffix () {
    this.state.data.suffix = this.refs.suffix.val()
  }

  _updateAffiliations () {
    this.state.data.affiliations = this.refs.affiliations.getSelectedValues()
  }
}
<|endoftext|>


<commit_before>'use strict';

/**
 * Useful things related to REST, including the
 * automatic translation of Ravel.ApplicationError
 * errors into appropriate response codes
 */

var url = require('url');

module.exports = function(Ravel) {
  var scope = {
    OK:200,
    CREATED:201,
    NO_CONTENT:204,
    PARTIAL_CONTENT:206,
    UNAUTHORIZED:401,
    FORBIDDEN:403,
    NOT_FOUND:404,
    METHOD_NOT_ALLOWED:405,
    CONFLICT:409,
    REQUESTED_RANGE_NOT_SATISFIABLE:416,
    INTERNAL_SERVER_ERROR:500,
    NOT_IMPLEMENTED:501,

    /**
     * Translates API JavaScript errors into equivalent HTTP response codes, and/or
     * packages the result of the API call into the response.
     *
     * @param {Object} req Express.js request
     * @param {Object} res Express.js response
     * @param {Object | null} err Error object from API, or null
     * @param {Object | undefined} result the JSON payload to send in the reponse
     * @param {Object | undefined} Desired 'success' response code (20x) to send if
     *                             there are no API errors. 200 OK by default  for
     *                             GET/PUT/DELETE and 201 CREATED by default for POST
     * @param {Object} options any extra data
     */
    buildRestResponse: function(req, res, err, result, okCode, options) {
      if (okCode === undefined) {
        if (result && req.method === 'POST') {
          okCode = scope.CREATED;
        } else if (result) {
          okCode = scope.OK;
        } else {
          okCode = scope.NO_CONTENT;
        }
      } else if (okCode === scope.CREATED && result !== undefined && result.id) {
        //try to set location header if we're creating something
        res.location(url.resolve(String(req.headers.origin),String(req.url))+'/'+String(result.id));
      } else if (okCode === scope.PARTIAL_CONTENT && result !== undefined &&
                 options !== undefined && options.start !== undefined &&
                 options.end !== undefined && options.count !== undefined) {
        res.setHeader('Content-Range', 'items '+options.start+'-'+options.end+'/'+options.count);
      }

      if (err) {
        if (err instanceof Ravel.ApplicationError.NotFound) {
          res.status(scope.NOT_FOUND).end();
        } else if (err instanceof Ravel.ApplicationError.Access) {
          res.status(scope.FORBIDDEN).end();
        } else if (err instanceof Ravel.ApplicationError.NotAllowed) {
          res.status(scope.METHOD_NOT_ALLOWED).end();
        } else if (err instanceof Ravel.ApplicationError.NotImplemented) {
          res.status(scope.NOT_IMPLEMENTED).end();
        } else if (err instanceof Ravel.ApplicationError.DuplicateEntry) {
          res.status(scope.CONFLICT).end();
        } else if (err instanceof Ravel.ApplicationError.RangeOutOfBounds) {
          res.status(scope.REQUESTED_RANGE_NOT_SATISFIABLE).end();
        } else if (err instanceof Ravel.ApplicationError.IllegalValue) {
          res.status(scope.NOT_FOUND).end();
        } else {
          res.status(scope.INTERNAL_SERVER_ERROR).end();
        }
      } else {
        //protect against JSON vulnerability
        //http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx/
        if (Ravel.get('disable json vulnerability protection') && result) {
          res.status(okCode).send(result);
        } else if (result) {
          var prefixed = ')]}\',\n' + JSON.stringify(result);
          res.status(okCode).send(prefixed);
        } else {
          res.status(okCode).end();
        }
      }
    }
  };

  return scope;
};
<commit_msg>Fixing unreachable code in util/rest<commit_after>'use strict';

/**
 * Useful things related to REST, including the
 * automatic translation of Ravel.ApplicationError
 * errors into appropriate response codes
 */

var url = require('url');

module.exports = function(Ravel) {
  var scope = {
    OK:200,
    CREATED:201,
    NO_CONTENT:204,
    PARTIAL_CONTENT:206,
    UNAUTHORIZED:401,
    FORBIDDEN:403,
    NOT_FOUND:404,
    METHOD_NOT_ALLOWED:405,
    CONFLICT:409,
    REQUESTED_RANGE_NOT_SATISFIABLE:416,
    INTERNAL_SERVER_ERROR:500,
    NOT_IMPLEMENTED:501,

    /**
     * Translates API JavaScript errors into equivalent HTTP response codes, and/or
     * packages the result of the API call into the response.
     *
     * @param {Object} req Express.js request
     * @param {Object} res Express.js response
     * @param {Object | null} err Error object from API, or null
     * @param {Object | undefined} result the JSON payload to send in the reponse
     * @param {Object | undefined} Desired 'success' response code (20x) to send if
     *                             there are no API errors. 200 OK by default  for
     *                             GET/PUT/DELETE and 201 CREATED by default for POST
     * @param {Object} options any extra data
     */
    buildRestResponse: function(req, res, err, result, okCode, options) {
      if (okCode === undefined) {
        if (result && req.method === 'POST') {
          okCode = scope.CREATED;
          //try to set location header if we're creating something
          if (result.id) {
            res.location(url.resolve(String(req.headers.origin),String(req.url))+'/'+String(result.id));
          }
        } else if (result) {
          okCode = scope.OK;
        } else {
          okCode = scope.NO_CONTENT;
        }
      } else if (okCode === scope.PARTIAL_CONTENT && result !== undefined &&
                 options !== undefined && options.start !== undefined &&
                 options.end !== undefined && options.count !== undefined) {
        res.setHeader('Content-Range', 'items '+options.start+'-'+options.end+'/'+options.count);
      }

      if (err) {
        if (err instanceof Ravel.ApplicationError.NotFound) {
          res.status(scope.NOT_FOUND).end();
        } else if (err instanceof Ravel.ApplicationError.Access) {
          res.status(scope.FORBIDDEN).end();
        } else if (err instanceof Ravel.ApplicationError.NotAllowed) {
          res.status(scope.METHOD_NOT_ALLOWED).end();
        } else if (err instanceof Ravel.ApplicationError.NotImplemented) {
          res.status(scope.NOT_IMPLEMENTED).end();
        } else if (err instanceof Ravel.ApplicationError.DuplicateEntry) {
          res.status(scope.CONFLICT).end();
        } else if (err instanceof Ravel.ApplicationError.RangeOutOfBounds) {
          res.status(scope.REQUESTED_RANGE_NOT_SATISFIABLE).end();
        } else if (err instanceof Ravel.ApplicationError.IllegalValue) {
          res.status(scope.NOT_FOUND).end();
        } else {
          res.status(scope.INTERNAL_SERVER_ERROR).end();
        }
      } else {
        //protect against JSON vulnerability
        //http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx/
        if (Ravel.get('disable json vulnerability protection') && result) {
          res.status(okCode).send(result);
        } else if (result) {
          var prefixed = ')]}\',\n' + JSON.stringify(result);
          res.status(okCode).send(prefixed);
        } else {
          res.status(okCode).end();
        }
      }
    }
  };

  return scope;
};
<|endoftext|>


<commit_before>'use strict';

var fs            = require('fs');
var path          = require('path');
var fsReadDirSync = fs.readdirSync;
var resolve       = path.resolve;
var statSync      = fs.statSync;

/*
  ┌─────────────────────────────────────────────────────────────────────────────
  │ args:
  │ 		folderPath [string] - a resolved path to an existing folder.
  │
  │ returns:
  │ 		mappedObj [obj] - folder mappings json obj.
  │
  │     mappedObj = {
  │         path: 'absolute/path/to/entry',
  │         type: 0|1,  // folder|file
  │         entries: {
  │             {mappedObj},
  │             {mappedObj},
  │             {mappedObj}
  │         }
  │     };
  │
  │ return_example = {
  │		path : 'C:\\bootstruct\\www',
  │		entries : {
  │			'first.js' : {
  │				path : 'C:\\bootstruct\\www\\first.js',
  │				type : 1
  │			},
  │			foo : {
  │				path : 'C:\\bootstruct\\www\\foo',
  │				type : 0,
  │				entries : {
  │					'first.js' : {
  │						path : 'C:\\bootstruct\\www\\foo\\first.js',
  │						type : 1
  │					},
  │					'last.js' : {
  │						path : 'C:\\bootstruct\\www\\foo\\last.js',
  │						type : 1
  │					},
  │					verbs : {
  │						path : 'Cbootstruct\\www\\foo\\verbs',
  │						entries : [Object],
  │						type : 0
  │					}
  │				}
  │			},
  │			'get.js' : {
  │				path : 'F:\\Google Drive\\Code\\Javascript\\Node\\_tests\\bootstruct\\www\\get.js',
  │				type : 1
  │			},
  │			'last.js' : {
  │				path : 'F:\\Google Drive\\Code\\Javascript\\Node\\_tests\\bootstruct\\www\\last.js',
  │				type : 1
  │			}
  │		}
  │	};
  │
 */
var f2j = function (_path) {
	var folderObj = {
		path    :_path,
		type    : 0,
		entries :{}
	};

	var entries = fsReadDirSync(resolve(_path));

	entries.forEach(function (entryName) {
		var newObj;
		var resolved  = resolve(_path, entryName);
		var entryStat = statSync(resolved);
		var isFile    = entryStat.isFile();

		if (isFile) {
			newObj = {
				path: resolved,
				type: 1
			};
		}
		else {
			newObj = f2j(resolved);
		}

		folderObj.entries[entryName] = newObj;
	});

	return folderObj;
};

// ------------------
module.exports = f2j;<commit_msg>new line at the end of the file<commit_after>'use strict';

var fs            = require('fs');
var path          = require('path');
var fsReadDirSync = fs.readdirSync;
var resolve       = path.resolve;
var statSync      = fs.statSync;

/*
  ┌─────────────────────────────────────────────────────────────────────────────
  │ args:
  │ 		folderPath [string] - a resolved path to an existing folder.
  │
  │ returns:
  │ 		mappedObj [obj] - folder mappings json obj.
  │
  │     mappedObj = {
  │         path: 'absolute/path/to/entry',
  │         type: 0|1,  // folder|file
  │         entries: {
  │             {mappedObj},
  │             {mappedObj},
  │             {mappedObj}
  │         }
  │     };
  │
  │ return_example = {
  │		path : 'C:\\bootstruct\\www',
  │		entries : {
  │			'first.js' : {
  │				path : 'C:\\bootstruct\\www\\first.js',
  │				type : 1
  │			},
  │			foo : {
  │				path : 'C:\\bootstruct\\www\\foo',
  │				type : 0,
  │				entries : {
  │					'first.js' : {
  │						path : 'C:\\bootstruct\\www\\foo\\first.js',
  │						type : 1
  │					},
  │					'last.js' : {
  │						path : 'C:\\bootstruct\\www\\foo\\last.js',
  │						type : 1
  │					},
  │					verbs : {
  │						path : 'Cbootstruct\\www\\foo\\verbs',
  │						entries : [Object],
  │						type : 0
  │					}
  │				}
  │			},
  │			'get.js' : {
  │				path : 'F:\\Google Drive\\Code\\Javascript\\Node\\_tests\\bootstruct\\www\\get.js',
  │				type : 1
  │			},
  │			'last.js' : {
  │				path : 'F:\\Google Drive\\Code\\Javascript\\Node\\_tests\\bootstruct\\www\\last.js',
  │				type : 1
  │			}
  │		}
  │	};
  │
 */
var f2j = function (_path) {
	var folderObj = {
		path    :_path,
		type    : 0,
		entries :{}
	};

	var entries = fsReadDirSync(resolve(_path));

	entries.forEach(function (entryName) {
		var newObj;
		var resolved  = resolve(_path, entryName);
		var entryStat = statSync(resolved);
		var isFile    = entryStat.isFile();

		if (isFile) {
			newObj = {
				path: resolved,
				type: 1
			};
		}
		else {
			newObj = f2j(resolved);
		}

		folderObj.entries[entryName] = newObj;
	});

	return folderObj;
};

// ------------------
module.exports = f2j;
<|endoftext|>