<commit_before>/*
 * Vector rendering for all browsers that support canvas.
 */

L.Browser.canvas = (function () {
	return !!document.createElement('canvas').getContext;
}());

L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path : L.Path.extend({
	statics: {
		//CLIP_PADDING: 0.02, // not sure if there's a need to set it to a small value
		CANVAS: true,
		SVG: false
	},

	redraw: function () {
		if (this._map) {
			this.projectLatlngs();
			this._requestUpdate();
		}
		return this;
	},

	setStyle: function (style) {
		L.setOptions(this, style);

		if (this._map) {
			this._updateStyle();
			this._requestUpdate();
		}
		return this;
	},

	onRemove: function (map) {
		map
		    .off('viewreset', this.projectLatlngs, this)
		    .off('moveend', this._updatePath, this);

		if (this.options.clickable) {
			this._map.off('click', this._onClick, this);
		}

		this._requestUpdate();

		this._map = null;
	},

	_requestUpdate: function () {
		if (this._map && !L.Path._updateRequest) {
			L.Path._updateRequest = L.Util.requestAnimFrame(this._fireMapMoveEnd, this._map);
		}
	},

	_fireMapMoveEnd: function () {
		L.Path._updateRequest = null;
		this.fire('moveend');
	},

	_initElements: function () {
		this._map._initPathRoot();
		this._ctx = this._map._canvasCtx;
	},

	_updateStyle: function () {
		var options = this.options;

		if (options.stroke) {
			this._ctx.lineWidth = options.weight;
			this._ctx.strokeStyle = options.color;
		}
		if (options.fill) {
			this._ctx.fillStyle = options.fillColor || options.color;
		}
	},

	_drawPath: function () {
		var i, j, len, len2, point, drawMethod;

		this._ctx.beginPath();

		for (i = 0, len = this._parts.length; i < len; i++) {
			for (j = 0, len2 = this._parts[i].length; j < len2; j++) {
				point = this._parts[i][j];
				drawMethod = (j === 0 ? 'move' : 'line') + 'To';

				this._ctx[drawMethod](point.x, point.y);
			}
			// TODO refactor ugly hack
			if (this instanceof L.Polygon) {
				this._ctx.closePath();
			}
		}
	},

	_checkIfEmpty: function () {
		return !this._parts.length;
	},

	_updatePath: function () {
		if (this._checkIfEmpty()) { return; }

		var ctx = this._ctx,
		    options = this.options;

		this._drawPath();
		ctx.save();
		this._updateStyle();

		if (options.fill) {
			ctx.globalAlpha = options.fillOpacity;
			ctx.fill();
		}

		if (options.stroke) {
			ctx.globalAlpha = options.opacity;
			ctx.stroke();
		}

		ctx.restore();

		// TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
	},

	_initEvents: function () {
		if (this.options.clickable) {
			// TODO mouseout, dblclick
			this._map.on('mousemove', this._onMouseMove, this);
			this._map.on('click', this._onClick, this);
		}
	},

	_onClick: function (e) {
		if (this._containsPoint(e.layerPoint)) {
			this.fire('click', {
				latlng: e.latlng,
				layerPoint: e.layerPoint,
				containerPoint: e.containerPoint,
				originalEvent: e
			});
		}
	},

	_onMouseMove: function (e) {
		if (this._containsPoint(e.layerPoint)) {
			this._ctx.canvas.style.cursor = 'pointer';
			this.fire('mouseover', {
				latlng: e.latlng,
				layerPoint: e.layerPoint,
				containerPoint: e.containerPoint,
				originalEvent: e
			});
		} else {
			this._ctx.canvas.style.cursor = '';
		}
	}
});

L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {} : {
	_initPathRoot: function () {
		var root = this._pathRoot,
		    ctx;

		if (!root) {
			root = this._pathRoot = document.createElement("canvas");
			root.style.position = 'absolute';
			ctx = this._canvasCtx = root.getContext('2d');

			ctx.lineCap = "round";
			ctx.lineJoin = "round";

			this._panes.overlayPane.appendChild(root);

			if (this.options.zoomAnimation) {
				this._pathRoot.className = 'leaflet-zoom-animated';
				this.on('zoomanim', this._animatePathZoom);
				this.on('zoomend', this._endPathZoom);
			}
			this.on('moveend', this._updateCanvasViewport);
			this._updateCanvasViewport();
		}
	},

	_updateCanvasViewport: function () {
		// don't redraw while zooming. See _updateSvgViewport for more details
		if (this._pathZooming) { return; }
		this._updatePathViewport();

		var vp = this._pathViewport,
		    min = vp.min,
		    size = vp.max.subtract(min),
		    root = this._pathRoot;

		//TODO check if this works properly on mobile webkit
		L.DomUtil.setPosition(root, min);
		root.width = size.x;
		root.height = size.y;
		root.getContext('2d').translate(-min.x, -min.y);
	}
});
<commit_msg>don't perform if animatingZoom<commit_after>/*
 * Vector rendering for all browsers that support canvas.
 */

L.Browser.canvas = (function () {
	return !!document.createElement('canvas').getContext;
}());

L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path : L.Path.extend({
	statics: {
		//CLIP_PADDING: 0.02, // not sure if there's a need to set it to a small value
		CANVAS: true,
		SVG: false
	},

	redraw: function () {
		if (this._map) {
			this.projectLatlngs();
			this._requestUpdate();
		}
		return this;
	},

	setStyle: function (style) {
		L.setOptions(this, style);

		if (this._map) {
			this._updateStyle();
			this._requestUpdate();
		}
		return this;
	},

	onRemove: function (map) {
		map
		    .off('viewreset', this.projectLatlngs, this)
		    .off('moveend', this._updatePath, this);

		if (this.options.clickable) {
			this._map.off('click', this._onClick, this);
		}

		this._requestUpdate();

		this._map = null;
	},

	_requestUpdate: function () {
		if (this._map && !L.Path._updateRequest) {
			L.Path._updateRequest = L.Util.requestAnimFrame(this._fireMapMoveEnd, this._map);
		}
	},

	_fireMapMoveEnd: function () {
		L.Path._updateRequest = null;
		this.fire('moveend');
	},

	_initElements: function () {
		this._map._initPathRoot();
		this._ctx = this._map._canvasCtx;
	},

	_updateStyle: function () {
		var options = this.options;

		if (options.stroke) {
			this._ctx.lineWidth = options.weight;
			this._ctx.strokeStyle = options.color;
		}
		if (options.fill) {
			this._ctx.fillStyle = options.fillColor || options.color;
		}
	},

	_drawPath: function () {
		var i, j, len, len2, point, drawMethod;

		this._ctx.beginPath();

		for (i = 0, len = this._parts.length; i < len; i++) {
			for (j = 0, len2 = this._parts[i].length; j < len2; j++) {
				point = this._parts[i][j];
				drawMethod = (j === 0 ? 'move' : 'line') + 'To';

				this._ctx[drawMethod](point.x, point.y);
			}
			// TODO refactor ugly hack
			if (this instanceof L.Polygon) {
				this._ctx.closePath();
			}
		}
	},

	_checkIfEmpty: function () {
		return !this._parts.length;
	},

	_updatePath: function () {
		if (this._checkIfEmpty()) { return; }

		var ctx = this._ctx,
		    options = this.options;

		this._drawPath();
		ctx.save();
		this._updateStyle();

		if (options.fill) {
			ctx.globalAlpha = options.fillOpacity;
			ctx.fill();
		}

		if (options.stroke) {
			ctx.globalAlpha = options.opacity;
			ctx.stroke();
		}

		ctx.restore();

		// TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
	},

	_initEvents: function () {
		if (this.options.clickable) {
			// TODO mouseout, dblclick
			this._map.on('mousemove', this._onMouseMove, this);
			this._map.on('click', this._onClick, this);
		}
	},

	_onClick: function (e) {
		if (this._containsPoint(e.layerPoint)) {
			this.fire('click', {
				latlng: e.latlng,
				layerPoint: e.layerPoint,
				containerPoint: e.containerPoint,
				originalEvent: e
			});
		}
	},

	_onMouseMove: function (e) {
		if (this._map._animatingZoom) { return; }

		if (this._containsPoint(e.layerPoint)) {
			this._ctx.canvas.style.cursor = 'pointer';
			this.fire('mouseover', {
				latlng: e.latlng,
				layerPoint: e.layerPoint,
				containerPoint: e.containerPoint,
				originalEvent: e
			});
		} else {
			this._ctx.canvas.style.cursor = '';
		}
	}
});

L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {} : {
	_initPathRoot: function () {
		var root = this._pathRoot,
		    ctx;

		if (!root) {
			root = this._pathRoot = document.createElement("canvas");
			root.style.position = 'absolute';
			ctx = this._canvasCtx = root.getContext('2d');

			ctx.lineCap = "round";
			ctx.lineJoin = "round";

			this._panes.overlayPane.appendChild(root);

			if (this.options.zoomAnimation) {
				this._pathRoot.className = 'leaflet-zoom-animated';
				this.on('zoomanim', this._animatePathZoom);
				this.on('zoomend', this._endPathZoom);
			}
			this.on('moveend', this._updateCanvasViewport);
			this._updateCanvasViewport();
		}
	},

	_updateCanvasViewport: function () {
		// don't redraw while zooming. See _updateSvgViewport for more details
		if (this._pathZooming) { return; }
		this._updatePathViewport();

		var vp = this._pathViewport,
		    min = vp.min,
		    size = vp.max.subtract(min),
		    root = this._pathRoot;

		//TODO check if this works properly on mobile webkit
		L.DomUtil.setPosition(root, min);
		root.width = size.x;
		root.height = size.y;
		root.getContext('2d').translate(-min.x, -min.y);
	}
});
<|endoftext|>


<commit_before>/*
 * L.TimeDimension.Layer.WMS: wms Layer associated to a TimeDimension
 */

