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", ->
it "should not have a type set by default", ->
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", ->
beforeEach ->
......
describe "CMS.Views.SystemFeedback", ->
tpl = readFixtures('system-feedback.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)
describe "CMS.Views.Alert as base class", ->
tpl = readFixtures('alert.underscore')
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({
"title": "Portal"
"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
spyOn(CMS.Views.SystemFeedback.prototype, 'render').andCallThrough()
spyOn(CMS.Views.Alert.prototype, 'render').andCallThrough()
it "renders on initalize", ->
view = new CMS.Views.Notification({model: @model})
view = new CMS.Views.Alert({model: @model})
expect(view.render).toHaveBeenCalled()
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.$('.wrapper')).toHaveClass("is-shown")
text = view.$el.text()
......@@ -31,26 +25,40 @@ describe "CMS.Views.SystemFeedback", ->
expect(text).toMatch(/Aperture Science/)
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()
expect(CMS.Views.SystemFeedback.prototype.hide).toHaveBeenCalled()
expect(CMS.Views.Alert.prototype.hide).toHaveBeenCalled()
expect(view.$('.wrapper')).not.toHaveClass("is-shown")
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
# just exercises the Prompt rather than asserting on anything. Best I can
# do for now. :(
it "prompt view changes class on body", ->
it "changes class on body", ->
# expect($("body")).not.toHaveClass("prompt-is-shown")
view = new CMS.Views.Prompt({model: @model})
# expect($("body")).toHaveClass("prompt-is-shown")
@model.hide()
view.hide()
# expect($("body")).not.toHaveClass("prompt-is-shown")
describe "SystemFeedback click events", ->
describe "CMS.Views.Alert click events", ->
tpl = readFixtures('alert.underscore')
beforeEach ->
@model = new CMS.Models.WarningMessage(
title: "Unsaved",
......@@ -69,6 +77,7 @@ describe "SystemFeedback click events", ->
)
setFixtures(sandbox({id: "page-alert"}))
appendSetFixtures($("<script>", {id: "alert-tpl", type: "text/template"}).text(tpl))
@view = new CMS.Views.Alert({model: @model})
it "should trigger the primary event on a primary click", ->
......
......@@ -2,11 +2,7 @@ CMS.Models.SystemFeedback = Backbone.Model.extend({
defaults: {
"type": null, // "warning", "confirmation", "error", "announcement", "step-required", etc
"title": "",
"message": "",
"shown": true,
"close": false, // show a close button?
"icon": true, // show an icon?
"status": false // example: "saving" popup
"message": ""
/* could also have an "actions" hash: here is an example demonstrating
the expected structure
"actions": {
......@@ -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({
if(!this.msg) {
this.msg = new CMS.Models.SystemFeedback({
type: "saving",
title: "Saving&hellip;",
icon: true,
status: true
title: "Saving&hellip;"
});
}
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() {
if(!this.msg) { return; }
this.msg.hide();
if(!this.msgView) { return; }
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() {
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);
return this.render();
},
template: _.template($("#system-feedback-tpl").text()),
render: function() {
var attrs = $.extend({}, this.model.attributes);
if(attrs.type) {
attrs.modelType = attrs.type;
delete attrs.type;
}
attrs.viewType = this.type;
var attrs = $.extend({}, this.options, this.model.attributes);
this.$el.html(this.template(attrs));
return this;
},
......@@ -20,8 +20,13 @@ CMS.Views.SystemFeedback = Backbone.View.extend({
"click .action-primary": "primaryClick",
"click .action-secondary": "secondaryClick"
},
show: function() {
this.options.shown = true;
return this.render();
},
hide: function() {
this.model.hide();
this.options.shown = false;
return this.render();
},
primaryClick: function() {
var actions = this.model.get("actions");
......@@ -29,7 +34,7 @@ CMS.Views.SystemFeedback = Backbone.View.extend({
var primary = actions.primary;
if(!primary) { return; }
if(primary.click) {
primary.click.call(this.model);
primary.click.call(this.model, this);
}
},
secondaryClick: function(e) {
......@@ -46,19 +51,22 @@ CMS.Views.SystemFeedback = Backbone.View.extend({
}
var secondary = this.model.get("actions").secondary[i];
if(secondary.click) {
secondary.click.call(this.model);
secondary.click.call(this.model, this);
}
}
});
CMS.Views.Alert = CMS.Views.SystemFeedback.extend({
type: "alert"
});
CMS.Views.Notification = CMS.Views.SystemFeedback.extend({
type: "notification"
CMS.Views.Notification = CMS.Views.Alert.extend({
options: $.extend({}, CMS.Views.Alert.prototype.options, {
type: "notification",
closeIcon: false
})
});
CMS.Views.Prompt = CMS.Views.SystemFeedback.extend({
type: "prompt",
CMS.Views.Prompt = CMS.Views.Alert.extend({
options: $.extend({}, CMS.Views.Alert.prototype.options, {
type: "prompt",
closeIcon: false
}),
render: function() {
if(!window.$body) { window.$body = $(document.body); }
if(this.model.get('shown')) {
......@@ -67,6 +75,6 @@ CMS.Views.Prompt = CMS.Views.SystemFeedback.extend({
$body.removeClass('prompt-is-shown');
}
// 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 @@
<%include file="courseware_vendor_js.html"/>
## js templates
<script id="system-feedback-tpl" type="text/template">
<%static:include path="js/system-feedback.underscore" />
<script id="alert-tpl" type="text/template">
<%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>
## javascript
......@@ -68,8 +74,8 @@ $(document).ajaxError(function(event, jqXHR, ajaxSettings, thrownError) {
"actions": {
"primary": {
"text": "Dismiss",
"click": function() {
this.hide();
"click": function(view) {
view.hide();
}
}
}
......
<div class="wrapper
wrapper-<%= viewType %>
wrapper-<%= viewType %>-<%= modelType %>
<% if(obj.status) { %>wrapper-<%= viewType %>-status <% } %>
<div class="wrapper wrapper-alert wrapper-alert-<%= type %>
<% if(obj.shown) { %>is-shown<% } else { %>is-hiding<% } %>
<% if(obj.fleeting) { %>is-fleeting<% } %>"
id="<%= viewType %>-<%= modelType %>"
id="alert-<%= type %>"
aria-hidden="<% if(obj.shown) { %>false<% } else { %>true<% } %>"
aria-labelledby="<%= viewType %>-<%= modelType %>-title"
<% if (obj.message) { %>aria-describedby="<%= viewType %>-<%= modelType %>-description" <% } %>
aria-labelledby="alert-<%= type %>-title"
<% if (obj.message) { %>aria-describedby="alert-<%= type %>-description" <% } %>
<% if (obj.actions) { %>role="dialog"<% } %>
>
<div class="<%= modelType %> <%= viewType %> <% if(obj.actions) { %>has-actions <% } %>">
<% if (icon) { %>
<% 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>
<% } %>
<div class="alert <%= 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="<%= viewType %>-<%= modelType %>-title"><%= title %></h2>
<% if(obj.message) { %><p class="message" id="<%= viewType %>-<%= modelType %>-description"><%= message %></p><% } %>
<h2 class="title title-3" id="alert-<%= type %>-title"><%= title %></h2>
<% if(obj.message) { %><p class="message" id="alert-<%= type %>-description"><%= message %></p><% } %>
</div>
<% if(obj.actions) { %>
<nav class="nav-actions">
<h3 class="sr"><%= viewType %> Actions</h3>
<h3 class="sr">Alert Actions</h3>
<ul>
<% if(actions.primary) { %>
<li class="nav-item">
......@@ -41,10 +35,10 @@
</nav>
<% } %>
<% if(obj.close) { %>
<a href="#" rel="view" class="action action-close action-<%= viewType %>-close">
<% if(obj.closeIcon) { %>
<a href="#" rel="view" class="action action-close action-alert-close">
<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>
<% } %>
</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