Crear un elemento arrastrable (drag and drop) con jQuery, sin jQuery UI

jQuery UI nos permite definir un elemento como «draggable» para que podamos «agarrarlo» con el ratón para arrastrarlo por la pantalla y situarlo donde mejor nos convenga. Hoy en el trabajo me veía con la limitación de no poder recurrir a jQuery UI, sólo podía utilizar jQuery 1.11 y tenía que lograr el mismo efecto.

Mentiría si dijera que lo hice yo, básicamente cogí este código de CSS-Tricks que os dejo a continuación y le hice un par de pequeños cambios para que funcionara de la forma deseada en la aplicación.

(function($) {
    $.fn.drags = function(opt) {

        opt = $.extend({handle:"",cursor:"move"}, opt);

        if(opt.handle === "") {
            var $el = this;
        } else {
            var $el = this.find(opt.handle);
        }

        return $el.css('cursor', opt.cursor).on("mousedown", function(e) {
            if(opt.handle === "") {
                var $drag = $(this).addClass('draggable');
            } else {
                var $drag = $(this).addClass('active-handle').parent().addClass('draggable');
            }
            var z_idx = $drag.css('z-index'),
                drg_h = $drag.outerHeight(),
                drg_w = $drag.outerWidth(),
                pos_y = $drag.offset().top + drg_h - e.pageY,
                pos_x = $drag.offset().left + drg_w - e.pageX;
            $drag.css('z-index', 1000).parents().on("mousemove", function(e) {
                $('.draggable').offset({
                    top:e.pageY + pos_y - drg_h,
                    left:e.pageX + pos_x - drg_w
                }).on("mouseup", function() {
                    $(this).removeClass('draggable').css('z-index', z_idx);
                });
            });
            e.preventDefault(); // disable selection
        }).on("mouseup", function() {
            if(opt.handle === "") {
                $(this).removeClass('draggable');
            } else {
                $(this).removeClass('active-handle').parent().removeClass('draggable');
            }
        });

    }
})(jQuery);

Tras esto me bastó aplicar esa función sobre los elementos deseados, en ese caso por medio de una clase:

$('.ElementoMovil').drags();

En este caso dad gracias a esa excelente web que es CSSTricks cargada de trucos para mejorar el diseño y la experiencia del usuario, ya que el trabajo es suyo. En el artículo original podéis probar el funcionamiento de dicha función. Añado que en versiones muy antiguas de jQuery no funciona, al menos con 1.5.2 que fue con la que lo probé en principio. En cualquier caso, es recomendable tener siempre esta librería actualizada.

Hacer un window.open() en segundo plano

Vamos con un tip rápido de javascript ¿Cómo hago un window.open(), en una ventana nueva, de forma que la ventana nueva se sitúe detrás de la principal y no delante? Hay dos opciones:

Bueno, empezando por el principio, recordad que para que se abra en nueva ventana debéis añadir el parámetro «_blank» tal que así:

window.open('emergente.php', '_blank'); /*y ya luego le metéis las variables extra que os apetezca*/

Entonces ¿cómo lo mandamos a segundo plano?

  • Opción a: Centrando el foco en nuestra ventana principal, añadiendo la línea self.focus(); tras el window.open().
  • Opción b: Sacando el foco de la nueva ventana abierta, añadiendo en la cabecera el siguiente script:

    <script languaje="javascript">
      self.blur();
    </script>
    

Acceder a los elementos de un Iframe con Javascript

Un tip rápido de Javascript: Tienes un iframe en tu página y quieres acceder a los elementos que hay dentro. ¿Es posible hacerlo? Sí pero no. Me explico:

Si el iframe está en el mismo dominio que la página padre sí, si no, por motivos de seguridad, el navegador no te lo permitirá.

En caso de que la respuesta a si está en el mismo dominio sea afirmativa puedes acceder al iframe por su id, ya que habrá un objeto donde estarán guardados todos, y una vez accedido a él puedes llegar a sus elementos a través de su id. Un ejemplo simple:

// cambia miFrame por el id de tu iframe,
// cambia idDelElemento por el id que corresponda,
// puedes trabajar con los elementos del iframe como si fueran parte de la página padre.
window.frames['miFrame'].document.getElementById('idDelElemento')

Añadiendo elementos al DOM con Javascript: appendChild e insertBefore

Una de las funcionalidades potentes de Javascript es poder generar nuevo código dinámicamente y añadirlo a nuestra web, cosa que podemos hacer con las funciones appendChild e insertBefore.

El método appendChild nos permite insertar un elemento dentro de otro, al final del mismo. Imagina que quieres agregar un elemento a una lista que sea «nuevo elemento«, pues javascript permite crear este nuevo elemento, meterle un nodo de texto y agregarlo a la lista. Veamos como:

