Commit 4af33259 by David Baumgold

Move display logic into view

Model shouldn't know about things like close buttons, visibility, and so on
parent 52898734
../../../templates/js/alert.underscore
\ No newline at end of file
../../../templates/js/notification.underscore
\ No newline at end of file
../../../templates/js/prompt.underscore
\ No newline at end of file
../../../templates/js/system-feedback.underscore
\ No newline at end of file
...@@ -11,18 +11,6 @@ describe "CMS.Models.SystemFeedback", -> ...@@ -11,18 +11,6 @@ describe "CMS.Models.SystemFeedback", ->
it "should not have a type set by default", -> it "should not have a type set by default", ->
expect(@model.get("type")).toBeNull() expect(@model.get("type")).toBeNull()
it "should be shown by default", ->
expect(@model.get("shown")).toEqual(true)
it "should trigger a change event on calling .hide()", ->
spy = jasmine.createSpy()
@model.on("change", spy)
@model.hide()
expect(@model.get("shown")).toEqual(false)
expect(spy).toHaveBeenCalled()
describe "CMS.Models.WarningMessage", -> describe "CMS.Models.WarningMessage", ->
beforeEach -> beforeEach ->
......
describe "CMS.Views.SystemFeedback", -> describe "CMS.Views.Alert as base class", ->
tpl = readFixtures('system-feedback.underscore') tpl = readFixtures('alert.underscore')
# CMS.Views.SystemFeedback looks for a template on the page when the code
# is loaded, and even if we set that template on the page using a fixture,
# CMS.Views.SystemFeedback has already been loaded, and so that template
# won't be picked up. This is a dirty hack, to load that template into
# the code after the code has been loaded already.
CMS.Views.SystemFeedback.prototype.template = _.template(tpl)
beforeEach -> beforeEach ->
setFixtures(sandbox({id: "page-notification"})) setFixtures(sandbox({id: "page-alert"}))
appendSetFixtures($("<script>", {id: "alert-tpl", type: "text/template"}).text(tpl))
@model = new CMS.Models.ConfirmationMessage({ @model = new CMS.Models.ConfirmationMessage({
"title": "Portal" "title": "Portal"
"message": "Welcome to the Aperture Science Computer-Aided Enrichment Center" "message": "Welcome to the Aperture Science Computer-Aided Enrichment Center"
close: true
}) })
# it will be interesting to see when this.render is called, so lets spy on it # it will be interesting to see when this.render is called, so lets spy on it
spyOn(CMS.Views.SystemFeedback.prototype, 'render').andCallThrough() spyOn(CMS.Views.Alert.prototype, 'render').andCallThrough()
it "renders on initalize", -> it "renders on initalize", ->
view = new CMS.Views.Notification({model: @model}) view = new CMS.Views.Alert({model: @model})
expect(view.render).toHaveBeenCalled() expect(view.render).toHaveBeenCalled()
it "renders the template", -> it "renders the template", ->
view = new CMS.Views.Notification({model: @model}) view = new CMS.Views.Alert({model: @model})
expect(view.$(".action-close")).toBeDefined() expect(view.$(".action-close")).toBeDefined()
expect(view.$('.wrapper')).toHaveClass("is-shown") expect(view.$('.wrapper')).toHaveClass("is-shown")
text = view.$el.text() text = view.$el.text()
...@@ -31,26 +25,40 @@ describe "CMS.Views.SystemFeedback", -> ...@@ -31,26 +25,40 @@ describe "CMS.Views.SystemFeedback", ->
expect(text).toMatch(/Aperture Science/) expect(text).toMatch(/Aperture Science/)
it "close button sends a .hide() message", -> it "close button sends a .hide() message", ->
spyOn(CMS.Views.SystemFeedback.prototype, 'hide').andCallThrough() spyOn(CMS.Views.Alert.prototype, 'hide').andCallThrough()
view = new CMS.Views.Notification({model: @model}) view = new CMS.Views.Alert({model: @model})
view.$(".action-close").click() view.$(".action-close").click()
expect(CMS.Views.SystemFeedback.prototype.hide).toHaveBeenCalled() expect(CMS.Views.Alert.prototype.hide).toHaveBeenCalled()
expect(view.$('.wrapper')).not.toHaveClass("is-shown") expect(view.$('.wrapper')).not.toHaveClass("is-shown")
expect(view.$('.wrapper')).toHaveClass("is-hiding") expect(view.$('.wrapper')).toHaveClass("is-hiding")
describe "CMS.Views.Notification", ->
tpl = readFixtures('notification.underscore')
beforeEach ->
setFixtures(sandbox({id: "page-notification"}))
appendSetFixtures($("<script>", {id: "notification-tpl", type: "text/template"}).text(tpl))
@model = new CMS.Models.ConfirmationMessage({
"title": "Portal"
"message": "Welcome to the Aperture Science Computer-Aided Enrichment Center"
})
# for some reason, expect($("body")) blows up the test runner, so this test # for some reason, expect($("body")) blows up the test runner, so this test
# just exercises the Prompt rather than asserting on anything. Best I can # just exercises the Prompt rather than asserting on anything. Best I can
# do for now. :( # do for now. :(
it "prompt view changes class on body", -> it "changes class on body", ->
# expect($("body")).not.toHaveClass("prompt-is-shown") # expect($("body")).not.toHaveClass("prompt-is-shown")
view = new CMS.Views.Prompt({model: @model}) view = new CMS.Views.Prompt({model: @model})
# expect($("body")).toHaveClass("prompt-is-shown") # expect($("body")).toHaveClass("prompt-is-shown")
@model.hide() view.hide()
# expect($("body")).not.toHaveClass("prompt-is-shown") # expect($("body")).not.toHaveClass("prompt-is-shown")
describe "SystemFeedback click events", -> describe "CMS.Views.Alert click events", ->
tpl = readFixtures('alert.underscore')
beforeEach -> beforeEach ->
@model = new CMS.Models.WarningMessage( @model = new CMS.Models.WarningMessage(
title: "Unsaved", title: "Unsaved",
...@@ -69,6 +77,7 @@ describe "SystemFeedback click events", -> ...@@ -69,6 +77,7 @@ describe "SystemFeedback click events", ->
) )
setFixtures(sandbox({id: "page-alert"})) setFixtures(sandbox({id: "page-alert"}))
appendSetFixtures($("<script>", {id: "alert-tpl", type: "text/template"}).text(tpl))
@view = new CMS.Views.Alert({model: @model}) @view = new CMS.Views.Alert({model: @model})
it "should trigger the primary event on a primary click", -> it "should trigger the primary event on a primary click", ->
......
...@@ -2,11 +2,7 @@ CMS.Models.SystemFeedback = Backbone.Model.extend({ ...@@ -2,11 +2,7 @@ CMS.Models.SystemFeedback = Backbone.Model.extend({
defaults: { defaults: {
"type": null, // "warning", "confirmation", "error", "announcement", "step-required", etc "type": null, // "warning", "confirmation", "error", "announcement", "step-required", etc
"title": "", "title": "",
"message": "", "message": ""
"shown": true,
"close": false, // show a close button?
"icon": true, // show an icon?
"status": false // example: "saving" popup
/* could also have an "actions" hash: here is an example demonstrating /* could also have an "actions" hash: here is an example demonstrating
the expected structure the expected structure
"actions": { "actions": {
...@@ -31,12 +27,6 @@ CMS.Models.SystemFeedback = Backbone.Model.extend({ ...@@ -31,12 +27,6 @@ CMS.Models.SystemFeedback = Backbone.Model.extend({
] ]
} }
*/ */
},
show: function() {
this.set("shown", true);
},
hide: function() {
this.set("shown", false);
} }
}); });
......
...@@ -25,18 +25,19 @@ CMS.Models.Section = Backbone.Model.extend({ ...@@ -25,18 +25,19 @@ CMS.Models.Section = Backbone.Model.extend({
if(!this.msg) { if(!this.msg) {
this.msg = new CMS.Models.SystemFeedback({ this.msg = new CMS.Models.SystemFeedback({
type: "saving", type: "saving",
title: "Saving&hellip;", title: "Saving&hellip;"
icon: true,
status: true
}); });
} }
if(!this.msgView) { if(!this.msgView) {
this.msgView = new CMS.Views.Notification({model: this.msg}); this.msgView = new CMS.Views.Notification({
model: this.msg,
closeIcon: false
});
} }
this.msg.show(); this.msgView.show();
}, },
hideNotification: function() { hideNotification: function() {
if(!this.msg) { return; } if(!this.msgView) { return; }
this.msg.hide(); this.msgView.hide();
} }
}); });
CMS.Views.SystemFeedback = Backbone.View.extend({ CMS.Views.Alert = Backbone.View.extend({
options: {
type: "alert",
shown: true, // is this view currently being shown?
closeIcon: true // should we render a close button in the top right corner?
},
initialize: function() { initialize: function() {
this.setElement($("#page-"+this.type)); this.template = _.template($("#"+this.options.type+"-tpl").text()),
this.setElement($("#page-"+this.options.type));
this.listenTo(this.model, 'change', this.render); this.listenTo(this.model, 'change', this.render);
return this.render(); return this.render();
}, },
template: _.template($("#system-feedback-tpl").text()),
render: function() { render: function() {
var attrs = $.extend({}, this.model.attributes); var attrs = $.extend({}, this.options, this.model.attributes);
if(attrs.type) {
attrs.modelType = attrs.type;
delete attrs.type;
}
attrs.viewType = this.type;
this.$el.html(this.template(attrs)); this.$el.html(this.template(attrs));
return this; return this;
}, },
...@@ -20,8 +20,13 @@ CMS.Views.SystemFeedback = Backbone.View.extend({ ...@@ -20,8 +20,13 @@ CMS.Views.SystemFeedback = Backbone.View.extend({
"click .action-primary": "primaryClick", "click .action-primary": "primaryClick",
"click .action-secondary": "secondaryClick" "click .action-secondary": "secondaryClick"
}, },
show: function() {
this.options.shown = true;
return this.render();
},
hide: function() { hide: function() {
this.model.hide(); this.options.shown = false;
return this.render();
}, },
primaryClick: function() { primaryClick: function() {
var actions = this.model.get("actions"); var actions = this.model.get("actions");
...@@ -29,7 +34,7 @@ CMS.Views.SystemFeedback = Backbone.View.extend({ ...@@ -29,7 +34,7 @@ CMS.Views.SystemFeedback = Backbone.View.extend({
var primary = actions.primary; var primary = actions.primary;
if(!primary) { return; } if(!primary) { return; }
if(primary.click) { if(primary.click) {
primary.click.call(this.model); primary.click.call(this.model, this);
} }
}, },
secondaryClick: function(e) { secondaryClick: function(e) {
...@@ -46,19 +51,22 @@ CMS.Views.SystemFeedback = Backbone.View.extend({ ...@@ -46,19 +51,22 @@ CMS.Views.SystemFeedback = Backbone.View.extend({
} }
var secondary = this.model.get("actions").secondary[i]; var secondary = this.model.get("actions").secondary[i];
if(secondary.click) { if(secondary.click) {
secondary.click.call(this.model); secondary.click.call(this.model, this);
} }
} }
}); });
CMS.Views.Alert = CMS.Views.SystemFeedback.extend({ CMS.Views.Notification = CMS.Views.Alert.extend({
type: "alert" options: $.extend({}, CMS.Views.Alert.prototype.options, {
}); type: "notification",
CMS.Views.Notification = CMS.Views.SystemFeedback.extend({ closeIcon: false
type: "notification" })
}); });
CMS.Views.Prompt = CMS.Views.SystemFeedback.extend({ CMS.Views.Prompt = CMS.Views.Alert.extend({
options: $.extend({}, CMS.Views.Alert.prototype.options, {
type: "prompt", type: "prompt",
closeIcon: false
}),
render: function() { render: function() {
if(!window.$body) { window.$body = $(document.body); } if(!window.$body) { window.$body = $(document.body); }
if(this.model.get('shown')) { if(this.model.get('shown')) {
...@@ -67,6 +75,6 @@ CMS.Views.Prompt = CMS.Views.SystemFeedback.extend({ ...@@ -67,6 +75,6 @@ CMS.Views.Prompt = CMS.Views.SystemFeedback.extend({
$body.removeClass('prompt-is-shown'); $body.removeClass('prompt-is-shown');
} }
// super() in Javascript has awkward syntax :( // super() in Javascript has awkward syntax :(
return CMS.Views.SystemFeedback.prototype.render.apply(this, arguments); return CMS.Views.Alert.prototype.render.apply(this, arguments);
} }
}); });
...@@ -32,8 +32,14 @@ ...@@ -32,8 +32,14 @@
<%include file="courseware_vendor_js.html"/> <%include file="courseware_vendor_js.html"/>
## js templates ## js templates
<script id="system-feedback-tpl" type="text/template"> <script id="alert-tpl" type="text/template">
<%static:include path="js/system-feedback.underscore" /> <%static:include path="js/alert.underscore" />
</script>
<script id="notification-tpl" type="text/template">
<%static:include path="js/notification.underscore" />
</script>
<script id="prompt-tpl" type="text/template">
<%static:include path="js/prompt.underscore" />
</script> </script>
## javascript ## javascript
...@@ -68,8 +74,8 @@ $(document).ajaxError(function(event, jqXHR, ajaxSettings, thrownError) { ...@@ -68,8 +74,8 @@ $(document).ajaxError(function(event, jqXHR, ajaxSettings, thrownError) {
"actions": { "actions": {
"primary": { "primary": {
"text": "Dismiss", "text": "Dismiss",
"click": function() { "click": function(view) {
this.hide(); view.hide();
} }
} }
} }
......
<div class="wrapper <div class="wrapper wrapper-alert wrapper-alert-<%= type %>
wrapper-<%= viewType %>
wrapper-<%= viewType %>-<%= modelType %>
<% if(obj.status) { %>wrapper-<%= viewType %>-status <% } %>
<% if(obj.shown) { %>is-shown<% } else { %>is-hiding<% } %> <% if(obj.shown) { %>is-shown<% } else { %>is-hiding<% } %>
<% if(obj.fleeting) { %>is-fleeting<% } %>" id="alert-<%= type %>"
id="<%= viewType %>-<%= modelType %>"
aria-hidden="<% if(obj.shown) { %>false<% } else { %>true<% } %>" aria-hidden="<% if(obj.shown) { %>false<% } else { %>true<% } %>"
aria-labelledby="<%= viewType %>-<%= modelType %>-title" aria-labelledby="alert-<%= type %>-title"
<% if (obj.message) { %>aria-describedby="<%= viewType %>-<%= modelType %>-description" <% } %> <% if (obj.message) { %>aria-describedby="alert-<%= type %>-description" <% } %>
<% if (obj.actions) { %>role="dialog"<% } %> <% if (obj.actions) { %>role="dialog"<% } %>
> >
<div class="<%= modelType %> <%= viewType %> <% if(obj.actions) { %>has-actions <% } %>"> <div class="alert <%= type %> <% if(obj.actions) { %>has-actions<% } %>">
<% if (icon) { %>
<% var iconText = {"warning": "&#x26A0;", "confirmation": "&#x2713;", "error": "&#x26A0;", "announcement": "&#x1F4E2;", "step-required": "&#xE0D1", "help": "&#x2753;", "saving": "&#x2699;"} %> <% var iconText = {"warning": "&#x26A0;", "confirmation": "&#x2713;", "error": "&#x26A0;", "announcement": "&#x1F4E2;", "step-required": "&#xE0D1", "help": "&#x2753;", "saving": "&#x2699;"} %>
<i class="ss-icon ss-symbolicons-block icon icon-<%= modelType %>"><%= iconText[modelType] %></i> <i class="ss-icon ss-symbolicons-block icon icon-<%= type %>"><%= iconText[type] %></i>
<% } %>
<div class="copy"> <div class="copy">
<h2 class="title title-3" id="<%= viewType %>-<%= modelType %>-title"><%= title %></h2> <h2 class="title title-3" id="alert-<%= type %>-title"><%= title %></h2>
<% if(obj.message) { %><p class="message" id="<%= viewType %>-<%= modelType %>-description"><%= message %></p><% } %> <% if(obj.message) { %><p class="message" id="alert-<%= type %>-description"><%= message %></p><% } %>
</div> </div>
<% if(obj.actions) { %> <% if(obj.actions) { %>
<nav class="nav-actions"> <nav class="nav-actions">
<h3 class="sr"><%= viewType %> Actions</h3> <h3 class="sr">Alert Actions</h3>
<ul> <ul>
<% if(actions.primary) { %> <% if(actions.primary) { %>
<li class="nav-item"> <li class="nav-item">
...@@ -41,10 +35,10 @@ ...@@ -41,10 +35,10 @@
</nav> </nav>
<% } %> <% } %>
<% if(obj.close) { %> <% if(obj.closeIcon) { %>
<a href="#" rel="view" class="action action-close action-<%= viewType %>-close"> <a href="#" rel="view" class="action action-close action-alert-close">
<i class="ss-icon ss-symbolicons-block icon icon-close">&#x2421;</i> <i class="ss-icon ss-symbolicons-block icon icon-close">&#x2421;</i>
<span class="label">close <%= viewType %></span> <span class="label">close alert</span>
</a> </a>
<% } %> <% } %>
</div> </div>
......
<div class="wrapper wrapper-notification wrapper-notification-<%= type %>
<% if(obj.shown) { %>is-shown<% } else { %>is-hiding<% } %>
<% if(_.contains(['help', 'saving'], type)) { %>wrapper-notification-status<% } %>"
id="notification-<%= type %>"
aria-hidden="<% if(obj.shown) { %>false<% } else { %>true<% } %>"
aria-labelledby="notification-<%= type %>-title"
<% if (obj.message) { %>aria-describedby="notification-<%= type %>-description" <% } %>
<% if (obj.actions) { %>role="dialog"<% } %>
>
<div class="notification <%= type %> <% if(obj.actions) { %>has-actions<% } %>">
<% var iconText = {"warning": "&#x26A0;", "confirmation": "&#x2713;", "error": "&#x26A0;", "announcement": "&#x1F4E2;", "step-required": "&#xE0D1", "help": "&#x2753;", "saving": "&#x2699;"} %>
<i class="ss-icon ss-symbolicons-block icon icon-<%= type %>"><%= iconText[type] %></i>
<div class="copy">
<h2 class="title title-3" id="notification-<%= type %>-title"><%= title %></h2>
<% if(obj.message) { %><p class="message" id="notification-<%= type %>-description"><%= message %></p><% } %>
</div>
<% if(obj.actions) { %>
<nav class="nav-actions">
<h3 class="sr">Notification Actions</h3>
<ul>
<% if(actions.primary) { %>
<li class="nav-item">
<a href="#" class="button action-primary <%= actions.primary.class %>"><%= actions.primary.text %></a>
</li>
<% } %>
<% if(actions.secondary) {
_.each(actions.secondary, function(secondary) { %>
<li class="nav-item">
<a href="#" class="button action-secondary <%= secondary.class %>"><%= secondary.text %></a>
</li>
<% });
} %>
</ul>
</nav>
<% } %>
<% if(obj.closeIcon) { %>
<a href="#" rel="view" class="action action-close action-notification-close">
<i class="ss-icon ss-symbolicons-block icon icon-close">&#x2421;</i>
<span class="label">close notification</span>
</a>
<% } %>
</div>
</div>
<div class="wrapper wrapper-prompt wrapper-prompt-<%= type %>
<% if(obj.shown) { %>is-shown<% } else { %>is-hiding<% } %>
id="prompt-<%= type %>"
aria-hidden="<% if(obj.shown) { %>false<% } else { %>true<% } %>"
aria-labelledby="prompt-<%= type %>-title"
<% if (obj.message) { %>aria-describedby="prompt-<%= type %>-description" <% } %>
<% if (obj.actions) { %>role="dialog"<% } %>
>
<div class="prompt <%= type %> <% if(obj.actions) { %>has-actions<% } %>">
<div class="copy">
<h2 class="title title-3" id="prompt-<%= type %>-title"><%= title %></h2>
<% if(obj.message) { %><p class="message" id="prompt-<%= type %>-description"><%= message %></p><% } %>
</div>
<% if(obj.actions) { %>
<nav class="nav-actions">
<h3 class="sr">Prompt Actions</h3>
<ul>
<% if(actions.primary) { %>
<li class="nav-item">
<a href="#" class="button action-primary <%= actions.primary.class %>"><%= actions.primary.text %></a>
</li>
<% } %>
<% if(actions.secondary) {
_.each(actions.secondary, function(secondary) { %>
<li class="nav-item">
<a href="#" class="button action-secondary <%= secondary.class %>"><%= secondary.text %></a>
</li>
<% });
} %>
</ul>
</nav>
<% } %>
</div>
</div>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment