martes, septiembre 09, 2014

Creando un Servicio Web con PHP

Descargar Codigo Fuente

Introducción

Hoy, intentaremos escribir una funcionalidad de servicios web y también la forma de consumirla mediante código PHP. Básicamente, se trata de un nivel de servicio web de principiante pero podemos ver el nivel de sencillez que resulta para a futuro poder utilizarlo.

Contenido

  1. Que es SOAP?
  2. Porque SOAP?
  3. Creando un metodo para el web services.
  4. Consumiendo un web service(Cliente)

Desarrollo:

Que es SOAP?

SOAP iesta basado en XML lo que se considera de lectura humano. Es un protocolo para acceder a un servicio Web. Se trata de un sencillo protocolo basado en XML para permitir el intercambio de requerimientos de información a través del protocolo HTTP

Porque SOAP?

Para el caso, se requiere de tantas aplicaciones para la comunicación de datos entre los sistemas, una form es por medio de llamadas a procedimiento remoto (RPC) entre objetos como DCOM y CORBA pero HTTP no fue diseñado para esto.
  • RPC representa compatibilitibidad
  • La seguridad es un problema
  • Los servidores Firewalls y proxy, normalmente bloquean el trafico.
Una mejor manera de comunicarse entre las aplicaciones es a través de HTTP, HTTP es compatible con todos los navegadores y servidores de Internet. Por eso es preferible servicio SOAP.

Podemos colaborar con otros programadores de la construcción de gran tamaño y aplicaciones complejas en múltiples plataformas.

Utilizando el Codigo

Aquí, vamos a crear un servicio web utilizando el código PHP, para ello seguiremos los siguientes pasos:
Paso 1: Se debe descargar la libreria de esta URL http://sourceforge.net/projects/nusoap/.
Paso 2: Instalar WAMP/XAMPP en ambientes Windows o en Linux instalar el servidor Apache con PHP, luego ubicar para Windows www/httpdoc, en Linux en var/www/html.
Paso 3: Crear una carpeta llamada "serviciowebsoap"  segun el Paso 2.
Paso 4: Copiar en la carpeta "lib" lo que se descargo en el Paso 1, dentro de la libreria Paso 3.
Paso 5: Crear dos archivos "server.php" y "client.php" en la carpeta creada en el Paso 3.
Paso: Para el "server.php", escribir el sigiente codigo:
<?php
//Invocar a la libreria 
require_once ('lib/nusoap.php'); 
//uitilizando SOAP_SERVER para crear un objeto server 
$server = new soap_server; 

//registrr la function que trabajo en el servidor
$server->register('get_message'); 

// crear la funccion 
function get_message($your_name) 
{ 
if(!$your_name){ 
return new soap_fault('Client','','Digite su Nombre:!'); 
} 
$result = "Bienvenido a ".$your_name .". Gracias por utilizar el primer servicio web  en PHP con SOAP"; 
return $result; 
} 
// crear HTTP oyente
$server->service($HTTP_RAW_POST_DATA); 
exit(); 
?> 
 
Paso 7: Despues de haber creado el archivo "server.php" , vamos a crear ahora el archivo que podamos probar su funcionalidad. En el navegador escribirmos los siguiente http://localhost/serviciowebsoap/server.php?wsdl.
Paso 8: Crear el archivo cliente o consumidor "client.php" en la carpeta serviciowebsoap. Digitamos lo sigueinte:
<?php 
require_once ('lib/nusoap.php'); 
// Reuqerimos el valor del parametro
$param = array( 'your_name' => 'Jose Candelario'); 
// Crear un objeto que haga referencia a los servicios web
$client = new soapclient('http://localhost/serviciowebsoap/server.php'); 
// Invocar la funcion del servidor y enviar parametros
$response = $client->call('get_message',$param); 
//Process result 
if($client->fault) 
{ 
echo "FAULT: <p>Code: (".$client->faultcode."</p>"; 
echo "String: ".$client->faultstring; 
} 
else 
{ 
echo $response; 
} 
?> 
 
Paso 9: Respaldar todo los archivoss, seguidamente en el Navegador digite lo siguiente http://localhost/serviciowebsoap/client.php.
Se visualizara el siguiente resultado: 
Bienvenido a JOSE CANELARIO. Gracias por utilizar el primer servicio web utilizando  PHP con SOAP.

Como ven la creación y utilizacion de servicios web con PHP resulta bastante sencillos.

sábado, septiembre 06, 2014

Las reglas de Codd para bases de datos relacionales

 
database.jpg
Ted Codd presentó en 1985 en un artículo publicado en Computerworld las doce reglas que una base de datos debe obedecer para ser considerada verdaderamente cómo relacional. Estás reglas se han convertido desde entonces en una definición semioficial de una base de datos relacional. Las citadas reglas son las siguientes...
Regla de información: toda la información de una base de datos relacional está representada explícitamente a nivel lógico y exactamente de un modo mediante valores en tablas.
Regla de acceso garantizado: todos y cada uno de los datos (valores atómicos) de una base de datos relacional se garantiza que sean lógicamente accesibles recurriendo a una combinación de nombre de tabla, valor de clave primaria y nombre de columna.
Tratamiento sistemático de valores nulos: los valores nulos (distintos de la cadena de caracteres vacía o de una cadena de caracteres en blanco y distinta del cero o de cualquier otro número) se soportan en los DBMS completamente relacionales para representar la falta de información y la información inaplicable de un modo sistemático e independiente del tipo de datos.
Catálogo en línea dinámico basado en el modelo relacional: la descripción de la base de datos se presenta a nivel lógico del mismo modo que los datos ordinarios, de modo que los usuarios autorizados puedan aplicar a su interrogación el mismo lenguaje relacional que aplican a los datos regulares.
Regla de sublenguaje completo de datos: un sistema relacional puede soportar varios lenguajes y varios modos de usó terminal (por ejemplo, el modo de rellenar con blancos). Sin embargo, debe haber al menos un lenguaje cuyas sentencias sean expresables, mediante alguna sintaxis bien definida, cómo cadenas de caracteres, y que sea completa en cuanto al soporte de todos los puntos siguientes:
  • Definición de datos.
  • Definición de vista.
  • Manipulación de datos (interactiva y por programa).
  • Restricciones de integridad.
  • Autorización.
  • Fronteras de transacciones (comienzo, cumplimiento y vuelta atrás).
Regla de actualización de vista: todas las vistas que sean teóricas actualizables son también actualizables por el sistema.
Inserción, actualización y supresión de alto nivel: la capacidad de manejar una relación de base de datos o una relación derivada cómo un único operando se aplica no sólo a la recuperación de datos, sino también a la inserción, actualización y supresión de los datos.
Independencia física de los datos: los programas de aplicación y las actividades terminales permanecen lógicamente inalterados cualquiera que sean los cambios efectuados ya sea a las representaciones de almacenamiento o a los métodos de acceso.
Independencia lógica de los datos: los programas de aplicación y las actividades terminales permanecen lógicamente inalterados cuándo se efectúen sobre las tablas de base cambios preservadores de la información de cualquier tipo que teóricamente permita alteraciones.
Independencia de integridad: las restricciones de integridad específicas para una base de datos relacional particular deben ser definibles en el sublenguaje de datos relacional y almacenables en el catálogo, no en los programas de aplicación.
Independencia de distribución: un DBMS relacional tiene independencia de distribución.
Regla de no subversión: si un sistema tiene un lenguaje de bajo nivel (un sólo registro cada vez), ese bajo nivel no puede ser utilizado para subvertir o suprimir las reglas de integridad y las restricciones expresadas en el lenguaje relacional de nivel superior (múltiples registros a la vez).

martes, agosto 26, 2014

Códigos de estado de HTTP

Los códigos de estado 1XX
Son quienes informan al navegador de algunas acciones que se van a realizar:
100 (Continue): el navegador puede continuar realizando su petición.
101 (Switching Protocols): el servidor acepta el cambio de protocolo propuesto por el mismo navegador, un ejemplo sería un cambio de HTTP 1.0 a HTTP 1.1
102 (Processing (WebDAV)): aquí el servidor está procesando la petición del navegador pero aun no ha terminado (evitando que el navegador piense que la petición se ha perdido cuando no es recibida ninguna respuesta).
103 (Checkpoint): se va a reanudar una petición POST o PUT que fue abortada.
Los códigos de estado 2XX

Nos indican que la petición del navegador se ha recibido, procesado y respondido correctamente:
200 (Ok): indica que la petición del navegador se ha completado con éxito.
201 (Created): indica que la petición del navegador se ha completado con éxito y como resultado, se ha creado un nuevo recurso.
202 (Accepted): aquí la petición del servidor se ha aceptado y se está procesando en estos momentos, por lo que todavía no hay respuesta alguna.
203 (Non-Authoritative Information): la petición se ha completado con éxito, pero su contenido no se ha obtenido de la fuente originalmente solicitada, más bien de otro servidor.
204 (No Content): en este caso la petición se ha completado con éxito pero su respuesta no tienen ningún contenido.
205 (Reset Content): la petición se ha completado con éxito, pero su respuesta no tiene contenidos y además, el navegador tiene que inicializar la página desde la que se realizó la petición.
206 (Partial Content): la respuesta de esta petición sólo tiene parte de los contenidos, tal y como lo solicitó el mismo navegador.
 207 (Multi-Status (WebDAV)): la respuesta consiste en un archivo XML que tiene en su interior varias respuestas y diferentes.
208 (Already Reported (WebDAV)): aquí el listado de elementos DAV ya se notificó previamente.

Los códigos de estado 3XX
Son los que nos indican que el navegador debe realizar alguna acción adicional para que la petición se complete.
300 (Multiple Choices): existe más de una variante para el recurso solicitado por el mismo navegador.
301 (Moved Permanently): aquí el recurso solicitado por el navegador se encuentra en otro lugar y este cambio es permanente (este recurso es muy bueno para sitios SEO).
302 (Moved Temporarily): el recurso solicitado por el navegador se encuentra en otro lugar, aunque sólo por un tiempo limitado. El mismo navegador es redirigido en forma automática a la nueva localización de ese recurso.
303 (See Other): el recurso por el navegador se encuentra en otro lugar. Así, el servidor no redirige automáticamente al navegador, pero le indica que la nueval URI en la que puede obtener el recurso.
304 (Not Modified): en este caso cuando el navegador pregunta si un recurso ha cambiado desde la última vez que se solicitó el servidor responde con este código cuando el recurso no ha cambiado.
305 (Use Proxy): el recurso solicitado por el navegador debe obtenerse por medio del proxy cuya dirección en la cabecera Location de esta misma respuesta.
306 (Switch Proxy): este código lo utilizamos en las versiones que son antiguas de HTTP pero ya no se usa.
307 (Temporary Redirect): el recurso solicitado por el navegador se puede obtener de un lugar diferente, pero sólo para esta petición. Las próximas peticiones pueden seguir utilizando la localización original del recurso.
308 (Permanent Redirect): el recurso solicitado por el mismo navegador se encuentra en otro lugar y este cambio es permanente. Pero a diferencia del código 301, no se permite cambiar el método  HTTP para la nueva petición.
Los códigos de estado 4XX
Nos indican que se ha producido un error cuyo responsable es el navegador:
400 (Bad Request): aquí el servidor no es capaz de entender la petición del navegador porque su sintaxis no es correcta.
401 (Unauthorized): el recurso solicitado por el navegador requiere de autenticación. La respuesta incluye una cabecera de tipo WWW-Authenticate para que el navegador pueda iniciar el proceso de autenticación.
402 (Payment Required): este código está reservado para usos futuros.
403 (Forbidden): la petición del navegador es correcta, pero el servidor no puede responder con el recurso solicitado porque se ha denegado el acceso.
404 (Not Found): el servidor no puede encontrar el recurso solicitado por el navegador y no es posible determinar si esta ausencia es temporal o permanente.
405 (Method Not Allowed): el navegador ha utilizado un método (GET, POST, etc.) no permitido por el servidor para obtener ese recurso.
- See more at: http://www.codejobs.biz/es/blog/2014/07/10/codigos-de-estado-de-http#sthash.quLXkzXV.dpufgggggggggggfgdsgdfsgds
Los códigos de estado 1XX
Son quienes informan al navegador de algunas acciones que se van a realizar:
100 (Continue): el navegador puede continuar realizando su petición.
101 (Switching Protocols): el servidor acepta el cambio de protocolo propuesto por el mismo navegador, un ejemplo sería un cambio de HTTP 1.0 a HTTP 1.1
102 (Processing (WebDAV)): aquí el servidor está procesando la petición del navegador pero aun no ha terminado (evitando que el navegador piense que la petición se ha perdido cuando no es recibida ninguna respuesta).
103 (Checkpoint): se va a reanudar una petición POST o PUT que fue abortada.
Los códigos de estado 2XX

