Single Page Jade. The goal is to build a single page application that can show two different "pages" when the user asks to see them.
At program start up we:
When the user requests to see Page Two, we:
Make sure you have the latest scripts from JsObjects in your bin directory. In particular, get the latest CreateExpressProject and TestReady.
Create the application:
CreateExpressProject Week08-SinglePageExpress
In the Week08-SinglePageExpress directory:
TestReady
npm start
In views/index.jade: we create a simple page with two buttons and a DIV where we can display information:
extends layout
block content
h1= title
p Welcome to #{title}
button#page01 Page One
button#page02 Page Two
#displayContainer
In control.js we set up button response handlers for clicks on the Page One and Page Two buttons:
$(document).ready(function() { 'use strict';
$('#page01').click(function() {
$('#displayContainer').load('/page01');
});
$('#page02').click(function() {
$('#displayContainer').load('/page02');
});
});
We also declare two very simple "pages" that we can display in the displayContainer:
views/page01.jade
h1 Page One
views/page01.jade
h1 Page Two
In routes/index.js we set up routes for handling requests for Page One and Page Two:
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) { 'use strict';
res.render('index', { title: 'Week08-SinglePageJade' });
});
router.get('/page01', function(req, res, next) { 'use strict';
res.render('page01', { title: 'Week08-SinglePageJade Page One' });
});
router.get('/page02', function(req, res, next) { 'use strict';
res.render('page02', { title: 'Week08-SinglePageJade Page Two' });
});
module.exports = router;
In test basic:
describe('Single Page Proof of Existence', function() {
'use strict';
it('true be true', function() {
expect(true).toBeTruthy();
});
it('expects elfSinglePage to exist', function() {
expect(elfSinglePage).toBeDefined();
});
});
describe('Single Page Suite', function() {
'use strict';
var singlePageKeys;
beforeEach(function() {
singlePageKeys = Object.keys(elfSinglePage);
});
it('expects elfSinglePage to have three methods', function() {
expect(singlePageKeys.length).toBe(3);
});
it('expects elfSinglePage to have pageTwoButtonClick method', function() {
expect(singlePageKeys).toContain('pageTwoButtonClick');
});
it('expects elfSinglePage to have pageTwoDisplay Method', function() {
expect(singlePageKeys).toContain('pageTwoDisplay');
});
it('expects elfSinglePage to have setup Method', function() {
expect(singlePageKeys).toContain('setup');
});
});
These tests won't pass at first, but go ahead and put them in spec/test-single-page-actions.js:
describe('Button Clicks and Other Actions Suite', function() {
'use strict';
beforeEach(function() {
var fixture = '<div id="fixture">' +
'<button id="page01">Page One</button>' +
'<button id="page02">Page Two</button>' +
'<button id="pageTwoButton">Page Two Button</button>' +
'<p id="display"></p><p id="pageTwoButtonDisplay"></p>' +
'</div>';
document.body.insertAdjacentHTML('afterbegin', fixture);
elfSinglePage.setup();
});
afterEach(function() {
document.body.removeChild(document.getElementById('fixture'));
});
it('proves we can handle button click on #page01', function() {
spyOn($.fn, 'load').and.callFake(function(url) {
expect(url).toBe('/page01');
});
$('#page01').trigger('click');
});
it('proves we can handle button click on #page02', function() {
spyOn($.fn, 'load').and.callFake(function(url) {
expect(url).toBe('/page02');
});
$('#page02').trigger('click');
});
it('proves we can handle button click on #pageTwoButton', function() {
spyOn(elfSinglePage, 'pageTwoButtonClick');
elfSinglePage.pageTwoDisplay();
$('#pageTwoButton').trigger('click');
expect(elfSinglePage.pageTwoButtonClick).toHaveBeenCalled();
});
});
All that is well and could, but frequently we want to attach events to controls on the pages we loaded. We can't set up the events for controls declared on Page Two when Page One is loaded. This is because the DOM for Page Two is not available, and so jQuery/JavaScript can't find the controls which should trigger the events.
The solution is to wait until Page Two is loaded before we set up the events for the controls on Page Two.
NOTE: I can't emphasize enough how important it is that you understand this. Countless developers in my class and throughout the world have spent hours trying to understand why a click on a button doesn't generate the event. The problem is that they are trying to set up the handler before the button itself is loaded onto the page. This problem will bite you and bite you hard and unless you understand what is being said here.
The updated Jade for Page Two:
h1 Page Two
button#pageTwoButton Page Two Button
hr
div
p#display
p#pageTwoButtonDisplay
div
p#error
The updated control.js:
var elfSinglePage = {
pageTwoButtonClick: function(event) {
'use strict';
console.log('pageTwoButtonClick called');
$('#pageTwoButtonDisplay').html('Page two button works.');
},
pageTwoDisplay: function() {
'use strict';
console.log('pageTwoDisplay called');
$('#display').html('Page Two was loaded. Now we can set up Button handler');
$('#pageTwoButton').click(elfSinglePage.pageTwoButtonClick);
},
setup: function() {
'use strict';
console.log('elfSinglePage setup called');
$('#page01').click(function() {
console.log('#page01 click handler called');
$('#displayContainer').load('/page01');
});
$('#page02').click(function() {
$('#displayContainer').load('/page02', function(response, status, xhr) {
if (status == 'error') {
var msg = 'Sorry but there was an error: ';
$('#error').html(msg + xhr.status + ' ' + xhr.statusText);
console.log(msg + xhr.status + ' ' + xhr.statusText);
} else {
elfSinglePage.pageTwoDisplay();
}
});
});
}
};
$(document).ready(function() {
'use strict';
elfSinglePage.setup();
});
Do what you need to do to make sure there are no formatting or syntax errors in your code. Add these unit tests and make sure they pass:
grunt check
grunt test
The usual. Be sure to call out the folder name if it differs from what is specified above in the setup section.
Suppose you want to show the content from TwitterRefine in Page02. (This is close to what you do for the midterm.)
What I've done below, is copy part of the Jade from TwitterRefine into Page02.jade. Also copy over the mixins. Don't forget to get the CSS for elfDiv and scroller class.
include mixin-radios
include mixin-inputs
h1 Page Two
button#pageTwoButton Page Two Button
hr
div
p#display
p#pageTwoButtonDisplay
div
p#error
+elfPanel("TItle").elfDiv
h1= title
p Welcome to #{title}
// FILL IN THE REST OF YOUR TWITTER REFINE INDEX.JADE HERE
A rather subtle, and quite pernicious, bug can be introduced in our programs if we try to extend the file called layout.jade in the wrong places. This bug took me awhile to track down when I first saw it in a student's work. Don't let it happen to you, as it can cause a strange, repetitive loading of our jade files that quickly brings a program to its knees.
Our single page application has one main page defined in index.jade. That page extends layout.jade:
extends layout
include mixin-buttons
include mixin-inputs
include mixin-radios
block content
h1= title
etc...
We also have secondary pages that are swapped in and out as the user makes selections. Make sure you do not try to extend layout.jade in any of those files. For instance, this would cause an error if included at the top of a secondary file:
extends layout
include mixin-buttons
include mixin-inputs
include mixin-radios
block content
h1= title
etc...
Instead, the code might, in some cases look a bit like this:
include mixin-inputs
include mixin-radios
include mixin-buttons
+elfPanel("Bitly Links Table").elfDiv
div.scroller
table.table#tableLinks
etc...