Slide 1

Slide 1 text

Beyond the DOM: Sane Structure for JS Apps Rebecca Murphey • @rmurphey • FrontTrends 2012 Thursday, April 26, 12

Slide 2

Slide 2 text

rmurphey.com • @rmurphey • bocoup.com Thursday, April 26, 12

Slide 3

Slide 3 text

function ObjInlineDown(e) { if (is.ie) e = event if (is.ie && ! is.ieMac && e.button != 1 && e.button != 2) return if (is.ieMac && e.button != 0) return if (is.ns && ! is.ns4 && e.button != 0 && e.button != 2) return if (is.ns4 && e.which != 1 && e.which != 3) return this.onSelect() this.onDown() } function ObjInlineUp(e) { if (is.ie) e = event if (is.ie && ! is.ieMac && e.button != 1 && e.button != 2) return if (is.ieMac && e.button != 0) return if (is.ns && ! is.ns4 && ! is.nsMac && e.button != 0 && e.button != 2) return if (is.ns4 && e.which != 1 && e.which != 3) return if ((!is.ns4 && e.button == 2) || (is.ns4 && e.which == 3)) { if (this.hasOnRUp) { document.oncontextmenu = ocmNone this.onRUp() setTimeout("document.oncontextmenu = ocmOrig", 100) } } else if (this.hasOnUp) this.onUp() } Thursday, April 26, 12

Slide 4

Slide 4 text

Thursday, April 26, 12

Slide 5

Slide 5 text

Thursday, April 26, 12

Slide 6

Slide 6 text

Thursday, April 26, 12

Slide 7

Slide 7 text