Nos indican que la petición del navegador se ha recibido, procesado y respondido correctamente:
200 (Ok): indica que la petición del navegador se ha completado con éxito.
201 (Created): indica que la petición del navegador se ha completado con éxito y como resultado, se ha creado un nuevo recurso.
202 (Accepted): aquí la petición del servidor se ha aceptado y se está procesando en estos momentos, por lo que todavía no hay respuesta alguna.
203 (Non-Authoritative Information): la petición se ha completado con éxito, pero su contenido no se ha obtenido de la fuente originalmente solicitada, más bien de otro servidor.
204 (No Content): en este caso la petición se ha completado con éxito pero su respuesta no tienen ningún contenido.
205 (Reset Content): la petición se ha completado con éxito, pero su respuesta no tiene contenidos y además, el navegador tiene que inicializar la página desde la que se realizó la petición.
206 (Partial Content): la respuesta de esta petición sólo tiene parte de los contenidos, tal y como lo solicitó el mismo navegador.
 207 (Multi-Status (WebDAV)): la respuesta consiste en un archivo XML que tiene en su interior varias respuestas y diferentes.
208 (Already Reported (WebDAV)): aquí el listado de elementos DAV ya se notificó previamente.

Los códigos de estado 3XX
Son los que nos indican que el navegador debe realizar alguna acción adicional para que la petición se complete.
300 (Multiple Choices): existe más de una variante para el recurso solicitado por el mismo navegador.
301 (Moved Permanently): aquí el recurso solicitado por el navegador se encuentra en otro lugar y este cambio es permanente (este recurso es muy bueno para sitios SEO).
302 (Moved Temporarily): el recurso solicitado por el navegador se encuentra en otro lugar, aunque sólo por un tiempo limitado. El mismo navegador es redirigido en forma automática a la nueva localización de ese recurso.
303 (See Other): el recurso por el navegador se encuentra en otro lugar. Así, el servidor no redirige automáticamente al navegador, pero le indica que la nueval URI en la que puede obtener el recurso.
304 (Not Modified): en este caso cuando el navegador pregunta si un recurso ha cambiado desde la última vez que se solicitó el servidor responde con este código cuando el recurso no ha cambiado.
305 (Use Proxy): el recurso solicitado por el navegador debe obtenerse por medio del proxy cuya dirección en la cabecera Location de esta misma respuesta.
306 (Switch Proxy): este código lo utilizamos en las versiones que son antiguas de HTTP pero ya no se usa.
307 (Temporary Redirect): el recurso solicitado por el navegador se puede obtener de un lugar diferente, pero sólo para esta petición. Las próximas peticiones pueden seguir utilizando la localización original del recurso.
308 (Permanent Redirect): el recurso solicitado por el mismo navegador se encuentra en otro lugar y este cambio es permanente. Pero a diferencia del código 301, no se permite cambiar el método  HTTP para la nueva petición.
Los códigos de estado 4XX
Nos indican que se ha producido un error cuyo responsable es el navegador:
400 (Bad Request): aquí el servidor no es capaz de entender la petición del navegador porque su sintaxis no es correcta.
401 (Unauthorized): el recurso solicitado por el navegador requiere de autenticación. La respuesta incluye una cabecera de tipo WWW-Authenticate para que el navegador pueda iniciar el proceso de autenticación.
402 (Payment Required): este código está reservado para usos futuros.
403 (Forbidden): la petición del navegador es correcta, pero el servidor no puede responder con el recurso solicitado porque se ha denegado el acceso.
404 (Not Found): el servidor no puede encontrar el recurso solicitado por el navegador y no es posible determinar si esta ausencia es temporal o permanente.
405 (Method Not Allowed): el navegador ha utilizado un método (GET, POST, etc.) no permitido por el servidor para obtener ese recurso.
- See more at: http://www.codejobs.biz/es/blog/2014/07/10/codigos-de-estado-de-http#sthash.quLXkzXV.dpuf
Los códigos de estado 1XX
Son quienes informan al navegador de algunas acciones que se van a realizar:
100 (Continue): el navegador puede continuar realizando su petición.
101 (Switching Protocols): el servidor acepta el cambio de protocolo propuesto por el mismo navegador, un ejemplo sería un cambio de HTTP 1.0 a HTTP 1.1
102 (Processing (WebDAV)): aquí el servidor está procesando la petición del navegador pero aun no ha terminado (evitando que el navegador piense que la petición se ha perdido cuando no es recibida ninguna respuesta).
103 (Checkpoint): se va a reanudar una petición POST o PUT que fue abortada.
Los códigos de estado 2XX

Nos indican que la petición del navegador se ha recibido, procesado y respondido correctamente:
200 (Ok): indica que la petición del navegador se ha completado con éxito.
201 (Created): indica que la petición del navegador se ha completado con éxito y como resultado, se ha creado un nuevo recurso.
202 (Accepted): aquí la petición del servidor se ha aceptado y se está procesando en estos momentos, por lo que todavía no hay respuesta alguna.
203 (Non-Authoritative Information): la petición se ha completado con éxito, pero su contenido no se ha obtenido de la fuente originalmente solicitada, más bien de otro servidor.
204 (No Content): en este caso la petición se ha completado con éxito pero su respuesta no tienen ningún contenido.
205 (Reset Content): la petición se ha completado con éxito, pero su respuesta no tiene contenidos y además, el navegador tiene que inicializar la página desde la que se realizó la petición.
206 (Partial Content): la respuesta de esta petición sólo tiene parte de los contenidos, tal y como lo solicitó el mismo navegador.
 207 (Multi-Status (WebDAV)): la respuesta consiste en un archivo XML que tiene en su interior varias respuestas y diferentes.
208 (Already Reported (WebDAV)): aquí el listado de elementos DAV ya se notificó previamente.

Los códigos de estado 3XX
Son los que nos indican que el navegador debe realizar alguna acción adicional para que la petición se complete.
300 (Multiple Choices): existe más de una variante para el recurso solicitado por el mismo navegador.
301 (Moved Permanently): aquí el recurso solicitado por el navegador se encuentra en otro lugar y este cambio es permanente (este recurso es muy bueno para sitios SEO).
302 (Moved Temporarily): el recurso solicitado por el navegador se encuentra en otro lugar, aunque sólo por un tiempo limitado. El mismo navegador es redirigido en forma automática a la nueva localización de ese recurso.
303 (See Other): el recurso por el navegador se encuentra en otro lugar. Así, el servidor no redirige automáticamente al navegador, pero le indica que la nueval URI en la que puede obtener el recurso.
304 (Not Modified): en este caso cuando el navegador pregunta si un recurso ha cambiado desde la última vez que se solicitó el servidor responde con este código cuando el recurso no ha cambiado.
305 (Use Proxy): el recurso solicitado por el navegador debe obtenerse por medio del proxy cuya dirección en la cabecera Location de esta misma respuesta.
306 (Switch Proxy): este código lo utilizamos en las versiones que son antiguas de HTTP pero ya no se usa.
307 (Temporary Redirect): el recurso solicitado por el navegador se puede obtener de un lugar diferente, pero sólo para esta petición. Las próximas peticiones pueden seguir utilizando la localización original del recurso.
308 (Permanent Redirect): el recurso solicitado por el mismo navegador se encuentra en otro lugar y este cambio es permanente. Pero a diferencia del código 301, no se permite cambiar el método  HTTP para la nueva petición.
Los códigos de estado 4XX
Nos indican que se ha producido un error cuyo responsable es el navegador:
400 (Bad Request): aquí el servidor no es capaz de entender la petición del navegador porque su sintaxis no es correcta.
401 (Unauthorized): el recurso solicitado por el navegador requiere de autenticación. La respuesta incluye una cabecera de tipo WWW-Authenticate para que el navegador pueda iniciar el proceso de autenticación.
402 (Payment Required): este código está reservado para usos futuros.
403 (Forbidden): la petición del navegador es correcta, pero el servidor no puede responder con el recurso solicitado porque se ha denegado el acceso.
404 (Not Found): el servidor no puede encontrar el recurso solicitado por el navegador y no es posible determinar si esta ausencia es temporal o permanente.
405 (Method Not Allowed): el navegador ha utilizado un método (GET, POST, etc.) no permitido por el servidor para obtener ese recurso.
- See more at: http://www.codejobs.biz/es/blog/2014/07/10/codigos-de-estado-de-http#sthash.quLXkzXV.dpuf

jueves, agosto 21, 2014

Genexus - DownLoad desde campos Blob

Se tiene una tabla con archivos PDF almacenados en un campo blob, se requiere hacer la descarga desde estos campos blob.
 
Para el ejemplo se tiene la siguiente estructura de la tabla:
 
Nombre de la Tabla: reportespdf
   ReportesPdfSec                       N(5)    * Autonumerico
   ReportesPdfNombre               C(40)
   ReportesPdfArchivo               Blob;
 
Para ello es necesario crear un WebPanel con una grilla donde se despliegue los registros que contiene dicha tabla.
Seguidamente cada la fila de grilla permitirá hacer un link al procedimiento para descargar el archivo PDF.
 
En el Evento Load, debe tener lo siguiente:
Event Load
&DescargarPdf.Link = DESCARGASPDF.Link(ReportesPdfSec)
EndEvent

 
El procedimiento para descargar es el siguiente:
Setear sus propiedades:
     MAIN PROGRAM = TRUE
     CALL PROTOCOL = HTTP.
 
Crear las variables &HttpResponse, &ReportePdfSec 
 
Reglas:
parm(In: &ReportePdfSec);
 
Source: 
&HttpResponse.AddHeader(‘Content-type’, ‘application/download’)

// Leer Archivo de Reportes PDF 
For Each ReportesPdfSec 
       Where ReportesPdfSec = &ReportesPdfSec 
        Defined By  ReportesPdfNombre 
        &HttpResponse.AddHeader(‘Content-Disposition’, ‘attachment; filename=’ + ReportesPdfNombre)
&HttpResponse.AddFile(ReportesPdfArchivo)
EndFor
Return

miércoles, agosto 13, 2014

