Kendoui Grid and Spring Data Rest ServerSide sorting & pagination - kendo-ui

Had a tough time with this so thought I would ask for a better solution than mine and also be able to post an answer for those it might help.
Problem:
Working with KendoUI JSP Grid. Goal add server side pagination and sorting
I need to be able to have a GET request as follows:
URL Expected by Spring Data Rest
http://localhost:8080/api/accounts?page=1&size=20&sort=firstName,desc -> or asc
URL Presented by default from KendoUI
http://localhost:8080/api/accounts?take=20&skip=0&page=1&pageSize=20&sort%5B0%5D%5Bfield%5D=firstName&sort%5B0%5D%5Bdir%5D=asc
This is for Server Side sorting for a Spring Data Rest project. Currently the Sorting portion is showing up as an array. How do you change the way the sorting URL is formed?

After a fair amount of coding by Google came up with this as a solution. Please correct me if this is not correct.
Within KendoUI Grid you can modify a URL via the parameterMap. Below is how this will format the KendoUI URL to what Spring Data Rest is expecting.
<script>
function parameterMap(data, operation) {
if (operation == "read") {
if (data.sort && data.sort.length > 0) {
var p = {};
if(!data.sort && data.sort.length < 0) {
p = data.page(1);
} else {
p = data.page
}
var values = {};
values["page"] = p;
values["size"] = data.pageSize;
values["sort"] = data.sort[0]['field'] + ',' + data.sort[0]['dir'];
return values;
} else {
var values = {};
values["page"] = data.page;
values["size"] = data.pageSize;
return values;
}
}
}
</script>
The else portion will ensure that pagination will still work. Also setting values["page"] = p; within the sorting portion ensures that when you sort it takes you to the 1st page of the sort.
The problem I still have is if you load the page then navigate to a page then hit sort it takes you to page 1 of the sorted data. If you now navigate to another page in the sorted data set and then choose to sort again it will not take you to page one of the new sort.

Related

Multiple filtering and sort by in Shopify liquid

I am trying to merge storefront filtering and sorting in my custom theme collections page in Shopify.
Both things work, but the 'sort_by' parameters are overwriting the filtering ones when these are multiple.
i.e of how the URL should look once filtering with two parameters (sizes XXS and XL) and sorting by ascending price:
../collections/new-arrivals?filter.v.option.size=XXS&filter.v.option.size=XL&sort_by=price-ascending
But this is what happens when sorting:
../collections/new-arrivals?filter.v.option.size=XXS&sort_by=price-ascending
Second filtering parameter gets overwritten by the sorting one.
Pasting below my code for the JS piece that triggers the sorting behaviour.
// sortby
$(function() {
Shopify.queryParams = {};
if(location.search.length) {
for(var aKeyValue, i = 0, aCouples = location.search.substr(1).split('&'); i < aCouples.length; i++) {
aKeyValue = aCouples[i].split('=');
if (aKeyValue.length > 1) {
Shopify.queryParams[decodeURIComponent(aKeyValue[0])] = decodeURIComponent(aKeyValue[1]);
}
}
}
document.querySelector('.sort-by').addEventListener('change', function(e) {
var value = e.currentTarget.value;
Shopify.queryParams.sort_by = value;
location.search = new URLSearchParams(Shopify.queryParams).toString();
});
})
Has someone ever tried to achieve something like this?
I would appreciate any help.
Thanks in advance,

Dynamic menu configuration section with conditional inputs on Magento custom module

I've followed this tutorial to create a custom dynamic backend configuration with serialized data, and everything is working as expected. yay
But now I want to take another step and only show some inputs when a specific value is selected in a select box. I know that I can use when doing this with system.xml, but how can I accomplish the same thing via code with dynamics serialized tables?
I ended up doing some kind of Javascript workaround to enable/disable a certain input.
function togleSelect(element)
{
var val = element.value;
var name = element.name;
if (val == 0) // select value to be triggered
{
name = name.substr(0, name.lastIndexOf("[")) + "[name_of_my_input]";
var target = document.getElementsByName(name);
target[0].disabled = false;
}
else
{
name = name.substr(0, name.lastIndexOf("[")) + "[name_of_my_input]";
var target = document.getElementsByName(name);
target[0].disabled = true;
}
}
It's not the best solution but it's working.

