I'm loading multiple partial views into the same cshtml page. All goes well until they need to use the scripts. As i'm using code like
var grid = $("#grid").data("kendoGrid");
var selected = grid.selected();
This code works fine with one grid, but starts showing issues when multiple grids are in place. The problem is that "#grid" is a reference to the name of the kendo grid. Is there a way to make this dynamic so it can be used by multiple grids?
I think the same problem would occur when there are multiple grids in the same page as it can't distinct what grid to refer to. Giving the grids different id's would work, but then the code in the script will return an undefined error on grid.selected().
Update:
So the solution of using
var grid = $(".k-grid").data("kendoGrid");
works to a certain point. It loads the data into the grid, but fails to do anything else. For example a part of my code for enabling an update and delete button doesn't work on the 2nd and 3rd partial view.
var grid = $(".k-grid").data("kendoGrid");
var selected = grid.select();
if (selected.length > 0) {
$("#btnCopy,#btnEdit,#btnDelete").removeClass("k-state-disabled");
} else {
$("#btnCopy,#btnEdit,#btnDelete").addClass("k-state-disabled");
}
Somehow the code only starts working for grid 2 and 3 after i have selected a row on grid 1, which is not as intended.
Instead of id(#Grid) you can use class(.k-grid):
var grid = $(".k-grid").data("kendoGrid");
The solution I found with help of a senior programmer is to save the grid data into a global variable like this.
var PartialGridData = PartialGridData || {};
After that I'm setting and changing the variable whenever changing the partial view.
PartialGridData.selectedGrid = $("#PartialGrid1").data("kendoGrid");
Where the name #PartialGrid1 is the name of the current grid.
This means I need to write this code as many times as I have grids, but it also fixes a lot of problems. After that I use it to select the correct data.
var grid = PartialGridData.selectedGrid;
var selected = grid.select();
if (selected.length > 0) {
$("#btnCopy,#btnEdit,#btnDelete").removeClass("k-state-disabled");
} else {
$("#btnCopy,#btnEdit,#btnDelete").addClass("k-state-disabled");
}
Another option would be to use e.sender.
function onRowSelect(e) {
var grid = e.sender;
var selected = grid.select();
if (selected.length > 0) {
$("#btnCopy,#btnEdit,#btnDelete").removeClass("k-state-disabled");
} else {
$("#btnCopy,#btnEdit,#btnDelete").addClass("k-state-disabled");
}
}
Both solutions have their drawbacks though. Not all methods get the variable e used for e.sender and changing partial views in a way that is not caught will cause the global variable not to be updated, so this has to be kept in check.
Related
I'm using a custom Primefaces-based framework to display a datatable, and it looks like that:
<xy:dataTable id="tableId" value="#{lazyTableBean.dates}" var="date">
<xy:column id="nameColumnId">
<xy:outputText id="nameOutputId" value="date.name"/>
</xy:column>
<xy:column id="actionColumnId">
<xy:actionButton id="actionButtonId" label="Button"
action="#{someBean.someAction(date.id)}"/>
</xy:column>
</xy:dataTable>
Now I want to set the tooltip of the button. Since the actionButton component of that framework doesn't have the title attribute, I'm using JavaScript to alter it:
var rows = // getting the table content row components here
// iterating through table rows and setting the button tooltip to the name of the corresponding date
for (const row of rows) {
var myTooltip = row.children.item(0).textContent;
row.children.item(1).firstChild.setAttribute("title", myTooltip);
}
This basically works as it should when I import the JS script at the end of the file.
However, there are several AJAX events (e.g. when sorting or filtering the table, or when using pagination...) that reprint the table content. Since the JS script isn't triggered again, the tooltips aren't set in that case.
Now I've planned to simply import the script at some appropriate place (e.g. inside the component that gets rerendered) so that it's executed whenever the button is rendered. However, I haven't found quite the right place to make it work. When I'm putting it inside the column:
<xy:dataTable id="tableId" value="#{lazyTableBean.dates}" var="date">
<xy:column id="nameColumnId">
<xy:outputText id="nameColumnId" value="date.name"/>
</xy:column>
<xy:column id="actionColumnId">
<xy:actionButton id="actionColumnId" label="Button"
action="#{someBean.someAction(date.id)}"/>
<h:outputScript library="js" name="addTooltipToTableButtons.js" />
</xy:column>
</xy:dataTable>
This results in only the first row to correctly set their tooltip, all other rows keep their generic one. But on AJAX events, the correct behavior takes place, all rows set their tooltip correctly. The same behavior takes place if the script is also imported at the end. I guess this has to do with the table format of dynamically printing a number of rows with the same column components, but this is just guessing.
Putting it inside the table (directly before </xy:dataTable>) results in no script execution at all.
I'm totally new to JavaScript and we're just using this approach until our custom framework supports setting arbitrary attributes. I hope you have an idea (or an explanation why it won't work like that) - thanks in advance!
Greetings
In case anyone's interested in my solution, I used a MutationObserver to handle the events, in addition to the "normal" JS at page load.
The whole JS file looked like that:
var table = ...; // get table by normal means
for (var i = 0, row; row = table.rows[i]; i++) {
var tooltip = row.cells[0].textContent;
row.cells[1].firstChild.setAttribute(tooltip);
}
var observer = new MutationObserver(function( mutations ) {
mutations.forEach(function( mutation ) {
var newNodes = mutation.addedNodes;
if( newNodes !== null ) {
var $nodes = $( newNodes );
$nodes.each(function() {
var tooltip = this.cells[0].textContent;
this.cells[1].firstChild.setAttribute(tooltip);
});
}
});
});
var config = {
attributes: true,
childList: true,
characterData: true
};
observer.observe(table.children.item(1), config);
Whenever a user changes the pagination on a grid, I save the setting in localStorage and retrieve it to set it back whenever the user navigates again to the page. To retrieve it I am using the pageSize property of the dataSource where I pass an IIF like so:
pageSize: function () {
var pageSize = 10;
if (window.localStorage) {
var userPreferencePageSize = localStorage.getItem("somegridPageSize");
if (userPreferencePageSize === parseInt(userPreferencePageSize)) {
userPreferencePageSize = parseInt(userPreferencePageSize);
}
if (!isNaN(userPreferencePageSize)) {
pageSize = userPreferencePageSize;
}
}
return pageSize;
}()
This worked well but a requirement appeared for the user to be able to set the pageSize to "All". Kendo handles "All" in the Grid so I thought this will let me set the dataSource pageSize to the string "All" (pageSize="All") as well. When I do it however the grid starts displaying NaN of X records and displays an empty grid. So the question is.. how do I preset the pageSize of the dataSource to just "All"?
NOTE: An alternative is to just fetch the grid maximum total count and then replace the number displayed in the dropdown with jquery text("All) but that looks like a hack and it seems to me this should already be inbuilt into the framework but I can't find anything in the doc's.
EDIT: This is getting even funnier. Due to lack of other options I implemented it like in the note and just set the dataSource pageSize directly:
$("#Grid").data("kendoGrid").dataSource.pageSize(pageSize);
But this is causing the filters on grid to malfunction and throw "string is not in correct format" (misleading error) error from the endpoint. After enough research I found out its caused by the DataSourceRequest doing some unidentifiable shuru buru in the background. Since setting the dataSource pageSize causes issues, I tried just setting the dropdown programatically and let kendo trigger the request to update pageSize itself like so:
var selectBox = $(".k-pager-sizes").find("select");
selectBox.val(pageSize);
selectBox.find("option[value='" + pageSize + "']").prop('selected', true);
But the grid doesn't let me do it and keeps reverting any changes I do inside the DOM from javascript.
So the question is, how in earth can you change the pageSize of a server-side kendo grid from javascript (triggering an extra request to endpoint).
To answer the question. This appears to be a bug in Kendo. Setting the pageSize to 0 (shortcut for "all") after the dataSource was already bound will always result in the grid having issues with any custom filters declared inside of a toolbar template.
To be exact, if you have a custom filter defined inside of the toolbar template:
#(Html.Kendo().TextBox().Name("Filter").Deferred())
You wire it up through the dataSource definition on the grid like:
.DataSource(ds =>
ds.Ajax()
.PageSize(defaultPageSize)
.Read(a => a.Action(actionName, controllerName, new
{
param = Model.param
}).Data("getFilterParameters")))
in javaScript fetching the parameters like:
getFilterParameters: function () {
return this.filterParameters;
},
populating them with a method:
filter: function () {
var grid = $("#Grid").data("kendoGrid");
this.filterParameters = {
param: $("#Filter").val()
};
grid.dataSource.page(1);
}
that has a simple event listener wired to it:
$("#Filter").on("keyup", filter);
Then after changing the pageSize programatically to 0/"all" with:
$("#Grid").data("kendoGrid").dataSource.pageSize(0);
The filter will start to always return NaN as the current page / skip of the filter object passed to the server with the query parameters (even though grid will display the numbers correctly). This will cause a "string is not in correct format" exception inside framework code on the endpoint whenever you try using the filter. A solution to the above is to slightly modify the getFilterParameters method:
getFilterParameters: function (e) {
// Workaround for Kendo NaN pager values if pageSize is set to All
if (isNaN(e.page)) e.page = 1;
if (isNaN(e.skip)) e.skip = 0;
//
return this.filterParameters;
}
This will re-initialize the page and skip values before submitting the filter request. These values will anyway be re-populated on the endpoint with the correct values. Wasn't me who noticed it but another developer working on the project, credit goes to her.
I am building a meteor based webapp. One of the pages has a drop down menu, which the user has to select options (various school districts) from. The selection needs to be detected, followed by querying the database filtering documents based on the selection and counting the documents returned, followed by rendering a template (a chart built using highcharts.js)
Code as follows:
Template.districtDropdown.events({
'change' : function(event, template){
event.preventDefault();
var selectedValue = template.$("#selectDistrict").val();
console.log("You Selected " + selectedValue);
var filter = {
find: {
'School District' : selectedValue
}
};
Meteor.subscribe('aggByDistrict', filter);
productNames2 = _.uniq(CombinedData.find().map( function(doc) { return doc.Product; }));
console.log(productNames2);
var productValues2 = [];
for(var i = 0; i < productNames2.length; i++) {
productValues2.push(CombinedData.find({'Product' : productNames2[i]}).count())
};
console.log(productValues2);
}
});
I'm facing three issues.
The console on the client side throws an error "productNames2" has not been defined as soon as the page loads, pointing to the line which has the for loop (even before I've made any selection).
The first time I click on one of the options in the drop down menu, I get empty arrays (the two console.log(productNames2) and console.log(productValues2), but when I click on some other option, it works the second time. No idea why.
I want to render a template {{> highchart2}} after the user has selected an option from the drop down and the two arrays (productNames2, productValues2) have been populated.
Can anyone give me ideas on how I can go about resolving these issues?
Several problems with your code
Subscribe needs to be put in Template.districtDropdown.created. If you subscribe during the events, there might be the postpone during the subscription and no data available during the events
event should be attached to a DOM component. As in change #selectDistrict and then, you select the value like this var selectedValue = $(event.target).val();
I am using DataTables.
What I am trying to do is: by using one of the columns values, get page number, where this value is located.
I have tried this: jumpToData()
BUT this didn't work out. The reason is that
var pos = this.column(column, { order: 'current' }).data().indexOf(data);
in jQuery.fn.dataTable.Api.register('page.jumpToData()' returns value >=0 ONLY if I was placed on page where value was.
For example, I want to detect page where needed value is, but I am staying on another page, so to detect value on... page 3, I need to go to this page and only then I can detect it, which makes no sence at all.
What I need to do, is: by staying on pirst page, using value from another pages, detect those pages numbers and then navigate to them:
$('#Grid_grid').DataTable().page(PageNumber).draw(false);
How can I accomplish that?
EDIT:
Got some idea (several changes in jumpToData()):
jQuery.fn.dataTable.Api.register('page.jumpToData()', function (data, column) {
for (var i = 0; i < this.page.info().pages; i++) {
var test = this.page(i).column(column, { order: 'current' }).data().indexOf(data);
if (test >= 0) {
this.page(i).draw(false);
return this;
}
}
return this;
});
(EDIT 2: idea didn't paid off, no difference)
BUT now I got second issue:
None methods of datatable works in .cshtml page.
For example I need to get overall page count. I doing this:
$('#Grid_grid').DataTable().page.info().pages;
and this return me 0;
Meanwhile, putting it in to console (Chrome F12) works fine (returns 5). Whats the matter?
EDIT 3:
Came up with this:
function LoadPage(value) {
var table = $('#Grid_grid').DataTable();
var pageNumber = table.search(value).page();
table.page(pageNumber).draw(false);
}
Looks promising BUT, I still cant validate it because in console DataTable methods are working, but in .cshtml no. (search() or page() returns nothing).
EDIT 4:
Moved issue to another question
CAUSE
Your new API method page.jumpToData() tries to query all pages data because second argument selector-modifier in column() API method has property page: 'all' by default. As written it will always stay on first page.
SOLUTION
There is original page.jumpToData() plug-in posted by Allan Jardine, creator of DataTables. It works as intended and can be used instead of your modification to avoid unnecessary iterations.
$.fn.dataTable.Api.register('page.jumpToData()', function (data, column) {
var pos = this.column(column, {
order: 'current'
}).data().indexOf(data);
if (pos >= 0) {
var page = Math.floor(pos / this.page.info().length);
this.page(page).draw(false);
}
return this;
});
DEMO
See this jsFiddle for code and demonstration.
NOTES
In the demo above I added console.log("Number of pages", table.page.info().pages); just to demonstrate that API method works. However they may work because I have HTML-sourced data.
If you have Ajax-sourced data, you need to query number of pages only when data has been loaded. Use initComplete option to define a callback function that will be called when your table has fully been initialised, data loaded and drawn.
I have a Telerik MVC grid which uses custom binding.
Using javascript I store the filter, page and grouping in a cookie, such that when the user reloads the page, the filtering etc. is restored:
$(document).ready(function () {
//restore previously saved filters
var params = $.deparam($.cookie("MyGridCookie"));
var grid = $("#MyGrid").data("tGrid");
var refresh = false;
if (params.filterBy && params.filterBy != "") { grid.filterBy = params.filterBy; refresh = true; }
if (params.currentPage && params.currentPage != 1) { grid.currentPage = params.currentPage; refresh = true; }
if (params.orderBy && params.orderBy != "") { grid.orderBy = params.orderBy; refresh = true; }
if (refresh) grid.ajaxRequest();
});
I've used the exact same code for grids without custom binding and this works just fine. However, in this particular case, the parameter command.FilterDescriptors is empty. But when I manually input the same filter which is stored in the cookie, the filtering works just fine.
So apparently, when custom binding is enabled, the telerik grid does something different when it comes to filtering compared to a grid without custom binding. Does anyone know how to resolve this issue?
Indeed the problem was with the filters being stored in CompositeFilterDescriptors. Problem solved.
Did you try grid.rebind(); instead of grid.ajaxRequest();