Ejecutar un programa al momento de iniciar Tomcat

En el WEB:XML, debe constar lo siguiente:

<servlet>
    <servlet-name>programaAutomatico</servlet-name>    // Nombre del Programa a Ejecutarse
    <servlet-class>nombre.paquete</servlet-class>          // Nombre del Paquete
    <load-on-startup>1</load-on-startup>
</servlet>
 
 
Programa en Java
 
package nombre.paquete;
 
import javax.servlet.*;
import javax.servlet.http.HttpServlet;
 

@SuppressWarnings("serial")
public class programaAutomatico extends HttpServlet
{
 
    public void init() throws ServletException
    {
          System.out.println("----------");
          System.out.println("---------- Programa Automatico Ejecutado Correctamente ----------");
          System.out.println("----------");
    }
}
 

miércoles, junio 04, 2014

Envio de KB al GxServer - An Error Occurred While Importing File 'ATTRIBUTE'

Cuando se desplegue el siguiente error:

GeneXus Server: An Error Occurred While Importing File 'ATTRIBUTE' - SAC  # 28306 

GeneXus Server: bcp utility not found. Please install 'Microsoft SQL Server Command Line Utilities'
SAC  # 28443


Se debe principalmente que el programa BCP.EXE para 64 Bits no esta disponible, pero sin embargo el GXServer quiere ejecutarle.

Se debe instalar el programa: SqlCmdLnUtils.msi

<appSettings>
    <!--add key="GX" value="C:\Program Files\Artech\GeneXus\GeneXusX"/-->
    <add key="validateKB" value="true" />
    <add key="available" value="true" />
    <add key="message" value="" />
    <!-- for security set following key to true -->
    <add key="secure" value="true" />
    <add key="SqlTimeout" value="190" />
    <add key="AppMainNamespace" value="GeneXus.Programs" />
    <add key="DataStore1" value="Default" />
    <add key="DataStore-Count" value="1" />
    <add key="Connection-Default-DBMS" value="sqlserver" />
    <add key="Connection-Default-Port" value="" />
    <add key="Connection-Default-LockTimeout" value="0" />
    <add key="Connection-Default-LockRetryCount" value="10" />
    <add key="Connection-Default-Datasource" value="82sPNmMP8O3TlpdEZk1r8E==" />
    <add key="Connection-Default-User" value="82sPNmMP8O3TlpdEZk1r8E==" />
    <add key="Connection-Default-Password" value="82sPNmMP8O3TlpdEZk1r8E==" />
    <add key="Connection-Default-DB" value="" />
    <add key="Connection-Default-Opts" value=";Integrated Security=yes;" />
    <add key="Connection-Default-TrnInt" value="1" />
    <add key="DateFormat" value="MDY" />
    <add key="YearLimit" value="1940" />
    <add key="TimeAmPmFormat" value="12" />
    <add key="VER_STAMP" value="20081128.160526" />
    <add key="CS_BLOB_PATH" value="PublicTempStorage" />
    <add key="TMPMEDIA_DIR" value="PrivateTempStorage" />
    <add key="PRINT_LAYOUT_METADATA_DIR" value="LayoutMetadata" />
    <add key="StorageTimeZone" value="1" />
    <add key="LOGIN_AS_USERID" value="0" />
    <add key="LANGUAGE" value="eng" />
    <add key="LANG_NAME" value="English" />
    <add key="DECIMAL_POINT" value="." />
    <add key="DATE_FMT" value="MDY" />
    <add key="Culture" value="en-US" />
    <add key="Theme" value="IDE_WEB" />
    <add key="EnableIntegratedSecurity" value="0" />
    <add key="MAX_CURSOR" value="100" />
    <add key="ISOLATION_LEVEL" value="CR" />
    <add key="STATIC_CONTENT" value="" />
    <add key="GX_BUILD_NUMBER" value="80183" />
    <add key="CACHE_CONTENT_EXPIRATION" value="36" />
    <add key="PROVIDER" value="Microsoft" />
    <add key="ENABLE_MANAGEMENT" value="0" />
    <add key="COMPRESS_HTML" value="1" />
    <add key="IE_COMPATIBILITY_VIEW" value="EmulateIE7" />
    <add key="DocumentType" value="None" />

    <add key="bcpPath" value="C:\Program Files (x86)\Microsoft SQL Server\110\Tools\Binn\bcp.exe" />
  </appSettings>

Reinicia el IIS
Espera unos 30 segundos
Intentar pasar la KB de nuevo.


martes, junio 03, 2014

Sistema de ficheros en Linux


En Linux, el sistema de ficheros es bastante distinto al que se utiliza en Windows, de manera que la organización, ejecución y permisología de los archivos se hace de manera distinta. La principal ventaja es que en los archivos Linux, el usuario que crea los archivos puede gestionarlos como desee, ya que son de su propiedad, es decir, el mismo usuario puede decidir quien los ve, los lee y quien los ejecuta. En este artículo, veremos cómo se organizan los ficheros en Linux, cómo se visualizan y cómo cambiar los permisos de los ficheros.
Sistema de ficheros en Linux

Sistema de archivos

Un sistema de ficheros es una organización lógica de un dispositivo físico, que nos permite almacenar y recuperar cualquier tipo de información que deseemos en forma de fichero. Existen diferentes tipos de sistemas de ficheros, y en Linux los sistemas nativos son ext2 y ext3. Estos sistemas organizan los archivos en una especie de súper bloque con una cantidad de nodos específica y un área de datos extensa, que se asemeja a la organización de las ramas de un árbol.
Una de las principales ventajas de los sistemas de ficheros Linux, es que pueden realizarse tareas multiusuario, lo que permite al creador de los archivos adicionarles características específicas, así como también, ejercer propiedad sobre cada archivo creado o directorio.
Sistema de ficheros en Linux

Visualizando archivos en Linux

Los archivos en Linux son propiedad de quien los crea, este es el único usuario autorizado a eliminarlos (a excepción del administrador o root). Por supuesto, cuentan con una serie de permisos predeterminados, que puede cambiar el usuario creador a voluntad para, de esta manera, compartir los archivos con otros usuarios del sistema. Para visualizar estos archivos con todas sus características, debemos ejecutar la consola de Linux y escribir el siguiente comando: “ls –la”, desplegándose en la pantalla una lista de archivos con su permisología y características bien detalladas.
ficheros en Linux
Podemos notar como en la figura aparecen los atributos del archivo con una nomenclatura similar a “-rw-r–r—“ donde el primer espacio equivale a los directorios (d), en el caso de que sea un solo archivo queda con una barra “-“. Los tres espacios siguientes, son los permisos del creador r (lectura), w (escritura) y x (ejecución), vemos que en el ejemplo, el usuario creador sólo tiene permisos de lectura y escritura. El segundo bloque de tres espacios, indica los usuarios de un grupo y el último lote de tres, para todos los demás usuarios del sistema. Los permisos de lectura, escritura y ejecución, se indican de manera similar al del usuario creador, en este caso se otorga sólo lectura a los demás usuarios.
También, podemos observar que el nombre del usuario que aparece delante de users en las características del archivo, nos indica que ese usuario es el dueño del archivo. Por ejemplo, tutorial users, nos indica que el archivo es propiedad del usuario tutorial.
Ficheros en Linux

Cambiando los permisos de los archivos

Una vez que hemos aprendido a identificar y visualizar los atributos y permisos de los archivos en la línea de comandos de Linux, veremos la manera correcta de cambiar los permisos de los archivos que hemos creado, para ello, debemos utilizar la línea de comandos.
En este caso específico, vamos a utilizar el comando “chmod” que se encarga de cambiar los atributos de los archivos dentro del sistema. Debemos recordar, que utilizaremos las siguientes letras para indicar a que tipo de usuario le vamos a cambiar los permisos de acceso al fichero. Por ejemplo, utilizaremos la letra u para el usuario creador, la letra g para un grupo de usuarios, la letra o para los demás usuarios del sistema, y la letra a para todos los usuarios. De manera, que podamos cambiar los permisos a nuestros requerimientos, recordemos que solo el creador del archivo y root puede cambiar los permisos.
Permisología de archivos Linux
La estructura de cambio de permisos es la siguiente: “chmod usuario_o_grupo_de_usuarios_a_cambiar_el_permiso[+/-]permiso_a_otorgar_o_quitar archivo_a_modificar”. En donde, [+/-] dependerá de si deseamos quitar el permiso (-) o adicionarlo (+), y los permisos son: r (lectura), w (escritura), x (ejecución). A continuación, veremos un ejemplo explicativo:
El usuario llamado tux, desea cambiar los permisos de creación a lectura, escritura y ejecución para todos los usuarios, sólo debe escribir:
$ chmod a+rwx archivo.txt
El archivo de texto plano archive.txt podrá ser leído, cambiado y ejecutado por cualquier usuario del sistema, de este modo, podemos ver como los creadores de los archivos en sistema Linux, pueden otorgar diferentes permisos a los demás usuarios sobre sus propios archivos, realizando combinaciones de órdenes con el comando chmod.
Permiso de archivos en Linux
Los usuarios Linux, también pueden cambiar la propiedad de su archivo y otorgarla a otro usuario del mismo sistema, utilizando el comando chown, como podemos ver a continuación:
$ chown –c luis archivo.txt
Ahora, el archivo de texto plano pertenece al usuario luis, siendo este el único que podrá modificar sus atributos en el futuro.

Tomado de: http://culturacion.com/2012/07/sistema-de-ficheros-en-linux/

Cómo compartir archivos de gran tamaño por internet


Muchas veces, cuando deseamos enviar datos utilizamos el correo electrónico. Pero en oportunidades diferentes, nos hemos dado cuenta, que al tratar de adjuntar archivos de gran tamaño, se genera un error, ya que estos sistemas de documentación, por lo general, sólo permiten almacenar hasta 25 MB en cada mensaje de e-mail. Compartiendo archivos grandes por internetAfortunadamente, existen otras alternativas para compartir archivos de gran tamaño en internet y eso es lo que veremos en este artículo.

¿Qué servicio se utiliza para compartir archivos grandes? 

En la actualidad, existen muchos servicios en internet que nos permiten compartir archivos de gran tamaño, es decir, archivos que sobrepasen los 25 Mb de tamaño. Cada uno de ellos, cuenta con ventajas y desventajas. En este sentido, es decisión del usuario, elegir el que más le convenga, según su nivel de actividad o según la cantidad de espacio que requiera para compartir archivos.

Entre los servicios más utilizados para este tipo de tareas se encuentran los siguientes: Dropbox, WeTransfer, FTP, Google Drive, PipeBites. Sin embargo, existe un servicio que se encuentra 100% en idioma español y es muy simple de utilizar. Este servicio, lleva por nombre Ydray y es ideal para compartir archivos de gran tamaño.
Ydray es una opción desarrollada por programadores Españoles, lo que orienta su uso a usuarios de habla Hispana. Su funcionamiento es veloz y fluido y no coloca ningún tipo de restricciones en cuanto al tamaño de los archivos que vamos a enviar o recibir por internet.

Enviando archivos de gran tamaño con Ydray 

