jqGrid dataUrl dropdown list not refreshing - asp.net-mvc

I'm new to web development and working on my first ASP.NET MVC 3 app. I'm using jqGrid and noticed that refreshing the page does not refresh the values in the dropdownlist until I open the same page in another tab, then refreshing the first tab will pick up the changed values.
I've got a controller action that looks like this:
public JsonResult FavoriteToppings()
{
var all = GetFavoriteToppings();
return Json(all, JsonRequestBehavior.AllowGet);
}
and I've the part of the jqGrid definition looks like this:
{ name: 'ToppingID', index: 'ToppingID', width: 200,
editable: true, align: 'left', edittype: 'select', stype: 'select',
editoptions: {
dataUrl: '#Url.Action("FavoriteToppings", "Dessert")',
buildSelect: createSelectList
},
searchoptions: {
dataUrl: '#Url.Action("FavoriteToppings", "Dessert")',
buildSelect: createSelectList,
sopt: ['eq']
}
},
and createSelectList looks like this:
createSelectList = function (data) {
var response, s = '<select>', i, l, ri;
if (typeof (data) === "string") {
//var leng = data.length - 1;
response = jQuery.parseJSON(data);
}
else {
response = jQuery.parseJSON(data.responseText);
s += '<option value="">Select...</option>';
}
if (response && response.length) {
for (i = 0, l = response.length; i < l; i += 1) {
ri = response[i];
s += '<option value="' + ri + '">' + ri + '</option>';
}
}
return s + '</select>';
}
I noticed this when editing one of the topping names. I changed the misspelled "Hot Fugde" to "Hot Fudge" and saved that off. The underlying data in the table gets updated to show the correctly spelled topping (i.e., all the rows correctly reflect the change) when I refresh the page but the filter dropdown doesn't. That action isn't called at all after the first time to pick up the change.
When I do open the same page in a different page of the browser, then the action gets called. After that, refreshing the first tab will result in the correctly spelled entry showing up in the select list.
Perhaps I'm just going about this incorrectly. Any guidance?

I think you have to use
ajaxSelectOptions: { cache: false }
jqGrid parameter to set additional parameter cache: false for the jQuery.ajax used by jqGrid if it get the data from the server from the dataUrl.

I've noticed that cache policies vary from browser to browser (chrome seems to have the most aggressive caching policy). Does the behavior repeat in all browsers? Ctrl-F5 work?

Related

select2 (remote data) throws exception because of typing to fast

