Thinking in programming

Things I like to talk about programming

betterSelects – Much better HTML select-elements

with 4 comments

Introduction: betterSelects is intended to “transform” the old-fashioned HTML select-elements in much more sophisticated and flexible controls (see a demo here and screen-shots below). The plugin is released under the Apache License version 2.

For me, there are some essential premises at the moment of writing an UI-Control:

  • Must offers important advantages over already existing related controls
  • Usability
  • Flexibility to customize
  • Performance
  • And for JavaScript controls, them must degrades successfully

Background: a normal HTML select element is too much rigid regarding the customization of its look and feel, as it does not support the coolest CSS effects (aka rounded corners, transparency, shadows, columns, animations, etc.). Thus, this plug-in was created to provide such features.

Currently supported features:

  • displaying items in columns
  • transparency
  • rounded corners
  • animations
  • shadows
  • customization of the expansion button
  • degrades successfully when JavaScript is not enabled
  • and in general, a full customization through CSS

Planned features for future versions:

  • navigation by using arrows keys
  • support for select-multiple
  • auto completion (useful?)
  • among other minor improvements

The usage is very straightforward; just include the provided CSS file and the JavaScript file, then:

$('select').betterSelects({
  zIndexMax: 99, // The first betterSelect control will use this value, it will be smaller for following controls
  expandTime: 100, // The time it takes to expand the options
  marginTop: 1, // Margin between the preview and the expanded options list
  itemsPerCol: 6 // Number of items per column. Use 0 for a single column
});

See the plugin in action here: http://betterselects.appspot.com/

The source is located on Google Code: http://code.google.com/p/betterselects/

A read-only copy of the code can be obtained from http://betterselects.googlecode.com/svn/trunk/

And at last but not at least, it would be great if you wish to do any kind of collaboration with this project, suggestions, corrections, comments, are very welcome. You can ask for commit access if you wish, of course!.

Screen-shots v0.5:

This slideshow requires JavaScript.

JavaScript sourcode v0.5:

/**
 * jQuery.betterSelects plugin v0.5
 * Date: 2010.10.09
 * Web: https://rogerpadilla.wordpress.com/betterSelects
 * Copyright 2010, Roger Padilla Camacho - rogerjose81@gmail.com
 * Licensed under the Apache License version 2
 */

/**
 * This plug-in is intended to "transform" the old-fashioned HTML select-elements in much more sophisticated
 * and flexible controls.
 * 
 * Background: a normal HTML select element is too much rigid regarding the customization of its look and feel,
 * as it does not support the coolest CSS effects (aka rounded corners, transparency, shadows, columns,
 * animations, etc.). Thus, to count with such features in that control is the purpose of this plug-in.
 * 
 * Currently supported features: displaying items in columns, transparency, rounded corners, animations,
 * shadows, customization of the expansion button, and in general, a full customization through CSS.
 * The plug-in degrades successfully when JavaScript is not enabled.
 
 * Planned features for future versions: navigation by using arrows keys, support for select-multiple,
 * auto completion (useful?), among other minor improvements.
 * 
 * The usage is very straightforward; just include the provided CSS file and the JavaScript file, then:
 * <code>$('select').betterSelects();</code>
 */
(function($) {

	/**
	 * Main function; used to initialize the plugin
	 */
	$.fn.betterSelects = function() {

		if(this.length >= 0) {
			// keep a reference of the jQuery object to access it on other functions
			$.fn.betterSelects.self = this;
			// Creates a 'betterSelect' control for each 'select' element in the matched set using the given configuration
			$.fn.betterSelects.opts = $.extend({}, $.fn.betterSelects.defaults, arguments[0]);
			$.fn.betterSelects.createBetterSelects();
		}

		return this;
	};
	
	
	/**
	 * Default configuration
	 */
	$.fn.betterSelects.defaults = {
		zIndexMax: 99, // The first betterSelect control will use this value, it will be smaller for following controls
		expandTime: 100, // The time it takes to expand the options
		marginTop: 1, // Margin between the preview and the expanded options list
		itemsPerCol: 6 // Number of items per column. Use 0 for a single column
	};


	/**
	 * Creates a 'betterSelect' control for each HTML select-element in the matched set using the given configuration
	 */
	$.fn.betterSelects.createBetterSelects = function(){

		var selectsData = [];
		var bsHTML, bs, bsExpandedHeight, pos, ul, itMinHeight, ele;

		/** Loop through the matched set of HTML select-elements to build a 'betterSelect' control emulating each 'select' in the set **/
		$.fn.betterSelects.self.each(function(){
			
			// TODO remove this when support for select-multiple get added
			if(!this.multiple){

				ele = $(this);
	
				// Hide the original HTML select-element
				this.className += 'accesible-hidden-element';
	
				// the select's index being processed
				pos = selectsData.length;
	
				// Build the HTML for the 'betterSelect' control'
				bsHTML = createBetterSelectHTML(this, pos);
	
				// Inserts the 'betterSelect' control in the DOM
				bs = this.parentNode.insertBefore(bsHTML, this.nextSibling);
				bs = $(bs);
	
				bs.children('.betterSelect-preview').css({minWidth: ele.width()});
				
				// Obtains the height of the betterSelect's options on expanded state
				ul = bs.children('.betterSelect');
				bsExpandedHeight = ul.height();
				// Collapse the betterSelect by default
				ul[0].style.height = 0;
				ul[0].style.zIndex = $.fn.betterSelects.opts.zIndexMax - pos;
				itMinHeight = bs.height();
				// Store data related to the original 'select' DOM-element to reference it later
				selectsData.push({height: bsExpandedHeight, itMinHeight: itMinHeight});
				ul.addClass('betterSelect-processed');
			}
		});


		/**
		 * Obtains the data source from the given HTML select-element
		 * @param srcElem Source select-element
		 * @return Extracted select's data
		 */
		function getSelectData(srcElem){

			var bsData = [];
			var selectedIndex = 0;

			for(var i=0, l=srcElem.options.length; i<l; i++) {
				if(srcElem.options[i].selected) {
					selectedIndex = i;
				}
				bsData.push(srcElem.options[i].innerHTML);
			}

			return {options: bsData, selectedIndex: selectedIndex};
		}


		/**
		 * Build the HTML displaying the options
		 * @param src Data source
		 * @param pos The index of the data source being processed
		 * @return Options's data
		 */
		function createBetterSelectOptionsHTML(src, pos) {

			var optionsHTML = '<div class="betterSelect-nc">';
			var classes = '';

			// if the data source is a HTML select-element
			if(src.nodeType) {
				src = getSelectData(src);
			}

			for(var i=0, l=src.options.length; i<l; i++){
				classes = 'betterSelect-it betterSelect-it-i-' + i;
				// marks the selected option
				if(i == src.selectedIndex) {
					classes += ' betterSelect-it-state-selected';
				}
				// close the previous colum and open a new one
				if(i > 0 && (i % $.fn.betterSelects.opts.itemsPerCol) === 0){
					optionsHTML += '</div>\
									<div class="betterSelect-nc">';
				}
				// adds an option to the list of options
				optionsHTML +=
					'<li id="betterSelect_it:' + pos + '.' + i + '" class="' + classes + '">\
						<span class="betterSelect-it-val"></span><span class="betterSelect-it-label">' + src.options[i] + '</span>\
					</li>';
			}

			optionsHTML += '</div>';

			return {options: optionsHTML, selectedIndex: src.selectedIndex, selected: src.options[src.selectedIndex]};
		}


		/**
		 * Build the betterSelect's HTML using the given datasource
		 * @param src Datasource
		 * @param pos The index of the datasource being processed
		 * @return betterSelect's HTML
		 */
		function createBetterSelectHTML(src, pos){

			var bsData = createBetterSelectOptionsHTML(src, pos);

			bsHTML = document.createElement('div');
			bsHTML.id = 'betterSelect_box:' + pos;
			bsHTML.className = 'betterSelect-box';
			bsHTML.innerHTML = '\
				<span id="betterSelect_preview:' + pos + '" class="betterSelect-preview">' + bsData.selected + '</span>\
				<span id="betterSelect_maximize:' + pos +'" class="betterSelect-maximize"></span>\
				<ul id="betterSelect:' + pos +'" class="betterSelect">' + bsData.options + '</ul>\
			';

			return bsHTML;
		}


		/**
		 * Expands/collapses the 'betterSelect' control in the given index
		 * @param pos Index of the 'betterSelect' control
		 */
		function changeSelectMaximizeState(pos){
			var ul = $('#betterSelect\\:' + pos);
			ul.stop();
			if(ul.is('.betterSelect-state-expanded')){
				minimizeSelect(pos, ul);
			} else {
				maximizeSelect(pos, ul);
			}
		}


		/**
		 * Expands the 'betterSelect' control specified by the given index
		 * @param pos Index of the 'betterSelect' control
		 * @param ul Options of the 'betterSelect' control
		 */
		function maximizeSelect(pos, ul){
			$('#betterSelect_maximize\\:' + pos).addClass('betterSelect-maximize-state-expanded');
			var bs = $(ul[0].parentNode);
			var bsOffset = bs.offset();
			bsOffset.top += selectsData[pos].itMinHeight + $.fn.betterSelects.opts.marginTop;
			ul.css({top: bsOffset.top, left: bsOffset.left});
			ul.addClass('betterSelect-state-expanded');				
			ul.animate({height: selectsData[pos].height}, $.fn.betterSelects.opts.expandTime, function(){
				// TODO move the following line to single-selects handlers
				ul.find('.betterSelect-it-state-selected').addClass('betterSelect-it-hover');
				ul.focus();
			});
		}


		/**
		 * Collapses the 'betterSelect' control specified by the given index
		 * @param pos Index of the 'betterSelect' control
		 * @param ul Options container of the 'betterSelect' control
		 */
		function minimizeSelect(pos, ul){
			ul[0].style.height = 0;
			$('#betterSelect_maximize\\:' + pos).removeClass('betterSelect-maximize-state-expanded');
			ul.removeClass('betterSelect-state-expanded');
			ul.find('.betterSelect-it').removeClass('betterSelect-it-hover');
		}


		/**
		 * Processes a click on an item of a 'betteSelect' control
		 * @param elem The clicked item
		 */
		function changeSelectSingle(elem) {
			var ul = $(elem[0].parentNode.parentNode);
			var id = elem[0].id;
			pos = id.substr(id.indexOf(':') + 1).split('.');
			var it = $.fn.betterSelects.self[pos[0]].options[pos[1]];
			// updates the preview with the content of label of the selected item
			ul.siblings('.betterSelect-preview')[0].innerHTML = it.innerHTML;
			ul.find('.betterSelect-it').removeClass('betterSelect-it-state-selected');
			elem.addClass('betterSelect-it-state-selected');
			// updates the original HTML select-element with the current selection
			it.selected = 'selected';
		}


		/**
		 * Adds handlers for common behaviors for both select types: single and multiple
		 */
		function initCommon(selectsBoxes, items){

			// Handler to expand/collapse the 'betterSelect' controls
			selectsBoxes.click(function(evt){
				pos = this.id.substr(this.id.indexOf(':') + 1);
				changeSelectMaximizeState(pos);
			});

			items.filter(':last-child').addClass('betterSelect-it-last');
		}

		
		/**
		 * Adds handlers for single-selects' peculiar behaviors
		 */
		function initSingles(selectBoxes, items){
			
			items.click(function(evt){
				changeSelectSingle($(this));
			});
			
			items.hover(function(){
				$(this.parentNode.parentNode).find('.betterSelect-it').removeClass('betterSelect-it-hover');
				$(this).addClass('betterSelect-it-hover');
			}, function(){
				$(this.parentNode.parentNode).find('.betterSelect-it').removeClass('betterSelect-it-hover');
			});
		}
		

		// Read all the created 'betterSelect' controls which have not been yet processed
		selectsBoxes = $('.betterSelect-box:not(.betterSelect-box-processed)');
		items = selectsBoxes.find('.betterSelect-it');

		// Adds handlers for common behaviors of both select types: single and multiple
		initCommon(selectsBoxes, items);
		
		// Adds handlers for single-selects' peculiar behaviors
		initSingles(selectsBoxes, items);

		// Marks controls as processed
		selectsBoxes.addClass('betterSelect-box-processed');
	};

})(jQuery);

