diff --git a/doc/reference/app.md b/doc/reference/app.md index af5337fcf..67395eace 100644 --- a/doc/reference/app.md +++ b/doc/reference/app.md @@ -68,6 +68,7 @@ The `config` object is an object with some of the following keys: whenever it encounters a component that does not provide a [static props description](props.md#props-validation). - **`customDirectives (object)`**: if given, the corresponding function on the object will be called on the template custom directives: `t-custom-*` (see [Custom Directives](templates.md#custom-directives)). +- **`globalValues (object)`**: Global object of elements available at compilations. ## `mount` helper diff --git a/src/compiler/code_generator.ts b/src/compiler/code_generator.ts index 63e40f02a..7fc003690 100644 --- a/src/compiler/code_generator.ts +++ b/src/compiler/code_generator.ts @@ -43,6 +43,7 @@ export interface Config { export interface CodeGenOptions extends Config { hasSafeContext?: boolean; name?: string; + globalValues?: object; } // using a non-html document so that HTML serializes as XML instead @@ -286,6 +287,9 @@ export class CodeGenerator { this.dev = options.dev || false; this.ast = ast; this.templateName = options.name; + if (options.globalValues && Object.keys(options.globalValues).length) { + this.helpers.add("__globals__"); + } } generateCode(): string { diff --git a/src/compiler/index.ts b/src/compiler/index.ts index ba513de93..364570613 100644 --- a/src/compiler/index.ts +++ b/src/compiler/index.ts @@ -12,6 +12,7 @@ export type TemplateFunction = (app: TemplateSet, bdom: any, helpers: any) => Te interface CompileOptions extends Config { name?: string; customDirectives?: customDirectives; + globalValues?: object; } export function compile( template: string | Element, diff --git a/src/compiler/inline_expressions.ts b/src/compiler/inline_expressions.ts index e67b769ea..666b7f57e 100644 --- a/src/compiler/inline_expressions.ts +++ b/src/compiler/inline_expressions.ts @@ -28,7 +28,7 @@ import { OwlError } from "../common/owl_error"; //------------------------------------------------------------------------------ const RESERVED_WORDS = - "true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,eval,void,Math,RegExp,Array,Object,Date".split( + "true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,eval,void,Math,RegExp,Array,Object,Date,__globals__".split( "," ); diff --git a/src/index.ts b/src/index.ts index 3e4063f58..090a158aa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,5 +13,6 @@ TemplateSet.prototype._compileTemplate = function _compileTemplate( translateFn: this.translateFn, translatableAttributes: this.translatableAttributes, customDirectives: this.customDirectives, + globalValues: this.globalValues, }); }; diff --git a/src/runtime/template_set.ts b/src/runtime/template_set.ts index 3a13c365b..d33a6ba88 100644 --- a/src/runtime/template_set.ts +++ b/src/runtime/template_set.ts @@ -16,6 +16,7 @@ export interface TemplateSetConfig { templates?: string | Document | Record; getTemplate?: (s: string) => Element | Function | string | void; customDirectives?: customDirectives; + globalValues?: object; } export class TemplateSet { @@ -30,6 +31,7 @@ export class TemplateSet { translatableAttributes?: string[]; Portal = Portal; customDirectives: customDirectives; + globalValues: object; constructor(config: TemplateSetConfig = {}) { this.dev = config.dev || false; @@ -46,6 +48,7 @@ export class TemplateSet { } this.getRawTemplate = config.getTemplate; this.customDirectives = config.customDirectives || {}; + this.globalValues = config.globalValues || {}; } addTemplate(name: string, template: string | Element) { @@ -101,7 +104,8 @@ export class TemplateSet { this.templates[name] = function (context, parent) { return templates[name].call(this, context, parent); }; - const template = templateFn(this, bdom, helpers); + const runtimeUtils = { ...helpers, __globals__: this.globalValues }; + const template = templateFn(this, bdom, runtimeUtils); this.templates[name] = template; } return this.templates[name]; diff --git a/tests/app/__snapshots__/app.test.ts.snap b/tests/app/__snapshots__/app.test.ts.snap index 21d48b9c9..3e67b01a1 100644 --- a/tests/app/__snapshots__/app.test.ts.snap +++ b/tests/app/__snapshots__/app.test.ts.snap @@ -43,6 +43,21 @@ exports[`app app: clear scheduler tasks and destroy cancelled nodes immediately }" `; +exports[`app can add functions to the bdom 1`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + let { __globals__ } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let hdlr1 = [()=>__globals__.plop('click'), ctx]; + return block1([hdlr1]); + } +}" +`; + exports[`app can call processTask twice in a row without crashing 1`] = ` "function anonymous(app, bdom, helpers ) { diff --git a/tests/app/app.test.ts b/tests/app/app.test.ts index 0635eef9b..8820cf125 100644 --- a/tests/app/app.test.ts +++ b/tests/app/app.test.ts @@ -201,4 +201,22 @@ describe("app", () => { await app.mount(fixture); expect(fixture.innerHTML).toBe("parent
"); }); + + test("can add functions to the bdom", async () => { + const steps: string[] = []; + class SomeComponent extends Component { + static template = xml`
`; + } + const app = new App(SomeComponent, { + globalValues: { + plop: (string: any) => { + steps.push(string); + }, + }, + }); + await app.mount(fixture); + expect(fixture.innerHTML).toBe(`
`); + fixture.querySelector("div")!.click(); + expect(steps).toEqual(["click"]); + }); });