Odoo UI widget - how to get settings from database? - odoo

I'm writing an Odoo v9 widget, which renders a URL, based on concatenation of a setting in the database, and the actual form fields.
The setting in the database I figure should live in ir_config_parameter. I'm inserting a default value with my module.
What's the best way to get this value when rendering the widget? Doing an async ajax call using
new Model("ir.config_parameter")
seems a little heavy handed. Is there a better way to be doing this?
Thanks.
Widget code:
var UrlWidget2 = form_common.FormWidget.extend({
start: function() {
this._super();
this.field_manager.on("field_changed:ref", this, this.display_result);
this.display_result();
},
display_result: function() {
var ref = this.field_manager.get_field_value("ref");
if (!ref) return;
var baseUrl = 'https://example.com'; //this is the value I want to get from the setting in the database.
var url = baseUrl + '/foo/' + ref;
this.$el.html('View Externally<br /><br/>');
}
});

You can use RPC for this. This is example which work for me:
var Model = require('web.DataModel');
var UrlWidget2 = form_common.FormWidget.extend({
// just example how to get parameter from backend
display_result: function() {
var parameter = new Model('ir.config_parameter');
// get fields value, key
parameter.query(['value', 'key'])
// criteria of search - record with id = 1
.filter([['id', '=', 1]])
// only one record
.limit(1)
.all()
.then(function (parameter) {
// here data from server
console.log(parameter);
});
// ...
}
});
Hope this helps you.

Related

How to get odoo model from javascript?

I'm trying to do a widget to attach to the sysTrayMenu, I need to know on the on_click event, the current model of the view. I know that I can get it from the current browser url, but I wanted to know if is there a cleaner way to get it from the odoo js api.
For example if the user is in New quotation menu, I need to get sale.order
odoo.define('xx.systray', function (require) {
"use strict";
var config = require('web.config');
var SystrayMenu = require('web.SystrayMenu');
var Widget = require('web.Widget');
var ajax = require('web.ajax');
var xxMenu = Widget.extend({
template:'solvo-support.helpMenu',
events: {
"click": "on_click",
},
on_click: function () {
//HERE I NEED TO GET THE CURRENT MODEL
},
});
SystrayMenu.Items.push(xxMenu);
});
As I remember you can access the the model of the widget like this:
this.model // or self.model if you defined self (self = this)
all widget have this attribute it's string type contains the name of the model.

Async data fetching not updating reactive data property