elemento1 = document.createElement('li');
elemento1.appendChild(document.createTextNode('nuevo elemento'));
elemento2 = document.getElementById('laListaQueYaExiste');
elemento2.appendChild(elemento1);

¿Y si quieres insertar al principio en lugar de al final de la lista? Pues tan simple como usar insertBefore:

elemento1 = document.createElement('li');
elemento1.appendChild(document.createTextNode('nuevo elemento'));
elementoLista = document.getElementById('laListaQueYaExiste');
//hasta aquí todo igual
elemento2 = elementoLista.firstChild();
//en la línea de arriba creamos un elemento nuevo que es el primer elemento de la lista.
elemento3.parentNode.insertBefore(elemento1,elemento3);
//también podríamos haberlo hecho así:
elementoLista.insertBefore(elemento1,elemento3);

Detectar si un usuario usa Adblock Plus

El tema de la publicidad en internet está candente últimamente. Por un lado algunas páginas se pasan de publicidad intrusiva (y no hablo sólo de sitios donde ver retransmisiones deportivas on-line, basta con entrar en la web de cualquier periódico español «grande» para ver anuncios que a veces hasta dificultan la lectura), pero por otra parte también están muchas webs gratuitas cuya única fuente de ingresos es la publicidad, y al bloquearla les dejamos sin un dinero necesario para pagar los gastos ocasionados. Algunas web optan por bloquear a los usuarios que llegan con el adblock puesto, yo no comparto estas medidas draconianas ya que soy el primero que ve eso y lo siguiente que hace es salir de la página y buscar otra que me ofrezca lo mismo sin esta publicidad. Creo que la mejor solución sería el «buenrollismo«, vamos, que si se detecta en AdBlock se le saca al usuario un aviso tipo «tío, que alojamiento y dominio son 50 pavos anuales, que no es mucho pero me los sacaba con estos anuncillos de google, no me jodas y saca el bloqueador, man». La mayoría no te harán ni caso, pero bloqueándoles no te creas que lograrías mejores resultados, al menos así puede que lo compartan en redes sociales y logres ingresos de otros usuarios que lo miren.

Vamos pues con la chicha ¿cómo se detecta AdBlock? Bueno, yo lo he hecho mediante el método del señuelo, que de momento va furrulando. La cuestión es que AdBlock tiene una lista negra de palabras y bloquea según qué javascript si detecta alguna de ellas. El truco es meter un javascript con un nombre que haga saltar Adblock, comprobar que dicho javascript no está furrulando y, en ese caso, lanzar un aviso al usuario.

Empezamos creando un archivo de javascript con el título advertisement.js, un nombre que hará saltar a Adblock para evitar su carga, con un contenido más o menos como el siguiente:

document.write('<div id="le_truquito">Publicidad malvada y terrible!!</div>');

La idea es hacer el document.write de un div con un id concreto, lo del texto ya es chorrada mía. Por si entra un usuario sin Adblock lo mejor es que ocultemos ese div que se creará con CSS, para que no le aparezca por medio.

#le_truquito{
    display:none;
}

Ok, lista la trampa lo siguiente es añadir otro código de javascript que comprobará si se ha bloqueado el primero (cerciorándose de si existe el div que debería crear) y, en caso de que no lo haya hecho sacará un mensaje en pantalla.

<script type="text/javascript">
if (document.getElementById("le_truquito") == undefined)
{
    window.alert("tío, que alojamiento y dominio son 50 pavos anuales, que no es mucho pero me los sacaba con estos anuncillos de google, no me jodas y saca el bloqueador, man");
}
</script>

En el ejemplo puse un window.alert, pero también podéis crear un div con un texto en la página… en fin, lo que veáis más práctico.

De momento el sistema funciona, y tiene pinta de que al menos durante una temporada lo hará. En todo caso podéis buscar el nombre de un archivo de javascript de publicidad de Google, o de otra compañía, que sepáis positivamente que se está bloqueando por AdBlock y llamarle así en lugar de advertisement.js (aunque de momento con ese nombre furrula).

Redireccionar y recargar web con javascript

Todavía está virgen 2014 de alguna entrada sobre informática, al empezar el año de vacaciones me dediqué a temas más ociosos. Vamos pues con una minientrada sobre javascript ¿cómo redireccionar una web usando este lenguaje?

Empecemos por un punto, existen muchas formas de hacer redirecciones, tanto desde el servidor como desde el cliente. Hasta con una mera etiqueta en HTML podemos hacerlo. Vamos a ver cómo se puede hacer desde javascript dado que tiene mucha chicha. Para ello nos serviremos del objeto Location.

