organizing code with javascriptmvc

56
Organizing Code with JavaScriptMVC Thomas Reynolds git clone git://github.com/tdreyno/jquery-demo.git Sunday, October 17, 2010

Upload: thomas-reynolds

Post on 24-May-2015

5.655 views

Category:

Technology


1 download

DESCRIPTION

The jQuery community has provided thousands of useful plugins which can be stitched together to create exceptional websites. However, organizing those plugins, tracking their upstream changes and managing dependencies can become a nightmare with a system to help you manage. JavascriptMVC, and specifically its new version 3 release, provides a framework for organizing outside code, integrating it into your workflow and compressing down to a single output javascript file. This talk will focus on taking external plugins such as jQuery Tools, jQuery UI and other popular plugins and creating a workflow for building larger applications from these components. I will show how to use the JavascriptMVC “getter” and “pluginify” scripts to pull external resources. With JavascriptMVC 3, css and javascript can be packaged together creating truly convenient widgets. I will also demonstrate how often-used pieces of functionality can be abstracted into plugins and shared with the general community via Github.

TRANSCRIPT

Page 1: Organizing Code with JavascriptMVC

Organizing Code with JavaScriptMVC

Thomas Reynolds

git clone git://github.com/tdreyno/jquery-demo.git

Sunday, October 17, 2010

Page 2: Organizing Code with JavascriptMVC

Who am I?

Sunday, October 17, 2010

Page 3: Organizing Code with JavascriptMVC

Who am I?• JavascriptMVC Core Committer

• jQuery Plugin Developer

• @tdreyno

• github.com/tdreyno

• awardwinningfjords.com

Sunday, October 17, 2010

Page 4: Organizing Code with JavascriptMVC

Sunday, October 17, 2010

Page 5: Organizing Code with JavascriptMVC

Sunday, October 17, 2010

Page 6: Organizing Code with JavascriptMVC

Getting to Know You

Sunday, October 17, 2010

Page 7: Organizing Code with JavascriptMVC

Getting to Know You

• Traditional Website Developers

• Web Application Developers

• Mobile Web Developers

Sunday, October 17, 2010

Page 8: Organizing Code with JavascriptMVC

Organizing Code

Sunday, October 17, 2010

Page 9: Organizing Code with JavascriptMVC

Common Problems

Sunday, October 17, 2010

Page 10: Organizing Code with JavascriptMVC

One BIG File

Sunday, October 17, 2010

Page 11: Organizing Code with JavascriptMVC

$(document.body).removeClass("no-js").addClass("js");

var slide; //current slide for homepagevar weird = 0; // for some reason, i'm getting weird behavior // when I bind test the body class in $(document).ready(). // sometimes it binds twice. this is a fix for this behavior // until i figure out what's really going on.

creatorModalIsOpen = false;

