Merge branch 'master' into cpennington/cms-github
This commit is contained in:
@@ -1,10 +1,16 @@
|
||||
$fg-column: 70px;
|
||||
$fg-gutter: 26px;
|
||||
$fg-max-columns: 12;
|
||||
$body-font-family: "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif;
|
||||
$body-font-family: "Open Sans", "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif;
|
||||
$body-font-size: 14px;
|
||||
$body-line-height: 20px;
|
||||
|
||||
$light-blue: #f0f8fa;
|
||||
$dark-blue: #50545c;
|
||||
$bright-blue: #3c8ebf;
|
||||
$orange: #f96e5b;
|
||||
$yellow: #fff8af;
|
||||
|
||||
// Base html styles
|
||||
html {
|
||||
height: 100%;
|
||||
@@ -13,23 +19,44 @@ html {
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #888;
|
||||
@include transition;
|
||||
|
||||
&:hover {
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
font-family: $body-font-family;
|
||||
}
|
||||
|
||||
input[type="submit"], .button {
|
||||
border: 1px solid #ccc;
|
||||
background: #efefef;
|
||||
@include border-radius(3px);
|
||||
padding: 6px;
|
||||
button, input[type="submit"], .button {
|
||||
background-color: $orange;
|
||||
color: #fff;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
padding: 8px 10px;
|
||||
border: 0;
|
||||
font-weight: bold;
|
||||
|
||||
&:hover {
|
||||
background-color: shade($orange, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
#{$all-text-inputs}, textarea {
|
||||
border: 1px solid $dark-blue;
|
||||
font: $body-font-size $body-font-family;
|
||||
padding: 4px 6px;
|
||||
@include box-shadow(inset 0 3px 6px $light-blue);
|
||||
}
|
||||
|
||||
textarea {
|
||||
@include box-sizing(border-box);
|
||||
display: block;
|
||||
line-height: lh();
|
||||
padding: 15px;
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
|
||||
// Extends
|
||||
@@ -68,6 +95,15 @@ textarea {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.editable {
|
||||
&:hover {
|
||||
background: lighten($yellow, 10%);
|
||||
}
|
||||
button {
|
||||
padding: 4px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.wip {
|
||||
outline: 1px solid #f00 !important;
|
||||
position: relative;
|
||||
|
||||
@@ -6,8 +6,12 @@ section.cal {
|
||||
> header {
|
||||
@include clearfix;
|
||||
margin-bottom: 10px;
|
||||
background: #efefef;
|
||||
border: 1px solid #ddd;
|
||||
opacity: .4;
|
||||
@include transition;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@include inline-block();
|
||||
@@ -15,7 +19,6 @@ section.cal {
|
||||
letter-spacing: 1px;
|
||||
font-size: 14px;
|
||||
padding: 6px;
|
||||
margin-left: 6px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@@ -25,9 +28,8 @@ section.cal {
|
||||
li {
|
||||
@include inline-block;
|
||||
margin-left: 6px;
|
||||
padding-left: 6px;
|
||||
border-left: 1px solid #ddd;
|
||||
padding: 6px;
|
||||
padding: 0 6px;
|
||||
|
||||
a {
|
||||
@include inline-block();
|
||||
@@ -49,31 +51,35 @@ section.cal {
|
||||
ol {
|
||||
list-style: none;
|
||||
@include clearfix;
|
||||
border-left: 1px solid #333;
|
||||
border-top: 1px solid #333;
|
||||
border-left: 1px solid lighten($dark-blue, 40%);
|
||||
border-top: 1px solid lighten($dark-blue, 40%);
|
||||
width: 100%;
|
||||
@include box-sizing(border-box);
|
||||
|
||||
> li {
|
||||
border-right: 1px solid #333;
|
||||
border-bottom: 1px solid;
|
||||
border-right: 1px solid lighten($dark-blue, 40%);
|
||||
border-bottom: 1px solid lighten($dark-blue, 40%);
|
||||
@include box-sizing(border-box);
|
||||
float: left;
|
||||
width: flex-grid(3) + ((flex-gutter() * 3) / 4);
|
||||
background-color: lighten($light-blue, 2%);
|
||||
|
||||
|
||||
header {
|
||||
border-bottom: 1px solid #000;
|
||||
@include box-shadow(0 1px 2px #aaa);
|
||||
border-bottom: 1px solid lighten($dark-blue, 40%);
|
||||
@include box-shadow(0 2px 2px $light-blue);
|
||||
display: block;
|
||||
margin-bottom: 2px;
|
||||
background: #FFF;
|
||||
|
||||
h1 {
|
||||
font-size: 14px;
|
||||
text-transform: uppercase;
|
||||
border-bottom: 1px solid #ccc;
|
||||
border-bottom: 1px solid lighten($dark-blue, 60%);
|
||||
padding: 6px;
|
||||
|
||||
a {
|
||||
color: #000;
|
||||
color: $bright-blue;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
@@ -84,6 +90,10 @@ section.cal {
|
||||
color: #888;
|
||||
border-bottom: 0;
|
||||
font-size: 12px;
|
||||
|
||||
&:hover {
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,10 +103,17 @@ section.cal {
|
||||
margin-bottom: 1px;
|
||||
|
||||
li {
|
||||
background: #efefef;
|
||||
border-bottom: 1px solid #666;
|
||||
border-bottom: 1px solid darken($light-blue, 8%);
|
||||
padding: 6px;
|
||||
|
||||
&:hover {
|
||||
background: lighten($yellow, 10%);
|
||||
}
|
||||
|
||||
a {
|
||||
color: lighten($dark-blue, 10%);
|
||||
}
|
||||
|
||||
&.create-module {
|
||||
position: relative;
|
||||
|
||||
|
||||
36
cms/static/sass/_fonts.scss
Normal file
36
cms/static/sass/_fonts.scss
Normal file
@@ -0,0 +1,36 @@
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Open Sans Bold'), local('OpenSans-Bold'), url(http://themes.googleusercontent.com/static/fonts/opensans/v6/k3k702ZOKiLJc3WVjuplzKRDOzjiPcYnFooOUGCOsRk.woff) format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: local('Open Sans Light'), local('OpenSans-Light'), url(http://themes.googleusercontent.com/static/fonts/opensans/v6/DXI1ORHCpsQm3Vp6mXoaTaRDOzjiPcYnFooOUGCOsRk.woff) format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
src: local('Open Sans Bold Italic'), local('OpenSans-BoldItalic'), url(http://themes.googleusercontent.com/static/fonts/opensans/v6/PRmiXeptR36kaC0GEAetxhbnBKKEOwRKgsHDreGcocg.woff) format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
src: local('Open Sans Light Italic'), local('OpenSansLight-Italic'), url(http://themes.googleusercontent.com/static/fonts/opensans/v6/PRmiXeptR36kaC0GEAetxvR_54zmj3SbGZQh3vCOwvY.woff) format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://themes.googleusercontent.com/static/fonts/opensans/v6/xjAJXh38I15wypJXxuGMBrrIa-7acMAeDBVuclsi6Gc.woff) format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Open Sans'), local('OpenSans'), url(http://themes.googleusercontent.com/static/fonts/opensans/v6/cJZKeOuBrn4kERxqtaUH3bO3LdcAZYWl9Si6vvxL-qU.woff) format('woff');
|
||||
}
|
||||
@@ -9,13 +9,14 @@ body {
|
||||
}
|
||||
|
||||
> header {
|
||||
background: #000;
|
||||
background: $dark-blue;
|
||||
color: #fff;
|
||||
display: block;
|
||||
float: none;
|
||||
padding: 6px 20px;
|
||||
padding: 8px 25px;
|
||||
width: 100%;
|
||||
@include box-sizing(border-box);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
|
||||
nav {
|
||||
@include clearfix;
|
||||
@@ -24,6 +25,23 @@ body {
|
||||
font-size: 14px;
|
||||
text-transform: uppercase;
|
||||
float: left;
|
||||
margin-right: 15px;
|
||||
|
||||
a {
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
color: rgba(#fff, .6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(#fff, .8);
|
||||
|
||||
&:hover {
|
||||
color: rgba(#fff, .6);
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
@@ -35,7 +53,16 @@ body {
|
||||
|
||||
li {
|
||||
@include inline-block();
|
||||
margin-left: 15px;
|
||||
|
||||
a {
|
||||
padding: 8px 10px;
|
||||
display: block;
|
||||
margin: -8px 0;
|
||||
|
||||
&:hover {
|
||||
background-color: darken($dark-blue, 15%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,11 +70,11 @@ body {
|
||||
|
||||
&.content {
|
||||
section.main-content {
|
||||
border-left: 2px solid #000;
|
||||
border-left: 2px solid $dark-blue;
|
||||
@include box-sizing(border-box);
|
||||
width: flex-grid(9);
|
||||
float: left;
|
||||
@include box-shadow( -2px 0 3px #ddd );
|
||||
@include box-shadow( -2px 0 0 darken($light-blue, 3%));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,12 @@ section#unit-wrapper {
|
||||
section.filters {
|
||||
@include clearfix;
|
||||
margin-bottom: 10px;
|
||||
background: #efefef;
|
||||
border: 1px solid #ddd;
|
||||
opacity: .4;
|
||||
@include transition;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
ul {
|
||||
@include clearfix();
|
||||
@@ -22,14 +26,16 @@ section#unit-wrapper {
|
||||
|
||||
div.content {
|
||||
display: table;
|
||||
border: 1px solid;
|
||||
border: 1px solid lighten($dark-blue, 40%);
|
||||
width: 100%;
|
||||
|
||||
section {
|
||||
header {
|
||||
background: #eee;
|
||||
background: #fff;
|
||||
padding: 6px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
border-bottom: 1px solid lighten($dark-blue, 60%);
|
||||
border-top: 1px solid lighten($dark-blue, 60%);
|
||||
margin-top: -1px;
|
||||
@include clearfix;
|
||||
|
||||
h2 {
|
||||
@@ -37,6 +43,7 @@ section#unit-wrapper {
|
||||
letter-spacing: 1px;
|
||||
font-size: 12px;
|
||||
float: left;
|
||||
color: $bright-blue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +51,7 @@ section#unit-wrapper {
|
||||
@include box-sizing(border-box);
|
||||
display: table-cell;
|
||||
width: flex-grid(6, 9);
|
||||
border-right: 1px solid #333;
|
||||
border-right: 1px solid lighten($dark-blue, 40%);
|
||||
|
||||
&.empty {
|
||||
text-align: center;
|
||||
@@ -59,14 +66,9 @@ section#unit-wrapper {
|
||||
|
||||
ol {
|
||||
list-style: none;
|
||||
border-bottom: 1px solid #333;
|
||||
|
||||
li {
|
||||
border-bottom: 1px solid #333;
|
||||
|
||||
&:last-child{
|
||||
border-bottom: 0;
|
||||
}
|
||||
border-bottom: 1px solid lighten($dark-blue, 60%);
|
||||
|
||||
a {
|
||||
color: #000;
|
||||
@@ -78,6 +80,10 @@ section#unit-wrapper {
|
||||
li {
|
||||
padding: 6px;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
a.draggable {
|
||||
opacity: 1;
|
||||
@@ -127,11 +133,10 @@ section#unit-wrapper {
|
||||
|
||||
ol {
|
||||
list-style: none;
|
||||
border-bottom: 1px solid #999;
|
||||
|
||||
li {
|
||||
border-bottom: 1px solid #999;
|
||||
background: #f9f9f9;
|
||||
border-bottom: 1px solid darken($light-blue, 8%);
|
||||
background: lighten($light-blue, 2%);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
section#unit-wrapper {
|
||||
> header {
|
||||
border-bottom: 2px solid #333;
|
||||
border-bottom: 2px solid $dark-blue;
|
||||
@include clearfix();
|
||||
@include box-shadow( 0 2px 0 darken($light-blue, 3%));
|
||||
padding: 6px 20px;
|
||||
|
||||
section {
|
||||
@@ -12,6 +13,7 @@ section#unit-wrapper {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
@include inline-block();
|
||||
color: $bright-blue;
|
||||
}
|
||||
|
||||
p {
|
||||
@@ -28,11 +30,19 @@ section#unit-wrapper {
|
||||
color: #666;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
@include inline-block;
|
||||
|
||||
&.cancel {
|
||||
margin-right: 20px;
|
||||
font-style: italic;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&.save-update {
|
||||
@extend .button;
|
||||
margin: -6px -25px -6px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,16 +55,16 @@ section#unit-wrapper {
|
||||
&.status-settings {
|
||||
float: left;
|
||||
margin-bottom: 10px;
|
||||
color: $dark-blue;
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
@include border-radius(2px);
|
||||
border: 1px solid #999;
|
||||
border: 1px solid lighten($dark-blue, 40%);
|
||||
@include inline-block();
|
||||
|
||||
li {
|
||||
@include inline-block();
|
||||
border-right: 1px solid #999;
|
||||
border-right: 1px solid lighten($dark-blue, 40%);
|
||||
padding: 6px;
|
||||
|
||||
&:last-child {
|
||||
@@ -64,14 +74,19 @@ section#unit-wrapper {
|
||||
&.current {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $dark-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a.settings {
|
||||
@include inline-block();
|
||||
margin: 0 20px;
|
||||
border: 1px solid #999;
|
||||
padding: 6px;
|
||||
border: 1px solid lighten($dark-blue, 40%);
|
||||
color: $dark-blue;
|
||||
}
|
||||
|
||||
select {
|
||||
@@ -81,6 +96,7 @@ section#unit-wrapper {
|
||||
|
||||
&.author {
|
||||
float: right;
|
||||
color: lighten($dark-blue, 6%);
|
||||
|
||||
dl {
|
||||
dt {
|
||||
@@ -94,7 +110,8 @@ section#unit-wrapper {
|
||||
}
|
||||
|
||||
&.tags {
|
||||
background: #eee;
|
||||
background: $light-blue;
|
||||
color: lighten($dark-blue, 6%);
|
||||
padding: 10px;
|
||||
margin: 0 0 20px;
|
||||
@include clearfix();
|
||||
@@ -118,9 +135,9 @@ section#unit-wrapper {
|
||||
}
|
||||
|
||||
//general styles for main content
|
||||
div.preview {
|
||||
background: #eee;
|
||||
.preview {
|
||||
@include box-sizing(border-box);
|
||||
border: 1px solid lighten($dark-blue, 40%);
|
||||
min-height: 40px;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
@@ -153,13 +170,16 @@ section#unit-wrapper {
|
||||
|
||||
//notes
|
||||
section.notes {
|
||||
margin-top: 20px;
|
||||
padding: 20px 0 0;
|
||||
border-top: 1px solid #ccc;
|
||||
margin-top: 40px;
|
||||
padding: 40px 0 0;
|
||||
border-top: 2px solid lighten( $dark-blue, 60% );
|
||||
|
||||
h2 {
|
||||
font-size: 14px;
|
||||
margin-bottom: 6px;
|
||||
font-size: $body-font-size;
|
||||
text-transform: uppercase;
|
||||
color: $bright-blue;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
form {
|
||||
@@ -194,6 +214,12 @@ section#unit-wrapper {
|
||||
@include inline-block();
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
a.cancel {
|
||||
float: right;
|
||||
font-style: italic;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<form>
|
||||
<h2>Add a note</h2>
|
||||
<textarea name="" id= rows="8" cols="40"></textarea>
|
||||
<input type="submit" name="" id="" value="post" />
|
||||
<input type="submit" name="" id="" value="Post" />
|
||||
</form>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ describe 'Calculator', ->
|
||||
|
||||
it 'bind the help button', ->
|
||||
# These events are bind by $.hover()
|
||||
expect($('div.help-wrapper a')).toHandleWith 'mouseenter', @calculator.helpToggle
|
||||
expect($('div.help-wrapper a')).toHandleWith 'mouseleave', @calculator.helpToggle
|
||||
expect($('div.help-wrapper a')).toHandleWith 'mouseover', @calculator.helpToggle
|
||||
expect($('div.help-wrapper a')).toHandleWith 'mouseout', @calculator.helpToggle
|
||||
|
||||
it 'prevent default behavior on help button', ->
|
||||
$('div.help-wrapper a').click (e) ->
|
||||
|
||||
@@ -34,7 +34,7 @@ describe 'Courseware', ->
|
||||
<div class="course-content">
|
||||
<div id="video_1" class="video" data-streams="1.0:abc1234"></div>
|
||||
<div id="video_2" class="video" data-streams="1.0:def5678"></div>
|
||||
<div id="problem_3" class="problems-wrapper" data-url="/example/url/">
|
||||
<div id="problem_3" class="problems-wrapper" data-problem-id="3" data-url="/example/url/">
|
||||
<div id="histogram_3" class="histogram" data-histogram="[[0, 1]]" style="height: 20px; display: block;">
|
||||
</div>
|
||||
</div>
|
||||
@@ -46,7 +46,7 @@ describe 'Courseware', ->
|
||||
expect(window.Video).toHaveBeenCalledWith('2', '1.0:def5678')
|
||||
|
||||
it 'detect the problem element and convert it', ->
|
||||
expect(window.Problem).toHaveBeenCalledWith('3', '/example/url/')
|
||||
expect(window.Problem).toHaveBeenCalledWith(3, 'problem_3', '/example/url/')
|
||||
|
||||
it 'detect the histrogram element and convert it', ->
|
||||
expect(window.Histogram).toHaveBeenCalledWith('3', [[0, 1]])
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
jasmine.getFixtures().fixturesPath = "/_jasmine/fixtures/"
|
||||
|
||||
jasmine.stubbedMetadata =
|
||||
abc123:
|
||||
id: 'abc123'
|
||||
duration: 100
|
||||
def456:
|
||||
id: 'def456'
|
||||
slowerSpeedYoutubeId:
|
||||
id: 'slowerSpeedYoutubeId'
|
||||
duration: 300
|
||||
normalSpeedYoutubeId:
|
||||
id: 'normalSpeedYoutubeId'
|
||||
duration: 200
|
||||
bogus:
|
||||
duration: 300
|
||||
duration: 100
|
||||
|
||||
jasmine.stubbedCaption =
|
||||
start: [0, 10000, 20000, 30000]
|
||||
@@ -20,12 +20,12 @@ jasmine.stubRequests = ->
|
||||
settings.success data: jasmine.stubbedMetadata[match[1]]
|
||||
else if match = settings.url.match /static\/subs\/(.+)\.srt\.sjson/
|
||||
settings.success jasmine.stubbedCaption
|
||||
else if settings.url.match /modx\/problem\/.+\/problem_get$/
|
||||
else if settings.url.match /modx\/.+\/problem_get$/
|
||||
settings.success html: readFixtures('problem_content.html')
|
||||
else if settings.url == '/calculate' ||
|
||||
settings.url == '/6002x/modx/sequence/1/goto_position' ||
|
||||
settings.url.match(/modx\/.+\/goto_position$/) ||
|
||||
settings.url.match(/event$/) ||
|
||||
settings.url.match(/modx\/problem\/.+\/problem_(check|reset|show|save)$/)
|
||||
settings.url.match(/modx\/.+\/problem_(check|reset|show|save)$/)
|
||||
# do nothing
|
||||
else
|
||||
throw "External request attempted for #{settings.url}, which is not defined."
|
||||
@@ -49,10 +49,10 @@ jasmine.stubVideoPlayer = (context, enableParts, createPlayer=true) ->
|
||||
loadFixtures 'video.html'
|
||||
jasmine.stubRequests()
|
||||
YT.Player = undefined
|
||||
context.video = new Video 'example', '.75:abc123,1.0:def456'
|
||||
context.video = new Video 'example', '.75:slowerSpeedYoutubeId,1.0:normalSpeedYoutubeId'
|
||||
jasmine.stubYoutubePlayer()
|
||||
if createPlayer
|
||||
return new VideoPlayer context.video
|
||||
return new VideoPlayer(video: context.video)
|
||||
|
||||
spyOn(window, 'onunload')
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ describe 'Logger', ->
|
||||
|
||||
it 'send a request to log event', ->
|
||||
spyOn($, 'ajax')
|
||||
$(window).trigger('onunload')
|
||||
window.onunload()
|
||||
expect($.ajax).toHaveBeenCalledWith
|
||||
url: "#{Courseware.prefix}/event",
|
||||
data:
|
||||
|
||||
@@ -17,16 +17,16 @@ describe 'Problem', ->
|
||||
|
||||
describe 'constructor', ->
|
||||
beforeEach ->
|
||||
@problem = new Problem 1, '/problem/url/'
|
||||
@problem = new Problem 1, "problem_1", "/problem/url/"
|
||||
|
||||
it 'set the element', ->
|
||||
expect(@problem.element).toBe '#problem_1'
|
||||
expect(@problem.el).toBe '#problem_1'
|
||||
|
||||
describe 'bind', ->
|
||||
beforeEach ->
|
||||
spyOn window, 'update_schematics'
|
||||
MathJax.Hub.getAllJax.andReturn [@stubbedJax]
|
||||
@problem = new Problem 1, '/problem/url/'
|
||||
@problem = new Problem 1, "problem_1", "/problem/url/"
|
||||
|
||||
it 'set mathjax typeset', ->
|
||||
expect(MathJax.Hub.Queue).toHaveBeenCalled()
|
||||
@@ -60,7 +60,7 @@ describe 'Problem', ->
|
||||
|
||||
describe 'render', ->
|
||||
beforeEach ->
|
||||
@problem = new Problem 1, '/problem/url/'
|
||||
@problem = new Problem 1, "problem_1", "/problem/url/"
|
||||
@bind = @problem.bind
|
||||
spyOn @problem, 'bind'
|
||||
|
||||
@@ -69,7 +69,7 @@ describe 'Problem', ->
|
||||
@problem.render 'Hello World'
|
||||
|
||||
it 'render the content', ->
|
||||
expect(@problem.element.html()).toEqual 'Hello World'
|
||||
expect(@problem.el.html()).toEqual 'Hello World'
|
||||
|
||||
it 're-bind the content', ->
|
||||
expect(@problem.bind).toHaveBeenCalled()
|
||||
@@ -81,14 +81,14 @@ describe 'Problem', ->
|
||||
@problem.render()
|
||||
|
||||
it 'load the content via ajax', ->
|
||||
expect(@problem.element.html()).toEqual 'Hello World'
|
||||
expect(@problem.el.html()).toEqual 'Hello World'
|
||||
|
||||
it 're-bind the content', ->
|
||||
expect(@problem.bind).toHaveBeenCalled()
|
||||
|
||||
describe 'check', ->
|
||||
beforeEach ->
|
||||
@problem = new Problem 1, '/problem/url/'
|
||||
@problem = new Problem 1, "problem_1", "/problem/url/"
|
||||
@problem.answers = 'foo=1&bar=2'
|
||||
|
||||
it 'log the problem_check event', ->
|
||||
@@ -98,19 +98,19 @@ describe 'Problem', ->
|
||||
it 'submit the answer for check', ->
|
||||
spyOn $, 'postWithPrefix'
|
||||
@problem.check()
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith '/modx/problem/1/problem_check', 'foo=1&bar=2', jasmine.any(Function)
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith '/modx/1/problem_check', 'foo=1&bar=2', jasmine.any(Function)
|
||||
|
||||
describe 'when the response is correct', ->
|
||||
it 'call render with returned content', ->
|
||||
spyOn($, 'postWithPrefix').andCallFake (url, answers, callback) -> callback(success: 'correct', contents: 'Correct!')
|
||||
@problem.check()
|
||||
expect(@problem.element.html()).toEqual 'Correct!'
|
||||
expect(@problem.el.html()).toEqual 'Correct!'
|
||||
|
||||
describe 'when the response is incorrect', ->
|
||||
it 'call render with returned content', ->
|
||||
spyOn($, 'postWithPrefix').andCallFake (url, answers, callback) -> callback(success: 'incorrect', contents: 'Correct!')
|
||||
@problem.check()
|
||||
expect(@problem.element.html()).toEqual 'Correct!'
|
||||
expect(@problem.el.html()).toEqual 'Correct!'
|
||||
|
||||
describe 'when the response is undetermined', ->
|
||||
it 'alert the response', ->
|
||||
@@ -121,7 +121,7 @@ describe 'Problem', ->
|
||||
|
||||
describe 'reset', ->
|
||||
beforeEach ->
|
||||
@problem = new Problem 1, '/problem/url/'
|
||||
@problem = new Problem 1, "problem_1", "/problem/url/"
|
||||
|
||||
it 'log the problem_reset event', ->
|
||||
@problem.answers = 'foo=1&bar=2'
|
||||
@@ -131,22 +131,22 @@ describe 'Problem', ->
|
||||
it 'POST to the problem reset page', ->
|
||||
spyOn $, 'postWithPrefix'
|
||||
@problem.reset()
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith '/modx/problem/1/problem_reset', { id: 1 }, jasmine.any(Function)
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith '/modx/1/problem_reset', { id: 1 }, jasmine.any(Function)
|
||||
|
||||
it 'render the returned content', ->
|
||||
spyOn($, 'postWithPrefix').andCallFake (url, answers, callback) ->
|
||||
callback html: "Reset!"
|
||||
@problem.reset()
|
||||
expect(@problem.element.html()).toEqual 'Reset!'
|
||||
expect(@problem.el.html()).toEqual 'Reset!'
|
||||
|
||||
describe 'show', ->
|
||||
beforeEach ->
|
||||
@problem = new Problem 1, '/problem/url/'
|
||||
@problem.element.prepend '<div id="answer_1_1" /><div id="answer_1_2" />'
|
||||
@problem = new Problem 1, "problem_1", "/problem/url/"
|
||||
@problem.el.prepend '<div id="answer_1_1" /><div id="answer_1_2" />'
|
||||
|
||||
describe 'when the answer has not yet shown', ->
|
||||
beforeEach ->
|
||||
@problem.element.removeClass 'showed'
|
||||
@problem.el.removeClass 'showed'
|
||||
|
||||
it 'log the problem_show event', ->
|
||||
@problem.show()
|
||||
@@ -155,7 +155,7 @@ describe 'Problem', ->
|
||||
it 'fetch the answers', ->
|
||||
spyOn $, 'postWithPrefix'
|
||||
@problem.show()
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith '/modx/problem/1/problem_show', jasmine.any(Function)
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith '/modx/1/problem_show', jasmine.any(Function)
|
||||
|
||||
it 'show the answers', ->
|
||||
spyOn($, 'postWithPrefix').andCallFake (url, callback) ->
|
||||
@@ -172,11 +172,11 @@ describe 'Problem', ->
|
||||
it 'add the showed class to element', ->
|
||||
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback(answers: {})
|
||||
@problem.show()
|
||||
expect(@problem.element).toHaveClass 'showed'
|
||||
expect(@problem.el).toHaveClass 'showed'
|
||||
|
||||
describe 'multiple choice question', ->
|
||||
beforeEach ->
|
||||
@problem.element.prepend '''
|
||||
@problem.el.prepend '''
|
||||
<label for="input_1_1_1"><input type="checkbox" name="input_1_1" id="input_1_1_1" value="1"> One</label>
|
||||
<label for="input_1_1_2"><input type="checkbox" name="input_1_1" id="input_1_1_2" value="2"> Two</label>
|
||||
<label for="input_1_1_3"><input type="checkbox" name="input_1_1" id="input_1_1_3" value="3"> Three</label>
|
||||
@@ -194,8 +194,8 @@ describe 'Problem', ->
|
||||
|
||||
describe 'when the answers are alreay shown', ->
|
||||
beforeEach ->
|
||||
@problem.element.addClass 'showed'
|
||||
@problem.element.prepend '''
|
||||
@problem.el.addClass 'showed'
|
||||
@problem.el.prepend '''
|
||||
<label for="input_1_1_1" correct_answer="true">
|
||||
<input type="checkbox" name="input_1_1" id="input_1_1_1" value="1" />
|
||||
One
|
||||
@@ -216,11 +216,11 @@ describe 'Problem', ->
|
||||
|
||||
it 'remove the showed class from element', ->
|
||||
@problem.show()
|
||||
expect(@problem.element).not.toHaveClass 'showed'
|
||||
expect(@problem.el).not.toHaveClass 'showed'
|
||||
|
||||
describe 'save', ->
|
||||
beforeEach ->
|
||||
@problem = new Problem 1, '/problem/url/'
|
||||
@problem = new Problem 1, "problem_1", "/problem/url/"
|
||||
@problem.answers = 'foo=1&bar=2'
|
||||
|
||||
it 'log the problem_save event', ->
|
||||
@@ -230,7 +230,7 @@ describe 'Problem', ->
|
||||
it 'POST to save problem', ->
|
||||
spyOn $, 'postWithPrefix'
|
||||
@problem.save()
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith '/modx/problem/1/problem_save', 'foo=1&bar=2', jasmine.any(Function)
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith '/modx/1/problem_save', 'foo=1&bar=2', jasmine.any(Function)
|
||||
|
||||
it 'alert to the user', ->
|
||||
spyOn window, 'alert'
|
||||
@@ -240,7 +240,7 @@ describe 'Problem', ->
|
||||
|
||||
describe 'refreshMath', ->
|
||||
beforeEach ->
|
||||
@problem = new Problem 1, '/problem/url/'
|
||||
@problem = new Problem 1, "problem_1", "/problem/url/"
|
||||
$('#input_example_1').val 'E=mc^2'
|
||||
@problem.refreshMath target: $('#input_example_1').get(0)
|
||||
|
||||
@@ -250,7 +250,7 @@ describe 'Problem', ->
|
||||
|
||||
describe 'updateMathML', ->
|
||||
beforeEach ->
|
||||
@problem = new Problem 1, '/problem/url/'
|
||||
@problem = new Problem 1, "problem_1", "/problem/url/"
|
||||
@stubbedJax.root.toMathML.andReturn '<MathML>'
|
||||
|
||||
describe 'when there is no exception', ->
|
||||
@@ -270,8 +270,8 @@ describe 'Problem', ->
|
||||
|
||||
describe 'refreshAnswers', ->
|
||||
beforeEach ->
|
||||
@problem = new Problem 1, '/problem/url/'
|
||||
@problem.element.html '''
|
||||
@problem = new Problem 1, "problem_1", "/problem/url/"
|
||||
@problem.el.html '''
|
||||
<textarea class="CodeMirror" />
|
||||
<input id="input_1_1" name="input_1_1" class="schematic" value="one" />
|
||||
<input id="input_1_2" name="input_1_2" value="two" />
|
||||
|
||||
@@ -9,10 +9,10 @@ describe 'Sequence', ->
|
||||
|
||||
describe 'constructor', ->
|
||||
beforeEach ->
|
||||
@sequence = new Sequence '1', @items, 'sequence', 1
|
||||
@sequence = new Sequence '1', 'sequence_1', @items, 'sequence', 1
|
||||
|
||||
it 'set the element', ->
|
||||
expect(@sequence.element).toEqual $('#sequence_1')
|
||||
expect(@sequence.el).toEqual $('#sequence_1')
|
||||
|
||||
it 'build the navigation', ->
|
||||
classes = $('#sequence-list li>a').map(-> $(this).attr('class')).get()
|
||||
@@ -31,7 +31,7 @@ describe 'Sequence', ->
|
||||
|
||||
describe 'toggleArrows', ->
|
||||
beforeEach ->
|
||||
@sequence = new Sequence '1', @items, 'sequence', 1
|
||||
@sequence = new Sequence '1', 'sequence_1', @items, 'sequence', 1
|
||||
|
||||
describe 'when the first tab is active', ->
|
||||
beforeEach ->
|
||||
@@ -73,8 +73,8 @@ describe 'Sequence', ->
|
||||
describe 'render', ->
|
||||
beforeEach ->
|
||||
spyOn $, 'postWithPrefix'
|
||||
@sequence = new Sequence '1', @items, 'sequence'
|
||||
spyOnEvent @sequence.element, 'contentChanged'
|
||||
@sequence = new Sequence '1', 'sequence_1', @items, 'sequence'
|
||||
spyOnEvent @sequence.el, 'contentChanged'
|
||||
spyOn(@sequence, 'toggleArrows').andCallThrough()
|
||||
|
||||
describe 'with a different position than the current one', ->
|
||||
@@ -94,7 +94,7 @@ describe 'Sequence', ->
|
||||
expect($('[data-element="2"]')).toHaveClass 'seq_video_visited'
|
||||
|
||||
it 'save the new position', ->
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith '/modx/sequence/1/goto_position', position: 1
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith '/modx/1/goto_position', position: 1
|
||||
|
||||
it 'mark new tab as active', ->
|
||||
expect($('[data-element="1"]')).toHaveClass 'seq_video_active'
|
||||
@@ -109,18 +109,18 @@ describe 'Sequence', ->
|
||||
expect(@sequence.toggleArrows).toHaveBeenCalled()
|
||||
|
||||
it 'trigger contentChanged event', ->
|
||||
expect('contentChanged').toHaveBeenTriggeredOn @sequence.element
|
||||
expect('contentChanged').toHaveBeenTriggeredOn @sequence.el
|
||||
|
||||
describe 'with the same position as the current one', ->
|
||||
it 'should not trigger contentChanged event', ->
|
||||
@sequence.position = 2
|
||||
@sequence.render 2
|
||||
expect('contentChanged').not.toHaveBeenTriggeredOn @sequence.element
|
||||
expect('contentChanged').not.toHaveBeenTriggeredOn @sequence.el
|
||||
|
||||
describe 'goto', ->
|
||||
beforeEach ->
|
||||
jasmine.stubRequests()
|
||||
@sequence = new Sequence '1', @items, 'sequence', 2
|
||||
@sequence = new Sequence '1', 'sequence_1', @items, 'sequence', 2
|
||||
$('[data-element="3"]').click()
|
||||
|
||||
it 'log the sequence goto event', ->
|
||||
@@ -132,7 +132,7 @@ describe 'Sequence', ->
|
||||
describe 'next', ->
|
||||
beforeEach ->
|
||||
jasmine.stubRequests()
|
||||
@sequence = new Sequence '1', @items, 'sequence', 2
|
||||
@sequence = new Sequence '1', 'sequence_1', @items, 'sequence', 2
|
||||
$('.sequence-nav-buttons .next a').click()
|
||||
|
||||
it 'log the next sequence event', ->
|
||||
@@ -144,7 +144,7 @@ describe 'Sequence', ->
|
||||
describe 'previous', ->
|
||||
beforeEach ->
|
||||
jasmine.stubRequests()
|
||||
@sequence = new Sequence '1', @items, 'sequence', 2
|
||||
@sequence = new Sequence '1', 'sequence_1', @items, 'sequence', 2
|
||||
$('.sequence-nav-buttons .prev a').click()
|
||||
|
||||
it 'log the previous sequence event', ->
|
||||
@@ -155,5 +155,5 @@ describe 'Sequence', ->
|
||||
|
||||
describe 'link_for', ->
|
||||
it 'return a link for specific position', ->
|
||||
sequence = new Sequence '1', @items, 2
|
||||
sequence = new Sequence '1', 'sequence_1', @items, 2
|
||||
expect(sequence.link_for(2)).toBe '[data-element="2"]'
|
||||
|
||||
@@ -9,7 +9,7 @@ describe 'Tab', ->
|
||||
@tab = new Tab 1, @items
|
||||
|
||||
it 'set the element', ->
|
||||
expect(@tab.element).toEqual $('#tab_1')
|
||||
expect(@tab.el).toEqual $('#tab_1')
|
||||
|
||||
it 'build the tabs', ->
|
||||
links = $('.navigation li>a').map(-> $(this).attr('href')).get()
|
||||
@@ -34,6 +34,6 @@ describe 'Tab', ->
|
||||
expect($('#tab-1-2').html()).toEqual ''
|
||||
|
||||
it 'trigger contentChanged event on the element', ->
|
||||
spyOnEvent @tab.element, 'contentChanged'
|
||||
spyOnEvent @tab.el, 'contentChanged'
|
||||
$('[href="#tab-1-1"]').click()
|
||||
expect('contentChanged').toHaveBeenTriggeredOn @tab.element
|
||||
expect('contentChanged').toHaveBeenTriggeredOn @tab.el
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
describe 'VideoCaption', ->
|
||||
beforeEach ->
|
||||
@player = jasmine.stubVideoPlayer @
|
||||
jasmine.stubVideoPlayer @
|
||||
$('.subtitles').remove()
|
||||
|
||||
afterEach ->
|
||||
YT.Player = undefined
|
||||
@@ -12,10 +13,7 @@ describe 'VideoCaption', ->
|
||||
|
||||
describe 'always', ->
|
||||
beforeEach ->
|
||||
@caption = new VideoCaption @player, 'def456'
|
||||
|
||||
it 'set the player', ->
|
||||
expect(@caption.player).toEqual @player
|
||||
@caption = new VideoCaption el: $('.video'), youtubeId: 'def456', currentSpeed: '1.0'
|
||||
|
||||
it 'set the youtube id', ->
|
||||
expect(@caption.youtubeId).toEqual 'def456'
|
||||
@@ -30,26 +28,14 @@ describe 'VideoCaption', ->
|
||||
expect($.getWithPrefix).toHaveBeenCalledWith @caption.captionURL(), jasmine.any(Function)
|
||||
|
||||
it 'bind window resize event', ->
|
||||
expect($(window)).toHandleWith 'resize', @caption.onWindowResize
|
||||
|
||||
it 'bind player resize event', ->
|
||||
expect($(@player)).toHandleWith 'resize', @caption.onWindowResize
|
||||
|
||||
it 'bind player seek event', ->
|
||||
expect($(@player)).toHandleWith 'seek', @caption.onUpdatePlayTime
|
||||
|
||||
it 'bind player updatePlayTime event', ->
|
||||
expect($(@player)).toHandleWith 'updatePlayTime', @caption.onUpdatePlayTime
|
||||
|
||||
it 'bind player play event', ->
|
||||
expect($(@player)).toHandleWith 'play', @caption.onPlay
|
||||
expect($(window)).toHandleWith 'resize', @caption.resize
|
||||
|
||||
it 'bind the hide caption button', ->
|
||||
expect($('.hide-subtitles')).toHandleWith 'click', @caption.toggle
|
||||
|
||||
it 'bind the mouse movement', ->
|
||||
expect($('.subtitles')).toHandleWith 'mouseenter', @caption.onMouseEnter
|
||||
expect($('.subtitles')).toHandleWith 'mouseleave', @caption.onMouseLeave
|
||||
expect($('.subtitles')).toHandleWith 'mouseover', @caption.onMouseEnter
|
||||
expect($('.subtitles')).toHandleWith 'mouseout', @caption.onMouseLeave
|
||||
expect($('.subtitles')).toHandleWith 'mousemove', @caption.onMovement
|
||||
expect($('.subtitles')).toHandleWith 'mousewheel', @caption.onMovement
|
||||
expect($('.subtitles')).toHandleWith 'DOMMouseScroll', @caption.onMovement
|
||||
@@ -57,7 +43,7 @@ describe 'VideoCaption', ->
|
||||
describe 'when on a non touch-based device', ->
|
||||
beforeEach ->
|
||||
spyOn(window, 'onTouchBasedDevice').andReturn false
|
||||
@caption = new VideoCaption @player, 'def456'
|
||||
@caption = new VideoCaption el: $('.video'), youtubeId: 'def456', currentSpeed: '1.0'
|
||||
|
||||
it 'render the caption', ->
|
||||
expect($('.subtitles').html()).toMatch new RegExp('''
|
||||
@@ -81,7 +67,7 @@ describe 'VideoCaption', ->
|
||||
describe 'when on a touch-based device', ->
|
||||
beforeEach ->
|
||||
spyOn(window, 'onTouchBasedDevice').andReturn true
|
||||
@caption = new VideoCaption @player, 'def456'
|
||||
@caption = new VideoCaption el: $('.video'), youtubeId: 'def456', currentSpeed: '1.0'
|
||||
|
||||
it 'show explaination message', ->
|
||||
expect($('.subtitles li')).toHaveHtml "Caption will be displayed when you start playing the video."
|
||||
@@ -93,7 +79,7 @@ describe 'VideoCaption', ->
|
||||
beforeEach ->
|
||||
spyOn(window, 'setTimeout').andReturn 100
|
||||
spyOn window, 'clearTimeout'
|
||||
@caption = new VideoCaption @player, 'def456'
|
||||
@caption = new VideoCaption el: $('.video'), youtubeId: 'def456', currentSpeed: '1.0'
|
||||
|
||||
describe 'when cursor is outside of the caption box', ->
|
||||
beforeEach ->
|
||||
@@ -140,7 +126,7 @@ describe 'VideoCaption', ->
|
||||
|
||||
describe 'when the player is playing', ->
|
||||
beforeEach ->
|
||||
spyOn(@player, 'isPlaying').andReturn true
|
||||
@caption.playing = true
|
||||
$('.subtitles li[data-index]:first').addClass 'current'
|
||||
$('.subtitles').trigger jQuery.Event 'mouseout'
|
||||
|
||||
@@ -149,7 +135,7 @@ describe 'VideoCaption', ->
|
||||
|
||||
describe 'when the player is not playing', ->
|
||||
beforeEach ->
|
||||
spyOn(@player, 'isPlaying').andReturn false
|
||||
@caption.playing = false
|
||||
$('.subtitles').trigger jQuery.Event 'mouseout'
|
||||
|
||||
it 'does not scroll the caption', ->
|
||||
@@ -157,7 +143,7 @@ describe 'VideoCaption', ->
|
||||
|
||||
describe 'search', ->
|
||||
beforeEach ->
|
||||
@caption = new VideoCaption @player, 'def456'
|
||||
@caption = new VideoCaption el: $('.video'), youtubeId: 'def456', currentSpeed: '1.0'
|
||||
|
||||
it 'return a correct caption index', ->
|
||||
expect(@caption.search(0)).toEqual 0
|
||||
@@ -167,20 +153,20 @@ describe 'VideoCaption', ->
|
||||
expect(@caption.search(30000)).toEqual 3
|
||||
expect(@caption.search(30001)).toEqual 3
|
||||
|
||||
describe 'onPlay', ->
|
||||
describe 'play', ->
|
||||
describe 'when the caption was not rendered', ->
|
||||
beforeEach ->
|
||||
spyOn(window, 'onTouchBasedDevice').andReturn true
|
||||
@caption = new VideoCaption @player, 'def456'
|
||||
@caption.onPlay()
|
||||
@caption = new VideoCaption el: $('.video'), youtubeId: 'def456', currentSpeed: '1.0'
|
||||
@caption.play()
|
||||
|
||||
it 'render the caption', ->
|
||||
expect($('.subtitles').html()).toMatch new RegExp('''
|
||||
<li data-index="0" data-start="0">Caption at 0</li>
|
||||
<li data-index="1" data-start="10000">Caption at 10000</li>
|
||||
<li data-index="2" data-start="20000">Caption at 20000</li>
|
||||
<li data-index="3" data-start="30000">Caption at 30000</li>
|
||||
'''.replace(/\n/g, ''))
|
||||
expect($('.subtitles').html()).toMatch new RegExp(
|
||||
'''<li data-index="0" data-start="0">Caption at 0</li>''' +
|
||||
'''<li data-index="1" data-start="10000">Caption at 10000</li>''' +
|
||||
'''<li data-index="2" data-start="20000">Caption at 20000</li>''' +
|
||||
'''<li data-index="3" data-start="30000">Caption at 30000</li>'''
|
||||
)
|
||||
|
||||
it 'add a padding element to caption', ->
|
||||
expect($('.subtitles li:first')).toBe '.spacing'
|
||||
@@ -193,22 +179,34 @@ describe 'VideoCaption', ->
|
||||
it 'set rendered to true', ->
|
||||
expect(@caption.rendered).toBeTruthy()
|
||||
|
||||
describe 'onUpdatePlayTime', ->
|
||||
it 'set playing to true', ->
|
||||
expect(@caption.playing).toBeTruthy()
|
||||
|
||||
describe 'pause', ->
|
||||
beforeEach ->
|
||||
@caption = new VideoCaption @player, 'def456'
|
||||
@caption = new VideoCaption el: $('.video'), youtubeId: 'def456', currentSpeed: '1.0'
|
||||
@caption.playing = true
|
||||
@caption.pause()
|
||||
|
||||
it 'set playing to false', ->
|
||||
expect(@caption.playing).toBeFalsy()
|
||||
|
||||
describe 'updatePlayTime', ->
|
||||
beforeEach ->
|
||||
@caption = new VideoCaption el: $('.video'), youtubeId: 'def456', currentSpeed: '1.0'
|
||||
|
||||
describe 'when the video speed is 1.0x', ->
|
||||
beforeEach ->
|
||||
@video.setSpeed '1.0'
|
||||
@caption.onUpdatePlayTime {}, 25.000
|
||||
@caption.currentSpeed = '1.0'
|
||||
@caption.updatePlayTime 25.000
|
||||
|
||||
it 'search the caption based on time', ->
|
||||
expect(@caption.currentIndex).toEqual 2
|
||||
|
||||
describe 'when the video speed is not 1.0x', ->
|
||||
beforeEach ->
|
||||
@video.setSpeed '0.75'
|
||||
@caption.onUpdatePlayTime {}, 25.000
|
||||
@caption.currentSpeed = '0.75'
|
||||
@caption.updatePlayTime 25.000
|
||||
|
||||
it 'search the caption based on 1.0x speed', ->
|
||||
expect(@caption.currentIndex).toEqual 1
|
||||
@@ -217,7 +215,7 @@ describe 'VideoCaption', ->
|
||||
beforeEach ->
|
||||
@caption.currentIndex = 1
|
||||
$('.subtitles li[data-index=1]').addClass 'current'
|
||||
@caption.onUpdatePlayTime {}, 25.000
|
||||
@caption.updatePlayTime 25.000
|
||||
|
||||
it 'deactivate the previous caption', ->
|
||||
expect($('.subtitles li[data-index=1]')).not.toHaveClass 'current'
|
||||
@@ -235,16 +233,16 @@ describe 'VideoCaption', ->
|
||||
beforeEach ->
|
||||
@caption.currentIndex = 1
|
||||
$('.subtitles li[data-index=1]').addClass 'current'
|
||||
@caption.onUpdatePlayTime {}, 15.000
|
||||
@caption.updatePlayTime 15.000
|
||||
|
||||
it 'does not change current subtitle', ->
|
||||
expect($('.subtitles li[data-index=1]')).toHaveClass 'current'
|
||||
|
||||
describe 'onWindowResize', ->
|
||||
describe 'resize', ->
|
||||
beforeEach ->
|
||||
@caption = new VideoCaption @player, 'def456'
|
||||
@caption = new VideoCaption el: $('.video'), youtubeId: 'def456', currentSpeed: '1.0'
|
||||
$('.subtitles li[data-index=1]').addClass 'current'
|
||||
@caption.onWindowResize()
|
||||
@caption.resize()
|
||||
|
||||
it 'set the height of caption container', ->
|
||||
expect(parseInt($('.subtitles').css('maxHeight'))).toEqual $('.video-wrapper').height()
|
||||
@@ -260,7 +258,7 @@ describe 'VideoCaption', ->
|
||||
|
||||
describe 'scrollCaption', ->
|
||||
beforeEach ->
|
||||
@caption = new VideoCaption @player, 'def456'
|
||||
@caption = new VideoCaption el: $('.video'), youtubeId: 'def456', currentSpeed: '1.0'
|
||||
|
||||
describe 'when frozen', ->
|
||||
beforeEach ->
|
||||
@@ -288,18 +286,18 @@ describe 'VideoCaption', ->
|
||||
@caption.scrollCaption()
|
||||
|
||||
it 'scroll to current caption', ->
|
||||
expect($.fn.scrollTo).toHaveBeenCalledWith $('.subtitles .current:first', @player.element),
|
||||
expect($.fn.scrollTo).toHaveBeenCalledWith $('.subtitles .current:first', @caption.el),
|
||||
offset: - ($('.video-wrapper').height() / 2 - $('.subtitles .current:first').height() / 2)
|
||||
|
||||
describe 'seekPlayer', ->
|
||||
beforeEach ->
|
||||
@caption = new VideoCaption @player, 'def456'
|
||||
@caption = new VideoCaption el: $('.video'), youtubeId: 'def456', currentSpeed: '1.0'
|
||||
@time = null
|
||||
$(@player).bind 'seek', (event, time) => @time = time
|
||||
$(@caption).bind 'seek', (event, time) => @time = time
|
||||
|
||||
describe 'when the video speed is 1.0x', ->
|
||||
beforeEach ->
|
||||
@video.setSpeed '1.0'
|
||||
@caption.currentSpeed = '1.0'
|
||||
$('.subtitles li[data-start="30000"]').click()
|
||||
|
||||
it 'trigger seek event with the correct time', ->
|
||||
@@ -307,7 +305,7 @@ describe 'VideoCaption', ->
|
||||
|
||||
describe 'when the video speed is not 1.0x', ->
|
||||
beforeEach ->
|
||||
@video.setSpeed '0.75'
|
||||
@caption.currentSpeed = '0.75'
|
||||
$('.subtitles li[data-start="30000"]').click()
|
||||
|
||||
it 'trigger seek event with the correct time', ->
|
||||
@@ -315,25 +313,25 @@ describe 'VideoCaption', ->
|
||||
|
||||
describe 'toggle', ->
|
||||
beforeEach ->
|
||||
@caption = new VideoCaption @player, 'def456'
|
||||
@caption = new VideoCaption el: $('.video'), youtubeId: 'def456', currentSpeed: '1.0'
|
||||
$('.subtitles li[data-index=1]').addClass 'current'
|
||||
|
||||
describe 'when the caption is visible', ->
|
||||
beforeEach ->
|
||||
@player.element.removeClass 'closed'
|
||||
@caption.el.removeClass 'closed'
|
||||
@caption.toggle jQuery.Event('click')
|
||||
|
||||
it 'hide the caption', ->
|
||||
expect(@player.element).toHaveClass 'closed'
|
||||
expect(@caption.el).toHaveClass 'closed'
|
||||
|
||||
|
||||
describe 'when the caption is hidden', ->
|
||||
beforeEach ->
|
||||
@player.element.addClass 'closed'
|
||||
@caption.el.addClass 'closed'
|
||||
@caption.toggle jQuery.Event('click')
|
||||
|
||||
it 'show the caption', ->
|
||||
expect(@player.element).not.toHaveClass 'closed'
|
||||
expect(@caption.el).not.toHaveClass 'closed'
|
||||
|
||||
it 'scroll the caption', ->
|
||||
expect($.fn.scrollTo).toHaveBeenCalled()
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
describe 'VideoControl', ->
|
||||
beforeEach ->
|
||||
@player = jasmine.stubVideoPlayer @
|
||||
jasmine.stubVideoPlayer @
|
||||
$('.video-controls').html ''
|
||||
|
||||
describe 'constructor', ->
|
||||
it 'render the video controls', ->
|
||||
new VideoControl @player
|
||||
new VideoControl(el: $('.video-controls'))
|
||||
expect($('.video-controls').html()).toContain '''
|
||||
<div class="slider"></div>
|
||||
<div>
|
||||
@@ -21,14 +21,8 @@ describe 'VideoControl', ->
|
||||
</div>
|
||||
'''
|
||||
|
||||
it 'bind player events', ->
|
||||
control = new VideoControl @player
|
||||
expect($(@player)).toHandleWith 'play', control.onPlay
|
||||
expect($(@player)).toHandleWith 'pause', control.onPause
|
||||
expect($(@player)).toHandleWith 'ended', control.onPause
|
||||
|
||||
it 'bind the playback button', ->
|
||||
control = new VideoControl @player
|
||||
control = new VideoControl(el: $('.video-controls'))
|
||||
expect($('.video_control')).toHandleWith 'click', control.togglePlayback
|
||||
|
||||
describe 'when on a touch based device', ->
|
||||
@@ -36,7 +30,7 @@ describe 'VideoControl', ->
|
||||
spyOn(window, 'onTouchBasedDevice').andReturn true
|
||||
|
||||
it 'does not add the play class to video control', ->
|
||||
new VideoControl @player
|
||||
new VideoControl(el: $('.video-controls'))
|
||||
expect($('.video_control')).not.toHaveClass 'play'
|
||||
expect($('.video_control')).not.toHaveHtml 'Play'
|
||||
|
||||
@@ -46,24 +40,24 @@ describe 'VideoControl', ->
|
||||
spyOn(window, 'onTouchBasedDevice').andReturn false
|
||||
|
||||
it 'add the play class to video control', ->
|
||||
new VideoControl @player
|
||||
new VideoControl(el: $('.video-controls'))
|
||||
expect($('.video_control')).toHaveClass 'play'
|
||||
expect($('.video_control')).toHaveHtml 'Play'
|
||||
|
||||
describe 'onPlay', ->
|
||||
describe 'play', ->
|
||||
beforeEach ->
|
||||
@control = new VideoControl @player
|
||||
@control.onPlay()
|
||||
@control = new VideoControl(el: $('.video-controls'))
|
||||
@control.play()
|
||||
|
||||
it 'switch playback button to play state', ->
|
||||
expect($('.video_control')).not.toHaveClass 'play'
|
||||
expect($('.video_control')).toHaveClass 'pause'
|
||||
expect($('.video_control')).toHaveHtml 'Pause'
|
||||
|
||||
describe 'onPause', ->
|
||||
describe 'pause', ->
|
||||
beforeEach ->
|
||||
@control = new VideoControl @player
|
||||
@control.onPause()
|
||||
@control = new VideoControl(el: $('.video-controls'))
|
||||
@control.pause()
|
||||
|
||||
it 'switch playback button to pause state', ->
|
||||
expect($('.video_control')).not.toHaveClass 'pause'
|
||||
@@ -72,7 +66,7 @@ describe 'VideoControl', ->
|
||||
|
||||
describe 'togglePlayback', ->
|
||||
beforeEach ->
|
||||
@control = new VideoControl @player
|
||||
@control = new VideoControl(el: $('.video-controls'))
|
||||
|
||||
describe 'when the control does not have play or pause class', ->
|
||||
beforeEach ->
|
||||
@@ -80,41 +74,36 @@ describe 'VideoControl', ->
|
||||
|
||||
describe 'when the video is playing', ->
|
||||
beforeEach ->
|
||||
spyOn(@player, 'isPlaying').andReturn true
|
||||
spyOnEvent @player, 'pause'
|
||||
$('.video_control').addClass('play')
|
||||
spyOnEvent @control, 'pause'
|
||||
@control.togglePlayback jQuery.Event('click')
|
||||
|
||||
it 'does not trigger the pause event', ->
|
||||
expect('pause').not.toHaveBeenTriggeredOn @player
|
||||
expect('pause').not.toHaveBeenTriggeredOn @control
|
||||
|
||||
describe 'when the video is paused', ->
|
||||
beforeEach ->
|
||||
spyOn(@player, 'isPlaying').andReturn false
|
||||
spyOnEvent @player, 'play'
|
||||
$('.video_control').addClass('pause')
|
||||
spyOnEvent @control, 'play'
|
||||
@control.togglePlayback jQuery.Event('click')
|
||||
|
||||
it 'does not trigger the play event', ->
|
||||
expect('play').not.toHaveBeenTriggeredOn @player
|
||||
expect('play').not.toHaveBeenTriggeredOn @control
|
||||
|
||||
for className in ['play', 'pause']
|
||||
describe "when the control has #{className} class", ->
|
||||
describe 'when the video is playing', ->
|
||||
beforeEach ->
|
||||
$('.video_control').addClass className
|
||||
spyOnEvent @control, 'pause'
|
||||
$('.video_control').addClass 'pause'
|
||||
@control.togglePlayback jQuery.Event('click')
|
||||
|
||||
describe 'when the video is playing', ->
|
||||
beforeEach ->
|
||||
spyOn(@player, 'isPlaying').andReturn true
|
||||
spyOnEvent @player, 'pause'
|
||||
@control.togglePlayback jQuery.Event('click')
|
||||
it 'trigger the pause event', ->
|
||||
expect('pause').toHaveBeenTriggeredOn @control
|
||||
|
||||
it 'trigger the pause event', ->
|
||||
expect('pause').toHaveBeenTriggeredOn @player
|
||||
describe 'when the video is paused', ->
|
||||
beforeEach ->
|
||||
spyOnEvent @control, 'play'
|
||||
$('.video_control').addClass 'play'
|
||||
@control.togglePlayback jQuery.Event('click')
|
||||
|
||||
describe 'when the video is paused', ->
|
||||
beforeEach ->
|
||||
spyOn(@player, 'isPlaying').andReturn false
|
||||
spyOnEvent @player, 'play'
|
||||
@control.togglePlayback jQuery.Event('click')
|
||||
|
||||
it 'trigger the play event', ->
|
||||
expect('play').toHaveBeenTriggeredOn @player
|
||||
it 'trigger the play event', ->
|
||||
expect('play').toHaveBeenTriggeredOn @control
|
||||
|
||||
@@ -15,25 +15,25 @@ describe 'VideoPlayer', ->
|
||||
|
||||
describe 'always', ->
|
||||
beforeEach ->
|
||||
@player = new VideoPlayer @video
|
||||
@player = new VideoPlayer video: @video
|
||||
|
||||
it 'instanticate current time to zero', ->
|
||||
expect(@player.currentTime).toEqual 0
|
||||
|
||||
it 'set the element', ->
|
||||
expect(@player.element).toBe '#video_example'
|
||||
expect(@player.el).toBe '#video_example'
|
||||
|
||||
it 'create video control', ->
|
||||
expect(window.VideoControl).toHaveBeenCalledWith @player
|
||||
expect(window.VideoControl).toHaveBeenCalledWith el: $('.video-controls', @player.el)
|
||||
|
||||
it 'create video caption', ->
|
||||
expect(window.VideoCaption).toHaveBeenCalledWith @player, 'def456'
|
||||
expect(window.VideoCaption).toHaveBeenCalledWith el: @player.el, youtubeId: 'normalSpeedYoutubeId', currentSpeed: '1.0'
|
||||
|
||||
it 'create video speed control', ->
|
||||
expect(window.VideoSpeedControl).toHaveBeenCalledWith @player, ['0.75', '1.0']
|
||||
expect(window.VideoSpeedControl).toHaveBeenCalledWith el: $('.secondary-controls', @player.el), speeds: ['0.75', '1.0'], currentSpeed: '1.0'
|
||||
|
||||
it 'create video progress slider', ->
|
||||
expect(window.VideoProgressSlider).toHaveBeenCalledWith @player
|
||||
expect(window.VideoProgressSlider).toHaveBeenCalledWith el: $('.slider', @player.el)
|
||||
|
||||
it 'create Youtube player', ->
|
||||
expect(YT.Player).toHaveBeenCalledWith 'example'
|
||||
@@ -43,54 +43,53 @@ describe 'VideoPlayer', ->
|
||||
rel: 0
|
||||
showinfo: 0
|
||||
enablejsapi: 1
|
||||
videoId: 'def456'
|
||||
videoId: 'normalSpeedYoutubeId'
|
||||
events:
|
||||
onReady: @player.onReady
|
||||
onStateChange: @player.onStateChange
|
||||
|
||||
it 'bind to seek event', ->
|
||||
expect($(@player)).toHandleWith 'seek', @player.onSeek
|
||||
it 'bind to video control play event', ->
|
||||
expect($(@player.control)).toHandleWith 'play', @player.play
|
||||
|
||||
it 'bind to updatePlayTime event', ->
|
||||
expect($(@player)).toHandleWith 'updatePlayTime', @player.onUpdatePlayTime
|
||||
it 'bind to video control pause event', ->
|
||||
expect($(@player.control)).toHandleWith 'pause', @player.pause
|
||||
|
||||
it 'bidn to speedChange event', ->
|
||||
expect($(@player)).toHandleWith 'speedChange', @player.onSpeedChange
|
||||
it 'bind to video caption seek event', ->
|
||||
expect($(@player.caption)).toHandleWith 'seek', @player.onSeek
|
||||
|
||||
it 'bind to play event', ->
|
||||
expect($(@player)).toHandleWith 'play', @player.onPlay
|
||||
it 'bind to video speed control speedChange event', ->
|
||||
expect($(@player.speedControl)).toHandleWith 'speedChange', @player.onSpeedChange
|
||||
|
||||
it 'bind to paused event', ->
|
||||
expect($(@player)).toHandleWith 'pause', @player.onPause
|
||||
it 'bind to video progress slider seek event', ->
|
||||
expect($(@player.progressSlider)).toHandleWith 'seek', @player.onSeek
|
||||
|
||||
it 'bind to ended event', ->
|
||||
expect($(@player)).toHandleWith 'ended', @player.onPause
|
||||
it 'bind to video volume control volumeChange event', ->
|
||||
expect($(@player.volumeControl)).toHandleWith 'volumeChange', @player.onVolumeChange
|
||||
|
||||
it 'bind to key press', ->
|
||||
expect($(document)).toHandleWith 'keyup', @player.bindExitFullScreen
|
||||
|
||||
it 'bind to fullscreen switching button', ->
|
||||
console.debug $('.add-fullscreen')
|
||||
expect($('.add-fullscreen')).toHandleWith 'click', @player.toggleFullScreen
|
||||
|
||||
describe 'when not on a touch based device', ->
|
||||
beforeEach ->
|
||||
spyOn(window, 'onTouchBasedDevice').andReturn false
|
||||
$('.add-fullscreen, .hide-subtitles').removeData 'qtip'
|
||||
@player = new VideoPlayer @video
|
||||
@player = new VideoPlayer video: @video
|
||||
|
||||
it 'add the tooltip to fullscreen and subtitle button', ->
|
||||
expect($('.add-fullscreen')).toHaveData 'qtip'
|
||||
expect($('.hide-subtitles')).toHaveData 'qtip'
|
||||
|
||||
it 'create video volume control', ->
|
||||
expect(window.VideoVolumeControl).toHaveBeenCalledWith @player
|
||||
expect(window.VideoVolumeControl).toHaveBeenCalledWith el: $('.secondary-controls', @player.el)
|
||||
|
||||
describe 'when on a touch based device', ->
|
||||
beforeEach ->
|
||||
spyOn(window, 'onTouchBasedDevice').andReturn true
|
||||
$('.add-fullscreen, .hide-subtitles').removeData 'qtip'
|
||||
@player = new VideoPlayer @video
|
||||
@player = new VideoPlayer video: @video
|
||||
|
||||
it 'does not add the tooltip to fullscreen and subtitle button', ->
|
||||
expect($('.add-fullscreen')).not.toHaveData 'qtip'
|
||||
@@ -107,12 +106,6 @@ describe 'VideoPlayer', ->
|
||||
spyOnEvent @player, 'updatePlayTime'
|
||||
@player.onReady()
|
||||
|
||||
it 'reset the progress to zero', ->
|
||||
expect('updatePlayTime').toHaveBeenTriggeredOn @player
|
||||
|
||||
it 'trigger ready event on the player', ->
|
||||
expect('ready').toHaveBeenTriggeredOn @player
|
||||
|
||||
describe 'when not on a touch based device', ->
|
||||
beforeEach ->
|
||||
spyOn(window, 'onTouchBasedDevice').andReturn false
|
||||
@@ -133,93 +126,108 @@ describe 'VideoPlayer', ->
|
||||
|
||||
describe 'onStateChange', ->
|
||||
beforeEach ->
|
||||
@player = new VideoPlayer @video
|
||||
|
||||
describe 'when the video is playing', ->
|
||||
beforeEach ->
|
||||
spyOnEvent @player, 'play'
|
||||
@player.onStateChange data: YT.PlayerState.PLAYING
|
||||
|
||||
it 'trigger play event', ->
|
||||
expect('play').toHaveBeenTriggeredOn @player
|
||||
|
||||
describe 'when the video is paused', ->
|
||||
beforeEach ->
|
||||
spyOnEvent @player, 'pause'
|
||||
@player.onStateChange data: YT.PlayerState.PAUSED
|
||||
|
||||
it 'trigger pause event', ->
|
||||
expect('pause').toHaveBeenTriggeredOn @player
|
||||
@player = new VideoPlayer video: @video
|
||||
|
||||
describe 'when the video is unstarted', ->
|
||||
beforeEach ->
|
||||
spyOnEvent @player, 'pause'
|
||||
spyOn @player.control, 'pause'
|
||||
@player.caption.pause = jasmine.createSpy('VideoCaption.pause')
|
||||
@player.onStateChange data: YT.PlayerState.UNSTARTED
|
||||
|
||||
it 'trigger pause event', ->
|
||||
expect('pause').toHaveBeenTriggeredOn @player
|
||||
it 'pause the video control', ->
|
||||
expect(@player.control.pause).toHaveBeenCalled()
|
||||
|
||||
it 'pause the video caption', ->
|
||||
expect(@player.caption.pause).toHaveBeenCalled()
|
||||
|
||||
describe 'when the video is playing', ->
|
||||
beforeEach ->
|
||||
@anotherPlayer = jasmine.createSpyObj 'AnotherPlayer', ['pauseVideo']
|
||||
window.player = @anotherPlayer
|
||||
spyOn @video, 'log'
|
||||
spyOn(window, 'setInterval').andReturn 100
|
||||
spyOn @player.control, 'play'
|
||||
@player.caption.play = jasmine.createSpy('VideoCaption.play')
|
||||
@player.progressSlider.play = jasmine.createSpy('VideoProgressSlider.play')
|
||||
@player.player.getVideoEmbedCode.andReturn 'embedCode'
|
||||
@player.onStateChange data: YT.PlayerState.PLAYING
|
||||
|
||||
it 'log the play_video event', ->
|
||||
expect(@video.log).toHaveBeenCalledWith 'play_video'
|
||||
|
||||
it 'pause other video player', ->
|
||||
expect(@anotherPlayer.pauseVideo).toHaveBeenCalled()
|
||||
|
||||
it 'set current video player as active player', ->
|
||||
expect(window.player).toEqual @player.player
|
||||
|
||||
it 'set update interval', ->
|
||||
expect(window.setInterval).toHaveBeenCalledWith @player.update, 200
|
||||
expect(@player.player.interval).toEqual 100
|
||||
|
||||
it 'play the video control', ->
|
||||
expect(@player.control.play).toHaveBeenCalled()
|
||||
|
||||
it 'play the video caption', ->
|
||||
expect(@player.caption.play).toHaveBeenCalled()
|
||||
|
||||
it 'play the video progress slider', ->
|
||||
expect(@player.progressSlider.play).toHaveBeenCalled()
|
||||
|
||||
describe 'when the video is paused', ->
|
||||
beforeEach ->
|
||||
@player = new VideoPlayer video: @video
|
||||
window.player = @player.player
|
||||
spyOn @video, 'log'
|
||||
spyOn window, 'clearInterval'
|
||||
spyOn @player.control, 'pause'
|
||||
@player.caption.pause = jasmine.createSpy('VideoCaption.pause')
|
||||
@player.player.interval = 100
|
||||
@player.player.getVideoEmbedCode.andReturn 'embedCode'
|
||||
@player.onStateChange data: YT.PlayerState.PAUSED
|
||||
|
||||
it 'log the pause_video event', ->
|
||||
expect(@video.log).toHaveBeenCalledWith 'pause_video'
|
||||
|
||||
it 'set current video player as inactive', ->
|
||||
expect(window.player).toBeNull()
|
||||
|
||||
it 'clear update interval', ->
|
||||
expect(window.clearInterval).toHaveBeenCalledWith 100
|
||||
expect(@player.player.interval).toBeNull()
|
||||
|
||||
it 'pause the video control', ->
|
||||
expect(@player.control.pause).toHaveBeenCalled()
|
||||
|
||||
it 'pause the video caption', ->
|
||||
expect(@player.caption.pause).toHaveBeenCalled()
|
||||
|
||||
describe 'when the video is ended', ->
|
||||
beforeEach ->
|
||||
spyOnEvent @player, 'ended'
|
||||
spyOn @player.control, 'pause'
|
||||
@player.caption.pause = jasmine.createSpy('VideoCaption.pause')
|
||||
@player.onStateChange data: YT.PlayerState.ENDED
|
||||
|
||||
it 'trigger ended event', ->
|
||||
expect('ended').toHaveBeenTriggeredOn @player
|
||||
it 'pause the video control', ->
|
||||
expect(@player.control.pause).toHaveBeenCalled()
|
||||
|
||||
describe 'onPlay', ->
|
||||
beforeEach ->
|
||||
@player = new VideoPlayer @video
|
||||
@anotherPlayer = jasmine.createSpyObj 'AnotherPlayer', ['pauseVideo']
|
||||
window.player = @anotherPlayer
|
||||
spyOn Logger, 'log'
|
||||
spyOn(window, 'setInterval').andReturn 100
|
||||
@player.player.getVideoEmbedCode.andReturn 'embedCode'
|
||||
@player.onPlay()
|
||||
|
||||
it 'log the play_video event', ->
|
||||
expect(Logger.log).toHaveBeenCalledWith 'play_video', id: @player.currentTime, code: 'embedCode'
|
||||
|
||||
it 'pause other video player', ->
|
||||
expect(@anotherPlayer.pauseVideo).toHaveBeenCalled()
|
||||
|
||||
it 'set current video player as active player', ->
|
||||
expect(window.player).toEqual @player.player
|
||||
|
||||
it 'set update interval', ->
|
||||
expect(window.setInterval).toHaveBeenCalledWith @player.update, 200
|
||||
expect(@player.player.interval).toEqual 100
|
||||
|
||||
describe 'onPause', ->
|
||||
beforeEach ->
|
||||
@player = new VideoPlayer @video
|
||||
window.player = @player.player
|
||||
spyOn Logger, 'log'
|
||||
spyOn window, 'clearInterval'
|
||||
@player.player.interval = 100
|
||||
@player.player.getVideoEmbedCode.andReturn 'embedCode'
|
||||
@player.onPause()
|
||||
|
||||
it 'log the pause_video event', ->
|
||||
expect(Logger.log).toHaveBeenCalledWith 'pause_video', id: @player.currentTime, code: 'embedCode'
|
||||
|
||||
it 'set current video player as inactive', ->
|
||||
expect(window.player).toBeNull()
|
||||
|
||||
it 'clear update interval', ->
|
||||
expect(window.clearInterval).toHaveBeenCalledWith 100
|
||||
expect(@player.player.interval).toBeNull()
|
||||
it 'pause the video caption', ->
|
||||
expect(@player.caption.pause).toHaveBeenCalled()
|
||||
|
||||
describe 'onSeek', ->
|
||||
beforeEach ->
|
||||
@player = new VideoPlayer @video
|
||||
@player = new VideoPlayer video: @video
|
||||
spyOn window, 'clearInterval'
|
||||
@player.player.interval = 100
|
||||
spyOn @player, 'updatePlayTime'
|
||||
@player.onSeek {}, 60
|
||||
|
||||
it 'seek the player', ->
|
||||
expect(@player.player.seekTo).toHaveBeenCalledWith 60, true
|
||||
|
||||
it 'call updatePlayTime on player', ->
|
||||
expect(@player.updatePlayTime).toHaveBeenCalledWith 60
|
||||
|
||||
describe 'when the player is playing', ->
|
||||
beforeEach ->
|
||||
@player.player.getPlayerState.andReturn YT.PlayerState.PLAYING
|
||||
@@ -231,19 +239,16 @@ describe 'VideoPlayer', ->
|
||||
describe 'when the player is not playing', ->
|
||||
beforeEach ->
|
||||
@player.player.getPlayerState.andReturn YT.PlayerState.PAUSED
|
||||
spyOnEvent @player, 'updatePlayTime'
|
||||
@player.onSeek {}, 60
|
||||
|
||||
it 'set the current time', ->
|
||||
expect(@player.currentTime).toEqual 60
|
||||
|
||||
it 'trigger updatePlayTime event', ->
|
||||
expect('updatePlayTime').toHaveBeenTriggeredOn @player
|
||||
|
||||
describe 'onSpeedChange', ->
|
||||
beforeEach ->
|
||||
@player = new VideoPlayer @video
|
||||
@player = new VideoPlayer video: @video
|
||||
@player.currentTime = 60
|
||||
spyOn @player, 'updatePlayTime'
|
||||
spyOn(@video, 'setSpeed').andCallThrough()
|
||||
|
||||
describe 'always', ->
|
||||
@@ -256,34 +261,43 @@ describe 'VideoPlayer', ->
|
||||
it 'set video speed to the new speed', ->
|
||||
expect(@video.setSpeed).toHaveBeenCalledWith '0.75'
|
||||
|
||||
it 'tell video caption that the speed has changed', ->
|
||||
expect(@player.caption.currentSpeed).toEqual '0.75'
|
||||
|
||||
describe 'when the video is playing', ->
|
||||
beforeEach ->
|
||||
@player.player.getPlayerState.andReturn YT.PlayerState.PLAYING
|
||||
spyOnEvent @player, 'updatePlayTime'
|
||||
@player.onSpeedChange {}, '0.75'
|
||||
|
||||
it 'load the video', ->
|
||||
expect(@player.player.loadVideoById).toHaveBeenCalledWith 'abc123', '80.000'
|
||||
expect(@player.player.loadVideoById).toHaveBeenCalledWith 'slowerSpeedYoutubeId', '80.000'
|
||||
|
||||
it 'trigger updatePlayTime event', ->
|
||||
expect('updatePlayTime').toHaveBeenTriggeredOn @player
|
||||
expect(@player.updatePlayTime).toHaveBeenCalledWith '80.000'
|
||||
|
||||
describe 'when the video is not playing', ->
|
||||
beforeEach ->
|
||||
@player.player.getPlayerState.andReturn YT.PlayerState.PAUSED
|
||||
spyOnEvent @player, 'updatePlayTime'
|
||||
@player.onSpeedChange {}, '0.75'
|
||||
|
||||
it 'cue the video', ->
|
||||
expect(@player.player.cueVideoById).toHaveBeenCalledWith 'abc123', '80.000'
|
||||
expect(@player.player.cueVideoById).toHaveBeenCalledWith 'slowerSpeedYoutubeId', '80.000'
|
||||
|
||||
it 'trigger updatePlayTime event', ->
|
||||
expect('updatePlayTime').toHaveBeenTriggeredOn @player
|
||||
expect(@player.updatePlayTime).toHaveBeenCalledWith '80.000'
|
||||
|
||||
describe 'onVolumeChange', ->
|
||||
beforeEach ->
|
||||
@player = new VideoPlayer video: @video
|
||||
@player.onVolumeChange undefined, 60
|
||||
|
||||
it 'set the volume on player', ->
|
||||
expect(@player.player.setVolume).toHaveBeenCalledWith 60
|
||||
|
||||
describe 'update', ->
|
||||
beforeEach ->
|
||||
@player = new VideoPlayer @video
|
||||
spyOnEvent @player, 'updatePlayTime'
|
||||
@player = new VideoPlayer video: @video
|
||||
spyOn @player, 'updatePlayTime'
|
||||
|
||||
describe 'when the current time is unavailable from the player', ->
|
||||
beforeEach ->
|
||||
@@ -291,7 +305,7 @@ describe 'VideoPlayer', ->
|
||||
@player.update()
|
||||
|
||||
it 'does not trigger updatePlayTime event', ->
|
||||
expect('updatePlayTime').not.toHaveBeenTriggeredOn @player
|
||||
expect(@player.updatePlayTime).not.toHaveBeenCalled()
|
||||
|
||||
describe 'when the current time is available from the player', ->
|
||||
beforeEach ->
|
||||
@@ -299,60 +313,67 @@ describe 'VideoPlayer', ->
|
||||
@player.update()
|
||||
|
||||
it 'trigger updatePlayTime event', ->
|
||||
expect('updatePlayTime').toHaveBeenTriggeredOn @player
|
||||
expect(@player.updatePlayTime).toHaveBeenCalledWith(60)
|
||||
|
||||
describe 'onUpdatePlaytime', ->
|
||||
describe 'updatePlayTime', ->
|
||||
beforeEach ->
|
||||
@player = new VideoPlayer @video
|
||||
@player = new VideoPlayer video: @video
|
||||
spyOn(@video, 'getDuration').andReturn 1800
|
||||
@player.onUpdatePlayTime {}, 60
|
||||
@player.caption.updatePlayTime = jasmine.createSpy('VideoCaption.updatePlayTime')
|
||||
@player.progressSlider.updatePlayTime = jasmine.createSpy('VideoProgressSlider.updatePlayTime')
|
||||
@player.updatePlayTime 60
|
||||
|
||||
it 'update the video playback time', ->
|
||||
expect($('.vidtime')).toHaveHtml '1:00 / 30:00'
|
||||
|
||||
it 'update the playback time on caption', ->
|
||||
expect(@player.caption.updatePlayTime).toHaveBeenCalledWith 60
|
||||
|
||||
it 'update the playback time on progress slider', ->
|
||||
expect(@player.progressSlider.updatePlayTime).toHaveBeenCalledWith 60, 1800
|
||||
|
||||
describe 'toggleFullScreen', ->
|
||||
beforeEach ->
|
||||
@player = new VideoPlayer @video
|
||||
@player = new VideoPlayer video: @video
|
||||
@player.caption.resize = jasmine.createSpy('VideoCaption.resize')
|
||||
|
||||
describe 'when the video player is not full screen', ->
|
||||
beforeEach ->
|
||||
@player.element.removeClass 'fullscreen'
|
||||
spyOnEvent @player, 'resize'
|
||||
@player.el.removeClass 'fullscreen'
|
||||
@player.toggleFullScreen(jQuery.Event("click"))
|
||||
|
||||
it 'replace the full screen button tooltip', ->
|
||||
expect($('.add-fullscreen')).toHaveAttr 'title', 'Exit fill browser'
|
||||
|
||||
it 'add a new exit from fullscreen button', ->
|
||||
expect(@player.element).toContain 'a.exit'
|
||||
expect(@player.el).toContain 'a.exit'
|
||||
|
||||
it 'add the fullscreen class', ->
|
||||
expect(@player.element).toHaveClass 'fullscreen'
|
||||
expect(@player.el).toHaveClass 'fullscreen'
|
||||
|
||||
it 'trigger resize event', ->
|
||||
expect('resize').toHaveBeenTriggeredOn @player
|
||||
it 'tell VideoCaption to resize', ->
|
||||
expect(@player.caption.resize).toHaveBeenCalled()
|
||||
|
||||
describe 'when the video player already full screen', ->
|
||||
beforeEach ->
|
||||
@player.element.addClass 'fullscreen'
|
||||
spyOnEvent @player, 'resize'
|
||||
@player.el.addClass 'fullscreen'
|
||||
@player.toggleFullScreen(jQuery.Event("click"))
|
||||
|
||||
it 'replace the full screen button tooltip', ->
|
||||
expect($('.add-fullscreen')).toHaveAttr 'title', 'Fill browser'
|
||||
|
||||
it 'remove exit full screen button', ->
|
||||
expect(@player.element).not.toContain 'a.exit'
|
||||
expect(@player.el).not.toContain 'a.exit'
|
||||
|
||||
it 'remove the fullscreen class', ->
|
||||
expect(@player.element).not.toHaveClass 'fullscreen'
|
||||
expect(@player.el).not.toHaveClass 'fullscreen'
|
||||
|
||||
it 'trigger resize event', ->
|
||||
expect('resize').toHaveBeenTriggeredOn @player
|
||||
it 'tell VideoCaption to resize', ->
|
||||
expect(@player.caption.resize).toHaveBeenCalled()
|
||||
|
||||
describe 'play', ->
|
||||
beforeEach ->
|
||||
@player = new VideoPlayer @video
|
||||
@player = new VideoPlayer video: @video
|
||||
|
||||
describe 'when the player is not ready', ->
|
||||
beforeEach ->
|
||||
@@ -372,7 +393,7 @@ describe 'VideoPlayer', ->
|
||||
|
||||
describe 'isPlaying', ->
|
||||
beforeEach ->
|
||||
@player = new VideoPlayer @video
|
||||
@player = new VideoPlayer video: @video
|
||||
|
||||
describe 'when the video is playing', ->
|
||||
beforeEach ->
|
||||
@@ -390,7 +411,7 @@ describe 'VideoPlayer', ->
|
||||
|
||||
describe 'pause', ->
|
||||
beforeEach ->
|
||||
@player = new VideoPlayer @video
|
||||
@player = new VideoPlayer video: @video
|
||||
@player.pause()
|
||||
|
||||
it 'delegate to the Youtube player', ->
|
||||
@@ -398,7 +419,7 @@ describe 'VideoPlayer', ->
|
||||
|
||||
describe 'duration', ->
|
||||
beforeEach ->
|
||||
@player = new VideoPlayer @video
|
||||
@player = new VideoPlayer video: @video
|
||||
spyOn @video, 'getDuration'
|
||||
@player.duration()
|
||||
|
||||
@@ -407,22 +428,8 @@ describe 'VideoPlayer', ->
|
||||
|
||||
describe 'currentSpeed', ->
|
||||
beforeEach ->
|
||||
@player = new VideoPlayer @video
|
||||
@player = new VideoPlayer video: @video
|
||||
@video.speed = '3.0'
|
||||
|
||||
it 'delegate to the video', ->
|
||||
expect(@player.currentSpeed()).toEqual '3.0'
|
||||
|
||||
describe 'volume', ->
|
||||
beforeEach ->
|
||||
@player = new VideoPlayer @video
|
||||
@player.player.getVolume.andReturn 42
|
||||
|
||||
describe 'without value', ->
|
||||
it 'return current volume', ->
|
||||
expect(@player.volume()).toEqual 42
|
||||
|
||||
describe 'with value', ->
|
||||
it 'set player volume', ->
|
||||
@player.volume(60)
|
||||
expect(@player.player.setVolume).toHaveBeenCalledWith(60)
|
||||
|
||||
@@ -1,79 +1,13 @@
|
||||
describe 'VideoProgressSlider', ->
|
||||
beforeEach ->
|
||||
@player = jasmine.stubVideoPlayer @
|
||||
jasmine.stubVideoPlayer @
|
||||
|
||||
describe 'constructor', ->
|
||||
describe 'on a non-touch based device', ->
|
||||
beforeEach ->
|
||||
spyOn($.fn, 'slider').andCallThrough()
|
||||
spyOn(window, 'onTouchBasedDevice').andReturn false
|
||||
@slider = new VideoProgressSlider @player
|
||||
|
||||
it 'build the slider', ->
|
||||
expect(@slider.slider).toBe '.slider'
|
||||
expect($.fn.slider).toHaveBeenCalledWith
|
||||
range: 'min'
|
||||
change: @slider.onChange
|
||||
slide: @slider.onSlide
|
||||
stop: @slider.onStop
|
||||
|
||||
it 'build the seek handle', ->
|
||||
expect(@slider.handle).toBe '.slider .ui-slider-handle'
|
||||
expect($.fn.qtip).toHaveBeenCalledWith
|
||||
content: "0:00"
|
||||
position:
|
||||
my: 'bottom center'
|
||||
at: 'top center'
|
||||
container: @slider.handle
|
||||
hide:
|
||||
delay: 700
|
||||
style:
|
||||
classes: 'ui-tooltip-slider'
|
||||
widget: true
|
||||
|
||||
it 'bind player events', ->
|
||||
expect($(@player)).toHandleWith 'updatePlayTime', @slider.onUpdatePlayTime
|
||||
expect($(@player)).toHandleWith 'ready', @slider.onReady
|
||||
expect($(@player)).toHandleWith 'play', @slider.onPlay
|
||||
|
||||
describe 'on a touch-based device', ->
|
||||
beforeEach ->
|
||||
spyOn($.fn, 'slider').andCallThrough()
|
||||
spyOn(window, 'onTouchBasedDevice').andReturn true
|
||||
@slider = new VideoProgressSlider @player
|
||||
|
||||
it 'does not build the slider', ->
|
||||
expect(@slider.slider).toBeUndefined
|
||||
expect($.fn.slider).not.toHaveBeenCalled()
|
||||
|
||||
it 'bind player events', ->
|
||||
expect($(@player)).toHandleWith 'updatePlayTime', @slider.onUpdatePlayTime
|
||||
|
||||
describe 'onReady', ->
|
||||
beforeEach ->
|
||||
spyOn(@player, 'duration').andReturn 120
|
||||
@slider = new VideoProgressSlider @player
|
||||
@slider.onReady()
|
||||
|
||||
it 'set the max value to the length of video', ->
|
||||
expect(@slider.slider.slider('option', 'max')).toEqual 120
|
||||
|
||||
describe 'onPlay', ->
|
||||
beforeEach ->
|
||||
@slider = new VideoProgressSlider @player
|
||||
spyOn($.fn, 'slider').andCallThrough()
|
||||
|
||||
describe 'when the slider was already built', ->
|
||||
beforeEach ->
|
||||
@slider.onPlay()
|
||||
|
||||
it 'does not build the slider', ->
|
||||
expect($.fn.slider).not.toHaveBeenCalled
|
||||
|
||||
describe 'when the slider was not already built', ->
|
||||
beforeEach ->
|
||||
@slider.slider = null
|
||||
@slider.onPlay()
|
||||
@slider = new VideoProgressSlider el: $('.slider')
|
||||
|
||||
it 'build the slider', ->
|
||||
expect(@slider.slider).toBe '.slider'
|
||||
@@ -97,16 +31,64 @@ describe 'VideoProgressSlider', ->
|
||||
classes: 'ui-tooltip-slider'
|
||||
widget: true
|
||||
|
||||
describe 'onUpdatePlayTime', ->
|
||||
describe 'on a touch-based device', ->
|
||||
beforeEach ->
|
||||
spyOn($.fn, 'slider').andCallThrough()
|
||||
spyOn(window, 'onTouchBasedDevice').andReturn true
|
||||
@slider = new VideoProgressSlider el: $('.slider')
|
||||
|
||||
it 'does not build the slider', ->
|
||||
expect(@slider.slider).toBeUndefined
|
||||
expect($.fn.slider).not.toHaveBeenCalled()
|
||||
|
||||
describe 'play', ->
|
||||
beforeEach ->
|
||||
@slider = new VideoProgressSlider @player
|
||||
spyOn(@player, 'duration').andReturn 120
|
||||
@slider = new VideoProgressSlider el: $('.slider')
|
||||
spyOn($.fn, 'slider').andCallThrough()
|
||||
|
||||
describe 'when the slider was already built', ->
|
||||
beforeEach ->
|
||||
@slider.play()
|
||||
|
||||
it 'does not build the slider', ->
|
||||
expect($.fn.slider).not.toHaveBeenCalled
|
||||
|
||||
describe 'when the slider was not already built', ->
|
||||
beforeEach ->
|
||||
@slider.slider = null
|
||||
@slider.play()
|
||||
|
||||
it 'build the slider', ->
|
||||
expect(@slider.slider).toBe '.slider'
|
||||
expect($.fn.slider).toHaveBeenCalledWith
|
||||
range: 'min'
|
||||
change: @slider.onChange
|
||||
slide: @slider.onSlide
|
||||
stop: @slider.onStop
|
||||
|
||||
it 'build the seek handle', ->
|
||||
expect(@slider.handle).toBe '.ui-slider-handle'
|
||||
expect($.fn.qtip).toHaveBeenCalledWith
|
||||
content: "0:00"
|
||||
position:
|
||||
my: 'bottom center'
|
||||
at: 'top center'
|
||||
container: @slider.handle
|
||||
hide:
|
||||
delay: 700
|
||||
style:
|
||||
classes: 'ui-tooltip-slider'
|
||||
widget: true
|
||||
|
||||
describe 'updatePlayTime', ->
|
||||
beforeEach ->
|
||||
@slider = new VideoProgressSlider el: $('.slider')
|
||||
spyOn($.fn, 'slider').andCallThrough()
|
||||
|
||||
describe 'when frozen', ->
|
||||
beforeEach ->
|
||||
@slider.frozen = true
|
||||
@slider.onUpdatePlayTime {}, 20
|
||||
@slider.updatePlayTime 20, 120
|
||||
|
||||
it 'does not update the slider', ->
|
||||
expect($.fn.slider).not.toHaveBeenCalled()
|
||||
@@ -114,7 +96,7 @@ describe 'VideoProgressSlider', ->
|
||||
describe 'when not frozen', ->
|
||||
beforeEach ->
|
||||
@slider.frozen = false
|
||||
@slider.onUpdatePlayTime {}, 20
|
||||
@slider.updatePlayTime 20, 120
|
||||
|
||||
it 'update the max value of the slider', ->
|
||||
expect($.fn.slider).toHaveBeenCalledWith 'option', 'max', 120
|
||||
@@ -124,10 +106,10 @@ describe 'VideoProgressSlider', ->
|
||||
|
||||
describe 'onSlide', ->
|
||||
beforeEach ->
|
||||
@slider = new VideoProgressSlider @player
|
||||
@slider = new VideoProgressSlider el: $('.slider')
|
||||
@time = null
|
||||
$(@player).bind 'seek', (event, time) => @time = time
|
||||
spyOnEvent @player, 'seek'
|
||||
$(@slider).bind 'seek', (event, time) => @time = time
|
||||
spyOnEvent @slider, 'seek'
|
||||
@slider.onSlide {}, value: 20
|
||||
|
||||
it 'freeze the slider', ->
|
||||
@@ -137,12 +119,12 @@ describe 'VideoProgressSlider', ->
|
||||
expect($.fn.qtip).toHaveBeenCalled()
|
||||
|
||||
it 'trigger seek event', ->
|
||||
expect('seek').toHaveBeenTriggeredOn @player
|
||||
expect('seek').toHaveBeenTriggeredOn @slider
|
||||
expect(@time).toEqual 20
|
||||
|
||||
describe 'onChange', ->
|
||||
beforeEach ->
|
||||
@slider = new VideoProgressSlider @player
|
||||
@slider = new VideoProgressSlider el: $('.slider')
|
||||
@slider.onChange {}, value: 20
|
||||
|
||||
it 'update the tooltip', ->
|
||||
@@ -150,10 +132,10 @@ describe 'VideoProgressSlider', ->
|
||||
|
||||
describe 'onStop', ->
|
||||
beforeEach ->
|
||||
@slider = new VideoProgressSlider @player
|
||||
@slider = new VideoProgressSlider el: $('.slider')
|
||||
@time = null
|
||||
$(@player).bind 'seek', (event, time) => @time = time
|
||||
spyOnEvent @player, 'seek'
|
||||
$(@slider).bind 'seek', (event, time) => @time = time
|
||||
spyOnEvent @slider, 'seek'
|
||||
spyOn(window, 'setTimeout')
|
||||
@slider.onStop {}, value: 20
|
||||
|
||||
@@ -161,7 +143,7 @@ describe 'VideoProgressSlider', ->
|
||||
expect(@slider.frozen).toBeTruthy()
|
||||
|
||||
it 'trigger seek event', ->
|
||||
expect('seek').toHaveBeenTriggeredOn @player
|
||||
expect('seek').toHaveBeenTriggeredOn @slider
|
||||
expect(@time).toEqual 20
|
||||
|
||||
it 'set timeout to unfreeze the slider', ->
|
||||
@@ -171,7 +153,7 @@ describe 'VideoProgressSlider', ->
|
||||
|
||||
describe 'updateTooltip', ->
|
||||
beforeEach ->
|
||||
@slider = new VideoProgressSlider @player
|
||||
@slider = new VideoProgressSlider el: $('.slider')
|
||||
@slider.updateTooltip 90
|
||||
|
||||
it 'set the tooltip value', ->
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
describe 'VideoSpeedControl', ->
|
||||
beforeEach ->
|
||||
@player = jasmine.stubVideoPlayer @
|
||||
jasmine.stubVideoPlayer @
|
||||
$('.speeds').remove()
|
||||
|
||||
describe 'constructor', ->
|
||||
describe 'always', ->
|
||||
beforeEach ->
|
||||
@speedControl = new VideoSpeedControl @player, @video.speeds
|
||||
@speedControl = new VideoSpeedControl el: $('.secondary-controls'), speeds: @video.speeds, currentSpeed: '1.0'
|
||||
|
||||
it 'add the video speed control to player', ->
|
||||
expect($('.secondary-controls').html()).toContain '''
|
||||
@@ -19,9 +19,6 @@ describe 'VideoSpeedControl', ->
|
||||
</div>
|
||||
'''
|
||||
|
||||
it 'bind to player speedChange event', ->
|
||||
expect($(@player)).toHandleWith 'speedChange', @speedControl.onSpeedChange
|
||||
|
||||
it 'bind to change video speed link', ->
|
||||
expect($('.video_speeds a')).toHandleWith 'click', @speedControl.changeVideoSpeed
|
||||
|
||||
@@ -29,7 +26,7 @@ describe 'VideoSpeedControl', ->
|
||||
beforeEach ->
|
||||
spyOn(window, 'onTouchBasedDevice').andReturn true
|
||||
$('.speeds').removeClass 'open'
|
||||
@speedControl = new VideoSpeedControl @player, @video.speeds
|
||||
@speedControl = new VideoSpeedControl el: $('.secondary-controls'), speeds: @video.speeds, currentSpeed: '1.0'
|
||||
|
||||
it 'open the speed toggle on click', ->
|
||||
$('.speeds').click()
|
||||
@@ -41,7 +38,7 @@ describe 'VideoSpeedControl', ->
|
||||
beforeEach ->
|
||||
spyOn(window, 'onTouchBasedDevice').andReturn false
|
||||
$('.speeds').removeClass 'open'
|
||||
@speedControl = new VideoSpeedControl @player, @video.speeds
|
||||
@speedControl = new VideoSpeedControl el: $('.secondary-controls'), speeds: @video.speeds, currentSpeed: '1.0'
|
||||
|
||||
it 'open the speed toggle on hover', ->
|
||||
$('.speeds').mouseenter()
|
||||
@@ -59,31 +56,31 @@ describe 'VideoSpeedControl', ->
|
||||
|
||||
describe 'changeVideoSpeed', ->
|
||||
beforeEach ->
|
||||
@speedControl = new VideoSpeedControl @player, @video.speeds
|
||||
@speedControl = new VideoSpeedControl el: $('.secondary-controls'), speeds: @video.speeds, currentSpeed: '1.0'
|
||||
@video.setSpeed '1.0'
|
||||
|
||||
describe 'when new speed is the same', ->
|
||||
beforeEach ->
|
||||
spyOnEvent @player, 'speedChange'
|
||||
spyOnEvent @speedControl, 'speedChange'
|
||||
$('li[data-speed="1.0"] a').click()
|
||||
|
||||
it 'does not trigger speedChange event', ->
|
||||
expect('speedChange').not.toHaveBeenTriggeredOn @player
|
||||
expect('speedChange').not.toHaveBeenTriggeredOn @speedControl
|
||||
|
||||
describe 'when new speed is not the same', ->
|
||||
beforeEach ->
|
||||
@newSpeed = null
|
||||
$(@player).bind 'speedChange', (event, newSpeed) => @newSpeed = newSpeed
|
||||
spyOnEvent @player, 'speedChange'
|
||||
$(@speedControl).bind 'speedChange', (event, newSpeed) => @newSpeed = newSpeed
|
||||
spyOnEvent @speedControl, 'speedChange'
|
||||
$('li[data-speed="0.75"] a').click()
|
||||
|
||||
it 'trigger player speedChange event', ->
|
||||
expect('speedChange').toHaveBeenTriggeredOn @player
|
||||
it 'trigger speedChange event', ->
|
||||
expect('speedChange').toHaveBeenTriggeredOn @speedControl
|
||||
expect(@newSpeed).toEqual 0.75
|
||||
|
||||
describe 'onSpeedChange', ->
|
||||
beforeEach ->
|
||||
@speedControl = new VideoSpeedControl @player, @video.speeds
|
||||
@speedControl = new VideoSpeedControl el: $('.secondary-controls'), speeds: @video.speeds, currentSpeed: '1.0'
|
||||
$('li[data-speed="1.0"] a').addClass 'active'
|
||||
@speedControl.setSpeed '0.75'
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
describe 'VideoVolumeControl', ->
|
||||
beforeEach ->
|
||||
@player = jasmine.stubVideoPlayer @
|
||||
jasmine.stubVideoPlayer @
|
||||
$('.volume').remove()
|
||||
|
||||
describe 'constructor', ->
|
||||
beforeEach ->
|
||||
spyOn($.fn, 'slider')
|
||||
@volumeControl = new VideoVolumeControl @player
|
||||
@volumeControl = new VideoVolumeControl el: $('.secondary-controls')
|
||||
|
||||
it 'initialize previousVolume to 100', ->
|
||||
expect(@volumeControl.previousVolume).toEqual 100
|
||||
it 'initialize currentVolume to 100', ->
|
||||
expect(@volumeControl.currentVolume).toEqual 100
|
||||
|
||||
it 'render the volume control', ->
|
||||
expect($('.secondary-controls').html()).toContain """
|
||||
@@ -32,7 +32,6 @@ describe 'VideoVolumeControl', ->
|
||||
slide: @volumeControl.onChange
|
||||
|
||||
it 'bind the volume control', ->
|
||||
expect($(@player)).toHandleWith 'ready', @volumeControl.onReady
|
||||
expect($('.volume>a')).toHandleWith 'click', @volumeControl.toggleMute
|
||||
|
||||
expect($('.volume')).not.toHaveClass 'open'
|
||||
@@ -41,27 +40,19 @@ describe 'VideoVolumeControl', ->
|
||||
$('.volume').mouseleave()
|
||||
expect($('.volume')).not.toHaveClass 'open'
|
||||
|
||||
describe 'onReady', ->
|
||||
beforeEach ->
|
||||
@volumeControl = new VideoVolumeControl @player
|
||||
spyOn $.fn, 'slider'
|
||||
spyOn(@player, 'volume').andReturn 60
|
||||
@volumeControl.onReady()
|
||||
|
||||
it 'set the max value of the slider', ->
|
||||
expect($.fn.slider).toHaveBeenCalledWith 'option', 'max', 60
|
||||
|
||||
describe 'onChange', ->
|
||||
beforeEach ->
|
||||
spyOn @player, 'volume'
|
||||
@volumeControl = new VideoVolumeControl @player
|
||||
spyOnEvent @volumeControl, 'volumeChange'
|
||||
@newVolume = undefined
|
||||
@volumeControl = new VideoVolumeControl el: $('.secondary-controls')
|
||||
$(@volumeControl).bind 'volumeChange', (event, volume) => @newVolume = volume
|
||||
|
||||
describe 'when the new volume is more than 0', ->
|
||||
beforeEach ->
|
||||
@volumeControl.onChange undefined, value: 60
|
||||
|
||||
it 'set the player volume', ->
|
||||
expect(@player.volume).toHaveBeenCalledWith 60
|
||||
expect(@newVolume).toEqual 60
|
||||
|
||||
it 'remote muted class', ->
|
||||
expect($('.volume')).not.toHaveClass 'muted'
|
||||
@@ -71,32 +62,33 @@ describe 'VideoVolumeControl', ->
|
||||
@volumeControl.onChange undefined, value: 0
|
||||
|
||||
it 'set the player volume', ->
|
||||
expect(@player.volume).toHaveBeenCalledWith 0
|
||||
expect(@newVolume).toEqual 0
|
||||
|
||||
it 'add muted class', ->
|
||||
expect($('.volume')).toHaveClass 'muted'
|
||||
|
||||
describe 'toggleMute', ->
|
||||
beforeEach ->
|
||||
spyOn @player, 'volume'
|
||||
@volumeControl = new VideoVolumeControl @player
|
||||
@newVolume = undefined
|
||||
@volumeControl = new VideoVolumeControl el: $('.secondary-controls')
|
||||
$(@volumeControl).bind 'volumeChange', (event, volume) => @newVolume = volume
|
||||
|
||||
describe 'when the current volume is more than 0', ->
|
||||
beforeEach ->
|
||||
@player.volume.andReturn 60
|
||||
@volumeControl.currentVolume = 60
|
||||
@volumeControl.toggleMute()
|
||||
|
||||
it 'save the previous volume', ->
|
||||
expect(@volumeControl.previousVolume).toEqual 60
|
||||
|
||||
it 'set the player volume', ->
|
||||
expect(@player.volume).toHaveBeenCalledWith 0
|
||||
expect(@newVolume).toEqual 0
|
||||
|
||||
describe 'when the current volume is 0', ->
|
||||
beforeEach ->
|
||||
@player.volume.andReturn 0
|
||||
@volumeControl.currentVolume = 0
|
||||
@volumeControl.previousVolume = 60
|
||||
@volumeControl.toggleMute()
|
||||
|
||||
it 'set the player volume to previous volume', ->
|
||||
expect(@player.volume).toHaveBeenCalledWith 60
|
||||
expect(@newVolume).toEqual 60
|
||||
|
||||
@@ -3,6 +3,10 @@ describe 'Video', ->
|
||||
loadFixtures 'video.html'
|
||||
jasmine.stubRequests()
|
||||
|
||||
@videosDefinition = '.75:slowerSpeedYoutubeId,1.0:normalSpeedYoutubeId'
|
||||
@slowerSpeedYoutubeId = 'slowerSpeedYoutubeId'
|
||||
@normalSpeedYoutubeId = 'normalSpeedYoutubeId'
|
||||
|
||||
afterEach ->
|
||||
window.player = undefined
|
||||
window.onYouTubePlayerAPIReady = undefined
|
||||
@@ -15,26 +19,26 @@ describe 'Video', ->
|
||||
|
||||
describe 'by default', ->
|
||||
beforeEach ->
|
||||
@video = new Video 'example', '.75:abc123,1.0:def456'
|
||||
@video = new Video 'example', @videosDefinition
|
||||
|
||||
it 'reset the current video player', ->
|
||||
expect(window.player).toBeNull()
|
||||
|
||||
it 'set the elements', ->
|
||||
expect(@video.element).toBe '#video_example'
|
||||
expect(@video.el).toBe '#video_example'
|
||||
|
||||
it 'parse the videos', ->
|
||||
expect(@video.videos).toEqual
|
||||
'0.75': 'abc123'
|
||||
'1.0': 'def456'
|
||||
'0.75': @slowerSpeedYoutubeId
|
||||
'1.0': @normalSpeedYoutubeId
|
||||
|
||||
it 'fetch the video metadata', ->
|
||||
expect(@video.metadata).toEqual
|
||||
abc123:
|
||||
id: 'abc123'
|
||||
duration: 100
|
||||
def456:
|
||||
id: 'def456'
|
||||
slowerSpeedYoutubeId:
|
||||
id: @slowerSpeedYoutubeId
|
||||
duration: 300
|
||||
normalSpeedYoutubeId:
|
||||
id: @normalSpeedYoutubeId
|
||||
duration: 200
|
||||
|
||||
it 'parse available video speeds', ->
|
||||
@@ -51,20 +55,20 @@ describe 'Video', ->
|
||||
@originalYT = window.YT
|
||||
window.YT = { Player: true }
|
||||
spyOn(window, 'VideoPlayer').andReturn(@stubVideoPlayer)
|
||||
@video = new Video 'example', '.75:abc123,1.0:def456'
|
||||
@video = new Video 'example', @videosDefinition
|
||||
|
||||
afterEach ->
|
||||
window.YT = @originalYT
|
||||
|
||||
it 'create the Video Player', ->
|
||||
expect(window.VideoPlayer).toHaveBeenCalledWith @video
|
||||
expect(window.VideoPlayer).toHaveBeenCalledWith(video: @video)
|
||||
expect(@video.player).toEqual @stubVideoPlayer
|
||||
|
||||
describe 'when the Youtube API is not ready', ->
|
||||
beforeEach ->
|
||||
@originalYT = window.YT
|
||||
window.YT = {}
|
||||
@video = new Video 'example', '.75:abc123,1.0:def456'
|
||||
@video = new Video 'example', @videosDefinition
|
||||
|
||||
afterEach ->
|
||||
window.YT = @originalYT
|
||||
@@ -77,33 +81,33 @@ describe 'Video', ->
|
||||
@originalYT = window.YT
|
||||
window.YT = {}
|
||||
spyOn(window, 'VideoPlayer').andReturn(@stubVideoPlayer)
|
||||
@video = new Video 'example', '.75:abc123,1.0:def456'
|
||||
@video = new Video 'example', @videosDefinition
|
||||
window.onYouTubePlayerAPIReady()
|
||||
|
||||
afterEach ->
|
||||
window.YT = @originalYT
|
||||
|
||||
it 'create the Video Player for all video elements', ->
|
||||
expect(window.VideoPlayer).toHaveBeenCalledWith @video
|
||||
expect(window.VideoPlayer).toHaveBeenCalledWith(video: @video)
|
||||
expect(@video.player).toEqual @stubVideoPlayer
|
||||
|
||||
describe 'youtubeId', ->
|
||||
beforeEach ->
|
||||
$.cookie.andReturn '1.0'
|
||||
@video = new Video 'example', '.75:abc123,1.0:def456'
|
||||
@video = new Video 'example', @videosDefinition
|
||||
|
||||
describe 'with speed', ->
|
||||
it 'return the video id for given speed', ->
|
||||
expect(@video.youtubeId('0.75')).toEqual 'abc123'
|
||||
expect(@video.youtubeId('1.0')).toEqual 'def456'
|
||||
expect(@video.youtubeId('0.75')).toEqual @slowerSpeedYoutubeId
|
||||
expect(@video.youtubeId('1.0')).toEqual @normalSpeedYoutubeId
|
||||
|
||||
describe 'without speed', ->
|
||||
it 'return the video id for current speed', ->
|
||||
expect(@video.youtubeId()).toEqual 'def456'
|
||||
expect(@video.youtubeId()).toEqual @normalSpeedYoutubeId
|
||||
|
||||
describe 'setSpeed', ->
|
||||
beforeEach ->
|
||||
@video = new Video 'example', '.75:abc123,1.0:def456'
|
||||
@video = new Video 'example', @videosDefinition
|
||||
|
||||
describe 'when new speed is available', ->
|
||||
beforeEach ->
|
||||
@@ -124,7 +128,22 @@ describe 'Video', ->
|
||||
|
||||
describe 'getDuration', ->
|
||||
beforeEach ->
|
||||
@video = new Video 'example', '.75:abc123,1.0:def456'
|
||||
@video = new Video 'example', @videosDefinition
|
||||
|
||||
it 'return duration for current video', ->
|
||||
expect(@video.getDuration()).toEqual 200
|
||||
|
||||
describe 'log', ->
|
||||
beforeEach ->
|
||||
@video = new Video 'example', @videosDefinition
|
||||
@video.setSpeed '1.0'
|
||||
spyOn Logger, 'log'
|
||||
@video.player = { currentTime: 25 }
|
||||
@video.log 'someEvent'
|
||||
|
||||
it 'call the logger with valid parameters', ->
|
||||
expect(Logger.log).toHaveBeenCalledWith 'someEvent',
|
||||
id: 'example'
|
||||
code: @normalSpeedYoutubeId
|
||||
currentTime: 25
|
||||
speed: '1.0'
|
||||
|
||||
14
lms/static/coffee/src/_subview.coffee
Normal file
14
lms/static/coffee/src/_subview.coffee
Normal file
@@ -0,0 +1,14 @@
|
||||
class @Subview
|
||||
constructor: (options) ->
|
||||
$.each options, (key, value) =>
|
||||
@[key] = value
|
||||
@initialize()
|
||||
@render()
|
||||
@bind()
|
||||
|
||||
$: (selector) ->
|
||||
$(selector, @el)
|
||||
|
||||
initialize: ->
|
||||
render: ->
|
||||
bind: ->
|
||||
@@ -20,7 +20,7 @@ class @Courseware
|
||||
id = $(this).attr('id').replace(/video_/, '')
|
||||
new Video id, $(this).data('streams')
|
||||
$('.course-content .problems-wrapper').each ->
|
||||
id = $(this).attr('problem-id')
|
||||
id = $(this).data('problem-id')
|
||||
new Problem id, $(this).attr('id'), $(this).data('url')
|
||||
$('.course-content .histogram').each ->
|
||||
id = $(this).attr('id').replace(/histogram_/, '')
|
||||
|
||||
@@ -24,8 +24,8 @@ $ ->
|
||||
|
||||
# Preserved for backward compatibility
|
||||
window.submit_circuit = (circuit_id) ->
|
||||
$("input.schematic").each (index, element) ->
|
||||
element.schematic.update_value()
|
||||
$("input.schematic").each (index, el) ->
|
||||
el.schematic.update_value()
|
||||
|
||||
schematic_value $("#schematic_#{circuit_id}").attr("value")
|
||||
$.postWithPrefix "/save_circuit/#{circuit_id}", schematic: schematic_value, (data) ->
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
class @Problem
|
||||
constructor: (@id, @element_id, url) ->
|
||||
@element = $("##{element_id}")
|
||||
@el = $("##{@element_id}")
|
||||
@render()
|
||||
|
||||
$: (selector) ->
|
||||
$(selector, @element)
|
||||
$(selector, @el)
|
||||
|
||||
bind: =>
|
||||
MathJax.Hub.Queue ["Typeset", MathJax.Hub]
|
||||
@@ -18,16 +18,16 @@ class @Problem
|
||||
|
||||
updateProgress: (response) =>
|
||||
if response.progress_changed
|
||||
@element.attr progress: response.progress_status
|
||||
@element.trigger('progressChanged')
|
||||
@el.attr progress: response.progress_status
|
||||
@el.trigger('progressChanged')
|
||||
|
||||
render: (content) ->
|
||||
if content
|
||||
@element.html(content)
|
||||
@el.html(content)
|
||||
@bind()
|
||||
else
|
||||
$.postWithPrefix "/modx/#{@id}/problem_get", (response) =>
|
||||
@element.html(response.html)
|
||||
@el.html(response.html)
|
||||
@bind()
|
||||
|
||||
check: =>
|
||||
@@ -47,7 +47,7 @@ class @Problem
|
||||
@updateProgress response
|
||||
|
||||
show: =>
|
||||
if !@element.hasClass 'showed'
|
||||
if !@el.hasClass 'showed'
|
||||
Logger.log 'problem_show', problem: @id
|
||||
$.postWithPrefix "/modx/#{@id}/problem_show", (response) =>
|
||||
answers = response.answers
|
||||
@@ -59,12 +59,12 @@ class @Problem
|
||||
@$("#answer_#{key}, #solution_#{key}").html(value)
|
||||
MathJax.Hub.Queue ["Typeset", MathJax.Hub]
|
||||
@$('.show').val 'Hide Answer'
|
||||
@element.addClass 'showed'
|
||||
@el.addClass 'showed'
|
||||
@updateProgress response
|
||||
else
|
||||
@$('[id^=answer_], [id^=solution_]').text ''
|
||||
@$('[correct_answer]').attr correct_answer: null
|
||||
@element.removeClass 'showed'
|
||||
@el.removeClass 'showed'
|
||||
@$('.show').val 'Show Answer'
|
||||
|
||||
save: =>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
class @Sequence
|
||||
constructor: (@id, @element_id, @elements, @tag, position) ->
|
||||
@element = $("#sequence_#{@element_id}")
|
||||
@el = $("##{@element_id}")
|
||||
@buildNavigation()
|
||||
@initProgress()
|
||||
@bind()
|
||||
@render position
|
||||
|
||||
$: (selector) ->
|
||||
$(selector, @element)
|
||||
$(selector, @el)
|
||||
|
||||
bind: ->
|
||||
@$('#sequence-list a').click @goto
|
||||
@@ -57,7 +57,7 @@ class @Sequence
|
||||
when 'none' then element.addClass('progress-none')
|
||||
when 'in_progress' then element.addClass('progress-some')
|
||||
when 'done' then element.addClass('progress-done')
|
||||
|
||||
|
||||
buildNavigation: ->
|
||||
$.each @elements, (index, item) =>
|
||||
link = $('<a>').attr class: "seq_#{item.type}_inactive", 'data-element': index + 1
|
||||
@@ -65,10 +65,10 @@ class @Sequence
|
||||
# TODO (vshnayder): add item.progress_detail either to the title or somewhere else.
|
||||
# Make sure it gets updated after ajax calls.
|
||||
# implementation note: will need to figure out how to handle combining detail
|
||||
# statuses of multiple modules in js.
|
||||
# statuses of multiple modules in js.
|
||||
list_item = $('<li>').append(link.append(title))
|
||||
@setProgress item.progress_status, link
|
||||
|
||||
|
||||
@$('#sequence-list').append list_item
|
||||
|
||||
toggleArrows: =>
|
||||
@@ -89,7 +89,7 @@ class @Sequence
|
||||
if @position != undefined
|
||||
@mark_visited @position
|
||||
$.postWithPrefix "/modx/#{@id}/goto_position", position: new_position
|
||||
|
||||
|
||||
@mark_active new_position
|
||||
@$('#seq_content').html @elements[new_position - 1].content
|
||||
|
||||
@@ -97,7 +97,7 @@ class @Sequence
|
||||
@position = new_position
|
||||
@toggleArrows()
|
||||
@hookUpProgressEvent()
|
||||
@element.trigger 'contentChanged'
|
||||
@el.trigger 'contentChanged'
|
||||
|
||||
goto: (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
class @Tab
|
||||
constructor: (@id, @items) ->
|
||||
@element = $("#tab_#{id}")
|
||||
@el = $("#tab_#{id}")
|
||||
@render()
|
||||
|
||||
$: (selector) ->
|
||||
$(selector, @element)
|
||||
$(selector, @el)
|
||||
|
||||
render: ->
|
||||
$.each @items, (index, item) =>
|
||||
tab = $('<a>').attr(href: "##{@tabId(index)}").html(item.title)
|
||||
@$('.navigation').append($('<li>').append(tab))
|
||||
@element.append($('<section>').attr(id: @tabId(index)))
|
||||
@element.tabs
|
||||
@el.append($('<section>').attr(id: @tabId(index)))
|
||||
@el.tabs
|
||||
show: @onShow
|
||||
|
||||
onShow: (element, ui) =>
|
||||
@$('section.ui-tabs-hide').html('')
|
||||
@$("##{@tabId(ui.index)}").html(@items[ui.index]['content'])
|
||||
@element.trigger 'contentChanged'
|
||||
@el.trigger 'contentChanged'
|
||||
|
||||
tabId: (index) ->
|
||||
"tab-#{@id}-#{index}"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
class @Video
|
||||
constructor: (@id, videos) ->
|
||||
window.player = null
|
||||
@element = $("#video_#{@id}")
|
||||
@el = $("#video_#{@id}")
|
||||
@parseVideos videos
|
||||
@fetchMetadata()
|
||||
@parseSpeed()
|
||||
@@ -36,7 +36,7 @@ class @Video
|
||||
@speed = '1.0'
|
||||
|
||||
embed: ->
|
||||
@player = new VideoPlayer(this)
|
||||
@player = new VideoPlayer video: this
|
||||
|
||||
fetchMetadata: (url) ->
|
||||
@metadata = {}
|
||||
@@ -45,3 +45,10 @@ class @Video
|
||||
|
||||
getDuration: ->
|
||||
@metadata[@youtubeId()].duration
|
||||
|
||||
log: (eventName) ->
|
||||
Logger.log eventName,
|
||||
id: @id
|
||||
code: @youtubeId()
|
||||
currentTime: @player.currentTime
|
||||
speed: @speed
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
class @VideoCaption
|
||||
constructor: (@player, @youtubeId) ->
|
||||
@render()
|
||||
@bind()
|
||||
|
||||
$: (selector) ->
|
||||
@player.$(selector)
|
||||
|
||||
class @VideoCaption extends Subview
|
||||
bind: ->
|
||||
$(window).bind('resize', @onWindowResize)
|
||||
$(@player).bind('resize', @onWindowResize)
|
||||
$(@player).bind('updatePlayTime', @onUpdatePlayTime)
|
||||
$(@player).bind('seek', @onUpdatePlayTime)
|
||||
$(@player).bind('play', @onPlay)
|
||||
$(window).bind('resize', @resize)
|
||||
@$('.hide-subtitles').click @toggle
|
||||
@$('.subtitles').mouseenter(@onMouseEnter).mouseleave(@onMouseLeave)
|
||||
.mousemove(@onMovement).bind('mousewheel', @onMovement)
|
||||
@@ -70,12 +59,16 @@ class @VideoCaption
|
||||
|
||||
return min
|
||||
|
||||
onPlay: =>
|
||||
play: ->
|
||||
@renderCaption() unless @rendered
|
||||
@playing = true
|
||||
|
||||
onUpdatePlayTime: (event, time) =>
|
||||
pause: ->
|
||||
@playing = false
|
||||
|
||||
updatePlayTime: (time) ->
|
||||
# This 250ms offset is required to match the video speed
|
||||
time = Math.round(Time.convert(time, @player.currentSpeed(), '1.0') * 1000 + 250)
|
||||
time = Math.round(Time.convert(time, @currentSpeed, '1.0') * 1000 + 250)
|
||||
newIndex = @search time
|
||||
|
||||
if newIndex != undefined && @currentIndex != newIndex
|
||||
@@ -86,7 +79,7 @@ class @VideoCaption
|
||||
@currentIndex = newIndex
|
||||
@scrollCaption()
|
||||
|
||||
onWindowResize: =>
|
||||
resize: =>
|
||||
@$('.subtitles').css maxHeight: @captionHeight()
|
||||
@$('.subtitles .spacing:first').height(@topSpacingHeight())
|
||||
@$('.subtitles .spacing:last').height(@bottomSpacingHeight())
|
||||
@@ -102,7 +95,7 @@ class @VideoCaption
|
||||
onMouseLeave: =>
|
||||
clearTimeout @frozen if @frozen
|
||||
@frozen = null
|
||||
@scrollCaption() if @player.isPlaying()
|
||||
@scrollCaption() if @playing
|
||||
|
||||
scrollCaption: ->
|
||||
if !@frozen && @$('.subtitles .current:first').length
|
||||
@@ -111,8 +104,8 @@ class @VideoCaption
|
||||
|
||||
seekPlayer: (event) =>
|
||||
event.preventDefault()
|
||||
time = Math.round(Time.convert($(event.target).data('start'), '1.0', @player.currentSpeed()) / 1000)
|
||||
$(@player).trigger('seek', time)
|
||||
time = Math.round(Time.convert($(event.target).data('start'), '1.0', @currentSpeed) / 1000)
|
||||
$(@).trigger('seek', time)
|
||||
|
||||
calculateOffset: (element) ->
|
||||
@captionHeight() / 2 - element.height() / 2
|
||||
@@ -125,16 +118,16 @@ class @VideoCaption
|
||||
|
||||
toggle: (event) =>
|
||||
event.preventDefault()
|
||||
if @player.element.hasClass('closed')
|
||||
if @el.hasClass('closed')
|
||||
@$('.hide-subtitles').attr('title', 'Turn off captions')
|
||||
@player.element.removeClass('closed')
|
||||
@el.removeClass('closed')
|
||||
@scrollCaption()
|
||||
else
|
||||
@$('.hide-subtitles').attr('title', 'Turn on captions')
|
||||
@player.element.addClass('closed')
|
||||
@el.addClass('closed')
|
||||
|
||||
captionHeight: ->
|
||||
if @player.element.hasClass('fullscreen')
|
||||
if @el.hasClass('fullscreen')
|
||||
$(window).height() - @$('.video-controls').height()
|
||||
else
|
||||
@$('.video-wrapper').height()
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
class @VideoControl
|
||||
constructor: (@player) ->
|
||||
@render()
|
||||
@bind()
|
||||
|
||||
$: (selector) ->
|
||||
@player.$(selector)
|
||||
|
||||
class @VideoControl extends Subview
|
||||
bind: ->
|
||||
$(@player).bind('play', @onPlay)
|
||||
.bind('pause', @onPause)
|
||||
.bind('ended', @onPause)
|
||||
@$('.video_control').click @togglePlayback
|
||||
|
||||
render: ->
|
||||
@$('.video-controls').append """
|
||||
@el.append """
|
||||
<div class="slider"></div>
|
||||
<div>
|
||||
<ul class="vcr">
|
||||
@@ -31,16 +21,15 @@ class @VideoControl
|
||||
unless onTouchBasedDevice()
|
||||
@$('.video_control').addClass('play').html('Play')
|
||||
|
||||
onPlay: =>
|
||||
play: ->
|
||||
@$('.video_control').removeClass('play').addClass('pause').html('Pause')
|
||||
|
||||
onPause: =>
|
||||
pause: ->
|
||||
@$('.video_control').removeClass('pause').addClass('play').html('Play')
|
||||
|
||||
togglePlayback: (event) =>
|
||||
event.preventDefault()
|
||||
if $('.video_control').hasClass('play') || $('.video_control').hasClass('pause')
|
||||
if @player.isPlaying()
|
||||
$(@player).trigger('pause')
|
||||
else
|
||||
$(@player).trigger('play')
|
||||
if @$('.video_control').hasClass('play')
|
||||
$(@).trigger('play')
|
||||
else if @$('.video_control').hasClass('pause')
|
||||
$(@).trigger('pause')
|
||||
|
||||
@@ -1,38 +1,35 @@
|
||||
class @VideoPlayer
|
||||
constructor: (@video) ->
|
||||
class @VideoPlayer extends Subview
|
||||
initialize: ->
|
||||
# Define a missing constant of Youtube API
|
||||
YT.PlayerState.UNSTARTED = -1
|
||||
|
||||
@currentTime = 0
|
||||
@element = $("#video_#{@video.id}")
|
||||
@render()
|
||||
@bind()
|
||||
|
||||
$: (selector) ->
|
||||
$(selector, @element)
|
||||
@el = $("#video_#{@video.id}")
|
||||
|
||||
bind: ->
|
||||
$(@).bind('seek', @onSeek)
|
||||
.bind('updatePlayTime', @onUpdatePlayTime)
|
||||
.bind('speedChange', @onSpeedChange)
|
||||
.bind('play', @onPlay)
|
||||
.bind('pause', @onPause)
|
||||
.bind('ended', @onPause)
|
||||
$(@control).bind('play', @play)
|
||||
.bind('pause', @pause)
|
||||
$(@caption).bind('seek', @onSeek)
|
||||
$(@speedControl).bind('speedChange', @onSpeedChange)
|
||||
$(@progressSlider).bind('seek', @onSeek)
|
||||
if @volumeControl
|
||||
$(@volumeControl).bind('volumeChange', @onVolumeChange)
|
||||
$(document).keyup @bindExitFullScreen
|
||||
|
||||
@$('.add-fullscreen').click @toggleFullScreen
|
||||
@addToolTip() unless onTouchBasedDevice()
|
||||
|
||||
bindExitFullScreen: (event) =>
|
||||
if @element.hasClass('fullscreen') && event.keyCode == 27
|
||||
if @el.hasClass('fullscreen') && event.keyCode == 27
|
||||
@toggleFullScreen(event)
|
||||
|
||||
render: ->
|
||||
new VideoControl @
|
||||
new VideoCaption @, @video.youtubeId('1.0')
|
||||
new VideoVolumeControl @ unless onTouchBasedDevice()
|
||||
new VideoSpeedControl @, @video.speeds
|
||||
new VideoProgressSlider @
|
||||
@control = new VideoControl el: @$('.video-controls')
|
||||
@caption = new VideoCaption el: @el, youtubeId: @video.youtubeId('1.0'), currentSpeed: @currentSpeed()
|
||||
unless onTouchBasedDevice()
|
||||
@volumeControl = new VideoVolumeControl el: @$('.secondary-controls')
|
||||
@speedControl = new VideoSpeedControl el: @$('.secondary-controls'), speeds: @video.speeds, currentSpeed: @currentSpeed()
|
||||
@progressSlider = new VideoProgressSlider el: @$('.slider')
|
||||
@player = new YT.Player @video.id,
|
||||
playerVars:
|
||||
controls: 0
|
||||
@@ -52,90 +49,104 @@ class @VideoPlayer
|
||||
at: 'top center'
|
||||
|
||||
onReady: =>
|
||||
$(@).trigger('ready')
|
||||
$(@).trigger('updatePlayTime', 0)
|
||||
unless onTouchBasedDevice()
|
||||
$('.course-content .video:first').data('video').player.play()
|
||||
|
||||
onStateChange: (event) =>
|
||||
switch event.data
|
||||
when YT.PlayerState.UNSTARTED
|
||||
@onUnstarted()
|
||||
when YT.PlayerState.PLAYING
|
||||
$(@).trigger('play')
|
||||
when YT.PlayerState.PAUSED, YT.PlayerState.UNSTARTED
|
||||
$(@).trigger('pause')
|
||||
@onPlay()
|
||||
when YT.PlayerState.PAUSED
|
||||
@onPause()
|
||||
when YT.PlayerState.ENDED
|
||||
$(@).trigger('ended')
|
||||
@onEnded()
|
||||
|
||||
onUnstarted: =>
|
||||
@control.pause()
|
||||
@caption.pause()
|
||||
|
||||
onPlay: =>
|
||||
Logger.log 'play_video', id: @currentTime, code: @player.getVideoEmbedCode()
|
||||
@video.log 'play_video'
|
||||
window.player.pauseVideo() if window.player && window.player != @player
|
||||
window.player = @player
|
||||
unless @player.interval
|
||||
@player.interval = setInterval(@update, 200)
|
||||
@caption.play()
|
||||
@control.play()
|
||||
@progressSlider.play()
|
||||
|
||||
onPause: =>
|
||||
Logger.log 'pause_video', id: @currentTime, code: @player.getVideoEmbedCode()
|
||||
@video.log 'pause_video'
|
||||
window.player = null if window.player == @player
|
||||
clearInterval(@player.interval)
|
||||
@player.interval = null
|
||||
@caption.pause()
|
||||
@control.pause()
|
||||
|
||||
onSeek: (event, time) ->
|
||||
onEnded: =>
|
||||
@control.pause()
|
||||
@caption.pause()
|
||||
|
||||
onSeek: (event, time) =>
|
||||
@player.seekTo(time, true)
|
||||
if @isPlaying()
|
||||
clearInterval(@player.interval)
|
||||
@player.interval = setInterval(@update, 200)
|
||||
else
|
||||
@currentTime = time
|
||||
$(@).trigger('updatePlayTime', time)
|
||||
@updatePlayTime time
|
||||
|
||||
onSpeedChange: (event, newSpeed) =>
|
||||
@currentTime = Time.convert(@currentTime, parseFloat(@currentSpeed()), newSpeed)
|
||||
@video.setSpeed(parseFloat(newSpeed).toFixed(2).replace /\.00$/, '.0')
|
||||
newSpeed = parseFloat(newSpeed).toFixed(2).replace /\.00$/, '.0'
|
||||
@video.setSpeed(newSpeed)
|
||||
@caption.currentSpeed = newSpeed
|
||||
|
||||
if @isPlaying()
|
||||
@player.loadVideoById(@video.youtubeId(), @currentTime)
|
||||
else
|
||||
@player.cueVideoById(@video.youtubeId(), @currentTime)
|
||||
$(@).trigger('updatePlayTime', @currentTime)
|
||||
@updatePlayTime @currentTime
|
||||
|
||||
onVolumeChange: (event, volume) =>
|
||||
@player.setVolume volume
|
||||
|
||||
update: =>
|
||||
if @currentTime = @player.getCurrentTime()
|
||||
$(@).trigger('updatePlayTime', @currentTime)
|
||||
@updatePlayTime @currentTime
|
||||
|
||||
onUpdatePlayTime: (event, time) =>
|
||||
updatePlayTime: (time) ->
|
||||
progress = Time.format(time) + ' / ' + Time.format(@duration())
|
||||
@$(".vidtime").html(progress)
|
||||
@caption.updatePlayTime(time)
|
||||
@progressSlider.updatePlayTime(time, @duration())
|
||||
|
||||
toggleFullScreen: (event) =>
|
||||
event.preventDefault()
|
||||
if @element.hasClass('fullscreen')
|
||||
if @el.hasClass('fullscreen')
|
||||
@$('.exit').remove()
|
||||
@$('.add-fullscreen').attr('title', 'Fill browser')
|
||||
@element.removeClass('fullscreen')
|
||||
@el.removeClass('fullscreen')
|
||||
else
|
||||
@element.append('<a href="#" class="exit">Exit</a>').addClass('fullscreen')
|
||||
@el.append('<a href="#" class="exit">Exit</a>').addClass('fullscreen')
|
||||
@$('.add-fullscreen').attr('title', 'Exit fill browser')
|
||||
@$('.exit').click @toggleFullScreen
|
||||
$(@).trigger('resize')
|
||||
@caption.resize()
|
||||
|
||||
# Delegates
|
||||
play: ->
|
||||
play: =>
|
||||
@player.playVideo() if @player.playVideo
|
||||
|
||||
isPlaying: ->
|
||||
@player.getPlayerState() == YT.PlayerState.PLAYING
|
||||
|
||||
pause: ->
|
||||
@player.pauseVideo()
|
||||
pause: =>
|
||||
@player.pauseVideo() if @player.pauseVideo
|
||||
|
||||
duration: ->
|
||||
@video.getDuration()
|
||||
|
||||
currentSpeed: ->
|
||||
@video.speed
|
||||
|
||||
volume: (value) ->
|
||||
if value?
|
||||
@player.setVolume value
|
||||
else
|
||||
@player.getVolume()
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
class @VideoProgressSlider
|
||||
constructor: (@player) ->
|
||||
class @VideoProgressSlider extends Subview
|
||||
initialize: ->
|
||||
@buildSlider() unless onTouchBasedDevice()
|
||||
$(@player).bind('updatePlayTime', @onUpdatePlayTime)
|
||||
$(@player).bind('ready', @onReady)
|
||||
$(@player).bind('play', @onPlay)
|
||||
|
||||
$: (selector) ->
|
||||
@player.$(selector)
|
||||
|
||||
buildSlider: ->
|
||||
@slider = @$('.slider').slider
|
||||
@slider = @el.slider
|
||||
range: 'min'
|
||||
change: @onChange
|
||||
slide: @onSlide
|
||||
@@ -17,7 +11,7 @@ class @VideoProgressSlider
|
||||
@buildHandle()
|
||||
|
||||
buildHandle: ->
|
||||
@handle = @$('.slider .ui-slider-handle')
|
||||
@handle = @$('.ui-slider-handle')
|
||||
@handle.qtip
|
||||
content: "#{Time.format(@slider.slider('value'))}"
|
||||
position:
|
||||
@@ -30,28 +24,25 @@ class @VideoProgressSlider
|
||||
classes: 'ui-tooltip-slider'
|
||||
widget: true
|
||||
|
||||
onReady: =>
|
||||
@slider.slider('option', 'max', @player.duration()) if @slider
|
||||
|
||||
onPlay: =>
|
||||
play: =>
|
||||
@buildSlider() unless @slider
|
||||
|
||||
onUpdatePlayTime: (event, currentTime) =>
|
||||
updatePlayTime: (currentTime, duration) ->
|
||||
if @slider && !@frozen
|
||||
@slider.slider('option', 'max', @player.duration())
|
||||
@slider.slider('option', 'max', duration)
|
||||
@slider.slider('value', currentTime)
|
||||
|
||||
onSlide: (event, ui) =>
|
||||
@frozen = true
|
||||
@updateTooltip(ui.value)
|
||||
$(@player).trigger('seek', ui.value)
|
||||
$(@).trigger('seek', ui.value)
|
||||
|
||||
onChange: (event, ui) =>
|
||||
@updateTooltip(ui.value)
|
||||
|
||||
onStop: (event, ui) =>
|
||||
@frozen = true
|
||||
$(@player).trigger('seek', ui.value)
|
||||
$(@).trigger('seek', ui.value)
|
||||
setTimeout (=> @frozen = false), 200
|
||||
|
||||
updateTooltip: (value)->
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
class @VideoSpeedControl
|
||||
constructor: (@player, @speeds) ->
|
||||
@render()
|
||||
@bind()
|
||||
|
||||
$: (selector) ->
|
||||
@player.$(selector)
|
||||
|
||||
class @VideoSpeedControl extends Subview
|
||||
bind: ->
|
||||
$(@player).bind('speedChange', @onSpeedChange)
|
||||
@$('.video_speeds a').click @changeVideoSpeed
|
||||
if onTouchBasedDevice()
|
||||
@$('.speeds').click (event) ->
|
||||
@@ -23,7 +15,7 @@ class @VideoSpeedControl
|
||||
$(this).removeClass('open')
|
||||
|
||||
render: ->
|
||||
@$('.secondary-controls').prepend """
|
||||
@el.prepend """
|
||||
<div class="speeds">
|
||||
<a href="#">
|
||||
<h3>Speed</h3>
|
||||
@@ -36,15 +28,14 @@ class @VideoSpeedControl
|
||||
$.each @speeds, (index, speed) =>
|
||||
link = $('<a>').attr(href: "#").html("#{speed}x")
|
||||
@$('.video_speeds').prepend($('<li>').attr('data-speed', speed).html(link))
|
||||
@setSpeed(@player.currentSpeed())
|
||||
@setSpeed(@currentSpeed)
|
||||
|
||||
changeVideoSpeed: (event) =>
|
||||
event.preventDefault()
|
||||
unless $(event.target).parent().hasClass('active')
|
||||
$(@player).trigger 'speedChange', $(event.target).parent().data('speed')
|
||||
|
||||
onSpeedChange: (event, speed) =>
|
||||
@setSpeed(parseFloat(speed).toFixed(2).replace /\.00$/, '.0')
|
||||
@currentSpeed = $(event.target).parent().data('speed')
|
||||
$(@).trigger 'speedChange', $(event.target).parent().data('speed')
|
||||
@setSpeed(parseFloat(@currentSpeed).toFixed(2).replace /\.00$/, '.0')
|
||||
|
||||
setSpeed: (speed) ->
|
||||
@$('.video_speeds li').removeClass('active')
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
class @VideoVolumeControl
|
||||
constructor: (@player) ->
|
||||
@previousVolume = 100
|
||||
@render()
|
||||
@bind()
|
||||
|
||||
$: (selector) ->
|
||||
@player.$(selector)
|
||||
class @VideoVolumeControl extends Subview
|
||||
initialize: ->
|
||||
@currentVolume = 100
|
||||
|
||||
bind: ->
|
||||
$(@player).bind('ready', @onReady)
|
||||
@$('.volume').mouseenter ->
|
||||
$(this).addClass('open')
|
||||
@$('.volume').mouseleave ->
|
||||
@@ -16,7 +10,7 @@ class @VideoVolumeControl
|
||||
@$('.volume>a').click(@toggleMute)
|
||||
|
||||
render: ->
|
||||
@$('.secondary-controls').prepend """
|
||||
@el.prepend """
|
||||
<div class="volume">
|
||||
<a href="#"></a>
|
||||
<div class="volume-slider-container">
|
||||
@@ -33,16 +27,14 @@ class @VideoVolumeControl
|
||||
change: @onChange
|
||||
slide: @onChange
|
||||
|
||||
onReady: =>
|
||||
@slider.slider 'option', 'max', @player.volume()
|
||||
|
||||
onChange: (event, ui) =>
|
||||
@player.volume ui.value
|
||||
@$('.secondary-controls .volume').toggleClass 'muted', ui.value == 0
|
||||
@currentVolume = ui.value
|
||||
$(@).trigger 'volumeChange', @currentVolume
|
||||
@$('.volume').toggleClass 'muted', @currentVolume == 0
|
||||
|
||||
toggleMute: =>
|
||||
if @player.volume() > 0
|
||||
@previousVolume = @player.volume()
|
||||
if @currentVolume > 0
|
||||
@previousVolume = @currentVolume
|
||||
@slider.slider 'option', 'value', 0
|
||||
else
|
||||
@slider.slider 'option', 'value', @previousVolume
|
||||
|
||||
@@ -137,7 +137,6 @@ section.course-content {
|
||||
float: left;
|
||||
margin-bottom: 0;
|
||||
|
||||
|
||||
a {
|
||||
border-bottom: none;
|
||||
border-right: 1px solid #000;
|
||||
@@ -199,6 +198,8 @@ section.course-content {
|
||||
ol.video_speeds {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -476,6 +477,7 @@ section.course-content {
|
||||
|
||||
ol.subtitles {
|
||||
width: 0px;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,6 +500,7 @@ section.course-content {
|
||||
ol.subtitles {
|
||||
right: -(flex-grid(4));
|
||||
width: auto;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
function ${ id }_content_updated() {
|
||||
MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
|
||||
update_schematics();
|
||||
|
||||
|
||||
// dynamic math display: generate MathML on click
|
||||
$.each($("[id^=display_${ id }_]"), function(index,value){
|
||||
theid = value.id.replace("display_",""); // ID of the response
|
||||
if (document.getElementById("input_" + theid)){
|
||||
MathJax.Hub.queue.Push(function () {
|
||||
math = MathJax.Hub.getAllJax(value.id)[0];
|
||||
if (math){
|
||||
math.Text(document.getElementById("input_" + theid).value);
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
$('#check_${ id }').unbind('click').click(function() {
|
||||
$("input.schematic").each(function(index,element){ element.schematic.update_value(); });
|
||||
$(".CodeMirror").each(function(index,element){ if (element.CodeMirror.save) element.CodeMirror.save(); });
|
||||
|
||||
// dynamic math display: generate MathML on click
|
||||
$.each($("[id^=input_${ id }_]"), function(index,value){
|
||||
theid = value.id.replace("input_",""); // ID of the response
|
||||
if (document.getElementById("display_" + theid)){
|
||||
MathJax.Hub.queue.Push(function () {
|
||||
math = MathJax.Hub.getAllJax("display_" + theid)[0];
|
||||
if (math){
|
||||
math.Text(document.getElementById("input_" + theid).value);
|
||||
UpdateMathML(math,theid);
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
var submit_data={};
|
||||
$.each($("[id^=input_${ id }_]"), function(index,value){
|
||||
if (value.type==="checkbox"){
|
||||
if (value.checked) {
|
||||
if (typeof submit_data[value.name] == 'undefined'){
|
||||
submit_data[value.name]=[];
|
||||
}
|
||||
submit_data[value.name].push(value.value);
|
||||
}
|
||||
}
|
||||
if (value.type==="radio"){
|
||||
if (value.checked) {
|
||||
submit_data[value.name]= value.value;
|
||||
}
|
||||
}
|
||||
else{
|
||||
submit_data[value.id]=value.value;
|
||||
}
|
||||
});
|
||||
postJSON('${ MITX_ROOT_URL }/modx/problem/${ id }/problem_check',
|
||||
submit_data,
|
||||
function(json) {
|
||||
switch(json.success) {
|
||||
case 'incorrect': // Worked, but answer not
|
||||
case 'correct':
|
||||
$('#main_${ id }').html(json.contents);
|
||||
${ id }_content_updated();
|
||||
break;
|
||||
default:
|
||||
alert(json.success);
|
||||
}
|
||||
}
|
||||
);
|
||||
log_event('problem_check', submit_data);
|
||||
});
|
||||
|
||||
$('#reset_${ id }').unbind('click').click(function() {
|
||||
var submit_data={};
|
||||
$.each($("[id^=input_${ id }_]"), function(index,value){
|
||||
submit_data[value.id]=value.value;
|
||||
});
|
||||
|
||||
postJSON('${ MITX_ROOT_URL }/modx/problem/${ id }/problem_reset', {'id':'${ id }'}, function(html_as_json) {
|
||||
$('#main_${ id }').html(html_as_json);
|
||||
${ id }_content_updated();
|
||||
});
|
||||
log_event('problem_reset', submit_data);
|
||||
});
|
||||
|
||||
// show answer button
|
||||
// TODO: the button should turn into "hide answer" afterwards
|
||||
$('#show_${ id }').unbind('click').click(function() {
|
||||
postJSON('${ MITX_ROOT_URL }/modx/problem/${ id }/problem_show', {}, function(data) {
|
||||
for (var key in data) {
|
||||
if ($.isArray(data[key])){
|
||||
for (var ans_index in data[key]){
|
||||
var choice_id = 'input_'+key+'_'+data[key][ans_index];
|
||||
$("label[for="+choice_id+"]").attr("correct_answer", "true");
|
||||
}
|
||||
}
|
||||
// $("#answer_"+key).text(data[key]);
|
||||
$("#answer_"+key).html(data[key]);
|
||||
}
|
||||
});
|
||||
|
||||
for (var key in codemirror_set) {
|
||||
codemirror_set[key].refresh();
|
||||
}
|
||||
|
||||
log_event('problem_show', {'problem':'${ id }'});
|
||||
});
|
||||
|
||||
$('#save_${ id }').unbind('click').click(function() {
|
||||
$("input.schematic").each(function(index,element){ element.schematic.update_value(); });
|
||||
var submit_data={};
|
||||
$.each($("[id^=input_${ id }_]"), function(index,value) {
|
||||
submit_data[value.id]=value.value;
|
||||
});
|
||||
postJSON('${ MITX_ROOT_URL }/modx/problem/${ id }/problem_save',
|
||||
submit_data,
|
||||
function(data) {
|
||||
if(data.success) {
|
||||
alert('Saved');
|
||||
}});
|
||||
log_event('problem_save', submit_data);
|
||||
});
|
||||
}
|
||||
|
||||
function ${ id }_load() {
|
||||
$('#main_${ id }').load('${ ajax_url }problem_get?id=${ id }', ${ id }_content_updated);
|
||||
}
|
||||
|
||||
$(function() {
|
||||
${ id }_load();
|
||||
});
|
||||
@@ -1 +1 @@
|
||||
<section id="problem_${element_id}" class="problems-wrapper" problem-id="${id}" data-url="${ajax_url}"></section>
|
||||
<section id="problem_${element_id}" class="problems-wrapper" data-problem-id="${id}" data-url="${ajax_url}"></section>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<%block name="js_extra">
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
new Sequence('${item_id}', '${element_id}', ${items}, '${tag}', ${position});
|
||||
new Sequence('${item_id}', 'sequence_${element_id}', ${items}, '${tag}', ${position});
|
||||
});
|
||||
</script>
|
||||
</%block>
|
||||
|
||||
Reference in New Issue
Block a user