| /** |
| * @license |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| import {PolymerElement} from '@polymer/polymer/polymer-element.js'; |
| |
| /** @constructor */ |
| export function GrDomHooksManager(plugin) { |
| this._plugin = plugin; |
| this._hooks = {}; |
| } |
| |
| GrDomHooksManager.prototype._getHookName = function(endpointName, |
| opt_moduleName) { |
| if (opt_moduleName) { |
| return endpointName + ' ' + opt_moduleName; |
| } else { |
| // lowercase in case plugin's name contains uppercase letters |
| // TODO: this still can not prevent if plugin has invalid char |
| // other than uppercase, but is the first step |
| // https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name |
| const pluginName = this._plugin.getPluginName() || 'unknown_plugin'; |
| return pluginName.toLowerCase() + '-autogenerated-' + endpointName; |
| } |
| }; |
| |
| GrDomHooksManager.prototype.getDomHook = function(endpointName, |
| opt_moduleName) { |
| const hookName = this._getHookName(endpointName, opt_moduleName); |
| if (!this._hooks[hookName]) { |
| this._hooks[hookName] = new GrDomHook(hookName, opt_moduleName); |
| } |
| return this._hooks[hookName]; |
| }; |
| |
| /** @constructor */ |
| export function GrDomHook(hookName, opt_moduleName) { |
| this._instances = []; |
| this._attachCallbacks = []; |
| this._detachCallbacks = []; |
| if (opt_moduleName) { |
| this._moduleName = opt_moduleName; |
| } else { |
| this._moduleName = hookName; |
| this._createPlaceholder(hookName); |
| } |
| } |
| |
| GrDomHook.prototype._createPlaceholder = function(hookName) { |
| class HookPlaceholder extends PolymerElement { |
| static get is() { return hookName; } |
| |
| static get properties() { |
| return { |
| plugin: Object, |
| content: Object, |
| }; |
| } |
| } |
| |
| customElements.define(HookPlaceholder.is, HookPlaceholder); |
| }; |
| |
| GrDomHook.prototype.handleInstanceDetached = function(instance) { |
| const index = this._instances.indexOf(instance); |
| if (index !== -1) { |
| this._instances.splice(index, 1); |
| } |
| this._detachCallbacks.forEach(callback => callback(instance)); |
| }; |
| |
| GrDomHook.prototype.handleInstanceAttached = function(instance) { |
| this._instances.push(instance); |
| this._attachCallbacks.forEach(callback => callback(instance)); |
| }; |
| |
| /** |
| * Get instance of last DOM hook element attached into the endpoint. |
| * Returns a Promise, that's resolved when attachment is done. |
| * |
| * @return {!Promise<!Element>} |
| */ |
| GrDomHook.prototype.getLastAttached = function() { |
| if (this._instances.length) { |
| return Promise.resolve(this._instances.slice(-1)[0]); |
| } |
| if (!this._lastAttachedPromise) { |
| let resolve; |
| const promise = new Promise(r => resolve = r); |
| this._attachCallbacks.push(resolve); |
| this._lastAttachedPromise = promise.then(element => { |
| this._lastAttachedPromise = null; |
| const index = this._attachCallbacks.indexOf(resolve); |
| if (index !== -1) { |
| this._attachCallbacks.splice(index, 1); |
| } |
| return element; |
| }); |
| } |
| return this._lastAttachedPromise; |
| }; |
| |
| /** |
| * Get all DOM hook elements. |
| */ |
| GrDomHook.prototype.getAllAttached = function() { |
| return this._instances; |
| }; |
| |
| /** |
| * Install a new callback to invoke when a new instance of DOM hook element |
| * is attached. |
| * |
| * @param {function(Element)} callback |
| */ |
| GrDomHook.prototype.onAttached = function(callback) { |
| this._attachCallbacks.push(callback); |
| return this; |
| }; |
| |
| /** |
| * Install a new callback to invoke when an instance of DOM hook element |
| * is detached. |
| * |
| * @param {function(Element)} callback |
| */ |
| GrDomHook.prototype.onDetached = function(callback) { |
| this._detachCallbacks.push(callback); |
| return this; |
| }; |
| |
| /** |
| * Name of DOM hook element that will be installed into the endpoint. |
| */ |
| GrDomHook.prototype.getModuleName = function() { |
| return this._moduleName; |
| }; |
| |
| GrDomHook.prototype.getPublicAPI = function() { |
| const result = {}; |
| const exposedMethods = [ |
| 'onAttached', |
| 'onDetached', |
| 'getLastAttached', |
| 'getAllAttached', |
| 'getModuleName', |
| ]; |
| for (const p of exposedMethods) { |
| result[p] = this[p].bind(this); |
| } |
| return result; |
| }; |