Odoo: JSON: Inherit view, hide edit button conditionally - javascript

I'm trying to hide the edit button in the bill of material form in Odoo, dependent on the state of a boolean.
I managed to remove the edit button permanent with the code below:
<xpath expr="//form[#string='Bill of Material']" position="attributes">
<attribute name="edit">false</attribute>
</xpath>
Now I tried to make it conditional with a boolean like this:
<xpath expr="//form[#string='Bill of Material']" position="attributes">
<attribute name="edit">true:realman==True;false:realman==False;</attribute>
</xpath>
This gives error:
SyntaxError: JSON.parse: unexpected non-whitespace character after
JSON data at line 1 column 5 of the JSON data
When I looked up the javascript file, I've found this is the code to handle the edit attribute:
/**
* Return whether the user can perform the action ('create', 'edit', 'delete') in this view.
* An action is disabled by setting the corresponding attribute in the view's main element,
* like: <form string="" create="false" edit="false" delete="false">
*/
is_action_enabled: function(action) {
var attrs = this.fields_view.arch.attrs;
return (action in attrs) ? JSON.parse(attrs[action]) : true;
},
I suppose I need to get false in that var attrs when the boolean realman in my form is False?
I've already tried to write it in curly brackets like the answer in this question: JSON.parse unexpected character error
That gave me errors too.
Why do I get this error and how can I fix this? Is this just a syntax error or are there more problems?