Ok, guys, I´m having a little issue today, all day long, trying to solve, the deal goes like this...
I´m fetching some data from firebase to render on the html template with asynchronous functions
I have a fetchList Method that is like this:
async mounted() {
let ret = await this.fetchJobRequireList()
console.log('fetchjoblist' , ret)
async fetchJobRequireList() {
// debugger
let services = JSON.parse(sessionStorage.getItem('required_services'))
services != null ? this.required_services = services : null
let docs_ = []
let result = []
if (!services) {
// this.required_services = []
// get required services per user id
let collections = this.$options.firebase.functions().httpsCallable('getRequiredServices')
let docs = await this.$options.firebase.firestore().collection('required_services').get()
// console.log('required services docs', docs)
let _ = this
for (let doc of docs.docs) {
result[doc.id] =
await collections({doc_id: doc.id}).then( async r => {
// debugger
let collections_ = r.data.cols
docs_ = []
_.required_services[doc.id] = []
for (let collection of collections_) {
let path = collection._referencePath.segments
// let documents =
let __ = _
await this.$options.firebase.firestore().collection(path[0])
.doc(path[1]).collection(path[2]).get()
.then(async documents => {
// console.log('__documents__', documents)
for (let doc_ of documents.docs) {
doc_ = await documents.docs[0].ref.get()
doc_ = {
id: doc_.id,
path: doc_.ref.path,
data: doc_.data()
}
// debugger
__.required_services[doc.id].push(doc_)
console.log("this?", this.required_services[doc.id], '__??', __.required_services)
docs_.push(doc_)
}
})
}
console.log('__docs__', docs_)
return docs_
}).catch(err => console.error(err))
// console.log('this.required_services', this.required_services)
}
}
// console.log('object entries', Object.entries(result))
// console.log('__this.required_services__', Object.entries(this.required_services))
// sessionStorage.setItem('required_services', JSON.stringify(this.required_services))
return result
}
The expected result would be for the data function properties to be update after the firebase response came, but no update is happening.
If anyone, have any clues, of what could be happening... some people told me that asynchrounous functions could cause problems... but there is no alternative for them, I guess...
This line
_.required_services[doc.id] = []
is not reactive. See the first point in the docs
So as pointed by #StephenThomas, there is some limitations in array change detection capabilities in reactive property usage.
So after loading the content from firebase, try to push it like this.joblist.push(doc) vue property will not react properly and make some confusion in the head of someone that doesn´t know about this limitation in detecting this kind of array mutation (https://v2.vuejs.org/v2/guide/list.html#Caveats)...
By using this line, now is possible to see the changes in property inside the Vue dev tools
_.joblist.splice(0,0, local_doc)
Thanks #SthephenThomas, for pointing this out!!

Jqgrid change nav properties on callback function

i try to change the navbar properties on a jqgrid in a callback function without succes.
The grid is display afeter user is chosing a period. Depend on either the period is open or close user can or cannot edit, add, delete rows. So the navbar need to change properties dynamically.
My code look like that:
$('#mygrid').jqGrid({
// some properties of my grid that works fine
pager : '#gridpager'
});
$("#mygrid").bind("jqGridLoadComplete",function(){
$.ajax({
url: 'checkifperiodopen.php',
data: {
$("#period").val()
},
success: function(data){
if(period==='open'){
jQuery("#mygrid").jqGrid('navGrid','#gridpager',{add:false,edit:false,del:true,search:true,refresh:true});
}
if(period==='close'){
jQuery("#mygrid").jqGrid('navGrid','#gridpager',{add:true,edit:true,del:true,search:true,refresh:true});
}
}
});
});
$('#validChossenPeriod').click(function () {
ajax call to get data on choosen period
success:function(data){
$("#mygrid").jqGrid('clearGridData');
$("#mygrid").jqGrid('setGridParam', { datatype: 'local'});
$("#mygrid").jqGrid('setGridParam', { data: data});
$("#mygrid").trigger('reloadGrid');
}
});
I finally found the answer by show or hide the div that include the navgrid button:
grid = $("#mygrid");
gid = $.jgrid.jqID(grid[0].id);
var $tdadd = $('#add_' + gid);
var $tdedit = $('#edit_' + gid);
var $tddel = $('#del_' + gid);
$("#mygrid").jqGrid('navGrid','#gridpager',{add:true,edit:true,del:true,search:true,refresh:true});
condition if false =
$tdadd.hide();
$tdedit.hide();
$tddel.hide();
if true =
$tdadd.show();
$tdedit.show();
$tddel.show();
Why so complex? There is a other clear way to do this
var view_buttons = true;
if(condition_to_hide) {
view_buttons = false;
}
$("#mygrid").jqGrid('navGrid','#gridpager', { add:view_buttons, edit:view_buttons, del:view_buttons, search:true, refresh:true});

DataTables: How to set page in preInit?

The docs say that I should be able to set the page via DataTablesApiInstance.page(pageNumber), but I can't get it to work.
All the other API methods like search and order seem to work fine.
Here's my code:
$(document)
.on('preInit.dt', (ev, settings) => {
let tableId = ev.target.id;
let tableState = _.get(['datatables', tableId], history.state) || {};
let api = new $.fn.dataTable.Api(settings);
if(tableState.hasOwnProperty('page')) {
api.page(tableState.page); // <-- problem is here; page doesn't get set
}
if(tableState.hasOwnProperty('search')) {
api.search(tableState.search);
}
if(tableState.hasOwnProperty('order')) {
api.order(tableState.order);
}
const setState = (key, value) => {
history.replaceState(_.set(['datatables', tableId, key], value, history.state), '');
};
api.on('page', ev => {
let info = api.page.info();
// console.log('page', tableId, info.page);
setState('page', info.page);
});
api.on('order', ev => {
let order = api.order();
// console.log('order', tableId, order);
setState('order', order);
});
api.on('search', ev => {
setState('search', api.search());
});
});
The method is hit, but the page isn't set. Am I using the wrong API method? Is there another way to set the page before the data loads?
I'm using datatables.net#1.10.12.
If I defer the call to init instead of preInit then the correct page number is highlighted, but the data is still from the first page. If I add a 0ms delay on top of that (as below), it does work, but causes a 2nd data fetch + draw.
if(tableState.page) {
api.on('init', ev => {
setTimeout(() => {
api.page(tableState.page).draw('page');
}, 0);
});
}
How can I set the page without incurring a 2nd ajax request?
You can use displayStart option to define the starting point for data display when using DataTables with pagination as recommended by the author of jQuery DataTables.
It works correctly with table in server-side processing mode and only 1 Ajax request is performed.
var table = $("#example").DataTable({
"processing": true,
"serverSide": true,
"ajax": "/test",
"displayStart": 200
});
From the documentation:
Note that this parameter is the number of records (counting from 0), rather than the page number, so if you have 10 records per page and want to start on the third page, it should be 20 rather than 2 or 3.
This doesn't (directly) answer the question of how to set the page in the preInit, but it solves my problem.
We can use the stateLoadCallback to load the state (including page) from the history API instead of using localStorage as the default implementation does (which will remember the state even when navigating away and then back again).
$.extend(true, $.fn.dataTable.defaults, {
stateSave: true,
stateSaveCallback: (settings, data) => {
let tableId = settings.nTable.id;
if(!tableId) {
// console.warn(`DataTable is missing an ID; cannot save its state`);
return;
}
history.replaceState(_.set(['datatables', tableId], data, history.state), '');
},
stateLoadCallback: settings => {
let tableId = settings.nTable.id;
if(!tableId) {
console.warn(`DataTable is missing an ID; cannot load its state`);
return;
}
return _.get(['datatables', tableId], history.state) || null;
}
});

Can I use Ext's loader to load non-ext scripts/object dynamically?

In my ExtJS 4.0.7 app I have some 3rd party javascripts that I need to dynamically load to render certain panel contents (some fancy charting/visualization widgets).
I run in to the age-old problem that the script doesn't finish loading before I try to use it. I thought ExtJS might have an elegant solution for this (much like the class loader: Ext.Loader).
I've looked at both Ext.Loader and Ext.ComponentLoader, but neither seem to provide what I'm looking for. Do I have to just "roll my own" and setup a timer to wait for a marker variable to exist?
Here's an example of how it's done in ExtJS 4.1.x:
Ext.Loader.loadScript({
url: '...', // URL of script
scope: this, // scope of callbacks
onLoad: function() { // callback fn when script is loaded
// ...
},
onError: function() { // callback fn if load fails
// ...
}
});
I've looked at both Ext.Loader and Ext.ComponentLoader, but neither
seem to provide what I'm looking for
Really looks like it's true. The only thing that can help you here, I think, is Loader's injectScriptElement method (which, however, is private):
var onError = function() {
// run this code on error
};
var onLoad = function() {
// run this code when script is loaded
};
Ext.Loader.injectScriptElement('/path/to/file.js', onLoad, onError);
Seems like this method would do what you want (here is example). But the only problem is that , ... you know, the method is marked as private.
This is exactly what newest Ext.Loader.loadScript from Ext.4-1 can be used for.
See http://docs.sencha.com/ext-js/4-1/#!/api/Ext.Loader-method-loadScript
For all you googlers out there, I ended up rolling my own by borrowing some Ext code:
var injectScriptElement = function(id, url, onLoad, onError, scope) {
var script = document.createElement('script'),
documentHead = typeof document !== 'undefined' && (document.head || document.getElementsByTagName('head')[0]),
cleanupScriptElement = function(script) {
script.id = id;
script.onload = null;
script.onreadystatechange = null;
script.onerror = null;
return this;
},
onLoadFn = function() {
cleanupScriptElement(script);
onLoad.call(scope);
},
onErrorFn = function() {
cleanupScriptElement(script);
onError.call(scope);
};
// if the script is already loaded, don't load it again
if (document.getElementById(id) !== null) {
onLoadFn();
return;
}
script.type = 'text/javascript';
script.src = url;
script.onload = onLoadFn;
script.onerror = onErrorFn;
script.onreadystatechange = function() {
if (this.readyState === 'loaded' || this.readyState === 'complete') {
onLoadFn();
}
};
documentHead.appendChild(script);
return script;
}
var error = function() {
console.log('error occurred');
}
var init = function() {
console.log('should not get run till the script is fully loaded');
}
injectScriptElement('myScriptElem', 'http://www.example.com/script.js', init, error, this);
From looking at the source it seems to me that you could do it in a bit of a hackish way. Try using Ext.Loader.setPath() to map a bogus namespace to your third party javascript files, and then use Ext.Loader.require() to try to load them. It doesn't look like ExtJS actually checks if required class is defined in the file included.