// @ts-nocheck /** * @name Sidebar * @class L.Control.Sidebar * @extends L.Control * @param {string} id - The id of the sidebar element (without the # character) * @param {Object} [options] - Optional options object * @param {string} [options.autopan=false] - whether to move the map when opening the sidebar to make maintain the visible center point * @param {string} [options.position=left] - Position of the sidebar: 'left' or 'right' * @param {string} [options.id] - ID of a predefined sidebar container that should be used * @param {boolean} [data.close=true] Whether to add a close button to the pane header * @see L.control.sidebar */ L.Control.Sidebar = L.Control.extend(/** @lends L.Control.Sidebar.prototype */ { includes: L.Evented ? L.Evented.prototype : L.Mixin.Events, options: { autopan: false, closeButton: true, id: '', position: 'right' }, /** * Create a new sidebar on this object. * * @constructor * @param {Object} [options] - Optional options object * @param {string} [options.autopan=false] - whether to move the map when opening the sidebar to make maintain the visible center point * @param {string} [options.position=left] - Position of the sidebar: 'left' or 'right' * @param {string} [options.id] - ID of a predefined sidebar container that should be used * @param {bool} [data.close=true] Whether to add a close button to the pane header */ initialize: function(options, deprecatedOptions) { if (typeof options === 'string') { console.warn('this syntax is deprecated. please use L.control.sidebar({ id }) now'); options = { id: options }; } this._tabitems = []; this._panes = []; this._closeButtons = []; L.setOptions(this, Object.assign({}, options, deprecatedOptions)); return this; }, /** * Add this sidebar to the specified map. * * @param {L.Map} map * @returns {Sidebar} */ onAdd: function(map) { var i, j, child, tabContainers, newContainer, container; // Find sidebar HTMLElement via .sidebar, create it if none was found container = L.DomUtil.get(this.options.id); if (container == null) container = L.DomUtil.create('div', 'sidebar collapsed'); // Find paneContainer in DOM & store reference this._paneContainer = container.querySelector('div.sidebar-content'); // If none is found, create it if (this._paneContainer === null) this._paneContainer = L.DomUtil.create('div', 'sidebar-content', container); // Find tabContainerTop & tabContainerBottom in DOM & store reference tabContainers = container.querySelectorAll('ul.sidebar-tabs, div.sidebar-tabs > ul'); this._tabContainerTop = tabContainers[0] || null; this._tabContainerBottom = tabContainers[1] || null; // If no container was found, create it if (this._tabContainerTop === null) { newContainer = L.DomUtil.create('div', 'sidebar-tabs', container); newContainer.setAttribute('role', 'tablist'); this._tabContainerTop = L.DomUtil.create('ul', '', newContainer); } if (this._tabContainerBottom === null) { newContainer = this._tabContainerTop.parentNode; this._tabContainerBottom = L.DomUtil.create('ul', '', newContainer); } // Store Tabs in Collection for easier iteration for (i = 0; i < this._tabContainerTop.children.length; i++) { child = this._tabContainerTop.children[i]; child._sidebar = this; child._id = child.querySelector('a').hash.slice(1); // FIXME: this could break for links! this._tabitems.push(child); } for (i = 0; i < this._tabContainerBottom.children.length; i++) { child = this._tabContainerBottom.children[i]; child._sidebar = this; child._id = child.querySelector('a').hash.slice(1); // FIXME: this could break for links! this._tabitems.push(child); } // Store Panes in Collection for easier iteration for (i = 0; i < this._paneContainer.children.length; i++) { child = this._paneContainer.children[i]; if (child.tagName === 'DIV' && L.DomUtil.hasClass(child, 'sidebar-pane')) { this._panes.push(child); // Save references to close buttons var closeButtons = child.querySelectorAll('.sidebar-close'); if (closeButtons.length) { this._closeButtons.push(closeButtons[closeButtons.length - 1]); this._closeClick(closeButtons[closeButtons.length - 1], 'on'); } } } // set click listeners for tab & close buttons for (i = 0; i < this._tabitems.length; i++) { this._tabClick(this._tabitems[i], 'on'); } return container; }, /** * Remove this sidebar from the map. * * @param {L.Map} map * @returns {Sidebar} */ onRemove: function (map) { var i; this._map = null; this._tabitems = []; this._panes = []; this._closeButtons = []; // Remove click listeners for tab & close buttons for (i = 0; i < this._tabitems.length - 1; i++) this._tabClick(this._tabitems[i], 'off'); for (i = 0; i < this._closeButtons.length; i++) this._closeClick(this._closeButtons[i], 'off'); return this; }, /** * @method addTo(map: Map): this * Adds the control to the given map. Overrides the implementation of L.Control, * changing the DOM mount target from map._controlContainer.topleft to map._container */ addTo: function (map) { this.onRemove(); this._map = map; this._sidebar = this.onAdd(map); L.DomUtil.addClass(this._sidebar, 'leaflet-control'); L.DomUtil.addClass(this._sidebar, 'sidebar-' + this.getPosition()); if (L.Browser.touch) L.DomUtil.addClass(this._sidebar, 'leaflet-touch'); // when adding to the map container, we should stop event propagation L.DomEvent.disableScrollPropagation(this._sidebar); L.DomEvent.disableClickPropagation(this._sidebar); // insert as first child of map container (important for css) map._container.insertBefore(this._sidebar, map._container.firstChild); return this; }, /** * @deprecated - Please use remove() instead of removeFrom(), as of Leaflet 0.8-dev, the removeFrom() has been replaced with remove() * Removes this sidebar from the map. * @param {L.Map} map * @returns {Sidebar} */ removeFrom: function(map) { console.log('removeFrom() has been deprecated, please use remove() instead as support for this function will be ending soon.'); this.onRemove(map); }, /** * Open sidebar (if it's closed) and show the specified tab. * * @param {string} id - The ID of the tab to show (without the # character) * @returns {L.Control.Sidebar} */ open: function(id) { var i, child, tab; // If panel is disabled, stop right here tab = this._getTab(id); if (L.DomUtil.hasClass(tab, 'disabled')) return this; // Hide old active contents and show new content for (i = 0; i < this._panes.length; i++) { child = this._panes[i]; if (child.id === id) L.DomUtil.addClass(child, 'active'); else if (L.DomUtil.hasClass(child, 'active')) L.DomUtil.removeClass(child, 'active'); } // Remove old active highlights and set new highlight for (i = 0; i < this._tabitems.length; i++) { child = this._tabitems[i]; if (child.querySelector('a').hash === '#' + id) L.DomUtil.addClass(child, 'active'); else if (L.DomUtil.hasClass(child, 'active')) L.DomUtil.removeClass(child, 'active'); } this.fire('content', { id: id }); // Open sidebar if it's closed if (L.DomUtil.hasClass(this._sidebar, 'collapsed')) { this.fire('opening'); L.DomUtil.removeClass(this._sidebar, 'collapsed'); if (this.options.autopan) this._panMap('open'); } return this; }, /** * Close the sidebar (if it's open). * * @returns {L.Control.Sidebar} */ close: function() { var i; // Remove old active highlights for (i = 0; i < this._tabitems.length; i++) { var child = this._tabitems[i]; if (L.DomUtil.hasClass(child, 'active')) L.DomUtil.removeClass(child, 'active'); } // close sidebar, if it's opened if (!L.DomUtil.hasClass(this._sidebar, 'collapsed')) { this.fire('closing'); L.DomUtil.addClass(this._sidebar, 'collapsed'); if (this.options.autopan) this._panMap('close'); } return this; }, /** * Add a panel to the sidebar * * @example * sidebar.addPanel({ * id: 'userinfo', * tab: '', * pane: someDomNode.innerHTML, * position: 'bottom' * }); * * @param {Object} [data] contains the data for the new Panel: * @param {String} [data.id] the ID for the new Panel, must be unique for the whole page * @param {String} [data.position='top'] where the tab will appear: * on the top or the bottom of the sidebar. 'top' or 'bottom' * @param {HTMLString} {DOMnode} [data.tab] content of the tab item, as HTMLstring or DOM node * @param {HTMLString} {DOMnode} [data.pane] content of the panel, as HTMLstring or DOM node * @param {String} [data.link] URL to an (external) link that will be opened instead of a panel * @param {String} [data.title] Title for the pane header * @param {String} {Function} [data.button] URL to an (external) link or a click listener function that will be opened instead of a panel * @param {bool} [data.disabled] If the tab should be disabled by default * * @returns {L.Control.Sidebar} */ addPanel: function(data) { var i, pane, tab, tabHref, closeButtons, content; // Create tab node tab = L.DomUtil.create('li', data.disabled ? 'disabled' : ''); tabHref = L.DomUtil.create('a', '', tab); tabHref.href = '#' + data.id; tabHref.setAttribute('role', 'tab'); tabHref.innerHTML = data.tab; tab._sidebar = this; tab._id = data.id; tab._button = data.button; // to allow links to be disabled, the href cannot be used if (data.title && data.title[0] !== '<') tab.title = data.title; // append it to the DOM and store JS references if (data.position === 'bottom') this._tabContainerBottom.appendChild(tab); else this._tabContainerTop.appendChild(tab); this._tabitems.push(tab); // Create pane node if (data.pane) { if (typeof data.pane === 'string') { // pane is given as HTML string pane = L.DomUtil.create('DIV', 'sidebar-pane', this._paneContainer); content = ''; if (data.title) content += '