$(function(){

// make external links open in new window $('a[rel="external"]').attr('target','_blank');

if ($("body").attr('id') == 'homepage'){ bindHomepage(); } else if ($("body").hasClass('lessons_index') || $("body").hasClass('lessons_list')) { bindLessonsList(); } else if ($("body").hasClass('documents_detail')) { if (weird === 0){ showDetail(); } weird++; } else if ($("body").hasClass('lessons_detail')) { if (weird === 0){ showDetail(); } weird++;

// look for requests to launch the activity editor var edit_matches = window.location.search.match(/[?&]edit_activity=(\d+)&type=([^=?&]+)/) if (edit_matches) { var edit_activity_id = edit_matches[1]; var edit_activity_type = edit_matches[2]; launchCreatorModal({ id: edit_activity_id, type: edit_activity_type, mode: 'edit' }); }

} else if ($("body").attr('id')=='my_classroom'){ bindMyClassroom(); } else if ($("body").hasClass('templates_index')){ bindTemplates(); }

bindCreatorLinks();

// This can cause an error in Chrome, but the problem is already patched (03/2010) // and awaiting a release. window.onbeforeunload = handleOnBeforeUnload; window.onunload = handleOnUnload;

if (window.location.search.match(/[?&]from_activity=1/)) { $('#back_to_activity a').pulse(); }

bindSearchToggle();

// Load the "Star This" code // Should be inlined using something like Sprockets in the future $.getScript("/js/stars-and-add-to-buttons.js");

// Handle resources page toggles $("p.more a.morelink, ul.more a.morelink, a.arrow").click(function(e) { e.preventDefault(); var parentElem = $(this).parents(".openRow, .closedRow"); parentElem[0].className = parentElem.is(".openRow") ? "closedRow" : "openRow"; });

if (document.location.hash.length) { $("a[name=" + document.location.hash.replace(/#/, "") + "]").parents(".closedRow").find(".arrow").click(); }

$("#new_classroom").example();});

function handleDisableMousewheel(event, delta) { event.preventDefault();}

function bindCreatorLinks() { if (swfobject.hasFlashPlayerVersion("10.0.0")) { $('a.creator_link').click(function() { return handleCreatorLinkClick(this); });

$('a.creator_link').bind("contextmenu", function(e) { e.preventDefault(); }); }}

function handleCreatorLinkClick(link) { var href = $(link).attr('href'); if (!link.href || !href) return false;

var params = getUrlVars(href); launchCreatorModal(params);

return false;}

function handleOnBeforeUnload() { if (creatorModalIsOpen) { return 'If you navigate away from the activity you are creating, any unsaved work will be lost.'; } else { // IE: if you return any value (even null) it will trigger the warning return; }}

function handleOnUnload() { if (creatorModalIsOpen) { closeCreator(); }}

function embedCreatorModal(args) { var noflash_message = '<div id="no_flash_message">' + '<div id="message_box">' + '<p>' + 'To use the online Activity Creator, you must first' + '<a href="http://www.adobe.com/go/getflashplayer" target="_blank">download Flash</a>' + '</p>' + '</div>' + '</div>' ;

var modal_html = '<div id="creator_container">' + noflash_message + '</div><!--div id="creator_scrim"></div-->'; ;

$.fn.colorbox({ html: noflash_message, overlayClose: false, scrolling: false, width: 955, innerHeight: 580, opacity: 0.8, transition: "none", speed: 0, onComplete: function() { $('body').bind('mousewheel', handleDisableMousewheel);

var flashvars = { type: args.type, mode: args.mode, lesson_id: args.id, step: args.step };

if (!flashvars.lesson_id) { flashvars.lesson_id = ''; }

if (!flashvars.step) { flashvars.step = 1; }

var params = { menu: "false", scale: "noBorder", align: "TL", allowScriptAccess: "always", bgcolor: "#6EA354", base: "/swf", wmode: "window" }; var attributes = { id:"lesson_creator" };

swfobject.embedSWF("/swf/CreatorShell.swf", "no_flash_message", "955", "580","10.0.0", "expressInstall.swf", flashvars, params, attributes); }, onCleanup: function() { swfobject.removeSWF('lesson_creator'); creatorModalIsOpen = false; $('body').unbind('mousewheel', handleDisableMousewheel); } });}

function removeCreatorModal() { $.fn.colorbox.close();}

function launchCreatorModal(args) { if (!args.step) { args.step = ''; } // check for a valid session; display the modal if valid, else redirect $.ajax({ url: '/srv/sessions/current.json', type: "GET", success: function(data) { handleLaunchOfCreatorModal(args, data); }, error: function(data) { window.location.replace("/account/login"); } });}

function handleLaunchOfCreatorModal(args, data) { var proceed_with_launch = true; if (data.creator_modal_is_active) { alert("Warning: you already have an activity creator open in another window or tab. Please close it to continue."); proceed_with_launch = false; } else if (data.current_lesson_id && data.current_lesson_id !== args.id) { alert("Warning: you already have an activity creator for a different activity minimized. Please close it to continue."); proceed_with_launch = false; }

if (proceed_with_launch) { $.ajax({ url: '/srv/sessions/current.json', type: "POST", data: { '_method': 'PUT', 'session[current_lesson_id]': '', 'session[current_lesson_step]': '', 'session[creator_modal_is_active]': 1 }, success: function(data) { embedCreatorModal(args); hideBackToActivity(); creatorModalIsOpen = true; trackCreatorModalLaunch(args); }, error: function(data) { window.location.replace("/account/login"); } }); }}

// track the launch of the model using analyticsfunction trackCreatorModalLaunch(args) { var url = "/activities/creator/" + args.type + "/" + args.mode; if (args.id) { url = url + "/" + args.id; }

if (pageTracker) { try { pageTracker._trackPageview(url); } catch(err) {} }}

function minimizeCreator(params) { if (!params.step) params.step = 1;

$.ajax({ url: '/srv/sessions/current.json', type: "POST", data: { '_method': 'PUT', 'session[current_lesson_id]': params.lesson_id, 'session[current_lesson_step]': params.step, 'session[creator_modal_is_active]': 0 }, success: function(data) { if (params.go_to_docs) { goToDocumentSection(); } else if (params.go_to_lesson && params.lesson_id) { goToLesson(params.lesson_id, true); } else if (params.go_to_account) { goToAccount(params.go_to_account, true); } else if (params.force_reload) { creatorModalIsOpen = false; window.location.reload(); } else { removeCreatorModal(); revealBackToActivity(params.lesson_id, params.type, params.step); } }, error: function(data) { removeCreatorModal(); } });}

// navigate to a specific lesson, clearing the creatorModalIsOpen is flag.function goToLesson(lesson_id, pulse) { if (!lesson_id) { return; } creatorModalIsOpen = false; var url = "/activities/" + lesson_id + "/detail"; if (pulse) { url = url + "?from_activity=1"; } window.location.replace(url);}

function goToAccount(username, pulse) { var url, path;

creatorModalIsOpen = false; url = "/account/users/" + username + "/activities";

if (pulse) { url = url + "?from_activity=1"; }

path = window.location.href; if (path.indexOf(url) === -1) { window.location.replace(url); } else { window.location.reload(); }}

// if we aren't already in the document section, go therefunction goToDocumentSection() { var path, regex;

creatorModalIsOpen = false; path = window.location.pathname; regex = /^(\/dev[.]php)\/documents/;

if (!path.match(regex)) { window.location.replace("/documents?from_activity=1"); }}

function closeCreator(params) { $.ajax({ async: false, url: '/srv/sessions/current.json', type: "POST", data: { '_method': 'PUT', 'session[current_lesson_id]': '', 'session[current_lesson_step]': '', 'session[creator_modal_is_active]': 0 }, complete: function(data) { var on_deleted_lesson_page = onLessonPage(params.deleted_lesson_id);

if (params.go_to_lesson && params.lesson_id) { goToLesson(params.lesson_id); } else if (params.go_to_account && (!params.deleted_lesson_id || on_deleted_lesson_page)) { goToAccount(params.go_to_account); } else if (params.force_reload) { creatorModalIsOpen = false; window.location.reload(); } else { removeCreatorModal(); hideBackToActivity(); $('.add_to_activity').remove(); } } });}

function onLessonPage(lesson_id) { if (!lesson_id) return false; var lesson_url, path; lesson_url = "/activities/" + lesson_id + "/detail"; var path = window.location.href; return (path.indexOf(lesson_url) !== -1);}

function maximizeCreatorModal() { $.ajax({ url: '/srv/sessions/current.json', type: "GET", success: function(data) { var step = data.current_lesson_step; if (!step) step = 1; var lesson_id = data.current_lesson_id; var type = data.current_lesson_type; launchCreatorModal({type: type, mode: 'edit', id: lesson_id, step: step}); }, error: function(data) { } });}

function revealBackToActivity(lesson_id, type, step) { var query_string = 'id=' + lesson_id + '&type=' + type + '&step=' + step + '&mode=edit';

var button_html = '<div id="back_to_activity">' + '<a href="/activities/create?' + query_string + '" class="creator_link">Back to Activity</a>' + '</div>'; ;

hideBackToActivity(); $('#account_widget').append(button_html); $('#back_to_activity a').click(function() { return handleCreatorLinkClick(this); }) $('#back_to_activity a').bind("contextmenu", function(e) { e.preventDefault(); });

$('#back_to_activity a').pulse();}

function hideBackToActivity() { $('#back_to_activity').remove();}

jQuery.fn.pulse = function(duration, times, opacity_floor){ if (!duration) duration = 600; if (!times) times = 2; if (!opacity_floor) opacity_floor = 0;

try { for (i = 0; i < times; i++) { this.animate( { opacity: opacity_floor }, duration / 2 ); this.animate( { opacity: 1 }, duration / 2 ); } } catch (err) { }}

// Read a page's GET URL variables and return them as an objectfunction getUrlVars(href) { var vars = {}, hash; var hashes = href.slice(href.indexOf('?') + 1).split('&'); for(var i = 0; i < hashes.length; i++) { hash = hashes[i].split('='); vars[hash[0]] = hash[1]; } return vars;}

// Document detail modal windowfunction showDetail(){ if ($("body").attr('id') == 'documents' && $('#modal_image_viewer').length > 0){ $('#document_image').bind('click', function(){

var classname = $("#modal_image_viewer")[0].className, results = classname.match(/record-id\[(\d+)\]/), arc_id = results[1];

$.fn.colorbox({ html: $("#modal_image_viewer").html(), scrolling: false, width: 895, height: 580, opacity: 0.8, transition: "none", speed: 0, onComplete: function() { $('body').bind('mousewheel', handleDisableMousewheel);

var flashvars = { recordurl: "/srv/records/" + arc_id + ".xml", page: 0 }; var params = { menu: "true", scale: "noBorder", align: "TL", allowScriptAccess: "always", bgcolor: "#ffffff", base: "/swf" };

var attributes = { id:"swf" };

swfobject.embedSWF("/swf/zoomviewer.swf", "no_flash_message", "895", "580", "10.0.0", "expressInstall.swf", flashvars, params, attributes); }, onCleanup: function() { $('body').unbind('mousewheel', handleDisableMousewheel); } }); }); }}

