Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
1160 lines (1065 sloc) 27 KB
<!DOCTYPE html>
<meta charset="utf-8">
<title>Dependency injection with perl</title>
<section>
<h1>Dependency injection</h1>
<h2>... with perl</h2>
<footer>notbenh</footer>
<details>Hello and welcome</details>
</section>
<section>
<h1>First... IoC</h1>
<ul>
<li>Control = rm -r<span class='red'>f</span> dir</li>
<ul>
<details>Lets get this out of the way first. Control = I got this.
</details>
</section>
<section>
<h1>First... IoC</h1>
<ul>
<li class='grey'>Control = rm -rf dir</li>
<li>Inverse = rm -r<span class='red'>i</span> dir</li>
<ul>
<details>IoC = I just want to make sure I've got this right.</details>
</section>
<section>
<h1>First... IoC</h1>
<ul>
<li class='grey'>Control = rm -rf dir</li>
<li class='grey'>Inverse = rm -ri dir</li>
<li>IoC = code has an <span class='red'>outside consultant</span></li>
<ul>
</section>
<section>
<h1>Dependency injection (DI)</h1>
<!-- <q>...is an OO in technique that indicates to a part of a program which other parts it can use.</q>
<author>paraphrase from wikipedia</author>
-->
<q>... is a design pattern in object-oriented computer programming whose purpose is to reduce the coupling between software components. ... Frequently an object uses (depends on) work produced by another part of the system. With DI, the object does not need to know in advance about how the other part of the system works. Instead, the programmer provides (injects) the relevant system component in advance along with a contract that it will behave in a certain way.</q>
<details>in short DI is a lable from OO land, to refer to a practice of being able to shove in something from the outside.</details>
</section>
<section>
<h1>Dependency injection (DI)</h1>
<ul>
<li>non-DI&nbsp;[code]<span class='red'>-></span>{dep}</li>
</ul>
<details>In non-DI land your code builds it's own deps, thus it knows the shape and value.</details>
</section>
<section>
<h1>Dependency injection (DI)</h1>
<ul>
<li class='grey'>non-DI&nbsp;[code]->{dep}</li>
<li>DI&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[code]<span class='red'><-</span>{dep}</li>
</ul>
<details>In DI land your code only knows the shape of a dep... value is left to the outside world.</details>
</section>
<section>
<h1>ORM-ish EXAMPLE TIME</h1>
<pre>
<code>
my $orm = ORM->new();
my $val = $orm->find(this=>'that');
</code>
</pre>
<details>Lets say we have this ORM-ish thing...</details>
</section>
<section>
<h1>ORM-ish EXAMPLE TIME</h1>
<pre>
<code>
sub find {
my $self = shift;
$self->d->selectall_arrayref(
$self->qbuild( from => 'SomeTable'
, where=> {@_} # our input
)->dbi
,{Slice=>{}}
);
}
# qbuild is 'magic' just ignore it
# yes this is not DBIx::Class
</code>
</pre>
<details>Ok so all find does is take input, toss off to some "make SQL magic thing" (that we will be avoiding for now) and then calling some 'd' thing that appears to be a DBI object... Lets look further
</details>
</section>
<section>
<h1>ORM-ish EXAMPLE TIME</h1>
<pre>
<code class='grey'>
sub find {
my $self = shift;
$self-><span class='red'>d</span>->selectall_arrayref(
$self->qbuild( from => 'SomeTable'
, where=> {@_} # our input
)->dbi #
,{Slice=>{}}
);
}
# qbuild is 'magic' just ignore it
# yes this is not DBIx::Class
</code>
</pre>
<details>Ok so all find does is take input, toss off to some "make SQL magic thing" (that we will be avoiding for now) and then calling some 'd' thing that appears to be a DBI object... Lets look further
</details>
</section>
<section>
<h1>ORM-ish EXAMPLE TIME</h1>
<pre>
<code>
# non-DI way to define 'd'
use Memoize;
memoize('d');
sub d {
require DBI;
DBI->connect(...);
}
</code>
</pre>
</section>
<section>
<h1>ORM-ish EXAMPLE TIME</h1>
<pre>
<code>
# non-DI way to define 'd'
sub new {
require DBI;
return bless {d => DBI->connect(...)
}, shift;
}
sub d { shift->{d} };
</code>
</pre>
</section>
<section>
<!--<h1>ORM-ish EXAMPLE TIME</h1>-->
<center><h1 class='red'>***</h1></center>
</section>
<section>
<h1>ORM-ish EXAMPLE TIME</h1>
<pre>
<code>
# DI by the books
sub new {
my $class = shift;
my %opts = @_;
die unless $opts{d}->isa('DBD::db');
return bless {d=>$opts{d}}, $class;
}
sub d { shift->{d} };
</code>
</pre>
<details>If we can refactor our code then we could... </details>
</section>
<section>
<h1>ORM-ish EXAMPLE TIME</h1>
<pre>
<code class='grey'>
# DI by the books
sub new {
my $class = shift;
<span class='red'>my %opts = @_;
die unless $opts{d}->isa('DBD::db');</span>
return bless {d=><span class='red'>$opts{d}</span>}, $class;
}
sub d { shift->{d} };
</code>
</pre>
</section>
<section>
<h1>ORM-ish EXAMPLE TIME</h1>
<pre>
<code>
# DI by the books via <span class='red'>evil</span>
require ORM;
do { no warnings;
no strict 'refs';
*{ORM::d} = sub{
require DBI;
DBI->connect(...);
};
}; # remember this was a method
my $orm = ORM->new();
</code>
</pre>
<details>though if we can't ikk... who really wants to do that?</details>
</section>
<section>
<h1>ORM-ish EXAMPLE TIME</h1>
<pre>
<code>
# DI by the books via Moose
has d =>
is => 'ro',
isa => 'DBD::db',
required => 1,
;
</code>
</pre>
</section>
<section>
<h1>ORM-ish EXAMPLE TIME</h1>
<pre>
<code class='grey'>
# DI by the books
has d =>
is => 'ro',
isa => 'DBD::db',
<span class='red'>required => 1</span>,
;
</code>
</pre>
</section>
<section>
<h1>What does this give us?</h1>
<pre>
<code>
my $orm = ORM->new(d=>DBI->connect(...));
my $val = $orm->find(this=>'that');
</code>
</pre>
</section>
<section>
<h1>What does this give us?</h1>
<pre>
<code class='grey'>
my $orm = ORM->new(<span class='red'>d=>DBI->connect(...)</span>);
my $val = $orm->find(this=>'that');
</code>
</pre>
</section>
<section>
<h1>Advantage?</h1>
<ul>
<li>Flexibility</li>
</ul>
</section>
<section>
<h2>Flexibility</h2>
<pre>
<code>
require 'DBI'
my $db = DBI->connect(
<span class='red'>'dbi:Pg:dbname=Stage'</span>
,''
,''
);
my $orm = ORM->new(d=>$db);
# !!! never touches live data
my $val = $orm->find(...);
</code>
</pre>
</section>
<section>
<h2>Flexibility</h2>
<pre>
<code>
require 'DBI'
my $db = DBI->connect(
<span class='red'>'dbi:SQLite:dbname=./testing.db'</span>
,''
,''
);
my $orm = ORM->new(d=>$db);
# !!! never touches live data
is $orm->find(...), 'something';
</code>
</pre>
</section>
<section>
<h1>Disadvantage?</h1>
<ul>
<li>Verbose</li>
</ul>
</section>
<section>
<h2>Verbose</h2>
<pre>
<code>
my $orm = ORM->new(d=>DBI->connect(...));
my $val = $orm->find(this=>'that');
</code>
</pre>
</section>
<section>
<h2>Verbose</h2>
<pre>
<code>
# Happy middle ground
has d =>
is => 'ro',
isa => 'DBD::db',
default => sub{
require DBI;
my $connect = is_live ? ...
: is_stage ? ...
: ... #testing
;
DBI->connect($connect);
},
;
</code>
</pre>
</section>
<section>
<h2>Verbose</h2>
<pre>
<code class='grey'>
# Happy middle ground
has d =>
is => 'ro',
isa => 'DBD::db',
<span class='red'>default => sub{
require DBI;
my $connect = is_live ? ...
: is_stage ? ...
: ... #testing
;
DBI->connect($connect);
},
</span>
;
</code>
</pre>
</section>
<section>
<h2>Verbose</h2>
<pre>
<code>
my $foo = ORM->new()->find(...);
my $bar = ORM->new(d=>$db)->find(...);
</code>
</pre>
</section>
<section>
<h1>Advantage?</h1>
<ul>
<li class='grey'>Flexibility</li>
<li>Reusability</li>
</ul>
</section>
<section>
<h2>Reusability</h2>
<pre>
<code>
sub find {
my $self = shift;
$self->d->selectall_arrayref(
$self->qbuild( from => 'SomeTable'
, where=> {@_} # our input
)->dbi
,{Slice=>{}}
);
}
</code>
</pre>
<details>Lets take a look at that find method again</details>
</section>
<section>
<h2>Reusability</h2>
<pre>
<code class='grey'>
sub find {
my $self = shift;
$self->d->selectall_arrayref(
$self->qbuild( from => '<span class='red'>SomeTable</span>'
, where=> {@_} # our input
)->dbi
,{Slice=>{}}
);
}
</code>
</pre>
<details>We currently have a hard coded tablename</details>
</section>
<section>
<h2>Reusability</h2>
<pre>
<code>
has table =>
is => 'ro',
isa => 'Str',
default => 'SomeTable'
;
</code>
</pre>
</section>
<section>
<h2>Reusability</h2>
<pre>
<code>
sub find {
my $self = shift;
$self->d->selectall_arrayref(
$self->qbuild( from => $self->table
, where=> {@_} # our input
)->dbi
,{Slice=>{}}
);
}
</code>
</pre>
</section>
<section>
<h2>Reusability</h2>
<pre>
<code class='grey'>
sub find {
my $self = shift;
$self->d->selectall_arrayref(
$self->qbuild( from => <span class='red'>$self->table</span>
, where=> {@_} # our input
)->dbi
,{Slice=>{}}
);
}
</code>
</pre>
</section>
<section>
<h2>Reusability</h2>
<pre>
<code>
my $orm = ORM->new(
table => 'OtherTable'
, d => DBI->connect(...)
);
my $foo = $orm->find(...);
my $bar = ORM->new()->find(...);
</code>
</pre>
<details> So wait a sec... we just created a very basic 'table object' that we can now pass it just about anything and we get what we want... yup Bingo.
</details>
</section>
<section>
<h1>Disadvantage?</h1>
<ul>
<li class='grey'>Verbose</li>
<li>Abstraction</li>
</ul>
</section>
<section>
<h2>Abstraction</h2>
<pre>
<code>
my $orm = ORM->new(
table => 'Other_Table'
);
my $val = $orm->find(...);
# !!FAIL: 'Other_Table' not found !!
</code>
</pre>
</section>
<section>
<h2>Abstraction</h2>
<pre>
<code>
package My::OtherTable;
use Moose;
extends 'ORM';
has '+table' => default => 'OtherTable';
1;
my $val = My::OtherTable->new->find(...);
</code>
</pre>
</section>
<section>
<h1>Disadvantage?</h1>
<ul>
<li class='grey'>Verbose</li>
<li class='grey'>Abstraction</li>
<li>Complex</li>
</ul>
</section>
<section>
<h2>Complex</h2>
<pre>
<code>
perl -MDBI -e 'printf q{table has %d rows}\
DBI->connect(...)->selectcol_arrayref( \
q{SELECT count(*) FROM table})->[0]'
</code>
</pre>
</section>
<section>
<h1>now for something completely different</h1>
<h1>Bread::Board</h1>
<details>Bread::Board is a IoC framework that goes one step further</details>
</section>
<section>
<h1>Bread::Board</h1>
<q>... is an inversion of control framework with a focus on dependency injection and lifecycle management. It's goal is to help you write more decoupled objects and components by removing the need for you to manually wire those objects/components together.</q>
<author>Stevan Little</author>
<details> BB sees the world a bit diffrently then we tend to look at objects</details>
</section>
<section>
<h1>Bread::Board</h1>
<pre>
<code>
# First, a container
my $c = Bread::Board::Container->new(
name => 'Application'
);
</code>
</section>
<section>
<h1>Bread::Board</h1>
<pre>
<code>
# Next, we add a service to that container
$c->add_service(
Bread::Board::BlockInjection->new(
name => 'db_conn',
block => sub { DBI->connect(...) },
)
);
</code>
</section>
<section>
<h1>Bread::Board</h1>
<pre>
<code>
# Now to access our logger:
my $db = $c->resolve(
service => 'db_conn'
);
my $val = $db->selectall_arrayref(...);
</code>
</section>
<section>
<h1>draoB::daerB</h1>
<center><q>Seems backwards, just use Moose!</q></center>
</section>
<section>
<h1>draoB::daerB</h1>
<pre>
<code class='smaller'>
package Application;
use Moose;
has db_conn =>
is => 'ro',
isa => 'DBD::db',
default => sub{ DBI->connect(...) )
;
</code>
</pre>
<details>This would do the same thing, and it's shorter!</details>
</section>
<section>
<h1>Why Bread::Board</h1>
<ul>
<li>Manages dependency relationships</li>
</ul>
</section>
<section>
<h2>Manages dependency relationships</h2>
<pre>
<code class='smaller'>
$c->add_service(
Bread::Board::BlockInjection->new(
name => 'logger',
block => sub {
my $service = shift;
Logger->new(
db_conn => $service->param('db_conn'),
);
},
<span class='red'>dependencies => {
db_conn => Bread::Board::Dependency->new(
service_path => 'db_conn'
),
}</span>
)
);
</code>
</pre>
<details>So Bread::Board allows you to map out the things that do stuff, and then you can stich them together to do extra stuff</details>
</section>
<section>
<h1>draoB::daerB</h1>
<pre>
<code class='smaller'>
package Application;
use Moose;
has db_conn =>
is => 'ro',
isa => 'DBD::db',
<span class='red'>lazy => 1</span>,
default => sub{ DBI->connect(...) )
;
has logger =>
is => 'ro',
isa => 'Logger',
default => sub{
Logger->new(db_conn => shift->db_conn)
}
;
</code>
</pre>
<details>This would do the same thing, and it's shorter!</details>
</section>
<section>
<h1>Why Bread::Board</h1>
<ul>
<li class='grey'>Manages dependency relationships</li>
<li>Manages dependency lifecycle</li>
</ul>
</section>
<section>
<h2>Manages dependency lifecycle</h2>
<pre>
<code>
$c->add_service(
Bread::Board::BlockInjection->new(
<span class='red'>lifecycle => 'Singleton'</span>,
name => 'db_conn',
block => sub { DBI->connect(...) },
)
);
</code>
</pre>
</section>
<section>
<h1>Why Bread::Board</h1>
<ul>
<li class='grey'>Manages dependency relationships</li>
<li class='grey'>Manages dependency lifecycle</li>
<li>Multiple types of injection</li>
</ul>
</section>
<section>
<h2>Multiple types of injection</h2>
<ul>
<li>BlockInjection</li>
</ul>
<pre>
<code>
$c->add_service(
Bread::Board::<span class='red'>BlockInjection</span>->new(
name => 'db_conn',
block => sub { DBI->connect(...) },
)
);
</code>
</pre>
<details> The I know what I'm doing and can do it my self injection</details>
</section>
<section>
<h2>Multiple types of injection</h2>
<ul>
<li class='grey'>BlockInjection</li>
<li>ConstructorInjection</li>
</ul>
<pre>
<code class='smaller'>
$c->add_service(
Bread::Board::<span class='red'>ConstructorInjection</span>->new(
name => 'authenticator',
class => 'Authenticator',
dependencies => {
db_conn => Bread::Board::Dependency->new(
service_path => 'db_conn'
),
}
)
);
</code>
</pre>
<details> The toss the deps in to the constructor injector.</details>
</section>
<section>
<h2>Multiple types of injection</h2>
<ul>
<li class='grey'>BlockInjection</li>
<li>ConstructorInjection</li>
</ul>
<pre>
<code>
require Authenticator;
my $auth = Authenticator->new(
db_conn => $c->resolve(
service => 'db_conn'
)
);
</code>
</pre>
<details> The I know what I'm doing and can do it my self injection</details>
</section>
<section>
<h2>Multiple types of injection</h2>
<ul>
<li class='grey'>BlockInjection</li>
<li class='grey'>ConstructorInjection</li>
<li>SetterInjection</li>
</ul>
<pre>
<code class='smaller'>
$c->add_service(
Bread::Board::SetterInjection->new(
name => 'json',
class => 'JSON',
dependencies => {
utf8 => Bread::Board::Literal->new(
name => 'true',
value => 1
)
}
)
);
</code>
</section>
<section>
<h2>Multiple types of injection</h2>
<ul>
<li class='grey'>BlockInjection</li>
<li class='grey'>ConstructorInjection</li>
<li>SetterInjection</li>
</ul>
<pre>
<code>
require JSON;
my $json = JSON->new;
$json->utf8(1);
</code>
</section>
<section>
<h1>Why Bread::Board</h1>
<ul>
<li class='grey'>Manages dependency relationships</li>
<li class='grey'>Manages dependency lifecycle</li>
<li class='grey'>Multiple types of injection</li>
<li>Hierarchal Containers</li>
</ul>
</section>
<section>
<h2>Hierarchal Containers</h2>
<pre>
<code>
my $app = Bread::Board::Container->new(
name => 'app'
);
my $db = Bread::Board::Container->new(
name => 'database'
);
</code>
</pre>
</section>
<section>
<h2>Hierarchal Containers</h2>
<pre style='font-size:80%;'>
<code class='smaller'>
$db->add_service(
Bread::Board::BlockInjection->new(
name => 'connection'
block => sub { my $s = shift;
return DBI->connect(
'dbi:mysql:test',
$s->param('username'),
$s->param('password')
)},
dependencies => {
username => Bread::Board::Literal->new(
name => 'username',
value => 'user'
),
password => Bread::Board::Literal->new(
name => 'password',
value => '****'
),
}));
</code>
</pre>
</section>
<section>
<h2>Hierarchal Containers</h2>
<pre>
<code class='smaller'>
$app-><span class='red'>add_sub_container</span>( $db );
$app->add_service(
Bread::Board::ConstructorInjection->new(
name => 'app',
class => 'Application',
dependencies => {
db_conn => Bread::Board::Dependency->new(
service_path => <span class='red'>'/database/db_conn'</span>
),
}
)
);
</code>
</pre>
</section>
<!-- ---------------------------------------------------------------- -->
<section>
<center><h1>???<h1></center>
</section>
<section>
<h1>Thank you!</h1>
<ul>
<li> <a href='https://metacpan.org/author/STEVAN'>STEVAN</a> for <a href='https://metacpan.org/release/Bread-Board'>Bread::Board</a> </li>
<li> <a href='https://github.com/paulrouget'>Paul Rouget</a> for <a href='https://github.com/paulrouget/dzslides'>DZslides</a></li>
</ul>
<h2>About talk</h2>
<ul>
<li> Slides on GitHub <a href='https://github.com/notbenh/DepIn-pdx.pm-talk'>DepIn-pdx.pm-talk</a> </li>
</ul>
</section>
<!-- Your Style -->
<!-- Define the style of you presentation -->
<style>
.red { font-weight: bolder }
.grey { color: #AAAAAA; }
.smaller { font-size: 90%; }
q {
margin-right: 50px;
margin-left : 50px;
display: block;
}
author {
float: right;
margin-right:50px;
color: #AAAAAA;
}
author:before {
content:"- "
}
center {margin-top:100px;}
center h1 { font-size:400px }
</style>
<!-- Maybe a font from http://www.google.com/webfonts ? -->
<link href='http://fonts.googleapis.com/css?family=Oswald' rel='stylesheet'>
<style>
html { background-color: black; }
body {background-color: white;}
/* A section is a slide. It's size is 800x600, and this will never change */
section {
/* The font from Google */
font-family: 'Oswald', arial, serif;
font-size: 2em;
}
h1, h2, h3 {
margin-top: 50px;
text-align: center;
}
ul {
margin-left: 200px;
}
a { color: #FF0066; } a:hover {text-decoration: underline;}
footer { position: absolute; bottom: 50px; right: 50px; }
/* Transition effect */
/* Feel free to change the transition effect for original
animations. See here:
https://developer.mozilla.org/en/CSS/CSS_transitions
How to use CSS3 Transitions: */
/*
section {
-moz-transition: left 400ms linear 0s;
-webkit-transition: left 400ms linear 0s;
-ms-transition: left 400ms linear 0s;
transition: left 400ms linear 0s;
}
*/
section {
-moz-transition: left 100ms linear 0s;
-webkit-transition: left 100ms linear 0s;
-ms-transition: left 100ms linear 0s;
transition: left 100ms linear 0s;
}
/* Before */
section { left: -150%; }
/* Now */
section[aria-selected] { left: 0; }
/* After */
section[aria-selected] ~ section { left: +150% }
</style>
<!-- {{{{ ***************** DZSlides CORE 2.0b1 *************************** -->
<!-- *********************************************************************** -->
<!-- *********************************************************************** -->
<!-- *********************************************************************** -->
<!-- *********************************************************************** -->
<!-- This block of code is not supposed to be edited, but if you want to change the behavior of the slides, feel free to hack it ;) -->
<!-- Default Style -->
<style>
* { margin: 0; padding: 0; }
details {display: none;}
body {
width: 800px; height: 600px;
margin-left: -400px; margin-top: -300px;
position: absolute; top: 50%; left: 50%;
overflow: hidden;
}
html {
overflow: hidden;
}
section {
position: absolute;
pointer-events: none;
width: 100%; height: 100%;
}
section[aria-selected] { pointer-events: auto;}
body {display: none}
body.loaded {display: block}
</style>
<script>
var friendWindows = [];
var idx = 1;
var slides;
/* main() */
window.onload = function() {
slides = document.querySelectorAll("body > section");
onhashchange();
setSlide();
document.body.className = "loaded";
setupTouchEvents();
onresize();
}
/* Handle keys */
window.onkeydown = function(e) {
// Don't intercept keyboard shortcuts
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) {
return;
}
if ( e.keyCode == 37 // left arrow
|| e.keyCode == 38 // up arrow
|| e.keyCode == 33 // page up
) {
e.preventDefault();
back();
}
if ( e.keyCode == 39 // right arrow
|| e.keyCode == 40 // down arrow
|| e.keyCode == 34 // page down
) {
e.preventDefault();
forward();
}
if ( e.keyCode == 32) { // space
e.preventDefault();
toggleContent();
}
}
/* Touch Events */
function setupTouchEvents() {
var orgX, newX;
var tracking = false;
var body = document.body;
body.addEventListener("touchstart", start, false);
body.addEventListener("touchmove", move, false);
function start(e) {
e.preventDefault();
tracking = true;
orgX = e.changedTouches[0].pageX;
}
function move(e) {
if (!tracking) return;
newX = e.changedTouches[0].pageX;
if (orgX - newX > 100) {
tracking = false;
forward();
} else {
if (orgX - newX < -100) {
tracking = false;
back();
}
}
}
}
/* Adapt the size of the slides to the window */
window.onresize = function() {
var sx = document.body.clientWidth / window.innerWidth;
var sy = document.body.clientHeight / window.innerHeight;
var transform = "scale(" + (1/Math.max(sx, sy)) + ")";
document.body.style.MozTransform = transform;
document.body.style.WebkitTransform = transform;
document.body.style.OTransform = transform;
document.body.style.msTransform = transform;
document.body.style.transform = transform;
}
function getDetails(idx) {
var s = document.querySelector("section:nth-of-type("+ idx +")");
var d = s.querySelector("details");
return d?d.innerHTML:"";
}
window.onmessage = function(e) {
msg = e.data;
win = e.source;
if (msg === "register") {
friendWindows.push(win);
win.postMessage(JSON.stringify({method: "registered", title: document.title, count: slides.length}), "*");
win.postMessage(JSON.stringify({method: "newslide", details: getDetails(idx), idx: idx}), "*");
return;
}
if (msg === "back") back();
if (msg === "forward") forward();
if (msg === "toggleContent") toggleContent();
// setSlide(42)
var r = /setSlide\((\d+)\)/.exec(msg);
if (r) {
idx = r[1];
setSlide();
}
}
/* If a Video is present in this new slide, play it.
If a Video is present in the previous slide, stop it. */
function toggleContent() {
var s = document.querySelector("section[aria-selected]");
if (s) {
var video = s.querySelector("video");
if (video) {
if (video.ended || video.paused) {
video.play();
} else {
video.pause();
}
}
}
}
/* If the user change the slide number in the URL bar, jump
to this slide. */
window.onhashchange = function(e) {
var newidx = ~~window.location.hash.split("#")[1];
if (!newidx) newidx = 1;
if (newidx == idx) return;
idx = newidx;
setSlide();
}
/* Slide controls */
function back() {
if (idx == 1) return;
idx--;
setSlide();
}
function forward() {
if (idx >= slides.length) return;
idx++;
setSlide();
}
function setSlide() {
var old = document.querySelector("section[aria-selected]");
var next = document.querySelector("section:nth-of-type("+ idx +")");
if (old) {
old.removeAttribute("aria-selected");
var video = old.querySelector("video");
if (video) { video.pause(); }
}
if (next) {
next.setAttribute("aria-selected", "true");
var video = next.querySelector("video");
if (video) { video.play(); }
} else {
console.warn("No such slide: " + idx);
idx = 0;
for (var i = 0; i < slides.length; i++) {
if (slides[i].hasAttribute("aria-selected")) {
idx = i + 1;
}
}
}
window.location.hash = idx;
for (var i = 0; i < friendWindows.length; i++) {
friendWindows[i].postMessage(JSON.stringify({method: "newslide", details: getDetails(idx), idx: idx}), "*");
}
}
</script>
<!-- vim: set fdm=marker: }}} -->