Cómo codificar una divertida lista de tareas pendientes con PHP y AJAX
Spanish (Español) translation by Ana Paulina Figueroa (you can also view the original English article)
Para el tutorial prémium de Tuts+ de esta semana, trabajaremos con muchas tecnologías diferentes. En última instancia, crearemos una lista de tareas pendientes que te permitirá a ti o a tu usuario crear, actualizar y eliminar elementos de forma asíncrona. Para lograr nuestra tarea, vamos a usar PHP y las capacidades de AJAX de jQuery. Creo que descubrirás que no es tan difícil como podrías pensar inicialmente. ¡Te mostraré exactamente cómo!
Este tutorial incluye un screencast disponible para los miembros de Tuts+ prémium.



Paso 1: creando una nueva base de datos
Como puedes imaginar, no podemos guardar, eliminar y actualizar registros en un entorno estático. Por lo tanto, debemos crear una base de datos MySql que almacenará la información.
Si estás usando PHPMyAdmin, entra al panel de control visitando https://localhost/phpmyadmin.



Dentro del cuadro de texto "Create New Database" ("Crear nueva base de datos"), escribe "db" y haz clic en "Create" ("Crear"). Luego necesitarás crear una tabla. Escribe "todo" y '3' en "Number of fields" ("Número de campos").



Creando nuestras columnas
Ahora necesitaremos agregar las columnas apropiadas.
- id: id único para identificar cada fila.
- title (título): el título de nuestro elemento.
- description (descripción): ¡una descripción que detalla lo que tenemos que hacer!
Asegúrate de que las opciones de cada campo coincidan con las que se muestran en la siguiente imagen.



Inserta filas de prueba
Ahora que hemos creado nuestra base de datos, agreguemos rápidamente algunas filas de prueba. Haz clic en tu base de datos "db", y luego elige "Browse" ("Examinar"). Serás llevado a una pantalla que lista el contenido de cada fila de tu base de datos. Obviamente, esta sección está vacía en este momento. Elige "Insert" ("Insertar") y agrega algunas columnas. Escribe lo que tú desees aquí.




Screencast completo
Paso 2: la clase Db



Aunque no es necesario de ninguna manera, en mi experiencia es más sencillo administrar mis funciones cuando las agrupo en una clase. Teniendo esto en cuenta, ahora crearemos una clase "Db" que contendrá varias funciones.
- __construct: esta función se ejecuta automáticamente tan pronto como se crea una instancia del objeto.
- delete_by_id(): elimina la fila necesaria enviando el id único de la fila.
- update_by_id(): actualiza la fila enviando su id único.
Abre el editor de código de tu preferencia y crea un nuevo archivo llamado "db.php". Pega las siguientes líneas de código dentro de este documento en blanco.
1 |
|
2 |
class Db { |
3 |
|
4 |
private $mysql; |
5 |
|
6 |
function __construct() { |
7 |
$this->mysql = new mysqli('localhost', 'root', 'yourPassword', 'db') or die('problem'); |
8 |
}
|
9 |
} // end class |
Para crear una nueva clase, usamos la sintaxis que se muestra a continuación.
1 |
|
2 |
class 'myClass' {
|
3 |
|
4 |
} |
Usando solamente el código anterior, hemos creado con éxito una nueva clase. Todavía no hace nada, ¡pero es una clase de todos modos!
__construct()
El método __construct() (terminología de clases para "función") es conocido como un "método mágico". Este se ejecutará inmediatamente después de crear una instancia de una clase. Vamos a usar este método para crear nuestra conexión inicial a la base de datos MySql.
1 |
|
2 |
function __construct() {
|
3 |
$this->mysql = new mysqli('localhost', 'root', 'yourPassword', 'db') or die('problem');
|
4 |
} |
Si no estás familiarizado con la programación orientada a objetos, esto puede resultar un poco abrumador al principio. Afortunadamente no es muy difícil de entender. Queremos que nuestra conexión mysql esté disponible para todos los métodos de nuestra clase. Tomando en cuenta esto, no sería una buena idea almacenar la variable $mysql dentro de una función específica. En lugar de eso debería ser una propiedad de la clase.
1 |
|
2 |
private $mysql; |
Accediendo a propiedades desde métodos
Dentro de un método, no podemos simplemente acceder a nuestra propiedad escribiendo "$mysql". Primero debemos hacer referencia al objeto.
1 |
|
2 |
$this->mysql |
Asegúrate de tomar nota del hecho de que, al acceder a una propiedad, podemos omitir el signo de dólar.
Mysqli



Es preferible usar mysql mejorado (mysqli) en lugar del método tradicional mysql_connect al conectarnos a una base de datos. No solo es más rápido, sino que también nos permite tener un enfoque que usa la programación orientada a objetos.
Al crear una nueva instancia de la clase mysqli, debemos enviar cuatro parámetros.
- host (servidor de alojamiento): 'localhost'
- username (nombre de usuario): root
- password (contraseña): 'tuContraseña'
- database name (nombre de la base de datos): db
Eso debería ser suficiente por ahora. Regresaremos a nuestra clase a lo largo de este tutorial para agregar nuevos métodos. Solo recuerda que, cuando creamos una nueva instancia de esta clase...
1 |
|
2 |
require 'db.php'; |
3 |
$db = new Db(); |
... abrimos automáticamente una conexión a nuestra base de datos, gracias al método mágico __construct().
El marcado



Ahora necesitamos crear nuestro marcado para la página de inicio. Agrega una nueva página a tu solución y guárdala como "index.php". Después, pega lo siguiente.
1 |
|
2 |
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> |
3 |
<html xmlns="http://www.w3.org/1999/xhtml"> |
4 |
<head> |
5 |
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> |
6 |
<link rel="stylesheet" href="css/default.css" /> |
7 |
<title>My To-Do List</title> |
8 |
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.1/jquery.min.js"></script> |
9 |
<script type="text/javascript" src="js/scripts.js"></script> |
10 |
</head> |
11 |
|
12 |
<body> |
13 |
|
14 |
<div id="container"> |
15 |
|
16 |
<h1>My to-Do List</h1> |
17 |
|
18 |
<ul id="tabs"> |
19 |
<li id="todo_tab" class="selected"><a href="#">To-Do</a></li> |
20 |
</ul> |
21 |
|
22 |
<div id="main"> |
23 |
|
24 |
<div id="todo"> |
25 |
... |
26 |
</div><!--end todo wrap--> |
27 |
|
28 |
<div id="addNewEntry"> |
29 |
... |
30 |
</div><!-- end add new entry --> |
31 |
|
32 |
</div><!-- end main--> |
33 |
</div><!--end container--> |
34 |
|
35 |
</body> |
36 |
</html> |
Análisis
Dentro del encabezado de nuestro documento, estoy haciendo referencia a CDN de Google para acceder a jQuery. Este es fácilmente el método preferido al usar jQuery. Después, estoy haciendo referencia a un archivo 'scripts.js' que vamos a crear más adelante en este tutorial.
Revisemos rápidamente para qué es cada div.
- container: div de envoltura estándar.
- ul#tabs: nuestra navegación. Usaremos JavaScript para agregar las pestañas adicionales. Explicaré por qué en breve.
- main: envoltura para el contenido principal.
- todo: pestaña 1.
- addNewEntry: pestaña 2.
Paso 4: CSS



