How can I reset the sort order after a row has been selected? - smart-table

I am using smart-table to show a list of possible matches. When a row is selected, it then shows the next list of possibilities.
By default, the table is sorted by a "ranking" column. Its possible for the user to click a different header and have it sorted by that column instead.
After a row has been selected, and the next results are loaded, is it possible to reset the st-sort back to what it was initially? I've searched the github for terms like "reset sort" and I haven't been successful.

I worked it out.
Use this directive:
ng.module('smart-table')
.directive('stResetSearchOrder',
[
'stConfig', function(stConfig) {
return {
restrict: 'A',
require: '^stTable',
scope: {
row: '=stSelectRow',
defaultOrderBy: '<',
defaultReverseOrder: '#'
},
link: function(scope, element, attr, ctrl) {
element.bind('click',
function () {
var orderable = scope.defaultOrderBy;
ctrl.sortBy(orderable, scope.defaultReverseOrder != "true");
});
}
};
}
]);
Then add these attributes on your tr in your ng-repeat
st-reset-search-order default-order-by="the name of your property" default-reverse-order="false"
In my case I'm sorting by a function, and it works well.

Related

Cypress: How to scroll a dropdown to find item

I need to click a dropdown list and scroll to find an item by text.
At the moment I know that the item is at the bottom of the list so I can do:
cy.get('.ng-dropdown-panel-items').scrollTo("bottom").contains(/test/i).click()
and this works, but if the item moves and is no longer at the bottom, this will break.
I tried scrollIntoView but with no luck:
cy.get('.ng-dropdown-panel-items').contains(/test/i).scrollIntoView().click()
and
cy.get('.ng-dropdown-panel-items').scrollIntoView().contains(/test/i).click()
Does anyone know how I can do this?
Update: the list of options is dynamically generated (not all options are in the DOM initially) so scrolling to the bottom is required to get all options. Once all options are available .contains() can be used to find the element.
The Angular ng-select in virtual mode is quite tricky to handle.
It's list is virtual, which means it only has some of the items in the DOM at one time, so you can't select them all and iterate over them.
You can recursively scan the options list and use .type({downarrow}) to move new options into the DOM (which is one way a user would interact with it).
it('selects an option in a virtual-scroll ng-select', () => {
cy.visit('https://ng-select.github.io/ng-select#/virtual-scroll')
cy.get('ng-select').click(); // open the dropdown panel
cy.get('.ng-option')
.should('have.length.gt', 1); // wait for the option list to populate
function searchForOption(searchFor, level = 0) {
if (level > 100) { // max options to scan
throw 'Exceeded recursion level' // only useful for 100's
} // not 1000's of options
return cy.get('ng-select input')
.then($input => {
const activeOptionId = $input.attr('aria-activedescendant') // highlighted option
const text = Cypress.$(`#${activeOptionId}`).text() // get it's text
if (!text.includes(searchFor)) { // not the one?
cy.wrap($input).type('{downarrow}') // move the list
return searchForOption(searchFor, level + 1) // try the next
}
return cy.wrap(Cypress.$(`#${activeOptionId}`))
})
}
searchForOption('ad et natus qui').click(); // select the matching option
cy.get('.ng-value')
.should('contain', 'ad et natus qui'); // confirm the value
})
Note that recursion can be hard on memory usage, and this code could be optimized a bit.
For most cases you would need cy.get().select like for example:
cy.get('.ng-dropdown-panel-items').select(/test/i)
You can use an each() to loop through the drop down elements and when you find the desired text, make an click().
cy.get('span.ng-option-label.ng-star-inserted').each(($ele) => {
if($ele.text() == 'desired text') {
cy.wrap($ele).click({force: true})
}
})
Try something like below recursive function:
function scrollUntilElementFound(scrollIndex) {
scrollIndex = scrollIndex+10;
if(!cy.get('.ng-dropdown-panel-items').contains(/test/i)){
cy.get('.ng-dropdown-panel-items').scrollTo(scrollIndex);
scrollUntilElementFound(scrollIndex);
} else {
//element found
return;
}
}

Get current sort options when sort changes in vuetify data tables