Search
    Thursday, April 26, 12

    Slide 8

    Slide 8 text

    $("#searchForm form").submit(function(e) { alert('submit'); e.preventDefault(); var term = $('#searchForm input').val(), req = $.getJSON('http://search.twitter.com/search.json?callback=?&q=' + encodeURIComponent(term)); req.then(function(resp) { var resultsHTML = $.map(resp.results, function(r) { return '
  • ' + '

    ' + r.text + '

    ' + '

    ' + r.from_user + '

    ' + '
  • '; }).join(''); $('#searchResults').html(resultsHTML); }); }); Thursday, April 26, 12

    Slide 9

    Slide 9 text

    Thursday, April 26, 12

    Slide 10

    Slide 10 text

    Thursday, April 26, 12

    Slide 11

    Slide 11 text

    a').hasClass('md_fullpage')) { // alert('clicked section is current section AND fullpage mode is active; teaser should load'); // Minimize jQuery('#md_tabs_navigation a').removeClass('md_fullpage'); jQuery('.md_body').hide(); jQuery('#md_feature').slideDown('normal',function(){ var bodyContent = jQuery('#md_body_'+ section); bodyContent.fadeOut('normal',function(){ jQuery('#md_tabs_navigation a').each(function(){ var thisSection = jQuery(this).html().replace('

    Slide 12

    Slide 12 text

    Thursday, April 26, 12

    Slide 13

    Slide 13 text

    search data search input search results $("#searchForm form").submit(function(e) { alert('submit'); e.preventDefault(); var term = $('#searchForm input').val(), req = $.getJSON('http://search.twitter.com encodeURIComponent(term)); req.then(function(resp) { var resultsHTML = $.map(resp.results, functi return '
  • ' + '

    ' + r.text + '

    ' + '

    ' + r.from_user + ' '

  • '; }).join(''); $('#searchResults').html(resultsHTML); }); }); Thursday, April 26, 12

    Slide 14

    Slide 14 text

    Thursday, April 26, 12

    Slide 15

    Slide 15 text

    Thursday, April 26, 12

    Slide 16

    Slide 16 text

    Thursday, April 26, 12

    Slide 17

    Slide 17 text

    Thursday, April 26, 12

    Slide 18

    Slide 18 text

    Thursday, April 26, 12

    Slide 19

    Slide 19 text

    define([ 'jquery', 'text!template.html' ], function($, html) { return function() { $('body').append(html); }; }); Thursday, April 26, 12

    Slide 20

    Slide 20 text

    Thursday, April 26, 12

    Slide 21

    Slide 21 text

    require.config({ deps : [ 'main' ], paths : { // JavaScript folders lib : '../lib', plugins : '../lib/plugins', tests : '../tests', app : '.', // Libraries jquery : '../lib/jquery', underscore : '../lib/underscore', backbone : '../lib/backbone', text : '../lib/plugins/text' } }); Thursday, April 26, 12

    Slide 22

    Slide 22 text

    require([ 'use!backbone', 'jquery', 'router', 'models/app' ], function(B, $, Router, app) { $(function() { app.router = new Router(); B.history.start(); }); }); app/main Thursday, April 26, 12

    Slide 23

    Slide 23 text

    Thursday, April 26, 12

    Slide 24

    Slide 24 text

    views display data, announce user interaction, and await further instruction models & collections manage application state and communicate with the server controllers set up views, transport messages from views to models & collections Thursday, April 26, 12

    Slide 25

    Slide 25 text

    app/views/results app/views/searchForm app/views/recentSearches Thursday, April 26, 12

    Slide 26

    Slide 26 text

    #mainbar #sidebar app/controllers/search Thursday, April 26, 12

    Slide 27

    Slide 27 text

    searches collection keeps track of recent search terms search data collection fetches results from the server for a given search term app model keeps track of general application state, including the current search search model for representing individual searches Thursday, April 26, 12

    Slide 28

    Slide 28 text

    search controller searches collection app model app/views/results app/views/searchForm app/views/recentSearches search data server Thursday, April 26, 12

    Slide 29

    Slide 29 text

    $("#searchForm form").submit(function(e) { alert('submit'); e.preventDefault(); var term = $('#searchForm input').val(), req = $.getJSON('http://search.twitter.com/search.json?callba encodeURIComponent(term)); req.then(function(resp) { var resultsHTML = $.map(resp.results, function(r) { return '
  • ' + '

    ' + r.text + '

    ' + '

    ' + r.from_user + '

    ' + '
  • '; }).join(''); $('#searchResults').html(resultsHTML); }); }); Thursday, April 26, 12

    Slide 30

    Slide 30 text

    search controller searches collection app model app/views/results app/views/searchForm app/views/recentSearches search data server Thursday, April 26, 12

    Slide 31

    Slide 31 text

    prepare : function() { _.bindAll(this, 'release', '_onSearch', '_disable'); }, events : { 'submit .search-form' : '_onSearch' }, _onSearch : function(e) { e.preventDefault(); if (this.disabled) { return; } var term = $.trim(this.$('.js-input').val()); if (!term) { return; } this._disable(); this.trigger('search', term); }, release : function() { this.disabled = false; this.$('.js-submit').removeAttr('disabled'); }, Thursday, April 26, 12

    Slide 32

    Slide 32 text

    searchForm.on('search', update); function update(t) { var term = $.trim(t), existing = searches.where({ term : term }), dfd = $.Deferred(), search; app.set('currentSearch', term); if (term) { if (existing.length) { search = existing[0]; search.update(); } else { search = new Search({ term : term }); searches.add(search); } searchData.fetch({ data : { term : term } }) .then(dfd.resolve, dfd.reject) .always(searchForm.release); app.router.navigate('search/' + term); } else { dfd.resolve(); } return dfd; } Thursday, April 26, 12

    Slide 33

    Slide 33 text

    it("should announce the form submission", function() { var t; sf.on('search', function(term) { t = term; }); el.find('.js-input').val('searchterm'); el.find('.search-form').submit(); expect(t).to.be('searchterm'); }); Thursday, April 26, 12

    Slide 34

    Slide 34 text

    it("should update the page when the search form announces a search", function(done) { var searchFormEl = $('.component.search-form').parent(), searchForm = _.filter(s.views, function(v) { return v.$el[0] === searchFormEl[0]; })[0]; s.searchData.on('change', function() { expect($('.component.results').html()).to.contain('srchr'); expect($('.component.recent-searches').html()).to.contain('srchr'); expect(navigatedTo).to.be('search/srchr'); done(); }); searchForm.trigger('search', 'srchr'); }); Thursday, April 26, 12

    Slide 35

    Slide 35 text

    search controller searches collection app model app/views/results app/views/searchForm app/views/recentSearches search data server Thursday, April 26, 12

    Slide 36

    Slide 36 text

    function update(t) { var term = $.trim(t), existing = searches.where({ term : term }), dfd = $.Deferred(), search; app.set('currentSearch', term); if (term) { if (existing.length) { search = existing[0]; search.update(); } else { search = new Search({ term : term }); searches.add(search); } searchData.fetch({ data : { term : term } }) .then(dfd.resolve, dfd.reject) .always(searchForm.release); app.router.navigate('search/' + term); } else { dfd.resolve(); } return dfd; } Thursday, April 26, 12

    Slide 37

    Slide 37 text

    this.bindTo(this.searchData, 'add change', this._update); this.bindTo(this.searchData, 'fetching', function() { this._empty(); this.reset(); }); Thursday, April 26, 12

    Slide 38

    Slide 38 text

    Thursday, April 26, 12

    Slide 39

    Slide 39 text

    function update(t) { var term = $.trim(t), existing = searches.where({ term : term }), search; app.set('currentSearch', term); if (existing.length) { search = existing[0]; search.update(); } else { search = new Search({ term : term }); searches.add(search); } searchData.fetch({ data : { term : term } }) .always(searchForm.release); app.router.navigate('search/' + term); } Thursday, April 26, 12

    Slide 40

    Slide 40 text

    describe("#update", function() { it("should update the time", function(done) { var search = new Search(), oldTime = search.get('time'); setTimeout(function() { search.update(); expect(search.get('time')).to.be.greaterThan(oldTime); done(); }, 1000); }); }); Thursday, April 26, 12

    Slide 41

    Slide 41 text

    it("should update when there is a new search", function() { expect(el.html()).not.to.contain('baz'); rs.currentSearch = function() { return 'baz'; }; rs.searches.add({ term : 'baz' }); expect(el.html()).to.contain('baz'); expect(el.find('.active').html()).to.contain('baz'); }); Thursday, April 26, 12

    Slide 42

    Slide 42 text

    memory management requirejs builds for production multi-page apps w/history api Thursday, April 26, 12

    Slide 43

    Slide 43 text

    rmurphey.com • @rmurphey • bocoup.com github.com/rmurphey/srchr-demo Thursday, April 26, 12