CSS sourcode v0.5:

.betterSelect-box {
	overflow: auto;	
	margin: 0.5em 0;
	width: auto;
}
.betterSelect-preview {
	cursor: pointer;
	float: left;
	padding: 0.1em 0.3em;
	background: none repeat scroll 0 0 #fff;
	border: 1px solid #BBB;
	margin: 0;
	border-radius: 4px;
	-moz-border-radius: 4px;
	-webkit-border-radius: 4px;
}
.betterSelect-maximize {
	float: left;
	cursor: pointer;
	height: 20px;
	width: 20px;
	background: url("../images/expand.png") no-repeat scroll 0 0 transparent;
}
.betterSelect-maximize-state-expanded {
	background-position: 0 -21px;
}
.betterSelect {
	position: absolute;
	border: 0 none;
	margin: 0;
	padding: 0;
	list-style: none none;
	overflow: auto;
	background: none repeat scroll 0 0 #FEAA25;
	color: #FFF;
	opacity: 0.95;
	filter: alpha(opacity=95);
	-ms-filter: "alpha(opacity=95)";
	border-radius: 2px 2px 6px 6px;
	-moz-border-radius: 2px 2px 6px 6px;
	-webkit-border-radius: 2px 2px 6px 6px;
}
.betterSelect-state-expanded {
	padding: 2px 0 1px 0;
	border: 1px solid #BBB;
	border-top: 1px inset;
}
.betterSelect-nc {
	float: left;
}
.betterSelect-it {
	width: auto;
	margin: 0 4px 4px 4px;
	padding: 4px 2px 6px 0;
	overflow: hidden;
	cursor: pointer;
	border-radius: 4px;
	-moz-border-radius: 4px;
	-webkit-border-radius: 4px;
}
.betterSelect-it-hover {
	background-color: #FF7F2A;
}
.betterSelect-it-last {
	margin: 0 4px;
}
.betterSelect-it-val, .betterSelect-it-label {
	height: 14px;
	display: block;
	float: left;
}
.betterSelect-it-label {
	padding: 0 0 0 5px;
}
.betterSelect-it-val {
	background: url("../images/check.png") no-repeat scroll 0 0 transparent;
	width: 14px;
}
.betterSelect-it-state-selected &gt; .betterSelect-it-val  {
	background-position: 0 -14px;
}
.accesible-hidden-element {
	position: absolute;
	left: -1999px;
}

jquery.synchHeights an introductory plugin to the jQuery-plugins-creation world

leave a comment »

This time I’m going to “move a bit back in the time” in order to show how to create a very basic jQuery plugin, hopefully, it will be useful for those persons looking to get into the jQuery-plugins-creation world.

The plugin is intended to synchronize the heights of the elements matched by the given CSS selector.
The functionality is going to be very simple, it will just assign the tallest element’s height to all the
elements in the set. It should take advantage of all the power of the jQuery selectors and its usage
should be very straightforward, in short, something like that:

$('.element1-class, #element2_id', more_CSS_selectors').synchHeights();

So, lets go to the action:

/**
 * jQuery.synchHeights plugin v1.0
 * Copyright 2010, Roger Padilla - @rogerjose81@gmail.com
 * Licensed under Apache License 2.0
 * This plugin is intended to synchronize the heights of the elements matched
 * by the given CSS selector. The functionality of this plugin is very simple,
 * it just assigns the tallest element's height to all the elements in the set.
 */
$.fn.synchHeights = function() {

	// the 'this' object is a jQuery object which keep reference to DOM-elements in the set
	var elemsSet = this;
	
	// calls the function which obtains the greater hight
	var maxHeight = getMaximumHeight(elemsSet);
	
	// calls the function used to synchronize the heights
	synchHeights(elemsSet, maxHeight);
	

	// loops through the elements set to obtain the greater height
	function getMaximumHeight(elemsSet){
		var maxHeight = 0, item;
		elemsSet.each(function(){
			item = $(this);
			if (item.height() > maxHeight) {
				maxHeight = item.height();
			}
		});
		return maxHeight;
	}

	/*
	 * Synchronizes the heights of the elements in the set by using
	 * the calculated maximun-height
	 */
	function synchHeights(elemsSet, maxHeight) {
		// for ie6, set height since min-height isn't supported
		if ($.browser.msie && $.browser.version == 6.0) {
			elemsSet.css({'height': maxHeight});
		}
		elemsSet.css({'min-height': maxHeight});
	}

	// returns 'this' to be able to use chaining selectors
	return this;
};

Written by roger.padilla

July 20, 2010 at 13:15

jquery.synchHeights un plugin introductorio al mundo de la creación de plugins jQuery

leave a comment »

Esta vez voy a “retroceder un poco en el tiempo” con el fin de mostrar cómo crear un plug-in jQuery muy básico, con suerte, será útil para aquellas personas que buscan entrar en el mundo de la creación de plug-ins jQuery.

synchHeights está diseñado para sincronizar las alturas de los elementos HTML que coincidan con el selector CSS dado.

La funcionalidad va a ser muy básica, se asignará la altura del elemento más alto a todos los elementos del conjunto. Este plugin debe aprovechar el poder de los selectores de jQuery y la forma de usarlo debe ser muy sencilla, en fin, será algo así:

$('.elemento1-class, #elemento2_id, mas_selectores_CSS').synchHeights();

Entonces, comencemos con la acción:

/**
 * jQuery.synchHeights plugin v1.0
 * Copyright 2010, Roger Padilla - @rogerjose81@gmail.com
 * Licenciado bajo Apache Licence v 2.0
 * Este plug-in sincroniza las alturas de los elementos seleccionados por el selector CSS dado
 */
$.fn.synchHeights = function() {

	// el objeto 'this' es un objeto jQuery el cual mantiene referencias a los elementos del DOM en el conjunto
	var elemsSet = this;
	
	
	// llamado a la función que obtiene la mayor altura
	var maxHeight = getMaximumHeight(elemsSet);
	
	// llamado a la función que sincroniza las alturas de los elementos
	synchHeights(elemsSet, maxHeight);
	

	// recorre cada elemento en el conjunto en búsqueda de la mayor altura
	function getMaximumHeight(elemsSet){
		var maxHeight = 0, item;
		elemsSet.each(function(){
			item = $(this);
			if (item.height() > maxHeight) {
				maxHeight = item.height();
			}
		});
		return maxHeight;
	}

	/*
	 * Sincroniza las altura de los elementos del conjunto con la mayor altura calculada previamente
	 */
	function synchHeights(elemsSet, maxHeight) {
		// para IE6, usamos la propiedad 'height' ya que ese navegador no soporta 'min-height'
		if ($.browser.msie && $.browser.version == 6.0) {
			elemsSet.css({'height': maxHeight});
		}
		elemsSet.css({'min-height': maxHeight});
	}

	// retornamos el objeto 'this' para poner usar selectores concatenados
	return this;
};

Deepening at CSS selectors

leave a comment »

It is always good to ask ourselves if the code we write can be improved in some way – is it possible to improve the quality and/or its performance?. In this case, I’m going deepen a bit in the way the browsers read the CSS “code”.

Web browsers use a engine in charge of the process of rendering the DOM elements. For each element, the engine looks at which CSS styles should be applied to it (according to the CSS rules matching it.) Each rule is evaluated by parts, from right to left (yes, in reverse). Starting from the one located on the far right (called the “key”, because it indicates which elements should be chosen) and sequentially moves through each part until it reach the one located at the far left, or alternatively, until the rule is discarded for that item (if it is concluded that there is no match).

Under the previous system, the fewer rules have to evaluate the engine and the more specific are these rules, it will be better (in terms of performance).

CSS Selectors are classified accordingly to their efficiency in the following groups (in order from most to least efficient):

  1. #element_id (selector by ID) The ID are uniques by definition, so, this is the fastest rule.
  2. .main_menu (selector by class)
  3. span (selector by tag)
  4. Universal Rules – All the others rules, goes inside this category

For example:

<ul id="”ul_id”" class="”ul-class”">
  <li id="”li_id”" class="”li-class”">
    <span id="”span_id”" class="”span-class”"></span>
  </li>
  <li>
    <p></p>
  </li>
</ul>

Supposing it is desired to put in red-color the text inside the span tag, then some possible selectors are the following (from the most to the least efficient):

/* Usin this selector, the browser will look element by element until it finds one with this ID */
#span_id {
  color: #ff0033;
}

/* In this case, it will look in all elements and it will select the ones having this class */
.span-class {
  color: #ff0033;
}

/* Here, it will look in all elements and it will select the span tags */
span  {
  color: #ff0033;
}

/* This time it will look in all the elements and it will pre-select the ones having this class, following that, it will inspect the parent of each pre-selected element and then filtering the elements being childrens of an element having this ID */
#li_id > .span-class {
  color: #ff0033;
}

/* Using the following selector, it will look in all the elements and it will pre-select the ones having this class, following that, it will inspect the ancestors of each pre-selected element and then it will filter the descendant elements of one element having this ID */
#li_id .span-class {
  color: #ff0033;
}

Conclusion:

  1. #element_id (selector by ID)
    Uses the element’s ID always possible.
  2. ul#main_menu (selector by tag and ID)
    Do NOT add a tag or any other selector as the prefix of a class if it is not necessary, such thing would only adds redundant information which would be also evaludated, slowing down the process.
  3. .main_menu > li (child selector)
    Child selectors are inefficient, because of, by each element matching the criteria indicated by the key, the browser must evaluate the parent of this, which results in a double expensive process. At less specified is the key, greater will be the number of nodes to be evaluated. It should be clarified that, in terms of performance, it is still preferable to use child-selectors instead of descendant-selectors.
  4. .main_menu li (descendant selector)
    Descendant selectors are very inneficients because of, by each element matching the criteria indicated by the key, evaluating each one of its ancestors through the DOM tree until it found a matching or until it reaches the root element. Again, at least specified is the key, the number of nodes to be evaluated will be greater.
  5. At last but not at least, it is necessary to keep in mind that there should be a balance between readibility and performance. In short, it is generally preferable to sacrifice a bit of speed in favor of readibility and manteanibility of the code. At the same way, it is good to keep present that a mobil phone will not interpret a web page with the same speed than a desktop computer

You can find the Spanish version of this article here.

References:
Mozilla – Writing efficient CSS
Google – Optimize browser rendering

Written by roger.padilla

June 2, 2010 at 22:27

Profundizando en los Selectores CSS

with one comment

Siempre es bueno preguntarse si el código que se escribe puede ser mejorado de alguna forma – Es posible improvisar su calidad y/o rapidez de ejecución?. En este caso voy a profundizar un poco en la forma en la que los navegadores leen el “código” CSS.

Los navegadores web utilizan un sistema encargado del proceso de pintado de los elementos del DOM. Para cada elemento, el sistema examina cuales estilos CSS deben ser aplicados al mismo (según las reglas CSS que coincidan con él). Cada regla es evaluada por partes, de derecha a izquierda (así es, en reversa). Se comienza desde la parte ubicada en el extremo derecho (llamada “llave” porque es la que señala que elementos se deben escoger) y consecutivamente se va moviendo por cada parte hasta llegar a la ubicada en el extremo izquierdo, o en su defecto, hasta que la regla sea descartada para ese elemento (si se concluye que no hay coincidencia).

