I'm trying to add some functions in the POS buttons, specifically the button that shows up like "Validate". To test if the guide in this link https://odoo-development.readthedocs.io/en/latest/dev/pos/gui.html works, I'm just adding a console.log like the following:
odoo.define('my_module.js_file', function (require) {
"use strict";
var screens = require('point_of_sale.screens');
screens.PaymentScreenWidget.include({
init: function(parent, options) {
this._super(parent, options);
//My console log message
console.log('Hello world!')
this.pos.on('updateDebtHistory', function(partner_ids){
this.update_debt_history(partner_ids);
}, this);
},
});
But the message only shows up once when the POS ends loading the data and not when I push the button. What am I doing wrong here?
To add your code to the Validate button you will need to modify the payment screen widget via the include method (You already did that).
If you inspect the button from the browser you will find that it has a class next which is used to bind an event handler to the click JavaScript event.
Example:
var screens = require('point_of_sale.screens');
var PaymentScreenWidget = screens.PaymentScreenWidget;
PaymentScreenWidget.include({
validate_order: function(force_validation) {
console.log('Hello world!');
this._super(force_validation);
},
});
Related
I have included some functionionality for ReceiptScreenWidget(using _super()) in my custom module, so that i can include some more logic in that function. But it is executing only after page load.
How to make it work along with the actual function.
Here is the code:
odoo.define('extended.screens', function (require) {
var screens = require('point_of_sale.screens');
screens.ReceiptScreenWidget.include({
show: function() {
this._super();
console.log("inside show custom>>>");
}
});
})
Now the "inside show custom>>>" message is printing in console only after page load.
I have html grid table consisting of comment link in each row.Clicking on any one opens a bootstrap modal with textbox and save button.So I wrote a library consisting of functions related to that comment system.Below is basic code.
HTML :
<td><a class="addComment" data-notedate="somevalue" data-toggle='modal' href='#addnotesdiv' data-oprid="somevalue" data-soid="somevalue" data-type="1"><i class="fa fa-comments-o fa-2"></i></a></td> ..... n
JS :
var Inventory={};
Inventory.notes={
defaults:{
type:'1',
soid:0,
operator_id:0,
date:'',
target:'div#addnotesdiv',
},
init:function()
{
var self=this;
$('div#addnotesdiv').on('show.bs.modal',function(e){
self.getandsetdefaults(e);
self.setmodalelements(e);
self.getNotes();
self.addnote();
self.activaterefresh();
});
},
getandsetdefaults:function(e)
{
this.defaults.soid = $(e.relatedTarget).data('soid');
this.defaults.operator_id=$(e.relatedTarget).data('oprid');
this.defaults.type=$(e.relatedTarget).data('type');
this.defaults.date=$(e.relatedTarget).data('notedate');
},
setmodalelements:function(e)
{
$(e.currentTarget).find('#notesthread').empty();
$(e.currentTarget).find('input#inpnotesoid').val(this.defaults.soid);
$(e.currentTarget).find('input#inpnoteoprid').val(this.defaults.operator_id);
$(e.currentTarget).find('input#inpnotetype').val(this.defaults.type);
},
addnote:function()
{
var self=this;
$('button#btnaddnote').on('click',function(){
var message=$(self.defaults.target).find('textarea#addnotemsg').val();
var soid=$(self.defaults.target).find('input[type=hidden][id=inpnotesoid]').val();
var note_date=$(self.defaults.target).find('input#addnotedate').val();
var oprid=$(self.defaults.target).find('input[type=hidden][id=inpnoteoprid]').val();
var type=$(self.defaults.target).find('input[type=hidden][id=inpnotetype]').val();
if(message=="" || soid=="" || note_date=="")
{
alert("Fill all details");
return;
}
var savenote=$.post(HOST+'notes/save',{message:message,soid:soid,note_date:note_date,type:type,operator_id:oprid});
savenote.done(function(res){
res=$.parseJSON(res);
if(res.status && res.error){
alert(res.message);
return;
}
if(res.status && res.type)
{
$('div#addnotemsg').showSuccess("Done").done(function(){self.getNotes();});
$('div#addnotesdiv').find('textarea#addnotemsg').val('');
}
else
{
$('div#addnotemsg').showFailure("Error");
}
});
});
},
getNotes:function()
{
$('button#btnrefreshcomments i').addClass('glyphicon-refresh-animate');
var getnotes=$.getJSON(HOST,{soid:this.defaults.soid,type:this.defaults.type,note_date:this.defaults.date,operator_id:this.defaults.operator_id});
getnotes.done(function(res){
if(res.status && res.data.length)
{
--somecode---
}
});
},
activaterefresh:function(){
var self=this;
$(document).on('click','#btnrefreshcomments',function(){
$('#notesthread').empty();
self.getNotes();
return false;
});
return false;
}
}
In Order to activate this functionality on that page I wrote
Inventory.notes.init();
Above code works perfectly when I open modal once but when I close that same modal and open it again but by clicking on different link all events are fired twice,thrice and so on.Number of events fired is equal to number of times modal opened on that page.
Is there any thing wrong in code Or any other way to perform this same task.
I know this is not a plugin all I wanted was to store all functionality related to comment system under one roof as library.
every time you open the modal box, it triggered show.bs.modal event, then all methods was exec again, including the event bindings. e.g. event bind in [addnote]
$('div#addnotesdiv').on('show.bs.modal',function(e){
self.getandsetdefaults(e);
self.setmodalelements(e);
self.getNotes();
self.addnote();
self.activaterefresh();
});
Problem was whenever modal was shown getNotes,addnote,activatereferesh functions were called but when the modal was reopened again this functions are called again so thats twice and so on.Putting it in more simpler way is there were multiple listeners attached to single element without destroying previous one because my init function was called many times.
At last there were two solutions in both I need to unbind events or attach them only once.Got idea from here
1) Modified Init function with below code and added one unbind listener function
init:function(selector)
{
var self=this;
$(self.defaults.target).on('show.bs.modal',function(e){
self.getandsetdefaults(e);
self.setmodalelements(e);
self.getNotes();
self.addnote();
self.activaterefresh();
});
$(self.defaults.target).on('hide.bs.modal',function(e){
self.unbindlistners();
});
}
unbindlistners:function()
{
var self=this;
$('#btnrefreshcomments').unbind('click');
$('button#btnaddnote').unbind('click');
return false;
}
}
2) Place event binding function outside show.bs.modal
init:function(selector)
{
var self=this;
$(self.defaults.target).on('show.bs.modal',function(e){
self.getandsetdefaults(e);
self.setmodalelements(e);
});
self.getNotes();
self.addnote();
self.activaterefresh();
}
There is small catch in second solution that is when first time my DOM is loaded function getNotes function is called with default values.
I have a single shared jQuery function that checks a RadioButton selection: if 1 is selected, it hides a span, otherwise it shows it.
This shared function is called both on startup and on Change, because on startup, it needs to do the same thing. The startup works, but the onChange reference does NOT work:
JS_OBJ = {
toggleTier : function() {
if ($('input[name="tier"]:checked').val() == 'Y_YES')
{
$('#tierSpan').hide();
}
else
{
$('#tierSpan').show();
}
},
// this is called from document.onReady - it comes here, OK
onReady : function() {
// on startup, toggle Tier - works OK
this.toggleTier();
// Also link the radio button Change to this shared function
$('input[name="tier"]:radio').change(function () {
alert('About to enter toggle...');
// NEVER COMES HERE - Object doesn't support this property or method
this.toggleTier();
});
}
};
the this is changing value as it is passing thru the different zones. when it is first instantiated, it has a good value, but the radiobutton:change has a different this
I was able to change it get it to work:
$('input[name="tier"]:radio').change(function () {
alert('About to enter toggle...');
self; //closure
toggleTier();
});
see this: What underlies this JavaScript idiom: var self = this?
Inside the change event, this does not refer to the current JS_OBJ, it refers to the current event target in stead. You want to explicitly save your reference to this, so you can use it inside the event.
Example:
onReady : function() {
var me = this;
me.toggleTier();
// Also link the radio button Change to this shared function
$('input[name="tier"]:radio').change(function () {
me.toggleTier();
});
}
I'm having some problems with users clicking buttons multiple times and I want to suppress/ignore clicks while the first Ajax request does its thing. For example if a user wants add items to their shopping cart, they click the add button. If they click the add button multiple times, it throws a PK violation because its trying to insert duplicate items into a cart.
So there are some possible solutions mentioned here: Prevent a double click on a button with knockout.js
and here: How to prevent a double-click using jQuery?
However, I'm wondering if the approach below is another possible solution. Currently I use a transparent "Saving" div that covers the entire screen to try to prevent click throughs, but still some people manage to get a double click in. I'm assuming because they can click faster than the div can render. To combat this, I'm trying to put a lock on the Ajax call using a global variable.
The Button
<span style="SomeStyles">Add</span>
Knockout executes this script on button click
vmProductsIndex.AddItemToCart = function (item) {
if (!app.ajaxService.inCriticalSection()) {
app.ajaxService.criticalSection(true);
app.ajaxService.ajaxPostJson("#Url.Action("AddItemToCart", "Products")",
ko.mapping.toJSON(item),
function (result) {
ko.mapping.fromJS(result, vmProductsIndex.CartSummary);
item.InCart(true);
item.QuantityOriginal(item.Quantity());
},
function (result) {
$("#error-modal").modal();
},
vmProductsIndex.ModalErrors);
app.ajaxService.criticalSection(false);
}
}
That calls this script
(function (app) {
"use strict";
var criticalSectionInd = false;
app.ajaxService = (function () {
var ajaxPostJson = function (method, jsonIn, callback, errorCallback, errorArray) {
//Add the item to the cart
}
};
var inCriticalSection = function () {
if (criticalSectionInd)
return true;
else
return false;
};
var criticalSection = function (flag) {
criticalSectionInd = flag;
};
// returns the app.ajaxService object with these functions defined
return {
ajaxPostJson: ajaxPostJson,
ajaxGetJson: ajaxGetJson,
setAntiForgeryTokenData: setAntiForgeryTokenData,
inCriticalSection: inCriticalSection,
criticalSection: criticalSection
};
})();
}(app));
The problem is still I can spam click the button and get the primary key violation. I don't know if this approach is just flawed and Knockout isn't quick enough to update the button's visible binding before the first Ajax call finishes or if every time they click the button a new instance of the criticalSectionInd is created and not truely acting as a global variable.
If I'm going about it wrong I'll use the approaches mentioned in the other posts, its just this approach seems simpler to implement without having to refactor all of my buttons to use the jQuery One() feature.
You should set app.ajaxService.criticalSection(false); in the callback methods.
right now you are executing this line of code at the end of your if clause and not inside of the success or error callback, so it gets executed before your ajax call is finished.
vmProductsIndex.AddItemToCart = function (item) {
if (!app.ajaxService.inCriticalSection()) {
app.ajaxService.criticalSection(true);
app.ajaxService.ajaxPostJson("#Url.Action("AddItemToCart", "Products")",
ko.mapping.toJSON(item),
function (result) {
ko.mapping.fromJS(result, vmProductsIndex.CartSummary);
item.InCart(true);
item.QuantityOriginal(item.Quantity());
app.ajaxService.criticalSection(false);
},
function (result) {
$("#error-modal").modal();
app.ajaxService.criticalSection(false);
},
vmProductsIndex.ModalErrors);
}
}
you could use the "disable" binding from knockout to prevent the click binding of the anchor tag to be fired.
here is a little snippet for that. just set a flag to true when your action starts and set it to false again when execution is finished. in the meantime, the disable binding prevents the user from executing the click function.
function viewModel(){
var self = this;
self.disableAnchor = ko.observable(false);
self.randomList = ko.observableArray();
self.loading = ko.observable(false);
self.doWork = function(){
if(self.loading()) return;
self.loading(true);
setTimeout(function(){
self.randomList.push("Item " + (self.randomList().length + 1));
self.loading(false);
}, 1000);
}
}
ko.applyBindings(new viewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.0.0/knockout-min.js"></script>
Click me
<br />
<div data-bind="visible: loading">...Loading...</div>
<br />
<div data-bind="foreach: randomList">
<div data-bind="text: $data"></div>
</div>
There are menu button ("clients"), tree panel with clients list (sorted by name) and viewer with selected client details. There is also selectionchange action..
My task - on button click switch to client view and select and load details for first client every time button has been clicked. My problem - store is not loaded, how waiting until ext js will autoload data to the store?
my controller code:
me.control({
'#nav-client': {
click: me.onNavClientClick
},
...
'clientlist': {
// load: me.selectClient,
selectionchange: me.showClient
}
});
onNavClientClick: function(view, records) {
var me = this,
content = Ext.getCmp("app-content");
content.removeAll();
content.insert(0, [{xtype: 'clientcomplex'}]);
var first = me.getClientsStore().first();
if (first) {
Ext.getCmp("clientList").getSelectionModel().select(me.getClientsListStore().getNodeById(first.get('clientId')));
}
},
...
Two main questions:
is it good solution in my case? (to select first client in tree panel)
var first = me.getClientsStore().first();
// i use another store to get first record because of i dont know how to get first record (on root level) in TreeStore
...
Ext.getCmp("clientList").getSelectionModel().select(me.getClientsListStore().getNodeById(first.get('clientId')));
i know this code works ok in case of "load: me.selectClient," (but only once),
if i place this code on button click - i see error
Uncaught TypeError: Cannot read property 'id' of undefined
because of me.getClientsListStore() is not loaded.. so how to check loading status of this store and wait some until this store will be completely autoloaded?..
Thank you!
You can listen the store 'load' event. Like this:
...
onNavClientClick: function(view, records) {
var me = this;
// if the store isn't loaded, call load method and defer the 'client view' creation
if (me.getClientsStore.getCount() <= 0) {
me.getClientsStore.on('load', me.onClientsStoreLoad, me, { single : true});
me.getClientsStore.load();
}
else {
me.onClientsStoreLoad();
}
},
onClientsStoreLoad : function () {
var me = this,
content = Ext.getCmp("app-content");
content.removeAll();
content.insert(0, [{xtype: 'clientcomplex'}]);
var first = me.getClientsStore().first();
if (first) {
Ext.getCmp("clientList").getSelectionModel().select(me.getClientsListStore().getNodeById(first.get('clientId')));
}
},
...