I have the following HTML code:
<table class="viewTable">
<tr>
<td>Price</td>
</tr>
</table>
and I want to insert data dynamically using Javascript as follows:
var totalPrice = 0;
map.each(function(key , value , i) {
params = {};
params.id = key;
// get datas from Controller class via ajax
ajax(url, params, false, function(result) {
totalPrice += setData(result , key , value);
});
});
// alert("something!"); // this may satisfy my problem.. I have no idea..
// Total Price shown on last row
$('table.viewTable tr:last').after("<tr class='title_bar'><td colspan='5' style='text-align: right;padding-right: 35px;'>"+num2Currency(totalPrice)+"</td></tr>");
The setData function is:
function setData(result , partsId , count) {
var price = result.price;
html = [];
html.push("<tr>");
html.push("<td><div>"+price+"</div></td>");
html.push("</tr>");
$('table.viewTable').append(html.join(''));
return price;}
I used the map function from Jade's answer to this question: Map in JavaScript.
My problem is either the displaying of the results or the procedure itself isn't correct. It should be inserting price rows first and then the totalPrice row afterwards; instead, the order is reversed, with totalPrice appearing first followed by the price rows. When I inserted an alert statement before insertion of totalPrice, it worked fine. Any suggestions? What's wrong with my code? Is jQuery compiled asynchronously?
Jep. Ajax calls are asynchronous, which means they don't execute immediately. You will need to keep track of the amount of completed ajax calls, and when all of them are done you can append the total.
Something like this:
var totalPrice = 0;
var completedAjaxCalls = 0;
map.each(function(key , value , i) {
params = {};
params.id = key;
// get datas from Controller class via ajax
ajax(url, params, false, function(result) {
totalPrice += setData(result , key , value);
completedAjaxCalls += 1;
if(completedAjaxCalls == map.length) {
$('table.viewTable tr:last').after("<tr class='title_bar'><td colspan='5' style='text-align: right;padding-right: 35px;'>"+num2Currency(totalPrice)+"</td></tr>");
}
});
});
Edit: there are probably better ways of achieving this, but since you still need to grasp the concept of asynchronous methods, I thought a simple approach was appropriate here.
You need to try
var totalPrice = 0;
var requests = [];
map.each(function(key, value, i) {
params = {};
params.id = key;
// get datas from Controller class via ajax
// make sure that `ajax()` return the promise returned by $.ajax()
requests.push(ajax(url, params, false, function(result) {
totalPrice += setData(result, key, value);
}));
});
$.when.apply($, requests).done(function() {
// alert("something!"); // this may satisfy my problem.. I have no idea..
// Total Price show at last row
$('table.viewTable tr:last')
.after("<tr class='title_bar'><td colspan='5' style='text-align: right;padding-right: 35px;'>"
+ num2Currency(totalPrice) + "</td></tr>");
})
Related
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...
$("#username").autocomplete({
target: $('#usersss'),
source: function() {
var db = decoara.webdb.db;
db.transaction(function(tx) {
tx.executeSql("SELECT codigo as value, fantasia as label, tabela FROM clientes WHERE fantasia LIKE '%"+$("#username").val()+"%' LIMIT 4",null,
function (tx, results) {
jsonClientes = '[';
console.log('Rows:'+results.rows.length);
for (i=0; i<results.rows.length; i++) {
row = results.rows.item(i);
jsonClientes += '{"value":'+row['value']+',"label":"'+row['label']+'"}';
if (i!=results.rows.length-1)
jsonClientes += ',';
}
jsonClientes += ']';
console.log(jsonClientes);
return $.parseJSON(jsonClientes);
},function (tx, e) {
console.log('error: ' + e.message);
}
);
});
},
link: 'xxx.html?ops=',
minLength: 1
});
The source's function return this json code:
[{"value":27,"label":"NARDIM"},{"value":38,"label":"MORO"},{"value":39,"label":"MH"},{"value":40,"label":"IRMAO SAPIENZA"}]
But the list-view just don't appear. When I change the source to a var using the same jSON data, it works fine:
var autocompleteData = $.parseJSON('[{"value":27,"label":"NARDIM"},{"value":38,"label":"MORO"},{"value":39,"label":"MH"},{"value":40,"label":"IRMAO SAPIENZA"}]');
source: autocompleteData,
Why is that?
Try declaring the jsonClientes as an array and then use .push to store values inside it.
i.e
var jsonClientes = [];
console.log('Rows:'+results.rows.length);
for (i=0; i<results.rows.length; i++) {
row = results.rows.item(i);
jsonClientes.push ('{"value:"'+row.value+'",label:"'+row.label+'"},');
}
The database stuff happens asyncronous. When the .autocomplete() gets called, the function on the "source" gets called and in there a database query is executed. But this database query tx.executeSql() will finish at any time in the future (async) and the "source" of the autocomplete will not have the result.
Unfortunately I have not found out how to solve this for those kind of jquery addons, so I went ahead to build an autocomplete around my specific database query. Basically, I start with the database query and provide a callback for the tx.executeSql() that will fill my autocomplete dropdown.
I loop through an ajax recordset and insert rows into an html5 database.
In Google Chrome, the program inserts 581 rows, whereas on the iPad, it only inserts between 20 and 80 rows.
I output the commands to the document body just to make sure they are being run, so I know there are 581 insert statements being run on the iPad, but then the table only has a handful.
OK, here's how I do it.
I first drop the table, then when that's done, I create the table.
Then when that's done, I do my ajax call.
When that comes back, I loop through the recordset and insert into the html5 local database.
var DropTableiUsr = function() {
var DropTableDeferred = new $.Deferred();
var CreateTableDeferred = new $.Deferred();
dbo.transaction(function(myTrans) {
myTrans.executeSql(
'drop table iUsr;'
,[]
,DropTableDeferred.resolve()
);
});
DropTableDeferred.done(function() {
dbo.transaction(function(myTrans) {
myTrans.executeSql(
'CREATE TABLE IF NOT EXISTS iUsr'
+ '(UsrID Integer NOT NULL PRIMARY KEY'
+ ',UsrGradeDate Varchar(128)'
+ ');'
,[]
,CreateTableDeferred.resolve()
);
});
});
CreateTableDeferred.done(function() {
var settings = {};
settings.data = {};
settings.data.method = 'View0';
var myPromise = $(this).myAjax('com/Usr.cfc', settings); // 'this' normally points to the DOM element that is the context of what caused the Ajax call.
myPromise.done(function(result) {
if (result.RTN) {
var qryUsr = result.qry.DATA;
qryUsr.RecordCount = result.qry.ROWCOUNT;
// qryUsr.ColumnList = result.qry.COLUMNS;
for (var CurrentRow=0;CurrentRow < qryUsr.RecordCount;CurrentRow++) {
myFunction(CurrentRow);
};
function myFunction(CurrentRow) {
$('body').append('INSERT INTO iUsr(UsrID,UsrGradeDate) VALUES(' + qryUsr.USRID[CurrentRow] + ',' + qryUsr.USRGRADEDATE[CurrentRow] + ')<br>');
dbo.transaction(function(myTrans) {
myTrans.executeSql(
'INSERT INTO iUsr(UsrID,UsrGradeDate) VALUES(?,?)',
[
qryUsr.USRID[CurrentRow],
qryUsr.USRGRADEDATE[CurrentRow]
]
)
});
};
} else {
$('#msg').text(result.MSG);
}
});
myPromise.fail(function(jqXHR, textStatus,C ) {
alert('PopulateiUsr: ' + C);
$('.container').append(jqXHR.responseText);
})
$('body').append('iUsr<br>');
});
};
$('#Reset').click(function() {
DropTableiUsr();
});
If you move the screen around while something is processing, it stops the processing.
Try displaying a countdown on the INSERT INTO callback, and you will see that it stops the countdown if you scroll the screen down.
hi i am also facing the data error problem. i wanted to filter the data coming from json result from the groovy controller in key:value pair. even if i chage the TYPE_JSON to TYPE_JSARRAY,i am getting NoRecordas found in data table but the json result has the data.
please can u correct me.
Thanks in advance!!
dobMenuButton.subscribe("selectedMenuItemChange",function(e) {
var value =e.newValue.value;
if(YAHOO.lang.isValue(value)) {
myDataTable.getDataSource().sendRequest(null, {
success:function(request, response, payload) {
this.initializeTable();
var rs = response.results;
var filtered = [];
for(var i = 0; i < rs.length; i++) {
if(((rs[i].dateOfBirth).format("MM/dd/yyyy")) == value) {
filtered[filtered.length] = rs[i];
}
}
this.getRecordSet().reset();
MCMPagination.paginatorvar.setTotalRecords(filtered.length,true);
this.getRecordSet().setRecords(filtered, 0);
this.render();
},
scope:myDataTable,
argument:myDataTable.getState()
});
}
});
I am using JQuery UI Autocomplete in my JSP. Whenever user keys character, i made the request to server and get the data as JSON and load with the pulgin.
It's working fine. But Whenever i typed the same character as previous term. It's not populating any values.
For eg., First i typed p, it lists the p starting elements. I have the button to reset the text content of autocompleter. After reset, if i am typing same character p, it doesn't show anything.
my code as follows,
var cache = {};
$("#Name").autocomplete({
source: function(req, add){
if (req.term in cache) {
add(cache[req.term]);
return;
}
$.getJSON("/store/StockManagement?action=getMedicinesStock",req, function(data) {
var medicines = [];
$.each(data, function(i, val){
medicines.push(val.name + "," + val.code);
});
cache[req.term] = medicines;
add(medicines);
});
},select: function(e, ui) {
var medicine = ui.item.value;
$('#Code').val(medicine.split(",")[1]);
setTimeout(function(){
var med = $('#Name').val();
$('#Name').val(med.split(",")[0]);
},500);
}
});
// Taken from jquery-ui-1.8.4.custom.min.js
if (a.term != a.element.val()) { // *** THE MATCH IS HERE
//console.log("a.term != a.element.val(): "+a.term+", "+a.element.val());
a.selectedItem = null;
a.search(null, c) // *** SEARCH IS TRIGGERED HERE
}
I just commented the condition. now it's works fine.