' + data.title; if (this.options.closeButton) content += ''; if (data.title) content += '

'; pane.innerHTML = content + data.pane; } else { // pane is given as DOM object pane = data.pane; this._paneContainer.appendChild(pane); } pane.id = data.id; this._panes.push(pane); // Save references to close button & register click listener closeButtons = pane.querySelectorAll('.sidebar-close'); if (closeButtons.length) { // select last button, because thats rendered on top this._closeButtons.push(closeButtons[closeButtons.length - 1]); this._closeClick(closeButtons[closeButtons.length - 1], 'on'); } } // Register click listeners, if the sidebar is on the map this._tabClick(tab, 'on'); return this; }, /** * Removes a panel from the sidebar * * @example * sidebar.remove('userinfo'); * * @param {String} [id] the ID of the panel that is to be removed * @returns {L.Control.Sidebar} */ removePanel: function(id) { var i, j, tab, pane, closeButtons; // find the tab & panel by ID, remove them, and clean up for (i = 0; i < this._tabitems.length; i++) { if (this._tabitems[i]._id === id) { tab = this._tabitems[i]; // Remove click listeners this._tabClick(tab, 'off'); tab.remove(); this._tabitems.slice(i, 1); break; } } for (i = 0; i < this._panes.length; i++) { if (this._panes[i].id === id) { pane = this._panes[i]; closeButtons = pane.querySelectorAll('.sidebar-close'); // FIXME: broken for loop. close button logic? for (j = 0; i < closeButtons.length; i++) { this._closeClick(closeButtons[j], 'off'); } pane.remove(); this._panes.slice(i, 1); break; } } return this; }, /** * enables a disabled tab/panel * * @param {String} [id] ID of the panel to enable * @returns {L.Control.Sidebar} */ enablePanel: function(id) { var tab = this._getTab(id); L.DomUtil.removeClass(tab, 'disabled'); return this; }, /** * disables an enabled tab/panel * * @param {String} [id] ID of the panel to disable * @returns {L.Control.Sidebar} */ disablePanel: function(id) { var tab = this._getTab(id); L.DomUtil.addClass(tab, 'disabled'); return this; }, /** * (un)registers the onclick event for the given tab, * depending on the second argument. * @private * * @param {DOMelement} [tab] * @param {String} [on] 'on' or 'off' */ _tabClick: function(tab, on) { var link = tab.querySelector('a'); if (link.hasAttribute('href') && link.getAttribute('href')[0] !== '#') return; var onTabClick = function(e) { // `this` points to the tab DOM element! if (L.DomUtil.hasClass(this, 'active')) { this._sidebar.close(); } else if (!L.DomUtil.hasClass(this, 'disabled')) { if (typeof this._button === 'string') // an url window.location.href = this._button; else if (typeof this._button === 'function') // a clickhandler this._button(e); else // a normal pane this._sidebar.open(this.querySelector('a').hash.slice(1)); } }; if (on === 'on') { L.DomEvent .on(tab.querySelector('a'), 'click', L.DomEvent.preventDefault) .on(tab.querySelector('a'), 'click', onTabClick, tab); } else { L.DomEvent.off(tab.querySelector('a'), 'click', onTabClick); } }, /** * (un)registers the onclick event for the given close button * depending on the second argument * @private * * @param {DOMelement} [closeButton] * @param {String} [on] 'on' or 'off' */ _closeClick: function(closeButton, on) { var onCloseClick = function() { this.close(); }; if (on === 'on') { L.DomEvent.on(closeButton, 'click', onCloseClick, this); } else { L.DomEvent.off(closeButton, 'click', onCloseClick, this); } }, /** * Finds & returns the DOMelement of a tab * * @param {String} [id] the id of the tab * @returns {DOMelement} the tab specified by id, null if not found */ _getTab: function(id) { for (var i = 0; i < this._tabitems.length; i++) { if (this._tabitems[i]._id === id) return this._tabitems[i]; } return null; }, /** * Helper for autopan: Pans the map for open/close events * * @param {String} [openClose] The behaviour to enact ('open' | 'close') */ _panMap: function(openClose) { var panWidth = Number.parseInt(L.DomUtil.getStyle(this._sidebar, 'max-width')) / 2; if ( openClose === 'open' && this.options.position === 'left' || openClose === 'close' && this.options.position === 'right' ) panWidth *= -1; this._map.panBy([panWidth, 0], { duration: 0.5 }); } }); /** * Create a new sidebar. * * @example * var sidebar = L.control.sidebar({ id: 'sidebar' }).addTo(map); * * @param {Object} [options] - Optional options object * @param {string} [options.autopan=false] - whether to move the map when opening the sidebar to make maintain the visible center point * @param {string} [options.position=left] - Position of the sidebar: 'left' or 'right' * @param {string} [options.id] - ID of a predefined sidebar container that should be used * @param {boolean} [data.close=true] Whether to add a close button to the pane header * @returns {Sidebar} A new sidebar instance */ L.control.sidebar = function(options, deprecated) { return new L.Control.Sidebar(options, deprecated); };