Una vez que tengamos una cuenta en Ydray, abrimos nuestra sesión y seleccionamos el servicio de envío de archivos, debemos introducir nuestros datos (nuestro correo y el correo del destinatario), como lo vemos en la figura.
Ingresando datos a ydray
Inmediatamente, se abrirá la ventana de selección de archivos y los seleccionamos desde su origen. Tenemos que destacar que Ydray no restringe tamaño alguno de archivo, por lo tanto, tendrás libertad de seleccionar cualquier archivo.
Adjuntando archivos a Ydray
Ahora es momento de finalizar todo este procedimiento. Para ello, sólo debemos hacer clic sobre el botón de transferir y listo, el destinatario recibirá los archivos sin inconveniente alguno. Como podemos ver, el proceso es sumamente rápido y sin complicaciones, lo que lo hace ideal para enviar todo tipo de información, ya sea corporativa, personal o educativa. De manera, que ya no tendrás excusas para no enviar el informe que tienes que presentar, o para dejar por fuera un video o una lista de reproducción grande en una presentación, ya que ahora cuentas con Ydray.
FInalizando el envio de archivos d egran tamaño

Navegadores de Linea de Comandos.


Lynx

Lynx es quizás el programa de navegación por línea de comandos más extendido de todo el mundo. Se trata de un programa que se instala en el sistema y al cual podemos acceder mediante un simple comando y que nos permite visualizar por pantalla en modo texto toda la información que estamos buscando. Podemos conectarnos a cualquier buscador web e incluso, ingresar a cualquier cuenta de corro electrónico.
Una de las características de Lynx, es que es multiplataforma y sus mandos de operación son bastante simples.

eLinks

Este programa (eLinks) de navegación web, a pesar de ser hecho para trabajar bajo la terminal de Linux, es bastante simple de utilizar. Cuenta con una serie de opciones bastante amigables y permite realizar descargas completas desde cualquier sitio web.
eLinks
Figura 4: Navegando con eLinks

W3M

Este sencillo navegador web está basado en FM y cuenta con un extraordinario sistema de seguridad (sistema SSL), que lo hace destacar entre sus rivales más cercanos. Si bien, W3M no es tan completo como Lynx, cuenta con una serie de herramientas de navegación, como sistema de descarga completa, y posibilidad de acceder a cualquier sitio web de forma simple.

Línea de comandos
Crear un procedimiento Main como línea de comandos, ejecutarlo programadamente desde el sistema operativo (tareas programadas o CRON).
Pros:
* Si da excepción vuelve a ejecutar
* La configuración de frecuencia es sencilla
Cons:
* Requiere configurar en el host, no está embebido en la aplicación
* Hay que configurar el main de java y el classpath a mano y mantenerlo actualizado si se agregan o cambian jars
* Si el proceso se queda bloqueado por algún error, se iran acumulando ejecuciones cada vez que el SO inicie la tarea.


Main HTTP

Crear un procedimiento Main como HTTP, ejecutarlo programadamente desde el sistema operativo (tareas programadas o CRON) utilizando un navegador de línea de comandos (wget, curl).
Pros:
* Si da excepción vuelve a ejecutar
* La configuración de frecuencia es sencilla
* No hay que configurar java ni classpath
Cons:
* Requiere configurar en el host, no está embebido en la aplicación
* Requiere un browser de línea de comandos (sencillo)
* Si el proceso se queda bloqueado por algún error, se iran acumulando ejecuciones cada vez que el SO inicie la tarea. 


Programador de tareas Java (ej: Quartz)
Crear un procedimiento Main como HTTP, ejecutarlo programadamente desde el sistema operativo (tareas programadas o CRON) utilizando un navegador de línea de comandos (wget, curl).
Pros:
* Si da excepción vuelve a ejecutar
* Es más fácil recuperar información de si una tarea quedó bloqueada
* No hay que configurar java ni classpath
* Está embebido en la aplicación (no requiere configuración en el host)
Cons:
* Configuración más complicada, suele requerir código java



ServletContextListener
Programar en Java un ServletContextListener que llame en loop infinito a un procedimiento GX.
Pros:
* No hay que configurar java ni classpath
* Está embebido en la aplicación (no requiere configuración en el host)
Cons:
* Requiere código java
* Se deben controlar las excepciones para evitar el que cancele
* Si se quiere flexibilidad en la frecuencia de ejecución, hay que programarlo (ej: leer parámetros)

Gracias a:  pablo@cybernet.com.uy Del Foro de Genexus

martes, mayo 27, 2014

Gestión de Archivos de Texto con Genexus.

Event 'Archivo'
    &Fecha = &Today
    Do 'Formatear_Fecha'
    &ArchivoDeTexto = 'C:\Log_'+&ResultadoFecha.Trim()
    &ArchivoDeTexto = &ArchivoDeTexto.Trim()+'_'+&Time.Substring(1,2)+&Time.Substring(4,2)+&Time.Substring(7,2)
    &ArchivoDeTexto = &ArchivoDeTexto.Trim()+'.txt'

    //Cargo el SDT con los datos necesarios para poder generar el LogFile
    &ArchivoLog.NombreArchivo = &ArchivoDeTexto.Trim()
    For Each
        Defined By telefono
        &LineaLog.Telefono = telefono.Trim()
        &LineaLog.Gestion = gestion
        &LineaLog.PrimerNombre = primernombre
        &LineaLog.PrimerApellido = primerapellido
        &LineaLog.Monto = monto
        &LineaLog.FechaDeIngreso = fechadeingreso
        &ArchivoLog.Lineas.Add(&LineaLog)
        &LineaLog = New LogFile.Lineas()
    EndFor
    &Cant = &ArchivoLog.Lineas.Count

    If &Cant > 0
        Confirm('¿Desea exportar resultados a un Archivo de Texto?')
        If Confirmed()
            &i = dfwopen( &ArchivoDeTexto, ",", '"')
            For &LineaLog In &ArchivoLog.Lineas
                &i = dfwptxt( &LineaLog.Telefono.Trim() )
                &i = dfwpnum( &LineaLog.Gestion, 0 )
                &i = dfwptxt( &LineaLog.PrimerNombre )
                &i = dfwptxt( &LineaLog.PrimerApellido )
                &i = dfwpnum( &LineaLog.Monto, 0 )
                &i = dfwptxt( &LineaLog.FechaDeIngreso.ToString())
                &i = dfwpdate( &LineaLog.FechaDeIngreso, "dmy", "-" )
                &I = dfwnext()
            EndFor
            &i = dfwclose( )
            If &i = 0
                Msg('Archivo de texto generado correctamente!')
            Else
                Msg('Han ocurrido errores al generar el Archivo de texto!')
            EndIf
        Else
            Msg('Paso a XML')
            &xml = &ArchivoLog.ToXml()
            //Grabar en archivo XML
            &filexml.open('C:\CLIENTES.xml')
            &filexml.WriteStartDocument()
            &filexml.WriteStartElement('CLIENTES')
            For Each
                &filexml.WriteStartElement('CLIENTE')
                    &filexml.WriteElement('NOMBRE', primernombre.Trim()  )
                    &filexml.WriteElement('APELLIDO', primerapellido.Trim()  )
                    &filexml.WriteElement('INGRESO', dtoc(fechadeingreso)  )
                &filexml.WriteEndElement()  
            EndFor
            &filexml.WriteEndElement()  
            &filexml.Close()
        EndIf
    EndIF
EndEvent  // 'Archivo'

Sub 'Formatear_Fecha'
    &ResultadoFecha = Trim(Str(&Fecha.Year()))+'-'
    If &Fecha.Month() > 9
        &ResultadoFecha = &ResultadoFecha.Trim()+Trim(Str(&Fecha.Month()))+'-'
    Else
        &ResultadoFecha = &ResultadoFecha.Trim()+'0'+Trim(Str(&Fecha.Month()))+'-'
    EndIf
    If &Fecha.Day() > 9
        &ResultadoFecha = &ResultadoFecha.Trim()+Trim(Str(&Fecha.Day()))
    Else
        &ResultadoFecha = &ResultadoFecha.Trim()+'0'+Trim(Str(&Fecha.Day()))
    EndIf
EndSub

//Consideraciones sobre los archivos ASCII delimitados
//
//Sólo puede estar abierto un archivo ASCII delimitado en un momento específico. La función dfropen retornará un error si se la llama más de una vez sin haber llamado previamente a dfrclose.
//Es posible ignorar los campos del final de un registro al llamar a dfrnext cuando queden campos por leer. No es posible ‘saltearse’ campos. Si se quiere leer el campo número 5 es necesario leer los 4 anteriores.
//Es posible leer registros con diferentes formatos como en el siguiente ejemplo:
//
//&i = dfropen( "invoices.txt", 80, “,”)
//do while dfrnext( ) = 0
//    &i = dfrgtxt( &RecType)
//    if &RecType = “H”
//       &i = dfrgnum( &InvNum )
//       &i = dfrgdate( &InvDat, “ymd”, “/“)
//    else
//       &i = dfrgnum( &PrdNum )
//       &i = dfrgnum( &InvQty)
//    endif
//enddo
//&i = dfrclose( )

Event 'UnDato'
    &Fecha = &Today
    Do 'Formatear_Fecha'
    &i = dfwopen( "C:\Empleados.txt", ",", '"')
    &i = dfwptxt( 'Empleado' )
    &i = dfwpnum( 1, 0 )
    &i = dfwptxt( 'Luis' )
    &i = dfwptxt( 'Rosas' )
    &i = dfwpnum( 1200, 0 )
    &i = dfwptxt( &ResultadoFecha )
    &i = dfwpdate( &Today, "dmy", "-" )
    &i = dfwptxt( &Time )
    &I = dfwnext()
    &i = dfwclose( )
    If &i = 0
        Msg('Archivo de texto generado correctamente!')
    Else
        Msg('Han ocurrido errores al generar el Archivo de texto!')
    EndIf
EndEvent  // 'UnDato'

Gracias a  rosastoriani@gmail.com

lunes, mayo 26, 2014

Print screen utilizando java


  • Importar la Clase descrita mas abajo
  • Utilizar ToolKit para definir el tamaño
  • Objeto a recuperar en imagen debe tener width y height
  • Tine un tiempo de espera de 2 segundos para mejor manejo
  • Un Ejemplo de como especificar ancho y alto
rect = new Rectangle(640,480);

import java.awt.image.BufferedImage;
import java.awt.Rectangle;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.Robot;
import java.io.File;
import javax.imageio.ImageIO;
   



class ScreenRecorder {
public static void main(String args[]) {
   try {
       Toolkit tool = Toolkit.getDefaultToolkit();
       Dimension d = tool.getScreenSize();
       Rectangle rect = new Rectangle(d);
       Robot robot = new Robot();
       Thread.sleep(2000);
       File f = new File("screenshot.jpg");
       BufferedImage img = robot.createScreenCapture(rect);
       ImageIO.write(img,"jpeg",f);
       tool.beep();
       } catch(Exception e){
        e.printStackTrace();
      }
    }
}

domingo, mayo 11, 2014

JavaScript - DOM y CSSOM Capitulo - 3


Capítulo 3: DOM y CSSOM

Luego de haber visto las bases del lenguaje, es momento de conocer más del navegador y aprender a crear y modificar la interfaz de usuario. Existen dos APIs en el navegador que permiten manipular la estructura, contenido y presentación visual de lo que se muestra dentro de un navegador: el Document Object Model, o DOM, y el Cascade Style Sheet Object Model, o CSSOM.

DOM

El Document Object Model, o DOM, es una API para documentos HTML que representa cada elemento de una página web en forma de objetos, permitiendo su manipulación para cambiar tanto la estructura como presentación visual. De igual forma, permite manejar eventos del usuario dentro del navegador.

HTML