Para ir a una URL determinada lo más sencillo es usar la propiedad href de location. Con href podremos tanto recuperar la url en la que navegamos como cambiar el valor de la misma. Podemos usar una url absoluta para ir a otra web externa a la nuestra o una relativa para navegar dentro de nuestra web:

/*Ruta absoluta, nos llevaría a una web externa*/ 
location.href = "http://www.webquequeremosver.com"; 
/*En cambio esta nos llevaría a una dentro de nuestro dominio*/ 
location.href="admin.php";

Otra opción son los métodos replace y assign. Ambos hacen que location cargue un nuevo documento, pero con una diferencia: replace quita el sitio actual de la historia de navegación del documento, de esa forma el botón «atrás» del navegador no puede utilizarse para volver a la página anterior. En cambio assign simplemente carga el nuevo documento, por lo que sí tendrás esa función de navegar hacia atrás activa.

/*Con replace nos vamos a un nuevo document y bloqueamos el botón de atrás*/
location.replace("http://google.es"); 
/*Con assign en cambio conservamos esa funcionalidad*/ 
location.assign("http://google.es");

¿Y para recargar la página? Pues está el método reload, el cual por defecto recargará desde la caché a no ser que le pasemos el parámetro true para forzar la recarga desde el servidor. Es decir, el método por defecto funcionaría como la tecla F5 en vuestro navegador, y con el true en cambio lo haría como ctrl+F5

/*Recargamos desde caché*/ 
location.reload(); 
/*Forzamos la recarga*/ 
location.reload(true);

Listas con imágenes en jQueryMobile

Vamos con una entrada sobre jQueryMobile, que hace que no le damos a la Mobile Web (sigo a la espera de que empiece el curso de Audacity, al que me apunté ya en agosto…). Ya hablamos de cómo hacer listas, desde luego, pero ¿cómo hacer listas con un pequeño thumbnail a la izquierda? Es muy simple, de hecho.

Como siempre empiezas descargando jQuery y jQueryMobile y añadiéndolos a tu código. Luego en el código metes una lista aplicándole un par de clases… pero eso mejor lo vemos con ejemplos de código. En este primer ejemplo veremos una lista donde cada elemento es un enlace, con una foto en la izquierda y un texto grande:

<ul data-role="listview">/*A la lista le aplicamos el data-role listview*/
  <li><a href="#1"><img src="img/foto1.png" width="100" height="100"/><h3>Elemento1</h3></a></li> /*En cada elemento metemos un enlace, una imagen (tamaño 100+100) y un título h3*/
  <li><a href="#2"><img src="img/foto2.png" width="100" height="100"/><h3>Elemento2</h3></a></li>
  <li><a href="#3"><img src="img/foto3.png" width="100" height="100"/><h3>Elemento3</h3></a></li>
</ul>

Esto es muy básico, vamos con algo más chulo: el mismo ejemplo, pero con un texto más pequeño debajo del título y con divisores:

<ul data-role="listview" data-inset="true" data-autodividers="true" data-filter="true">/*A la lista le aplicamos el data-role listview y el resto de configuración*/
  <li><a href="#1"><img src="img/foto1.png" width="100" height="100"/><h3>Elemento1</h3>
<p>Este es el primer elemento</p></a></li> /*En cada elemento metemos un enlace, una imagen (tamaño 100+100) y un título h3, y además una pequeña descripción*/
  <li><a href="#2"><img src="img/foto2.png" width="100" height="100"/><h3>Elemento2</h3>
<p>Este es el segundo elemento</p></a></li>
  <li><a href="#3"><img src="img/foto3.png" width="100" height="100"/><h3>Elemento3</h3>
<p>Este es el tercer elemento</p></a></li>
  <li><a href="#3"><img src="img/foto3.png" width="100" height="100"/><h3>Forma1</h3>
<p>Este es un elemento con otra letra para que se vea el divider</p></a></li> /**Y este último... para que se vea el divisor*/
</ul>

Y con este simple ejemplo puedes darle vida a tus listas.

Tutorial de IdexedDB

El almacenamiento en el navegador se está convirtiendo en algo fundamental para el desarrollo de aplicaciones web con HTML5 y Javascript. En un primer momento parecía que WebSQL se iba a convertir en el standar, apoyado por Google y Apple en sus navegadores basados en Webkit. Pero la oposición de Microsoft y, sobre todo, de Mozilla sumada al auge de las bases de datos no relacionales en el ámbito del desarrollo móvil, ha hecho que IndexedDB se postule como el nuevo standar para almacenamiento en estos desarrollos. Desde 2010 la W3C ha dado por obsoleto WebSQL.

