blob: b998733b455967d48538aecd5ddb7aea8421f767 [file] [log] [blame]
/**
* @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 '../../../scripts/bundled-polymer.js';
import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
const $_documentContainer = document.createElement('template');
$_documentContainer.innerHTML = `<dom-module id="gr-dom-hooks">
</dom-module>`;
document.head.appendChild($_documentContainer.content);
/** @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) {
Polymer({
is: hookName,
properties: {
plugin: Object,
content: Object,
},
});
};
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;
};