update
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,136 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import {
|
||||
Component,
|
||||
onWillStart,
|
||||
onWillRender,
|
||||
onRendered,
|
||||
onMounted,
|
||||
onWillUpdateProps,
|
||||
onWillPatch,
|
||||
onPatched,
|
||||
onWillUnmount,
|
||||
onWillDestroy,
|
||||
onError,
|
||||
useState,
|
||||
useRef
|
||||
} from "@odoo/owl";
|
||||
import { standardActionServiceProps } from "@web/webclient/actions/action_service";
|
||||
import { Layout } from "@web/search/layout";
|
||||
|
||||
class CheatOwl extends Component {
|
||||
static template = "cheat_owl";
|
||||
static components = { Layout };
|
||||
static props = {
|
||||
...standardActionServiceProps,
|
||||
};
|
||||
|
||||
getRandomInteger(min, max) {
|
||||
return Math.floor(Math.random() * (max - min)) + min;
|
||||
}
|
||||
|
||||
setup() {
|
||||
this.useRefCardTextRef = useRef("useRefCardText");
|
||||
|
||||
//#region non reactive
|
||||
this.nonReactiveRandomValue = this.getRandomInteger(0, 100);
|
||||
this.nonReactiveRandomValue1Ref = useRef("nonReactiveRandomValue1");
|
||||
this.nonReactiveRandomValue2Ref = useRef("nonReactiveRandomValue2");
|
||||
this.nonReactiveRandomValue3Ref = useRef("nonReactiveRandomValue3");
|
||||
this.nonReactiveRandomValue4Ref = useRef("nonReactiveRandomValue4");
|
||||
//#endregion
|
||||
|
||||
//#region reactive
|
||||
this.state = useState({
|
||||
randomValue: this.getRandomInteger(0, 100),
|
||||
});
|
||||
//#endregion
|
||||
|
||||
//#region input binding
|
||||
this.bindingValue = "";
|
||||
this.inputBindingRef = useRef("inputBinding");
|
||||
//#endregion
|
||||
|
||||
//#region two way input binding
|
||||
this.bindingState = useState({
|
||||
valueStandardInputBinding: "",
|
||||
valueTextAreaInputBinding: "",
|
||||
valueCheckBoxInputBinding: false,
|
||||
valueRadioButtonInputBinding: "",
|
||||
valueSelectionInputBinding: "",
|
||||
valueRangeInputBinding: 0,
|
||||
});
|
||||
//#endregion
|
||||
|
||||
//#region owl lifecycle
|
||||
onWillStart(() => {
|
||||
console.log("onWIllStart.");
|
||||
});
|
||||
|
||||
onWillRender(() => {
|
||||
console.log("onWillRender.");
|
||||
});
|
||||
|
||||
onRendered(() => {
|
||||
console.log("onRendered.");
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
console.log("onMounted.");
|
||||
});
|
||||
|
||||
onWillUpdateProps((nextProps) => {
|
||||
console.log("onWillUpdateProps:", nextProps);
|
||||
});
|
||||
|
||||
onWillPatch(() => {
|
||||
console.log("onWillPatch.");
|
||||
});
|
||||
|
||||
onPatched(() => {
|
||||
console.log("onPatched.");
|
||||
});
|
||||
|
||||
onWillUnmount(() => {
|
||||
console.log("onWillUnmount.");
|
||||
});
|
||||
|
||||
onWillDestroy(() => {
|
||||
console.log("onWillDestroy.");
|
||||
});
|
||||
|
||||
onError(() => {
|
||||
console.log("onError.");
|
||||
});
|
||||
//#endregion
|
||||
}
|
||||
|
||||
//#region functions
|
||||
generateNonReactiveRandomValue() {
|
||||
let randomVal = this.getRandomInteger(0, 100);
|
||||
this.nonReactiveRandomValue1Ref.el.innerText = randomVal;
|
||||
this.nonReactiveRandomValue2Ref.el.innerText = randomVal;
|
||||
this.nonReactiveRandomValue3Ref.el.innerText = randomVal;
|
||||
this.nonReactiveRandomValue4Ref.el.innerText = randomVal;
|
||||
}
|
||||
|
||||
generateRandomValue() {
|
||||
this.state.randomValue = this.getRandomInteger(0, 100);
|
||||
}
|
||||
|
||||
changeUseRefCardTextColor() {
|
||||
this.useRefCardTextRef.el.classList.toggle("text-danger");
|
||||
}
|
||||
|
||||
getInputBindingValue(){
|
||||
this.inputBindingRef.el.innerText = this.bindingValue;
|
||||
}
|
||||
|
||||
changeValueStandardInputBinding(){
|
||||
this.bindingState.valueStandardInputBinding = "Random value: " + this.getRandomInteger(0, 100);
|
||||
}
|
||||
//#endregion
|
||||
}
|
||||
|
||||
registry.category("actions").add("cheat_owl", CheatOwl);
|
||||
@@ -0,0 +1,199 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="cheat_owl" owl="1">
|
||||
<Layout display="{ controlPanel: {} }" className="'overflow-auto h-100'">
|
||||
<div class="cheat-web-container">
|
||||
<div class="row g-2">
|
||||
<div class="col-xl-2 col-sm-6">
|
||||
<div class="card">
|
||||
<h5 class="card-header">
|
||||
<a class="collapsed d-block" data-bs-toggle="collapse" href="#collapse-owl-use-ref" aria-expanded="true" aria-controls="collapse-collapsed">
|
||||
<em>useRef</em> Example
|
||||
<i class="fa fa-chevron-down float-end"></i>
|
||||
</a>
|
||||
</h5>
|
||||
<div id="collapse-owl-use-ref" class="collapse">
|
||||
<div class="card-body">
|
||||
<p class="card-text" t-ref="useRefCardText">
|
||||
This text color can be changed by clicking the button below.
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<div class="d-flex justify-content-end">
|
||||
<button t-on-click="changeUseRefCardTextColor" class="btn btn-sm btn-outline-primary w-50">Change Color</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-2 col-sm-6">
|
||||
<div class="card">
|
||||
<h5 class="card-header">
|
||||
<a class="collapsed d-block" data-bs-toggle="collapse" href="#collapse-owl-use-state" aria-expanded="true" aria-controls="collapse-collapsed">
|
||||
<em>useState</em> Example
|
||||
<i class="fa fa-chevron-down float-end"></i>
|
||||
</a>
|
||||
</h5>
|
||||
<div id="collapse-owl-use-state" class="collapse">
|
||||
<div class="card-body">
|
||||
<p class="card-text">
|
||||
This is a random value: <br/>
|
||||
<span class="text-primary"><t t-out="this.state.randomValue" /></span>
|
||||
<span class="text-danger ms-1"><t t-out="this.state.randomValue" /></span>
|
||||
<span class="text-warning ms-1"><t t-out="this.state.randomValue" /></span>
|
||||
<span class="text-info ms-1"><t t-out="this.state.randomValue" /></span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<div class="d-flex justify-content-end">
|
||||
<button t-on-click="generateRandomValue" class="btn btn-sm btn-outline-primary w-50">Randomize</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-2 col-sm-6">
|
||||
<div class="card">
|
||||
<h5 class="card-header">
|
||||
<a class="collapsed d-block" data-bs-toggle="collapse" href="#collapse-owl-non-reactive" aria-expanded="true" aria-controls="collapse-collapsed">
|
||||
Non Reactive Example
|
||||
<i class="fa fa-chevron-down float-end"></i>
|
||||
</a>
|
||||
</h5>
|
||||
<div id="collapse-owl-non-reactive" class="collapse">
|
||||
<div class="card-body">
|
||||
<p class="card-text">
|
||||
This is a random value: <br/>
|
||||
<span class="text-primary" t-ref="nonReactiveRandomValue1"><t t-out="this.nonReactiveRandomValue" /></span>
|
||||
<span class="text-danger ms-1" t-ref="nonReactiveRandomValue2"><t t-out="this.nonReactiveRandomValue" /></span>
|
||||
<span class="text-warning ms-1" t-ref="nonReactiveRandomValue3"><t t-out="this.nonReactiveRandomValue" /></span>
|
||||
<span class="text-info ms-1" t-ref="nonReactiveRandomValue4"><t t-out="this.nonReactiveRandomValue" /></span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<div class="d-flex justify-content-end">
|
||||
<button t-on-click="generateNonReactiveRandomValue" class="btn btn-sm btn-outline-primary w-50">Randomize</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-1 g-2">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<h5 class="card-header">
|
||||
<a class="collapsed d-block" data-bs-toggle="collapse" href="#collapse-owl-input-binding" aria-expanded="true" aria-controls="collapse-collapsed">
|
||||
Input Binding
|
||||
<i class="fa fa-chevron-down float-end"></i>
|
||||
</a>
|
||||
</h5>
|
||||
<div id="collapse-owl-input-binding" class="collapse">
|
||||
<div class="row m-2 mt-0 g-2">
|
||||
<div class="col-xl-2 col-sm-6">
|
||||
<div class="card h-100">
|
||||
<h5 class="card-header"><i class="fa fa-keyboard-o" /> Input</h5>
|
||||
<div class="card-body">
|
||||
<p class="card-text">
|
||||
<input t-model="this.bindingValue"/>
|
||||
<hr/>
|
||||
<div>Input value: <span t-ref="inputBinding"> <t t-out="this.bindingValue" /></span></div>
|
||||
<button class="btn btn-sm btn-outline-primary" t-on-click="getInputBindingValue">Get Input Value</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row m-2 mt-0 g-2">
|
||||
<div class="hr-sect">Two-way Binding</div>
|
||||
<div class="col-xl-2 col-sm-6">
|
||||
<div class="card h-100">
|
||||
<h5 class="card-header"><i class="fa fa-keyboard-o" /> Input</h5>
|
||||
<div class="card-body">
|
||||
<p class="card-text justify-content-between d-flex flex-column h-100">
|
||||
<input t-model="this.bindingState.valueStandardInputBinding"/>
|
||||
<div><hr/>Input value: <t t-out="this.bindingState.valueStandardInputBinding" /></div>
|
||||
<button class="btn btn-sm btn-outline-primary" t-on-click="changeValueStandardInputBinding">Randomize</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-2 col-sm-6">
|
||||
<div class="card h-100">
|
||||
<h5 class="card-header"><i class="fa fa-font" /> Textarea</h5>
|
||||
<div class="card-body">
|
||||
<p class="card-text justify-content-between d-flex flex-column h-100">
|
||||
<textarea t-model="this.bindingState.valueTextAreaInputBinding"/>
|
||||
<div><hr/>Input value: <t t-out="this.bindingState.valueTextAreaInputBinding" /></div>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-2 col-sm-6">
|
||||
<div class="card h-100">
|
||||
<h5 class="card-header"><i class="fa fa-check-square" /> Checkbox</h5>
|
||||
<div class="card-body">
|
||||
<p class="card-text justify-content-between d-flex flex-column h-100">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="cbInputBindingCheck" t-model="this.bindingState.valueCheckBoxInputBinding"/>
|
||||
<label class="form-check-label" for="cbInputBindingCheck">Checkbox</label>
|
||||
</div>
|
||||
<div><hr/>Input value: <t t-out="this.bindingState.valueCheckBoxInputBinding" /></div>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-2 col-sm-6">
|
||||
<div class="card h-100">
|
||||
<h5 class="card-header"><i class="fa fa-stop-circle-o" /> Radio Buttons</h5>
|
||||
<div class="card-body">
|
||||
<p class="card-text justify-content-between d-flex flex-column h-100">
|
||||
<div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" id="cbInputBindingOne" value="one" t-model="this.bindingState.valueRadioButtonInputBinding"/>
|
||||
<label class="form-check-label" for="cbInputBindingOne">One</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" id="cbInputBindingTwo" value="two" t-model="this.bindingState.valueRadioButtonInputBinding" />
|
||||
<label class="form-check-label" for="cbInputBindingTwo">Two</label>
|
||||
</div>
|
||||
</div>
|
||||
<div><hr/>Input value: <t t-out="this.bindingState.valueRadioButtonInputBinding" /></div>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-2 col-sm-6">
|
||||
<div class="card h-100">
|
||||
<h5 class="card-header"><i class="fa fa-caret-square-o-down" /> Select</h5>
|
||||
<div class="card-body">
|
||||
<p class="card-text justify-content-between d-flex flex-column h-100">
|
||||
<select class="form-select" t-model="this.bindingState.valueSelectionInputBinding">
|
||||
<option value="red">Red</option>
|
||||
<option value="blue">Blue</option>
|
||||
</select>
|
||||
<div><hr/>Input value: <t t-out="this.bindingState.valueSelectionInputBinding" /></div>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-2 col-sm-6">
|
||||
<div class="card h-100">
|
||||
<h5 class="card-header"><i class="fa fa-arrows-h" /> Range</h5>
|
||||
<div class="card-body">
|
||||
<p class="card-text justify-content-between d-flex flex-column h-100">
|
||||
<input class="form-range" min="0" max="5" step="0.5" type="range" t-model="this.bindingState.valueRangeInputBinding"/>
|
||||
<div><hr/>Input value: <t t-out="this.bindingState.valueRangeInputBinding" /></div>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,79 @@
|
||||
/** @odoo-module */
|
||||
import { registry } from "@web/core/registry";
|
||||
import { Component, useState, markup } from "@odoo/owl";
|
||||
import { standardActionServiceProps } from "@web/webclient/actions/action_service";
|
||||
import { Layout } from "@web/search/layout";
|
||||
import { HAccordion } from "./core/haccordion/haccordion";
|
||||
import { Collapsible } from "./core/collapsible/collapsible";
|
||||
|
||||
import { CheatOwlQwebInlineTemplate } from "./cheat_owl_qweb_inline_template"
|
||||
|
||||
class CheatOwlQweb extends Component {
|
||||
static template = "cheat_owl_qweb";
|
||||
static components = { Layout, CheatOwlQwebInlineTemplate, HAccordion, Collapsible };
|
||||
static props = {
|
||||
...standardActionServiceProps,
|
||||
};
|
||||
|
||||
//#region Misc Functions
|
||||
getRandomInteger(min, max) {
|
||||
return Math.floor(Math.random() * (max - min)) + min;
|
||||
}
|
||||
|
||||
get randomString() {
|
||||
var letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
var llen = letters.length;
|
||||
var res = "";
|
||||
for (var i = 0; i < 6; i++) {
|
||||
res += letters[Math.floor(Math.random() * llen)];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
get randomBoolean() {
|
||||
return Math.round(Math.random()) == 1;
|
||||
}
|
||||
|
||||
get dynamicTemplate() {
|
||||
if (this.state.dynamicTemplate === "one") {
|
||||
return "cheat_owl_qweb_sub_template_one"
|
||||
} else if (this.state.dynamicTemplate === "two"){
|
||||
return "cheat_owl_qweb_sub_template_two"
|
||||
} else if (this.state.dynamicTemplate === "three"){
|
||||
return "cheat_owl_qweb_sub_template_three"
|
||||
} else {
|
||||
return "cheat_owl_qweb_sub_template_four"
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
setup() {
|
||||
this.state = useState({
|
||||
randomValue: this.getRandomInteger(0, 100),
|
||||
checkBoxValue: "one",
|
||||
dynamicTag: "div",
|
||||
dynamicTemplate: "one"
|
||||
});
|
||||
|
||||
this.markup = markup;
|
||||
this.divText = "<div>some text 1</div>";
|
||||
this.markupDivText = markup("<div>some text 2</div>");
|
||||
this.arrayOfObjects = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Item No. 1",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Item No. 2",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Item No. 3",
|
||||
},
|
||||
];
|
||||
this.aSetOfObjects = new Set(this.arrayOfObjects);
|
||||
}
|
||||
}
|
||||
|
||||
registry.category("actions").add("cheat_owl_qweb", CheatOwlQweb);
|
||||
@@ -0,0 +1,404 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="cheat_owl_qweb">
|
||||
<Layout display="{ controlPanel: {} }" className="'overflow-auto h-100'">
|
||||
<HAccordion name="'hacc'" startItem="0">
|
||||
<t t-set-slot="basics-1" title.translate="The Basics - I">
|
||||
<div class="d-flex w-100 flex-column gap-2 pt-2">
|
||||
<div class="row g-2 w-100">
|
||||
<div class="col-xl-4 col-sm-12">
|
||||
<Collapsible name="'inline-template'" title.translate="Inline Template">
|
||||
<CheatOwlQwebInlineTemplate />
|
||||
</Collapsible>
|
||||
</div>
|
||||
<div class="col-xl-4 col-sm-12">
|
||||
<Collapsible name="'white-spaces'" title.translate="White Spaces">
|
||||
<p>
|
||||
<span>This is white spaces in a <span class="badge"><span></span> : ' '
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
<span>This is a <span class="badge"><span></span> with a line break:
|
||||
'
|
||||
'
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
<pre>This is white spaces in a <pre>: ' '</pre>
|
||||
</p>
|
||||
</Collapsible>
|
||||
</div>
|
||||
<div class="col-xl-4 col-sm-12">
|
||||
<Collapsible name="'outputs'" title.translate="Displaying Data">
|
||||
<p>
|
||||
<span>This is an html fragment <span class="badge">t-out</span> :
|
||||
<pre><t t-out="divText" /></pre>
|
||||
</span>
|
||||
|
||||
<span>This is an html fragment <span class="badge">t-out</span> with markup:
|
||||
<pre><t t-out="markupDivText" /></pre>
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
<span>This is an html fragment <span class="badge">t-esc</span> :
|
||||
<pre><t t-esc="divText" /></pre>
|
||||
</span>
|
||||
|
||||
<span>This is an html fragment <span class="badge">t-esc</span> with markup:
|
||||
<pre><t t-esc="markupDivText" /></pre>
|
||||
</span>
|
||||
</p>
|
||||
</Collapsible>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-2 w-100">
|
||||
<div class="col-xl-4 col-sm-12">
|
||||
<Collapsible name="'variables'" title.translate="Variables">
|
||||
<p>
|
||||
<t t-set="foo" t-value="'bar'" />
|
||||
<span>The variable <span class="badge">foo</span> is set to <span class="badge"><t t-out="foo"/></span>.</span>
|
||||
</p>
|
||||
<p>
|
||||
<t t-set="wal">
|
||||
<strong>do</strong>
|
||||
</t>
|
||||
<span>The variable <span class="badge">wal</span> is set to <span class="badge"><t t-out="wal"/></span> but inside tags.</span>
|
||||
</p>
|
||||
</Collapsible>
|
||||
</div>
|
||||
<div class="col-xl-4 col-sm-12">
|
||||
<Collapsible name="'expressions'" title.translate="Basic Expressions">
|
||||
<p>
|
||||
<span>This is evaluation result of <span class="badge">1 + 3</span> :
|
||||
<pre><t t-out="1 + 3" /></pre>
|
||||
</span>
|
||||
<span>With <span class="badge">state.randomValue = <t t-out="state.randomValue"/></span>, this expression <span class="badge">1 + state.randomValue</span> will result to:
|
||||
<pre><t t-out="1 + state.randomValue" /></pre>
|
||||
</span>
|
||||
</p>
|
||||
</Collapsible>
|
||||
</div>
|
||||
<div class="col-xl-4 col-sm-12">
|
||||
<Collapsible name="'logical-expressions'" title.translate="Logical Expressions">
|
||||
<p>
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr class="table-secondary">
|
||||
<th>Operation</th>
|
||||
<th>Operator</th>
|
||||
<th>Example</th>
|
||||
<th>Result</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Equality</td>
|
||||
<td>==</td>
|
||||
<td>1 == 3</td>
|
||||
<td><t t-out="1 == 3" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>And</td>
|
||||
<td>and</td>
|
||||
<td>true and false</td>
|
||||
<td><t t-out="true and false" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Or</td>
|
||||
<td>or</td>
|
||||
<td>true or false</td>
|
||||
<td><t t-out="true or false" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Greater Than</td>
|
||||
<td>gt</td>
|
||||
<td>1 gt 1</td>
|
||||
<td><t t-out="1 gt 1" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Greater Than or Equal</td>
|
||||
<td>gte</td>
|
||||
<td>1 gte 1</td>
|
||||
<td><t t-out="1 gte 1" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Less Than</td>
|
||||
<td>lt</td>
|
||||
<td>1 lt 1</td>
|
||||
<td><t t-out="1 lt 1" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Less Than or Equal</td>
|
||||
<td>lte</td>
|
||||
<td>1 lte 1</td>
|
||||
<td><t t-out="1 lte 1" /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</p>
|
||||
</Collapsible>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-set-slot="basics-2" title.translate="The Basics - II">
|
||||
<div class="d-flex w-100 flex-column gap-2 pt-2">
|
||||
<div class="row g-2 w-100">
|
||||
<div class="col-xl-4 col-sm-12">
|
||||
<Collapsible name="'conditionals'" title.translate="Conditionals">
|
||||
<p>
|
||||
<div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input"
|
||||
type="radio"
|
||||
id="cbConditionalOne"
|
||||
name="cbConditionals"
|
||||
value="one"
|
||||
t-model="state.checkBoxValue" />
|
||||
<label class="form-check-label" for="cbConditionalOne">One</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input"
|
||||
type="radio"
|
||||
id="cbConditionalTwo"
|
||||
name="cbConditionals"
|
||||
value="two"
|
||||
t-model="state.checkBoxValue" />
|
||||
<label class="form-check-label" for="cbConditionalTwo">Two</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input"
|
||||
type="radio"
|
||||
id="cbConditionalThree"
|
||||
name="cbConditionals"
|
||||
value="three"
|
||||
t-model="state.checkBoxValue" />
|
||||
<label class="form-check-label" for="cbConditionalThree">Three</label>
|
||||
</div>
|
||||
</div>
|
||||
<span t-if="state.checkBoxValue === 'one'">This will be displayed if the first radio button above is selected.</span>
|
||||
<span t-elif="state.checkBoxValue === 'two'">This will be displayed if the second radio button is checked.</span>
|
||||
<span t-else="">This will be displayed if the third radio button is checked.</span>
|
||||
</p>
|
||||
</Collapsible>
|
||||
</div>
|
||||
<div class="col-xl-4 col-sm-12">
|
||||
<Collapsible name="'loops'" title.translate="Loops">
|
||||
<p>
|
||||
<span>This list is made using <span class="badge">t-foreach</span> loop:</span>
|
||||
<ul>
|
||||
<t t-foreach="[1, 2, 3]" t-as="i" t-key="i">
|
||||
<li><t t-out="'Item: ' + i.toString()"/></li>
|
||||
</t>
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
<span>This list is made using <span class="badge">t-foreach</span> loop too but applied to the element instead of the <span class="badge">t</span> element:</span>
|
||||
<ul>
|
||||
<li t-foreach="[1, 2, 3]" t-as="i" t-key="i"><t t-out="'Item: ' + i.toString()"/></li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
<span>These demonstrate the <span class="badge">t-foreach</span>'s special variables:</span>
|
||||
<table class="table table-striped table-hover mt-1">
|
||||
<thead>
|
||||
<tr class="table-secondary">
|
||||
<td>x_index</td>
|
||||
<td>x_value</td>
|
||||
<td>x_first</td>
|
||||
<td>x_last</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr t-foreach="arrayOfObjects" t-as="item" t-key="item.id">
|
||||
<td><t t-out="item_index"/></td>
|
||||
<td><t t-out="item_value.toString()"/></td>
|
||||
<td><t t-out="item_first"/></td>
|
||||
<td><t t-out="item_last"/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</p>
|
||||
<p>
|
||||
<span>This list is made using <span class="badge">Set</span> and spread operator <span class="badge">...</span> :</span>
|
||||
<ul>
|
||||
<t t-foreach="[...aSetOfObjects]" t-as="i" t-key="i.id">
|
||||
<li><t t-out="i.name"/></li>
|
||||
</t>
|
||||
</ul>
|
||||
</p>
|
||||
</Collapsible>
|
||||
</div>
|
||||
<div class="col-xl-4 col-sm-12">
|
||||
<Collapsible name="'dynamics'" title.translate="Dynamics">
|
||||
<p>
|
||||
<span>The placeholder attribute is set using random value:</span>
|
||||
<input type="text" class="form-control mt-1" t-att-placeholder="randomString" />
|
||||
</p>
|
||||
<p>
|
||||
<span>The placeholder attribute is set using string interpolation with random value:</span>
|
||||
<input type="text" class="form-control mt-1" t-attf-placeholder="This is a random string: {{randomString}}" />
|
||||
</p>
|
||||
<p>
|
||||
<span>The CSS class is set using object with CSS class as key and boolean value to indicate the inclusion
|
||||
<span class="badge">{'text-info': true, 'fw-bold': true}</span>:</span>
|
||||
<p t-att-class="{'text-info': true, 'fw-bold': true}">This is an example text.</p>
|
||||
</p>
|
||||
<p>
|
||||
<span>This is an exmple of dynamic tag:</span>
|
||||
<div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input"
|
||||
type="radio"
|
||||
id="cbDynamicTagDiv"
|
||||
name="cbDynamicTag"
|
||||
value="div"
|
||||
t-model="state.dynamicTag" />
|
||||
<label class="form-check-label" for="cbDynamicTagDiv">Div</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input"
|
||||
type="radio"
|
||||
id="cbDynamicTagSpan"
|
||||
name="cbDynamicTag"
|
||||
value="pre"
|
||||
t-model="state.dynamicTag" />
|
||||
<label class="form-check-label" for="cbDynamicTagSpan">Pre</label>
|
||||
</div>
|
||||
</div>
|
||||
<t t-tag="state.dynamicTag">
|
||||
<span>This text is inside <span class="badge"><t t-out="state.dynamicTag"/> element</span></span>
|
||||
</t>
|
||||
</p>
|
||||
</Collapsible>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-set-slot="more-on-templates" title.translate="More On Templates">
|
||||
<div class="d-flex w-100 flex-column gap-2 pt-2">
|
||||
<div class="row g-2 w-100">
|
||||
<div class="col-xl-6 col-sm-12">
|
||||
<Collapsible name="'sub-templates'" title.translate="Sub Templates">
|
||||
<p>
|
||||
<div>Sub templates are called with <span class="badge">t-call</span>:</div>
|
||||
<t t-call="cheat_owl_qweb_sub_template_two"/>
|
||||
</p>
|
||||
<p>
|
||||
<div>This templates is using special placeholder <span class="badge">t-out="0"</span>:</div>
|
||||
<t t-call="cheat_owl_qweb_sub_template_three">
|
||||
<span>This text is rendered inside the sub template.</span>
|
||||
</t>
|
||||
</p>
|
||||
<p>
|
||||
<div>Variables are passed down to the sub template:</div>
|
||||
<t t-set="parentTemplateVariable" t-value="'This is a string declared on parent template.'"/>
|
||||
<t t-call="cheat_owl_qweb_sub_template_four">
|
||||
<t t-set="scopedTemplateVariable" t-value="'This is declared inside the t element.'"/>
|
||||
</t>
|
||||
<!-- scopedTemplateVariable does not exist here -->
|
||||
</p>
|
||||
<p>
|
||||
<span>This is an example of dynamic sub templates:</span>
|
||||
<div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input"
|
||||
type="radio"
|
||||
id="cbDynamicTemplateOne"
|
||||
name="cbDynamicTemplate"
|
||||
value="one"
|
||||
t-model="state.dynamicTemplate" />
|
||||
<label class="form-check-label" for="cbDynamicTemplateOne">One</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input"
|
||||
type="radio"
|
||||
id="cbDynamicTemplateTwo"
|
||||
name="cbDynamicTemplate"
|
||||
value="two"
|
||||
t-model="state.dynamicTemplate" />
|
||||
<label class="form-check-label" for="cbDynamicTemplateTwo">Two</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input"
|
||||
type="radio"
|
||||
id="cbDynamicTemplateThree"
|
||||
name="cbDynamicTemplate"
|
||||
value="three"
|
||||
t-model="state.dynamicTemplate" />
|
||||
<label class="form-check-label" for="cbDynamicTemplateThree">Three</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input"
|
||||
type="radio"
|
||||
id="cbDynamicTemplateFour"
|
||||
name="cbDynamicTemplate"
|
||||
value="four"
|
||||
t-model="state.dynamicTemplate" />
|
||||
<label class="form-check-label" for="cbDynamicTemplateFour">Four</label>
|
||||
</div>
|
||||
</div>
|
||||
<t t-call="{{ dynamicTemplate }}"/>
|
||||
</p>
|
||||
</Collapsible>
|
||||
</div>
|
||||
<div class="col-xl-6 col-sm-12">
|
||||
<Collapsible name="'template-inheritance'" title.translate="Template Inheritance">
|
||||
<p>
|
||||
<div>We call the parent template, but since there's an extension child which modified the content we get the modified version:</div>
|
||||
<t t-call="cheat_owl.qweb_parent_template"/>
|
||||
</p>
|
||||
<p>
|
||||
<div>This is the primary child template, which we can call directly by it's name:</div>
|
||||
<t t-call="cheat_owl.qweb_primary_child_template"/>
|
||||
</p>
|
||||
</Collapsible>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-set-slot="subscribe" title.translate="Subscribe for more tutorials">
|
||||
<div class="d-flex d-flex h-100 justify-content-center align-items-start">
|
||||
<div class="card" style="width: 150px;">
|
||||
<img src="/cheat_web/static/img/exploring-odoo.png" class="card-img-top" />
|
||||
<div class="card-body text-center">
|
||||
<p class="card-text">
|
||||
<a href="https://youtube.com/@exploring-odoo">Exploring Odoo</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</HAccordion>
|
||||
</Layout>
|
||||
</t>
|
||||
<t t-name="cheat_owl_qweb_sub_template_one">
|
||||
<div>This text is inside a template <span class="badge">cheat_owl_qweb_sub_template_one</span>.</div>
|
||||
</t>
|
||||
<t t-name="cheat_owl_qweb_sub_template_two">
|
||||
<div>This text is inside a template <span class="badge">cheat_owl_qweb_sub_template_two</span>.</div>
|
||||
<t t-call="cheat_owl_qweb_sub_template_one"/>
|
||||
</t>
|
||||
<t t-name="cheat_owl_qweb_sub_template_three">
|
||||
<div>This text is inside a template <span class="badge">cheat_owl_qweb_sub_template_three</span>.</div>
|
||||
<t t-out="0" />
|
||||
</t>
|
||||
<t t-name="cheat_owl_qweb_sub_template_four">
|
||||
<div>This text is inside a template <span class="badge">cheat_owl_qweb_sub_template_four</span>.</div>
|
||||
<div><t t-out="parentTemplateVariable" /></div>
|
||||
<div><t t-out="scopedTemplateVariable" /></div>
|
||||
</t>
|
||||
<t t-name="cheat_owl.qweb_parent_template">
|
||||
<p>This is parent template.</p>
|
||||
</t>
|
||||
<t t-name="cheat_owl.qweb_primary_child_template" t-inherit="cheat_owl.qweb_parent_template" t-inherit-mode="primary">
|
||||
<xpath expr="//p[1]" position="after">
|
||||
<p>This is primary child template.</p>
|
||||
</xpath>
|
||||
</t>
|
||||
<t t-inherit="cheat_owl.qweb_parent_template" t-inherit-mode="extension">
|
||||
<xpath expr="//p[1]" position="after">
|
||||
<p>This is extension child template.</p>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,11 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { Component, xml } from "@odoo/owl";
|
||||
|
||||
export class CheatOwlQwebInlineTemplate extends Component {
|
||||
static template = xml`
|
||||
<p>
|
||||
This text is in an inline template
|
||||
</p>`;
|
||||
static props = { };
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
.cheat-web-container {
|
||||
margin: 2rem !important;
|
||||
}
|
||||
|
||||
.cheat-web-user-profile {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.cheat-web-user-profile .card {
|
||||
width: 400px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.cheat-web-user-profile .stats {
|
||||
background: #f2f5f8 !important;
|
||||
|
||||
color: #000 !important;
|
||||
}
|
||||
.cheat-web-user-profile .articles {
|
||||
font-size: 10px;
|
||||
color: #a1aab9;
|
||||
}
|
||||
.cheat-web-user-profile .followers {
|
||||
font-size: 10px;
|
||||
color: #a1aab9;
|
||||
}
|
||||
.cheat-web-user-profile .rating {
|
||||
font-size: 10px;
|
||||
color: #a1aab9;
|
||||
}
|
||||
.cheat-web-user-profile .stat-number,
|
||||
.cheat-web-user-profile .stat-string {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.cheat-web-container .card-header .fa {
|
||||
transition: 0.3s transform ease-in-out;
|
||||
}
|
||||
|
||||
.cheat-web-container .card-header .collapsed .fa {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.hr-sect {
|
||||
display: flex;
|
||||
flex-basis: 100%;
|
||||
align-items: center;
|
||||
color: rgba(0, 0, 0, 0.35);
|
||||
margin: 8px 0px;
|
||||
}
|
||||
.hr-sect::before,
|
||||
.hr-sect::after {
|
||||
content: "";
|
||||
flex-grow: 1;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
height: 1px;
|
||||
font-size: 0px;
|
||||
line-height: 0px;
|
||||
margin: 0px 8px;
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { Component, useState, xml } from "@odoo/owl";
|
||||
import { standardActionServiceProps } from "@web/webclient/actions/action_service";
|
||||
import { Layout } from "@web/search/layout";
|
||||
import { AlertDialog, ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
|
||||
import { Dialog } from "@web/core/dialog/dialog";
|
||||
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
import { user } from "@web/core/user";
|
||||
import { url } from "@web/core/utils/urls";
|
||||
|
||||
const { DateTime } = luxon;
|
||||
|
||||
//#region custom dialog
|
||||
class MyDialog extends Component {
|
||||
static components = { Dialog };
|
||||
static template = xml`
|
||||
<Dialog size="'md'" title="'This is my dialog title'">
|
||||
<p>
|
||||
This is my dialog content.
|
||||
</p>
|
||||
</Dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
class MyDialogWithButtons extends Component {
|
||||
static components = { Dialog };
|
||||
static template = xml`
|
||||
<Dialog size="'md'" title="'This is my dialog with buttons'">
|
||||
<p>
|
||||
This is my dialog with buttons content.
|
||||
</p>
|
||||
<t t-set-slot="footer">
|
||||
<button class="btn btn-primary" t-on-click="onConfirm">Ok</button>
|
||||
<button class="btn btn-secondary" t-on-click="onCancel">Cancel</button>
|
||||
</t>
|
||||
</Dialog>
|
||||
`;
|
||||
|
||||
setup() {
|
||||
this.notification = useService("notification");
|
||||
}
|
||||
|
||||
onConfirm() {
|
||||
this.notification.add('You confirmed the dialog.');
|
||||
this.props.close();
|
||||
}
|
||||
|
||||
onCancel() {
|
||||
this.notification.add('You canceled the dialog.');
|
||||
this.props.close();
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
class CheatWeb extends Component {
|
||||
//#region statics
|
||||
static template = "cheat_web";
|
||||
static components = { Layout };
|
||||
static props = {
|
||||
...standardActionServiceProps,
|
||||
};
|
||||
//#endregion
|
||||
|
||||
setup() {
|
||||
//#region services
|
||||
this.orm = useService("orm");
|
||||
this.notification = useService("notification");
|
||||
this.dialog = useService("dialog");
|
||||
//#endregion
|
||||
|
||||
this.user = user;
|
||||
this.state = useState({
|
||||
userCharValue: user.settings.cheat_web_user_setting_char_field,
|
||||
userIntegerValue: user.settings.cheat_web_user_setting_integer_field
|
||||
});
|
||||
}
|
||||
|
||||
//#region user
|
||||
get userImage(){
|
||||
return url("/web/image", {
|
||||
model: 'res.users',
|
||||
id: user.userId,
|
||||
field: "avatar_128",
|
||||
});
|
||||
}
|
||||
|
||||
async dumpUserSettings(){
|
||||
console.log("User:", user);
|
||||
console.log("User settings:", user.settings);
|
||||
}
|
||||
|
||||
async dumpState(){
|
||||
console.log("state:", this.state);
|
||||
}
|
||||
|
||||
async saveUserSettings(){
|
||||
await user.setUserSettings(
|
||||
"cheat_web_user_setting_char_field",
|
||||
this.state.userCharValue
|
||||
);
|
||||
await user.setUserSettings(
|
||||
"cheat_web_user_setting_integer_field",
|
||||
this.state.userIntegerValue
|
||||
);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region misc function
|
||||
getRandomInteger(min, max) {
|
||||
return Math.floor(Math.random() * (max - min) ) + min;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region rpc
|
||||
async rpcDoSomething() {
|
||||
const res = await rpc("/cheat/webrpc");
|
||||
console.log(res);
|
||||
}
|
||||
|
||||
async rpcDoSomethingElse() {
|
||||
const res = await rpc("/cheat/webrpcwithparam", { param1: 1, param2: "2"});
|
||||
console.log(res);
|
||||
}
|
||||
|
||||
async rpcDoSomethingWithRouteParam() {
|
||||
const param1 = 3;
|
||||
const param2 = 4;
|
||||
const res = await rpc(`/cheat/webrpc/${param1}/${param2}`);
|
||||
console.log(res);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region orm
|
||||
async ormCreate() {
|
||||
const res = await this.orm.create("cheat.web", [
|
||||
{
|
||||
char_field: "Char Field #" + DateTime.now().toUnixInteger(),
|
||||
int_field: this.getRandomInteger(1, 1000)
|
||||
},
|
||||
{
|
||||
char_field: "Char Field #" + DateTime.now().toUnixInteger(),
|
||||
int_field: this.getRandomInteger(1, 1000)
|
||||
},
|
||||
]);
|
||||
console.log(res);
|
||||
}
|
||||
|
||||
async ormSearch() {
|
||||
const res = await this.orm.search("cheat.web", [['id', '>=', 0]], { order: "id desc", limit: 100, offset: 0 });
|
||||
console.log(res);
|
||||
}
|
||||
|
||||
async ormSearchCount() {
|
||||
const res = await this.orm.searchCount("cheat.web", [['id', '>=', 0]]);
|
||||
console.log(res);
|
||||
}
|
||||
|
||||
async ormSearchRead() {
|
||||
const res = await this.orm.searchRead("cheat.web", [['id', '>=', 0]], ["char_field", "int_field"]);
|
||||
console.log(res);
|
||||
}
|
||||
|
||||
async ormRead() {
|
||||
const ids = await this.orm.search("cheat.web", [['id', '>=', 0]]);
|
||||
if (ids.length > 0){
|
||||
const res = await this.orm.read("cheat.web", [ids[0]], ["char_field"]);
|
||||
console.log(res);
|
||||
}
|
||||
}
|
||||
|
||||
async ormWrite() {
|
||||
const ids = await this.orm.search("cheat.web", [['id', '>=', 0]]);
|
||||
if (ids.length > 0) {
|
||||
let res = await this.orm.read("cheat.web", [ids[0]], ["char_field"]);
|
||||
console.log('Old Values:', res);
|
||||
await this.orm.write('cheat.web', [ids[0]],
|
||||
{
|
||||
'char_field': `Updated #${DateTime.now().toUnixInteger()}`
|
||||
}
|
||||
);
|
||||
res = await this.orm.read("cheat.web", [ids[0]], ["char_field"]);
|
||||
console.log('New Values:', res);
|
||||
}
|
||||
}
|
||||
|
||||
async ormUnlink() {
|
||||
let ids = await this.orm.search("cheat.web", [['id', '>=', 0]], { order: "id desc" });
|
||||
if (ids.length > 0) {
|
||||
console.log('Search Result:', ids);
|
||||
await this.orm.unlink('cheat.web', [ids[0]]);
|
||||
ids = await this.orm.search("cheat.web", [['id', '>=', 0]], { order: "id desc" });
|
||||
console.log('Search Result:', ids);
|
||||
}
|
||||
}
|
||||
|
||||
async ormDoSomething() {
|
||||
const res = await this.orm.call("cheat.web", "do_something", [1]);
|
||||
console.log(res);
|
||||
}
|
||||
|
||||
async ormDoSomethingElse() {
|
||||
const res = await this.orm.call("cheat.web", "do_something_else", [1], { param1: '1', param2: "2"});
|
||||
console.log(res);
|
||||
}
|
||||
|
||||
async ormDoModelMethod() {
|
||||
const res = await this.orm.call("cheat.web", "do_model_method", [], { param1: "3", param2: "4" });
|
||||
console.log(res);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region notification
|
||||
async notifSimple(){
|
||||
this.notification.add('This is a simple Notification');
|
||||
}
|
||||
|
||||
async notifSticky(){
|
||||
this.notification.add('This is a sticky Notification', {
|
||||
title: 'Sticky Notification',
|
||||
type: 'success',
|
||||
sticky: true,
|
||||
});
|
||||
}
|
||||
|
||||
async notifCallback(){
|
||||
this.notification.add('This is a Notification with onClose callback', {
|
||||
title: 'Sticky Notification',
|
||||
type: 'info',
|
||||
sticky: true,
|
||||
onClose: () => {
|
||||
this.notification.add('This is from onclose callback.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async notifWithButtons(){
|
||||
this.notification.add('This is a Notification with Buttons', {
|
||||
title: 'Sticky Notification',
|
||||
type: 'warning',
|
||||
sticky: true,
|
||||
buttons: [
|
||||
{
|
||||
name: 'Button 1',
|
||||
onClick: () => {
|
||||
this.notification.add('This is from button 1 onclick.');
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Button 2',
|
||||
onClick: () => {
|
||||
this.notification.add('This is from button 2 onclick.');
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region dialog
|
||||
async dialogAlert(){
|
||||
this.dialog.add(AlertDialog, {
|
||||
title: 'This is an Alert Dialog',
|
||||
body: 'This is the alert message.',
|
||||
contentClass: 'text-danger'
|
||||
});
|
||||
}
|
||||
|
||||
async dialogConfirm(){
|
||||
this.dialog.add(ConfirmationDialog, {
|
||||
title: 'This is an Confirmation Dialog',
|
||||
body: 'This is the dialog message.',
|
||||
confirmLabel: 'Click here to confirm.',
|
||||
cancelLabel: "Click here to cancel.",
|
||||
cancel: async () => {
|
||||
this.notification.add('You canceled the dialog.');
|
||||
},
|
||||
confirm: async () => {
|
||||
this.notification.add('You confirmed the dialog.');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async dialogCustom(){
|
||||
this.dialog.add(MyDialog);
|
||||
}
|
||||
|
||||
async dialogCustomWithButtons(){
|
||||
this.dialog.add(MyDialogWithButtons);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
}
|
||||
|
||||
registry.category("actions").add("cheat_web", CheatWeb);
|
||||
@@ -0,0 +1,152 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="cheat_web" owl="1">
|
||||
<Layout display="{ controlPanel: {} }" className="'overflow-auto h-100'">
|
||||
<div class="cheat-web-container">
|
||||
<div class="accordion shadow-sm" id="mainAccordion">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseRpc" aria-expanded="true" aria-controls="collapseRpc">
|
||||
RPC
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseRpc" class="accordion-collapse collapse show" data-bs-parent="#mainAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="container mx-2 my-4 gx-2">
|
||||
<div class="btn-group me-2" role="group">
|
||||
<button class="btn btn-primary" t-on-click="rpcDoSomething">RPC Call</button>
|
||||
<button class="btn btn-primary" t-on-click="rpcDoSomethingElse">RPC Call With Param</button>
|
||||
<button class="btn btn-primary" t-on-click="rpcDoSomethingWithRouteParam">RPC Call With Router Param</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOrm" aria-expanded="true" aria-controls="collapseOrm">
|
||||
ORM
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseOrm" class="accordion-collapse collapse" data-bs-parent="#mainAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="container mx-2 my-4 gx-2">
|
||||
<div class="btn-group me-2" role="group">
|
||||
<button class="btn btn-primary" t-on-click="ormCreate">ORM Create</button>
|
||||
<button class="btn btn-primary" t-on-click="ormSearch">ORM Search</button>
|
||||
<button class="btn btn-primary" t-on-click="ormSearchCount">ORM Search Count</button>
|
||||
<button class="btn btn-primary" t-on-click="ormSearchRead">ORM Search Read</button>
|
||||
<button class="btn btn-primary" t-on-click="ormRead">ORM Read</button>
|
||||
<button class="btn btn-primary" t-on-click="ormWrite">ORM Write</button>
|
||||
<button class="btn btn-primary" t-on-click="ormUnlink">ORM Unlink</button>
|
||||
</div>
|
||||
<div class="btn-group me-2" role="group">
|
||||
<button class="btn btn-primary" t-on-click="ormDoSomething">ORM Call</button>
|
||||
<button class="btn btn-primary" t-on-click="ormDoSomethingElse">ORM Call With Param</button>
|
||||
<button class="btn btn-primary" t-on-click="ormDoModelMethod">ORM Call Model Method</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseNotification" aria-expanded="true" aria-controls="collapseNotification">
|
||||
Notification
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseNotification" class="accordion-collapse collapse" data-bs-parent="#mainAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="container mx-2 my-4 gx-2">
|
||||
<div class="btn-group me-2" role="group">
|
||||
<button class="btn btn-primary" t-on-click="notifSimple">Simple Notification</button>
|
||||
<button class="btn btn-primary" t-on-click="notifSticky">Sticky Notification</button>
|
||||
<button class="btn btn-primary" t-on-click="notifCallback">Notification with On Close Callback</button>
|
||||
<button class="btn btn-primary" t-on-click="notifWithButtons">Notification with Buttons</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseDialogBox" aria-expanded="true" aria-controls="collapseDialogBox">
|
||||
Dialog Box
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseDialogBox" class="accordion-collapse collapse" data-bs-parent="#mainAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="container mx-2 my-4 gx-2">
|
||||
<div class="btn-group me-2" role="group">
|
||||
<button class="btn btn-primary" t-on-click="dialogAlert">Alert Dialog</button>
|
||||
<button class="btn btn-primary" t-on-click="dialogConfirm">Confirmation Dialog</button>
|
||||
<button class="btn btn-primary" t-on-click="dialogCustom">Custom Dialog</button>
|
||||
<button class="btn btn-primary" t-on-click="dialogCustomWithButtons">Custom Dialog with Buttons</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseUserService" aria-expanded="true" aria-controls="collapseUserService">
|
||||
User Information
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseUserService" class="accordion-collapse collapse" data-bs-parent="#mainAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="container d-flex justify-content-center cheat-web-user-profile">
|
||||
<div class="card p-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="image">
|
||||
<img t-att-src="userImage" class="rounded" width="155" />
|
||||
</div>
|
||||
<div class="ms-3 w-100">
|
||||
<h4 class="mb-0 mt-0"><t t-out="this.user.name" /></h4>
|
||||
<span><t t-out="this.user.login" /></span>
|
||||
<div class="p-2 mt-2 bg-primary d-flex justify-content-between rounded text-white stats">
|
||||
<div class="d-flex flex-column">
|
||||
<span class="articles">Language</span>
|
||||
<span class="stat-string"><t t-out="this.user.lang" /></span>
|
||||
</div>
|
||||
<div class="d-flex flex-column">
|
||||
<span class="followers">Timezone</span>
|
||||
<span class="stat-string"><t t-out="this.user.tz" /></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button mt-2 d-flex flex-row align-items-center">
|
||||
<button t-on-click="dumpUserSettings" class="btn btn-sm btn-outline-primary w-100">Dump User Data</button>
|
||||
<button data-bs-toggle="collapse" data-bs-target="#collapseUserSettings" class="btn btn-sm btn-outline-primary w-100 ms-3">Show Settings</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="collapse" id="collapseUserSettings">
|
||||
<div class="card-body">
|
||||
<div class="row mb-3">
|
||||
<label for="userSettingCharField" class="col-sm-4 col-form-label">Char Field</label>
|
||||
<div class="col-sm-8">
|
||||
<input class="form-control" id="userSettingCharField" t-model.lazy="this.state.userCharValue" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label for="userSettingIntegerField" class="col-sm-4 col-form-label">Integer Field</label>
|
||||
<div class="col-sm-8">
|
||||
<input class="form-control" id="userSettingIntegerField" t-model.lazy.number="this.state.userIntegerValue" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="button mt-2 d-flex flex-row align-items-center">
|
||||
<button t-on-click="dumpState" class="btn btn-sm btn-outline-primary w-100">Dump State</button>
|
||||
<button t-on-click="saveUserSettings" class="btn btn-sm btn-outline-primary w-100 ms-3">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,17 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { Component, useState } from "@odoo/owl";
|
||||
|
||||
export class Collapsible extends Component {
|
||||
static template = "web.collapsible";
|
||||
static components = { };
|
||||
static props = {
|
||||
name: { type: String },
|
||||
title: { type: String },
|
||||
slots: { type: Object, optional: true },
|
||||
collapsed: { type: Boolean, optional: true}
|
||||
};
|
||||
static defaultProps = {
|
||||
collapsed: true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="web.collapsible" owl="1">
|
||||
<div class="card">
|
||||
<h5 class="card-header">
|
||||
<a class="d-block" data-bs-toggle="collapse" t-attf-href="{{ '#' + props.name }}" aria-expanded="true" aria-controls="collapse-collapsed">
|
||||
<t t-out="props.title" />
|
||||
<i class="fa fa-chevron-down float-end"></i>
|
||||
</a>
|
||||
</h5>
|
||||
<div t-att-id="props.name" t-attf-class="{{ props.collapsed ? 'collapse' : 'collapse show' }}">
|
||||
<div class="card-body">
|
||||
<p class="card-text text-wrap overflow-auto" t-ref="useRefCardText">
|
||||
<t t-slot="default"/>
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-footer" t-if="props.slots['footer']">
|
||||
<t t-slot="footer"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,25 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { Component, useState } from "@odoo/owl";
|
||||
|
||||
export class HAccordion extends Component {
|
||||
static template = "web.haccordion";
|
||||
static components = { };
|
||||
static props = {
|
||||
name: { type: String },
|
||||
startItem: { type: Number, optional: true },
|
||||
slots: { type: Object, optional: true },
|
||||
};
|
||||
static defaultProps = {
|
||||
startItem: 0
|
||||
}
|
||||
|
||||
setup() {
|
||||
this.state = useState({ activeItem: this.props.startItem });
|
||||
this.itemNames = Object.keys(this.props.slots);
|
||||
}
|
||||
|
||||
onClick(item) {
|
||||
this.state.activeItem = item;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// Variables
|
||||
$tab-width: 60px;
|
||||
$screen-width: 100vw;
|
||||
|
||||
.haccordion-root {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
.haccordion-item {
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
transition: width 500ms ease;
|
||||
}
|
||||
|
||||
.haccordion-fold {
|
||||
width: $tab-width !important;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
|
||||
& .haccordion-title-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
background-color: $o-gray-800 !important;
|
||||
|
||||
& .haccordion-title {
|
||||
white-space: nowrap;
|
||||
transform: rotate(-90deg);
|
||||
align-self: end;
|
||||
position: relative;
|
||||
bottom: 75px;
|
||||
left: 5px;
|
||||
width: 100%;
|
||||
|
||||
& * {
|
||||
color: $o-gray-600 !important;
|
||||
|
||||
&:hover {
|
||||
color: $o-gray-300 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .haccordion-content {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.haccordion-expand {
|
||||
width: 100%;
|
||||
|
||||
& .haccordion-title {
|
||||
display: none;
|
||||
}
|
||||
|
||||
& .haccordion-content {
|
||||
padding-left: 10px;
|
||||
display: revert;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="web.haccordion" owl="1">
|
||||
<div class="haccordion-root">
|
||||
<div t-attf-class="{{ 'haccordion-item ' + (this.state.activeItem === item_index ? 'haccordion-expand' : 'haccordion-fold') }}"
|
||||
t-foreach="itemNames"
|
||||
t-as="item"
|
||||
t-key="item_index"
|
||||
t-on-click="() => this.state.activeItem = item_index" >
|
||||
<div class="haccordion-title-container">
|
||||
<div class="haccordion-title" t-attf-id="{{ props.name + '-' + item + '-' + item_index.toString()}}">
|
||||
<h1><t t-out="props.slots[item].title" /></h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="haccordion-content p-4">
|
||||
<h1>
|
||||
<t t-out="props.slots[item].title" />
|
||||
</h1>
|
||||
<t t-slot="{{ item }}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,41 @@
|
||||
import { visitXML } from "@web/core/utils/xml";
|
||||
import { Field } from "@web/views/fields/field";
|
||||
|
||||
export class CheatArchParser {
|
||||
parseFieldNode(node, models, modelName) {
|
||||
return Field.parseFieldNode(node, models, modelName, "cheat");
|
||||
}
|
||||
|
||||
parse(xmlDoc, models, modelName) {
|
||||
console.log("Cheat Arch Parser - xmlDoc: ", xmlDoc);
|
||||
console.log("Cheat Arch Parser - models: ", models);
|
||||
console.log("Cheat Arch Parser - modelName: ", modelName);
|
||||
|
||||
const fieldNodes = {};
|
||||
const limit = xmlDoc.getAttribute("limit") || 80;
|
||||
|
||||
let display_name_field = [...xmlDoc.children].find(o => o.attributes['name'].value === "display_name");
|
||||
if (!display_name_field){
|
||||
display_name_field = document.createElement("field");
|
||||
display_name_field.setAttribute("name", "display_name");
|
||||
xmlDoc.appendChild(display_name_field);
|
||||
}
|
||||
|
||||
visitXML(xmlDoc, (node) => {
|
||||
console.log("Cheat Arch Parser - node: ", node);
|
||||
|
||||
if (node.tagName === "field") {
|
||||
const fieldNode = this.parseFieldNode(node, models, modelName);
|
||||
|
||||
const conserveLineBreaks = node.getAttribute("conserve-line-breaks");
|
||||
fieldNode.conserveLineBreaks = conserveLineBreaks === 'true';
|
||||
fieldNodes[fieldNode.name] = fieldNode;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
fieldNodes,
|
||||
limit
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
|
||||
import { Component, useState, useRef, onWillPatch } from "@odoo/owl";
|
||||
|
||||
import { Layout } from "@web/search/layout";
|
||||
import { usePager } from "@web/search/pager_hook";
|
||||
import { useSearchBarToggler } from "@web/search/search_bar/search_bar_toggler";
|
||||
import { SearchBar } from "@web/search/search_bar/search_bar";
|
||||
|
||||
import { useModel } from "@web/model/model";
|
||||
import { extractFieldsFromArchInfo } from "@web/model/relational_model/utils";
|
||||
|
||||
import { standardViewProps } from "@web/views/standard_view_props";
|
||||
import { executeButtonCallback } from "@web/views/view_button/view_button_hook";
|
||||
|
||||
export class CheatController extends Component {
|
||||
static template = `cheat_web.CheatView`;
|
||||
static props = {
|
||||
...standardViewProps,
|
||||
offset: { type: Number, optional: true },
|
||||
Model: Function,
|
||||
Renderer: Function,
|
||||
archInfo: Object,
|
||||
}
|
||||
static components = {
|
||||
Layout,
|
||||
SearchBar
|
||||
};
|
||||
|
||||
setup() {
|
||||
console.log("Statistic Controller - this: ", this);
|
||||
console.log("Statistic Controller - props: ", this.props);
|
||||
|
||||
this.rootRef = useRef("root");
|
||||
|
||||
this.props.offset = 0;
|
||||
this.model = useState(useModel(this.props.Model, this.modelParams));
|
||||
|
||||
usePager(() => {
|
||||
return {
|
||||
offset: this.model.offset,
|
||||
limit: this.model.limit,
|
||||
total: this.model.recordsLength,
|
||||
onUpdate: async ({ offset, limit }) => {
|
||||
this.props.offset = offset;
|
||||
this.props.limit = limit;
|
||||
await this.model.load(this.props);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
this.searchBarToggler = useSearchBarToggler();
|
||||
|
||||
this.firstLoad = true;
|
||||
onWillPatch(() => {
|
||||
this.firstLoad = false;
|
||||
});
|
||||
}
|
||||
|
||||
get modelParams() {
|
||||
const { activeFields, fields } = extractFieldsFromArchInfo(
|
||||
this.props.archInfo,
|
||||
this.props.fields
|
||||
);
|
||||
|
||||
for (let [key, value] of Object.entries(activeFields)) {
|
||||
let fieldNode = this.props.archInfo.fieldNodes[key];
|
||||
value.conserveLineBreaks = fieldNode.conserveLineBreaks;
|
||||
}
|
||||
|
||||
return {
|
||||
resModel: this.props.resModel,
|
||||
fields,
|
||||
activeFields,
|
||||
};
|
||||
}
|
||||
|
||||
get rendererProps() {
|
||||
return {
|
||||
model: this.model,
|
||||
records: this.model.records,
|
||||
activeFields: this.model.config.activeFields,
|
||||
editRecord: this.onClickEdit.bind(this),
|
||||
};
|
||||
}
|
||||
|
||||
get className() {
|
||||
return this.props.className;
|
||||
}
|
||||
|
||||
async onClickCreate() {
|
||||
return executeButtonCallback(this.rootRef.el, () => this.props.createRecord());
|
||||
}
|
||||
|
||||
async onClickEdit(ev, record) {
|
||||
return executeButtonCallback(this.rootRef.el, () => this.props.selectRecord(record.id, true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="cheat_web.CheatView">
|
||||
<div t-att-class="className" t-ref="root">
|
||||
<Layout display="props.display" className="'h-100 overflow-auto'">
|
||||
<t t-set-slot="control-panel-create-button">
|
||||
<button type="button" class="btn btn-primary o_list_button_add" t-on-click="onClickCreate">
|
||||
New
|
||||
</button>
|
||||
</t>
|
||||
<t t-set-slot="layout-actions">
|
||||
<SearchBar t-if="searchBarToggler.state.showSearchBar" autofocus="firstLoad"/>
|
||||
</t>
|
||||
<t t-component="props.Renderer" t-props="rendererProps"/>
|
||||
</Layout>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,46 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { markRaw } from "@odoo/owl";
|
||||
import { KeepLast } from "@web/core/utils/concurrency";
|
||||
import { Model } from "@web/model/model";
|
||||
import { getFieldsSpec } from "@web/model/relational_model/utils";
|
||||
import { orderByToString } from "@web/search/utils/order_by";
|
||||
|
||||
export class CheatModel extends Model {
|
||||
setup(params) {
|
||||
console.log("Cheat Model - this: ", this);
|
||||
console.log("Cheat Model - setup params: ", params);
|
||||
|
||||
this.keepLast = markRaw(new KeepLast());
|
||||
this.config = params;
|
||||
}
|
||||
|
||||
_getNextConfig(currentConfig, params) {
|
||||
const config = Object.assign({}, currentConfig, params);
|
||||
return config;
|
||||
}
|
||||
|
||||
async load(params = {}) {
|
||||
console.log("Cheat Model - load params: ", params);
|
||||
|
||||
const config = this._getNextConfig(this.config, params);
|
||||
|
||||
const kwargs = {
|
||||
specification: getFieldsSpec(config.activeFields, config.fields, config.context),
|
||||
offset: config.offset,
|
||||
order: orderByToString(config.orderBy),
|
||||
limit: config.limit,
|
||||
context: { ...config.context }
|
||||
};
|
||||
|
||||
const { length, records } = await this.keepLast.add(
|
||||
this.orm.webSearchRead(config.resModel, config.domain, kwargs)
|
||||
);
|
||||
|
||||
this.offset = config.offset;
|
||||
this.limit = config.limit;
|
||||
this.records = records;
|
||||
this.recordsLength = length;
|
||||
this.config = config;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Component, useRef } from "@odoo/owl";
|
||||
import { executeButtonCallback } from "@web/views/view_button/view_button_hook";
|
||||
|
||||
export class CheatRenderer extends Component {
|
||||
static template = `cheat_web.CheatRenderer`;
|
||||
static props = {
|
||||
model: Object,
|
||||
records: Object,
|
||||
activeFields: Object,
|
||||
editRecord: Function
|
||||
}
|
||||
|
||||
setup() {
|
||||
console.log("Cheat Renderer - this: ", this);
|
||||
this.rootRef = useRef("renderer_root");
|
||||
}
|
||||
|
||||
getInputId(record, field){
|
||||
return record.id + '_' + field
|
||||
}
|
||||
|
||||
getFirstFiveFields(){
|
||||
const fields = Object.keys(this.props.activeFields).filter(o => o !== "display_name" & o !== "id") .sort().slice(0, 4);
|
||||
return fields;
|
||||
}
|
||||
|
||||
getLabel(field){
|
||||
const config = this.props.model.config;
|
||||
return config.fields[field].string;
|
||||
}
|
||||
|
||||
getFieldValue(record, field){
|
||||
return record[field];
|
||||
}
|
||||
|
||||
getConserveLineBreakSetting(field){
|
||||
return this.props.activeFields[field].conserveLineBreaks;
|
||||
}
|
||||
|
||||
onEditButtonClick(ev, record) {
|
||||
this.props.editRecord(ev, record);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="cheat_web.CheatRenderer">
|
||||
<div class="row m-2" t-ref="renderer_root">
|
||||
<t t-foreach="props.records" t-as="record" t-key="record.id">
|
||||
<div class="col-lg-6 col-sm-12 p-2">
|
||||
<div class="card">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-3 d-flex justify-content-center align-items-center bg-secondary rounded-start">
|
||||
<h1><t t-out="record.id"/></h1>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title"><t t-out="record.display_name"/></h2>
|
||||
<div class="card-text card-text border rounded">
|
||||
<t t-foreach="getFirstFiveFields()" t-as="field" t-key="field_index">
|
||||
<div t-attf-class="form-group d-flex {{ field_last ? '' : 'border-bottom' }}">
|
||||
<t t-set="fieldId" t-value="getInputId(record, field)"/>
|
||||
<label t-att-for="fieldId" class="col-sm-4 col-form-label me-1 px-1 bg-secondary">
|
||||
<t t-out="getLabel(field)"/>
|
||||
</label>
|
||||
<div class="col-sm-8 px-1">
|
||||
<t t-if="getConserveLineBreakSetting(field)" >
|
||||
<textarea readonly="1" rows="5" class="form-control-plaintext" t-att-id="fieldId">
|
||||
<t t-out="getFieldValue(record, field)" />
|
||||
</textarea>
|
||||
</t>
|
||||
<t t-else="" >
|
||||
<input type="text" readonly="1" class="form-control-plaintext text-truncate"
|
||||
t-att-id="fieldId" t-att-value="getFieldValue(record, field)"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end">
|
||||
<button t-on-click="(ev) => this.onEditButtonClick(ev, record)" class="btn btn-primary mt-2">Edit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,33 @@
|
||||
import { registry } from "@web/core/registry";
|
||||
import { CheatModel } from "./cheat_model";
|
||||
import { CheatController } from "./cheat_controller";
|
||||
import { CheatArchParser } from "./cheat_arch_parser";
|
||||
import { CheatRenderer } from "./cheat_renderer";
|
||||
|
||||
export const cheatView = {
|
||||
type: "cheat",
|
||||
searchMenuTypes: ["filter", "favorite"],
|
||||
Controller: CheatController,
|
||||
Renderer: CheatRenderer,
|
||||
Model: CheatModel,
|
||||
ArchParser: CheatArchParser,
|
||||
|
||||
props(genericProps, view) {
|
||||
console.log("Cheat View - this: ", this);
|
||||
console.log("Cheat View - genericProps: ", genericProps);
|
||||
console.log("Cheat View - view: ", view);
|
||||
|
||||
const { ArchParser } = view;
|
||||
const { arch, relatedModels, resModel } = genericProps;
|
||||
const archInfo = new ArchParser().parse(arch, relatedModels, resModel);
|
||||
|
||||
return {
|
||||
...genericProps,
|
||||
Model: view.Model,
|
||||
Renderer: view.Renderer,
|
||||
archInfo,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
registry.category("views").add("cheat", cheatView);
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
import { standardViewProps } from "@web/views/standard_view_props";
|
||||
import { Layout } from "@web/search/layout";
|
||||
|
||||
export class HelloController extends Component {
|
||||
static template = `cheat_web.HelloView`;
|
||||
static props = {
|
||||
...standardViewProps,
|
||||
aValue: { type: String }
|
||||
};
|
||||
static components = { Layout };
|
||||
|
||||
setup() {
|
||||
console.log("Hello Controller - this: ", this);
|
||||
console.log("Hello Controller - props: ", this.props);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="cheat_web.HelloView">
|
||||
<div class="h-100" t-ref="root">
|
||||
<Layout display="props.display" className="'h-100'">
|
||||
<div class="d-flex flex-column h-75 justify-content-center align-items-center">
|
||||
<h1><span><i class="fa fa-smile-o" style="font-size: xxx-large !important;"></i></span></h1>
|
||||
<h1>Hello there!</h1>
|
||||
</div>
|
||||
</Layout>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,20 @@
|
||||
import { registry } from "@web/core/registry";
|
||||
import { HelloController } from "./hello_controller";
|
||||
|
||||
export const helloView = {
|
||||
type: "hello",
|
||||
Controller: HelloController,
|
||||
|
||||
props(genericProps, view) {
|
||||
console.log("Hello View - this: ", this);
|
||||
console.log("Hello View - genericProps: ", genericProps);
|
||||
console.log("Hello View - view: ", view);
|
||||
|
||||
return {
|
||||
...genericProps,
|
||||
aValue: 'A value from view type.'
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
registry.category("views").add("hello", helloView);
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Component, useState, useRef, onWillStart } from "@odoo/owl";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
import { standardViewProps } from "@web/views/standard_view_props";
|
||||
import { Layout } from "@web/search/layout";
|
||||
|
||||
export class StatisticController extends Component {
|
||||
static template = `cheat_web.StatisticView`;
|
||||
static props = {
|
||||
...standardViewProps,
|
||||
Model: Function,
|
||||
Renderer: Function,
|
||||
};
|
||||
static components = { Layout };
|
||||
|
||||
setup() {
|
||||
console.log("Statistic Controller - this: ", this);
|
||||
console.log("Statistic Controller - props: ", this.props);
|
||||
|
||||
this.orm = useService("orm");
|
||||
|
||||
this.model = new this.props.Model(
|
||||
this.orm,
|
||||
this.props.resModel,
|
||||
this.props.fields
|
||||
);
|
||||
|
||||
onWillStart(async () => {
|
||||
await this.model.load(this.props);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="cheat_web.StatisticView">
|
||||
<div class="h-100">
|
||||
<Layout display="props.display" className="'h-100'">
|
||||
<t t-component="props.Renderer" model="model"/>
|
||||
</Layout>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,21 @@
|
||||
import { KeepLast } from "@web/core/utils/concurrency";
|
||||
|
||||
export class StatisticModel {
|
||||
constructor(orm, resModel, fields) {
|
||||
this.orm = orm;
|
||||
this.resModel = resModel;
|
||||
this.fields = fields;
|
||||
this.keepLast = new KeepLast();
|
||||
|
||||
console.log("Statistic Model - this: ", this);
|
||||
}
|
||||
|
||||
async load(params) {
|
||||
console.log("Statistic Model - load params: ", params);
|
||||
|
||||
const recordCount = await this.keepLast.add(
|
||||
this.orm.searchCount(this.resModel, [])
|
||||
);
|
||||
this.recordCount = recordCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import {
|
||||
Component
|
||||
} from "@odoo/owl";
|
||||
|
||||
export class StatisticRenderer extends Component {
|
||||
static template = `cheat_web.StatisticRenderer`;
|
||||
static props = {
|
||||
model: Object
|
||||
}
|
||||
|
||||
fieldInfo(name){
|
||||
return this.props.model.fields[name];
|
||||
}
|
||||
|
||||
fieldCount() {
|
||||
return Object.keys(this.props.model.fields).length;
|
||||
}
|
||||
|
||||
fieldNames() {
|
||||
return Object.keys(this.props.model.fields).sort();
|
||||
}
|
||||
|
||||
setup() {
|
||||
console.log("Statistic Renderer - this: ", this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="cheat_web.StatisticRenderer">
|
||||
<div class="row m-3 d-flex justify-content-center align-items-center">
|
||||
<div class="card p-0">
|
||||
<div class="row g-0 ">
|
||||
<div class="col-md-1 col-2 d-flex flex-column pt-4 justify-content-start align-items-center bg-secondary rounded-start">
|
||||
<h1><t t-out="props.model.recordCount"/></h1>
|
||||
<div>records</div>
|
||||
</div>
|
||||
<div class="col-md-11 col-10">
|
||||
<div class="card-body overflow-y-auto">
|
||||
<h2 class="card-title">Model: <t t-out="props.model.resModel"/></h2>
|
||||
<div class="card-text">
|
||||
<div>Fields count: <t t-out="fieldCount()"/></div>
|
||||
<div style="max-height: 75dvh;">
|
||||
<table class="table table-striped table-hover mt-3 no-cellpadding">
|
||||
<thead>
|
||||
<tr class="table-secondary">
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr t-foreach="fieldNames()" t-as="field" t-key="field_index">
|
||||
<t t-set="fInfo" t-value="fieldInfo(field)" />
|
||||
<td><t t-out="fInfo['name']"/></td>
|
||||
<td><t t-out="fInfo['type']"/></td>
|
||||
<td><t t-out="fInfo['help']"/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { registry } from "@web/core/registry";
|
||||
import { StatisticModel } from "./statistic_model";
|
||||
import { StatisticController } from "./statistic_controller";
|
||||
import { StatisticRenderer } from "./statistic_renderer";
|
||||
|
||||
export const statisticView = {
|
||||
type: "statistic",
|
||||
Controller: StatisticController,
|
||||
Renderer: StatisticRenderer,
|
||||
Model: StatisticModel,
|
||||
|
||||
props(genericProps, view) {
|
||||
console.log("Statistic View - this: ", this);
|
||||
console.log("Statistic View - genericProps: ", genericProps);
|
||||
console.log("Statistic View - view: ", view);
|
||||
|
||||
return {
|
||||
...genericProps,
|
||||
Model: view.Model,
|
||||
Renderer: view.Renderer
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
registry.category("views").add("statistic", statisticView);
|
||||
Reference in New Issue
Block a user