HyperText Markup Language, o HTML, es un lenguaje de marcado que permite definir la estructura y contenido de un documento mediante el uso de etiquetas. El proceso de convertir un documento en HTML en una estructura visual es denominado renderizar, y es el motor de renderizado el encargado de realizar esta acción.
Es este motor de renderizado el que, a su vez, se encarga de utilizar las hojas de estilo en cascada (CSS) para darle la presentación adecuada al documento HTML que se está renderizando. Actualmente, los motores de renderizado más populares son:
  • Webkit, utilizado en Safari y Chrome hasta su versión 27.
  • Gecko, utilizado por Firefox y los productos de la Fundación Mozilla.
  • Blink (fork de Webkit), utilizado actualmente por Chrome a partir de su versión 28.
  • Presto, utilizado por Opera, que luego pasó a utilizar Blink.
  • Trident, utilizado principalmente por Internet Explorer y otros productos de Microsoft.

Nodos y Elementos

La abstracción que el DOM realiza de un documento HTML utiliza el concepto de árbol de nodos para representar la estructura de elementos anidados que tiene el documento. Esto quiere decir que un elemento (o nodo en el árbol) puede tener elementos anidados dentro del mismo (denominados nodos hijos); al tener nodos hijos, este elemento automáticamente se convierte en un nodo padre. El primer nodo de un árbol, es decir, aquel que tenga nodos hijos pero no es hijo de ningún otro nodo, es llamado nodo raíz.
Según la tabla de valores de la propiedad nodeType (ver Apéndice A), se pueden ver diferentes tipos de nodos. Al ser el DOM una abstracción basada en árboles de nodos, cada dato dentro de un documento HTML debe pertenecer a un tipo de nodo. Por ejemplo:
<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <div class="empty-div"></div>
  <!-- Comentario dentro de un documento HTML -->
  Contenido de texto
</body>
</html>
nodeTypeTipo de dato en HTML
ELEMENT_NODEElemento (<div class="empty-div"></div>)
ATTRIBUTE_NODEAtributo (class="empty-div")
TEXT_NODETexto (Contenido de texto)
COMMENT_NODEComentario (<!-- Comentario dentro de un documento HTML -->)
DOCUMENT_NODEDocumento (window.document)
DOCUMENT_TYPE_NODEDoctype (<!DOCTYPE html>)
Uno de estos tipos de nodos es el elemento (ELEMENT_NODE), el cual es la representación para toda etiqueta en un documento HTML. Esto quiere decir que todos los elementos son nodos, pero no todos los nodos son elementos.

window y document

El contexto global de una aplicación web recae en window, el cual contiene referencias a diferentes APIs y objetos del navegador, como screennavigatorhistorylocation y document. Cada iframe tiene su propio objeto window, y pueden acceder al window que lo contiene mediante la propiedad parent.
document es el objeto que representa al nodo raíz de un documento HTML, y tiene acceso a los nodos que representan a las etiquetas <head> y <body>. Los iframes tienen su propio documento HTML, por lo que, si un documento tiene iframes, cada elemento iframe puede acceder a su propio documento mediante la propiedadcontentDocument.
Las propiedades y métodos de document están definidas por dos interfaces: HTMLDocument y Document. Adicionalmente, Document hereda de Node. Mientras HTMLDocument y Document le dan a document la capacidad de representar al nodo raíz de un documento, Node añade propiedades y métodos relacionados al manejo de nodos. Tanto window y document heredan de la interfaz EventTarget, por lo que tienen la capacidad de manejar eventos.
El DOM en su forma más abstracta es una interfaz que permite leer y manipular documentos en XML y HTML (incluyendo XHTML) y representarlos como un árbol de nodos; sin embargo, el tipo documento estándar mostrado en un navegador es HTML, el cual tiene propiedades propias y distintas a un documento XML cualquiera. Es por eso que, en el navegador, el DOM tiene dos interfaces para representar a un documento: Document para un documento genérico, y HTMLDocument para un documento HTML.
HTMLDocument tiene propiedades propias de un documento HTML, como: domaintitlebodyforms,anchorslinks e images. Cabe aclarar que anchors y links devuelven listas de elementos de la misma etiqueta (<a>). Esto sucede porque esta etiqueta es usada tanto como ancla dentro del documento como para enlazar el documento actual a otros documentos, imágenes, archivos, etc.
Document tiene 3 propiedades: doctype, que devuelve el tipo de documento (DTD); documentElement, que representa a la etiqueta html; e implementation, que permite crear documentos (HTML o no), así como hojas de estilos. Así mismo, tiene una serie de métodos útiles para la manipulación de elementos dentro del documento, como crear nodos de tipo elemento, comentario, texto y atributo.

Agregando y eliminando nodos

Agregar un nodo en el árbol DOM consta de 2 pasos: Crear el nodo, y añadirlo. El primer paso utiliza un método de document que varía según el tipo de nodo que se desee agregar:
  • Para agregar un nodo elemento se utiliza document.createElement.
  • Para agregar un nodo atributo se utiliza document.createAttribute.
  • Para agregar un nodo texto se utiliza document.createTextNode.
  • Para agregar un nodo comentario se utiliza document.createComment.
Para el segundo paso, el futuro nodo padre debe ejecutar el método appendChild.
Para eliminar un nodo solo es necesario que el nodo padre ejecute el método removeChild.
Simplificando el manejo del DOM con dom.js
Como vimos en el punto anterior, tener que realizar 2 pasos para agregar un nodo al DOM puede llegar a ser tedioso (sobre todo si tenemos que hacerlo varias veces). Adicionalmente, pronto notarás que la API del DOM es verbosa, por lo que sería una buena idea crear una biblioteca que permita reducir el número de palabras escritas y simplifique los pasos para manejar el DOM; así que realizaremos una biblioteca llamada dom.js, la cual usaremos dentro de La Buena Espina.
Empecemos por crear un constructor llamado Dom:
function Dom(selector) {
  this.selector = selector;
  this.elements = document.querySelectorAll(selector);
};
Dentro de esta función utilizamos document.querySelectorAll para poder obtener una lista de elementos a través de un selector CSS. En este caso preferimos utilizar document.querySelectorAll y nodocument.querySelector para darnos la flexibilidad de trabajar con múltiples nodos.
Para poder crear un elemento también necesitamos un método propio, que también nos permita definir sus atributos:
Dom.createElement = function(options) {
  var element = document.createElement(options.tag),
      attributes = Object.keys(options.attributes || {}),
      i = 0;

  for(i; i < attributes.length; i++) {
    element.setAttribute(attributes[i], options.attributes[attributes[i]]);
  }

  return element;
};
Luego de haber creado un elemento, debemos agregarlo a un elemento padre:
Dom.prototype.append = function(newChildElement) {
  var i = 0;

  for (i; i < this.elements.length; i++) {
    this.elements[i].appendChild(newChildElement);
  }

  return this;
};
En el caso anterior, newChildElement debería ser un elemento; pero también debería poder aceptar un objeto similar al pasado en Dom.createElement:
Dom.prototype.append = function(newChildElement) {
  var i = 0;

  if (!(newChildElement instanceof Element)) {
    if (newChildElement.hasOwnProperty('tag')) {
      newChildElement = Dom.createElement(newChildElement);
    }
  }

  for (i; i < this.elements.length; i++) {
    this.elements[i].appendChild(newChildElement);
  }
};
De esta forma, verificamos si el parámetro pasado a Dom.prototype.append es un objeto plano o una instancia de Element.
var nav = new Dom('header nav');
nav.append({
  tag: 'a',
  content: 'Reservaciones',
  attributes: {
    href: '#reservaciones'
  }
});

Recorriendo nodos y elementos

Tanto las interfaces Node como Element tienen propiedades que permiten obtener los nodos (y elementos) hijos de otro nodo, así como obtener los nodos hermanos de un nodo en específico (un nodo hermano es aquel nodo que está al mismo nivel que otro y comparten el mismo nodo padre).
Cada interfaz tiene sus propias propiedades para obtener nodos hijos y nodos hermanos; así, childNodesdevuelve todos los nodos hijos, incluyendo nodos textos, comentarios o elementos; mientras que childrendevuelve todos los nodos hijos que son elementos. La diferencia es más notoria con las propiedadesfirstChild y firstElementChild, o nextSibling y nextElementSibling.
Tanto childNodes como children devuelven una lista viva (también llamada colección viva).

Lista *viva*
Algunas propiedades y métodos del DOM devuelven listas "vivas". Una lista viva es una lista de elementos que automáticamente actualiza su contenido cuando estos cambian en otra parte del programa. Es decir, tanto si se agrega un elemento que concuerde con la lista o si se elimina un elemento que se encuentre en la lista, esta se actualizará con los elementos nuevos o quitando los eliminados posteriormente.

Vamos a extender dom.js para que permita obtener la lista de elementos hijos, que es con la que usualmente se trabaja.
Dom.prototype.children = function() {
  return this.elements[0].children;
};
Este método no es tan útil, ya que nos devuelve una lista nativa que no tendrá los métodos de Dom, por lo que deberíamos envolver esta lista en una instancia de Dom. Para esto vamos a actualizar el constructor:
function Dom(selectorOrElements) {
  if (typeof selectorOrElements === 'string') {
    this.selector = selectorOrElements;
    this.elements = document.querySelectorAll(selectorOrElements);
  }
  else {
    if (selectorOrElements instanceof Node) {   // aprovechamos para verificar si se envolverá un solo elemento o una lista de elementos
      this.elements = [selectorOrElements];
    }
    else {
      this.elements = selectorOrElements;
    }
  }
};
De esta forma, Dom.prototype.children quedará de la siguiente forma:
Dom.prototype.children = function() {
  if (this.elements[0] !== undefined) {
    return new Dom(this.elements[0].children);
  }
  else {
    return Dom.empty([]);
  }
};
Adicionalmente, agregamos una validación simple para saber si existen elementos dentro de la instancia de Domque permitan leer nodos hijos, de no existir elementos en this.elements, debería devolver una lista vacía. Si bien una instancia de NodeList no es un arreglo, ambas tienen una propiedad llamada length, por lo que, para efectos prácticos, sirve para representar una lista vacía.

Atributos

En HTML, las etiquetas pueden guardar información sobre sus propiedades mediante atributos. Los atributos más comunes son id y class y, en el caso de elementos de formulario, los atributos más importantes son type yname.
La interfaz Element tiene métodos para leer, definir, eliminar y verificar si un atributo está definido:getAttributesetAttributeremoveAttribute y hasAttribute, respectivamente. Estos métodos reciben un parámetro en forma de cadena para el nombre (y otro para el valor, en el caso de setAttribute).
Dentro de dom.js crearemos los métodos getsetunset y has:
Dom.prototype.get = function(attributeName) {
  var i = 0,
      attributeValues = [];

  for (i; i < this.elements.length; i++) {
    attributeValues.push(this.elements[i].getAttribute(attributeName));
  }

  return attributeValues;
};

Dom.prototype.set = function(attributeName, attributeValue) {
  var i = 0;

  for (i; i < this.elements.length; i++) {
    this.elements[i].setAttribute(attributeName, attributeValue);
  }
};

Dom.prototype.unset = function(attributeName) {
  var i = 0;

  for (i; i < this.elements.length; i++) {
    this.elements[i].removeAttribute(attributeName);
  }
};

