Derby


Está versión está aún bajo revisión. La traducción se comenzó con la versión 0.3.0 y se terminó con la 0.3.2. Para cualquier duda o sugerencia no dude en contactar conmigo en lasarux@impulzia.com. 27/4/2012


Infraestructura MVC que hace fácil escribir aplicaciones colaborativas y en tiempo real que funcionan tanto en Node.js como en servidores.

hello.js

var hello = require('derby').createApp(module)
  , view = hello.view
  , get = hello.get;

// Templates define both HTML and model <- -> view bindings
view.make('Body'
, 'Holler: <input value="{message}"><h1>{message}</h1>'
);

// Routes render on client as well as server
get('/', function (page, model) {
  // Subscribe specifies the data to sync
  model.subscribe('message', function () {
    page.render();
  });
});

 

server.js

var http = require('http')
  , express = require('express')
  , hello = require('./hello');

var expressApp = express()
  .use(express.static(__dirname + '/public'))
  // Apps create an Express middleware
  .use(hello.router());

var server = http.createServer(expressApp).listen(3000);

// Apps also provide a server-side store for syncing data
hello.createStore({listen: server});

Añadir agua y…

Holler:

Introducción


Derby incluye un motor potente para la sincronización de datos llamado Racer. Mientras que funciona diferencialmente, Racer es a Derby algo así como ActiveRecord es a Rails. Racer sincroniza automáticamente los datos entre navegadores, servidores y la base de datos. Los modelos se suscriben a cambios en objetos y consultas específicas, permitiendo un control granular de la propagación de datos sin definir canales. Racer permite el uso sin conexión, y trae la resolución de conflictos de serie, lo que simplifica gratamente el desarrollo de aplicaciones multiusuario. Derby hace simple la escritura de aplicaciones que cargan tan rápido como un motor de búsqueda, son tan interactivas como un editor de documentos y funcionan sin conexión.

Características

¿Por qué no usar Rails y Backbone?

Derby representa una nueva rama de infraestructura para aplicaciones, que creemos reemplazará a librerías actualmente populares como Rails y Backbone.

Añadiendo características dinámicas a las aplicaciones escritas con Rails, Django y otras infraestructuras orientadas al servidor tiende a producir un revoltijo enmarañado. El código del servidor muestra varios estados iniciales mientras que los selectores de JQuery y los “callbacks” intentan desesperadamente que tenga sentido del DOM y los eventos de usuario. Añadir nuevas funciones típicamente require cambios en ambos lados, en el código del cliente y del servidor, y con frecuencia en diferentes lenguajes.

Muchos desarrolladores ahora incluyen una infraestructura MVC cliente como “Backbone” para estructurar mejor el código del mismo. Unos pocos han comenzado a usar unas librerías sobre modelo-vista declarativas, como son “Knockout” y “Angular”, para reducir estereotipos para la manipulación del DOM y sobre los eventos. Existen grandes conceptos, y añadir algo de estructura ciertamente mejor el código del cliente. Sin embargo , todavía tienden a duplicar el código de pintado y hacer manualmente la sincronización de cambios aumenta la complejidad del código base en el cliente y en el servidor. No sólo eso, cada una de estas piezas tienen que ser manualmente enlazadas juntas y empaquetadas para el cliente.

Derby radicalmente simplifica este proceso de añadir interacciones dinámicas. Ejecuta el mismo código en servidores y navegadores y sincronizada los datos automáticamente. Derby se ocupa del procesado de plantillas, su empaquetado y los acoplamientos de modelo-vista sin más. Desde que todas esas funciones están diseñadas para trabajar juntas, no hay código duplicado ni hace falta código extra para integrarlo. Derby pone en mano de los desarrolladores un futuro dónde todos los datos y todas las aplicaciones funcionan en tiempo real.

Flexibilidad sin código extra

Derby eliminate el tedio de unificar servidor, motor de plantillas del servidor, compilador CSS, empaquetador de “scripts”, minificadores, infraestructuras de cliente MVC, librerías de cliente JavaScript, plantillas de cliente y/o motores de conexiones, librerías del histórico del cliente, transporte en tiempo real, ORM y bases de datos. Elimina la complejidad de guardar los estados de sincronización entre modelos y vistas, clientes y servidores, múltiples ventanas, múltiples usuarios, y modelos y bases de datos.

Al mismo tiempo, juega bien con otros. Berby está desarrollado sobre librerías populares, incluyendo Node.js, Express, Socket.IO, Browserify, Stylus, UglifyJS, MongoDB y pronto otras bases de datos populares. Estas librerías pueden ser también utilizadas directamente. La capa de sincronización de datos, Racer, puede ser usado por separado. Otras librerías del cliente, como es JQuery, y otros módulos de Node.js de npm funcionan perfectamente con Derby.

Cuando se sigue la estructura de ficheros por defecto para plantillas, estilos y “scripts”, estos son automáticamente empaquetas e incluidos en las páginas de forma apropiada. Adicionalmente, Derby puede ser usado vía una API dinámica, como ha podido ver en el ejemplo de arriba.

Demos

El código de las demostraciones está incluído en Derby.

Chat

http://chat.derbyjs.com/lobby

Es una simple demostración de “chat”. Observe que cuando edita su nombre, se actualiza en tiempo real. Los cambios de nombre son también mostrados en el título de la página y en otros canales. Compruebe el código fuente en el directorio de ejemplos para ver como estas conexiones son creadas automáticamente.

Todos

http://todos.derbyjs.com/derby

¡La demo obligado en todo MVC, pero colaborativa y en tiempo real! Los ítems “por hacer” son campos de contenido evitable con soporte para negrita e itálica.

Sink

http://sink.derbyjs.com/

Un ejemplo al estilo desagüe de cocina con características al azar. Mayormente usado para “testing”.

Responsabilidad

Derby y Racer son software alfa. Mientras que Derby debiera funcionar suficientemente bien para prototipos y proyectos de fin de semana, está todavía lejos de desarrollos mayores. Las APIs están sujetas a cambios.

Si ousted tine feedback, ideas o sugerencias, por favor ascribe al Grupo de Google. Si está interesado en contribuir, por favor póngase en contacto con Brian y Nate.

Comenzando


Como con todos los módulos de Node.js, primero instale Node. El instalador de Node también instalará npm.

Instale Derby con:

$ npm install -g derby

Crear una aplicación

Derby incluye un generador de proyectos sencillo:

$ cd ~
$ derby new first-project
$ cd first-project

o, para CoffeeScript:

$ cd ~
$ derby new --coffee first-project
$ cd first-project
$ make

Esto hace que se ejecute el compilador de coffee con la opción de observación, así que déjelo ejecutándose en un terminal de forma separada.

Entonces, simplemente, arranque Node:

$ node server.js

Estructura de ficheros

La estructura de ficheros por defecto es:

/lib
  /app
    index.js
  /server
    index.js
/public
  /img
  /gen
/styles
  /app
    index.styl
  404.styl
  base.styl
  reset.styl
    /views
  /app
    index.html
  404.html
.gitignore
package.json
README.md
server.js

En proyectos de CoffeeScript, el directorio lib es generado por el compilador y los ficheros de “script” tendrían que ser editados en el directorio “src” en su lugar. El generador de proyecto creará un Makefile para compilar los proyectos en CoffeeScript.

Derby use una convención de nombres de ficheros similares a la de los módulos de Node.js. Un fichero llamado demo.js y un directorio llamado “demo”. Lo mismo se aplica para los estilos y vistas, que pueden ser tanto demo.styl o demo\index.styl e demo.html o demo\index.html.

Las aplicaciones están asociadas con sus respectivas vistas y estilos sólo por su nombre de fichero. Derby las incluye automáticamente cuando las procesa. Ambas soportan la importación, así que el compartir estilos y plantillas puede ser definido en ficheros separados.

Los ficheros estáticos pueden ser puestos en la carpeta public. El servidor por defecto de Express creado por el generador de proyectos de Derby fija un tiempo de cache de un año para todos los ficheros estáticos. Por tanto, nuevas versiones de ficheros deben tener nuevos nombres. Derby compila los “scripts” para el navegador en la carpeta public\gen por defecto. Cada nombre de “script” es generado desde un “hash”, así que puede ser cacheado durante bastante tiempo.

Aplicaciones y páginas estáticas


Los proyectos de Derby soportan una o más aplicaciones de página única tanto como páginas estáticas. Las aplicaciones tienen una estructura completa de MVC, incluyendo un modelo aportado por Racer, una vista basada en una plantilla y estilos, y el código del controlador con la lógica de la aplicación las rutas (que malean URLs a acciones). Las páginas estáticas consisten únicamente en plantillas y estilos.

En el servidor, las aplicaciones proveen un router “middleware” para Express. Se pueden incluir uno o más enredadores de aplicaciones tan bien como rutas únicamente del servidor en el mismo servidor de “Express”.

Derby está empaquetado con todo lo necesario para una aplicación: plantillas, rutas y código de la aplicación cuando es procesada.

Derby packages up all of an app’s templates, routes, and application code when rendering. Regardless of which app URL the browser requests initially, the app is able to render any other state within the same application client-side. If the app cannot handle a URL, it will fall through and request from the server. Errors thrown during route handling also cause requests to fall through to the server.

Derby funciona muy bien con una única aplicación, aunque los desarrolladores podrían querer crear aplicaciones separadas si algunos grupos de páginas tienen que ser usadas juntas. Por ejemplo, un proyecto podría tener una aplicación web para ordenadores de escritorio y una aplicación web para móviles. Nuestro proyecto podría tener una aplicación para el panel de administración interno y otra para el contenido público.

Creando aplicaciones

Las aplicaciones son creadas en el fichero que define el controlador de la aplicación. Son entonces asociadas con un servidor por un requerimiento de la aplicación dentro del fichero del servidor.

app = derby.createApp ( module )

module: Derby use el módulo objeto para crear una aplicación. El hombre de la aplicación es cogido de su nombre de fichero, y Derby export un número de métodos de la aplicación.

app: Devuelve un objeto app, que es equivalente al module.exports.

El nombre del fichero de la aplicación es usado para determinar el nombre de la aplicación. Los nombres de las aplicaciones son usados para automáticamente asociar una aplicación con los ficheros de plantillas y estilos de la misma aplicación.

El nombre de la aplicación es usado como el nombre de la variable global que la aplicación expone en el servidor. Por lo tanto, los nombres de las aplicaciones deben ser nombres de variables válidas JavaScript, comenzando con una letra y continuando solo con caracteres alfanuméricos y subrayados.

El método createApp añade un número de métodos a la aplicación tanto en el cliente como en el servidor, estos son view, render, ready, get, post, put, del y hook. En el servidor únicamente Derby también añade route, createStore y session.

Conectando servidores a aplicaciones

El generador de proyectos de Derby crea un servidor Express para una configuración típica. Dado que Derby comparte la mayoría de código entre servidor y cliente, los ficheros del servidor de Derby pueden ser mínimos.

El servidor incluye una aplicación con un estamento estándar de requisitos de Node.js. Puede usar entonces el método app.router() para crear un enrutador “middleware” de Express que controle todas las rutas de la aplicación.

El servidor también necesita crear un objeto “store” (tienda), que es lo que configura Socket.IO, crea modelos, coordina la sincronización de datos e interfaces con bases de datos. Un “store” asociado con una aplicación puede ser creado usando el método app.createStore() de la aplicación. Si una “store” es compartida entre múltiples aplicaciones, tendría que ser creada usando el método derby.createStore(), que es pasado a cada una de las aplicaciones como argumento. Ver Creando “stores”.

Páginas estáticas

Derby puede también procesar páginas estáticas a partir de plantillas y estilos que no están asociados con una aplicación. Esto es útil para las páginas de error y para otras páginas que no necesitan contenido dinámico.

staticPages = derby.createStatic ( root )

