El primer paso para la organización del código es separar la aplicación en distintas piezas. Muchas veces, este esfuerzo suele ser suficiente para mantener al código en orden.
10.2.1. El objeto literal
Un objeto literal es tal vez la manera más simple de encapsular código relacionado. Este no ofrece ninguna privacidad para propiedades o métodos, pero es útil para eliminar funciones anónimas, centralizar opciones de configuración, y facilitar el camino para la reutilización y refactorización.
Un objeto literal
var myFeature = {
myProperty : 'hello',
myMethod : function() {
console.log(myFeature.myProperty);
},
init : function(settings) {
myFeature.settings = settings;
},
readSettings : function() {
console.log(myFeature.settings);
}
};
myFeature.myProperty; // 'hello'
myFeature.myMethod(); // registra 'hello'
myFeature.init({ foo : 'bar' });
myFeature.readSettings(); // registra { foo : 'bar' }
El objeto posee una propiedad y varios métodos, los cuales son públicos (es decir, cualquier parte de la aplicación puede verlos). ¿Cómo se puede aplicar este patrón con jQuery? Por ejemplo, en el siguiente código escrito en el estilo tradicional:
// haciendo click en un item de la lista se carga cierto contenido,
// luego utilizando el ID de dicho item se ocultan
// los items aledaños
$(document).ready(function() {
$('#myFeature li')
.append('<div/>')
.click(function() {
var $this = $(this);
var $div = $this.find('div');
$div.load('foo.php?item=' +
$this.attr('id'),
function() {
$div.show();
$this.siblings()
.find('div').hide();
}
);
});
});
Si el ejemplo mostrado representa el 100% de la aplicación, es conveniente dejarlo como esta, ya que no amerita hacer una reestructuración. En cambio, si la pieza es parte de una aplicación más grande, estaría bien separar dicha funcionalidad de otras no relacionadas. Por ejemplo, es conveniente mover la URL a la cual se hace la petición fuera del código y pasarla al área de configuración. También romper la cadena de métodos para hacer luego más fácil la modificación.
Utilizar un objeto literal para una funcionalidad jQuery
var myFeature = {
init : function(settings) {
myFeature.config = {
$items : $('#myFeature li'),
$container : $('<div class="container"></div>'),
urlBase : '/foo.php?item='
};
// permite sobreescribir la configuración predeterminada
$.extend(myFeature.config, settings);
myFeature.setup();
},
setup : function() {
myFeature.config.$items
.each(myFeature.createContainer)
.click(myFeature.showItem);
},
createContainer : function() {
var $i = $(this),
$c = myFeature.config.$container.clone()
.appendTo($i);
$i.data('container', $c);
},
buildUrl : function() {
return myFeature.config.urlBase +
myFeature.$currentItem.attr('id');
},
showItem : function() {
var myFeature.$currentItem = $(this);
myFeature.getContent(myFeature.showContent);
},
getContent : function(callback) {
var url = myFeature.buildUrl();
myFeature.$currentItem
.data('container').load(url, callback);
},
showContent : function() {
myFeature.$currentItem
.data('container').show();
myFeature.hideContent();
},
hideContent : function() {
myFeature.$currentItem.siblings()
.each(function() {
$(this).data('container').hide();
});
}
};
$(document).ready(myFeature.init);
La primera característica a notar es que el código es más largo que el original — como se dijo anteriormente, si este fuera el alcance de la aplicación, utilizar un objeto literal seria probablemente una exageración.
Con la nueva organización, las ventajas obtenidas son:
- Separación de cada funcionalidad en pequeños métodos. En un futuro, si se quiere cambiar la forma en que el contenido se muestra, será claro en donde habrá que hacerlo. En el código original, este paso es mucho más difícil de localizar.
- Se eliminaron los usos de funciones anónimas.
- Las opciones de configuración se movieron a una ubicación central.
- Se eliminaron las limitaciones que poseen las cadenas de métodos, haciendo que el código sea más fácil para refactorizar, mezclar y reorganizar.
Por sus características, la utilización de objetos literales permiten una clara mejora para tramos largos de código insertados en un bloque $(document).ready()
. Sin embargo, no son más avanzados que tener varias declaraciones de funciones dentro de un bloque $(document).ready()
.
10.2.2. El patrón modular
El patrón modular supera algunas limitaciones del objeto literal, ofreciendo privacidad para variables y funciones, exponiendo a su vez (si se lo desea) una API pública.
El patrón modular
var feature =(function() {
// variables y funciones privadas
var privateThing = 'secret',
publicThing = 'not secret',
changePrivateThing = function() {
privateThing = 'super secret';
},
sayPrivateThing = function() {
console.log(privateThing);
changePrivateThing();
};
// API publica
return {
publicThing : publicThing,
sayPrivateThing : sayPrivateThing
}
})();
feature.publicThing; // registra 'not secret'
feature.sayPrivateThing();
// registra 'secret' y cambia el valor de privateThing
En el ejemplo, se autoejecuta una función anónima la cual devuelve un objeto. Dentro de la función, se definen algunas variables. Debido a que ellas son definidas dentro de la función, desde afuera no se tiene acceso a menos que se pongan dentro del objeto que se devuelve. Esto implica que ningún código fuera de la función tiene acceso a la variable privateThing
o a la función sayPrivateThing
. Sin embargo, sayPrivateThing
posee acceso a privateThing
y changePrivateThing
debido a estar definidos en el mismo alcance.
El patrón es poderoso debido a que permite tener variables y funciones privadas, exponiendo una API limitada consistente en devolver propiedades y métodos de un objeto.
A continuación se muestra una revisión del ejemplo visto anteriormente, con las mismas características, pero exponiendo un único método público del modulo, showItemByIndex()
.
Utilizar el patrón modular para una funcionalidad jQuery
$(document).ready(function() {
var feature = (function() {
var $items = $('#myFeature li'),
$container = $('<div class="container"></div>'),
$currentItem,
urlBase = '/foo.php?item=',
createContainer = function() {
var $i = $(this),
$c = $container.clone().appendTo($i);
$i.data('container', $c);
},
buildUrl = function() {
return urlBase + $currentItem.attr('id');
},
showItem = function() {
var $currentItem = $(this);
getContent(showContent);
},
showItemByIndex = function(idx) {
$.proxy(showItem, $items.get(idx));
},
getContent = function(callback) {
$currentItem.data('container').load(buildUrl(), callback);
},
showContent = function() {
$currentItem.data('container').show();
hideContent();
},
hideContent = function() {
$currentItem.siblings()
.each(function() {
$(this).data('container').hide();
});
};
$items
.each(createContainer)
.click(showItem);
return { showItemByIndex : showItemByIndex };
})();
feature.showItemByIndex(0);
});