integrando react.js en aplicaciones symfony (desymfony 2016)

Post on 08-Jan-2017

771 Views

Category:

Software

1 Downloads

Preview:

Click to see full reader

TRANSCRIPT

deSymfony 16-17 septiembre 2016 Madrid

INTEGRANDO REACT.JS EN APLICACIONES SYMFONYNacho Martín

deSymfony

¡Muchas gracias a nuestros patrocinadores!

Programo en Limenius

Casi todos los proyectos necesitan un frontend rico, por una razón o por otra

Hacemos aplicaciones a medida

Así que le hemos dado unas cuantas vueltas

Objetivo: Mostrar cosas que nos encontramos al usar React desde Symfony, en tierra de nadie, y que ninguno de los dos va a documentar.

¿A mí qué me importa el frontend?

¿Qué es React.js?

Echar huevos en sartén

La premisa fundamental

Cómo hacer una tortillaComprar huevos

Romper huevos

Echar huevos en sartén

Batir huevos

La premisa fundamental

Cómo hacer una tortillaComprar huevos

Romper huevos

Opciones:

La premisa fundamental

Opciones:

La premisa fundamental

1: Repintamos todo.

Opciones:

La premisa fundamental

1: Repintamos todo. Simple

Opciones:

La premisa fundamental

1: Repintamos todo. Simple Poco eficiente

Opciones:

2: Buscamos en el DOM dónde insertar, qué mover, qué eliminar.

La premisa fundamental

1: Repintamos todo. Simple Poco eficiente

Opciones:

2: Buscamos en el DOM dónde insertar, qué mover, qué eliminar.

La premisa fundamental

1: Repintamos todo. Simple

Complejo

Poco eficiente

Opciones:

2: Buscamos en el DOM dónde insertar, qué mover, qué eliminar.

La premisa fundamental

1: Repintamos todo. Simple

Muy eficienteComplejo

Poco eficiente

Opciones:

2: Buscamos en el DOM dónde insertar, qué mover, qué eliminar.

La premisa fundamental

1: Repintamos todo. Simple

Muy eficienteComplejo

Poco eficiente

React nos permite hacer 1, aunque en la sombra hace 2

Dame un estado y una función render() que dependa de él, y olvídate de cómo y cuándo pinto.*

La premisa fundamental

Dame un estado y una función render() que dependa de él, y olvídate de cómo y cuándo pinto.*

La premisa fundamental

* A menos que quieras tener control absoluto.

¡Clícame! Clicks: 0

Nuestro primer componente

¡Clícame! Clicks: 1

Nuestro primer componente

¡Clícame!

Nuestro primer componenteimport React, { Component } from 'react';

class Counter extends Component { constructor(props) { super(props); this.state = {count: 1}; }

tick() { this.setState({count: this.state.count + 1}); }

render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); }}

export default Counter;

Nuestro primer componenteimport React, { Component } from 'react';

class Counter extends Component { constructor(props) { super(props); this.state = {count: 1}; }

tick() { this.setState({count: this.state.count + 1}); }

render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); }}

export default Counter;

Sintaxis ES6 (opcional)

Nuestro primer componenteimport React, { Component } from 'react';

class Counter extends Component { constructor(props) { super(props); this.state = {count: 1}; }

tick() { this.setState({count: this.state.count + 1}); }

render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); }}

export default Counter;

Sintaxis ES6 (opcional)

Estado inicial

Nuestro primer componenteimport React, { Component } from 'react';

class Counter extends Component { constructor(props) { super(props); this.state = {count: 1}; }

tick() { this.setState({count: this.state.count + 1}); }

render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); }}

export default Counter;

Sintaxis ES6 (opcional)

Modificar estado

Estado inicial

Nuestro primer componenteimport React, { Component } from 'react';

class Counter extends Component { constructor(props) { super(props); this.state = {count: 1}; }

tick() { this.setState({count: this.state.count + 1}); }

render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); }}

export default Counter;

Sintaxis ES6 (opcional)

Modificar estado

render(), lo llama React

Estado inicial

Trabajar con el estado

Trabajar con el estado

constructor(props) { super(props); this.state = {count: 1};}

Estado inicial

Trabajar con el estado

constructor(props) { super(props); this.state = {count: 1};}

Estado inicial

this.setState({count: this.state.count + 1});

Asignar estado

Trabajar con el estado