// Close document detail modal window, made independent function for easy Flash access.function closeModal(){ $.fn.colorbox.close();}

function openFormattingHelp() { var url = '/formatting_syntax.html'; var format_window = window.open(url, 'helpwin', 'width=640,height=600,scrollbars=yes,resizable=yes,status=no,location=no'); if (format_window) { format_window.focus(); }}

function bindHomepage(){ $('#header ul#navigation a.documents').bind('mouseover', function(){ $('#meta_nav').text('Search from thousands of primary sources selected from the National Archives'); }); $('#header ul#navigation a.lessons').bind('mouseover', function(){ $('#meta_nav').text('Find ready-to-use activities, or create your own, built around primary sources.'); }); $('#header ul#navigation a.account').bind('mouseover', function(){ $('#meta_nav').text('Manage your activities and classrooms or edit your user profile'); }); $('#header ul#navigation a').bind('mouseout', function(){ $('#meta_nav').text(''); });}//function bindLessonsList(){ $('.star').bind('click', function(){ id = $(this).attr('id'); arc_id = id.split('-')[1]; if ($(this).children('a').hasClass('starred') != true) { $.ajax({ url: '/srv/sessions/current/favorites/lessons/'+arc_id+'.json', type: "PUT", success: function(data){ $('.star#'+id+' a').addClass('starred'); } }); } else { $.ajax({ url: '/srv/sessions/current/favorites/lessons/'+arc_id+'.json', type: "DELETE", success: function(data){ $('.star#'+id+' a').removeClass('starred'); } }); } //disable HTML link return false; });}