Todavía no me he puesto a trabajar con él en profundidad, pero os traigo aquí un tutorial basado en este de HTML5Rocks donde se nos explica cómo migrar de WebSQL a IndexedDB.

El ejemplo es una base de datos para un TODO list. Con WebSQL simplemente operábamos como si de una base de datos SQL se tratara, pero el concepto de IdexedDB es distinto. Se trata de un almacén de objetos Javascript donde guardamos parejas de valores: una clave y el objeto. Las búsquedas no se hacen con una query sino que se realiza una consulta sobre un índice que luego es iterado.

El primer paso con IdexedDB es abrir una base de datos:

window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;

// Manejando el prefijo de Chrome para IDBTransaction/IDBKeyRange.
if ('webkitIndexedDB' in window) {
  window.IDBTransaction = window.webkitIDBTransaction;
  window.IDBKeyRange = window.webkitIDBKeyRange;
}

indexedDB.db = null;

// Para depurar enviamos los errores a la consola
indexedDB.onerror = function(e) {
  console.log(e);
};

Arriba ya tenemos abierta una base de datos. Lo segundo, y también fundamental, es crear un almacén de objetos, igual que creamos tablas cuando trabajamos con una base de datos SQL.

indexedDB.open = function() {
  var request = indexedDB.open("todos");

  request.onsuccess = function(e) {
    var v = "2.0 beta"; // puedes poner cadenas en el nombre de la versión
    todoDB.indexedDB.db = e.target.result;
    var db = todoDB.indexedDB.db;
    // En la transacción setVersión es donde podemos crear los almacenes
    if (v!= db.version) {
      var setVrequest = db.setVersion(v);

      // para poder crear almacenes tenemos que hacerlo en onsuccess
      setVrequest.onfailure = todoDB.indexedDB.onerror;
      setVrequest.onsuccess = function(e) {
        if (db.objectStoreNames.contains("todo")) {
          db.deleteObjectStore("todo");
        }

        var store = db.createObjectStore("todo", {keyPath: "timeStamp"});

        var transaction = e.target.result;
        transaction.oncomplete = function() {
          todoDB.indexedDB.getAllTodoItems();
        }
      };
    } else {
      todoDB.indexedDB.getAllTodoItems();
    }
  };

  request.onfailure = todoDB.indexedDB.onerror;
};

Aquí hemos abierto el almacén todos, tras ello comprobamos que la versión de la base de datos sea diferente a la que estamos definiendo. En caso de que no lo sea, con setVersion creamos el nuevo almacén. El único sitio donde podemos modificar la estructura de la base de datos es en setVersion, donde se nos permite crear y eliminar almacenes de objetos.

El siguiente paso es ver cómo podemos añadir objetos a la colección. Para ello creamos la función addTodo.

indexedDB.addTodo = function() {
  var db = todoDB.indexedDB.db;
  var trans = db.transaction(['todo'], 'readwrite');
  var store = trans.objectStore('todo');

  var data = {
    "text": todoText, // el texto debe ser visible aquí
    "timeStamp": new Date().getTime()
  };

  var request = store.put(data);

  request.onsuccess = function(e) {
    todoDB.indexedDB.getAllTodoItems();
  };

  request.onerror = function(e) {
    console.log("Error Adding: ", e);
  };
};

Este método addTodo comienza cogiendo una referencia a la base de datos, seguimos definiendo una transacción readwrite y obtenemos una referencia a la coleccion de objetos. Luego creamos los datos como un objeto JSON y lo metemos en la base con el método put. Guardaremos el texto y un timestamp que servirá como clave única. Definimos que se re-renderizará en caso de éxito y que en caso de error se enviará a la consola.

Y si podemos insertar datos, desde luego podemos también borrarlos. Para ello crearemos el método deleteTodos:

indexedDB.deleteTodo = function(id, text) {
  if (confirm("Are you sure you want to Delete " + text + "?")) {
    var db = todoDB.indexedDB.db;
    var trans = db.transaction(["todo"], 'readwrite');
    var store = trans.objectStore("todo");

    var request = store.delete(id);

    request.onsuccess = function(e) {
      todoDB.indexedDB.getAllTodoItems();
    };

    request.onerror = function(e) {
      console.log("Error Adding: ", e);
    };
  }
};

El funcionamiento es similar al anterior método, pero con un delete en lugar del put, que recibe la clave del objeto para ser borrado.

Finalmente vamos con la lectura e impresioń de datos en pantalla, que a fin de cuentas es lo que nos importa en estos casos. Si queremos almacenar algo es para poder recuperarlo posteriormente.

