Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A simple Backbone.js powered Slideshow, with pause/play controls and jump-to controls.

#A simple Slideshow module wrapped in a Backbone View

  • Dependencies ** underscore.js ** backbone.js

Viewable in action in this jsfiddle

APP.Slideshow = new APP.Views.Slideshow({
collection: new APP.Collections.Slides([
{
id:1,
headline: 'Welcome to APP',
caption: 'The best online tool for finding eLearning materials.'
},
{
id:2,
headline: 'Thousands of Products',
caption: 'APP provides a huge catalogue of eLearning products.<br/> Search the products tab below to get started.'
},
{
id:3,
headline: 'Mobile Ready',
caption: 'Are you ready for the mobile web?'
},
{
id:4,
headline: 'Rich Media',
caption: 'Search the APP media collection for access to thousands of videos and audio files to enrich your stuff'
},
{
id:5,
headline: 'Built for Medical Professionals',
caption: 'APP provides tools for people that allow you to manage and organize your purchases with ease.'
}
])
}).render();
<!-- Initial Markup Required -->
<!-- note: .icon is a simple vertical image sprite -->
<div id="slideshow">
<ul class="slides"></ul>
<ul class="controls">
<li class="slide-control toggle-play-pause icon">&nbsp;</li>
</ul>
<span class="toggler icon"></span>
</div>
APP.Views.Slideshow = Backbone.View.extend({
el: '#slideshow',
slides: '#slideshow .slides',
controls: '#slideshow .controls',
playPauseControl: '#slideshow .controls .toggle-play-pause',
delay: 10000,
currentIndex: 0,
events: {
'click .toggler' : 'toggleVisibility',
'click .toggle-play-pause' : 'togglePlayPause',
'click .jump-to' : 'jumpTo'
},
slideTemplate: _.template(
'<li id="slide-{{ id }}" class="slide {{ layout }}" style="background: url(images/slideshow/{{ id }}.jpg) no-repeat;">' +
'<p class="headline">{{ headline }}</p>' +
'<p class="caption">{{ caption }}</p>' +
'</li>'
),
controlTemplate: _.template(
'<li class="slide-control jump-to" data-index="{{ index }}">{{ human_readable_index }}</li>'
),
initialize: function() {
_.bindAll(this, 'render', 'rotateSlides', 'togglePlayPause', 'play', 'pause', 'initialPlay', 'transition', 'jumpTo');
},
render: function() {
var self = this;
this.collection.each(function(slide, i) {
$(self.slides).append(self.slideTemplate(slide.toJSON()));
$(self.controls).append(self.controlTemplate({ index: i, human_readable_index: ++i }));
});
this.initialPlay();
return this;
},
rotateSlides: function() {
var current = this.currentIndex;
var next = this.currentIndex === (this.collection.length - 1) ? 0 : this.currentIndex + 1;
this.transition(current, next);
},
transition: function(from, to) {
var current = this.collection.at(from);
var next = this.collection.at(to);
current.getEl().fadeOut('slow', function() {
next.getEl().fadeIn('slow');
});
current.getControl().toggleClass('current');
next.getControl().toggleClass('current');
this.currentIndex = to;
},
toggleVisibility: function() {
var slides = $(this.slides);
slides.toggle();
$(this.el).toggleClass('collapsed');
if(slides.is(":visible")) {
this.play();
} else {
this.pause();
}
},
togglePlayPause: function() {
if(this.isPlaying()) {
this.pause();
} else {
this.play();
}
},
initialPlay: function() {
this.collection.at(0).show();
this.collection.at(0).getControl().toggleClass('current');
this.play();
},
pause: function() {
if(this.isPaused()) { return; }
this.state = 'paused';
clearInterval(this.intervalID);
$(this.playPauseControl).toggleClass('playing', false);
},
play: function() {
if(this.isPlaying()) { return; }
this.state = 'playing';
this.intervalID = setInterval(this.rotateSlides, this.delay);
$(this.playPauseControl).toggleClass('playing', true);
},
jumpTo: function(e) {
var next = $(e.currentTarget).data('index');
this.pause();
this.transition(this.currentIndex, next);
},
isPlaying: function() {
return this.state === 'playing';
},
isPaused: function() {
return !this.isPlaying();
}
});
APP.Collections.Slides = Backbone.Collection.extend({ model: APP.Models.Slide });
#slideshow {
height: 150px;
border: 1px solid #ccc;
border-top: none;
position: relative;
overflow: hidden;
}
#slideshow.collapsed {
height: 20px;
border: none;
}
.slides {
overflow: hidden;
}
.slide {
height: 150px;
position: relative;
display: none; /** slides are hidden by default **/
}
.slide.right {
background-position: right !important;
}
.slide.left {
background-position: left !important;
}
.slide .headline {
font-size: xx-large;
font-weight: bold;
}
.slide .caption {
color: #999;
}
.slide .headline,
.slide .caption {
position: absolute;
}
.slide.right .headline {
top: 30px;
left: 30px;
}
.slide.right .caption {
left: 30px;
top: 70px;
}
.slide.left .headline {
top: 30px;
right: 30px;
}
.slide.left .caption {
top: 70px;
right: 30px;
}
#slideshow .toggler {
display: block;
height: 19px;
width: 19px;
background-color: white;
background-position: 0 -493px;
border: 1px solid #CCCCCC;
bottom: -1px;
color: black;
position: absolute;
right: -1px;
}
#slideshow.collapsed .toggler {
right: 0;
bottom: 0;
background-position: 0 -438px;
}
#slideshow .controls {
position: absolute;
bottom: -1px;
left: -1px;
}
#slideshow.collapsed .controls {
display: none;
}
#slideshow .toggler:hover,
.slide-control:hover,
.jump-to.current {
cursor: pointer;
background-color: #333;
color: white;
}
.toggle-play-pause {
background-position: 0 -527px;
}
.toggle-play-pause.playing {
background-position: 0 -509px;
}
.slide-control {
display: inline-block;
width: 20px;
height: 17px;
padding-top: 3px;
text-align: center;
margin-right: 2px;
border: 1px solid #ccc;
background-color: white;
}
APP.Models.Slide = Backbone.Model.extend({
defaults: {
id: 1,
headline: 'Welcome to APP',
caption: 'This is an awesome slide',
layout: 'right'
},
show: function() {
this.getEl().show();
},
getEl: function() {
return $('#slide-' + this.id);
},
getControl: function() {
return $('.jump-to').eq(this.id - 1);
}
});
describe('Slideshow View', function() {
var view, models, slideshow, slides, controls, events;
beforeEach(function() {
spyOn(window, 'setInterval');
spyOn(window, 'clearInterval');
slideshow = $.jasmine.inject('<div id="slideshow"><ul class="slides"></ul><ul class="controls"><li class="slide-control toggle-play-pause"></li></ul></div>');
slides = slideshow.find('.slides');
controls = slideshow.find('.controls');
models = [
new APP.Models.Slide({id:1}),
new APP.Models.Slide({id:2}),
new APP.Models.Slide({id:3})
];
view = new views.Slideshow({collection: new APP.Collections.Slides(models)});
spyOn(view, 'initialPlay');
});
describe('events', function() {
it('defines these default events', function() {
events = {
'click .toggler' : 'toggleVisibility',
'click .toggle-play-pause' : 'togglePlayPause',
'click .jump-to' : 'jumpTo'
};
expect(view.events).toEqual(events);
});
});
describe('#render', function() {
beforeEach(function() {
view.render();
});
it('renders 3 slides out to the .slides element', function() {
expect(slides).toContain('li.slide');
expect(slides.find('.slide').length).toBe(3);
});
it('renders .jump-to controls for the slides to the .controls element', function() {
expect(controls).toContain('li.jump-to');
expect(controls.find('.jump-to').length).toBe(3);
});
it('shows the first slide', function() {
expect($('.slide:first')).toBeVisible();
});
it('initializes play', function() {
expect(view.initialPlay).toHaveBeenCalled();
});
});
describe('#rotateSlides', function() {
beforeEach(function() {
spyOn(view, 'transition');
});
context('current slide is not at the end', function() {
it('should transition from the current slide to the next slide', function() {
view.rotateSlides();
expect(view.transition).toHaveBeenCalledWith(0, 1);
});
});
context('current slide is at the end', function() {
it('should show the first slide and hide the last slide', function() {
view.currentIndex = 2;
view.rotateSlides();
expect(view.transition).toHaveBeenCalledWith(2, 0);
});
});
});
describe('#transition', function() {
beforeEach(function() {
spyOn($.fn, 'fadeOut');
$.jasmine.inject(
'<li class="slide-control jump-to current" data-index="0">1</li>' +
'<li class="slide-control jump-to" data-index="1">2</li>' +
'<li class="slide-control jump-to" data-index="2">3</li>'
);
$.jasmine.inject(
'<li id="slide-1">1</li>' +
'<li id="slide-2">2</li>' +
'<li id="slide-3">3</li>'
);
});
it('fades out the current slide', function() {
view.transition(0, 1);
expect($.fn.fadeOut).toHaveBeenInvokedOnSelector('#slide-1');
});
it('toggles the "current" class on the controls representing the current and next slides', function() {
view.transition(0, 1);
expect(view.collection.at(0).getControl()).not.toHaveClass('current');
expect(view.collection.at(1).getControl()).toHaveClass('current');
});
it('sets the currentIndex to the value of "to"', function() {
view.transition(0, 1);
expect(view.currentIndex).toBe(1);
});
});
describe('#toggleVisibility', function() {
context('visible slideshow', function() {
it('should hide .slides and set the class collapsed on #slideshow', function() {
view.toggleVisibility();
expect(slides).toBeHidden();
expect(slideshow).toHaveClass('collapsed');
});
});
context('hidden slideshow', function() {
it('should show .slides and remove the class collapsed from #slideshow', function() {
view.toggleVisibility();
view.toggleVisibility();
expect(slides).toBeVisible();
expect(slideshow).not.toHaveClass('collapsed');
});
});
});
describe('#play', function() {
beforeEach(function() {
view.play();
});
it('sets the state of the view to "playing"', function() {
expect(view.state).toBe('playing');
});
it('sets the views intervalID via setInterval', function() {
expect(view.intervalID).not.toBeNull();
expect(window.setInterval).toHaveBeenCalledWith(view.rotateSlides, view.delay);
});
});
describe('#pause', function() {
beforeEach(function() {
view.state = 'playing';
view.pause();
});
it('sets the state of the view to "paused"', function() {
expect(view.state).toBe('paused');
});
it('clears the interval to stop rotating', function() {
expect(window.clearInterval).toHaveBeenCalledWith(view.intervalID);
});
});
describe('#jumpTo', function() {
var eventMock;
beforeEach(function() {
spyOn(view, 'pause');
spyOn(view, 'transition');
eventMock = {
currentTarget: $.jasmine.inject('<li class="slide-control jump-to current" data-index="3">4</li>')
};
view.jumpTo(eventMock);
});
it('pauses play of the slideshow', function() {
expect(view.pause).toHaveBeenCalled();
});
it('initiates a transition between the current slide and the one passed in via event', function() {
expect(view.transition).toHaveBeenCalledWith(view.currentIndex, 3);
});
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.