Getting lightswitch HTML client to load related entities

I am trying to load an entity based on a Query and allow the user to edit it. The entity loads without issues from the query, however it does not load its related entities, leaving detail pickers unfilled when loading the edit screen.
This is the code that I have:
myapp.BrowseCOAMissingHoldingCompanies.VW_ChartOfAccountsWithMissingHoldingCompanies_ItemTap_execute = function (screen) {
var accountName = screen.VW_ChartOfAccountsWithMissingHoldingCompanies.selectedItem.AccountFullName;
return myapp.activeDataWorkspace.Accounting360Data.FindChartOfAccountsMappingByAccountName(accountName)
.execute().then(function (query) {
var coa = query.results[0];
return myapp.showAddEditChartOfAccountsMapping(coa, {
beforeShown: function (addEditScreen) {
addEditScreen.ChartOfAccountsMapping = coa;
},
afterClosed: function () {
screen.VW_ChartOfAccountsWithMissingHoldingCompanies.refresh();
}
});
});
};
Interestingly if I open the browse screen (and nothing else) of that entity type first (which does retrieve the entity), then the related entities load correctly and everything works, but I can't figure out how to make that level of load happen in this code.
One method of tackling this (and to avoid the extra query execution of a follow on refresh) is to use the expand method to include any additional navigation properties as follows:
myapp.BrowseCOAMissingHoldingCompanies.VW_ChartOfAccountsWithMissingHoldingCompanies_ItemTap_execute = function (screen) {
var accountName = screen.VW_ChartOfAccountsWithMissingHoldingCompanies.selectedItem.AccountFullName;
return myapp.activeDataWorkspace.Accounting360Data.FindChartOfAccountsMappingByAccountName(
accountName
).expand(
"RelatedEntity," +
"AnotherRelatedEntity," +
"AnotherRelatedEntity/SubEntity"
).execute().then(function (query) {
var coa = query.results[0];
return myapp.showAddEditChartOfAccountsMapping(coa, {
beforeShown: function (addEditScreen) {
addEditScreen.ChartOfAccountsMapping = coa;
},
afterClosed: function () {
screen.VW_ChartOfAccountsWithMissingHoldingCompanies.refresh();
}
});
});
}
As you've not mentioned the name of your entity's navigational properties, I've used coa.RelatedEntity, coa.AnotherRelatedEntity and coa.AnotherRelatedEntity.SubEntity in the above example.
As covered by LightSwitch's intellisense (in msls-?.?.?-vsdoc.js) this method 'Expands results by including additional navigation properties using an expression defined by the OData $expand system query option' and it accepts a single parameter of 'An OData expand expression (a comma-separated list of names of navigation properties)'.
The reason your forced refresh of coa also populates the navigational properties is that LightSwitch's refresh method implicitly expands all navigation properties (provided you don't specify the navigationPropertyNames parameter when calling the refresh). The following shows the internal implementation of the LightSwitch refresh method (with the implicit expand behaviour executing if the navigationPropertyNames parameter is null):
function refresh(navigationPropertyNames) {
var details = this,
properties = details.properties.all(),
i, l = properties.length,
property,
propertyEntry,
query;
if (details.entityState !== _EntityState.unchanged) {
return WinJS.Promise.as();
}
if (!navigationPropertyNames) {
navigationPropertyNames = [];
for (i = 0; i < l; i++) {
property = properties[i];
propertyEntry = property._entry;
if (isReferenceNavigationProperty(propertyEntry) &&
!isVirtualNavigationProperty(propertyEntry)) {
navigationPropertyNames.push(propertyEntry.serviceName);
}
}
}
query = new _DataServiceQuery(
{
_entitySet: details.entitySet
},
details._.__metadata.uri);
if (navigationPropertyNames.length > 0) {
query = query.expand(navigationPropertyNames.join(","));
}
return query.merge(msls.MergeOption.unchangedOnly).execute();
}
However, if you take the refresh approach, you'll be performing an additional unnecessary query operation.
Entity Framework uses lazy loading by default, so related data will be loaded on demand, but in your case that's too late because the entity is already client-side a that point.
Try using the Include method in your query if you want eager loading.
Calling refresh on the details of the entity seems to do it:
return coa.details.refresh().then(function() {
return myapp.showAddEditChartOfAccountsMapping(coa, {
beforeShown: function (addEditScreen) {
addEditScreen.ChartOfAccountsMapping = coa;
},
afterClosed: function () {
screen.VW_ChartOfAccountsWithMissingHoldingCompanies.refresh();
}
});
});
You should use load method to fetch related data from Server. At this time we don't have any ways to force msls load related data.