De acuerdo al anterior sistema, entre menos reglas tenga que evaluar el motor y entre más especificas sean dichas reglas, será mejor (en términos de velocidad).

Los Selectores CSS, se clasifican según su eficiencia, en los siguientes grupos (ordenados del más al menos eficiente):

  1. #el_id_del_elemento (selector por ID) Los ID son únicos por definición, por consiguiente, esta es la regla más rápida.
  2. .menu-principal (selector por clase)
  3. span (selector por etiqueta)
  4. Reglas Universales – Todas las demás reglas clasifican en esta categoría

Para el ejemplo:

<ul id="”id_del_ul”" class="”clase-del-ul”">
  <li id="”id_del_li”" class="”clase-del-li”">
    <span id="”id_del_span”" class="”clase-del-span”"></span>
  </li>
  <li>
    <p></p>
  </li>
</ul>

Suponiendo que se desea poner en color rojo el texto dentro de la etiqueta span, algunos potenciales selectores serian los siguientes (del más veloz al más lento):

/* Usando esta regla, el navegador buscará elemento por elemento hasta que encuentre uno con este ID */
#id_del_span {
  color: #ff0033;
}

/* En este caso, buscará entre todos los elementos y aplicará los estilos a los que posean esta clase */
.clase-del-span  {
  color: #ff0033;
}

/* Aqui, buscará entre todos los elementos y aplicará los estilos a los de tipo span */
span  {
  color: #ff0033;
}

/* En este caso, buscará entre todos los elementos, pre-seleccionará los que tengan esta clase, seguidamente examinará el padre de cada elemento pre-seleccionado y al final filtrará  los que sean hijos de un elemento cuyo ID sea el especificado */
#id_del_li > .clase-del-span  {
  color: #ff0033;
}

/* Usando el siguiente selector, buscará entre todos los elementos, pre-seleccionará los que tengan esta clase, seguidamente examinará los ancestros de cada elemento pre-seleccionado y al final filtrará los que sean descendientes de un elemento cuyo ID sea el ID especificado */
#id_del_li .clase-del-span  {
  color: #ff0033;
}

Conclusión:

  1. #el_id_de_un_elemento (selector por ID)
    Utilizar el ID del elemento siempre que sea posible.
  2. ul#menu_principal (selector por etiqueta e ID)
    No anteponer una etiqueta o una clase como clasificadores a un ID. Esto solo agrega información redundante que necesita ser evaluada innecesariamente, por lo tanto, ralentiza el proceso.
  3. .menu-principal > li (selector hijo)
    Los selectores hijos son ineficientes porque, por cada elemento que coincida con el criterio indicado en la llave, el navegador debe evaluar el padre de este. Lo cual se convierte en un proceso doblemente costoso por cada selector hijo en la regla. Entre menos especifica sea la llave, mayor será el número de nodos a evaluar. Cabe aclarar que, en términos de velocidad, es preferible utilizar selectores hijos en vez de selectores descendientes.
  4. .main_menu li (selector descendiente)
    Los selectores descendientes son generalmente, muy ineficientes porque, por cada elemento que coincida con el criterio indicado en la llave, el navegador debe recorrer el árbol DOM, evaluando cada elemento ancestro hasta que se encuentre una coincidencia o hasta llegar al elemento raíz. Nuevamente, entre menos especifica sea la llave, mayor será el número de nodos a evaluar.
  5. Por último, pero no menos importante, hay que tener presente que debe haber un equilibrio entre legibilidad del código y la optimización del mismo. En general, es preferible sacrificar un poco de velocidad en pro de legibilidad y mantenibilidad del código. Así mismo, se debe tener claro que un teléfono móvil no interpretará una pagina web con la misma rapidez que un computador de escritorio.

La versión en Ingles de este articulo se puede encontrar aqui

Referencias:
Mozilla – Writing efficient CSS
Google – Optimize browser rendering

Written by roger.padilla

June 2, 2010 at 22:27

“busyBox”, a simpler and cooler AJAX-Loader jQuery plugin

with 8 comments

This time I’m going to show how to create a jQuery plugin. For such purpose, I’ve thought in something simple, nice and overall, useful. With that premise in mind, lets create a plugin to make appears some HTML boxes as “busy” (useful for example, while performing an AJAX request to update some section or block of a page), thus lets name this plugin as busyBox (v1.1 released at 2010-10-12).

Yes, out there exists several plugins intended to achieved something like that. The problem I see about these plugins is they puts a transparent layer over the entire page; but which I’m wanting for, is just to put an individual transparent layer over the involved boxes in the background process. The main advantages of this technique are: the user will be able to perform other actions while waiting for others being executed, further, the user will specifically know which sections of the page are being updated without breaks his navigation workflow, and at last but not at least, the user will see a cooler effect 😉

Which I’m looking for, is to be able to use the plugin in the following way:


var box= $('my-selector');

// Show the 'busy boxes' passing some configuration
box.busyBox(
  spinner: '<img src="path-to-the-image.gif" />'
);

$.ajax(
  url: 'my-url',
  success: function(response){
    box.html(response);
  },
  complete: function(){
    // Closes the 'busy boxes'
    box.busyBox('close');
  }
);

Lets know the basic structure of a jQuery plugin:

(function($) {

  // Using this, you'll be able to use your plugin using all the jQuery's power
  $.fn.myPluginName = function() {

    // Returning "this" is needed to be able to use chaining
    return this;
  };

})(jQuery);

Now that we know the basic structure of a plugin, lets go to the action creating our own.

As first step, lets create the basic structure of our plugin:

(function($) {

 /**
  * Main function; used to initialize the plugin or for calling the available functions provides by the plugin (such as 'open' or 'close').
  * We are going to use the 'arguments' array to obtain the passed parameters
  */
  $.fn.busyBox = function() {

    if(arguments[0] == 'open') {
      // some code to put the "busy boxes" over the wanted sections of the page
    } else if(arguments[0] == 'close') {
      // some code to close the previously-opened "busy boxes"
    } else {
      // some code to initialize the plugin
    }

    // Returning "this" is needed to be able to use chaining
    return this;
  };

})(jQuery);

As second step, wold be useful to make our plugin configurable, thus, lets create some default configuration:

(function($) {

 /**
  * Main function; used to initialize the plugin or for calling the available functions provides by the plugin (such as 'open' or 'close').
  * We are going to use the 'arguments' array to obtain the passed parameters
  */
  $.fn.busyBox = function() {

    if(arguments[0] == 'open') {
      // some code to put the "busy boxes" over the wanted sections of the page
    } else if(arguments[0] == 'close') {
      // some code to close the previously-opened "busy boxes"
    } else {
      // some code to initialize the plugin
    }

    // Returning "this" is needed to be able to use chaining
    return this;
  };

 /**
  * Default configuration
  */
  $.fn.busyBox.defaults = {
    autoOpen: true,
    spinner: '<em>Loading...</em>',
    classes: 'busybox',
    top: 'auto',
    left: 'auto'
  };

})(jQuery);

As third step, lets create the basic structure of all our functions:

(function($) {
 /**
  * Main function; used to initialize the plugin or for calling the available functions provides by the plugin (such as 'open' or 'close').
  * We are going to use the 'arguments' array to obtain the passed parameters
  */
  $.fn.busyBox = function() {

    $.fn.busyBox.self = this;

    if(arguments[0] == 'open') {
      $.fn.busyBox.open();
    } else if(arguments[0] == 'close') {
      $.fn.busyBox.close();
    } else {
      $.fn.busyBox.init(arguments[0]);
    }

    return this;
  };

 /**
  * Initialize the plugin using the passed options
  */
  $.fn.busyBox.init = function(options) {

    $.fn.busyBox.opts = $.extend({}, $.fn.busyBox.defaults, options);

    // Adds the default classes if they are not present in the passed classes
    if($.fn.busyBox.opts.classes.indexOf($.fn.busyBox.defaults.classes) === -1){
      $.fn.busyBox.opts.classes += ' ' + $.fn.busyBox.defaults.classes;
    }

    $.fn.busyBox.container = $(document.body);

    if($.fn.busyBox.opts.autoOpen){
      $.fn.busyBox.open();
    }
  };

 /**
  * Display all the 'busyBoxes' over the matched boxes
  */
  $.fn.busyBox.open = function(){
    // some code to display the "busy boxes" effect
  };

 /**
  * Closes all the 'busyBoxes' being showed
  */
  $.fn.busyBox.close = function(){
    // some code to quit the "busy boxes" effect
  };

 /**
  * Default configuration
  */
  $.fn.busyBox.defaults = {
    autoOpen: true,
    spinner: '<em>Loading…</em>',
    classes: 'busybox',
    top: 'auto',
    left: 'auto'
  };

})(jQuery);

Now, putting together all the JavaScript code:

/**
 * 'busyBox' v1.1
 * @author Roger Padilla C. - rogerjose81@gmail.com
 * Web: https://rogerpadilla.wordpress.com/2010/05/24/jquery-busybox/
 * Licensed under Apached License v2.0
 */
(function($) {

	/**
	 * Main function; used to initialize the plugin or for calling the available functionalities of the plugin (such as 'open' or 'close').
	 * The 'arguments' array is used to obtain the received parameters
	 */
	$.fn.busyBox = function() {

		$.fn.busyBox.self = this;

		if(arguments[0] == 'open') {
			$.fn.busyBox.open();
		} else if(arguments[0] == 'close') {
			$.fn.busyBox.close();
		} else {
			$.fn.busyBox.init(arguments[0]);
		}

		return this;
	};

	/**
	 * Initialize the plugin using the passed options
	 */
	$.fn.busyBox.init = function(options) {

		$.fn.busyBox.opts = $.extend({}, $.fn.busyBox.defaults, options);

		// Adds the default classes if they are not present in the passed classes
		if($.fn.busyBox.opts.classes.indexOf($.fn.busyBox.defaults.classes) === -1){
			$.fn.busyBox.opts.classes += ' ' + $.fn.busyBox.defaults.classes;
		}

		$.fn.busyBox.container = $(document.body);

		if($.fn.busyBox.opts.autoOpen){
			$.fn.busyBox.open();
		}
	};

	/**
	 * Display all the 'busyBoxes' over the matched boxes
	 */
	$.fn.busyBox.open = function(){

		var box, inner, e, bOffset, bWidth, bHeight, iTop, iLeft;

		$.fn.busyBox.self.each(function(index) {

			e = $(this);
			bWidth = e.outerWidth();
			bHeight = e.outerHeight();
			bOffset = e.offset();

			box = $('<div />');
			box.attr('id', 'busybox_' + index);
			box.addClass($.fn.busyBox.opts.classes);

			box.css({
				width: bWidth,
				height: bHeight,
				top: bOffset.top,
				left: -9999 // Used to not display the box yet without hidden it (needed to be able to calculate its dimensions)
			});

			inner = $($.fn.busyBox.opts.spinner);
			inner.attr('id', 'busybox_spinner_' + index);
			inner.addClass('busybox-spinner');

			box.append(inner);

			$.fn.busyBox.container.append(box);

			// Set the position of the inner message inside its parent. Calculates the 'top' and/or 'left' coordinates of the inner-message (inside its parent) if those properties were configured as 'auto'
			iTop = ($.fn.busyBox.opts.top == 'auto' ? ((bHeight / 2) - (inner.outerHeight() / 2)) + 'px' : $.fn.busyBox.opts.top);
			iLeft = ($.fn.busyBox.opts.left == 'auto' ? ((bWidth / 2) - (inner.outerWidth() / 2)) + 'px' : $.fn.busyBox.opts.left);

			inner.css({
				position: 'absolute',
				top: iTop,
				left: iLeft,
				opacity: 1.0
			});

			// Hidde and relocate the 'busyBox' (previously displayed using a negative left-coord to be able to calculate its dimensions)
			box.css({display: 'none', left: bOffset.left});
		});

		// Display all the 'busyBoxes'
		$.fn.busyBox.container.find('.' + $.fn.busyBox.defaults.classes).fadeIn('fast', $.fn.busyBox.opts.displayed.call(this));
	};

	/**
	 * Closes all the 'busyBoxes' being showed
	 */
	$.fn.busyBox.close = function(){
		if($.fn.busyBox.container) {
			$.fn.busyBox.container.find('.' + $.fn.busyBox.defaults.classes).fadeOut('fast', function(){
				$(this).remove();
			});
		}
	};

	/**
	 * Default configuration
	 */
	$.fn.busyBox.defaults = {
		autoOpen: true,
		spinner: '<em>Cargando…</em>',
		classes: 'busybox',
		top: 'auto',
		left: 'auto',
		displayed: function(){}
	};

})(jQuery);