Este no es un tutorial de CSS en sí mismo. Siéntete en la libertad de revisar la hoja de estilo que he usado. Se encuentra en el paquete de descarga. Si deseas obtener un análisis más profundo, ve el screencast.
Paso 5: recuperando registros
Ahora que nos hemos conectado a la base de datos y que hemos creado nuestro marcado/CSS, escribamos un poco de código que recuperará las filas de la base de datos.
Inserta lo siguiente dentro del div "todo".
1 |
|
2 |
<div id="todo"> |
3 |
|
4 |
<?php |
5 |
require 'db.php'; |
6 |
$db = new Db(); |
7 |
$query = "SELECT * FROM todo ORDER BY id asc"; |
8 |
$results = $db->mysql->query($query); |
9 |
|
10 |
if($results->num_rows) {
|
11 |
while($row = $results->fetch_object()) {
|
12 |
$title = $row->title; |
13 |
$description = $row->description; |
14 |
$id = $row->id; |
15 |
|
16 |
echo '<div class="item">'; |
17 |
|
18 |
$data = <<<EOD |
19 |
<h4> $Title </h4> |
20 |
<p> $description </p> |
21 |
|
22 |
<input type="hidden" name="id" id="id" value="$id" /> |
23 |
|
24 |
<div class="options"> |
25 |
<a class="deleteEntryAnchor" href="delete.php?id=$id">D</a> |
26 |
<a class="editEntry" href="#">E</a> |
27 |
</div> |
28 |
EOD; |
29 |
|
30 |
echo $data; |
31 |
echo '</div>'; |
32 |
} // end while |
33 |
} else {
|
34 |
echo "<p>There are zero items. Add one now!</p>"; |
35 |
} |
36 |
?> |
37 |
</div><!--end todo wrap--> |
Análisis
- Usa 'require' para acceder a nuestra clase Db.
- Crea una nueva instancia de la clase Db.
- Crea una consulta. Esta recuperará todos los registros de la tabla "todo" y los ordenará de forma ascendente.
- Ahora debemos ejecutar nuestra consulta. $db->mysql->query($query). $db hace referencia al objeto. $mysql hace referencia a la clase mysqli. $query es un método de la clase mysqli que nos permite enviar una consulta. Aquí estamos enviando la cadena que acabamos de crear.
- $results->num_rows devolverá el número total de filas recuperadas de la base de datos. Si una o más han sido devueltas, entonces usaremos una instrucción while para recorrer las filas con un bucle.
- Crea una variable temporal llamada $row que hará referencia a la información para cada iteración. Después creamos tres variables que hacen referencia a sus respectivas contrapartes dentro de la base de datos.
- Cada elemento será envuelto dentro de un div con una clase "item".
- A continuación usamos heredocs para dar formato a nuestro elemento de tarea pendiente. Los heredocs son una forma fácil y organizada de mezclar html y php. Para aprender más, asegúrate de revisar este screencast.
- Envuelve el título dentro de etiquetas h4 y la descripción dentro de etiquetas p.
- El usuario necesita tener una forma de editar y eliminar cada elemento. Por lo tanto, hemos creado dos etiquetas de anclaje que nos permitirán hacerlo. Volveremos a esto más tarde.
- Mostramos la información de nuestro heredoc y cerramos el div ".item".
- Si no se devolvieron filas de la base de datos, despliega "There are zero items. Add one now!" ("Hay cero elementos. ¡Agrega uno ahora!").
Ojalá todo eso haya tenido sentido. En este punto, deberías tener algo como lo siguiente:
Paso 6: agrega un nuevo elemento