function bindSearchToggle() { if ($("body").is("#templates")) { return; }

$('#btn_browse a.button').click(function() { $('#header_bar').removeClass('mode_closed mode_search mode_create').addClass('mode_browse'); $('#palette_container').removeClass('closed mode_search mode_create').addClass('mode_browse'); $('#btn_browse a.button').focus(); return false; });

$('#btn_search a.button').click(function() { $('#header_bar').removeClass('mode_closed mode_browse mode_create').addClass('mode_search'); $('#palette_container').removeClass('closed mode_browse mode_create').addClass('mode_search'); $('#header_bar .search_field').focus(); return false; });}

function bindMyClassroom(){ //hide the 'manual' html button $('.add_button').hide(); //clean up the list of options to remove anything that's already selected. $('.label_list li[class!=label_list_title]').each(function (){ alreadyAdded = $(this).text().replace(',',''); parentMenu = $(this).closest('.col_classroom').find('select.picker'); existingOptions = parentMenu.find("option:not(:contains('Assign to classroom'))"); existingOptions.each(function(){ if ($(this).text() == alreadyAdded) $(this).remove(); }); //set the menu back to "Assign to Classroom" parentMenu.val(''); //if there's only one option in the menu, change it to "Added to all classrooms" parentMenu = $(this).closest('.col_classroom').find('select.picker'); if (parentMenu.children().size() == 1){ parentMenu.children('option:contains("Assign to classroom")').text('Added to all classrooms'); } });

//AJAX behavior for adding lessons to classrooms $("select[name='label']").change(function(){ selected = $(this); selected = $(this).find('option:selected'); form = $(this).closest('form'); form = selected.closest('form');

// /users/dbrewer/labels/activities/411/add var regex = /\/users\/([^\/]+)\/labels\/([^\/]+)\/(\d+)\/add$/; var matches = form.attr('action').match(regex); if (!matches) return false;

var username = matches[1]; var label_slug = matches[2]; var lesson_id = matches[3];

var label_slug = this.value; var label_name = selected.html();

$.ajax({ url: '/srv/classrooms/'+username+'/'+label_slug+'/'+lesson_id+'.json', type: "PUT", success: function(data){ //remove from drop-down menu selected.hide(); picker = selected.closest('.picker'); list = selected.closest('.col_classroom').children('.classrooms_list'); selected.remove(); if (list.children('ul').length <= 0){ //if not, make the list list.append('<ul class="label_list"><li class="label_list_title">Classrooms: </li></ul>'); } else { //otherwise, add a comma to the last item list.children('ul.label_list').children('li:last').append(', '); } //then add the selected item to this list. newUrl = '/account/users/'+username+'/classrooms/'+label_slug; list.children('.label_list').append('<li><a href="'+newUrl+'">'+label_name+'</a></li>'); //set the drop-down menu back to "Assign to classroom" $('select').children("option:contains('Assign to classroom')").attr('selected', 'selected'); //if that was the last classroom that the item could be added to... if(picker.children().size() < 2){ //make it say "Added to all classrooms" picker.children().html('Added to all classrooms'); } } }); });

// bind behaviour for my classroom list bindClassroomDetail()}////This is a helper for figuring out which table we're looking at, My Activities or Starred Activitiesfunction myActivitiesPage(){ if ($('#classroom_sidebar_menu').children().first().hasClass('here')){ return true; } else { return false }}

function bindClassroomDetail(){

$('.form-remove-from-classroom').bind('click', function(){

var my_form = this;

// extract parameters from form action regex = /([^\/]+)\/classrooms\/([^\/]+)\/(\d+)$/ var matches = my_form.action.match(regex) if (!matches) return false;

var username = matches[1]; var label = matches[2]; var lesson_id = matches[3];

// handler for successful removal of item from page function handleLessonRemoval(data) { var row_count = $('#classroom_table tbody tr').size(); if (row_count == 1) { location.reload(); } else { $('#lesson_row_' + lesson_id).remove(); fix_stripes('#classroom_table tbody tr'); } }

// formulate rest uri and call delete var rest_uri = "/srv/classrooms/" + username + "/" + label + "/" + lesson_id + ".json"; $.ajax({ url: rest_uri, type: "DELETE", success: handleLessonRemoval });

return false; });}

function bindTemplates(){ $("#best_tool_js").show(); $("#best_tool").bind('change', function(){ switch($('option:selected').html()){ case 'Chronological Thinking': filterTemplates(['finding_a_sequence', 'making_connections']); break; case 'Historical Comprehension': filterTemplates(['making_connections', 'seeing_the_big_picture', 'mapping_history', 'interpreting_data']); break; case 'Historical Analysis and Interpretation': filterTemplates(['focusing_on_details', 'making_connections', 'weighing_the_evidence']); break; case 'Historical Research Capabilities': filterTemplates(['weighing_the_evidence', 'mapping_history', 'seeing_the_big_picture', 'interpreting_data']); break; case 'Historical Issues-Analysis and Decision Making': filterTemplates(['weighing_the_evidence', 'making_connections', 'finding_a_sequence']); break; case 'Show the best tools for...': $("option#default").attr('selected', 'selected'); $('.scrim').fadeTo('2000', 0).hide(); break; }; $('.scrim').bind('click', function(){ $("option#default").attr('selected', 'selected'); $('.scrim').fadeTo('2000', 0).hide(); }); });}

function filterTemplates(bestTools){ $('.scrim').each(function(){ myParent = $(this).closest('.template_type').attr('id'); if(bestTools.has(myParent)) $(this).fadeTo('2000', 0).hide(); else $(this).fadeTo('2000', 0.5); });}

/** * Go through each item in the set identified by the selector, applying * class 'row_odd' to odd items and 'row_even' to even items. */function fix_stripes(selector) { $(selector).each(function(n) { var new_class = 'row_odd'; if ((n % 2) === 1) { new_class = 'row_even'; } $(this).removeClass('row_odd row_even').addClass(new_class); });}

function trace(message){ if (window.console){ console.log(message); }}

/* method for checking if an array['of', 'things'].has('the one you're looking for') */Array.prototype.has = function(value) { var i; for (var i = 0, loopCnt = this.length; i < loopCnt; i++) { if (this[i] === value) { return true; } } return false;};

Sunday, October 17, 2010

Page 12: Organizing Code with JavascriptMVC

One BIG File

• 800+ Lines

• Difficult to read

• No Documentation

• No Test Cases

• Not DRY

Sunday, October 17, 2010

Page 13: Organizing Code with JavascriptMVC

The Solution?

Break into Maintainable Chunks of Code

Sunday, October 17, 2010

Page 14: Organizing Code with JavascriptMVC

Problems with Multiple Files

• Does everything go into one folder?

• What if something is needed in two different places?

• Communication

• Loading speed

• Loading order

Sunday, October 17, 2010

Page 15: Organizing Code with JavascriptMVC

JavascriptMVC Provides

Dependency Management

Sunday, October 17, 2010

Page 16: Organizing Code with JavascriptMVC

StealJS• Asynchronously Loading

• Provides a structure for organizing files

• Cleans, Combines & Compresses

• Bundles Presentational CSS

• Supports CoffeeScript & Less.js

Sunday, October 17, 2010

Page 17: Organizing Code with JavascriptMVC

steal.plugins("jquery/controller", "jquery/event/drag", "jquery/dom/within").then(function($) { // Configure JavascriptMVC plugins}).resources("jquerytools.tabs", "jquery.ui.position", "jquerytools.scrollable").then(function($) { // Configure jQuery plugins}).controllers("tile", "large_zoom", "medium_zoom", "small_zoom", "control", "image").then(function($) { // All dependencies are satisfied})

StealJS Example

Sunday, October 17, 2010

Page 18: Organizing Code with JavascriptMVC

Plugin

• Basic unit of organization

• Simply, a folder.

• Initialized by a .js file named the same as the app.

• For example: my_plugin/my_plugin.js

• Can include other plugins

Sunday, October 17, 2010

Page 19: Organizing Code with JavascriptMVC

Resources• A folder named “resources/” in your plugin

• Contains raw Javascript files, often a jQuery plugin

• jQuery UI

• jQuery Tools

• Modernizr

• Et cetera

Sunday, October 17, 2010

Page 20: Organizing Code with JavascriptMVC

Controller

• Could be called several things:

• View Controller

• Widget

• $.fn on Steroids

Sunday, October 17, 2010

Page 21: Organizing Code with JavascriptMVC

Controller• Controls a DOM element

• Uses templates to create & manipulate the DOM

• Manages state

• Uses PubSub (OpenAjax) to communicate

• Responds to events

Sunday, October 17, 2010

Page 22: Organizing Code with JavascriptMVC

Example Site

Sunday, October 17, 2010

Page 23: Organizing Code with JavascriptMVC

Example Site

Sunday, October 17, 2010

Page 24: Organizing Code with JavascriptMVC

Example Site

Sunday, October 17, 2010

Page 25: Organizing Code with JavascriptMVC

Example Site• Homepage

• A slideshow

• Tabs

• Custom Autocomplete Search

• Contact Page

• Form with Validator

• “Success” lightbox

• Custom Autocomplete Search

Sunday, October 17, 2010

Page 26: Organizing Code with JavascriptMVC

Getting Started• Download zip file from GitHub

• http://github.com/jupiterjs/framework/downloads

• Unzip

• Install gem

• gem install javascriptmvc --pre

• jmvc-init jqconf-demo

Sunday, October 17, 2010

Page 27: Organizing Code with JavascriptMVC

Fresh JavascriptMVC Application

Sunday, October 17, 2010

Page 28: Organizing Code with JavascriptMVC

Homepage “App”

Sunday, October 17, 2010

Page 29: Organizing Code with JavascriptMVC

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"><html lang="en"> <head> <title>homepage</title> </head> <body> <h1>Thanks for stealing StealJS!</h1> <p>Don't worry, it's open source. It's only stealing if you don't do something awesome with it.</p> <div id='content'></div> <script type='text/javascript' src='../steal/steal.js?homepage'></script> </body></html>

Homepage HTML

Sunday, October 17, 2010

Page 30: Organizing Code with JavascriptMVC

steal( 'resources/example' ) // Loads 'resources/example.js' .css( 'homepage' ) // Loads 'homepage.css' .plugins( 'steal/less', 'steal/coffee' ) // Loads 'steal/less/less.js' and // 'steal/coffee/coffee.js'

.then(function(){ // Adds a function to be called back // once all prior files have been // loaded and run steal.coffee('resources/example') // Loads 'resources/example.coffee' .less('resources/example'); // Loads 'resources/example.less' });

Homepage Javascript

Sunday, October 17, 2010

Page 31: Organizing Code with JavascriptMVC

Resources (jQuery Plugins)

Sunday, October 17, 2010

Page 32: Organizing Code with JavascriptMVC

homepage/homepage.jssteal.plugins("jquery") .resources("slider", "tabs") .then(function($) {

$(document).ready(function() { $("#slider").slider(); $("#tabs").tabs();});

});

Sunday, October 17, 2010

Page 33: Organizing Code with JavascriptMVC

Sunday, October 17, 2010

Page 34: Organizing Code with JavascriptMVC

Contact Page “App”

Sunday, October 17, 2010

Page 35: Organizing Code with JavascriptMVC

Contact Pagesteal.plugins("jquery") .resources("validator", "lightbox") .then(function() {

$("form").validator();

$("form").submit(function() { $.lightbox();});

});

Sunday, October 17, 2010

Page 36: Organizing Code with JavascriptMVC

Autocomplete Search(Plugin)

Sunday, October 17, 2010

Page 37: Organizing Code with JavascriptMVC

TR.SearchControlPlugin

Sunday, October 17, 2010

Page 38: Organizing Code with JavascriptMVC

But Wait!?

Sunday, October 17, 2010

Page 39: Organizing Code with JavascriptMVC

FuncUnit• Test-Driven Development:

• Results well thought-out code

• Inspires trust in your users

• Documents logic

• Provides an automated means of making sure everything still works

Sunday, October 17, 2010

Page 40: Organizing Code with JavascriptMVC

TR.SearchControlFuncUnit

module("TR.SearchControl", { setup: function() { $("form#search").tr_search_control(); }})

test("Autocomplete", function(){ // Type into text box // Extra results should appear})

test("Submit", function(){ // Clicking submit or hitting enter // Should submit the form})

Sunday, October 17, 2010

Page 41: Organizing Code with JavascriptMVC

TR.SearchControlFuncUnit

Sunday, October 17, 2010

Page 42: Organizing Code with JavascriptMVC

Search Controllersteal.plugins("jquery/controller") .resources("autocomplete") .then(function($) { $.Controller.extend("TR.SearchControl", { }, { init: function() { this.element.autocomplete(); }, "input:text change": function() { // Autocomplete lookup }, "submit": function() { // Ajax form submit }});

$(document).ready(function() { $("form#search").tr_search_control();});

});Sunday, October 17, 2010

Page 43: Organizing Code with JavascriptMVC

TR.SearchControlFuncUnit

Sunday, October 17, 2010

Page 44: Organizing Code with JavascriptMVC

homepage/homepage.jssteal.plugins("jquery") .resources("slider", "tabs") .plugins("tr/search_control") .then(function($) {

$(document).ready(function() { $("#slider").slider(); $("#tabs").tabs();});

});

Sunday, October 17, 2010

Page 45: Organizing Code with JavascriptMVC

Sunday, October 17, 2010

Page 46: Organizing Code with JavascriptMVC

All Done Coding, Time to Optimize

Sunday, October 17, 2010

Page 47: Organizing Code with JavascriptMVC

• Packages

• 0.js - Shared between all apps (jQuery, JavascriptMVC componetns, etc)

• homepage/production.jsUnique to Homepage

• contacts/contact.jsUnique to Contact

• Google Closure Compiler

• Dojo Shrinksafe also available

Building & Compression

Sunday, October 17, 2010

Page 48: Organizing Code with JavascriptMVC

./steal/js steal/buildjs homepage contact

Building & Compression

Sunday, October 17, 2010

Page 49: Organizing Code with JavascriptMVC

Building & Compression

Sunday, October 17, 2010

Page 50: Organizing Code with JavascriptMVC

Production HTML<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"><html lang="en"> <head> <title>homepage</title> </head> <body> <h1>Thanks for stealing StealJS!</h1> <p>Don't worry, it's open source. It's only stealing if you don't do something awesome with it.</p> <div id='content'></div> <script type='text/javascript' src='../steal/steal.js?homepage,production'> </script> </body></html>

Sunday, October 17, 2010

Page 51: Organizing Code with JavascriptMVC

Sunday, October 17, 2010

Page 52: Organizing Code with JavascriptMVC

Summary• Organize code into apps & plugins

• Always Test Your Code

• Use consistent naming and organization

• Create sharable modules (Github)

• Optimize!

Sunday, October 17, 2010

Page 53: Organizing Code with JavascriptMVC

JavascriptMVC ProvidesModels (JSON API wrappers)

Views (EJS, Micro, Jaml)

Controllers (Event system, data binding)

Functional testing (FuncUnit, qunit, selenium)

Dependency management (Optimization)

Sunday, October 17, 2010

Page 54: Organizing Code with JavascriptMVC

My Plugins

JavascriptMVC convenience gem

• gem install javascriptmvc --pre

• jmvc-init newproject

• jmvc-gen newapp

Sunday, October 17, 2010

Page 55: Organizing Code with JavascriptMVC

• steal.plugins("ss/state_machine")http://github.com/secondstory/secondstoryjs-statemachine

• steal.plugins("ss/router")http://github.com/secondstory/secondstoryjs-router

• steal.plugins("tr/views/mustache")http://github.com/tdreyno/mustache-javascriptmvc

• steal.plugins("tr/controller/pure")http://github.com/tdreyno/javascriptmvc-pure

My Plugins

Sunday, October 17, 2010

Page 56: Organizing Code with JavascriptMVC

Thank you• Questions?

• http://v3.javascriptmvc.com/index.html

• @tdreyno

• awardwinningfjords.com

• github.com/secondstory

• github.com/tdreyno

• http://github.com/tdreyno/jquery-demo

Sunday, October 17, 2010