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

Clean and Maintainable Ext JS: Tools and Patterns for Greater Code Clarity

Clean and Maintainable Ext JS: Tools and Patterns for Greater Code Clarity

Have you ever looked back on an Ext JS or Sencha Touch project and been bewildered by the volumes of code you’ve written? Perhaps you’ve joined a team and had start editing some Ext JS classes, but found yourself lost in reams of JSON configurations? This talk will cover concepts and techniques to clean up some of this code using tooling and design patterns. You’ll learn how to use some or all of these techniques to your code base, to make your code base more readable and maintainable.

From Senchacon 2013.

Matt Goldspink

July 17, 2013
Tweet

Other Decks in Technology

Transcript

  1. if  ((me.ignoreSelection  <=  0)  &&  me.isExpanded)  {      valueStore.each(function(rec)

      {      if  (Ext.Array.contains(selectedRecords,  rec))  {                  mergedRecords.push(rec);              }          });          mergedRecords  =  Ext.Array.merge(mergedRecords,  selectedR                                if  (!me.multiSelect  ||  !me.pinList)  {                                        Ext.defer(me.collapse,  1,  me);                                }      if  (valueStore.getCount()  >  0)  {                    me.fireEvent('select',  me,  valueStore.getRange());      } }
  2. if  ((me.ignoreSelection  <=  0)  &&  me.isExpanded)  {      

           valueStore.each(function(rec)  {                    if  (Ext.Array.contains(selectedRecords,  rec))  {                            mergedRecords.push(rec);                    }              });              mergedRecords  =  Ext.Array.merge(mergedRecords,  selectedR              if  (!me.multiSelect  ||  !me.pinList)  {                      Ext.defer(me.collapse,  1,  me);              }              if  (valueStore.getCount()  >  0)  {                      me.fireEvent('select',  me,  valueStore.getRange());              } }
  3. - Donald Knuth “Programs are meant to be read by

    humans and only incidentally for computers to execute.”
  4. Existing Style Guides • Douglas Crockford - Code Conventions for

    JavaScript • Google - JavaScript Style Guide
  5. Existing Style Guides • Douglas Crockford - Code Conventions for

    JavaScript • Google - JavaScript Style Guide • JQuery - JavaScript Style Guide
  6. Existing Style Guides • Douglas Crockford - Code Conventions for

    JavaScript • Google - JavaScript Style Guide • JQuery - JavaScript Style Guide • Idiomatic.js - Style Manifesto
  7. Existing Style Guides • Douglas Crockford - Code Conventions for

    JavaScript • Google - JavaScript Style Guide • JQuery - JavaScript Style Guide • Idiomatic.js - Style Manifesto • Sencha - ???
  8. {        quotmark:  "double",        

     indent:  4,          //  no  comma  first        laxcomma:  false,          //  enforce  braces        curly:  true,          //  constructors  must  be  caps        newcap:  true,          //  max  line  length        maxlen:  80,          //  max  number  of  fn  params        maxparams:  3 }
  9. $  jshint  ./www/app www/app/app.js:  line  2,  col  12,  Strings  

    must  use  doublequote. www/app/app.js:  line  7,  col  9,  Expected   ‘stores’  to  have  an  indentation  at  5   instead  at  9. 2  errors $ 2  errors
  10. Ext.application({        name:  'Pandora',        autoCreateViewport:

     true,        models:  ["Station",  "Song"],                stores:  ["Stations"],        controllers:  ["Station",  "Song"] });
  11. $  jsduck  www/app  \            -­‐-­‐builtin-­‐classes

     \            -­‐-­‐output  build/docs
  12. describe("MyApp.view.AppGrid",  function()  {        beforeEach(function()  {    

               Ext.syncRequire("MyApp.view.AppGrid");        });        it("can  be  loaded",  function()  {                expect(MyApp.view.AppGrid).not.toBe(undefined);        }); });
  13. Running   “sencha_jasmine_wrapper:app”   (sencha_jasmine_wrapper)  task Testing  jasmine  specs  via

     phantom ............. 13  specs  in  0.117s. >>  0  failures Done,  without  errors.
  14.                /**    

                 *  Sencha  Jasmine                  *                  *  Sets  up  Jasmine  and  runs  specs  using                  *  PhantomJS  headlessly.                  */                sencha_jasmine:  {                        options:  {                                specs:  ["tests/specs/**/*.js"],                                extFramework:  "www/ext-­‐4.1.1a",                                extLoaderPaths      :  {                                        "Pandora"  :  "www/app"                                }                        }                  }
  15. new  Ext.form.field.ComboBox({        fieldLabel:  'Choose  State',    

       store:  states,        displayField:  'name',        typeAhead:  true }); new  Ext.form.field.ComboBox({        fieldLabel:  'Choose  State',        store:  states,        displayField:  'name',        editable:  false }); new  Ext.form.field.ComboBox({        fieldLabel:  'Choose  State',        store:  states,        displayField:  'name',        validateOnChange:  true }); version 2 version 1 version 3
  16.        makeDropDown:  function(label,  extraConfig)  {      

             return  new  Ext.form.field.ComboBox(                        Ext.apply(extraConfig,  {                                emptyText:  label,                                triggerWidth:  24,                                queryMode:  "local"                        })                );        } Ext.define("Causata.factory.DropDownFactory",  {               });
  17. Ext.define("MyApp.view.UpdateForm",  {        createOptionDropDown:  function()  {    

               } });                return  new  Ext.form.field.ComboBox({                                emptyText:  "Select  option",                                triggerWidth:  24,                                queryMode:  "local",                                store:  this.getOptionStore(),                                displayField:  "name"                });
  18. Ext.define("MyApp.view.UpdateForm",  {        createOptionDropDown:  function()  {    

               } });        return  this.dropDownFactory.makeDropDown(                "Select  option",  {                        store:  this.getOptionStore(),                        displayField:  "name"              }        );
  19.        dropDownFactory:  new  Causata.factory.DropDownFactory(), Ext.define("MyApp.view.UpdateForm",  {    

       extend:  "Ext.form.Panel",                initComponent:  function()  {                this.buildItems();                this.callParent();        } });
  20. Ext.define("MyApp.view.UpdateForm",  {        extend:  "Ext.form.Panel",      

             initComponent:  function()  {                this.buildItems();                this.callParent();        } });        dropDownFactory:  Causata.factory.DropDownFactory,
  21.        inject:  ["dropDownFactory"], Ext.define("MyApp.view.UpdateForm",  {      

     extend:  "Ext.form.Panel",                initComponent:  function()  {                this.buildItems();                this.callParent();        } });
  22. Ext.define("MyApp.view.UpdateForm",  {        extend:  "Ext.form.Panel",      

             initComponent:  function()  {                this.buildItems();                this.callParent();        } });        inject:  ["dropDownFactory"],
  23. Ext.define("MyApp.view.UpdateForm",  {        extend:  "Ext.form.Panel",      

             initComponent:  function()  {                this.buildItems();                this.callParent();        } });        inject:  ["dropDownFactory"], Advanced Application Architecture with Deft JS Thursday 3:35pm - Northern Hemisphere E-3
  24. new  Ext.grid.Panel({        columns:  [{      

             dataIndex:  "date",                renderer:  Ext.util.Format.dateRenderer("m/d/Y")      },  {                dataIndex:  "price",                renderer:  Ext.util.Format.usMoney      }],      store:  transactionStore });
  25. new  Ext.grid.Panel({        columns:  [{      

             dataIndex:  "date",                renderer:  formatterFactory.getDateFormatter()      },  {                dataIndex:  "price",                renderer:  formatterFactory.getCurrencyFormatter()      }],      store:  transactionStore });
  26. gridRowDblClick:  function(rowModel,  song,  index)  {        this.getApplication().fireEvent(  

                 “PLAY_SONG”,  song        ); } Ext.define("SubTunes.controller.SongGrid",  {         });
  27. onLaunch:  function()  {        this.getApplication().on(      

             "PLAY_SONG",  this.playSong,  this        ); }, playSong:  function(song)  {        this.getPlayBarView().update(song); } Ext.define("SubTunes.controller.PlayBar",  {         });
  28. gridRowDblClick:  function(rowModel,  song,  index)  {        this.getApplication().fireEvent(  

                 "PLAY_SONG",  song        ); } Ext.define("SubTunes.controller.SongGrid",  {         });
  29. gridRowDblClick:  function(rowModel,  song,  index)  {        this.getApplication().fireEvent(  

                 SubTunes.Channels.PLAY_SONG,                  song        ); } Ext.define("SubTunes.controller.SongGrid",  {         });
  30. Ext.define("SubTunes.Channels",  {        statics:  {      

             PLAY_SONG:  "st_play_song"        } } gridRowDblClick:  function(rowModel,  song,  index)  {        this.getApplication().fireEvent(                SubTunes.Channels.PLAY_SONG,                  song        ); } Ext.define("SubTunes.controller.SongGrid",  {         });
  31. this.getApplication().on(channel,  safeWrap(this.handler)); safeWrap:  function(fn)  {        return  ()

     {                try  {                        fn.apply(this,  arguments);                }  catch  (e)  {                        console.log(e);                }        } }
  32. Ext.define("MyApp.adapter.ForceToD3Adapter",  {        constructor:  function(forceExtModel)  {    

               this.forceExtModel  =  forceExtModel;        },        getNodes:  function()  {                //  iterate  through  each  node  and  convert  to  d3  format        },        getLinks:  function()  {                //  iterate  through  each  link  and  convert  to  d3  format        } });
  33. Ext.define("MyApp.adapter.ForceToD3Adapter",  {        constructor:  function(forceExtModel)  {...},    

       getNodes:  function()  {                var  d3nodes  =  [];                this.forceExtModel.nodes().each(function(node)  {                        d3nodes.push({                                name:  node.get("name"),                                id:  node.getId()                        });                });                return  d3nodes;        },        getLinks:  function()  {...} });
  34. setForceAdapter:  function(extAdapter)  {        var  force  =  d3.layout.force()

                                       .charge(-­‐120)                                    .size([500,  500]);        force.on("tick",  this.handleTick);        force.nodes(extAdapter.getNodes())                  .links(extAdapter.getLinks())                  .start(); }
  35. Laying foundations • Automated builds • JSHint for enforcing coding

    standards • JSDuck docs • PhantomJS and Jasmine for testing
  36. Design patterns • The factory pattern for re-usable UI components

    • The mediator pattern for cross-app communication • The adapter pattern for integrating with 3rd party lib
  37. More design patterns • Service proxy pattern - deal with

    unwieldy services • Dependency Injection with Deft.js - highly recommend going to their talk • Promises - also with Deft.js • Memento pattern - re-instate destroyed views in Sencha Touch
  38. • Maintainable Javascript - Nicholas Zakas • Design Patterns -

    Gang Of Four • Learning JavaScript Design Patterns - Addy Osmani • Clean Code - Robert C Martin
  39. Take the Survey! • Session Survey - Available on the

    SenchaCon mobile app - http://app.senchacon.com • Be Social! - @SenchaCon - #SenchaCon - @mattgoldspink