How to use JQUERY to filter table rows dynamically using multiple form inputs

I'm displaying a table with multiple rows and columns. I'm using a JQUERY plugin called uiTableFilter which uses a text field input and filters (shows/hides) the table rows based on the input you provide. All you do is specify a column you want to filter on, and it will display only rows that have the text field input in that column. Simple and works fine.
I want to add a SECOND text input field that will help me narrow the results down even further. So, for instance if I had a PETS table and one column was petType and one was petColor -- I could type in CAT into the first text field, to show ALL cats, and then in the 2nd text field, I could type black, and the resulting table would display only rows where BLACK CATS were found. Basically, a subset.
Here is the JQUERY I'm using:
$("#typeFilter").live('keyup', function() {
if ($(this).val().length > 2 || $(this).val().length == 0)
{
var newTable = $('#pets');
$.uiTableFilter( theTable, this.value, "petType" );
}
}) // end typefilter
$("#colorFilter").live('keyup', function() {
if ($(this).val().length > 2 || $(this).val().length == 0)
{
var newTable = $('#pets');
$.uiTableFilter( newTable, this.value, "petColor" );
}
}) // end colorfilter
Problem is, I can use one filter, and it will display the correct subset of table rows, but when I provide input for the other filter, it doesn't seem to recognize the visible table rows that are remaining from the previous column, but instead it appears that it does an entirely new filtering of the original table. If 10 rows are returned after applying one filter, the 2nd filter should only apply to THOSE 10 rows. I've tried LIVE and BIND, but not working.
Can anyone shed some light on where I'm going wrong? Thanks!
The uiTableFilter plugin doesn't support what you're trying to do. A quick look at the source reveals this:
elems.each(function(){
var elem = jQuery(this);
jQuery.uiTableFilter.has_words(getText(elem), words, false)
? matches(elem)
: noMatch(elem);
});
and that expands to (essentially) this:
elems.each(function(){
var elem = jQuery(this);
jQuery.uiTableFilter.has_words(getText(elem), words, false)
? elem.show()
: elem.hide();
});
So all it does is spin through all the rows, .show() those that match, and .hide() those that don't; uiTableSorter doesn't pay attention to the current shown/hidden state of the rows and there's no way to tell it to filter on multiple columns.
If you really need your desired functionality then you can modify the plugin's behavior (the code is pretty small and simple) or just write your own. Here's a stripped down and simplified version that supports multiple filters and is a more conventional jQuery plugin than uiTableFilter:
(function($) {
$.fn.multiFilter = function(filters) {
var $table = $(this);
return $table.find('tbody > tr').each(function() {
var tr = $(this);
// Make it an array to avoid special cases later.
if(!$.isArray(filters))
filters = [ filters ];
howMany = 0;
for(i = 0, f = filters[0]; i < filters.length; f = filters[++i]) {
var index = 0;
$table.find('thead > tr > th').each(function(i) {
if($(this).text() == f.column) {
index = i;
return false;
}
});
var text = tr.find('td:eq(' + index + ')').text();
if(text.toLowerCase().indexOf(f.word.toLowerCase()) != -1)
++howMany;
}
if(howMany == filters.length)
tr.show();
else
tr.hide();
});
};
})(jQuery);
I'll leave error handling and performance as an exercise for the reader, this is just an illustrative example and I wouldn't want to get in the way of your learning. You could wire it up something like this:
$('#type').keyup(function() {
$('#leeLooDallas').multiFilter({ column: 'petType', word: this.value });
});
$('#color').keyup(function() {
$('#leeLooDallas').multiFilter([
{ column: 'petType', word: $('#type').val() },
{ column: 'petColor', word: this.value }
]);
});
And here's a live example (which assumes that you're going to enter something in "type" before "color"): http://jsfiddle.net/ambiguous/hdFDt/1/