También queremos que el usuario tenga la capacidad de insertar registros nuevos. Vamos a crear un formulario que permitirá esto mismo.
1 |
|
2 |
<div id="addNewEntry"> |
3 |
|
4 |
<hr /> |
5 |
<h2>Add New Entry</h2> |
6 |
<form action="addItem.php" method="post"> |
7 |
<p> |
8 |
<label for="title"> Title</label> |
9 |
<input type="text" name="title" id="title" class="input"/> |
10 |
</p> |
11 |
|
12 |
<p> |
13 |
<label for="description"> Description</label> |
14 |
<textarea name="description" id="description" rows="10" cols="35"></textarea> |
15 |
</p> |
16 |
|
17 |
<p> |
18 |
<input type="submit" name="addEntry" id="addEntry" value="Add New Entry" /> |
19 |
</p> |
20 |
</form> |
21 |
|
22 |
</div><!-- end add new entry --> |
Este es tu formulario estándar "común y corriente". Hemos agregado campos de entrada para un título y una descripción. Cuando se haga clic en el botón de envío, la información ingresada será enviada a "addItem.php". Creemos esta página ahora.
Step 7: AddItem.php
Crea un nuevo documento y guárdalo como "addItem.php". Pega el siguiente código:
1 |
|
2 |
<?php |
3 |
|
4 |
require 'db.php'; |
5 |
$db = new Db(); |
6 |
|
7 |
// adds new item |
8 |
if(isset($_POST['addEntry'])) {
|
9 |
$query = "INSERT INTO todo VALUES('', ?, ?)";
|
10 |
|
11 |
if($stmt = $db->mysql->prepare($query)) {
|
12 |
$stmt->bind_param('ss', $_POST['title'], $_POST['description']);
|
13 |
$stmt->execute(); |
14 |
header("location: index.php");
|
15 |
} else die($db->mysql->error); |
16 |
} |
- Haz referencia a nuestra clase Db.
- Crea una instancia de la clase.
- Si el botón de envío con el nombre "addEntry" existe, entonces ejecuta el siguiente código.
- Crea una nueva consulta. Notarás que estoy usando signos de interrogación como valores. Ese es el método preferido para usar sentencias preparadas al actualizar nuestra base de datos. Es una excelente manera de protegerte contra la inyección SQL.
- Prepara nuestra variable mysql enviando la consulta que acabamos de crear.
- Si se preparó correctamente, vincula los parámetros apropiados. El primer parámetro solicita los tipos de datos de cada elemento. He usado 's' para hacer referencia a "string". Los siguientes dos parámetros obtienen los valores del título y la descripción del arreglo superglobal POST.
- Ejecuta la instrucción.
- Finalmente, redirige al usuario de vuelta a la página de inicio.
Paso 7: actualiza los elementos



