Berichten met tag Herbruikbaarheid
JavaScript wrappen in herbruikbare jQuery plugins
0In mijn vorige blog Gebruikersergernis: verspringende scrollbars is een oplossing in JavaScript uitgewerkt. jQuery is een veelgebruikte JavaScript library die gebruik maakt van een plugin architectuur. Daarmee kun je uitbreidingen realiseren. Plugins maken het mogelijk om functionaliteit en programmatuur eenvoudig herbruikbaar te maken. De JavaScript-oplossing om scrollbarposities te onthouden, kan in een jQuery plugin verwerkt worden om de herbruikbaarheid en het onderhoud van de functionaliteit te verbeteren. Ook zijn er nieuwe mogelijkheden door gebruik te maken van jQuery. Zo is het eenvoudig om configuraties en extra functionaliteit toe te voegen en deze netjes te bundelen binnen de namespace van de plugin.
jQuery plugins
jQuery heeft een standaardarchitectuur voor het schrijven van plugins. Andere plugins en libraries, zoals jQuery UI, maken hier eveneens gebruik van. Een plugin heeft een eigen namespace waarin de methodes van de plugin ook binnen dezelfde namespace blijven. Dit voorkomt onduidelijkheden en conflicten met andere plugins en jQuery functionaliteiten. Het framework van de plugin architectuur ziet er als volgt uit:
(function($){
var methods = {
init : function(options) {
scrollpositionSave : function() {
scrollpositionRestoreAll : function() {
scrollpositionClearAll : function() {
};
//Plugin namespace 'scrollPosition' within jQuery
$.fn.scrollPosition = function(method) {
// Method calling logic
if methods[method]) {
return methods[ method ].apply( this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || ! method) {
return methods.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist on jQuery.scrollPosision');
}
};
})(jQuery);
Methodes en events koppelen
De scrollbarmethodes scrollpositionSave(), scrollpositionRestoreAll() en scrollpositionClearAll() worden verwerkt in de plugin architectuur. De werking en benaming van deze methodes zal iets wijzigen door de nieuwe plugin structuur. Ook het koppelen van het scroll event van de HTML DOM-elementen wordt naar de plugin verplaatst. Het koppelen van het event met scrollpositionSave() ziet er in de plugin structuur als volgt uit:
var methods = {
remember: function () {
var obj = $(this);
obj.each(function () {
$(this).scroll(function () {
var element = this;
if (elementCanBeIdentified(element) && elementHasScrolled(element)) {
setCookie(element.id, element.scrollTop);
}
});
});
$(this).scrollPosition("restore");
}
};
Vervolgens wordt een ‘remember’-methode gemaakt die voor ieder object in de jQuery selectie, logica koppelt aan de scrollmethode. Hierin wordt, net zoals in de oude situatie, de schuifbalkpositie in een cookie opgeslagen. De overige methodes worden op dezelfde manier verwerkt in de ‘methods’ array variabele.
Elementen selecteren
Wat verder opvalt is de context van geselecteerde DOM-elementen waarin de plugin zich bevindt. Dit gebeurt op de gebruikelijke jQuery selectie-manier. De gewenste elementen worden op de pagina geselecteerd en doorgegeven aan de plugin:
$().ready(function () {
$('.RememberScrollPosition').scrollPosition('remember');
});
Met $(‘.RememberScrollPosition’) worden DOM-elementen geselecteerd welke ‘RememberScrollPosititon’ als waarde hebben voor het class attribuut. Vervolgens wordt op deze selectie met .scrollPosition(‘remember’) de jQuery plugin ‘scrollPosition’ gestart en en de methode ‘remember’ aangeroepen. Hierdoor is het mogelijk om in de plugin met .each de geselecteerde elementen te doorlopen en per element uit te voeren. Alle bewerkingen in de methodes gebeuren op basis van de geselecteerde elementen. Het is dus niet meer nodig om alle cookies te doorlopen.
Configureren
In jQuery plugins is het gebruikelijk om de werking instelbaar te maken zodat de plugin breed inzetbaar is. Het wordt bijvoorbeeld mogelijk conflicten met naamgevingen van cookies te voorkomen door eenvoudig een prefix toe te voegen aan de cookienaam, aangevuld met een identificatie van de pagina waarop het element zich bevindt. Wanneer een element met hetzelfde ID zich op een andere pagina bevindt zal de scrollpositie niet worden ingesteld. In de oude situatie was dit nog wel het geval. Ook zullen we, in de vorm van een namespace, gebruik maken van een koppelkarakter tussen de cookie prefix, de paginanaam en het ID van het DOM-element. Verder zullen we debug/log informatie geven voor ontwikkelaars. Deze instellingen en hun standaardwaarden nemen we als volgt op in de jQuery-plugin in de ‘settings’ variabele:
(function ($) {
// Default/initial settings for this jQuery plugin.
var settings = {
cookiePrefix: "scrollpos",
page: "default",
concatChar: "::",
debug: false
};
var methods = {
//...
})(jQuery);
Deze plugin instellingen kunnen we meegeven in de aanroep van de methode:
$().ready(function () {
$('.RememberScrollPosition').scrollPosition('remember', { page: "Default.aspx", debug: true });
});
In de plugin methodes moeten de parameters in de methodeaanroep dan enkel nog verwerkt worden in de standaardwaarden van de jQuery-plugin:
var methods = {
remember: function (options) {
options = $.extend(settings, options);
//...
Private methods
Debug informatie en andere logica die enkel binnen de plugin beschikbaar hoeft te zijn, houden we binnen de namespace van de plugin en publiceren we niet met behulp van de methods-constructie. Het verwerken van debug functionaliteit en het toevoegen van de cookie namespace ziet er dan als volgt uit:
(function ($) {
// Default/initial settings for this jQuery plugin.
var settings = {
cookiePrefix: "scrollpos",
page: "default",
concatChar: "::",
debug: false
};
// Public methods
var methods = {
remember: function (options) {
_debug("Calling jQuery.scrollPosition.remember");
options = $.extend(settings, options);
var obj = $(this);
obj.each(function () {
$(this).scroll(function () {
var element = this;
if (_elementCanBeIdentified(element)) {
if (_elementHasScrolled(element)) {
var elementStorageKey = _getElementStoragePropertyName(element);
var elementScrollPosition = element.scrollTop;
_setCookie(elementStorageKey, elementScrollPosition);
_debug("Remembered scroll position in cookie: " + _getElementStorageProperty(element));
} else {
$(this).scrollPosition("forget", options);
}
}
});
});
$(this).scrollPosition("restore", options);
}
};
$.fn.scrollPosition = function (method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || !method) {
return methods.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist on jQuery.scrollPosition');
}
};
// Private methods
function _debug(logMessage) {
if (settings._debug == false) {
return;
}
if (window.console != undefined) {
console.log(logMessage);
} else {
//alert(logMessage);
}
}
function _getElementStorageProperty(element) {
return _getElementStoragePropertyName(element) + "=" + element.scrollTop;
};
function _getElementStoragePropertyName(element) {
return _getElementStoragePropertyPrefix() + element.id;
};
function _getElementStoragePropertyPrefix() {
return settings.cookiePrefix + settings.concatChar + settings.page + settings.concatChar;
};
function _setCookie(key, value) {
document.cookie = key + "=" + value;
}
function _elementCanBeIdentified(element) {
try {
return document.getElementById(element.id) && element.id.length > 0;
}
catch (e) {
return false;
}
}
function _elementHasScrolled(element) {
try {
return element.scrollTop > 0;
}
catch (e) {
return null;
}
}
})(jQuery);
De debug logging informatie – die de werking van de programmatuur inzichtelijker maakt voor ontwikkelaars – ziet er als volgt uit:
Conclusies
Zoals je kunt zien is het vrij eenvoudig om bestaande JavaScript-functionaliteit in een jQuery plugin te wrappen. De voordelen zijn direct duidelijk; de code is volgens een duidelijke, vaste en (ook voor andere ontwikkelaars) herkenbare structuur uitgewerkt, hetgeen de onderhoudbaarheid en kwaliteit van de code ten goede komt. Ook kunnen we de standaard functionaliteit van jQuery eenvoudig gebruiken in de plugins. Door gebruik te maken van de plugin hebben we eenvoudig uitbreidingen kunnen realiseren. Deze functionaliteit is in meerdere situaties te gebruiken.
» Download hier het demo project (Visual Studio 2010)
- Visual Studio 2010
- ASP.NET 4.0 Web Application
- jQuery 1.4.1 (standaard beschikbaar in ASP.NET 4.0 Web Application)
De volledige jQuery plugin ziet er als volgt uit:
(function ($) {
// Default/initial settings for this jQuery plugin.
var settings = {
cookiePrefix: "scrollpos",
page: "default",
concatChar: "::",
debug: false
};
// Public methods
var methods = {
// Initialize plugin
init: function (options) {
},
// Store scrollbar position on each scroll of the selected elements
// and restores known scrollbar positions (useful when script is fired, e.g. on request/postback).
// Uses cookie storage.
remember: function (options) {
_debug("Calling jQuery.scrollPosition.remember");
options = $.extend(settings, options);
var obj = $(this);
obj.each(function () {
$(this).scroll(function () {
var element = this;
if (_elementCanBeIdentified(element)) {
if (_elementHasScrolled(element)) {
var elementStorageKey = _getElementStoragePropertyName(element);
var elementScrollPosition = element.scrollTop;
_setCookie(elementStorageKey, elementScrollPosition);
_debug("Remembered scroll position in cookie: " + _getElementStorageProperty(element));
} else {
$(this).scrollPosition("forget", options);
}
}
});
});
$(this).scrollPosition("restore", options);
},
// Restores known scrollbar positions. Uses cookie storage.
restore: function (options) {
_debug("Calling jQuery.scrollPosition.restore");
options = $.extend(settings, options);
var obj = $(this);
obj.each(function () {
var element = this;
var cookies = _getCookiesAsArray();
try {
var elementStorageKey = _getElementStoragePropertyName(element);
var elementScrollPosition = cookies[elementStorageKey];
_elementSetScrollPosition(element.id, elementScrollPosition);
_debug("Restored scroll position: " + element.id + "=" + elementScrollPosition);
}
catch (e) {
}
});
},
// Forget known scrollbar positions. Expire cookie storage.
// Note: does not decouple the scroll event which may store the scroll
// position again if 'remember' is called on the element!
forget: function (options) {
_debug("Calling jQuery.scrollPosition.forget");
options = $.extend(settings, options);
var obj = $(this);
obj.each(function () {
var element = this;
try {
var elementStorageKey = _getElementStoragePropertyName(element);
_clearCookie(elementStorageKey);
_debug("Cleared scroll position: " + element.id);
}
catch (e) {
}
});
}
};
// Private methods
function _debug(logMessage) {
if (settings.debug == false) {
return;
}
if (window.console != undefined) {
console.log(logMessage);
} else {
//alert(logMessage);
}
}
function _getElementStorageProperty(element) {
return _getElementStoragePropertyName(element) + "=" + element.scrollTop;
};
function _getElementStoragePropertyName(element) {
return _getElementStoragePropertyPrefix() + element.id;
};
function _getElementStoragePropertyPrefix() {
return settings.cookiePrefix + settings.concatChar + settings.page + settings.concatChar;
};
function _getCookiesAsArray() {
var result = {};
var cookieContent = document.cookie;
if (cookieContent.length > 0) {
var cookies = cookieContent.split(";");
for (var i = 0; i < cookies.length; i++) {
var keyValuePair = cookies[i].split("=");
try {
var key = keyValuePair[0].replace(" ", "");
var value = keyValuePair[1];
result[key] = value;
}
catch (e) {
}
}
}
return result;
}
function _setCookie(key, value) {
document.cookie = key + "=" + value;
}
function _clearCookie(key) {
try {
var date = new Date();
document.cookie = key + "=0;expires=" + date.toGMTString() + ";";
return true;
}
catch (e) {
return false;
}
}
function _elementCanBeIdentified(element) {
try {
return document.getElementById(element.id) && element.id.length > 0;
}
catch (e) {
return false;
}
}
function _elementHasScrolled(element) {
try {
return element.scrollTop > 0;
}
catch (e) {
return null;
}
}
function _elementSetScrollPosition(elementId, position) {
try {
document.getElementById(elementId).scrollTop = position;
return true;
}
catch (e) {
return false;
}
}
//Prevent jQuery namespacing cluttering by using the suggested jQuery plugin architecture.
//http://docs.jquery.com/Plugins/Authoring#Plugin_Methods
$.fn.scrollPosition = function (method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || !method) {
return methods.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist on jQuery.scrollPosition');
}
};
})(jQuery);

