Trying to access a sublist in Netsuite with suitescript - javascript

I am trying to access a sublist in NetSuite with a workflow script. I placed a button on all sales orders that once pressed will execute this script. I keep getting an error that my sublist is null. If it is null can someone explain why?
function(record) {
var salesorder = record.newRecord;
var salesordernumber = salesorder.getValue('tranid');
var date = salesorder.getValue('trandate');
var sublist = salesorder.getSublistValue({
Sublistid : 'item'
});
log.debug('Employee Code', salesordernumber);
log.debug('Supervisior Name', date);
log.debug('itemr', sublist);
/**
* Definition of the Suitelet script trigger point.
*
* #param {Object} scriptContext
* #param {Record} scriptContext.newRecord - New record
* #param {Record} scriptContext.oldRecord - Old record
* #Since 2016.1
*/
});
function onAction(context) {
return {
onAction : onAction
};
}

//Load created Sales Order so that we can fetch data
var salesObjRecord = record.load({
type: record.Type.SALES_ORDER,
id: salesOrderID,
isDynamic: true
});
var itemDetailsObj = new Object();
var numLines = salesObjRecord.getLineCount({
sublistId : 'item'
}); // to get sublist line number
if (numLines > 0) {
for (var i = 0; i < numLines; i++) {
itemDetailsObj.amount = salesObjRecord.getSublistValue({
sublistId : 'item',
fieldId : 'amount',
line : i
});
itemDetailsObj.rate = salesObjRecord.getSublistValue({
sublistId : 'item',
fieldId : 'rate',
line : i
});
itemDetailsObj.quantity = salesObjRecord.getSublistValue({
sublistId : 'item',
fieldId : 'quantity',
line : i
});
}
}

record.getSublistValue() returns the value of a sublist field. A sublist field is uniquely identified by 3 parameters:
sublistId
fieldId
line
All these parameters are required, but your code is not providing all of them.

Related

Javascript sanity check of netsuite suitescript

Currently, I have a Netsuite SuiteScript where I export saved searches to csv's. It runs but it's not reusable and I'd like to make it easier by just adding key-value pairs. I have to a lot of copy pasting and it's easy to forget to update to the latest iteration of the run. It's a scheduled search which means it runs every 15 minutes and makes it hard to debug and test.
Right now, my code looks like this, but with more searches, and it's a pain is not reusable.
/**
* #NApiVersion 2.x
* #NScriptType ScheduledScript
* #NModuleScope SameAccount
*/
define(['N/task'],
/**
* #param {record} record
* #param {search} search
*/
function(task) {
var FILE_ID = 2992639;
var SEARCH_ID = 2993;
function execute(scriptContext) {
//first search
var searchTask1 = task.create({
taskType: task.TaskType.SEARCH
});
searchTask1.savedSearchId = SEARCH_ID;
searchTask1.fileId = FILE_ID;
var searchTaskId1 = searchTask1.submit();
//next search
FILE_ID = 2992640;
SEARCH_ID = 3326;
var searchTask2 = task.create({
taskType: task.TaskType.SEARCH
});
searchTask2.savedSearchId = SEARCH_ID;
searchTask2.fileId = FILE_ID;
var searchTaskId2 = searchTask2.submit();
//next search
FILE_ID = 2992634;
SEARCH_ID = 3327;
var searchTask3 = task.create({
taskType: task.TaskType.SEARCH
});
searchTask3.savedSearchId = SEARCH_ID;
searchTask3.fileId = FILE_ID;
var searchTaskId3 = searchTask3.submit();
//this pattern repeats 19 times total.
}
return {
execute: execute
};
});
I've tried to code below
/**
* #NApiVersion 2.x
* #NScriptType ScheduledScript
* #NModuleScope SameAccount
*/
define(['N/task'],
/**
* #param {record} record
* #param {search} search
*/
function(task) {
const searchList = {
2993:2992639,
3326:2992640,
3327:2992634
};
function execute(scriptContext) {
for (const [key, value] of Object.entries(searchList)) {
var searchTask = task.create({
taskType: task.TaskType.SEARCH
});
searchTask.savedSearchId = $key;
searchTask.fileId = $value;
var searchTaskId = searchTask.submit();
}
}
return {
execute: execute
};
});
but get the following error, and I'm not sure what is wrong with my syntax. Netsuite makes it hard to tell what I'm doing wrong, so I'm hoping someone can help here. Thanks!
{"type":"error.SuiteScriptError","name":"SSS_MISSING_REQD_ARGUMENT","message":"task.submit: Missing a required argument: SearchTask.savedSearchId","stack":["createError(N/error)","execute(/SuiteScripts/dashboardreports.js:224)","createError(N/error)"],"cause":{"name":"SSS_MISSING_REQD_ARGUMENT","message":"task.submit: Missing a required argument: SearchTask.savedSearchId"},"id":"","notifyOff":false,"userFacing":true}
I would use a custom record to store the search and file IDs, so you can add/update without modifying code. Then I would do something like the code below. This code first queries your custom record to get all of the search and file ids, then for each one, starts a new task.
/**
* #NApiVersion 2.0
* #NScriptType ScheduledScript
* #NModuleScope Public
*/
define(['N/log', 'N/search', 'N/task'], function(log, search, task) {
function execute(context) {
var searchInfos = getSearchInfo();
searchInfos.forEach(function(searchInfo) {
var searchTask = task.create({
taskType: task.TaskType.SEARCH
});
searchTask.savedSearchId = searchInfo.searchId;
searchTask.fileId = searchInfo.fileId;
var searchTaskId = searchTask.submit();
log.debug({ title: 'searchTaskId', details: searchTaskId });
});
}
function getSearchInfo() {
var results = search.create({
type: 'customrecord_search_to_csv_info',
filters: [
['isinactive', 'is', 'F']
],
columns: [
'custcolumn_search_id',
'custcolumn_file_id'
]
}).run().getRange({ start: 0, end: 1000 });
return (results || []).map(function(result) {
return {
searchId: result.getValue({ name: 'custcolumn_search_id '}),
fileId: result.getValue({ name: 'custcolumn_file_id' })
}
});
}
return {
execute: execute
};
});

Remove Specific Item From Array With Class Objects Inside

