Multidimensional Arrays and jQuery's getJSON - javascript

I am submitted a getJSON request to a controller in my application, this controller is returning valid JSON with 2 "applications." I know this for a fact as if I move the alert statement to within jQuery's each function it will give me the expected result.
I am attempting to store this data within a multidimensional array to later be used with extJS' Menu Control.
Code:
Ext.onReady(function() {
var applicationList = [];
jQuery.getJSON('index.php/applications', function(data) {
jQuery.each(data.applications, function (i, app) {
applicationList[i] = [];
applicationList[i]['text'] = app['title'];
applicationList[i]['id'] = app['slug'];
});
});
alert(applicationList[0]['text']);
var applicationMenu = Ext.menu.Menu({
items: applicationList
});
});
JSON Response:
{"applications":[{"slug":"test","title":"Test"},{"slug":"hardware","title":"Hardware"}]}
Expected Result:
Test
Actual Result (from Firebug):
applicationList[0] is undefined
If I replace the alert() above, with the following code I get one alert window with the text "remove":
for (p in applicationList) {
alert(p);
}
Now, my thinking is that the JSON request isn't completing in-time for the alert() so I'll use a named callback function to ensure the request has completed:
var data;
jQuery.getJSON('index.php/applications', get_applications(data));
function get_applications(data) {
jQuery.each(data.applications, function (i, app) {
applicationList[i] = [];
applicationList[i]['text'] = app['title'];
applicationList[i]['id'] = app['slug'];
});
};
But now Firebug is telling me that data is undefined...
I feel like I am almost there, but I've been almost there for the past hour and I feel as if I am just polluting the source now in trying to get it to work.

This should do it:
Ext.onReady(function() {
var applicationList = [];
var applicationMenu;
jQuery.getJSON('index.php/applications', function(data) {
jQuery.each(data.applications, function (i, app) {
applicationList[i] = [];
applicationList[i]['text'] = app['title'];
applicationList[i]['id'] = app['slug'];
});
applicationMenu = Ext.menu.Menu({
items: applicationList
});
});
});
Your thinking is right; the reason it is not working is because AJAX is an asynchronous process and as you fire off the getJSON function javascript keeps on trucking along. Your solution doesn't work because making it a named callback is not changing the fact that it won't be fired until you've already tried to initialize the Menu. All my solution is doing is moving the Menu initilization code INSIDE the callback, as it is then and only then that you will have access to the filled out applicationList.

You're right, you should use your "applicationList" variable only after the getJSON callback has finished.
You should call Ext.menu.Menu() inside your getJSON callback, after jQuery.each().

Paolo Bergantino's solution should work, but you can also do it your way, using a named callback function -- you just made some small errors in your code:
data should not be declared as a variable -- data in "function get_applications(data)" just stands for whatever .getJSON returns
The callback function in .getJSON should just be the name of the function, without (data)
Here's the corrected code:
jQuery.getJSON('index.php/applications', get_applications);
function get_applications(data) {
jQuery.each(data.applications, function (i, app) {
applicationList[i] = [];
applicationList[i]['text'] = app['title'];
applicationList[i]['id'] = app['slug'];
});
};

$.getJSON("getq2.php", {
yr: year,
typ: type
}, function(data) {
var items1 = new Array();
var j = 0;
for (var i in data) {
var items = new Array();
items.push(data[i][0], Number(data[i][1]));
items1[j++] = items;
}
console.log(items1);
var plot1 = jQuery.jqplot('chartContainer', [items1], {
seriesDefaults: {
// Make this a pie chart.
renderer: jQuery.jqplot.PieRenderer,
rendererOptions: {
startAngle: 180,
sliceMargin: 2,
dataLabelThreshold: 2,
// Put data labels on the pie slices.
// By default, labels show the percentage of the slice.
showDataLabels: true,
}
},
grid: {
borderWidth: 0,
shadow: false,
background: '#d8d6cb'
},
legend: {
placement: 'outside',
show: true,
location: 'e',
background: 'transparent',
marginRight: '30px'
}
});
});

Related

I want to delete multiple CRM records from Dynamics with Xrm.WebApi.online.executeMultiple

Multiple entity records have to be deleted in one call instead of multiple callbacks so trying to use Xrm.WebApi.online.executeMultiple to delete records. but the code written below is not working. Any help will be appreciated.
for (var i=0; i<Checkbox.length; i++)
{
if(Checkbox[i].checked)
{
var id = Checkbox[i].value;// GUID of the record to be deleted
Checkbox[i].checked = false;
DeleteRequests[i]={};
DeleteRequests[i].getMetadata = function(){
return{
boundParameter: undefined,
operationType: 2,
operationName: "Delete",
parameterTypes: {
}
}
}
DeleteRequests[i].etn="cme_entity";
DeleteRequests[i].payload=id;
}
}
window.parent.Xrm.WebApi.online.executeMultiple(DeleteRequests).then(
function (results) {alert("Success");},
function (error) {alert("Failed");});
Getting weird error that this operation could not be processed. Please contact Microsoft.
The issue has to do with how you are constructing the delete request objects. You need to declare a function that sets up the getMetadata function and the required entityReference object.
I've tested the below solution and it works.
var Sdk = window.Sdk || {};
Sdk.DeleteRequest = function (entityReference) {
this.entityReference = entityReference;
this.getMetadata = function () {
return {
boundParameter: null,
parameterTypes: {},
operationType: 2,
operationName: "Delete",
};
};
};
for (var i = 0; i < Checkbox.length; i++) {
if (Checkbox[i].checked) {
var id = Checkbox[i].value;
Checkbox[i].checked = false;
DeleteRequests[i] = new Sdk.DeleteRequest({ entityType: "account", id: id });
}
}
window.parent.Xrm.WebApi.online.executeMultiple(DeleteRequests).then(
function (results) { alert("Success"); },
function (error) { alert("Failed"); });
Unfortunately CRUD operations with Xrm.WebApi.online.execute and Xrm.WebApi.online.executeMultiple are not very well documented. I've written a blog post with some code samples.
The important parts are the declaration of the Sdk.DeleteRequest function as a property on window and instantiating a request object using new Sdk.DeleteRequest(). I experimented a little and determined that just simply creating a request object like you were doing before, even with the right attributes does not work either.
Hope this helps! :)

Javascript return function in another function