a bit of CSS and we are done!

.busybox {
	position: absolute;
	z-index: 999;
	color: #fff;
	background-color: #666;
	margin: 0;
	padding: 0;
	opacity: 0.5;
	filter: alpha(opacity=95);
	-ms-filter: "alpha(opacity=95)";
}

Screenshot:

Written by roger.padilla

May 24, 2010 at 10:49

Remove empty items from a multidimensional array in PHP

with 7 comments

Removing empty items from a single-level array is something trivial in PHP; but it is not so if our array has 2 levels or more. The following function removes empty items from an (multidimensional) array, and returns the resulting array. Please notice that the use of recursion is required because of the array can be of an unknown number of levels, that is, arrays inside the cells of the array, an so on.

This function is useful when by instance, we have a search-form and the user can enter some optional criteria.

  function array_non_empty_items($input) {
    // If it is an element, then just return it
    if (!is_array($input)) {
      return $input;
    }

    $non_empty_items = array();

    foreach ($input as $key => $value) {
      // Ignore empty cells
      if($value) {
        // Use recursion to evaluate cells
        $non_empty_items[$key] = array_non_empty_items($value);
      }
    }

    // Finally return the array without empty items
    return $non_empty_items;
  }

By example, if we pass the following array to the function:

    $anArray = array(
      ‘country’ => ‘CO’,
      ‘region’ => ,
      ‘city’ => ,
      ‘features’ => array(
          ‘home’ => array(‘garden’, ‘bedrooms’=>3),
          ‘beach’ => array(‘no_more_than’=>30, ‘yachts_rental’),
          ‘supermarkets’ => ,
          ‘discotheque’ =>
       )
    );

Then we will obtain an array without empty items, like this:

   $anArray = array(
      ‘country’ => ‘CO’,
      ‘features’ => array(
          ‘home’ => array(‘garden’, ‘bedrooms’=>3),
          ‘beach’ => array(‘no_more_than’=>30, ‘yachts_rental’)
       )
    );

Written by roger.padilla

August 21, 2009 at 18:43

Posted in PHP

Tagged with , ,

Flex Coding Conventions

with 3 comments

This document is intended to set the coding
conventions for the Flex side.

General Requirements

Only the most recent release of Adobe Flex
should be used. Currently the release is “Flex 3”.

Standard object oriented programming principles
must be used in the Flex code development.

A concept for error handling should be provided
and used by the Flex developers.

A concept for security should be provided and

Necessary Development Concepts

The following is a list of necessary concepts for Flex application development:

  • Error handling and reporting.
  • Application security and authentication.
  • Client side input validation. In certain modules, it may be necessary to create concepts for L10N and I18N.

Packages

All package names should be meaningful and
should be prefixed with com.companyname. For example, org.rogerpadilla

When importing packages into classes, it is
preferred to import concrete classes instead of entire packages.

Files

All files must be encoded in the UTF8
format.

  • MXML file names: UpperCamelCase.mxml
  • ActionScript file names: UpperCamelCase.as for classes and all interfaces should be prefixed with I ( IUpperCamelCase.as).

Classes, functions and code

The following is the set of coding rules
for ActionScript:

  • There should not be magic numbers, strings or values present in the code. Easily accessible constants, property files, xml files should be created where all such values will be kept. For example, a remote service URL should not be hardcoded.
  • Each class, function and class property should be commented. Non-obvious blocks of code should be preceded with a commented explanation of what they do.
  • Multiple variables should not be declared in a single line.
  • Multiple statements should not be placed on a single line.

The following is the set of coding rules
for MXML:

  • Use external style files.
  • There should be only one script block per MXML file, and it should follow the same guidelines as usual ActionScript files, because it should be exportable with asdoc.

Documentation

All MXML files should contain a brief
desctiption if what they do and where they are used.

All ActionScript classes, functions and
class attributes should be commented with meaningful information in English. It
should be easy to create a documentation export using Flex’s asdoc tool,
where all packages, classes and functions can be reviewed.

For functions, there should be at minimum a
@param tag for each parameter, @return tag for returned
values, and a description of what the function does.

Written by roger.padilla

March 31, 2009 at 10:28

Posted in Coding Conventions

Tagged with ,

Te queda una semana de vida. ¿qué vas a hacer con ella?

with 5 comments

Imaginen por un momento que alguien les dice que les queda una semana de vida. ¿Qué harían, ante tal perspectiva? La pregunta es peliaguda desde el momento en que se plantea, y a medida que se reflexiona, se entra más a fondo en una espiral en la que parece no existir la respuesta correcta. ¿Pero realmente, qué es una respuesta correcta? Esta es otra interesante pregunta, a la que quizá dedique unas líneas otro día.

