Crear persistentes notas adhesivas con Local Storage
Spanish (Español) translation by Elías Nicolás (you can also view the original English article)
El almacenamiento local HTML5 es como las cookies con esteroides; es increíblemente simple de usar y aún así tan poderoso. En este tutorial, le mostraré cómo crear la funcionalidad de "notas adhesivas", que permite a los usuarios tomar notas persistentes mientras navega por su sitio.
Paso 1: El HTML
Debido a la naturaleza dinámica de este proyecto, no hay mucho para codificar en el camino del antiguo marcado semántico. Simplemente simularemos una página web reuniendo un poco de contenido de relleno:
1 |
<!DOCTYPE html>
|
2 |
<html>
|
3 |
<head>
|
4 |
<meta charset='utf-8' /> |
5 |
<title>HTML 5 complete</title> |
6 |
<link rel="stylesheet" href="default.css" /> |
7 |
<link rel="stylesheet" href="stickies/stickies.css" /> |
8 |
<!--[if IE]>
|
9 |
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
10 |
<![endif]-->
|
11 |
</head>
|
12 |
<body>
|
13 |
<article>
|
14 |
<header>
|
15 |
<h1> Sample Article Title</h1> |
16 |
</header>
|
17 |
<p>Lorem ipsum dolor. . . </p> |
18 |
<!-- a few lorem-ipsum paragraphs later . . . -->
|
19 |
<footer>
|
20 |
<p>Copyright 2010 Andrew Burgess</p> |
21 |
</footer>
|
22 |
</article>
|
23 |
|
24 |
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script> |
25 |
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/jquery-ui.min.js"></script> |
26 |
<script src="json2.js"></script> |
27 |
<script src="stickies/stickies.js"></script> |
28 |
<script>
|
29 |
</script>
|
30 |
</body>
|
31 |
</html>
|
Hay algunas
cosas importantes que notar aquí: estamos incluyendo dos archivos CSS:
el primero es el estilo simple de la página, que hemos llamado
default.css. Luego, tenemos un archivo CSS especial para estilos relacionados con
nuestras notas adhesivas; se llama stickies.css, y como puede ver, vive
en la carpeta "stickies". En la parte inferior, estamos incluyendo
cuatro scripts:
- jQuery, del CDN de Google
- JQuery UI, del CDN de Google
- JSON2, de Douglas Crockford
- Nuestro propio
stickies.js, que vive en el directorio "stickies"
Luego, tenemos una etiqueta de script vacía que usaremos para iniciar el motor un poco más tarde.
¡Y eso es todo por HTML!
Paso 2: El CSS
El contenido de default.css es
increíblemente simple:
1 |
body { |
2 |
margin:0; |
3 |
padding:0; |
4 |
background:#ccc; |
5 |
font:14px/1.5 "Helvetica Neue", Helvetica, Arial, san-serif; |
6 |
}
|
7 |
article, footer, header { display: block; } |
8 |
article { |
9 |
width:880px; |
10 |
background:#fff; |
11 |
margin:auto; |
12 |
padding:40px; |
13 |
}
|
14 |
article header { |
15 |
color:#474747; |
16 |
border-bottom:1px solid #474747 |
17 |
}
|
18 |
article footer { |
19 |
font-size:90%; |
20 |
color:#ccc; |
21 |
}
|
Eso es; ahora, está el CSS de stickies.css que cuidar ... pero aún no tenemos ese marcado. Empecemos con JavaScript, y cuando lo hagamos, veremos el CSS de las notas adhesivas.
Paso 3: El JavaScript
Aquí está el esqueleto de nuestra aplicación de JavaScript:
1 |
var STICKIES = (function () { |
2 |
var initStickies = function () {}, |
3 |
openStickies = function () {}, |
4 |
createSticky = function (data) {}, |
5 |
deleteSticky = function (id) {}, |
6 |
saveSticky = function () {}, |
7 |
markUnsaved = function () {}; |
8 |
|
9 |
return { |
10 |
open : openStickies, |
11 |
init : initStickies |
12 |
};
|
13 |
}());
|
Aquí tenemos algunas técnicas interesantes. Primero
está la función de auto-involuntario: puede parecer que asignamos una
función a la variable STICKIES, pero si miras detenidamente al final de
la función, verás que la estamos ejecutando de inmediato. Como
sugerencia, para recordarnos que esta no es una función normal, estamos
envolviendo toda la función entre paréntesis. Entonces, STICKIES no es
una función, es el valor devuelto por esa función, que es un objeto, en
este caso.
Eso nos lleva a la siguiente técnica: cierre. Tenga
en cuenta que de las seis funciones que creamos, solo dos de ellas
están expuestas al usuario (realmente, solo una es necesaria para el uso
que estamos planificando, si quisiéramos crear soporte para crear notas
en su sitio web, podríamos exponer el createSticky y deleteSticky). Aunque
la función de autoinversión finaliza antes de que usemos los métodos,
podremos usar las otras funciones que hemos definido.
De acuerdo, pasemos al contenido de estas funciones.
initStickies
Comenzaremos mirando la
función initStickies:
1 |
var initStickies = function initStickies() { |
2 |
$("<div />", { |
3 |
text : "+", |
4 |
"class" : "add-sticky", |
5 |
click : function () { createSticky(); } |
6 |
}).prependTo(document.body); |
7 |
initStickies = null; |
8 |
},
|
Esto es bastante simple. Usaremos
jQuery para crear elementos bastante, y estamos usando alguna sintaxis
especial en v. 1.4: eso es pasar un literal de objeto con las
especificaciones para el elemento como un segundo parámetro a la función
jQuery. Aquí, estamos creando un botón para crear una nueva nota. Eso significa que necesitamos un nuevo div; estamos configurando el
texto en "+" y dándole una clase "add-sticky"; entonces,
estamos configurando un manejador de clics para llamar al método
createSticky (es importante llamar a createSticky desde dentro de una
función, y no tener la llamada del manejador de clics directamente a
createSticky; esto es porque createSticky puede tomar un solo parámetro,
y no quiero que sea el objeto del evento). Finalmente,
estamos anteponiendo este div al cuerpo. Terminamos estableciendo
initStickies en null; Sí, básicamente nos estamos deshaciendo de la
función que estamos ejecutando. Esto nos asegura que esta función solo
se ejecutará una vez; no queremos que el usuario de nuestra API agregue
involuntariamente múltiples botones de "agregar nota" a la
página.
openStickies
Pasemos al siguiente método, openStickies:
1 |
openStickies = function openStickies() { |
2 |
initStickies && initStickies(); |
3 |
for (var i = 0; i < localStorage.length; i++) { |
4 |
createSticky(JSON.parse(localStorage.getItem(localStorage.key(i)))); |
5 |
}
|
6 |
},
|
Comenzamos ejecutando initStickies... pero ¿qué pasa con la sintaxis sofisticada? Bueno, probablemente esté familiarizado con el operador &&: el operador AND booleano. Usualmente lo usaría para verificar múltiples condiciones en una
instrucción if. Esto es lo que hace: evalúa la primera expresión, y si
eso es cierto, continuará evaluando la segunda expresión. En este caso,
si initStickies aún no se ha establecido en nulo, ejecutaremos la
función. Esto evita el error que vendría al tratar de ejecutar una
variable nula como una función.
A continuación, colocamos un bucle sobre cada elemento en localStorage. Esto es lo que hacemos en ese for-loop (de adentro hacia afuera):
-
localStorage.key()es una gran función que devuelve el nombre clave del valorlocalStorage; toma un número como un parámetro. Es una excelente forma de recorrer cada elemento enlocalStorage. - Una vez que tenemos la clave para un artículo
almacenado, podemos pasarlo a
localStorage.getItem()para obtener su valor. - Luego, pasamos ese valor a
JSON.parse();esto proviene de la biblioteca de Crockford. Debido a que estamos almacenando algunos valores para cada nota, estamos usandoJSON.stringify()en el otro extremo para convertir un objeto en una cadena JSON, que almacenamos. Aquí, lo convertimos de una cadena a un objeto. - Finalmente, pasamos ese objeto a
createSticky(), que lo vuelve a convertir en una nota adhesiva.
createSticky
Ahora, veamos ese
método createSticky.
1 |
createSticky = function createSticky(data) { |
2 |
data = data || { id : +new Date(), top : "40px", left : "40px", text : "Note Here" } |
3 |
|
4 |
return $("<div />", { |
5 |
"class" : "sticky", |
6 |
'id' : data.id |
7 |
})
|
8 |
.prepend($("<div />", { "class" : "sticky-header"} ) |
9 |
.append($("<span />", { |
10 |
"class" : "status-sticky", |
11 |
click : saveSticky |
12 |
}))
|
13 |
.append($("<span />", { |
14 |
"class" : "close-sticky", |
15 |
text : "trash", |
16 |
click : function () { deleteSticky($(this).parents(".sticky").attr("id")); } |
17 |
}))
|
18 |
)
|
19 |
.append($("<div />", { |
20 |
html : data.text, |
21 |
contentEditable : true, |
22 |
"class" : "sticky-content", |
23 |
keypress : markUnsaved |
24 |
}))
|
25 |
.draggable({ |
26 |
handle : "div.sticky-header", |
27 |
stack : ".sticky", |
28 |
start : markUnsaved, |
29 |
stop : saveSticky |
30 |
})
|
31 |
.css({ |
32 |
position: "absolute", |
33 |
"top" : data.top, |
34 |
"left": data.left |
35 |
})
|
36 |
.focusout(saveSticky) |
37 |
.appendTo(document.body); |
38 |
},
|
Sí, es largo, pero no va a ser muy difícil. Primero, observe que esta función toma un objeto de datos; como acabamos de ver en openStickies, estamos pasando los datos almacenados a esta función. Sin
embargo, si no transmitimos ningún dato (es decir, estamos creando una
nota nueva), crearemos el objeto de nota predeterminado. Como todas las
notas deben crearse en un punto, todas las notas comenzarán con esta
configuración. Tenga en cuenta que para el ID de la nota, estamos usando
+new Date(); El
operador unario plus antepuesto convierte la fecha que obtenemos de una
nueva fecha en un número, por lo que esta declaración da como resultado
un número que representa el número de milisegundos desde el 1 de enero
de 1970. Obviamente, este número cambiará continuamente, por lo que es
una excelente forma de identificar de forma única cada nota.
El resto de la función es una larga cadena de métodos jQuery encadenados. Antes de pasar por esto, observe que estamos devolviendo el resultado. Si expusimos este método a los desarrolladores que usan nuestro mirco-API, devolvería una referencia al elemento div de nota adhesiva.
Entonces, esto es lo que está pasando:
-
Primero, creamos el
divque es el caparazón de la nota adhesiva. Usando esa útil sintaxis de jQuery 1.4, le damos una clase de "pegajoso" y la identificación del objeto de datos. -
Entonces, anteponemos un
diva ese; estedivobtiene una clase "sticky-header".div.sticky-headerrecibe dos tramos agregados a él. El primer,span.sticky-status, obtiene un controlador de clics que llama a la funciónsaveSticky. Sin embargo, eso es en realidad una función oculta: este lapso mostrará el estado del adhesivo: guardado o no guardado. Habrá algunas formas en las que Sticky guardará sus datos enlocalStorage; es posible que el usuario piense que al hacer clic en "no guardado" se guardará la nota, por lo que le proporcionaremos esa funcionalidad. El segundo tramo,span.close-sticky, será el botón Eliminar: cuando el usuario haga clic en él, eliminaremos el material adhesivo delocalStorage, a través del métododeleteSticky. Pasamos ese método la identificación de la nota. -
A continuación, agregamos otro div al
div.stickyprincipal; observe que establecemos la propiedadhtmlendata.text; cuando guardamos el texto de la nota, estamos utilizando el métodohtml()de jQuery, porque el uso detext()elimina los saltos de línea. También establecemoscontentEditable:trueen este div, porque es el contenido de la nota. Como tal, también obtiene la clasesticky-content. Finalmente, cuando se presiona una tecla en este div (lo que significa que el usuario está cambiando el contenido), queremos marcarlo como no guardado, así que llamaremos a esa función (lo cual haremos pronto). -
Ahora, estamos usando la característica jQuery UI que se puede arrastrar para hacer que nuestra nota adhesiva sea movible. En nuestro objeto de parámetro, usamos la propiedad
handlepara hacer que nuestras notas solo se puedan mover desde la barra de encabezado. La propiedadstackes un selector para los elementos arrastrables que desea "apilar"; estableciendo esto, la nota arrastrada siempre estara sobre todo. Finalmente, cuando comenzamos a arrastrar la nota, queremos marcarla como "no guardada" (porque también tenemos que guardar sus coordenadas), y cuando dejamos de arrastrarla, guardaremos esa. -
A continuación, configuramos algunos estilos para nuestro
div.sticky; lo posicionamos en forma absoluta, y luego establecemos sus valores superior e izquierdo a los que están en el objeto de datos. De esta manera, la nota mantendrá su posición así como su contenido cuando refresquemos la página. -
Finalmente, configuraremos un manejador de eventos para cuando
focusout(esencialmente, haga clic fuera luego de hacer clic dentro): queremos guardarla. Por último, lo agregaremos al cuerpo. Como referencia, aquí está la estructura html que deberíamos haber generado:
1 |
<div class="sticky ui-draggable" id="1281194825332" style="position: absolute; top: 40px; left: 40px;"> |
2 |
<div class="sticky-header"> |
3 |
<span class="sticky-status"></span> |
4 |
<span class="close-sticky">trash</span> |
5 |
</div>
|
6 |
<div contenteditable="true" class="sticky-content"> |
7 |
Note Here |
8 |
</div>
|
9 |
</div>
|
Y esa es nuestra función createSticky.
deleteSticky
Ahora tenemos la función deleteSticky; es realmente simple:
1 |
deleteSticky = function deleteSticky(id) { |
2 |
localStorage.removeItem("sticky-" + id); |
3 |
$("#" + id).fadeOut(200, function () { $(this).remove(); }); |
4 |
},
|
Como recordará, la función deleteSticky toma el ID de una nota como su parámetro. localStorage.removeItem() es el método de la hora: le pasamos la clave a un valor almacenado
localmente para eliminar ese par clave-valor (Observe que cuando
almacenamos los datos de la nota, estamos anteponiendo "sticky-" al ID). Luego, encontramos el elemento con el ID dado, lo
atenuamos y lo eliminamos. Nota borrada
saveSticky
El penúltimo podría ser
el método más importante hoy en día: saveSticky: este es el pegamento
que hace que todo funcione.
1 |
saveSticky = function saveSticky() { |
2 |
var that = $(this), sticky = (that.hasClass("sticky-status") || that.hasClass("sticky-content")) ? that.parents('div.sticky'): that, |
3 |
obj = { |
4 |
id : sticky.attr("id"), |
5 |
top : sticky.css("top"), |
6 |
left: sticky.css("left"), |
7 |
text: sticky.children(".sticky-content").html() |
8 |
}
|
9 |
localStorage.setItem("sticky-" + obj.id, JSON.stringify(obj)); |
10 |
sticky.find(".sticky-status").text("saved"); |
11 |
},
|
La primera línea es un poco de resolución: hay tres elementos diferentes a los que podemos llamar esta función. Primero, "jQuerifaremos" this en that; luego, si el elemento tiene las clases "sticky-status" o "sticky-content", obtendremos el padre div.sticky; si no tiene ninguna de esas clases, entonces es div.sticky en sí mismo, así que solo usaremos eso.
Entonces, necesitamos obtener los valores que queremos almacenar. Como
puede ver, estamos obteniendo la identificación, el desplazamiento
desde la parte superior e izquierda, y el html del hijo .sticky-content; recuerde, estamos usando html() en lugar de text() porque
queremos mantener los saltos de línea. Luego, usamos
localStorage.setItem para almacenar los datos. Recuerde, toma dos
parámetros: la clave y el valor para almacenar. Como localStorage solo
almacena cadenas, usamos JSON.stringify() para convertir el objeto a
una cadena.
Por último, cambie el estado adhesivo a "guardado".
markUnsaved
Tenemos una última función, que es solo una función auxiliar:
1 |
markUnsaved = function markUnsaved() { |
2 |
var that = $(this), sticky = that.hasClass("sticky-content") ? that.parents("div.sticky") : that; |
3 |
sticky.find(".sticky-status").text("unsaved"); |
4 |
}
|
Nuevamente, debemos comenzar resolviendo la referencia a div.sticky; una vez que lo hacemos, podemos simplemente encontrar el lapso de estado y establecer el texto como "no guardado".
Lo creas o no, eso es todo el JavaScript.
Paso 4: El CSS, revisitado
Ahora que sabemos cuál es nuestro marcado de notas adhesivas, podemos darle un estilo. Es bastante simple; pero mira, y haré algunos comentarios al final:
1 |
:focus { |
2 |
outline:0; |
3 |
}
|
4 |
.add-sticky { |
5 |
cursor: default; |
6 |
position:absolute; |
7 |
top:1px; |
8 |
left:1px; |
9 |
font-size:200%; |
10 |
background:#000; |
11 |
color:#fff; |
12 |
border:2px solid #fff; |
13 |
border-radius:40px; |
14 |
-webkit-border-radius:40px; |
15 |
-moz-border-radius:40px; |
16 |
text-align:center; |
17 |
line-height:25px; |
18 |
width:30px; |
19 |
height:30px; |
20 |
}
|
21 |
.add-sticky:hover { |
22 |
background: #474747; |
23 |
}
|
24 |
.sticky { |
25 |
width:300px; |
26 |
background:#fdfdbe; |
27 |
box-shadow:3px 3px 10px rgba(0,0,0,0.45); |
28 |
-webkit-box-shadow:3px 3px 10px rgba(0,0,0,0.45); |
29 |
-moz-box-shadow:3px 3px 10px rgba(0,0,0,0.45); |
30 |
}
|
31 |
.sticky-content { |
32 |
min-height:150px; |
33 |
border-left:3px double rgba(238, 150, 122, .75); |
34 |
margin-left:30px; |
35 |
padding:5px; |
36 |
}
|
37 |
.sticky-header { |
38 |
padding:5px; |
39 |
background:#f3f3f3; |
40 |
border-bottom:2px solid #fefefe; |
41 |
box-shadow:0 3px 5px rgba(0,0,0,0.25); |
42 |
-webkit-box-shadow:0 3px 5px rgba(0,0,0,0.25); |
43 |
-moz-box-shadow:0 3px 5px rgba(0,0,0,0.25); |
44 |
}
|
45 |
.sticky-status { |
46 |
color:#ccc; |
47 |
padding:5px; |
48 |
}
|
49 |
.close-sticky { |
50 |
background:#474747; |
51 |
float:right; |
52 |
cursor:default; |
53 |
color:#ececec; |
54 |
padding:1px 5px; |
55 |
border-radius:20px; |
56 |
-webkit-border-radius:20px; |
57 |
-moz-border-radius:20px; |
58 |
}
|
Aquí hay algunos puntos de interés:
- Algunos navegadores ponen un contorno alrededor de los elementos
con
contenteditable=truecuando editas el contenido. No queremos eso, así que nos deshacemos de él con nuestra declaración de enfoque:focus. - El botón "Agregar adhesivo" está ubicado en la esquina superior izquierda; se ve vagamente similar al "Agregar widget del tablero de instrumentos" en Mac OS X.
- Estamos utilizando las propiedades CSS3 border-box y box-shadow (y sus respectivas encarnaciones de prefijo de proveedor).
- También usamos
rgba()para nuestros colores de sombras. Toma cuatro parámetros: el rojo, la codicia y el azul, y el valor alfa (transparencia).
Aparte de eso, es solo su CSS estándar. Esto es lo que debería ser una nota con estilo:

Paso 5: Comenzando los Stickies
Ahora que
hemos creado nuestra API, es hora de comenzar; podemos hacerlo desde la
etiqueta script vacía adicional en nuestro archivo index.html:
1 |
STICKIES.open(); |
Conclusión: el producto final
Bueno, ¡ya terminamos! Aquí está el producto final en acción:
Eso es todo lo que tengo para hoy; ¿Cómo planeas utilizar el almacenamiento local HTML5 para darle más vida a tus proyectos web? ¡Házmelo saber en los comentarios!