constructor(props) { super(props); this.state = {count: 1};}

Estado inicial

this.setState({count: this.state.count + 1});

Asignar estado

this.state.count = this.state.count + 1;

Simplemente recordar evitar

render() y JSXrender() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> );}

No es HTML, es JSX. React lo transforma internamente a elementos. Buena práctica: Dejar render() lo más limpio posible, solo un return.

render() y JSXrender() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> );}

No es HTML, es JSX. React lo transforma internamente a elementos.

Algunas cosas cambian

Buena práctica: Dejar render() lo más limpio posible, solo un return.

render() y JSXrender() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> );}

No es HTML, es JSX. React lo transforma internamente a elementos.

Algunas cosas cambian

Entre {} podemos insertar expresiones JS

Buena práctica: Dejar render() lo más limpio posible, solo un return.

Thinking in Reactrender() {

return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> );}

Thinking in Reactrender() {

return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> );}

Aquí no modificar el estado

Thinking in Reactrender() {

return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> );}

Aquí no Ajax

Thinking in Reactrender() {

return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> );}

Aquí no calcular decimales de pi y enviar un email

Importante: pensar la jerarquía

Importante: pensar la jerarquía

Jerarquía de componentes: propsclass CounterGroup extends Component { render() { return ( <div> <Counter name="amigo"/> <Counter name="señor"/> </div> ); }}

render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}> Clícame {this.props.name} </button> <span>Clicks: {this.state.count}</span> </div> );}

y en Counter…

Jerarquía de componentes: propsclass CounterGroup extends Component { render() { return ( <div> <Counter name="amigo"/> <Counter name="señor"/> </div> ); }}

render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}> Clícame {this.props.name} </button> <span>Clicks: {this.state.count}</span> </div> );}

y en Counter…

Jerarquía de componentes: propsclass CounterGroup extends Component { render() { return ( <div> <Counter name="amigo"/> <Counter name="señor"/> </div> ); }}

Pro tip: componentes sin estado

const Saludador = (props) => { <div> <div>Hola {props.name}</div> </div>}

Todo depende del estado, por tanto:

Todo depende del estado, por tanto:

•Podemos reproducir estados,

Todo depende del estado, por tanto:

•Podemos reproducir estados,•rebobinar,

Todo depende del estado, por tanto:

•Podemos reproducir estados,•rebobinar,•loguear cambios de estado,

Todo depende del estado, por tanto:

•Podemos reproducir estados,•rebobinar,•loguear cambios de estado,•hacer álbum de estilo

Todo depende del estado, por tanto:

•Podemos reproducir estados,•rebobinar,•loguear cambios de estado,•hacer álbum de estilo•…

Learn once, write everywhere

¿Y si en lugar de algo así…

render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); }

…tenemos algo así?render () { return ( <View> <ListView dataSource={dataSource} renderRow={(rowData) => <TouchableOpacity > <View> <Text>{rowData.name}</Text> <View> <SwitchIOS onValueChange={(value) => this.setMissing(item, value)} value={item.missing} /> </View> </View> </TouchableOpacity> } /> </View> );}

…tenemos algo así?render () { return ( <View> <ListView dataSource={dataSource} renderRow={(rowData) => <TouchableOpacity > <View> <Text>{rowData.name}</Text> <View> <SwitchIOS onValueChange={(value) => this.setMissing(item, value)} value={item.missing} /> </View> </View> </TouchableOpacity> } /> </View> );}

React Native

React Targets

• Web - react-dom • Mobile - React Native • Gl shaders - gl-react • Canvas - react-canvas • Terminal - react-blessed

react-blessed (terminal)

Setup

¿Assetic?

Setup recomendado

Setup recomendado

WebpackPros

Webpack

• Gestiona dependencias por nosotros.Pros

Webpack

• Gestiona dependencias por nosotros.• Permite varios entornos: producción, desarrollo, ….

Pros

Webpack

• Gestiona dependencias por nosotros.• Permite varios entornos: producción, desarrollo, ….• Recarga automática de página (e incluso hot reload).

Pros

Webpack

• Gestiona dependencias por nosotros.• Permite varios entornos: producción, desarrollo, ….• Recarga automática de página (e incluso hot reload).• Uso de preprocesadores/“transpiladores”, como Babel.

Pros

Webpack