Volvamos al asunto. Solamente hay que ponerse en situación. Joder, es una putada, pero esto no es un juego. O bueno, quizá sí, pero el objetivo es conocernos un poco más a nosotros mismos, con lo cual es un juego muy serio, digamos que instructivo. Como el Mecano, vamos. Bien, mucha gente diría que visitaría a sus seres más queridos, que pasaría todo el tiempo con ellos. Otros dirían que eso no le aporta nada, porque cuando desaparezcan nada quedará de ellos y que para cuando desaparezcas ya todo dará igual, se deprimirán y quizás se suiciden, como venganza ante la muerte, para decidir ellos el momento final. Otros, terceros, se gastarían todos sus ahorros en grandes lujos, en vivir la vida a lo grande, a mandarlo todo a la mierda en un intento de vivir deprisa. Quizá habría un cuarto tipo, los buenos samaritanos, que aprovecharían el resto del tiempo para hacer el bien a los demás, dedicarlo a ayudar todo lo que puedan a sus congéneres, para sentirse mejor. Cadena de favores, y esas cosas. Y el antagónico de este grupo, psicópata enfermizo quizá se dedicaría a delinquir durante toda esa semana, sin reglas, ni leyes, libertinaje, pues no hay miedo a la prisión ni a la perpetua ante la perspectiva de la inminente visita al barquero Caronte.

Me interesan todos estos tipos, pero todos tienen algo en común que les caracteriza profundamente: todos cambiarían de forma más o menos radical sus vidas. El egocéntrico solitario pasaría los últimos momentos con su familia, los fuertes moralistas con grandes ideas para el futuro se desmoronarían como castillos de naipes, sumergidos en los mares de la depresión y, quizá, el suicidio; para los terceros, austeros usureros ahorradores para las generaciones futuras, el shock les produciría la fiebre de vivir ahora deprisa; los no empáticos, egoístas que viven en su nube de algodón se convertirían en misioneros sin fronteras, y los espíritus tranquilos y extremadamente tímidos desatarían la furia sociópata que siempre llevaron dentro.

Sé que estoy generalizando, pero es fácil pensar en todos estos arquetipos. No digo que yo no pertenezca a ninguno de ellos, pero pienso que la respuesta correcta sería seguir haciendo lo que he hecho hasta ahora. Imposible, ¿verdad? ¡Qué utopía! ¡Qué imbecilidad! No me digas que tus últimas horas las “gastarías” haciendo lo que has hecho siempre. Bien, ahora yo les diré que si tanto les trastorna esta respuesta es porque quizá no están viviendo como desearían vivir.

Aquí quería llegar, amigos: todos somos, en mayor o menor medida, dueños de nuestro destino y de nuestra forma de vivir. Desafortunadamente hay gente que no puede, pero la mayoría, al menos en los países desarrollados, tenemos esa inmensa suerte. Puede ELEGIR cómo quiere vivir su vida. Si tus últimos momentos no serían igual que el resto es porque quizás no estás viviendo adecuadamente. Es un cambio de filosofía, amigos: “vive cada momento como si fuera el último”, pero no atropelladamente, entiéndanme. La idea es no dejar todo aquello que deseamos para el final, aprovechar que estamos vivos, valorar lo que tenemos y estar agradecidos por ello.

La idea es decir: voy a visitar a mis padres y a mi familia más a menudo, mejor visitarlos ahora que llevarles flores cuando estén criando malvas. La idea es pensar: voy a cogerme unas vacaciones y visitar la India, un país que siempre quise visitar, por ejemplo. Es cavilar en lo banal que es casi todo en esta vida y que al final, lo único que merece la pena realmente son las personas. Es reflexionar: debería apadrinar un niño, donar dinero a cierta organización, pensar en algo para que los demás puedan disfrutar de la vida con plenitud, como lo hago yo. Finalmente, la idea también es: voy a enfrentarme a los demás, no callarme lo que me sienta mal, ni dejarme pisotear; voy a quererme a mí mismo y a intentar que se me respete igual que yo respeto a los demás. Así seré más feliz.

Por ello, a la pregunta: ¿qué harías si te quedara una semana de vida? creo que la respuesta correcta debería ser: lo mismo que he hecho todos los días hasta el día de hoy…

Espero que hayan reflexionado un poco con estas líneas. También me gustaría recibir sus opiniones al respecto.

Original en: http://luixrodriguezneches.wordpress.com/2008/06/27/te-queda-una-semana-de-vida-%C2%BFque-vas-a-hacer-con-ella/

Written by roger.padilla

March 31, 2009 at 09:53

Posted in Interes General

Conventions for Creation of SQL Queries, Indexes Tables and Fields

with 4 comments

  1. Whenever added a new table/field to the database, a comment with his purpose must be included.
  2. Use NOT NULL. Always define columns as NOT NULL unless there is a very good reason not to do so:
    • can save up to a byte per column per row of data
    • nullable columns make indexes, index statistics, and value comparisons more complicated.
  3. Use UNIQUE INDEX. If you have verified that each value for that index will/should not be repeated, then use an unique index (UNIQUE INDEX).
  4. Declaring the data type of a field to the more fixed-value, i. e., if you know the value of an int field is always going to become smaller than 10000, the you should use SMALLINT instead of INT.
  5. Use the smallest possible value for the lengths of the text fields. If you know that a zip code field will always hold not more than 10 characters, then you should use VARCHAR (10) and not VARCHAR (25).
    In cases like encrypted values with the MD5 algorithm, use CHAR (32).
  6. While write reserved keys use always capitalized sustained.

Useful info about the most common MySQL Data Types:

/* Unsigned Numeric Data Types */

TINYINT 0 to 255

SMALLINT 0 to 65535

MEDIUMINT 0 to 16777215

INT 0 to 4294967295

BIGINT 0 to 18446744073709551615

/* Text Data Types */

TEXT 65535 OR 64KB characters

MEDIUMTEXT 16777215 OR 16MB characters

LONGTEXT 4294967295 OR 4GB
characters

Written by roger.padilla

March 31, 2009 at 09:25