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.

Creating 'Vuetiful' Data-Driven User Interfaces

394 views

Published on

Have you ever needed to build a form or wizard based on an API response? In this talk Evan will explore dynamic components in Vue, and how to leverage them to create dynamic, data driven UIs.

This talk was first presented at https://jscamp.tech/ in Barcelona - July 20, 2018

Published in: Technology
  • Be the first to comment

  • Be the first to like this

Creating 'Vuetiful' Data-Driven User Interfaces

  1. 1. VUETIFUL DATA-DRIVEN USER INTERFACES EVAN SCHULTZ
  2. 2. VUETIFUL DATA-DRIVEN USER INTERFACES
  3. 3. DATA DRIVEN INTERFACES WHATS COMING UP? ▸ What is a Data Driven Dynamic UI?
  4. 4. DATA DRIVEN INTERFACES WHATS COMING UP? ▸ What is a Data Driven Dynamic UI? ▸ Understanding `<component>`
  5. 5. DATA DRIVEN INTERFACES WHATS COMING UP? ▸ What is a Data Driven Dynamic UI? ▸ Understanding `<component>` ▸ Basic use
  6. 6. DATA DRIVEN INTERFACES WHATS COMING UP? ▸ What is a Data Driven Dynamic UI? ▸ Understanding `<component>` ▸ Basic use ▸ Props and Events
  7. 7. DATA DRIVEN INTERFACES WHATS COMING UP? ▸ What is a Data Driven Dynamic UI? ▸ Understanding `<component>` ▸ Basic use ▸ Props and Events ▸ Dynamic Forms
  8. 8. WHAT DOES IT MEAN DATA DRIVEN UI
  9. 9. “ WE DON’T KNOW THE COMPONENTS UNKNOWN UNTIL RUNTIME
  10. 10. WHAT DO WE MEAN? UI BASED ON RUNTIME DATA ▸ API Response
  11. 11. WHAT DO WE MEAN? UI BASED ON RUNTIME DATA ▸ API Response ▸ Application State
  12. 12. WHAT DO WE MEAN? UI BASED ON RUNTIME DATA ▸ API Response ▸ Application State ▸ Data Driven Configuration
  13. 13. “ WHEN WOULD YOU NEED THIS?
  14. 14. “ “DYNAMIC COMPONENTS ARE NOT A COMMON REQUIREMENT” BLOG POST I (SORTA) DISAGREE WITHBLOG POST I (SORTA) DISAGREE WITH
  15. 15. CAN BE AN ELEGANT SOLUTION FOR ▸ Workflow Builders WHAT DO WE MEAN?
  16. 16. CAN BE AN ELEGANT SOLUTION FOR ▸ Workflow Builders ▸ Personalization WHAT DO WE MEAN?
  17. 17. CAN BE AN ELEGANT SOLUTION FOR ▸ Workflow Builders ▸ Personalization ▸ A/B Testing WHAT DO WE MEAN?
  18. 18. CAN BE AN ELEGANT SOLUTION FOR ▸ Workflow Builders ▸ Personalization ▸ A/B Testing ‣ Data Driven Customization WHAT DO WE MEAN?
  19. 19. CAN BE AN ELEGANT SOLUTION FOR ▸ Workflow Builders ▸ Personalization ▸ A/B Testing ‣ Data Driven Customization ▸ Form Generators WHAT DO WE MEAN?
  20. 20. CAN BE AN ELEGANT SOLUTION FOR ▸ Workflow Builders ▸ Personalization ▸ A/B Testing ‣ Data Driven Customization ▸ Form Generators ▸ … and lots more WHAT DO WE MEAN?
  21. 21. “ DATA / STATE UITRANSFORM
  22. 22. “ BASICALLY
  23. 23. WE WANT TO GO FROM THIS
  24. 24. TO THIS
  25. 25. COMPONENT THE BASICS
  26. 26. WHY AM I EXCITED?
  27. 27. “ SO EASY IT’S ALMOST BORING*
  28. 28. “ SO EASY IT’S ALMOST BORING* * If I haven’t felt the pain points with other frameworks - I’d just expect this is how they work.
  29. 29. “ SPEND TIME BUILDING SOLUTIONS WITH THEM.
  30. 30. “ SPEND TIME BUILDING SOLUTIONS WITH THEM. NOT TRYING TO FIGURE OUT HOW TO LOAD COMPONENTS DYNAMICALLY.
  31. 31. THE BASICS - COMPONENT ▸ Component is a place holder <component :is=“componentType”/> <section class="markup-demo-wrap"> <component :is="activeView" v-model="contact"> </component> </section> <component :is=“componentType"/>
  32. 32. THE BASICS - COMPONENT ▸ Component is a place holder <component :is=“componentType”/> <section class="markup-demo-wrap"> <component :is="activeView" v-model="contact"> </component> </section> <component :is=“componentType"/>
  33. 33. THE BASICS - COMPONENT ▸ Component is a place holder <component :is=“componentType”/> <section class="markup-demo-wrap"> <component :is="activeView" v-model="contact"> </component> </section> <component :is=“componentType"/>
  34. 34. THE BASICS - COMPONENT ▸ Component is a place holder ▸ Does not introduce any host elements <component :is=“componentType”/><component :is=“componentType"/>
  35. 35. THE BASICS - COMPONENT <component :is=“componentType"/>
  36. 36. THE BASICS - COMPONENT <component :is="componentType"/>
  37. 37. THE BASICS - COMPONENT ▸ :is can bound to <component :is="componentType"/>
  38. 38. THE BASICS - COMPONENT ▸ :is can bound to ▸ Prop <component :is="componentType"/>
  39. 39. THE BASICS - COMPONENT ▸ :is can bound to ▸ Prop ▸ Data Property <component :is="componentType"/>
  40. 40. THE BASICS - COMPONENT ▸ :is can bound to ▸ Prop ▸ Data Property ▸ Computed Property <component :is="componentType"/>
  41. 41. THE BASICS - COMPONENT ▸ :is can bound to ▸ Prop ▸ Data Property ▸ Computed Property ▸ Can be derived from Vuex store <component :is="componentType"/>
  42. 42. THE BASICS - COMPONENT <component :is="propExample" /> <template> <is-prop :activeView="activeView">
 </is-prop> </template> <script> export default { name: "IsPropContainer", computed: { activeView(){ return this.isContact ? "ContactDetails" : "AddressDetails"; } </script> <template> <component 
 :is="activeView" v-model="contact"/> </template> <script> export default { name: "IsProp", props: ["activeView"], components: { /* ... */ }, </script> PARENT COMPONENT CHILD LOADING DYNAMIC BASED ON PROP
  43. 43. THE BASICS - COMPONENT <component :is="propExample" />
  44. 44. THE BASICS - COMPONENT :IS A COMPUTED PROPERTY <component :is="activeView" v-model="contact" /> <script> export default { /* .... */ computed: { activeView: function() { return this.isContact ? "ContactDetails" : "AddressDetails"; } }, data() { return { isContact: true, } } </script>
  45. 45. THE BASICS - COMPONENT <component :is="computedExample"/>
  46. 46. PROPS AND EVENTS COMPONENTS
  47. 47. <address-view :street1="contact.street1" :street2="contact.street1" :country="contact.street1" :province="contact.street1" :postalCode=“contact.street1"/> <contact-view :firstName="contact.firstName" :lastName="contact.lastName" :userName="contact.userName" :email=“contact.email"/> PROPS AND EVENTS PROPS ▸ Props can be bound just like any other component
  48. 48. <component :is="activeView" :firstName="contact.firstName" :lastName="contact.lastName" :password="contact.password" :email="contact.email" :userName="contact.userName" 
 :street1="contact.street1" :street2="contact.street2" :country="contact.country" :postalCode="contact.postalCode" :province="contact.province"> </component> PROPS AND EVENTS PROPS ▸ Props can be bound just like any other component
  49. 49. <component :is="activeView" :firstName="contact.firstName" :lastName="contact.lastName" :password="contact.password" :email="contact.email" :userName="contact.userName" 
 :street1="contact.street1" :street2="contact.street2" :country="contact.country" :postalCode="contact.postalCode" :province="contact.province"> </component> PROPS AND EVENTS PROPS ▸ Props can be bound just like any other component ▸ Unknown props will not cause errors CONTACT PROPS ADDRESS PROPS
  50. 50. PROPS AND EVENTS DO WE NEED TO KNOW ALL PROPS UP FRONT?
  51. 51. PROPS AND EVENTS DO WE NEED TO KNOW ALL PROPS UP FRONT? ▸ No, “v-bind” to the rescue
  52. 52. PROPS AND EVENTS DO WE NEED TO KNOW ALL PROPS UP FRONT? ▸ No, “v-bind” to the rescue ▸ Object Properties that match Props get bound as Props
  53. 53. PROPS AND EVENTS DO WE NEED TO KNOW ALL PROPS UP FRONT? ▸ No, “v-bind” to the rescue ▸ Object Properties that match Props get bound as Props <component :is="activeView" :firstName="contact.firstName" :lastName="contact.lastName" :password="contact.password" :email="contact.email" :userName="contact.userName" :street1="contact.street1" :street2="contact.street2" :country="contact.country" :postalCode="contact.postalCode" :province="contact.province"> </component> V-BIND BEFORE
  54. 54. PROPS AND EVENTS DO WE NEED TO KNOW ALL PROPS UP FRONT? ▸ No, “v-bind” to the rescue ▸ Object Properties that match Props get bound as Props <component :is="activeView" :firstName="contact.firstName" :lastName="contact.lastName" :password="contact.password" :email="contact.email" :userName="contact.userName" :street1="contact.street1" :street2="contact.street2" :country="contact.country" :postalCode="contact.postalCode" :province="contact.province"> </component> V-BIND BEFORE <component :is="activeView" v-bind="contact"> </component> V-BIND AFTER
  55. 55. PROPS AND EVENTS WHAT HAPPENS TO THE OTHER PROPS?
  56. 56. PROPS AND EVENTS WHAT HAPPENS TO THE OTHER PROPS? ▸ Available in $attrs
  57. 57. PROPS AND EVENTS WHAT HAPPENS TO THE OTHER PROPS? ▸ Available in $attrs ▸ inheritAttrs: true (default)
  58. 58. PROPS AND EVENTS WHAT HAPPENS TO THE OTHER PROPS? ▸ Available in $attrs ▸ inheritAttrs: true (default) ▸ Become attributes on the root of the component
  59. 59. PROPS AND EVENTS WHAT HAPPENS TO THE OTHER PROPS? ▸ Available in $attrs ▸ inheritAttrs: true (default) ▸ Become attributes on the root of the component ▸ inheritAttrs: false
  60. 60. PROPS AND EVENTS WHAT HAPPENS TO THE OTHER PROPS? ▸ Available in $attrs ▸ inheritAttrs: true (default) ▸ Become attributes on the root of the component ▸ inheritAttrs: false ▸ Can control how they are bound
  61. 61. PROPS AND EVENTS INHERITATTRS: TRUE (DEFAULT) ▸ Become attributes on the root element of the component contact: { street1: "19 York Street", street2: "6th Floor", country: "Canada", postalCode: "N1N 2N2", province: "Ontario", firstName: "Evan", lastName: "Schultz", password: "iwantmymoney", email: "evan@", userName: "e-schultz", } CONTACT UNKNOWN PROPS
  62. 62. PROPS AND EVENTS INHERITATTRS: FALSE ▸ Can control where they are bound
  63. 63. PROPS AND EVENTS INHERITATTRS: FALSE ▸ Can control where they are bound <label>{{label}}</label> <input type="text" :name="name" :placeholder="placeholder" v-bind="$attrs">
  64. 64. PROPS AND EVENTS INHERITATTRS: FALSE ▸ Can control where they are bound ▸ Can pass down to other components <label>{{label}}</label> <input type="text" :name="name" :placeholder="placeholder" v-bind="$attrs">
  65. 65. PROPS AND EVENTS INHERITATTRS: FALSE ▸ Can control where they are bound ▸ Can pass down to other components ▸ Useful for working with other libraries <b-dropdown :value="value" @input="$emit('input',$event)" v-bind="$attrs"> <!-- ... --> <b-dropdown-item v-for="(option) in options" :key="option" :value="option"> {{option}} </b-dropdown-item> </b-dropdown>
  66. 66. PROPS AND EVENTS EVENTS
  67. 67. PROPS AND EVENTS EVENTS ▸ Can bind DOM events
  68. 68. PROPS AND EVENTS EVENTS ▸ Can bind DOM events ▸ @click
  69. 69. PROPS AND EVENTS EVENTS ▸ Can bind DOM events ▸ @click ▸ @focus
  70. 70. PROPS AND EVENTS EVENTS ▸ Can bind DOM events ▸ @click ▸ @focus ▸ Can bind custom events
  71. 71. PROPS AND EVENTS EVENTS ▸ Can bind DOM events ▸ @click ▸ @focus ▸ Can bind custom events ▸ @upperCase=“switchCase(‘upperCase')"
  72. 72. FORMS PUTTING IT TOGETHER
  73. 73. FORMS THE SETUP
  74. 74. FORMS THE SETUP ▸ JSON Schema
  75. 75. FORMS THE SETUP ▸ JSON Schema ▸ v-for over the collection
  76. 76. FORMS THE SETUP ▸ JSON Schema ▸ v-for over the collection ▸ Playing nice with v-model
  77. 77. schema: [{ fieldType: "SelectList", name: "title", multi: false, label: "Title", options: ["Ms", "Mr", "Mx", "Dr", "Madam", "Lord"] }, { fieldType: "TextInput", placeholder: "First Name", label: "First Name", name: "firstName" }, { fieldType: "NumberInput", placeholder: "Age", name: "age", label: "Age", minValue: 0 }
  78. 78. FORMS EXAMPLE COMPONENT - TEXT INPUT <template> <div> <label>{{label}}</label> <input type="text" :name=“name" :value=“value" :placeholder="placeholder"> </div> </template>
 <script> export default { name: "TextInput", props: ["placeholder", "label", “name”, "value"] }; </script>
  79. 79. FORMS V-FOR <component v-for="(field, index) in schema" :key="index" :is="field.fieldType" v-bind="field"> </component>
  80. 80. FORMS <component v-for="(field, index) in schema" :key="index" :is="field.fieldType" v-bind="field"> </component>
  81. 81. V-IF WHAT ABOUT
  82. 82. FORMS WHAT ABOUT V-IF ▸ Still useful for simple cases
  83. 83. FORMS WHAT ABOUT V-IF ▸ Still useful for simple cases ▸ Can quickly bloat templates
  84. 84. FORMS WHAT ABOUT V-IF ▸ Still useful for simple cases ▸ Can quickly bloat templates ▸ Repetitive code can become error prone
  85. 85. FORMS WHAT ABOUT V-IF <div v-for="(field, index) in schema" :key="index"> <text-input v-if="field.fieldType === 'TextInput'" :value="formData[field.name]" @input="updateForm(field.name, $event)" v-bind="field.props"></text-input> <password-input v-else-if="field.fieldType === 'PasswordInput'" :value="formData[field.name]" @input="updateForm(field.name, $event)" v-bind="field.props"></password-input> <select-list v-else-if="field.fieldType === 'SelectList'" :value="formData[field.name]" @input="updateForm(field.name, $event)" v-bind="field.props"></select-list> <!--- and repeat for each dynamically loadable component --> </div>
  86. 86. FORMS WHAT ABOUT V-IF <component v-for="(field, index) in schema" :key="index" :is="field.fieldType" :value="formData[field.name]" @input="updateForm(field.name, $event)" v-bind="field.props"> </component>
  87. 87. DATA BINDING FORMS
  88. 88. FORMS DATA BINDING - EXPLORING V-MODEL <input v-model="value"> <input :value="value" @input=“value = $event.target.value"> IS SUGAR ON TOP OF
  89. 89. FORMS DATA BINDING - GOALS FOR THE COMPONENT ▸ Let the parent provide a value to the child component ▸ Let the parent know that the value has changed
  90. 90. FORMS DATA BINDING - GOALS FOR THE COMPONENT <label>{{label}}</label> <input type="text" :name="name" :value="value" @input="$emit('input',$event.target.value)" :placeholder="placeholder">
  91. 91. ▸ Bind to “:value” FORMS DATA BINDING - GOALS FOR THE COMPONENT <label>{{label}}</label> <input type="text" :name="name" :value="value" @input="$emit('input',$event.target.value)" :placeholder="placeholder">
  92. 92. ▸ Bind to “:value” ▸ Emit an “@input” event to notify the parent FORMS DATA BINDING - GOALS FOR THE COMPONENT <label>{{label}}</label> <input type="text" :name="name" :value="value" @input="$emit('input',$event.target.value)" :placeholder="placeholder">
  93. 93. FORMS DATA BINDING - COMPONENT WITH V-MODEL <component v-for="(field, index) in schema" :key="index" :is="field.fieldType" v-model="formData[field.name]" v-bind="field"> </component> export default {   data() { return { formData: { firstName: 'Evan' }, schema: [ { /* .... */ }] }
  94. 94. DEMO TIME LETS SEE IT
  95. 95. THATS ALL THANKS! ▸ Repo: bit.ly/js-camp-2018-demo ▸ Demo:bit.ly/js-camp-bcn-demo ▸ Blog: bit.ly/data-driven-vue @e_p82 e-schultz
  96. 96. THANKS! Evan Schultz @e_p82 e-schultz evan@rangle.io

×