import * as LogManager from 'aurelia-logging'; import {metadata} from 'aurelia-metadata'; import {ViewEngine} from './view-engine'; import {validateBehaviorName} from './view-resources'; import {BindableProperty} from './bindable-property'; import {ElementConfigResource} from './element-config'; import {RelativeViewStrategy, NoViewStrategy, InlineViewStrategy, ViewStrategyDependencyConfig} from './view-strategy'; import {ViewLocator} from './view-locator'; import {HtmlBehaviorResource} from './html-behavior'; import { ProcessContentCallback } from './type-extension'; /** * Decorator: Specifies a resource instance that describes the decorated class. * @param instanceOrConfig The resource instance. */ export function resource(instanceOrConfig: string | object): any { return function(target) { let isConfig = typeof instanceOrConfig === 'string' || Object.getPrototypeOf(instanceOrConfig) === Object.prototype; if (isConfig) { target.$resource = instanceOrConfig; } else { metadata.define(metadata.resource, instanceOrConfig, target); } }; } /** * Decorator: Specifies a custom HtmlBehaviorResource instance or an object that overrides various implementation details of the default HtmlBehaviorResource. * @param override The customized HtmlBehaviorResource or an object to override the default with. */ export function behavior(override: HtmlBehaviorResource | Object): any { return function(target) { if (override instanceof HtmlBehaviorResource) { metadata.define(metadata.resource, override, target); } else { let r = metadata.getOrCreateOwn(metadata.resource, HtmlBehaviorResource, target); Object.assign(r, override); } }; } /** * Decorator: Indicates that the decorated class is a custom element. * @param name The name of the custom element. */ export function customElement(name: string): any { return function(target) { let r = metadata.getOrCreateOwn(metadata.resource, HtmlBehaviorResource, target) as HtmlBehaviorResource; r.elementName = validateBehaviorName(name, 'custom element'); }; } /** * Decorator: Indicates that the decorated class is a custom attribute. * @param name The name of the custom attribute. * @param defaultBindingMode The default binding mode to use when the attribute is bound with .bind. * @param aliases The array of aliases to associate to the custom attribute. */ export function customAttribute(name: string, defaultBindingMode?: number, aliases?: string[]): any { return function(target) { let r = metadata.getOrCreateOwn(metadata.resource, HtmlBehaviorResource, target) as HtmlBehaviorResource; r.attributeName = validateBehaviorName(name, 'custom attribute'); r.attributeDefaultBindingMode = defaultBindingMode; r.aliases = aliases as any; }; } /** * Decorator: Applied to custom attributes. Indicates that whatever element the * attribute is placed on should be converted into a template and that this * attribute controls the instantiation of the template. */ export function templateController(target?): any { let deco = function(t) { let r = metadata.getOrCreateOwn(metadata.resource, HtmlBehaviorResource, t) as HtmlBehaviorResource; r.liftsContent = true; }; return target ? deco(target) : deco; } /** * Decorator: Specifies that a property is bindable through HTML. * @param nameOrConfigOrTarget The name of the property, or a configuration object. */ export function bindable(nameOrConfigOrTarget?: string | Object, key?, descriptor?): any { let deco = function(target, key2, descriptor2) { let actualTarget = key2 ? target.constructor : target; //is it on a property or a class? let r = metadata.getOrCreateOwn(metadata.resource, HtmlBehaviorResource, actualTarget); let prop; if (key2) { //is it on a property or a class? nameOrConfigOrTarget = nameOrConfigOrTarget || {}; (nameOrConfigOrTarget as any).name = key2; } prop = new BindableProperty(nameOrConfigOrTarget); return prop.registerWith(actualTarget, r, descriptor2); }; if (!nameOrConfigOrTarget) { //placed on property initializer with parens return deco; } if (key) { //placed on a property initializer without parens let target = nameOrConfigOrTarget; nameOrConfigOrTarget = null; return deco(target, key, descriptor); } return deco; //placed on a class } /** * Decorator: Specifies that the decorated custom attribute has options that * are dynamic, based on their presence in HTML and not statically known. */ export function dynamicOptions(target?): any { let deco = function(t) { let r = metadata.getOrCreateOwn(metadata.resource, HtmlBehaviorResource, t) as HtmlBehaviorResource; r.hasDynamicOptions = true; }; return target ? deco(target) : deco; } const defaultShadowDOMOptions = { mode: 'open' }; /** * Decorator: Indicates that the custom element should render its view in Shadow * DOM. This decorator may change slightly when Aurelia updates to Shadow DOM v1. */ export function useShadowDOM(targetOrOptions?): any { let options = typeof targetOrOptions === 'function' || !targetOrOptions ? defaultShadowDOMOptions : targetOrOptions; let deco = function(t) { let r = metadata.getOrCreateOwn(metadata.resource, HtmlBehaviorResource, t) as HtmlBehaviorResource; r.targetShadowDOM = true; r.shadowDOMOptions = options; }; return typeof targetOrOptions === 'function' ? deco(targetOrOptions) : deco; } /** * Decorator: Enables custom processing of the attributes on an element before the framework inspects them. * @param processor Pass a function which can provide custom processing of the content. */ export function processAttributes(processor: Function): any { return function(t) { let r = metadata.getOrCreateOwn(metadata.resource, HtmlBehaviorResource, t) as HtmlBehaviorResource; r.processAttributes = function(compiler, resources, node, attributes, elementInstruction) { try { processor(compiler, resources, node, attributes, elementInstruction); } catch (error) { LogManager.getLogger('templating').error(error); } }; }; } function doNotProcessContent() { return false; } /** * Decorator: Enables custom processing of the content that is places inside the * custom element by its consumer. * @param processor Pass a boolean to direct the template compiler to not process * the content placed inside this element. Alternatively, pass a function which * can provide custom processing of the content. This function should then return * a boolean indicating whether the compiler should also process the content. */ export function processContent(processor: boolean | Function): any { return function(t) { let r = metadata.getOrCreateOwn(metadata.resource, HtmlBehaviorResource, t) as HtmlBehaviorResource; r.processContent = processor ? function(compiler, resources, node, instruction) { try { return (processor as ProcessContentCallback)(compiler, resources, node, instruction); } catch (error) { LogManager.getLogger('templating').error(error); return false; } } : doNotProcessContent; }; } /** * Decorator: Indicates that the custom element should be rendered without its * element container. */ export function containerless(target?): any { let deco = function(t) { let r = metadata.getOrCreateOwn(metadata.resource, HtmlBehaviorResource, t) as HtmlBehaviorResource; r.containerless = true; }; return target ? deco(target) : deco; } /** * Decorator: Associates a custom view strategy with the component. * @param strategy The view strategy instance. */ export function useViewStrategy(strategy: Object): any { return function(target) { metadata.define(ViewLocator.viewStrategyMetadataKey, strategy, target); }; } /** * Decorator: Provides a relative path to a view for the component. * @param path The path to the view. */ export function useView(path: string): any { return useViewStrategy(new RelativeViewStrategy(path)); } /** * Decorator: Provides a view template, directly inline, for the component. Be * sure to wrap the markup in a template element. * @param markup The markup for the view. * @param dependencies A list of dependencies that the template has. * @param dependencyBaseUrl A base url from which the dependencies will be loaded. */ export function inlineView(markup:string, dependencies?:Array, dependencyBaseUrl?:string): any { return useViewStrategy(new InlineViewStrategy(markup, dependencies, dependencyBaseUrl)); } /** * Decorator: Indicates that the component has no view. */ export function noView(targetOrDependencies?:Function|Array, dependencyBaseUrl?:string): any { let target; let dependencies; if (typeof targetOrDependencies === 'function') { target = targetOrDependencies; } else { dependencies = targetOrDependencies; target = undefined; } let deco = function(t) { metadata.define(ViewLocator.viewStrategyMetadataKey, new NoViewStrategy(dependencies, dependencyBaseUrl), t); }; return target ? deco(target) : deco; } export type IStaticViewCustomElement = Function & { $view?: string | IStaticViewStrategyConfig; } export interface IStaticViewStrategyConfig { template: string | HTMLTemplateElement; dependencies?: Function[] | { (): (Promise> | Function)[] } } /** * Decorator: Indicates that the element use static view */ export function view(templateOrConfig:string|HTMLTemplateElement|IStaticViewStrategyConfig): any { return function(target) { target.$view = templateOrConfig; }; } /** * Decorator: Indicates that the decorated class provides element configuration * to the EventManager for one or more Web Components. */ export function elementConfig(target?): any { let deco = function(t) { metadata.define(metadata.resource, new ElementConfigResource(), t); }; return target ? deco(target) : deco; } /** * Decorator: Provides the ability to add resources to the related View * Same as: * @param resources Either: strings with moduleIds, Objects with 'src' and optionally 'as' properties or one of the classes of the module to be included. */ export function viewResources(...resources) { // eslint-disable-line return function(target) { metadata.define(ViewEngine.viewModelRequireMetadataKey, resources, target); }; }