Our tables pagination and sorting are all done on the server, but I still want to allow the user to click column headers to sort. I need to access the tables sort options when the sorting changes. It seems the events are split
update:sort-by
update:sort-desc
I could create two methods, have the first event set the column (sort-by) value, and then the second method actually trigger the sort. But that sounds horrible and prone to race conditions or future bugs.
It would be better if the sort-by included the desc/asc data as well. I tried creating a ref to the table, but for some reason the properties containing the sort information are empty.
The event update:options contains all the necessary information, but that could fire for reasons other than sorting which isn't ideal either.
So I'm not sure if I'm missing something here. Is there a better way to accomplish this?
<v-data-table
ref="contractItemTable"
:headers="headers"
:items="contracts"
:disable-sort="isLoadingPage"
:server-items-length="tableTotal"
disable-pagination
hide-default-footer
#click:row="navigateToContract"
single-select
#update:sort-by="sortTable"
#update:sort-desc="sortTable"
item-key="id.id">
And the JS
public sortTable(event) {
console.debug(this.$refs.contractItemTable.sortBy);
console.debug(this.$refs.contractItemTable.sortDesc);
}
No matter what I click, the sortBy and sortDesc properties are empty. If I use the options event, the event contains the correct sortBy and sortDesc. But as stated above, not exactly the event I want to use. I have it working, but I have to "ignore" the initial load options event since it's not a valid time for this method to fire.
In data table add attribute:
:sort-desc.sync="sort_desc"
In data:
sort_desc:true,//or what you need by default
In observable:
sort_desc:function(val,_prev){
//do what you need to change sort and refresh
},
You're mostly there (and found your question while trying to sort out the best way to apply sorting to derived/computed column-items myself) ... just need to bind the v-data-table sort-by and sort-desc attributes to data elements so that you can refer to them in your sortTable function:
<v-data-table
...
:items="contracts"
:headers="headers"
:sort-by.sync="sortBy"
:sort-desc.sync="sortDesc"
#update:sort-by="sortTable"
#update:sort-desc="sortTable"
...
>
export default {
data() {
contracts: [],
sortBy: 'contractDate', // make sure this matches headers[].value
sortDesc: false, // and both match your initial from-server sort
...
headers: [
{ text: 'Contract Date', sortable: true, value: 'contractDate' },
{ text: 'Contract Value', sortable: true, value: 'contractValue' }
...
]
},
methods: {
sortTable() {
if(this.sortBy === 'contractDate') {
this.contracts.sort( (a,b) => {
return ( a.contractDate > b.contractDate ? 1 : -1 ) * (this.sortDesc ? -1 : 1 );
});
}
if(this.sortBy === 'contractValue') {
this.contracts.sort( (a,b) => {
return ( a.contractValue > b.contractValue ? 1 : -1 ) * (this.sortDesc ? -1 : 1 );
});
}
}
}
}
Your sort logic obviously will vary, but included the Array.sort() callback example there to show where this.sortDesc is used to reverse the evaluation.
A big caveat to be aware of is that if you do not specify
<v-data-table :must-sort="true" >
Then the bound #update:sort-by and #update:sort-desc events will BOTH fire when sort is deactivated which happens when user clicks the same column header a third time (it cycles through sorted to no-sort states.)
To get around this, add something like
sortPending: false
to your data state, and when the event fires, return immediately out of the event if this.sortPending === true, and move your sort logic into a Vue.nextTick() callback:
methods: {
sortTable() {
if(this.sortPending) return;
this.sortPending = true;
this.$nextTick( () => {
this.sortPending = false;
/* your sort logic here */
});

bootstrap-multiselect rebuild/refresh not working

I have 2 bootstrap-multiselect boxes. I want to refresh the items in one based on the selections made in the other via an Ajax call.
Everything work except that the that the multiselect uses to disoplay the options is not being populated by either 'rebuild' or 'refresh'.
Code:
Decalring the multiselect"
$('#Groups').multiselect({
onChange: function (option, checked) {
//get all of the selected tiems
var values = $('#Groups option:selected');
var selected = "";
$(values).each(function (index, value) {
selected += $(value).prop('value') + ",";
});
selected = selected.substring(0, selected.length - 1);
//update hidden field with comma delimited string for state persistance
$("#Hidden_Groups").val(selected);
},
includeSelectAllOption: true
});
Updating the options list:
$('#JobCodes').multiselect({
onDropdownHide: function(event) {
console.log("start");
var option = $("#Groups");
//clear out old consolidations
option.empty();
console.log("emptied");
////get consolidation and append to select options list
$.getJSON("/Reports/GetGroups/", { options: $("#Hidden_JobCodes").val() }, function (result) {
$.each(result, function (index, item) {
console.log(item.text);
option.append($("<option />").val(item.id).text(item.text));
});
});
option.multiselect('destroy');
option.multiselect('rebuild');
option.multiselect('refresh');
},
Have left the destroy, rebuild and refresh in just as an example things i have tried. Have used all of these in every order and combination and no luck.
Destroy will "change" my multiselect back to a standard select but no matter what I do after that I cannot get the multiselect to come back with the new list of options, including using the full multiselect call wiht the onchange event present. Its either empty of has the old list. The select list has the correct options present when I make the refresh/rebuild calls.
Thanks in advance.
T
Totally my fault!!
The call to /destroy/refresh/rebuild was outside of the ajax call. so the were executing before my new list had been returned, hence no list to rebuild at that time.
Solution:
////get consolidation and append to select options list
$.getJSON("/Reports/GetGroups/", { options: $("#Hidden_JobCodes").val() }, function (result) {
$.each(result, function (index, item) {
console.log(item.text);
option.append($("<option />").val(item.id).text(item.text));
});
option.multiselect('rebuild')
});

How to check if one list menu have a value greater than zero in jquery form validation?

I have more select menus, named menu1, menu2, menu3 etc...
All of them have values from 0 to 10. By default all are on 0.
How do I check with jquery validation plugin that at least one of the menus have a greater than zero value? Because each has a different name (I can give same class, but I do not see how this helps in validation plugin, because I can validate rules against form names not classes).
One solution is to create a custom rule.
I have adapted this answer and added a custom rule called mulitselect. This adds an error to the first dropdown list if none have a value > 0. Check the demo on jsfiddle.
note1 This currently is applied to every select list in the form, change the selector to limit it to certain select lists by class or otherwise.
note2 I added the onclick function after an invalid form has been submitted as adding it in the options resulted in validation that was a little too 'eager'.
js is as follows
$(function () {
$.validator.addMethod("multiselect", function (value, element) {
var countValid = $('select').filter(function () {
return $(this).val() > 0;
}).length;
if (countValid === 0 && $('select:first')[0] === element) {
return false;
}
else {
return true;
}
}, "please select at least one of the menus");
$('select').addClass("multiselect");
$('form').bind("invalid-form", function () {
$('form').validate().settings.onclick = function () { $("form").valid() };
}).validate({
debug: true
});
});

Prevent certain SlickGrid columns from being reordered

The drag/drop column reordering is a great feature, but how do I prevent the users from moving particular (non-data) columns?
For example, I am using the Checkbox Selectors for my mutli-select Grid, but this column should always be locked to the left, while the other columns can be freely reordered.
I looked at the sortable demos on jQuery UI and modified the setupColumnReorder function in slick.grid.js to exclude certain items. By excluding the checkbox column I was able to prevent it from getting reordered, even by dragging other columns before it.
function setupColumnReorder() {
var checkBoxColumn = $headers.children([0]).attr('id');
$headers.sortable({
items: "div:not('.slick-resizable-handle','#"+checkBoxColumn+"')",
...
Since my checkbox column is always first, I just grab the id like so. A bit of a hack, but it worked.
I tried this:
function setupColumnReorder() {
var checkBoxColumn = $headers.children([0]).attr('id');
$headers.sortable({
items: "div:not('.slick-resizable-handle','#"+checkBoxColumn+"')",
...
but i had problems when dragging other columns before it. Then i tried this:
grid.onColumnsReordered.subscribe(function (e, args) {
if (myGridColumns[0].id != grid.getColumns()[0].id)
{
grid.setColumns(myGridColumns);
}
else
{
myGridColumns= grid.getColumns();
}
});
and it was just fine.

Resources