I successfully solved a similar issue by using field_view_get, but only if "realman" is passed into context
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
res = models.Model.fields_view_get(self, cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
realman = context.get('realman', True)
if not realman and view_type == 'form':
doc = etree.XML(res['arch'])
for t in doc.xpath("//form[#string='Bill of Material']"):
t.attrib['edit'] = 'false'
res['arch'] = etree.tostring(doc)
return res
if realman is instead a field and you need to enable/disable the edit button, then I'm afraid it is just not possible. AFAIK.

As Alessandro Ruffolo stated his solution doesn't work for buttons/actions depending on model's field. I have written solution in Javascript for that. It works in ODOO 10 (should in 9 also but I haven't tested it).
Example is checking on model's field "state". If it has value "noEditable" edit and delete buttons would hide. It isn't enough to overwrite is_action_enabled because ODOO is calling the method when there is no datarecord loaded yet. Therefore it's needed to check it again after methods do_show and reload.
// modify default form view for custom model my.custom.model
odoo.define('my.custom_model_form', function (require) {
"use strict";
var FormView = require('web.FormView');
FormView.include({
is_action_enabled: function(action) {
if (this.model == "my.custom.model" && this.datarecord && this.datarecord.state == "noEditable" &&
(action == 'delete' || action == 'edit')) {
// don't allow edit nor delete
return false;
}
// call default is_action_enabled method
return this._super.apply(this, arguments);
},
deleteItem: null,
deleteItemIdx: null,
deleteItemShown: true,
reinit_actions: function() {
// apply for my custom model only
if (this.model == "my.custom.model") {
// hide/show edit button
if (this.is_action_enabled('edit')) {
this.$buttons.find(".o_form_button_edit").show();
} else {
this.$buttons.find(".o_form_button_edit").hide();
}
// find delete item in sidebar's items
if (!this.deleteItem) {
// form view is adding it to "other"
if (this.sidebar && this.sidebar.items && this.sidebar.items.other) {
for (var i = 0; i < this.sidebar.items.other.length; i++) {
// on_button_delete is used as callback for delete button
// it's ugly way to find out which one is delete button, haven't found better way
if (this.sidebar.items.other[i].callback == this.on_button_delete) {
this.deleteItem = this.sidebar.items.other[i];
this.deleteItemIdx = i;
break;
}
}
}
}
// hide/show delete button
if (this.is_action_enabled('delete')) {
if (!this.deleteItemShown) {
this.deleteItemShown = true;
// add delete item to sidebar's items
this.sidebar.items.other.splice(this.deleteItemIdx, 0, this.deleteItem);
}
} else
if (this.deleteItemShown) {
this.deleteItemShown = false;
// remove delete item from sidebar's items
this.sidebar.items.other.splice(this.deleteItemIdx, 1);
}
}
},
reload: function() {
var self = this;
// run reinit_actions after reload finish
return this._super.apply(this, arguments).done(function() {
self.reinit_actions();
});
},
do_show: function() {
var self = this;
// run reinit_actions after do_show finish
return this._super.apply(this, arguments).done(function() {
self.reinit_actions();
});
}
});
});

I believe a solution with a computed field makes things simpler for these cases.
You can inherit the bill of material form and override it. Point the invisible condition to a computed field where you'll create your validation. Note that is necessary to add your computed field to the form, even though hiding it.
<xpath expr="//form[#string='Bill of Material']" position="attributes">
<attribute name="attrs">
{'invisible': [('show_bm_button', '=', False)]}
</attribute>
<field name="show_bm_button" invisible="1"/>
</xpath>
Then override the object in python and add the new computed field.
class ProductTemplate(models.Model):
_inherit = 'product.template'
show_bm_button = fields.Boolean(
compute='_compute_show_bm_button',
readonly=False, store=False
)
# #api.depends('realman') #Just as a sample, if you have this field in this Model
def _compute_show_bm_button(self):
for record in self:
realman = self._context.get('realman') or True
if realman:
record.show_bm_button = True
else:
record.show_bm_button = False

Related

AngularJS: Write inside input box via JS, Do not transfer the value in the JSON

Background: I have an external device (barcode reader) that sends information back to a tablet when the user scans something. I subscribe to that channel and I need the value to be inside the currently focused cell and write it there.
Bug: I can catch the subscription and write the value visually in the Input box, but it never reaches the JSON underneath.
I also tried $scope.$apply() but it did not change anything (maybe I used it wrong).
"Working" Plunker with the problem
$scope.randomClickOnStuff = function() {
// Here Randomely publish stuff with value so we can write it in specific field.
window.setTimeout(function() {
if (!$scope.stopMe) {
vm.objectOtSubscribeTo.publish(channelToUse, Date.now());
$scope.randomClickOnStuff();
} else {
// Stop the loop.
}
}, 1000);
};
var callbackCompleted = function(resultValue) {
// Important code Here
// Code to write in the input box here.
console.log(resultValue);
if (document.activeElement.localName == "input") {
// Option 1:
//--> Work Visually <-- but do not put the value inside the JSON.
document.activeElement.value = resultValue;
$scope.$apply();
// Option 2:
// http://stackoverflow.com/questions/11873627/angularjs-ng-model-binding-not-updating-when-changed-with-jquery
// Problem: The "document.activeElement.attributes['ng-model'].value" is not link with the scope, but with the ng-repeat row. So I have access to the Scope, but not the Row item.
//var binding = document.activeElement.attributes['ng-model'].value;
// Rule: I might not know where the Item is so I cannot do $scope.complexObject[row][binding]
} else {
console.log("not inside a Input box.");
}
};
vm.objectOtSubscribeTo.subscribe(channelToUse, callbackCompleted);
Thanks
One solution would be to keep track of the selected row and cell by setting them on focus of one of the cells
$scope.focusedRow = false;
$scope.focusedCell = false;
$scope.setFocused = (row, cell) => {
$scope.focusedRow = row;
$scope.focusedCell = cell;
};
/* In callback... */
if ($scope.focusedRow !== false && $scope.focusedCell !== false) {
$scope.$apply(
() => $scope.complexObject[$scope.focusedRow]
["cellInTheRow"][$scope.focusedCell] = resultValue
);
}
<input type="text" ng-model="row.cellInTheRow[key]"
ng-focus="setFocused(rowKey, key)" ng-blur="setFocused(false, false)">
Example: https://plnkr.co/edit/och5PoepJuRde0oONIjm?p=preview

Select2 3.5.2 mousedown jumps to top of list in IE10 & IE11

Using Select2 v3.5.2 to create a dynamic option list. The input allows users to select from the list, type to search through options and if the option doesn't exist it is created. The function which created this data is called within an angular controller. (ui-select was not used for this particular input as I did not see how to implement the search + custom input when this was implemented)
var dataQuant = {results: []};
for (var key in response.defaultQuantities) {
var objQuant = response.defaultQuantities[key];
dataQuant.results.push({id:key, text:key + 'otherText'});
}
$('.customClass').select2({
createSearchChoice:function(term, data) {
if ($(data).filter(function() {return this.text.localeCompare(term)===0; }).length===0) {
return {id:term, text:term};
}
},
matcher: function(term, text) {
return text.toUpperCase().indexOf(term.toUpperCase())==0;
},
data: dataQuant,
dropdownCssClass: "customClass2",
selectOnBlur: true,
initSelection : function (element, callback) {
var data = {id: response.defaultQuantity , text: response.defaultQuantity};
callback(data);
}
});
$('.customClass').on('change',function(){
var newQuantityData = $('.customClass').select2('data');
if ($scope.formData["quantity"] != newQuantityData.id){
$scope.formData["quantity"] = newQuantityData.id;
$scope.updateFunction();
}
});
This works perfectly fine in chrome/firefox/opera/safari & IE9 and below. In IE10 and 11 any options seen initially can be clicked and work fine. Any options in the option list hidden initially (user has to scroll to) mousedown jumps back up to the top of the option list. If the mouse is held down and you then scroll back down the options when released the correct option is selected.
After some searching I have found that within the select.js under
// single
postprocessResults: function (data, initial, noHighlightUpdate) {
the culprit was
if (initial === true && selected >= 0) {
this.highlight(selected);
} else {
this.highlight(0);
}
All other browsers have the 'initial' value as true passed into the function. IE10/11 has an object passed in which fails at the if statement resulting in the first option being highlighted. I'm not sure why an object is being passed in rather than true/false which it seems is what it's expecting. Anyone with more understanding of Select2 able to weigh in?
EDIT:
After removing this.highlight(0) I have now found that custom inputs that didn't exist before are not selected, so clicking the enter key does not select them. For now I'm just going to add a conditional to ignore this line if in IE.
I solved this with the following:
var scrollTop;
$('#mySelect2').on("select2:selecting", function( event ){
var $pr = $( '#'+event.params.args.data._resultId ).parent();
scrollTop = $pr.prop('scrollTop');
});
$('#mySelect2').on("select2:select", function( event ){
var $pr = $( '#'+event.params.data._resultId ).parent();
$pr.prop('scrollTop', scrollTop );
});
Perhaps not the most elegant solution, but essentially we listen for the selecting event and then grab the current scrollTop of the selectable options panel. On the actual selection, we reset the panel's scrollTop back to its original. This happens so fast that you should see no jump in the control window. Only test with Select2 v. 4.x.
The nice thing about this solution is that you don't have to hack the component or include anything in your config functions.
Using and seems to function correctly in all browsers.
Changing
if (initial === true && selected >= 0) {
this.highlight(selected);
} else {
this.highlight(0);
}
to
if (initial === true && selected >= 0) {
this.highlight(selected);
} else {
var docMode = document.documentMode,
hasDocumentMode = (docMode !== undefined),
isIE10 = (docMode === 10),
isIE11 = (docMode === 11);
if(hasDocumentMode && (isIE11 || isIE10)){
if(initial.target.value == data.results[0].id || data.results.length == 1){
this.highlight(0);
}
}else{
this.highlight(0);
}
}
Option list no longer jumps to the top in IE 10/11, but still allows users to enter custom values and the 'enter' key selects the typed values.

Popup - if user enters bad input, how can I get it to error on submit

I have a popup on my page that has a typeahead input on it. Right now you can type garbage and click submit and it lets you. I'm trying to write code that will throw an error on the popup if you type something that isn't included in the typeahead options and it won't let you submit it until you fix it. Here is my code, it is for making a school schedule that has classes in the typeahead dropdown.
var schedule = schedule.content.get();
var validClasses = Fp.filter(schedule.classes, function (class) { return !class.passed; }),
inputClasses = $('.optimizeViaClasses input.className').map(function () { return $(this).val(); }),
isErrorForValidClasses = Fp.all(inputClasses, function (inputClass) { return Fp.contains(validClasses, inputClass); });
if(validClasses !== inputClasses){
$errorMessage.text('Your selection does not match the class(es) in the current schedule!');
$errorMessage.show();
}
Right now if you enter garbage in the input field, this will throw an error but still let the user submit. How can I stop the user from submitting until the input is correct?
Here is my button:
$submitBtn.on('click', function(event){
if(inputParameters() !== false){
$myPopUp= $modal.find('#myData').detach()[0];
}
event.preventDefault();
});
and I checked the output of inputClasses in the Google developer console, it outputs the class and a prevObject. I just need the class...
Let javascript return either True or false and if the popup comes out return false other wise true.
For instance if it get into if return false other wise true.
since you modified your code i suppose you might want to try this instead:
http://api.jquery.com/event.stoppropagation/
also you might want to be doing something along the lines of this if stoppropagation does not result in the desired effect:
$("#formid").on("submit",function(e){
// declare isValid outside of this and set it in your validation function or call it inside this and check for that.
if(isValid) {
e.stopPropagation();
}
});
at least that's how i went about solving such issues usually. i hope it helps.
got it. the error i had was throwing an error.
var schedule = schedule.content.get(),
validClasses = Fp.filter(schedule.classes, function (class) { return !class.passed; }),
inputClasses = $('.optimizeViaClasses input.className').map(function () { return $(this).val(); }),
actualValidClasses = Fp.pluck(validClasses, 'className');
$.each(inputClasses , function(index, value){
if($.inArray(value, actualValidClasses ) === -1){
$errorMessage.text('Your selection does not match the class(es) in the current schedule!');
$errorMessage.show();
error = true;
return false;
}
});

jQuery validation form submit when form is incomplete

I am using MVC3 and unobstrusive validation. On certain occasions I want to submit a form even if the form is incomplete providing two fields are selected. The JavaScript I am using is as follows:
$('#form_section11').submit(function () {
//event.preventDefault(); //stops form from submitting immediately
var validatorSection11 = $('#form_section11').validate();
if (!$(this).valid()) {
// or family name and date of birth
var FamilyNameExists = true;
var DateOfBirthExists = true;
if (validatorSection11.errorMap["FamilyName"]) { FamilyNameExists = false; }
if (validatorSection11.errorMap["DateOfBirth"]) { DateOfBirthExists = false; }
// show the partial save rules
$('#ParitalSaveIntructions').show();
// show the partial save checkbox
if (FamilyNameExists && DateOfBirthExists) {
$("#AgreePartialSave").show();
if ($("#PartialSave").is(':checked')) {
// partial save has been requested
return true; //// <- save not happening, INCORRECT action
}
// clear perinatalWomanView_PartialSave
$("#PartialSave").removeAttr('checked');
}
return false; // <- save not happening, correct action
}
return true; // <- save happens, correct action
});
The user is presented with a checkbox to confirm incomplete submission. I've indicated where the JavaScript works and where it fails.
I've also added
var validatorSection11 = $('#form_section11').validate(
{
onsubmit: false
}
);
This has no effect. My questions are:
Why is the original return true not functioning?
Am I using the onsubmit: false correctly?
Is there a better way of doing this?
Thanks in advance.
Try using a variable (save) instead of multiple return statements:
$('#form_section11').submit(function (e) {
'use strict';
var self = $(this),
save = false,
FamilyNameExists = true,
DateOfBirthExists = true,
validatorSection11 = self.validate();
if (!self.valid()) {
// or family name and date of birth
FamilyNameExists = true;
DateOfBirthExists = true;
if (validatorSection11.errorMap["FamilyName"]) {
FamilyNameExists = false;
}
if (validatorSection11.errorMap["DateOfBirth"]) {
DateOfBirthExists = false;
}
// show the partial save rules
$('#ParitalSaveIntructions').show(); // should this be "Parital" or "Partial"
// show the partial save checkbox
if (FamilyNameExists && DateOfBirthExists) {
$("#AgreePartialSave").show();
//if ($("#PartialSave").is(':checked')) { // Comment in answer
// // partial save has been requested
// save = true;
//}
save = $("#PartialSave").is(':checked');
// clear perinatalWomanView_PartialSave
$("#PartialSave").removeAttr('checked');
}
//save = false; //This was overriding the `save = true` above.
}
if (!save) {
e.preventDefault(); // stops form from submitting immediately
}
return save;
});
Also, at the section "Comment in answer", this section will likely only execute after the form has been resubmitted because the following would have to happen:
$("#AgreePartialSave").show(); has to execute and show the section.
The user has to put a check in $("#PartialSave") for $("#PartialSave").is(':checked') to return true.
The $('#form_section11').submit() has to fire again for that section of the handler to evaluate.
If there is a different button that the user has to click to do a partial save, you'll likely want to move that whole section into that button handler.

How to test if users has made any changes to a form if they haven't saved it

Basically the same functionality as stackoverflow when posting a question, if you start writing a post then try to reload the page. You get a javascript alert box warning message.
I understand how to check if the form has been changed, although how do I do the next step.
I.E: How to I check this when leaving the page, on here you get "This page is asking you to confirm that you want to leave - data you have entered may not be saved."?
EDIT: found correct answer here to another question https://stackoverflow.com/a/2366024/560287
I'm very sure that if you search, 'jQuery detect form change plugin', you will find something much more usable than this semi-pseudo code i'm about to write:
formChanged = function(form) {
form.find('input[type="text"], textarea').each(function(elem) {
if (elem.defaultValue != elem.value) {
return true;
}
});
// repeat for checkbox/radio: .defaultChecked
// repeat for ddl/listbox: .defaultSelected
return false;
}
usage:
if (formChanged($('form')) { // do something }
Note that this is to detect changes against the original rendered value. For instance, if a textbox has a value = "x", and the user changes it to "y", then changes it back to "x"; this will detect it as NO change.
If you do not care about this scenario, you can just do this:
window.formChanged = false;
$(':input').change(function() {
window.formChanged = true;
});
Then you can just check that value.
Yes, it is JavaScript as HTML is just a markup language.
Yes, jQuery can be used for this. It's preferable over vanilla JavaScript as it makes things easier, although it does add some overhead.
There are a number of ways to check if any of a form's controls have changed.
To check for changes from the default, most can be checked against the defaultValue property. For radio buttons, you should always have one checked by default, so check if it's still selected or not. Similarly for selects, set the selected attribute for the default option and see if it's still selected, and so on.
Alternatively, if all your form controls have an ID or unique name, you can collect all their values onload and then check their values when the form is submitted.
Another method is to listen for change events on each form control, but that is a bit over the top.
Here's a POJS version that takes the same approach as rkw's answer:
/*
Check if any control in a form has changed from its default value.
Checks against the default value for inputs and textareas,
defaultChecked for radio buttons and checkboxes, and
default selected for select (option) elements.
*/
function formChanged(form) {
var control, controls = form.elements;
var tagName, type;
for (var i=0, iLen=controls.length; i<iLen; i++) {
control = controls[i];
tagName = control.tagName.toLowerCase();
type = control.type;
// textarea
if (tagName == 'textarea') {
if (control.value != control.defaultValue) {
return true;
}
// input
} else if (tagName == 'input') {
// text
if (type == 'text') {
if (control.value != control.defaultValue) {
return true;
}
// radio and checkbox
} else if (type == 'radio' || type == 'checkbox') {
if (control.checked != control.defaultChecked) {
return true;
}
}
// select multiple and single
} else if (tagName == 'select') {
var option, options = control.options;
for (var j=0, jLen=options.length; j<jLen; j++) {
option = options[j];
if (option.selected != option.defaultSelected) {
return true;
}
}
}
}
// Not really needed, but some like the return value to
// be a consistent Type
return false;
}
Note that you need to be careful with select elements. For a single select, you should always set one option to selected, as if there is no default selected, some browsers will make the first option selected and others wont.

Categories