L.TimeDimension.Layer.WMS = L.TimeDimension.Layer.extend({

    initialize: function(layer, options) {
        L.TimeDimension.Layer.prototype.initialize.call(this, layer, options);
        this._timeCacheBackward = this.options.cacheBackward || this.options.cache || 0;
        this._timeCacheForward = this.options.cacheForward || this.options.cache || 0;
        this._wmsVersion = this.options.wmsVersion || this.options.version || layer.options.version || "1.1.1";
        this._getCapabilitiesParams = this.options.getCapabilitiesParams || {};
        this._getCapabilitiesAlternateUrl = this.options.getCapabilitiesUrl || null;
        this._getCapabilitiesAlternateLayerName = this.options.getCapabilitiesLayerName || null;
        this._proxy = this.options.proxy || null;
        this._updateTimeDimension = this.options.updateTimeDimension || false;
        this._setDefaultTime = this.options.setDefaultTime || false;
        this._updateTimeDimensionMode = this.options.updateTimeDimensionMode || 'intersect'; // 'union' or 'replace'
        this._period = this.options.period || null;
        this._layers = {};
        this._defaultTime = 0;
        this._availableTimes = [];
        this._capabilitiesRequested = false;
        if (this._updateTimeDimension || this.options.requestTimeFromCapabilities) {
            this._requestTimeDimensionFromCapabilities();
        }

        this._baseLayer.on('load', (function() {
            this._baseLayer.setLoaded(true);
            this.fire('timeload', {
                time: this._defaultTime
            });
        }).bind(this));
    },

    getEvents: function() {
        var clearCache = L.bind(this._unvalidateCache, this);
        return {
            moveend: clearCache,
            zoomend: clearCache
        }
    },

    eachLayer: function(method, context) {
        for (var prop in this._layers) {
            if (this._layers.hasOwnProperty(prop)) {
                method.call(context, this._layers[prop]);
            }
        }
        return L.TimeDimension.Layer.prototype.eachLayer.call(this, method, context);
    },

    _onNewTimeLoading: function(ev) {
        // console.log('Layer._onNewTimeLoading: ' + this._baseLayer.wmsParams.layers + ' with time: ' + new Date(ev.time).toISOString());
        var layer = this._getLayerForTime(ev.time);
        if (!this._map.hasLayer(layer)) {
            this._map.addLayer(layer);
            // console.log('Layer._onNewTimeLoading: layer added to map');
        }
    },

    isReady: function(time) {
        var layer = this._getLayerForTime(time);
        if (this.options.bounds && this._map)
            if (!this._map.getBounds().contains(this.options.bounds))
                return true;
        return layer.isLoaded();
    },

    onAdd: function(map) {
        L.TimeDimension.Layer.prototype.onAdd.call(this, map);
        if (this._availableTimes.length == 0) {
            this._requestTimeDimensionFromCapabilities();
        } else {
            this._updateTimeDimensionAvailableTimes();
        }
    },

    _update: function() {
        if (!this._map)
            return;
        var time = this._timeDimension.getCurrentTime();
        // It will get the layer for this time (create or get)
        // Then, the layer will be loaded if necessary, adding it to the map (and show it after loading).
        // If it already on the map (but probably hidden), it will be shown
        var layer = this._getLayerForTime(time);
        if (this._currentLayer == null) {
            this._currentLayer = layer;
        }
        if (!this._map.hasLayer(layer)) {
            this._map.addLayer(layer);
        } else {
            this._showLayer(layer, time);
        }
    },

    setOpacity: function(opacity) {
        L.TimeDimension.Layer.prototype.setOpacity.apply(this, arguments);
        // apply to all preloaded caches
        for (var prop in this._layers) {
            if (this._layers.hasOwnProperty(prop) && this._layers[prop].setOpacity) {
                this._layers[prop].setOpacity(opacity);
            }
        }
    },

    setZIndex: function(zIndex){
        L.TimeDimension.Layer.prototype.setZIndex.apply(this, arguments);
        // apply to all preloaded caches
        for (var prop in this._layers) {
            if (this._layers.hasOwnProperty(prop) && this._layers[prop].setZIndex) {
                this._layers[prop].setZIndex(zIndex);
            }
        }
    },

    setParams: function(params, noRedraw) {
        L.extend(this._baseLayer.options, params);
        if (this._baseLayer.setParams) {
            this._baseLayer.setParams(params, noRedraw);
        }
        for (var prop in this._layers) {
            if (this._layers.hasOwnProperty(prop) && this._layers[prop].setParams) {
                this._layers[prop].setLoaded(false); // mark it as unloaded
                this._layers[prop].setParams(params, noRedraw);
            }
        }
        return this;
    },

    _unvalidateCache: function() {
        var time = this._timeDimension.getCurrentTime();
        for (var prop in this._layers) {
            if (time != prop && this._layers.hasOwnProperty(prop)) {
                this._layers[prop].setLoaded(false); // mark it as unloaded
                this._layers[prop].redraw();
            }
        }
    },

    _evictCachedTimes: function(keepforward, keepbackward) {
        // Cache management
        var times = this._getLoadedTimes();
        var strTime = String(this._currentTime);
        var index = times.indexOf(strTime);
        var remove = [];
        // remove times before current time
        if (keepbackward > -1) {
            var objectsToRemove = index - keepbackward;
            if (objectsToRemove > 0) {
                remove = times.splice(0, objectsToRemove);
                this._removeLayers(remove);
            }
        }
        if (keepforward > -1) {
            index = times.indexOf(strTime);
            var objectsToRemove = times.length - index - keepforward - 1;
            if (objectsToRemove > 0) {
                remove = times.splice(index + keepforward + 1, objectsToRemove);
                this._removeLayers(remove);
            }
        }
    },
    _showLayer: function(layer, time) {
        if (this._currentLayer && this._currentLayer !== layer) {
            this._currentLayer.hide();
        }
        layer.show();
        if (this._currentLayer && this._currentLayer === layer) {
            return;
        }
        this._currentLayer = layer;
        this._currentTime = time;
        console.log('Show layer ' + layer.wmsParams.layers + ' with time: ' + new Date(time).toISOString());

        this._evictCachedTimes(this._timeCacheForward, this._timeCacheBackward);
    },

    _getLayerForTime: function(time) {
        if (time == 0 || time == this._defaultTime || time == null) {
            return this._baseLayer;
        }
        if (this._layers.hasOwnProperty(time)) {
            return this._layers[time];
        }
        var nearestTime = this._getNearestTime(time);
        if (this._layers.hasOwnProperty(nearestTime)) {
            return this._layers[nearestTime];
        }

        var newLayer = this._createLayerForTime(nearestTime);

        this._layers[time] = newLayer;

        newLayer.on('load', (function(layer, time) {
            layer.setLoaded(true);
            // this time entry should exists inside _layers
            // but it might be deleted by cache management
            if (!this._layers[time]) {
                this._layers[time] = layer;
            }
            if (this._timeDimension && time == this._timeDimension.getCurrentTime() && !this._timeDimension.isLoading()) {
                this._showLayer(layer, time);
            }
            // console.log('Loaded layer ' + layer.wmsParams.layers + ' with time: ' + new Date(time).toISOString());
            this.fire('timeload', {
                time: time
            });
        }).bind(this, newLayer, time));

        // Hack to hide the layer when added to the map.
        // It will be shown when timeload event is fired from the map (after all layers are loaded)
        newLayer.onAdd = (function(map) {
            Object.getPrototypeOf(this).onAdd.call(this, map);
            this.hide();
        }).bind(newLayer);
        return newLayer;
    },

    _createLayerForTime:function(time){
        var wmsParams = this._baseLayer.options;
        wmsParams.time = new Date(time).toISOString();
        return new this._baseLayer.constructor(this._baseLayer.getURL(), wmsParams);
    },

    _getLoadedTimes: function() {
        var result = [];
        for (var prop in this._layers) {
            if (this._layers.hasOwnProperty(prop)) {
                result.push(prop);
            }
        }
        return result.sort(function(a, b) {
            return a - b;
        });
    },

    _removeLayers: function(times) {
        for (var i = 0, l = times.length; i < l; i++) {
            if (this._map)
                this._map.removeLayer(this._layers[times[i]]);
            delete this._layers[times[i]];
        }
    },

    setMinimumForwardCache: function(value) {
        if (value > this._timeCacheForward) {
            this._timeCacheForward = value;
        }
    },

    _requestTimeDimensionFromCapabilities: function() {
        if (this._capabilitiesRequested) {
            return;
        }
        this._capabilitiesRequested = true;
        var url = this._getCapabilitiesUrl();
        if (this._proxy) {
            url = this._proxy + '?url=' + encodeURIComponent(url);
        }
        var oReq = new XMLHttpRequest();
        oReq.addEventListener("load", (function(xhr) {
            var data = xhr.currentTarget.responseXML;
            this._defaultTime = Date.parse(this._getDefaultTimeFromCapabilities(data));
            this._setDefaultTime = this._setDefaultTime || (this._timeDimension && this._timeDimension.getAvailableTimes().length == 0);
            this.setAvailableTimes(this._parseTimeDimensionFromCapabilities(data));
            if (this._setDefaultTime && this._timeDimension) {
                this._timeDimension.setCurrentTime(this._defaultTime);
            }
        }).bind(this));
        oReq.overrideMimeType('application/xml');
        oReq.open("GET", url);
        oReq.send();
    },

    _getCapabilitiesUrl: function() {
        var url = this._baseLayer.getURL();
        if (this._getCapabilitiesAlternateUrl)
            url = this._getCapabilitiesAlternateUrl;
        var params = L.extend({}, this._getCapabilitiesParams, {
          'request': 'GetCapabilities',
          'service': 'WMS',
          'version': this._wmsVersion
        });
        url = url + L.Util.getParamString(params, url, params.uppercase);
        return url;
    },

    _parseTimeDimensionFromCapabilities: function(xml) {
        var layers = xml.querySelectorAll('Layer[queryable="1"]');
        var layerName = this._baseLayer.wmsParams.layers;
        var layer = null;
        var times = null;

        layers.forEach(function(current) {
            if (current.querySelector("Name").innerHTML === layerName) {
                layer = current;
            }
        })
        if (layer) {
            times = this._getTimesFromLayerCapabilities(layer);
            if (!times) {
                times = this._getTimesFromLayerCapabilities(layer.parentNode);
            }
        }

        return times;
    },

    _getTimesFromLayerCapabilities: function(layer) {
        var times = null;
        var nodes = layer.children;
        for (var i=0, l=nodes.length; i<l; i++){
            if (nodes[i].nodeName !== 'Extent' && nodes[i].nodeName !== 'Dimension') continue;
            if (nodes[i].getAttribute('name') !== 'time') continue;
            if (!nodes[i].textContent.length) continue;
            times = nodes[i].textContent.trim();
            break;
        };
        return times;
    },

    _getDefaultTimeFromCapabilities: function(xml) {
        var layers = xml.querySelectorAll('Layer[queryable="1"]');
        var layerName = this._baseLayer.wmsParams.layers;
        var layer = null;

        layers.forEach(function(current) {
            if (current.querySelector("Name").innerHTML === layerName) {
                layer = current;
            }
        })

        var defaultTime = 0;
        if (layer) {
            defaultTime = this._getDefaultTimeFromLayerCapabilities(layer);
            if (defaultTime == 0) {
                defaultTime = this._getDefaultTimeFromLayerCapabilities(layer.parentNode);
            }
        }
        return defaultTime;
    },

    _getDefaultTimeFromLayerCapabilities: function(layer) {
        var defaultTime = 0;
        var nodes = layer.children;
        for (var i=0, l=nodes.length; i<l; i++) {
            if (nodes[i].nodeName !== 'Extent' && nodes[i].nodeName !== 'Dimension') continue;
            if (nodes[i].getAttribute('name') !== 'time') continue;
            if (!nodes[i].attributes.default) continue;
            if (!nodes[i].attributes.default.textContent.length) continue;
            defaultTime = nodes[i].attributes.default.textContent.trim();
            break;
        };
        return defaultTime;
    },

    setAvailableTimes: function(times) {
        this._availableTimes = L.TimeDimension.Util.parseTimesExpression(times, this._period);
        this._updateTimeDimensionAvailableTimes();
    },

    _updateTimeDimensionAvailableTimes: function() {
        if ((this._timeDimension && this._updateTimeDimension) ||
            (this._timeDimension && this._timeDimension.getAvailableTimes().length == 0)) {
            this._timeDimension.setAvailableTimes(this._availableTimes, this._updateTimeDimensionMode);
            if (this._setDefaultTime && this._defaultTime > 0) {
                this._timeDimension.setCurrentTime(this._defaultTime);
            }
        }
    },

    _getNearestTime: function(time) {
        if (this._layers.hasOwnProperty(time)) {
            return time;
        }
        if (this._availableTimes.length == 0) {
            return time;
        }
        var index = 0;
        var len = this._availableTimes.length;
        for (; index < len; index++) {
            if (time < this._availableTimes[index]) {
                break;
            }
        }
        // We've found the first index greater than the time. Get the previous
        if (index > 0) {
            index--;
        }
        if (time != this._availableTimes[index]) {
            console.log('Search layer time: ' + new Date(time).toISOString());
            console.log('Return layer time: ' + new Date(this._availableTimes[index]).toISOString());
        }
        return this._availableTimes[index];
    },

});

if (!L.NonTiledLayer) {
    L.NonTiledLayer = (L.Layer || L.Class).extend({});
}

L.NonTiledLayer.include({
    _visible: true,
    _loaded: false,

    _originalUpdate: L.NonTiledLayer.prototype._update,
    _originalOnRemove: L.NonTiledLayer.prototype.onRemove,

    _update: function() {
        if (!this._visible && this._loaded) {
            return;
        }
        this._originalUpdate();
    },

    onRemove: function(map) {
        this._loaded = false;
        this._originalOnRemove(map);
    },

    setLoaded: function(loaded) {
        this._loaded = loaded;
    },

    isLoaded: function() {
        return this._loaded;
    },

    hide: function() {
        this._visible = false;
        this._div.style.display = 'none';
    },

    show: function() {
        this._visible = true;
        this._div.style.display = 'block';
    },

    getURL: function() {
        return this._wmsUrl;
    }

});

L.TileLayer.include({
    _visible: true,
    _loaded: false,

    _originalUpdate: L.TileLayer.prototype._update,

    _update: function() {
        if (!this._visible && this._loaded) {
            return;
        }
        this._originalUpdate();
    },

    setLoaded: function(loaded) {
        this._loaded = loaded;
    },

    isLoaded: function() {
        return this._loaded;
    },

    hide: function() {
        this._visible = false;
        if (this._container) {
            this._container.style.display = 'none';
        }
    },

    show: function() {
        this._visible = true;
        if (this._container) {
            this._container.style.display = 'block';
        }
    },

    getURL: function() {
        return this._url;
    }

});

L.timeDimension.layer.wms = function(layer, options) {
    return new L.TimeDimension.Layer.WMS(layer, options);
};
<commit_msg>Minor fix. Check if GetCapabilities response has data<commit_after>/*
 * L.TimeDimension.Layer.WMS: wms Layer associated to a TimeDimension
 */

L.TimeDimension.Layer.WMS = L.TimeDimension.Layer.extend({

    initialize: function(layer, options) {
        L.TimeDimension.Layer.prototype.initialize.call(this, layer, options);
        this._timeCacheBackward = this.options.cacheBackward || this.options.cache || 0;
        this._timeCacheForward = this.options.cacheForward || this.options.cache || 0;
        this._wmsVersion = this.options.wmsVersion || this.options.version || layer.options.version || "1.1.1";
        this._getCapabilitiesParams = this.options.getCapabilitiesParams || {};
        this._getCapabilitiesAlternateUrl = this.options.getCapabilitiesUrl || null;
        this._getCapabilitiesAlternateLayerName = this.options.getCapabilitiesLayerName || null;
        this._proxy = this.options.proxy || null;
        this._updateTimeDimension = this.options.updateTimeDimension || false;
        this._setDefaultTime = this.options.setDefaultTime || false;
        this._updateTimeDimensionMode = this.options.updateTimeDimensionMode || 'intersect'; // 'union' or 'replace'
        this._period = this.options.period || null;
        this._layers = {};
        this._defaultTime = 0;
        this._availableTimes = [];
        this._capabilitiesRequested = false;
        if (this._updateTimeDimension || this.options.requestTimeFromCapabilities) {
            this._requestTimeDimensionFromCapabilities();
        }

        this._baseLayer.on('load', (function() {
            this._baseLayer.setLoaded(true);
            this.fire('timeload', {
                time: this._defaultTime
            });
        }).bind(this));
    },

    getEvents: function() {
        var clearCache = L.bind(this._unvalidateCache, this);
        return {
            moveend: clearCache,
            zoomend: clearCache
        }
    },

    eachLayer: function(method, context) {
        for (var prop in this._layers) {
            if (this._layers.hasOwnProperty(prop)) {
                method.call(context, this._layers[prop]);
            }
        }
        return L.TimeDimension.Layer.prototype.eachLayer.call(this, method, context);
    },

    _onNewTimeLoading: function(ev) {
        // console.log('Layer._onNewTimeLoading: ' + this._baseLayer.wmsParams.layers + ' with time: ' + new Date(ev.time).toISOString());
        var layer = this._getLayerForTime(ev.time);
        if (!this._map.hasLayer(layer)) {
            this._map.addLayer(layer);
            // console.log('Layer._onNewTimeLoading: layer added to map');
        }
    },

    isReady: function(time) {
        var layer = this._getLayerForTime(time);
        if (this.options.bounds && this._map)
            if (!this._map.getBounds().contains(this.options.bounds))
                return true;
        return layer.isLoaded();
    },

    onAdd: function(map) {
        L.TimeDimension.Layer.prototype.onAdd.call(this, map);
        if (this._availableTimes.length == 0) {
            this._requestTimeDimensionFromCapabilities();
        } else {
            this._updateTimeDimensionAvailableTimes();
        }
    },

    _update: function() {
        if (!this._map)
            return;
        var time = this._timeDimension.getCurrentTime();
        // It will get the layer for this time (create or get)
        // Then, the layer will be loaded if necessary, adding it to the map (and show it after loading).
        // If it already on the map (but probably hidden), it will be shown
        var layer = this._getLayerForTime(time);
        if (this._currentLayer == null) {
            this._currentLayer = layer;
        }
        if (!this._map.hasLayer(layer)) {
            this._map.addLayer(layer);
        } else {
            this._showLayer(layer, time);
        }
    },

    setOpacity: function(opacity) {
        L.TimeDimension.Layer.prototype.setOpacity.apply(this, arguments);
        // apply to all preloaded caches
        for (var prop in this._layers) {
            if (this._layers.hasOwnProperty(prop) && this._layers[prop].setOpacity) {
                this._layers[prop].setOpacity(opacity);
            }
        }
    },

    setZIndex: function(zIndex){
        L.TimeDimension.Layer.prototype.setZIndex.apply(this, arguments);
        // apply to all preloaded caches
        for (var prop in this._layers) {
            if (this._layers.hasOwnProperty(prop) && this._layers[prop].setZIndex) {
                this._layers[prop].setZIndex(zIndex);
            }
        }
    },

    setParams: function(params, noRedraw) {
        L.extend(this._baseLayer.options, params);
        if (this._baseLayer.setParams) {
            this._baseLayer.setParams(params, noRedraw);
        }
        for (var prop in this._layers) {
            if (this._layers.hasOwnProperty(prop) && this._layers[prop].setParams) {
                this._layers[prop].setLoaded(false); // mark it as unloaded
                this._layers[prop].setParams(params, noRedraw);
            }
        }
        return this;
    },

    _unvalidateCache: function() {
        var time = this._timeDimension.getCurrentTime();
        for (var prop in this._layers) {
            if (time != prop && this._layers.hasOwnProperty(prop)) {
                this._layers[prop].setLoaded(false); // mark it as unloaded
                this._layers[prop].redraw();
            }
        }
    },

    _evictCachedTimes: function(keepforward, keepbackward) {
        // Cache management
        var times = this._getLoadedTimes();
        var strTime = String(this._currentTime);
        var index = times.indexOf(strTime);
        var remove = [];
        // remove times before current time
        if (keepbackward > -1) {
            var objectsToRemove = index - keepbackward;
            if (objectsToRemove > 0) {
                remove = times.splice(0, objectsToRemove);
                this._removeLayers(remove);
            }
        }
        if (keepforward > -1) {
            index = times.indexOf(strTime);
            var objectsToRemove = times.length - index - keepforward - 1;
            if (objectsToRemove > 0) {
                remove = times.splice(index + keepforward + 1, objectsToRemove);
                this._removeLayers(remove);
            }
        }
    },
    _showLayer: function(layer, time) {
        if (this._currentLayer && this._currentLayer !== layer) {
            this._currentLayer.hide();
        }
        layer.show();
        if (this._currentLayer && this._currentLayer === layer) {
            return;
        }
        this._currentLayer = layer;
        this._currentTime = time;
        console.log('Show layer ' + layer.wmsParams.layers + ' with time: ' + new Date(time).toISOString());

        this._evictCachedTimes(this._timeCacheForward, this._timeCacheBackward);
    },

    _getLayerForTime: function(time) {
        if (time == 0 || time == this._defaultTime || time == null) {
            return this._baseLayer;
        }
        if (this._layers.hasOwnProperty(time)) {
            return this._layers[time];
        }
        var nearestTime = this._getNearestTime(time);
        if (this._layers.hasOwnProperty(nearestTime)) {
            return this._layers[nearestTime];
        }

        var newLayer = this._createLayerForTime(nearestTime);

        this._layers[time] = newLayer;

        newLayer.on('load', (function(layer, time) {
            layer.setLoaded(true);
            // this time entry should exists inside _layers
            // but it might be deleted by cache management
            if (!this._layers[time]) {
                this._layers[time] = layer;
            }
            if (this._timeDimension && time == this._timeDimension.getCurrentTime() && !this._timeDimension.isLoading()) {
                this._showLayer(layer, time);
            }
            // console.log('Loaded layer ' + layer.wmsParams.layers + ' with time: ' + new Date(time).toISOString());
            this.fire('timeload', {
                time: time
            });
        }).bind(this, newLayer, time));

        // Hack to hide the layer when added to the map.
        // It will be shown when timeload event is fired from the map (after all layers are loaded)
        newLayer.onAdd = (function(map) {
            Object.getPrototypeOf(this).onAdd.call(this, map);
            this.hide();
        }).bind(newLayer);
        return newLayer;
    },

    _createLayerForTime:function(time){
        var wmsParams = this._baseLayer.options;
        wmsParams.time = new Date(time).toISOString();
        return new this._baseLayer.constructor(this._baseLayer.getURL(), wmsParams);
    },

    _getLoadedTimes: function() {
        var result = [];
        for (var prop in this._layers) {
            if (this._layers.hasOwnProperty(prop)) {
                result.push(prop);
            }
        }
        return result.sort(function(a, b) {
            return a - b;
        });
    },

    _removeLayers: function(times) {
        for (var i = 0, l = times.length; i < l; i++) {
            if (this._map)
                this._map.removeLayer(this._layers[times[i]]);
            delete this._layers[times[i]];
        }
    },

    setMinimumForwardCache: function(value) {
        if (value > this._timeCacheForward) {
            this._timeCacheForward = value;
        }
    },

    _requestTimeDimensionFromCapabilities: function() {
        if (this._capabilitiesRequested) {
            return;
        }
        this._capabilitiesRequested = true;
        var url = this._getCapabilitiesUrl();
        if (this._proxy) {
            url = this._proxy + '?url=' + encodeURIComponent(url);
        }
        var oReq = new XMLHttpRequest();
        oReq.addEventListener("load", (function(xhr) {
            var data = xhr.currentTarget.responseXML;
            if (data !== null){
                this._defaultTime = Date.parse(this._getDefaultTimeFromCapabilities(data));
                this._setDefaultTime = this._setDefaultTime || (this._timeDimension && this._timeDimension.getAvailableTimes().length == 0);
                this.setAvailableTimes(this._parseTimeDimensionFromCapabilities(data));
                if (this._setDefaultTime && this._timeDimension) {
                    this._timeDimension.setCurrentTime(this._defaultTime);
                }
            }
        }).bind(this));
        oReq.overrideMimeType('application/xml');
        oReq.open("GET", url);
        oReq.send();
    },

    _getCapabilitiesUrl: function() {
        var url = this._baseLayer.getURL();
        if (this._getCapabilitiesAlternateUrl)
            url = this._getCapabilitiesAlternateUrl;
        var params = L.extend({}, this._getCapabilitiesParams, {
          'request': 'GetCapabilities',
          'service': 'WMS',
          'version': this._wmsVersion
        });
        url = url + L.Util.getParamString(params, url, params.uppercase);
        return url;
    },

    _parseTimeDimensionFromCapabilities: function(xml) {
        var layers = xml.querySelectorAll('Layer[queryable="1"]');
        var layerName = this._baseLayer.wmsParams.layers;
        var layer = null;
        var times = null;

        layers.forEach(function(current) {
            if (current.querySelector("Name").innerHTML === layerName) {
                layer = current;
            }
        })
        if (layer) {
            times = this._getTimesFromLayerCapabilities(layer);
            if (!times) {
                times = this._getTimesFromLayerCapabilities(layer.parentNode);
            }
        }

        return times;
    },

    _getTimesFromLayerCapabilities: function(layer) {
        var times = null;
        var nodes = layer.children;
        for (var i=0, l=nodes.length; i<l; i++){
            if (nodes[i].nodeName !== 'Extent' && nodes[i].nodeName !== 'Dimension') continue;
            if (nodes[i].getAttribute('name') !== 'time') continue;
            if (!nodes[i].textContent.length) continue;
            times = nodes[i].textContent.trim();
            break;
        };
        return times;
    },

    _getDefaultTimeFromCapabilities: function(xml) {
        var layers = xml.querySelectorAll('Layer[queryable="1"]');
        var layerName = this._baseLayer.wmsParams.layers;
        var layer = null;

        layers.forEach(function(current) {
            if (current.querySelector("Name").innerHTML === layerName) {
                layer = current;
            }
        })

        var defaultTime = 0;
        if (layer) {
            defaultTime = this._getDefaultTimeFromLayerCapabilities(layer);
            if (defaultTime == 0) {
                defaultTime = this._getDefaultTimeFromLayerCapabilities(layer.parentNode);
            }
        }
        return defaultTime;
    },

    _getDefaultTimeFromLayerCapabilities: function(layer) {
        var defaultTime = 0;
        var nodes = layer.children;
        for (var i=0, l=nodes.length; i<l; i++) {
            if (nodes[i].nodeName !== 'Extent' && nodes[i].nodeName !== 'Dimension') continue;
            if (nodes[i].getAttribute('name') !== 'time') continue;
            if (!nodes[i].attributes.default) continue;
            if (!nodes[i].attributes.default.textContent.length) continue;
            defaultTime = nodes[i].attributes.default.textContent.trim();
            break;
        };
        return defaultTime;
    },

    setAvailableTimes: function(times) {
        this._availableTimes = L.TimeDimension.Util.parseTimesExpression(times, this._period);
        this._updateTimeDimensionAvailableTimes();
    },

    _updateTimeDimensionAvailableTimes: function() {
        if ((this._timeDimension && this._updateTimeDimension) ||
            (this._timeDimension && this._timeDimension.getAvailableTimes().length == 0)) {
            this._timeDimension.setAvailableTimes(this._availableTimes, this._updateTimeDimensionMode);
            if (this._setDefaultTime && this._defaultTime > 0) {
                this._timeDimension.setCurrentTime(this._defaultTime);
            }
        }
    },

    _getNearestTime: function(time) {
        if (this._layers.hasOwnProperty(time)) {
            return time;
        }
        if (this._availableTimes.length == 0) {
            return time;
        }
        var index = 0;
        var len = this._availableTimes.length;
        for (; index < len; index++) {
            if (time < this._availableTimes[index]) {
                break;
            }
        }
        // We've found the first index greater than the time. Get the previous
        if (index > 0) {
            index--;
        }
        if (time != this._availableTimes[index]) {
            console.log('Search layer time: ' + new Date(time).toISOString());
            console.log('Return layer time: ' + new Date(this._availableTimes[index]).toISOString());
        }
        return this._availableTimes[index];
    },

});

if (!L.NonTiledLayer) {
    L.NonTiledLayer = (L.Layer || L.Class).extend({});
}

L.NonTiledLayer.include({
    _visible: true,
    _loaded: false,

    _originalUpdate: L.NonTiledLayer.prototype._update,
    _originalOnRemove: L.NonTiledLayer.prototype.onRemove,

    _update: function() {
        if (!this._visible && this._loaded) {
            return;
        }
        this._originalUpdate();
    },

    onRemove: function(map) {
        this._loaded = false;
        this._originalOnRemove(map);
    },

    setLoaded: function(loaded) {
        this._loaded = loaded;
    },

    isLoaded: function() {
        return this._loaded;
    },

    hide: function() {
        this._visible = false;
        this._div.style.display = 'none';
    },

    show: function() {
        this._visible = true;
        this._div.style.display = 'block';
    },

    getURL: function() {
        return this._wmsUrl;
    }

});

L.TileLayer.include({
    _visible: true,
    _loaded: false,

    _originalUpdate: L.TileLayer.prototype._update,

    _update: function() {
        if (!this._visible && this._loaded) {
            return;
        }
        this._originalUpdate();
    },

    setLoaded: function(loaded) {
        this._loaded = loaded;
    },

    isLoaded: function() {
        return this._loaded;
    },

    hide: function() {
        this._visible = false;
        if (this._container) {
            this._container.style.display = 'none';
        }
    },

    show: function() {
        this._visible = true;
        if (this._container) {
            this._container.style.display = 'block';
        }
    },

    getURL: function() {
        return this._url;
    }

});

L.timeDimension.layer.wms = function(layer, options) {
    return new L.TimeDimension.Layer.WMS(layer, options);
};
<|endoftext|>


<commit_before>'use strict';

var Immutable = require('immutable');
var Collection = Immutable.Collection;
var KeyedCollection = Immutable.Collection.Keyed;

module.exports = function (chai, utils) {
  var Assertion = chai.Assertion;

  /**
   * ## BDD API Reference
   */

  /**
   * ### .empty
   *
   * Asserts that the immutable collection is empty.
   *
   * ```js
   * expect(List()).to.be.empty;
   * expect(List.of(1, 2, 3)).to.not.be.empty;
   * ```
   *
   * @name empty
   * @api public
   */

  Assertion.overwriteProperty('empty', function (_super) {
    return function () {
      var obj = this._obj;

      if (obj && obj instanceof Collection) {
        var size = obj.size;
        new Assertion(size).a('number');

        this.assert(
          size === 0,
          'expected #{this} to be empty but got size #{act}',
          'expected #{this} to not be empty',
          0,
          size
        );
      }
      else _super.apply(this, arguments);
    };
  });

  /**
   * ### .equal(collection)
   *
   * Asserts that the values of the target are equvalent to the values of
   * `collection`. Aliases of Chai's original `equal` method are also supported.
   *
   * ```js
   * var a = List.of(1, 2, 3);
   * var b = List.of(1, 2, 3);
   * expect(a).to.equal(b);
   * ```
   *
   * @name equal
   * @alias equals
   * @alias eq
   * @alias deep.equal
   * @param {Collection} value
   * @api public
   */

  function assertCollectionEqual(_super) {
    return function (collection) {
      var obj = this._obj;

      if (obj && obj instanceof Collection) {
        this.assert(
          Immutable.is(obj, collection),
          'expected #{this} to equal #{exp}',
          'expected #{this} to not equal #{exp}',
          collection
        );
      }
      else _super.apply(this, arguments);
    };
  }

  Assertion.overwriteMethod('equal', assertCollectionEqual);
  Assertion.overwriteMethod('equals', assertCollectionEqual);
  Assertion.overwriteMethod('eq', assertCollectionEqual);

  /**
   * ### .keys(key1[, key2, ...[, keyN]])
   *
   * Asserts that the keyed collection contains all of the passed-in keys.
   *
   * `key` is an alias to `keys`.
   *
   * ```js
   * expect(new Map({ foo: 1, bar: 2 })).to.have.key('foo');
   * expect(new Map({ foo: 1, bar: 2 })).to.have.keys('foo', 'bar');
   * expect(new Map({ foo: 1, bar: 2 })).to.have.keys(['bar', 'foo']);
   * expect(new Map({ foo: 1, bar: 2 })).to.have.keys({ 'bar': 6, 'foo': 7 });
   * ```
   *
   * @name keys
   * @param {String...|Array|Object} keyN
   * @alias key
   * @api public
   */

  function assertKeys(_super) {
    return function (keys) {
      var obj = this._obj;

      if (obj && obj instanceof KeyedCollection) {
        if (utils.type(keys) === 'object') {
          keys = Object.keys(keys);
        }
        else if (utils.type(keys) !== 'array') {
          keys = Array.prototype.slice.call(arguments);
        }

        if (!keys.length) throw new Error('keys required');

        var ok = keys.every(function (key) { return obj.has(key); });
        var str;

        if (keys.length > 1) {
          keys = keys.map(utils.inspect);
          var last = keys.pop();
          str = 'keys ' + keys.join(', ') + ', and ' + last;
        }
        else str = 'key ' + utils.inspect(keys[0]);

        this.assert(
          ok,
          'expected #{this} to have ' + str,
          'expected #{this} to not have ' + str
        );
      }
      else _super.apply(this, arguments);
    };
  }

  Assertion.overwriteMethod('keys', assertKeys);
  Assertion.overwriteMethod('key', assertKeys);

  /**
   * ### .size(value)
   *
   * Asserts that the immutable collection has the expected size.
   *
   * ```js
   * expect(List.of(1, 2, 3)).to.have.size(3);
   * ```
   *
   * It can also be used as a chain precursor to a value comparison for the
   * `size` property.
   *
   * ```js
   * expect(List.of(1, 2, 3)).to.have.size.least(3);
   * expect(List.of(1, 2, 3)).to.have.size.most(3);
   * expect(List.of(1, 2, 3)).to.have.size.above(2);
   * expect(List.of(1, 2, 3)).to.have.size.below(4);
   * expect(List.of(1, 2, 3)).to.have.size.within(2,4);
   * ```
   *
   * Similarly to `length`/`lengthOf`, `sizeOf` is an alias of `size`:
   *
   * ```js
   * expect(List.of(1, 2, 3)).to.have.sizeOf(3);
   * ```
   *
   * @name size
   * @alias sizeOf
   * @param {Number} size
   * @api public
   */

  function assertCollectionSize(n) {
    new Assertion(this._obj).instanceof(Collection);

    var size = this._obj.size;
    new Assertion(size).a('number');

    this.assert(
      size === n,
      'expected #{this} to have size #{exp} but got #{act}',
      'expected #{this} to not have size #{act}',
      n,
      size
    );
  }

  function chainCollectionSize() {
    utils.flag(this, 'immutable.collection.size', true);
  }

  Assertion.addChainableMethod('size', assertCollectionSize, chainCollectionSize);
  Assertion.addMethod('sizeOf', assertCollectionSize);

  // Numerical comparator overwrites

  function assertCollectionSizeLeast(_super) {
    return function (n) {
      if (utils.flag(this, 'immutable.collection.size')) {
        new Assertion(this._obj).instanceof(Collection);

        var size = this._obj.size;
        new Assertion(size).a('number');

        this.assert(
          size >= n,
          'expected #{this} to have a size of at least #{exp} but got #{act}',
          'expected #{this} to not have a size of at least #{exp} but got #{act}',
          n,
          size
        );
      }
      else _super.apply(this, arguments);
    };
  }

  function assertCollectionSizeMost(_super) {
    return function (n) {
      if (utils.flag(this, 'immutable.collection.size')) {
        new Assertion(this._obj).instanceof(Collection);

        var size = this._obj.size;
        new Assertion(size).a('number');

        this.assert(
          size <= n,
          'expected #{this} to have a size of at most #{exp} but got #{act}',
          'expected #{this} to not have a size of at most #{exp} but got #{act}',
          n,
          size
        );
      }
      else _super.apply(this, arguments);
    };
  }

  function assertCollectionSizeAbove(_super) {
    return function (n) {
      if (utils.flag(this, 'immutable.collection.size')) {
        new Assertion(this._obj).instanceof(Collection);

        var size = this._obj.size;
        new Assertion(size).a('number');

        this.assert(
          size > n,
          'expected #{this} to have a size above #{exp} but got #{act}',
          'expected #{this} to not have a size above #{exp} but got #{act}',
          n,
          size
        );
      }
      else _super.apply(this, arguments);
    };
  }

  function assertCollectionSizeBelow(_super) {
    return function (n) {
      if (utils.flag(this, 'immutable.collection.size')) {
        new Assertion(this._obj).instanceof(Collection);

        var size = this._obj.size;
        new Assertion(size).a('number');

        this.assert(
          size < n,
          'expected #{this} to have a size below #{exp} but got #{act}',
          'expected #{this} to not have a size below #{exp} but got #{act}',
          n,
          size
        );
      }
      else _super.apply(this, arguments);
    };
  }

  Assertion.overwriteMethod('least', assertCollectionSizeLeast);
  Assertion.overwriteMethod('gte', assertCollectionSizeLeast);

  Assertion.overwriteMethod('most', assertCollectionSizeMost);
  Assertion.overwriteMethod('lte', assertCollectionSizeMost);

  Assertion.overwriteMethod('above', assertCollectionSizeAbove);
  Assertion.overwriteMethod('gt', assertCollectionSizeAbove);
  Assertion.overwriteMethod('greaterThan', assertCollectionSizeAbove);

  Assertion.overwriteMethod('below', assertCollectionSizeBelow);
  Assertion.overwriteMethod('lt', assertCollectionSizeBelow);
  Assertion.overwriteMethod('lessThan', assertCollectionSizeBelow);

  Assertion.overwriteMethod('within', function (_super) {
    return function (min, max) {
      if (utils.flag(this, 'immutable.collection.size')) {
        new Assertion(this._obj).instanceof(Collection);

        var size = this._obj.size;
        new Assertion(size).a('number');

        this.assert(
          min <= size && size <= max,
          'expected #{this} to have a size within #{exp} but got #{act}',
          'expected #{this} to not have a size within #{exp} but got #{act}',
          min + '..' + max,
          size
        );
      }
      else _super.apply(this, arguments);
    };
  });

  /**
   * ## TDD API Reference
   */

  var assert = chai.assert;

  /**
   * ### .equal(actual, expected)
   *
   * Asserts that the values of the target are equvalent to the values of
   * `collection`. Note that `.strictEqual` and `.deepEqual` assert exactly like
   * `.equal` in the context of Immutable data structures.
   *
   * ```js
   * var a = List.of(1, 2, 3);
   * var b = List.of(1, 2, 3);
   * assert.equal(a, b);
   * ```
   *
   * @name equal
   * @param {Collection} actual
   * @param {Collection} expected
   * @api public
   */

  assert.equal = function (actual, expected) {
    if (actual instanceof Collection) {
      return new Assertion(actual).equal(expected);
    }
    else return assert.equal;
  };

  /**
   * ### .sizeOf(collection, length)
   *
   * Asserts that the immutable collection has the expected size.
   *
   * ```js
   * assert.sizeOf(List.of(1, 2, 3), 3);
   * assert.sizeOf(new List(), 0);
   * ```
   *
   * @name sizeOf
   * @param {Collection} collection
   * @param {Number} size
   * @api public
   */

  assert.sizeOf = function (collection, expected) {
    new Assertion(collection).size(expected);
  };
};
<commit_msg>Add an error message displayed when array or object *and* multiple arguments are given to keys assertion<commit_after>'use strict';

var Immutable = require('immutable');
var Collection = Immutable.Collection;
var KeyedCollection = Immutable.Collection.Keyed;

module.exports = function (chai, utils) {
  var Assertion = chai.Assertion;

  /**
   * ## BDD API Reference
   */

  /**
   * ### .empty
   *
   * Asserts that the immutable collection is empty.
   *
   * ```js
   * expect(List()).to.be.empty;
   * expect(List.of(1, 2, 3)).to.not.be.empty;
   * ```
   *
   * @name empty
   * @api public
   */

  Assertion.overwriteProperty('empty', function (_super) {
    return function () {
      var obj = this._obj;

      if (obj && obj instanceof Collection) {
        var size = obj.size;
        new Assertion(size).a('number');

        this.assert(
          size === 0,
          'expected #{this} to be empty but got size #{act}',
          'expected #{this} to not be empty',
          0,
          size
        );
      }
      else _super.apply(this, arguments);
    };
  });

  /**
   * ### .equal(collection)
   *
   * Asserts that the values of the target are equvalent to the values of
   * `collection`. Aliases of Chai's original `equal` method are also supported.
   *
   * ```js
   * var a = List.of(1, 2, 3);
   * var b = List.of(1, 2, 3);
   * expect(a).to.equal(b);
   * ```
   *
   * @name equal
   * @alias equals
   * @alias eq
   * @alias deep.equal
   * @param {Collection} value
   * @api public
   */

  function assertCollectionEqual(_super) {
    return function (collection) {
      var obj = this._obj;

      if (obj && obj instanceof Collection) {
        this.assert(
          Immutable.is(obj, collection),
          'expected #{this} to equal #{exp}',
          'expected #{this} to not equal #{exp}',
          collection
        );
      }
      else _super.apply(this, arguments);
    };
  }

  Assertion.overwriteMethod('equal', assertCollectionEqual);
  Assertion.overwriteMethod('equals', assertCollectionEqual);
  Assertion.overwriteMethod('eq', assertCollectionEqual);

  /**
   * ### .keys(key1[, key2, ...[, keyN]])
   *
   * Asserts that the keyed collection contains all of the passed-in keys.
   *
   * `key` is an alias to `keys`.
   *
   * ```js
   * expect(new Map({ foo: 1, bar: 2 })).to.have.key('foo');
   * expect(new Map({ foo: 1, bar: 2 })).to.have.keys('foo', 'bar');
   * expect(new Map({ foo: 1, bar: 2 })).to.have.keys(['bar', 'foo']);
   * expect(new Map({ foo: 1, bar: 2 })).to.have.keys({ 'bar': 6, 'foo': 7 });
   * ```
   *
   * @name keys
   * @param {String...|Array|Object} keyN
   * @alias key
   * @api public
   */

  function assertKeys(_super) {
    return function (keys) {
      var obj = this._obj;

      if (obj && obj instanceof KeyedCollection) {
        switch (utils.type(keys)) {
          case 'object':
            keys = Object.keys(keys);
          case 'array':
            if (arguments.length > 1) throw new Error(
              'keys must be given single argument of Array|Object|String, ' +
              'or multiple String arguments'
            );
            break;
          default:
            keys = Array.prototype.slice.call(arguments);
            break;
        }

        if (!keys.length) throw new Error('keys required');

        var ok = keys.every(function (key) { return obj.has(key); });
        var str;

        if (keys.length > 1) {
          keys = keys.map(utils.inspect);
          var last = keys.pop();
          str = 'keys ' + keys.join(', ') + ', and ' + last;
        }
        else str = 'key ' + utils.inspect(keys[0]);

        this.assert(
          ok,
          'expected #{this} to have ' + str,
          'expected #{this} to not have ' + str
        );
      }
      else _super.apply(this, arguments);
    };
  }

  Assertion.overwriteMethod('keys', assertKeys);
  Assertion.overwriteMethod('key', assertKeys);

  /**
   * ### .size(value)
   *
   * Asserts that the immutable collection has the expected size.
   *
   * ```js
   * expect(List.of(1, 2, 3)).to.have.size(3);
   * ```
   *
   * It can also be used as a chain precursor to a value comparison for the
   * `size` property.
   *
   * ```js
   * expect(List.of(1, 2, 3)).to.have.size.least(3);
   * expect(List.of(1, 2, 3)).to.have.size.most(3);
   * expect(List.of(1, 2, 3)).to.have.size.above(2);
   * expect(List.of(1, 2, 3)).to.have.size.below(4);
   * expect(List.of(1, 2, 3)).to.have.size.within(2,4);
   * ```
   *
   * Similarly to `length`/`lengthOf`, `sizeOf` is an alias of `size`:
   *
   * ```js
   * expect(List.of(1, 2, 3)).to.have.sizeOf(3);
   * ```
   *
   * @name size
   * @alias sizeOf
   * @param {Number} size
   * @api public
   */

  function assertCollectionSize(n) {
    new Assertion(this._obj).instanceof(Collection);

    var size = this._obj.size;
    new Assertion(size).a('number');

    this.assert(
      size === n,
      'expected #{this} to have size #{exp} but got #{act}',
      'expected #{this} to not have size #{act}',
      n,
      size
    );
  }

  function chainCollectionSize() {
    utils.flag(this, 'immutable.collection.size', true);
  }

  Assertion.addChainableMethod('size', assertCollectionSize, chainCollectionSize);
  Assertion.addMethod('sizeOf', assertCollectionSize);

  // Numerical comparator overwrites

  function assertCollectionSizeLeast(_super) {
    return function (n) {
      if (utils.flag(this, 'immutable.collection.size')) {
        new Assertion(this._obj).instanceof(Collection);

        var size = this._obj.size;
        new Assertion(size).a('number');

        this.assert(
          size >= n,
          'expected #{this} to have a size of at least #{exp} but got #{act}',
          'expected #{this} to not have a size of at least #{exp} but got #{act}',
          n,
          size
        );
      }
      else _super.apply(this, arguments);
    };
  }

  function assertCollectionSizeMost(_super) {
    return function (n) {
      if (utils.flag(this, 'immutable.collection.size')) {
        new Assertion(this._obj).instanceof(Collection);

        var size = this._obj.size;
        new Assertion(size).a('number');

        this.assert(
          size <= n,
          'expected #{this} to have a size of at most #{exp} but got #{act}',
          'expected #{this} to not have a size of at most #{exp} but got #{act}',
          n,
          size
        );
      }
      else _super.apply(this, arguments);
    };
  }

  function assertCollectionSizeAbove(_super) {
    return function (n) {
      if (utils.flag(this, 'immutable.collection.size')) {
        new Assertion(this._obj).instanceof(Collection);

        var size = this._obj.size;
        new Assertion(size).a('number');

        this.assert(
          size > n,
          'expected #{this} to have a size above #{exp} but got #{act}',
          'expected #{this} to not have a size above #{exp} but got #{act}',
          n,
          size
        );
      }
      else _super.apply(this, arguments);
    };
  }

  function assertCollectionSizeBelow(_super) {
    return function (n) {
      if (utils.flag(this, 'immutable.collection.size')) {
        new Assertion(this._obj).instanceof(Collection);

        var size = this._obj.size;
        new Assertion(size).a('number');

        this.assert(
          size < n,
          'expected #{this} to have a size below #{exp} but got #{act}',
          'expected #{this} to not have a size below #{exp} but got #{act}',
          n,
          size
        );
      }
      else _super.apply(this, arguments);
    };
  }

  Assertion.overwriteMethod('least', assertCollectionSizeLeast);
  Assertion.overwriteMethod('gte', assertCollectionSizeLeast);

  Assertion.overwriteMethod('most', assertCollectionSizeMost);
  Assertion.overwriteMethod('lte', assertCollectionSizeMost);

  Assertion.overwriteMethod('above', assertCollectionSizeAbove);
  Assertion.overwriteMethod('gt', assertCollectionSizeAbove);
  Assertion.overwriteMethod('greaterThan', assertCollectionSizeAbove);

  Assertion.overwriteMethod('below', assertCollectionSizeBelow);
  Assertion.overwriteMethod('lt', assertCollectionSizeBelow);
  Assertion.overwriteMethod('lessThan', assertCollectionSizeBelow);

  Assertion.overwriteMethod('within', function (_super) {
    return function (min, max) {
      if (utils.flag(this, 'immutable.collection.size')) {
        new Assertion(this._obj).instanceof(Collection);

        var size = this._obj.size;
        new Assertion(size).a('number');

        this.assert(
          min <= size && size <= max,
          'expected #{this} to have a size within #{exp} but got #{act}',
          'expected #{this} to not have a size within #{exp} but got #{act}',
          min + '..' + max,
          size
        );
      }
      else _super.apply(this, arguments);
    };
  });

  /**
   * ## TDD API Reference
   */

  var assert = chai.assert;

  /**
   * ### .equal(actual, expected)
   *
   * Asserts that the values of the target are equvalent to the values of
   * `collection`. Note that `.strictEqual` and `.deepEqual` assert exactly like
   * `.equal` in the context of Immutable data structures.
   *
   * ```js
   * var a = List.of(1, 2, 3);
   * var b = List.of(1, 2, 3);
   * assert.equal(a, b);
   * ```
   *
   * @name equal
   * @param {Collection} actual
   * @param {Collection} expected
   * @api public
   */

  assert.equal = function (actual, expected) {
    if (actual instanceof Collection) {
      return new Assertion(actual).equal(expected);
    }
    else return assert.equal;
  };

  /**
   * ### .sizeOf(collection, length)
   *
   * Asserts that the immutable collection has the expected size.
   *
   * ```js
   * assert.sizeOf(List.of(1, 2, 3), 3);
   * assert.sizeOf(new List(), 0);
   * ```
   *
   * @name sizeOf
   * @param {Collection} collection
   * @param {Number} size
   * @api public
   */

  assert.sizeOf = function (collection, expected) {
    new Assertion(collection).size(expected);
  };
};
<|endoftext|>


<commit_before>(function(exports) {

  function mixin(object, source, options) {
    var chain = true,
      supe = false;

    if (options === false) {
      chain = false;
    } else if (_.isObject(options)) {
      if ('chain' in options)
        chain = options.chain;
      if ('super' in options)
        supe = true;
    }

    //options = options || {};

    // set each method (in source) for object
    for(methodName in source) {
      prop = source[methodName];

      // this will set the methods to the global stats
      if (supe && object[methodName]) {

        (function() {
          this.$super = prop;

          this[methodName] = this[methodName].bind(this);

        }.call(object));
      } else {
        object[methodName] = prop;
      }

      if (_.isFunction(prop)) {
        // this will set the methods to the wrapped object.
        // But now, we already have a collection defined,
        // so we just pass it to the original function;
        // object.prototype[methodName] = prop;

        if (supe && _.isFunction(object.prototype[methodName])) {

          (function() {
            this.$super = prop;

            this[methodName] = this[methodName].bind(this);

          }.call(object));

          var temp = (function() {
            var fn = object[methodName];
            return function() {
              return fn.apply(object, arguments);
            };
          }.call(object));

          object.prototype[methodName] = (function(prop, options) {

            function mixed() {

              // $wrapped is just one (or) the first argument,
              // so we pass to the first arguments;

              if (chain || !_.isEmpty(temp.prototype)) {

                var args = [this];
                Array.prototype.push.apply(args, arguments);

                // in this mixin we just execute the method, and mantain the wrapped
                temp.apply(this, args);

                return this;
              }

              var args = [];
              Array.prototype.push.apply(args, arguments);

              return temp.apply(this, args);
            }

            return mixed;
          }.call(object, prop, options));

        } else {

          object.prototype[methodName] = (function(prop, options) {

            function mixed() {

              // $wrapped is just one (or) the first argument,
              // so we pass to the first arguments;

              if (chain || !_.isEmpty(prop.prototype)) {

                var args = [this];
                Array.prototype.push.apply(args, arguments);

                // in this mixin we just execute the method, and mantain the wrapped
                prop.apply(this, args);

                return this;
              }

              var args = [];
              Array.prototype.push.apply(args, arguments);

              return prop.apply(this, args);
            }

            return mixed;
          }.call(object, prop, options));
        }
      } else {
        // only mix functions
        // console.log('do nothing?', methodName);
      }
    }

    return object;
  }

  function mixin2(object, source, chain) {
    chain = !chain;

    for(var methodName in source) {
      if (_.isFunction(source[methodName])) {
        object.prototype[methodName] = function(method, methodName) {
          function mixed() {
            if (chain) {
              var args = [this];
              Array.prototype.push.apply(args, arguments);

              method.apply(this, args);

              // return the current class when mixed
              return this;
            }

            var args = [];
            Array.prototype.push.apply(args, arguments);

            return prop.apply(this, args);
          }
          return mixed;
        }.call(source, source[methodName], methodName);
      } else {
        object.prototype[methodName] = source[methodName];
      }
    }
    return object;
  }

  var ai = {};

  var extensions = [];
  var extensions_proto = [];

  function Class(name, methods, inherits) {

    var classed = {},
      original_name = name;

    methods = methods || {};

    // methods = _.defaults(methods, {
    //   $pk: 'id'
    // });

    // prevent injection on eval
    name = name.toLowerCase().replace(/[^a-z0-1_A-Z]/g, '');
    var Name = _.capitalize(name.replace(/[^a-z0-1_A-Z]/g, ''));
    var NameWrapper = (Name + "Wrapper").replace(/[^a-z0-1_A-Z]/g, '');

    ai[name] = 1;

    var defaults = methods.$defaults || {};

    function hasMany() {
      if (methods.$hasMany) {
        _.each(methods.$hasMany, function(r, k) {
          defaults[k] = r.$constructor();

          defaults[k].$belongsTo[original_name] = this;
        }, this);
      }
    }

    eval('function '+Name+'(o){\
      hasMany.call(this);\
      var defs = _.defaults(_.cloneDeep(defaults), {\
        id: ai[name]\
      });\
      o = _.defaults(o || {}, defs);\
      _.assign(this,o);\
      ai[name] += 1;\
    }');

    eval('function '+NameWrapper+'(value){\
      var u = new '+Name+'(value);\
      _.assign(this,u);\
      this.$wrapped = u;\
    }');

    var constructor;

    eval('var '+name+' = constructor = function '+name+'(value){\
      if (value instanceof '+NameWrapper+')\
        return value;\
      return new '+NameWrapper+'(value);\
    }');

    // eval(name+'.$data = [];');
    // eval(name+'.$current = null;');
    // eval(name+'.$current = $current.bind('+name+');');
    // eval(name+'.$add = $add.bind('+name+');');

    var listeners = {};

    function dispatch(listener) {
      var args = Array.prototype.slice.call(arguments, 1);

      if (listeners[listener]) {
        _.each(listeners[listener], function(callback) {
          callback.apply(this, args);
        }, this);
      }

      return;
    }

    function Classy() {
      var current = null;
      var data = [];
      var indexed = {};
      var index = methods.$index || 'id';

      this.$belongsTo = {};

      this.$on = function(listener, callback) {
        listeners[listener] = listeners[listener] || [];

        if (_.isFunction(callback))
          listeners[listener].push(callback);

        return this;
      };

      this.$constructor = function() {
        return Class(name, methods);
      };

      this.$current = function (c) {
        if (!_.isUndefined(c)) {
          current = (c && c.id) ? this.$data(c) : null;
          return this;
        } else {
          return current;
        }
      };

      this.$first = function() {
        return _.first(data);
      };

      this.$last = function() {
        return _.last(data);
      };

      this.$next = function() {
        var k = _.indexOf(data, this.$current());

        return (k > -1) ? (data[k+1] ? data[k+1] : false) : false;
      };

      this.$prev = function() {
        var k = _.indexOf(data, this.$current());

        return (k > -1) ? (data[k-1] ? data[k-1] : false) : false;
      };

      this.$goNext = function() {
        var k = _.indexOf(data, this.$current());

        this.$current(data[k+1] ? data[k+1] : this.$current());

        return this;
      };

      this.$goPrev = function() {
        var k = _.indexOf(data, this.$current());

        this.$current(data[k-1] ? data[k-1] : this.$current());

        return this;
      };

      this.$add = function(u) {
        u = constructor(u);

        data.push(u);
        indexed[u[index]] = u;

        dispatch.call(this, '$add', u);

        return this;
      };

      this.$load = function(data) {
        this.$removeAll();

        _.each(data, function(d) {
          this(d).$add();
        }, this);

        return this;
      };

      this.$removeAll = function() {
        data.length = 0;
        // WARNING
        // to not remove the assigment, use the code above
        // for (var j in indexed) delete indexed[j];
        indexed = {};

        ai[name] = 1;
        current = null;

        dispatch.call(this, '$removeAll');

        return this;
      };

      this.$removeAt = function(at, n) {
        var rem = data.splice(at, n || 1);

        _.each(rem, function(k) {
          if (_.isEqual(current, k))
            current = null;
          delete indexed[k[index]];
        });

        delete rem;

        dispatch.call(this, '$removeAt', at, n);

        return this;
      };

      this.$remove = function(u) {
        delete indexed[u[index]];
        _.pull(data, u);

        if (_.isEqual(current, u))
          current = null;

        dispatch.call(this, '$remove', u);

        return this;
      };

      this.$data = function(i) {
        return _.isFinite(i)
          ? data[i]
          : (_.isObject(i) ? _.find(data, i) : data);
      };

      this.$filter = function(ff) {
        return _.filter(data, ff);
      };

      this.$find = function(ff) {
        return _.find(data, ff);
      };

      this.$export = function() {
        return _.filter(data, function(d) {
          return d.$value();
        });
      };

      this.$change = function(a, b) {
        var k;
        if (_.isFinite(a))
          k = a;
        else
          k = _.findIndex(data, {id: a.id});

        if (data[k])
          data[k] = this(b);

        return this;
      };

      this.$set = function(u, key, value) {
        if (_.isArray(key)) {
          // TODO
        } else if(_.isPlainObject(key)) {
          this.$change(a, b);
        } else {
          u[key] = value;
          u.$wrapped[key] = u[key];
        }

        return this;
      };

      this.$push = function(u, key, value) {

        if (_.isArray(u[key])) {
          u[key].push(value);
          u.$wrapped[key] = u[key];
        }

        return this;
      };

      this.$pull = function(u, key, value) {
        if (_.isArray(u[key])) {
          _.pull(u[key], value);
          u.$wrapped[key] = u[key];
        }

        return this;
      };

      this.$concat = function(u, key, value) {
        if (_.isArray(u[key])) {
          u[key] = u[key].concat(value);
          u.$wrapped[key] = u[key];
        }

        return this;
      };

      this.$indexed = function(k) {
        return k ? indexed[k] : indexed;
      };

      // FIXME
      _.each(extensions, function(e) {
        if (e.options.chain)
          _.assign(this, e.extension);
      }, this);

      // FIXME
      _.each(extensions_proto, function(e) {
        if (e.options.chain)
          _.assign(this, e.extension);
      }, this);

      // FIXME
      if (methods && !methods.prototype)
        _.assign(this, methods);
    }

    // methods only for the constructor not his instances
    function ClassyProto() {}

    // unchainable methods
    function ClassyUnchain() {}

    ClassyUnchain.prototype.$value = function(u) {
      var n = {};

      _.each(this, function(v, k) {
        if (_.indexOf(k, '$') !== 0)
          n[k] = v;
      }, this);

      return n;
    };

    if (methods && methods.$unchain) {
      mixin(ClassyUnchain, methods.$unchain, false);
    }

    // FIXME
    if (methods && methods.prototype) {
      Classy.prototype = methods.prototype;
    }

    if (inherits)
      eval('mixin('+name+', inherits, {chain: true, super: true});');

    function $extend(_o) {
      eval('mixin('+name+', _o, {chain: true, super: true});');
      return this;
    }
    eval(''+name+'.$extend = $extend;');

    // function $current(c) {
    //   if (c) {
    //     eval(name+'.$current = c');
    //     return this;
    //   } else {
    //     return eval(name+'.$current');
    //   }
    // }
    // function $add(u) {
    //   eval(name+'.$data.push(u);');
    //   return this;
    // };

    // _.each(new Classy(), function(d, k) {
    //   eval(''+name+'.'+k+' = d;');
    // });

    eval('mixin('+name+', new Classy(), {chain: true, super: true});');

    eval('mixin('+name+', new ClassyUnchain(), false);');

    // eval('mixin('+name+', '+name+');');
    eval(NameWrapper+'.prototype = '+name+'.prototype;');

    // private methods for the Constructor only
    _.each(new ClassyProto(), function(d, k) {
      eval(''+name+'.'+k+' = d;');
    });

    return eval(name);
  }

  Class.$extend = function(obj, options) {
    _.defaults(options, {
      chain: true
    });

    obj = {
      extension: obj,
      options: options
    };

    if (_.isPlainObject(obj)) {
      extensions.push(obj);
    } else {
      extensions_proto.push(obj);
    }

    return Class;
  };

  if (exports.Classy) {
    console.info("The library Classy is already loaded.");
  } else {
    exports.Classy = Class;
  }

}(window));
<commit_msg>updated the sticky component; create a new component spinner, creates a loading spinner on elements that depends on promises; added server comunication for address and user/profile.<commit_after>(function(exports) {

  function mixin(object, source, options) {
    var chain = true,
      supe = false;

    if (options === false) {
      chain = false;
    } else if (_.isObject(options)) {
      if ('chain' in options)
        chain = options.chain;
      if ('super' in options)
        supe = true;
    }

    //options = options || {};

    // set each method (in source) for object
    for(methodName in source) {
      prop = source[methodName];

      // this will set the methods to the global stats
      if (supe && object[methodName]) {

        (function() {
          this.$super = prop;

          this[methodName] = this[methodName].bind(this);

        }.call(object));
      } else {
        object[methodName] = prop;
      }

      if (_.isFunction(prop)) {
        // this will set the methods to the wrapped object.
        // But now, we already have a collection defined,
        // so we just pass it to the original function;
        // object.prototype[methodName] = prop;

        if (supe && _.isFunction(object.prototype[methodName])) {

          (function() {
            this.$super = prop;

            this[methodName] = this[methodName].bind(this);

          }.call(object));

          var temp = (function() {
            var fn = object[methodName];
            return function() {
              return fn.apply(object, arguments);
            };
          }.call(object));

          object.prototype[methodName] = (function(prop, options) {

            function mixed() {

              // $wrapped is just one (or) the first argument,
              // so we pass to the first arguments;

              if (chain || !_.isEmpty(temp.prototype)) {

                var args = [this];
                Array.prototype.push.apply(args, arguments);

                // in this mixin we just execute the method, and mantain the wrapped
                temp.apply(this, args);

                return this;
              }

              var args = [];
              Array.prototype.push.apply(args, arguments);

              return temp.apply(this, args);
            }

            return mixed;
          }.call(object, prop, options));

        } else {

          object.prototype[methodName] = (function(prop, options) {

            function mixed() {

              // $wrapped is just one (or) the first argument,
              // so we pass to the first arguments;

              if (chain || !_.isEmpty(prop.prototype)) {

                var args = [this];
                Array.prototype.push.apply(args, arguments);

                // in this mixin we just execute the method, and mantain the wrapped
                prop.apply(this, args);

                return this;
              }

              var args = [];
              Array.prototype.push.apply(args, arguments);

              return prop.apply(this, args);
            }

            return mixed;
          }.call(object, prop, options));
        }
      } else {
        // only mix functions
        // console.log('do nothing?', methodName);
        // object[methodName] = prop;
      }
    }

    return object;
  }

  function mixin2(object, source, chain) {
    chain = !chain;

    for(var methodName in source) {
      if (_.isFunction(source[methodName])) {
        object.prototype[methodName] = function(method, methodName) {
          function mixed() {
            if (chain) {
              var args = [this];
              Array.prototype.push.apply(args, arguments);

              method.apply(this, args);

              // return the current class when mixed
              return this;
            }

            var args = [];
            Array.prototype.push.apply(args, arguments);

            return prop.apply(this, args);
          }
          return mixed;
        }.call(source, source[methodName], methodName);
      } else {
        object.prototype[methodName] = source[methodName];
      }
    }
    return object;
  }

  var ai = {};

  var extensions = [];
  var extensions_proto = [];

  function Class(name, methods, inherits) {

    var classed = {},
      original_name = name;

    methods = methods || {};

    // methods = _.defaults(methods, {
    //   $pk: 'id'
    // });

    // prevent injection on eval
    name = name.toLowerCase().replace(/[^a-z0-1_A-Z]/g, '');
    var Name = _.capitalize(name.replace(/[^a-z0-1_A-Z]/g, ''));
    var NameWrapper = (Name + "Wrapper").replace(/[^a-z0-1_A-Z]/g, '');

    ai[name] = 1;

    var defaults = methods.$defaults || {};

    function hasMany() {
      if (methods.$hasMany) {
        console.log(methods.$hasMany);
        _.each(methods.$hasMany, function(r, k) {
          defaults[k] = r.$constructor();

          defaults[k].$belongsTo[original_name] = this;
        }, this);
      }
    }

    eval('function '+Name+'(o){\
      hasMany.call(this);\
      var defs = _.defaults(_.cloneDeep(defaults), {\
        id: ai[name],\
        $loading: false\
      });\
      o = _.defaults(o || {}, defs);\
      _.assign(this,o);\
      ai[name] += 1;\
    }');

    eval('function '+NameWrapper+'(value){\
      var u = new '+Name+'(value);\
      _.assign(this,u);\
      this.$wrapped = u;\
    }');

    var constructor;

    eval('var '+name+' = constructor = function '+name+'(value){\
      if (value instanceof '+NameWrapper+')\
        return value;\
      return new '+NameWrapper+'(value);\
    }');

    // eval(name+'.$data = [];');
    // eval(name+'.$current = null;');
    // eval(name+'.$current = $current.bind('+name+');');
    // eval(name+'.$add = $add.bind('+name+');');

    var listeners = {};

    function dispatch(listener) {
      var args = Array.prototype.slice.call(arguments, 1);

      if (listeners[listener]) {
        _.each(listeners[listener], function(callback) {
          callback.apply(this, args);
        }, this);
      }

      return;
    }

    function Classy() {
      var current = null;
      var data = [];
      var indexed = {};
      var index = methods.$index || 'id';

      this.$belongsTo = {};

      this.$loading = false;

      this.$on = function(listener, callback) {
        listeners[listener] = listeners[listener] || [];

        if (_.isFunction(callback))
          listeners[listener].push(callback);

        return this;
      };

      this.$constructor = function() {
        return Class(name, methods, inherits);
      };

      this.$current = function (c) {
        if (!_.isUndefined(c)) {
          current = (c && c.id) ? this.$data(c) : null;
          return this;
        } else {
          return current;
        }
      };

      this.$first = function() {
        return _.first(data);
      };

      this.$last = function() {
        return _.last(data);
      };

      this.$next = function() {
        var k = _.indexOf(data, this.$current());

        return (k > -1) ? (data[k+1] ? data[k+1] : false) : false;
      };

      this.$prev = function() {
        var k = _.indexOf(data, this.$current());

        return (k > -1) ? (data[k-1] ? data[k-1] : false) : false;
      };

      this.$goNext = function() {
        var k = _.indexOf(data, this.$current());

        this.$current(data[k+1] ? data[k+1] : this.$current());

        return this;
      };

      this.$goPrev = function() {
        var k = _.indexOf(data, this.$current());

        this.$current(data[k-1] ? data[k-1] : this.$current());

        return this;
      };

      this.$add = function(u) {
        u = constructor(u);

        data.push(u);
        indexed[u[index]] = u;

        dispatch.call(this, '$add', u);

        return this;
      };

      this.$load = function(data) {
        this.$removeAll();

        _.each(data, function(d) {
          this(d).$add();
        }, this);

        return this;
      };

      this.$removeAll = function() {
        data.length = 0;
        // WARNING
        // to not remove the assigment, use the code above
        // for (var j in indexed) delete indexed[j];
        indexed = {};

        ai[name] = 1;
        current = null;

        dispatch.call(this, '$removeAll');

        return this;
      };

      this.$removeAt = function(at, n) {
        var rem = data.splice(at, n || 1);

        _.each(rem, function(k) {
          if (_.isEqual(current, k))
            current = null;
          delete indexed[k[index]];
        });

        delete rem;

        dispatch.call(this, '$removeAt', at, n);

        return this;
      };

      this.$remove = function(u) {
        delete indexed[u[index]];
        _.pull(data, u);

        if (_.isEqual(current, u))
          current = null;

        dispatch.call(this, '$remove', u);

        return this;
      };

      this.$data = function(i) {
        return _.isFinite(i)
          ? data[i]
          : (_.isObject(i) ? _.find(data, i) : data);
      };

      this.$filter = function(ff) {
        return _.filter(data, ff);
      };

      this.$find = function(ff) {
        return _.find(data, ff);
      };

      this.$export = function() {
        return _.filter(data, function(d) {
          return d.$value();
        });
      };

      this.$change = function(a, b) {
        var k;
        if (_.isFinite(a))
          k = a;
        else
          k = _.findIndex(data, {id: a.id});

        if (data[k])
          data[k] = this(b);

        return this;
      };

      this.$set = function(u, key, value) {
        if (_.isArray(key)) {
          // TODO
        } else if(_.isPlainObject(key)) {
          this.$change(a, b);
        } else {
          u[key] = value;
          u.$wrapped[key] = u[key];
        }

        return this;
      };

      this.$push = function(u, key, value) {

        if (_.isArray(u[key])) {
          u[key].push(value);
          u.$wrapped[key] = u[key];
        }

        return this;
      };

      this.$pull = function(u, key, value) {
        if (_.isArray(u[key])) {
          _.pull(u[key], value);
          u.$wrapped[key] = u[key];
        }

        return this;
      };

      this.$concat = function(u, key, value) {
        if (_.isArray(u[key])) {
          u[key] = u[key].concat(value);
          u.$wrapped[key] = u[key];
        }

        return this;
      };

      this.$indexed = function(k) {
        return k ? indexed[k] : indexed;
      };

      // FIXME
      _.each(extensions, function(e) {
        if (e.options.chain)
          _.assign(this, e.extension);
      }, this);

      // FIXME
      _.each(extensions_proto, function(e) {
        if (e.options.chain)
          _.assign(this, e.extension);
      }, this);

      // FIXME
      if (methods && !methods.prototype)
        _.assign(this, methods);
    }

    // methods only for the constructor not his instances
    function ClassyProto() {}

    // unchainable methods
    function ClassyUnchain() {}

    ClassyUnchain.prototype.$value = function(u) {
      var n = {};

      _.each(this, function(v, k) {
        if (_.indexOf(k, '$') !== 0)
          n[k] = v;
      }, this);

      return n;
    };

    if (methods && methods.$unchain) {
      mixin(ClassyUnchain, methods.$unchain, false);
    }

    // FIXME
    if (methods && methods.prototype) {
      Classy.prototype = methods.prototype;
    }

    if (inherits)
      eval('mixin('+name+', inherits, {chain: true, super: true});');

    function $extend(_o) {
      eval('mixin('+name+', _o, {chain: true, super: true});');
      return this;
    }
    eval(''+name+'.$extend = $extend;');

    // function $current(c) {
    //   if (c) {
    //     eval(name+'.$current = c');
    //     return this;
    //   } else {
    //     return eval(name+'.$current');
    //   }
    // }
    // function $add(u) {
    //   eval(name+'.$data.push(u);');
    //   return this;
    // };

    // _.each(new Classy(), function(d, k) {
    //   eval(''+name+'.'+k+' = d;');
    // });

    eval('mixin('+name+', new Classy(), {chain: true, super: true});');

    eval('mixin('+name+', new ClassyUnchain(), false);');

    // eval('mixin('+name+', '+name+');');
    eval(NameWrapper+'.prototype = '+name+'.prototype;');

    // private methods for the Constructor only
    _.each(new ClassyProto(), function(d, k) {
      eval(''+name+'.'+k+' = d;');
    });

    return eval(name);
  }

  Class.$extend = function(obj, options) {
    _.defaults(options, {
      chain: true
    });

    obj = {
      extension: obj,
      options: options
    };

    if (_.isPlainObject(obj)) {
      extensions.push(obj);
    } else {
      extensions_proto.push(obj);
    }

    return Class;
  };

  if (exports.Classy) {
    console.info("The library Classy is already loaded.");
  } else {
    exports.Classy = Class;
  }

}(window));
<|endoftext|>