root: La ruta raiz sue contiene los directories “views” y “styles”.

staticPages: Devuelve un objeto staticPages sue tiene un método rendir. (Mientras no se use, static es una palabra reserved de JavaScript, y no puede ser un nombre de variable.)

El objeto staticPages guarda una referencia al directorio raíz y provee un método staticPages.render(). Está pensado para usarse en rutas de servidor de Express únicamente. Mirar Procesando.

Vistas


Típicamente la escritura de aplicaciones Derby comienza con las plantillas HTML. Estas plantillas definen tanto el HTML procesado como las conexiones modelo-vista.

Creando vistas

Derby compile una colección de plantillas basadas en HTML en una página compuesta de un número predefinido de nombres. Las páginas normalmente definen al menos una plantilla “Title” y “Body”. Las plantillas pueden ser creadas programaticamente vía el método view.make():

var view = require('derby').createApp(module).view;

view.make('Body', '<h1>Howdy!</h1>');

Sin embargo, estas son puestas generalmente en los ficheros de plantillas dentro del directorio de vistas. Cada aplicación busca automáticamente un fichero de plantilla que comparta el mismo nombre y llama a view.make para cada plantilla. Las plantillas que están en un fichero de plantilla son también automáticamente empaquetadas con los “scripts” de la aplicación así que pueden ser procesados en el cliente.

Los ficheros de plantillas son también HTML, pero cada plantilla está envueltas en un tag que le da nombre a la plantilla. Este nombre debe terminar con dos puntos para direferenciarlo de una etiqueta normal de HTML. Estas etiquetas no tienen que estar cerrados. Por ejemplo:

<Title:>
  Silly example

<Body:>
  <h1>Howdy!</h1>

Plantillas predefinidas

Por defecto Derby incluye plantillas con los nombres Doctype, Root, Charset, Title, Head, Header, Body, Footer, Scripts y Tail cuando procesa la página en el servidor.

En el navegador sólo las plantillas Root, Title, Header, Body y Footer son reprocesadas. De este modo las conexiones vista-modelo pueden ser sólo definidas dentro de estas plantillas.

Algunas de las plantillas predefinidas tiene nombres que son los nombres de etiquetas HTML, pero únicamente Title recubre la plantilla dentro de la etiqueta <title>. Derby no incluyen ninguno de los elementos HTML que no son obligatorios, como son <html>, <head> y <body> por defecto.

Por convención, los nombres de las plantillas predefinidas comienzan por mayúsculas para indicar que el procesador de la página lo incluirá automáticamente. Sin embargo, desde que las etiquetas HTML da igual que estén en mayúsculas o minúsculas, los nombres de las platillas de Derby son también indiferentes a mayúsculas y minúsculas. De este modo, Body, BODY y body representan todos la misma plantilla.

Observe que los ficheros de la plantilla no contienen HTML reutilizable, como son definiciones de doctype, hojas de estilo o incluye “scripts”. Por defecto, Derby incluye estos ítems para poder optimizar rápidos tiempos de carga. También para optimizar tiempo de carga envía a las páginas un número de trozos:

Primer trozo

  1. Doctype: doctype estandard HTML5 —<!DOCTYPE html>— a menos que se sobreescriba.
  2. Root: localización optional para un elemento <html> si lo desea. Esta plantilla no debiera Optional location for an element if desired. This template should not include any other elements
  3. Charset: será <meta charset=utf-8> a menos que sea sobreescrito
  4. Title: el contenido de text del elemento <title> de la página
  5. Head: Optional location for meta tags, scripts that must be placed in the HTML , and manually included stylesheets
  6. El CSS es compilado e insertado después de la plantilla del Head automáticamente
  7. Header: localización opcional para la cabecera de la página sqe será enviada con el trozo de respuesta inicial. Observe sue esto es actualmente parte del / de HTML, pero tendrían que ser procesado correctamente por él mismo. Está separado así que puede ser mostrado al usuario antes que el resto de la página si el recordatorio de la página se toma un momento para descargar. Normalmente esto incluye contenido fijo, como un logo y la barra de navegación superior.

Segundo trozo

  1. Body: el contenido inicial de la página
  2. Footer: localización opcional para el contenido a incluir después del “body”. Usado para noticias de derechos, enlaces a pié de página y cualquier otro contenido que se repita al final de múltiples páginas.

Tercer trozo

  1. Los “scripts” incrustados se encuentran en un fichero llamado inline.js o son añadidos vía método view.inline(). Los “scripts” son incluídos normalmente de esta manera si son necesarios para que la página sea procesada adecuadamente, como por ejemplo cuando se necesita adecuar el tamaño de un elemento basándose en el tamaño de la ventana
  2. Scripts: localización opcional para los “scripts” externos que cargan antes que los “scripts” del cliente. Por ejemplo, éste es el lugar dónde va una etiqueta “script” que incluye JQuery. Observe que esta plantilla es sólo un lugar dentro de la página, y no está conectado con una etiqueta “script”
  3. Los “scripts” del cliente son incluídos automáticamente vía un “script” externo cargado asincronamente. El nombre del “script” es un “hash” de su contenido así que puede ser guardado por el navegador durante un largo tiempo

Cuarto trozo

  1. JSON bundle of the model data, event bindings, and other data resulting from rendering the page. This bundle initializes the application once the external client script loads.
  2. Tail: localización opcional para “scripts” adicionales que serán incluídos al final del todo de la página

Importando plantillas

Las plantillas pueden ser importadas desde otro fichecho haciendo múltiples aplicaciones de páginas y compartiendo las plantillas entre las múltiples páginas. La localización de los ficheros están expresados relativamente, de forma similiar a como los módulos de Node.js son cargados. Como en los módulos de Node.js, ni pageName.html ni pageName/index.html pueden ser importados como pageName.

<!-- all templates from "./home.html" with the namespace "home" -->
<import: src="home">

<!-- all templates from "./home.html" into the current namespace -->
<import: src="home" ns="">

<!-- one or more specific templates with the namespace "home" -->
<import: src="home" template="message alert">

<!-- one template as a different name in the current namespace -->
<import: src="home" template="message" as="myMessage">

La plantillas definidas en un nombre de espacio padre son heredadas a menos que sean sobreescritas por una plantilla con el mismo nombre en el nombre de espacicio hijo. Así con frecuencia tiene sentido colocar elementos de la página comunes en un fichero principal que importa otros ficheros y sobreescribir la parte de la página que es diferente.

Los componentes de la plantilla son referenciados relavitamente a su nombre de espacio actual. Los nombre de espacio están separados por comos, y un nombre de espacio puede ser pasado al método page.render() para procesear una página específica o un estado de la aplicación.

shared.html
<profile:>
  <div class="profile">
    ...
  </div>
home.html
<import: src="shared">

<Body:>
  Welcome to the home page
  <!-- include component from an imported namespace -->
  <app:shared:profile>
index.html
<import: src="home">
<import: src="contact">
<import: src="about">

<Body:>
  Default page content

<Footer:>
  <p><small>&copy; {{year}}</small></p>
Context
page.render('home', {
  year: 2012
});

Sintaxis de la plantilla


La sintaxis de la plantilla de Derby está basada en gran medida en Handlebears, un lenguaje de plantillas semántico similar a Mustache.

Si usa Sublime Text 2 o TextMate, puede usar nuestro “fork” del “bundle” de HTML5 para el correcto remarcado de sintaxis de las plantillas de Derby. Si quieres también puedes probar nuestro tema de colores “Clean”, que remarca cada tipo de etiqueta de la plantilla adecuadamente.

Una plantilla simple de Handlebears:

Hello {{name}}
You have just won ${{value}}!
{{#if inCalifornia}}
Well, ${{taxedValue}}, after taxes.
{{/if}}

Dados el siguiente contexto de datos:

{
  name: "Chris",
  value: 10000,
  taxedValue: 10000 - (10000 * 0.4),
  inCalifornia: true
}

Producirá lo siguiente:

Hello Chris
You have just won $10000!
Well, $6000.0, after taxes.

Las plantillas semánticas favorecen la separación de la lógica frente a la presentación restringuiendo la capacidad para contener lógica dentro de las vistas. En lugar de los estamentos condicionales y los bucles, hay un pequño conjunto de etiquetas de plantilla. Durante el procesado, los datos son pasados a la plantilla y las etiquetas de la plantilla son reemplazadas con los valores apropiados. Estos datos son frecuentemente referenciados como el “context” (contexto).

Con Handlebear, el código de la aplicación genera un objeto de contexto antes de procesar la vista. Entonces se pasa ese objeto junto con la plantilla en el momento del procesado. Las plantillas de Derby pueden ser usadas de esta manera también. Sin embargo, como añadido a la búsqueda de objetos en un objeto de contexto, Derby asume que el modelo es parte del contexto. Incluso mejor, Derby es capaz de establecer automáticamente conexiones automáticas entre la vista y los objetos del model. Derby extiende ligeramente la sintaxis de Handlebear para poder ofrecer estas funcionalidades.

La otra principal diferencia entre las plantillas de Handlebear y Derby es que las plantillas de Derby deben ser HTML váludo primero. Handlebear es agnóstico al lenguaje y puede ser usado para compilar cualquier cosa desde HTML a código fuente o a documento. Sin embargo, las plantillas Derby son inicialmente validadas como HTML así que el validador puede entener como conectar datos a los objetos DOM subyacentes. Las etiquetas de la plantilla son únicamnete permitidas dentro de elementos o texto, dentro también de valores de atributos y dentro de los elementos subyacentes.

The other major difference between Handlebars and Derby templates is that Derby templates must be valid HTML first. Handlebars is language agnostic—it can be used to compile anything from HTML to source code to a document. However, Derby templates are first parsed as HTML so that the parser can understand how to bind data to the surrounding DOM objects. Template tags are only allowed within elements or text, within attribute values, and surrounding elements.

Emplazamientos de etiquetas de plantilla inváludos

<!-- INVALID: Within element names -->
<{{tagName}}>Bad boy!</{{tagName}}>

<!-- INVALID: Within attribute names -->
<b {{attrName}}="confused" {{booleanAttr}}>Bad boy!</b>

<!-- INVALID: Splitting an html tag -->
<b{{#if maybe}}>Bad boy!</b{{/}}>

<!-- INVALID: Splitting an element -->
{{#if maybe}}<b>{{/}}Bad boy!</b>

Emplazamientos válidos

<!-- Within an element -->
Let's go <b>{{activity}}</b>!

<!-- Within text -->
<b>Let's go {{activity}}!</b>

<!-- Within attribute values -->
<b style="color:{{displayColor}}">Let's go running!</b>

<!-- Surrounding one or more elements and text -->
{{#if maybe}}<b>Let's go dancing!</b>{{/}}

Espacio en blanco y cumplimento de HTML

Antes de la validación, todos los mentarios de HTML, espacio en blanco sobrante y saltos de línea son eliminados de las plantillas. Esto reduce el tamaño de la página, mantiene el código de la plantilla más legible cuando los espacios no son deseados entre elementos internos. El espacio en blanco al final de las líneas es mantenido, en caso de que un espacio sea deseado en la salida del HTML.

Los contenidos de las etiquetas <scrip> y <style> son pasados tal cual literalmente, excepto en lo relativo a la supresión del espacio en blanco. Esta eliminación del espacio en blanco puede ser deshabilitado dentro de un elemento añadiendo un atributo x-no-minify.

<script type="application/x-yaml" x-no-minify>
  firstName: Sam
  lastName : Reed
</script>

El analizador HTML de Derby debiera ser capaz de analizar cualquier HTML válido, incluídos los elementos que no necesitan etiquetas de cierre y atributos sin encomillar. Sin embargo, es recomendable que incluya siempre etiquetas de cerrado para elementos como <p> y <li> que podrían no necesitarlos. Las reglas acerca de cómo las etiquetas son cerradas son complejas y existen ciertos casos dónde las secciones de la plantilla podrían ser incluídos dentre un elemento inesperadamente.

Los valores del atributo HTML sólo necesitan ser entrecomillados si hay una cadena vacia o si contienen un espacio, signo de igual o un símbolo mayor que. Desde que las plantillas de Derby son analizadas con HTML primero, cualquiera de estos caracteres dentro de una etiqueta de la plantilla requieren de un atributo que esté escapado. Usar comillas en todos los valores de atributo está recomendado.

Debido a que entiende el contexto HTML, el escado de HTML en Derby es mucho menor que el de la mayoría de librerías de plantillas. Se sorprenderá de ver caracteres > y & sin escapar. Sólo es necesario escaparlos en ciertos contextos, y Derby sólo los escapa cuando es necesario. Si es escéptico, un validador de HTML5 detectará la mayoría de los fallos de escapado.

A través de estos documentos, la salida de las plantillas es mostrada con indentación y en múltiples líneas en aras de la legibilidad. Sin embargo el procesador de Derby no da como resultado ninguna indentación o saltos de línea. Como adición los valores de los atributos son entrecomillados, pero Derby únicamente incluye comillas en los valores de los atributos si son necesarios.

Variables

Las variables insertan un valor del contexto o del model con un nombre dado. Si el nombre no es encontrado, no será insertado nada. Los valores son escapados en HTML por defecto. La palabra reservada “unescaped” puede ser usada para insertar estos valores sin escapar.

Plantilla

<Body:>
  <p>{{name}}
  <p>{{age}}
  <p>{{location}}
  <p>{{unescaped location}}

Contexto

page.render({ name: 'Parker', location: '<b>500 ft</b> away' });

Resultado

<p>Parker
<p>
<p>&lt;b>500 ft&lt;/b> away
<p><b>500 ft</b> away

Secciones

Las secciones fijan el alcance del contexto para sus contenidos. En el caso de “if”, “unless”, “else if”, “else” y “each” pueden originar que sus contenidos sean condicionalmente procesados. “with” es usado únicamente para determinar el alcance y es procesado siempre. En Handlebear, las secciones comienzan y terminan con el mismo tipo de bloque, pero Derby requiere únicamente de una barra de terminación.

Como en Handlebear, los valores para falso incluyen todos los valores que hay para Javascript (false, null, undefined, 0, ‘’, and NaN) además de listas vacías ([]). Todos los demás valores dan verdad.

Plantilla

<Body:>
  <h1>
    {{#if visited}}
      Welcome back!
    {{else}}
      Welcome to the party!
    {{/}}
  </h1>
  <ul>
    {{#each users}}
      <li>{{name}}: {{motto}}
    {{/}}
  </ul>
  {{#unless hideFooter}}
    {{#with meta}}
      <small>Copyright &copy; {{year}} Party Like It's.</small>
    {{/}}
  {{/}}

Contexto

page.render({
  visited: true
, users: [
    { name: 'Billy', motto: "Shufflin', shufflin'" }
  , { name: 'Ringo', motto: "Make haste slowly." }
  ]
, meta: {
    year: 1999
  }
});

Resultado

<h1>Welcome back!</h1>
<ul>
  <li>Billy: Shufflin', shufflin'
  <li>Ringo: Make haste slowly
</ul>
<small>Copyright &copy; 1999 Party Like It's.</small>

Observe cómo en el ejemplo anterior la lista del contexto es llevado dentro de la sección “#each users”. De forma similar, las secciones fijan su alcance cuand se refieren al nombre de un contexto. En adición al alcance local, las etiquetas de la plantilla pueden referirse a cualquier cosa del ámbito del padre.

Plantilla

<Body:>
  {{#with users.jill}}
    I like <a href="{{link}}">{{favorite}}</a>.
  {{/}}

Contexto

page.render({
  users: {
    jill: {
      favorite: 'turtles'
    }
  }
, link: 'http://derbyjs.com/'
});

Resultado

I like <a href="http://derbyjs.com/">turtles</a>.

Interconexiones

La conexión modelo-vista es una relativamente reciente aproximación para añadir interacción dinámica a una página. Usa una sintaxis declarativa que disminuye de forma dramática la cantidad de código que puede fallar en la manipulación del DOM en una aplicación. Con el sistema de conexión de Derby raramente será necesario escribir código de DOM para nada.

Las plantillas de Derby declaran interconexiones usando corchetes simple en lugar de dobles. Si necesita un carácter corchete de apertura ({) en su salida HTML, use la entidad HTML &#123;

Las etiquetas de la plantilla conectadas muestran sus valores en el HTML incialmente procesado justo como en las etiquetas desconectadas. Como adición, ellas crean conexiones que actualizan la vista inmediatamente con cualquier cambio en el model. Si los conectores son usados para elementos que cambian cuando hay alguna interacción con el usuario -como en las entradas de un formulario- Derby actualizará el modelo automáticamente en cuanto sus valores cambien.

Cualquier etiqueta de la plantilla puede tener una conexión abierta, excepto para las que están dentro de un atributo “id”. El “id” debe ser fijad en el momento del procesado y no cambia hasta que el elemento sea reprocesado, ya es que usado para saber que elemento hay que actualizar.

Las interconexiones sólo funcionan para datos en el model. Los datos del contexto son pasado en el momento del procesado y no cambian dinámicamente. Si una etiqueta de conexión usar un nombre que no esté en objeto de contexto o en el modelo en el momento del procesado, todavía está conectado al modelo ya que el “path” puede ser definido más tarde.

Plantilla

<Body:>
  Holler: <input value="{message}"><h1>{message}</h1>

Contexto

model.set('message', 'Yo, dude.');
page.render();

Salida

Holler: <input value="Yo, dude." id="$0"><h1 id="$1">Yo, dude.</h1>

Observe que el valor en el modelo en el momento del procesado es insertado en el HTML como una etiqueta de plantilla sin conexión. Adicionalmente Derby establece un evento que está a la escucha para el elemento “input” que fija el valor de “message” sin que el usuario modifique el texto del elemento “input”. También pone escuchas tanto para el elemento “h1” como para “input” para actualizar los valores que se muestran si cambiara “message”.

Mejor que reprocesar la plantilla completamente cuando un valor cambia, sólo son actualizados los elementos individualmente. En el caso de “input”, su propiedad valor es fijada; En el caso de “h1”, su “innerHTML” es fijado. Dede que ninguno de estos elementos tienen un atributo “id” especificado en la plantilla, Derby automáticamente crea ids para ellos. Todos los ids de DOM creados por Derby comienzan con un símbolo de dolar ($). Si un elemento ya tiene un “id”, Derby lo usará en su lugar.

Derby asocia todos los eventos de las escuchas al DOM con un “id”, debido a que obtener los objetos por “id” es la operación DOM rápida, que hace el trato con eventos DOM más eficiente y que las eschas a ventos continúen funcionando incluso si otros “scripts” modifican el DOM inexperadamente. Derby internamente sigue los eventos vía “ids”, permitiéndo el procesado de páginas en el servidor y luego el reestablecimiento de las mismas escuchas a los eventos en el cliente eficientemente.

Si una etiqueta de plantilla conectada o sección no está totalmente contenida en un elemento HTML, Derby envolverá la plantilla poniendo marcadores de comentario antes y después de la localización de la plantilla. Los comentarios son usados ya que son válidos en cualquier sitio. Unos cuantos elementos HTML tienen restricciones que hace imposible envolver una plantilla en un elemento adicional. Por ejemplo los elementos <tr> sólo pueden contener elementos <td> y <th>.

Plantilla

<Body:>
  Welcome to our {adjective} city!

Contexto

model.set('adjective', 'funny');
page.render();

Salida

Welcome to our <!--$0-->funny<!--$$0--> city!

Componentes

Los componentes son similares a los parciales de Handlebear, pero mucho más poderosos. Como los parciales, heredan el ámbito del contexto del padre dónde sean usados. Adicionalmente, los componentes de Derby le permiten añadir argumentos adicionales tales como atributos y contenido HTML. Tanto para la legibilidad del código como para una más eficiente complicación de la plantilla, es mejor mantener las plantillas individuales relativamente simples y usar comoponentes para cada unidad significativa.

Cualquier plantilla de Derby puede ser usada como un componente. Éstas son incluídas como etiquetas HTML a medida con un espacio de nombre especial. Se puede acceder a los componente definidos dentro de una aplicación desde el nombre de espacio de la aplicación. En el futuro, será posible crear librerías de componentes con su propio espacio de nombres.

Plantilla

<Body:>
  <app:nav>

<nav:>
  <ul>{{each navItems}}<app:navItem>{{/}}</ul>

<navItem:>
  <li><a href="{{link}}">{{title}}</a></li>

Contexto

page.render({
  navItems: [
    { title: 'Home', link '/' }
  , { title: 'About', link '/about' }
  , { title: 'Contact us', link '/contact' }
  ]
});

Salida

<ul>
  <li><a href="/">Home</a></li>
  <li><a href="/about">About</a></li>
  <li><a href="/contact">Contact us</a></li>
</ul>

Los valores literales o de variables pueden ser pasados a los componentes. Estos atributos del componente están disponibles a través de etiquetas de plantilla “macro”, que tienen triple corchete. Las etiquetas de plantilla marco unicamente referencian nombres de atributo del componente, y las etiquetas de plantilla normales (con uno o dos corchetes) sólo referencian nombres de un modelo o de un objeto contexto. Es posible usar etiquetas de plantilla tipo macro para procesamiento condicional de cualquier contenido HTML o de otras etiquetas de plantilla.

<Body:>
  <h1><app:greeting message="Hello" to="{_user.name}"></h1>

<greeting:>
  {{{#if to}}}
    {{{message}}}, {{{to}}}!
  {{{else}}}
    {{{message}}}!
  {{{/}}}

produce la misma salida que:

<Body:>
  <h1>
    {#if _user.name}
      Hello, {_user.name}!
    {else}
      Hello!
    {/}
  </h1>

Por defecto, todos los componentes son elementos HTML vacíos. Esto significa que sólo deben tener una etiqueta de apertura y ninguna de cierre, justo como la de los elementos <img> y <br>. Un componente puede ser definido como no vacío, lo que significa que puede tener tanto etiqueta de apertura y de cierre. Los componentes no vacios tienen acceso a una macro de contenido especial que hace posible pasar el contenido HTML al componente. Por ejemplo:

<Body:>
  Welcome!
  <app:fancyButton>
<b>Click me {{#if isUrgent}}now!{{/}}</b>
  </app:fancyButton>

<fancyButton: nonvoid>
  <button class="fancy">
    {{{content}}}
  </button>

produce la misma salida que:

<Body:>
  Welcome!
  <button class="fancy">
    <b>Click me {{#if isUrgent}}now!{{/}}</b>
  </button>

Rutas de modelos relativas y alias

Para los ítems en el objeto de contexto, los objetos del ámbido del padre pueden ser todavía referenciados diréctamente desde las secciones interiores. Sin embargo, las conexiones son configuradas cuando las plantillas son compiladas inicialmente, y los objetos definidos en el modelo pueden cambiar. De esta manera, las rutas del modelo deben referenciar a toda costa la ruta completa de la localización dentro de la plantilla.

Aún, una plantilla podría necesitar definir cómo cada ítem en una lista debiera ser procesado junto a las conexiones para esos ítems. En este caso, las rutas del modelo relativas pueden ser usadas. Las rutas relativas al ámbito actual comienzan con un punto (.).

Plantilla

<Body:>
  <ul>{#each items}<app:item>{/}</ul>

<item:>
  <li><a href="{{url}}">{.name}</a>: ${.price}

Contexto

model.set('items', [
  { name: 'Cool can', price: 5.99, url: '/p/0' }
, { name: 'Fun fin', price: 10.99, url: '/p/1' }
, { name: 'Bam bot', price: 24.95, url: '/p/2' }
]);
page.render();

Salida

<ul id="$0">
  <li><a href="/p/0" id="$1">Cool can</a>: $<!--$2-->5.99<!--$$2-->
  <li><a href="/p/1" id="$3">Fun fin</a>: $<!--$4-->10.99<!--$$4-->
  <li><a href="/p/2" id="$5">Bam bot</a>: $<!--$6-->24.95<!--$$6-->
</ul>

En el ejemplo anterior, observe que la url no está conectada, y que no comienza con un punto. Desde que el que contexto será ligado al ítem de la lista en el momento del procesado, esto procesará el valor correctamente, pero no se actualizará si el valor cambia. “.name” y “.price” comienzan con un punto, debido a ello están ligados a las rutas del modelo relativas al ítem que está siendo procesado. Siempre que el nombre o el precio de un ítem cambie, los campos apropiados serán actualizados en tiempo real. Además, la lista entera está ligada. Si un nuevo ítem es añadido, eliminado o reordenado, la lista será actualizada en tiempo real.

Los alias a un ámbito específico pueden ser definidos, habilitanto las referencias de la ruta del modelo relativa dentro de secciones anidadas. Los alias comienzan con dos puntos (:), y pueden ser definidos al final de una etiqueta de sección la palabra reservada “as”.

Plantilla

<Body:>
  <h2>Toys in use:</h2>
  {#each toys as :toy}
    {#if :toy.inUse}
      <app:toyStatus>
    {/}
  {/}
  <h2>All toys:</h2>
  {each toys as :toy}
    <app:toyStatus>
  {/}

<toyStatus:>
  <p>{{name}} on the {:toy.location}</p>

Contexto

model.set('toys', [
  { name: 'Ball', location: 'floor', inUse: true }
, { name: 'Blocks', location: 'shelf' }
, { name: 'Truck', location: 'shelf' }
]);
page.render();

Salida

<h2>Toys in use:</h2>
<!--$0-->
  <!--$1--><p>Ball on the <!--$2-->floor<!--$$2--></p><!--$$1-->
  <!--$3--><!--$$3-->
  <!--$4--><!--$$4-->
<!--$$0-->
<h2>All toys:</h2>
<!--$5-->
  <p>Ball on the <!--$6-->floor<!--$$6--></p>
  <p>Blocks on the <!--$7-->shelf<!--$$7--></p>
  <p>Truck on the <!--$8-->shelf<!--$$8--></p>
<!--$$5-->

Funciones de apoyo a las vistas

Derby sigue la aproximación de plantillas semánticas de Handlebars y Mustache, lo que ayuda a reducir la cantidad de lógica en las plantillas. Sin embargo, debido al enlazado automático de Derby entre la vista y el model, puede ser útil tener valores calculados que son creados sólo por la vista.

Las funciones de apoyo a las vistas son reactivas, y son evaluadas tanto en el procesado con con cualquier cambio en “inputs” enlazados. Adicionalmente puede funcionar tanto como “getters” y como “setters”. Esto es especialmente útil cuando enlazamos a elementos de un formulario, como las opciones seleccionadas o en radio botones.

Controlador

// Remove all whitespace from a string
view.fn('unspace', function(value) {
  return value && value.replace(/\s/g, '')
})

Plantilla

{{#with home}}
  <h1 style="color:{unspace(.title.color)}">
    Welcome in {.title.color}!
  </h1>
  <select>
    {#each .colors}
      <option selected="{equal(.name, home.title.color)}">
        {{.name}}
      </option>
    {/}
  </select>
{{/}}

Hay dos funciones de apoyo por defecto, “equal” y “not”, que siempre están disponibles. Es también posible definir funciones de apoyo a las vistas a medida, como “unspace” en el ejemplo anterior. La funciones “equal” y “not” pueden actuar tanto como “getters” que como “setters”. En este ejemplo, cuando la página se procesa, la opción con un nombre igual al valor de “home.title.color” tendrá un attributo de selección y las otras no. Cuando el usuario seleccione una opción diferente desde el desplegable, “home.title.color” será puesto al valor de la opción que esté ahora seleccionada.

Observer que las funciones de apoyo tienen la suficiente flexibilidad como para introducir lógica en las plantillas, lo que es considerado una mala práxis. Por ejemplo:

<!-- WARNING: Not recommended -->
{#if lessThan(5, _user.score)}
  <b>Let's try that again!</b>
{/}

<!-- A little better: -->
{#if lessThan(lowScoreCutoff, _user.score)}
  <b>Let's try that again!</b>
{/}

<!-- Preferred: -->
{#if isLowScore(_user.score)}
  <b>Let's try that again!</b>
{/}

El primer ejemplo es básicamente poner la lógica interna dentro de la plantilla. Es no está recomendado ya que las reglas de negocio cambian (como con el cambio de puntación que ahora sea 20 la nueva puntuación más baja), las plantillas no tendrían que ser modificadas. Es mejor definir constantes en el código del controlador y guardarlas en el modelo o pasarlas en los datos del contexto. Mejor aún es definir una función específica para cada propósito, que determine que la puntuación baja pueda cambiar completamente a una función con una entrada adicional y no un simple corte más.

Cuando defina apoyos a la vista, intente evitar las funciones de comparación básicas como “lessThan”. Aunque sea tentador, estas funciones tienden a producir código menos mantenible en el tiempo.

Extensiones HTML


Derby viene con unas pocas extensiones al HTML para hacer más sencillo el enlazado entre modelos y vistas.

Los atributos a medida usados durante el analizado de la plantilla comienzan con el prefijo “x-” para evitar conflictos con futuras extensiones del HTML. Observe que Derby usa estos prefijos en lugar de “data-” ya que este prefijo está pensado para atributos de datos a medida que están incluídos en el DOM. Derby elimina los atributos “x-” cuando lo analiza, y el HTML generado no incluye estos atributos no estandar.

Enlazando eventos del DOM

El atributo “x-bind” puede ser añadido a cualquier elemento HTML para enlazar uno o más eventos del DOM a una función controlador por nombre. La función enlazada debe estar exportada en la aplicación. Las funciones enlazadas son pasadas al objeto del evento original, el elemento en el que el atributo “x-bind” fue puesto, y una función next() que puede ser llamada para continuar el encadenamiento de eventos.

Los navegadores emiten eventos DOM sobre el objetivo y entoces cada uno de sus nodos padres lo pasan arriba hacie el elemento raiz del documento , a menos que un controlador llame a e.stopPropagation(). Derby ejecuta un burbujeo de eventos más como la función del controlador de rutas para el elemento objetivo o el primer elemento padre enlazado es llamado y el burbujeo de eventos para. El controlador puede llamar a la función next() para continuar el burbujeo al siguiente evento.

El atributo x-capture puede ser usado si un controlador debiera siempre ser llamado cual sea el elemeno hijo que emite el evento dado. Esto puede ser específicamente útil en el elemento raiz para controlar eventos como mousemove.

Si el evento “click” está conectado con una etiqueta <a> sin un atributo href, Derby añadirá los atributos href=“#” y onclick=“return false” automáticamente. Si el evento enviar está conectado con un elemento <form>, onsubmit=“return false” será añadido para prevenir una acción de redirección por defecto.

Las conexiones de eventos pueden también retrasar la ejecución de un “callback” después de un “timeout”. Esto puede ser útil cuando manejamos eventos como psate, que es disparadon antes de que el nuevo contenido sea insertado. El valor del retraso, en milisegundos, es includo después del nombre del evento, algo como x-bind=“paste/:0 afterPaste” =“paste/0: afterPaste”.

Internamente Deby sólo conectecta cada tipo de evento una vez al documento y ejecuta la delegación del evento. Esto usa las “ids” del elemento para mantener el seguimiento de qué elementos deberían ser conectados a qué eventos. De este modo, como con las conexiones modelo-vista, Derby añadirá un atributo “id” generado automáticamente a un elemento que use x-bind si éste no tiene uno ya.

Plantilla

<Root:>
  <!-- always called regardless of event bubbling -->
  <html x-capture="mousemove: move">

<Body:>
  <button x-bind="click: start">Start</button>

  <!-- href="#" and onclick="return false" will be added -->
  <a x-bind="click: cancel">Cancel</a>

  <!-- onsubmit="return false" will be added -->
  <form x-bind="submit: search"> </form>

  <!-- Multiple events on one element -->
  <img src="example.png" x-bind="mousedown: down, mouseup: up">

  <!-- Wait for timeout of 50ms before calling back -->
  <input x-bind="paste/50: afterPaste">

Frecuentemente es útil relacionar un elemento DOM a la ruta del model que fue usado para procesar el ítem. Por ejemplo, uno podría querer eliminar un ítem de una lista cuando un botón es presionado. Derby extiende el método model.at() para aceptar un nodo DOM o un objeto JQuery. Cuando emitimos uno de este, el método devolverá un modelo con ámbido que será al del contexto de la ruta conectada más cercana en la plantilla.

Plantilla

<Body:>
  <ul>
    {#each _users}
      <li x-bind="click: upcase">{.name}</li>
    {/}
  </ul>

App

exports.upcase = function (e, el, next) {
  user = model.at(el);

  // Logs something like "_users.3"
  console.log(user.path());

  user.set('name', user.get('name').toUpperCase());
}

Atributos booleanos

En HYML, los atributos booleanos son verdaderos cuando ellos están incluídos y falsos cuando están excluídos. Desde que Derby únicamente permite etiquetas de plantilla dentro de los valores del atributo, esto hace difícil el conectar estos atributos al modelo. Sin embargo, Debry usa una sintaxis ligeramente modificada que funciona de forma más natural con la sintaxis de la plantilla para los atributos “checked”, “selected” y “disabled”, que gustosamente estarán conectados a los datos.

Los valores del atributo booleano puden ser invertidos vía la función de apoyo ya inclída “not()”.

<Body:>
  <!-- Outputs:
    <input type="checkbox" checked>
    - or -
    <input type="checkbox">
  -->
  <input type="checkbox" checked="{{active}}">

  <!-- Bound to model -->
  <input type="checkbox" checked="{active}">

  <!-- Inverted value -->
  <input type="checkbox" disabled="{not(active)}">

Elementos de formulario

Conectar los atributos seleccionados de los elementos <option> en un <select> es difícil, ya que el evento de cambio sólo es disparado en el elemeno <select>, y el atributo seleccionado debe ser emplazado en las opciones. Sin embargo Derby distribuye el evento de cambio de cada uno de los hijos de un elemento “select”, llevando el evento a cada una de las opción también. Esto hace posible el conectar el estado selecionado co cada una de las opciones.

Para los botones de radio, los eventos de cambio sólo son disparado en el elemento que es pulsado. Sin embargo, pulsar un botón de radio deseleciona el valor de todos los demás botones radio que tengan el mismo nombre. De este modo Derby también emite el evento de cambio sobre los otros elementos con el mismo nombre así que cada atributo para seleccionar en el botón de radio puede estar conectado.

Rendimiento

Mientras que el rendimiento del procesamiento en Derby tiene que ser medido y optimiazo, su arquitectura le permitirá hacer la mayoría de las aproximaciones para el procesamiento de aplicaciones web de uso real.

Cuando trozos grandes de una página requieran actualización, el procesamiento de HTML y entonces la actualización del innerHYML de un elemento es la aproximación más rápida. Sin embargo, cuando ocurren pequeños cambios en un ítem en una plantilla, el precesado de toda la plantilla y reemplazar una sección entera del DOM es mucho mas lento que simplemente actualizar una propiedad única o un innerHTML de un único elemento.

Además, el procesamiento de ciertas secciones o de una página entera en la parte de cliente ralentiza dramáticamente las cargas de la página. Incluso en un procesado sólo en cliente extremandamente rápido origina que el navegador espere por la página para cargar el “script” (mayormente vía una petición adicional), interpretar el “script”, procesar la plantilla y actualizar el DOM antes de que se pueda comenzar a hacer la distribucón del contenido HTML.

La arquitectura de Deby optimiza el tiempo de carga de la página inicialmente para reprocesar las secciones o la página entera en la parte cliente, y la actualización de elementos individuales en tiempo real. Esto hace que sea fácil para los diseñadores y desarrolladores crear vistas de la aplicación con plantillas basadas en HTML y les otorga respuestas instantáneas con las conexiones modelo-vista.

Hojas de estilo

Derby usa Styulos para compilar automáticamente e incluir los estilos de cada página. Stylus extiende el CSS con variables, “mixins”, funciones y otras características estupendas. Soporta la sintaxis de estilos CSS intercambiables con una sintaxis basada en espacios mínima.

Derby también incluye Nib, que añade un número de “mixins” de CSS3 convenientes a Stylus. Nib se cuida de añadir los prefijos de los navegadores, hace que los gradientes CSS sean mucho más fáciles y un montón de características útiles más.

Stylus necesita que los ficheros terminen en una extensión .styl. Soporta la importación de otros ficheros, incluyendo el soporte para los ficheros index.styl. Desde Node.js, las plantillas de Derby y Stylus soportan todas convenciones similares para la importación de ficheros, es fácil usar la misma estructura de directorios para los ficheros análogos en los directorios lib/src, views y styles.

Derby incluye los CSS compilados en el principio de cada página. Tener el CSS dentro siempre disminuye el tiempo de carga, y la importación de Stylus hace fácil el trozear estilos compartidos en ficheros que se incluyen en las páginas apropiadas. Observer, sin embargo que no es lo óptimo incluir gran cantidad de CSS, como con imágenes codificadas en una URI de datos grandes, en el principio de la página. Las imágenes grandes se cargan mejor como ficheros seperados o incluírlos al final de la página, así el resto de la página puede ser mostrado primero.

Procesando

Las vistas son procesadas en respuesto a las rutas. la mayoría de las rutas debieran estar definidas dentro de una aplicación, así que éstas puedan ser controladas tanto en el cliente como en el servidor. Las vistas pueden también ser procesadas en respuesta a rutas del servidor únicamente.

En cada método de procesado, el espacio de nombres, modelo, contexto y los argumentos de estados pueden estar en cualquier orden u omitidos.

page.render ( [namespace], [context], [status] )

namespace: (opcional) Un espacio de nombre con lo que hay que procesar. Normalmente es el nombre de una página o el tipo de una página.

context: (opcional) Objeto que especifica objetos de contexto adicional para usar en el procesado de plantillas.

status: (opcional) Número especificando el código de estado de HTTP. Es 200 por defecto. No tiene efecto en el procesado del cliente.

app.view

Derby añade un objeto app.view para la creación y el procesado de vistas.

view.make ( name, template )

name: Nombre de la plantilla

template: Una cadena de caracteres que contiene la plantilla de Derby. Observe que esto debería tener únicamente el contenido de la plantilla, y no debería tener el elemnento nombre dela plantilla, como un <Body:> en el inicio.

Las aplicaciones normalmente tendrían que colocar todas las plantillas en un fichero plantilla en la carpeta views en lugar de llamar a view.make() directamente. Sin embargo, las plantillas pueden ser añadidas a una aplicación de esta forma también.

Llamar a view.make() sólo procesa la plantilla; no la incluye en el fichero “script” externo de forma separada. Además de esto, puede ser llamado de nuevo desde el cliente cuando la aplicación carga.

view.inline ( fn )

fn: Función que está incluída en la pagina y llamada inmediatamente cuando la página carga.

Este método está pensado para su uso en el servidor y no afecta cuando es llamado desde el navegador.

Los “scripts” deberían incluirse en la página si se necesitaran para procesar adecuadamente la página. Por ejemplo, un “script” podría ajustar la organización de la pagina basándose en el tamaño de la ventana, o podría poner el “autofocus” de un símbolo en una caja en navegadores que no soporten el atributo “autofocus” de HTML5.

Normalemente, es preferible colocar estos “scripts” en un fichero separado llamado inline.js en el mismo directorio de la aplicación. Este fichero será automáticamente incrustado cuando la aplicación es creada. Llamando a view.inline() diréctamente hace lo mismo, pero es redundante enviar el “script” incrustrado e incluirlo en el fichero “script” externo de la aplicación.

Controladores


Los controladores en Derby están definidos en el fichero de “script” que llama a derby.createApp(). Normalmente los controladores están localizados en \lib\app_name\index.js o en src\app_name\index.coffee. Ver la parte de “Creando aplicaciones”.

Los controladores incluyen rutas, manejadores de vento de usuario y la lógica de la aplicación. Debido a que Derby provee de conectores modelo-vista y sincroniza los modelos automáticamente, la manipulación directa del DOM y el envío de mensajes de forma manuel al servidor debiera ser raramente necesaria.

Rutas

Las rutas mapean patrones de URL a acciones. Las rutas en Derby está impulsado por Expres, que es similar a Sinatra. Dentro de las aplicaciones las rutas son definidas a través de los métodos “get”, “post”, “put” y “del” de la aplicación creada por derby.createApp().

app.get ( pattern, callback(page, model, params, next) )

app.post ( pattern, callback(page, model, params, next) )

app.put ( pattern, callback(page, model, params, next) )

app.del ( pattern, callback(page, model, params, next) )

pattern: Carácteres conteniendo un literal de URL, un patrón de ruta de Express o una expresión regular. Mire la documentación de Express para obtener más información.

callback: Función llamada cuando un petición de URL recibida concuerda tanto en el método apropiado de HTTP como con el patrón. Observer que esta función es llamada tando por el servidor como por el cliente.

page: Objeto con los métodos page.render() y page.redirect(). Todas las rutas de la aplicación debieran llamar a uno de estos dos métodos o pasar el control llamando a next().

model: objeto del modelo de Derby

params: Un objeto que contiene los parámetros de la URL que coincidieron. La url, la consulta y las propiedades del "body" están disponibles normalmente a través de "req" y son añadidas a este objeto.

next: Una función que puede ser llamada para pasar el control a la siguiente ruta que coincida. Si esto es llamado en el cliente, el control será pasado a la siguiente ruta definida en la aplicación. Si ninguna otra ruta concuerda en la misma aplicación, entoces seguirá a través de una petición al servidor.

 

page.redirect ( url, [status] )

url: Destiono de la redirección. Como en Exprees, también puede ser la cadena ‘home’ (que redirige a ’/’) o ‘back’ (que vuelve a la anterior URL).

status: (opcional) Número definiendo el código de estado HTTP. Por defecto es 302 en el servidor. No afecta en el cliente.

Al contrario de Expreess, que da acceso directo a los objetos “req” y “res” creados por los servidores HTTP de Node, Derby devuelve objetos “page”, “model” y “params”. Estos tienen la misma interfaz en el cliente y en el servidor, así que los controladores de ruta pueden ser ejecutados en ambos entornos.

Express es usado diréctamente en el servidor. En el cliente, Derby incluye el módulo de concordandia de ruta de Express. Cuando un enlace es pulsado o un formulario es enviado, Derby intenta primero procesar la nueva URL en el cliente.

Derby también puede capturar envíos de formularios en la parte de cliente. Provee soporte para los métodos “post”, “put” y “del” de HTTP usando la misma aproximación de introducción de campos ocultos en el formulario que hace Express.

Rutas transitorias

En el cliente existen un número de situaciones donde tiene sentido actualizar la URL pero en la que sólo una parte de la UI (Interfaz de Usuario) tiene que actualizarse. Por ejemplo, uno podría querer mostrar un “lightbox” encima de una página dada o actualizar sólo el contenido de un área de una página y no lo que hay alrededor.

En estos caso, las rutas transitoras dan una solución más flexible y eficiente para actualizar la página. En el servidor o desde una página diferente, llamar a una ruta transitoria produce el procesado de la página completa como en una ruta normal. Sin embargo, cuando viene desde la misma página en el cliente, una ruta transitoria sólo ejecuta el código para actualizar el modelo y los conectores de la vista apropiados.

Las rutas transitorias hacen posible el uso de animaciones CSS, dede que sólo los elementos relevantes son actualizados. Si la página completa fuera reprocesada los actuales elementos HTML debieran ser reemplazados con los nuevos. Entonces la animación CSS no sería capaz de saber cómo los estilos de un elemento dado han cambiado.

Las rutas transitorias usan los mismos métodos “get”, “post”, “put” y “del” pero los toman desde y hacia el patrón tanto como un llamada anterior y posterior. Desde que las rutas transitorias no pueden procesar la página entera sino actualizar datos en el modelo, sus llamadas no tienen el argumento “page”.

get('/photo/:id', function (page, model, params, next) {
  // Normal page rendering code goes here
  ...

  // Any state set in the `forward` route callback should be
  // reset in the main route, in case the user navigates to
  // a different page and then back to this page directly
  model.del('_showLightbox');

  // The transitional route callback will execute right before
  // the render method is called
  page.render();
})

get({from: '/photo/:id', to: '/photo/:id/lightbox'}, {
  forward: function (model, params, next) {
    model.set('_showLightbox', true);
  }
, back: function (model, params, next) {
    model.del('_showLightbox');
  }
});

Las rutas transitorias permiten cadenas de literales y rutas con patros con parámetros con nombre como “:id” y capturas con comodines como “*”. Sin embargo no funcionan las expresiones regulares arbitrarias.

Cuando una ruta es requerida en el servidor o desde una página diferente, el enrutador primero actía como el “from” de la ruta que fue llamada. Reemplaza cuaquier parámetro nombrado y los basados en comodines en sus equivalentes en la ruta “from” y entonces hace una búsqueda para el “from” de la ruta.

Después de eso, la ruta original es ejecutada hace la llamada page.render(). A continuación la llamada “forward” de la ruta transitoria es llamada. Esto simula la navegación por primera vez a la ruta original y entonces la transición a la nueva ruta. Finalmente page.render() es ejecutado.

En el ejemplo de arriba, las posibles transiciones debieran ser:

Starting from Going to Effect
From server or another page /photo/42 Run the original route normally
/photo/42  /photo/42/lightbox Run the forward callback only
/photo/42/lightbox /photo/42 Run the back callback only
From server or another page /photo/42/lightbox Run the original route for /photo/42 up to page.render(), then the forward callback, then render

Historial

Para la mayor parte, la actualización en la parte del cliente en la URL debiera ser hecha con enlaces normales de HTML. La acción por defecto de pedir una nueva página desde el servidor es cancelada automáticamente si la aplicación tiene una ruta que coinciada con la nueva URL.

Para actualizar la URL después de cualquier acción diferente a pulsar en un enlace los “scripts” pueden llamar a métodos en view.history. Por ejempli, una aplicación podría actualizar la URL cuando el usuario baja en el contenido y la página carga más contenido desde una página listada.

view.history.push ( url, [render], [state], [e] )

view.history.replace ( url, [render], [state], [e] )

url: Nueva URL que será puesta en la actual ventana

render: (opcional) Reprocesa la página después de actualizar la página si está puesto a "true". Por defecto lo está.

state: (opcional) Un objeto estado es pasado a los métodos window.history.pushState o window.history.replaceState. Las propiedades $render y $method son añadidas a este objeto para su uso interno cuando tenga que tratar eventos post-estado.

e: (optional) Un objeto evento al cual el método stopPropogation será llamado si la URL puede ser procesada desde el lado del cliente.

Los métodos de Derby history.push e history.replace actualizarán la URL a través de window.history.pushState o window.history.replaceState respectivamente. Si un navegador no tiene estos métodos se actualizará window.location y el procesado en el servidor de la nueva URL. El método “push” es usado para actualizar la URL y crear una nueva entrada en el historial del navegador de atrás/adelante. El método “replace” es usado para actualizar únicamente la URL sin crear la entrada en el historial atrás/adelante.

view.history.refresh ( )

Reprocesa la actual URL en la parte del cliente.

For convenience, the navigational methods of window.history can also be called on view.history.

view.history.back ( )

Llama a window.history.back(), lo que es equivalente a pulsar el botón de ir atrás del navegador

 

view.history.forward ( )

Llama a window.history.forward(), lo que es equivalente a pulsar el botón de ir adelante del navegador

 

view.history.go ( i )

Llama a window.history.go()

i: Un entero que especifica el número de veces que iremos atrás o adelante. Navega hace atrás si es negativo y hacia adelante si es positivo

Eventos de usuario

Derby automatiza una gran parte del control de eventos de usuario a través del conector modelo-vista. Esto debería ser usado por cualquiera de los datos que está atado directamente a un atributo de un elemento o al contenido HTML. Por ejemplo, como usuarios interactuando con un <input>, el valor y las propiedades de selección serán actualizados. Además, el atributo seleccionado de los elementos de <option> y ediciones al innerHTML de elementos de contenido editables actualizarán los objetos del model relacionado.

Para otros tipos de eventos de usuario, como los click o los arrastres, el atributo x-bind de Derby puede ser usado para atar eventos DOM a un elemento específico a una función de llamada en el controlador. Estas funciones deben ser exportadas en el módulo de la aplicación.

Incluso si el código del controlador está respondiendo a un evento del DOM, debería unicamente actualizar la vista indirectamente por la manipulación de los datos en el modelo. Desde que las vistas están conectadas a los datos del modelo, la vista se actualizará automáticamente cuando los datos correctos sean fijados. Mientras que esta manera de escribir código podría tomar algo de tiempo para acostumbrarse, es mucho más simple y menos dado a error.

Eventos del modelo

Los eventos del modelo son emitidos en respuesta a cambios en el modelo. Estos podrían sern usados diréctamente para actualizar otros ítems del modelo y las vistas resultantes, como la actualización dl contandor de ítems en una lista cada vez que ésta es modificada.

Lógica de la aplicación

La lógica de la aplicación se ejecuta en respuesta a rutas, eventos de usuario y eventos del modelo. Código que responde a los eventos de usuario y a los eventos del modelo deberían ser puestos dentro de la retrollamada app.ready(). Esto expone el objeto del modelo a el cliente y se aegura de que el código sólo es ejecutado en el cliente.

app.ready ( callback(model) )

callback: Función llamada tan pronto como la aplicación Derby sea cargada en el cliente. Observer que cualquier código dentro de esta retrollamada es únicamente ejecutada en el cliente y no en el servidor.

model: El objeto del modelo de Derby para un cliente dado

La lógica de la aplicación debiera estar escrita para compartir tanto como sea posible entre servidores y cliente. Por razones de segurid para el acceso directo a los servicios de “backend” debiera ser necesario realizar algunas funciones únicamente en los servidores. Sin embargo, poner tanto código como sea posible en un lugar que permita a las aplicaciones de Derby ser extremadante sensible y funcionar desconectad por defecto.

Modelos


Los modelos de Derby están impulsados por Racer, un motor de sincronización de modelos en tiempo real. Racer permite a multiples usuarios interacturar con los mismos datos a través de una sofisticada detección de conflictos y algoritmos para resolverlos. Al mismo tiempo permite un acceso simple al objeto y a la interfaz de eventos para escribir la lógica de la aplicación.

Introducción a Racer

En el servidor, Racer crea un contenedor que administra las actualizaciones de los datos. Es posible manipular directamente los datos vía métodos asíncronos en el contenedor,similar a interactuar con una base de datos. Los contenedores también crear objetos del modelo que muestran una interfaz síncrona que permite interactuar diréctamente con objetos.

Los modelos mantienen su propia copia de un subconjunto del estado global. Este subconjunto es definido a través de suscripciones a ciertas rutas. Los modelos ejecutan operaciones independientemente y automáticmanete sincronizan su estado con el contenedor asociado sobre Socket.IO.

Cuando los modelos son modificados inmendiatamente reflejarán los cambios. Detrás, Racer transforma las operaciones en transacciones que son enviadas al servidor y son aplicadas o descartadas. Si las tranasacciones son aceptadas, son enviadas a todos los demás clientes suscritos a los mismos datos. Esta aproximación optimista trae interaccion inmediata para el usuario, hace la escritura de la lógia de la aplicación más sencilla y posibilita a Racer trabajar sin conexión.

Los métodos del mutador del Modelo nos dan retrollamadas que son invocadas despés del éxito o fallo de una transacción. Estas retrollamadas pueden ser usadas para proveer interfaces de usuario para la resolución de conflictos específicas a la aplicación. Los modelos también emiten eventos cuando sus contenidos son actualizados, que Derby usa para actualizar la vista en tiempo real.

Resolución de conflictos

Actualmente, Racer por defecto aplica todas las transacciones en el orden que se reciven, por ejemplo, el ultimo-en-escribir-gana. Para los clientes conectados en tiempo real este será normalmente el comportamiento esperado. Sin embargo, los usuarios desconectados que están interactuando con los mismos datos producirán conflictos en las actualizaciones que conducirán a comportamientos inesperados.

Por tanto Racer admite la resolución de conflictos a través del las técnicas Software Transactional Memory (STM), Operational Transformation (OT) y Diff-match-patch.

Estas características no están totalmente implementadas todavía, pero las demos de Racer muestran ejemplos preliminares de DTM y de OT. “Letters” usa el modo STM para detectar automáticamente conflictos cuando diferentes ususarios mueven las mismas letras al mismo tiempo. Pad usa OT para obtener un editor de textos colaborativo minimalista.

Para ejecutar estos algoritmos, Racer guarda un diaro con todas las transacciones. Cuando nuevas transacciones llegan, su rutas y versiones son comparadas con las transacciones que ya han sido incluídas en el diario. STMP acepta una transacción si no tiene ningún conflicto con cualquiera de las otras operaciones en el mismo path. STM funciona bien cuando los cambios tienen que ser totalmente aceptados o rechazado, como en la actualización de un nombre de usuario. En contraste, OT está diseñado para situaciones como la de edición de texto colaborativa, dónde los cambios debieran de estar mezclados juntos. En OT las transacciones son modificadas para funcionar juntas en cualquier orden, en lugar de ser rechazadas.

Creando contenedores

Por defecto el servidor creado por el generador de proyectos de Derby creará un contenedor asociado con un aplicación. Derby usarán entonces ese contenedor para crear los modelos cuando se invoquen las rutas de la aplicación.

store = app.createStore ( options )

store = derby.createStore ( apps..., options )

options: Un objeto que configura el contenedor

store: Devuelve un objeto contenedor de Racer

apps: El método derby.createStore() acepta una o más aplicaciones Derby como argumentos. Cada una de estas aplicaciones es asociada con el contenedor creado.

Normalmente, un proyecto tendrá sólo una tienda, incluso si tiene múltiples aplicaciones. Es posible tener múltiples contenedores, aunque una aplicación sólo puede ser asociada con un contenedor.

Opciones de configuración

Normalmente una opción de escucha tiene que ser especificada, la cual es usada para configurar Socket.IO. Esta opción puede ser un servidor de Express o el número de un puerto. Además de la escuche, un argumento con el espacio de nombres tiene que ser dado para configurar Socket.IO bajo un espacio de nombres. Vea “Restringiéndose usted mismo a un espacio de nombres” en la guía de Socket.IO.

De forma alternativa, las opciones pueden especificar sockets y socketUri si el objeto de sockets de Socket.IO ya está creado. Esta opción de sockets debiera ser el objeto devuelto por io.listen() o io.listen().if() de Socket.IO. Más información acerca de como configurar Racer para ejecutarse con varios PubSub, base de datos y adaptadores de diarios estarán pronto.

Persistencia

Los modelos en Derby son mantenidos por Racer. Por defecto, Racer guarda los datos en memoria así que nada será persistenten entre los reinicios del servidor. Esto es una manera sencilla de comenzar y prototipar una aplicación. Añadiendo persistencia meramente requiere incluir un adaptador a una base de datos dada. Esto es configurado cuando se crea el contenedor:

derby.use(require('racer-db-mongo'));

app.createStore({
  listen:  server
, db:      {type: 'Mongo', uri: 'mongodb://localhost/database'}
});

Cada uno de los adaptadores de la base de datos están separados en módulos de npm. Como con todos los módulos de npm, cada adaptador debiera de añadirse como una dependencia dentro del fichero package.json del proyecto. Después de añadir una nueva dependencia asegurésese de ejecutar de nuevo “$ npm install”

Las rutas de Racer son traducidas en colecciones en la base de datos y documentos usando un mapeo natural:

collection.documentId.document

Todas las rutas sincronizadas (todo lo que no comienze con un guión bajo) debe seguir esta convención. En otras palabras, todos los datos del model guardados en en los dos segementos de ruta debieran ser un objeto y no una cadena de caracteres, un número u otro tipo de primitiva.

// Ejemplos:
model.set('todos.id_0.completed', true);
model.set('rooms.lobby.messages.5.text', 'Call me');
model.set('meta', {
  app: {
    title: 'Hi there'
  , author: 'Erik Mathers'
  }
});

// The first and second segments in root paths must be objects
model.set('title', 'Hi there');      // WARNING INVALID
model.set('app.title', 'Hi there');  // WARNING INVALID

// However, any type may be stored at any private path, which
// starts with an underscore and is not synced back to the server
model.set('_title', 'Hi there');     // OK

El id del documento (el segundo segmento de la ruta) es automáticamente añadido como la propiedad id del documento, así que puede ser recuperado desde el contenedor de datos.

model.set('meta', {
  app: {
    title: 'Hi there'
  , author: 'Erik Mathers'
  }
});
// Logs: {id: 'app', title: 'Hi there', author: 'Erik Mathers'}
console.log(model.get('meta.app'));

Creando modelos

Derby proporciona un modelo cuando son llamadas las rutas de la aplicación. En el servidor crea un modelo vacío desde el contenedor asociado con la aplicación. Cuando el servidor procesa la página, el modelo es serializado. Entonces es reiniciado en el mismo estado en el cliente. Este objeto modelo es pasado a la retrollamda app.ready() y a las rutas de la aplicación procesadas en el cliente.

Si un modelo es asignado a req.model, Derby lo usa en lugar de crear un nuevo modelo. Esto puede ser utilizado para pasar datos desde el “middleware” del servidor a una ruta de la aplicación. El “middleware” de sesión de Racer usa esto para crar un model coun objeto _session para una cookie dada.

    model = store.createModel ( )

    model: Un objeto del modelo Racer asociado a un contenedor dado

Si se usa el “middleware” de sesión de Racer las rutas en el lado del servidor pueden usar el modelo suministrado en req.model. Si no también pueden crear un modelo a través de store.createModel().

Características del modelo

Rutas

Todas las operaciones del modelo ocurren en rutas que represental objetos anidados literalmente. Estas rutas deben ser globalmente únicas dentro un contenedor particular.

Por ejemplo, el modelo:

{
  title: 'Fruit store',
  fruits: [
    { name: 'banana', color: 'yellow' },
    { name: 'apple', color: 'red' },
    { name: 'lime', color: 'green' }
  ]
}

Debería tener rutas como title, fruits.1 y fruits.0.color. Las rutas consisten en variables con nombres alfanuméricos o con guión bajo (_) de JavaScript válido, comenzando por una letra o un un guión bajo o un array de índices unidos con puntos (.). No deberían de contener el símbolo del dolar($), ya que está reservado para uso interno.

Rutas privadas

Las rutas que contienen un segmento que comienza con un guión bajo (p.e. _showFooter o flower.10._hovered) tienen un significado especial. Estas rutas son consideradas “privadas” y no so sincronizadas de nuevo hacia el servidor o hacia otros clientes. Las rutas privadas son frecuentemente usadas con referencias y para propósitos de procesamiento.

GUIDs

Los modelos tienen un método para crear globalmente ids únicos. Estos pueden ser usados como parte de una ruta o dentro de los métodos mutadores.

guid = model.id ( )

guid: Devuelve un identificador único globalmente que puede ser usado para operaciones con modelos

Consultas

Los modelos tienen acceso a una API de consulta encadenable y expresiva. Las consultas permiten una aproximación más versatil que las Rutas (ver arriba) para suscribirse a un conjunto de datos. Por ejemplo, con rutas no es posible especificar una suscripción a todos los usuarios mayores de 25. Las consultas permiten la suscripción a un conjunto de documentos que satisfacen algún conjunto de condiciones.

model.query (namespace)

namespace: El espacio de nombre conteniendo los documentos que quieres consultar.

El método model.query devuelve una instancia Query que puede ser usada para construir cualquier número de consultas sobre los documentos en ese espacio de nombres. Por ejemplo suponga que tiene documentos con las siguientes rutas:

Entonces model.query(‘users’) devolverá una Query que tiene la habilidad de encontrar los documentos en ‘users.1’ y/o ‘users.2’. Query no encontrará el dcoumento en ‘groups.1’ que tiene el espacio de nombres ‘groups’.

Después de generar una instancia a Query con model.query(namespace), la instancia de la consulta puede comenzar a añadir condiciones y opciones a través de un conjunto de métodos encadenables.

var query = model.query('users').where('name').equals('Lars');

Esto generará una consulta que encuentra cualquier documento bajo el espacio de nombres ‘users’ cuya propiedad ‘name’ es igual a ‘Lars’. En nuestro ejemplo con un conjunto de datos de 3 elementos debería encontrar {id: ‘1’, name: ‘ Lars’}.

Aquí están ejemplo que usan otros métodos de consulta:

// Find users
model.query('users')
  // With name 'Gnarls'
  .where('name').equals('Gnarls')
  // With gender != 'female'
  .where('gender').notEquals('female')
  // With 21 < age <=30
  .where('age').gt(21).lte(30)
  // With 100 <= numFriends < 200
  .where('numFriends').gte(100).lt(200)
  // With tags that include both 'super' and 'derby'
  .where('tags').contains(['super', 'derby'])
  // With shoe as either 'nike' or 'adidas'
  .where('shoe').within(['nike', 'adidas'])
  // Pagination ftw!
  .skip(10).limit(5);

Observer como podemos encadenar descriptores de consulta juntos para construir nuestra consulta.

Si por casualidad sabe el ide del documento que quiera encontrar entonces puede usar el método de consulta byKey. Así, por ejemplo,

model.query('users').byKey('1');

Esto encontrará el docuemnto único {id: ‘1’, name: ‘Lars’}.

Las consultas también permiten paginación. Si queremos los usarios mayores de 21 empezando por el onceavo hasta el quinceavo, entocnes puede escribir algo así:

model.query('users').where('age').gt(21).skip(10).limit(5);

Las cosultas pueden limitar las propiedes de un documento que quieren incluir o excluir:

// This will find documents but strip out all properties except
// 'id', 'name', and 'age' before passing the results back to the app.
model.query('users').where('age').gte(30).only('id', 'name', 'age');

// This will find documents and strip our the given property 'name'
// before passing the results back to the application.
model.query('users').where('age').gte(30).except('name');

Las consultas también pueden ordenar los resultados que obtienen:

// This will sort the results in age-ascending, name-descending order
model.query('users')
  .where('age').gte(25)
  .sort('age', 'asc', 'name','desc');

Las consultas por el momento pueden ser únicamente usadas para la suscripción y la recuperación de datos nuevos pero en el futro también estarán disponibles para filtrar datos que ya están cargados en el modelo.

Suscripción

El método model.subscribe rellena un modelo con datos de su contenedor asociado y declara que estos datos debieran de mentenerse al día cuando cambien. Es posible definir suscripciones en términos de patrones de rutas o consultas.

Normalmente las suscripciones son fijadas en respuesta a rutas antes del procesamiento de una página. Sin embargo el método de suscripción puede ser llamando en cualquier contexto en el servidor o en el navegador. Todas las suscripciones establecidas antes del procesado de la pa´gina en el servidor serán reestablecidas una vez la página cargue en el navegador.

model.subscribe ( targets..., [callback] )

targets: Uno o más patronoes de rutas o consultas a las que suscribirse

callback: (opcional) Llamada después de que la suscripción ha tenido éxito y los datos están en el model o cuando hay un error

La retrollamada de suscripción coge los argumentos callback(err, scopedModels…). Si la transacción tiene éxito, err es nulo. Si no, es una cadena de caracteres con un mensaje de error. Este mensaje es ‘disconnected’ si Socket.IO no está actualmente conectado. Los argumentos que quedan están enfocados a los modelos que corresponden a cada ruta objetivo de la suscripción respectivamente.

Si un modelo ya está suscrito a un objetivo, llmando a la suscripción de nuevo para el mismo objetivo no tendrá efecto. Si todos los objetivos ya están suscritos, la retrollamada será invocada inmediatamente.

model.unsubscribe ( [targets...], [callback] )

targets: (opcional) Uno o más patrones de rutas o constulas desde la que desuscribrse. Todas las suscripciones actuales del model serán desuscritas si ningún objetivo es especificado

callback: (opcional) Llmado después de que la desuscripción tenga éxito o hasta un error

La retrollamada de desuscripción toma el argumento callback(err). Como con subscribe, err es null cuando la desuscripción tiene éxito, y es ‘disnonnected’ si Socket.IO no está actualmente conectado.

Llamando a desuscribe sin ningún objetivo especificado elimana todas las suscripciones para un modelo. ‘Unsubscribe’ elimina las suscripciones, pero no elimina ningún dato del modelo.

Los patrones de ruta son especificados como cadenas de caracteres que corresponden a rutes del modelo. Un patrón de la ruta suscribe a un objeto entero, incluyendo a todas sus subrutas. Por ejemplo, suscribirse a rooms.lobby suscribe a todos los conjuntos de datos bajo el path, como rooms.lobby.name o rooms.lobby.items.3.location.

Es también posible usar un asterisco como carácter comodín en lugar de un segmento de ruta. Por ejemplo, rooms.*.playerCount suscribe un modelo a el playerCount para todas las habitaciones (rooms) pero no a otras propiedades. El model enfocado pasado a una retrollamada de suscripción está enfoncado a los segmentos hasta el primer carácter comodín. Para este ejemplo, el modelo debiera estar enfocado a rooms. Suscripciones más complejas pueden ser especificadas a través de consultas.

var roomName = 'lobby';
model.subscribe('rooms.' + roomName, function (err, room) {
  // Logs: 'rooms.lobby'
  console.log(room.path());
  // A reference is frequently created from a parameterized
  // path pattern for use later. Refs may be created directly
  // from a scoped model
  model.ref('_room', room);
});

Además a la suscripción, los modelos tiene un método fetch (obtener) con el mismo formato. Como suscribe, fetch llena a un modelo con datos de un contenedor basándose en los patrones de ruta y consultas. Sin embargo, fetch sólo obtiene los datos una vez, y no establece ninguna suscripción. Fetch puede ser usado para cualquier dato que necesite ser actualizado en tiempo real y evita usar el sistema PubSub

model.fetch ( targets..., callback )

targets: Uno o más patrones de ruta o llamadas

callback: Llamado después de que fetch tenga éxito y los datos estén en el modelo o hasta un error

La retrollamada fetch tiene los mismos argumentos que los de suscribe: callback(err, scopedModels…)

Modelos enfocados

Los modelos enfocados nos dan una manera más conveniente de interactuar con rutas usadas comunmente. Soportan los mismos métodos y nos dan el argumento de la ruta a accesores, mutadores y suscriptores de eventos.

scoped = model.at ( path, [absolute] )

path: La ruta de referencia a fijar. Observe que Derby también permite especificar un nodo DOM en lugar de una cadena de caracteres con∫ una ruta.

inputPaths: (opcional) Reemplazará la ruta de referencia al modelo si es "true". Por defecto la ruta es añadida

scoped: Devuelve un modelo enfocado

 

scoped = model.parent ( [levels] )

levels: (opcional) Por defecto a 1. El número de segmentos de la ruta a eliminar desde el final de la ruta de referencia

scoped: Devuelve un modelo enfocado

 

path = model.path ( )

path: Devuelve la ruta de refencia pra el modelo que fue puesta a través de  model.at o model.parent

 

segment = model.leaf ( )

segment: Devuelve el último segmento para la ruta de referencia. Esto puede ser útil para obtener índeces u otra conjunto de propiedas al final de una ruta

room = model.at('_room');

// These are equivalent:
room.at('name').set('Fun room');
room.set('name', 'Fun room');

// Logs: {name: 'Fun room'}
console.log(room.get());
// Logs: 'Fun room'
console.log(room.get('name'));

// Array methods can take a subpath as a first argument
// when the scoped model points to an object
room.push('toys', 'blocks', 'puzzles');
// When the scoped model points to an array, no subpath
// argument should be supplied
room.at('toys').push('cards', 'dominoes');

Observer que Derby también extiende model.at para aceptar un nodo DOM como un argumento. Esto se úsa típicamente con e.target en una retrollada de evento. Vea x-bind.

Mutadores

Los métodos de mutador del modelo son aplicados con optimismo. Esto signifina que los cambios son reflejados inmediatamente, pero pueden fallar en última instancia y ser anulados. Todos los mutadores del model son sincronos y permiten una retrollada opcional.

Métodos basicos

Estos métodos puede ser usados sobre cualquier ruta del modelo para obtener, actualizar o eliminar un objeto.

value = model.get ( [path] )

path: (opcional) Ruta del objeto a obtener. No dar una ruta devolverá todos los datos de un modelo

value: Valor actual del objeto con la ruta especificada. Observer que los objetos son devueltos por referencia y no debieran ser modificados diréctamente

Todos los mutadores del modelo tienen una retrollamada opcional con los argumentos callback(err, methodArgs…). Si la transacción tiene éxito err es null. En caso contrario es una cadena de caracteres con el mensaje de error. Este mensaje es ‘conflict’ cuando hubiera un conflicto con otra transacción. Los argumentos del método usado para llamar a la función original (p.e. path, value para el método model.set()) son también pasados a la retrollamada.

previous = model.set ( path, value, [callback] )

path: Fija la ruta del modelo

value: Valor a asignar

previous: Devuelve el valor que estaba puesto en la ruta previamente

callback: (opcional) Invocado una vez se haya completado con éxito o haya fallado la transacción

 

obj = model.del ( path, [callback] )

path: Ruta del modelo del objeto a eliminar

obj: Devuelve el objeto eliminado

Los modelos permiten el coger y poner a rutas anidadas no definidas. Coger sin una ruta devuelve “undefined”. Poner sin una ruta primeramente fija cada padre nulo o indefinido a un objeto vacío.

var model = store.createModel();
model.set('cars.DeLorean.DMC12.color', 'silver');
// Logs: { cars: { DeLorean: { DMC12: { color: 'silver' }}}}
console.log(model.get());

 

obj = model.setNull ( path, value, [callback] )

path: Ruta del modelo a definir

value: Valor a asignar sólo si la ruta es null o undefinded

obj: Devuelve el objeto de la ruta si es nul o undefined, si no, devuelve el nuevo valor

 

num = model.incr ( path, [byNum], [callback] )

path: Ruta del modelo a definir

byNum: (opcional) Número que especifica la cantidad de incremento o decremento si es negativo. Por defecto es 1

num: Devuelve el nuevo valor que tenía antes del incremento

Los métodos model.setNull() y model.incr() nos dan una forma más conveniente de hacer combinaciones de get y set comunes. Internamente hacen un model.get() y un model.set() así que los eventos del modelo son lanzados por ambos de estos métodos que son eventos set u no setNull o incr. Observer que incr puede ser llamado sobre una ruta nula en cuyo caso el valor será puesto a byNum.

Métodos con cadenas (strings)

Los métodos de cadena sólo pueden ser usados sobre rutas puestas a cadenas, null o undefined. Si la ruta es null o undefined, la ruta será puesta primera como un cadena vacía antes de aplicar el método.

length = model.push ( path, items..., [callback] )

path: Ruta del modelo a una lista

items: Uno o más ítems para añadir al final de la cadena

length: Devuelve la longitud de una cadena con los nuevos ítems añadidos

 

length = model.unshift ( path, items..., [callback] )

path: Ruta del modelo a una lista

items: Ítems a añadir al comienzo de la cadena

length: Devuelve la longitud del array con los nuevos ítems añadidos

 

length = model.insert ( path, index, items..., [callback] )

path: Ruta del modelo a una lista

index: Índice en el que se comienza la inserción Esto puede ser especificado añadiendolo a la ruta en lugar de usar un argumento separado

items: Uno o más ítems para insertar en el índice

length: Devuelve la longitud del la cadena con los nuevos ítemas añadidos

 

item = model.pop ( path, [callback] )

path: Ruta del modelo a una lista

item: Elimina el último ítem en la cadena y lo devuelve

 

item = model.shift ( path, [callback] )

path: Ruta del modelo a una lista

item: Eliminar el primer elemento de en la cadena y lo devuelve

 

removed = model.remove ( path, index, [howMany], [callback] )

path: Ruta del modelo a una lista

index: Índice para comenzar a eliminar elementos. Esto puede ser especificado añadiéndolo a la ruta en lugar de usar un argumento separado.

howMany: (opcional) Número de elementos a eliminar. Por defecto a 1

removed: Devuelve una lista de elementos eliminados

 

item = model.move ( path, from, to, [callback] )

path: Ruta del modelos a una lista

from: Índice de inicio de los elementos a mover. Esto puede ser también especificado añadiendo lo a la ruta en lugar de usar un argumento separado

to: Nuevo índece dónde los elementos serán movidos

item: Devuelve los elementos que fueron movidos

Métodos OT

El soporte a OT es experimental y no viene activado por defecto. El “plugin” de OT debe ser incluído para poder usar los métodos de OT. Vea el ejemplo de pad de Racer para más información.

previous = model.ot ( path, value, [callback] )

path: Ruta del modelo para inicializar como un campo OT

value: Una cadena para usar como el valor inicial del campo OT

previous: Devuelve el valor que estaba en la ruta previamente

 

obj = model.otNull ( path, value, [callback] )

path: Ruta del modelo para inicializar como un campo OT si la ruta es actualmente null o undefined

value: Una cadena para usar como el valor inicial de un campo OT

obj: Devuelve el objeto en la ruta si no es null o undefined. Si no devuelve el nuevo valor

 

model.otInsert ( path, index, text, [callback] )

path: Ruta del modelo a un campo OT

index: Posición dentro del campo actual OT en el que insertar

text: Cadena a insertar

 

deleted = model.otDel ( path, index, length, [callback] )

path: Ruta del model a un campo OT

index: Posición dentro del actual campo OT en el que comenzar la eliminación

length: Número de caracteres a eliminar

deleted: Devuelve la cadena que fue eliminada

Eventos

Los modelos heredan de EventEmitter estandar de Node.js y tienen los mismos métodos: on, once, removeListener, emit, etc.

Eventos del mutador del modelo

Racer emite eventos cuando hay mutación de datos a través de model.set, model.puest, etc. Estos eventos nos dan un punto de entrada a la aplicación para reaccionar a una mutación específica de datos o patrones de mutaciones de datos.

model.on y model.once aceptan un segundo argumento para estos tipos de eventos. El segundo argumento puede ser una patrón de ruta o una expresión regular que filtrará los elementos emitidos, llamando a la función controladora sólo cuando un mutador coincida con un patrón.

listener = model.on ( method, path, eventCallback )

method: Nomnre del método mutador - p.e. “set”, “push”

path: Patrón expresión regular que coincide con la ruta que está siendo mutada

eventCallback: Función a llamar cuando un método coincide y una ruta son mutadas

listener: Devuelve la función de escucha suscrita al emisor del evento. Esta es la función debiera ser pasada a model.removeListener

La retrollamada del evento recibe un número de eventos basado en el patrón de la ruta y el método. Los argumentos son:

eventCallback ( captures..., args..., out, isLocal, passed )

captures: La captura de grupos desde el patrón de ruta o la expresión regular. Si especifica un patrón de cadena de caracteres un grupo de captura será creado por cada comodín * y cualquier cosa entre paréntesis, como en (one|two)

args: Los argumentos al método. Observe que los argumentos opcionales con un valor por defecto (como con el arguemnto byNum de model.incr) siempre serán incluídos

out: El valor devuelto del método mutador del modelo

isLocal: true si la mutación del modelos fué originalmente llamada desde el mismo modelo y falso en caso contrario

passed: undefined, a menos que un valor sea especificado a través de model.pass. Vea la descipción abajo

// Matches only model.push('messages', message)
model.on('push', 'messages', function (message, messagesLength) {
  ...
});

// Matches model.set('todos.4.completed', true), etc.
model.on('set', 'todos.*.completed', function (todoId, isComplete) {
  ...
});

// Matches all set operations
model.on('set', '*', function (path, value) {
  ...
});

model.pass

Este método puede ser encadenado antes de la llamada a un método mutador que pase un argumento a las escuchas del evento del modelo. Observer que este valor sólo es pasado a las escuchas locales y no es enviado al servidor o a otros clientes

// Logs:
//   'red', undefined
//   'green', 'hi'

model.on('set', 'color', function (value, out, isLocal, passed) {
  console.log(value, passed);
});
model.set('color', 'red');
model.pass('hi').set('color', 'green');

Funciones reactivas

Las funciones reactivas nos dan una forma simple para actualizar un valor calculado cuando uno o más objetos cambien. Mientras los eventos del modelo responden a métodos específicos del modelo y a patrones de la ruta, las fucniones reactivas serán reevaluadas siempre que cualquiera de sus entradas o sus propiedes cambien de cualquier foma.

out = model.fn ( path, inputPaths..., fn )

path: La localización en la que crear una función reactiva. Esto debe ser una ruta privada ya que las funciones reactivas deben ser declaradas por modelo

inputPaths: Una o más rutas para las entradas de la función. La función será llamada siempre que una de las entradas o sus subrutas se modifiquen

fn: La función a evaluar. La función será llamada con cada uno de sus entradas como argumentos

out: Devuelve el resultado de la función

Onserver que las funciones reactivas deben ser funciones puras. En otroas palabaras, deben siempre devolver el mismo resultada dados los mismos arguementos de entrada, y debe estar libres de efectos colaterales. No debieran depender de cualqueir estado de una cerradura (closure) y las entradas deberían declararse explícitamente.

Las funciones reactivas creadas en el servidor son enviadas al cliente como una cadena y reinicializadas cuando la página carga. Si la selida de una función es usada para el procesado, debiera ser creada en el servidor.

model.set('players', [
  {name: 'John', score: 4000}
, {name: 'Bill', score: 600}
, {name: 'Kim', score: 9000}
, {name: 'Megan', score: 3000}
, {name: 'Sam', score: 2000}
]);
model.set('cutoff', 3);

// Sort the players by score and return the top X players. The
// function will automatically update the value of '_leaders' as
// players are added and removed, their scores change, and the
// cutoff value changes.
model.fn('_leaders', 'players', 'cutoff', function (players, cutoff) {
  // Note that the input array is copied with slice before sorting
  // it. The function should not modify the values of its inputs.
  return players.slice().sort(function (a, b) {
    return a.score - b.score;
  }).slice(0, cutoff - 1);
});

References

Las refencias hacen posible escribir lógica de negocio y plantillas que interactúan con el modelo de una forma general. Ellas redireccionan las operaciones del model desde una ruta de referencia a los datos subyacentes y fijan las escuchas al evento que emiten los eventos del modelo tanto en la referencia como en la ruta actual del objeto.

Las referencias deben ser declaradas por modelo desde que llamar a model.ref crea un número de escuchas del evento además de poner un objeto ref en el modelo. CUando una referencia es creada un evento set del modeloes emitido. Internamente model.set es usadao para añadir la refencia al modelo.

fn = model.ref ( path, to, [key] )

path: La localización en la que crear una referenica. Debe ser una ruta privada desde que las referencias deben ser decladas por modelo

to: La localización a la que la referencia se enlaza. Esto es dónde los datos actualmente son guardados. Puede ser una ruta o un modelo enfoncado

key: (opcional) Una ruta cuyo valor debiera sera añadido como una propiedad adicional por debajo cuando se accede a la referncia. Puede ser una ruta a un modelo enfocado

fn: Devuelve la función que está guardada en el modelo que representa la referencia. Esta función no debería ser usada diréctamente

 

model.set('colors', {
  red: {hex: '#f00'}
, green: {hex: '#0f0'}
, blue: {hex: '#00f'}
});

// Getting a reference returns the referenced data
model.ref('_green', 'colors.green');
// Logs {hex: '#0f0'}
console.log(model.get('_green'));

// Setting a property of the reference path modifies
// the underlying data
model.set('_green.rgb', [0, 255, 0]);
// Logs {hex: '#0f0', rgb: [0, 255, 0]}
console.log(model.get('colors.green'));

// Setting or deleting the reference path modifies
// the reference and not the underlying data
model.del('_green');
// Logs undefined
console.log(model.get('_green'));
// Logs {hex: '#0f0', rgb: [0, 255, 0]}
console.log(model.get('colors.green'));

// Changing a reference key updates the reference
model.set('selected', 'red');
model.ref('_selectedColor', 'colors', 'selected');
// Logs '#f00'
console.log(model.get('_selectedColor.hex'));
model.set('selected', 'blue');
// Logs '#00f'
console.log(model.get('_selectedColor.hex'));

Racer también tiene un tipo especial de referencia creado a través de model.refList. Este tipo de refencia es útil cuando un número de objetos necesitar ser procesados o manipulados con una lista incluso aunque estén guardadas como propiedades de otro objeto. Una lista de referencias tiene los mismos métodos mutador que una lista así que puede ser conectado en una plantilla de vista como cualquier lista.

fn = model.refList ( path, to, key )

path: The location at which to create a reference list. This must be a private path, since references must be declared per model

to: The location of an object that has properties to be mapped onto an array. Each property must be an object with a unique id property of the same value. May be a path or scoped model

key: A path whose value is an array of ids that map the to object’s properties to a given order. May be a path or scoped model

fn: Returns the function that is stored in the model to represent the reference. This function should not be used directly

 

// refLists may only consist of objects with an id that matches
// their property on their parent
model.set('colors', {
  red: {hex: '#f00', id: 'red'}
, green: {hex: '#0f0', id: 'green'}
, blue: {hex: '#00f', id: 'blue'}
});
model.set('_colorIds', ['blue', 'red']);
model.ref('_myColors', 'colors', '_colorIds');

model.push('_myColors', {hex: '#ff0', id: 'yellow'});

// Logs: [
//   {hex: '#00f', id: 'blue'},
//   {hex: '#f00', id: 'red'},
//   {hex: '#ff0', id: 'yellow'}
// ]

console.log(model.get('_myColors'));

Observer que is los objetos son añadidos a la una refList sin una propiedad id, un id único de model.id() será automáticamente añadido al objeto.