I have some selection field in my model. Here example:
class MyModel(models.Model):
_name = 'my_app.my_model'
example_selection = fields.Selection(
[
('first', 'First'),
('second', 'Second'),
# etc.
],
string='My selection',
)
In some cases I need hide specific options in selection(or radio buttons). How I can do this properly?
Below screen from base calendar module which can more explain about my problem.
Thanks in advance.
It's a bit too late. In my case, I did it like this :
odoo.define('my_module.custom_selection', function(require) {
"use strict";
var registry = require('web.field_registry');
var relational_fields = require('web.relational_fields');
var MySelection = relational_fields.FieldRadio.extend({
init: function() {
this._super.apply(this, arguments);
// use to decrement in splice, bc position change when element is removed
let decrement = 0;
// this.values can be undefined or [[], [], []]
// copying the content of original array or []
let value_copies = this.values? [...this.values]: [];
for (let index = 0; index < value_copies.length; index++) {
// 'other' is the value to be removed
if (value_copies[index].includes('other')) {
this.values.splice(index - decrement, 1);
decrement++;
}
}
},
});
registry.add('custom_selection', MySelection);
return MySelection;
});
You can check my repo here: https://github.com/m0r7y/wdgt_hide_option
I found the solution.
First of all we need to create custom widget for FieldSelection. Here example(path_to_your_module/static/src/js/form_widgets.js):
odoo.define('your_module.form_widgets', function (require) {
"use strict";
var core = require('web.core');
var FieldSelection = core.form_widget_registry.get('selection');
var MySelection = FieldSelection.extend({
// add events to base events of FieldSelection
events: _.defaults({
// we will change of visibility on focus of field
'focus select': 'onFocus'
}, FieldSelection.prototype.events),
onFocus: function() {
if (
// check values of fields. for example I need to check many fields
this.field_manager.fields.name_field_1.get_value() == 'value1' &&
this.field_manager.fields.name_field_2.get_value() == 'value2' /* && etc fields...*/
) {
// for example just hide all options. You can create any kind of logic here
this.$el.find('option').hide();
}
}
});
// register your widget
core.form_widget_registry.add('your_selection', MySelection);
});
After this you need just set your widget to field in your view like this:
<field name="example_selection" widget="your_selection"/>
If you don't know how to include static of your module HERE example which can help you.
I hope this helps someone ;)
AFAIK this is not possible, but you can achieve something similar if you use a MAny2one instead of a selection (thus using the domain) end in the view you can use
<field name="example_with_domain" widget="selection"/>
to obtain the same visual behaviour (no create, no edit) of a selection field.
Related
I need to Create a Kendo ui grid. Since this has many filters, I need to have 4 regular filters and rest should be able to add dynamically according to users choice. Can someone provide assistance on this?
In order to filter by text box you can hook up a keyUp event in order to retrieve the value. You can then add this as a filter to the existing filter object.
$('#NameOfInput').keyup(function () {
var val = $('#NameOfInput').val();
var grid = $("#yourGrid").data("kendoGrid");
var filter = grid.dataSource.filter();
filter.filters.push({
field: "NameOfFieldYouWishToFilter",
operator: "eq",
value: val,
FilterName: "UniqueIdentifierForFilter"
});
grid.dataSource.filter(filter);
});
Using a drop down box, you can achieve the desired functionality by using the onChange event, get the value using $('#yourDropDown').val();.
The FilterName is optional incase you require additional logic to add/remove filters. i.e. you can use this to determine whether the filter already exists in the array and if so you can use splice to remove it.
EDIT
Using FilterName you can search to see if a filter already exists and remove it:
var filterIndex = filter.filters.map((e: any) => { return e.FilterName }).indexOf("UniqueIdentifierForFilter");
if (filterIndex > -1)
{
filter.filters.splice(filterIndex, 1);
}
For #lakshan, while this is largely correct, you will get an error if there are no filters at first. I found this answer when I encountered the undefined filter error. My full solution for adding a filter, either to an undefined filter set, or along with an existing one:
var grid = $("#ActivityGrid").data("kendoGrid");
var dataSource = grid.dataSource;
var gridFilter = dataSource.filter();
var upcomingFilter = {
field: "ActivityDate",
operator: "gte",
value: new Date(),
FilterName: "UpcomingOnly"
};
if ($("#UpcomingOnlyCheckbox")[0].checked) {
if (gridFilter == undefined) {
dataSource.filter(upcomingFilter);
}
else {
gridFilter.filters.push(upcomingFilter);
dataSource.filter(gridFilter);
}
}
I'm trying to have 4 sub fields, billing code, channel, subject code and name, autopopulate certain values based on the answer of their parent field, Event Type. In other words, if the answer to event type is "A", dropdown fields will appear for each sub field based on "A".
Let's say the first select-element of the document has the values
'Plant', 'Animal' and 'Ghost' and the next select-element should change
when the selected value of first ele changes, then this is how we do it:
var majorField = document.getElementsByTagName('select')[0]
var minorField = document.getElementsByTagName('select')[1]
// Define a dict, mapping each possible value to a function:
majorField.onChangeMap = {
Plant: function() { minorField.innerHTML = '<option>Cocoa</option><option>Cocos</option>' },
Animal: function() { minorField.innerHTML = '<option>Cat</option><option>Cow</option>' },
Ghost: function() { minorField.style.visibility = 'hidden' },
}
// When value changes, execute function of dict-map:
majorField.onchange = function(event) {
event.target.onChangeMap[event.target.value]()
}
// Make sure he minor-field has initially the correct state, by
// executing the mapped function for the major-field's current-value:
majorField.onChangeMap[majorField.value]()
Note that this is a very minimal illustration-example for the mapping only, as
one wouldn't set the options as an html-string, and hiding the minorField on
Ghost should be reversed on all other options with visibility = 'visible', here.
Person = Backbone.Model.extend({
defaults: {
name: 'Fetus',
age: 0,
children: []
},
initialize: function(){
alert("Welcome to this world");
},
adopt: function( newChildsName ){
var children_array = this.get("children");
children_array.push( newChildsName );
this.set({ children: children_array });
}
});
var person = new Person({ name: "Thomas", age: 67, children: ['Ryan']});
person.adopt('John Resig');
var children = person.get("children"); // ['Ryan', 'John Resig']
In this example code we have:
children_array = this.get("children")
I was thinking this would just point to the same array in memory (and so would be O(1)). However then I thought that would be a design floor because one could manipulate the array without using this.set() and then event listeners wouldn't fire.
So I'm guessing it (somehow magically) copies the array??
http://backbonejs.org/#Model-set
What happens?
edit: I just found the implementation in the backbone source code at https://github.com/documentcloud/backbone/blob/master/backbone.js (I've pasted relevant code at bottom)
Get returns:
return this.attributes[attr]
so this would just point to the same array in memory right? So one could change the array without using set() and that would be bad.. ? am i correct?
get: function(attr) {
return this.attributes[attr];
},
// Get the HTML-escaped value of an attribute.
escape: function(attr) {
var html;
if (html = this._escapedAttributes[attr]) return html;
var val = this.get(attr);
return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
},
// Returns `true` if the attribute contains a value that is not null
// or undefined.
has: function(attr) {
return this.get(attr) != null;
},
// Set a hash of model attributes on the object, firing `"change"` unless
// you choose to silence it.
set: function(key, value, options) {
var attrs, attr, val;
// Handle both `"key", value` and `{key: value}` -style arguments.
if (_.isObject(key) || key == null) {
attrs = key;
options = value;
} else {
attrs = {};
attrs[key] = value;
}
// Extract attributes and options.
options || (options = {});
if (!attrs) return this;
if (attrs instanceof Model) attrs = attrs.attributes;
if (options.unset) for (attr in attrs) attrs[attr] = void 0;
// Run validation.
if (!this._validate(attrs, options)) return false;
// Check for changes of `id`.
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
var changes = options.changes = {};
var now = this.attributes;
var escaped = this._escapedAttributes;
var prev = this._previousAttributes || {};
// For each `set` attribute...
for (attr in attrs) {
val = attrs[attr];
// If the new and current value differ, record the change.
if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
delete escaped[attr];
(options.silent ? this._silent : changes)[attr] = true;
}
// Update or delete the current value.
options.unset ? delete now[attr] : now[attr] = val;
// If the new and previous value differ, record the change. If not,
// then remove changes for this attribute.
if (!_.isEqual(prev[attr], val) || (_.has(now, attr) !== _.has(prev, attr))) {
this.changed[attr] = val;
if (!options.silent) this._pending[attr] = true;
} else {
delete this.changed[attr];
delete this._pending[attr];
}
}
// Fire the `"change"` events.
if (!options.silent) this.change(options);
return this;
},
The documented interface doesn't actually specify who owns the array reference so you're on your own here. If you look at the implementation, you'll see (as you did) that get just returns a reference straight out of the model's internal attributes. This works fine with immutable types (such as numbers, strings, and booleans) but runs into problems with mutable types such as arrays: you can easily change something without Backbone having any way of knowing about it.
Backbone models appear to be intended to contain primitive types.
There are three reasons to call set:
That's what the interface specification says to do.
If you don't call set, you don't trigger events.
If you don't call set, you'll bypass the validation logic that set has.
You just have to be careful if you're working with array and object values.
Note that this behavior of get and set is an implementation detail and future versions might get smarter about how they handle non-primitive attribute values.
The situation with array attributes (and object attributes for that matter) is actually worse than you might initially suspect. When you say m.set(p, v), Backbone won't consider that set to be a change if v === current_value_of_p so if you pull out an array:
var a = m.get(p);
then modify it:
a.push(x);
and send it back in:
m.set(p, a);
you won't get a "change" event from the model because a === a; Backbone actually uses Underscore's isEqual combined with !== but the effect is the same in this case.
For example, this simple bit of chicanery:
var M = Backbone.Model.extend({});
var m = new M({ p: [ 1 ] });
m.on('change', function() { console.log('changed') });
console.log('Set to new array');
m.set('p', [2]);
console.log('Change without set');
m.get('p').push(3);
console.log('Get array, change, and re-set it');
var a = m.get('p'); a.push(4); m.set('p', a);
console.log('Get array, clone it, change it, set it');
a = _(m.get('p')).clone(); a.push(5); m.set('p', a);
produces two "change" events: one after the first set and one after the last set.
Demo: http://jsfiddle.net/ambiguous/QwZDv/
If you look at set you'll notice that there is some special handling for attributes that are Backbone.Models.
The basic lesson here is simple:
If you're going to use mutable types as attribute values, _.clone them on the way out (or use $.extend(true, ...) if you need a deep copy) if there is any chance that you'll change the value.
I want to refactor Snippet 1 to Snippet 2. I don't think performance is quite an issue here considering the size, but I wanted to understand what was going on as far as memory use goes regarding this refactor to the module pattern.
The module pattern ensures that I only pull in this data from the DOM once which is what I want and it also forms a mini-registry pattern in that the data is private.
Both snippets have been tested and basically work.
Snippet 1 // Replace SUniverisals w/ SU
var SUniversals = function () {
// Pull from Server
this.universals.path = document.getElementById('universals').getAttribute('data-path');
this.universals.load = document.getElementById('universals').getAttribute('data-load');
// Set Manually
this.universals.debug = false;
};
SUniversals.prototype.universals = {};
SUniversals.prototype.get = function( key ) {
return this.universals[ key ];
};
SUniversals.prototype.set = function( key, value ) {
this.universals[ key ] = value;
};
Snippet 2
var SU = ( function ()
{
// private SU.get('load');
var universals = {};
universals.path = document.getElementById('universals').getAttribute('data-path');
universals.load = document.getElementById('universals').getAttribute('data-load');
universals.debug = false;
// pubulic
var publik = {};
publik.get = function( key )
{
return universals[ key ];
};
publik.set = function( key, value )
{
universals[ key ] = value;
};
return publik;
}());
There are few things which are different. Snippet 2 is essentially creating a singleton. Snippet 1 can be looked at like a 'class'. You can create multiple instances/objects of 'SUniversals' and do different things with them.
Actually, snippet 1 is more efficient in terms of memory. By adding to the object's prototype, you essentially will have only 1 copy of each function irrespective of the number of objects you create. The module pattern will create separate entities.
Not enough to worry about ;-)
Seriously, the only thing you need to worry about with the module pattern is creating memory leaks; by itself the pattern uses basically nothing.
I am using jqgrid in 'multiselect' mode and without pagination. When the user selects individual records by using mouse click, is there any way that I can bring those selected records to the top of the grid?
Thanks in advance for your help.
After small discussion with you in comments I could reformulate your question so: "how one can implement sorting by multiselect column?"
The question find is very interesting so I invested some time and could suggest a solution in case of jqGrid which hold local data (datatype which is not 'xml' or 'json' or which has 'loadonce: true' option).
First of all the working demo which demonstrate my suggestion you can find here:
The implementation consist from two parts:
Making selection as part of local data. As the bonus of the selection will be hold during paging of local data. This feature is interesting independent on the sorting by multiselect column.
The implementation of sorting by multiselect column.
To implement of holding selection I suggest to extend local data parameter, which hold local data with the new boolean property cb (exactly the same name like the name of the multiselect column). Below you find the implementation:
multiselect: true,
onSelectRow: function (id) {
var p = this.p, item = p.data[p._index[id]];
if (typeof (item.cb) === "undefined") {
item.cb = true;
} else {
item.cb = !item.cb;
}
},
loadComplete: function () {
var p = this.p, data = p.data, item, $this = $(this), index = p._index, rowid;
for (rowid in index) {
if (index.hasOwnProperty(rowid)) {
item = data[index[rowid]];
if (typeof (item.cb) === "boolean" && item.cb) {
$this.jqGrid('setSelection', rowid, false);
}
}
}
}
To make 'cb' column (multiselect column) sortable I suggest to do following:
var $grid = $("#list");
// ... create the grid
$("#cb_" + $grid[0].id).hide();
$("#jqgh_" + $grid[0].id + "_cb").addClass("ui-jqgrid-sortable");
cbColModel = $grid.jqGrid('getColProp', 'cb');
cbColModel.sortable = true;
cbColModel.sorttype = function (value, item) {
return typeof (item.cb) === "boolean" && item.cb ? 1 : 0;
};
UPDATED: The demo contain a little improved code based on the same idea.
If you have the IDs of the row(s) you can do a special sort on server side by using following command for e.g. MySQL:
Select a,b,c
FROM t
ORDER BY FIND_IN_SET(yourColumnName, "5,10,44,29") DESC
or
ORDER BY FIELD(yourColumnName, "5") DESC