My Problem
I am trying to remove a specific item from my array, however, my array contains other objects which I cannot get a handle to.
I am defining a "Station" like this:
/* CLASS Station
* #param id int unique id this station
* #param name str name of the station
* #param location obj the position of the station
* in the workflow diagram
*/
var Station = function(id, name, posX=null, posY=null) {
this.id = ko.observable(id || self.getUniqueId());
this.name = ko.observable(name);
this.posX = ko.observable(posX);
this.posY = ko.observable(posY);
};
So I added a station to my array by using this function ...
.
.
self.addStation(new Station(76, "Receiving", 0, 10));
Now, I want to know how to remove from array by passing the name, as in:
self.removeStation("Receiving");
I can't figure it out. I have researched all the links on here, with no luck.
Entire Source Code
// CLASS Workflow
var Workflow = function(id, status){
this.status = status || false;
this.id = id;
}
/* CLASS Station
* #param id int unique id this station
* #param name str name of the station
* #param location obj the position of the station
* in the workflow diagram
*/
var Station = function(id, name, posX=null, posY=null) {
this.id = ko.observable(id || self.getUniqueId());
this.name = ko.observable(name);
this.posX = ko.observable(posX);
this.posY = ko.observable(posY);
};
// CLASS ViewModel
var ViewModel = function(workFlowId) {
var self = this; // Scope Trick
/*******************************
* Observables
*-----------------------------*/
self.station = ko.observableArray();
/*******************************
* Initialize The Builder
*-----------------------------*/
self.workflowId = ko.observable();
/*******************************
* Arrays
*-----------------------------*/
self.workflow = ko.observableArray();
/*******************************
* Actions
*-----------------------------*/
/* Method: initWorkflow
*
* Desc: When the user gets to the builder
* page, we have to configure a Workflow.
* If they are loading a saved one, the ID
* will be passed. If they are starting a new
* one, we will give it a unique ID.
*
* #param int workFlowId The id of the workflow
* #return int workFlowId The id is returned
*/
self.initWorkflow = function(workFlowId, status=false) {
var id;
if(!workFlowId){
/* Call Function to generate unique ID */
id = self.getUniqueId();
} else {
id = workFlowId;
}
/* Set ID */
this.workflowId = id;
this.workflow = new Workflow(id, status);
};
/*-------------------------------------------------------
* Method: addStation
*
* Desc: Adds a station to current workflow
* #param station object A station object
*--------------------------------------------------------*/
self.addStation = function(station){
self.station.push(station);
}
/* Remove Station - */
self.removeStation = function (Name) {
for( var i = 0; i < self.station().length; i++){
console.dir("In Remove Function: " + self.station()[i]);
}
}
/*-------------------------------------------------------
* Method: getUniqueId
*
* Desc: Generates a random unique Id
* #returns id int A unique random ID
*--------------------------------------------------------*/
self.getUniqueId = function(){
var id = new Date().getTime();
console.group("In Funtion: self.getUniqueId");
console.log("Returned unique id of: " + id);
console.groupEnd("In Funtion: self.getUniqueId");
return id;
}
/* Start it up */
self.initWorkflow(workFlowId);
//------------------------
// UNIT TESTING
//------------------------
//........ STATION RELATED ..........................
// 1. Add
self.addStation(new Station(76, "Receiving", 0, 10));
// 2. Remove
self.removeStation("Receiving");
} // end ViewModel
// Instantiate the ViewModel
window.view_model = new ViewModel();
// Away we go...
ko.applyBindings(window.view_model);
I can't seem to get a hold of the name in the array:
// DON'T WORK
self.station()[i].Station.name
Thanks for looking.
John
u could use function to find its index look like:
function arrayFirstIndexOf(array, predicate, predicateOwner) {
for (var i = 0, j = array.length; i < j; i++) {
if (predicate.call(predicateOwner, array[i])) {
return i;
}
}
return -1;
}
then in your /* Remove Station - */u edit code look like this:
/* Remove Station - */
self.removeStation = function (Name) {
var index = arrayFirstIndexOf(self.station(), function(item){
return item.name === Name;
});
index > -1 && self.station.splice(index, 1);
}
Hope this help !
you can use ko.utils.arrayRemoveItem to remove an item from the array you need to find it first
/* Remove Station */
self.removeStation = function (search) {
// find the station
var foundStation = ko.utils.arrayFirst(this.station, function(item) {
return ko.utils.stringStartsWith(item.name().toLowerCase(), search);
});
// remove the station
ko.utils.arrayRemoveItem(this.station, foundStation);
}
Thank you for your responses. But I found an acceptable solution to my problem by using this following code:
/* Remove Station */
self.removeStation = function (Name) {
var c = -1;
ko.utils.arrayForEach(this.station(), function(item) {
c++;
var value = item.name();
if(value==Name){
console.log("found it! at index " + c);
self.station().splice(c,1);
}
});
// Force the UI to update
self.station.valueHasMutated();
}
I realize this is not the cleanest or most efficient solution, but it seems to work. I would love to know how to optimize this, but that's above my pay grade, lol.
sry for long days answer, i think the problem you got is about variable you declared and variable you use not the same type, and you not use any convert of them when u assigned to your object look like:
var Station = function(id, name, posX=null, posY=null) {
this.id = ko.observable(id || self.getUniqueId()); //observalbe declared
this.name = ko.observable(name);//observalbe declared
this.posX = ko.observable(posX);//observalbe declared
this.posY = ko.observable(posY);//observalbe declared
};
but then you use assign look like
var newObj = {
id: "82",
name: "Updated Name",
posX: 92,
posY: 88
}
self.modifyStation("name","Shipping",newObj);
when u assign it:
objNewItem.id = oldId; // Put old ID back in
self.station.push(objNewItem); //not convert its properties to observable will make some errors on binding html.
i make a working jsfildle with your example, please read it, and reply when u not understand which part. hope this help.
function arrayFirstIndexOf(array, predicate, predicateOwner) {
for (var i = 0, j = array.length; i < j; i++) {
if (predicate.call(predicateOwner, array[i])) {
return i;
}
}
return -1;
}
// CLASS Workflow
var Workflow = function(id, status) {
this.status = status || false;
this.id = id;
}
/* CLASS Field
* #param id int unique id this station
* #param fieldType str type of input
* #param fieldName obj name of the input
* #param options array options array
*/
var Field = function(fieldId, fieldType, fieldName, options) {
this.fieldId = ko.observable(fieldId);
this.fieldType = ko.observable(fieldType);
this.fieldName = ko.observable(fieldName);
this.options = ko.observableArray(options);
};
/* CLASS Station
* #param id int unique id this station
* #param name str name of the station
* #param location obj the position of the station
* in the workflow diagram
*/
var Station = function(id, name, posX = null, posY = null, fields) {
this.id = ko.observable(id || self.getUniqueId());
this.name = ko.observable(name);
this.posX = ko.observable(posX);
this.posY = ko.observable(posY);
this.fields = ko.observableArray(fields || []);
};
// CLASS ViewModel
var ViewModel = function(workFlowId) {
var self = this; // Scope Trick
/*******************************
* Observables
*-----------------------------*/
self.fieldId = ko.observable();
self.fieldName = ko.observable();
self.fieldType = ko.observable();
/*******************************
* Initialize The Builder
*-----------------------------*/
self.workflowId = ko.observable();
/*******************************
* Arrays
*-----------------------------*/
self.workflow = ko.observableArray();
self.station = ko.observableArray();
self.fields = ko.observableArray();
/*******************************
* Computed Observables
*-----------------------------*/
/*******************************
* Actions
*-----------------------------*/
/* Method: initWorkflow
*
* Desc: When the user gets to the builder
* page, we have to configure a Workflow.
* If they are loading a saved one, the ID
* will be passed. If they are starting a new
* one, we will give it a unique ID.
*
* #param int workFlowId The id of the workflow
* #return int workFlowId The id is returned
*/
self.initWorkflow = function(workFlowId, status = false) {
var id;
if (!workFlowId) {
/* Call Function to generate unique ID */
id = self.getUniqueId();
} else {
id = workFlowId;
}
/* Set ID */
this.workflowId = id;
this.workflow = new Workflow(id, status);
};
/*-------------------------------------------------------
* Method: addStation
*
* Desc: Adds a station to current workflow
* #param station object A station object
*--------------------------------------------------------*/
self.addStation = function(station) {
self.station.push(station);
}
/* Remove Station */
self.removeStation = function(Name) {
var index = arrayFirstIndexOf(self.station(), function(item){
return item.name() === Name;
});
index > -1 && self.station.splice(index, 1);
}
/* Update A Station ** NEEDS FIXING **
*
* #param findBy string Property to find ( "id" or "name")
* #param cmpVal string Value to compare against
* #param objNewItem object The new object replacing old
* */
self.modifyStation = function(findBy, cmpVal, objNewItem) {
var sourceIndex;
var oldId;
var found = false;
/* Find Index Of Old Station */
var c = -1;
ko.utils.arrayForEach(this.station(), function(item) {
c++;
switch (findBy) {
case "id":
var value = ko.unwrap(item.id);
if (value == cmpVal) {
sourceIndex = c;
oldId = value;
found = true;
}
break;
case "name":
var value = ko.unwrap(item.name);
if (value == cmpVal) {
sourceIndex = c;
oldId = ko.unwrap(item.id);
found = true;
}
break;
}
});
/* Remove Old */
if (found === true) {
self.station().splice(sourceIndex, 1);
/* Insert New Station
* [For Now] not allowing updating of ID. Only
* can update the other properties (yes, I realize that
* only leaves "name", but more will be added )
*/
objNewItem.id(oldId); // Put old ID back in
self.station.push(objNewItem);
} else {
alert(cmpVal + " was not found in array!");
}
}
self.addField = function(stationId, newField) {
var c = -1;
found = false;
ko.utils.arrayForEach(this.station(), function(item) {
c++;
var value = ko.unwrap(item.id);
console.log(value, c);
if (value == stationId) {
//console.log("found it! at index " + c);
self.station()[c].fields.push(newField);
}
});
self.station.valueHasMutated();
};
self.modifyField = function(stationId, oldFieldId, newObjField) {
// TO DO
};
self.removeField = function(field) {
self.fields.remove(field);
};
/* Perform Test On Button Click */
self.doTest = function() {
self.removeStation("Shipping");
}
self.doTest2 = function() {
var newObj = {
id: ko.observable("82"),
name: ko.observable("Updated Name"),
posX: ko.observable(92),
posY: ko.observable(88),
fields: ko.observableArray([])
}
self.modifyStation("name", "Shipping", newObj);
self.station.valueHasMutated();
}
// Add Fields
self.doTest3 = function() {
var objNewField = {
fieldId: 456,
fieldName: "Last Name",
fieldType: "Text"
}
self.addField(86, objNewField);
}
/*-------------------------------------------------------
* Method: getUniqueId
*
* Desc: Generates a random unique Id
* #returns id int A unique random ID
*--------------------------------------------------------*/
self.getUniqueId = function() {
var id = new Date().getTime();
console.group("In Funtion: self.getUniqueId");
console.log("Returned unique id of: " + id);
console.groupEnd("In Funtion: self.getUniqueId");
return id;
}
/*-------------------------------------------------------
* Method: debugAll
*
* Desc: Console Logs Our View Model
*--------------------------------------------------------*/
this.debugAll = function() {
console.group("Debug:");
// Workflow
console.group("Current Workflow Id")
console.log("self.workflowId = " + self.workflowId);
console.groupEnd("Current Workflow Id")
// Stations
console.group("Stations In This Workflow")
console.table(self.station());
console.groupEnd("Stations In This Workflow")
// Fields
console.groupEnd("Debug:");
}
/* Start it up */
self.initWorkflow(workFlowId);
//------------------------
// UNIT TESTING
//------------------------
//........ STATION RELATED ..........................
// 1. Add
self.addStationShipping = function() {
self.addStation(new Station(86, "Shipping", 0, 10, [{
fieldId: 45,
fieldName: "First Name",
fieldType: "Text"
}]));
}
self.addStation(new Station(76, "Receiving", 0, 10, null));
self.addStationShipping();
/* Dump ViewModel */
self.debugAll();
//----------------------------------------------------------------
} // end ViewModel
// Instantiate the ViewModel
window.view_model = new ViewModel(1213131212);
// Away we go...
ko.applyBindings(window.view_model);
// Page Utility Functions
function wait(ms) {
var start = new Date().getTime();
var end = start;
while (end < start + ms) {
end = new Date().getTime();
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div>
<ul data-bind="foreach: station">
<li data-bind="text: 'id: ' + id()"></li>
<li data-bind="text: 'name: ' +name()"></li>
<li data-bind="text: 'posX: ' +posX()"></li>
<li data-bind="text: 'posY: ' +posY()"></li>
<ul data-bind="foreach: fields">
<li data-bind="text: fieldId"></li>
<li data-bind="text: fieldName"></li>
<li data-bind="text: fieldType"></li>
</ul>
</ul>
</div>
<button data-bind="click: doTest">Remove "Shipping" From Station Array</button>
<button data-bind="click: doTest2">Update "Shipping" In Station Array</button>
<hr>
<h3>Test: Add Field To Shipping Station</h3>
<button data-bind="click: function() { addStationShipping()}">Add Shpping</button>
<button data-bind="click: doTest3()">Add Field</button>

jQuery - code repetition

I am absolute begginer in jQuery. I wrote some code for my app and put it into .js file:
How should I avoid code repetition in jQuery? Where should I store my .js code (one huge .js file, couple of smaller or straight in html source)?
This is my .js file:
$(document).ready(function() {
$("label[for='id_category1'], #id_category1, label[for='id_subcategory1'],#id_subcategory1 ").hide();
$('select[name=subcategory]').empty();
$('select[name=subcategory]').prepend('<option value="Not selected" selected disabled>Select Category...</option>');
$('select[name=subcategory1]').empty();
$('select[name=subcategory1]').prepend('<option value="Not selected" selected disabled>Select Category...</option>');
// called when category field changes from initial value
$('#id_group').change(function() {
var $this = $(this);
if ($this.find('option:selected').attr('value') == 'premium') {
$("label[for='id_category1'], #id_category1").show();
$("label[for='id_subcategory1'], #id_subcategory1").show();
} else {
$("label[for='id_category1'], #id_category1").hide();
$("label[for='id_subcategory1'], #id_subcategory1").hide();
}
})
$('#id_category').change(function() {
var $this = $(this);
if ($this.find('option:selected').index() !== 0) {
category_id = $('select[name=category]').val();
request_url = '/get_subcategory/' + category_id + '/';
$.ajax({
url: request_url,
type: "GET",
success: function(data) {
$('select[name=subcategory]').empty();
$.each(data, function(key, value) {
$('select[name=subcategory]').append('<option value="' + key + '">' + value + '</option>');
});
}
})
}
})
$('#id_category1').change(function() {
var $this = $(this);
if ($this.find('option:selected').index() !== 0) {
category_id = $('select[name=category1]').val();
request_url = '/get_subcategory/' + category_id + '/';
$.ajax({
url: request_url,
type: "GET",
success: function(data) {
$('select[name=subcategory1]').empty();
$.each(data, function(key, value) {
$('select[name=subcategory1]').append('<option value="' + key + '">' + value + '</option>');
});
}
})
}
})
$("label[for='id_keywords']").html("Keywords 0/100");
$('#id_keywords').keyup(function() {
var charsno = $(this).val().length;
$("label[for='id_keywords']").html("Keywords (" + charsno + "/100)");
});
$("label[for='id_description']").html("Description 0/250");
$('#id_description').keyup(function() {
var charsno = $(this).val().length;
$("label[for='id_description']").html("Description (" + charsno + "/2500)");
});
});
Thank you for any clues to beginner.
ALWAYS separate javascript from HTML.
ALWAYS separate and load separate javascript files according to separate functionality.
Then try and break down into MVC type architecture. I've been there where a javascript file is 2000 lines long - its unmanageable for editing and debugging.
ALWAYS try and use classes.
Class architecture is pseudo in Javascript but:
var view;
var model;
var ctrl;
/** - js_include_mvc.js
* as per:
* http://stackoverflow.com/questions/15192722/javascript-extending-class
* provides the bare minimum facility to instantiate MVC objects if the base mvc classes are not subclassed
* this ensures there is always js mvc
*
*/
Ctrl.inherits(BaseCtrl);
function Ctrl(args) {
BaseCtrl.apply(this, arguments);
Ctrl.prototype.boot = function () {
}
}
View.inherits(BaseView);
function View(args) {
BaseView.apply(this, arguments);
View.prototype.boot = function () {
}
}
Model.inherits(BaseModel);
function Model(args) {
BaseModel.apply(this, arguments);
Model.prototype.boot = function () {
}
}
Then for example:
function BaseView() {
BaseView.prototype.somemethod = function () {
//some functionality here
return "I am a View";
}
}
And finally call it in the global page script:
var view=new BaseView();
view.somemethod();
outputs:
"I am a view"
I term the class BaseView as I put code that is always reused here, however the BaseView class can be further extended according to application. So BaseView is used a a main repository for all my apps and I extend it according to requirements.
Some of my code comments need a rewrite but my Ajax class goes like this:
if (SERVER_INTERFACES == undefined) {
var SERVER_INTERFACES = {};//global server array required for named multiple servers and asynch data callbacks
}
/** Ajax_js - communicates with the server to retrieve data
*
* #param String newName is the name this class can reference for things like callbacks
* #param Strin newUrl
* #param String newNamedServer is the resource url defined as a php ajax server class
* #returns {Ajax}
*/
function Ajax_m(newName, newUrl, newNamedServer) {
this.namedServer = newNamedServer;
this.interface_name = newName;
this.url = newUrl;
this.server_data; //allows a query to be given an id and the result data stored and accessible without having to manipulate into method arguments which might require data changes
this.server_data = new Array(); //allows a query to be given an id and the result data stored and accessible without having to manipulate into method arguments which might require data changes
this.request_index = 0;
this.multiqueryindex = 0;
this.multiqueryctrl = new Array();
this.dataType = "json";
this.reqType = "post";
SERVER_INTERFACES[this.interface_name] = this;
Ajax_m.prototype.request = function (requestobj, callback, optargs) {
optargs = model.array_defaults(optargs, {runtagvals: {}});
if (optargs.id == undefined) {
this.request_index++;
} else {
this.request_index = optargs.id;
}
var request_id = this.request_index;
if (typeof requestobj == "string") {
//legacy was the requestobj as a string which is set to request and defaults assumed
//attempt to parse ajaxRequestSubType as first 'word'
var clauseend = requestobj.indexOf(" ");
var ajaxRequestSubType = requestobj.substr(0, clauseend);
requestobj = {ajaxRequestSubType: ajaxRequestSubType, request: requestobj, callback: callback, request_id: request_id};
} else {
//for legacy if callback and id are defined they are added but if in requestobj they will be overridden
var args = {callback: callback, request_id: request_id};
for (var r in requestobj) {
args[r] = requestobj[r];
}
requestobj = args;
}
//ensure default settings
var requestbuild = model.array_defaults(
requestobj,
{
request: null,
callback: "",
errorCallback: null,
request_id: null,
interface_name: this.interface_name,
responsedata: null,
multirequestid: null,
ajaxRequestSubType: "",
ajaxRequestType: "database", //default to use database cfg definitions of site
ismultiparent: false,
_method: "PATCH"//for laravel5
}
);
requestbuild.request = model.runtagreplace(requestbuild.request, optargs.runtagvals);
this.server_data[request_id] = requestbuild;
//debug in chrome as firebug fucks up badly with switch control
switch (requestbuild.ajaxRequestSubType) {
//control processes so no actual ajax requests are required
case "multirequest_parent":
case "multilrequest_submit":
break;
default:
this.server_data[request_id].ajax = $.ajax({
headers: {
'X-CSRF-TOKEN': laravelCSRF//this is a constant from php JSCONSTANTS
},
url: this.url,
type: this.reqType,
data: requestbuild,
dataType: this.dataType,
success: function (server_response) {
var requestobj = SERVER_INTERFACES[server_response.interface_name]; //.server_data[request_id];
if (requestobj.server_data[request_id] != undefined) {//check existence - a reset might have been requested since query sent
requestobj.setRequestResult(server_response.request_id, server_response.data);
//check through request types to eventual detect a simple callback from client object
switch (server_response.ajaxRequestSubType) {
case "select":
case "call":
if (server_response.flag_multiRequestChild) {
var parentinterface_name = requestobj.interface_name;
var parentinterface_id = server_response.multirequest_parent;
var multiobj =
SERVER_INTERFACES[parentinterface_name].
server_data[parentinterface_id];
multiobj.request_returned++;
if (multiobj.request_returned == multiobj.request_total) {
requestobj.multiRequestFinish(requestobj.server_data[request_id].multirequest_parent);
}
} else if (server_response.callback != "") {
eval(server_response.callback + "(" + server_response.request_id + ",'" + requestobj.interface_name + "')");
}
break;
case "submit":
if (!server_response.ismultipart) {
if (server_response.callback != "") {
eval(server_response.callback + "(" + server_response.request_id + ")");
}
} else {
var multiobj = SERVER_INTERFACES[server_response.interface_name].server_data[requestobj.server_data[request_id].multisubmit_parent];
multiobj.submit_returned++;
if (multiobj.submit_returned == multiobj.submit_total) {
requestobj.multiSubmitFinish(requestobj.server_data[request_id].multisubmit_parent);
}
}
break;
case "jscsv":
var downloadobj = SERVER_INTERFACES[server_response.interface_name].server_data[request_id];
requestobj.downloadrun(downloadobj);
break;
default://need failover as cannot determine what to do
break;
}
} else {
var faildata = "error";
}
},
error: function (server_response) {
//parse unhelpful error 'data' to relocate correct ajax request object
var errordata = this.data.split("&");
var intefacename;
var requestid;
var errorobj = {isajaxerror: true};
for (var i in errordata) {
var keyval = errordata[i].split("=");
errorobj[keyval[0]] = keyval[1];
if (errordata[i].indexOf("request_id=") != -1) {
requestid = errordata[i].substr(errordata[i].indexOf("=") + 1);
}
if (errordata[i].indexOf("interface_name=") != -1) {
interfacename = errordata[i].substr(errordata[i].indexOf("=") + 1);
}
}
var parentobj = SERVER_INTERFACES[interfacename];
var requestobj = parentobj.server_data[requestid];
//new object required as out of scope
errorobj["responseText"] = server_response.responseText;
parentobj.setRequestResult(errorobj["request_id"], errorobj);
eval(errorobj["callback"] + "(" + errorobj["request_id"] + ")");
}
});
break;
}
return request_id;
}
/*
* handles ajax where data is not expected back such as an insert statement or email send
* but can expect a response message such as 'ok' or an error message
*/
Ajax_m.prototype.submit = function (type, submitdata, callback) {
this.request({ajaxRequestSubType: "submit", type: type, submitdata: submitdata, ismultipart: false, callback: callback});
}
/*
* 'multiSubmit' handles ajax where data is not expected back such as an insert statement or email send
* but can expect a response message such as 'ok' or an error message
* EXAMPLE
var multisub = [
{
type: "update",
data: {
table: "[xondbs1].stats.dbo.a_ppuser",
values: {'email': 'tim'},
matchfield: 'keyval', matchvalue: 'Q00010017'
}
},
{
type: "update",
data: {
table: "[xondbs1].stats.dbo.a_ppuser",
values: {'email': 'tim'},
matchfield: 'keyval', matchvalue: 'Q00010017'
}
}
];
ajaxdbobj.multiSubmit(multisub, "callbackfunctionname");
*/
Ajax_m.prototype.multiSubmit = function (submitobject, callback) {
var parent_request_id = this.request({ajaxRequestSubType: "multisubmit_parent", submit_total: count(submitobject), submit_returned: 0, id_list: [], submit: {}, response: {}, callback: callback});
for (var s in submitobject) {
var child_request_id = this.request({
ajaxRequestSubType: "submit",
multisubmit_parent: parent_request_id,
ismultipart: true,
type: submitobject[s].type,
submitdata: submitobject[s].data
});
this.server_data[parent_request_id].id_list[child_request_id] = s;
}
return parent_request_id;
}
/*
* sets up mutli queries to only run callback when all complete
* the requestobject is key=>query assoc to which results are assign the same key
* when all results complete the callback is run giving the request_id in the normal way
* to return the multi result object
*/
Ajax_m.prototype.multiRequest = function (requestobject, callback) {
var parent_request_id = this.request({ajaxRequestSubType: "multirequest_parent", request_total: count(requestobject), request_returned: 0, id_list: [], request: {}, data: {}, callback: callback});
for (var r in requestobject) {
/*var child_request = {
request: requestobject[r],
ajaxRequestSubType: "multirequest_child",
}*/
var child_request = requestobject[r];
child_request.multirequest_parent = parent_request_id;
child_request.flag_multiRequestChild = true;
var child_request_id = this.request(child_request);
this.server_data[parent_request_id].id_list[child_request_id] = r;
}
return parent_request_id;
}
/*
* sets up facility for javascript to download data locally
* there is no callback facility required as this is a one-way request
* with a url returned pointing to the resultant file, this is ultimately sent as a new
* window request to the file which will be handled on the native machine settings
*
* for integrity and security the server sets a unique filename ending
*
* First a meta query is sent to process what action to take - eg inform that data is emailed when ready if a length query
* This is then fed back to the user as to what is happening before the actual query can begin.
* #param type determines what download process should be used
* #param dataArgs determines how the data is retrieved
*/
Ajax_m.prototype.requestDownload = function (fileprefix, type, downloadData) {
view.dialogOpen({
title: "Download",
dialogbody: "preparing download . . .",
closeButton: false//change to false after test
});
this.request({ajaxRequestType: "requestDownload", fileprefix: fileprefix, ajaxRequestSubType: type, downloadData: downloadData});
//this.server_data[downloadid].callbacktype = action;
}
/*
* opens url to processed data downloadRequest in a new window / tab
*/
Ajax_m.prototype.downloadrun = function (reqobj) {
//getRequestResult
var meta = this.getRequestResult(reqobj.request_id);
switch (reqobj.ajaxRequestSubType) {
case "jscsv":
view.dialogOpen({
title: "Download",
dialogbody: "Your file download has finished processing.<br />Please ensure you have popups enabled for this site to be able to access the file.",
closeOK: true
});
window.open(meta.downloadurl, "download");
break;
case "dbcsv":
view.dialogOpen({
title: "Download",
dialogbody: meta.msg,
closeOK: true
});
this.request({ajaxRequestSubType: "downloadrun", filename: reqobj.filename, type: reqobj.type, downloadsource: reqobj.downloadsource}, '');
break;
}
}
/*
* kills data (returning requested will be ignored)
*/
Ajax_m.prototype.reset = function () {
for (var s in this.server_data) {
if (this.server_data[s].ajax) {
this.server_data[s].ajax.abort();
}
}
this.server_data = new Array();
this.request_index = 0;
}
/*
* relates misc data to query eg
*/
Ajax_m.prototype.setMisc = function (request_id, miscobject) {
this.server_data[request_id].misc = miscobject;
}
/*
* gets misc data to query eg
*/
Ajax_m.prototype.getMisc = function (request_id) {
return this.server_data[request_id].misc;
}
/*
* get orig query sent
*/
Ajax_m.prototype.setAjaxRequestType = function (type) {
this.reqType = type;
}
/*
* get orig query sent
*/
Ajax_m.prototype.setDataType = function (type) {
this.dataType = type;
}
/*
* get orig query sent
*/
Ajax_m.prototype.getRequest = function (request_id) {
return this.server_data[request_id].request;
}
/*
* return result data for given request id
*/
Ajax_m.prototype.getRequestResult = function (id) {
var data;//leave as undefined so code fails in the client
if (this.server_data[id]) {
data = this.server_data[id].data;
}
return data;
//return data;
}
/*
* return result data for given request id indexed by a field name
* if its not unique there will be data loss and a 'distinct' set will be returned
*/
Ajax_m.prototype.getRequestResultByCol = function (id, colname) {
var reqdata = this.getRequestResult(id);
var data = {};
for (var r in reqdata) {
data.r[colname] = data.r;
delete data.r[colname][colname];
}
return data;
//return data;
}
/**
* return a single value for given request id, if request id did actual generate
* multiple values then only the first is returned
* if this was a multirequest, the named key of the multi request is required
*/
Ajax_m.prototype.getRequestValue = function (id, multi_id) {
var retval;
if (!this.server_data[id].ismultiparent) {
retval = this.server_data[id].data[0];
} else {
retval = this.server_data[id].data[multi_id][0];
}
if (retval == undefined) {
retval = null;
}
return retval;
}
/*
* removes a query and data from memory
*/
Ajax_m.prototype.deleteRequest = function (request_id) {
delete this.server_data[request_id];
}
Ajax_m.prototype.error = function (serverData, st, ert) {
//all errors should have been handled, but just in case
var errorReport = "error";
errorReport += st;
alert("error");
}
/**********************************************************************************************************
INTENDED AS PRIVATE FUNCTIONS
**********************************************************************************************************/
/*
* sets result data for this instance for given query id - eliminates the need for eval unknown ajax data
*/
Ajax_m.prototype.setRequestResult = function (request_id, data) {
this.server_data[request_id].data = data;
}
/*
* compiles the data from the multi query parts into the array entry referenced by the query_index returned
* from client code calling the multiquery function. This also allows the required callback with a reference id to this data
*/
Ajax_m.prototype.multiRequestFinish = function (multirequestid) {
var requestobj = this.server_data[multirequestid];
var multidata = {};
var compactdata;
for (var i in requestobj.id_list) {
requestobj.data[requestobj.id_list[i]] = this.getRequestResult(i);
this.deleteRequest(i);
}
eval(requestobj.callback + "(" + multirequestid + ")");
}
/*
* finalises multisubmit and runs callback
*/
Ajax_m.prototype.multiSubmitFinish = function (multirequestid) {
var requestobj = this.server_data[multirequestid];
var multidata = {};
var compactdata;
for (var i in requestobj.id_list) {
//requestobj.data[requestobj.id_list[i]] = this.getRequestResult(i);
this.deleteRequest(i);
}
eval(requestobj.callback + "(" + multirequestid + ")");
}
}
There were quite a few repetitions in your code. Contrary to #Datadimension I did not restructure your code completely but was trying to show you ways of parameterizing a few of your expressions and functions:
$(document).ready(function() {
var labcatsubcat1=
$("label[for='id_category1'],#id_category1,label[for='id_subcategory1'],#id_subcategory1 ")
.hide();
$('select[name=subcategory],select[name=subcategory1]')
.html('<option value="Not selected" selected disabled>Select Category...</option>');
// called when category field changes from initial value:
$('#id_group').change(function() {
labcatsubcat1.toggle($(this).val()=='premium');
})
$('#id_category,#id_category1').change(function() {
var $this = $(this), postfix=this.id.match(/1*$/)[0];
if ($this.find('option:selected').index() > 0) {
category_id = $('select[name=category'+postfix+']').val();
request_url = '/get_subcategory/' + category_id + '/';
$.ajax({
url: request_url,
type: "GET",
success: function(data) {
$('select[name=subcategory'+postfix+']').empty();
$.each(data, function(key, value) {
$('select[name=subcategory'+postfix+']').append('<option value="' + key + '">' + value + '</option>');
});
}
})
}
});
["keywords,Keywords,100","description,Description,250"].forEach(e=>{
var[id,nam,len]=e.split(",");
$("label[for='id_"+id+"']").html(nam+" 0/"+len);
$('#id_'+id).keyup(function() {
var charsno = $(this).val().length;
$("label[for='id_"+id+"']").html(nam+" (" + charsno + "/"+len+")");
});
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Unfortunately, this is not (yet) a working snippet, as you did not post your HTML ...
Just a short explanation regarding postfix: This variable will hold the string "1" or "" depending on, whether the this refers to the #id_category1 or the #id_category DOM-element.
One thing I'd do is combine the two AJAX requests. You can use multiple selectors for the change handler. Something like $('#id_category', '#id_category1').change(etc.... I'm sure you could find a way to target exactly which one is handling which request. You can use multiple selectors for the other functions as well as per the docs.
If the file is relatively short (~100 - 200 lines) I think you're fine keeping everything all in one place. Otherwise, definitely separate the functions into files and then require them as needed. You can use requireJS for this among others.

Google Charts - Moving Range Slider Control Through Buttons Causes Error

For the life of me I cannot figure out why I am having this issue since there are no real hints in the Google Chart error message.
Problem
Once chart is loaded/displayed, click on any of the Filter by Performance Change buttons, error message is displayed by Google Charts
You called the draw() method with the wrong type of data rather than a DataTable or DataView
This only seems to happen when I use ajax to get the table rows populated and only after the chart is drawn
JFiddle that DOES NOT exhibit the issue (without ajax call - Static row data)
https://jsfiddle.net/insatiabledev/owqkwvz7/23/
JS Code with Ajax that exhibit issue when Filter by Performance Change buttons are clicked. The same row data is returned as in the static version
/** Name Space global variables **/
reportingTableBarChart = {};
/** Google Visualization API DataTable Object **/
reportingTableBarChart.gvDataTable;
/** Google Visualization API Dashboard Object **/
reportingTableBarChart.gvDashboard;
/** Google Visualization API ChartWrapper Object **/
reportingTableBarChart.gvChartWrapper;
/** Google Visualization API Chart Filter Options **/
reportingTableBarChart.gvFilterPerformance;
reportingTableBarChart.gvFilterCatagory;
reportingTableBarChart.gvFilterDescription;
reportingTableBarChart.gvFilterUrl;
/** Table Chart Header **/
reportingTableBarChart.tableChartHeaders = [];
/** Database Data Results **/
reportingTableBarChart.databaseDataResults;
/**
* Creates and draws report data table and bar chart elements using the Google Visualization API
*
* #param json
* JSON results from database
*
* #TODO COPY URL method
*/
reportingTableBarChart.createTableBarChart = function (urlQuery) {
console.log("Google Visualization API Loaded");
/** Get data for report from database and store it for later **/
reportingTableBarChart.getReportData(urlQuery);
/** Initialize Google Visualization API Objects **/
reportingTableBarChart.gvDataTable = new google.visualization.DataTable();
reportingTableBarChart.gvChartWrapper = new google.visualization.ChartWrapper();
reportingTableBarChart.gvDashboard = new google.visualization.Dashboard(document.getElementById('dashboard_div'));
/** Get filter options for manipulating chart data **/
reportingTableBarChart.getFilterPerformance();
//reportingTableBarChart.getFilterCategory();
//reportingTableBarChart.getFilterDescription();
//reportingTableBarChart.getFilterUrl();
/** Set ChartWrapper attributes **/
reportingTableBarChart.gvChartWrapper.setChartType('BarChart');
reportingTableBarChart.gvChartWrapper.setContainerId('tableChartContainer');
reportingTableBarChart.gvChartWrapper.setOptions(reportingTableBarChart.getTableChartOptions());
//reportingTableBarChart.gvChartWrapper.setViews(reportingTableBarChart.getTableChartView());
/** Create chart columns **/
reportingTableBarChart.getTableChartColumns();
/** Add report data to chart **/
reportingTableBarChart.gvDataTable.addRows(Object.values(reportingTableBarChart.databaseDataResults));
console.log("ChartWrapper : " + reportingTableBarChart.gvChartWrapper.toJSON());
console.log("DataTable : " + reportingTableBarChart.gvDataTable.toJSON());
/** Add resize Listener to chart **/
//reportingTableBarChart.addListenerTableChartHeightAutoResize();
/** Add ability to click chart row item **/
reportingTableBarChart.addListenerTableChartClickRow();
/** Bind controls and chart to the Dashboard **/
reportingTableBarChart.gvDashboard.bind( [
reportingTableBarChart.gvFilterPerformance//,
//reportingTableBarChart.gvFilterUrl,
//reportingTableBarChart.gvFilterDescription,
//reportingTableBarChart.gvFilterCatagory
],
reportingTableBarChart.gvChartWrapper
);
/** Draw chart in the Dashboard **/
reportingTableBarChart.gvDashboard.draw(reportingTableBarChart.gvDataTable);
// detect when screen
//autoResizeChart(reportingTableBarChart.gvChartWrapper, reportingTableBarChart.gvDataTable, reportingTableBarChart.getTableChartOptions);
// apply default filter
//reportingTableBarChart.setFilterPerformance(reportingTableBarChart.gvFilterPerformance);
reportingTableBarChart.addListenerLoaded();
};
/**
* JSON formatted Google Table Chart Options
*
* #returns json
* JSON formatted Google Table Chart Options
*/
reportingTableBarChart.getTableChartOptions = function () {
/** Chart Table Options object **/
var options = new Object();
options.axisTitlesPosition = 'in';
options.fontSize = 11;
/** Set Chart Colors **/
options.colors = [ '#0F59A9', // Blue
'#E07804', // Orange
'#609D09', // Green
'#D12112', // Red
'#813AA7', // Purple
'#808284' // Gray
];
/** Change cursor when hover on **/
options.forceIFrame = false;
/** Legend Options **/
options.legend = [ { position : 'none' } ];
/** Bar options **/
options.bar = [ { groupWidth : '60%' } ];
/** Chart Area Options **/
options.chartArea = [ {
left : 350,
top : 0,
width : '100%',
height : '95%'
} ];
/** Vertical Axis Options **/
options.vAxis = [ {
title : 'Description of Test Case ',
titleTextStyle : {
fontSize : 9
}
} ];
/** Horizontal Axis Options **/
options.hAxis = [ {
title : 'Time Process(Milliseconds) & Build#',
titleTextStyle : {
fontSize : 9
},
gridlines : [ {
color: '#F4F4F4',
count : 10
} ]
} ];
/** Tooltip Options **/
options.tooltip = [ {
textStyle : {
fontSize: 13
}
} ];
console.log("Table Chart Options : " + JSON.stringify(options));
return options;
};
/**
* Defines Table Chart Columns and adds them to the DataTable
*
*/
reportingTableBarChart.getTableChartColumns = function () {
/** Get Number of columns **/
var numElements = reportingTableBarChart.databaseDataResults[0].length;
var numOfCommonFields = 2;
var numOfFieldsInSet = 5;
console.log("Number of Columns : " + numElements);
/** Testcase Description Value **/
reportingTableBarChart.gvDataTable.addColumn( {
type : 'string',
label : 'Description',
id : 'Description',
role : 'domain'
} );
//reportingTableBarChart.tableChartHeaders.push('Description');
/** Loop through sets of testcase data to create the correct number of columns **/
/** Formula: (Number of Database fields returned - 2 (common fields - Description and Diff)) / 5 (number of fields in set) **/
for ( i = 0; i < ( (numElements - numOfCommonFields) / numOfFieldsInSet); i++) {
/** Testcase Process Time value **/
reportingTableBarChart.gvDataTable.addColumn( {
type : 'number',
label : 'Processing Time',
id : 'ProcessingTime',
role : 'data'
} );
//reportingTableBarChart.tableChartHeaders.push('Processing Time');
/** "Build Number: xxxx" **/
reportingTableBarChart.gvDataTable.addColumn( {
type : 'string',
label : 'Build Number',
id : 'BuildNumber',
role : 'annotation'
} );
/** "Process Time: xx.xx Request: xxxxxxx" **/
reportingTableBarChart.gvDataTable.addColumn( {
type : 'string',
label : 'Request',
id : 'Request',
role : 'annotationText'
} );
/** Testcase Process Time value **/
reportingTableBarChart.gvDataTable.addColumn( {
type : 'number',
label : 'Processing Time Tooltip',
id : 'ProcessingTimeTooltip',
role : 'tooltip'
} );
/** Category **/
reportingTableBarChart.gvDataTable.addColumn( {
type : 'string',
label : 'Category',
id : 'Category',
role : 'interval'
} );
}
/** Diff Value **** HIDE SOMEHOW in tableview **** **/
reportingTableBarChart.gvDataTable.addColumn( {
type : 'number',
label : 'Diff',
id : 'Diff',
role : 'data'
} );
//reportingTableBarChart.tableChartHeaders.push('diff');
}
/**
* Performance Filter
*
* #returns ControlWrapper Object
* ControlWrapper Object for Performance Filter
*/
reportingTableBarChart.getFilterPerformance = function () {
/** Performance Filter object **/
reportingTableBarChart.gvFilterPerformance = new google.visualization.ControlWrapper();
reportingTableBarChart.gvFilterPerformance.setControlType('NumberRangeFilter');
reportingTableBarChart.gvFilterPerformance.setContainerId('chartFilterPerformance');
reportingTableBarChart.gvFilterPerformance.setOptions( {
filterColumnLabel : 'Diff',
minValue : -1000,
maxValue : 100,
ui : {
label : 'Filter by variation',
labelSeparator : '(%)',
labelStacking : 'verical'
}
} );
console.log("Table Chart Performance Filter Options : " + reportingTableBarChart.gvFilterPerformance.toJSON());
}
/**
* Category Filter
*
* #returns ControlWrapper Object
* ControlWrapper Object for Catagory Filter
*/
reportingTableBarChart.getFilterCategory = function () {
/** Chart Table Category Filter object **/
reportingTableBarChart.gvFilterCatagory = new google.visualization.ControlWrapper();
reportingTableBarChart.gvFilterCatagory.setControlType('StringFilter');
reportingTableBarChart.gvFilterCatagory.setContainerId('chartFilterCategory');
reportingTableBarChart.gvFilterCatagory.setOptions( {
filterColumnIndex : '5', // filter by Category
matchType : 'exact',
ui : {
label : 'Filter by Category'
}
} );
console.log("Table Chart Category Filter Options : " + reportingTableBarChart.gvFilterCatagory.toJSON());
}
/**
* Description Filter
*
* #returns ControlWrapper Object
* ControlWrapper Object for Description Filter
*/
reportingTableBarChart.getFilterDescription = function () {
/** Chart Table Description Filter object **/
reportingTableBarChart.gvFilterDescription = new google.visualization.ControlWrapper();
reportingTableBarChart.gvFilterDescription.setControlType('StringFilter');
reportingTableBarChart.gvFilterDescription.setContainerId('chartFilterDescription');
reportingTableBarChart.gvFilterDescription.setOptions( {
filterColumnIndex : '0', // filter by request description
ui : {
label : 'Filter by Description'
}
} );
console.log("Table Chart Description Filter Options : " + reportingTableBarChart.gvFilterDescription.toJSON());
}
/**
* URL Filter
*
* #returns ControlWrapper Object
* ControlWrapper Object for URL Filter
*/
reportingTableBarChart.getFilterUrl = function () {
/** Chart Table URL Filter object **/
reportingTableBarChart.gvFilterUrl = new google.visualization.ControlWrapper();
reportingTableBarChart.gvFilterUrl.setControlType('StringFilter');
reportingTableBarChart.gvFilterUrl.setContainerId('chartFilterUrl');
reportingTableBarChart.gvFilterUrl.setOptions( {
filterColumnIndex : '3', // filter by request URL
matchType : 'any',
ui : {
label : 'Filter by URL string'
}
} );
console.log("Table Chart URL Filter Options : " + reportingTableBarChart.gvFilterUrl.toJSON());
}
/**
* JSON formatted Google Table Chart Bar Chart
* ????????????????????
*/
reportingTableBarChart.getTableChartView = function () {
var view = new google.visualization.DataView(reportingTableBarChart.databaseDataResults);
view.setColumns(reportingTableBarChart.getTableChartColumns());
console.log("Chart View : " + view.toJSON());
return view.toJSON();
}
/**
* Adds simple "element has been loaded" listeners that write to JS console
*
*/
reportingTableBarChart.addListenerLoaded = function () {
google.visualization.events.addListener(
reportingTableBarChart.gvFilterPerformance, 'ready', function() {
console.log('Performance Filter loaded');
} );
/* google.visualization.events.addListener(
reportingTableBarChart.gvFilterCatagory, 'ready', function() {
console.log('Category Filter loaded');
} );
google.visualization.events.addListener(
reportingTableBarChart.gvFilterDescription, 'ready', function() {
console.log('Description Filter loaded');
} );
google.visualization.events.addListener(
reportingTableBarChart.gvFilterUrl, 'ready', function() {
console.log('URL Filter loaded');
} );*/
google.visualization.events.addListener(
reportingTableBarChart.gvChartWrapper, 'ready', function() {
console.log('Bar Chart loaded');
} );
}
/**
* Adds a listener to auto resize the Table Chart Dashboard when browser is resized
*
*/
reportingTableBarChart.addListenerTableChartHeightAutoResize = function () {
google.visualization.events.addListener(reportingTableBarChart.gvDashboard, 'ready', function() {
console.log('Resizing Chart...');
var recordRowsTotal = reportingTableBarChart.gvDataTable.getNumberOfRows();
var numRows = reportingTableBarChart.gvChartWrapper.getDataTable().getNumberOfRows();
var expectedHeight = numRows * 60;
if( numRows == 0 )
expectedHeight = 60;
if( parseInt( reportingTableBarChart.gvChartWrapper.getOption('height'), 10 ) != expectedHeight ) {
$("#recordRowsDispalyed").text(numRows);
$("#recordRowsTotal").text(recordRowsTotal);
reportingTableBarChart.gvChartWrapper.setOption('height', expectedHeight);
reportingTableBarChart.gvChartWrapper.draw();
}
} );
}
/**
* Adds a listener allowing user to click on Table Chart row to view test details
*
*/
reportingTableBarChart.addListenerTableChartClickRow = function () {
/** Enable clicking on chart to view testcase detail **/
google.visualization.events.addListener(reportingTableBarChart.gvChartWrapper, 'select', function (){
var chartObject = reportingTableBarChart.gvChartWrapper.getChart();
var selectedItem = chartObject.getSelection()[0];
var selectedData = reportingTableBarChart.gvChartWrapper.getDataTable();
if( selectedItem ) {
var description = selectedData.getValue(selectedItem.row, 0);
/** Redirect when clicked **/
window.location.href = '<%=request.getContextPath()%>/report?name=custom&description=' + escape(description); //WTF
}
});
}
/**
* Sets Performance filter result range and updates Performance filter
*
*/
reportingTableBarChart.setFilterPerformanceRange = function (low, high) {
reportingTableBarChart.gvFilterPerformance.setState( {
lowValue : low,
highValue : high
} );
reportingTableBarChart.gvFilterPerformance.draw();
};
/**
* Sets Performance filter based on URL query modifier 'filter'
* Is injected when clicking pie chart to view report
*
*/
reportingTableBarChart.setFilterPerformanceFromUrl = function () {
var params = new URLSearchParams(location.search);
var param = params.get('filter');
if (param != null ) {
switch(param.toLowerCase()) {
case 'faster':
reportingTableBarChart.setFilterPerformanceRange(-100, 0);
$("#btnAllFaster").attr("disabled", "disabled");
break;
case 'slower':
reportingTableBarChart.setFilterPerformanceRange(0, 100);
$("#btnAllSlower").attr("disabled", "disabled");
break;
case 'unchanged':
reportingTableBarChart.setFilterPerformanceRange(0, 0);
$("#unchanged").attr("disabled", "disabled");
break;
case 'new':
default:
reportingTableBarChart.setFilterPerformanceRange(-1000, -1000);
break;
}
}
};
/**
* Resets Performance and Category filters
*
*/
reportingTableBarChart.resetAllFilters = function () {
/** Reset Performance filter **/
reportingTableBarChart.setFilterPerformanceRange(-1000, 100);
/** Reset Category filter **/
reportingTableBarChart.gvFilterCatagory.setState( { value: '' } );
reportingTableBarChart.gvFilterCatagory.draw();
};
/**
* Resets Performance filter, then sets Category filter
*
*/
reportingTableBarChart.setFilterCategory = function (category) {
/** Reset Performance filter **/
reportingTableBarChart.setFilterPerformanceRange(-1000, 100);
/** Set Category filter **/
reportingTableBarChart.gvFilterCatagory.setState( { value: category } );
reportingTableBarChart.gvFilterCatagory.draw();
};
/**
* Set Database results
*
*/
reportingTableBarChart.setDatabaseDataResults = function (jsonRecords) {
reportingTableBarChart.databaseDataResults = jsonRecords;
}
/**
* Grabs JSON results from database data servlet via Ajax and creates a
* Google Table Chart
*/
reportingTableBarChart.getReportData = function (urlQuery) {
databasedata.setRequestQuery(urlQuery);
/** Check that the server query is set **/
if (databasedata.requestQuery == null) {
console.log('databasedata.requestQuery is not set - See databasedata.setRequestQuery()');
return;
}
/** Sends request to database data servlet to grab JSON and turns it into an HTML table **/
databasedata.getJsonData().done(reportingTableBarChart.setDatabaseDataResults);
};

aoData is null when using multiple instances of jquery datatable

Scenario:
On a webpage I have three divs that contain table tags.
There are 3 buttons, clicking on each button creates an instance of the datatable on a particular div with the table tag.
The datatable gets the data from serverside
All the data returned and displayed, pagination, filtering works fine.
So when all three instances are created, using fnSettings() on only the last instance created returns the proper object, and the other two instances return null
So using fnData() etc api methods throw an error saying : "TypeError: Cannot read property 'aoData' of null" because settings object of that datatable instance is somehow null
Code Description
I have made a class called datagrid, and I create multiple instances of this class:
/**
* datagrid class contains methods and properties that will help in controllling and manipulating the multiple instances of the datagrid class
*
* This function is the constructor for the datagrid class
*
* #param {string} domContainerSelector DOM selector of the element containing the datagrid
* #param {Array} columns Definitions of the columns of the datagrid
* #param {string} ajaxSource The url that the jqgrid will use to request for data
* #param {Object} configurationParameters The configuration parameters that will be used by the jqGrid and this datagrid instance. Currently suppoted configuration parameters are: initialCacheSize, iDisplayLength, sScrollY, bPaginate, bFilter, sDom, bSort
* #param {Object} uiCallback Contains callback functions that are used when a server request is in progress and after the completion of the request. Mainly used for showing progress indicators.
* #returns {datagrid}
*/
function datagrid(domContainerSelector, columns, ajaxSource, configurationParameters, uiCallback)
{
this.domContainerSelector = domContainerSelector;
this.domTableSelector = this.domContainerSelector + " #grid";
this.domRowSelector = this.domTableSelector + " tbody tr";
this.domGridWrapperSelector = this.domContainerSelector + " .dataTables_wrapper";
this.columns = columns;
this.ajaxSource = ajaxSource;
this.configParams = configurationParameters;
this.uiCallback = uiCallback;
this.cache= {
start: 0,
end: 0,
initialSize:this.configParams.initialCacheSize == undefined ? 2 : this.configParams.initialCacheSize,
pageSize:this.configParams.iDisplayLength == undefined ? 10 : this.configParams.iDisplayLength,
loading:false,
jsondata: {},
reset: function(){
this.start=0;
this.end=0;
this.loading=false;
this.jsondata={};
}
};
/**
* This method returns the row selected by the user
*
* #return {Object} Row object containing columns as its properties
*/
this.getSelectedRow = function()
{
var allrows = this.dataTable.fnGetNodes();
for (i = 0; i < allrows.length; i++)
if ($(allrows[i]).hasClass('row_selected'))
return this.dataTable.fnGetData(allrows[i]);
};
this.getPostDataValue=function(postData, key){
for (var i=0;i<postData.length;i++)
{
if (postData[i].name == key)
{
return postData[i].value;
}
}
return null;
};
this.setPostDataValue=function(postData, key, value){
for (var i=0; i<postData.length;i++)
{
if (postData[i].name == key)
{
postData[i].value = value;
}
}
};
this.setPostDataFilterValues=function(postData){
for (i=0;i<this.columns.length;i++)
{
var key="sSearch_"+i;
this.setPostDataValue(postData,key,this.columns[i].sSearch===undefined?'':this.columns[i].sSearch);
}
};
this.filterColumnKeyupHandler = function(evt) {
var id=evt.target.id;
var index=id.charAt(id.length-1);
var oldvalue=this.columns[index].sSearch;
var value = evt.target.value == '' ? undefined : evt.target.value;
if (oldvalue!=value) this.cache.reset();//resetting the cache because the datagrid is in dirty state
this.columns[index].sSearch=value;
if (evt.keyCode == 13) this.dataTable.fnFilter();
};
/**
* This method acts as the general button handler when an operation is in progress
*/
this.busyStateButtonHandler=function()
{
ui.showmessage("Another operation is in progress. Please wait for the operation to complete");
};
/**
* This method sets the event handlers for the datagrid
*/
this.setEventHandlers = function() {
var self=this;
$(this.domGridWrapperSelector + " input[class='columnfilterinput']").off("keyup").on("keyup", function(evt) {self.filterColumnKeyupHandler(evt,self)});
$(this.domGridWrapperSelector + " .filterbar .searchbtn").off("click").on("click", function() {self.dataTable.fnFilter()});
};
/**
* This method sets the appropriate event handlers to indicate busy status
*/
this.setBusyStatusEventHandlers=function()
{
$(this.domGridWrapperSelector + " input[class='columnfilterinput']").off("keyup").on("keyup", this.busyStateButtonHandler);
$(this.domGridWrapperSelector + " .filterbar .searchbtn").off("click").on("click", this.busyStateButtonHandler);
};
/**
* This method enables column specific filtering
*
* This methods adds filtering capability to columns whose definitions indicate that they are searchable (bSearchable:true)
*/
this.enablecolumnfilter = function() {
var self = this;
var oTable = self.dataTable;
var oSettings = oTable.fnSettings();
var aoColumns = oSettings.aoColumns;
var nTHead = oSettings.nTHead;
var htmlTrTemplate = "<tr class='filterbar'>{content}</tr>";
var htmlTdTemplate = "<td>{content}</td>";
var htmlInputTemplate = "<input type='text' name='{name}' id='{id}' class='{class}' /><div class='searchbtn' id='{searchbtnid}'><div class='icon-filter'></div></div>";
var isAnyColumnFilterable = false;
var htmlTr = htmlTrTemplate;
var allHtmlTds = "";
for (i = 0; i < aoColumns.length; i++)
{
var column = aoColumns[i];
var htmlTd = htmlTdTemplate;
if (column.bSearchable == true)
{
isAnyColumnFilterable = true;
var htmlInput = htmlInputTemplate;
htmlInput = htmlInput.replace('{name}', column.mData);
htmlInput = htmlInput.replace('{id}', "sSearch_" + i);
htmlInput = htmlInput.replace('{class}', 'columnfilterinput');
htmlTd = htmlTd.replace('{content}', htmlInput);
}
else
htmlTd = htmlTd.replace('{content}', '');
allHtmlTds += htmlTd;
}
if (isAnyColumnFilterable)
{
htmlTr = htmlTr.replace('{content}', allHtmlTds);
nTHead.innerHTML += htmlTr;
$(this.domGridWrapperSelector + " .filterbar input[class='columnfilterinput']").each(function(){
$(this).width($(this).parent().width()-26);
});
}
};
/**
* This method enables single selection on the rows of the grid
*/
this.enableSelection = function()
{
$(this.domRowSelector).die("click").live("click", function() {
if ($(this).hasClass('row_selected')) {
$(this).removeClass('row_selected');
}
else {
$(this).siblings().removeClass('row_selected');
$(this).addClass('row_selected');
}
});
};
this.loadDataIntoCache=function(postData, sourceUrl, start, length){
if (!this.cache.loading)
{
var postData=$.extend(true, [], postData);
var start = start==undefined?this.cache.end:start;
var length = length==undefined?this.cache.pageSize:length;
var end = start + length;
this.setPostDataValue(postData, "iDisplayStart", start);
this.setPostDataValue(postData, "iDisplayLength", length);
var self=this;
this.cache.loading=true;
$.ajax({
type: "POST",
url: sourceUrl,
data: postData,
success:
function(json, textStatus, jqXHR)
{
json = JSON.parse(json);
var olddata=self.cache.jsondata.aaData;
if (olddata===undefined) self.cache.jsondata = $.extend(true, {}, json);
else olddata.push.apply(olddata,json.aaData);
self.cache.end=end;
},
error:
function(jqXHR, textStatus, errorThrown)
{
ui.showmessage(jqXHR.responseText);//remove this from here
},
complete:
function()
{
self.cache.loading=false;
}
});
}
};
this.loadDataFromCache=function(postData,sourceUrl){
var start=this.getPostDataValue(postData, "iDisplayStart");
var length=this.cache.pageSize;
var end=start+length;
var sEcho = this.getPostDataValue(postData,"sEcho");
if (this.cache.end>=end)
{
var jsondata=$.extend(true, {},this.cache.jsondata);
var data=jsondata.aaData;
jsondata.aaData=data.splice(start,length);
jsondata.sEcho = sEcho;
var totalRecords=jsondata.iTotalRecords;
if ((this.cache.end-end)<((this.cache.initialSize*this.cache.pageSize)/2) && (totalRecords==0 || this.cache.end<totalRecords) ) this.loadDataIntoCache(postData, sourceUrl);//prefetch data if needed
return jsondata;
}
else
{
this.loadDataIntoCache(postData,sourceUrl);
return null;
}
};
/**
* This method interfaces with the backend end controller
*
* This method is called when the grid initiates any operation that requires server side processing
*
* #param {String} sSource The source url that will be used for the xhr request
* #param {Array} aoData Contains the parameters sent by the dataTable that will be forwarded to the backend controller
* #param {Function} fnCallback The callback function of the dataTable that gets executed to finally render the grid with the data
*/
this.interfaceWithServer = function(sSource, aoData, fnCallback)
{
this.setPostDataFilterValues(aoData);
var self=this;
if (this.cache.end==0)
{
this.setPostDataValue(aoData, "iDisplayStart", this.cache.start);
if (this.dataTable!=undefined) this.dataTable.fnSettings()._iDisplayStart=0;
this.loadDataIntoCache(aoData, sSource, 0, (this.cache.initialSize*this.cache.pageSize));
}
var data=this.loadDataFromCache(aoData,sSource);
if (data!=null) fnCallback(data);
else
{
this.setBusyStatusEventHandlers();
this.uiCallback.inprogress();
self.cacheLoadingTimerId=setInterval(function(){
if (self.cache.loading==false)
{
clearInterval(self.cacheLoadingTimerId);
var data=self.loadDataFromCache(aoData,sSource);
fnCallback(data);
self.uiCallback.completed();
self.setEventHandlers();
}
},500);
}
};
/**
* This method destroys the datatable instance
*
* Remove all the contents from the parent div and reinserts a simple table tag on which a fresh datatable will be reinitialized
*/
this.destroy = function()
{
$(this.domRowSelector).die("click");
$(this.domGridWrapperSelector).remove();//remove only the datatable generated dynamic code
$(this.domContainerSelector).prepend("<table id='grid'></table>");
};
/**
* The dataTable property holds the instance of the jquery Datatable
*/
this.dataTable = $(this.domTableSelector).dataTable({
"bJQueryUI": true,
"sScrollY": this.configParams.sScrollY == undefined ? "320px" : this.configParams.sScrollY,
"bAutoWidth": true,
"bPaginate": this.configParams.bPaginate == undefined ? true : this.configParams.bPaginate,
"sPaginationType": "two_button",
"bLengthChange": false,
"bFilter": this.configParams.bFilter == undefined ? true : this.configParams.bFilter,
"sDom": this.configParams.sDom == undefined ? '<"H"lfr>t<"F"ip>' : this.configParams.sDom,
"bSort": this.configParams.bSort == undefined ? true : this.configParams.bSort,
"iDisplayLength": this.configParams.iDisplayLength == undefined ? 10 : this.configParams.iDisplayLength,
"bServerSide": true,
"sAjaxSource": this.ajaxSource,
"fnServerData": this.interfaceWithServer.bind(this),
"oLanguage": {
"sZeroRecords": "No Records Found",
"sInfo": "_START_ - _END_ of _TOTAL_",
"sInfoEmpty": "0 to 0 of 0"
},
"aoColumns": this.columns
});
this.init=function(){
this.enableSelection();
this.enablecolumnfilter();
this.setEventHandlers();
};
this.init();
};
Now in my web page this is where I create the 3 instances :
switch (dialog)
{
case "cusgrp_dialog":
var columndefs = [
{
"sTitle": "XWBNCD",
"mData": "xwbncd",
"sWidth": "40%"
},
{
"sTitle": "XWKHTX",
"mData": "xwkhtx",
"sWidth": "60%"
}
];
var ajaxSource = "./entities/Cusgrp";
var configurationParameters = {
bFilter: null,
sDom: 't<"dataTable_controlbar"ip>'
};
this.customergroupDatagrid = new datagrid(domContainerSelector, columndefs, ajaxSource, configurationParameters, uiCallback);
break;
case "slmen_dialog":
var columndefs = [
{
"sTitle": "PERSON",
"mData": "person",
"sWidth": "40%"
},
{
"sTitle": "PNAME",
"mData": "pname",
"sWidth": "60%"
}
];
var ajaxSource = "./entities/Slmen";
var configurationParameters = {
bFilter: null,
sDom: 't<"dataTable_controlbar"ip>'
};
this.salesmanDatagrid = new datagrid(domContainerSelector, columndefs, ajaxSource, configurationParameters, uiCallback);
break;
case "dists_dialog":
var columndefs = [
{
"sTitle": "DSDCDE",
"mData": "dsdcde",
"sWidth": "40%"
},
{
"sTitle": "DNAME",
"mData": "dname",
"sWidth": "60%"
}
];
var ajaxSource = "./entities/Dists";
var configurationParameters = {
bFilter: null,
sDom: 't<"dataTable_controlbar"ip>'
};
this.distributorDatagrid = new datagrid(domContainerSelector, columndefs, ajaxSource, configurationParameters, uiCallback);
break;
}
After all three instances are created, only the last one supposedly has fnSettings() object defined rest instances return null for fnSettings and thus calling other api methods that use aoData (which is a member of the fnSettings() returned object) show the error that can't read property aoData of null
Console Preview:
The 3 instances are stored in customergroupDatagrid, salesmanDatagrid, distributorDatagrid variables
When the customergroupDatagrid instance is created
customergroupDatagrid.dataTable.fnSettings(); // returns object
When the salesmanDatagrid instance is created
salesmanDatagrid.dataTable.fnSettings(); // returns object
customergroupDatagrid.dataTable.fnSettings(); // returns null
When the distributorDatagrid instance is created
distributorDatagrid.dataTable.fnSettings(); // returns object
salesmanDatagrid.dataTable.fnSettings(); // returns null
customergroupDatagrid.dataTable.fnSettings(); // returns null
I believe the problem is that your tables all have the same ID. Please note proper HTML requires unique IDs:
http://www.w3.org/TR/html401/struct/global.html#h-7.5.2
id = name [CS]
This attribute assigns a name to an element. This name
must be unique in a document.
Here are two jsfiddles.
http://jsfiddle.net/QFrz9/
var dt1 = $('#div1 #grid').dataTable();
alert('dt1 settings: ' + dt1.fnSettings());
var dt2 = $('#div2 #grid').dataTable();
alert('dt1 settings: ' + dt1.fnSettings());
alert('dt2 settings: ' + dt2.fnSettings());
http://jsfiddle.net/mRFaP/1/
var dt1 = $('#div1 #grid1').dataTable();
alert('dt1 settings: ' + dt1.fnSettings());
var dt2 = $('#div2 #grid2').dataTable();
alert('dt1 settings: ' + dt1.fnSettings());
alert('dt2 settings: ' + dt2.fnSettings());
The first one duplicates your code, using the same id for the two tables. It displays an alert after the first table is created; fnSettings is not null. Then it displays an alert after the next table is created, and suddenly the fnSettings of table 1 is null. The second jsfiddle uses unique ids, and the problem disappears.
Perhaps your table id could be a combination of the div ID and "grid", e.g., div1grid, div2grid etc. Then you would use domContainerSelector + 'grid' instead of ' #grid'.

Categories