What I'm building is a URL builder. A marketing person inputs a URL and can test that URL to see if it's valid, then they select a demand channel and then hit 'Generate Campaign URL'. I've omitted the other form fields, as I'm only interesting in refactoring the code for the onclick event of the 'Test' URL button.
In an effort to become a better developer, I'd like to start writing unit tests and adopting a more TDD approach to my code. I've been working off the example in Writing Testable JavaScript in an effort to refactor my jQuery onclick event into something that can be tested using a framework like Mocha, Jasmine, etc.
$.validator.setDefaults({
highlight: function (element) {
$(element).closest('.form-group').addClass('has-error');
},
unhighlight: function (element) {
$(element).closest('.form-group').removeClass('has-error');
},
errorElement: 'span',
errorClass: 'help-block',
errorPlacement: function (error, element) {
if (element.parent('.input-group').length) {
error.insertAfter(element.parent());
} else {
error.insertAfter(element);
}
}
});
$('#form1').validate({
rules: {
baseURL: {
required: true,
url: true
},
demandChannel: {
required: true
},
userEmail: {
required: true,
email: true
}
}
});
$('#testBaseURL').on('click', function () {
var $baseURL = $('#baseURL');
var inputIsValid = $baseURL.valid();
var url = $baseURL.val();
if (inputIsValid) {
window.open(url, '_blank');
}
});
var TestButton = function (el) {
this.el = $(el);
this._bindEvents();
};
TestButton.prototype._handleClick = function (evt) {
var baseURL = $(evt.target).closest('.input-group').find('#baseURL');
console.log('baseURL = ' + baseURL.val());
};
TestButton.prototype._bindEvents = function () {
this.el.on('click', this._handleClick.bind(this));
};
var foo = new TestButton('#testBaseURL');
<!DOCTYPE html>
<html>
<head>
<script src="//code.jquery.com/jquery.min.js"></script>
<script src="http://ajax.aspnetcdn.com/ajax/jquery.validate/1.13.1/jquery.validate.js"></script>
<script src="http://ajax.aspnetcdn.com/ajax/jquery.validate/1.13.1/additional-methods.js"></script>
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<div class="container">
<form class="form-horizontal" id="form1">
<!-- Email -->
<div class="form-group">
<label for="userEmail" class="col-sm-3 control-label">Email</label>
<div class="col-sm-9">
<input type="email" name="userEmail" class="form-control" id="userEmail" placeholder="[email protected]">
</div>
</div>
<!-- Campaign URL -->
<div class="form-group">
<label for="campUrl" class="col-sm-3 control-label">URL</label>
<div class="col-sm-9">
<div class="input-group">
<input type="url" name="baseURL" class="form-control" id="baseURL" placeholder="example.com">
<span class="input-group-btn">
<button class="btn btn-default" id="testBaseURL" type="button">Test!</button>
</span>
</div>
</div>
</div>
<!-- Demand Channel -->
<div class="form-group">
<label for="selector" class="col-sm-3 control-label">Demand Channel</label>
<div class="col-sm-9">
<select class="form-control" name="demandChannel" id="demandChannel">
<option value="" selected disabled>Please select</option>
<option value="display">Display</option>
<option value="email">Email</option>
<option value="ppc">PPC</option>
<option value="social">Social</option>
<option value="affiliate">Affiliate</option>
</select>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9">
<button type="submit" class="btn btn-default" id="generateURL">Generate Campaign URL</button>
</div>
</div>
</form>
</div>
</body>
</html>
In Rebecca's example, she took this code (do not review it):
var liked = $('#liked'); var resultsList = $('#results'); // ... resultsList.on('click', '.like', function (e) { e.preventDefault(); var name = $(this).closest('li').find('h2').text(); liked.find( '.no-results' ).remove(); $('<li>', { text: name }).appendTo(liked); });
Doing this:
var Likes = function (el) { this.el = $(el); return this; }; Likes.prototype.add = function (name) { this.el.find('.no-results').remove(); $('<li>', { text: name }).appendTo(this.el); }; var SearchResults = function (el) { this.el = $(el); this.el.on( 'click', '.btn.like', _.bind(this._handleClick, this) ); }; SearchResults.prototype.setResults = function (results) { var templateRequest = $.get('people-detailed.tmpl'); templateRequest.then( _.bind(this._populate, this, results) ); }; SearchResults.prototype._handleClick = function (evt) { var name = $(evt.target).closest('li.result').attr('data-name'); $(document).trigger('like', [ name ]); }; SearchResults.prototype._populate = function (results, tmpl) { var html = _.template(tmpl, { people: results }); this.el.html(html); };
To end up with this:
var liked = new Likes('#liked'); var resultsList = new SearchResults('#results'); // ... $(document).on('like', function (evt, name) { liked.add(name); });
In the project I'm working on, I have some very similar code, so I thought I would attempt to replicate it.
My before:
$('#testBaseURL').on('click', function () {
var $baseURL = $('#baseURL');
var inputIsValid = $baseURL.valid(); // returns true/false
var url = $baseURL.val();
if (inputIsValid) {
window.open(url, '_blank');
}
});
My after:
var TestButton = function (el) {
this.el = $(el);
this._bindEvents();
};
TestButton.prototype._handleClick = function (evt) {
var baseURL = $(evt.target).closest('.input-group').find('#baseURL');
console.log('baseURL = ' + baseURL.val());
};
TestButton.prototype._bindEvents = function () {
this.el.on('click', this._handleClick.bind(this));
};
As you can see, it's a blatant ripoff of her code. And now I'm at a point where I'm not sure how to continue the refactoring of my code, and basically all I'm doing is just console logging the value of the field. I know I need to include some sort of check to see if the input is valid, and if it is, pass the URL into a new window, but I'm not sure how to go about this.
I've created a JSBin with a working example to help assist in what I'm trying to do.