mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[REF] reorganize file structure
in order to separate compiler/ and runtime/ code
This commit is contained in:
committed by
Sam Degueldre
parent
e4b810c027
commit
22a79cdedd
@@ -0,0 +1,251 @@
|
||||
import { BDom, multi, text, toggler, createCatcher } from "./blockdom";
|
||||
import { Markup } from "./utils";
|
||||
import { html } from "./blockdom/index";
|
||||
import { isOptional, validateSchema } from "./validation";
|
||||
import type { ComponentConstructor } from "./component";
|
||||
import { markRaw } from "./reactivity";
|
||||
|
||||
/**
|
||||
* This file contains utility functions that will be injected in each template,
|
||||
* to perform various useful tasks in the compiled code.
|
||||
*/
|
||||
|
||||
function withDefault(value: any, defaultValue: any): any {
|
||||
return value === undefined || value === null || value === false ? defaultValue : value;
|
||||
}
|
||||
|
||||
function callSlot(
|
||||
ctx: any,
|
||||
parent: any,
|
||||
key: string,
|
||||
name: string,
|
||||
dynamic: boolean,
|
||||
extra: any,
|
||||
defaultContent?: (ctx: any, node: any, key: string) => BDom
|
||||
): BDom {
|
||||
key = key + "__slot_" + name;
|
||||
const slots = ctx.props.slots || {};
|
||||
const { __render, __ctx, __scope } = slots[name] || {};
|
||||
const slotScope = Object.create(__ctx || {});
|
||||
if (__scope) {
|
||||
slotScope[__scope] = extra;
|
||||
}
|
||||
const slotBDom = __render ? __render.call(__ctx.__owl__.component, slotScope, parent, key) : null;
|
||||
if (defaultContent) {
|
||||
let child1: BDom | undefined = undefined;
|
||||
let child2: BDom | undefined = undefined;
|
||||
if (slotBDom) {
|
||||
child1 = dynamic ? toggler(name, slotBDom) : slotBDom;
|
||||
} else {
|
||||
child2 = defaultContent.call(ctx.__owl__.component, ctx, parent, key);
|
||||
}
|
||||
return multi([child1, child2]);
|
||||
}
|
||||
return slotBDom || text("");
|
||||
}
|
||||
|
||||
function capture(ctx: any): any {
|
||||
const component = ctx.__owl__.component;
|
||||
const result = Object.create(component);
|
||||
for (let k in ctx) {
|
||||
result[k] = ctx[k];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function withKey(elem: any, k: string) {
|
||||
elem.key = k;
|
||||
return elem;
|
||||
}
|
||||
|
||||
function prepareList(collection: any): [any[], any[], number, any[]] {
|
||||
let keys: any[];
|
||||
let values: any[];
|
||||
|
||||
if (Array.isArray(collection)) {
|
||||
keys = collection;
|
||||
values = collection;
|
||||
} else if (collection) {
|
||||
values = Object.keys(collection);
|
||||
keys = Object.values(collection);
|
||||
} else {
|
||||
throw new Error("Invalid loop expression");
|
||||
}
|
||||
const n = values.length;
|
||||
return [keys, values, n, new Array(n)];
|
||||
}
|
||||
|
||||
const isBoundary = Symbol("isBoundary");
|
||||
|
||||
function setContextValue(ctx: { [key: string]: any }, key: string, value: any): void {
|
||||
const ctx0 = ctx;
|
||||
while (!ctx.hasOwnProperty(key) && !ctx.hasOwnProperty(isBoundary)) {
|
||||
const newCtx = ctx.__proto__;
|
||||
if (!newCtx) {
|
||||
ctx = ctx0;
|
||||
break;
|
||||
}
|
||||
ctx = newCtx;
|
||||
}
|
||||
ctx[key] = value;
|
||||
}
|
||||
|
||||
function toNumber(val: string): number | string {
|
||||
const n = parseFloat(val);
|
||||
return isNaN(n) ? val : n;
|
||||
}
|
||||
|
||||
function shallowEqual(l1: any[], l2: any[]): boolean {
|
||||
for (let i = 0, l = l1.length; i < l; i++) {
|
||||
if (l1[i] !== l2[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
class LazyValue {
|
||||
fn: any;
|
||||
ctx: any;
|
||||
node: any;
|
||||
constructor(fn: any, ctx: any, node: any) {
|
||||
this.fn = fn;
|
||||
this.ctx = capture(ctx);
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
evaluate(): any {
|
||||
return this.fn(this.ctx, this.node);
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.evaluate().toString();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Safely outputs `value` as a block depending on the nature of `value`
|
||||
*/
|
||||
export function safeOutput(value: any): ReturnType<typeof toggler> {
|
||||
if (!value) {
|
||||
return value;
|
||||
}
|
||||
let safeKey;
|
||||
let block;
|
||||
if (value instanceof Markup) {
|
||||
safeKey = `string_safe`;
|
||||
block = html(value as string);
|
||||
} else if (value instanceof LazyValue) {
|
||||
safeKey = `lazy_value`;
|
||||
block = value.evaluate();
|
||||
} else if (value instanceof String || typeof value === "string") {
|
||||
safeKey = "string_unsafe";
|
||||
block = text(value);
|
||||
} else {
|
||||
// Assuming it is a block
|
||||
safeKey = "block_safe";
|
||||
block = value;
|
||||
}
|
||||
return toggler(safeKey, block);
|
||||
}
|
||||
|
||||
let boundFunctions = new WeakMap();
|
||||
|
||||
function bind(ctx: any, fn: Function): Function {
|
||||
let component = ctx.__owl__.component;
|
||||
let boundFnMap = boundFunctions.get(component);
|
||||
if (!boundFnMap) {
|
||||
boundFnMap = new WeakMap();
|
||||
boundFunctions.set(component, boundFnMap);
|
||||
}
|
||||
let boundFn = boundFnMap.get(fn);
|
||||
if (!boundFn) {
|
||||
boundFn = fn.bind(component);
|
||||
boundFnMap.set(fn, boundFn);
|
||||
}
|
||||
return boundFn;
|
||||
}
|
||||
|
||||
type RefMap = { [key: string]: HTMLElement | null };
|
||||
type RefSetter = (el: HTMLElement | null) => void;
|
||||
|
||||
function multiRefSetter(refs: RefMap, name: string): RefSetter {
|
||||
let count = 0;
|
||||
return (el) => {
|
||||
if (el) {
|
||||
count++;
|
||||
if (count > 1) {
|
||||
throw new Error("Cannot have 2 elements with same ref name at the same time");
|
||||
}
|
||||
}
|
||||
if (count === 0 || el) {
|
||||
refs[name] = el;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the component props (or next props) against the (static) props
|
||||
* description. This is potentially an expensive operation: it may needs to
|
||||
* visit recursively the props and all the children to check if they are valid.
|
||||
* This is why it is only done in 'dev' mode.
|
||||
*/
|
||||
export function validateProps<P>(name: string | ComponentConstructor<P>, props: P, parent?: any) {
|
||||
const ComponentClass =
|
||||
typeof name !== "string"
|
||||
? name
|
||||
: (parent.constructor.components[name] as ComponentConstructor<P> | undefined);
|
||||
|
||||
if (!ComponentClass) {
|
||||
// this is an error, wrong component. We silently return here instead so the
|
||||
// error is triggered by the usual path ('component' function)
|
||||
return;
|
||||
}
|
||||
|
||||
const schema = ComponentClass.props;
|
||||
if (!schema) {
|
||||
if (parent.__owl__.app.warnIfNoStaticProps) {
|
||||
console.warn(`Component '${ComponentClass.name}' does not have a static props description`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const defaultProps = ComponentClass.defaultProps;
|
||||
if (defaultProps) {
|
||||
let isMandatory = (name: string) =>
|
||||
Array.isArray(schema)
|
||||
? schema.includes(name)
|
||||
: name in schema && !("*" in schema) && !isOptional(schema[name]);
|
||||
for (let p in defaultProps) {
|
||||
if (isMandatory(p)) {
|
||||
throw new Error(
|
||||
`A default value cannot be defined for a mandatory prop (name: '${p}', component: ${ComponentClass.name})`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const errors = validateSchema(props, schema);
|
||||
if (errors.length) {
|
||||
throw new Error(`Invalid props for component '${ComponentClass.name}': ` + errors.join(", "));
|
||||
}
|
||||
}
|
||||
|
||||
export const helpers = {
|
||||
withDefault,
|
||||
zero: Symbol("zero"),
|
||||
isBoundary,
|
||||
callSlot,
|
||||
capture,
|
||||
withKey,
|
||||
prepareList,
|
||||
setContextValue,
|
||||
multiRefSetter,
|
||||
shallowEqual,
|
||||
toNumber,
|
||||
validateProps,
|
||||
LazyValue,
|
||||
safeOutput,
|
||||
bind,
|
||||
createCatcher,
|
||||
markRaw,
|
||||
};
|
||||
Reference in New Issue
Block a user