OpenERP 6.1 - Web Framework Tutorial

Post on 19-May-2015

17.537 views 5 download

description

Technical Presentation of the new Web client and framework of OpenERP 6.1: architecture, technologies, API, best practices, Javascript pitfalls, etc. DON'T FORGET to scroll each slide to read the NOTES. See also the related presentation about the changes at the server API/framework level: http://www.slideshare.net/openobject/openerp-61-framework-changes

Transcript of OpenERP 6.1 - Web Framework Tutorial

Upgrade Training Web Client

Migrating 6.0 web addons to 6.1

Migrating 6.0 web addons to 6.1

Rewriting 6.0 web addons to 6.1

There is no migrationEverything has been rewritten from scratch

Been at the community days, probably seen it. Not been and looked at OpenERP Web (6.1), probably noticed.

I doubt there’s a line left from the 6.0 codebase (we even split the project itself out), meaning there isn’t a single API left either, meaning you’ll have to rewrite everything as well.

Technological changes

6.0 6.1

Python (+ javascript) Javascript

Mako QWeb

CherryPy Werkzeug

Pages Single Page Interface

Page requests JSON-RPC

Client used to be mostly Python and HTML-ish templates, bulk of logic moved to javascript.* Template language QWeb, reminiscent of Kid/Genshi (though not inspired by them), XML-based

More applicative interaction, more stateful, no page requests and full reloads.

cloc: 6.0

Python

Javascript

Templates

16059

9782

5905

cloc: 6.1

Javascript

Templates

CSS

Python

16925

3693

3064

1874

Deployment changes

• Embedded mode, primary M.O.

• Stand-alone mode for dev/testing

• Official OpenERP bundle embeds

* Web Client has become “just another” OpenERP addon, though remains a different project* Standalone mostly to avoid full server reload when writing Python code** Or to put proxy inbetween

Architecture

Network

OpenERP Web Client Browser

HTTP

HTML

XML-RPC JSON-RPC

6.0’s network arch: typical web frontend, communicates with server via XML-RPC, with client via regular HTTP requests (e.g. link clicks) and HTML pages * Most client logic in the Web Client layer, not really a good fit as OpenERP has lots of client state * Tentative to migrate to more stateful browser-side (iframe then ~pjax), half-hearted and unsuccesful6.1 merges server and web’s python: web’s python becomes a JSON-RPC interface to OpenERP (+ some specific services), vast majority of logic moves to browser

Network

OpenERP Web Client Browser

HTTP

HTML

XML-RPCJSON-RPC

6.0’s network arch: typical web frontend, communicates with server via XML-RPC, with client via regular HTTP requests (e.g. link clicks) and HTML pages * Most client logic in the Web Client layer, not really a good fit as OpenERP has lots of client state * Tentative to migrate to more stateful browser-side (iframe then ~pjax), half-hearted and unsuccesful6.1 merges server and web’s python: web’s python becomes a JSON-RPC interface to OpenERP (+ some specific services), vast majority of logic moves to browser

Layout

Layout

Web Client

LayoutHeaderMenu

Menu (secondary)

Layout

Action Manager

OpenERP’s actions (ir.actions) -> primary drivers of everything (pretty much)No visuals, dispatches to various children based on action executed (window, client) or does work itself and handles result (act_url, server, report_xml)

Layout

View Manager

That slide was hard work

Actual handling of window actions: parses view descriptions, initializes views and handles inter-view communications, view switching, ...

Layout

View (form)

Layout

View (not form either)

View (not form)

Layout: summary• WebClient

• Action Manager

• View Manager (?)

• Views (n)

• Client Action (?)

• ...

Reusable pieces

Dashboard (form view)

Reusable pieces

action manager& client action

action manager&

view manager& list view

Widgets

Base unit of (visual) work. All the blue boxes are “widgets” (a class of openerp web)

Widget ~ MVC view

~ Backbone.View~ NSView~ QAbstractItemView~ Spine.Controller

Couldn’t use “View” due to confusion potential with OpenERP views (list, form, ...)

Widget Responsibilities

• Drawing on-screen (template rendering)

• User events handling (DOM events)

• Delegation

Widgets: templates

• Built-in handling

• Only trivial information

• Widget DOM root set up during rendering

Widgets: events

• No special provisions

• Use regular DOM events handling

Widgets: delegation

• Create children widgets to manage sub-sections

• Implementation detail of the widget

• Cooperative behavior between parent and children

Widgets lifecycle

• Synchronous initialization (construction)

• Template rendering

• DOM insertion

• Asynchronous initialization (secondary)

• [life]

• Cooperative destruction

See code (and detail of APIs) later

