Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Integrando React.js en aplicaciones Symfony (deSymfony 2016)

3,048 views

Published on

Introducción a React.js + técnicas y conceptos útiles, como aplicaciones universales (isomórficas) o cómo usar json schema para facilitarnos la vida al trabajar con formularios.

Published in: Software
  • Be the first to comment

Integrando React.js en aplicaciones Symfony (deSymfony 2016)

  1. 1. deSymfony 16-17 septiembre 2016 Madrid INTEGRANDO REACT.JS EN APLICACIONES SYMFONY Nacho Martín
  2. 2. deSymfony ¡Muchas gracias a nuestros patrocinadores!
  3. 3. 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
  4. 4. Objetivo: Mostrar cosas que nos encontramos al usar React desde Symfony, en tierra de nadie, y que ninguno de los dos va a documentar.
  5. 5. ¿A mí qué me importa el frontend?
  6. 6. ¿Qué es React.js?
  7. 7. Echar huevos en sartén La premisa fundamental Cómo hacer una tortilla Comprar huevos Romper huevos
  8. 8. Echar huevos en sartén Batir huevos La premisa fundamental Cómo hacer una tortilla Comprar huevos Romper huevos
  9. 9. Opciones: La premisa fundamental
  10. 10. Opciones: La premisa fundamental 1: Repintamos todo.
  11. 11. Opciones: La premisa fundamental 1: Repintamos todo. Simple
  12. 12. Opciones: La premisa fundamental 1: Repintamos todo. Simple Poco eficiente
  13. 13. Opciones: 2: Buscamos en el DOM dónde insertar, qué mover, qué eliminar. La premisa fundamental 1: Repintamos todo. Simple Poco eficiente
  14. 14. Opciones: 2: Buscamos en el DOM dónde insertar, qué mover, qué eliminar. La premisa fundamental 1: Repintamos todo. Simple Complejo Poco eficiente
  15. 15. Opciones: 2: Buscamos en el DOM dónde insertar, qué mover, qué eliminar. La premisa fundamental 1: Repintamos todo. Simple Muy eficienteComplejo Poco eficiente
  16. 16. 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
  17. 17. 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
  18. 18. 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.
  19. 19. ¡Clícame! Clicks: 0 Nuestro primer componente
  20. 20. ¡Clícame! Clicks: 1 Nuestro primer componente ¡Clícame!
  21. 21. Nuestro primer componente import 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;
  22. 22. Nuestro primer componente import 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)
  23. 23. Nuestro primer componente import 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
  24. 24. Nuestro primer componente import 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
  25. 25. Nuestro primer componente import 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
  26. 26. Trabajar con el estado
  27. 27. Trabajar con el estado constructor(props) { super(props); this.state = {count: 1}; } Estado inicial
  28. 28. Trabajar con el estado constructor(props) { super(props); this.state = {count: 1}; } Estado inicial this.setState({count: this.state.count + 1}); Asignar estado
  29. 29. 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
  30. 30. render() y JSX render() { 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.
  31. 31. render() y JSX render() { 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.
  32. 32. render() y JSX render() { 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.
  33. 33. Thinking in React render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); }
  34. 34. Thinking in React render() { 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
  35. 35. Thinking in React render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); } Aquí no Ajax
  36. 36. Thinking in React render() { 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
  37. 37. Importante: pensar la jerarquía
  38. 38. Importante: pensar la jerarquía
  39. 39. Jerarquía de componentes: props class CounterGroup extends Component { render() { return ( <div> <Counter name="amigo"/> <Counter name="señor"/> </div> ); } }
  40. 40. 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: props class CounterGroup extends Component { render() { return ( <div> <Counter name="amigo"/> <Counter name="señor"/> </div> ); } }
  41. 41. 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: props class CounterGroup extends Component { render() { return ( <div> <Counter name="amigo"/> <Counter name="señor"/> </div> ); } }
  42. 42. Pro tip: componentes sin estado const Saludador = (props) => { <div> <div>Hola {props.name}</div> </div> }
  43. 43. Todo depende del estado, por tanto:
  44. 44. Todo depende del estado, por tanto: •Podemos reproducir estados,
  45. 45. Todo depende del estado, por tanto: •Podemos reproducir estados, •rebobinar,
  46. 46. Todo depende del estado, por tanto: •Podemos reproducir estados, •rebobinar, •loguear cambios de estado,
  47. 47. Todo depende del estado, por tanto: •Podemos reproducir estados, •rebobinar, •loguear cambios de estado, •hacer álbum de estilo
  48. 48. Todo depende del estado, por tanto: •Podemos reproducir estados, •rebobinar, •loguear cambios de estado, •hacer álbum de estilo •…
  49. 49. Learn once, write everywhere
  50. 50. ¿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> ); }
  51. 51. …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> ); }
  52. 52. …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
  53. 53. React Targets •Web - react-dom •Mobile - React Native •Gl shaders - gl-react •Canvas - react-canvas •Terminal - react-blessed
  54. 54. react-blessed (terminal)
  55. 55. Setup
  56. 56. ¿Assetic? Setup recomendado
  57. 57. Setup recomendado
  58. 58. Webpack Pros
  59. 59. Webpack • Gestiona dependencias por nosotros. Pros
  60. 60. Webpack • Gestiona dependencias por nosotros. • Permite varios entornos: producción, desarrollo, …. Pros
  61. 61. Webpack • Gestiona dependencias por nosotros. • Permite varios entornos: producción, desarrollo, …. • Recarga automática de página (e incluso hot reload). Pros
  62. 62. 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
  63. 63. 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
  64. 64. 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
  65. 65. 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.
  66. 66. 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
  67. 67. Inserción <div id="react-placeholder"></div> import ReactDOM from 'react-dom'; ReactDOM.render( <Counter name="amigo">, document.getElementById('react-placeholder') ); HTML JavaScript
  68. 68. Integración con Symfony https://github.com/Limenius/ReactBundle
  69. 69. https://github.com/shakacode/react_on_rails Basado en
  70. 70. ReactBundle {{ react_component('RecipesApp', {'props': props}) }} import ReactOnRails from 'react-on-rails'; import RecipesApp from './RecipesAppServer'; ReactOnRails.register({ RecipesApp }); Twig: JavaScript:
  71. 71. ReactBundle {{ react_component('RecipesApp', {'props': props}) }} import ReactOnRails from 'react-on-rails'; import RecipesApp from './RecipesAppServer'; ReactOnRails.register({ RecipesApp }); Twig: JavaScript:
  72. 72. ReactBundle {{ react_component('RecipesApp', {'props': props}) }} import ReactOnRails from 'react-on-rails'; import RecipesApp from './RecipesAppServer'; ReactOnRails.register({ RecipesApp }); Twig: JavaScript:
  73. 73. 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:
  74. 74. Aplicaciones universales
  75. 75. 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
  76. 76. 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.
  77. 77. 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.
  78. 78. 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.
  79. 79. 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.
  80. 80. Client-side {{ react_component('RecipesApp', {'props': props, 'rendering': 'client-side'}}) }} TWIG
  81. 81. Client-side {{ react_component('RecipesApp', {'props': props, 'rendering': 'client-side'}}) }} TWIG
  82. 82. 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
  83. 83. 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>
  84. 84. Client-side y server-side {{ react_component('RecipesApp', {'props': props, 'rendering': 'both'}}) }} TWIG
  85. 85. Client-side y server-side {{ react_component('RecipesApp', {'props': props, 'rendering': 'both'}}) }} TWIG
  86. 86. 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>
  87. 87. 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
  88. 88. Aplicaciones universales: Opciones
  89. 89. Opción 1: llamar a subproceso node.js Llamamos a node.js con el componente Process de Symfony * Cómodo (si tenemos node.js instalado). * Lento. Librería: https://github.com/nacmartin/phpexecjs
  90. 90. Opción 2: v8js Usamos 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
  91. 91. 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:
  92. 92. Opción 3: Servidor externo Tenemos 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
  93. 93. Configuración Opción 3 limenius_react: serverside_rendering: mode: “external” server-socket-path: “../tal_y_tal/node.sock” config.yml:
  94. 94. Lo mejor de los dos mundos En desarrollo usar llamada a node.js o v8js con phpexecjs. En producción tener un servidor externo. Si podemos cachear, menos problema.
  95. 95. 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:
  96. 96. ¿Vale la pena una app universal?
  97. 97. ¿Vale la pena una app universal? En ocasiones sí, pero introduce complejidad.
  98. 98. Soporte para Redux (+brevísima introducción a Redux)
  99. 99. Redux: una cuestión de estado guardar Tu nombre: Juan Hola, Juan Cosas de Juan:
  100. 100. Redux: una cuestión de estado guardar Tu nombre: Juan Hola, Juan Cosas de Juan:
  101. 101. Redux: una cuestión de estado guardar Tu nombre: Juan Hola, Juan Cosas de Juan: state.name callback para cambiarlo
  102. 102. dispatch(changeName(‘Juan')); Componente
  103. 103. dispatch(changeName(‘Juan')); Componente changeName = (name) => { return { type: ‘CHANGE_NAME', name } } Action
  104. 104. 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
  105. 105. 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
  106. 106. 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
  107. 107. 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') }}
  108. 108. 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') }}
  109. 109. Compartir store entre componentes
  110. 110. Compartir store entre componentes React React React Twig Twig React Al compartir store comparten estado Twig
  111. 111. Formularios, un caso especial
  112. 112. 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).
  113. 113. El problema
  114. 114. 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, ...); };
  115. 115. Forms en HTML $form->createView(); state.usuario
  116. 116. Forms en HTML $form->createView(); state.usuario
  117. 117. Forms en HTML $form->createView(); submit país: España España Portugal direcciones: C tal- +state.usuario
  118. 118. Forms en HTML $form->createView(); submit país: España España Portugal direcciones: C tal- +state.usuario
  119. 119. Forms en HTML $form->createView(); submit país: España España Portugal direcciones: C tal- +state.usuario POST bien formadito con country:’es’ y no ‘España’, ‘espana', ‘spain', ‘0’…
  120. 120. Forms en HTML $form->createView(); $form->submit($request); submit país: España España Portugal direcciones: C tal- +state.usuario POST bien formadito con country:’es’ y no ‘España’, ‘espana', ‘spain', ‘0’…
  121. 121. Forms en API $form; state.usuario
  122. 122. Forms en API $form; submit país: España España Portugal direcciones: C tal- +state.usuario ✘
  123. 123. Forms en API $form; submit país: España España Portugal direcciones: C tal- +state.usuario ✘ ¿Cómo sabemos los campos, o los choices? ¡A documentar! :(
  124. 124. Forms en API $form; $form->submit($request); submit país: España España Portugal direcciones: C tal- +state.usuario POST “voy a tener suerte” ✘ ¿Cómo sabemos los campos, o los choices? ¡A documentar! :(
  125. 125. Forms en API $form; $form->submit($request); submit país: España España Portugal 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
  126. 126. Forms en API $form; $form->submit($request); submit país: España España Portugal 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 The value you selected is not a valid choice!!
  127. 127. Forms en API $form; $form->submit($request); submit país: España España Portugal 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 The value you selected is not a valid choice!!One or more of the given values is invalid!! :D
  128. 128. Forms en API $form; $form->submit($request); submit país: España España Portugal 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 The value you selected is not a valid choice!!One or more of the given values is invalid!! :DMUHAHAHAHAHA!!!!!
  129. 129. Definir (y mantener) por triplicado Form SF API docs Form Cliente :( ¿Cuántos programadores hacen falta para hacer un formulario?
  130. 130. Caso: Wizard complejo
  131. 131. Caso: Wizard complejo
  132. 132. Caso: Wizard complejo
  133. 133. Lo que necesitamos $form->createView(); HTML API $miTransformador->transform($form);
  134. 134. Lo que necesitamos $form->createView(); HTML ¡Serializar! Vale, ¿A qué formato? API $miTransformador->transform($form);
  135. 135. JSON Schema
  136. 136. 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
  137. 137. A partir del schema generamos form
  138. 138. 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.
  139. 139. Generadores client-side: diferencias Cada 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
  140. 140. 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
  141. 141. Cómo serializar: resolvers + transformers $transformer = $resolver->resolve($form); $jsonSchema = $transformer->transform($form); Ejemplo de esta técnica: https://github.com/Limenius/LiformBundle
  142. 142. 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
  143. 143. Transformer Misió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; } }
  144. 144. Transformer Recopila información de cada Form Field. Podemos sacar mucha información: •Valores por defecto & placeholders. •Atributos del formulario. •Validadores.
  145. 145. Ejemplo: validación ‘pattern’ protected function addPattern($form, &$schema) { if ($attr = $form->getConfig()->getOption('attr')) { if (isset($attr['pattern'])) { $schema['pattern'] = $attr['pattern']; } } }
  146. 146. Esta técnica vale también para Angular, Backbone, mobile…
  147. 147. Repaso:
  148. 148. Repaso: • Qué es React • Setup • Apps universales (ReactBundle) • Para qué sirve Redux • El problema de los formularios • Cómo aliviarlo con JSON Schema
  149. 149. MADRID · NOV 27-28 · 2015 ¡Gracias! @nacmartin nacho@limenius.com http://limenius.com Formación, consultoría y desarrollo de proyectos

×