mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 94e978815f | |||
| 9c40ee67e1 | |||
| b1690f19cc | |||
| 5dcee2564c | |||
| 752160fd85 | |||
| 3937966b74 | |||
| e7ebb92104 | |||
| c78e070636 | |||
| 610ed02373 | |||
| 4b23f51974 | |||
| b25e988628 | |||
| e0758c0e9e | |||
| ba34811f71 | |||
| 28672096a2 | |||
| 9823a4e7dd | |||
| 105ec7ced8 | |||
| 8f9ad987b9 | |||
| 6050827689 | |||
| 9b1ec5dc0e | |||
| 9b1c270501 | |||
| 9d99f8936b | |||
| 7bc9d34a64 | |||
| b1a3b322ee | |||
| ca688d8640 | |||
| ea9f533536 | |||
| 875ebdcfb0 | |||
| 2e07799250 | |||
| 0d9d21a5c1 | |||
| 836e12b1c5 | |||
| 7538aeae0e | |||
| 3e9ba9ca8e | |||
| e4c296a7d2 | |||
| 44748270da | |||
| 8b1dc4c43d | |||
| c105c6da38 | |||
| 3001420a1d | |||
| 432ff444a1 | |||
| aa3c88a6c4 | |||
| 601a98e649 | |||
| 23c7d19ef0 | |||
| 59c49b5833 | |||
| 2cca0bd819 | |||
| fbf4c4add2 | |||
| 78d6ff735e | |||
| a7f51fa666 | |||
| 5175f95289 | |||
| 3c9f4a8ae9 | |||
| 310730782c | |||
| 77a413d750 | |||
| fe31f93c94 | |||
| 9cc0f88e02 | |||
| 412fda10fd | |||
| aa441274c7 | |||
| ba20267151 | |||
| aabb7559b8 | |||
| 606d14a399 | |||
| c049022798 | |||
| cae32c5bd4 | |||
| 63128b7d29 | |||
| f9d810a42f | |||
| 1784a98b2c | |||
| 52be5ae85f | |||
| 0765b19367 | |||
| b8b5190bc6 | |||
| 9475de4d18 |
@@ -20,9 +20,6 @@ yarn-error.log*
|
||||
|
||||
node_modules
|
||||
|
||||
# Extras temp file
|
||||
/tools/owl.js
|
||||
|
||||
release-notes.md
|
||||
|
||||
.rpt2_cache
|
||||
|
||||
@@ -451,5 +451,6 @@ console.log(status(component));
|
||||
// logs either:
|
||||
// - 'new', if the component is new and has not been mounted yet
|
||||
// - 'mounted', if the component is currently mounted
|
||||
// - 'cancelled', if the component has not been mounted yet but will be destroyed soon
|
||||
// - 'destroyed' if the component is currently destroyed
|
||||
```
|
||||
|
||||
@@ -175,7 +175,7 @@ class Notebook extends Component {
|
||||
<div class="notebook">
|
||||
<div class="tabs">
|
||||
<t t-foreach="tabNames" t-as="tab" t-key="tab_index">
|
||||
<span t-att-class="{active:tab_index === activeTab}" t-on-click="() => state.activeTab=tab">
|
||||
<span t-att-class="{active:tab_index === activeTab}" t-on-click="() => state.activeTab=tab_index">
|
||||
<t t-esc="props.slots[tab].title"/>
|
||||
</span>
|
||||
</t>
|
||||
|
||||
@@ -376,15 +376,16 @@ An important difference should be made with the usual `QWeb` behaviour: Owl
|
||||
requires the presence of a `t-key` directive, to be able to properly reconcile
|
||||
renderings.
|
||||
|
||||
`t-foreach` can iterate on an array (the current item will be the current value)
|
||||
or an object (the current item will be the current key).
|
||||
`t-foreach` can iterate on any iterable, and also has special support for objects
|
||||
and maps, it will expose the key of the current iteration as the contents of the
|
||||
`t-as`, and the corresponding value with the same name and the suffix `_value`.
|
||||
|
||||
In addition to the name passed via t-as, `t-foreach` provides a few other
|
||||
variables for various data points (note: `$as` will be replaced with the name
|
||||
passed to `t-as`):
|
||||
In addition to the name passed via t-as, `t-foreach` provides a few other useful
|
||||
variables (note: `$as` will be replaced with the name passed to `t-as`):
|
||||
|
||||
- `$as_value`: the current iteration value, identical to `$as` for lists and
|
||||
integers, but for objects, it provides the value (where `$as` provides the key)
|
||||
- `$as_value`: the current iteration value, identical to `$as` for arrays and
|
||||
other iterables, but for objects and maps, it provides the value (where `$as`
|
||||
provides the key)
|
||||
- `$as_index`: the current iteration index (the first item of the iteration has index 0)
|
||||
- `$as_first`: whether the current item is the first of the iteration
|
||||
(equivalent to `$as_index == 0`)
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,72 @@
|
||||
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
|
||||
|
||||
/* Tomorrow Comment */
|
||||
.hljs-comment,
|
||||
.hljs-quote {
|
||||
color: #8e908c;
|
||||
}
|
||||
|
||||
/* Tomorrow Red */
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-tag,
|
||||
.hljs-name,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class,
|
||||
.hljs-regexp,
|
||||
.hljs-deletion {
|
||||
color: #c82829;
|
||||
}
|
||||
|
||||
/* Tomorrow Orange */
|
||||
.hljs-number,
|
||||
.hljs-built_in,
|
||||
.hljs-builtin-name,
|
||||
.hljs-literal,
|
||||
.hljs-type,
|
||||
.hljs-params,
|
||||
.hljs-meta,
|
||||
.hljs-link {
|
||||
color: #f5871f;
|
||||
}
|
||||
|
||||
/* Tomorrow Yellow */
|
||||
.hljs-attribute {
|
||||
color: #eab700;
|
||||
}
|
||||
|
||||
/* Tomorrow Green */
|
||||
.hljs-string,
|
||||
.hljs-symbol,
|
||||
.hljs-bullet,
|
||||
.hljs-addition {
|
||||
color: #718c00;
|
||||
}
|
||||
|
||||
/* Tomorrow Blue */
|
||||
.hljs-title,
|
||||
.hljs-section {
|
||||
color: #4271ae;
|
||||
}
|
||||
|
||||
/* Tomorrow Purple */
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag {
|
||||
color: #8959a8;
|
||||
}
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
background: white;
|
||||
color: #4d4d4c;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
html, body {
|
||||
background-color: #fefefe;
|
||||
font-family: Roboto, -apple-system, BlinkMacSystemFont, "Segoe UI", Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
body {
|
||||
display: grid;
|
||||
grid-gap: 3rem;
|
||||
grid-template-areas: "header" "nav" "main" "footer";
|
||||
grid-template-columns: 1fr;
|
||||
height: 100%;
|
||||
grid-template-rows: 1fr 3rem 1fr 3rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
body {
|
||||
grid-template-rows: 1fr 3rem 1fr 5rem;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: #00A09D;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover, a:active, a:visited {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
header {
|
||||
grid-area: header;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
text-align: center;
|
||||
padding-top: 3rem !important;
|
||||
}
|
||||
|
||||
hgroup > p {
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
nav {
|
||||
grid-area: nav;
|
||||
}
|
||||
|
||||
main {
|
||||
grid-area: main;
|
||||
/*justify-self: center;*/
|
||||
grid-column: 1 / -1;
|
||||
/*max-width: 80rem !important;*/
|
||||
/*margin: 3rem 0;*/
|
||||
}
|
||||
|
||||
footer {
|
||||
grid-area: footer;
|
||||
padding-bottom: 3rem !important;
|
||||
}
|
||||
|
||||
header, nav, footer {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
article {
|
||||
padding: 3rem 0;
|
||||
}
|
||||
|
||||
article h3 {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
article.brand {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
article.design {
|
||||
background-color: #875A7B;
|
||||
color: #f0eeee;
|
||||
}
|
||||
|
||||
header .homepage-logo {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
@media (min-width: 40rem) {
|
||||
.row {
|
||||
flex-direction: column;
|
||||
margin-left: initial;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.row .column {
|
||||
margin-bottom: initial;
|
||||
padding: initial;
|
||||
}
|
||||
}
|
||||
@media (min-width: 60rem) {
|
||||
.row {
|
||||
flex-direction: row;
|
||||
margin-left: -1.0rem;
|
||||
width: calc(100% + 2.0rem);
|
||||
}
|
||||
.row .column {
|
||||
margin-bottom: inherit;
|
||||
padding: 0 1.0rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,602 @@
|
||||
/*!
|
||||
* Milligram v1.3.0
|
||||
* https://milligram.github.io
|
||||
*
|
||||
* Copyright (c) 2017 CJ Patoilo
|
||||
* Licensed under the MIT license
|
||||
*/
|
||||
|
||||
*,
|
||||
*:after,
|
||||
*:before {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
font-size: 62.5%;
|
||||
}
|
||||
|
||||
body {
|
||||
color: #606c76;
|
||||
font-family: 'Roboto', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
|
||||
font-size: 1.6em;
|
||||
font-weight: 300;
|
||||
letter-spacing: .01em;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 0.3rem solid #d1d1d1;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
padding: 1rem 1.5rem;
|
||||
}
|
||||
|
||||
blockquote *:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.button,
|
||||
button,
|
||||
input[type='button'],
|
||||
input[type='reset'],
|
||||
input[type='submit'] {
|
||||
background-color: #9b4dca;
|
||||
border: 0.1rem solid #9b4dca;
|
||||
border-radius: .4rem;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
height: 3.8rem;
|
||||
letter-spacing: .1rem;
|
||||
line-height: 3.8rem;
|
||||
padding: 0 3.0rem;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.button:focus, .button:hover,
|
||||
button:focus,
|
||||
button:hover,
|
||||
input[type='button']:focus,
|
||||
input[type='button']:hover,
|
||||
input[type='reset']:focus,
|
||||
input[type='reset']:hover,
|
||||
input[type='submit']:focus,
|
||||
input[type='submit']:hover {
|
||||
background-color: #606c76;
|
||||
border-color: #606c76;
|
||||
color: #fff;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.button[disabled],
|
||||
button[disabled],
|
||||
input[type='button'][disabled],
|
||||
input[type='reset'][disabled],
|
||||
input[type='submit'][disabled] {
|
||||
cursor: default;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.button[disabled]:focus, .button[disabled]:hover,
|
||||
button[disabled]:focus,
|
||||
button[disabled]:hover,
|
||||
input[type='button'][disabled]:focus,
|
||||
input[type='button'][disabled]:hover,
|
||||
input[type='reset'][disabled]:focus,
|
||||
input[type='reset'][disabled]:hover,
|
||||
input[type='submit'][disabled]:focus,
|
||||
input[type='submit'][disabled]:hover {
|
||||
background-color: #9b4dca;
|
||||
border-color: #9b4dca;
|
||||
}
|
||||
|
||||
.button.button-outline,
|
||||
button.button-outline,
|
||||
input[type='button'].button-outline,
|
||||
input[type='reset'].button-outline,
|
||||
input[type='submit'].button-outline {
|
||||
background-color: transparent;
|
||||
color: #9b4dca;
|
||||
}
|
||||
|
||||
.button.button-outline:focus, .button.button-outline:hover,
|
||||
button.button-outline:focus,
|
||||
button.button-outline:hover,
|
||||
input[type='button'].button-outline:focus,
|
||||
input[type='button'].button-outline:hover,
|
||||
input[type='reset'].button-outline:focus,
|
||||
input[type='reset'].button-outline:hover,
|
||||
input[type='submit'].button-outline:focus,
|
||||
input[type='submit'].button-outline:hover {
|
||||
background-color: transparent;
|
||||
border-color: #606c76;
|
||||
color: #606c76;
|
||||
}
|
||||
|
||||
.button.button-outline[disabled]:focus, .button.button-outline[disabled]:hover,
|
||||
button.button-outline[disabled]:focus,
|
||||
button.button-outline[disabled]:hover,
|
||||
input[type='button'].button-outline[disabled]:focus,
|
||||
input[type='button'].button-outline[disabled]:hover,
|
||||
input[type='reset'].button-outline[disabled]:focus,
|
||||
input[type='reset'].button-outline[disabled]:hover,
|
||||
input[type='submit'].button-outline[disabled]:focus,
|
||||
input[type='submit'].button-outline[disabled]:hover {
|
||||
border-color: inherit;
|
||||
color: #9b4dca;
|
||||
}
|
||||
|
||||
.button.button-clear,
|
||||
button.button-clear,
|
||||
input[type='button'].button-clear,
|
||||
input[type='reset'].button-clear,
|
||||
input[type='submit'].button-clear {
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
color: #9b4dca;
|
||||
}
|
||||
|
||||
.button.button-clear:focus, .button.button-clear:hover,
|
||||
button.button-clear:focus,
|
||||
button.button-clear:hover,
|
||||
input[type='button'].button-clear:focus,
|
||||
input[type='button'].button-clear:hover,
|
||||
input[type='reset'].button-clear:focus,
|
||||
input[type='reset'].button-clear:hover,
|
||||
input[type='submit'].button-clear:focus,
|
||||
input[type='submit'].button-clear:hover {
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
color: #606c76;
|
||||
}
|
||||
|
||||
.button.button-clear[disabled]:focus, .button.button-clear[disabled]:hover,
|
||||
button.button-clear[disabled]:focus,
|
||||
button.button-clear[disabled]:hover,
|
||||
input[type='button'].button-clear[disabled]:focus,
|
||||
input[type='button'].button-clear[disabled]:hover,
|
||||
input[type='reset'].button-clear[disabled]:focus,
|
||||
input[type='reset'].button-clear[disabled]:hover,
|
||||
input[type='submit'].button-clear[disabled]:focus,
|
||||
input[type='submit'].button-clear[disabled]:hover {
|
||||
color: #9b4dca;
|
||||
}
|
||||
|
||||
code {
|
||||
background: #f4f5f6;
|
||||
border-radius: .4rem;
|
||||
font-size: 86%;
|
||||
margin: 0 .2rem;
|
||||
padding: .2rem .5rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #f4f5f6;
|
||||
border-left: 0.3rem solid #9b4dca;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
pre > code {
|
||||
border-radius: 0;
|
||||
display: block;
|
||||
padding: 1rem 1.5rem;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 0;
|
||||
border-top: 0.1rem solid #f4f5f6;
|
||||
margin: 3.0rem 0;
|
||||
}
|
||||
|
||||
input[type='email'],
|
||||
input[type='number'],
|
||||
input[type='password'],
|
||||
input[type='search'],
|
||||
input[type='tel'],
|
||||
input[type='text'],
|
||||
input[type='url'],
|
||||
textarea,
|
||||
select {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
background-color: transparent;
|
||||
border: 0.1rem solid #d1d1d1;
|
||||
border-radius: .4rem;
|
||||
box-shadow: none;
|
||||
box-sizing: inherit;
|
||||
height: 3.8rem;
|
||||
padding: .6rem 1.0rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input[type='email']:focus,
|
||||
input[type='number']:focus,
|
||||
input[type='password']:focus,
|
||||
input[type='search']:focus,
|
||||
input[type='tel']:focus,
|
||||
input[type='text']:focus,
|
||||
input[type='url']:focus,
|
||||
textarea:focus,
|
||||
select:focus {
|
||||
border-color: #9b4dca;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
select {
|
||||
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="14" viewBox="0 0 29 14" width="29"><path fill="#d1d1d1" d="M9.37727 3.625l5.08154 6.93523L19.54036 3.625"/></svg>') center right no-repeat;
|
||||
padding-right: 3.0rem;
|
||||
}
|
||||
|
||||
select:focus {
|
||||
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="14" viewBox="0 0 29 14" width="29"><path fill="#9b4dca" d="M9.37727 3.625l5.08154 6.93523L19.54036 3.625"/></svg>');
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 6.5rem;
|
||||
}
|
||||
|
||||
label,
|
||||
legend {
|
||||
display: block;
|
||||
font-size: 1.6rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border-width: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input[type='checkbox'],
|
||||
input[type='radio'] {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.label-inline {
|
||||
display: inline-block;
|
||||
font-weight: normal;
|
||||
margin-left: .5rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 0 auto;
|
||||
max-width: 112.0rem;
|
||||
padding: 0 2.0rem;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.row.row-no-padding {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.row.row-no-padding > .column {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.row.row-wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.row.row-top {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.row.row-bottom {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.row.row-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.row.row-stretch {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.row.row-baseline {
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.row .column {
|
||||
display: block;
|
||||
flex: 1 1 auto;
|
||||
margin-left: 0;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.row .column.column-offset-10 {
|
||||
margin-left: 10%;
|
||||
}
|
||||
|
||||
.row .column.column-offset-20 {
|
||||
margin-left: 20%;
|
||||
}
|
||||
|
||||
.row .column.column-offset-25 {
|
||||
margin-left: 25%;
|
||||
}
|
||||
|
||||
.row .column.column-offset-33, .row .column.column-offset-34 {
|
||||
margin-left: 33.3333%;
|
||||
}
|
||||
|
||||
.row .column.column-offset-50 {
|
||||
margin-left: 50%;
|
||||
}
|
||||
|
||||
.row .column.column-offset-66, .row .column.column-offset-67 {
|
||||
margin-left: 66.6666%;
|
||||
}
|
||||
|
||||
.row .column.column-offset-75 {
|
||||
margin-left: 75%;
|
||||
}
|
||||
|
||||
.row .column.column-offset-80 {
|
||||
margin-left: 80%;
|
||||
}
|
||||
|
||||
.row .column.column-offset-90 {
|
||||
margin-left: 90%;
|
||||
}
|
||||
|
||||
.row .column.column-10 {
|
||||
flex: 0 0 10%;
|
||||
max-width: 10%;
|
||||
}
|
||||
|
||||
.row .column.column-20 {
|
||||
flex: 0 0 20%;
|
||||
max-width: 20%;
|
||||
}
|
||||
|
||||
.row .column.column-25 {
|
||||
flex: 0 0 25%;
|
||||
max-width: 25%;
|
||||
}
|
||||
|
||||
.row .column.column-33, .row .column.column-34 {
|
||||
flex: 0 0 33.3333%;
|
||||
max-width: 33.3333%;
|
||||
}
|
||||
|
||||
.row .column.column-40 {
|
||||
flex: 0 0 40%;
|
||||
max-width: 40%;
|
||||
}
|
||||
|
||||
.row .column.column-50 {
|
||||
flex: 0 0 50%;
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
.row .column.column-60 {
|
||||
flex: 0 0 60%;
|
||||
max-width: 60%;
|
||||
}
|
||||
|
||||
.row .column.column-66, .row .column.column-67 {
|
||||
flex: 0 0 66.6666%;
|
||||
max-width: 66.6666%;
|
||||
}
|
||||
|
||||
.row .column.column-75 {
|
||||
flex: 0 0 75%;
|
||||
max-width: 75%;
|
||||
}
|
||||
|
||||
.row .column.column-80 {
|
||||
flex: 0 0 80%;
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
.row .column.column-90 {
|
||||
flex: 0 0 90%;
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.row .column .column-top {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.row .column .column-bottom {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.row .column .column-center {
|
||||
-ms-grid-row-align: center;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
@media (min-width: 40rem) {
|
||||
.row {
|
||||
flex-direction: row;
|
||||
margin-left: -1.0rem;
|
||||
width: calc(100% + 2.0rem);
|
||||
}
|
||||
.row .column {
|
||||
margin-bottom: inherit;
|
||||
padding: 0 1.0rem;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: #9b4dca;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:focus, a:hover {
|
||||
color: #606c76;
|
||||
}
|
||||
|
||||
dl,
|
||||
ol,
|
||||
ul {
|
||||
list-style: none;
|
||||
margin-top: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
dl dl,
|
||||
dl ol,
|
||||
dl ul,
|
||||
ol dl,
|
||||
ol ol,
|
||||
ol ul,
|
||||
ul dl,
|
||||
ul ol,
|
||||
ul ul {
|
||||
font-size: 90%;
|
||||
margin: 1.5rem 0 1.5rem 3.0rem;
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style: decimal inside;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: circle inside;
|
||||
}
|
||||
|
||||
.button,
|
||||
button,
|
||||
dd,
|
||||
dt,
|
||||
li {
|
||||
margin-bottom: 1.0rem;
|
||||
}
|
||||
|
||||
fieldset,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
blockquote,
|
||||
dl,
|
||||
figure,
|
||||
form,
|
||||
ol,
|
||||
p,
|
||||
pre,
|
||||
table,
|
||||
ul {
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
table {
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
border-bottom: 0.1rem solid #e1e1e1;
|
||||
padding: 1.2rem 1.5rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
td:first-child,
|
||||
th:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
td:last-child,
|
||||
th:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-weight: 300;
|
||||
letter-spacing: -.1rem;
|
||||
margin-bottom: 2.0rem;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 4.6rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 3.6rem;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 2.8rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 2.2rem;
|
||||
letter-spacing: -.08rem;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.8rem;
|
||||
letter-spacing: -.05rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1.6rem;
|
||||
letter-spacing: 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.clearfix:after {
|
||||
clear: both;
|
||||
content: ' ';
|
||||
display: table;
|
||||
}
|
||||
|
||||
.float-left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.float-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=milligram.css.map */
|
||||
Vendored
+349
@@ -0,0 +1,349 @@
|
||||
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
|
||||
|
||||
/* Document
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Correct the line height in all browsers.
|
||||
* 2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
*/
|
||||
|
||||
html {
|
||||
line-height: 1.15; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/* Sections
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the margin in all browsers.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the `main` element consistently in IE.
|
||||
*/
|
||||
|
||||
main {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the font size and margin on `h1` elements within `section` and
|
||||
* `article` contexts in Chrome, Firefox, and Safari.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in Firefox.
|
||||
* 2. Show the overflow in Edge and IE.
|
||||
*/
|
||||
|
||||
hr {
|
||||
box-sizing: content-box; /* 1 */
|
||||
height: 0; /* 1 */
|
||||
overflow: visible; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the gray background on active links in IE 10.
|
||||
*/
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Remove the bottom border in Chrome 57-
|
||||
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: none; /* 1 */
|
||||
text-decoration: underline; /* 2 */
|
||||
text-decoration: underline dotted; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font weight in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` elements from affecting the line height in
|
||||
* all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the border on images inside links in IE 10.
|
||||
*/
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Change the font styles in all browsers.
|
||||
* 2. Remove the margin in Firefox and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit; /* 1 */
|
||||
font-size: 100%; /* 1 */
|
||||
line-height: 1.15; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the overflow in IE.
|
||||
* 1. Show the overflow in Edge.
|
||||
*/
|
||||
|
||||
button,
|
||||
input { /* 1 */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
||||
* 1. Remove the inheritance of text transform in Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select { /* 1 */
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the inability to style clickable types in iOS and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
[type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner border and padding in Firefox.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the focus styles unset by the previous rule.
|
||||
*/
|
||||
|
||||
button:-moz-focusring,
|
||||
[type="button"]:-moz-focusring,
|
||||
[type="reset"]:-moz-focusring,
|
||||
[type="submit"]:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the padding in Firefox.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
padding: 0.35em 0.75em 0.625em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the text wrapping in Edge and IE.
|
||||
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
||||
* 3. Remove the padding so developers are not caught out when they zero out
|
||||
* `fieldset` elements in all browsers.
|
||||
*/
|
||||
|
||||
legend {
|
||||
box-sizing: border-box; /* 1 */
|
||||
color: inherit; /* 2 */
|
||||
display: table; /* 1 */
|
||||
max-width: 100%; /* 1 */
|
||||
padding: 0; /* 3 */
|
||||
white-space: normal; /* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the default vertical scrollbar in IE 10+.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in IE 10.
|
||||
* 2. Remove the padding in IE 10.
|
||||
*/
|
||||
|
||||
[type="checkbox"],
|
||||
[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the cursor style of increment and decrement buttons in Chrome.
|
||||
*/
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the odd appearance in Chrome and Safari.
|
||||
* 2. Correct the outline style in Safari.
|
||||
*/
|
||||
|
||||
[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
outline-offset: -2px; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner padding in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inability to style clickable types in iOS and Safari.
|
||||
* 2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/* Interactive
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Add the correct display in Edge, IE 10+, and Firefox.
|
||||
*/
|
||||
|
||||
details {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the correct display in all browsers.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/* Misc
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10+.
|
||||
*/
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10.
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,15 @@
|
||||
import { mount, Component, useState, loadFile } from "@odoo/owl";
|
||||
|
||||
class Counter extends Component {
|
||||
static template = "Counter";
|
||||
setup() {
|
||||
this.state = useState({ value: 0 });
|
||||
}
|
||||
|
||||
increment() {
|
||||
this.state.value++;
|
||||
}
|
||||
}
|
||||
const templates = await loadFile("./counter.xml");
|
||||
|
||||
mount(Counter, document.getElementById("app-container"), { templates });
|
||||
@@ -0,0 +1,7 @@
|
||||
<templates>
|
||||
<t t-name="Counter">
|
||||
<button t-on-click="increment">
|
||||
Click Me! [<t t-esc="state.value"/>]
|
||||
</button>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,9 @@
|
||||
import { loadFile } from "@odoo/owl";
|
||||
|
||||
for (const [className, type] of [["xml", "xml"], ["javascript", "js"]]) {
|
||||
loadFile(`./counter.${type}`).then(code => {
|
||||
const el = document.querySelector(`code.${className}`);
|
||||
el.textContent = code;
|
||||
hljs.highlightBlock(el);
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>OWL Framework</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic">
|
||||
<link rel="stylesheet" href="assets/normalize.css">
|
||||
<link rel="stylesheet" href="assets/milligram.css">
|
||||
<link rel="stylesheet" href="assets/highlight.tomorrow.css">
|
||||
<link rel="stylesheet" href="assets/main.css">
|
||||
<script type="importmap">{ "imports": { "@odoo/owl": "./owl.js" } }</script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="container">
|
||||
<img class="homepage-logo" src="assets/owl_1f989.png" alt="OWL Logo">
|
||||
<hgroup>
|
||||
<h1>OWL Framework</h1>
|
||||
<p>Mysterious OWL: A web framework for structured, dynamic and maintainable applications</p>
|
||||
</hgroup>
|
||||
</header>
|
||||
<nav class="container">
|
||||
<p>
|
||||
<a href="https://github.com/odoo/owl" title="https://github.com/odoo/owl">Github</a>
|
||||
•
|
||||
<a href="https://github.com/odoo/owl/blob/master/doc/readme.md">Documentation</a>
|
||||
•
|
||||
<a href="playground/">Playground</a>
|
||||
</p>
|
||||
</nav>
|
||||
<main>
|
||||
<article class="design">
|
||||
<div class="container">
|
||||
<h2>Why OWL?</h2>
|
||||
<div class="row">
|
||||
<section class="column">
|
||||
<h3>XML based</h3>
|
||||
<p>Templates are based on the XML format, which allows interesting applications. For example, they could be stored in a database and modified dynamically with <tt>xpaths</tt>.</p>
|
||||
</section>
|
||||
<section class="column">
|
||||
<h3>Templates compiled in the browser</h3>
|
||||
<p>This may not be a good fit for all applications, but if you need to generate dynamically user interfaces in the browser, this is very powerful. For example, a generic form view component could generate a specific form user interface for each various models, from a XML view.</p>
|
||||
</section>
|
||||
<section class="column">
|
||||
<h3>No toolchain required</h3>
|
||||
<p>This is extremely useful for some applications, if, for various reasons (security/deployment/dynamic modules/specific assets tools), it is not ok to use standard web tools based on <tt>npm</tt>.</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="example">
|
||||
<div class="container">
|
||||
<h2>Example</h2>
|
||||
<div class="row">
|
||||
<section class="column">
|
||||
<pre><code class="javascript"></code></pre>
|
||||
</section>
|
||||
<section class="column">
|
||||
<pre><code class="xml"></code></pre>
|
||||
<div id="app-container"></div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
<footer>
|
||||
<p><a href=".">OWL</a> is licensed under LGPLv3.<br>Logo from <a href="https://github.com/googlefonts/noto-emoji">Google Noto Emoji Font</a>, licensed under Apache License 2.0</p>
|
||||
</footer>
|
||||
<script src="assets/highlight.pack.js"></script>
|
||||
<script type="module" src="display_code.js"></script>
|
||||
<script type="module" src="counter.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
+5989
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>OWL Playground</title>
|
||||
<link rel="icon" href="data:,">
|
||||
|
||||
<script src="libs/ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/solid.css" integrity="sha384-QokYePQSOwpBDuhlHOsX0ymF6R/vLk/UQVz3WHa6wygxI5oGTmDTv8wahFOSspdm" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/fontawesome.css" integrity="sha384-vd1e11sR28tEK9YANUtpIOdjGW14pS87bUBuOIoBILVWLFnS+MCX9T6MMf0VdPGq" crossorigin="anonymous">
|
||||
|
||||
<!-- Application JS/CSS -->
|
||||
<link rel="stylesheet" href="playground.css">
|
||||
<script type="module" src="playground.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -20,7 +20,7 @@ body {
|
||||
display: grid;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
grid-template-columns: auto 8px 100%;
|
||||
grid-template-columns: auto 8px 1fr;
|
||||
}
|
||||
|
||||
/* LEFT BAR ****************************************/
|
||||
@@ -1,7 +1,19 @@
|
||||
import { SAMPLES } from "./samples.js";
|
||||
import { debounce, loadJS } from "./utils.js";
|
||||
import { exportStandaloneApp } from "./export_snippet.js";
|
||||
const { useState, useRef, onMounted, onWillUnmount, onPatched, onWillUpdateProps } = owl;
|
||||
import {
|
||||
App,
|
||||
Component,
|
||||
useState,
|
||||
useRef,
|
||||
onMounted,
|
||||
onWillUnmount,
|
||||
onPatched,
|
||||
onWillUpdateProps,
|
||||
loadFile as _loadFile,
|
||||
whenReady,
|
||||
__info__,
|
||||
useEffect,
|
||||
onWillStart,
|
||||
} from "../owl.js";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Constants, helpers, utils
|
||||
@@ -16,40 +28,38 @@ const MODES = {
|
||||
const DEFAULT_XML = `<templates>
|
||||
</templates>`;
|
||||
|
||||
// Memoize loadFile so that samples aren't reloaded whenever a sample is selected
|
||||
const fileCache = {};
|
||||
const loadFile = (path) => {
|
||||
if (!(path in fileCache)) {
|
||||
fileCache[path] = _loadFile(path);
|
||||
}
|
||||
return fileCache[path];
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an iframe, with all the js, css and xml properly injected.
|
||||
*/
|
||||
function makeCodeIframe(js, css, xml) {
|
||||
const sanitizedXML = xml.replace(/<!--[\s\S]*?-->/g, "").replace(/`/g, '\\\`');
|
||||
// escape backticks in the xml so they don't close the template string
|
||||
const escapedXml = xml.replace(/`/g, '\\\`');
|
||||
|
||||
// create iframe
|
||||
const iframe = document.createElement("iframe");
|
||||
|
||||
iframe.onload = () => {
|
||||
const doc = iframe.contentDocument;
|
||||
const utilsMod = doc.createElement("script");
|
||||
utilsMod.setAttribute("type", "module");
|
||||
utilsMod.setAttribute("src", "utils.js");
|
||||
doc.head.appendChild(utilsMod);
|
||||
// inject js
|
||||
const owlScript = doc.createElement("script");
|
||||
owlScript.type = "text/javascript";
|
||||
owlScript.src = "../owl.js";
|
||||
owlScript.addEventListener("load", () => {
|
||||
const script = doc.createElement("script");
|
||||
script.type = "module";
|
||||
const content = `
|
||||
(async function(TEMPLATES) {
|
||||
${js}
|
||||
})(\`${sanitizedXML}\`)`;
|
||||
script.innerHTML = content;
|
||||
doc.body.appendChild(script);
|
||||
});
|
||||
doc.head.appendChild(owlScript);
|
||||
|
||||
// inject css
|
||||
const importMap = doc.createElement("script");
|
||||
importMap.type = "importmap";
|
||||
importMap.textContent = `{ "imports": { "@odoo/owl": "../owl.js" } }`;
|
||||
doc.head.appendChild(importMap);
|
||||
|
||||
const script = doc.createElement("script");
|
||||
script.type = "module";
|
||||
script.textContent = `const TEMPLATES = \`${escapedXml}\`\n${js}`;
|
||||
doc.body.appendChild(script);
|
||||
|
||||
const style = document.createElement("style");
|
||||
style.innerHTML = css;
|
||||
style.innerText = css;
|
||||
doc.head.appendChild(style);
|
||||
};
|
||||
return iframe;
|
||||
@@ -58,53 +68,84 @@ function makeCodeIframe(js, css, xml) {
|
||||
//------------------------------------------------------------------------------
|
||||
// SAMPLES
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const SAMPLES = [
|
||||
{
|
||||
description: "Components",
|
||||
folder: "components",
|
||||
code: ["js", "xml", "css"],
|
||||
},
|
||||
{
|
||||
description: "Form Input Bindings",
|
||||
folder: "form",
|
||||
code: ["js", "xml"],
|
||||
},
|
||||
{
|
||||
description: "Inline templates",
|
||||
folder: "single_file_component",
|
||||
code: ["js"],
|
||||
},
|
||||
{
|
||||
description: "Lifecycle demo",
|
||||
folder: "lifecycle_demo",
|
||||
code: ["js", "xml", "css"],
|
||||
},
|
||||
{
|
||||
description: "Customized hook",
|
||||
folder: "custom_hooks",
|
||||
code: ["js", "xml", "css"],
|
||||
},
|
||||
{
|
||||
description: "Todo List App (with reactivity)",
|
||||
folder: "todo_app",
|
||||
code: ["js", "xml", "css"],
|
||||
},
|
||||
{
|
||||
description: "Responsive app",
|
||||
folder: "responsive_app",
|
||||
code: ["js", "xml", "css"],
|
||||
},
|
||||
{
|
||||
description: "Slots And Generic Components",
|
||||
folder: "slots",
|
||||
code: ["js", "xml", "css"],
|
||||
},
|
||||
{
|
||||
description: "Window Management System",
|
||||
folder: "window_manager",
|
||||
code: ["js", "xml", "css"],
|
||||
},
|
||||
{
|
||||
description: "Benchmark example",
|
||||
folder: "benchmark",
|
||||
code: ["js", "xml", "css"],
|
||||
},
|
||||
]
|
||||
|
||||
function loadSamples() {
|
||||
let result = SAMPLES.slice();
|
||||
const result = SAMPLES.map(({ description, folder, code }) => ({
|
||||
description,
|
||||
code: async () => Object.fromEntries(
|
||||
await Promise.all(
|
||||
code.map(async (type) => [type, await loadFile(`./samples/${folder}/${folder}.${type}`)])
|
||||
)
|
||||
),
|
||||
}));
|
||||
const localSample = localStorage.getItem("owl-playground-local-sample");
|
||||
if (localSample) {
|
||||
const { js, css, xml } = JSON.parse(localSample);
|
||||
result.unshift({
|
||||
description: "Local Storage Code",
|
||||
code: js,
|
||||
xml,
|
||||
css
|
||||
code: () => Promise.resolve({ js, xml, css }),
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function saveLocalSample(js, css, xml) {
|
||||
const str = JSON.stringify({ js, css, xml });
|
||||
localStorage.setItem("owl-playground-local-sample", str);
|
||||
}
|
||||
|
||||
function deleteLocalSample() {
|
||||
localStorage.removeItem("owl-playground-local-sample");
|
||||
}
|
||||
|
||||
function useSamples() {
|
||||
const samples = loadSamples();
|
||||
const component = owl.useComponent();
|
||||
let interval;
|
||||
|
||||
onMounted(() => {
|
||||
const state = component.state;
|
||||
interval = setInterval(() => {
|
||||
if (component.isDirty) {
|
||||
saveLocalSample(state.js, state.css, state.xml);
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
onWillUnmount(() => {
|
||||
clearInterval(interval);
|
||||
});
|
||||
return samples;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Tabbed editor
|
||||
//------------------------------------------------------------------------------
|
||||
class TabbedEditor extends owl.Component {
|
||||
class TabbedEditor extends Component {
|
||||
setup() {
|
||||
const props = this.props;
|
||||
this.state = useState({
|
||||
@@ -168,7 +209,7 @@ class TabbedEditor extends owl.Component {
|
||||
const resizer = ev => {
|
||||
const delta = ev.clientY - y;
|
||||
y = ev.clientY;
|
||||
this.trigger("updatePanelHeight", { delta });
|
||||
this.props.updatePanelHeight({ delta });
|
||||
};
|
||||
document.body.addEventListener("mousemove", resizer);
|
||||
document.body.addEventListener("mouseup", () => {
|
||||
@@ -205,36 +246,53 @@ TabbedEditor.template = "TabbedEditor";
|
||||
//------------------------------------------------------------------------------
|
||||
// MAIN APP
|
||||
//------------------------------------------------------------------------------
|
||||
class App extends owl.Component {
|
||||
class Playground extends Component {
|
||||
static template = "Playground";
|
||||
static components = { TabbedEditor };
|
||||
setup() {
|
||||
this.version = owl.__info__.version;
|
||||
this.SAMPLES = useSamples();
|
||||
this.isDirty = false;
|
||||
this.version = __info__.version;
|
||||
|
||||
this.isDirty = false;
|
||||
this.state = useState({
|
||||
js: this.SAMPLES[0].code,
|
||||
css: this.SAMPLES[0].css || "",
|
||||
xml: this.SAMPLES[0].xml || DEFAULT_XML,
|
||||
js: "",
|
||||
css: "",
|
||||
xml: DEFAULT_XML,
|
||||
displayWelcome: true,
|
||||
splitLayout: true,
|
||||
leftPaneWidth: Math.ceil(window.innerWidth / 2),
|
||||
topPanelHeight: null
|
||||
});
|
||||
|
||||
this.toggleLayout = debounce(this.toggleLayout, 250, true);
|
||||
this.runCode = debounce(this.runCode, 250, true);
|
||||
this.downloadCode = debounce(this.downloadCode, 250, true);
|
||||
this.content = useRef("content");
|
||||
this.updateCode = this.updateCode.bind(this);
|
||||
this.samples = loadSamples();
|
||||
if (window.location.hash) {
|
||||
try {
|
||||
const { js, css, xml } = JSON.parse(atob(decodeURIComponent(window.location.hash.slice(1))));
|
||||
if (![js, css, xml].every(item => typeof item === "string")) {
|
||||
return;
|
||||
if ([js, css, xml].every(item => typeof item === "string")) {
|
||||
Object.assign(this.state, { js, css, xml });
|
||||
}
|
||||
Object.assign(this.state, { js, css, xml });
|
||||
} catch {}
|
||||
}
|
||||
onWillStart(async () => {
|
||||
if (!this.state.js) {
|
||||
this.setSample(await this.samples[0].code());
|
||||
}
|
||||
})
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
if (this.isDirty) {
|
||||
const { js, css, xml } = this.state;
|
||||
const str = JSON.stringify({ js, css, xml });
|
||||
localStorage.setItem("owl-playground-local-sample", str);
|
||||
}
|
||||
}, 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, () => []);
|
||||
|
||||
this.toggleLayout = debounce(this.toggleLayout, 250, true);
|
||||
this.runCode = debounce(this.runCode, 250, true);
|
||||
this.exportStandaloneApp = debounce(this.exportStandaloneApp, 250, true);
|
||||
this.content = useRef("content");
|
||||
this.updateCode = this.updateCode.bind(this);
|
||||
}
|
||||
|
||||
runCode() {
|
||||
@@ -258,12 +316,11 @@ class App extends owl.Component {
|
||||
window.location.href = link.href;
|
||||
}
|
||||
|
||||
setSample(ev) {
|
||||
const sample = this.SAMPLES.find(s => s.description === ev.target.value);
|
||||
this.state.js = sample.code;
|
||||
setSample(sample) {
|
||||
this.state.js = sample.js;
|
||||
this.state.css = sample.css || "";
|
||||
this.state.xml = sample.xml || DEFAULT_XML;
|
||||
deleteLocalSample();
|
||||
localStorage.removeItem("owl-playground-local-sample");
|
||||
this.isDirty = false;
|
||||
}
|
||||
|
||||
@@ -271,14 +328,14 @@ class App extends owl.Component {
|
||||
return `width:${this.state.leftPaneWidth}px`;
|
||||
}
|
||||
|
||||
get rightPaneStyle() {
|
||||
return `width:${window.innerWidth - 6 - this.state.leftPaneWidth}px`;
|
||||
}
|
||||
|
||||
get topEditorStyle() {
|
||||
return `flex: 0 0 ${this.state.topPanelHeight}px`;
|
||||
}
|
||||
|
||||
async onSampleChange(ev) {
|
||||
this.setSample(await this.samples.find(s => s.description === ev.target.value).code());
|
||||
}
|
||||
|
||||
onMouseDown() {
|
||||
const resizer = ev => {
|
||||
this.state.leftPaneWidth = ev.clientX;
|
||||
@@ -305,39 +362,40 @@ class App extends owl.Component {
|
||||
toggleLayout() {
|
||||
this.state.splitLayout = !this.state.splitLayout;
|
||||
}
|
||||
updatePanelHeight(ev) {
|
||||
if (!ev.detail.delta) {
|
||||
return;
|
||||
}
|
||||
let height = this.state.topPanelHeight;
|
||||
if (!height) {
|
||||
height = document.getElementsByClassName("tabbed-editor")[0].clientHeight;
|
||||
}
|
||||
this.state.topPanelHeight = height + ev.detail.delta;
|
||||
updatePanelHeight({ delta }) {
|
||||
let height = this.state.topPanelHeight || document.querySelector(".tabbed-editor").clientHeight;
|
||||
this.state.topPanelHeight = height + delta;
|
||||
}
|
||||
|
||||
async downloadCode() {
|
||||
async exportStandaloneApp() {
|
||||
const { js, css, xml } = this.state;
|
||||
const content = await exportStandaloneApp(js, css, xml);
|
||||
|
||||
await loadJS("libs/jszip.min.js");
|
||||
const zip = new JSZip();
|
||||
zip.file("app.py", await loadFile("./standalone_app/app.py"));
|
||||
zip.file("index.html", await loadFile("./standalone_app/index.html"));
|
||||
zip.file("owl.js", await loadFile("../owl.js"));
|
||||
zip.file("app.js", `const TEMPLATES = await (await fetch('app.xml')).text();\n${js}`);
|
||||
zip.file("app.css", css);
|
||||
zip.file("app.xml", xml);
|
||||
|
||||
await loadJS("libs/FileSaver.min.js");
|
||||
saveAs(content, "app.zip");
|
||||
saveAs(await zip.generateAsync({ type: "blob" }), "app.zip");
|
||||
}
|
||||
}
|
||||
App.components = { TabbedEditor };
|
||||
App.template = "App";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Application initialization
|
||||
//------------------------------------------------------------------------------
|
||||
async function start() {
|
||||
document.title = `${document.title} (v${owl.__info__.version})`;
|
||||
const commit = `https://github.com/odoo/owl/commit/${owl.__info__.hash}`;
|
||||
document.title = `${document.title} (v${__info__.version})`;
|
||||
const commit = `https://github.com/odoo/owl/commit/${__info__.hash}`;
|
||||
console.info(`This application is using Owl built with the following commit:`, commit);
|
||||
const [templates] = await Promise.all([
|
||||
owl.loadFile("templates.xml"),
|
||||
owl.whenReady()
|
||||
loadFile("templates.xml"),
|
||||
whenReady()
|
||||
]);
|
||||
const rootApp = new owl.App(App);
|
||||
const rootApp = new App(Playground, { name: "Owl Playground" });
|
||||
rootApp.addTemplates(templates);
|
||||
|
||||
await rootApp.mount(document.body);
|
||||
@@ -0,0 +1,11 @@
|
||||
tr.danger {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.remove:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
.remove {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import { Component, mount, useState, onPatched} from "@odoo/owl";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Data generation
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
let idCounter = 1;
|
||||
const adjectives = [
|
||||
"pretty", "large", "big", "small", "tall", "short", "long", "handsome", "plain",
|
||||
"quaint", "clean", "elegant", "easy", "angry", "crazy", "helpful", "mushy", "odd",
|
||||
"unsightly", "adorable", "important", "inexpensive", "cheap", "expensive", "fancy"];
|
||||
const colours = ["red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black", "orange"];
|
||||
const nouns = ["table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger", "pizza", "mouse", "keyboard"];
|
||||
|
||||
function _random (max) { return Math.round(Math.random() * 1000) % max; };
|
||||
|
||||
function buildData(count) {
|
||||
const data = new Array(count);
|
||||
for (let i = 0; i < count; i++) {
|
||||
const label = `${adjectives[_random(adjectives.length)]} ${colours[_random(colours.length)]} ${nouns[_random(nouns.length)]}`;
|
||||
data[i] = {
|
||||
id: idCounter++,
|
||||
label,
|
||||
};
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Components
|
||||
// -----------------------------------------------------------------------------
|
||||
class Button extends Component {
|
||||
static template = "Button";
|
||||
}
|
||||
|
||||
|
||||
class Row extends Component {
|
||||
static template = "Row";
|
||||
}
|
||||
|
||||
class Root extends Component {
|
||||
static template = "Root";
|
||||
static components = { Button, Row };
|
||||
|
||||
setup() {
|
||||
this.state = useState({
|
||||
rows: [],
|
||||
selectedRowId: null
|
||||
});
|
||||
this.benchmarking = false;
|
||||
onPatched(() => {
|
||||
if (this.benchmarking) {
|
||||
this.stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
start(descr) {
|
||||
this.benchmarking = `[${descr}]`;
|
||||
console.time(this.benchmarking);
|
||||
}
|
||||
stop() {
|
||||
console.timeEnd(this.benchmarking);
|
||||
this.benchmarking = false;
|
||||
}
|
||||
|
||||
run() {
|
||||
this.start('add1000');
|
||||
this.state.rows = buildData(1000);
|
||||
this.state.selectedRowId = null;
|
||||
}
|
||||
|
||||
runLots() {
|
||||
this.start('add10_000');
|
||||
this.state.rows = buildData(10_000);
|
||||
this.state.selectedRowId = null;
|
||||
}
|
||||
|
||||
add() {
|
||||
this.start('append1000');
|
||||
this.state.rows = this.state.rows.concat(buildData(1000));
|
||||
}
|
||||
|
||||
update() {
|
||||
this.start('update1/10th');
|
||||
let index = 0;
|
||||
const rows = this.state.rows;
|
||||
while (index < rows.length) {
|
||||
rows[index].label = rows[index].label + " !!!";
|
||||
index += 10;
|
||||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.start('clear');
|
||||
this.state.rows = [];
|
||||
this.state.selectedRowId = null;
|
||||
}
|
||||
|
||||
swapRows() {
|
||||
this.start('swap');
|
||||
const rows = this.state.rows;
|
||||
if (rows.length > 998) {
|
||||
let tmp = rows[1];
|
||||
rows[1] = rows[998];
|
||||
rows[998] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
selectRow(id) {
|
||||
this.start('select');
|
||||
this.state.selectedRowId = id;
|
||||
}
|
||||
|
||||
removeRow(id) {
|
||||
this.start('remove1');
|
||||
const rows = this.state.rows;
|
||||
rows.splice(rows.findIndex(row => row.id === id), 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// dev=false for benchmarking. we don't want to benchmark dev code!
|
||||
mount(Root, document.body, { templates: TEMPLATES, dev: false });
|
||||
@@ -0,0 +1,52 @@
|
||||
<templates>
|
||||
<t t-name="Button">
|
||||
<div class='col-sm-6 smallpad'>
|
||||
<button t-att-id="props.id" class='btn btn-primary btn-block' type='button' t-on-click="props.onClick">
|
||||
<t t-esc="props.text"/>
|
||||
</button>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="Row">
|
||||
<tr t-att-class="props.isSelected ? 'danger' : ''">
|
||||
<td class="col-md-1" t-esc="props.row.id" />
|
||||
<td class="col-md-4">
|
||||
<a t-on-click="() => props.onSelect(props.row.id)" t-esc="props.row.label" />
|
||||
</td>
|
||||
<td class="col-md-1">
|
||||
<a t-on-click="() => props.onRemove(props.row.id)" class="remove">[x]
|
||||
<span class='glyphicon glyphicon-remove' aria-hidden="true" />
|
||||
</a>
|
||||
</td>
|
||||
<td class='col-md-6'/>
|
||||
</tr>
|
||||
</t>
|
||||
<t t-name="Root">
|
||||
<div class='container'>
|
||||
<div class='jumbotron'>
|
||||
<div class='row'>
|
||||
<div class='col-md-6'>
|
||||
<h1>Owl Keyed</h1>
|
||||
</div>
|
||||
<div class='col-md-6'>
|
||||
<div class='row'>
|
||||
<Button id="'run'" onClick.bind="run" text="'Create 1,000 rows'" />
|
||||
<Button id="'runlots'" onClick.bind="runLots" text="'Create 10,000 rows'" />
|
||||
<Button id="'add'" onClick.bind="add" text="'Append 1,000 rows'" />
|
||||
<Button id="'update'" onClick.bind="update" text="'Update every 10th row'" />
|
||||
<Button id="'clear'" onClick.bind="clear" text="'Clear'" />
|
||||
<Button id="'swaprows'" onClick.bind="swapRows" text="'Swap Rows'" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<table class='table table-hover table-striped test-data'>
|
||||
<tbody>
|
||||
<t t-foreach="state.rows" t-as="row" t-key="row.id">
|
||||
<Row row="row" isSelected="row.id === state.selectedRowId" onSelect.bind="selectRow" onRemove.bind="removeRow"/>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
<span class='preloadicon glyphicon glyphicon-remove' aria-hidden="true" />
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,10 @@
|
||||
.greeter {
|
||||
font-size: 20px;
|
||||
width: 300px;
|
||||
height: 100px;
|
||||
margin: 5px;
|
||||
text-align: center;
|
||||
line-height: 100px;
|
||||
background-color: #eeeeee;
|
||||
user-select: none;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// In this example, we show how components can be defined and created.
|
||||
import { Component, useState, mount } from "@odoo/owl";
|
||||
|
||||
class Greeter extends Component {
|
||||
static template = "Greeter";
|
||||
|
||||
setup() {
|
||||
this.state = useState({ word: 'Hello' });
|
||||
}
|
||||
|
||||
toggle() {
|
||||
this.state.word = this.state.word === 'Hi' ? 'Hello' : 'Hi';
|
||||
}
|
||||
}
|
||||
|
||||
// Main root component
|
||||
class Root extends Component {
|
||||
static components = { Greeter };
|
||||
static template = "Root"
|
||||
|
||||
setup() {
|
||||
this.state = useState({ name: 'World'});
|
||||
}
|
||||
}
|
||||
|
||||
mount(Root, document.body, { templates: TEMPLATES, dev: true });
|
||||
@@ -0,0 +1,9 @@
|
||||
<templates>
|
||||
<div t-name="Greeter" class="greeter" t-on-click="toggle">
|
||||
<t t-esc="state.word"/>, <t t-esc="props.name"/>
|
||||
</div>
|
||||
|
||||
<t t-name="Root">
|
||||
<Greeter name="state.name"/>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
button {
|
||||
width: 120px;
|
||||
height: 35px;
|
||||
font-size: 16px;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// In this example, we show how hooks can be used or defined.
|
||||
import { Component, mount, useState, onWillDestroy } from "@odoo/owl";
|
||||
|
||||
// We define here a custom behaviour: this hook tracks the state of the mouse
|
||||
// position
|
||||
function useMouse() {
|
||||
const position = useState({x:0, y: 0});
|
||||
|
||||
function update(e) {
|
||||
position.x = e.clientX;
|
||||
position.y = e.clientY;
|
||||
}
|
||||
window.addEventListener('mousemove', update);
|
||||
onWillDestroy(() => {
|
||||
window.removeEventListener('mousemove', update);
|
||||
});
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
|
||||
// Main root component
|
||||
class Root extends Component {
|
||||
static template = "Root";
|
||||
|
||||
setup() {
|
||||
// simple state hook (reactive object)
|
||||
this.counter = useState({ value: 0 });
|
||||
|
||||
// this hooks is bound to the 'mouse' property.
|
||||
this.mouse = useMouse();
|
||||
}
|
||||
|
||||
increment() {
|
||||
this.counter.value++;
|
||||
}
|
||||
}
|
||||
|
||||
// Application setup
|
||||
mount(Root, document.body, { templates: TEMPLATES, dev: true });
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
<templates>
|
||||
<div t-name="Root">
|
||||
<button t-on-click="increment">Click! <t t-esc="counter.value"/></button>
|
||||
<div>Mouse: <t t-esc="mouse.x"/>, <t t-esc="mouse.y"/></div>
|
||||
</div>
|
||||
</templates>
|
||||
@@ -0,0 +1,22 @@
|
||||
// This example illustrate how the t-model directive can be used to synchronize
|
||||
// data between html inputs (and select/textareas) and the state of a component.
|
||||
// Note that there are two controls with t-model="color": they are totally
|
||||
// synchronized.
|
||||
import { Component, useState, mount } from "@odoo/owl";
|
||||
|
||||
class Form extends Component {
|
||||
static template = "Form";
|
||||
|
||||
setup() {
|
||||
this.state = useState({
|
||||
text: "",
|
||||
othertext: "",
|
||||
number: 11,
|
||||
color: "",
|
||||
bool: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Application setup
|
||||
mount(Form, document.body, { templates: TEMPLATES, dev: true });
|
||||
@@ -0,0 +1,37 @@
|
||||
<templates>
|
||||
<div t-name="Form">
|
||||
<h1>Form</h1>
|
||||
<div>
|
||||
Text (immediate): <input t-model="state.text"/>
|
||||
</div>
|
||||
<div>
|
||||
Other text (lazy): <input t-model.lazy="state.othertext"/>
|
||||
</div>
|
||||
<div>
|
||||
Number: <input t-model.number="state.number"/>
|
||||
</div>
|
||||
<div>
|
||||
Boolean: <input type="checkbox" t-model="state.bool"/>
|
||||
</div>
|
||||
<div>
|
||||
Color, with a select:
|
||||
<select t-model="state.color">
|
||||
<option value="">Select a color</option>
|
||||
<option value="red">Red</option>
|
||||
<option value="blue">Blue</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
Color, with radio buttons:
|
||||
<span><input type="radio" name="color" id="red" value="red" t-model="state.color"/><label for="red">Red</label></span>
|
||||
<span><input type="radio" name="color" id="blue" value="blue" t-model="state.color"/><label for="blue">Blue</label></span>
|
||||
</div>
|
||||
<hr/>
|
||||
<h1>State</h1>
|
||||
<div>Text: <t t-esc="state.text"/></div>
|
||||
<div>Other Text: <t t-esc="state.othertext"/></div>
|
||||
<div>Number: <t t-esc="state.number"/></div>
|
||||
<div>Boolean: <t t-if="state.bool">True</t><t t-else="">False</t></div>
|
||||
<div>Color: <t t-esc="state.color"/></div>
|
||||
</div>
|
||||
</templates>
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"owl": ["../../../src/index.ts"],
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
button {
|
||||
font-size: 18px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.demo {
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
background-color: #dddddd;
|
||||
width: 250px;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// This example shows all the possible lifecycle hooks
|
||||
//
|
||||
// The root component controls a sub component (DemoComponent). It logs all its lifecycle
|
||||
// methods in the console. Try modifying its state by clicking on it, or by
|
||||
// clicking on the two main buttons, and look into the console to see what
|
||||
// happens.
|
||||
import {
|
||||
Component,
|
||||
useState,
|
||||
mount,
|
||||
useComponent,
|
||||
onWillStart,
|
||||
onMounted,
|
||||
onWillUnmount,
|
||||
onWillUpdateProps,
|
||||
onPatched,
|
||||
onWillPatch,
|
||||
onWillRender,
|
||||
onRendered,
|
||||
onWillDestroy,
|
||||
} from "@odoo/owl";
|
||||
|
||||
function useLogLifecycle() {
|
||||
const component = useComponent();
|
||||
const name = component.constructor.name;
|
||||
onWillStart(() => console.log(`${name}:willStart`));
|
||||
onMounted(() => console.log(`${name}:mounted`));
|
||||
onWillUpdateProps(() => console.log(`${name}:willUpdateProps`));
|
||||
onWillRender(() => console.log(`${name}:willRender`));
|
||||
onRendered(() => console.log(`${name}:rendered`));
|
||||
onWillPatch(() => console.log(`${name}:willPatch`));
|
||||
onPatched(() => console.log(`${name}:patched`));
|
||||
onWillUnmount(() => console.log(`${name}:willUnmount`));
|
||||
onWillDestroy(() => console.log(`${name}:willDestroy`));
|
||||
}
|
||||
|
||||
class DemoComponent extends Component {
|
||||
static template = "DemoComponent";
|
||||
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
this.state = useState({ n: 0 });
|
||||
}
|
||||
increment() {
|
||||
this.state.n++;
|
||||
}
|
||||
}
|
||||
|
||||
class Root extends Component {
|
||||
static template = "Root";
|
||||
static components = { DemoComponent };
|
||||
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
this.state = useState({ n: 0, flag: true });
|
||||
}
|
||||
|
||||
increment() {
|
||||
this.state.n++;
|
||||
}
|
||||
|
||||
toggleSubComponent() {
|
||||
this.state.flag = !this.state.flag;
|
||||
}
|
||||
}
|
||||
|
||||
mount(Root, document.body, { templates: TEMPLATES, dev: true });
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<templates>
|
||||
<div t-name="DemoComponent" t-on-click="increment" class="demo">
|
||||
<div>Demo Sub Component</div>
|
||||
<div>(click on me to update me)</div>
|
||||
<div>Props: <t t-esc="props.n"/>, State: <t t-esc="state.n"/>. </div>
|
||||
</div>
|
||||
|
||||
<div t-name="Root">
|
||||
<button t-on-click="increment">Increment Parent State</button>
|
||||
<button t-on-click="toggleSubComponent">Toggle SubComponent</button>
|
||||
<div t-if="state.flag">
|
||||
<DemoComponent n="state.n"/>
|
||||
</div>
|
||||
</div>
|
||||
</templates>
|
||||
@@ -0,0 +1,56 @@
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.app {
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.app.desktop {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
flex: 0 0 30px;
|
||||
height: 30px;
|
||||
background-color: cadetblue;
|
||||
color: white;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.controlpanel {
|
||||
flex: 0 0 100px;
|
||||
height: 100px;
|
||||
background-color: #dddddd;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
flex: 1 1 auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.formview {
|
||||
overflow-y: auto;
|
||||
flex: 1 1 60%;
|
||||
min-height: 200px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.chatter {
|
||||
overflow-y: auto;
|
||||
flex: 1 1 40%;
|
||||
background-color: #eeeeee;
|
||||
color: #333333;
|
||||
padding: 8px;
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
// In this example, we show how one can design an application that is responsive:
|
||||
// its UI is different in mobile mode or in desktop mode.
|
||||
//
|
||||
// The main idea is to have a "isMobile" key in the environment, then listen
|
||||
// to resize events and update the env if needed. Then, the whole interface
|
||||
// will be updated, creating and destroying components as needed.
|
||||
//
|
||||
// To see this in action, try resizing the window. The application will switch
|
||||
// to mobile mode whenever it has less than 768px.
|
||||
import { Component, useState, mount, reactive, useEnv } from "@odoo/owl";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
function debounce(func, wait, immediate) {
|
||||
let timeout;
|
||||
return function () {
|
||||
const context = this;
|
||||
const args = arguments;
|
||||
function later() {
|
||||
timeout = null;
|
||||
if (!immediate) {
|
||||
func.apply(context, args);
|
||||
}
|
||||
}
|
||||
const callNow = immediate && !timeout;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
if (callNow) {
|
||||
func.apply(context, args);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Responsive hook
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
function createUI() {
|
||||
const getIsMobile = () => window.innerWidth <= 768;
|
||||
|
||||
const ui = reactive({ isMobile: getIsMobile() });
|
||||
|
||||
const updateEnv = debounce(() => {
|
||||
const isMobile = getIsMobile();
|
||||
if (ui.isMobile !== isMobile) {
|
||||
ui.isMobile = isMobile;
|
||||
}
|
||||
}, 15);
|
||||
window.addEventListener("resize", updateEnv);
|
||||
return ui;
|
||||
}
|
||||
|
||||
function useUI() {
|
||||
const env = useEnv();
|
||||
return useState(env.ui);
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Components
|
||||
//------------------------------------------------------------------------------
|
||||
class Navbar extends Component {
|
||||
static template = "Navbar";
|
||||
}
|
||||
|
||||
class MobileSearchView extends Component {
|
||||
static template = "MobileSearchView";
|
||||
}
|
||||
|
||||
class ControlPanel extends Component {
|
||||
static template = "ControlPanel";
|
||||
static components = { MobileSearchView };
|
||||
setup() {
|
||||
this.ui = useUI();
|
||||
}
|
||||
}
|
||||
|
||||
class AdvancedComponent extends Component {
|
||||
static template = "AdvancedComponent";
|
||||
}
|
||||
|
||||
class FormView extends Component {
|
||||
static template = "FormView";
|
||||
static components = { AdvancedComponent };
|
||||
setup() {
|
||||
this.ui = useUI();
|
||||
}
|
||||
}
|
||||
|
||||
class Chatter extends Component {
|
||||
static template = "Chatter";
|
||||
|
||||
setup() {
|
||||
this.messages = Array.from(Array(100).keys());
|
||||
}
|
||||
}
|
||||
|
||||
class Root extends Component {
|
||||
static template = "Root";
|
||||
static components = { Navbar, ControlPanel, FormView, Chatter };
|
||||
|
||||
setup() {
|
||||
this.ui = useUI();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Application Startup
|
||||
//------------------------------------------------------------------------------
|
||||
const env = {
|
||||
ui: createUI()
|
||||
};
|
||||
|
||||
mount(Root, document.body, { templates: TEMPLATES, env, dev: true });
|
||||
@@ -0,0 +1,42 @@
|
||||
<templates>
|
||||
<div t-name="Navbar" class="navbar">Navbar</div>
|
||||
|
||||
<div t-name="ControlPanel" class="controlpanel">
|
||||
<h2>Control Panel</h2>
|
||||
<MobileSearchView t-if="ui.isMobile" />
|
||||
</div>
|
||||
|
||||
<div t-name="FormView" class="formview">
|
||||
<h2>Form View</h2>
|
||||
<AdvancedComponent t-if="!ui.isMobile" />
|
||||
</div>
|
||||
|
||||
<div t-name="Chatter" class="chatter">
|
||||
<h2>Chatter</h2>
|
||||
<t t-foreach="messages" t-as="item" t-key="item"><div>Message <t t-esc="item"/></div></t>
|
||||
</div>
|
||||
|
||||
<div t-name="MobileSearchView">Mobile searchview</div>
|
||||
|
||||
<div t-name="AdvancedComponent">
|
||||
This component is only created in desktop mode.
|
||||
<button>Button!</button>
|
||||
</div>
|
||||
|
||||
<t t-name="maincontent">
|
||||
<FormView />
|
||||
<Chatter />
|
||||
</t>
|
||||
<div t-name="Root" class="app" t-att-class="{mobile: ui.isMobile, desktop: !ui.isMobile}">
|
||||
<Navbar/>
|
||||
<ControlPanel/>
|
||||
<div class="content-wrapper" t-if="!ui.isMobile">
|
||||
<div class="content">
|
||||
<t t-call="maincontent"/>
|
||||
</div>
|
||||
</div>
|
||||
<t t-else="">
|
||||
<t t-call="maincontent"/>
|
||||
</t>
|
||||
</div>
|
||||
</templates>
|
||||
@@ -0,0 +1,28 @@
|
||||
// This example illustrates how one can write Owl components with
|
||||
// inline templates.
|
||||
|
||||
import { Component, useState, xml, mount } from "@odoo/owl";
|
||||
|
||||
// Counter component
|
||||
class Counter extends Component {
|
||||
static template = xml`
|
||||
<button t-on-click="() => state.value++">
|
||||
Click! [<t t-esc="state.value"/>]
|
||||
</button>`;
|
||||
|
||||
state = useState({ value: 0 })
|
||||
}
|
||||
|
||||
// Root
|
||||
class Root extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<Counter/>
|
||||
<Counter/>
|
||||
</div>`;
|
||||
|
||||
static components = { Counter };
|
||||
}
|
||||
|
||||
// Application setup
|
||||
mount(Root, document.body, { templates: TEMPLATES, dev: true});
|
||||
@@ -0,0 +1,43 @@
|
||||
.main {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #eeeeee;
|
||||
width: 200px;
|
||||
height: 100px;
|
||||
margin: 10px;
|
||||
border: 1px solid gray;
|
||||
}
|
||||
|
||||
.card.full {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.card.small {
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
flex: 0 0 25px;
|
||||
font-weight: bold;
|
||||
background-color: darkcyan;
|
||||
color: white;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.card-title button {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
flex: 1 1 auto;
|
||||
padding: 5px;
|
||||
border-top: 1px solid white;
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
border-top: 1px solid white;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// We show here how slots can be used to create generic components.
|
||||
// In this example, the Card component is basically only a container. It is not
|
||||
// aware of its content. It just knows where it should be (with t-slot).
|
||||
// The parent component define the content with t-set-slot.
|
||||
//
|
||||
// Note that the t-on-click event, defined in the Root template, is executed in
|
||||
// the context of the Root component, even though it is inside the Card component
|
||||
import { Component, useState, mount } from "@odoo/owl";
|
||||
|
||||
class Card extends Component {
|
||||
static template = "Card";
|
||||
|
||||
setup() {
|
||||
this.state = useState({ showContent: true });
|
||||
}
|
||||
|
||||
toggleDisplay() {
|
||||
this.state.showContent = !this.state.showContent;
|
||||
}
|
||||
}
|
||||
|
||||
class Counter extends Component {
|
||||
static template = "Counter";
|
||||
|
||||
setup() {
|
||||
this.state = useState({val: 1});
|
||||
}
|
||||
|
||||
inc() {
|
||||
this.state.val++;
|
||||
}
|
||||
}
|
||||
|
||||
// Main root component
|
||||
class Root extends Component {
|
||||
static template = "Root"
|
||||
static components = { Card, Counter };
|
||||
|
||||
setup() {
|
||||
this.state = useState({a: 1, b: 3});
|
||||
}
|
||||
|
||||
inc(key, delta) {
|
||||
this.state[key] += delta;
|
||||
}
|
||||
}
|
||||
|
||||
// Application setup
|
||||
mount(Root, document.body, { templates: TEMPLATES, dev: true});
|
||||
@@ -0,0 +1,33 @@
|
||||
<templates>
|
||||
<div t-name="Card" class="card" t-att-class="state.showContent ? 'full' : 'small'">
|
||||
<div class="card-title">
|
||||
<t t-esc="props.title"/><button t-on-click="toggleDisplay">Toggle</button>
|
||||
</div>
|
||||
<t t-if="state.showContent">
|
||||
<div class="card-content" >
|
||||
<t t-slot="content"/>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<t t-slot="footer"/>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<div t-name="Counter">
|
||||
<t t-esc="state.val"/><button t-on-click="inc">Inc</button>
|
||||
</div>
|
||||
|
||||
<div t-name="Root" class="main">
|
||||
<Card title="'Title card A'">
|
||||
<t t-set-slot="content">Content of card 1... [<t t-esc="state.a"/>]</t>
|
||||
<t t-set-slot="footer"><button t-on-click="() => this.inc('a', 1)">Increment A</button></t>
|
||||
</Card>
|
||||
<Card title="'Title card B'">
|
||||
<t t-set-slot="content">
|
||||
<div>Card 2... [<t t-esc="state.b"/>]</div>
|
||||
<Counter />
|
||||
</t>
|
||||
<t t-set-slot="footer"><button t-on-click="() => this.inc('b', -1)">Decrement B</button></t>
|
||||
</Card>
|
||||
</div>
|
||||
</templates>
|
||||
@@ -0,0 +1,379 @@
|
||||
html,body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: none;
|
||||
font-size: 100%;
|
||||
vertical-align: baseline;
|
||||
font-family: inherit;
|
||||
font-weight: inherit;
|
||||
color: inherit;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body {
|
||||
font: 14px "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
line-height: 1.4em;
|
||||
background: #f5f5f5;
|
||||
color: #4d4d4d;
|
||||
min-width: 230px;
|
||||
max-width: 550px;
|
||||
margin: 0 auto;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.todoapp {
|
||||
background: #fff;
|
||||
margin: 130px 0 40px 0;
|
||||
position: relative;
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.todoapp input::-webkit-input-placeholder {
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
color: #e6e6e6;
|
||||
}
|
||||
|
||||
.todoapp input::-moz-placeholder {
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
color: #e6e6e6;
|
||||
}
|
||||
|
||||
.todoapp input::input-placeholder {
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
color: #e6e6e6;
|
||||
}
|
||||
|
||||
.todoapp h1 {
|
||||
position: absolute;
|
||||
top: -155px;
|
||||
width: 100%;
|
||||
font-size: 100px;
|
||||
font-weight: 100;
|
||||
text-align: center;
|
||||
color: rgba(175, 47, 47, 0.15);
|
||||
-webkit-text-rendering: optimizeLegibility;
|
||||
-moz-text-rendering: optimizeLegibility;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
.new-todo,
|
||||
.edit {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
font-size: 24px;
|
||||
font-family: inherit;
|
||||
font-weight: inherit;
|
||||
line-height: 1.4em;
|
||||
border: 0;
|
||||
color: inherit;
|
||||
padding: 6px;
|
||||
border: 1px solid #999;
|
||||
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
|
||||
box-sizing: border-box;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.new-todo {
|
||||
padding: 16px 16px 16px 60px;
|
||||
border: none;
|
||||
background: rgba(0, 0, 0, 0.003);
|
||||
box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
.main {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
border-top: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
.toggle-all {
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
border: none; /* Mobile Safari */
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
right: 100%;
|
||||
bottom: 100%;
|
||||
}
|
||||
|
||||
.toggle-all + label {
|
||||
width: 60px;
|
||||
height: 34px;
|
||||
font-size: 0;
|
||||
position: absolute;
|
||||
top: -52px;
|
||||
left: -13px;
|
||||
-webkit-transform: rotate(90deg);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.toggle-all + label:before {
|
||||
content: "❯";
|
||||
font-size: 22px;
|
||||
color: #e6e6e6;
|
||||
padding: 10px 27px 10px 27px;
|
||||
}
|
||||
|
||||
.toggle-all:checked + label:before {
|
||||
color: #737373;
|
||||
}
|
||||
|
||||
.todo-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.todo-list li {
|
||||
position: relative;
|
||||
font-size: 24px;
|
||||
border-bottom: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.todo-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.todo-list li.editing {
|
||||
border-bottom: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.todo-list li.editing .edit {
|
||||
display: block;
|
||||
width: calc(100% - 43px);
|
||||
padding: 12px 16px;
|
||||
margin: 0 0 0 43px;
|
||||
}
|
||||
|
||||
.todo-list li.editing .view {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.todo-list li .toggle {
|
||||
text-align: center;
|
||||
width: 40px;
|
||||
/* auto, since non-WebKit browsers doesn't support input styling */
|
||||
height: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto 0;
|
||||
border: none; /* Mobile Safari */
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.todo-list li .toggle {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.todo-list li .toggle + label {
|
||||
/*
|
||||
Firefox requires \`#\` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433
|
||||
IE and Edge requires *everything* to be escaped to render, so we do that instead of just the \`#\` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/
|
||||
*/
|
||||
background-image: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center left;
|
||||
}
|
||||
|
||||
.todo-list li .toggle:checked + label {
|
||||
background-image: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.todo-list li label {
|
||||
word-break: break-all;
|
||||
padding: 15px 15px 15px 60px;
|
||||
display: block;
|
||||
line-height: 1.2;
|
||||
transition: color 0.4s;
|
||||
}
|
||||
|
||||
.todo-list li.completed label {
|
||||
color: #d9d9d9;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.todo-list li .destroy {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 10px;
|
||||
bottom: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin: auto 0;
|
||||
font-size: 30px;
|
||||
color: #cc9a9a;
|
||||
margin-bottom: 11px;
|
||||
transition: color 0.2s ease-out;
|
||||
}
|
||||
|
||||
.todo-list li .destroy:hover {
|
||||
color: #af5b5e;
|
||||
}
|
||||
|
||||
.todo-list li .destroy:after {
|
||||
content: "×";
|
||||
}
|
||||
|
||||
.todo-list li:hover .destroy {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.todo-list li .edit {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.todo-list li.editing:last-child {
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
color: #777;
|
||||
padding: 10px 15px;
|
||||
height: 20px;
|
||||
text-align: center;
|
||||
border-top: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
.footer:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 50px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6,
|
||||
0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6,
|
||||
0 17px 2px -6px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.todo-count {
|
||||
float: left;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.todo-count strong {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.filters {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.filters li {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.filters li a {
|
||||
color: inherit;
|
||||
margin: 3px;
|
||||
padding: 3px 7px;
|
||||
text-decoration: none;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.filters li a:hover {
|
||||
border-color: rgba(175, 47, 47, 0.1);
|
||||
}
|
||||
|
||||
.filters li a.selected {
|
||||
border-color: rgba(175, 47, 47, 0.2);
|
||||
}
|
||||
|
||||
.clear-completed,
|
||||
html .clear-completed:active {
|
||||
float: right;
|
||||
position: relative;
|
||||
line-height: 20px;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.clear-completed:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.info {
|
||||
margin: 65px auto 0;
|
||||
color: #bfbfbf;
|
||||
font-size: 10px;
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.info p {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.info a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.info a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/*
|
||||
Hack to remove background from Mobile Safari.
|
||||
Can't use it globally since it destroys checkboxes in Firefox
|
||||
*/
|
||||
@media screen and (-webkit-min-device-pixel-ratio: 0) {
|
||||
.toggle-all,
|
||||
.todo-list li .toggle {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.todo-list li .toggle {
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 430px) {
|
||||
.footer {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.filters {
|
||||
bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.filters a {
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
// This example is an implementation of the TodoList application, from the
|
||||
// www.todomvc.com project. This is a non trivial application with some
|
||||
// interesting user interactions. It uses the local storage for persistence.
|
||||
//
|
||||
// In this implementation, we use the owl reactivity mechanism.
|
||||
import { Component, useState, mount, useRef, reactive, useEnv, useEffect } from "@odoo/owl";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Constants, helpers
|
||||
//------------------------------------------------------------------------------
|
||||
const ENTER_KEY = 13;
|
||||
const ESC_KEY = 27;
|
||||
|
||||
function useAutofocus(name) {
|
||||
let ref = useRef(name);
|
||||
useEffect(el => el && el.focus(), () => [ref.el]);
|
||||
}
|
||||
|
||||
function useStore() {
|
||||
const env = useEnv();
|
||||
return useState(env.store);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Task store
|
||||
//------------------------------------------------------------------------------
|
||||
class TaskList {
|
||||
constructor(tasks) {
|
||||
this.tasks = tasks || [];
|
||||
const taskIds = this.tasks.map((t) => t.id);
|
||||
this.nextId = taskIds.length ? Math.max(...taskIds) + 1 : 1;
|
||||
}
|
||||
|
||||
addTask(text) {
|
||||
text = text.trim();
|
||||
if (text) {
|
||||
const task = {
|
||||
id: this.nextId++,
|
||||
text: text,
|
||||
isCompleted: false,
|
||||
};
|
||||
this.tasks.push(task);
|
||||
}
|
||||
}
|
||||
|
||||
toggleTask(id) {
|
||||
const task = this.tasks.find(t => t.id === id);
|
||||
task.isCompleted = !task.isCompleted;
|
||||
}
|
||||
|
||||
toggleAll(value) {
|
||||
for (let task of this.tasks) {
|
||||
task.isCompleted = value;
|
||||
}
|
||||
}
|
||||
|
||||
clearCompleted() {
|
||||
const tasks = this.tasks.filter(t => t.isCompleted);
|
||||
for (let task of tasks) {
|
||||
this.deleteTask(task.id);
|
||||
}
|
||||
}
|
||||
|
||||
deleteTask(id) {
|
||||
const index = this.tasks.findIndex((t) => t.id === id);
|
||||
this.tasks.splice(index, 1);
|
||||
}
|
||||
|
||||
updateTask(id, text) {
|
||||
const value = text.trim();
|
||||
if (!value) {
|
||||
this.deleteTask(id);
|
||||
} else {
|
||||
const task = this.tasks.find(t => t.id === id);
|
||||
task.text = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createTaskStore() {
|
||||
const saveTasks = () => localStorage.setItem("todoapp", JSON.stringify(taskStore.tasks));
|
||||
const initialTasks = JSON.parse(localStorage.getItem("todoapp") || "[]");
|
||||
const taskStore = reactive(new TaskList(initialTasks), saveTasks);
|
||||
saveTasks();
|
||||
return taskStore;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Todo
|
||||
//------------------------------------------------------------------------------
|
||||
class Todo extends Component {
|
||||
static template = "Todo";
|
||||
|
||||
setup() {
|
||||
useAutofocus("input");
|
||||
this.store = useStore();
|
||||
this.state = useState({
|
||||
isEditing: false
|
||||
});
|
||||
}
|
||||
|
||||
handleKeyup(ev) {
|
||||
if (ev.keyCode === ENTER_KEY) {
|
||||
this.updateText(ev.target.value);
|
||||
}
|
||||
if (ev.keyCode === ESC_KEY) {
|
||||
ev.target.value = this.props.text;
|
||||
this.state.isEditing = false;
|
||||
}
|
||||
}
|
||||
|
||||
handleBlur(ev) {
|
||||
this.updateText(ev.target.value);
|
||||
}
|
||||
|
||||
updateText(text) {
|
||||
this.store.updateTask(this.props.id, text);
|
||||
this.state.isEditing = false;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// TodoList
|
||||
//------------------------------------------------------------------------------
|
||||
class TodoList extends Component {
|
||||
static template = "TodoList";
|
||||
static components = { Todo };
|
||||
|
||||
setup() {
|
||||
this.store = useStore();
|
||||
this.state = useState({ filter: "all" });
|
||||
}
|
||||
|
||||
get displayedTasks() {
|
||||
const tasks = this.store.tasks;
|
||||
switch (this.state.filter) {
|
||||
case "active":
|
||||
return tasks.filter((t) => !t.isCompleted);
|
||||
case "completed":
|
||||
return tasks.filter((t) => t.isCompleted);
|
||||
case "all":
|
||||
return tasks;
|
||||
}
|
||||
}
|
||||
|
||||
get allChecked() {
|
||||
return this.store.tasks.every(todo => todo.isCompleted);
|
||||
}
|
||||
|
||||
get remaining() {
|
||||
return this.store.tasks.filter(todo => !todo.isCompleted).length;
|
||||
}
|
||||
|
||||
get remainingText() {
|
||||
const items = this.remaining < 2 ? "item" : "items";
|
||||
return ` ${items} left`;
|
||||
}
|
||||
|
||||
addTodo(ev) {
|
||||
if (ev.keyCode === ENTER_KEY) {
|
||||
const text = ev.target.value;
|
||||
if (text.trim()) {
|
||||
this.store.addTask(text);
|
||||
}
|
||||
ev.target.value = "";
|
||||
}
|
||||
}
|
||||
|
||||
setFilter(filter) {
|
||||
this.state.filter = filter;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// App Initialization
|
||||
//------------------------------------------------------------------------------
|
||||
const env = { store: createTaskStore() };
|
||||
mount(TodoList, document.body, { env, templates: TEMPLATES, dev: true });
|
||||
@@ -0,0 +1,50 @@
|
||||
<templates>
|
||||
<section t-name="TodoList" class="todoapp">
|
||||
<header class="header">
|
||||
<h1>todos</h1>
|
||||
<input class="new-todo" autofocus="true" autocomplete="off" placeholder="What needs to be done?" t-on-keyup="addTodo"/>
|
||||
</header>
|
||||
<section class="main" t-if="store.tasks.length">
|
||||
<input class="toggle-all" id="toggle-all" type="checkbox" t-att-checked="allChecked" t-on-click="() => store.toggleAll(!allChecked)"/>
|
||||
<label for="toggle-all"></label>
|
||||
<ul class="todo-list">
|
||||
<t t-foreach="displayedTasks" t-as="todo" t-key="todo.id">
|
||||
<Todo id="todo.id" isCompleted="todo.isCompleted" text="todo.text"/>
|
||||
</t>
|
||||
</ul>
|
||||
</section>
|
||||
<footer class="footer" t-if="store.tasks.length">
|
||||
<span class="todo-count">
|
||||
<strong>
|
||||
<t t-esc="remaining"/>
|
||||
</strong>
|
||||
<t t-esc="remainingText"/>
|
||||
</span>
|
||||
<ul class="filters">
|
||||
<li>
|
||||
<a t-on-click="() => this.setFilter('all')" t-att-class="{selected: state.filter === 'all'}">All</a>
|
||||
</li>
|
||||
<li>
|
||||
<a t-on-click="() => this.setFilter('active')" t-att-class="{selected: state.filter === 'active'}">Active</a>
|
||||
</li>
|
||||
<li>
|
||||
<a t-on-click="() => this.setFilter('completed')" t-att-class="{selected: state.filter === 'completed'}">Completed</a>
|
||||
</li>
|
||||
</ul>
|
||||
<button class="clear-completed" t-if="store.tasks.length gt remaining" t-on-click="() => store.clearCompleted()">
|
||||
Clear completed
|
||||
</button>
|
||||
</footer>
|
||||
</section>
|
||||
|
||||
<li t-name="Todo" class="todo" t-att-class="{completed: props.isCompleted, editing: state.isEditing}">
|
||||
<div class="view">
|
||||
<input class="toggle" type="checkbox" t-on-change="() => store.toggleTask(props.id)" t-att-checked="props.completed"/>
|
||||
<label t-on-dblclick="() => state.isEditing = true">
|
||||
<t t-esc="props.text"/>
|
||||
</label>
|
||||
<button class="destroy" t-on-click="() => store.deleteTask(props.id)"></button>
|
||||
</div>
|
||||
<input class="edit" t-ref="input" t-if="state.isEditing" t-att-value="props.text" t-on-keyup="handleKeyup" t-on-blur="handleBlur"/>
|
||||
</li>
|
||||
</templates>
|
||||
@@ -0,0 +1,70 @@
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-rows: auto 50px;
|
||||
}
|
||||
|
||||
.window-manager {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #eeeeee;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.menubar {
|
||||
background-color: #875a7b;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.menubar button {
|
||||
height: 40px;
|
||||
font-size: 18px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.window {
|
||||
display: grid;
|
||||
grid-template-rows: 30px auto;
|
||||
border: 1px solid gray;
|
||||
background-color: white;
|
||||
position: absolute;
|
||||
box-shadow: 1px 1px 2px 1px grey;
|
||||
}
|
||||
|
||||
.window.dragging {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.window .header {
|
||||
background-color: #875a7b;
|
||||
display: grid;
|
||||
grid-template-columns: auto 24px;
|
||||
color: white;
|
||||
line-height: 30px;
|
||||
padding-left: 5px;
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.window .header .close {
|
||||
cursor: pointer;
|
||||
font-size: 22px;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.counter {
|
||||
font-size: 20px;
|
||||
}
|
||||
.counter button {
|
||||
width: 80px;
|
||||
height:40px;
|
||||
font-size: 20px;
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
// This example is slightly more complex than usual. We demonstrate
|
||||
// here a way to manage sub windows in Owl, declaratively. This is still just a
|
||||
// demonstration. Managing windows can be as complex as we want. For example,
|
||||
// we could implement the following features:
|
||||
// - resizing windows
|
||||
// - minimizing windows
|
||||
// - configuration options for windows to make a window non resizeable
|
||||
// - minimal width/height
|
||||
// - better heuristic for initial window position
|
||||
// - ...
|
||||
import { Component, useState, mount, useRef, reactive, useEnv, onMounted } from "@odoo/owl";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Window manager code
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
class WindowManager {
|
||||
// contains all components with metadata
|
||||
static Windows = {};
|
||||
windows = {}; // mapping id => info
|
||||
nextId = 1;
|
||||
|
||||
add(type) {
|
||||
const Comp = WindowManager.Windows[type];
|
||||
const left = 50 + Math.round(Math.random()*(window.innerWidth - 50 - Comp.defaultWidth));
|
||||
const top = 50 + Math.round(Math.random()*(window.innerHeight - 100 - Comp.defaultHeight));
|
||||
const id = this.nextId++;
|
||||
this.windows[id] = {
|
||||
id,
|
||||
title: Comp.defaultTitle,
|
||||
width: Comp.defaultWidth,
|
||||
height: Comp.defaultHeight,
|
||||
left,
|
||||
top,
|
||||
Component: Comp,
|
||||
};
|
||||
}
|
||||
|
||||
close(id) {
|
||||
delete this.windows[id];
|
||||
}
|
||||
|
||||
updatePosition(id, left, top) {
|
||||
const w = this.windows[id];
|
||||
w.left = left;
|
||||
w.top = top;
|
||||
}
|
||||
|
||||
getWindows() {
|
||||
return Object.values(this.windows);
|
||||
}
|
||||
}
|
||||
|
||||
function createWindowService() {
|
||||
return reactive(new WindowManager());
|
||||
}
|
||||
|
||||
function useWindowService() {
|
||||
const env = useEnv();
|
||||
return useState(env.windowService);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Generic Window Component
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
class Window extends Component {
|
||||
static template = "Window";
|
||||
static nextZIndex = 1;
|
||||
zIndex = 0;
|
||||
|
||||
setup() {
|
||||
this.windowService = useWindowService();
|
||||
this.root = useRef('root');
|
||||
onMounted(this.updateZIndex);
|
||||
}
|
||||
|
||||
get style() {
|
||||
let { width, height, top, left } = this.props.info;
|
||||
return `width: ${width}px;height: ${height}px;top:${top}px;left:${left}px;z-index:${this.zIndex}`;
|
||||
}
|
||||
|
||||
close() {
|
||||
this.windowService.close(this.props.info.id);
|
||||
}
|
||||
|
||||
startDragAndDrop(ev) {
|
||||
this.updateZIndex();
|
||||
const self = this;
|
||||
const root = this.root;
|
||||
|
||||
const el = root.el;
|
||||
el.classList.add('dragging');
|
||||
|
||||
const current = this.props.info;
|
||||
const offsetX = current.left - ev.pageX;
|
||||
const offsetY = current.top - ev.pageY;
|
||||
let left, top;
|
||||
|
||||
window.addEventListener("mousemove", moveWindow);
|
||||
window.addEventListener("mouseup", stopDnD, { once: true });
|
||||
|
||||
function moveWindow(ev) {
|
||||
left = Math.max(offsetX + ev.pageX, 0);
|
||||
top = Math.max(offsetY + ev.pageY, 0);
|
||||
el.style.left = `${left}px`;
|
||||
el.style.top = `${top}px`;
|
||||
}
|
||||
function stopDnD() {
|
||||
window.removeEventListener("mousemove", moveWindow);
|
||||
el.classList.remove('dragging');
|
||||
|
||||
if (top !== undefined && left !== undefined) {
|
||||
self.windowService.updatePosition(current.id, left, top);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateZIndex() {
|
||||
this.zIndex = Window.nextZIndex++;
|
||||
this.root.el.style['z-index'] = this.zIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Two concrete Window type implementations
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
class HelloWorld extends Component {
|
||||
static template = "HelloWorld";
|
||||
static defaultTitle = "Hello Owl!";
|
||||
static defaultWidth = 200;
|
||||
static defaultHeight = 100;
|
||||
}
|
||||
|
||||
|
||||
class Counter extends Component {
|
||||
static template = "Counter";
|
||||
static defaultTitle = "Click Counter";
|
||||
static defaultWidth = 300;
|
||||
static defaultHeight = 120;
|
||||
|
||||
state = useState({ value: 0 });
|
||||
|
||||
inc() {
|
||||
this.state.value++;
|
||||
}
|
||||
}
|
||||
|
||||
// register window components
|
||||
WindowManager.Windows.Hello = HelloWorld;
|
||||
WindowManager.Windows.Counter = Counter;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Window Container
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
class WindowContainer extends Component {
|
||||
static template = "WindowContainer";
|
||||
static components = { Window };
|
||||
|
||||
setup() {
|
||||
this.windowService = useWindowService();
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Root Component
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
class Root extends Component {
|
||||
static template = "Root";
|
||||
static components = { WindowContainer };
|
||||
|
||||
setup() {
|
||||
this.windowService = useWindowService();
|
||||
}
|
||||
|
||||
addWindow(type) {
|
||||
this.windowService.add(type);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Setup
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const env = {
|
||||
windowService: createWindowService(),
|
||||
};
|
||||
|
||||
mount(Root, document.body, { templates: TEMPLATES, env, dev: true });
|
||||
@@ -0,0 +1,32 @@
|
||||
<templates>
|
||||
<div t-name="Window" class="window" t-att-style="style" t-on-click="updateZIndex" t-ref="root">
|
||||
<div class="header">
|
||||
<span t-on-mousedown="startDragAndDrop"><t t-esc="props.info.title"/></span>
|
||||
<span class="close" t-on-click.stop="close">×</span>
|
||||
</div>
|
||||
<t t-slot="default"/>
|
||||
</div>
|
||||
|
||||
<div t-name="WindowContainer" class="window-manager">
|
||||
<Window t-foreach="windowService.getWindows()" t-as="w" t-key="w.id" info="w">
|
||||
<t t-component="w.Component"/>
|
||||
</Window>
|
||||
</div>
|
||||
|
||||
<div t-name="Root" class="app">
|
||||
<WindowContainer/>
|
||||
<div class="menubar">
|
||||
<button t-on-click="() => this.addWindow('Hello')">Say Hello</button>
|
||||
<button t-on-click="() => this.addWindow('Counter')">Counter</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div t-name="HelloWorld">
|
||||
Some content here...
|
||||
</div>
|
||||
|
||||
<div t-name="Counter" class="counter">
|
||||
<button t-on-click="inc">Click</button>
|
||||
<span><t t-esc="state.value"/></span>
|
||||
</div>
|
||||
</templates>
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import threading
|
||||
import time
|
||||
|
||||
from http.server import SimpleHTTPRequestHandler, HTTPServer
|
||||
|
||||
httpd = None
|
||||
def start_server():
|
||||
global httpd
|
||||
SimpleHTTPRequestHandler.extensions_map['.js'] = 'application/javascript'
|
||||
httpd = HTTPServer(('0.0.0.0', 3600), SimpleHTTPRequestHandler)
|
||||
httpd.serve_forever()
|
||||
|
||||
url = 'http://127.0.0.1:3600'
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Owl Application")
|
||||
print("---------------")
|
||||
print("Server running on: {}".format(url))
|
||||
threading.Thread(target=start_server, daemon=True).start()
|
||||
|
||||
while True:
|
||||
try:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
httpd.server_close()
|
||||
quit(0)
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>OWL App</title>
|
||||
<link rel="icon" href="data:,">
|
||||
|
||||
<link rel="stylesheet" href="app.css">
|
||||
<script type="importmap">{ "imports": { "@odoo/owl": "./owl.js" } }</script>
|
||||
<script type="module" src="app.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<pre>
|
||||
This app requires a web server, simply opening the html file using your browser will not work.
|
||||
You can run a simple web server with one of the following:
|
||||
- if you have python3 installed, run app.py with python3 or run 'python3 -m http.server' in the app folder
|
||||
- if you have node/npm installed, run 'npx serve' in the app folder
|
||||
- if you have php installed, run 'php -S localhost:8080 -t .' in the app folder
|
||||
</pre>
|
||||
<script>if (window.location.protocol !== "file:") document.querySelector("pre").remove()</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,5 +1,5 @@
|
||||
<templates>
|
||||
<div t-name="TabbedEditor" class="tabbed-editor">
|
||||
<div t-name="TabbedEditor" class="tabbed-editor" t-att-style="props.style">
|
||||
<div class="tabBar" t-att-class="{resizeable: props.resizeable}" t-on-mousedown="onMouseDown">
|
||||
<t t-foreach="['js', 'xml', 'css']" t-as="tab" t-key="tab">
|
||||
<a t-ref="{{tab}}" t-if="props[tab] !== false" t-key="tab" class="tab flash" t-att-class="{active: state.currentTab===tab}" t-on-click="() => setTab(tab)">
|
||||
@@ -10,17 +10,17 @@
|
||||
<div class="code-editor" t-ref="editor"></div>
|
||||
</div>
|
||||
|
||||
<div t-name="App" class="playground">
|
||||
<div t-name="Playground" class="playground">
|
||||
<div class="left-bar" t-att-class="{split: state.splitLayout}"
|
||||
t-att-style="leftPaneStyle">
|
||||
<div class="menubar">
|
||||
<a class="btn run-code flash" t-on-click="runCode" title="Execute this Code">▶ Run</a>
|
||||
<select t-on-change="setSample">
|
||||
<option t-foreach="SAMPLES" t-as="sample" t-key="sample_index">
|
||||
<select t-on-change="onSampleChange">
|
||||
<option t-foreach="samples" t-as="sample" t-key="sample_index">
|
||||
<t t-esc="sample.description"/>
|
||||
</option>
|
||||
</select>
|
||||
<a class="btn flash" t-on-click="downloadCode" title="Download a Zip with this Code"><i class="fas fa-download"></i></a>
|
||||
<a class="btn flash" t-on-click="exportStandaloneApp" title="Download zip that bundles this app with a tiny python web server"><i class="fas fa-download"></i></a>
|
||||
<a class="layout-selector flash" t-on-click="toggleLayout" title="Toggle Layout"><i class="fas" t-att-class="state.splitLayout ? 'fa-toggle-on' : 'fa-toggle-off'"></i></a>
|
||||
<a class="btn flash share-code" t-on-click="shareCode" title="Copy a link to this playground"><span>Share</span><span class="copied-notification" t-if="state.copied">Copied link to clipboard!</span></a>
|
||||
</div>
|
||||
@@ -37,19 +37,19 @@
|
||||
css="state.css"
|
||||
xml="state.xml"
|
||||
resizeable="true"
|
||||
onUpdatePanelHeight="updatePanelHeight"
|
||||
updatePanelHeight.bind="updatePanelHeight"
|
||||
updateCode="updateCode"/>
|
||||
</t>
|
||||
</div>
|
||||
<div class="separator vertical" t-on-mousedown="onMouseDown"/>
|
||||
<div class="right-pane" t-att-style="rightPaneStyle">
|
||||
<div class="right-pane">
|
||||
<div class="welcome" t-if="state.displayWelcome">
|
||||
<div>🦉 Odoo Web Library 🦉</div>
|
||||
<div>v<t t-esc="version"/></div>
|
||||
<div class="url"><a href="https://github.com/odoo/owl">https://github.com/odoo/owl</a></div>
|
||||
<div class="note">
|
||||
<p>Note: these examples are using recent features of Javascript, and require a recent browser to work without a transpilation step!
|
||||
For example, it makes use of class fields and class static fields. These examples should work in a recent Chrome version.
|
||||
<p>
|
||||
Note: these examples make use of recent features of Javascript, and require a recent browser to work without a transpilation step! Among other things, it uses class fields and import maps. If you encounter issues, make sure your browser is up to date.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -18,12 +18,6 @@ export function debounce(func, wait, immediate) {
|
||||
};
|
||||
}
|
||||
|
||||
export function useBus(bus, evName, cb) {
|
||||
bus.addEventListener(evName, cb);
|
||||
owl.onWillUnmount(() => {
|
||||
bus.removeEventListener(evName, cb);
|
||||
});
|
||||
}
|
||||
|
||||
const loadedScripts = {};
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Github page and playground
|
||||
|
||||
This folder contains the code for owl's [github page](https://odoo.github.io/owl)
|
||||
and the owl [playground](https://odoo.github.io/owl/playground/). If you're
|
||||
looking for the owl documentation, click [here](/doc)
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@odoo/owl",
|
||||
"version": "2.1.1",
|
||||
"version": "2.2.6",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
+2
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@odoo/owl",
|
||||
"version": "2.1.1",
|
||||
"version": "2.2.6",
|
||||
"description": "Odoo Web Library (OWL)",
|
||||
"main": "dist/owl.cjs.js",
|
||||
"module": "dist/owl.es.js",
|
||||
@@ -24,14 +24,13 @@
|
||||
"test": "jest",
|
||||
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand --watch --testTimeout=5000000",
|
||||
"test:watch": "jest --watch",
|
||||
"playground:serve": "python3 tools/server.py || python tools/server.py",
|
||||
"playground:serve": "python3 tools/playground_server.py || python tools/playground_server.py",
|
||||
"playground": "npm run build && npm run playground:serve",
|
||||
"preplayground:watch": "npm run build",
|
||||
"playground:watch": "npm-run-all --parallel playground:serve \"build:* -- --watch\"",
|
||||
"prettier": "prettier {src/*.ts,src/**/*.ts,tests/*.ts,tests/**/*.ts,doc/*.md,doc/**/*.md,tools/devtools/**/*.js} --write",
|
||||
"check-formatting": "prettier {src/*.ts,src/**/*.ts,tests/*.ts,tests/**/*.ts,doc/*.md,doc/**/*.md,tools/devtools/**/*.js} --check",
|
||||
"lint": "eslint src/**/*.ts tests/**/*.ts",
|
||||
"publish": "npm run build && npm publish",
|
||||
"release": "node tools/release.js",
|
||||
"compile_templates": "node tools/compile_xml.js"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
// Custom error class that wraps error that happen in the owl lifecycle
|
||||
export class OwlError extends Error {
|
||||
cause?: any;
|
||||
}
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
Attrs,
|
||||
EventHandlers,
|
||||
} from "./parser";
|
||||
import { OwlError } from "../runtime/error_handling";
|
||||
import { OwlError } from "../common/owl_error";
|
||||
|
||||
type BlockType = "block" | "text" | "multi" | "list" | "html" | "comment";
|
||||
const whitespaceRE = /\s+/g;
|
||||
@@ -160,7 +160,6 @@ interface Context {
|
||||
block: BlockDescription | null;
|
||||
index: number | string;
|
||||
forceNewBlock: boolean;
|
||||
preventRoot?: boolean;
|
||||
isLast?: boolean;
|
||||
translate: boolean;
|
||||
tKeyExpr: string | null;
|
||||
@@ -377,7 +376,7 @@ export class CodeGenerator {
|
||||
): BlockDescription {
|
||||
const hasRoot = this.target.hasRoot;
|
||||
const block = new BlockDescription(this.target, type);
|
||||
if (!hasRoot && !ctx.preventRoot) {
|
||||
if (!hasRoot) {
|
||||
this.target.hasRoot = true;
|
||||
block.isRoot = true;
|
||||
}
|
||||
@@ -403,7 +402,7 @@ export class CodeGenerator {
|
||||
blockExpr = `toggler(${ctx.tKeyExpr}, ${blockExpr})`;
|
||||
}
|
||||
|
||||
if (block.isRoot && !ctx.preventRoot) {
|
||||
if (block.isRoot) {
|
||||
if (this.target.on) {
|
||||
blockExpr = this.wrapWithEventCatcher(blockExpr, this.target.on);
|
||||
}
|
||||
@@ -447,6 +446,11 @@ export class CodeGenerator {
|
||||
.join("");
|
||||
}
|
||||
|
||||
translate(str: string): string {
|
||||
const match = translationRE.exec(str) as any;
|
||||
return match[1] + this.translateFn(match[2]) + match[3];
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the newly created block name, if any
|
||||
*/
|
||||
@@ -527,8 +531,7 @@ export class CodeGenerator {
|
||||
|
||||
let value = ast.value;
|
||||
if (value && ctx.translate !== false) {
|
||||
const match = translationRE.exec(value) as any;
|
||||
value = match[1] + this.translateFn(match[2]) + match[3];
|
||||
value = this.translate(value);
|
||||
}
|
||||
if (!ctx.inPreTag) {
|
||||
value = value.replace(whitespaceRE, " ");
|
||||
@@ -582,11 +585,6 @@ export class CodeGenerator {
|
||||
}
|
||||
// attributes
|
||||
const attrs: Attrs = {};
|
||||
const nameSpace = ast.ns || ctx.nameSpace;
|
||||
if (nameSpace && isNewBlock) {
|
||||
// specific namespace uri
|
||||
attrs["block-ns"] = nameSpace;
|
||||
}
|
||||
|
||||
for (let key in ast.attrs) {
|
||||
let expr, attrName;
|
||||
@@ -715,7 +713,10 @@ export class CodeGenerator {
|
||||
attrs["block-ref"] = String(idx);
|
||||
}
|
||||
|
||||
const dom = xmlDoc.createElement(ast.tag);
|
||||
const nameSpace = ast.ns || ctx.nameSpace;
|
||||
const dom = nameSpace
|
||||
? xmlDoc.createElementNS(nameSpace, ast.tag)
|
||||
: xmlDoc.createElement(ast.tag);
|
||||
for (const [attr, val] of Object.entries(attrs)) {
|
||||
if (!(attr === "class" && val === "")) {
|
||||
dom.setAttribute(attr, val);
|
||||
@@ -757,7 +758,7 @@ export class CodeGenerator {
|
||||
if (!current) break;
|
||||
}
|
||||
}
|
||||
this.addLine(`let ${block!.children.map((c) => c.varName)};`, codeIdx);
|
||||
this.addLine(`let ${block!.children.map((c) => c.varName).join(", ")};`, codeIdx);
|
||||
}
|
||||
}
|
||||
return block!.varName;
|
||||
@@ -859,7 +860,7 @@ export class CodeGenerator {
|
||||
if (!current) break;
|
||||
}
|
||||
}
|
||||
this.addLine(`let ${block!.children.map((c) => c.varName)};`, codeIdx);
|
||||
this.addLine(`let ${block!.children.map((c) => c.varName).join(", ")};`, codeIdx);
|
||||
}
|
||||
|
||||
// note: this part is duplicated from end of compilemulti:
|
||||
@@ -890,18 +891,18 @@ export class CodeGenerator {
|
||||
}
|
||||
this.addLine(`for (let ${loopVar} = 0; ${loopVar} < ${l}; ${loopVar}++) {`);
|
||||
this.target.indentLevel++;
|
||||
this.addLine(`ctx[\`${ast.elem}\`] = ${vals}[${loopVar}];`);
|
||||
this.addLine(`ctx[\`${ast.elem}\`] = ${keys}[${loopVar}];`);
|
||||
if (!ast.hasNoFirst) {
|
||||
this.addLine(`ctx[\`${ast.elem}_first\`] = ${loopVar} === 0;`);
|
||||
}
|
||||
if (!ast.hasNoLast) {
|
||||
this.addLine(`ctx[\`${ast.elem}_last\`] = ${loopVar} === ${vals}.length - 1;`);
|
||||
this.addLine(`ctx[\`${ast.elem}_last\`] = ${loopVar} === ${keys}.length - 1;`);
|
||||
}
|
||||
if (!ast.hasNoIndex) {
|
||||
this.addLine(`ctx[\`${ast.elem}_index\`] = ${loopVar};`);
|
||||
}
|
||||
if (!ast.hasNoValue) {
|
||||
this.addLine(`ctx[\`${ast.elem}_value\`] = ${keys}[${loopVar}];`);
|
||||
this.addLine(`ctx[\`${ast.elem}_value\`] = ${vals}[${loopVar}];`);
|
||||
}
|
||||
this.define(`key${this.target.loopLevel}`, ast.key ? compileExpr(ast.key) : loopVar);
|
||||
if (this.dev) {
|
||||
@@ -985,7 +986,6 @@ export class CodeGenerator {
|
||||
block,
|
||||
index,
|
||||
forceNewBlock: !isTSet,
|
||||
preventRoot: ctx.preventRoot,
|
||||
isLast: ctx.isLast && i === l - 1,
|
||||
});
|
||||
this.compileAST(child, subCtx);
|
||||
@@ -994,20 +994,18 @@ export class CodeGenerator {
|
||||
}
|
||||
}
|
||||
if (isNewBlock) {
|
||||
if (block!.hasDynamicChildren) {
|
||||
if (block!.children.length) {
|
||||
const code = this.target.code;
|
||||
const children = block!.children.slice();
|
||||
let current = children.shift();
|
||||
for (let i = codeIdx; i < code.length; i++) {
|
||||
if (code[i].trimStart().startsWith(`const ${current!.varName} `)) {
|
||||
code[i] = code[i].replace(`const ${current!.varName}`, current!.varName);
|
||||
current = children.shift();
|
||||
if (!current) break;
|
||||
}
|
||||
if (block!.hasDynamicChildren && block!.children.length) {
|
||||
const code = this.target.code;
|
||||
const children = block!.children.slice();
|
||||
let current = children.shift();
|
||||
for (let i = codeIdx; i < code.length; i++) {
|
||||
if (code[i].trimStart().startsWith(`const ${current!.varName} `)) {
|
||||
code[i] = code[i].replace(`const ${current!.varName}`, current!.varName);
|
||||
current = children.shift();
|
||||
if (!current) break;
|
||||
}
|
||||
this.addLine(`let ${block!.children.map((c) => c.varName)};`, codeIdx);
|
||||
}
|
||||
this.addLine(`let ${block!.children.map((c) => c.varName).join(", ")};`, codeIdx);
|
||||
}
|
||||
|
||||
const args = block!.children.map((c) => c.varName).join(", ");
|
||||
@@ -1023,24 +1021,24 @@ export class CodeGenerator {
|
||||
ctxVar = generateId("ctx");
|
||||
this.addLine(`let ${ctxVar} = ${compileExpr(ast.context)};`);
|
||||
}
|
||||
const isDynamic = INTERP_REGEXP.test(ast.name);
|
||||
const subTemplate = isDynamic ? interpolate(ast.name) : "`" + ast.name + "`";
|
||||
if (block && !forceNewBlock) {
|
||||
this.insertAnchor(block);
|
||||
}
|
||||
block = this.createBlock(block, "multi", ctx);
|
||||
if (ast.body) {
|
||||
this.addLine(`${ctxVar} = Object.create(${ctxVar});`);
|
||||
this.addLine(`${ctxVar}[isBoundary] = 1;`);
|
||||
this.helpers.add("isBoundary");
|
||||
const subCtx = createContext(ctx, { preventRoot: true, ctxVar });
|
||||
const subCtx = createContext(ctx, { ctxVar });
|
||||
const bl = this.compileMulti({ type: ASTType.Multi, content: ast.body }, subCtx);
|
||||
if (bl) {
|
||||
this.helpers.add("zero");
|
||||
this.addLine(`${ctxVar}[zero] = ${bl};`);
|
||||
}
|
||||
}
|
||||
const isDynamic = INTERP_REGEXP.test(ast.name);
|
||||
const subTemplate = isDynamic ? interpolate(ast.name) : "`" + ast.name + "`";
|
||||
if (block) {
|
||||
if (!forceNewBlock) {
|
||||
this.insertAnchor(block);
|
||||
}
|
||||
}
|
||||
|
||||
const key = `key + \`${this.generateComponentKey()}\``;
|
||||
if (isDynamic) {
|
||||
const templateVar = generateId("template");
|
||||
@@ -1048,7 +1046,6 @@ export class CodeGenerator {
|
||||
this.staticDefs.push({ id: "call", expr: `app.callTemplate.bind(app)` });
|
||||
}
|
||||
this.define(templateVar, subTemplate);
|
||||
block = this.createBlock(block, "multi", ctx);
|
||||
this.insertBlock(`call(this, ${templateVar}, ${ctxVar}, node, ${key})`, block!, {
|
||||
...ctx,
|
||||
forceNewBlock: !block,
|
||||
@@ -1056,7 +1053,6 @@ export class CodeGenerator {
|
||||
} else {
|
||||
const id = generateId(`callTemplate_`);
|
||||
this.staticDefs.push({ id, expr: `app.getTemplate(${subTemplate})` });
|
||||
block = this.createBlock(block, "multi", ctx);
|
||||
this.insertBlock(`${id}.call(this, ${ctxVar}, node, ${key})`, block!, {
|
||||
...ctx,
|
||||
forceNewBlock: !block,
|
||||
@@ -1095,10 +1091,11 @@ export class CodeGenerator {
|
||||
} else {
|
||||
let value: string;
|
||||
if (ast.defaultValue) {
|
||||
const defaultValue = ctx.translate ? this.translate(ast.defaultValue) : ast.defaultValue;
|
||||
if (ast.value) {
|
||||
value = `withDefault(${expr}, \`${ast.defaultValue}\`)`;
|
||||
value = `withDefault(${expr}, \`${defaultValue}\`)`;
|
||||
} else {
|
||||
value = `\`${ast.defaultValue}\``;
|
||||
value = `\`${defaultValue}\``;
|
||||
}
|
||||
} else {
|
||||
value = expr;
|
||||
|
||||
+12
-1
@@ -2,6 +2,7 @@ import type { TemplateSet } from "../runtime/template_set";
|
||||
import type { BDom } from "../runtime/blockdom";
|
||||
import { CodeGenerator, Config } from "./code_generator";
|
||||
import { parse } from "./parser";
|
||||
import { OwlError } from "../common/owl_error";
|
||||
|
||||
export type Template = (context: any, vnode: any, key?: string) => BDom;
|
||||
|
||||
@@ -27,5 +28,15 @@ export function compile(
|
||||
const codeGenerator = new CodeGenerator(ast, { ...options, hasSafeContext });
|
||||
const code = codeGenerator.generateCode();
|
||||
// template function
|
||||
return new Function("app, bdom, helpers", code) as TemplateFunction;
|
||||
try {
|
||||
return new Function("app, bdom, helpers", code) as TemplateFunction;
|
||||
} catch (originalError: any) {
|
||||
const { name } = options;
|
||||
const nameStr = name ? `template "${name}"` : "anonymous template";
|
||||
const err = new OwlError(
|
||||
`Failed to compile ${nameStr}: ${originalError.message}\n\ngenerated code:\nfunction(app, bdom, helpers) {\n${code}\n}`
|
||||
);
|
||||
err.cause = originalError;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OwlError } from "../runtime/error_handling";
|
||||
import { OwlError } from "../common/owl_error";
|
||||
|
||||
/**
|
||||
* Owl QWeb Expression Parser
|
||||
|
||||
+34
-33
@@ -1,4 +1,4 @@
|
||||
import { OwlError } from "../runtime/error_handling";
|
||||
import { OwlError } from "../common/owl_error";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// AST Type definition
|
||||
@@ -213,14 +213,14 @@ export function parse(xml: string | Element): AST {
|
||||
|
||||
function _parse(xml: Element): AST {
|
||||
normalizeXML(xml);
|
||||
const ctx = { inPreTag: false, inSVG: false };
|
||||
const ctx = { inPreTag: false };
|
||||
return parseNode(xml, ctx) || { type: ASTType.Text, value: "" };
|
||||
}
|
||||
|
||||
interface ParsingContext {
|
||||
tModelInfo?: TModelInfo | null;
|
||||
nameSpace?: string;
|
||||
inPreTag: boolean;
|
||||
inSVG: boolean;
|
||||
}
|
||||
|
||||
function parseNode(node: Node, ctx: ParsingContext): AST | null {
|
||||
@@ -235,10 +235,10 @@ function parseNode(node: Node, ctx: ParsingContext): AST | null {
|
||||
parseTCall(node, ctx) ||
|
||||
parseTCallBlock(node, ctx) ||
|
||||
parseTEscNode(node, ctx) ||
|
||||
parseTOutNode(node, ctx) ||
|
||||
parseTKey(node, ctx) ||
|
||||
parseTTranslation(node, ctx) ||
|
||||
parseTSlot(node, ctx) ||
|
||||
parseTOutNode(node, ctx) ||
|
||||
parseComponent(node, ctx) ||
|
||||
parseDOMNode(node, ctx) ||
|
||||
parseTSetNode(node, ctx) ||
|
||||
@@ -323,9 +323,8 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
||||
if (tagName === "pre") {
|
||||
ctx.inPreTag = true;
|
||||
}
|
||||
const shouldAddSVGNS = ROOT_SVG_TAGS.has(tagName) && !ctx.inSVG;
|
||||
ctx.inSVG = ctx.inSVG || shouldAddSVGNS;
|
||||
const ns = shouldAddSVGNS ? "http://www.w3.org/2000/svg" : null;
|
||||
|
||||
let ns = !ctx.nameSpace && ROOT_SVG_TAGS.has(tagName) ? "http://www.w3.org/2000/svg" : null;
|
||||
const ref = node.getAttribute("t-ref");
|
||||
node.removeAttribute("t-ref");
|
||||
|
||||
@@ -336,10 +335,10 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
||||
|
||||
for (let attr of nodeAttrsNames) {
|
||||
const value = node.getAttribute(attr)!;
|
||||
if (attr.startsWith("t-on")) {
|
||||
if (attr === "t-on") {
|
||||
throw new OwlError("Missing event name with t-on directive");
|
||||
}
|
||||
if (attr === "t-on" || attr === "t-on-") {
|
||||
throw new OwlError("Missing event name with t-on directive");
|
||||
}
|
||||
if (attr.startsWith("t-on-")) {
|
||||
on = on || {};
|
||||
on[attr.slice(5)] = value;
|
||||
} else if (attr.startsWith("t-model")) {
|
||||
@@ -365,10 +364,8 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
||||
const typeAttr = node.getAttribute("type");
|
||||
const isInput = tagName === "input";
|
||||
const isSelect = tagName === "select";
|
||||
const isTextarea = tagName === "textarea";
|
||||
const isCheckboxInput = isInput && typeAttr === "checkbox";
|
||||
const isRadioInput = isInput && typeAttr === "radio";
|
||||
const isOtherInput = isInput && !isCheckboxInput && !isRadioInput;
|
||||
const hasLazyMod = attr.includes(".lazy");
|
||||
const hasNumberMod = attr.includes(".number");
|
||||
const hasTrimMod = attr.includes(".trim");
|
||||
@@ -381,8 +378,8 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
||||
specialInitTargetAttr: isRadioInput ? "checked" : null,
|
||||
eventType,
|
||||
hasDynamicChildren: false,
|
||||
shouldTrim: hasTrimMod && (isOtherInput || isTextarea),
|
||||
shouldNumberize: hasNumberMod && (isOtherInput || isTextarea),
|
||||
shouldTrim: hasTrimMod,
|
||||
shouldNumberize: hasNumberMod,
|
||||
};
|
||||
if (isSelect) {
|
||||
// don't pollute the original ctx
|
||||
@@ -391,6 +388,8 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
||||
}
|
||||
} else if (attr.startsWith("block-")) {
|
||||
throw new OwlError(`Invalid attribute: '${attr}'`);
|
||||
} else if (attr === "xmlns") {
|
||||
ns = value;
|
||||
} else if (attr !== "t-name") {
|
||||
if (attr.startsWith("t-") && !attr.startsWith("t-att")) {
|
||||
throw new OwlError(`Unknown QWeb directive: '${attr}'`);
|
||||
@@ -403,6 +402,9 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
||||
attrs[attr] = value;
|
||||
}
|
||||
}
|
||||
if (ns) {
|
||||
ctx.nameSpace = ns;
|
||||
}
|
||||
|
||||
const children = parseChildren(node, ctx);
|
||||
return {
|
||||
@@ -446,9 +448,6 @@ function parseTEscNode(node: Element, ctx: ParsingContext): AST | null {
|
||||
content: [tesc],
|
||||
};
|
||||
}
|
||||
if (ast.type === ASTType.TComponent) {
|
||||
throw new OwlError("t-esc is not supported on Component nodes");
|
||||
}
|
||||
return tesc;
|
||||
}
|
||||
|
||||
@@ -943,21 +942,23 @@ function normalizeTIf(el: Element) {
|
||||
*
|
||||
* @param el the element containing the tree that should be normalized
|
||||
*/
|
||||
function normalizeTEsc(el: Element) {
|
||||
const elements = [...el.querySelectorAll("[t-esc]")].filter(
|
||||
(el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component")
|
||||
);
|
||||
for (const el of elements) {
|
||||
if (el.childNodes.length) {
|
||||
throw new OwlError("Cannot have t-esc on a component that already has content");
|
||||
function normalizeTEscTOut(el: Element) {
|
||||
for (const d of ["t-esc", "t-out"]) {
|
||||
const elements = [...el.querySelectorAll(`[${d}]`)].filter(
|
||||
(el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component")
|
||||
);
|
||||
for (const el of elements) {
|
||||
if (el.childNodes.length) {
|
||||
throw new OwlError(`Cannot have ${d} on a component that already has content`);
|
||||
}
|
||||
const value = el.getAttribute(d);
|
||||
el.removeAttribute(d);
|
||||
const t = el.ownerDocument.createElement("t");
|
||||
if (value != null) {
|
||||
t.setAttribute(d, value);
|
||||
}
|
||||
el.appendChild(t);
|
||||
}
|
||||
const value = el.getAttribute("t-esc");
|
||||
el.removeAttribute("t-esc");
|
||||
const t = el.ownerDocument.createElement("t");
|
||||
if (value != null) {
|
||||
t.setAttribute("t-esc", value);
|
||||
}
|
||||
el.appendChild(t);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -969,7 +970,7 @@ function normalizeTEsc(el: Element) {
|
||||
*/
|
||||
function normalizeXML(el: Element) {
|
||||
normalizeTIf(el);
|
||||
normalizeTEsc(el);
|
||||
normalizeTEscTOut(el);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+12
-9
@@ -1,12 +1,14 @@
|
||||
import { version } from "../version";
|
||||
import { Component, ComponentConstructor, Props } from "./component";
|
||||
import { ComponentNode } from "./component_node";
|
||||
import { nodeErrorHandlers, OwlError, handleError } from "./error_handling";
|
||||
import { nodeErrorHandlers, handleError } from "./error_handling";
|
||||
import { OwlError } from "../common/owl_error";
|
||||
import { Fiber, RootFiber, MountOptions } from "./fibers";
|
||||
import { Scheduler } from "./scheduler";
|
||||
import { validateProps } from "./template_helpers";
|
||||
import { TemplateSet, TemplateSetConfig } from "./template_set";
|
||||
import { validateTarget } from "./utils";
|
||||
import { toRaw, reactive } from "./reactivity";
|
||||
|
||||
// reimplement dev mode stuff see last change in 0f7a8289a6fb8387c3c1af41c6664b2a8448758f
|
||||
|
||||
@@ -33,21 +35,21 @@ This is not suitable for production use.
|
||||
See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration for more information.`;
|
||||
};
|
||||
|
||||
const apps = new Set<App>();
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__OWL_DEVTOOLS__: {
|
||||
apps: Set<App>;
|
||||
Fiber: typeof Fiber;
|
||||
RootFiber: typeof RootFiber;
|
||||
toRaw: typeof toRaw;
|
||||
reactive: typeof reactive;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
window.__OWL_DEVTOOLS__ ||= {
|
||||
apps: new Set<App>(),
|
||||
Fiber: Fiber,
|
||||
RootFiber: RootFiber,
|
||||
};
|
||||
window.__OWL_DEVTOOLS__ ||= { apps, Fiber, RootFiber, toRaw, reactive };
|
||||
|
||||
export class App<
|
||||
T extends abstract new (...args: any) => any = any,
|
||||
@@ -55,6 +57,7 @@ export class App<
|
||||
E = any
|
||||
> extends TemplateSet {
|
||||
static validateTarget = validateTarget;
|
||||
static apps = apps;
|
||||
static version = version;
|
||||
|
||||
name: string;
|
||||
@@ -69,7 +72,7 @@ export class App<
|
||||
super(config);
|
||||
this.name = config.name || "";
|
||||
this.Root = Root;
|
||||
window.__OWL_DEVTOOLS__.apps.add(this);
|
||||
apps.add(this);
|
||||
if (config.test) {
|
||||
this.dev = true;
|
||||
}
|
||||
@@ -131,10 +134,10 @@ export class App<
|
||||
|
||||
destroy() {
|
||||
if (this.root) {
|
||||
this.scheduler.flush();
|
||||
this.root.destroy();
|
||||
this.scheduler.processTasks();
|
||||
}
|
||||
window.__OWL_DEVTOOLS__.apps.delete(this);
|
||||
apps.delete(this);
|
||||
}
|
||||
|
||||
createComponent<P extends Props>(
|
||||
|
||||
@@ -36,10 +36,18 @@ export function createAttrUpdater(attr: string): Setter<HTMLElement> {
|
||||
|
||||
export function attrsSetter(this: HTMLElement, attrs: any) {
|
||||
if (isArray(attrs)) {
|
||||
setAttribute.call(this, attrs[0], attrs[1]);
|
||||
if (attrs[0] === "class") {
|
||||
setClass.call(this, attrs[1]);
|
||||
} else {
|
||||
setAttribute.call(this, attrs[0], attrs[1]);
|
||||
}
|
||||
} else {
|
||||
for (let k in attrs) {
|
||||
setAttribute.call(this, k, attrs[k]);
|
||||
if (k === "class") {
|
||||
setClass.call(this, attrs[k]);
|
||||
} else {
|
||||
setAttribute.call(this, k, attrs[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,7 +60,11 @@ export function attrsUpdater(this: HTMLElement, attrs: any, oldAttrs: any) {
|
||||
if (val === oldAttrs[1]) {
|
||||
return;
|
||||
}
|
||||
setAttribute.call(this, name, val);
|
||||
if (name === "class") {
|
||||
updateClass.call(this, val, oldAttrs[1]);
|
||||
} else {
|
||||
setAttribute.call(this, name, val);
|
||||
}
|
||||
} else {
|
||||
removeAttribute.call(this, oldAttrs[0]);
|
||||
setAttribute.call(this, name, val);
|
||||
@@ -60,13 +72,21 @@ export function attrsUpdater(this: HTMLElement, attrs: any, oldAttrs: any) {
|
||||
} else {
|
||||
for (let k in oldAttrs) {
|
||||
if (!(k in attrs)) {
|
||||
removeAttribute.call(this, k);
|
||||
if (k === "class") {
|
||||
updateClass.call(this, "", oldAttrs[k]);
|
||||
} else {
|
||||
removeAttribute.call(this, k);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let k in attrs) {
|
||||
const val = attrs[k];
|
||||
if (val !== oldAttrs[k]) {
|
||||
setAttribute.call(this, k, val);
|
||||
if (k === "class") {
|
||||
updateClass.call(this, val, oldAttrs[k]);
|
||||
} else {
|
||||
setAttribute.call(this, k, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OwlError } from "../error_handling";
|
||||
import { OwlError } from "../../common/owl_error";
|
||||
import { attrsSetter, attrsUpdater, createAttrUpdater, setClass, updateClass } from "./attributes";
|
||||
import { config } from "./config";
|
||||
import { createEventHandler } from "./events";
|
||||
@@ -144,12 +144,7 @@ function buildTree(
|
||||
info.push({ type: "child", idx: index });
|
||||
el = document.createTextNode("");
|
||||
}
|
||||
const attrs = (node as Element).attributes;
|
||||
const ns = attrs.getNamedItem("block-ns");
|
||||
if (ns) {
|
||||
attrs.removeNamedItem("block-ns");
|
||||
currentNS = ns.value;
|
||||
}
|
||||
currentNS ||= (node as Element).namespaceURI;
|
||||
if (!el) {
|
||||
el = currentNS
|
||||
? document.createElementNS(currentNS, tagName)
|
||||
@@ -165,6 +160,7 @@ function buildTree(
|
||||
const fragment = document.createElement("template").content;
|
||||
fragment.appendChild(el);
|
||||
}
|
||||
const attrs = (node as Element).attributes;
|
||||
for (let i = 0; i < attrs.length; i++) {
|
||||
const attrName = attrs[i].name;
|
||||
const attrValue = attrs[i].value;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import type { App, Env } from "./app";
|
||||
import { BDom, VNode } from "./blockdom";
|
||||
import { Component, ComponentConstructor, Props } from "./component";
|
||||
import { fibersInError, OwlError } from "./error_handling";
|
||||
import { fibersInError } from "./error_handling";
|
||||
import { OwlError } from "../common/owl_error";
|
||||
import { Fiber, makeChildFiber, makeRootFiber, MountFiber, MountOptions } from "./fibers";
|
||||
import { clearReactivesForCallback, getSubscriptions, reactive, targets } from "./reactivity";
|
||||
import { STATUS } from "./status";
|
||||
import { batched, Callback } from "./utils";
|
||||
import { batched, Callback, possiblySync } from "./utils";
|
||||
|
||||
let currentNode: ComponentNode | null = null;
|
||||
|
||||
@@ -127,24 +128,35 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
|
||||
this.initiateRender(fiber);
|
||||
}
|
||||
|
||||
async initiateRender(fiber: Fiber | MountFiber) {
|
||||
initiateRender(fiber: Fiber | MountFiber) {
|
||||
this.fiber = fiber;
|
||||
if (this.mounted.length) {
|
||||
fiber.root!.mounted.push(fiber);
|
||||
}
|
||||
const component = this.component;
|
||||
try {
|
||||
await Promise.all(this.willStart.map((f) => f.call(component)));
|
||||
} catch (e) {
|
||||
this.app.handleError({ node: this, error: e });
|
||||
return;
|
||||
}
|
||||
if (this.status === STATUS.NEW && this.fiber === fiber) {
|
||||
fiber.render();
|
||||
}
|
||||
return possiblySync(
|
||||
() => {
|
||||
const willStartResults = this.willStart.map((f) => f.call(component));
|
||||
if (willStartResults.some((r) => typeof r?.then === "function")) {
|
||||
return Promise.all(willStartResults);
|
||||
}
|
||||
return;
|
||||
},
|
||||
() => {
|
||||
if (this.status === STATUS.NEW && this.fiber === fiber) {
|
||||
fiber.render();
|
||||
}
|
||||
},
|
||||
(e: any) => {
|
||||
this.app.handleError({ node: this, error: e });
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async render(deep: boolean) {
|
||||
if (this.status >= STATUS.CANCELLED) {
|
||||
return;
|
||||
}
|
||||
let current = this.fiber;
|
||||
if (current && (current.root!.locked || (current as any).bdom === true)) {
|
||||
await Promise.resolve();
|
||||
@@ -171,7 +183,7 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
|
||||
|
||||
this.app.scheduler.addFiber(fiber);
|
||||
await Promise.resolve();
|
||||
if (this.status === STATUS.DESTROYED) {
|
||||
if (this.status >= STATUS.CANCELLED) {
|
||||
return;
|
||||
}
|
||||
// We only want to actually render the component if the following two
|
||||
@@ -190,6 +202,20 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
|
||||
}
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this._cancel();
|
||||
delete this.parent!.children[this.parentKey!];
|
||||
this.app.scheduler.scheduleDestroy(this);
|
||||
}
|
||||
|
||||
_cancel() {
|
||||
this.status = STATUS.CANCELLED;
|
||||
const children = this.children;
|
||||
for (let childKey in children) {
|
||||
children[childKey]._cancel();
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
let shouldRemove = this.status === STATUS.MOUNTED;
|
||||
this._destroy();
|
||||
@@ -220,7 +246,7 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
|
||||
this.status = STATUS.DESTROYED;
|
||||
}
|
||||
|
||||
async updateAndRender(props: P, parentFiber: Fiber) {
|
||||
updateAndRender(props: P, parentFiber: Fiber) {
|
||||
this.nextProps = props;
|
||||
props = Object.assign({}, props);
|
||||
// update
|
||||
@@ -240,20 +266,29 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
|
||||
}
|
||||
}
|
||||
currentNode = null;
|
||||
const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
|
||||
await prom;
|
||||
if (fiber !== this.fiber) {
|
||||
return;
|
||||
}
|
||||
component.props = props;
|
||||
fiber.render();
|
||||
const parentRoot = parentFiber.root!;
|
||||
if (this.willPatch.length) {
|
||||
parentRoot.willPatch.push(fiber);
|
||||
}
|
||||
if (this.patched.length) {
|
||||
parentRoot.patched.push(fiber);
|
||||
}
|
||||
return possiblySync(
|
||||
() => {
|
||||
const willUpdateProps = this.willUpdateProps.map((f) => f.call(component, props));
|
||||
if (willUpdateProps.some((p) => typeof p?.then === "function")) {
|
||||
return Promise.all(willUpdateProps);
|
||||
}
|
||||
return;
|
||||
},
|
||||
() => {
|
||||
if (fiber !== this.fiber) {
|
||||
return;
|
||||
}
|
||||
component.props = props;
|
||||
fiber.render();
|
||||
const parentRoot = parentFiber.root!;
|
||||
if (this.willPatch.length) {
|
||||
parentRoot.willPatch.push(fiber);
|
||||
}
|
||||
if (this.patched.length) {
|
||||
parentRoot.patched.push(fiber);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { OwlError } from "../common/owl_error";
|
||||
import type { ComponentNode } from "./component_node";
|
||||
import type { Fiber } from "./fibers";
|
||||
|
||||
// Custom error class that wraps error that happen in the owl lifecycle
|
||||
export class OwlError extends Error {
|
||||
cause?: any;
|
||||
}
|
||||
|
||||
// Maps fibers to thrown errors
|
||||
export const fibersInError: WeakMap<Fiber, any> = new WeakMap();
|
||||
export const nodeErrorHandlers: WeakMap<ComponentNode, ((error: any) => void)[]> = new WeakMap();
|
||||
@@ -51,17 +47,19 @@ export function handleError(params: ErrorParams) {
|
||||
);
|
||||
}
|
||||
const node = "node" in params ? params.node : params.fiber.node;
|
||||
const fiber = "fiber" in params ? params.fiber : node.fiber!;
|
||||
const fiber = "fiber" in params ? params.fiber : node.fiber;
|
||||
|
||||
// resets the fibers on components if possible. This is important so that
|
||||
// new renderings can be properly included in the initial one, if any.
|
||||
let current: Fiber | null = fiber;
|
||||
do {
|
||||
current.node.fiber = current;
|
||||
current = current.parent;
|
||||
} while (current);
|
||||
if (fiber) {
|
||||
// resets the fibers on components if possible. This is important so that
|
||||
// new renderings can be properly included in the initial one, if any.
|
||||
let current: Fiber | null = fiber;
|
||||
do {
|
||||
current.node.fiber = current;
|
||||
current = current.parent;
|
||||
} while (current);
|
||||
|
||||
fibersInError.set(fiber.root!, error);
|
||||
fibersInError.set(fiber.root!, error);
|
||||
}
|
||||
|
||||
const handled = _handleError(node, error);
|
||||
if (!handled) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { filterOutModifiersFromData } from "./blockdom/config";
|
||||
import { STATUS } from "./status";
|
||||
import { OwlError } from "./error_handling";
|
||||
import { OwlError } from "../common/owl_error";
|
||||
|
||||
export const mainEventHandler = (data: any, ev: Event, currentTarget?: EventTarget | null) => {
|
||||
const { data: _data, modifiers } = filterOutModifiersFromData(data);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { BDom, mount } from "./blockdom";
|
||||
import type { ComponentNode } from "./component_node";
|
||||
import { fibersInError, OwlError } from "./error_handling";
|
||||
import { fibersInError } from "./error_handling";
|
||||
import { OwlError } from "../common/owl_error";
|
||||
import { STATUS } from "./status";
|
||||
|
||||
export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber {
|
||||
@@ -55,8 +56,7 @@ function cancelFibers(fibers: Fiber[]): number {
|
||||
let node = fiber.node;
|
||||
fiber.render = throwOnRender;
|
||||
if (node.status === STATUS.NEW) {
|
||||
node.destroy();
|
||||
delete node.parent!.children[node.parentKey!];
|
||||
node.cancel();
|
||||
}
|
||||
node.fiber = null;
|
||||
if (fiber.bdom) {
|
||||
|
||||
+13
-6
@@ -59,28 +59,35 @@ export function useChildSubEnv(envExtension: Env) {
|
||||
// useEffect
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
type EffectDeps<T extends any[]> = T | (T extends [...infer H, never] ? EffectDeps<H> : never);
|
||||
|
||||
/**
|
||||
* @param {...any} dependencies the dependencies computed by computeDependencies
|
||||
* @template T
|
||||
* @param {...T} dependencies the dependencies computed by computeDependencies
|
||||
* @returns {void|(()=>void)} a cleanup function that reverses the side
|
||||
* effects of the effect callback.
|
||||
*/
|
||||
type Effect = (...dependencies: any[]) => void | (() => void);
|
||||
type Effect<T extends [...T]> = (...dependencies: EffectDeps<T>) => void | (() => void);
|
||||
|
||||
/**
|
||||
* This hook will run a callback when a component is mounted and patched, and
|
||||
* will run a cleanup function before patching and before unmounting the
|
||||
* the component.
|
||||
*
|
||||
* @param {Effect} effect the effect to run on component mount and/or patch
|
||||
* @param {()=>any[]} [computeDependencies=()=>[NaN]] a callback to compute
|
||||
* @template T
|
||||
* @param {Effect<T>} effect the effect to run on component mount and/or patch
|
||||
* @param {()=>T} [computeDependencies=()=>[NaN]] a callback to compute
|
||||
* dependencies that will decide if the effect needs to be cleaned up and
|
||||
* run again. If the dependencies did not change, the effect will not run
|
||||
* again. The default value returns an array containing only NaN because
|
||||
* NaN !== NaN, which will cause the effect to rerun on every patch.
|
||||
*/
|
||||
export function useEffect(effect: Effect, computeDependencies: () => any[] = () => [NaN]) {
|
||||
export function useEffect<T extends [...T]>(
|
||||
effect: Effect<T>,
|
||||
computeDependencies: () => T = () => [NaN] as never
|
||||
) {
|
||||
let cleanup: (() => void) | void;
|
||||
let dependencies: any[];
|
||||
let dependencies: T;
|
||||
onMounted(() => {
|
||||
dependencies = computeDependencies();
|
||||
cleanup = effect(...dependencies);
|
||||
|
||||
@@ -55,7 +55,7 @@ export {
|
||||
onError,
|
||||
} from "./lifecycle_hooks";
|
||||
export { validate, validateType } from "./validation";
|
||||
export { OwlError } from "./error_handling";
|
||||
export { OwlError } from "../common/owl_error";
|
||||
|
||||
export const __info__ = {
|
||||
version: App.version,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { getCurrent } from "./component_node";
|
||||
import { nodeErrorHandlers, OwlError } from "./error_handling";
|
||||
import { nodeErrorHandlers } from "./error_handling";
|
||||
import { OwlError } from "../common/owl_error";
|
||||
|
||||
const TIMEOUT = Symbol("timeout");
|
||||
function wrapError(fn: (...args: any[]) => any, hookName: string) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { onMounted, onWillUnmount } from "./lifecycle_hooks";
|
||||
import { BDom, text, VNode } from "./blockdom";
|
||||
import { Component } from "./component";
|
||||
import { OwlError } from "./error_handling";
|
||||
import { OwlError } from "../common/owl_error";
|
||||
|
||||
const VText: any = text("").constructor;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Callback } from "./utils";
|
||||
import { OwlError } from "./error_handling";
|
||||
import { OwlError } from "../common/owl_error";
|
||||
|
||||
// Special key to subscribe to, to be notified of key creation/deletion
|
||||
const KEYCHANGES = Symbol("Key changes");
|
||||
@@ -368,7 +368,7 @@ function delegateAndNotify(
|
||||
if (hadKey !== hasKey) {
|
||||
notifyReactives(target, KEYCHANGES);
|
||||
}
|
||||
if (originalValue !== value) {
|
||||
if (originalValue !== target[getterName](key)) {
|
||||
notifyReactives(target, key);
|
||||
}
|
||||
return ret;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { ComponentNode } from "./component_node";
|
||||
import { fibersInError } from "./error_handling";
|
||||
import { Fiber, RootFiber } from "./fibers";
|
||||
import { STATUS } from "./status";
|
||||
@@ -14,6 +15,7 @@ export class Scheduler {
|
||||
requestAnimationFrame: Window["requestAnimationFrame"];
|
||||
frame: number = 0;
|
||||
delayedRenders: Fiber[] = [];
|
||||
cancelledNodes: Set<ComponentNode> = new Set();
|
||||
|
||||
constructor() {
|
||||
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
|
||||
@@ -23,6 +25,13 @@ export class Scheduler {
|
||||
this.tasks.add(fiber.root!);
|
||||
}
|
||||
|
||||
scheduleDestroy(node: ComponentNode) {
|
||||
this.cancelledNodes.add(node);
|
||||
if (this.frame === 0) {
|
||||
this.frame = this.requestAnimationFrame(() => this.processTasks());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process all current tasks. This only applies to the fibers that are ready.
|
||||
* Other tasks are left unchanged.
|
||||
@@ -39,15 +48,23 @@ export class Scheduler {
|
||||
}
|
||||
|
||||
if (this.frame === 0) {
|
||||
this.frame = this.requestAnimationFrame(() => {
|
||||
this.frame = 0;
|
||||
this.tasks.forEach((fiber) => this.processFiber(fiber));
|
||||
for (let task of this.tasks) {
|
||||
if (task.node.status === STATUS.DESTROYED) {
|
||||
this.tasks.delete(task);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.frame = this.requestAnimationFrame(() => this.processTasks());
|
||||
}
|
||||
}
|
||||
|
||||
processTasks() {
|
||||
this.frame = 0;
|
||||
for (let node of this.cancelledNodes) {
|
||||
node._destroy();
|
||||
}
|
||||
this.cancelledNodes.clear();
|
||||
for (let task of this.tasks) {
|
||||
this.processFiber(task);
|
||||
}
|
||||
for (let task of this.tasks) {
|
||||
if (task.node.status === STATUS.DESTROYED) {
|
||||
this.tasks.delete(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,15 +7,20 @@ import type { Component } from "./component";
|
||||
export const enum STATUS {
|
||||
NEW,
|
||||
MOUNTED, // is ready, and in DOM. It has a valid el
|
||||
// component has been created, but has been replaced by a newer component before being mounted
|
||||
// it is cancelled until the next animation frame where it will be destroyed
|
||||
CANCELLED,
|
||||
DESTROYED,
|
||||
}
|
||||
|
||||
type STATUS_DESCR = "new" | "mounted" | "destroyed";
|
||||
type STATUS_DESCR = "new" | "mounted" | "cancelled" | "destroyed";
|
||||
|
||||
export function status(component: Component): STATUS_DESCR {
|
||||
switch (component.__owl__.status) {
|
||||
case STATUS.NEW:
|
||||
return "new";
|
||||
case STATUS.CANCELLED:
|
||||
return "cancelled";
|
||||
case STATUS.MOUNTED:
|
||||
return "mounted";
|
||||
case STATUS.DESTROYED:
|
||||
|
||||
@@ -4,7 +4,7 @@ import { html } from "./blockdom/index";
|
||||
import { isOptional, validateSchema } from "./validation";
|
||||
import type { ComponentConstructor } from "./component";
|
||||
import { markRaw } from "./reactivity";
|
||||
import { OwlError } from "./error_handling";
|
||||
import { OwlError } from "../common/owl_error";
|
||||
import type { ComponentNode } from "./component_node";
|
||||
|
||||
const ObjectCreate = Object.create;
|
||||
@@ -60,18 +60,24 @@ function withKey(elem: any, k: string) {
|
||||
return elem;
|
||||
}
|
||||
|
||||
function prepareList(collection: any): [any[], any[], number, any[]] {
|
||||
let keys: any[];
|
||||
let values: any[];
|
||||
function prepareList(collection: unknown): [unknown[], unknown[], number, undefined[]] {
|
||||
let keys: unknown[];
|
||||
let values: unknown[];
|
||||
|
||||
if (Array.isArray(collection)) {
|
||||
keys = collection;
|
||||
values = collection;
|
||||
} else if (collection) {
|
||||
values = Object.keys(collection);
|
||||
keys = Object.values(collection);
|
||||
} else if (collection instanceof Map) {
|
||||
keys = [...collection.keys()];
|
||||
values = [...collection.values()];
|
||||
} else if (Symbol.iterator in Object(collection)) {
|
||||
keys = [...(<Iterable<unknown>>collection)];
|
||||
values = keys;
|
||||
} else if (collection && typeof collection === "object") {
|
||||
values = Object.values(collection);
|
||||
keys = Object.keys(collection);
|
||||
} else {
|
||||
throw new OwlError("Invalid loop expression");
|
||||
throw new OwlError(`Invalid loop expression: "${collection}" is not iterable`);
|
||||
}
|
||||
const n = values.length;
|
||||
return [keys, values, n, new Array(n)];
|
||||
@@ -134,7 +140,7 @@ class LazyValue {
|
||||
* Safely outputs `value` as a block depending on the nature of `value`
|
||||
*/
|
||||
export function safeOutput(value: any, defaultValue?: any): ReturnType<typeof toggler> {
|
||||
if (value === undefined) {
|
||||
if (value === undefined || value === null) {
|
||||
return defaultValue ? toggler("default", defaultValue) : toggler("undefined", text(""));
|
||||
}
|
||||
let safeKey;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { comment, createBlock, html, list, multi, text, toggler } from "./blockd
|
||||
import { getCurrent } from "./component_node";
|
||||
import { Portal, portalTemplate } from "./portal";
|
||||
import { helpers } from "./template_helpers";
|
||||
import { OwlError } from "./error_handling";
|
||||
import { OwlError } from "../common/owl_error";
|
||||
|
||||
const bdom = { text, createBlock, list, multi, html, toggler, comment };
|
||||
|
||||
@@ -66,6 +66,10 @@ export class TemplateSet {
|
||||
|
||||
addTemplate(name: string, template: string | Element) {
|
||||
if (name in this.rawTemplates) {
|
||||
// this check can be expensive, just silently ignore double definitions outside dev mode
|
||||
if (!this.dev) {
|
||||
return;
|
||||
}
|
||||
const rawTemplate = this.rawTemplates[name];
|
||||
const currentAsString =
|
||||
typeof rawTemplate === "string"
|
||||
@@ -125,7 +129,7 @@ export class TemplateSet {
|
||||
|
||||
callTemplate(owner: any, subTemplate: string, ctx: any, parent: any, key: any): any {
|
||||
const template = this.getTemplate(subTemplate);
|
||||
return toggler(subTemplate, template.call(owner, ctx, parent, key));
|
||||
return toggler(subTemplate, template.call(owner, ctx, parent, key + subTemplate));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+29
-15
@@ -1,4 +1,4 @@
|
||||
import { OwlError } from "./error_handling";
|
||||
import { OwlError } from "../common/owl_error";
|
||||
export type Callback = () => void;
|
||||
|
||||
/**
|
||||
@@ -9,20 +9,13 @@ export type Callback = () => void;
|
||||
* @returns a batched version of the original callback
|
||||
*/
|
||||
export function batched(callback: Callback): Callback {
|
||||
let called = false;
|
||||
return async () => {
|
||||
// This await blocks all calls to the callback here, then releases them sequentially
|
||||
// in the next microtick. This line decides the granularity of the batch.
|
||||
await Promise.resolve();
|
||||
if (!called) {
|
||||
called = true;
|
||||
// wait for all calls in this microtick to fall through before resetting "called"
|
||||
// so that only the first call to the batched function calls the original callback.
|
||||
// Schedule this before calling the callback so that calls to the batched function
|
||||
// within the callback will proceed only after resetting called to false, and have
|
||||
// a chance to execute the callback again
|
||||
Promise.resolve().then(() => (called = false));
|
||||
callback();
|
||||
let scheduled = false;
|
||||
return async (...args) => {
|
||||
if (!scheduled) {
|
||||
scheduled = true;
|
||||
await Promise.resolve();
|
||||
scheduled = false;
|
||||
callback(...args);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -95,3 +88,24 @@ export class Markup extends String {}
|
||||
export function markup(value: any) {
|
||||
return new Markup(value);
|
||||
}
|
||||
|
||||
export function possiblySync(computation: Function, onSuccess: Function, onError?: Function) {
|
||||
try {
|
||||
let result;
|
||||
if (onError) {
|
||||
try {
|
||||
result = computation();
|
||||
} catch (e) {
|
||||
return onError(e);
|
||||
}
|
||||
} else {
|
||||
result = computation();
|
||||
}
|
||||
if (typeof result?.then === "function") {
|
||||
return result.then(onSuccess, onError);
|
||||
}
|
||||
return onSuccess(result);
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OwlError } from "./error_handling";
|
||||
import { OwlError } from "../common/owl_error";
|
||||
import { toRaw } from "./reactivity";
|
||||
|
||||
type BaseType =
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
// do not modify manually. This file is generated by the release script.
|
||||
export const version = "2.1.1";
|
||||
export const version = "2.2.6";
|
||||
|
||||
@@ -353,7 +353,7 @@ exports[`Reactivity: useState useless atoms should be deleted 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(Object.keys(ctx['state']));;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`id\`] = v_block2[i1];
|
||||
ctx[\`id\`] = k_block2[i1];
|
||||
const key1 = ctx['id'];
|
||||
c_block2[i1] = withKey(comp1({id: ctx['id']}, key + \`__1__\${key1}\`, node, this, null), key1);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,34 @@ exports[`app App supports env with getters/setters 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`app app: clear scheduler tasks and destroy cancelled nodes immediately on destroy 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
const comp1 = app.createComponent(\`B\`, true, false, false, []);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2, b3;
|
||||
b2 = text(\`A\`);
|
||||
if (ctx['state'].value) {
|
||||
b3 = comp1({}, key + \`__1\`, node, this, null);
|
||||
}
|
||||
return multi([b2, b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`app app: clear scheduler tasks and destroy cancelled nodes immediately on destroy 3`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`B\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`app can configure an app with props 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
|
||||
+75
-2
@@ -1,6 +1,15 @@
|
||||
import { App, Component, mount, xml } from "../../src";
|
||||
import { App, Component, mount, onWillStart, useState, xml } from "../../src";
|
||||
import { status } from "../../src/runtime/status";
|
||||
import { makeTestFixture, snapshotEverything, nextTick, elem } from "../helpers";
|
||||
import {
|
||||
makeTestFixture,
|
||||
snapshotEverything,
|
||||
nextTick,
|
||||
elem,
|
||||
useLogLifecycle,
|
||||
makeDeferred,
|
||||
nextMicroTick,
|
||||
steps,
|
||||
} from "../helpers";
|
||||
|
||||
let fixture: HTMLElement;
|
||||
|
||||
@@ -94,4 +103,68 @@ describe("app", () => {
|
||||
expect(iframeDoc.contains(div)).toBe(false);
|
||||
expect(status(comp)).toBe("destroyed");
|
||||
});
|
||||
|
||||
test("app: clear scheduler tasks and destroy cancelled nodes immediately on destroy", async () => {
|
||||
let def = makeDeferred();
|
||||
class B extends Component {
|
||||
static template = xml`B`;
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
onWillStart(() => def);
|
||||
}
|
||||
}
|
||||
class A extends Component {
|
||||
static template = xml`A<t t-if="state.value"><B/></t>`;
|
||||
static components = { B };
|
||||
state = useState({ value: false });
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
}
|
||||
}
|
||||
|
||||
const app = new App(A);
|
||||
const comp = await app.mount(fixture);
|
||||
expect(steps.splice(0)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"A:setup",
|
||||
"A:willStart",
|
||||
"A:willRender",
|
||||
"A:rendered",
|
||||
"A:mounted",
|
||||
]
|
||||
`);
|
||||
|
||||
comp.state.value = true;
|
||||
await nextTick();
|
||||
expect(steps.splice(0)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"A:willRender",
|
||||
"B:setup",
|
||||
"B:willStart",
|
||||
"A:rendered",
|
||||
]
|
||||
`);
|
||||
|
||||
// rerender to force the instantiation of a new B component (and cancelling the first)
|
||||
comp.render();
|
||||
await nextMicroTick();
|
||||
expect(steps.splice(0)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"A:willRender",
|
||||
"B:setup",
|
||||
"B:willStart",
|
||||
"A:rendered",
|
||||
]
|
||||
`);
|
||||
|
||||
app.destroy();
|
||||
expect(steps.splice(0)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"A:willUnmount",
|
||||
"B:willDestroy",
|
||||
"A:willDestroy",
|
||||
"B:willDestroy",
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -244,12 +244,14 @@ describe("misc", () => {
|
||||
});
|
||||
|
||||
test("namespace is not propagated to siblings", () => {
|
||||
const block = createBlock(`<div><svg block-ns="someNameSpace"><g/></svg><div></div></div>`);
|
||||
const block = createBlock(`<div><svg xmlns="someNameSpace"><g/></svg><div></div></div>`);
|
||||
|
||||
const fixture = makeTestFixture();
|
||||
mount(block(), fixture);
|
||||
|
||||
expect(fixture.innerHTML).toBe("<div><svg><g></g></svg><div></div></div>");
|
||||
expect(fixture.innerHTML).toBe(
|
||||
'<div><svg xmlns="someNameSpace"><g></g></svg><div></div></div>'
|
||||
);
|
||||
expect(fixture.querySelector("svg")!.namespaceURI).toBe("someNameSpace");
|
||||
expect(fixture.querySelector("g")!.namespaceURI).toBe("someNameSpace");
|
||||
const allDivs = fixture.querySelectorAll("div");
|
||||
|
||||
@@ -145,3 +145,34 @@ test("class attribute (with a preexisting value", async () => {
|
||||
patch(tree, block([""]));
|
||||
expect(fixture.innerHTML).toBe(`<div class="tomato"></div>`);
|
||||
});
|
||||
|
||||
test("block-class attributes with preexisting class attribute", async () => {
|
||||
const block = createBlock('<div block-attributes="0" class="owl"></div>');
|
||||
const tree = block([{ class: "eagle" }]);
|
||||
|
||||
mount(tree, fixture);
|
||||
expect(fixture.innerHTML).toBe(`<div class="owl eagle"></div>`);
|
||||
|
||||
patch(tree, block([{ class: "falcon" }]));
|
||||
expect(fixture.innerHTML).toBe(`<div class="owl falcon"></div>`);
|
||||
|
||||
patch(tree, block([{}]));
|
||||
expect(fixture.innerHTML).toBe(`<div class="owl"></div>`);
|
||||
});
|
||||
|
||||
test("block-class attributes (array syntax) with preexisting class attribute", async () => {
|
||||
const block = createBlock('<div block-attributes="0" class="owl"></div>');
|
||||
const tree = block([["class", "eagle"]]);
|
||||
|
||||
mount(tree, fixture);
|
||||
expect(fixture.innerHTML).toBe(`<div class="owl eagle"></div>`);
|
||||
|
||||
patch(tree, block([["class", "falcon"]]));
|
||||
expect(fixture.innerHTML).toBe(`<div class="owl falcon"></div>`);
|
||||
|
||||
patch(tree, block([["class", ""]]));
|
||||
expect(fixture.innerHTML).toBe(`<div class="owl"></div>`);
|
||||
|
||||
patch(tree, block([["class", "buzzard"]]));
|
||||
expect(fixture.innerHTML).toBe(`<div class="owl buzzard"></div>`);
|
||||
});
|
||||
|
||||
@@ -30,22 +30,22 @@ describe("namespace", () => {
|
||||
expect(fixture.firstElementChild!.namespaceURI).toBe(XHTML_URI);
|
||||
});
|
||||
|
||||
test("namespace can be changed with block-ns", () => {
|
||||
const block = createBlock(`<tag block-ns="${SVG_URI}"/>`);
|
||||
test("namespace can be changed with xmlns", () => {
|
||||
const block = createBlock(`<tag xmlns="${SVG_URI}"/>`);
|
||||
const tree = block();
|
||||
mount(tree, fixture);
|
||||
expect(fixture.innerHTML).toBe("<tag></tag>");
|
||||
expect(fixture.innerHTML).toBe(`<tag xmlns="${SVG_URI}"></tag>`);
|
||||
expect(fixture.firstElementChild!.namespaceURI).toBe(SVG_URI);
|
||||
});
|
||||
|
||||
test("namespace is kept for children", () => {
|
||||
const block = createBlock(
|
||||
`<parent block-ns="${SVG_URI}"><child><subchild/></child><child/></parent>`
|
||||
`<parent xmlns="${SVG_URI}"><child><subchild/></child><child/></parent>`
|
||||
);
|
||||
const tree = block();
|
||||
mount(tree, fixture);
|
||||
expect(fixture.innerHTML).toBe(
|
||||
"<parent><child><subchild></subchild></child><child></child></parent>"
|
||||
`<parent xmlns="${SVG_URI}"><child><subchild></subchild></child><child></child></parent>`
|
||||
);
|
||||
const parent = fixture.firstElementChild!;
|
||||
const child1 = parent.firstElementChild!;
|
||||
@@ -58,10 +58,10 @@ describe("namespace", () => {
|
||||
});
|
||||
|
||||
test("various namespaces in same block", () => {
|
||||
const block = createBlock(`<none><one block-ns="one"/><two block-ns="two"/></none>`);
|
||||
const block = createBlock(`<none><one xmlns="one"/><two xmlns="two"/></none>`);
|
||||
const tree = block();
|
||||
mount(tree, fixture);
|
||||
expect(fixture.innerHTML).toBe("<none><one></one><two></two></none>");
|
||||
expect(fixture.innerHTML).toBe('<none><one xmlns="one"></one><two xmlns="two"></two></none>');
|
||||
const none = fixture.firstElementChild!;
|
||||
const one = none.firstElementChild!;
|
||||
const two = one.nextElementSibling!;
|
||||
|
||||
@@ -707,6 +707,123 @@ exports[`attributes updating classes (with obj notation) 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`attributes various combinations of class, t-att-class, and t-att 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div block-attributes=\\"0\\" class=\\"c\\">content</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let attr1 = {class:'a'};
|
||||
return block1([attr1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`attributes various combinations of class, t-att-class, and t-att 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div block-attributes=\\"0\\" block-attribute-1=\\"class\\" class=\\"c\\">content</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let attr1 = {class:'a'};
|
||||
let attr2 = {'b':true};
|
||||
return block1([attr1, attr2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`attributes various combinations of class, t-att-class, and t-att 3`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div block-attributes=\\"0\\" class=\\"c\\" block-attribute-1=\\"class\\">content</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let attr1 = {class:'a'};
|
||||
let attr2 = {'b':true};
|
||||
return block1([attr1, attr2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`attributes various combinations of class, t-att-class, and t-att 4`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div class=\\"c\\" block-attributes=\\"0\\" block-attribute-1=\\"class\\">content</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let attr1 = {class:'a'};
|
||||
let attr2 = {'b':true};
|
||||
return block1([attr1, attr2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`attributes various combinations of class, t-att-class, and t-att 5`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div class=\\"c\\" block-attribute-0=\\"class\\" block-attributes=\\"1\\">content</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let attr1 = {'b':true};
|
||||
let attr2 = {class:'a'};
|
||||
return block1([attr1, attr2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`attributes various combinations of class, t-att-class, and t-att 6`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div class=\\"c\\" block-attribute-0=\\"class\\">content</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let attr1 = {'b':true};
|
||||
return block1([attr1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`attributes various combinations of class, t-att-class, and t-att 7`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div class=\\"c\\" block-attribute-0=\\"class\\">content</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let attr1 = ('b');
|
||||
return block1([attr1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`attributes various combinations of class, t-att-class, and t-att 8`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div block-attributes=\\"0\\" block-attribute-1=\\"class\\">content</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let attr1 = {class:'a'};
|
||||
let attr2 = {'b':true};
|
||||
return block1([attr1, attr2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`attributes various escapes 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -34,7 +34,7 @@ exports[`comments properly handle comments between t-if/t-else 1`] = `
|
||||
let block3 = createBlock(\`<span>owl</span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2,b3;
|
||||
let b2, b3;
|
||||
if (true) {
|
||||
b2 = block2();
|
||||
} else {
|
||||
|
||||
@@ -72,7 +72,7 @@ exports[`t-on can bind handlers with empty object (with non empty inner string)
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(['someval']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`action\`] = v_block2[i1];
|
||||
ctx[\`action\`] = k_block2[i1];
|
||||
ctx[\`action_index\`] = i1;
|
||||
const key1 = ctx['action_index'];
|
||||
const v1 = ctx['activate'];
|
||||
@@ -142,7 +142,7 @@ exports[`t-on handler is bound to proper owner, part 2 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block1, v_block1, l_block1, c_block1] = prepareList([1]);;
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`value\`] = v_block1[i1];
|
||||
ctx[\`value\`] = k_block1[i1];
|
||||
const key1 = ctx['value'];
|
||||
let hdlr1 = [ctx['add'], ctx];
|
||||
c_block1[i1] = withKey(block2([hdlr1]), key1);
|
||||
@@ -189,11 +189,11 @@ exports[`t-on handler is bound to proper owner, part 4 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block1, v_block1, l_block1, c_block1] = prepareList([1]);;
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`value\`] = v_block1[i1];
|
||||
ctx[\`value\`] = k_block1[i1];
|
||||
ctx[\`value_first\`] = i1 === 0;
|
||||
ctx[\`value_last\`] = i1 === v_block1.length - 1;
|
||||
ctx[\`value_last\`] = i1 === k_block1.length - 1;
|
||||
ctx[\`value_index\`] = i1;
|
||||
ctx[\`value_value\`] = k_block1[i1];
|
||||
ctx[\`value_value\`] = v_block1[i1];
|
||||
const key1 = ctx['value'];
|
||||
c_block1[i1] = withKey(callTemplate_1.call(this, ctx, node, key + \`__1__\${key1}\`), key1);
|
||||
}
|
||||
@@ -348,7 +348,7 @@ exports[`t-on t-on modifiers (native listener) t-on with prevent modifier in t-f
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['projects']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`project\`] = v_block2[i1];
|
||||
ctx[\`project\`] = k_block2[i1];
|
||||
const key1 = ctx['project'];
|
||||
const v1 = ctx['onEdit'];
|
||||
const v2 = ctx['project'];
|
||||
|
||||
@@ -18,7 +18,7 @@ exports[`misc complex template 1`] = `
|
||||
let block13 = createBlock(\`<i class=\\"fa fa-fw fa-clock-o\\" title=\\"This commit is the head of a base branch\\"/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2,b3,b4,b6,b8;
|
||||
let b2, b3, b4, b6, b8;
|
||||
let attr1 = \`batch_tile \${ctx['options'].more?'more':'nomore'}\`;
|
||||
let attr2 = \`card bg-\${ctx['klass']}-light\`;
|
||||
let attr3 = \`/runbot/batch/\${ctx['batch'].id}\`;
|
||||
@@ -33,7 +33,7 @@ exports[`misc complex template 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block4, v_block4, l_block4, c_block4] = prepareList(ctx['batch'].slot_ids.filter(_slot=>_slot.build_id.id&&!_slot.trigger_id.manual&&(ctx['options'].trigger_display[_slot.trigger_id.id])));;
|
||||
for (let i1 = 0; i1 < l_block4; i1++) {
|
||||
ctx[\`slot\`] = v_block4[i1];
|
||||
ctx[\`slot\`] = k_block4[i1];
|
||||
const key1 = ctx['slot'].id;
|
||||
c_block4[i1] = withKey(comp1({class: ctx['slot_container'],slot: ctx['slot']}, key + \`__1__\${key1}\`, node, this, null), key1);
|
||||
}
|
||||
@@ -42,7 +42,7 @@ exports[`misc complex template 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block6, v_block6, l_block6, c_block6] = prepareList([1,2,3,4]);;
|
||||
for (let i1 = 0; i1 < l_block6; i1++) {
|
||||
ctx[\`x\`] = v_block6[i1];
|
||||
ctx[\`x\`] = k_block6[i1];
|
||||
const key1 = ctx['x'];
|
||||
c_block6[i1] = withKey(block7(), key1);
|
||||
}
|
||||
@@ -51,9 +51,9 @@ exports[`misc complex template 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block8, v_block8, l_block8, c_block8] = prepareList(ctx['commit_links']);;
|
||||
for (let i1 = 0; i1 < l_block8; i1++) {
|
||||
ctx[\`commit_link\`] = v_block8[i1];
|
||||
ctx[\`commit_link\`] = k_block8[i1];
|
||||
const key1 = ctx['commit_link'].id;
|
||||
let b10,b11,b12,b13;
|
||||
let b10, b11, b12, b13;
|
||||
let attr5 = \`/runbot/commit/\${ctx['commit_link'].commit_id}\`;
|
||||
let attr6 = \`badge badge-light batch_commit match_type_\${ctx['commit_link'].match_type}\`;
|
||||
if (ctx['commit_link'].match_type=='new') {
|
||||
@@ -99,11 +99,11 @@ exports[`misc global 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList([4,5,6]);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`value\`] = v_block2[i1];
|
||||
ctx[\`value\`] = k_block2[i1];
|
||||
ctx[\`value_first\`] = i1 === 0;
|
||||
ctx[\`value_last\`] = i1 === v_block2.length - 1;
|
||||
ctx[\`value_last\`] = i1 === k_block2.length - 1;
|
||||
ctx[\`value_index\`] = i1;
|
||||
ctx[\`value_value\`] = k_block2[i1];
|
||||
ctx[\`value_value\`] = v_block2[i1];
|
||||
const key1 = ctx['value'];
|
||||
let txt1 = ctx['value'];
|
||||
const b4 = block4([txt1]);
|
||||
@@ -112,16 +112,16 @@ exports[`misc global 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
setContextValue(ctx, \\"foo\\", 'aaa');
|
||||
const b6 = callTemplate_1.call(this, ctx, node, key + \`__1__\${key1}\`);
|
||||
const b7 = callTemplate_1.call(this, ctx, node, key + \`__1__\${key1}\`);
|
||||
ctx = ctx.__proto__;
|
||||
const b7 = callTemplate_2.call(this, ctx, node, key + \`__2__\${key1}\`);
|
||||
const b8 = callTemplate_2.call(this, ctx, node, key + \`__2__\${key1}\`);
|
||||
setContextValue(ctx, \\"foo\\", 'bbb');
|
||||
const b8 = callTemplate_3.call(this, ctx, node, key + \`__3__\${key1}\`);
|
||||
const b5 = multi([b6, b7, b8]);
|
||||
ctx[zero] = b5;
|
||||
const b9 = callTemplate_4.call(this, ctx, node, key + \`__4__\${key1}\`);
|
||||
const b9 = callTemplate_3.call(this, ctx, node, key + \`__3__\${key1}\`);
|
||||
const b6 = multi([b7, b8, b9]);
|
||||
ctx[zero] = b6;
|
||||
const b5 = callTemplate_4.call(this, ctx, node, key + \`__4__\${key1}\`);
|
||||
ctx = ctx.__proto__;
|
||||
c_block2[i1] = withKey(multi([b4, b9]), key1);
|
||||
c_block2[i1] = withKey(multi([b4, b5]), key1);
|
||||
}
|
||||
ctx = ctx.__proto__;
|
||||
const b2 = list(c_block2);
|
||||
@@ -217,13 +217,13 @@ exports[`misc other complex template 1`] = `
|
||||
let block25 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2,b4,b14,b17,b22,b23,b24,b25;
|
||||
let b2, b4, b14, b17, b22, b23, b24, b25;
|
||||
let attr1 = \`/runbot/\${ctx['project'].slug}\`;
|
||||
let txt1 = ctx['project'].name;
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['projects']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`project\`] = v_block2[i1];
|
||||
ctx[\`project\`] = k_block2[i1];
|
||||
const key1 = ctx['project'].id;
|
||||
let hdlr1 = [ctx['selectProject'](ctx['project']), ctx];
|
||||
let txt2 = ctx['project'].name;
|
||||
@@ -232,12 +232,12 @@ exports[`misc other complex template 1`] = `
|
||||
ctx = ctx.__proto__;
|
||||
b2 = list(c_block2);
|
||||
if (ctx['user']) {
|
||||
let b5,b6;
|
||||
let b5, b6;
|
||||
if (ctx['user'].public) {
|
||||
let attr2 = \`/web/login?redirect=/\`;
|
||||
b5 = block5([attr2]);
|
||||
} else {
|
||||
let b7,b10,b13;
|
||||
let b7, b10, b13;
|
||||
if (ctx['nb_assigned_errors']&&ctx['nb_assigned_errors']>0) {
|
||||
let attr3 = \`You have \${ctx['nb_assigned_errors']} random bug assigned\`;
|
||||
let txt3 = ctx['nb_assigned_errors'];
|
||||
@@ -263,7 +263,7 @@ exports[`misc other complex template 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block15, v_block15, l_block15, c_block15] = prepareList(ctx['categories']);;
|
||||
for (let i1 = 0; i1 < l_block15; i1++) {
|
||||
ctx[\`category\`] = v_block15[i1];
|
||||
ctx[\`category\`] = k_block15[i1];
|
||||
const key1 = ctx['category'].id;
|
||||
let attr6 = ctx['category'].id;
|
||||
let prop1 = new Boolean(ctx['category'].id==ctx['options'].active_category_id);
|
||||
@@ -284,7 +284,7 @@ exports[`misc other complex template 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block18, v_block18, l_block18, c_block18] = prepareList(ctx['triggers']);;
|
||||
for (let i1 = 0; i1 < l_block18; i1++) {
|
||||
ctx[\`trigger\`] = v_block18[i1];
|
||||
ctx[\`trigger\`] = k_block18[i1];
|
||||
const key1 = ctx['trigger'].id;
|
||||
let b20;
|
||||
if (!ctx['trigger'].manual&&ctx['trigger'].project_id===ctx['project'].id&&ctx['trigger'].category_id===ctx['options'].active_category_id) {
|
||||
|
||||
@@ -12,7 +12,7 @@ exports[`memory t-foreach does not leak stuff in global scope 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList([3,2,1]);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`item\`] = v_block2[i1];
|
||||
ctx[\`item\`] = k_block2[i1];
|
||||
ctx[\`item_index\`] = i1;
|
||||
const key1 = ctx['item_index'];
|
||||
c_block2[i1] = withKey(text(ctx['item']), key1);
|
||||
|
||||
@@ -5,7 +5,7 @@ exports[`properly support svg add proper namespace to g tags 1`] = `
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<g block-ns=\\"http://www.w3.org/2000/svg\\"><circle cx=\\"50\\" cy=\\"50\\" r=\\"4\\" stroke=\\"green\\" stroke-width=\\"1\\" fill=\\"yellow\\"/> </g>\`);
|
||||
let block1 = createBlock(\`<g xmlns=\\"http://www.w3.org/2000/svg\\"><circle cx=\\"50\\" cy=\\"50\\" r=\\"4\\" stroke=\\"green\\" stroke-width=\\"1\\" fill=\\"yellow\\"/> </g>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
@@ -18,7 +18,7 @@ exports[`properly support svg add proper namespace to svg 1`] = `
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\" width=\\"100px\\" height=\\"90px\\"><circle cx=\\"50\\" cy=\\"50\\" r=\\"4\\" stroke=\\"green\\" stroke-width=\\"1\\" fill=\\"yellow\\"/> </svg>\`);
|
||||
let block1 = createBlock(\`<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"100px\\" height=\\"90px\\"><circle cx=\\"50\\" cy=\\"50\\" r=\\"4\\" stroke=\\"green\\" stroke-width=\\"1\\" fill=\\"yellow\\"/> </svg>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
@@ -31,7 +31,7 @@ exports[`properly support svg namespace to g tags not added if already in svg na
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><g/></svg>\`);
|
||||
let block1 = createBlock(\`<svg xmlns=\\"http://www.w3.org/2000/svg\\"><g/></svg>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
@@ -44,7 +44,7 @@ exports[`properly support svg namespace to svg tags added even if already in svg
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><svg/></svg>\`);
|
||||
let block1 = createBlock(\`<svg xmlns=\\"http://www.w3.org/2000/svg\\"><svg/></svg>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
@@ -58,8 +58,8 @@ exports[`properly support svg svg creates new block if it is within html -- 2 1`
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
let block2 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><polygon fill=\\"#000000\\" points=\\"0 0 4 4 8 0\\" transform=\\"translate(5 7)\\"/><block-child-0/></svg>\`);
|
||||
let block3 = createBlock(\`<path block-ns=\\"http://www.w3.org/2000/svg\\"/>\`);
|
||||
let block2 = createBlock(\`<svg xmlns=\\"http://www.w3.org/2000/svg\\"><polygon fill=\\"#000000\\" points=\\"0 0 4 4 8 0\\" transform=\\"translate(5 7)\\"/><block-child-0 xmlns=\\"\\"/></svg>\`);
|
||||
let block3 = createBlock(\`<path xmlns=\\"http://www.w3.org/2000/svg\\"/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3;
|
||||
@@ -78,7 +78,7 @@ exports[`properly support svg svg creates new block if it is within html 1`] = `
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
let block2 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><polygon fill=\\"#000000\\" points=\\"0 0 4 4 8 0\\" transform=\\"translate(5 7)\\"/></svg>\`);
|
||||
let block2 = createBlock(\`<svg xmlns=\\"http://www.w3.org/2000/svg\\"><polygon fill=\\"#000000\\" points=\\"0 0 4 4 8 0\\" transform=\\"translate(5 7)\\"/></svg>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const b2 = block2();
|
||||
@@ -93,7 +93,7 @@ exports[`properly support svg svg namespace added to sub templates if root tag i
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
const callTemplate_1 = app.getTemplate(\`path\`);
|
||||
|
||||
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><block-child-0/></svg>\`);
|
||||
let block1 = createBlock(\`<svg xmlns=\\"http://www.w3.org/2000/svg\\"><block-child-0 xmlns=\\"\\"/></svg>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
@@ -107,7 +107,7 @@ exports[`properly support svg svg namespace added to sub templates if root tag i
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<path block-ns=\\"http://www.w3.org/2000/svg\\"/>\`);
|
||||
let block1 = createBlock(\`<path xmlns=\\"http://www.w3.org/2000/svg\\"/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
@@ -120,8 +120,8 @@ exports[`properly support svg svg namespace added to sub-blocks 1`] = `
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><block-child-0/></svg>\`);
|
||||
let block2 = createBlock(\`<path block-ns=\\"http://www.w3.org/2000/svg\\"/>\`);
|
||||
let block1 = createBlock(\`<svg xmlns=\\"http://www.w3.org/2000/svg\\"><block-child-0 xmlns=\\"\\"/></svg>\`);
|
||||
let block2 = createBlock(\`<path xmlns=\\"http://www.w3.org/2000/svg\\"/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2;
|
||||
|
||||
@@ -61,19 +61,19 @@ exports[`t-call (template calling) call with several sub nodes on same line 1`]
|
||||
const callTemplate_1 = app.getTemplate(\`sub\`);
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
let block3 = createBlock(\`<span>hey</span>\`);
|
||||
let block5 = createBlock(\`<span>yay</span>\`);
|
||||
let block4 = createBlock(\`<span>hey</span>\`);
|
||||
let block6 = createBlock(\`<span>yay</span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
const b3 = block3();
|
||||
const b4 = text(\` \`);
|
||||
const b5 = block5();
|
||||
const b2 = multi([b3, b4, b5]);
|
||||
ctx[zero] = b2;
|
||||
const b6 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
return block1([], [b6]);
|
||||
const b4 = block4();
|
||||
const b5 = text(\` \`);
|
||||
const b6 = block6();
|
||||
const b3 = multi([b4, b5, b6]);
|
||||
ctx[zero] = b3;
|
||||
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -101,19 +101,19 @@ exports[`t-call (template calling) cascading t-call t-out='0' 1`] = `
|
||||
const callTemplate_1 = app.getTemplate(\`subTemplate\`);
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
let block3 = createBlock(\`<span>hey</span>\`);
|
||||
let block5 = createBlock(\`<span>yay</span>\`);
|
||||
let block4 = createBlock(\`<span>hey</span>\`);
|
||||
let block6 = createBlock(\`<span>yay</span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
const b3 = block3();
|
||||
const b4 = text(\` \`);
|
||||
const b5 = block5();
|
||||
const b2 = multi([b3, b4, b5]);
|
||||
ctx[zero] = b2;
|
||||
const b6 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
return block1([], [b6]);
|
||||
const b4 = block4();
|
||||
const b5 = text(\` \`);
|
||||
const b6 = block6();
|
||||
const b3 = multi([b4, b5, b6]);
|
||||
ctx[zero] = b3;
|
||||
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -126,17 +126,17 @@ exports[`t-call (template calling) cascading t-call t-out='0' 2`] = `
|
||||
const callTemplate_1 = app.getTemplate(\`subSubTemplate\`);
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
let block3 = createBlock(\`<span>cascade 0</span>\`);
|
||||
let block4 = createBlock(\`<span>cascade 0</span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
const b3 = block3();
|
||||
const b4 = ctx[zero];
|
||||
const b2 = multi([b3, b4]);
|
||||
ctx[zero] = b2;
|
||||
const b5 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
return block1([], [b5]);
|
||||
const b4 = block4();
|
||||
const b5 = ctx[zero];
|
||||
const b3 = multi([b4, b5]);
|
||||
ctx[zero] = b3;
|
||||
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -149,17 +149,17 @@ exports[`t-call (template calling) cascading t-call t-out='0' 3`] = `
|
||||
const callTemplate_1 = app.getTemplate(\`finalTemplate\`);
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
let block3 = createBlock(\`<span>cascade 1</span>\`);
|
||||
let block4 = createBlock(\`<span>cascade 1</span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
const b3 = block3();
|
||||
const b4 = ctx[zero];
|
||||
const b2 = multi([b3, b4]);
|
||||
ctx[zero] = b2;
|
||||
const b5 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
return block1([], [b5]);
|
||||
const b4 = block4();
|
||||
const b5 = ctx[zero];
|
||||
const b3 = multi([b4, b5]);
|
||||
ctx[zero] = b3;
|
||||
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -186,17 +186,17 @@ exports[`t-call (template calling) cascading t-call t-out='0', without external
|
||||
let { isBoundary, zero } = helpers;
|
||||
const callTemplate_1 = app.getTemplate(\`subTemplate\`);
|
||||
|
||||
let block2 = createBlock(\`<span>hey</span>\`);
|
||||
let block4 = createBlock(\`<span>yay</span>\`);
|
||||
let block3 = createBlock(\`<span>hey</span>\`);
|
||||
let block5 = createBlock(\`<span>yay</span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
const b2 = block2();
|
||||
const b3 = text(\` \`);
|
||||
const b4 = block4();
|
||||
const b1 = multi([b2, b3, b4]);
|
||||
ctx[zero] = b1;
|
||||
const b3 = block3();
|
||||
const b4 = text(\` \`);
|
||||
const b5 = block5();
|
||||
const b2 = multi([b3, b4, b5]);
|
||||
ctx[zero] = b2;
|
||||
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
}
|
||||
}"
|
||||
@@ -209,15 +209,15 @@ exports[`t-call (template calling) cascading t-call t-out='0', without external
|
||||
let { isBoundary, zero } = helpers;
|
||||
const callTemplate_1 = app.getTemplate(\`subSubTemplate\`);
|
||||
|
||||
let block2 = createBlock(\`<span>cascade 0</span>\`);
|
||||
let block3 = createBlock(\`<span>cascade 0</span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
const b2 = block2();
|
||||
const b3 = ctx[zero];
|
||||
const b1 = multi([b2, b3]);
|
||||
ctx[zero] = b1;
|
||||
const b3 = block3();
|
||||
const b4 = ctx[zero];
|
||||
const b2 = multi([b3, b4]);
|
||||
ctx[zero] = b2;
|
||||
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
}
|
||||
}"
|
||||
@@ -230,15 +230,15 @@ exports[`t-call (template calling) cascading t-call t-out='0', without external
|
||||
let { isBoundary, zero } = helpers;
|
||||
const callTemplate_1 = app.getTemplate(\`finalTemplate\`);
|
||||
|
||||
let block2 = createBlock(\`<span>cascade 1</span>\`);
|
||||
let block3 = createBlock(\`<span>cascade 1</span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
const b2 = block2();
|
||||
const b3 = ctx[zero];
|
||||
const b1 = multi([b2, b3]);
|
||||
ctx[zero] = b1;
|
||||
const b3 = block3();
|
||||
const b4 = ctx[zero];
|
||||
const b2 = multi([b3, b4]);
|
||||
ctx[zero] = b2;
|
||||
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
}
|
||||
}"
|
||||
@@ -342,15 +342,15 @@ exports[`t-call (template calling) nested t-calls with magic variable 0 1`] = `
|
||||
const callTemplate_1 = app.getTemplate(\`grandchild\`);
|
||||
const callTemplate_2 = app.getTemplate(\`child\`);
|
||||
|
||||
let block1 = createBlock(\`<p>Some content...</p>\`);
|
||||
let block3 = createBlock(\`<p>Some content...</p>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
const b1 = block1();
|
||||
ctx[zero] = b1;
|
||||
const b3 = block3();
|
||||
ctx[zero] = b3;
|
||||
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
ctx = ctx.__proto__;
|
||||
ctx[zero] = b2;
|
||||
@@ -440,11 +440,11 @@ exports[`t-call (template calling) recursive template, part 2 2`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['node'].children||[]);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`subtree\`] = v_block2[i1];
|
||||
ctx[\`subtree\`] = k_block2[i1];
|
||||
ctx[\`subtree_first\`] = i1 === 0;
|
||||
ctx[\`subtree_last\`] = i1 === v_block2.length - 1;
|
||||
ctx[\`subtree_last\`] = i1 === k_block2.length - 1;
|
||||
ctx[\`subtree_index\`] = i1;
|
||||
ctx[\`subtree_value\`] = k_block2[i1];
|
||||
ctx[\`subtree_value\`] = v_block2[i1];
|
||||
const key1 = ctx['subtree_index'];
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
@@ -495,11 +495,11 @@ exports[`t-call (template calling) recursive template, part 3 2`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['node'].children||[]);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`subtree\`] = v_block2[i1];
|
||||
ctx[\`subtree\`] = k_block2[i1];
|
||||
ctx[\`subtree_first\`] = i1 === 0;
|
||||
ctx[\`subtree_last\`] = i1 === v_block2.length - 1;
|
||||
ctx[\`subtree_last\`] = i1 === k_block2.length - 1;
|
||||
ctx[\`subtree_index\`] = i1;
|
||||
ctx[\`subtree_value\`] = k_block2[i1];
|
||||
ctx[\`subtree_value\`] = v_block2[i1];
|
||||
const key1 = ctx['subtree_index'];
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
@@ -553,11 +553,11 @@ exports[`t-call (template calling) recursive template, part 4: with t-set recurs
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['node'].children||[]);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`subtree\`] = v_block2[i1];
|
||||
ctx[\`subtree\`] = k_block2[i1];
|
||||
ctx[\`subtree_first\`] = i1 === 0;
|
||||
ctx[\`subtree_last\`] = i1 === v_block2.length - 1;
|
||||
ctx[\`subtree_last\`] = i1 === k_block2.length - 1;
|
||||
ctx[\`subtree_index\`] = i1;
|
||||
ctx[\`subtree_value\`] = k_block2[i1];
|
||||
ctx[\`subtree_value\`] = v_block2[i1];
|
||||
const key1 = ctx['subtree_index'];
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
@@ -571,6 +571,134 @@ exports[`t-call (template calling) recursive template, part 4: with t-set recurs
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-call (template calling) root t-call with body: t-foreach 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { isBoundary, prepareList, withKey, zero } = helpers;
|
||||
const callTemplate_1 = app.getTemplate(\`subTemplate\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList([1]);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`i\`] = k_block2[i1];
|
||||
const key1 = ctx['i'];
|
||||
c_block2[i1] = withKey(text(\`1\`), key1);
|
||||
}
|
||||
ctx = ctx.__proto__;
|
||||
const b2 = list(c_block2);
|
||||
ctx[zero] = b2;
|
||||
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-call (template calling) root t-call with body: t-foreach 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`sub\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-call (template calling) root t-call with body: t-if false 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { isBoundary, zero } = helpers;
|
||||
const callTemplate_1 = app.getTemplate(\`subTemplate\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
let b3;
|
||||
if (false) {
|
||||
b3 = text(\`zero\`);
|
||||
}
|
||||
const b2 = multi([b3]);
|
||||
ctx[zero] = b2;
|
||||
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-call (template calling) root t-call with body: t-if false 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`sub\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-call (template calling) root t-call with body: t-if true 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { isBoundary, zero } = helpers;
|
||||
const callTemplate_1 = app.getTemplate(\`subTemplate\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
let b3;
|
||||
if (true) {
|
||||
b3 = text(\`zero\`);
|
||||
}
|
||||
const b2 = multi([b3]);
|
||||
ctx[zero] = b2;
|
||||
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-call (template calling) root t-call with body: t-if true 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`sub\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-call (template calling) root t-call with body: t-out with default 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { isBoundary, safeOutput, zero } = helpers;
|
||||
const callTemplate_1 = app.getTemplate(\`subTemplate\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
const b2 = safeOutput(ctx['nothing']);
|
||||
ctx[zero] = b2;
|
||||
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-call (template calling) root t-call with body: t-out with default 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`sub\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-call (template calling) scoped parameters 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
@@ -705,13 +833,13 @@ exports[`t-call (template calling) t-call with body content as root of a templat
|
||||
let { isBoundary, zero } = helpers;
|
||||
const callTemplate_1 = app.getTemplate(\`antony\`);
|
||||
|
||||
let block1 = createBlock(\`<p>antony</p>\`);
|
||||
let block2 = createBlock(\`<p>antony</p>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
const b1 = block1();
|
||||
ctx[zero] = b1;
|
||||
const b2 = block2();
|
||||
ctx[zero] = b2;
|
||||
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
}
|
||||
}"
|
||||
@@ -813,11 +941,11 @@ exports[`t-call (template calling) t-call with t-set inside and outside 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['list']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`v\`] = v_block2[i1];
|
||||
ctx[\`v\`] = k_block2[i1];
|
||||
ctx[\`v_first\`] = i1 === 0;
|
||||
ctx[\`v_last\`] = i1 === v_block2.length - 1;
|
||||
ctx[\`v_last\`] = i1 === k_block2.length - 1;
|
||||
ctx[\`v_index\`] = i1;
|
||||
ctx[\`v_value\`] = k_block2[i1];
|
||||
ctx[\`v_value\`] = v_block2[i1];
|
||||
const key1 = ctx['v_index'];
|
||||
setContextValue(ctx, \\"val\\", ctx['v'].val);
|
||||
ctx = Object.create(ctx);
|
||||
@@ -880,11 +1008,11 @@ exports[`t-call (template calling) t-call with t-set inside and outside. 2 2`] =
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['list']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`v\`] = v_block2[i1];
|
||||
ctx[\`v\`] = k_block2[i1];
|
||||
ctx[\`v_first\`] = i1 === 0;
|
||||
ctx[\`v_last\`] = i1 === v_block2.length - 1;
|
||||
ctx[\`v_last\`] = i1 === k_block2.length - 1;
|
||||
ctx[\`v_index\`] = i1;
|
||||
ctx[\`v_value\`] = k_block2[i1];
|
||||
ctx[\`v_value\`] = v_block2[i1];
|
||||
const key1 = ctx['v_index'];
|
||||
setContextValue(ctx, \\"val\\", ctx['v'].val);
|
||||
ctx = Object.create(ctx);
|
||||
@@ -928,7 +1056,7 @@ exports[`t-call (template calling) t-call, conditional and t-set in t-call body
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1
|
||||
let b2,b3;
|
||||
let b2, b3;
|
||||
setContextValue(ctx, \\"v1\\", 'elif');
|
||||
if (ctx['v1']==='if') {
|
||||
b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
@@ -1075,8 +1203,8 @@ exports[`t-call (template calling) with unused body 1`] = `
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
const b1 = text(\`WHEEE\`);
|
||||
ctx[zero] = b1;
|
||||
const b2 = text(\`WHEEE\`);
|
||||
ctx[zero] = b2;
|
||||
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
}
|
||||
}"
|
||||
@@ -1136,8 +1264,8 @@ exports[`t-call (template calling) with used body 1`] = `
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
const b1 = text(\`ok\`);
|
||||
ctx[zero] = b1;
|
||||
const b2 = text(\`ok\`);
|
||||
ctx[zero] = b2;
|
||||
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
}
|
||||
}"
|
||||
|
||||
@@ -180,15 +180,15 @@ exports[`t-esc t-esc=0 is escaped 1`] = `
|
||||
const callTemplate_1 = app.getTemplate(\`sub\`);
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
let block2 = createBlock(\`<p>escaped</p>\`);
|
||||
let block3 = createBlock(\`<p>escaped</p>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
const b2 = block2();
|
||||
ctx[zero] = b2;
|
||||
const b3 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
return block1([], [b3]);
|
||||
const b3 = block3();
|
||||
ctx[zero] = b3;
|
||||
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
@@ -12,7 +12,7 @@ exports[`t-foreach does not pollute the rendering context 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList([1]);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`item\`] = v_block2[i1];
|
||||
ctx[\`item\`] = k_block2[i1];
|
||||
const key1 = ctx['item'];
|
||||
c_block2[i1] = withKey(text(ctx['item']), key1);
|
||||
}
|
||||
@@ -35,7 +35,7 @@ exports[`t-foreach iterate on items (on a element node) 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList([1,2]);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`item\`] = v_block2[i1];
|
||||
ctx[\`item\`] = k_block2[i1];
|
||||
const key1 = ctx['item'];
|
||||
let txt1 = ctx['item'];
|
||||
c_block2[i1] = withKey(block3([txt1]), key1);
|
||||
@@ -58,9 +58,9 @@ exports[`t-foreach iterate on items 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList([3,2,1]);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`item\`] = v_block2[i1];
|
||||
ctx[\`item\`] = k_block2[i1];
|
||||
ctx[\`item_index\`] = i1;
|
||||
ctx[\`item_value\`] = k_block2[i1];
|
||||
ctx[\`item_value\`] = v_block2[i1];
|
||||
const key1 = ctx['item'];
|
||||
const b4 = text(\` [\`);
|
||||
const b5 = text(ctx['item_index']);
|
||||
@@ -77,6 +77,62 @@ exports[`t-foreach iterate on items 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-foreach iterate, Map param 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { prepareList, withKey } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['value']);;
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`item\`] = k_block1[i1];
|
||||
ctx[\`item_index\`] = i1;
|
||||
ctx[\`item_value\`] = v_block1[i1];
|
||||
const key1 = ctx['item_index'];
|
||||
const b3 = text(\` [\`);
|
||||
const b4 = text(ctx['item_index']);
|
||||
const b5 = text(\`: \`);
|
||||
const b6 = text(ctx['item']);
|
||||
const b7 = text(\` \`);
|
||||
const b8 = text(ctx['item_value']);
|
||||
const b9 = text(\`] \`);
|
||||
c_block1[i1] = withKey(multi([b3, b4, b5, b6, b7, b8, b9]), key1);
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-foreach iterate, Set param 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { prepareList, withKey } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['value']);;
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`item\`] = k_block1[i1];
|
||||
ctx[\`item_index\`] = i1;
|
||||
ctx[\`item_value\`] = v_block1[i1];
|
||||
const key1 = ctx['item_index'];
|
||||
const b3 = text(\` [\`);
|
||||
const b4 = text(ctx['item_index']);
|
||||
const b5 = text(\`: \`);
|
||||
const b6 = text(ctx['item']);
|
||||
const b7 = text(\` \`);
|
||||
const b8 = text(ctx['item_value']);
|
||||
const b9 = text(\`] \`);
|
||||
c_block1[i1] = withKey(multi([b3, b4, b5, b6, b7, b8, b9]), key1);
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-foreach iterate, dict param 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
@@ -89,9 +145,9 @@ exports[`t-foreach iterate, dict param 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['value']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`item\`] = v_block2[i1];
|
||||
ctx[\`item\`] = k_block2[i1];
|
||||
ctx[\`item_index\`] = i1;
|
||||
ctx[\`item_value\`] = k_block2[i1];
|
||||
ctx[\`item_value\`] = v_block2[i1];
|
||||
const key1 = ctx['item_index'];
|
||||
const b4 = text(\` [\`);
|
||||
const b5 = text(ctx['item_index']);
|
||||
@@ -108,6 +164,62 @@ exports[`t-foreach iterate, dict param 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-foreach iterate, generator param 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { prepareList, withKey } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['gen']());;
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`item\`] = k_block1[i1];
|
||||
ctx[\`item_index\`] = i1;
|
||||
ctx[\`item_value\`] = v_block1[i1];
|
||||
const key1 = ctx['item_index'];
|
||||
const b3 = text(\` [\`);
|
||||
const b4 = text(ctx['item_index']);
|
||||
const b5 = text(\`: \`);
|
||||
const b6 = text(ctx['item']);
|
||||
const b7 = text(\` \`);
|
||||
const b8 = text(ctx['item_value']);
|
||||
const b9 = text(\`] \`);
|
||||
c_block1[i1] = withKey(multi([b3, b4, b5, b6, b7, b8, b9]), key1);
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-foreach iterate, iterable param 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { prepareList, withKey } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['map'].values());;
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`item\`] = k_block1[i1];
|
||||
ctx[\`item_index\`] = i1;
|
||||
ctx[\`item_value\`] = v_block1[i1];
|
||||
const key1 = ctx['item_index'];
|
||||
const b3 = text(\` [\`);
|
||||
const b4 = text(ctx['item_index']);
|
||||
const b5 = text(\`: \`);
|
||||
const b6 = text(ctx['item']);
|
||||
const b7 = text(\` \`);
|
||||
const b8 = text(ctx['item_value']);
|
||||
const b9 = text(\`] \`);
|
||||
c_block1[i1] = withKey(multi([b3, b4, b5, b6, b7, b8, b9]), key1);
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-foreach iterate, position 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
@@ -120,12 +232,12 @@ exports[`t-foreach iterate, position 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(Array(5));;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`elem\`] = v_block2[i1];
|
||||
ctx[\`elem\`] = k_block2[i1];
|
||||
ctx[\`elem_first\`] = i1 === 0;
|
||||
ctx[\`elem_last\`] = i1 === v_block2.length - 1;
|
||||
ctx[\`elem_last\`] = i1 === k_block2.length - 1;
|
||||
ctx[\`elem_index\`] = i1;
|
||||
const key1 = ctx['elem'];
|
||||
let b4,b5,b6,b7,b8,b9;
|
||||
let b4, b5, b6, b7, b8, b9;
|
||||
b4 = text(\` -\`);
|
||||
if (ctx['elem_first']) {
|
||||
b5 = text(\` first\`);
|
||||
@@ -144,6 +256,34 @@ exports[`t-foreach iterate, position 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-foreach iterate, string param 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { prepareList, withKey } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block1, v_block1, l_block1, c_block1] = prepareList('abc');;
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`item\`] = k_block1[i1];
|
||||
ctx[\`item_index\`] = i1;
|
||||
ctx[\`item_value\`] = v_block1[i1];
|
||||
const key1 = ctx['item_index'];
|
||||
const b3 = text(\` [\`);
|
||||
const b4 = text(ctx['item_index']);
|
||||
const b5 = text(\`: \`);
|
||||
const b6 = text(ctx['item']);
|
||||
const b7 = text(\` \`);
|
||||
const b8 = text(ctx['item_value']);
|
||||
const b9 = text(\`] \`);
|
||||
c_block1[i1] = withKey(multi([b3, b4, b5, b6, b7, b8, b9]), key1);
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-foreach simple iteration (in a node) 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
@@ -156,7 +296,7 @@ exports[`t-foreach simple iteration (in a node) 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList([3,2,1]);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`item\`] = v_block2[i1];
|
||||
ctx[\`item\`] = k_block2[i1];
|
||||
const key1 = ctx['item'];
|
||||
c_block2[i1] = withKey(text(ctx['item']), key1);
|
||||
}
|
||||
@@ -176,7 +316,7 @@ exports[`t-foreach simple iteration 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block1, v_block1, l_block1, c_block1] = prepareList([3,2,1]);;
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`item\`] = v_block1[i1];
|
||||
ctx[\`item\`] = k_block1[i1];
|
||||
const key1 = ctx['item'];
|
||||
c_block1[i1] = withKey(text(ctx['item']), key1);
|
||||
}
|
||||
@@ -198,7 +338,7 @@ exports[`t-foreach simple iteration with two nodes inside 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block1, v_block1, l_block1, c_block1] = prepareList([3,2,1]);;
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`item\`] = v_block1[i1];
|
||||
ctx[\`item\`] = k_block1[i1];
|
||||
const key1 = ctx['item'];
|
||||
let txt1 = ctx['item'];
|
||||
const b3 = block3([txt1]);
|
||||
@@ -227,20 +367,20 @@ exports[`t-foreach t-call with body in t-foreach in t-foreach 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['numbers']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`a\`] = v_block2[i1];
|
||||
ctx[\`a\`] = k_block2[i1];
|
||||
ctx[\`a_first\`] = i1 === 0;
|
||||
ctx[\`a_last\`] = i1 === v_block2.length - 1;
|
||||
ctx[\`a_last\`] = i1 === k_block2.length - 1;
|
||||
ctx[\`a_index\`] = i1;
|
||||
ctx[\`a_value\`] = k_block2[i1];
|
||||
ctx[\`a_value\`] = v_block2[i1];
|
||||
const key1 = ctx['a'];
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block4, v_block4, l_block4, c_block4] = prepareList(ctx['letters']);;
|
||||
for (let i2 = 0; i2 < l_block4; i2++) {
|
||||
ctx[\`b\`] = v_block4[i2];
|
||||
ctx[\`b\`] = k_block4[i2];
|
||||
ctx[\`b_first\`] = i2 === 0;
|
||||
ctx[\`b_last\`] = i2 === v_block4.length - 1;
|
||||
ctx[\`b_last\`] = i2 === k_block4.length - 1;
|
||||
ctx[\`b_index\`] = i2;
|
||||
ctx[\`b_value\`] = k_block4[i2];
|
||||
ctx[\`b_value\`] = v_block4[i2];
|
||||
const key2 = ctx['b'];
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
@@ -296,20 +436,20 @@ exports[`t-foreach t-call without body in t-foreach in t-foreach 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['numbers']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`a\`] = v_block2[i1];
|
||||
ctx[\`a\`] = k_block2[i1];
|
||||
ctx[\`a_first\`] = i1 === 0;
|
||||
ctx[\`a_last\`] = i1 === v_block2.length - 1;
|
||||
ctx[\`a_last\`] = i1 === k_block2.length - 1;
|
||||
ctx[\`a_index\`] = i1;
|
||||
ctx[\`a_value\`] = k_block2[i1];
|
||||
ctx[\`a_value\`] = v_block2[i1];
|
||||
const key1 = ctx['a'];
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block4, v_block4, l_block4, c_block4] = prepareList(ctx['letters']);;
|
||||
for (let i2 = 0; i2 < l_block4; i2++) {
|
||||
ctx[\`b\`] = v_block4[i2];
|
||||
ctx[\`b\`] = k_block4[i2];
|
||||
ctx[\`b_first\`] = i2 === 0;
|
||||
ctx[\`b_last\`] = i2 === v_block4.length - 1;
|
||||
ctx[\`b_last\`] = i2 === k_block4.length - 1;
|
||||
ctx[\`b_index\`] = i2;
|
||||
ctx[\`b_value\`] = k_block4[i2];
|
||||
ctx[\`b_value\`] = v_block4[i2];
|
||||
const key2 = ctx['b'];
|
||||
c_block4[i2] = withKey(callTemplate_1.call(this, ctx, node, key + \`__1__\${key1}__\${key2}\`), key2);
|
||||
}
|
||||
@@ -363,12 +503,12 @@ exports[`t-foreach t-foreach in t-foreach 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['numbers']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`number\`] = v_block2[i1];
|
||||
ctx[\`number\`] = k_block2[i1];
|
||||
const key1 = ctx['number'];
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block3, v_block3, l_block3, c_block3] = prepareList(ctx['letters']);;
|
||||
for (let i2 = 0; i2 < l_block3; i2++) {
|
||||
ctx[\`letter\`] = v_block3[i2];
|
||||
ctx[\`letter\`] = k_block3[i2];
|
||||
const key2 = ctx['letter'];
|
||||
const b5 = text(\` [\`);
|
||||
const b6 = text(ctx['number']);
|
||||
@@ -397,7 +537,7 @@ exports[`t-foreach t-foreach with t-if inside (no external node) 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['elems']);;
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`elem\`] = v_block1[i1];
|
||||
ctx[\`elem\`] = k_block1[i1];
|
||||
const key1 = ctx['elem'].id;
|
||||
let b3;
|
||||
if (ctx['elem'].id<3) {
|
||||
@@ -424,7 +564,7 @@ exports[`t-foreach t-foreach with t-if inside 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['elems']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`elem\`] = v_block2[i1];
|
||||
ctx[\`elem\`] = k_block2[i1];
|
||||
const key1 = ctx['elem'].id;
|
||||
let b4;
|
||||
if (ctx['elem'].id<3) {
|
||||
@@ -452,7 +592,7 @@ exports[`t-foreach t-key on t-foreach 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['things']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`thing\`] = v_block2[i1];
|
||||
ctx[\`thing\`] = k_block2[i1];
|
||||
const key1 = ctx['thing'];
|
||||
c_block2[i1] = withKey(block3(), key1);
|
||||
}
|
||||
@@ -475,7 +615,7 @@ exports[`t-foreach throws error if invalid loop expression 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['abc']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`item\`] = v_block2[i1];
|
||||
ctx[\`item\`] = k_block2[i1];
|
||||
ctx[\`item_index\`] = i1;
|
||||
const key1 = ctx['item'];
|
||||
const tKey_1 = ctx['item_index'];
|
||||
@@ -502,7 +642,7 @@ exports[`t-foreach with t-memo 1`] = `
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['items']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`item\`] = v_block2[i1];
|
||||
ctx[\`item\`] = k_block2[i1];
|
||||
const key1 = ctx['item'].id;
|
||||
const memo1 = [ctx['item'].x];
|
||||
const vnode1 = cache[key1];;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user