How can I extend this function (edit button) on JavaScript within Odoo? - javascript

I would like to create a module which controls edit button on certain conditions. I tried the following code in js but I got no effect. So I would like to know how to extend a function in js.
formView.include({
init:function(){
var edits = new Model('sale.order');
edits.query(['validity_date']);
console.log(validity_date)
},
on_button_edit: function(){
this._super();

You can write something like this in the js file. I wrote some examples to help you.
openerp.custom_edit_button = function (instance) {
var _t = instance.web._t;
instance.web.FormView.include({
init: function() {
console.log('JS loaded')
this._super.apply(this, arguments);
},
to_edit_mode: function(){
// examples of useful methods
var field_values = this.get_fields_values();
var ids = this.get_selected_ids();
var id = field_values['id'];
var date = field_values['date'];
var model = this.model;
console.log(field_values)
console.log(ids)
console.log(id)
console.log(model)
console.log(date)
console.log(Date.today())
date_to_compare = new Date(date);
console.log(date_to_compare)
if(date_to_compare < Date.today()){
error = this.error;
var QWeb = instance.web.qweb;
var dialog = new instance.web.Dialog(this, {
title: _t("Set new expiry date"),
width: '30%',
size: 'medium',
/*dialogClass: 'oe_act_window',*/
buttons: [
{ text: _t("OK"), click: function() { self.set_new_expiry_date(); }},
{ text: _t("Close"), click: function() { dialog.close(); return; }
},
],
}, QWeb.render('custom_edit_button.expiry_date_form', {error: error})).open();
}
this._super();
}
});
}
So, if the expiry date is in the past, the form is going to appear to change it. You must define the method set_new_expiry_date as well. And on the other hand you must add this template, or something similar to show the form. Add the file in the qweb section of your __openerp__.py
<templates xml:space="preserve">
<div t-name="custom_edit_button.expiry_date_form" >
<div class="form-group">
<label for="date" class="control-label">New expiry date:</label>
<input name="date" class="form-control"/>
</div>
</div>
</templates>
Notice that the name of my module is custom_edit_button in the example

Related

Odoo Point Of Sale : Add button to print a different receipt format? Odoo 13

I added another button to print receipt.So, one that prints the default receipt when clicked and another that prints a different template. Here is my code which print the same receipt , my question is how to modify for the second button another template similar to the default with some modifications.
Any help please? Thanks.
odoo.define('pos_print.pos_report', function (require) { "use
strict";
var screens = require('point_of_sale.screens'); console.log("screens
: " + screens)
var gui = require('point_of_sale.gui'); var core =
require('web.core'); var _t = core._t;
screens.ReceiptScreenWidget.include({
renderElement: function() {
var self = this;
this._super();
this.$('.next').click(function(){
if (!self._locked) {
self.click_next();
}
});
this.$('.back').click(function(){
if (!self._locked) {
self.click_back();
}
});
this.$('.button.order-print').click(function(){
if (!self._locked) {
self.print();
}
});
},
}); });
<templates id="point_of_sale.template" xml:space="preserve">
<t t-extend="ReceiptScreenWidget">
<t t-jquery=".button.print" t-operation="after">
<div class="button order-print">
<i class='fa fa-print'></i>
Print POS Order
</div>
</t> </t> </templates>
you need to create new print method in ReceiptScreenWidget:
renderElement: function() {
var self = this;
this._super();
this.$('.next').click(function(){
if (!self._locked) {
self.click_next();
}
});
this.$('.back').click(function(){
if (!self._locked) {
self.click_back();
}
});
this.$('.button.print').click(function(){
if (!self._locked) {
self.print();
}
});
this.$('.button.order-print').click(function(){
// To print new format
if (!self._locked) {
self.myPrint();
}
});
},
myPrint: function () {
// MyNewOrderReceipt is your new receipt template
// YOUR_PARAMS is parameters required in your template
var receipt = QWeb.render('MyNewOrderReceipt', YOUR_PARAMS);
this.pos.proxy.printer.print_receipt(receipt);
this.pos.get_order()._printed = true;
this.lock_screen(false);
},
note: this is assuming you are aiming to print the new template directly to printer without preview, if you want to change the receipt screen you need to override ActionButtonWidget.

How to call a custom JS file in Odoo 8?

I have a model called student. I also have form view, tree view for the student model. What I want to do is call my custom javascript file only when the form view of the student model is loaded. Is it possible? How to achieve this? Thanks.
What I tried is .....
openerp.student= function (instance) {
instance.web.FormView.include({
load_form: function(data) {
var self = this;
if (data.model === "student") {
altert('HELLO');
console.log('BLAH BLAH');
}
return this._super(data);
},
});
};
You can override the load_form method of FormView.
openerp.module_name= function (instance) {
instance.web.FormView.include({
load_form: function(data) {
var self = this;
if (data.model === "student") {
// Your custom code
}
return this._super(data);
},
});
};
To add the above code check this link inherit-or-override-js
It is possible to add a new view mode by extending FormFiew as Odoo did with account_move_line_quickadd.
openerp.your_module_name = function (instance) {
var _t = instance.web._t,
_lt = instance.web._lt;
var QWeb = instance.web.qweb;
instance.web.your_module_name = instance.web.your_module_name || {};
instance.web.views.add('student_form', 'instance.web.StudentFormView');
instance.web.StudentFormView = instance.web.FormView.extend({
load_form: function(data) {
var self = this;
// Add your custom code here
return this._super(data);
},
});
};
You just need to add the new mode to window action.
<record id="student_action" model="ir.actions.act_window">
<field name="name">student.action</field>
<field name="res_model">student</field>
<field name="view_mode">student_form,tree</field>
...

Mapping a nested object as an observable from a complex JSON using the create callback

I've got a complex object in a JSON format. I'm using Knockout Mapping, customizing the create callback, and trying to make sure that every object that should be an observable - would actually be mapped as such.
The following code is an example of what I've got:
It enables the user to add cartItems, save them (as a JSON), empty the cart, and then load the saved items.
The loading part fails: It doesn't display the loaded option (i.e., the loaded cartItemName). I guess it's related to some mismatch between the objects in the options list and the object bounded as the cartItemName (see this post), but I can't figure it out.
Code (fiddle):
var cartItemsAsJson = "";
var handlerVM = function () {
var self = this;
self.cartItems = ko.observableArray([]);
self.availableProducts = ko.observableArray([]);
self.language = ko.observable();
self.init = function () {
self.initProducts();
self.language("english");
}
self.initProducts = function () {
self.availableProducts.push(
new productVM("Shelf", ['White', 'Brown']),
new productVM("Door", ['Green', 'Blue', 'Pink']),
new productVM("Window", ['Red', 'Orange'])
);
}
self.getProducts = function () {
return self.availableProducts;
}
self.getProductName = function (product) {
if (product) {
return self.language() == "english" ?
product.productName().english : product.productName().french;
}
}
self.getProductValue = function (selectedProduct) {
// if not caption
if (selectedProduct) {
var matched = ko.utils.arrayFirst(self.availableProducts(), function (product) {
return product.productName().english == selectedProduct.productName().english;
});
return matched;
}
}
self.getProductColours = function (selectedProduct) {
selectedProduct = selectedProduct();
if (selectedProduct) {
return selectedProduct.availableColours();
}
}
self.addCartItem = function () {
self.cartItems.push(new cartItemVM());
}
self.emptyCart = function () {
self.cartItems([]);
}
self.saveCart = function () {
cartItemsAsJson = ko.toJSON(self.cartItems);
console.log(cartItemsAsJson);
}
self.loadCart = function () {
var loadedCartItems = ko.mapping.fromJSON(cartItemsAsJson, {
create: function(options) {
return new cartItemVM(options.data);
}
});
self.cartItems(loadedCartItems());
}
}
var productVM = function (name, availableColours, data) {
var self = this;
self.productName = ko.observable({ english: name, french: name + "eux" });
self.availableColours = ko.observableArray(availableColours);
}
var cartItemVM = function (data) {
var self = this;
self.cartItemName = data ?
ko.observable(ko.mapping.fromJS(data.cartItemName)) :
ko.observable();
self.cartItemColour = data ?
ko.observable(data.cartItemColour) :
ko.observable();
}
var handler = new handlerVM();
handler.init();
ko.applyBindings(handler);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://rawgit.com/SteveSanderson/knockout.mapping/master/build/output/knockout.mapping-latest.js
"></script>
<div>
<div data-bind="foreach: cartItems">
<div>
<select data-bind="options: $parent.getProducts(),
optionsText: function (item) { return $parent.getProductName(item); },
optionsValue: function (item) { return $parent.getProductValue(item); },
optionsCaption: 'Choose a product',
value: cartItemName"
>
</select>
</div>
<div>
<select data-bind="options: $parent.getProductColours(cartItemName),
optionsText: $data,
optionsCaption: 'Choose a colour',
value: cartItemColour,
visible: cartItemName() != undefined"
>
</select>
</div>
</div>
<div>
<button data-bind="text: 'add cart item', click: addCartItem" />
<button data-bind="text: 'empty cart', click: emptyCart" />
<button data-bind="text: 'save cart', click: saveCart" />
<button data-bind="text: 'load cart', click: loadCart" />
</div>
</div>
What needs to be changed to fix it?
P.S.: I've got another piece of code (see it here) that demonstrates a persistance of the selected value even after changing the options - though there optionsValue is a simple string, while here it's an object.
EDIT:
I figured out the problem: the call ko.mapping.fromJS(data.cartItemName) creates a new productVM object, which is not one of the objects inside availableProducts array. As a result, none of the options corresponds to the productVM contained in the loaded cartItemName, so Knockout thereby clears the value altogether and passes undefined.
But the question remains: how can this be fixed?
In the transition from ViewModel -> plain object -> ViewModel you loose the relation between the products in your cart and the ones in your handlerVM.
A common solution is to, when loading a plain object, manually search for the existing viewmodels and reference those instead. I.e.:
We create a new cartItemVM from the plain object
Inside its cartItemName, there's an object that does not exist in handlerVM.
We look in handlerVM for a product that resembles this object, and replace the object by the one we find.
In code, inside loadCart, before setting the new viewmodels:
loadedCartItems().forEach(
ci => {
// Find out which product we have:
const newProduct = ci.cartItemName().productName;
const linkedProduct = self.availableProducts()
.find(p => p.productName().english === newProduct.english());
// Replace the newProduct by the one that is in `handlerVM`
ci.cartItemName(linkedProduct)
}
)
Fiddle: https://jsfiddle.net/7z6010jz/
As you can see, the equality comparison is kind of ugly. We look for the english product name and use it to determine the match. You can also see there's a difference in what is observable and what isn't.
My advice would be to use unique id properties for your product, and start using those. You can create a simpler optionsValue binding and matching new and old values happens automatically. If you like, I can show you an example of this refactor as well. Let me know if that'd help.

Properly using the data-bind visible property

I have a view that displays data from a foreach loop in different categories depending on the type. Each category will contain a number of users - I created an object that will check to see if the number of users in a category are more than 10 then the text for the visible bind will show. And for the category that doesn't have more than 10 it will not show the text.
My question: if the first category doesn't have 10 it won't show text does that mean that it won't also show text for the remaining categories?
Help with: the visible binding is not working even though a category would contain more than 10 and not sure why.
Here is my JSFiddle: http://jsfiddle.net/xNdJk/1/
JavaScript:
var userViewModel = function (data) {
var _self = this;
_self.Name = ko.observable(data.Name);
_self.Letter = ko.observable(data.Letter);
_self.ShowLetter = ko.computed(function () {
return (roleViewModel.UserCount > 13);
});
};
var typeViewModel = function (data) {
var _self = this;
_self.ContentType = ko.observable(data.ContentType);
_self.Name = ko.observable(data.Name);
_self.Rank = ko.observable(data.Rank);
_self.UserCount = ko.observable(data.UserCount);
_self.Users = ko.observableArray([]);
};
View:
<div class="collapse in" data-bind="template: { name: 'list', foreach: $data.Users }">
</div>
<div id="letter" data-bind="visible:ShowLetter, text: Letter"></div>
You are mixing classes and instances, you have created a secondModel class but you never instance it, here is a working example
http://jsfiddle.net/xNdJk/2/
var viewModel = function(){
this.Letter = ko.observable('Hello, World!');
this.secondModel = new secondModel();
this.shouldShowMessage = ko.computed(function() {
return (this.secondModel.UserCount() > 13);
}, this);
}
var secondModel = function(){
var self = this;
self.UserCount = ko.observable(153);
}

knockout.js - deferred databinding for modal?

I am using knockout.js to display a list of employees. I have a single hidden modal markup on the page. When the "details" button for a single employees is clicked, I want to data-bind that employee to the modal popup. I am using the ko.applyBindings(employee, element) but the problem is when the page loads, it is expecting the modal to start off as bound to something.
So I'm wondering, is there a trick/strategy to do a late/deferred databinding? I looked into virtual bindings but the documentation was not helpful enough.
Thanks!
I would like to propose a different way to work with modals in MVVVM. In MVVM, the ViewModel is data for the View, and the View is responsible for the UI. If we examine this proposal:
this.detailedEmployee = ko.observable({}),
var self = this;
this.showDetails = function(employee){
self.detailedEmployee(employee);
$("#dialog").dialog("show"); //or however your dialog works
}
I strongly agree with this.detailedEmployee = ko.observable({}), but I am in strong disagreement with this line: $("#dialog").dialog("show");. This code is placed in the ViewModel and shows the modal window, wherein fact it is View's responsibility, so we screw-up the MVVM approach. I would say this piece of code will solve your current task but it could cause lots of problems in future.
When closing the popup, you should set detailedEmployee to undefined to have your main ViewModel in a consistent state.
When closing the popup, you might want to have validation and the possibility to discard the close operation when you want to use another modal's component in the application
As for me, these points are very critical, so I would like to propose a different way. If we "forget" that you need to display data in popup, binding with could solve your issue.
this.detailedEmployee = ko.observable(undefined);
var self = this;
this.showDetails = function(employee){
self.detailedEmployee(employee);
}
<div data-bind="with: detailedEmployee">
Data to show
</div>
As you can see, your ViewModel don't know anything about how data should be shown. It knows only about data that should be shown. The with binding will display content only when detailedEmployee is defined. Next, we should find a binding similar to with but one that will display content in the popup. Let's give it the name modal. Its code is like this:
ko.bindingHandlers['modal'] = {
init: function(element) {
$(element).modal('init');
return ko.bindingHandlers['with'].init.apply(this, arguments);
},
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
var returnValue = ko.bindingHandlers['with'].update.apply(this, arguments);
if (value) {
$(element).modal('show');
} else {
$(element).modal('hide');
}
return returnValue;
}
};
As you can see, it uses the with plugin internally, and shows or hide a popup depending on value passed to binding. If it is defined - 'show'. If not - 'hide'. Its usage will be the as with:
<div data-bind="modal: detailedEmployee">
Data to show
</div>
The only thing you need to do is to use your favorite modals plugin. I prepared an example with the Twitter Bootstrap popup component: http://jsfiddle.net/euvNr/embedded/result/
In this example, custom binding is a bit more powerful; you could subscribe the onBeforeClose event and cancel this event if needed. Hope this helps.
The JSFiddle linked to in the answer provided by #Romanych didn't seem to work anymore.
So, I built my own example (based upon his original fiddle) with full CRUD support and basic validation using Bootstrap 3 and the Bootstrap Modal library: https://jsfiddle.net/BitWiseGuy/4u5egybp/
Custom Binding Handlers
ko.bindingHandlers['modal'] = {
init: function(element, valueAccessor, allBindingsAccessor) {
var allBindings = allBindingsAccessor();
var $element = $(element);
$element.addClass('hide modal');
if (allBindings.modalOptions && allBindings.modalOptions.beforeClose) {
$element.on('hide', function() {
var value = ko.utils.unwrapObservable(valueAccessor());
return allBindings.modalOptions.beforeClose(value);
});
}
},
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
if (value) {
$(element).removeClass('hide').modal('show');
} else {
$(element).modal('hide');
}
}
};
Example Usage
The View
<div data-bind="modal: UserBeingEdited" class="fade" role="dialog" tabindex="-1">
<form data-bind="submit: $root.SaveUser">
<div class="modal-header">
<a class="close" data-dismiss="modal">×</a>
<h3>User Details</h3>
</div>
<div class="modal-body">
<div class="form-group">
<label for="NameInput">Name</label>
<input type="text" class="form-control" id="NameInput" placeholder="User's name"
data-bind="value: UserBeingEdited() && UserBeingEdited().Name, valueUpdate: 'afterkeydown'">
</div>
<div class="form-group">
<label for="AgeInput">Age</label>
<input type="text" class="form-control" id="AgeInput" placeholder="User's age"
data-bind="value: UserBeingEdited() && UserBeingEdited().Age, valueUpdate: 'afterkeydown'">
</div>
<!-- ko if: ValidationErrors() && ValidationErrors().length > 0 -->
<div class="alert alert-danger" style="margin: 20px 0 0">
Please correct the following errors:
<ul data-bind="foreach: { data: ValidationErrors, as: 'errorMessage' }">
<li data-bind="text: errorMessage"></li>
</ul>
</div>
<!-- /ko -->
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal" class="btn btn-default">Cancel</button>
<button type="submit" class="btn btn-primary">Save Changes</button>
</div>
</form>
</div>
The ViewModel
/* ViewModel for the individual records in our collection. */
var User = function(name, age) {
var self = this;
self.Name = ko.observable(ko.utils.unwrapObservable(name));
self.Age = ko.observable(ko.utils.unwrapObservable(age));
}
/* The page's main ViewModel. */
var ViewModel = function() {
var self = this;
self.Users = ko.observableArray();
self.ValidationErrors = ko.observableArray([]);
// Logic to ensure that user being edited is in a valid state
self.ValidateUser = function(user) {
if (!user) {
return false;
}
var currentUser = ko.utils.unwrapObservable(user);
var currentName = ko.utils.unwrapObservable(currentUser.Name);
var currentAge = ko.utils.unwrapObservable(currentUser.Age);
self.ValidationErrors.removeAll(); // Clear out any previous errors
if (!currentName)
self.ValidationErrors.push("The user's name is required.");
if (!currentAge) {
self.ValidationErrors.push("Please enter the user's age.");
} else { // Just some arbitrary checks here...
if (Number(currentAge) == currentAge && currentAge % 1 === 0) { // is a whole number
if (currentAge < 2) {
self.ValidationErrors.push("The user's age must be 2 or greater.");
} else if (currentAge > 99) {
self.ValidationErrors.push("The user's age must be 99 or less.");
}
} else {
self.ValidationErrors.push("Please enter a valid whole number for the user's age.");
}
}
return self.ValidationErrors().length <= 0;
};
// The instance of the user currently being edited.
self.UserBeingEdited = ko.observable();
// Used to keep a reference back to the original user record being edited
self.OriginalUserInstance = ko.observable();
self.AddNewUser = function() {
// Load up a new user instance to be edited
self.UserBeingEdited(new User());
self.OriginalUserInstance(undefined);
};
self.EditUser = function(user) {
// Keep a copy of the original instance so we don't modify it's values in the editor
self.OriginalUserInstance(user);
// Copy the user data into a new instance for editing
self.UserBeingEdited(new User(user.Name, user.Age));
};
// Save the changes back to the original instance in the collection.
self.SaveUser = function() {
var updatedUser = ko.utils.unwrapObservable(self.UserBeingEdited);
if (!self.ValidateUser(updatedUser)) {
// Don't allow users to save users that aren't valid
return false;
}
var userName = ko.utils.unwrapObservable(updatedUser.Name);
var userAge = ko.utils.unwrapObservable(updatedUser.Age);
if (self.OriginalUserInstance() === undefined) {
// Adding a new user
self.Users.push(new User(userName, userAge));
} else {
// Updating an existing user
self.OriginalUserInstance().Name(userName);
self.OriginalUserInstance().Age(userAge);
}
// Clear out any reference to a user being edited
self.UserBeingEdited(undefined);
self.OriginalUserInstance(undefined);
}
// Remove the selected user from the collection
self.DeleteUser = function(user) {
if (!user) {
return falase;
}
var userName = ko.utils.unwrapObservable(ko.utils.unwrapObservable(user).Name);
// We could use another modal here to display a prettier dialog, but for the
// sake of simplicity, we're just using the browser's built-in functionality.
if (confirm('Are you sure that you want to delete ' + userName + '?')) {
// Find the index of the current user and remove them from the array
var index = self.Users.indexOf(user);
if (index > -1) {
self.Users.splice(index, 1);
}
}
};
}
Initializing Knockout with the View and the ViewModel
var viewModel = new ViewModel();
// Populate the ViewModel with some dummy data
for (var i = 1; i <= 10; i++) {
var letter = String.fromCharCode(i + 64);
var userName = 'User ' + letter;
var userAge = i * 2;
viewModel.Users.push(new User(userName, userAge));
}
// Let Knockout do its magic!
ko.applyBindings(viewModel);
I would create another observable that wraps the employee.
this.detailedEmployee = ko.observable({}),
var self = this;
this.showDetails = function(employee){
self.detailedEmployee(employee);
$("#dialog").dialog("show"); //or however your dialog works
}
Attach the click to showDetails. Then you can just call applyBindings on page load.

Categories