• Gestiona dependencias por nosotros.• Permite varios entornos: producción, desarrollo, ….• Recarga automática de página (e incluso hot reload).• Uso de preprocesadores/“transpiladores”, como Babel.• Los programadores de frontend no nos arquearán la ceja.

Pros

Webpack

• Gestiona dependencias por nosotros.• Permite varios entornos: producción, desarrollo, ….• Recarga automática de página (e incluso hot reload).• Uso de preprocesadores/“transpiladores”, como Babel.• Los programadores de frontend no nos arquearán la ceja.

Pros

Contras

Webpack

• Gestiona dependencias por nosotros.• Permite varios entornos: producción, desarrollo, ….• Recarga automática de página (e incluso hot reload).• Uso de preprocesadores/“transpiladores”, como Babel.• Los programadores de frontend no nos arquearán la ceja.

Pros

Contras• Tiene su curva de aprendizaje.

Webpack

• Gestiona dependencias por nosotros.• Permite varios entornos: producción, desarrollo, ….• Recarga automática de página (e incluso hot reload).• Uso de preprocesadores/“transpiladores”, como Babel.• Los programadores de frontend no nos arquearán la ceja.

Pros

Contras• Tiene su curva de aprendizaje.

Ejemplo completo: https://github.com/Limenius/symfony-react-sandbox

Inserción

<div id="react-placeholder"></div>

import ReactDOM from 'react-dom';

ReactDOM.render( <Counter name="amigo">, document.getElementById('react-placeholder'));

HTML

JavaScript

Integración con Symfony https://github.com/Limenius/ReactBundle

https://github.com/shakacode/react_on_rails

Basado en

ReactBundle

{{ react_component('RecipesApp', {'props': props}) }}

import ReactOnRails from 'react-on-rails';import RecipesApp from './RecipesAppServer';

ReactOnRails.register({ RecipesApp });

Twig:

JavaScript:

ReactBundle

{{ react_component('RecipesApp', {'props': props}) }}

import ReactOnRails from 'react-on-rails';import RecipesApp from './RecipesAppServer';

ReactOnRails.register({ RecipesApp });

Twig:

JavaScript:

ReactBundle

{{ react_component('RecipesApp', {'props': props}) }}

import ReactOnRails from 'react-on-rails';import RecipesApp from './RecipesAppServer';

ReactOnRails.register({ RecipesApp });

Twig:

JavaScript:

ReactBundle

{{ react_component('RecipesApp', {'props': props}) }}

import ReactOnRails from 'react-on-rails';import RecipesApp from './RecipesAppServer';

ReactOnRails.register({ RecipesApp });

Twig:

JavaScript:

<div class="js-react-on-rails-component" style="display:none" data-component-name=“RecipesApp” data-props=“[mi Array en JSON]" data-trace=“false" data-dom-id=“sfreact-57d05640f2f1a”></div>

HTML generado:

Aplicaciones universales

Dame un estado y una función render() que dependa de él, y olvídate de cómo y cuándo pinto.

La premisa fundamental

Dame un estado y una función render() que dependa de él, y olvídate de cómo y cuándo pinto.

La premisa fundamental

Podemos renderizar componentes desde el servidor.

Dame un estado y una función render() que dependa de él, y olvídate de cómo y cuándo pinto.

La premisa fundamental

Podemos renderizar componentes desde el servidor.

• SEO friendly.

Dame un estado y una función render() que dependa de él, y olvídate de cómo y cuándo pinto.

La premisa fundamental

Podemos renderizar componentes desde el servidor.

• SEO friendly.• Carga de página más rápida.

Dame un estado y una función render() que dependa de él, y olvídate de cómo y cuándo pinto.

La premisa fundamental

Podemos renderizar componentes desde el servidor.

• SEO friendly.• Carga de página más rápida.• Podemos cachear.

Client-side

{{ react_component('RecipesApp', {'props': props, 'rendering': 'client-side'}}) }}

TWIG

Client-side

{{ react_component('RecipesApp', {'props': props, 'rendering': 'client-side'}}) }}

TWIG

Client-side

{{ react_component('RecipesApp', {'props': props, 'rendering': 'client-side'}}) }}

TWIG

<div class="js-react-on-rails-component" style="display:none" data-component-name=“RecipesApp” data-props=“…” data-dom-id=“sfreact-57d05640f2f1a”></div>

HTML que devuelve el servidor

Client-side

{{ react_component('RecipesApp', {'props': props, 'rendering': 'client-side'}}) }}

