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
Related
I am creating an attendance tracker with the jQuery DataTables plugin! I have gotten really far with the functionality/capability and have just been stuck for weeks trying to figure out how to do this last portion of what I want it to do.
I will have a static/workable test case attached below. So the issue that I cannot figure out is how to style the parent rows based off of the child row cell values. The columns Sunday-Friday are colored based off of a hidden value called SundayStatus, MondayStatus, TuesdayStatus, and so on. There are two values that could cause it to turn green (TW & P), two values that could cause it to turn yellow (NR & O), and two values to cause it to turn red (PTO & H). In my rows.every(function ( rowIdx, tableLoop, rowLoop ) { function I need to find a way to manipulate the data and add classes to the parent rows based off the attendance values from each individual day.
P.S.(I created my own plugin $.fn.dataTable.ext.search.push(function to search through all of the data in the table and only show items where the dates Sunday-Friday are dates that are in the current week.
UPDATE 5/10 Andrew was on the right track with the update to his answer, I made one small change to today format, and changed var result = Object.keys(data).find(key => data[key].substring(0,10) === today); to var result = Object.keys(data).find(key => typeof data[key] === 'string' && data[key].startsWith(today));. I then created a conditional in my dynamic code, to read through the result from the reverse-lookup and depending on what the result is, to color the row a certain color.
Here is my JSFiddle of the Static Example that was previously in a snippet within the post: https://jsfiddle.net/BeerusDev/y8t0xoze/19/
In this update, my last and final issue that I am dealing with that I did not foresee, is that everything seems to be working fine, but it appends the status class from the first item that is posted to the DataTable and doesn't take into account for the other items. I have hit a mental block trying to figure out a way around this issue, but here is my rows.every function from my dynamic application which is inside of my startRender function
var statusClass = '';
rows.every(function ( rowIdx, tableLoop, rowLoop ) {
var data = this.data();
var node = this.node();
var today = moment().format("YYYY-MM-DD"); // "05/10/2021"
console.log(today);
//console.log(JSON.stringify(data));
var result = Object.keys(data).find(key => typeof data[key] === 'string' && data[key].startsWith(today)); // "Monday"
console.log(result);
var todayStatus = result ? data[result + 'Status'] : 'n/a';
console.log(todayStatus);
if(todayStatus === "P" || todayStatus === "TW") {
statusClass = 'green';
}
if(todayStatus === "NR" || todayStatus === "O") {
statusClass = 'yellow';
}
if (todayStatus === "PTO" || todayStatus === "H") {
statusClass = 'red';
}
});
//Add category name to the <tr>.
return $('<tr/>').addClass(statusClass)
.append('<td colspan="8">' + group + ' (' + rows.count() + ')</td>')
.attr('data-name', all)
.toggleClass('collapsed', collapsed);
This looks very close, to me!
Here are some changes I recommend:
After the end of your closing </table> tag, there is an extra <body> tag. That looks incorrect - it should be removed. I don't think this causes any errors - but it is worth fixing.
In your rows.every() function, the data variable is a plain array - for example:
[ "IT", "Name 1", "Locations Here", "05/02/2021", "05/03/2021", "P", … ]
Therefore you cannot use data.MondayStatus - because that will be undefined. Instead use something like data[5] to get the 6th item in the array (P).
If you want to change the background color of a row for a location (e.g. "IT" or "OM"), you can use a selector like this:
$("tr[data-name='IT'] td").addClass("green");
This works because you have already added a custom attribute called data-name to the relevant <td> tag. The selector finds the <td> tag which is the child of the <tr> tag using that custom attribute.
However, the problem here is: You are trying to assign the class to a table node before the DataTable has finished being built.
To address this you can move all of that row looping logic to an initComplete function:
initComplete: function(settings, json) {
this.api().rows().every(function ( rowIdx, tableLoop, rowLoop ) {
var data = this.data();
var node = this.node().previousSibling; // to handle your row grouping
if (node !== null) {
if (data[5] === "P") {
var selectorVar = "[data-name='" + data[0] + "'] td";
$( selectorVar ).addClass("green");
}
}
});
}
Instead of if (data[5] === "P"), you can expand this logic to handle different values and also different class names (not just "green"), for whatever the overall logic is that you need. My logic is just a small demo to show the color change.
Update to handle "today"
To show the approach, let's assume the following record:
var data = {
"Department": "IT",
"Name": "Name 1",
"Locations": "Locations Here",
"Sunday": "2021-05-09",
"Monday": "2021-05-10",
"MondayStatus": "P",
"Tuesday": "2021-05-11",
"TuesdayStatus": "Q",
"Wednesday": "2021-05-12",
"WednesdayStatus": "R",
"Thursday": "2021-05-13",
"ThursdayStatus": "S",
"Friday": "2021-05-14",
"FridayStatus": "T"
};
This data variable is what I think you are handling in the rows.every function. So, it's the equivalent of var data = this.data();. I may have got some of the keys wrong (uppercase/lowercase) - but you can adjust the test data if that is the case.
Now, I get today's date, formatted to match the same format as the dates in the data object:
var today = moment().format("YYYY-MM-DD"); // "2021-05-10"
I use this value to find the equivalent value in the data variable, and I return the key name for that entry:
var result = Object.keys(data).find(key => data[key].substring(0,10) === today); // "Monday"
This is basically a reverse-lookup from what you would normally do. Instead of starting with a key, we start with a value and end with a key - in this case, the key is the string "Friday".
Now we take this string and append "Status" to it.
This gives us an actual key string: "FridayStatus".
Now we use that key to find the status for today (if it exists at all in the data object):
var todayStatus = result ? data[result + 'Status'] : 'n/a'; // "P"
If the date does not exist, then you will end up with a status of "n/a".
Overall, this gives us a quick way to get today's status, without having to perform lots of if/else logic.
Once you have today's status you can use it in a smaller if/else to choose the required color you want to apply to the row.
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 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.
I am struggling trying to find the right method to do this. Basically, I have an array of id values that correspond to which rows have been selected in my table. To construct this list, I use the following code. this.options.ajaxId is the key that accesses my Id value in the data object passed to the table.
this.getRowData(target)[this.options.ajaxId]
where my getRowData function is:
getRowData: function (HTMLrow) {
return this.dataTable.row(HTMLrow).data();
},
This works great, but then I am stumped on my next step which is re-selecting the correct rows when the table is re-drawn via paging, sorting, or searching. My plan was to cycle through the ID's and find which table row corresponded to that ID value, but I cannot find a function to input a key value search pair and return the html row. Something like the following is what I was thinking,
this.dataTable.findRow( key, value );
// then my usage would be the following:
var that = this;
_.each(this.selectedList, function (id) {
var row = that.dataTable.findRow( that.options.ajaxId, id );
// code to select the row
});
I haven't written it yet, but I know I can cycle through each of the rows, get the data for that row, and check it against what I am looking for, but in cases where the user is viewing 100 rows and has only one selection I would like to avoid that.
Any insight?
Thanks
SOLUTION #1
You can use the following code to locate and highlight rows based on row IDs if row ID is stored in one of the fields.
// Index of column containing IDs
var colIdIndex = 0;
// List of row IDs
var rowIds = ['2', '4', '6'];
// Find indexes of rows which have IDs in the desired column
var rowIndexes = table.rows().eq(0).filter( function (rowIdx) {
return ($.inArray(table.cell( rowIdx, colIdIndex ).data(), rowIds) !== -1)
? true
: false;
});
// Select rows based on array of found row indexes
table.rows(rowIndexes)
.nodes()
.to$()
.addClass('selected');
See filter() API method for more details.
Please note that this method will work for client-side processing mode only.
DEMO
See this jsFiddle for code and demonstration.
SOLUTION #2
Alternative approach that would work both in client-side and server-side processing modes would be to use createdRow callback.
For example:
// Index of column containing IDs
var colIdIndex = 0;
// List of row IDs
var rowIds = ['2', '4', '6'];
var table = $('#example').DataTable({
createdRow: function( row, data, dataIndex ) {
if ( $.inArray(data[colIdIndex], rowIds) !== -1) {
$(row).addClass('selected');
}
}
});
DEMO
See this jsFiddle for code and demonstration.
I'm using Select2 version 4.0.0.
If my results contain multiple words, and the user enters one of those words, I
want to display the results sorted by where the entered word is within the result.
For example, a user enters "apple", and my results are:
"banana orange apple"
"banana apple orange"
"apple banana orange"
Then "apple banana orange" should appear first in the list of select2 results, because that is the result in which "apple" appears earliest within the result. I don't care so much about the ordering past that.
What do I override or configure to get something like this? It seems that matcher doesn't
handle ordering, and sorter doesn't contain query data.
You could grab the search query from the value of the input box generated by Select2 by identifying it with the select2-search__field class. That's probably going to break across versions, but since they don't provide a hook to get the query some sort of hack will be needed. You could submit an issue to have them add support for accessing the query during sort, especially since it looks like it was possible in Select2 3.5.2.
$('#fruit').select2({
width: '200px',
sorter: function(results) {
var query = $('.select2-search__field').val().toLowerCase();
return results.sort(function(a, b) {
return a.text.toLowerCase().indexOf(query) -
b.text.toLowerCase().indexOf(query);
});
}
});
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/js/select2.min.js"></script>
<link href="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/css/select2.min.css" rel="stylesheet" />
<select id="fruit">
<option>Banana Orange Apple</option>
<option>Banana Apple Orange</option>
<option>Apple Banana Orange</option>
<option>Achocha Apple Apricot</option>
<option>Durian Mango Papaya</option>
<option>Papaya</option>
<option>Tomato Papaya</option>
<option>Durian Tomato Papaya</option>
</select>
The problem here is that Select2, in the 4.0.0 release, separated the querying of results from the display of results. Because of this, the sorter option which you would normally use to sort the results does not pass in the query that was made (which includes the search term).
So you are going to need to find a way to cache the query that was made so you can use it when sorting. In my answer about underlining the search term in results, I cache the query through the loading templating method, which is always triggered whenever a search is being made. That same method can be used here as well.
var query = {};
$element.select2({
language: {
searching: function (params) {
// Intercept the query as it is happening
query = params;
// Change this to be appropriate for your application
return 'Searching…';
}
}
});
So now you can build a custom sorter method which uses the saved query (and using query.term as the search term). For my example sorting method, I'm using the position within the text where the search result is to sort results. This is probably similar to what you are looking for, but this is a pretty brute force method of going about it.
function sortBySearchTerm (results) {
// Don't alter the results being passed in, make a copy
var sorted = results.slice(0);
// Array.sort is an in-place sort
sorted.sort(function (first, second) {
query.term = query.term || "";
var firstPosition = first.text.toUpperCase().indexOf(
query.term.toUpperCase()
);
var secondPosition = second.text.toUpperCase().indexOf(
query.term.toUpperCase()
);
return firstPosition - secondPosition;
});
return sorted;
};
And this will sort things the way that you are looking to do it. You can find a full example with all of the parts connected together below. It's using the three example options that you mentioned in your question.
var query = {};
var $element = $('select');
function sortBySearchTerm (results) {
// Don't alter the results being passed in, make a copy
var sorted = results.slice(0);
// Array.sort is an in-place sort
sorted.sort(function (first, second) {
query.term = query.term || "";
var firstPosition = first.text.toUpperCase().indexOf(
query.term.toUpperCase()
);
var secondPosition = second.text.toUpperCase().indexOf(
query.term.toUpperCase()
);
return firstPosition - secondPosition;
});
return sorted;
};
$element.select2({
sorter: sortBySearchTerm,
language: {
searching: function (params) {
// Intercept the query as it is happening
query = params;
// Change this to be appropriate for your application
return 'Searching…';
}
}
});
<link href="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/css/select2.css" rel="stylesheet"/>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/js/select2.js"></script>
<select style="width: 50%">
<option>banana orange apple</option>
<option>banana apple orange</option>
<option>apple banana orange</option>
</select>
No needs to keep term:
$element.select2({
sorter: function (data) {
if(data && data.length>1 && data[0].rank){
data.sort(function(a,b) {return (a.rank > b.rank) ? -1 : ((b.rank > a.rank) ? 1 : 0);} );
}
return data;
}
,
matcher:function(params, data) {
// If there are no search terms, return all of the data
if ($.trim(params.term) === '') {
return data;
}
// Do not display the item if there is no 'text' property
if (typeof data.text === 'undefined') {
return null;
}
// `params.term` should be the term that is used for searching
// `data.text` is the text that is displayed for the data object
var idx = data.text.toLowerCase().indexOf(params.term.toLowerCase());
if (idx > -1) {
var modifiedData = $.extend({
// `rank` is higher when match is more similar. If equal rank = 1
'rank':(params.term.length / data.text.length)+ (data.text.length-params.term.length-idx)/(3*data.text.length)
}, data, true);
// You can return modified objects from here
// This includes matching the `children` how you want in nested data sets
return modifiedData;
}
// Return `null` if the term should not be displayed
return null;
}
})