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().
Related
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>
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
};
})();
Hi Stackoverflow Community,
I've got the following problem and I can't handle it myself:
I've got the following search form:
<li class="list-group-item active" data-ignore-list-search="ignore"><?php echo $translate->_('Animals'); ?><span class="icon-search pull-right" rel="SearchBox" data-placement="bottom" data-container="body"></span>
<div id="popover_content_wrapper" class="hide">
<input placeholder="<?php echo $translate->_('Search'); ?> " id="FilterForm" class="filterform filterinput" type="text">
</div>
</li>
And I'm using following jQuery code to filter ul items:
(function(jQuery){
var ListSearch = function() {};
ListSearch.prototype = {
/**
* run
* start incremental search.
* #param {} input
* #param {} target
* #returns {}
*/
run: function(input, target){
var _this = this;
var tagName = target.get(0).tagName;
input.on('keyup', function(e){
text = _this.getInputText(input);
switch(tagName){
case 'TABLE':
_this.listSearchTable(target, text);
break;
case 'UL':
_this.listSearchListUl(target, text);
break;
case 'OL':
_this.listSearchListOl(target, text);
break;
case 'DL':
_this.listSearchListDl(target, text);
break;
default:
throw new Error('Illegal tag name ' + targetObject.targetTagName);
}
});
},
getInputText: function(input){
return input.val();
},
/**
* tag table
*/
listSearchTable: function(target, text){
this.listSearchCommon('tr', target, text);
},
/**
* tag ul
*/
listSearchListUl: function(target, text){
this.listSearchCommon('li', target, text);
},
/**
* tag ol
*/
listSearchListOl: function(target, text){
return this.listSearchListUl(target, text);
},
/**
* tag dl
*/
listSearchListDl: function(target, text){
this.listSearchCommon('dd dt', target, text);
},
/**
* commondSearchList
*/
listSearchCommon: function(tagName ,target, text){
var _this = this;
var searchWords = this.searchWordToWords(text);
var wordLength = searchWords.length;
target.find(tagName).each(function(){
var thisJQuery = jQuery(this);
if (thisJQuery.data('ignore-list-search') === 'ignore') return true;
var body = _this.getBody(thisJQuery);
var displayFlag = true;
var wordCount = 0;
for(wordCount = 0; wordCount < wordLength; wordCount++){
var word = searchWords[wordCount];
var pattern = new RegExp(word, 'i');
if ( !body.match(pattern) ) {
displayFlag = false;
break;
}
}
jQuery(this).css('display', _this.getDisplayProperty(tagName, displayFlag));
return true;
})
},
/**
* getDisplayProperty
* #param {} tagName
* #param {} flag
* #returns {}
*/
getDisplayProperty: function(tagName, flag){
switch(tagName){
case 'tr':
return flag?'table-row':'none';
case 'li':
return flag?'block':'none';
case 'dd dt':
return flag?'list-item':'none';
default:
throw new Error('Illegal tag name ' + targetObject.targetTagName);
}
},
/**
* getBody
* #returns {}
*/
getBody: function(target){
var body = target.text();
return jQuery.trim(body);
},
/**
* searchWordToWords
* a search text split to search words.
* #param {} body
* #param {} searchWord
* #returns {}
*/
searchWordToWords: function(text){
text = jQuery.trim(text);
var pattern = new RegExp(/[ \-\/]/);
var words = text.split(pattern);
// delete empty element
var newWords = new Array();
var wordsLength = words.length;
var wordsCount = 0;
for (wordsCount = 0; wordsCount < wordsLength; wordsCount++){
var word = words[wordsCount];
if (word != ""){
newWords.push(words[wordsCount]);
}
}
return newWords;
}
}
/**
* Main stream
*/
jQuery.fn.listSearch = function(input, options){
var options = jQuery.extend(jQuery.fn.listSearch.defaults, options);
// set using objects.
var target = jQuery(this);
switch (jQuery.type(input)){
case 'string':
input = jQuery(input);
break;
case 'object':
input = input;
break;
default:
throw 'Argiment value "' + input + '" is invalid.';
}
// Event start
listSearch = new ListSearch();
listSearch.run(input, target);
return target;
};
/**
* default settings.
*/
jQuery.fn.listSearch.defaults = {
};
$('#AnimalList').listSearch('#FilterForm')
;
})(jQuery);
This works fine .. but if I put the #FilterForm input into the popover it doesn't filter out my uls anymore.
My Popover jQuery code:
$('[rel=SearchBox]').popover({
html : true,
content: function() {
return $('#popover_content_wrapper').html();
},
trigger: 'click'
});
my UL hasn't any special tags or anything.
Thanks in advance and sorry for my weird English [Ed - fixed]: I am from Germany.
Greetings,
Daniel
#user2908086, Its because you are using the same "filterform" text twice in the same input field but in different Types! Here is what you use: id="FilterForm" + class="filterform"! Locate the "filterform" class text in your CSS and rename the "filterform" and make sure its not the same as your ID. Then add the same class name back in your input and see if that will work for you! Good luck!
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'.
I have a javascript which works perfectly in chrome, FF2/3, and IE9
158: drop_area = $('#drop_area');
159: element = ui.helper;
however I get the following error in IE7 amd IE8:
SCRIPT438: Object doesn't support this property or method
dragdrop.js, line 158 character 2
My knowledge of IE7's debugging features is pretty limited but it doesn't look like I can really inspect the object in question from the console. Does anyone know what might be going on here or how I can better debug this error
EDIT:
Realized a little bit more context might be helpful
function on_element_drop(event, ui){
drop_area = $('#drop_area');
element = ui.helper;
on_element_drop is a callback method for a jQuery UI droppable 'drop' event
/*
* dragdrop.js
* Author: Casey Flynn
* June 10, 2011
* Global variables available to all functions
*/
//Keeps track of elements present in droppable and corresponding offsets/positions
var base_url = 'http://www.bla/';
var global_positions = new Array();
var current_item_group = 0;
var loaded = new Array();
var preloading = true;
var items = new Array(
new Array(
new Array(0, '2-Dollar'),
new Array(1, 'Cards'),
new Array(2, 'Cheese'),
new Array(3, 'Coupons'),
new Array(4, 'DogTags')),
new Array(
new Array(5, 'Doodle'),
new Array(6, 'Dreamcatcher'),
new Array(7, 'Fish'),
new Array(8, 'Fortune'),
new Array(9, 'Groucho')),
new Array(
new Array(10, 'HandTurkey'),
new Array(11, 'Key'),
new Array(12, 'Lolipop'),
new Array(13, 'LotteryTicket'),
new Array(14, 'Map')),
new Array(
new Array(15, 'Napkin'),
new Array(16, 'Notepad'),
new Array(17, 'OldPhoto'),
new Array(18, 'Oragami'),
new Array(19, 'Photo_Baby')),
new Array(
new Array(20, 'Photo_DJ'),
new Array(21, 'Photo_Dogs'),
new Array(22, 'Photo_Moustache'),
new Array(23, 'Pick'),
new Array(24, 'RabitsFoot')),
new Array(
new Array(25, 'Recipe'),
new Array(26, 'Reminder'),
new Array(27, 'Ribbon'),
new Array(28, 'SheetMusic'),
new Array(29, 'Smiley')),
new Array(
new Array(30, 'Spork'),
new Array(31, 'Tape'),
new Array(32, 'Ticket'),
new Array(33, 'USB'),
new Array(34, 'Viewmaster')
)
);
/*
* jQuery 'ready' handler, executes after DOM is ready and
* sets draggable/droppable properties of associated elements
*/
$(function(){
for(var i = 0; i < items.length; i++)
loaded[i] = false;
load_elements(0);
$('#drop_area').droppable({
//accept: '.draggable',
//hoverClass: 'vel_content_active',
drop: on_element_drop
});
$('#countdown').html((10 - global_positions.length)+'');
});
// preloads an array of images
function preload(arrayOfImages) {
$(arrayOfImages).each(function(){
console.log('Preloading ' + this);
$('<img/>')[0].src = this;
// Alternatively you could use:
// (new Image()).src = this;
});
}
/*
* Loads the first set of elements from 'items'
*/
function load_elements(arg0){
set = items[arg0];
box_handle = $('.bottom_content');
elements = '';
//construct html for elements to be added
for(i=0; i<set.length; i++){
elements += '<div class="draggable"><img alt="' + set[i][0] + '" src="' + base_url + 'application/assets/images/items/' + set[i][1] + '.png" /></div>';
}
//clear whatever was in there
box_handle.empty();
// element parent container
box_handle.html(elements);
//assign draggable status
$('.draggable').draggable({
revert: true,
revertDuration: 0,
helper: 'clone'
});
loaded[arg0] = true;
if(preloading){
var prev = next_elements(-1);
var next = next_elements(1);
if(!loaded[prev]){
preload(items[prev])
loaded[prev] = true;
}
if(!loaded[next]){
preload(items[next])
loaded[prev] = true;
}
}
}
function next_elements(arg0){
if((current_item_group + arg0) == -1){
return 6;
}else{
return ((current_item_group + arg0) % 7);
}
}
/*
* Cycles through element groups presented in .bottom_content
-1 to the left 1 to the right
*/
function cycle_elements(arg0){
if((current_item_group + arg0) == -1){
current_item_group = 6;
}else{
current_item_group = ((current_item_group + arg0) % 7);
}
load_elements(current_item_group);
}
/*
* Callback function on drop event for droppable
* Determines position of dropped element in droppable
*/
function on_element_drop(event, ui){
drop_area = $('#drop_area');
element = ui.helper;
//Find relative x/y position of element inside drop_area
var x = Math.floor((element.offset().left - drop_area.offset().left) / drop_area.width() * 100);
var y = Math.floor((element.offset().top - drop_area.offset().top) / drop_area.height() * 100);
//console.log(ui); return;
//console.log(ui.draggable.context.className.indexOf('draggable_dropped'));
if(ui.draggable.context.className.indexOf('draggable_dropped') == -1){
if(global_positions.length >= 10)
return;
//record element position and id
row = new Array(parseInt($(ui.draggable).find("img").attr('alt')),
x,
y);
//Add copy of item to drop_area at same x/y position where it was dropped
add_element_copy_to_div(row);
add_element_copy_to_global_positions(row);
}else{
//Item has already been dropped and is being adjusted, update global_positions
//update global_positions
id = ui.draggable.context.id;
update_global_positions(id, x, y);
}
//$('#countdown').html((10 - global_positions.length)+'');
console.log(global_positions);
}
/*
* Finds element in global_positions and updates x & y values
*/
function update_global_positions(id, newX, newY){
image_id = global_positions[id][0];
global_positions[id] = new Array(image_id, newX, newY);
/*
var old_array = global_positions[find_index(global_positions, index)];
var new_array = new Array(old_array[0], newX, newY);
//.splice(i,1,pos) -- remove 1 element at index i and replace with pos
global_positions.splice(index,1,new_array);
*/
}
/*
* Helper function, determines if element is already present in 'global_positions'
* Replaces if present, adds otherwise
*/
function add_element_copy_to_global_positions(pos){
global_positions.push(pos);
/*
var found = false;
for(i=0;i<global_positions.length;i++){
if(global_positions[i][0] == pos[0]){
//.splice(i,1,pos) -- remove 1 element at index i and replace with pos
global_positions.splice(i,1,pos);
found = true;
}
}
if(!found)
global_positions.push(pos);
*/
}
/*
* Helper function, adds a copy of the draggable that was dropped into the droppable
* for user visualization
*/
function add_element_copy_to_div(pos){
drop_area = $('#drop_area');
id = global_positions.length;
console.log('id: ' + id);
//Convert % x&y offsets into absolute pixel offsets based on div size
x = Math.floor(drop_area.width() * (pos[1] / 100));
y = Math.floor(drop_area.height() * (pos[2] / 100));
/*------- Find filename of image that has been dropped ------*/
index = find_index(items[current_item_group], pos[0]);
filename = items[current_item_group][index][1];
drop_area.append('<div style="position:absolute;" class="draggable_dropped" id="' + id + '"><img src="' + base_url + 'application/assets/images/items/' + filename + '.png" /></div>');
$('#'+id).css('left', x);
$('#'+id).css('top', y);
//Set the newly dropped element to draggable so it can be repositioned
$('#'+id).draggable({
stop:function(event, ui){
if(!is_valid_position(ui)) //invalid drop
delete_item(ui);
}
});
}
/*
* deletes element from global_positions and #drop_area when user drops item outside #drop_area
* also adjusts id attributes of all items
*/
function delete_item(ui){
id = ui.helper.context.id;
$('#'+id).remove();
global_positions.splice(id, 1);
$('#drop_area div').each(function(index){
if(parseInt($(this).attr('id')) > parseInt(id))
$(this).attr('id', parseInt($(this).attr('id')) - 1);
});
console.log(global_positions);
}
/*
* helper for add_element_copy_to_div
*/
function is_valid_position(ui){
drop_area = $('#drop_area');
element = ui.helper;
//Find relative x/y position of element inside drop_area
var x = Math.floor((element.offset().left - drop_area.offset().left) / drop_area.width() * 100);
var y = Math.floor((element.offset().top - drop_area.offset().top) / drop_area.height() * 100);
if( (x < -5) ||
(x > 105) ||
(y < -5) ||
(y > 105))
return false;
return true;
}
/*
* helper for add_element_copy_to_div
*/
function find_index(items, search_index){
for(i=0; i < items.length; i++){
if(items[i][0] == search_index)
return i;
}
}
/*
* Convert global_position array to JSON and submit to server via ajax along with csrf_token
*/
function update_layout(){
$.ajax({
url: '/projects/velcro/index.php/welcome/update_layout',
type: 'post',
data: {'layout_json' : $.toJSON(global_positions), 'ci_csrf_token' : $('input[name=ci_csrf_token]').val()},
success: function(data, textStatus, jqXHR){
//Redirect user to next page here...
if(data == '1'){
//alert('Layout successfully saved');
}else{
//alert('Layout save failed');
}
location.href = 'http://www.messageamplify.com/projects/velcro/index.php/welcome/create2';
},
error: function(jqXHR, textStatus, errorThrown){
console.log('error: '+jqXHR);
console.log(textStatus);
console.log(errorThrown);
}
});
}
//End file 'dragdrop.js'
drop_area = $('#drop_area');
This line will always throw a error in IE. That's because in IE every element with id will be accessible through the global window object, in this case, window.drop_area. But the fact that window is a global object makes possible to access the object without the global, in this case, just drop_area.
So, the sentence drop_area = $('#drop_area'); is not trying to assign a object to a variable, but is trying to overwrite a element reference with another object. This is the error you are seeing as a runtime exception.
To bypass this exception, you need to assign the jQuery object to a variable. To do this, you have two choices:
use a var statement to scope the variable inside the function that contains the code and hide the access to window.drop_area from the global, like var drop_area = $('#drop_area');, or
use another variable name, like var dropArea = $('#drop_area');
And, as a good advice, always give a scope to the variables you are using with var statement.
IE8 has a built in debugger. Press F12, click the Script tab, click Start Debugging, and debug away.