I have a javascript which is like this
function one (var1, var2){
var var3= var1+var2;
return var3;
}
function two( var4, var5){
var var6=var4*var5;
return var6;
}
function three(var7, var8){
var9=var7/var8;
return var9;
}
var first = one(2, 4);
var second= two(first, 8);
var third= three(second, 9);
I want all the function to be separate because they each are like module which would handle different operations. So I do not want to use callback, I have tried
$.ajax(first = one(2,4))
.then({second=two(first, 8)})
.then(three(second,9))
I have also tried this
$.ajax()
.then(function(first=one(2,4){
return first;
})
.then(function(second=two(first,4){
return second;
})
I have also tried this
$.ajax({
first:one(2,4),
second:two(first,4),
third:three(second,9),
})
.then(function(first=one(2,4){
return first;
})
.then(function(second=two(first,4){
return second;
})
this may sound funny but I even tried
var third= three(two(one(2, 4), 8), 9);
amongst many others.
All of them show operation takes place with the first function and other functions but the it does not give me result from previous function
This is an update to the above.
Modification
I do not want to use promise in the other functions as some other functions would also call those functions and they would not be expecting a promise but a valid data
function modifyData(details){
for(var x=0 ;x<array.length;x++){
//this would do a massive loop which would
//return a json string that would be
}
}
function tableFunction(url, tableNameVar){
//json fields
var details=[];
details[0] = 'name';
details[1] = 'sex';
details[2] = 'eye_color';
details[3] = 'height';
details[4] = 'body_built';
var jsonData='';
var main = "";
$(document).ready(function(){
$('#'+tableNameVar+'').dataTable({
destroy: true,
data: JSON.parse(extraFunctionProcess(modifyData(details),details)),
columns:[
{title: "Name", data:"name"} ,
{title: "Gender", data:"sex"} ,
{title: "Eye color", data:"eye_color"} ,
{title: "Height", data:"height"} ,
{title: "Body built", data:"body_built"}
]
});
});
}
I want to process the data and put it inside the table. The extraFunctionProcess has worked if the data is passed
straight into the data field without putting it inside the modifyData function.
which is instead of the above it is
data: JSON.parse(extraFunctionProcess(fullData,details))
This has worked but due to the modification which I have done, it has brought up an error in my browser console which is
undefined inside the extraFunctionProcess.
I just shortened the code into I put online now.
I have actually found a solution to the problem. The modifyData function was the one with the problem. The return was inside another function.
Intead of the following
function modifyData(details){
for(var x=0 ;x<array.length;x++){
//this would do a massive loop which would
//return a json string that would be inside a function
}
}
function modifyData(details){
for(var x=0 ;x<array.length;x++){
return functionName(callback to be done);
//this would do a massive loop which would
//return a json string that would be
}
}

Waiting to initialize untill data is loaded asynchronously

I am trying to design a personal app which loads data asynchronously and then displays a grid according to the windows 8.1 store apps.
i'm running into the issue that my ui is trying to execute before my data is loaded.
my current code:
(function () {
"use strict";
var asyncInProgress = true;
var groupedItems;
var list;
var observable;
var matches = new WinJS.Binding.List();
var matchGroups = new WinJS.Binding.List();
var BattleGrounds = new WinJS.Binding.List();
list = getData();
initGroups(list);
function initGroups(l) {
var groupedItems = list.createGrouped(
function groupKeySelector(item) { return item.group.key; },
function groupDataSelector(item) { return item.group; }
);
}
WinJS.Namespace.define("Data", {
Observable: WinJS.Class.define(function () {
this.dispatch = function () {
this.dispatchEvent("dataReady");
}
}),
getObservable: getObservable,
items: groupedItems,
groups: groupedItems.groups,
getItemReference: getItemReference,
getItemsFromGroup: getItemsFromGroup,
resolveGroupReference: resolveGroupReference,
resolveItemReference: resolveItemReference,
updateData: updateData,
getAsyncStatus: getAsyncStatus
});
WinJS.Class.mix(Data.Observable, WinJS.Utilities.eventMixin);
WinJS.Class.mix(Data.Observable, WinJS.Utilities.createEventProperties("dataReady"));
// Provides support for event listeners.
function getObservable() {
observable = new Data.Observable();
return observable;
}
// Get a reference for an item, using the group key and item title as a
// unique reference to the item that can be easily serialized.
function getItemReference(item) {
return [item.group.key, item.title, item.backgroundImage];
}
// This function returns a WinJS.Binding.List containing only the items
// that belong to the provided group.
function getItemsFromGroup(group) {
return list.createFiltered(function (item) { return item.group.key === group.key; });
}
// Get the unique group corresponding to the provided group key.
function resolveGroupReference(key) {
return groupedItems.groups.getItemFromKey(key).data;
}
// Get a unique item from the provided string array, which should contain a
// group key and an item title.
function resolveItemReference(reference) {
for (var i = 0; i < groupedItems.length; i++) {
var item = groupedItems.getAt(i);
if (item.group.key === reference[0] && item.title === reference[1]) {
return item;
}
}
}
function updateData() {
asyncInProgress = true;
BattleGrounds.splice(0, matches.length);
BattleGrounds._currentKey = 0;
groupedItems = null;
list = getData();
initGroups(list);
}
function getAsyncStatus() {
return asyncInProgress;
}
function getData() {
var darkGray = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXY3B0cPoPAANMAcOba1BlAAAAAElFTkSuQmCC";
var lightGray = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXY7h4+cp/AAhpA3h+ANDKAAAAAElFTkSuQmCC";
var mediumGray = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXY5g8dcZ/AAY/AsAlWFQ+AAAAAElFTkSuQmCC";
var url = 'https://api.guildwars2.com/v1/wvw/matches.json';
acquireSyndication(url).then(function (response) {
// Remove any invalid characters from JSONp response.
var fixedResponse = response.responseText.replace(/\\'/g, "'");
var jsonObj = JSON.parse(fixedResponse);
jsonObj.wvw_matches.forEach(function (battle) {
var anet_id = value.wvw_match_id;
// Create Group
var matchGroup = {
key: anet_id,
title: anet_id
};
matchGroups.push(matchGroup);
// Get Details
acquireSyndication("https://api.guildwars2.com/v1/wvw/match_details.json?match_id=" + anet_id).then(function (json) {
var fixedJson = json.responseText.replace(/\\'/g, "'");
var obj = JSON.parse(fixedJson);
fixedJson.maps.forEach(function (value) {
BattleGrounds.push({
group: matchGroup, key: matchGroup.title, title: value.type,
subtitle: value.type, map: "eb", description: "NA", content: "NA", "type": value.type,
"scores": value.scores, "objectives": value.objectives, "bonuses": value.bonuses, backgroundImage: lightGray
});
});
}, function (error) {
var x = error.getAllResponseHeaders();
var matchGroup = matchGroups[0];
for (var i = 0; i < matchGroups.length; i++) {
flickrPosts.push({
group: matchGroups[i], key: matchGroup.title, title: "Error loading",
subtitle: "Error", backgroundImage: lightGray, published: "N/A", description: "N/A"
});
}
asyncInProgress = false;
observable.dispatch();
});
});
}, function (error) {
var x = error.getAllResponseHeaders();
var matchGroup = matchGroups[0];
for (var i = 0; i < matchGroups.length; i++) {
flickrPosts.push({
group: matchGroups[i], key: matchGroup.title, title: "Error loading",
subtitle: "Error", backgroundImage: lightGray, published: "N/A", description: "N/A"
});
}
asyncInProgress = false;
observable.dispatch();
});
return BattleGrounds;
}
function acquireSyndication(url) {
return WinJS.xhr({
url: url,
headers: { "If-Modified-Since": "Mon, 27 Mar 1972 00:00:00 GMT" }
});
}
})();
This errors out on groups: groupedItems.groups. which says that groups is undefined.
i know this is because the data is still being processed.
How am i going to work around this?
i took a look at the promise object but the entire concept confuses me as i don't know enough about the infrastructure of a windows 8 app.
The core of your problem is in the getData() function - it is not returning your data because it uses asynchronous calls to get the data. The data is not yet available when it returns. It appears that that function makes several asynchronous calls to get data (using acquireSyndication()). When those asynchronous functions finish sometime in the future, you then put that data into matchGroups and then later into BattleGrounds after more calls to acquireSyndication().
What you're doing is quite messy so there isn't a simple fix. Conceptually, you need to process the BattleGrounds data from the completion handler of the asynchronous code and ALL code that uses it must continue from inside that completion handler, not after the getData() call. You cannot call getData() and use it like a synchronous function because it's asynchronous. This requires asynchronous programming techniques.
If you are doing multiple asynchronous calls and trying to carry out some action after all of them have completed (which I think is what you're doing), then you will need to code specifically for that condition too. You can either use promises or you can keep a counter of how many ajax calls there are and in each completion function, you increment the counter and see if this is the last one that just completed and, if so, then you can process all the data and continue executing the rest of your code.
I would also suggest that you don't use promises in one part of a function and then completion callbacks in the very next part. Use one of the other, not a mixture, to keep your code clean.

jqplot: How to use a JSON Data Renderer to Add 2 different lines to a graph

I am trying to write a renderer function as a test for JSON. The example code for jqplot works fine for a single line, but I want to be able to replace my existing data (4 different plots, 2 lines, 2 bar charts) with JSON loading.
An example of the test render functions are below (simplified to return COS/SIN data to test charting).
var SampleLine = function()
{
var data=[[]];
for(var i=0; i<13; i+=0.5)
{
data[0].push([i, Math.sin(i)]);
}
return data;
};
var SecondLine = function()
{
var data=[[]];
for(var i=0; i<13; i+=0.5)
{
data[0].push([i, Math.cos(i)]);
}
return data;
};
var plot3 = $.jqplot('chartdiv', [],
{
title:'JSON Test',
dataRenderer: SecondLine,
axes: {
xaxis: {
renderer: $.jqplot.CategoryAxisRenderer,
label:'Date',
labelRenderer: $.jqplot.CanvasAxisLabelRenderer,
labelOptions: {
fontFamily: 'Georgia, Serif',
fontSize: '12pt'
},
},
yaxis:{
label:'Units',
labelRenderer: $.jqplot.CanvasAxisLabelRenderer,
labelOptions: {
fontFamily: 'Georgia, Serif',
fontSize: '12pt'
},
}
},
series:[
{
showMarker:true,
markerOptions: { style:'circle' },
},
{
renderer:$.jqplot.BarRenderer,
},
{
renderer:$.jqplot.BarRenderer,
},
{
showMarker:true,
markerOptions: { style:'square' },
},
],
}
);
My question is how to add the second dataRenderer as I need data from different sources to combine the lines and bars on a graph. Hard setting the arrays works, but I am trying to do this with AJAX/JSON to get data from my DB, and other sources.
I don't believe there is a way to add multiple dataRenderer(s) but why not just use one dataRenderer to retrieve each series and then pass it back to jqPlot -
If you weren't using a dataRenderer you'd pass the 4 data series in like this -
$.jqplot('chartdiv', [dataSeries1, dataSeries2, dataSeries3, dataSeries4], {
So use your dataRenderer function to retrieve each data series, set each to an array and then pass the whole set back as an array -
var retrieveData = function()
{
var dataSeries1=[];
var dataSeries2=[];
var dataSeries3=[];
var dataSeries4=[];
var returnData=[];
//Populate each data array with values
for(var i=0; i<13; i+=0.5)
{
dataSeries1[0].push([i, Math.cos(i)]);
}
//Populate dataSeries2, dataSeries3, dataSeries4
......
//Add all to returnData array then return it
return returnData;
};
Hope this is helpful.
I don't know what is dataRenderer, and JQPlot doc also.
You can add or remove series by playing with plot1.series array.
Here is a good jsfiddle : jsfiddle.net/fracu/HrZcj
The idea is to create an array with data
for(var i=0 ; i<50 ; i++)
{
myNewSerie = Array();
x = (new Date()).getTime();
y = Math.floor(Math.random() * 100);
myNewSerie.push([x, y]);
}
Then add it to the graph using the next available slot
plot1.series[plot1.series.length] = myNewSerie
And finally redraw using plot1.replot();
Check the updateSeries function out in the end of the fiddle

How to serialize delete data with jqGrid, multiselection, and Spring?

Currently, I have an overridden delGridRow call that looks like this (credit to Krams and his Spring tutorial):
var row = $('#grid').jqGrid('getGridParam','selrow');
$('#grid').jqGrid( 'delGridRow', row,
{ url:'deleteRequirement.html',
recreateForm: true,
beforeShowForm: function(form) {
//Change title
$(".delmsg").replaceWith('<span style="white-space: pre;">' +
'Delete selected record?' + '</span>');
//hide arrows
$('#pData').hide();
$('#nData').hide();
},
reloadAfterSubmit:true,
closeAfterDelete: true,
serializeDelData: function (postdata) {
var rowdata = $('#grid').getRowData(postdata.id);
// append postdata with any information
return {id: postdata.id, oper: postdata.oper, reqID: rowdata.reqID};
},
afterSubmit : function(response, postdata)
{
var result = eval('(' + response.responseText + ')');
var errors = "";
if (result.success == false) {
for (var i = 0; i < result.message.length; i++) {
errors += result.message[i] + "<br/>";
}
} else {
$('#msgbox').text('Entry has been deleted successfully');
$('#msgbox').dialog(
{ title: 'Success',
modal: true,
buttons: {"Ok": function() {
$(this).dialog("close");
}
}
});
}
// only used for adding new records
var newId = null;
return [result.success, errors, newId];
}
});
else {
$('#msgbox').text('You must select a record first!');
$('#msgbox').dialog(
{ title: 'Error',
modal: true,
buttons: {"Ok": function() {
$(this).dialog("close");}
}
});
}
In order to add support for multiselection deletes, I changed the "selrow" first line to this:
var rowList = jQuery("#grid").getGridParam('selarrrow');
After this, things start getting sketchy fast. The spec says that the default delGridRow can accept an array of inputs records to delete. I made the following change to attempt to get the new 'rowList' variable to get used:
$('#grid').jqGrid( 'delGridRow', rowList, ...
I'm still hitting my deleteRequirement.html URL in my Spring controller, but only the last records appears to make it. I'm guessing the problem is in the postdata preparation in the serializeDelData section, but I haven't found the correct way to prepare this postdata with the list of records instead of the single record.
Any suggestions/insight would be appreciated.
Thanks all.
I don't use Spring myself, but some parts of your code seams be strange for me.
First of all the you can use two forms of the first parameter of delGridRow (row in your code). It can be either the comma-separated list of ids or an array of ids. If you use array of ids then jqGrid convert it to the comma-separated format by rowids = rowids.join();. As the result the format of postdata.id inside of serializeDelData can be also the comma-separated list of ids.
So if you need to support delete of multiple rows you should
modify the code of serializeDelData to send in reqID property also the list of the reqID. The corresponding code can be
serializeDelData: function (postdata) {
var ids = postdata.id.split(','), i, l = ids.length, reqIDList = [];
for (i = 0; i < l; i++) {
reqIDList.push($(this).jqGrid("getCell", ids[i], "reqID"));
}
return {id: postdata.id, oper: postdata.oper, reqID: reqIDList.join()};
}
modify your server code to support both id and reqID in comma-separated form.
Inside of afterSubmit callback you you the lines
// only used for adding new records
var newId = null;
return [result.success, errors, newId];
You can modify the lines to the following
return [result.success, errors];
because only the first two elements of the array returned by afterSubmit callback will be used.

Categories

Resources