summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/views/action_model.js
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/web/static/src/js/views/action_model.js
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/web/static/src/js/views/action_model.js')
-rw-r--r--addons/web/static/src/js/views/action_model.js236
1 files changed, 236 insertions, 0 deletions
diff --git a/addons/web/static/src/js/views/action_model.js b/addons/web/static/src/js/views/action_model.js
new file mode 100644
index 00000000..c3b69271
--- /dev/null
+++ b/addons/web/static/src/js/views/action_model.js
@@ -0,0 +1,236 @@
+odoo.define("web/static/src/js/views/action_model.js", function (require) {
+ "use strict";
+
+ const Domain = require("web.Domain");
+ const { FACET_ICONS } = require("web.searchUtils");
+ const { Model } = require("web/static/src/js/model.js");
+ const { parseArch } = require("web.viewUtils");
+ const pyUtils = require("web.py_utils");
+ const Registry = require("web.Registry");
+
+ const isNotNull = (value) => value !== null && value !== undefined;
+ const isObject = (obj) => typeof obj === "object" && obj !== null;
+
+ /**
+ * @extends Model.Extension
+ */
+ class ActionModelExtension extends Model.Extension {
+
+ //---------------------------------------------------------------------
+ // Public
+ //---------------------------------------------------------------------
+
+ /**
+ * Initiates the asynchronous tasks of the extension and returns a
+ * promise resolved as soon as all the informations necessary to build
+ * the search query are ready.
+ * @returns {Promise}
+ */
+ async callLoad() {
+ this.loadPromise = super.callLoad(...arguments);
+ await this.loadPromise;
+ }
+
+ /**
+ * Returns a promise resolved when the extension is completely ready.
+ * @returns {Promise}
+ */
+ async isReady() {
+ await this.loadPromise;
+ }
+
+ //---------------------------------------------------------------------
+ // Static
+ //---------------------------------------------------------------------
+
+ /**
+ * @abstract
+ * @param {Object} archs
+ * @param {string | null} [viewType=null]
+ * @returns {null}
+ */
+ static extractArchInfo() {
+ return null;
+ }
+ }
+
+ /**
+ * @extends Model
+ */
+ class ActionModel extends Model {
+
+ //---------------------------------------------------------------------
+ // Public
+ //---------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ get(property) {
+ switch (property) {
+ case "query": return this.config.searchQuery || this._getQuery();
+ case "facets": return this._getFacets();
+ }
+ return super.get(...arguments);
+ }
+
+ /**
+ * Returns a promise resolved when all extensions are completely ready.
+ * @returns {Promise}
+ */
+ async isReady() {
+ await this._awaitExtensions();
+ }
+
+ //---------------------------------------------------------------------
+ // Private
+ //---------------------------------------------------------------------
+
+ /**
+ * @private
+ * @returns {Promise}
+ */
+ async _awaitExtensions() {
+ await Promise.all(this.extensions.flat().map(
+ (extension) => extension.isReady()
+ ));
+ }
+
+ /**
+ * @override
+ */
+ __get(excluded, property) {
+ const results = super.__get(...arguments);
+ switch (property) {
+ case "domain": return [this.config.domain, ...results];
+ case "context": return [this.config.context, ...results];
+ }
+ return results;
+ }
+
+ /**
+ * Validates and formats all facets given by the extensions. This is
+ * done here rather than in the search bar because the searchMenuTypes
+ * are available only to the model.
+ * @private
+ * @returns {Object[]}
+ */
+ _getFacets() {
+ const types = this.config.searchMenuTypes || [];
+ const isValidType = (type) => (
+ !['groupBy', 'comparison'].includes(type) || types.includes(type)
+ );
+ const facets = [];
+ for (const extension of this.extensions.flat()) {
+ for (const facet of extension.get("facets") || []) {
+ if (!isValidType(facet.type)) {
+ continue;
+ }
+ facet.separator = facet.type === 'groupBy' ? ">" : this.env._t("or");
+ if (facet.type in FACET_ICONS) {
+ facet.icon = FACET_ICONS[facet.type];
+ }
+ facets.push(facet);
+ }
+ }
+ return facets;
+ }
+
+ /**
+ * @typedef TimeRanges
+ * @property {string} fieldName
+ * @property {string} comparisonRangeId
+ * @property {Array[]} range
+ * @property {string} rangeDescription
+ * @property {Array[]} comparisonRange
+ * @property {string} comparisonRangeDescription
+ */
+ /**
+ * @typedef Query
+ * @property {Object} context
+ * @property {Array[]} domain
+ * @property {string[]} groupBy
+ * @property {string[]} orderedBy
+ * @property {TimeRanges?} timeRanges
+ */
+ /**
+ * @private
+ * @returns {Query}
+ */
+ _getQuery() {
+ const evalContext = this.env.session.user_context;
+ const contexts = this.__get(null, "context");
+ const domains = this.__get(null, "domain");
+ const query = {
+ context: pyUtils.eval("contexts", contexts, evalContext),
+ domain: Domain.prototype.normalizeArray(
+ pyUtils.eval("domains", domains, evalContext)
+ ),
+ orderedBy: this.get("orderedBy") || [],
+ };
+ const searchMenuTypes = this.config.searchMenuTypes || [];
+ if (searchMenuTypes.includes("groupBy")) {
+ query.groupBy = this.get("groupBy") || [];
+ } else {
+ query.groupBy = [];
+ }
+ if (searchMenuTypes.includes("comparison")) {
+ query.timeRanges = this.get("timeRanges") || {};
+ }
+ return query;
+ }
+
+ /**
+ * Overridden to trigger a "search" event as soon as the query data
+ * are ready.
+ * @override
+ */
+ async _loadExtensions({ isInitialLoad }) {
+ await super._loadExtensions(...arguments);
+ if (!isInitialLoad) {
+ this.trigger("search", this.get("query"));
+ await this._awaitExtensions();
+ }
+ }
+
+ //---------------------------------------------------------------------
+ // Static
+ //---------------------------------------------------------------------
+
+ /**
+ * @param {Object} archs
+ * @param {string | null} [viewType=null]
+ * @returns {Object}
+ */
+ static extractArchInfo(archs, viewType = null) {
+ const parsedArchs = {};
+ if (!archs.search) {
+ archs.search = "<search/>";
+ }
+ for (const key in archs) {
+ const { attrs, children } = parseArch(archs[key]);
+ const objectChildren = children.filter(isObject);
+ parsedArchs[key] = {
+ attrs,
+ children: objectChildren,
+ };
+ }
+ const archInfo = {};
+ for (const key of this.registry.keys()) {
+ const extension = this.registry.get(key);
+ const result = extension.extractArchInfo(parsedArchs, viewType);
+ if (isNotNull(result)) {
+ archInfo[key] = result;
+ }
+ }
+ return archInfo;
+ }
+ }
+
+ ActionModel.Extension = ActionModelExtension;
+ ActionModel.registry = new Registry(null,
+ (value) => value.prototype instanceof ActionModel.Extension
+ );
+
+ return ActionModel;
+});