Skip to content
Snippets Groups Projects
Commit 1840cce6 authored by Jacob Poul Richardt's avatar Jacob Poul Richardt
Browse files

Added next button feedback.

parent 5649fbd9
Branches
No related tags found
No related merge requests found
......@@ -10,11 +10,17 @@
<!-- /ko -->
<div class="panel-footer clearfix">
<div class="pull-left">
<button type="button" data-bind="enable: IsPreviousSlideEnabled, click: GoToPreviousSlide, visible: IsPreviousSlideVisible" class="btn btn-primary">Previous</button>
<button type="button" data-bind="enable: IsPreviousSlideEnabled, click: GoToPreviousSlide, visible: IsPreviousSlideVisible" class="btn btn-primary">
<span class="glyphicon glyphicon-circle-arrow-left" aria-hidden="true"></span>
Previous
</button>
</div>
<div class="pull-right">
<span data-bind="text: SlideNumber"></span> of <span data-bind="text: NumberOfSlides"></span>
<button type="button" data-bind="enable: IsNextSlideEnabled, click: GoToNextSlide, visible: IsNextSlideVisible" class="btn btn-primary">Next</button>
<button type="button" data-bind="enable: IsNextSlideEnabled, click: GoToNextSlide, visible: IsNextSlideVisible" class="btn btn-primary">
Next
<span class="glyphicon glyphicon-circle-arrow-right" aria-hidden="true" data-bind="css: {Rotating: IsWaitingForNext}"></span>
</button>
<button type="button" data-bind="click: Close, visible: IsCloseExperimentVisible" class="btn btn-primary">Close</button>
</div>
</div>
......
......@@ -5,22 +5,26 @@ define(["require", "exports", "knockout", "Managers/Experiment", "Models/Slide"]
this.SlideData = knockout.observable();
this.AreAllQuestionsAnswered = knockout.observable(false);
this.IsHighlighted = knockout.observable(false);
this.IsWaitingForNext = knockout.observable(false);
this._subscriptions = [];
this.IsLoadingSlide = knockout.computed(function () { return _this.SlideData() == null; });
this.SlideIndex = ExperimentManager.CurrentSlideIndex;
this.SlideNumber = knockout.computed(function () { return _this.SlideIndex() + 1; });
this.NumberOfSlides = ExperimentManager.NumberOfSlides;
this.IsWaiting = knockout.computed(function () { return _this.IsWaitingForNext(); });
this.IsPreviousSlideVisible = knockout.computed(function () { return ExperimentManager.GoToPreviousSlideEnabled() && !ExperimentManager.CloseSlidesEnabled(); });
this.IsPreviousSlideEnabled = knockout.computed(function () { return _this.IsPreviousSlideVisible() && !_this.IsLoadingSlide() && _this.SlideIndex() !== 0; });
this.IsPreviousSlideEnabled = knockout.computed(function () { return _this.IsPreviousSlideVisible() && !_this.IsLoadingSlide() && _this.SlideIndex() !== 0 && !_this.IsWaiting(); });
this.IsNextSlideVisible = knockout.computed(function () { return _this.SlideNumber() !== _this.NumberOfSlides(); });
this.IsNextSlideEnabled = knockout.computed(function () { return _this.IsNextSlideVisible() && !_this.IsLoadingSlide(); });
this.IsNextSlideEnabled = knockout.computed(function () { return _this.IsNextSlideVisible() && !_this.IsLoadingSlide() && !_this.IsWaiting(); });
this.IsCloseExperimentVisible = knockout.computed(function () { return ExperimentManager.IsExperimentCompleted() && ExperimentManager.CloseExperimentEnabled(); });
this.IsCloseExperimentEnabled = knockout.computed(function () { return _this.IsCloseExperimentVisible() && !_this.IsWaiting(); });
this.Title = ExperimentManager.SlideTitle;
this.HasTitle = knockout.computed(function () { return _this.Title() !== ""; });
this._experimentMangerIsReadySubscription = ExperimentManager.IsReady.subscribe(function (r) {
this._subscriptions.push(ExperimentManager.IsReady.subscribe(function (r) {
if (!r)
return;
_this.LoadNextSlide();
});
}));
this.IsHighlighted.subscribe(function (value) {
if (value)
setTimeout(function () { return _this.IsHighlighted(false); }, 3000);
......@@ -30,18 +34,22 @@ define(["require", "exports", "knockout", "Managers/Experiment", "Models/Slide"]
}
SlideShell.prototype.GoToNextSlide = function () {
var _this = this;
if (this.AreAllQuestionsAnswered()) {
this.LoadNextSlide();
this.IsWaitingForNext(true);
this.DoWhenDone(function () { return !_this.IsLoadingSlide() && !_this.SlideData().IsWorking(); }, function () {
_this.IsWaitingForNext(false);
if (_this.AreAllQuestionsAnswered()) {
_this.LoadNextSlide();
}
else {
this.SlideData().ScrollToFirstInvalidAnswer();
if (this.IsHighlighted()) {
this.IsHighlighted(false);
_this.SlideData().ScrollToFirstInvalidAnswer();
if (_this.IsHighlighted()) {
_this.IsHighlighted(false);
setTimeout(function () { return _this.IsHighlighted(true); }, 50);
}
else
this.IsHighlighted(true);
_this.IsHighlighted(true);
}
});
};
SlideShell.prototype.LoadNextSlide = function () {
var _this = this;
......@@ -53,6 +61,17 @@ define(["require", "exports", "knockout", "Managers/Experiment", "Models/Slide"]
this.UnloadSlide(false);
ExperimentManager.LoadPreviousSlide(function (index, questions) { return _this.SlideData(new SlideModel("Slides/Default", index, _this.AreAllQuestionsAnswered, questions)); });
};
SlideShell.prototype.DoWhenDone = function (check, action) {
if (check()) {
action();
return;
}
var sub = knockout.computed(check).subscribe(function (v) {
sub.dispose();
action();
});
this._subscriptions.push(sub);
};
SlideShell.prototype.UnloadSlide = function (complete) {
this.IsHighlighted(false);
if (complete && this.SlideData() != null) {
......@@ -64,13 +83,8 @@ define(["require", "exports", "knockout", "Managers/Experiment", "Models/Slide"]
SlideShell.prototype.Close = function () {
ExperimentManager.Close();
};
SlideShell.prototype.CleanExperimentLoaded = function () {
this._experimentMangerIsReadySubscription.dispose();
this._experimentMangerIsReadySubscription = null;
};
SlideShell.prototype.dispose = function () {
if (this._experimentMangerIsReadySubscription)
this.CleanExperimentLoaded();
this._subscriptions.forEach(function (s) { return s.dispose(); });
};
return SlideShell;
})();
......
......@@ -21,9 +21,12 @@ class SlideShell
public IsNextSlideVisible: KnockoutComputed<boolean>;
public IsNextSlideEnabled: KnockoutComputed<boolean>;
public IsCloseExperimentVisible: KnockoutComputed<boolean>;
public IsCloseExperimentEnabled: KnockoutComputed<boolean>;
public IsHighlighted: KnockoutObservable<boolean> = knockout.observable(false);
public IsWaiting: KnockoutComputed<boolean>;
public IsWaitingForNext: KnockoutObservable<boolean> = knockout.observable(false);
private _experimentMangerIsReadySubscription: KnockoutSubscription;
private _subscriptions:KnockoutSubscription[] = [];
constructor()
{
......@@ -32,21 +35,24 @@ class SlideShell
this.SlideNumber = knockout.computed(() => this.SlideIndex() + 1);
this.NumberOfSlides = ExperimentManager.NumberOfSlides;
this.IsWaiting = knockout.computed(() => this.IsWaitingForNext());
this.IsPreviousSlideVisible = knockout.computed(() => ExperimentManager.GoToPreviousSlideEnabled() && !ExperimentManager.CloseSlidesEnabled());
this.IsPreviousSlideEnabled = knockout.computed(() => this.IsPreviousSlideVisible() && !this.IsLoadingSlide() && this.SlideIndex() !== 0);
this.IsPreviousSlideEnabled = knockout.computed(() => this.IsPreviousSlideVisible() && !this.IsLoadingSlide() && this.SlideIndex() !== 0 && !this.IsWaiting());
this.IsNextSlideVisible = knockout.computed(() => this.SlideNumber() !== this.NumberOfSlides());
this.IsNextSlideEnabled = knockout.computed(() => this.IsNextSlideVisible() && !this.IsLoadingSlide());
this.IsNextSlideEnabled = knockout.computed(() => this.IsNextSlideVisible() && !this.IsLoadingSlide() && !this.IsWaiting());
this.IsCloseExperimentVisible = knockout.computed(() => ExperimentManager.IsExperimentCompleted() && ExperimentManager.CloseExperimentEnabled());
this.IsCloseExperimentEnabled = knockout.computed(() => this.IsCloseExperimentVisible() && !this.IsWaiting());
this.Title = ExperimentManager.SlideTitle;
this.HasTitle = knockout.computed(() => this.Title() !== "");
this._experimentMangerIsReadySubscription = ExperimentManager.IsReady.subscribe(r =>
this._subscriptions.push(ExperimentManager.IsReady.subscribe(r =>
{
if (!r) return;
this.LoadNextSlide();
});
}));
this.IsHighlighted.subscribe(value =>
{
......@@ -58,6 +64,12 @@ class SlideShell
public GoToNextSlide():void
{
this.IsWaitingForNext(true);
this.DoWhenDone(() => !this.IsLoadingSlide() && !this.SlideData().IsWorking(), () =>
{
this.IsWaitingForNext(false);
if (this.AreAllQuestionsAnswered())
{
this.LoadNextSlide();
......@@ -74,6 +86,7 @@ class SlideShell
else
this.IsHighlighted(true);
}
});
}
private LoadNextSlide():void
......@@ -90,6 +103,21 @@ class SlideShell
ExperimentManager.LoadPreviousSlide((index, questions) => this.SlideData(new SlideModel("Slides/Default", index, this.AreAllQuestionsAnswered, questions)));
}
private DoWhenDone(check:() => boolean, action:() => void):void
{
if (check())
{
action();
return;
}
var sub = knockout.computed(check).subscribe(v =>
{
sub.dispose();
action();
});
this._subscriptions.push(sub);
}
private UnloadSlide(complete:boolean):void
{
this.IsHighlighted(false);
......@@ -108,16 +136,10 @@ class SlideShell
ExperimentManager.Close();
}
private CleanExperimentLoaded():void
{
this._experimentMangerIsReadySubscription.dispose();
this._experimentMangerIsReadySubscription = null;
}
public dispose():void
{
if (this._experimentMangerIsReadySubscription)
this.CleanExperimentLoaded();
this._subscriptions.forEach(s => s.dispose());
}
}
......
......@@ -4,11 +4,13 @@ define(["require", "exports", "knockout", "Models/Question", "Managers/Experimen
var _this = this;
this._uiLessQuestions = [];
this._activeAnsweSets = knockout.observable(0);
this._isWorking = knockout.observable(false);
this.Questions = [];
this._slide = slide;
slide.SlideCompleted = function (callback) { return _this.SlideCompleted(callback); };
slide.ScrollToFirstInvalidAnswerCallback = function () { return _this.ScrollToFirstInvalidAnswer(); };
this.HaveActiveAnswersSet = knockout.computed(function () { return _this._activeAnsweSets() !== 0; });
this.HaveActiveAnswersSets = knockout.computed(function () { return _this._activeAnsweSets() !== 0; });
slide.SetIsWorking(knockout.computed(function () { return _this._isWorking() || _this.HaveActiveAnswersSets(); }));
this.InitializeQuestions(slide.Questions);
}
Default.prototype.InitializeQuestions = function (questions) {
......@@ -39,7 +41,7 @@ define(["require", "exports", "knockout", "Models/Question", "Managers/Experimen
waitForAnswerSaved = this._uiLessQuestions[i].SlideCompleted() || waitForAnswerSaved;
}
if (waitForAnswerSaved) {
var sub = this.HaveActiveAnswersSet.subscribe(function (v) {
var sub = this.HaveActiveAnswersSets.subscribe(function (v) {
if (!v) {
sub.dispose();
completed();
......@@ -61,8 +63,10 @@ define(["require", "exports", "knockout", "Models/Question", "Managers/Experimen
ExperimentManager.SaveQuestionAnswer(question.Id, question.Answer(), function (success) {
if (!success)
question.HasValidAnswer(false);
_this._isWorking(true);
_this._activeAnsweSets(_this._activeAnsweSets() - 1);
_this.CheckIfAllQuestionsAreAnswered();
_this._isWorking(false);
});
}
this.CheckIfAllQuestionsAreAnswered();
......@@ -75,7 +79,7 @@ define(["require", "exports", "knockout", "Models/Question", "Managers/Experimen
return null;
};
Default.prototype.CheckIfAllQuestionsAreAnswered = function () {
this._slide.CanGoToNextSlide(this.GetFirstQuestionWithoutValidAnswer() == null && !this.HaveActiveAnswersSet());
this._slide.CanGoToNextSlide(this.GetFirstQuestionWithoutValidAnswer() == null && !this.HaveActiveAnswersSets());
};
return Default;
})();
......
......@@ -10,9 +10,10 @@ class Default
private _slide: SlideModel;
private _uiLessQuestions: IQuestionViewModel[] = [];
private _activeAnsweSets: KnockoutObservable<number> = knockout.observable(0);
private _isWorking:KnockoutObservable<boolean> = knockout.observable(false);
public Questions: QuestionModel[] = [];
public HaveActiveAnswersSet:KnockoutComputed<boolean>;
public HaveActiveAnswersSets:KnockoutComputed<boolean>;
constructor(slide: SlideModel)
{
......@@ -20,7 +21,8 @@ class Default
slide.SlideCompleted = callback => this.SlideCompleted(callback);
slide.ScrollToFirstInvalidAnswerCallback = () => this.ScrollToFirstInvalidAnswer();
this.HaveActiveAnswersSet = knockout.computed(() => this._activeAnsweSets() !== 0);
this.HaveActiveAnswersSets = knockout.computed(() => this._activeAnsweSets() !== 0);
slide.SetIsWorking(knockout.computed(() => this._isWorking() || this.HaveActiveAnswersSets()));
this.InitializeQuestions(slide.Questions);
}
......@@ -63,7 +65,7 @@ class Default
if (waitForAnswerSaved)
{
var sub = this.HaveActiveAnswersSet.subscribe(v =>
var sub = this.HaveActiveAnswersSets.subscribe(v =>
{
if (!v)
{
......@@ -91,8 +93,11 @@ class Default
ExperimentManager.SaveQuestionAnswer(question.Id, question.Answer(), success =>
{
if (!success) question.HasValidAnswer(false);
this._isWorking(true);
this._activeAnsweSets(this._activeAnsweSets() - 1);
this.CheckIfAllQuestionsAreAnswered();
this._isWorking(false);
});
}
......@@ -111,7 +116,7 @@ class Default
private CheckIfAllQuestionsAreAnswered():void
{
this._slide.CanGoToNextSlide(this.GetFirstQuestionWithoutValidAnswer() == null && !this.HaveActiveAnswersSet());
this._slide.CanGoToNextSlide(this.GetFirstQuestionWithoutValidAnswer() == null && !this.HaveActiveAnswersSets());
}
}
......
define(["require", "exports"], function (require, exports) {
define(["require", "exports", "knockout"], function (require, exports, knockout) {
var Slide = (function () {
function Slide(name, index, canGoToNextSlide, questions) {
var _this = this;
if (index === void 0) { index = null; }
if (canGoToNextSlide === void 0) { canGoToNextSlide = null; }
if (questions === void 0) { questions = null; }
this._isWorking = knockout.observable(null);
this.Index = index;
this.Name = name;
this.CanGoToNextSlide = canGoToNextSlide;
this.Questions = questions;
this.IsWorking = knockout.computed(function () { return _this._isWorking() != null ? _this._isWorking()() : false; });
}
Slide.prototype.Complete = function (callback) {
if (this.SlideCompleted != null)
......@@ -17,6 +20,9 @@ define(["require", "exports"], function (require, exports) {
if (this.ScrollToFirstInvalidAnswerCallback != null)
this.ScrollToFirstInvalidAnswerCallback();
};
Slide.prototype.SetIsWorking = function (observeable) {
this._isWorking(observeable);
};
return Slide;
})();
return Slide;
......
......@@ -5,17 +5,21 @@ class Slide
{
public Index:number;
public Name: string;
public IsWorking:KnockoutComputed<boolean>;
public CanGoToNextSlide:KnockoutObservable<boolean>;
public Questions:CockpitPortal.IQuestion[];
public SlideCompleted: (completed: () => void) => void;
public ScrollToFirstInvalidAnswerCallback: () => void;
private _isWorking:KnockoutObservable<KnockoutComputed<boolean>> = knockout.observable(null);
constructor(name: string, index: number = null, canGoToNextSlide: KnockoutObservable<boolean> = null, questions:CockpitPortal.IQuestion[] = null)
{
this.Index = index;
this.Name = name;
this.CanGoToNextSlide = canGoToNextSlide;
this.Questions = questions;
this.IsWorking = knockout.computed(() => this._isWorking() != null ? this._isWorking()() : false);
}
public Complete(callback:()=>void):void
......@@ -27,6 +31,11 @@ class Slide
{
if (this.ScrollToFirstInvalidAnswerCallback != null) this.ScrollToFirstInvalidAnswerCallback();
}
public SetIsWorking(observeable: KnockoutComputed<boolean>): void
{
this._isWorking(observeable);
}
}
export = Slide;
\ No newline at end of file
......@@ -3,16 +3,33 @@
}
.Highlight .DoesNotHaveValidAnswer {
-webkit-animation: Highlight 3s;
animation: Highlight 3s;
}
@keyframes Highlight {
10% {
background: orange
10% { background: orange; }
100% { background: none; }
}
100% {
background: none;
@-webkit-keyframes Highlight {
10% { background: orange; }
100% { background: none; }
}
.Rotating {
-webkit-animation: Rotating 1s linear 0s infinite;
animation: Rotating 1s linear 0s infinite;
}
@keyframes Rotating {
from { transform: rotate(0); }
to { transform: rotate(359deg); }
}
@-webkit-keyframes Rotating {
from { transform: rotate(0); }
to { transform: rotate(359deg); }
}
.HalfColumn {
......
......@@ -5,7 +5,7 @@
<meta charset="utf-8" />
<title>Cockpit Experiments</title>
<script type="text/javascript">
var CacheBuster = 27;
var CacheBuster = 28;
</script>
</head>
<body>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment