825 lines
29 KiB
JavaScript
825 lines
29 KiB
JavaScript
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.MapboxGLLayers = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
|
var yo = require('yo-yo')
|
|
var Control = require('mapbox-gl/js/ui/control/control')
|
|
|
|
module.exports = Layers
|
|
|
|
/**
|
|
* Creates a layer toggle control
|
|
* @param {Object} [options]
|
|
* @param {string} [options.type='multiple'] Selection type: `multiple` to allow independently toggling each layer/group, `single` to only choose one at a time.
|
|
* @param {Object} [options.layers] An object determining which layers to include. Each key is a display name (what's shown in the UI), and each value is the corresponding layer id in the map style (or an array of layer ids).
|
|
* @param {string} [options.position='top-right'] A string indicating position on the map. Options are `top-right`, `top-left`, `bottom-right`, `bottom-left`.
|
|
* @param {function} [options.onChange] Optional callback called with `{name: dispayName, layerIds: [...], active: true|false }` for the clicked layer
|
|
* @example
|
|
* (new Layers({ 'National Parks': 'national_park', 'Other Parks': 'parks' }))
|
|
* .addTo(map)
|
|
*/
|
|
function Layers (options) {
|
|
this.options = Object.assign({}, this.options, options)
|
|
if (options.layers) {
|
|
// normalize layers to arrays
|
|
var layers = {}
|
|
for (var k in this.options.layers) {
|
|
layers[k] = Array.isArray(this.options.layers[k])
|
|
? this.options.layers[k] : [this.options.layers[k]]
|
|
}
|
|
this.options.layers = layers
|
|
}
|
|
|
|
this._onClick = this._onClick.bind(this)
|
|
this._isActive = this._isActive.bind(this)
|
|
this._layerExists = this._layerExists.bind(this)
|
|
this._update = this._update.bind(this)
|
|
}
|
|
|
|
Layers.prototype = Object.create(Control.prototype)
|
|
Layers.prototype.constructor = Layers
|
|
Layers.prototype.options = { position: 'top-right', type: 'multiple' }
|
|
Layers.prototype.onAdd = function onAdd (map) {
|
|
this._map = map
|
|
var style = map.getStyle()
|
|
this._allLayers = style.layers.map((layer) => layer.id)
|
|
if (!this.options.layers) {
|
|
this.options.layers = {}
|
|
|
|
// if there's Mapbox Studio metadata available, use any groups we can find
|
|
var groups = {}
|
|
if (style.metadata && style.metadata['mapbox:groups']) {
|
|
groups = style.metadata['mapbox:groups']
|
|
Object.keys(groups).forEach((g) => { this.options.layers[groups[g].name] = [] })
|
|
}
|
|
|
|
style.layers.forEach((layer) => {
|
|
var group = layer.metadata ? groups[layer.metadata['mapbox:group']] : null
|
|
if (layer.metadata && group) {
|
|
this.options.layers[group.name].push(layer.id)
|
|
} else {
|
|
this.options.layers[layer.id] = [layer.id]
|
|
}
|
|
})
|
|
}
|
|
this._map.on('style.change', this._update)
|
|
this._map.style.on('layer.remove', this._update)
|
|
this._map.style.on('layer.add', this._update)
|
|
return this._render()
|
|
}
|
|
|
|
Layers.prototype.onRemove = function onRemove () {
|
|
this._map.off('style.change', this._update)
|
|
this._map.style.off('layer.remove', this._update)
|
|
this._map.style.off('layer.add', this._update)
|
|
}
|
|
|
|
Layers.prototype._update = function _update () {
|
|
this._allLayers = this._map.getStyle().layers.map((layer) => layer.id)
|
|
yo.update(this._container, this._render())
|
|
}
|
|
Layers.prototype._render = function _render () {
|
|
var layers = this.options.layers
|
|
var className = 'mapboxgl-ctrl mapboxgl-layers'
|
|
return (function () {
|
|
function appendChild (el, childs) {
|
|
for (var i = 0; i < childs.length; i++) {
|
|
var node = childs[i];
|
|
if (Array.isArray(node)) {
|
|
appendChild(el, node)
|
|
continue
|
|
}
|
|
if (typeof node === "number" ||
|
|
typeof node === "boolean" ||
|
|
node instanceof Date ||
|
|
node instanceof RegExp) {
|
|
node = node.toString()
|
|
}
|
|
|
|
if (typeof node === "string") {
|
|
if (el.lastChild && el.lastChild.nodeName === "#text") {
|
|
el.lastChild.nodeValue += node
|
|
continue
|
|
}
|
|
node = document.createTextNode(node)
|
|
}
|
|
|
|
if (node && node.nodeType) {
|
|
el.appendChild(node)
|
|
}
|
|
}
|
|
}
|
|
var bel1 = document.createElement("div")
|
|
bel1.setAttribute("class", arguments[1])
|
|
var bel0 = document.createElement("ul")
|
|
appendChild(bel0, ["\n ",arguments[0],"\n "])
|
|
appendChild(bel1, ["\n ",bel0,"\n "])
|
|
return bel1
|
|
}(Object.keys(layers)
|
|
.filter((name) => layers[name].some(this._layerExists))
|
|
.map((name) => {
|
|
var ids = layers[name].filter(this._layerExists)
|
|
var className = ids.every(this._isActive) ? 'active'
|
|
: ids.some(this._isActive) ? 'active partially-active'
|
|
: ''
|
|
return (function () {
|
|
function appendChild (el, childs) {
|
|
for (var i = 0; i < childs.length; i++) {
|
|
var node = childs[i];
|
|
if (Array.isArray(node)) {
|
|
appendChild(el, node)
|
|
continue
|
|
}
|
|
if (typeof node === "number" ||
|
|
typeof node === "boolean" ||
|
|
node instanceof Date ||
|
|
node instanceof RegExp) {
|
|
node = node.toString()
|
|
}
|
|
|
|
if (typeof node === "string") {
|
|
if (el.lastChild && el.lastChild.nodeName === "#text") {
|
|
el.lastChild.nodeValue += node
|
|
continue
|
|
}
|
|
node = document.createTextNode(node)
|
|
}
|
|
|
|
if (node && node.nodeType) {
|
|
el.appendChild(node)
|
|
}
|
|
}
|
|
}
|
|
var bel0 = document.createElement("li")
|
|
bel0.setAttribute("data-layer-name", arguments[0])
|
|
bel0.setAttribute("data-layer-id", arguments[1])
|
|
bel0["onclick"] = arguments[2]
|
|
bel0.setAttribute("class", arguments[3])
|
|
appendChild(bel0, ["\n ",arguments[4],"\n "])
|
|
return bel0
|
|
}(name,ids.join(','),this._onClick,className,name))
|
|
}),className))
|
|
}
|
|
|
|
Layers.prototype._onClick = function _onClick (e) {
|
|
var ids = e.currentTarget.getAttribute('data-layer-id').split(',')
|
|
.filter(this._layerExists)
|
|
|
|
var activated = false
|
|
if (this.options.type === 'single') {
|
|
// single selection mode
|
|
if (this._currentSelection) {
|
|
this._currentSelection.forEach((id) => {
|
|
this._map.setLayoutProperty(id, 'visibility', 'none')
|
|
})
|
|
}
|
|
// turn on any layer that IS in the selected group
|
|
ids.forEach((id) => {
|
|
this._map.setLayoutProperty(id, 'visibility', 'visible')
|
|
})
|
|
this._currentSelection = ids
|
|
activated = true
|
|
} else {
|
|
// 'toggle' mode
|
|
var visibility = ids.some(this._isActive) ? 'none' : 'visible'
|
|
ids.forEach((id) => {
|
|
this._map.setLayoutProperty(id, 'visibility', visibility)
|
|
})
|
|
activated = visibility === 'visible'
|
|
}
|
|
|
|
if (this.options.onChange) {
|
|
this.options.onChange({
|
|
name: e.currentTarget.getAttribute('data-layer-name'),
|
|
layerIds: ids,
|
|
active: activated
|
|
})
|
|
}
|
|
}
|
|
|
|
Layers.prototype._isActive = function isActive (id) {
|
|
return this._map.getLayoutProperty(id, 'visibility') === 'visible'
|
|
}
|
|
|
|
Layers.prototype._layerExists = function (id) {
|
|
return this._allLayers.indexOf(id) >= 0
|
|
}
|
|
|
|
|
|
},{"mapbox-gl/js/ui/control/control":2,"yo-yo":4}],2:[function(require,module,exports){
|
|
'use strict';
|
|
|
|
module.exports = Control;
|
|
|
|
/**
|
|
* A base class for map-related interface elements.
|
|
*
|
|
* @class Control
|
|
*/
|
|
function Control() {}
|
|
|
|
Control.prototype = {
|
|
/**
|
|
* Add this control to the map, returning the control itself
|
|
* for chaining. This will insert the control's DOM element into
|
|
* the map's DOM element if the control has a `position` specified.
|
|
*
|
|
* @param {Map} map
|
|
* @returns {Control} `this`
|
|
*/
|
|
addTo: function(map) {
|
|
this._map = map;
|
|
var container = this._container = this.onAdd(map);
|
|
if (this.options && this.options.position) {
|
|
var pos = this.options.position;
|
|
var corner = map._controlCorners[pos];
|
|
container.className += ' mapboxgl-ctrl';
|
|
if (pos.indexOf('bottom') !== -1) {
|
|
corner.insertBefore(container, corner.firstChild);
|
|
} else {
|
|
corner.appendChild(container);
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Remove this control from the map it has been added to.
|
|
*
|
|
* @returns {Control} `this`
|
|
*/
|
|
remove: function() {
|
|
this._container.parentNode.removeChild(this._container);
|
|
if (this.onRemove) this.onRemove(this._map);
|
|
this._map = null;
|
|
return this;
|
|
}
|
|
};
|
|
|
|
},{}],3:[function(require,module,exports){
|
|
// Create a range object for efficently rendering strings to elements.
|
|
var range;
|
|
|
|
var testEl = typeof document !== 'undefined' ? document.body || document.createElement('div') : {};
|
|
|
|
// Fixes https://github.com/patrick-steele-idem/morphdom/issues/32 (IE7+ support)
|
|
// <=IE7 does not support el.hasAttribute(name)
|
|
var hasAttribute;
|
|
if (testEl.hasAttribute) {
|
|
hasAttribute = function hasAttribute(el, name) {
|
|
return el.hasAttribute(name);
|
|
};
|
|
} else {
|
|
hasAttribute = function hasAttribute(el, name) {
|
|
return el.getAttributeNode(name);
|
|
};
|
|
}
|
|
|
|
function empty(o) {
|
|
for (var k in o) {
|
|
if (o.hasOwnProperty(k)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
function toElement(str) {
|
|
if (!range && document.createRange) {
|
|
range = document.createRange();
|
|
range.selectNode(document.body);
|
|
}
|
|
|
|
var fragment;
|
|
if (range && range.createContextualFragment) {
|
|
fragment = range.createContextualFragment(str);
|
|
} else {
|
|
fragment = document.createElement('body');
|
|
fragment.innerHTML = str;
|
|
}
|
|
return fragment.childNodes[0];
|
|
}
|
|
|
|
var specialElHandlers = {
|
|
/**
|
|
* Needed for IE. Apparently IE doesn't think
|
|
* that "selected" is an attribute when reading
|
|
* over the attributes using selectEl.attributes
|
|
*/
|
|
OPTION: function(fromEl, toEl) {
|
|
if ((fromEl.selected = toEl.selected)) {
|
|
fromEl.setAttribute('selected', '');
|
|
} else {
|
|
fromEl.removeAttribute('selected', '');
|
|
}
|
|
},
|
|
/**
|
|
* The "value" attribute is special for the <input> element
|
|
* since it sets the initial value. Changing the "value"
|
|
* attribute without changing the "value" property will have
|
|
* no effect since it is only used to the set the initial value.
|
|
* Similar for the "checked" attribute.
|
|
*/
|
|
INPUT: function(fromEl, toEl) {
|
|
fromEl.checked = toEl.checked;
|
|
|
|
if (fromEl.value != toEl.value) {
|
|
fromEl.value = toEl.value;
|
|
}
|
|
|
|
if (!hasAttribute(toEl, 'checked')) {
|
|
fromEl.removeAttribute('checked');
|
|
}
|
|
|
|
if (!hasAttribute(toEl, 'value')) {
|
|
fromEl.removeAttribute('value');
|
|
}
|
|
},
|
|
|
|
TEXTAREA: function(fromEl, toEl) {
|
|
var newValue = toEl.value;
|
|
if (fromEl.value != newValue) {
|
|
fromEl.value = newValue;
|
|
}
|
|
|
|
if (fromEl.firstChild) {
|
|
fromEl.firstChild.nodeValue = newValue;
|
|
}
|
|
}
|
|
};
|
|
|
|
function noop() {}
|
|
|
|
/**
|
|
* Loop over all of the attributes on the target node and make sure the
|
|
* original DOM node has the same attributes. If an attribute
|
|
* found on the original node is not on the new node then remove it from
|
|
* the original node
|
|
* @param {HTMLElement} fromNode
|
|
* @param {HTMLElement} toNode
|
|
*/
|
|
function morphAttrs(fromNode, toNode) {
|
|
var attrs = toNode.attributes;
|
|
var i;
|
|
var attr;
|
|
var attrName;
|
|
var attrValue;
|
|
var foundAttrs = {};
|
|
|
|
for (i=attrs.length-1; i>=0; i--) {
|
|
attr = attrs[i];
|
|
if (attr.specified !== false) {
|
|
attrName = attr.name;
|
|
attrValue = attr.value;
|
|
foundAttrs[attrName] = true;
|
|
|
|
if (fromNode.getAttribute(attrName) !== attrValue) {
|
|
fromNode.setAttribute(attrName, attrValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Delete any extra attributes found on the original DOM element that weren't
|
|
// found on the target element.
|
|
attrs = fromNode.attributes;
|
|
|
|
for (i=attrs.length-1; i>=0; i--) {
|
|
attr = attrs[i];
|
|
if (attr.specified !== false) {
|
|
attrName = attr.name;
|
|
if (!foundAttrs.hasOwnProperty(attrName)) {
|
|
fromNode.removeAttribute(attrName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Copies the children of one DOM element to another DOM element
|
|
*/
|
|
function moveChildren(fromEl, toEl) {
|
|
var curChild = fromEl.firstChild;
|
|
while(curChild) {
|
|
var nextChild = curChild.nextSibling;
|
|
toEl.appendChild(curChild);
|
|
curChild = nextChild;
|
|
}
|
|
return toEl;
|
|
}
|
|
|
|
function defaultGetNodeKey(node) {
|
|
return node.id;
|
|
}
|
|
|
|
function morphdom(fromNode, toNode, options) {
|
|
if (!options) {
|
|
options = {};
|
|
}
|
|
|
|
if (typeof toNode === 'string') {
|
|
if (fromNode.nodeName === '#document' || fromNode.nodeName === 'HTML') {
|
|
var toNodeHtml = toNode;
|
|
toNode = document.createElement('html');
|
|
toNode.innerHTML = toNodeHtml;
|
|
} else {
|
|
toNode = toElement(toNode);
|
|
}
|
|
}
|
|
|
|
var savedEls = {}; // Used to save off DOM elements with IDs
|
|
var unmatchedEls = {};
|
|
var getNodeKey = options.getNodeKey || defaultGetNodeKey;
|
|
var onBeforeNodeAdded = options.onBeforeNodeAdded || noop;
|
|
var onNodeAdded = options.onNodeAdded || noop;
|
|
var onBeforeElUpdated = options.onBeforeElUpdated || options.onBeforeMorphEl || noop;
|
|
var onElUpdated = options.onElUpdated || noop;
|
|
var onBeforeNodeDiscarded = options.onBeforeNodeDiscarded || noop;
|
|
var onNodeDiscarded = options.onNodeDiscarded || noop;
|
|
var onBeforeElChildrenUpdated = options.onBeforeElChildrenUpdated || options.onBeforeMorphElChildren || noop;
|
|
var childrenOnly = options.childrenOnly === true;
|
|
var movedEls = [];
|
|
|
|
function removeNodeHelper(node, nestedInSavedEl) {
|
|
var id = getNodeKey(node);
|
|
// If the node has an ID then save it off since we will want
|
|
// to reuse it in case the target DOM tree has a DOM element
|
|
// with the same ID
|
|
if (id) {
|
|
savedEls[id] = node;
|
|
} else if (!nestedInSavedEl) {
|
|
// If we are not nested in a saved element then we know that this node has been
|
|
// completely discarded and will not exist in the final DOM.
|
|
onNodeDiscarded(node);
|
|
}
|
|
|
|
if (node.nodeType === 1) {
|
|
var curChild = node.firstChild;
|
|
while(curChild) {
|
|
removeNodeHelper(curChild, nestedInSavedEl || id);
|
|
curChild = curChild.nextSibling;
|
|
}
|
|
}
|
|
}
|
|
|
|
function walkDiscardedChildNodes(node) {
|
|
if (node.nodeType === 1) {
|
|
var curChild = node.firstChild;
|
|
while(curChild) {
|
|
|
|
|
|
if (!getNodeKey(curChild)) {
|
|
// We only want to handle nodes that don't have an ID to avoid double
|
|
// walking the same saved element.
|
|
|
|
onNodeDiscarded(curChild);
|
|
|
|
// Walk recursively
|
|
walkDiscardedChildNodes(curChild);
|
|
}
|
|
|
|
curChild = curChild.nextSibling;
|
|
}
|
|
}
|
|
}
|
|
|
|
function removeNode(node, parentNode, alreadyVisited) {
|
|
if (onBeforeNodeDiscarded(node) === false) {
|
|
return;
|
|
}
|
|
|
|
parentNode.removeChild(node);
|
|
if (alreadyVisited) {
|
|
if (!getNodeKey(node)) {
|
|
onNodeDiscarded(node);
|
|
walkDiscardedChildNodes(node);
|
|
}
|
|
} else {
|
|
removeNodeHelper(node);
|
|
}
|
|
}
|
|
|
|
function morphEl(fromEl, toEl, alreadyVisited, childrenOnly) {
|
|
var toElKey = getNodeKey(toEl);
|
|
if (toElKey) {
|
|
// If an element with an ID is being morphed then it is will be in the final
|
|
// DOM so clear it out of the saved elements collection
|
|
delete savedEls[toElKey];
|
|
}
|
|
|
|
if (!childrenOnly) {
|
|
if (onBeforeElUpdated(fromEl, toEl) === false) {
|
|
return;
|
|
}
|
|
|
|
morphAttrs(fromEl, toEl);
|
|
onElUpdated(fromEl);
|
|
|
|
if (onBeforeElChildrenUpdated(fromEl, toEl) === false) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (fromEl.tagName != 'TEXTAREA') {
|
|
var curToNodeChild = toEl.firstChild;
|
|
var curFromNodeChild = fromEl.firstChild;
|
|
var curToNodeId;
|
|
|
|
var fromNextSibling;
|
|
var toNextSibling;
|
|
var savedEl;
|
|
var unmatchedEl;
|
|
|
|
outer: while(curToNodeChild) {
|
|
toNextSibling = curToNodeChild.nextSibling;
|
|
curToNodeId = getNodeKey(curToNodeChild);
|
|
|
|
while(curFromNodeChild) {
|
|
var curFromNodeId = getNodeKey(curFromNodeChild);
|
|
fromNextSibling = curFromNodeChild.nextSibling;
|
|
|
|
if (!alreadyVisited) {
|
|
if (curFromNodeId && (unmatchedEl = unmatchedEls[curFromNodeId])) {
|
|
unmatchedEl.parentNode.replaceChild(curFromNodeChild, unmatchedEl);
|
|
morphEl(curFromNodeChild, unmatchedEl, alreadyVisited);
|
|
curFromNodeChild = fromNextSibling;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
var curFromNodeType = curFromNodeChild.nodeType;
|
|
|
|
if (curFromNodeType === curToNodeChild.nodeType) {
|
|
var isCompatible = false;
|
|
|
|
if (curFromNodeType === 1) { // Both nodes being compared are Element nodes
|
|
if (curFromNodeChild.tagName === curToNodeChild.tagName) {
|
|
// We have compatible DOM elements
|
|
if (curFromNodeId || curToNodeId) {
|
|
// If either DOM element has an ID then we handle
|
|
// those differently since we want to match up
|
|
// by ID
|
|
if (curToNodeId === curFromNodeId) {
|
|
isCompatible = true;
|
|
}
|
|
} else {
|
|
isCompatible = true;
|
|
}
|
|
}
|
|
|
|
if (isCompatible) {
|
|
// We found compatible DOM elements so transform the current "from" node
|
|
// to match the current target DOM node.
|
|
morphEl(curFromNodeChild, curToNodeChild, alreadyVisited);
|
|
}
|
|
} else if (curFromNodeType === 3) { // Both nodes being compared are Text nodes
|
|
isCompatible = true;
|
|
// Simply update nodeValue on the original node to change the text value
|
|
curFromNodeChild.nodeValue = curToNodeChild.nodeValue;
|
|
}
|
|
|
|
if (isCompatible) {
|
|
curToNodeChild = toNextSibling;
|
|
curFromNodeChild = fromNextSibling;
|
|
continue outer;
|
|
}
|
|
}
|
|
|
|
// No compatible match so remove the old node from the DOM and continue trying
|
|
// to find a match in the original DOM
|
|
removeNode(curFromNodeChild, fromEl, alreadyVisited);
|
|
curFromNodeChild = fromNextSibling;
|
|
}
|
|
|
|
if (curToNodeId) {
|
|
if ((savedEl = savedEls[curToNodeId])) {
|
|
morphEl(savedEl, curToNodeChild, true);
|
|
curToNodeChild = savedEl; // We want to append the saved element instead
|
|
} else {
|
|
// The current DOM element in the target tree has an ID
|
|
// but we did not find a match in any of the corresponding
|
|
// siblings. We just put the target element in the old DOM tree
|
|
// but if we later find an element in the old DOM tree that has
|
|
// a matching ID then we will replace the target element
|
|
// with the corresponding old element and morph the old element
|
|
unmatchedEls[curToNodeId] = curToNodeChild;
|
|
}
|
|
}
|
|
|
|
// If we got this far then we did not find a candidate match for our "to node"
|
|
// and we exhausted all of the children "from" nodes. Therefore, we will just
|
|
// append the current "to node" to the end
|
|
if (onBeforeNodeAdded(curToNodeChild) !== false) {
|
|
fromEl.appendChild(curToNodeChild);
|
|
onNodeAdded(curToNodeChild);
|
|
}
|
|
|
|
if (curToNodeChild.nodeType === 1 && (curToNodeId || curToNodeChild.firstChild)) {
|
|
// The element that was just added to the original DOM may have
|
|
// some nested elements with a key/ID that needs to be matched up
|
|
// with other elements. We'll add the element to a list so that we
|
|
// can later process the nested elements if there are any unmatched
|
|
// keyed elements that were discarded
|
|
movedEls.push(curToNodeChild);
|
|
}
|
|
|
|
curToNodeChild = toNextSibling;
|
|
curFromNodeChild = fromNextSibling;
|
|
}
|
|
|
|
// We have processed all of the "to nodes". If curFromNodeChild is non-null then
|
|
// we still have some from nodes left over that need to be removed
|
|
while(curFromNodeChild) {
|
|
fromNextSibling = curFromNodeChild.nextSibling;
|
|
removeNode(curFromNodeChild, fromEl, alreadyVisited);
|
|
curFromNodeChild = fromNextSibling;
|
|
}
|
|
}
|
|
|
|
var specialElHandler = specialElHandlers[fromEl.tagName];
|
|
if (specialElHandler) {
|
|
specialElHandler(fromEl, toEl);
|
|
}
|
|
} // END: morphEl(...)
|
|
|
|
var morphedNode = fromNode;
|
|
var morphedNodeType = morphedNode.nodeType;
|
|
var toNodeType = toNode.nodeType;
|
|
|
|
if (!childrenOnly) {
|
|
// Handle the case where we are given two DOM nodes that are not
|
|
// compatible (e.g. <div> --> <span> or <div> --> TEXT)
|
|
if (morphedNodeType === 1) {
|
|
if (toNodeType === 1) {
|
|
if (fromNode.tagName !== toNode.tagName) {
|
|
onNodeDiscarded(fromNode);
|
|
morphedNode = moveChildren(fromNode, document.createElement(toNode.tagName));
|
|
}
|
|
} else {
|
|
// Going from an element node to a text node
|
|
morphedNode = toNode;
|
|
}
|
|
} else if (morphedNodeType === 3) { // Text node
|
|
if (toNodeType === 3) {
|
|
morphedNode.nodeValue = toNode.nodeValue;
|
|
return morphedNode;
|
|
} else {
|
|
// Text node to something else
|
|
morphedNode = toNode;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (morphedNode === toNode) {
|
|
// The "to node" was not compatible with the "from node"
|
|
// so we had to toss out the "from node" and use the "to node"
|
|
onNodeDiscarded(fromNode);
|
|
} else {
|
|
morphEl(morphedNode, toNode, false, childrenOnly);
|
|
|
|
/**
|
|
* What we will do here is walk the tree for the DOM element
|
|
* that was moved from the target DOM tree to the original
|
|
* DOM tree and we will look for keyed elements that could
|
|
* be matched to keyed elements that were earlier discarded.
|
|
* If we find a match then we will move the saved element
|
|
* into the final DOM tree
|
|
*/
|
|
var handleMovedEl = function(el) {
|
|
var curChild = el.firstChild;
|
|
while(curChild) {
|
|
var nextSibling = curChild.nextSibling;
|
|
|
|
var key = getNodeKey(curChild);
|
|
if (key) {
|
|
var savedEl = savedEls[key];
|
|
if (savedEl && (curChild.tagName === savedEl.tagName)) {
|
|
curChild.parentNode.replaceChild(savedEl, curChild);
|
|
morphEl(savedEl, curChild, true /* already visited the saved el tree */);
|
|
curChild = nextSibling;
|
|
if (empty(savedEls)) {
|
|
return false;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (curChild.nodeType === 1) {
|
|
handleMovedEl(curChild);
|
|
}
|
|
|
|
curChild = nextSibling;
|
|
}
|
|
};
|
|
|
|
// The loop below is used to possibly match up any discarded
|
|
// elements in the original DOM tree with elemenets from the
|
|
// target tree that were moved over without visiting their
|
|
// children
|
|
if (!empty(savedEls)) {
|
|
handleMovedElsLoop:
|
|
while (movedEls.length) {
|
|
var movedElsTemp = movedEls;
|
|
movedEls = [];
|
|
for (var i=0; i<movedElsTemp.length; i++) {
|
|
if (handleMovedEl(movedElsTemp[i]) === false) {
|
|
// There are no more unmatched elements so completely end
|
|
// the loop
|
|
break handleMovedElsLoop;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fire the "onNodeDiscarded" event for any saved elements
|
|
// that never found a new home in the morphed DOM
|
|
for (var savedElId in savedEls) {
|
|
if (savedEls.hasOwnProperty(savedElId)) {
|
|
var savedEl = savedEls[savedElId];
|
|
onNodeDiscarded(savedEl);
|
|
walkDiscardedChildNodes(savedEl);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!childrenOnly && morphedNode !== fromNode && fromNode.parentNode) {
|
|
// If we had to swap out the from node with a new node because the old
|
|
// node was not compatible with the target node then we need to
|
|
// replace the old DOM node in the original DOM tree. This is only
|
|
// possible if the original DOM node was part of a DOM tree which
|
|
// we know is the case if it has a parent node.
|
|
fromNode.parentNode.replaceChild(morphedNode, fromNode);
|
|
}
|
|
|
|
return morphedNode;
|
|
}
|
|
|
|
module.exports = morphdom;
|
|
|
|
},{}],4:[function(require,module,exports){
|
|
var bel = {} // turns template tag into DOM elements
|
|
var morphdom = require('morphdom') // efficiently diffs + morphs two DOM elements
|
|
var defaultEvents = require('./update-events.js') // default events to be copied when dom elements update
|
|
|
|
module.exports = bel
|
|
|
|
// TODO move this + defaultEvents to a new module once we receive more feedback
|
|
module.exports.update = function (fromNode, toNode, opts) {
|
|
if (!opts) opts = {}
|
|
if (opts.events !== false) {
|
|
if (!opts.onBeforeMorphEl) opts.onBeforeMorphEl = copyEvents
|
|
}
|
|
|
|
morphdom(fromNode, toNode, opts)
|
|
|
|
// morphdom only copies attributes. we decided we also wanted to copy events
|
|
// that can be set via attributes
|
|
function copyEvents (f, t) {
|
|
var events = opts.events || defaultEvents
|
|
for (var i = 0; i < events.length; i++) {
|
|
var ev = events[i]
|
|
if (t[ev]) { // if new element has a whitelisted attribute
|
|
f[ev] = t[ev] // update existing element
|
|
} else if (f[ev]) { // if existing element has it and new one doesnt
|
|
f[ev] = undefined // remove it from existing element
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
},{"./update-events.js":5,"morphdom":3}],5:[function(require,module,exports){
|
|
module.exports = [
|
|
// attribute events (can be set with attributes)
|
|
'onclick',
|
|
'ondblclick',
|
|
'onmousedown',
|
|
'onmouseup',
|
|
'onmouseover',
|
|
'onmousemove',
|
|
'onmouseout',
|
|
'ondragstart',
|
|
'ondrag',
|
|
'ondragenter',
|
|
'ondragleave',
|
|
'ondragover',
|
|
'ondrop',
|
|
'ondragend',
|
|
'onkeydown',
|
|
'onkeypress',
|
|
'onkeyup',
|
|
'onunload',
|
|
'onabort',
|
|
'onerror',
|
|
'onresize',
|
|
'onscroll',
|
|
'onselect',
|
|
'onchange',
|
|
'onsubmit',
|
|
'onreset',
|
|
'onfocus',
|
|
'onblur',
|
|
// other common events
|
|
'oncontextmenu',
|
|
'onfocusin',
|
|
'onfocusout'
|
|
]
|
|
|
|
},{}]},{},[1])(1)
|
|
}); |