Dom.prototype.has = function(attributeName) {
  var i = 0,
      hasAttributeValues = [];

  for (i; i < this.elements.length; i++) {
    hasAttributeValues.push(this.elements[i].hasAttribute(attributeName));
  }

  return hasAttributeValues;
};
Como los atributos también son nodos dentro del DOM, no solo se pueden manipular atributos con los métodosgetAttributesetAttribute y removeAttribute. También es posible utilizar nodos de tipo atributo en vez de cadenas como parámetros con los métodos getAttributeNodesetAttributeNode yremoveAttributeNode.

Eventos

Los eventos permiten comunicar acciones realizadas tanto por el navegador como por el usuario, y ayudan a mejorar la interacción entre una persona y un sitio o aplicación web. Como ejemplo: cuando un usuario hace clic en un enlace, se puede capturar el evento click de ese elemento y lanzar una acción diferente a la habitual (la cual es enviar al usuario al documento enlazado). Otro ejemplo es validar formularios antes de ser enviados, capturando el evento submit de el elemento <form>.
Todos los elementos del DOM, además de window, heredan de la interfaz EventTarget, el cual permite enlazar eventos a callbacks definidos dentro de la aplicación. La interfaz EventTarget tiene 3 métodos:addEventListenerremoveEventListener y dispatchEvent.

addEventListener

Para enlazar un evento a un callback se utiliza addEventListener:
window.addEventListener('load', function(e) {
  console.log('window:load', e);
});
El ejemplo anterior agrega un listener al evento load de window, donde el callback pasado como segundo parámetro es la función que se ejecutará cuando el evento se dispare (que es cuando el navegador termina de cargar el documento).
Todos los callbacks enlazados a eventos toman un solo parámetro (en este caso, e). Este parámetro puede ser instancia de FocusEventMouseEventKeyboardEventUIEvent o WheelEvent, dependiendo del evento que sea lanzado. Todos los eventos heredan de la interfaz Event.

removeEventListener

Para eliminar un listener de un elemento se utiliza el método removeEventListener, que toma los mismos valores de addEventListener. Esto quiere decir que, para eliminar un listener de un elemento, es obligatorio mandar como parámetro el mismo callback utilizado en addEventListener.
En este ejemplo el evento no se eliminará, puesto que los callbacks son diferentes:
window.addEventListener('load', function(e) {
  console.log('window:load', e);
});

window.removeEventListener('load', function(e) {
  console.log('window:load', e);
});
Por eso, es necesario guardar el callback utilizado en addEventListener en una variable para utlizarla luego en removeEventListener:
var windowOnLoad = function(e) {
  console.log('window:load', e);
};

window.addEventListener('load', windowOnLoad);

window.removeEventListener('load', windowOnLoad);
Ahora que ya vimos como agregar y eliminar listeners a un elemento, vamos a añadir esta funcionalidad adom.js:
Dom.prototype.on = function (eventName, callback) {
  var i = 0,
      eventIdentifier = this.selector + ':' + eventName;

  if (this.events === undefined) {
    this.events = {};
  }

  if (this.events[eventIdentifier] === undefined) {
    this.events[eventIdentifier] = [];
  }

  this.events[eventIdentifier].push(callback);

  for (i; i < this.elements.length; i++) {
    this.elements[i].addEventListener(eventName, callback, true);
  }
};

Dom.prototype.off = function(eventName) {
  var i = 0,
      e = 0,
      eventIdentifier = this.selector + ':' + eventName;

  if (this.events === undefined) {
    this.events = {};
  }

  if (this.events[eventIdentifier] != undefined) {
    for (e; e < this.events[eventIdentifier].length; e++) {
      var callback = this.events[eventIdentifier][e];

      for (i; i < this.elements.length; i++) {
        this.elements[i].removeEventListener(eventName, callback, true);
      }
    }

    this.events[eventIdentifier] = [];
  }
};
Como debemos tener constancia de los callbacks que están siendo utilizados en addEventListener, los guardamos en la propiedad this.events. Luego, si queremos eliminarlos, iteramos dentro de esa propiedad y eliminamos los listeners con removeEventListener.
Así, utilizamos nuestros métodos de la siguiente forma:
var win = new Dom(window);

win.on('load', function(e) {
  console.log('window:load');
});
Para que nuestro dom.js funcione con window debemos cambiar el constructor una vez más, y debemos verificar si el argumento pasado al constructor es instancia de EventTarget (de todas formas, Node hereda deEventTarget):
function Dom(selectorOrElements) {
  if (typeof selectorOrElements === 'string') {
    this.selector = selectorOrElements;
    this.elements = document.querySelectorAll(selectorOrElements);
  }
  else {
    if (selectorOrElements instanceof EventTarget) {
      this.elements = [selectorOrElements];
    }
    else {
      this.elements = selectorOrElements;
    }
  }
};

Eventos propios

Adicionalmente a los eventos nativos del navegador (como loadclicky otros), también se pueden crear eventos propios. Estos eventos propios son utilizados para propósitos propios de la aplicación. Por ejemplo, y siguiendo con el caso de La Buena Espina, como desarrollador es vital saber cuándo un usuario ha cambiado de sección. Podemos saber esto mediante el uso de eventos propios.
Existen dos formas de crear eventos personalizados:
La primera es utilizando el método document.createEvent. Este método funciona en todos los navegadores, incluyendo Internet Explorer 9 y superiores:
var sectionChangedEvent = document.createEvent('CustomEvent');
sectionChangedEvent.initCustomEvent('sectionchanged', true, false, { previousSection: 'carta', nextSection: 'locales' });

document.addEventListener('sectionchanged', function(e) {
  console.log(e.detail.previousSection + ' → ' + e.detail.nextSection);
});

document.dispatchEvent(sectionChangedEvent);
// "carta → locales"
La segunda forma es utilizando el constructor de CustomEvent, el cual funciona para todos los navegadores, excepto Internet Explorer:
var sectionChangedEvent = new CustomEvent('sectionchanged', {
  bubbles: true,
  cancelable: false,
  detail: {
    previousSection: 'carta',
    nextSection: 'locales'
  }
});

document.addEventListener('sectionchanged', function(e) {
  console.log(e.detail.previousSection + ' → ' + e.detail.nextSection);
});

document.dispatchEvent(sectionChangedEvent);
// "carta → locales"

*Event flow*

Cuando un evento es lanzado, este pasa por 3 fases, en el siguiente orden: Capture phaseTarget phase yBubbling phase. El hecho de pasar por las 3 fases es denominado event flow.
Document Object Model (DOM) Level 3 Events Specification: Event flow
Document Object Model (DOM) Level 3 Events Specification. http://www.w3.org/TR/DOM-Level-3-Events/
En el event flow, cada evento lanzado en el DOM empieza en el contexto global (es decir, window), pasa por el nodo raíz del documento (document) y sigue un camino a través de una serie de nodos hijos (Capture phase) que le permita llegar al elemento que lanza dicho evento (Target phase). En la target phase, el evento es lanzado. Luego, empieza la bubbling phase, siguiendo el mismo camino de la capture phase, pero en sentido inverso, hasta llegar al contexto global (window).
Cuando se registra un listener, se puede definir para que sea ejecutado en la capture phase o en la bubbling phase. El orden en que un listener es ejecutado depende de la fase en la que está agregado:
window.addEventListener('click', function() {
  console.log('Bubbling click event');
}, false);                                       // Este listener se ejecutará segundo

window.addEventListener('click', function() {
  console.log('Capturing click event');
}, true);                                       // Este listener se ejecutará primero
Para definir la fase en la que se ejecutará un listener se pasa un tercer parámetro a addEventListener, el cual debe tener un valor booleano: si el parámetro es true, el listener se ejecutará en la capture phase, y si esfalse el listener se ejecutará en la bubbling phase. Por defecto, el valor de este parámetro es false. Cabe señalar que también debe ser pasado a removeEventListener si existen dos listeners, uno para cada fase, que apunten al mismo evento y elemento.
En dom.js definimos el tercer parámetro como true, tanto en Dom.prototype.on como enDom.prototype.off, haciendo que todos los listeners sean ejecutados en la capture phase. De esta forma, el orden en el que agregamos los listeners será el mismo en el que son lanzados.

Rendimiento

Trabajar con el DOM puede traer consecuencias inesperadas en temas de rendimiento si no se toman en cuenta algunas características propias de los navegadores. Por ejemplo, cuando se manipula el árbol DOM, el navegador recalcula posiciones y re-renderiza la pantalla (ver Reflow y repaint).

*Reflow* y *repaint*

Cuando se renderiza un documento HTML en un navegador ocurren dos acciones: reflow (o layout) y repaint. Al realizar el reflow, el navegador calcula las dimensiones y posiciones de cada elemento visible y los coloca en la posición previamente calculada dentro de la zona visible del navegador (o viewport). Cuando se realiza el repaint, el navegador obtiene la información de las hojas de estilo del documento, así como de los estilos del sistema y del navegador, y muestra los elementos de la forma como fue ideada (bordes, fondos, colores, imágenes, etc). Cuando se realiza un reflow, también se realiza un repaint, pero no es así de forma inversa (puede cambiarse el fondo de un elemento y el navegador no tendrá que recalcular posiciones de elementos).
Algunas de las acciones que obligan al navegador a realizar reflow (y su respectivo repaint) está relacionadas al uso de CSS; y otras a la manipulación del árbol DOM con JavaScript, como:
  • Agregar o eliminar un elemento al documento.
  • Cambiar el contenido de un elemento con innerText e innerHTML.
  • Cambiar la visibilidad de un elemento con la propiedad display del CSS (manipulando el atributo stylede un elemento).
  • Cambiar la clase CSS o los estilos de un elemento (atributos classNameclassList o style).
  • Redimensionar la ventana o el viewport.
  • Utilizar el método getComputedStyle.
  • Leer las propiedades de MouseEventlayerXlayerYoffsetX y offsetY.
  • Realizar scroll con los métodos scrollIntoViewscrollIntoViewIfNeededscrollByLines oscrollByPages.
  • Leer algunas propiedades de elementos: clientLeft/Top/Width/Height,scrollLeft/Top/Width/HeightoffsetLeft/Top/Width/Heightentre otras.