function showAll() {
  document.getElementById("ourList").innerHTML = "";

  var request = window.indexedDB.open("todos");
  request.onsuccess = function(event) {
    // Enumerar el almacén de objetos completo.
    var db = todoDB.indexedDB.db;
    var trans = db.transaction(["todo"], 'readonly');
    var request = trans.objectStore("todo").openCursor();
    var ul = document.createElement("ul");

    request.onsuccess = function(event) {
      // Esto es un hack para versiones antiguas de Firefox (older versions than 6)
      var cursor = request.result || event.result;

      // Cuando el cursor esté a nulo es que hemos terminado la enumeración, por lo que
      // actualizamos el DOM
      if (!cursor) {
        document.getElementById("ourList").appendChild(ul);
        return;
      }

      var li = document.createElement("div");
      li.textContent = "key: " + cursor.key + " => Todo text: " + cursor.value.text;
      ul.appendChild(li);
      cursor.continue();
    }
  }
}

La idea en el caso de mostrar los resultados es abrir la transacción como readonly, creamos una lista (para este caso) y en cada iteración del cursor creamos un elemento li con un div dentro con los datos que vamos agregando a la lista. Cuando se termine el cursor, en la última iteración, añadimos la lista al DOM.

Como apreciación he de decir que IndexedDB, aunque desde luego es muy útil, tiene cosas que me parecen innecesariamente complicadas. Tal vez sea porque vengo del mundo de las bases de datos SQL pero WebSQL me parecía más claro y usable. Y con todo yo estoy acostumbrado a trabajar con Javascript (reconozco que nunca cosas muy complejas), no quiero imaginarme a muchos programadores que no tocan jamás el lado del cliente.

DonnieTabs, plugin simple de pestañas para jQuery

El plugin DonnieTabs, a cargo del menda, está disponible con licencia MIT en GitHub para que lo utilicéis, modifiquéis y forkeéis a vuestro gusto.

Su uso es simple:

Añades en tu web la librería jQuery, el archivo .js de DonnieTabs y el archivo CSS (y si necesitas el CSS extra para IE también viene).

Luego para crear las pestañas y el contenido simplemente tenéis que usar unas clases y nomenclatura concretos en el marcado HTML y punto:

<!-- Para las pestañas una lista y dentro cada elemento así-->
<ul class="simpletabs">
    <li>
	<a href="#tab1">Tab1</a>
    </li>
    <li>
	<a href="#tab2">Tab2</a>
    </li>
    <li>
	<a href="#tab3">Third tab</a>
    </li>						
</ul>
<!-- La lista debe ser la clase SimpleTabs, y la referencia del enlace debe ser al Id de la pestaña de contenido-->

Con eso tenéis las pestañas de navegación, la parte del contenido irá así:

<!-- Todo el contenido irá dentro un div de la clase tab_container -->
<div class="tab_container">
<!-- Y dentro, el contenido va en divs de la clase tab_content-->
    <div id="tab1" class="tab_content">
	<h3>titulo</h3>
	<p>Contenido</p>	
    </div>
    <div id="tab2" class="tab_content">
	<h3>titulo</h3>
	<p>Contenido</p>
    </div>
    <div id="tab3" class="tab_content">
	<h3>titulo</h3>
	<p>Contenido</p>
    </div>			
</div>	

Con esto ya tenéis vuestra página con navegación por pestañas. Podéis aportar todo lo que queráis a este proyecto. Yo intentaré meterle más cosas, por ahora es algo muy básico y ya tengo alguna idea.

Colorbox: Plugin de jQuery para ventanas modales y lightbox, ahora en gallego

Español:

Soy habitual usuario de Colorbox, plugin que he utilizado en varios desarrollos (Loterías Cedeira, Lana y Punto) para la creación de ventanas modales con contenido externo o con galerías de fotos, una alternativa a las ventanas modales de jQueryUi o a Lightbox. Desde hoy además incluye traducción al gallego dentro de los complementos de internalización (gracias al menda, que vale que no era mucho pero nadie lo había hecho). Podéis descargarlo desde este enlace.

Galego:

Son un devoto usuario de Colorbox , plugin que xa utilicéi en varios desenvolvementos (Loterías Cedeira, Lana Y Punto) para xerar fiestras modáis con contido externo ou con galerías de imaxes, unha alternativa ás fiestras modáis de jQueryUi ou ó Lightbox. Dende hoxe además inclúe a traducción ó galego dentro dos seus complementos de internacionalización (grazas a min, que vale que non era moito choio pero ninguén o fixera antes). Podedes descargalo dende esta ligazón.