| /* |
| * Copyright (C) 2013 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| WI.GeneralTreeElement = class GeneralTreeElement extends WI.TreeElement |
| { |
| constructor(classNames, title, subtitle, representedObject, options) |
| { |
| super("", representedObject, options); |
| |
| this.classNames = classNames; |
| |
| this._tooltipHandledSeparately = false; |
| this._mainTitle = title || ""; |
| this._subtitle = subtitle || ""; |
| this._status = ""; |
| } |
| |
| // Public |
| |
| get element() |
| { |
| return this._listItemNode; |
| } |
| |
| get iconElement() |
| { |
| this._createElementsIfNeeded(); |
| return this._iconElement; |
| } |
| |
| get statusElement() |
| { |
| return this._statusElement; |
| } |
| |
| get titlesElement() |
| { |
| this._createElementsIfNeeded(); |
| return this._titlesElement; |
| } |
| |
| get mainTitleElement() |
| { |
| this._createElementsIfNeeded(); |
| return this._mainTitleElement; |
| } |
| |
| get subtitleElement() |
| { |
| this._createElementsIfNeeded(); |
| this._createSubtitleElementIfNeeded(); |
| return this._subtitleElement; |
| } |
| |
| get classNames() |
| { |
| return this._classNames; |
| } |
| |
| set classNames(x) |
| { |
| x = x || []; |
| |
| if (typeof x === "string") |
| x = [x]; |
| |
| if (Array.shallowEqual(this._classNames, x)) |
| return; |
| |
| if (this._listItemNode && this._classNames) |
| this._listItemNode.classList.remove(...this._classNames); |
| |
| this._classNames = x; |
| |
| if (this._listItemNode) |
| this._listItemNode.classList.add(...this._classNames); |
| } |
| |
| addClassName(className) |
| { |
| if (this._classNames.includes(className)) |
| return; |
| |
| this._classNames.push(className); |
| |
| if (this._listItemNode) |
| this._listItemNode.classList.add(className); |
| } |
| |
| removeClassName(className) |
| { |
| if (!this._classNames.includes(className)) |
| return; |
| |
| this._classNames.remove(className); |
| |
| if (this._listItemNode) |
| this._listItemNode.classList.remove(className); |
| } |
| |
| get mainTitle() |
| { |
| return this._mainTitle; |
| } |
| |
| set mainTitle(x) |
| { |
| x = x || ""; |
| |
| if (this._mainTitle === x) |
| return; |
| |
| this._mainTitle = x; |
| this._updateTitleElements(); |
| this.didChange(); |
| this.dispatchEventToListeners(WI.GeneralTreeElement.Event.MainTitleDidChange); |
| } |
| |
| get subtitle() |
| { |
| return this._subtitle; |
| } |
| |
| set subtitle(x) |
| { |
| x = x || ""; |
| |
| if (this._subtitle === x) |
| return; |
| |
| this._subtitle = x; |
| this._updateTitleElements(); |
| this.didChange(); |
| } |
| |
| get status() |
| { |
| return this._status; |
| } |
| |
| set status(x) |
| { |
| x = x || ""; |
| |
| if (this._status === x) |
| return; |
| |
| if (!this._statusElement) { |
| this._statusElement = document.createElement("div"); |
| this._statusElement.className = WI.GeneralTreeElement.StatusElementStyleClassName; |
| } |
| |
| this._status = x; |
| this._updateStatusElement(); |
| } |
| |
| get filterableData() |
| { |
| return {text: [this.mainTitle, this.subtitle]}; |
| } |
| |
| get tooltipHandledSeparately() |
| { |
| return this._tooltipHandledSeparately; |
| } |
| |
| set tooltipHandledSeparately(x) |
| { |
| this._tooltipHandledSeparately = !!x; |
| } |
| |
| createFoldersAsNeededForSubpath(subpath, comparator) |
| { |
| if (!subpath) |
| return this; |
| |
| let components = subpath.split("/"); |
| if (components.length === 1) |
| return this; |
| |
| if (!this._subpathFolderTreeElementMap) |
| this._subpathFolderTreeElementMap = new Map; |
| |
| let currentPath = ""; |
| let currentFolderTreeElement = this; |
| |
| for (let component of components) { |
| if (component === components.lastValue) |
| break; |
| |
| if (currentPath) |
| currentPath += "/"; |
| currentPath += component; |
| |
| let cachedFolder = this._subpathFolderTreeElementMap.get(currentPath); |
| if (cachedFolder) { |
| currentFolderTreeElement = cachedFolder; |
| continue; |
| } |
| |
| let newFolder = new WI.FolderTreeElement(component); |
| this._subpathFolderTreeElementMap.set(currentPath, newFolder); |
| |
| let index = insertionIndexForObjectInListSortedByFunction(newFolder, currentFolderTreeElement.children, comparator || WI.ResourceTreeElement.compareFolderAndResourceTreeElements); |
| currentFolderTreeElement.insertChild(newFolder, index); |
| currentFolderTreeElement = newFolder; |
| } |
| |
| return currentFolderTreeElement; |
| } |
| |
| // Overrides from TreeElement (Private) |
| |
| isEventWithinDisclosureTriangle(event) |
| { |
| return event.target === this._disclosureButton; |
| } |
| |
| onattach() |
| { |
| this._createElementsIfNeeded(); |
| this._updateTitleElements(); |
| |
| this._listItemNode.classList.add("item"); |
| |
| if (this._classNames) |
| this._listItemNode.classList.add(...this._classNames); |
| |
| this._listItemNode.appendChild(this._disclosureButton); |
| this._listItemNode.appendChild(this._iconElement); |
| if (this._statusElement) |
| this._listItemNode.appendChild(this._statusElement); |
| this._listItemNode.appendChild(this._titlesElement); |
| } |
| |
| ondetach() |
| { |
| // Overridden by subclasses. |
| } |
| |
| onreveal() |
| { |
| if (this._listItemNode) |
| this._listItemNode.scrollIntoViewIfNeeded(false); |
| } |
| |
| // Protected |
| |
| callFirstAncestorFunction(functionName, args) |
| { |
| // Call the first ancestor that implements a function named functionName (if any). |
| var currentNode = this.parent; |
| while (currentNode) { |
| if (typeof currentNode[functionName] === "function") { |
| currentNode[functionName].apply(currentNode, args); |
| break; |
| } |
| |
| currentNode = currentNode.parent; |
| } |
| } |
| |
| customTitleTooltip() |
| { |
| // Implemented by subclasses. |
| } |
| |
| // Private |
| |
| _createElementsIfNeeded() |
| { |
| if (this._createdElements) |
| return; |
| |
| this._disclosureButton = document.createElement("button"); |
| this._disclosureButton.className = WI.GeneralTreeElement.DisclosureButtonStyleClassName; |
| |
| // Don't allow the disclosure button to be keyboard focusable. The TreeOutline is focusable and has |
| // its own keybindings for toggling expand and collapse. |
| this._disclosureButton.tabIndex = -1; |
| |
| this._iconElement = document.createElement("img"); |
| this._iconElement.className = WI.GeneralTreeElement.IconElementStyleClassName; |
| |
| this._titlesElement = document.createElement("div"); |
| this._titlesElement.className = WI.GeneralTreeElement.TitlesElementStyleClassName; |
| |
| this._mainTitleElement = document.createElement("span"); |
| this._mainTitleElement.className = WI.GeneralTreeElement.MainTitleElementStyleClassName; |
| this._titlesElement.appendChild(this._mainTitleElement); |
| |
| this._createdElements = true; |
| } |
| |
| _createSubtitleElementIfNeeded() |
| { |
| if (this._subtitleElement) |
| return; |
| |
| this._subtitleElement = document.createElement("span"); |
| this._subtitleElement.className = WI.GeneralTreeElement.SubtitleElementStyleClassName; |
| this._titlesElement.appendChild(this._subtitleElement); |
| } |
| |
| _updateTitleElements() |
| { |
| if (!this._createdElements) |
| return; |
| |
| if (typeof this._mainTitle === "string") { |
| if (this._mainTitleElement.textContent !== this._mainTitle) |
| this._mainTitleElement.textContent = this._mainTitle; |
| } else if (this._mainTitle instanceof Node) { |
| this._mainTitleElement.removeChildren(); |
| this._mainTitleElement.appendChild(this._mainTitle); |
| if (this._mainTitle instanceof DocumentFragment) |
| this._mainTitle = this._mainTitleElement.textContent; |
| } |
| |
| if (typeof this._subtitle === "string" && this._subtitle) { |
| this._createSubtitleElementIfNeeded(); |
| if (this._subtitleElement.textContent !== this._subtitle) |
| this._subtitleElement.textContent = this._subtitle; |
| this._titlesElement.classList.remove(WI.GeneralTreeElement.NoSubtitleStyleClassName); |
| } else if (this._subtitle instanceof Node) { |
| this._createSubtitleElementIfNeeded(); |
| this._subtitleElement.removeChildren(); |
| this._subtitleElement.appendChild(this._subtitle); |
| if (this._subtitle instanceof DocumentFragment) |
| this._subtitle = this._subtitleElement.textContent; |
| this._titlesElement.classList.remove(WI.GeneralTreeElement.NoSubtitleStyleClassName); |
| } else { |
| if (this._subtitleElement) |
| this._subtitleElement.textContent = ""; |
| this._titlesElement.classList.add(WI.GeneralTreeElement.NoSubtitleStyleClassName); |
| } |
| |
| // Set a default tooltip if there isn't a custom one already assigned. |
| if (!this.tooltip && !this._tooltipHandledSeparately) |
| this._updateTitleTooltip(); |
| } |
| |
| _updateTitleTooltip() |
| { |
| console.assert(this._listItemNode); |
| if (!this._listItemNode) |
| return; |
| |
| let tooltip = this.customTitleTooltip(); |
| if (!tooltip) { |
| // Get the textContent for the elements since they can contain other nodes, |
| // and the tool tip only cares about the text. |
| let mainTitleText = this._mainTitleElement.textContent; |
| let subtitleText = this._subtitleElement ? this._subtitleElement.textContent : ""; |
| let large = this.treeOutline && this.treeOutline.large; |
| if (mainTitleText && subtitleText) |
| tooltip = mainTitleText + (large ? "\n" : " \u2014 ") + subtitleText; |
| else if (mainTitleText) |
| tooltip = mainTitleText; |
| else |
| tooltip = subtitleText; |
| } |
| |
| this._listItemNode.title = tooltip; |
| } |
| |
| _updateStatusElement() |
| { |
| if (!this._statusElement) |
| return; |
| |
| if (!this._statusElement.parentNode && this._listItemNode) |
| this._listItemNode.insertBefore(this._statusElement, this._titlesElement); |
| |
| if (this._status instanceof Node) { |
| this._statusElement.removeChildren(); |
| this._statusElement.appendChild(this._status); |
| } else |
| this._statusElement.textContent = this._status; |
| } |
| }; |
| |
| WI.GeneralTreeElement.DisclosureButtonStyleClassName = "disclosure-button"; |
| WI.GeneralTreeElement.IconElementStyleClassName = "icon"; |
| WI.GeneralTreeElement.StatusElementStyleClassName = "status"; |
| WI.GeneralTreeElement.TitlesElementStyleClassName = "titles"; |
| WI.GeneralTreeElement.MainTitleElementStyleClassName = "title"; |
| WI.GeneralTreeElement.SubtitleElementStyleClassName = "subtitle"; |
| WI.GeneralTreeElement.NoSubtitleStyleClassName = "no-subtitle"; |
| |
| WI.GeneralTreeElement.Event = { |
| MainTitleDidChange: "general-tree-element-main-title-did-change" |
| }; |