I'm using select2 for loading remote data. I declared the minimumInputLength to 3 letters, so after that it will start searching.
Whenever I hit the fourth letter while typing fast I get an Javascript exception saying :
Sorry. An error occured while communicating with the server. Please try again later.
How can I avoid this? I already changed the quietMillis (waitTimesMs) to lower or higher (does this even have something to do with it?).
Every help is appreciate.
My code is like:
$(function () {
$("#Search").select2({
minimumInputLength: 3,
ajax: {
url: site,
dataType: "json",
quietMillis: waitTimeMs,
data: function (params) {
var page = (params.page || 1) - 1;
return {
searchText: params.term,
pageCount: 10,
page: page
};
},
processResults: function (data) {
var select2Data = $.map(data.Items, function (obj) {
obj.id = obj.ID;
obj.text = obj.Name;
return obj;
});
return {
results: select2Data,
pagination: { more: (data.PageNo * 10) < data.TotalCount }
};
}
Finally it works!
select2 changed "quietMillis" to "delay" so I could change quietMillis as big as I wanted and nothing changed at all...

processAjaxOnInit: is set to "false" but Ajax is still called

I'm preloading my table using PHP and then have processAjaxOnInit: false in my config. What happens is that the call to my Ajax url is still made and instead of appending rows to the table it wipes out the rows that are there. I'm assuming that it's still making the call to get the total number of rows. Can I set this on page load and completely bypass calling the Ajax url?
Thanks
.tablesorterPager({
container: $(".pager"),
ajaxUrl : '/documents_table_data.php?page={page}&size={size}&{filterList:filter}&{sortList:column}',
// use this option to manipulate and/or add additional parameters to the ajax url
customAjaxUrl: function(table, url) {
// manipulate the url string as you desire
//url += '&archive=<?php echo $_GET[archive] ?>&wor=<?php echo $_GET[wor] ?>';
// trigger a custom event; if you want
$(table).trigger('changingUrl', url);
// send the server the current page
return url;
},
ajaxError: null,
ajaxObject: {
dataType: 'json'
},
ajaxProcessing: function(data){
if (data && data.hasOwnProperty('rows')) {
var r, row, c, d = data.rows,
total = data.total_rows,
headers = data.headers,
rows = [],
len = d.length;
for ( r=0; r < len; r++ ) {
row = [];
for ( c in d[r] ) {
if (typeof(c) === "string") {
row.push(d[r][c]);
}
}
// is there a way to do that here when it pushes the row onto the array
// or perhaps there is another funtion you have implemented that will let me do that
rows.push(row);
}
return [ total, rows, headers ];
}
},
// Set this option to false if your table data is preloaded into the table, but you are still using ajax
processAjaxOnInit: false,
output: '{startRow} to {endRow} ({totalRows})',
updateArrows: true,
page: 0,
size: 10,
savePages: true,
storageKey: 'tablesorter-pager',
pageReset: 0,
fixedHeight: false,
removeRows: false,
countChildRows: false,
// css class names of pager arrows
cssNext : '.next', // next page arrow
cssPrev : '.prev', // previous page arrow
cssFirst : '.first', // go to first page arrow
cssLast : '.last', // go to last page arrow
cssGoto : '.gotoPage', // page select dropdown - select dropdown that set the "page" option
cssPageDisplay : '.pagedisplay', // location of where the "output" is displayed
cssPageSize : '.pagesize', // page size selector - select dropdown that sets the "size" option
// class added to arrows when at the extremes; see the "updateArrows" option
// (i.e. prev/first arrows are "disabled" when on the first page)
cssDisabled : 'disabled', // Note there is no period "." in front of this class name
cssErrorRow : 'tablesorter-errorRow' // error information row
});
I just added a pager option named initialRows (currently only available in the master branch). When processAjaxOnInit is false and this option is set, no initial ajax call to the server is done (demo):
$(function(){
$('table').tablesorter({
widgets: ['pager'],
widgetOptions : {
pager_processAjaxOnInit: false,
pager_initialRows: {
// these are both set to 50 initially
// the server can return different values
// and the output will update automatically
total: 50,
filtered: 50
},
// other ajax settings...
}
});
});

Full-featured autocomplete widget for Dojo

As of now (Dojo 1.9.2) I haven't been able to find a Dojo autocomplete widget that would satisfy all of the following (typical) requirements:
Only executes a query to the server when a predefined number of characters have been entered (without this, big datasets should not be queried)
Does not require a full REST service on the server, only a URL which can be parametrized with a search term and simply returns JSON objects containing an ID and a label to display (so the data-query to the database can be limited just to the required data fields, not loading full data-entities and use only one field thereafter)
Has a configurable time-delay between the key-releases and the start of the server-query (without this excessive number of queries are fired against the server)
Capable of recognizing when there is no need for a new server-query (since the previously executed query is more generic than the current one would be).
Dropdown-stlye (has GUI elements indicating that this is a selector field)
I have created a draft solution (see below), please advise if you have a simpler, better solution to the above requirements with Dojo > 1.9.
The AutoComplete widget as a Dojo AMD module (placed into /gefc/dijit/AutoComplete.js according to AMD rules):
//
// AutoComplete style widget which works together with an ItemFileReadStore
//
// It will re-query the server whenever necessary.
//
define([
"dojo/_base/declare",
"dijit/form/FilteringSelect"
],
function(declare, _FilteringSelect) {
return declare(
[_FilteringSelect], {
// minimum number of input characters to trigger search
minKeyCount: 2,
// the term for which we have queried the server for the last time
lastServerQueryTerm: null,
// The query URL which will be set on the store when a server query
// is needed
queryURL: null,
//------------------------------------------------------------------------
postCreate: function() {
this.inherited(arguments);
// Setting defaults
if (this.searchDelay == null)
this.searchDelay = 500;
if (this.searchAttr == null)
this.searchAttr = "label";
if (this.autoComplete == null)
this.autoComplete = true;
if (this.minKeyCount == null)
this.minKeyCount = 2;
},
escapeRegExp: function (str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
},
replaceAll: function (find, replace, str) {
return str.replace(new RegExp(this.escapeRegExp(find), 'g'), replace);
},
startsWith: function (longStr, shortStr) {
return (longStr.match("^" + shortStr) == shortStr)
},
// override search method, count the input length
_startSearch: function (/*String*/ key) {
// If there is not enough text entered, we won't start querying
if (!key || key.length < this.minKeyCount) {
this.closeDropDown();
return;
}
// Deciding if the server needs to be queried
var serverQueryNeeded = false;
if (this.lastServerQueryTerm == null)
serverQueryNeeded = true;
else if (!this.startsWith(key, this.lastServerQueryTerm)) {
// the key does not start with the server queryterm
serverQueryNeeded = true;
}
if (serverQueryNeeded) {
// Creating a query url templated with the autocomplete term
var url = this.replaceAll('${autoCompleteTerm}', key, this.queryURL);
this.store.url = url
// We need to close the store in order to allow the FilteringSelect
// to re-open it with the new query term
this.store.close();
this.lastServerQueryTerm = key;
}
// Calling the super start search
this.inherited(arguments);
}
}
);
});
Notes:
I included some string functions to make it standalone, these should go to their proper places in your JS library.
The JavaScript embedded into the page which uses teh AutoComplete widget:
require([
"dojo/ready",
"dojo/data/ItemFileReadStore",
"gefc/dijit/AutoComplete",
"dojo/parser"
],
function(ready, ItemFileReadStore, AutoComplete) {
ready(function() {
// The initially displayed data (current value, possibly null)
// This makes it possible that the widget does not fire a query against
// the server immediately after initialization for getting a label for
// its current value
var dt = null;
<g:if test="${tenantInstance.technicalContact != null}">
dt = {identifier:"id", items:[
{id: "${tenantInstance.technicalContact.id}",
label:"${tenantInstance.technicalContact.name}"
}
]};
</g:if>
// If there is no current value, this will have no data
var partnerStore = new ItemFileReadStore(
{ data: dt,
urlPreventCache: true,
clearOnClose: true
}
);
var partnerSelect = new AutoComplete({
id: "technicalContactAC",
name: "technicalContact.id",
value: "${tenantInstance?.technicalContact?.id}",
displayValue: "${tenantInstance?.technicalContact?.name}",
queryURL: '<g:createLink controller="partner"
action="listForAutoComplete"
absolute="true"/>?term=\$\{autoCompleteTerm\}',
store: partnerStore,
searchAttr: "label",
autoComplete: true
},
"technicalContactAC"
);
})
})
Notes:
This is not standalone JavaScript, but generated with Grails on the server side, thus you see <g:if... and other server-side markup in the code). Replace those sections with your own markup.
<g:createLink will result in something like this after server-side page generation: /Limes/partner/listForAutoComplete?term=${autoCompleteTerm}
As of dojo 1.9, I would start by recommending that you replace your ItemFileReadStore by a store from the dojo/store package.
Then, I think dijit/form/FilteringSelect already has the features you need.
Given your requirement to avoid a server round-trip at the initial page startup, I would setup 2 different stores :
a dojo/store/Memory that would handle your initial data.
a dojo/store/JsonRest that queries your controller on subsequent requests.
Then, to avoid querying the server at each keystroke, set the FilteringSelect's intermediateChanges property to false, and implement your logic in the onChange extension point.
For the requirement of triggering the server call after a delay, implement that in the onChange as well. In the following example I did a simple setTimeout, but you should consider writing a better debounce method. See this blog post and the utility functions of dgrid.
I would do this in your GSP page :
require(["dojo/store/Memory", "dojo/store/JsonRest", "dijit/form/FilteringSelect", "dojo/_base/lang"],
function(Memory, JsonRest, FilteringSelect, lang) {
var initialPartnerStore = undefined;
<g:if test="${tenantInstance.technicalContact != null}">
dt = {identifier:"id", items:[
{id: "${tenantInstance.technicalContact.id}",
label:"${tenantInstance.technicalContact.name}"
}
]};
initialPartnerStore = new Memory({
data : dt
});
</g:if>
var partnerStore = new JsonRest({
target : '<g:createLink controller="partner" action="listForAutoComplete" absolute="true"/>',
});
var queryDelay = 500;
var select = new FilteringSelect({
id: "technicalContactAC",
name: "technicalContact.id",
value: "${tenantInstance?.technicalContact?.id}",
displayValue: "${tenantInstance?.technicalContact?.name}",
store: initialPartnerStore ? initialPartnerStore : partnerStore,
query : { term : ${autoCompleteTerm} },
searchAttr: "label",
autoComplete: true,
intermediateChanges : false,
onChange : function(newValue) {
// Change to the JsonRest store to query the server
if (this.store !== partnerStore) {
this.set("store", partnerStore);
}
// Only query after your desired delay
setTimeout(lang.hitch(this, function(){
this.set('query', { term : newValue }
}), queryDelay);
}
}).startup();
});
This code is untested, but you get the idea...

How to call Modal Dialog from Datatables row - seem to have conflict with Jquery UI

I want to create "CRUD" functions by calling a modal form by clicking on a row in Datatables.
I've been at this for hours traversing through each step of my code and it seems I'm getting a conflict between my JQ-UI and Datatables. I found several examples, including the Datatables example for "live" functions, where you can initialize a table and call a simple jquery function.
I'm using:
code.jquery.com/jquery-1.9.1.js
code.jquery.com/ui/1.10.2/jquery-ui.js
../DataTables-1.9.4/media/js/jquery.dataTables.js
This example will give me the cursor, then makes the table "jump" across the page.
Does anyone have a working example or a fiddle I can experiment with?
function openDialog() {
$("#dialog-modal").dialog({
height: 140,
modal: true
});
}
/* Init DataTables */
$('#example').dataTable();
/* Add events */
$('#example tbody tr').on('click', function () {
$('#example tbody tr').css('cursor', 'pointer');
var sTitle;
var nTds = $('td', this);
var sBrowser = $(nTds[1]).text();
var sGrade = $(nTds[4]).text();
/*
if (sGrade == "A")
sTitle = sBrowser + ' will provide a first class (A) level of CSS support.';
else if (sGrade == "C")
sTitle = sBrowser + ' will provide a core (C) level of CSS support.';
else if (sGrade == "X")
sTitle = sBrowser + ' does not provide CSS support or has a broken implementation. Block CSS.';
else
sTitle = sBrowser + ' will provide an undefined level of CSS support.';
*/
openDialog();
//alert( sTitle )
});
A little sleep and another stab at this yielded a solution that at least solves the Datatable Dialog issue, I'll have to assume that any other issues I was having lies the other add-ins that I included. So to me this is solved.
The answer was 99% in this post - thanks to the author for the great working example.
I modified their link solution, combined with Datatables "live" solution example with variables, and was able to successfully pass data to a working dialog that works with pagination as the previous link explains.
This set up would allow me to create JQuery-UI Modal Forms, pass the ID from mySQL table column, and execute the form that's handing the Server Side PHP CRUD functions I needed.
(I can't take credit for any part of this, other than time spent making sure it worked).
The working example is taken straight from Datatables "live events" example, should be easy to drop in if you remove the sAjaxsource and go with a plain Datatable..
$('#example').dataTable( {
"bProcessing": true,
"bServerSide": true,
"bJQueryUI": true,
"bStateSave": true,
"sPaginationType": "full_numbers",
"sAjaxSource": " /*your data source page here*/ "
} );
/* Add events */
$("body").on("click", "#example tbody tr", function (e) {
e.preventDefault();
var nTds = $('td', this);
//example to show any cell data can be gathered, I used to get my ID from the first coumn in my final code
var sBrowser = $(nTds[1]).text();
var sGrade = $(nTds[4]).text();
var dialogText="The info cell I need was in (col2) as:"+sBrowser+" and in (col5) as:"+sGrade+"" ;
var targetUrl = $(this).attr("href");
$('#table-dialog').dialog({
buttons: {
"Delete": function() {
window.location.href = targetUrl;
},
"Cancel": function() {
$(this).dialog("close");
}
}
});
//simple dialog example here
$('#table-dialog').text(dialogText ).dialog("open");
});

Inserting html on the fly and tabs

I'm building an add-on that uses a browser overlay.xul, which has included js files. The easiest way I found to modify HTML on a webpage was this bit of code I found on the mdn I believe.
const STATE_START = Components.interfaces.nsIWebProgressListener.STATE_START;
const STATE_STOP = Components.interfaces.nsIWebProgressListener.STATE_STOP;
var myListener = {
QueryInterface: function(aIID) {
if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
aIID.equals(Components.interfaces.nsISupports))
return this;
throw Components.results.NS_NOINTERFACE;
},
onStateChange: function(aWebProgress, aRequest, aFlag, aStatus) { },
onLocationChange: function(aProgress, aRequest, aURI) {
PageLoad.initialzed = false;
PageLoad.init();
},
onProgressChange: function(aWebProgress, aRequest, curSelf, maxSelf, curTot, maxTot) { },
onStatusChange: function(aWebProgress, aRequest, aStatus, aMessage) { },
onSecurityChange: function(aWebProgress, aRequest, aState) { }
};
gBrowser.addProgressListener(myListener);
var PageLoad = {
initialzed : false,
browser : null,
domain : null,
appcontent : null,
init : function() {
PageLoad.appcontent = document.getElementById("appcontent");
if (PageLoad.appcontent) {
PageLoad.appcontent.addEventListener("DOMContentLoaded", PageLoad.load, true);
}
},
load : function() {
//I do this so it only calls the function once
if (PageLoad.initialzed == false) {
PageLoad.initialzed = true;
PageLoad.browser = gBrowser.contentDocument;
PageLoad.domain = (PageLoad.browser.location.host.match(/([^.]+)\.\w{2,3}(?:\.\w{2})?$/) || [])[1];
myFunctiontoInsertHTML();
}
}
};
Then I can use PageLoad.browser.createElement('div') and so on in my function.. This works good so far but I have come across a problem that with multiple tabs open, my PageLoad.domain variable contains the domain of the last loaded tab or web page (which in turn causes errors inserting html on 2 or more different pages/tabs). So what I want to change is everytime the user clicks on a different tab or reloads the page I want to call the function but I have run into a dead end.
gBrowser.contentDocument is the document of the page loaded in the current browser tab - as soon as the user switches tabs or navigates to a different page its value changes and you shouldn't keep the old value (it is a memory leak). You are observing the progress of all browser tabs, you should look at the one that you got called for. Meaning in your case: aProgress.DOMWindow (see nsIWebProgress.DOMWindow). That's also where you should attach your DOMContentLoaded event listener if you are interested in the load progress of a single tab.

Resources