* Difficulty of parent/child relationships is not leaving “dangling” events or children** Recursively stops children widgets** Removes widget from DOM** Removes widget from parent** Must explicitly stop children when “killing” them during normal parent life (e.g. ActionManager)

JavaScript

Your new frienemy.

$ var a;$ a = 3.42;$ a = "42";$ a = true;

$ var array = [];$ var obj = {};

$ "foo".indexOf('o');1$ (3).toString();"3"$ [1, 2, 3, 4, 5].slice(1, 3);[2, 3]

Base language has Python similarities:* Dynamic typing* Literal booleans, strings, numbers, arrays, ~hashmap* Object-oriented, methods on a lot of things

> if (condition) {> // action> }

> while (condition) {> // body> }

> for(init; end; each) {> // stuff> }

Statements-heavy, close to C in its core syntax: if, while, for, return (explicit), ...

$ function f1 () {> // things> }$ b = f1;

$ var anon = function () {> // body> };

$ [1, 2, 3].forEach(function (item) {> console.log(item);> });

$ var a = 4;$ var fn = function () {> a = 5;> };$ a;4$ fn();$ a;5

* First-class functions (& higher-order functions)* Anonymous functions* Full closure (read/write)

$ var r = /foo/;$ r.test("a foo");true$ r.exec("a foo");["foo"]

Additional stuff:* Regex literals (~Perl)** RegExp#test(String) -> Boolean** RegExp#exec(String) -> MatchArray** String#match(RegExp) -> MatchArray** String#search(RegExp) -> IndexNumber** String#replace(RegExp|String, String|Function) -> String** String#split([String|RexExp], [Number]) -> Array<String>

Not Python

* Empty array, object is true (empty string is false)* objects only have string keys, few methods (and no map-like methods)* “for (i in array)” does not work “correctly”, “for (o in object)” is tricky, only iterates on key* ‘key in object’?* “weak” types (+/-; ==; ?)* objects v primitives; typeof v instanceof* Very small “standard library”: 10 global functions, 8 global constructors and the Math and JSON namespaces... and the DOM (in browsers)

DOM

* Nodes & Elements

$ var root = document.getElementById('root');$ root[object HTMLDivElement]$ var p = document.createElement('p');$ p.setAttribute('title', "This is a paragraph");$ root.appendChild(p);$ p.appendChild(document.createTextNode("Text Content"));$ p.textContent = "text content";

$ root.childNodes[0];[object Text]$ root.childNodes[1] === p;true$ root.childNodes[0].nodeType;3$ root.childNodes[1].nodeType;1$ p.textContent;"text content"$ p.childNodes[0].data;"text content"

* Very verbose creation and query API [insert node/JSDOM examples** HTML5 APIs/modest improvements (querySelector, querySelectorAll)

$ p.onclick = function () {> writeln("clicked paragraph");> };$ click(p);;clicked paragraph

$ p.onclick = function () {> writeln("clicked paragraph 2");> };$ click(p);clicked paragraph 2

$ p.addEventListener('click', function () {> writeln("clicked paragraph");> });$ click(p);clicked paragraph$ p.addEventListener('click', function () {> writeln("clicked paragraph 2");> });$ click(p);clicked paragraphclicked paragraph 2

* Events** “DOM0” simple but broken** DOM level 2 meh (add) to horrible (trigger)** MSIE < 9 incompatible (global event object, element.fireEvent(name[, options]))

$ var $root = $('#root').empty();$ $root;[[object HTMLDivElement]]$ var $p = $("<p>", {'title': "This is a paragraph"});$ $p;[[object HTMLParagraphElement]]$ $root.append($p);$ $p.text("text content");$ $root.children("p");$ $root.find("p");$ $p.text();"text content"

$ $p.click(function () { writeln("clicked paragraph"); });$ $p.click();clicked paragraph[[object HTMLParagraphElement]]$ $p.click(function () { writeln("clicked paragraph 2"); });$ $p.click();clicked paragraphclicked paragraph 2[[object HTMLParagraphElement]]

Pitfallshttp://bonsaiden.github.com/JavaScript-Garden/

$ {> var glob = 3;> }$ glob;3$ (function () {> var loc = 42;> })();$ loc;Error: Can't find variable: loc

$ var shadow = 3;$ (function () {> var shadow = 4;> writeln(shadow);> })();4$ shadow;3$ (function () {> writeln(local);> var local = 3;> writeln(local);> })();undefined3

$ does_not_exist;Error: Can't find variable: does_not_exist$ (function () {> does_not_exist = 42;> })();$ does_not_exist;42

$ var undef;$ writeln(undef);undefined

Declared but not defined -> undefined (oddly makes sense)Function scope (!C, !java)* Python has UnboundLocalError v NameError, JS has undefined v ErrorImplicit declaration -> global

$ var somevar = 3;$ somevar;3$ somevar = 4;$ somevar;4$ (function () {> somevar = 5;> })();$ somevar;5$ (function () {> var somevar = 42;> (function () {> somevar = 36;> writeln(somevar);> })();> writeln(somevar);> })();3636$ somevar;5

$ var fns = [];$ for (var i=0; i != 10; ++i) {> fns.push(function () { writeln(i); });> }$ fns.forEach(function (f) { f(); });10101010101010101010

* Writable closures (not possible in Python 2)* Danger! closures in loops

$ writeln(null);null$ typeof null;"object"$ null instanceof Object;false

$ writeln(undefined);undefined$ typeof undefined;"undefined"$ undefined === null;false$ undefined == null;true$ writeln((function () {})());undefined$ writeln((function () { return; })());undefined$ (function (a) { writeln(a); })();undefined$ writeln(({}).does_not_exist);undefined$ var no_such_variable;$ writeln(no_such_variable);undefined

$ NaN;NaN$ typeof NaN;"number"$ NaN === NaN;false$ NaN == NaN;false$ isNaN(NaN);true$ parseInt("foo");NaN$ parseInt("1foo");1$ 1 - "foo";NaN$ +"foo";NaN

Python -> None and exceptions (errors)JS -> null (None), undefined, NaN and exceptions.

* null !== undefined, but null == undefined* typeof null === “object”* Access undefined property -> undefined** Access property set to undefined -> also undefined* NaN exists in Python but very rare, more common in JS due to weak typing e.g. +”foo”** NaN != NaN, use isNaN** typeof NaN === ‘number’

$ function Foo() {};$ var foo = new Foo();$ foo instanceof Foo;true$ var bar = Foo();$ writeln(bar);undefined$ var a = function () { this.b = "b"; };$ a.b = function () { this.c = "c"; };$ a.b.c = function () {};$ new a.b.c instanceof (a.b.c);true$ new a.b().c instanceof (a.b.c);false$ (new (function () { this.ok = true; })).ok;true$ var baz = new Foo;

JavaScript OO is “interesting” (mostly bad kind)Not going to dwelve far into it, but knowledge of *constructors* useful as object layers are generally sugar over JS OO.* Constructors are functions used in special context** Any function can be used as a constructor (!= is useful a constructor)* new $type() vs $type()* $type an expression, ends at first parens by default (kinda weird)** new a.b.c -> new (a.b.c); new a.b().c -> (new a.b()).c** parens optional

$ this;[object DOMWindow]Actual output:[]$ this === window;true

$ var fn = function (f) { f(this); }$ var a = {b: fn};$ fn(function (t) {> writeln(t === window);> });true$ a.b(function (t) {> writeln(t === a);> });true$ new fn(function (t) {> writeln(t instanceof fn);> });true{}$ var c = {};$ c.b = a.b;$ c.b(function (t) {> writeln(t === c);> });true

$ var o = {};$ fn.call(o, function (t) {> writeln(t === o);> });true$ fn.apply(o, [function (t) {> writeln(t === o);> }]);true

* Call site decides of `this` in callee* default (global, function): “global object” (browsers: window)* Not transitive** b() -> `this` is window** a.b() -> `this` is a** new a.b -> `this` is instance of `a.b`** c.b = a.b; c.b() -> `this` is c** Function#call, Function#apply -> `this` is arg0

$ var a = {> attr: 1,> b: function () {> writeln(this.attr);> return function () {> writeln(this.attr);> };> }> };$ var f = a.b();1$ f();undefined

$ a = {> attr: 1,> b: function () {> var self = this;> return function () {> writeln(self.attr);> };> }> };$ var f = a.b();$ f();1

$ a = {> attr: 1,> b: function () {> var fn = function () {> writeln(this.attr);> };> return fn.bind(this);> }> };$ var f = a.b();$ f();1

* In closures, use alias (`var self = this`)** Function#bind, _.bind** Widget#proxy

JavaScript concurrency

• JS runtime is a reactor (pattern)

• Event loop

• Single threaded

• Blocking (synchronous)

Actually not what you’ll have with network requests: page (or even browser) frozen, no dialog no nothing.

• Continuation Passing Style (CPS)

• Call long-running operation

• Pass “continuation” callback

• Called whenever operation completes

1. Forwarding (all methods must take 1/2 callbacks)

2. Composition (waiting on multiple events)

3. Post-hoc decisions (act on an event which may or may not have happened)

Callback Mess

* Lower evolutivity** Many methods need extra 2 params, harder to read &use** Harder to add new params (callback @end)** Params post-callbacks hard to read/unreadable** Costlier for methods which may become async*** sync -> async transformation horrible so want to have API correct early* Ad-hoc mess of flags &stuff* Need to complement with flags as well

Deferred

• AKA Futures, promises

• (Success, Error)

• Pipelining

• Composition

• Promises/A

• jQuery.Deferred, #pipe, jQuery.when

* Stateful, tri-state (pending, resolved, rejected)* Callback queues** Deferred#then** Deferred#done/Deferred#fail/Deferred#always* Single state change* $.Deferred#pipe: pipelining (async chains)* $.when: composition** Original: wrap value in deferred

Tooling

• Webkit Developer Tools / CDT

• Firebug

• Dragonfly

• IE Developer Tools

Actual capabilities vary, some things might work better in some tools than in other

1. WDT/CDT ~ Firebug2. Dragonfly3. IE9 DevTools4. IE8 DevTools

Console

* Can actually get CLI JS console (à la `python`): spidermonkey or nodejs* Access to page content (and libraries)* Test code snippets** jsfiddle** jsbin** tinker.io** ...* Interactively inspect objects

Console API

``console`` object, functions for programmatic printing of information* Basic logging (log, debug, info, warn, error, exception)* Formatting (group/groupEnd)* Timers (time/timeEnd)* Profiling (profile/profileEnd)* trace* dir* count

Visual Debugger

* Breakpoints (visual or programmatic)* Conditional breakpoints* call stack* Locals** CONSOLE

DOM Inspector

* “Inspect Element” entry point* Edit DOM (as HTML text, add/remove/change attributes)* Visualize margin/padding/borders* CSS adjustments** Metrics section (overview)** enable/disable rules* DOM breakpoints (flaky?)

Network

* List requests, time, latency, payload size* Request & response header* Request payload* Response payload (formatted JSON)

Profiler

Nothing much to say, usual visual profiler, have to learn on your own

Console (again)

command-line API http://getfirebug.com/wiki/index.php/Command_Line_API

OpenERP Web APIs

6.1 Caveat

• API stability (and the lack thereof)

• Python controller

• RPC (connection, dataset)

• QWeb

• Widget

• View

• form.Widget

• Client actions

• Translations

• underscore.js

• Dialog

• CrashManager

Classes & subclassing

* JS OO somewhat similar to Python’s* Single-inheritance* No sugar for classes, plumbing a bit hard to get your head around and verbose** Helper: Class & Class.extend** this._super, doesn’t play well with async

QWeb

• openerp.web.qweb

• QWeb.render(template, context) => String

• t-name

• t-call

Usage

Logic

• t-set t-value

• t-if

• t-foreach t-as

Output• t-esc

• t-escf

• t-raw

• t-rawf

• t-att

• t-att-*

• t-attf-*

API: Widget

Client actionsregistries (instance.web.client_actions)<iframe class="youtube-player" type="text/html" width="640" height="385" t-attf-src="http://youtube.com/embed/#{widget.params.id}" frameborder="0"></iframe>* sn2l2_v6Ur8 * pOA9PGYeP3E* oHg5SJYRHA0 * wmkoJGm6y6k* ZZ5LpwO-An4 * eQemvyyJ--g* qWkUFxItWmU * hXlzci1rKNM

• Web-oriented keys: css, js, qweb

• ‘static’ folder

• module pattern: private namespace, initialization code

• Widget#init :: * => ()

• Widget#render :: () => String

• Widget#start :: () => Deferred

• Widget#stop :: () => ()

API: Search Fields

meetings

• Field

• non-standard cycle

• get_context, get_domain

{instance.web.search.fields}* Weird cycle** Render everything (recursive, defaults)** Bind (element_id, in template context)** Start* dygraphs.com** dygraphs.com/dygraph-combined.js** new Dygraph(element, CSV_string)

API: Form Fields

hr -> email field{instance.web.form.widgets}Also weird cycle/management* $element set to parent (table cell), in start() (_super() call required)

• Field#value :: *

• Field#set_value :: * => Deferred

• Field#on_ui_change :: () => ()

• Field#update_dom :: () => ()

• Field#validate :: () => ()

#value -> current value for the widget#set_value -> form sets value on widget#on_ui_change -> signal value change via UI to form* Should have set #value# update_dom -> resync non-value DOM (readonly, invalid, required)# validate -> set/unset “invalid”

Debugging

Debugging with OpenERP Web

Debugging OpenERP Web

Third-party Embedding

Install “share”Share button “link or embed”Put in HTML file on diskpython -mSimpleHTTPServer