Usando las capacidades de AJAX de jQuery, permitamos que el usuario actualice cada elemento sin un postback. Crea un archivo nuevo dentro de una carpeta "js" y asígnale el nombre "scripts.js". Recuerda que ya hemos hecho referencia a este archivo en nuestro marcado.
1 |
|
2 |
$(function() {
|
3 |
$('.editEntry').click(function() {
|
4 |
var $this = $(this); |
5 |
var oldText = $this.parent().parent().find('p').text();
|
6 |
var id = $this.parent().parent().find('#id').val();
|
7 |
$this.parent().parent().find('p').empty().append('<textarea class="newDescription" cols="33">' + oldText + '</textarea>');
|
8 |
$('.newDescription').blur(function() {
|
9 |
var newText = $(this).val(); |
10 |
$.ajax({
|
11 |
type: 'POST', |
12 |
url: 'updateEntry.php', |
13 |
data: 'description=' + newText + '&id=' + id, |
14 |
|
15 |
success: function(results) {
|
16 |
$this.parent().parent().find('p').empty().append(newText);
|
17 |
} |
18 |
}); |
19 |
}); |
20 |
return false; |
21 |
}); |
22 |
}); |
Si regresaras a nuestro marcado en index.php, verías:
1 |
|
2 |
<div class="options"> |
3 |
<a class="deleteEntryAnchor" href="delete.php?id=$id">D</a> |
4 |
<a class="editEntry" href="#">E</a> |
5 |
</div> |
Decodificando cada línea
1 |
|
2 |
$('.editEntry').click(function() {
|
Usando jQuery, necesitamos escuchar para detectar cuando se haga clic en la etiqueta de anclaje con la clase "editEntity".
1 |
|
2 |
var $this = $(this); |
A continuación estamos guardando $(this) en caché, que hace referencia a la etiqueta de anclaje en la que se hizo clic.
1 |
|
2 |
var oldText = $this.parent().parent().find('p').text();
|
Necesitamos almacenar la descripción original. Le indicamos a la etiqueta de anclaje que encuentre el div padre, y buscamos la etiqueta p, que contiene el texto de la descripción. Obtenemos ese valor usando "text()".
1 |
|
2 |
var id = $this.parent().parent().find('#id').val();
|
Para actualizar la fila correcta de nuestra base de datos, necesito saber cuál es el id de esa fila específica. Si vuelves a consultar tu código, verás un campo de entrada oculto que contiene este valor.
1 |
|
2 |
<input type="hidden" name="id" id="id" value="$id" /> |
Una vez más, usamos "find" para acceder a este campo de entrada oculto, y luego obtenemos su valor.
1 |
|
2 |
$this.parent().parent().find('p').empty().append('<textarea class="newDescription" cols="33">' + oldText + '</textarea>');
|
Ahora necesitamos permitir que el usuario escriba una nueva descripción. Es por eso que hizo clic en "Edit Entry" ("Editar entrada"), ¡¿no es así!? Encontramos la etiqueta p de la descripción, y luego agregamos un control de área de texto. Usamos "empty()" para asegurarnos de deshacernos de todo el texto; ya no es necesario. El valor de este control de área de texto será igual al de oldText, por conveniencia.

1 |
|
2 |
$('.newDescription').blur(function() {
|
Localiza este nuevo control de área de texto, y cuando el usuario salga del cuadro de texto, ejecuta una función.
1 |
|
2 |
var newText = $(this).val(); |
Captura el nuevo texto que el usuario escriba en este control de área de texto.
1 |
|
2 |
$.ajax({
|
3 |
type: 'POST', |
4 |
url: 'updateEntry.php', |
5 |
data: 'description=' + newText + '&id=' + id, |
6 |
|
7 |
success: function(results) {
|
8 |
$this.parent().parent().find('p').empty().append(newText);
|
9 |
} |
10 |
}); |
Llama a la función .ajax, y envía algunos parámetros. El tipo será "POST". El url al que hay que acceder es "updateEntry.php". Los datos que deben enviarse a esta página son el valor de newtext que el usuario introdujo y el id único de esa fila en la base de datos.Cuando la actualización se lleve a cabo con éxito, ejecuta una función ¡y actualiza el texto anterior con el texto nuevo!
1 |
|
2 |
return false; |
Devuelve el valor false para asegurarte de que el hecho de hacer clic en la etiqueta de anclaje no redirija al usuario a otra parte.
Paso 7b: el PHP
Recuerda, hemos llamado a nuestra página "updateEntry" de PHP con jQuery, ¡pero en realidad no la hemos creado! Hagámoslo ahora. Crea una nueva página llamada "updateEntry.php" y pega lo siguiente.
1 |
|
2 |
<?php |
3 |
|
4 |
require_once 'db.php'; |
5 |
$db = new Db(); |
6 |
$response = $db->update_by_id($_POST['id'], $_POST['description']); |
7 |
|
8 |
?> |
Como antes, estamos haciendo referencia a nuestra clase Db, y luego creamos una instancia de ella. A continuación estamos creando una nueva variable llamada $response, y la igualamos a lo que sea que se devuelva del método "update_by_id()". Todavía no hemos creado este método. Ahora es un buen momento para hacerlo.
Agregando un nuevo método a nuestra clase
Regresa a tu página db.php y agrega un nuevo método en la parte inferior.
1 |
|
2 |
function update_by_id($id, $description) {
|
3 |
$query = "UPDATE todo |
4 |
SET description = ? |
5 |
WHERE id = ? |
6 |
LIMIT 1"; |
7 |
|
8 |
if($stmt = $this->mysql->prepare($query)) {
|
9 |
$stmt->bind_param('si', $description, $id);
|
10 |
$stmt->execute(); |
11 |
return 'good job! Updated'; |
12 |
} |
13 |
} |
Este método acepta dos parámetros: el id y la descripción del elemento. Entonces, cuando llamemos a este método ¡debemos recordar enviar esos dos parámetros! Comenzamos creando nuestra consulta: actualiza la tabla "todo" y cambia la descripción a lo que sea que se envíe, pero solamente actualiza la fila cuyo id sea igual al parámetro que se haya enviado.
Como la vez pasada, usaremos sentencias preparadas para actualizar nuestra base de datos. ¡Es la manera más segura! Prepara nuestra consulta, vincula los parámetros ("string" e "integer", o 'si') y ejecuta. Estamos devolviendo una cadena genérica, pero en realidad no es necesaria en absoluto. ¡Ahora nuestra actualización debería funcionar a la perfección!
Paso 8: elimina elementos

También creemos un buen método asíncrono para que el usuario elimine entradas. Cuando este haga clic en el botón de eliminar de un elemento, desvaneceremos el div y actualizaremos la base de datos para reflejar la eliminación. Abre tu archivo de JavaScript y agrega lo siguiente:
1 |
|
2 |
// Delete anchor tag clicked |
3 |
$('a.deleteEntryAnchor').click(function() {
|
4 |
var thisparam = $(this); |
5 |
thisparam.parent().parent().find('p').text('Please Wait...');
|
6 |
$.ajax({
|
7 |
type: 'GET', |
8 |
url: thisparam.attr('href'),
|
9 |
|
10 |
success: function(results){
|
11 |
thisparam.parent().parent().fadeOut('slow');
|
12 |
} |
13 |
}) |
14 |
return false; |
15 |
}); |
Decodificando
1 |
|
2 |
$('a.deleteEntryAnchor').click(function() {
|
Cuando se haga clic en la etiqueta de anclaje con la clase "deleteEntryAnchor", ejecuta una función.
1 |
|
2 |
var thisparam = $(this); |
Guarda $(this) en caché como thisparam.
1 |
|
2 |
thisparam.parent().parent().find('p').text('Please Wait...');
|
Cambia el texto de la descripción a "Please Wait" ("Por favor espera"). Debemos hacer esto para darle al usuario un poco de retroalimentación, en caso de que el proceso tarde más de lo esperado.
1 |
|
2 |
$.ajax({
|
3 |
type: 'GET', |
4 |
url: thisparam.attr('href'),
|
5 |
|
6 |
success: function(results){
|
7 |
thisparam.parent().parent().fadeOut('slow');
|
8 |
} |
9 |
}) |
Al igual que la vez pasada, enviamos algunos parámetros que acceden a "delete.php". En vez de codificar directamente la página en el valor del url, estoy accediendo a attr('href'), que es igual a 'delete.php?id=$id'.
1 |
|
2 |
<a class="deleteEntryAnchor" href="delete.php?id=$id">D</a> |
No necesitamos un parámetro "DATA", ya que toda la información apropiada se encuentra dentro de la cadena de consulta del url. Una vez que la eliminación se lleve a cabo con éxito, encontramos el div padre '.item' y lo desvanecemos lentamente.
Delete.php
Hemos llamado a nuestra página de eliminación con jQuery, pero aún no hemos creado el código PHP. Crea tu nueva página y agrega el siguiente código.
1 |
|
2 |
<?php |
3 |
|
4 |
require 'db.php'; |
5 |
|
6 |
$db = new Db(); |
7 |
$response = $db->delete_by_id($_GET['id']); |
8 |
header("Location: index.php");
|
Ya deberías estar acostumbrado a estos procedimientos. Crea una nueva instancia de nuestra clase, y llama al método "delete_by_id". Una vez que se haya ejecutado con éxito, redirige al usuario de vuelta a "index.php". Como habrás adivinado, necesitamos crear un nuevo método dentro de nuestra clase Db. Regresa a db.php y agrega tu nueva función.
Método delete_by_id()
1 |
|
2 |
function delete_by_id($id) {
|
3 |
$query = "DELETE from todo WHERE id = $id"; |
4 |
$result = $this->mysql->query($query) or die("there was a problem, man.");
|
5 |
|
6 |
if($result) return 'yay!'; |
7 |
} |
Este método aceptará un parámetro: el id. Recuerda que, para poder actualizar una fila, debemos conocer el id único de esa fila. De lo contrario el método actualizará todas las filas. Estamos eliminando todas las filas de la tabla en donde el id sea igual a lo que se envíe. Como cada fila tiene su propio id único, solamente una de ellas será afectada. A continuación, enviamos esta consulta a nuestro objeto mysql. Nuevamente es innecesario devolver un valor; es solo por diversión.
Paso 9: jQuery adicional
¡Hemos terminado nuestro trabajo con PHP! El paso final es agregar un poco de jQuery para hacer que todo funcione un poquito mejor. En la parte superior de tu archivo de JavaScript, justo después del método document.ready, agrega el siguiente código:
1 |
|
2 |
// Don't display the addNewEntry tab when the page loads. |
3 |
$('#addNewEntry').css('display', 'none');
|
4 |
|
5 |
// We're using jQuery to create our tabs. If Javascript is disabled, they won't work. Considering |
6 |
// this, we should append our tabs, so that they won't show up if disabled. |
7 |
$('#tabs').append('<li id="newitem_tab"><a href="#">New Item</a></li>');
|
8 |
|
9 |
// Hide the description for each to-do item. Only display the h4 tag for each one. |
10 |
$('div.item').children().not('h4').hide();
|
11 |
|
12 |
// The entire item div is clickable. To provide that feedback, we're changing the cursor of the mouse. |
13 |
// When this div is clicked, we're going to toggle the display from visible to hidden each time it's clicked. |
14 |
// However, when the user clicks the "update" button, the div will close when they click inside the textarea |
15 |
// to edit their description. This code detects if the target of the click was the textarea. If it was, |
16 |
// we do nothing. |
17 |
$('div.item').css('cursor', 'pointer').click(function(e) {
|
18 |
if (!$(e.target).is('textarea')) {
|
19 |
$(this).children().not('h4').slideToggle();
|
20 |
$(this).children('h4').toggleClass('expandDown');
|
21 |
} |
22 |
}); |
He comentado cada paso bastante bien. Por lo tanto, me abstendré de repetir mis palabras. Tu archivo final scripts.js debería verse así.
1 |
|
2 |
$(function() {
|
3 |
// Don't display the addNewEntry tab when the page loads. |
4 |
$('#addNewEntry').css('display', 'none');
|
5 |
|
6 |
// We're using jQuery to create our tabs. If Javascript is disabled, they won't work. Considering |
7 |
// this, we should append our tabs, so that they won't show up if disabled. |
8 |
$('#tabs').append('<li id="newitem_tab"><a href="#">New Item</a></li>');
|
9 |
|
10 |
// Hide the description for each to-do item. Only display the h4 tag for each one. |
11 |
$('div.item').children().not('h4').hide();
|
12 |
|
13 |
// The entire item div is clickable. To provide that feedback, we're changing the cursor of the mouse. |
14 |
// When this div is clicked, we're going to toggle the display from visible to hidden each time it's clicked. |
15 |
// However, when the user clicks the "update" button, the div will close when they click inside the textarea |
16 |
// to edit their description. This code detects if the target of the click was the textarea. If it was, |
17 |
// we do nothing. |
18 |
$('div.item').css('cursor', 'pointer').click(function(e) {
|
19 |
if (!$(e.target).is('textarea')) {
|
20 |
$(this).children().not('h4').slideToggle();
|
21 |
$(this).children('h4').toggleClass('expandDown');
|
22 |
} |
23 |
}); |
24 |
|
25 |
// add new item tab click |
26 |
|
27 |
$('#tabs li').click(function() {
|
28 |
$('#tabs li').removeClass('selected');
|
29 |
|
30 |
$(this).addClass('selected');
|
31 |
|
32 |
if($(this).attr('id') == 'newitem_tab') {
|
33 |
$('#todo').css('display', 'none');
|
34 |
$('#addNewEntry').css('display', 'block');
|
35 |
} else {
|
36 |
$('#addNewEntry').css('display', 'none');
|
37 |
$('#todo').css('display', 'block');
|
38 |
} |
39 |
return false; |
40 |
}); |
41 |
|
42 |
$('#todo div:first').children('h4').addClass('expandDown').end().children().show();
|
43 |
|
44 |
// Delete anchor tag clicked |
45 |
$('a.deleteEntryAnchor').click(function() {
|
46 |
var thisparam = $(this); |
47 |
thisparam.parent().parent().find('p').text('Please Wait...');
|
48 |
$.ajax({
|
49 |
type: 'GET', |
50 |
url: thisparam.attr('href'),
|
51 |
|
52 |
success: function(results){
|
53 |
thisparam.parent().parent().fadeOut('slow');
|
54 |
} |
55 |
}) |
56 |
return false; |
57 |
}); |
58 |
|
59 |
// Edit an item asynchronously |
60 |
|
61 |
$('.editEntry').click(function() {
|
62 |
var $this = $(this); |
63 |
var oldText = $this.parent().parent().find('p').text();
|
64 |
var id = $this.parent().parent().find('#id').val();
|
65 |
console.log('id: ' + id);
|
66 |
$this.parent().parent().find('p').empty().append('<textarea class="newDescription" cols="33">' + oldText + '</textarea>');
|
67 |
$('.newDescription').blur(function() {
|
68 |
var newText = $(this).val(); |
69 |
$.ajax({
|
70 |
type: 'POST', |
71 |
url: 'updateEntry.php', |
72 |
data: 'description=' + newText + '&id=' + id, |
73 |
|
74 |
success: function(results) {
|
75 |
$this.parent().parent().find('p').empty().append(newText);
|
76 |
} |
77 |
}); |
78 |
}); |
79 |
return false; |
80 |
}); |
81 |
|
82 |
}); |
Paso 10: ¡espera! el diseño se ve raro en IE6
¡Todavía no podemos dar por terminado el día! Ese buen viejo Internet Explorer 6 está causando algunos problemas de diseño.



- Los PNGs del fondo son de 24 bits. IE6 no tiene soporte nativo para esto. Necesitaremos importar un script para solucionarlo.
- Las pestañas de navegación no están apareciendo en el lugar correcto.
- Cada div.item se muestra incorrectamente cuando se expande.
- Nuestros botones de edición y eliminación están demasiado a la derecha de nuestro div.
La solución
Aunque nos gustaría, todavía no podemos ignorar este navegador. Afortunadamente, descubrirás que la mayoría de los problemas de IE6 pueden solucionarse con bastante facilidad. Primero necesitamos importar un script que solucionará nuestro problema con la transparencia alfa. Dean Martin tiene un fantástico archivo de JavaScript que logra que IE6 cumpla con los estándares. Podemos solucionar nuestro problema simplemente agregando "-trans" al final de los nombres de nuestros archivos PNG de 24 bits. Asegúrate de visitar la carpeta de las imágenes y editar los nombres.
1 |
|
2 |
<!--[if lt IE 7]> |
3 |
<script src="http://ie7-js.googlecode.com/svn/version/2.0(beta3)/IE7.js" type="text/javascript"></script> |
4 |
<link rel="stylesheet" href="css/ie.css" /> |
5 |
<![endif]--> |
CDN de Google viene al rescate de nuevo proporcionando una versión alojada del script para IE7. Eso soluciona nuestro problema con la transparencia, pero aún tenemos algunas peculiaridades más.



Observa que, en nuestra sentencia condicional, también importamos un archivo "ie.css". Crea ese archivo ahora mismo y pega lo siguiente en el interior:
1 |
|
2 |
body {
|
3 |
margin: 0; padding: 0; |
4 |
} |
5 |
|
6 |
#tabs {
|
7 |
height: 100%; |
8 |
|
9 |
} |
10 |
|
11 |
#main {
|
12 |
height: 100%; |
13 |
} |
14 |
|
15 |
#main div.item {
|
16 |
width: 100%; |
17 |
overflow: hidden; |
18 |
position: relative; |
19 |
} |
Descubrirás que agregar "position: relative", "overflow: hidden" y "height: 100%" resolverá el 90% de tus problemas con IE6. ¡Ahora nuestro diseño funciona perfectamente en todos los navegadores!



¡Has terminado!



Había mucho que cubrir aquí. Ojalá haya podido explicar con la suficiente profundidad. Si no, ¡para eso está el screencast asociado! Asegúrate de revisarlo para despejar cualquier área en la que tengas dudas. Si aún tienes dudas, ¡simplemente pregúntame! Muchas gracias por leer.