document.createDocumentFragment
Este método permite crear una versión más ligera y limitada de document, y sirve para mejorar el rendimiento de operaciones donde se necesiten agregar muchos nodos.
Cuando se agregan nodos dentro de un bucle (la operación más común cuando se quieren agregar un indeterminado número de elementos), se utiliza el método appendChild, el cual hace que el navegador recalcule posiciones y renderice la pantalla tantas veces como iteraciones tuvo el bucle.
La ventaja de utilizar un fragmento (una instancia de DocumentFragment) es que, al estar separado del árbol DOM del documento y es guardado en memoria, evita que el navegador tenga que renderizar de nuevo cada vez que se agregue un nodo al fragmento.
Para probar este método implementaremos un método llamado html, el cual permitirá agregar elementos a un nodo pero utilizando cadenas. Inicialmente usaremos la propiedad innerHTML:
Dom.prototype.html = function(htmlString) {
  var i = 0;

  // Eliminamos el contenido de todos los elementos
  for (i; i < this.elements.length; i++) {
    this.elements[i].textContent = '';
  }

  // Agregamos el nuevo contenido a todos los elementos
  for (i = 0; i < this.elements.length; i++) {
    this.elements[i].innerHTML = htmlString;
  }
};
Vamos a probar en un nodo vacío ejecutando la siguiente instrucción:
new Dom('#background').html('<div class="slide" id="slide-1" title="Créditos: http://www.flickr.com/photos/saucesupreme/6774616862/"></div><div class="slide" id="slide-2" title="Créditos: http://www.flickr.com/photos/c32/4775267221/"></div><div class="slide" id="slide-3" title="Créditos: http://www.flickr.com/photos/renzovallejo/7998183161/"></div><div class="slide" id="slide-4" title="Créditos: http://www.flickr.com/photos/renzovallejo/7998217897/"></div><div class="slide" id="slide-5" title="Créditos: http://www.flickr.com/photos/renzovallejo/7998185723/"></div><div class="slide" id="slide-6" title="Créditos: http://www.flickr.com/photos/renzovallejo/7998141711/"></div>');
Este método nos da el siguiente gráfico dentro de la pestaña Timeline de Chrome:
Utilizando innerHTML
Utilizando innerHTML
Ahora vamos a utilizar un fragmento:
Dom.prototype.html = function(htmlString) {
  var i = 0,
      f = 0;

  var fragment = document.createDocumentFragment(),
      root = Dom.createElement({
        tag: 'div',
        attributes: {
          id: 'root'
        }
      });

  root.innerHTML = htmlString;

  for (f; f < root.childNodes.length; f++) {
    fragment.appendChild(root.childNodes[f].cloneNode(true));
  }

  // Eliminamos el contenido de todos los elementos
  for (i; i < this.elements.length; i++) {
    this.elements[i].textContent = '';
  }

  root = null;

  // Agregamos el fragmento a todos los elementos
  for (i = 0; i < this.elements.length; i++) {
    this.elements[i].appendChild(fragment.cloneNode(true));
  }
};
Utilizando fragmento
Utilizando fragmento
El tiempo utilizado por el navegador para recalcular estilos luego de añadir los elementos al DOM, segun la técnica:
TécnicaTiempo (ms)
Element.prototype.innerHTML4.047ms
document.createDocumentFragment2.576ms

Enlazando eventos a múltiples elementos

Uno de los casos más comunes de uso de eventos es el de enlazar eventos a diferentes elementos que son similares (por ejemplo, un cliente de correo tiene el mismo enlace "marcar como importante" para cada correo en la bandeja). Con el DOM, se agrega un listener de un evento a un elemento utilizando addEventListener, pero no se puede agregar un listener a una lista de elementos.
Agregar un listener por cada elemento puede ser una solución pero, a medida que existan más elementos del mismo tipo, se necesitarán agregar más listeners, aumentando la cantidad de memoria utilizada por el navegador.
*Event delegation*
Event delegation es una técnica que permite disminuir la cantidad de listeners creados, y que tiene como ventaja adicional el permitir que un elemento recién creado pueda escuchar un evento, sin necesidad de tener su propiolistener.
Esta técnica utiliza el event flow para agregar un listener al elemento padre de todos los elementos que compartirán la misma funcionalidad. Debido a la naturaleza del event flow, el elemento padre lanzará el evento si tiene un listener registrado.
Existen dos formas de obtener el elemento que lanza el evento: el contexto del mismo (this) o la propiedadtarget del evento (el parámetro del callback): Cuando se usa addEventListenerthis y targetreferencian al mismo elemento, mientras que en event delegationthis será el elemento que ejecuteaddEventListener (es decir, el elemento padre), mientras que target será el elemento que lance el evento (es aquí donde ocurre la target phase).
Dentro del callback del listener, se verifica que el elemento referenciado en target sea el que se desea utilizar (usualmente comparando las clases o el id de target).
Vamos a implementar el event delegation en el método delegate:
Dom.prototype.delegate = function(eventName, selector, callbak) {
  var i = 0,
      eventIdentifier = selector + ':' + eventName;

  if (this.events === undefined) {
    this.events = {};
  }

  if (this.events[eventIdentifier] === undefined) {
    this.events[eventIdentifier] = [];
  }

  this.events[eventIdentifier].push(callback);

  for (i; i < this.elements.length; i++) {
    this.elements[i].addEventListener(eventName, function(e) {
      if (e.target.webkitMatchesSelector(selector)) {
        callback(e);
      }
    }, true);
  }
};
En esta primera implementación utilizamos el método webkitMatchesSelector, el cual verifica que un elemento concuerde con un selector CSS dado. Si lo dejamos de esta forma, Dom.prototype.delegate solo funcionará en navegadores basados en Webkit y Blink, así que crearemos un método auxiliar:
Dom.match = function(element, selector) {
  var matchesSelector = element.matchesSelector || element.webkitMatchesSelector || element.mozMatchesSelector || element.oMatchesSelector || element.msMatchesSelector;

  return matchesSelector.call(element, selector);
};
Así, cambiamos e.target.webkitMatchesSelector(selector) por Dom.match(e.target, selector):
Dom.prototype.delegate = function(eventName, selector, callbak) {
  var i = 0,
      eventIdentifier = selector + ':' + eventName;

  if (this.events === undefined) {
    this.events = {};
  }

  if (this.events[eventIdentifier] === undefined) {
    this.events[eventIdentifier] = [];
  }

  this.events[eventIdentifier].push(callback);

  for (i; i < this.elements.length; i++) {
    this.elements[i].addEventListener(eventName, function(e) {
      if (Dom.match(e.target, selector)) {
        callback(e);
      }
    }, true);
  }
};
Y lo utilizamos de la siguiente manera (podemos probar en el archivo http://cevichejs.herokuapp.com/files/3-dom-cssom/index.html):
var doc = new Dom(document);

doc.delegate('click', 'nav a', function(e) {
  console.log(e.target.getAttribute('href'));
});

// Si empezamos a hacer clic a los enlaces de la barra superior nos saldrán los siguientes mensajes:
// "#carta"
// "#locales"
// "#historia"
Si agregamos un enlace más a la barra superior y hacemos clic en él, el evento click también se lanzará:
var nav = new Dom('nav');
nav.append({
  tag: 'a',
  attributes: {
    href: '#reservaciones'
  },
  content: 'Reservaciones'
});

// Hacemos clic en el enlace recientemente creado y saldrá el siguiente mensaje:
// "#reservaciones"
Esta es una de las ventajas de utilizar event delegation: con un solo listener hemos capturado eventos de 4 enlaces.

Mejorando dom.js

Existen algunos métodos útiles que podemos agregar a dom.js y que nos servirán en La Buena Espina.
Por ejemplo, necesitaremos agregar y eliminar clases a elementos dentro del DOM:
Dom.prototype.addClass = function(className) {
  var i = 0;

  for (i; i < this.elements.length; i++) {
    this.elements[i].classList.add(className);
  }
};

Dom.prototype.removeClass = function(className) {
  var i = 0;

  for (i; i < this.elements.length; i++) {
    this.elements[i].classList.remove(className);
  }
};

Dom.prototype.hasClass = function(className) {
  var i = 0,
      hasClass = [];

  for (i; i < this.elements.length; i++) {
    hasClass.push(this.elements[i].classList.contains(className));
  }
};
Así como saber cuál es el primer y último elemento hijo de un contenedor:
Dom.prototype.firstChild = function() {
  return new Dom(this.elements[0].firstElementChild);
};

Dom.prototype.lastChild = function() {
  return new Dom(this.elements[0].lastElementChild);
};
O el lugar que ocupa un elemento entre sus elementos hermanos:
Dom.prototype.index = function() {
  return Array.prototype.indexOf.call(this.elements[0].parentNode.children, this.elements[0]);
};
En este método utilizamos el método indexOf de Array. Las propiedades en el DOM como children no devuelven arreglos, si no listas instancias de NodeList (o HTMLCollection dependiendo del navegador); y si bien estas listas no son arreglos, es posible utilizar los métodos propios de instancias de Array juntando dos conceptos vistos en el capítulo anterior: prototypes y el método call.
A grandes rasgos, Array.prototype.indexOf es una función que itera a partir de los elementos del arregloque lo ejecuta (es decir, su contexto) mientras busca por el elemento que es pasado como parámetro, cuando lo encuentra devuelve el número de la iteración en la que ha sido encontrado, el cual es el índice en el que se encuentra dicho elemento. En nuestro caso, children no es un arreglo, pero todas sus propiedades pueden ser accesibles mediante índices que empiezan en 0, y tiene una propiedad length que contiene el número de elementos dentro de la lista. Este tipo de objetos son llamados array-like objects y se pueden ver en muchas partes del DOM y del lenguaje en sí (por ejemplo, la palabra reservada arguments o las instancias deCSSStyleDeclaration y NamedNodeMap son array-like objects).

CSSOM

El Cascade Style Sheet Object Model, o CSSOM, toma los conceptos de DOM y los lleva a las hojas de estilo en cascada que componen un documento HTML. Esto permite tener un control más profundo de las reglas y propiedades que se aplican tanto a un elemento como a un documento HTML, utilizando JavaScript.

Box model

En el navegador, cada elemento mostrado en pantalla tiene forma de caja (o rectángulo), por lo que tiene un alto y un ancho, el cual puede ser definido por el navegador mismo o por el usuario mediante CSS.
La especificación de CSS define el denominado box model, el cual empieza definido como un rectángulo que limite el contenido del elemento (content area). Este rectángulo content area está rodeado por otro rectángulo más grande, denominado padding; el padding, a su vez, está rodeado por un rectángulo externo denominadoborder. Por último, el rectángulo border está rodeado por el rectángulo margin:
Box model
Los rectángulos paddingborder y margin pueden ser personalizados mediante CSS. Incluso, los 4 lados de cada rectángulo pueden tener valores diferentes.

Reglas y propiedades

Cada elemento de una hoja de estilos es una regla, y cada regla está definida por dos partes: el selector (es decir, el elemento o elementos a los cuales aplicará la regla), y una lista de propiedades.
body {
  font-family: 'Arial';
  background: green;
  color: white;
}
En este caso, existe una regla cuyo selector es body, y sus propiedades son las definidas por font-family,background y color.

Agregando reglas

Cada hoja de estilos en un documento es accesible mediante la propiedad styleSheets de document. Esta devuelve una lista viva, instancia de StyleSheetList (similar a NodeList), conteniendo objetos instancias de CSSStyleSheet. Por cada hoja de estilos, enlazada mediante la etiqueta <link> o definida dentro del documento con <style>, existe una instancia de CSSStyleSheet dentro de document.styleSheets.
Cada instancia de CSSStyleSheet tiene una propiedad cssRules, una lista viva instancia de CSSRuleList(similar a StyleSheetList y NodeList). Esta lista contiene objetos de diferentes interfaces, dependiendo del tipo de regla que referencia en la hoja de estilos:
Tipo de reglaInterfaz
EstilosCSSStyleRule
Definir un charset (@charset)CSSCharsetRule
Importar una hoja de estilos (@import)CSSImportRule
Media query (@media)CSSMediaRule
Font face (@font-face)CSSFontFaceRule
Definir estilos para impresión (@page)CSSPageRule
Una lista de key frames (@keyframes)CSSKeyframesRule
Un elemento de una lista de key framesCSSKeyframeRule
Solo en Webkit y Blink, los objetos CSSStyleSheet que referencian a hojas de estilos enlazadas desde un dominio diferente al del documento (como un CDN) no permiten leer sus reglas, mientras que en el resto de casos y navegadores sí es posible.
Para poder agregar una regla a una hoja de estilos se utiliza el método addRule o insertRule, disponible en cada instancia de CSSStyleSheet. Mientras addRule acepta 3 parámetros (selector, propiedades, índice donde agregar la regla), insertRule acepta solo 2 (cadena con el contenido completo de la regla, índice donde agregar la regla).

window.getComputedStyle

Este método devuelve un objeto instancia de CSSStyleDeclaration con todos los estilos de un elemento pasado como parámetro. Estos estilos son calculados por el navegador a partir de estilos propios del sistema operativo, el navegador y hojas de estilos incluidas en el documento que contengan reglas aplicables a dicho elemento. Estos estilos son de solo lectura, a diferencia de los obtenidos por la propiedad style de cada instancia de CSSStyleRule.
En dom.js crearemos un método style:
Dom.prototype.style = function() {
  var i = 0;

  var styles = [];

  for (i; i < this.elements.length; i++) {
    styles.push(window.getComputedStyle(this.elements[i]));
  }

  return styles;
};
Sin embargo, window.getComputedStyle es un método pesado que impacta en el rendimiento de la aplicación. Para evitar esto guardaremos el resultado en una propiedad llamada styles:
Dom.prototype.style = function() {
  var i = 0,
      element;

  if (this.styles === undefined) {
    this.styles = {};
  }

  for (i; i < this.elements.length; i++) {
    element = this.elements[i];

    if (this.styles[element] === undefined) {
      this.styles[element] = window.getComputedStyle(element);
    }
  }

  return this.styles;
};
Dentro del DOM, cada nodo es un objeto único, por lo que podemos utilizarlos como nombres dentro del objeto plano this.styles. Así mismo, window.getComputedStyle devuelve una lista viva, así que cada vez que cambiemos algún estilo dentro de un elemento, este cambio se reflejará en su respectivo valor dentro dethis.styles.

*Media queries*

Los media queries permiten utilizar CSS para actualizar la presentación de un documento en cuanto cumpla algunas condiciones, como el ancho y alto del viewport, orientación del dispositivo, entre otras. Si bien muchas de las condiciones se pueden verificar mediante JavaScript y el DOM, este tipo de operaciones son lentas y suelen perjudicar notablemente el rendimiento de una aplicación web (este tipo de perjuicio se nota aún más en dispositivos móviles), por lo que, de ser posible, es recomendable utilizar media queries.

window.matchMedia

Con este método se puede saber si una o más condiciones corresponden al estado actual del navegador, y devuelve una instancia de MediaQueryList que permite agregar listeners con el método addListener. A diferencia del DOM, este método solo acepta un parámetro, el cual es un callback que se ejecutará cada vez que cambie alguna de las condiciones que se están evaluando. Este manejo de eventos es útil para precargar estilos,scripts, o imágenes específicas según resoluciones diferentes.
Para manejar mejor este método crearemos un archivo cssom.js, el cual contendrá un objeto CSSom:
CSSom = {
  mediaQueries: {}
};
Como las instancias de MediaQueryList permiten agregar listeners, crearemos dos métodos para manejarlos de forma más simple:
CSSom.on = function(mediaQueryString, callback) {
  var mediaQueryList;

  if (this.mediaQueries[mediaQueryString] === undefined) {
    this.mediaQueries[mediaQueryString] = [];
  }

  mediaQueryList = window.matchMedia(mediaQueryString);
  mediaQueryList.addListener(callback);

  this.mediaQueries[mediaQueryString].push({
    mediaQueryList: mediaQueryList,
    callback: callback
  });
};

CSSom.off = function(mediaQueryString) {
  var i = 0,
      mediaQueryResult;

  if (this.mediaQueries[mediaQueryString] !== undefined) {
    for (i; i < this.mediaQueries[mediaQueryString]; i++) {
      mediaQueryResult = this.mediaQueries[mediaQueryString];
      mediaQueryResult.mediaQueryList.removeListener(mediaQueryResult.callback);
    }

    this.mediaQueries[mediaQueryString] = [];
  }
};
De esta forma, podemos definir eventos para determinados media queries:
CSSom.on('(orientation: portrait)', function(mq) {
  if (mq.matches) {
    document.body.className = 'portrait';
  }
  else {
    document.body.className = 'landscape';
  }
});

Transiciones y Animaciones

Las transiciones y animaciones son nuevos estilos en CSS que permiten animar, valga la redundancia, elementos dentro de un documento, interpolando los valores de algunas de sus propiedades, como el alto, ancho y posición (aunque muchas otras propiedades pueden ser animadas).
Por ejemplo, La Buena Espina se vería bien si le ponemos un efecto simple para cambiar la imagen de fondo cada cierto tiempo. Inicialmente necesitaremos tener una serie de elementos div con clase slide, y definimos que todos esos elementos por defecto no deben ser visibles, dándole un valor de 0 a la propiedad opacity, mientras que el slide que quiera mostrarse debe tener el valor de 1 en la misma propiedad:
.slide {
  opacity: 0;
}

.slide.current {
  opacity: 1;
}
El siguiente paso es definir una transición para la propiedad opacity, la cual durará 5 segundos. El navegador es el encargado de calcular los valores que tendrá opacity durante los 5 segundos que dure la transición:
.slide {
  opacity: 0;
  transition-property: opacity;
  transition-duration: 5.0s;
}

.slide.current {
  opacity: 1;
}
Las animaciones en CSS son similares a las transiciones, con la principal diferencia que las animaciones dan más control al usuario con respecto a los valores que puede tener una propiedad dentro del ciclo de vida de una animación (mientras que, en una transición, es el navegador el que calcula dichos valores).
Para definir una animación en CSS, necesitamos definir una regla del tipo keyframes, la cual contendrá una lista de puntos en donde una propiedad cambiará de valor. De esta forma, el navegador se encargará de tomar doskeyframes (uno de partida y uno de fin) y calculará los valores intermedios para el tiempo que debe transcurrir entre estos dos keyframes. Si bien en esto las animaciones son similares a las transiciones, en las animaciones pueden haber más de dos keyframes, lo que le da más control al usuario al momento de animar un elemento.
Basados en el ejemplo anterior, podemos utilizar animaciones en vez de transiciones:
.slide {
  opacity: 0;
}

.slide.current {
  animation-name: slide;
  animation-duration: 5s;
  animation-fill-mode: forwards;
}

@keyframes slide {
  0% {
    opacity: 0;
  }

  50% {
    opacity: 0.3;
  }

  80% {
    opacity: 0.7;
  }

  100% {
    opacity: 1;
  }
}
En este caso, con las animaciones tenemos el poder de definir qué valores tendrá opacity al inicio (0%), fin (100%), a los 2 segundos y medio (50%), y a los 4 segundos (80%) de transcurrida la animación.
El hecho de tener más control en las animaciones también se ve reflejado en el CSSOM. Mientras que solo existe el evento transitionend para las transiciones, las animaciones tienen 3 eventos: animationstart,animationiterationanimationend.
Por ejemplo, si queremos agregar un listener al evento transitionend que muestre en la consola el tiempo transcurido en la transición:
dom('.slide').on('transitionend', function(e) {
  console.log('transitionend', e.elapsedTime);
});
Mientras que, si utilizamos animaciones, podemos agregar un listener al evento animationstart para saber cuándo empezó una animación:
dom('.slide').on('animationstart', function(e) {
  console.log('animationstart', e);
});
Y agregar un listener al evento animationend para saber en qué momento terminó una animación:
dom('.slide').on('animationend', function(e) {
  console.log('animationend', e);
});
El evento animationiteration es lanzado cada vez que empieza una iteración de la animación. Una animación puede ser definida para ser ejecutada un número determinado de veces (y cada vez es una iteración), mediante la propiedad animation-iteration-count.

Sabiendo un poco más sobre transiciones y animaciones crearemos un script simple para crear el efecto para cambiar la imagen de fondo. Primero, debemos tener un poco de HTML base:
<div id="background">
  <div class="slide current" id="slide-1" title="Créditos: http://www.flickr.com/photos/saucesupreme/6774616862/"></div>
  <div class="slide" id="slide-2" title="Créditos: http://www.flickr.com/photos/c32/4775267221/"></div>
  <div class="slide" id="slide-3" title="Créditos: http://www.flickr.com/photos/renzovallejo/7998183161/"></div>
</div>
Notemos que el primer slide tiene la clase current, de esta forma nos aseguramos de mostrar una imagen al cargar el sitio.
Ahora, agregamos el CSS respectivo:
#background {
  display: block;
  width: 100%;
  height: 100%;
  position: relative;
}