How do you model form changes under Spring MVC?

Say you're writing a web page for fruit vendors using Spring MVC's SimpleFormController, version 2.5.6. On this page the vendor can do simple things like change their name or their address. They can also change their inventory based on a drop down list filled with present inventory selections.
When this drop down list selection changes, the entire form changes to match the inventory of what has been selected. So one stock selection may have bananas and pears, another may have melons, blueberries and grapefruit.
Inside each inventory selection is a input field that needs to be propagated back to the database, for the sake of this example let's say that the user enters the number of fruit.
The way this is modeled in the database is that each Stock name is stored in a table, which has a one to many relationship with the contents of each stock, which would be the type of fruit in this example. Then the type of fruit has a one to many relationship with the quantity the vendor selects. Stock name and the type of fruit in each stock are stored in the database and are unchangeable by the user, with the connected fruit quantity table being editable.
My question is, how do you model the form described above in Spring MVC?
I've tried overriding the isFormChangeRequest and onFormChange to facilitate the form change, but I think I may be misunderstanding the intent of these methods. When I change my backing command object the next time the page is post it tries to bind the request into the form, which breaks if you adjust the size of the Stock array (say from 3 to 2, it will try and bind into the 3rd value, even if it is empty).
If you have a limited amount of different stocks, you can use different handler mappings for each one with a different backing model:
#RequestMapping(params="stock=example1")
ModelAndView handleExample1(#ModelAttribute("stock") ApplesOrangesPears stockObject)
#RequestMapping(params="stock=example2")
ModelAndView handleExample2(#ModelAttribute("stock") BananasPotatos stockObject)
But I guess that is not the case, there are a lot of different stock types and they are dynamic. In that case you can register custom property editor (#InitBinder), and determine dynamically the actual type of the backing object for the inventory, then validate, and convert to or from it explicitly.
What I ended up doing is firing a JavaScript event when the selection in the drop down is changed. This JavaScript (seen below) generates a URL based on the selection of the drop down and uses a location.replace to go to the new URL, which causes the controller to generate a new form.
Using this method over overriding the isFormChangeRequest and onFormChange has allowed me to avoid binding errors caused by left over post data.
function changeUrl(selectionValue) {
var param = getParams();
param["dropdownselection"] = selectionValue;
window.location.replace(getBaseUrl() + buildQueryString(param));
}
//taken from http://javascript.about.com/library/blqs1.htm
function getParams() {
var qsParm = new Array();
var query = window.location.search.substring(1);
var parms = query.split('&');
for (var i = 0; i < parms.length; i++) {
var pos = parms[i].indexOf('=');
if (pos > 0) {
var key = parms[i].substring(0,pos);
var val = parms[i].substring(pos+1);
qsParm[key] = val;
}
}
return qsParm;
}
function getBaseUrl() {
var url = document.location.toString();
if (url.indexOf('?') != -1) {
url = url.substring(0, url.indexOf('?'));
}
return url;
}
function buildQueryString(param) {
var queryString = "?";
for (var key in param) {
queryString += key + "=" + param[key] + "&";
}
//remove last "&"
return queryString.substring(0,queryString.length - 1);
}

Resources