mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
refactoring, work on environment
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
roots: ["<rootDir>/web/static", "<rootDir>/tests"],
|
||||
roots: ["<rootDir>/web/static"],
|
||||
transform: {
|
||||
"^.+\\.ts?$": "ts-jest"
|
||||
},
|
||||
|
||||
+3
-1
@@ -9,6 +9,7 @@
|
||||
"build": "cp -r web/ dist/app && npm run build:js && npm run build:css",
|
||||
"build:css": "sass web/static/src/scss/app.scss dist/app/app.css",
|
||||
"build:js": "tsc --allowjs -m amd --lib es2017,dom --target esnext --outfile dist/app/main.js web/static/src/ts/main.ts",
|
||||
"minify": "uglifyjs dist/app/main.js -o dist/app/main.js --compress --mangle",
|
||||
"serve": "live-server --entry-file=index.html dist/app/",
|
||||
"predev": "npm run build",
|
||||
"dev": "npm-run-all --parallel \"build:* -- --watch\" serve"
|
||||
@@ -34,7 +35,8 @@
|
||||
"snabbdom-to-html": "^5.1.1",
|
||||
"source-map-support": "^0.5.10",
|
||||
"ts-jest": "^23.10.5",
|
||||
"typescript": "^3.2.2"
|
||||
"typescript": "^3.2.2",
|
||||
"uglify-es": "^3.3.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"snabbdom": "^0.7.3"
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { init } from "../../../libs/snabbdom/src/snabbdom";
|
||||
import sdListeners from "../../../libs/snabbdom/src/modules/eventlisteners";
|
||||
import sdAttrs from "../../../libs/snabbdom/src/modules/attributes";
|
||||
import sdListeners from "../../../libs/snabbdom/src/modules/eventlisteners";
|
||||
import { init } from "../../../libs/snabbdom/src/snabbdom";
|
||||
import { VNode } from "../../../libs/snabbdom/src/vnode";
|
||||
import QWeb from "./qweb_vdom";
|
||||
|
||||
const patch = init([sdListeners, sdAttrs]);
|
||||
|
||||
export interface WidgetEnv {
|
||||
export interface WEnv {
|
||||
qweb: QWeb;
|
||||
}
|
||||
|
||||
export default class Widget<T extends WidgetEnv> {
|
||||
export default class Widget<T extends WEnv> {
|
||||
name: string = "widget";
|
||||
template: string = "<div></div>";
|
||||
vnode: VNode | null = null;
|
||||
@@ -49,7 +49,7 @@ export default class Widget<T extends WidgetEnv> {
|
||||
|
||||
async mount(target?: HTMLElement): Promise<VNode> {
|
||||
await this.willStart();
|
||||
this.env!.qweb.addTemplate(this.name, this.template);
|
||||
this.env.qweb.addTemplate(this.name, this.template);
|
||||
delete this.template;
|
||||
const vnode = await this.render();
|
||||
|
||||
|
||||
@@ -640,7 +640,7 @@ const forEachDirective: Directive = {
|
||||
const onDirective: Directive = {
|
||||
name: "on",
|
||||
priority: 90,
|
||||
atNodeCreation({ ctx, fullName, value, nodeID }) {
|
||||
atNodeCreation({ ctx, fullName, value, nodeID, qweb }) {
|
||||
const eventName = fullName.slice(5);
|
||||
let extraArgs;
|
||||
let handler = value.replace(/\(.*\)/, function(args) {
|
||||
@@ -649,7 +649,7 @@ const onDirective: Directive = {
|
||||
});
|
||||
ctx.addLine(
|
||||
`p${nodeID}.on = {${eventName}: context['${handler}'].bind(context${
|
||||
extraArgs ? ", " + extraArgs : ""
|
||||
extraArgs ? ", " + qweb._formatExpression(extraArgs) : ""
|
||||
})}`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,29 @@
|
||||
import QWeb from "./core/qweb_vdom";
|
||||
import { WEnv } from "./core/widget";
|
||||
import actions, { Action } from "./services/actions";
|
||||
import ActionManager from "./services/action_manager";
|
||||
import Ajax from "./services/ajax";
|
||||
import Router from "./services/router";
|
||||
import actions from "./services/actions";
|
||||
import { Env } from "./types";
|
||||
|
||||
const env: Env = {
|
||||
qweb: new QWeb(),
|
||||
router: new Router(),
|
||||
services: { actions }
|
||||
};
|
||||
export interface Env extends WEnv {
|
||||
actionManager: ActionManager;
|
||||
actions: Action[];
|
||||
ajax: Ajax;
|
||||
router: Router;
|
||||
}
|
||||
|
||||
export function makeEnvironment(): Env {
|
||||
const qweb = new QWeb();
|
||||
const router = new Router();
|
||||
const ajax = new Ajax();
|
||||
const actionManager = new ActionManager();
|
||||
|
||||
return {
|
||||
qweb,
|
||||
ajax,
|
||||
router,
|
||||
actionManager,
|
||||
actions
|
||||
};
|
||||
}
|
||||
|
||||
export default env;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
///<amd-module name="main" />
|
||||
|
||||
import RootWidget from "./widgets/root_widget";
|
||||
import env from "./env";
|
||||
import {makeEnvironment} from "./env";
|
||||
|
||||
document.addEventListener("DOMContentLoaded", async function() {
|
||||
const env = makeEnvironment();
|
||||
const rootWidget = new RootWidget(env);
|
||||
await rootWidget.mount(document.body);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Action } from "./actions";
|
||||
|
||||
// export interface Action {
|
||||
// id: number;
|
||||
// title: string;
|
||||
// Widget: Type<Widget<Env>>;
|
||||
// default?: boolean;
|
||||
// }
|
||||
|
||||
// const actions: Action[] = [
|
||||
// { id: 1, title: "Discuss", Widget: Discuss, default: true },
|
||||
// { id: 2, title: "CRM", Widget: CRM }
|
||||
// ];
|
||||
|
||||
|
||||
|
||||
export default class ActionManager {
|
||||
doAction(action: Action) {
|
||||
console.log(action);
|
||||
// load data (??)
|
||||
// trigger action somewhere
|
||||
// upload url with router
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import CRM from "../widgets/crm";
|
||||
import Discuss from "../widgets/discuss";
|
||||
import Widget from "../core/widget";
|
||||
import { Env, Type } from "../types";
|
||||
|
||||
import { Env } from "../env";
|
||||
|
||||
interface Type<T> extends Function {
|
||||
new (...args: any[]): T;
|
||||
}
|
||||
|
||||
export interface Action {
|
||||
id: number;
|
||||
@@ -19,3 +21,9 @@ const actions: Action[] = [
|
||||
|
||||
export default actions;
|
||||
|
||||
// class ActionManager {
|
||||
|
||||
// doAction(action: Action) {
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
|
||||
export default class Ajax{}
|
||||
@@ -1,39 +1,46 @@
|
||||
export type Route = string;
|
||||
export type Query = { [key: string]: string };
|
||||
|
||||
export interface RouteInfo {
|
||||
route: Route;
|
||||
export interface Route {
|
||||
// this is the part before the hash: www.something.com/web#action=1 => web
|
||||
path: string;
|
||||
query: Query;
|
||||
title: string;
|
||||
}
|
||||
|
||||
function clearSlaches(s: string): string {
|
||||
return s.replace(/\/$/, "").replace(/^\//, "");
|
||||
}
|
||||
|
||||
export default class Router {
|
||||
listeners: { owner: any; callback: (info: RouteInfo) => void }[] = [];
|
||||
listeners: { owner: any; callback: (info: Route) => void }[] = [];
|
||||
|
||||
constructor() {
|
||||
window.addEventListener("popstate", this.onUrlChange.bind(this));
|
||||
window.onhashchange = function () { console.log('aaaa'); debugger; }
|
||||
}
|
||||
|
||||
onUrlChange() {
|
||||
const info = this.getRouteInfo();
|
||||
for (let listener of this.listeners) {
|
||||
listener.callback.call(listener.owner, info);
|
||||
}
|
||||
onUrlChange(event: PopStateEvent) {
|
||||
debugger
|
||||
event.preventDefault();
|
||||
const info = this.getRoute();
|
||||
for (let listener of this.listeners) {
|
||||
listener.callback.call(listener.owner, info);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Route} route relative route: for example, /web/
|
||||
* @param {Query} query
|
||||
*/
|
||||
navigate(info: Partial<RouteInfo>) {
|
||||
const currentRouteInfo = this.getRouteInfo();
|
||||
const route = info.route || currentRouteInfo.route;
|
||||
navigate(info: Partial<Route>) {
|
||||
const currentRoute = this.getRoute();
|
||||
const route = info.path || currentRoute.path;
|
||||
const query = info.query || {};
|
||||
const title = info.title || currentRouteInfo.title;
|
||||
const title = info.title || currentRoute.title;
|
||||
const url = this.formatURL(route, query);
|
||||
window.history.pushState(null, title, url);
|
||||
}
|
||||
register(owner: any, callback: (info: RouteInfo) => void) {
|
||||
register(owner: any, callback: (info: Route) => void) {
|
||||
this.listeners.push({ owner, callback });
|
||||
}
|
||||
|
||||
@@ -41,24 +48,24 @@ export default class Router {
|
||||
this.listeners = this.listeners.filter(l => l.owner !== owner);
|
||||
}
|
||||
|
||||
formatURL(route: Route, query: Query): string {
|
||||
let url = route;
|
||||
formatURL(path: string, query: Query): string {
|
||||
let url = clearSlaches(path);
|
||||
let hasHash = false;
|
||||
for (let key in query) {
|
||||
url = url + (hasHash ? "&" : "#");
|
||||
url = url + `${key}=${query[key]}`;
|
||||
hasHash = true;
|
||||
}
|
||||
return url;
|
||||
return "/" + url;
|
||||
}
|
||||
|
||||
getRouteInfo(): RouteInfo {
|
||||
const route = window.location.pathname.slice(1);
|
||||
getRoute(): Route {
|
||||
const path = clearSlaches(window.location.pathname);
|
||||
const query = {};
|
||||
for (let part of window.location.hash.slice(1).split("?")) {
|
||||
let [key, value] = part.split("=");
|
||||
query[key] = value;
|
||||
}
|
||||
return { route, query, title: document.title };
|
||||
return { path, query, title: document.title };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import { WidgetEnv } from "./core/widget";
|
||||
import Router from "./services/router";
|
||||
|
||||
export interface Env extends WidgetEnv {
|
||||
router: Router;
|
||||
services: { [key: string]: any };
|
||||
}
|
||||
|
||||
export interface Type<T> extends Function {
|
||||
new (...args: any[]): T;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import Widget from "../core/widget";
|
||||
import { Env } from "../types";
|
||||
import { Env } from "../env";
|
||||
|
||||
const template = `
|
||||
<div class="o_crm">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Widget from "../core/widget";
|
||||
import { Env } from "../types";
|
||||
import { Env } from "../env";
|
||||
|
||||
const template = `
|
||||
<div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Widget from "../core/widget";
|
||||
import Counter from "./counter";
|
||||
import { Env } from "../types";
|
||||
import { Env } from "../env";
|
||||
|
||||
const template = `
|
||||
<div class="o_discuss">
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import Widget from "../core/widget";
|
||||
import { Action } from "../services/actions";
|
||||
import { Env } from "../types";
|
||||
import { Env } from "../env";
|
||||
|
||||
const template = `
|
||||
<div class="o_navbar">
|
||||
<span class="title">Odoo</span>
|
||||
<ul>
|
||||
<li t-foreach="env.services.actions" t-as="action">
|
||||
<a t-att-href="getUrl(action)"><t t-esc="action.title"/></a>
|
||||
<li t-foreach="env.actions" t-as="action">
|
||||
<a t-on-click="activateAction(action)" t-att-href="getUrl(action)">
|
||||
<t t-esc="action.title"/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -21,4 +23,9 @@ export default class Navbar extends Widget<Env> {
|
||||
const action_id = String(action.id);
|
||||
return this.env.router.formatURL("web", { action_id });
|
||||
}
|
||||
|
||||
activateAction(action: Action, event: MouseEvent) {
|
||||
event.preventDefault();
|
||||
this.env.actionManager.doAction(action);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Widget from "../core/widget";
|
||||
import Navbar from "./navbar";
|
||||
import { Action } from "../services/actions";
|
||||
import { Env } from "../types";
|
||||
import { Env } from "../env";
|
||||
|
||||
const template = `
|
||||
<div class="o_web_client">
|
||||
@@ -39,9 +39,9 @@ export default class RootWidget extends Widget<Env> {
|
||||
}
|
||||
|
||||
getAction(): Action {
|
||||
const routeInfo = this.env.router.getRouteInfo();
|
||||
const routeInfo = this.env.router.getRoute();
|
||||
const actionID = parseInt(routeInfo.query.action_id);
|
||||
let actions: Action[] = this.env.services.actions;
|
||||
let actions: Action[] = this.env.actions;
|
||||
let action = actions.find(a => a.id === actionID);
|
||||
if (!action) {
|
||||
action = actions.find(a => a.default === true);
|
||||
|
||||
@@ -651,6 +651,21 @@ describe("t-on", () => {
|
||||
(<HTMLElement>node).click();
|
||||
expect(a).toBe(6);
|
||||
});
|
||||
|
||||
test("can bind handlers with loop variable as argument", () => {
|
||||
expect.assertions(1);
|
||||
const qweb = new QWeb();
|
||||
qweb.addTemplate("test", `
|
||||
<ul>
|
||||
<li t-foreach="['someval']" t-as="action"><a t-on-click="activate(action)">link</a></li>
|
||||
</ul>`);
|
||||
const node = renderToDOM(qweb, "test", {
|
||||
activate(action) {
|
||||
expect(action).toBe('someval');
|
||||
}
|
||||
});
|
||||
(<HTMLElement>node).getElementsByTagName('a')[0].click();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import Widget, {WidgetEnv} from "../src/ts/core/widget";
|
||||
import Widget, {WEnv} from "../src/ts/core/widget";
|
||||
import QWeb from "../src/ts/core/qweb_vdom";
|
||||
import { Type } from "../src/ts/types";
|
||||
|
||||
|
||||
type TestEnv = WidgetEnv;
|
||||
interface Type<T> extends Function {
|
||||
new (...args: any[]): T;
|
||||
}
|
||||
|
||||
type TestEnv = WEnv;
|
||||
type TestWidget = Widget<TestEnv>
|
||||
|
||||
function makeWidget(W: Type<TestWidget>): TestWidget {
|
||||
|
||||
Reference in New Issue
Block a user