TWIG

<div class="js-react-on-rails-component" style="display:none" data-component-name=“RecipesApp” data-props=“…” data-dom-id=“sfreact-57d05640f2f1a”></div>

HTML que devuelve el servidor

Generado en el navegador<div id="sfreact-57d05640f2f1a"><div data-reactroot="" data-reactid="1" data-react-checksum="2107256409"><ol class="breadcrumb" data-reactid="2"><li class="active" data-reactid=“3">Recipes</li>……</div>

Client-side y server-side

{{ react_component('RecipesApp', {'props': props, 'rendering': 'both'}}) }}

TWIG

Client-side y server-side

{{ react_component('RecipesApp', {'props': props, 'rendering': 'both'}}) }}

TWIG

Client-side y server-side

{{ react_component('RecipesApp', {'props': props, 'rendering': 'both'}}) }}

TWIG

HTML que devuelve el servidor<div id="sfreact-57d05640f2f1a"><div data-reactroot="" data-reactid="1" data-react-checksum="2107256409"><ol class="breadcrumb" data-reactid="2"><li class="active" data-reactid=“3">Recipes</li>……</div>

Client-side y server-side

{{ react_component('RecipesApp', {'props': props, 'rendering': 'both'}}) }}

TWIG

HTML que devuelve el servidor<div id="sfreact-57d05640f2f1a"><div data-reactroot="" data-reactid="1" data-react-checksum="2107256409"><ol class="breadcrumb" data-reactid="2"><li class="active" data-reactid=“3">Recipes</li>……</div>

Y React en el navegador toma el control al evaluar el código

Aplicaciones universales: Opciones

Opción 1: llamar a subproceso node.jsLlamamos a node.js con el componente Process de Symfony

* Cómodo (si tenemos node.js instalado).

* Lento.

Librería: https://github.com/nacmartin/phpexecjs

Opción 2: v8jsUsamos la extensión de PHP v8js

* Cómodo (aunque puede que haya que compilar la extensión y v8).

* Lento por ahora, potencialmente podríamos tener v8 precargada usando php-pm.

Librería: https://github.com/nacmartin/phpexecjs

Configuración Opciones 1 y 2

limenius_react: serverside_rendering: mode: "phpexecjs"

phpexecjs detecta si tenemos la extensión v8js, y si no llama a node.js

config.yml:

Opción 3: Servidor externoTenemos un servidor node.js “tonto” que nos renderiza React.

Es un servidor de <100 líneas, que es independiente de nuestra lógica.

* “Incómodo” (hay que mantener el servidor node.js corriendo, que tampoco es para morirse).

* Rápido.

Ver ejemplo en https://github.com/Limenius/symfony-react-sandbox

Configuración Opción 3

limenius_react: serverside_rendering: mode: “external” server-socket-path: “../tal_y_tal/node.sock”

config.yml:

Lo mejor de los dos mundosEn desarrollo usar llamada a node.js o v8js con phpexecjs.

En producción tener un servidor externo.

Si podemos cachear, menos problema.

Es decir:

limenius_react: serverside_rendering: mode: “external” server-socket-path: “../tal_y_tal/node.sock”

config.yml:

limenius_react: serverside_rendering: mode: "phpexecjs"

config_dev.yml:

¿Vale la pena una app universal?

¿Vale la pena una app universal?

En ocasiones sí, pero introduce complejidad.

Soporte para Redux (+brevísima introducción a Redux)

Redux: una cuestión de estado

guardar

Tu nombre: Juan

Hola, Juan

Cosas de Juan:

Redux: una cuestión de estado

guardar

Tu nombre: Juan

Hola, Juan

Cosas de Juan:

Redux: una cuestión de estado

guardar

Tu nombre: Juan

Hola, Juan

Cosas de Juan:

state.name

callback para cambiarlo