#background .slide {
  display: block;
  width: 100%;
  height: 100%;
  position: absolute;
  opacity: 0;
  transition-property: opacity;
  transition-duration: 3.5s;
}

#background .slide.current {
  opacity: 1;
}

#slide-1 {
  background: url('../images/slides/slide-1.jpg') no-repeat center center;
  background-size: 100% auto;
}

#slide-2 {
  background: url('../images/slides/slide-2.jpg') no-repeat center center;
  background-size: 100% auto;
}

#slide-3 {
  background: url('../images/slides/slide-3.jpg') no-repeat center center;
  background-size: 100% auto;
}
Hemos definido una transición para la propiedad opacity que dure 3 segundos y medio. De esta forma, si le agregamos la clase current a un elemento con la clase slide, se disparará la transición que cambie el valor de opacity de 0 a 1.
Hasta este momento solo se mostrará la primera imagen de fondo y no cambiará. Para empezar con la secuencia de imágenes, usaremos el evento load de window para agregarle la clase current al siguiente elemento:
dom(window).on('load', function() {
  var current = dom('.slide.current'),
      next = current.next();

  current.removeClass('current');
  next.addClass('current');
});
Este código tiene dos particularidades que no hemos visto en este capítulo:
  • La función dom: En realidad solo devuelve una instancia de Dom, pero es útil ya que evita tener que crear una instancia de Dom cada vez que queramos trabajar con el DOM.
  • El método next: Toma el elemento actual (en este caso, el slide que tenga la clase current) y devuelve su siguiente elemento hermano, con nextElementSibling.
Con este código, ya podremos ver que cambia de la primera imagen a la segunda mediante una transición de opacidad (propiedad opacity), pero al terminar esta transición no cambia a la tercera. Para lograrlo usaremos el evento transitionend:
dom('#background').delegate('transitionend', '.slide.current', function(e) {
  var current = dom(e.target),
      next = current.next();

  current.removeClass('current');
  next.addClass('current');
});
Estamos utilizando el event delegation para definir un solo evento transitionend, en vez de tener uno por cada elemento con clase slide. Así mismo, definimos el evento solo para el slide visible en el momento, ya que el evento transitionend se disparará tanto para cuando termina la transición del valor de opacity de 0 a 1 (invisible a visible) como de 1 a 0 (visible a invisible).
Sin embargo, cuando ya se haya mostrado el último elemento (aquel que tiene id slide-3) y se lance el eventotransitionend, el callback tratará de encontrar el siguiente elemento con current.next(). Dado quecurrent guarda una referencia al último slide de la lista, next() no encontrará ningún valor, por lo que, internamente, la propiedad elements tendrá valor null y dará error al intentar ejecutar el método addClass.
Para corregir este pequeño bug, verificamos si se llegó al último elemento mediante el método isLastSibling. Si es el último elemento de la lista (en otras palabras, el último de sus elementos hermanos), next guarda una referencia al primer elemento de la lista con el método firstSibling.
dom('#background').delegate('transitionend', '.slide.current', function(e) {
  var current = dom(e.target),
      next = current.next();

  current.removeClass('current');

  if (current.isLastSibling()) {
    next = current.firstSibling();
  }

  next.addClass('current');
});