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 <a href="%s">Webadministrator.</a>',
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:
or
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().