dispatch(changeName(‘Juan'));

Componente

dispatch(changeName(‘Juan'));

Componente

changeName = (name) => { return { type: ‘CHANGE_NAME', name }}

Action

dispatch(changeName(‘Juan'));

Componente

changeName = (name) => { return { type: ‘CHANGE_NAME', name }}

Action

const todo = (state = {name: null}, action) => { switch (action.type) { case 'CHANGE_USER': return { name: action.name } }}

Reducer

dispatch(changeName(‘Juan'));

Componente

changeName = (name) => { return { type: ‘CHANGE_NAME', name }}

Action

const todo = (state = {name: null}, action) => { switch (action.type) { case 'CHANGE_USER': return { name: action.name } }}

Reducer

Store

this.props.name == ‘Juan';dispatch(changeName(‘Juan'));

Componente

changeName = (name) => { return { type: ‘CHANGE_NAME', name }}

Action

const todo = (state = {name: null}, action) => { switch (action.type) { case 'CHANGE_USER': return { name: action.name } }}

Reducer

Store

Redux en ReactBundle

Ver ejemplo en https://github.com/Limenius/symfony-react-sandbox

import ReactOnRails from 'react-on-rails';import RecipesApp from './RecipesAppClient';import recipesStore from '../store/recipesStore';

ReactOnRails.registerStore({recipesStore})ReactOnRails.register({ RecipesApp });

Twig:

JavaScript:

{{ redux_store('recipesStore', props) }}{{ react_component('RecipesApp') }}

Redux en ReactBundle

Ver ejemplo en https://github.com/Limenius/symfony-react-sandbox

import ReactOnRails from 'react-on-rails';import RecipesApp from './RecipesAppClient';import recipesStore from '../store/recipesStore';

ReactOnRails.registerStore({recipesStore})ReactOnRails.register({ RecipesApp });

Twig:

JavaScript:

{{ redux_store('recipesStore', props) }}{{ react_component('RecipesApp') }}{{ react_component('OtroComponente') }}

Compartir store entre componentes

Compartir store entre componentesReact

ReactReact

Twig

Twig

React

Al compartir store comparten estado

Twig

Formularios, un caso especial

Formularios muy dinámicos•Dentro de aplicaciones React.

•Formularios importantísimos en los que un detalle de usabilidad vale mucho dinero.

•Formularios muy específicos.

•Formularios muy dinámicos que no cansen (ver typeform por ejemplo).

El problema

Supongamos un form así

public function buildForm(FormBuilderInterface $builder, array $options){ $builder ->add('country', ChoiceType::class, [ 'choices' => [ 'España' => 'es', 'Portugal' => 'pt', ] ]) ->add('addresses', CollectionType::class, ...);};

Forms en HTML

$form->createView();

state.usuario

Forms en HTML

$form->createView();

state.usuario

Forms en HTML

$form->createView();

submit

país: España

EspañaPortugal

direcciones:

C\ tal-+state.usuario

Forms en HTML

$form->createView();

submit

país: España

EspañaPortugal

direcciones:

C\ tal-+state.usuario

Forms en HTML

$form->createView();

submit

país: España

EspañaPortugal

direcciones:

C\ tal-+state.usuario

POST bien formadito con country:’es’

y no ‘España’, ‘espana', ‘spain', ‘0’…

Forms en HTML

$form->createView();

$form->submit($request);

submit

país: España

EspañaPortugal

direcciones:

C\ tal-+state.usuario

POST bien formadito con country:’es’

y no ‘España’, ‘espana', ‘spain', ‘0’…

Forms en API

$form;

state.usuario

Forms en API

$form;

submit

país: España

EspañaPortugal

direcciones:

C\ tal-+state.usuario

Forms en API

$form;

submit

país: España

EspañaPortugal

direcciones:

C\ tal-+state.usuario

✘¿Cómo sabemos los campos,

o los choices? ¡A documentar! :(

Forms en API

$form;

$form->submit($request);

submit

país: España

EspañaPortugal

direcciones:

C\ tal-+state.usuario

POST “voy a tener suerte”

✘¿Cómo sabemos los campos,

o los choices? ¡A documentar! :(

Forms en API

$form;

$form->submit($request);

submit

país: España

EspañaPortugal

direcciones:

C\ tal-+state.usuario

POST “voy a tener suerte”

✘¿Cómo sabemos los campos,

o los choices? ¡A documentar! :(

This form should not contain extra fields!!1

Forms en API

$form;

$form->submit($request);

submit

país: España

EspañaPortugal

direcciones:

C\ tal-+state.usuario

POST “voy a tener suerte”

✘¿Cómo sabemos los campos,

o los choices? ¡A documentar! :(

This form should not contain extra fields!!1The value you selected is not a valid choice!!

Forms en API

$form;

$form->submit($request);

submit

país: España

EspañaPortugal

direcciones:

C\ tal-+state.usuario

POST “voy a tener suerte”

✘¿Cómo sabemos los campos,

o los choices? ¡A documentar! :(

This form should not contain extra fields!!1The value you selected is not a valid choice!!One or more of the given values is invalid!! :D

Forms en API

$form;

$form->submit($request);

submit

país: España

EspañaPortugal

direcciones:

C\ tal-+state.usuario

POST “voy a tener suerte”

✘¿Cómo sabemos los campos,

o los choices? ¡A documentar! :(

This form should not contain extra fields!!1The value you selected is not a valid choice!!One or more of the given values is invalid!! :DMUHAHAHAHAHA!!!!!

Definir (y mantener) por triplicado

Form SF API docs Form Cliente

:(

¿Cuántos programadores hacen falta para hacer un formulario?

Caso: Wizard complejo

Caso: Wizard complejo

Caso: Wizard complejo

Lo que necesitamos

$form->createView();

HTML

API$miTransformador->transform($form);

Lo que necesitamos

$form->createView();

HTML

¡Serializar! Vale, ¿A qué formato?

API$miTransformador->transform($form);

JSON Schema

Qué pinta tiene{ "$schema": "http://json-schema.org/draft-04/schema#", "title": "Product", "description": "A product from Acme's catalog", "type": "object", "properties": { "name": { "description": "Name of the product", "type": "string" }, "price": { "type": "number", "minimum": 0, "exclusiveMinimum": true }, "tags": { "type": "array", "items": { "type": "string" }, "minItems": 1, "uniqueItems": true } }, "required": ["id", "name", "price"]}

definiciones, tipos, reglas de validación :)

Nuevo recurso: mi-api/products/form

A partir del schema generamos form

Generadores client-side

• jdorn/json-editor: no React, es un veterano.

• mozilla/react-jsonschema-form: React. creado por un antiguo Symfonero (Niko Perriault).

• limenius/liform-react: React + redux; integrado con redux-form (I ♥ redux-form)

• …

• Crear el nuestro puede ser conveniente.

Generadores client-side: diferenciasCada uno amplía json-schema a su manera para

especificar detalles UI: Orden de campos, qué widget específico usar, etc.

Si queremos usarlos al máximo hay que generar un json-schema específico para cada uno

(no son totalmente intercambiables).

Ejemplo: usar editor Wysiwyg en un campo texto

Ejemplo: LiformBundle y liform-react

limenius/LiformBundle: Genera json-schema a partir de formularios Symfony.

limenius/liform-react: Generador de formularios React (con redux-form) a partir de json-schema.

Son Work in progress

Cómo serializar: resolvers + transformers

$transformer = $resolver->resolve($form);

$jsonSchema = $transformer->transform($form);

Ejemplo de esta técnica: https://github.com/Limenius/LiformBundle

Resolver

public function resolve(FormInterface $form){ $types = FormUtil::typeAncestry($form);

foreach ($types as $type) { if (isset($this->transformers[$type])) { return $this->transformers[$type]; } }}

Misión: Encuentra el transformer apropiado para el form

TransformerMisión: Inspecciona el form y crea un array.

Si es compuesto resuelve+transforma los hijos.class IntegerTransformer extends AbstractTransformer{ public function transform(FormInterface $form) { $schema = [ 'type' => 'integer', ]; if ($liform = $form->getConfig()->getOption('liform')) { if ($format = $liform['format']) { $schema['format'] = $format; } } $this->addCommonSpecs($form, $schema);

return $schema; }} protected function addLabel($form, &$schema) { if ($label = $form->getConfig()->getOption('label')) { $schema['title'] = $label; } }

TransformerRecopila información de cada Form Field.

Podemos sacar mucha información: •Valores por defecto & placeholders. •Atributos del formulario. •Validadores.

Ejemplo: validación ‘pattern’

protected function addPattern($form, &$schema){ if ($attr = $form->getConfig()->getOption('attr')) { if (isset($attr['pattern'])) { $schema['pattern'] = $attr['pattern']; } }}

Esta técnica vale también para Angular, Backbone, mobile…

Repaso:

Repaso:• Qué es React • Setup • Apps universales (ReactBundle) • Para qué sirve Redux • El problema de los formularios • Cómo aliviarlo con JSON Schema

MADRID · NOV 27-28 · 2015

¡Gracias!@nacmartin

nacho@limenius.com

http://limenius.com Formación, consultoría

y desarrollo de proyectos

top related