Google Charts - Moving Range Slider Control Through Buttons Causes Error - javascript

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);
};

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
};
});

Cannot read property '0' of undefined - Date

I would like to get the publicationdate from a URL, I can get it with the following code: window.dataLayer[0]['publicationdate'], but when it's time to make it work locally, the console gives me the following error "Cannot read property '0' of undefined". I have the following code, can someone please help me? Thank you!
"use strict";
var takashyx = takashyx || {};
takashyx.toast = (function() {
/**
* The main Toast object
* #param {Object} options See Toast.prototype.DEFAULT_SETTINGS for more info
*/
function Toast(text, options) {
if(getToastStage() != null) {
// If there is already a Toast being shown, hide it
Toast.prototype.destroy();
}
var _options = options || {};
_options = Toast.prototype.mergeOptions(Toast.prototype.DEFAULT_SETTINGS, _options);
Toast.prototype.show(text, _options);
_options = null;
};
/**
* The toastStage. This is the HTML element in which the toast resides
* Getter and setter methods are available privately
* #type {Element}
*/
var _toastStage = null;
function getToastStage() {
return _toastStage;
};
function setToastStage(toastStage) {
_toastStage = toastStage;
};
/**
* The Toast animation speed; how long the Toast takes to move to and from the screen
* #type {Number}
*/
Toast.prototype.TOAST_ANIMATION_SPEED = 400;
// Toast classes
Toast.prototype.CLASS_TOAST_GONE = "takashyx_toast_gone";
Toast.prototype.CLASS_TOAST_VISIBLE = "takashyx_toast_visible";
Toast.prototype.CLASS_TOAST_ANIMATED = "takashyx_toast_animated";
/**
* The default Toast settings
* #type {Object}
*/
Toast.prototype.DEFAULT_SETTINGS = {
style: {
main: {
"background": "rgba(0, 0, 0, .85)",
"box-shadow": "0 0 10px rgba(0, 0, 0, .8)",
"border-radius": "3px",
"z-index": "99999",
"color": "#01ff00",
"padding": "10px 15px",
"max-width": "40%",
"word-break": "keep-all",
"margin": "0 auto",
"text-align": "center",
"position": "fixed",
"left": "0",
"right": "0"
}
},
settings: {
duration: 4000
}
};
/**
* Specifies whether or not the inline style in the <head> exists. It only needs to be added once to a page
* #type {Boolean}
*/
Toast.prototype.styleExists = false;
/**
* The Timeout object for animations.
* This should be shared among the Toasts, because timeouts may be cancelled e.g. on explicit call of hide()
* #type {Object}
*/
Toast.prototype.timeout = null;
/**
* Merge the DEFAULT_SETTINGS with the user defined options if specified
* #param {Object} options The user defined options
*/
Toast.prototype.mergeOptions = function(initialOptions, customOptions) {
var merged = customOptions;
for(var prop in initialOptions) {
if(merged.hasOwnProperty(prop)) {
if(initialOptions[prop] != null && initialOptions[prop].constructor == Object) {
merged[prop] = Toast.prototype.mergeOptions(initialOptions[prop], merged[prop]);
}
} else {
merged[prop] = initialOptions[prop];
}
}
return merged;
};
/**
* Add the inline stylesheet to the <head>
* These inline styles are needed for animation purposes.
*/
Toast.prototype.initializeStyles = function() {
if(Toast.prototype.styleExists) return;
var style = document.createElement("style");
style.insertAdjacentHTML("beforeend",
Toast.prototype.generateInlineStylesheetRules(this.CLASS_TOAST_GONE, {
"opacity": "0",
"top": "10%"
}) +
Toast.prototype.generateInlineStylesheetRules(this.CLASS_TOAST_VISIBLE, {
"opacity": "1",
"top": "10%"
}) +
Toast.prototype.generateInlineStylesheetRules(this.CLASS_TOAST_ANIMATED, {
"transition": "opacity " + this.TOAST_ANIMATION_SPEED + "ms, bottom " + this.TOAST_ANIMATION_SPEED + "ms"
})
);
document.head.appendChild(style);
style = null;
// Notify that the stylesheet exists to avoid creating more
Toast.prototype.styleExists = true;
};
/**
* Generate the Toast with the specified text.
* #param {String|Object} text The text to show inside the Toast, can be an HTML element or plain text
* #param {Object} style The style to set for the Toast
*/
Toast.prototype.generate = function(text, style) {
var toastStage = document.createElement("div");
/**
* If the text is a String, create a textNode for appending
*/
if(typeof text === "string") {
var lines = text.split('[[[br]]]');
for (let i=0; i<lines.length; ++i) {
var l = document.createTextNode(lines[i]);
toastStage.appendChild(l);
var r = document.createElement('br');
toastStage.appendChild(r);
}
setToastStage(toastStage);
toastStage = null;
Toast.prototype.stylize(getToastStage(), style);
};
/**
* Stylize the Toast.
* #param {Element} element The HTML element to stylize
* #param {Object} styles An object containing the style to apply
* #return Returns nothing
*/
Toast.prototype.stylize = function(element, styles) {
Object.keys(styles).forEach(function(style) {
element.style[style] = styles[style];
console.log(style + ": "+ styles[style]);
});
};
/**
* Generates styles to be used in an inline stylesheet. The output will be something like:
* .class {background: blue;}
* #param {String} elementClass The class of the element to style
* #param {Object} styles The style to insert into the inline stylsheet
* #return {String} The inline style as a string
*/
Toast.prototype.generateInlineStylesheetRules = function(elementClass, styles) {
var out = "." + elementClass + "{";
Object.keys(styles).forEach(function(style) {
out += style + ":" + styles[style] + ";";
});
out += "}";
return out;
};
/**
* Show the Toast
* #param {String} text The text to show inside the Toast
* #param {Object} options The object containing the options for the Toast
*/
Toast.prototype.show = function(text, options) {
this.initializeStyles();
this.generate(text, options.style.main);
var toastStage = getToastStage();
toastStage.classList.add(this.CLASS_TOAST_ANIMATED);
toastStage.classList.add(this.CLASS_TOAST_GONE);
document.body.insertBefore(toastStage, document.body.firstChild);
// This is a hack to get animations started. Apparently without explicitly redrawing, it'll just attach the class and no animations would be done
toastStage.offsetHeight;
toastStage.classList.remove(this.CLASS_TOAST_GONE);
toastStage.classList.add(this.CLASS_TOAST_VISIBLE);
var toastStage = null;
clearTimeout(Toast.prototype.timeout);
Toast.prototype.timeout = setTimeout(Toast.prototype.hide, options.settings.duration);
};
/**
* Hide the Toast that's currently shown
*/
Toast.prototype.hide = function() {
var toastStage = getToastStage();
toastStage.classList.remove(Toast.prototype.CLASS_TOAST_VISIBLE);
toastStage.classList.add(Toast.prototype.CLASS_TOAST_GONE);
toastStage = null;
// Destroy the Toast element after animations end
clearTimeout(Toast.prototype.timeout);
Toast.prototype.timeout = setTimeout(Toast.prototype.destroy, Toast.prototype.TOAST_ANIMATION_SPEED);
};
/**
* Clean up after the Toast slides away. Namely, removing the Toast from the DOM.
*/
Toast.prototype.destroy = function() {
var toastStage = getToastStage();
document.body.removeChild(toastStage);
toastStage = null;
setToastStage(null);
};
return {
Toast: Toast
};
})();

Trying to access a sublist in Netsuite with suitescript

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.

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'.

jQuery Plugin settings on multiple instances

I have a problem with settings on multiple instances on a jquery plugin.
If I cast my plugin multiple times and alert them on an onclick binding, it always alerts the same parameter.
This is my plugin.
/**
* Provide user search for input elements.
*
* Call this jQuery plugin on an input element to add a button
* for searching the active directory on userdata.
* Returned data are saved in declared target input elements.
*
* Depends:
* jQuery UI Dialog
*
* #license http://www.opensource.org/licenses/mit-license.php
* #version 1.0
*/
;(function ( $, window, document, undefined ){
var PROP_NAME = 'userdata';
var IS_IFRAME = ( self !== top ) ? true : false;
var rfuuid = new Date().getTime();
var $jParent = ( self !== top ) ? window.parent.jQuery.noConflict() : $;
/**
* Userdata manager.
* Use the singleton instance of this class, $.userdata, to interact with the date picker.
* Settings for (groups of) date pickers are maintained in an instance object,
* allowing multiple different settings on the same page.
*/
function Userdata() {
this.regional = [];
this.regional[''] = {
abortText: 'Abbrechen',
acceptText: 'Hinzufügen',
errorTitle: 'Fehler beim Suchen',
errorFilterText: 'Suchkriterien einschränken.',
errorVoidText: 'Der gesuchte Kontakt konnte nicht gefunden werden.',
errorScriptText: 'Bei der Suche ist ein Fehler aufgetreten. Falls der Fehler wieder auftritt, wenden Sie sich bitte an Ihren Webadministrator.',
searchText: 'Durchsuchen',
selectionTitle: 'Namen überprüfen',
selectionInfoText: '"%s" kommt mehrmals vor.',
selectionDescText: 'Wählen Sie die Adresse aus, die Sie verwenden möchten:'
};
this._defaults = {
ajaxURL: 'userdata.php',
buttonClass: 'rf-button secondary',
buttonContainer: '<div>',
buttonContainerClass: 'grid_2',
targets: {}
}
$.extend(this._defaults, this.regional['']);
}
$.extend(Userdata.prototype, {
/**
* Override the default settings for all instances of the userdata plugin.
*
* #param object settings The new settings to use as defaults (anonymous object).
* #return the manager object.
*/
setDefaults: function( settings ){
$.extend(this._defaults, settings);
return this;
},
/**
*
*
* #param object input DOM input element.
* #param object settings Settings for attaching new userdata functionality.
*/
_attachDialog: function( input, settings ){
var settings = $.extend(this._defaults, settings);
if ( !document.getElementById('rf-userdata-dialog') ){
var inst = $jParent('<div id="rf-userdata-dialog"></div>').appendTo('body');
inst.dialog({ autoOpen: false, close: function(){ $jParent('body').css('overflow', 'auto'); input.focus(); }, modal: true, resizable: false }).after('<span class="ui-dialog-footer" /><span class="ui-dialog-footer-edge" />');
}
else {
var inst = $('#rf-userdata-dialog');
}
input.settings = settings;
$(input).data('settings', settings);
this._attachButton(input, inst);
},
/**
*
*
* #param object input DOM input element.
* #param object inst jQuery dialog instance.
*/
_attachButton: function( input, inst ){
var manager = this,
$input = $(input);
// Create search button.
var $button = $('<button>').attr('type', 'button').html(input.settings.searchText).addClass(input.settings.buttonClass);
alert(dump($(input).data('settings'))); // WORKS FINE, everything is unique.
/**
* Bind manager._searchUserdata() function on button click.
*/
$button.bind('click', { settings : $(input).data('settings') }, function(e){
alert(dump(e.data.settings)); // DOES NOT WORK, always settings from last plugin call...
//manager._searchUserdata(input, inst);
});
/**
* Insert container with search button after input field.
*/
$input.closest('[class*="grid_"]').after(function(){
return $(input.settings.buttonContainer).addClass(input.settings.buttonContainerClass).append($button);
});
/**
* Change default enterkey behaviour on triggering the userdata button.
*/
$input.bind('focusin', function(){
$input.bind('keydown.enterOpen', function(e){
if ( e.keyCode === 13 ){
$button.trigger('click');
return false;
}
});
});
/**
* Unbind keydown event with enterOpen namespace.
*/
$input.bind('focusout', function(){
$input.unbind('keydown.enterOpen');
});
},
/**
*
*
* #param object input DOM input element.
* #param object inst jQuery dialog instance.
*/
_searchUserdata: function( input, inst ){
var manager = this,
$input = $(input),
value = $input.val();
/**
* Perform an Ajax request to get the userdata of specified user.
*/
$.ajax({
url: input.settings.ajaxURL,
dataType: 'json',
data: 'value=' + encodeURIComponent(value),
/**
* A pre-request callback function.
* Returning false in the beforeSend function will cancel the request.
*/
beforeSend: function(){
// If value is smaller than two characters is equal space character
// call showError and cancel ajax call.
if ( value.length <= 2 || value === ' ' || value === '' ){
manager._showError(input.settings.errorFilterText, inst, input);
return false;
}
},
/**
* A function to be called if the request succeeds.
*
* #see manager._showError() for error display.
* #see manager._checkName() for selecting dialog.
*
* #param object data LDAP userdata returned from server.
*/
success: function( data ){
if ( $.isEmptyObject(data) ){
manager._showError(input.settings.errorVoidText, inst, input);
}
else if ( data.error === 4 ){
manager._showError(input.settings.errorFilterText, inst, input);
}
else {
// If request returned more than one user, call checkName() function.
if ( data.length > 1 ){
manager._checkName(input, inst, data);
}
else {
manager._setUserdata(inst, data[0], input);
}
}
},
/**
* A function to be called if the request fails.
*
* #see manager._showError() for more information.
*
* #param object jqXHR XMLHttpRequest object.
* #param string textStatus Description of occurred error.
* #param object errorThrown Exception object.
*/
error: function( jqXHR, textStatus, errorThrown ){
manager._showError(input.settings.errorScriptText, inst, input);
}
});
},
/**
*
*
* #param string error Error to display.
* #param object inst jQuery dialog instance.
* #param object input DOM input element.
*/
_showError: function( error, inst, input ){
inst.html('<div class="ui-dialog-container">' + error + '</div>')
inst.dialog({ title: input.settings.errorTitle, width: 400 });
inst.dialog('open');
},
/**
*
*
* #param object input DOM input element.
* #param object inst jQuery dialog instance.
* #param array data LDAP userdata.
*/
_checkName: function( input, inst, data ){
var manager = this,
$container = $('<div>').addClass('ui-dialog-container').html('<p>' + sprintf(input.settings.selectionInfoText, $(input).val()) + '</p><p>' + input.settings.selectionDescText + '</p>'),
$tableWrapperOuter = $('<div>').addClass('rf-select-list-wrapper-outer'),
$tableWrapperInner = $('<div>').addClass('rf-select-list-wrapper-inner').css('height', 240),
$table = $('<table>').addClass('rf-select-list'),
$thead = $('<thead>').html('<tr><th>Name</th><th>Position</th><th>Telefon</th><th>Ort</th><th>E-Mail</th><th>Alias</th></tr>'),
$tbody = $('<tbody>');
// Loop trough userdata and create a table row for each entry.
for ( var i = 0, length = data.length; i < length; i++ ){
var $row = $('<tr>').html(function(){
this.onselectstart = function(){ return false; }
//return '<td class="user-icon">' + data[i].sn + ' ' + data[i].givenname + '</td><td>' + data[i].title + '</td><td>' + data[i].telephonenumber + '</td><td>' + data[i].location + '</td><td>' + data[i].mail + '</td><td>' + data[i].cn + '</td>';
return '<td class="user-icon">' + data[i].sn + ' ' + data[i].givenname + '</td><td>' + data[i].title + '</td><td>' + data[i].location + '</td><td>' + data[i].cn + '</td>';
});
$row.bind('click', { obj: data[i] }, function(e){
var $this = $(this);
// Temp-save data from selection for ok-button.
inst.selection = e.data.obj;
$this.siblings().removeClass('ui-state-active');
$this.addClass('ui-state-active');
});
$row.bind('dblclick', { obj: data[i] }, function(e){
inst.dialog('close');
manager._setUserdata(inst, e.data.obj, input);
});
$row.appendTo($tbody);
}
// Trigger first row selection.
$tbody.find('tr').first().trigger('click');
// Hide scrollbar on form body to prevent scrolling problem.
$jParent('body').css('overflow', 'hidden');
// Create buttons and append them to a container.
var $buttonAccept = $('<button>').addClass("rf-button primary").html(input.settings.acceptText).bind('click', function(){
inst.dialog('close');
manager._setUserdata(inst, inst.selection, input);
});
var $buttonAbort = $('<button>').addClass("rf-button secondary").html(input.settings.abortText).bind('click', function(){
inst.dialog('close');
});
// Toggle 'rf-button-hover' class on buttons hover state.
$buttonAccept.hover(function(){ $buttonAccept.toggleClass('rf-button-hover'); });
$buttonAbort.hover(function(){ $buttonAbort.toggleClass('rf-button-hover'); });
var $buttonContainer = $('<div>').addClass('float-right').append($buttonAccept, $buttonAbort);
// Append dialog html to container.
$container.append($tableWrapperOuter.append($tableWrapperInner.append($table.append(/*$thead,*/ $tbody))), $buttonContainer);
inst.html($container);
inst.dialog({ title: input.settings.selectionTitle, width: 800 });
inst.dialog('open');
},
/**
*
*
* #param object inst jQuery dialog instance.
* #param array data LDAP userdata.
*/
_setUserdata: function( inst, data, input ){
for ( var target in input.settings.targets ){
var values = [];
if ( typeof(input.settings.targets[target]) === 'object' ){
for ( var i = 0, length = input.settings.targets[target].length; i < length; i++ ){
values.push(data[input.settings.targets[target][i]]);
}
}
else {
values.push(data[input.settings.targets[target]]);
}
$(target).val(values.join(' '));
}
}
});
/**
* Invoke the userdata functionality.
*
* #param object options Settings for attaching new userdata functionality.
* #return jQuery object.
*/
$.fn.userdata = function( options ){
// Verify an empty collection wasn't passed.
if ( !this.length ){
return this;
}
/**
* Loop through each plugin object.
*/
return this.each(function(){
$.userdata._attachDialog(this, options);
});
};
$.userdata = new Userdata();
$.userdata.uuid = new Date().getTime();
})( jQuery, window, document );
I call it in my html multiple times:
$('#inputid_1').userdata({ targets: {'#targetid_1': 'cn'} });
$('#inputid_2').userdata({ targets: {'#targetid_2': 'phone'} });
Now if you look at the _attachButton method, there are two alerts. One outside the click bind and one inside the click bind. Outside the click bind, the settings are unique foreach plugin call. Inside the click bind, it always alerts the settings from the last call, even if I pass them with event.data.
Extend the settings like this:
var settings = $.extend(settings, this._defaults); // invert the params
or
var settings = $.extend({}, this._defaults, settings);
Why ?
The $.extend() takes as first param a target. So you were merging the properties of this._default with the settings and not the contrary.
The second form (with {}) says: ignore target, let both this._default and settings untouched, simply return a merged object (hope i'm clear ^^).
See jQuery documentation about .extend().

Categories