[IMP] app: add global values at compile time

This commit, add a new configuration on the App: globalValues.
It's a global object of elements available at compilations.

For instance:
```js
    const app = new App(SomeComponent, {
      globalValues: {
        plop: (string: any) => {
          steps.push(string);
        },
      },
    });
```

The plop function will be available at the compilation, so it can be
used on the templates :
```xml
<div t-on-click="() => __globals__.plop('click')" class="my-div"/>
```
This commit is contained in:
Jorge Pinna Puissant
2024-11-12 16:37:03 +01:00
committed by Géry Debongnie
parent 7e687234bf
commit 6b2486473f
8 changed files with 50 additions and 3 deletions
+1
View File
@@ -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). 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 - **`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)). 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 ## `mount` helper
+4
View File
@@ -43,6 +43,7 @@ export interface Config {
export interface CodeGenOptions extends Config { export interface CodeGenOptions extends Config {
hasSafeContext?: boolean; hasSafeContext?: boolean;
name?: string; name?: string;
hasGlobalValues: boolean;
} }
// using a non-html document so that <inner/outer>HTML serializes as XML instead // using a non-html document so that <inner/outer>HTML serializes as XML instead
@@ -286,6 +287,9 @@ export class CodeGenerator {
this.dev = options.dev || false; this.dev = options.dev || false;
this.ast = ast; this.ast = ast;
this.templateName = options.name; this.templateName = options.name;
if (options.hasGlobalValues) {
this.helpers.add("__globals__");
}
} }
generateCode(): string { generateCode(): string {
+4 -1
View File
@@ -12,10 +12,13 @@ export type TemplateFunction = (app: TemplateSet, bdom: any, helpers: any) => Te
interface CompileOptions extends Config { interface CompileOptions extends Config {
name?: string; name?: string;
customDirectives?: customDirectives; customDirectives?: customDirectives;
hasGlobalValues: boolean;
} }
export function compile( export function compile(
template: string | Element, template: string | Element,
options: CompileOptions = {} options: CompileOptions = {
hasGlobalValues: false,
}
): TemplateFunction { ): TemplateFunction {
// parsing // parsing
const ast = parse(template, options.customDirectives); const ast = parse(template, options.customDirectives);
+1 -1
View File
@@ -28,7 +28,7 @@ import { OwlError } from "../common/owl_error";
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
const RESERVED_WORDS = 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(
"," ","
); );
+1
View File
@@ -13,5 +13,6 @@ TemplateSet.prototype._compileTemplate = function _compileTemplate(
translateFn: this.translateFn, translateFn: this.translateFn,
translatableAttributes: this.translatableAttributes, translatableAttributes: this.translatableAttributes,
customDirectives: this.customDirectives, customDirectives: this.customDirectives,
hasGlobalValues: this.hasGlobalValues,
}); });
}; };
+6 -1
View File
@@ -16,6 +16,7 @@ export interface TemplateSetConfig {
templates?: string | Document | Record<string, string>; templates?: string | Document | Record<string, string>;
getTemplate?: (s: string) => Element | Function | string | void; getTemplate?: (s: string) => Element | Function | string | void;
customDirectives?: customDirectives; customDirectives?: customDirectives;
globalValues?: object;
} }
export class TemplateSet { export class TemplateSet {
@@ -30,6 +31,8 @@ export class TemplateSet {
translatableAttributes?: string[]; translatableAttributes?: string[];
Portal = Portal; Portal = Portal;
customDirectives: customDirectives; customDirectives: customDirectives;
runtimeUtils: object;
hasGlobalValues: boolean;
constructor(config: TemplateSetConfig = {}) { constructor(config: TemplateSetConfig = {}) {
this.dev = config.dev || false; this.dev = config.dev || false;
@@ -46,6 +49,8 @@ export class TemplateSet {
} }
this.getRawTemplate = config.getTemplate; this.getRawTemplate = config.getTemplate;
this.customDirectives = config.customDirectives || {}; this.customDirectives = config.customDirectives || {};
this.runtimeUtils = { ...helpers, __globals__: config.globalValues || {} };
this.hasGlobalValues = Boolean(config.globalValues && Object.keys(config.globalValues).length);
} }
addTemplate(name: string, template: string | Element) { addTemplate(name: string, template: string | Element) {
@@ -101,7 +106,7 @@ export class TemplateSet {
this.templates[name] = function (context, parent) { this.templates[name] = function (context, parent) {
return templates[name].call(this, context, parent); return templates[name].call(this, context, parent);
}; };
const template = templateFn(this, bdom, helpers); const template = templateFn(this, bdom, this.runtimeUtils);
this.templates[name] = template; this.templates[name] = template;
} }
return this.templates[name]; return this.templates[name];
+15
View File
@@ -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(\`<div class=\\"my-div\\" block-handler-0=\\"click\\"/>\`);
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`] = ` exports[`app can call processTask twice in a row without crashing 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
+18
View File
@@ -201,4 +201,22 @@ describe("app", () => {
await app.mount(fixture); await app.mount(fixture);
expect(fixture.innerHTML).toBe("parent<div></div>"); expect(fixture.innerHTML).toBe("parent<div></div>");
}); });
test("can add functions to the bdom", async () => {
const steps: string[] = [];
class SomeComponent extends Component {
static template = xml`<div t-on-click="() => __globals__.plop('click')" class="my-div"/>`;
}
const app = new App(SomeComponent, {
globalValues: {
plop: (string: any) => {
steps.push(string);
},
},
});
await app.mount(fixture);
expect(fixture.innerHTML).toBe(`<div class="my-div"></div>`);
fixture.querySelector("div")!.click();
expect(steps).toEqual(["click"]);
});
}); });