@adpt/core
Version:
AdaptJS core library
214 lines • 8.96 kB
JavaScript
;
/*
* Copyright 2019 Unbounded Systems, LLC
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const dom_serialize_1 = require("../dom_serialize");
const jsx_1 = require("../jsx");
const use_async_1 = require("./use_async");
// Function implementation
function useMethod(hand, initialOrMethod, method, ...args) {
const mName = method || initialOrMethod;
const initial = method ? initialOrMethod : undefined;
return use_async_1.useAsync(async () => {
if (hand == null)
return initial;
return callInstanceMethod(hand, initial, mName, ...args);
}, initial);
}
exports.useMethod = useMethod;
function hasInstanceMethod(name, skip) {
return (el) => {
if (el === skip)
return false;
if (!jsx_1.isMountedElement(el))
throw new Error(`Element is not an ElementImpl`);
const inst = el.instance;
return lodash_1.default.isFunction(inst[name]);
};
}
exports.hasInstanceMethod = hasInstanceMethod;
function notReplacedByStyle() {
return (el) => {
if (!jsx_1.isMountedElement(el))
throw new Error(`Element is not an ElementImpl`);
const succ = el.buildData.successor;
if (succ == null)
return true;
if (!jsx_1.isApplyStyle(succ))
return true;
return false;
};
}
exports.notReplacedByStyle = notReplacedByStyle;
function _callInstanceMethod(hand, def, methodName, args, options = {}) {
const method = getInstanceValue(hand, () => def, methodName, options);
if (!lodash_1.default.isFunction(method)) {
const mountedOrig = hand.associated ? hand.mountedOrig : null;
throw new Error(`${methodName} exists but is not a function on handle instance:\n` +
((mountedOrig != null) ? dom_serialize_1.serializeDom(mountedOrig) : `mountedOrig is ${mountedOrig}`));
}
return method(...args);
}
/**
* Search for the first built Element in the handle chain of `hand` and
* immediately execute the instance method `methodName` on that Element's
* instance.
*
* @remarks
* If an Element is found that satisfies the search, but method `methodName`
* does not exist on the Element's instance, an error is thrown.
*
* The exact check that is currently used when searching the handle chain is
* for mounted Elements that satisfy the predicate {@link notReplacedByStyle}.
* In practice, this only selects Elements that are both mounted and built.
*
* @returns The return value of the called instance method if `hand` is
* associated and there is an Element in the handle chain that has not been
* replaced by a style sheet rule. Otherwise, returns the default value `def`.
* @beta
*/
function callInstanceMethod(hand, def, methodName, ...args) {
return _callInstanceMethod(hand, def, methodName, args);
}
exports.callInstanceMethod = callInstanceMethod;
/**
* Search for the first built Element instance in the Handle chain of `hand` that
* implements method `methodName` and immediately execute it.
*
* @remarks
* The exact check that is currently used when searching the handle chain is
* mounted Elements that have an instance method `methodName`. Because only
* built Elements have an Element instance, this only selects Elements that
* are mounted and built and will not select Elements that have been replaced
* by a style sheet rule.
*
* @returns The return value of the called instance method if `hand` is
* associated and there is an Element in the handle chain that has method
* `methodName`. Otherwise, returns the default value `def`.
* @beta
*/
function callFirstInstanceWithMethod(hand, def, methodName, ...args) {
// Walk until we find an instance that has the requested method.
const options = { pred: hasInstanceMethod(methodName) };
return _callInstanceMethod(hand, def, methodName, args, options);
}
exports.callFirstInstanceWithMethod = callFirstInstanceWithMethod;
/**
* Starting with the successor of `hand`, search for a built Element instance in
* the handle chain that implements method `methodName` and immediately
* execute it.
*
* @remarks
* If `hand` is not associated with an Element, an error is thrown.
*
* The exact check that is currently used when searching the handle chain is
* mounted Elements that have an instance method `methodName`. Because only
* built Elements have an Element instance, this only selects Elements that
* are mounted and built and will not select Elements that have been replaced
* by a style sheet rule.
*
*
* @returns The return value of the called instance method if `hand` is
* associated and there is an Element in the handle chain (other than `hand`)
* that has method `methodName`. Otherwise, returns the default value `def`.
* @beta
*/
function callNextInstanceWithMethod(hand, def, methodName, ...args) {
if (!hand.associated) {
// tslint:disable-next-line: max-line-length
throw new Error(`Cannot find next instance when calling ${methodName}: handle is not associated with any element`);
}
// Skip hand.mountedOrig and start with its successor. Walk until we
// find an instance that has the requested method.
const options = { pred: hasInstanceMethod(methodName, hand.mountedOrig) };
return _callInstanceMethod(hand, def, methodName, args, options);
}
exports.callNextInstanceWithMethod = callNextInstanceWithMethod;
/**
* Starting with the successor of `hand`, search for a built Element instance in
* the handle chain that implements method `methodName` and immediately
* execute it.
*
* @remarks
* If `hand` is not associated with an Element, an error is thrown.
*
* The exact check that is currently used when searching the handle chain is
* mounted Elements that have an instance method `methodName`. Because only
* built Elements have an Element instance, this only selects Elements that
* are mounted and built and will not select Elements that have been replaced
* by a style sheet rule.
*
* @returns The return value of the called instance method if `hand` is
* associated and there is an Element in the handle chain (other than `hand`)
* that has method `methodName`. Otherwise, returns the default value `def`.
* @deprecated
* Renamed to {@link callNextInstanceWithMethod}.
*/
exports.callNextInstanceMethod = callNextInstanceWithMethod;
exports.defaultGetInstanceValueOptions = {
pred: notReplacedByStyle(),
throwOnNoElem: false
};
/**
* Get the value of a field on an element instance
*
* @beta
*/
function getInstanceValue(hand, def, field, optionsIn) {
const options = Object.assign({}, exports.defaultGetInstanceValueOptions, optionsIn);
const pred = options.pred;
if (!hand.associated) {
if (!options.throwOnNoElem)
return def;
throw new Error(`Cannot get instance field ${field}: Handle is not associated with element`);
}
const elem = hand.nextMounted(pred);
if (!elem) {
if (!options.throwOnNoElem)
return def;
throw new Error(`Cannot get instance field ${field}: Handle does not point to mounted element`);
}
if (!elem.instance) {
throw new Error(`Internal Error: Element is mounted but instance is ${elem.instance}`);
}
if (!(field in elem.instance)) {
throw new Error(`${field} does not exist on handle instance:\n` + dom_serialize_1.serializeDom(elem));
}
const val = elem.instance[field];
if (lodash_1.default.isFunction(val)) {
return val.bind(elem.instance);
}
return val;
}
exports.getInstanceValue = getInstanceValue;
/**
* Get the value of field from the instance referenced by handled instance.
*
* @remarks
* On first invocation, or if the handle is not associated with an element, or the field is not found,
* the value of `initial` will be returned. After the element referenced by handle has been instantiated,
* this hook will fetch the actual value of `field`, cause a rebuild, and then return that value
* on the next call of the hook.
*
* @beta
*/
function useInstanceValue(hand, initial, field) {
return use_async_1.useAsync(async () => getInstanceValue(hand, initial, field), initial);
}
exports.useInstanceValue = useInstanceValue;
//# sourceMappingURL=use_method.js.map