Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Web developers, meet Flight!

Web developers, meet Flight!

It becomes increasingly important to keep your JavaScript code structured once you start taking client-side development more seriously, even more so when you make the switch to building single page apps using Rails (either the full stack or just Rails API) for purely JSON responses, letting JavaScript handle the bulk of the client-side rendering. Twitter's [Flight](http://flightjs.github.io/) framework can fly you through the spaghetti code horror by wrapping the behaviour of all your UI elements into modular components that are purely event-driven. This leads to highly decoupled and bug-free code that makes maintenance and testing a charm.

Peppered with real-world code and sprinkled with best practices gained through my experience, this talk will prepare you to start using Flight in your Rails app today.

Avatar for Prashant Tiwari

Prashant Tiwari

July 19, 2014

Other Decks in Programming

Transcript

  1. FLIGHT “Flight is a lightweight, component-based JavaScript framework that maps

    behavior to DOM nodes.” http://flightjs.github.io
  2. A FLIGHT COMPONENT Is a constructor for an object Is

    attached to one or more DOM nodes using a CSS selector Has in-built features for triggering and responding to events
  3. MOTIVATION Getting the current Bootstrap environment v a r g

    e t B o o t s t r a p E n v i r o n m e n t = f u n c t i o n ( ) { v a r e n v s = [ ' x s ' , ' s m ' , ' m d ' , ' l g ' ] ; v a r $ e l = $ ( ' < d i v > ' ) ; $ e l . a p p e n d T o ( $ ( ' b o d y ' ) ) ; f o r ( v a r i = e n v s . l e n g t h - 1 ; i > = 0 ; i - - ) { v a r e n v = e n v s [ i ] ; $ e l . a d d C l a s s ( ' h i d d e n - ' + e n v ) ; i f ( $ e l . i s ( ' : h i d d e n ' ) ) { $ e l . r e m o v e ( ) ; r e t u r n e n v ; } } ; }
  4. Reporting the environment whenever w i n d o w

    is resized $ ( w i n d o w ) . r e s i z e ( g e t B o o t s t r a p E n v i r o n m e n t ) ;
  5. Reporting environment only when it changes v a r c

    h e c k E n v C h a n g e d = f u n c t i o n ( ) { i f ( ! t h i s . b o o t s t r a p E n v ) { t h i s . b o o t s t r a p E n v = t h i s . g e t B o o t s t r a p E n v i r o n m e n t ( ) ; } e l s e i f ( ( v a r n e w B o o t s t r a p E n v = t h i s . g e t B o o t s t r a p E n v i r o n m e n t ( ) ) ! = t h i s . b o o t s t r a p E n v ) { t h i s . b o o t s t r a p E n v = n e w B o o t s t r a p E n v ; } r e t u r n t h i s . b o o t s t r a p E n v ; } t h i s . b o o t s t r a p E n v ; $ ( w i n d o w ) . r e s i z e ( c h e c k E n v C h a n g e d ) ;
  6. Create a Flight component and trigger the event! / /

    . . . f u n c t i o n b o o t s t r a p E n v C h a n g e ( ) { t h i s . r e s i z e d = f u n c t i o n ( e v e n t ) { $ t h i s = e v e n t . d a t a ; i f ( ! $ t h i s . b o o t s t r a p E n v ) { $ t h i s . b o o t s t r a p E n v = $ t h i s . g e t B o o t s t r a p E n v i r o n m e n t ( ) ; } e l s e i f ( ( n e w B o o t s t r a p E n v = $ t h i s . g e t B o o t s t r a p E n v i r o n m e n t ( ) ) ! = $ t h i s . b o o t s t r a p E n v ) { $ t h i s . b o o t s t r a p E n v = n e w B o o t s t r a p E n v ; $ t h i s . t r i g g e r ( ' d a t a B o o t s t r a p E n v C h a n g e d ' , { e n v : n e w B o o t s t r a p E n v } ) ; } } / / t h i s . g e t B o o t s t r a p E n v i r o n m e n t = f u n c t i o n ( ) { } t h i s . a f t e r ( ' i n i t i a l i z e ' , f u n c t i o n ( ) { $ ( w i n d o w ) . r e s i z e ( t h i s , t h i s . r e s i z e d ) ; } ) ; } / / . . .
  7. And attach it to the DOM / / g e

    t t h e c o m p o n e n t a n d a t t a c h i t t o a s e l e c t o r o n t h e D O M B o o t s t r a p E n v C h a n g e . a t t a c h T o ( d o c u m e n t ) ; There may also be other components... M y L i s t . a t t a c h T o ( ' . l i s t ' ) ; M y F o r m . a t t a c h ( ' f o r m ' ) ;
  8. One of which may be listening for just that event

    / / m y _ l i s t . j s / / . . . t h i s . h a n d l e B o o t s t r a p E n v C h a n g e d = f u n c t i o n ( e v e n t , d a t a ) { / / d o s o m e t h i n g t h i s . t r i g g e r ( ' l i s t R e f r e s h e d ' , { / / . . . a n d p e r h a p s t r i g g e r a n o t h e r e v e n t t o i n f o r m s o m e t h i n g h a p p e n e d } ) ; } t h i s . a f t e r ( ' i n i t i a l i z e ' , f u n c t i o n ( ) { t h i s . o n ( d o c u m e n t , ' d a t a B o o t s t r a p E n v C h a n g e d ' , t h i s . h a n d l e B o o t s t r a p E n v C h a n g e d ) ; } ) ; And may now trigger its own event (l i s t R e f r e s h e d )
  9. # n p m i n s t a l

    l - g b o w e r b o w e r i n s t a l l f l i g h t - - s a v e < 6K gzipped + minified / Depends on jQuery
  10. ASYNCHRONOUS MODULE DEFINITION Load JS asynchronously in the browser Specify

    load order for dependencies Bring the joy of OOP to JS
  11. Loading the Bootstrap collapse plugin using RequireJS d e f

    i n e ( [ ' j q u e r y ' , ' b o o t s t r a p / t r a n s i t i o n ' , ' b o o t s t r a p / c o l l a p s e ' ] , f u n c t i o n ( $ , t r a n s i t i o n , c o l l a p s e ) { $ ( ' . c o l l a p s e ' ) . c o l l a p s e ( ) ; } ) ; You maintained the correct order of dependencies You loaded only the plugins you need (You probably didn't need the other Bootstrap plugins anyway)
  12. Alternate syntax d e f i n e ( f

    u n c t i o n ( r e q u i r e ) { v a r $ = r e q u i r e ( ' j q u e r y ' ) ; r e q u i r e ( ' b o o t s t r a p / t r a n s i t i o n ' ) ; r e q u i r e ( ' b o o t s t r a p / c o l l a p s e ' ) ; $ ( ' . c o l l a p s e ' ) . c o l l a p s e ( ) ; } ) ;
  13. WHAT THIS MEANS FOR YOU No longer using the /

    / = r e q u i r e directives in a p p l i c a t i o n . j s Requiring each script manually, as and when needed (RequireJS optimizer can still compile and digest an uglified a p p l i c a t i o n . j s for production) Losing the ability to intentionally or accidentally re-use variables/functions defined in other JS files without specifically requiring them Forcing yourself to think in terms of completely encapsulated modules Becoming a better JavaScript developer!
  14. So you could write your component as v a r

    N a v b a r = f l i g h t . c o m p o n e n t ( f u n c t i o n ( ) { t h i s . a t t r i b u t e s ( { n a v b a r : ' . n a v b a r ' , m o b i l e E n v : ' x s ' } ) ; t h i s . h a n d l e B o o t s t r a p E n v C h a n g e d = f u n c t i o n ( e v e n t , d a t a ) { i f ( d a t a . e n v = = t h i s . a t t r . m o b i l e E n v ) t h i s . s e l e c t ( ' n a v b a r ' ) . r e m o v e C l a s s ( ' n a v b a r - f i x e d - t o p ' ) . a d d C l a s s ( ' n a v b a r - s t a t i c - t o p ' ) ; e l s e t h i s . s e l e c t ( ' n a v b a r ' ) . r e m o v e C l a s s ( ' n a v b a r - s t a t i c - t o p ' ) . a d d C l a s s ( ' n a v b a r - f i x e d - t o p ' ) ; } t h i s . a f t e r ( ' i n i t i a l i z e ' , f u n c t i o n ( ) { t h i s . o n ( d o c u m e n t , ' d a t a B o o t s t r a p E n v C h a n g e d ' , t h i s . h a n d l e B o o t s t r a p E n v C h a n g e d ) ; } ) ; } ) ; N a v b a r . a t t a c h T o ( ' d i v . n a v ' ) ;
  15. Or use the AMD loader like so d e f

    i n e ( f u n c t i o n ( r e q u i r e ) { v a r d e f i n e C o m p o n e n t = r e q u i r e ( ' f l i g h t / l i b / c o m p o n e n t ' ) ; r e t u r n d e f i n e C o m p o n e n t ( n a v b a r ) ; f u n c t i o n n a v b a r ( ) { t h i s . a t t r i b u t e s ( { n a v b a r : ' . n a v b a r ' , m o b i l e E n v : ' x s ' } ) ; t h i s . h a n d l e B o o t s t r a p E n v C h a n g e d = f u n c t i o n ( e v e n t , d a t a ) { i f ( d a t a . e n v = = t h i s . a t t r . m o b i l e E n v ) t h i s . s e l e c t ( ' n a v b a r ' ) . r e m o v e C l a s s ( ' n a v b a r - f i x e d - t o p ' ) . a d d C l a s s ( ' n a v b a r - s t a t i c - t o p ' ) ; e l s e t h i s . s e l e c t ( ' n a v b a r ' ) . r e m o v e C l a s s ( ' n a v b a r - s t a t i c - t o p ' ) . a d d C l a s s ( ' n a v b a r - f i x e d - t o p ' ) ; } t h i s . a f t e r ( ' i n i t i a l i z e ' , f u n c t i o n ( ) { t h i s . o n ( d o c u m e n t , ' d a t a B o o t s t r a p E n v C h a n g e d ' , t h i s . h a n d l e B o o t s t r a p E n v C h a n g e d ) ; } ) ; } } ) ;
  16. We can then instantiate our component in a p p

    l i c a t i o n . j s v a r N a v b a r = r e q u i r e ( ' n a v b a r ' ) ; N a v b a r . a t t a c h T o ( ' d i v . n a v ' ) ; (It's ok if you aren't ready to switch to RequireJS yet. You can use the standalone version in the first example.)
  17. WHAT WE JUST DID Defined a Flight component that Is

    attached to the d i v element(s) with the n a v class Is encapsulated from the outside world Nobody knows it even exists (a t t a c h T o doesn't return a handle) Can trigger events on any changes to itself and its children Will react only to events it’s listening for
  18. COMPONENT TYPES (Conceptual distinctions) UI Components Attached to nodes user

    directly interacts with Generate UI events Data Components Attached to top-level nodes with no user interaction (usually d o c u m e n t ) Listen to UI events to monitor application state Generate data events
  19. LIFECYCLE METHODS i n i t i a l i

    z e — called at C o m p o n e n t . a t t a c h T o ( ) t e a r d o w n — called at C o m p o n e n t . t e a r d o w n A l l ( ) MIXED IN WITH THE ADVICE API b e f o r e — do something before a function call a f t e r — after a function call a r o u n d — during a function call, can override the function definition itself!
  20. Good news: Advice API can be used with regular objects

    too! / / m y _ w o r l d . j s d e f i n e ( f u n c t i o n ( ) { v a r m y W o r l d = { p r i n t : f u n c t i o n ( ) { c o n s o l e . l o g ( ' w o r l d ' ) ; } } ; r e t u r n m y W o r l d ; } ) ; / / m y _ h e l l o . j s d e f i n e ( f u n c t i o n ( r e q u i r e ) { v a r a d v i c e = r e q u i r e ( ' f l i g h t / l i b / a d v i c e ' ) ; v a r m y W o r l d = r e q u i r e ( ' m y _ w o r l d ' ) ; a d v i c e . w i t h A d v i c e . c a l l ( m y W o r l d ) ; m y W o r l d . b e f o r e ( ' p r i n t ' , f u n c t i o n ( ) { c o n s o l e . l o g ( ' h e l l o ' ) ; } ) } ) ; v a r m y W o r l d = r e q u i r e ( ' m y _ w o r l d ' ) ; m y W o r l d . p r i n t ( ) ; / / h e l l o \ n w o r l d
  21. OTHER OBJECTS OF INTEREST t h i s . n

    o d e / t h i s . $ n o d e The HTML/jQuery element corresponding to this component t h i s . a t t r . < a t t r i b u t e N a m e > Refers to an attribute stored in t h i s . a t t r i b u t e s * t h i s . s e l e c t ( ' l i s t I t e m S e l e c t o r ' ) = = t h i s . $ n o d e . f i n d ( ' . i t e m ' ) Refers to the jQuery element named by l i s t I t e m S e l e c t o r in t h i s . a t t r i b u t e s *
  22. TRIGGERING AN EVENT t h i s . t r

    i g g e r ( [ s e l e c t o r , ] e v e n t T y p e [ , d a t a ] ) s e l e c t o r : the triggering node (default = component node) e v e n t T y p e : the event name d a t a : serializable object to pass along t h i s . t r i g g e r ( ' u i C a l e n d a r R e n d e r e d ' , { i d : t h i s . t o d a y E l e m I d , d a t e : t h i s . t o d a y I d } ) ;
  23. HANDLING AN EVENT t h i s . o n

    ( [ s e l e c t o r , ] e v e n t T y p e , h a n d l e r ) s e l e c t o r : the node on which to listen (default = component node) h a n d l e r : a callback function or another named event that will be triggered t h i s . o n ( ' u i C a l e n d a r R e n d e r e d ' , t h i s . h a n d l e C a l e n d a r R e n d e r e d ) ; t h i s . h a n d l e C a l e n d a r R e n d e r e d = f u n c t i o n ( e v e n t , d a t a ) { / / e v e n t i s t h e j Q u e r y e v e n t / / d a t a c o n t a i n s t h e p a y l o a d o r t h e t a r g e t e l e m e n t ( = = d a t a . e l ) v a r i d = d a t a . i d ; v a r d a t e = d a t a . d a t e ; }
  24. EVENT BUBBLING For the event u i L i s

    t I t e m C l i c k e d < h t m l > < b o d y > < d i v > < / d i v > < u l > < l i c l a s s = ' i t e m ' > < / l i > < l i c l a s s = ' i t e m ' > < / l i > < / u l > < / b o d y > < / h t m l > l i . i t e m > u l > b o d y > d o c u m e n t > w i n d o w
  25. NAMING CONVENTIONS Name events in the passive — assume nobody

    exists to listen to or serve you! Prefix UI events with 'ui': u i F o r m S u b m i t t e d Prefix data events with 'data': d a t a A j a x R e s p o n s e Make data requests sound very needy: u i N e e d s L i s t D a t a Prefix handlers with 'handle': h a n d l e N e e d s L i s t D a t a
  26. HANDS OFF APPROACH TO DATA HANDLING Agnostic on how to

    provide, maintain and render data (Including whether to render on the server or client) Encourages to map data on the DOM itself, using d a t a - a t t r i b u t e s to maintain state Event data is passed around as plain (serializable) objects Just enough data may be passed around to help infer full state from the target node's d a t a - a t t r i b u t e s
  27. FLIGHT MIXINS Abstract out common, reusable functionality from component(s) Integrate

    with the lifecycle of the mixing component(s) Inherit the components’ a t t r i b u t e s , can also define their own Receive all the components’ events (and override their handlers if re-defined) Multiple mixins can be applied to a component
  28. MIXIN EXAMPLE HTML < d i v > < h

    1 c l a s s = " t i t l e " > < / h 1 > < h 4 > B y < s p a n c l a s s = " a u t h o r " > < / s p a n > < / h 4 > < d i v c l a s s = " c o n t e n t " > < / d i v > < / d i v > Typical code for client-side rendering v a r g e t P o s t = f u n c t i o n ( ) { $ . a j a x ( { u r l : ' / p o s t s . j s o n ' , s u c c e s s : f u n c t i o n ( d a t a ) { s h o w P o s t ( d a t a . p o s t ) ; } } ) ; } v a r s h o w P o s t = f u n c t i o n ( p o s t ) { $ ( ' . t i t l e ' ) . t e x t ( p o s t . t i t l e ) ; $ ( ' . a u t h o r ' ) . t e x t ( p o s t . a u t h o r ) ; $ ( ' . c o n t e n t ' ) . h t m l ( p o s t . c o n t e n t ) ; } $ ( d o c u m e n t ) . r e a d y ( g e t P o s t ) ;
  29. Using Mustache Templates < ! - - p o s

    t s . h t m l . e r b - - > < d i v i d = " o u t p u t " > < / d i v > < s c r i p t i d = " t e m p l a t e " t y p e = " t e x t / t e m p l a t e " > { { # p o s t } } < h 1 > { { t i t l e } } < / h 1 > < h 4 > B y { { a u t h o r } } < / h 4 > < d i v > { { c o n t e n t } } < / d i v > { { / p o s t } } < / s c r i p t > Rendering template with Hogan / / p o s t s . j s d e f i n e ( [ ' h o g a n ' ] , f u n c t i o n ( H o g a n ) { v a r t e m p l = H o g a n . c o m p i l e ( $ ( ' # t e m p l a t e ' ) . h t m l ( ) ) $ ( ' # o u t p u t ' ) . h t m l ( t e m p l . r e n d e r ( p o s t ) ) ; } ) ;
  30. Extract the boilerplate into a mixin, using templating / /

    w i t h _ d a t a _ h a n d l e r . j s ( c o n v e n t i o n f o r n a m i n g m i x i n s ) f u n c t i o n w i t h D a t a H a n d l e r ( ) { / / . . . t h i s . g e t D a t a = f u n c t i o n ( ) { v a r $ t h i s = t h i s ; $ . a j a x ( { u r l : t h i s . a t t r . u r l , s u c c e s s : f u n c t i o n ( d a t a ) { $ t h i s . t r i g g e r ( ' d a t a A j a x S u c c e s s ' , d a t a ) ; } , e r r o r : f u n c t i o n ( x h r , s t a t u s , e r r o r ) { $ t h i s . t r i g g e r ( ' d a t a A j a x E r r o r ' , { e r r o r : e r r o r } ) ; } } ) } ; t h i s . h a n d l e S u c c e s s = f u n c t i o n ( e v e n t , d a t a ) { v a r t e m p l = H o g a n . c o m p i l e ( t h i s . s e l e c t ( ' t e m p l a t e ' ) . h t m l ( ) ) t h i s . s e l e c t ( ' o u t p u t ' ) . h t m l ( t e m p l . r e n d e r ( d a t a ) ) ; t h i s . t r i g g e r ( ' d a t a R e n d e r e d ' ) ; } ; t h i s . a f t e r ( ' i n i t i a l i z e ' , f u n c t i o n ( ) { t h i s . g e t D a t a ( ) ; t h i s . o n ( ' d a t a A j a x S u c c e s s ' , t h i s . h a n d l e S u c c e s s ) ; } ) ; } / / . . .
  31. Component using mixin, elegance personified! / / p o s

    t s . j s / / . . . v a r w i t h D a t a R e n d e r e r = r e q u i r e ( ' w i t h _ d a t a _ h a n d l e r ' ) ; r e t u r n d e f i n e C o m p o n e n t ( p o s t , w i t h D a t a R e n d e r e r ) ; f u n c t i o n p o s t ( ) { t h i s . a t t r i b u t e s ( { u r l : ' / p o s t s . j s o n ' } ) ; }
  32. REFERENCES Flight APIs: Flight components: Testing: Bower blog post: AMD:

    RequireJS: requirejs-rails: Mustache: Hogan: Sample app: github.com/flightjs/flight/blob/master/doc/README.md flight-components.jit.su/ http://learnflight.io/blog/2013/10/01/testing-a-flight- data-component bit.ly/bower-rails github.com/amdjs/amdjs-api requirejs.org github.com/jwhitley/requirejs-rails github.com/janl/mustache.js/blob/master/README.md twitter.github.io/hogan.js github.com/prashaantt/flight-on-rails