My current work is a large, old, public-facing web application, a dating site that has been hacked on by many people over many years. It's 300,000 lines of code. 50,000 lines of that are Javascript.
Some of it is really astonishingly bad.
<div class='profile oneProf bluebg'>
<a href='#' onclick='popModal("somePage");return false'>Joe User</a>
<div class='profimg onepic' id=profileimg1234 style='background-image:url(/image/profile1234.jpg)'></div>
<p id=profile1235 class='profile oneprofile profileblk red newoldstyle'>18 | Versatile <br>Boston Massachusetts</p>
<a id='profmaillink1235' class='button btn btnnew oneclickbutton'>Mail</a>
<a id='profimlink1235' class='button btn btnnew oneclickbutton disabled'>IM</a>
<script>
jQuery('#profile1235').click(function() { popModal('profile1234') });
jQuery('#profmaillink1235').click(function() { popMail('profile1234') });
jQuery('#profimlink1235').click(function() { popChat('profile1234') });
</script>
</div>
That's a single profile on a dating site.
In a word: focus.
We found some strategies for fixing it up, and some libraries that help a lot.
Break things into pieces.
a user's profile block
the menu bar
Event handlers at component roots
var Profile = Backbone.View.extend({
events: {
'click .mail': 'openMail',
'click .im': 'openChat'
},
openMail: function (ev) {
if ($(ev.target).is('.disabled')) return false;
popModal(this.attr('data-profile'));
},
...
});
We can modify the contents of the elements without rebinding event handlers. They're all in predictable places. Getting event handling right is a great place to start cleaning up code.
Styles broken up by component root (We use LESS)
.Profile {
.img {
width: 50px;
height: 50px;
}
a.disabled {
color: #444;
}
background-color: #69e;
display: inline-block;
}
<link href='/components/window.css' rel='stylesheet'>
<link href='/components/profile.css' rel='stylesheet'>
<link href='/components/menu.css' rel='stylesheet'>
<link href='/components/searchbox.css' rel='stylesheet'>
<link href='/components/map.css' rel='stylesheet'>
<link href='/components/autocomplete.css' rel='stylesheet'>
<script src='/components/window.js'></script>
<script src='/components/profile.js'></script>
<script src='/components/menu.js'></script>
<script src='/components/searchbox.js'></script>
<script src='/components/map.js'></script>
<script src='/components/autocomplete.js'></script>
<script>
var ourAppWindow = new AppWindow({el: $('body')});
</script>
That's a lot of things to maintain on a page
profile.js
define(['backbone'], function(Backbone) {
return Backbone.View.extend({
events: {
'click .mail': 'openMail',
'click .im': 'openChat'
},
openMail: function (ev) {
if ($(ev.target').is('.disabled')) return false;
popModal(this.attr('data-profile'));
}
});
});
profile.less
.Profile {
.img {
width: 50px;
height: 50px;
}
a.disabled {
color: #444;
}
background-color: #69e;
display: inline-block;
}
window.js
define(['backbone', 'components/profile'], function(Backbone, Profile) {
return Backbone.View.extend({
initialize: function () {
var views = this.views = [];
this.$('.Profile').each(function() {
views.push(new Profile({el: $(this)});
});
}
});
});
window.less
@import url('./profile.less');
<link href='/components/window.css' rel='stylesheet'>
<div class='Profile'>
<div class='img' style='background-image: url(/i/12345.jpg)'>
</div>
<div class='info'>
<div data-field='age'>18</div><br>
<div data-field='location'>Boston, MA</div>
</div>
<div class='controls'>
<a href='/mail?to=1235' class='mail'>Mail</a>
<a href='/chat/1235' class='im'>IM</a>
</div>
</div> <!-- Times a hundred! -->
<script>
require(['jquery', 'components/window'], function($, Window) {
var ourAppWindow = new AppWindow($('body'));
});
</script>
A lot of the time, it's perfectly okay to render the document server-side
Radius control: the template is just an HTML5 range input. The view is trivial, and controls just a radius property in a model.
Map control: The view instantiates a Google Map. The inputs are lat and lon from a model — and a radius. The two views communicate only through that.
I'm @aredridel (Aria Stewart), and I'm a Backboneaholic.