Skip to content

Commit

Permalink
implementing the Header sub-application
Browse files Browse the repository at this point in the history
  • Loading branch information
davidsulc committed Oct 22, 2013
1 parent 5899800 commit fef637d
Show file tree
Hide file tree
Showing 9 changed files with 324 additions and 7 deletions.
1 change: 1 addition & 0 deletions assets/js/app.js
@@ -1,6 +1,7 @@
var ContactManager = new Marionette.Application();

ContactManager.addRegions({
headerRegion: "#header-region",
mainRegion: "#main-region",
dialogRegion: Marionette.Region.Dialog.extend({
el: "#dialog-region"
Expand Down
1 change: 1 addition & 0 deletions assets/js/apps/about/about_app.js
Expand Up @@ -8,6 +8,7 @@ ContactManager.module("AboutApp", function(AboutApp, ContactManager, Backbone, M
var API = {
showAbout: function(){
AboutApp.Show.Controller.showAbout();
ContactManager.execute("set:active:header", "about");
}
};

Expand Down
3 changes: 3 additions & 0 deletions assets/js/apps/contacts/contacts_app.js
Expand Up @@ -10,14 +10,17 @@ ContactManager.module("ContactsApp", function(ContactsApp, ContactManager, Backb
var API = {
listContacts: function(criterion){
ContactsApp.List.Controller.listContacts(criterion);
ContactManager.execute("set:active:header", "contacts");
},

showContact: function(id){
ContactsApp.Show.Controller.showContact(id);
ContactManager.execute("set:active:header", "contacts");
},

editContact: function(id){
ContactsApp.Edit.Controller.editContact(id);
ContactManager.execute("set:active:header", "contacts");
}
};

Expand Down
15 changes: 15 additions & 0 deletions assets/js/apps/header/header_app.js
@@ -0,0 +1,15 @@
ContactManager.module("HeaderApp", function(Header, ContactManager, Backbone, Marionette, $, _){
var API = {
listHeader: function(){
Header.List.Controller.listHeader();
}
};

ContactManager.commands.setHandler("set:active:header", function(name){
ContactManager.HeaderApp.List.Controller.setActiveHeader(name);
});

Header.on("start", function(){
API.listHeader();
});
});
26 changes: 26 additions & 0 deletions assets/js/apps/header/list/list_controller.js
@@ -0,0 +1,26 @@
ContactManager.module("HeaderApp.List", function(List, ContactManager, Backbone, Marionette, $, _){
List.Controller = {
listHeader: function(){
var links = ContactManager.request("header:entities");
var headers = new List.Headers({collection: links});

headers.on("brand:clicked", function(){
ContactManager.trigger("contacts:list");
});

headers.on("itemview:navigate", function(childView, model){
var trigger = model.get("navigationTrigger");
ContactManager.trigger(trigger);
});

ContactManager.headerRegion.show(headers);
},

setActiveHeader: function(headerUrl){
var links = ContactManager.request("header:entities");
var headerToSelect = links.find(function(header){ return header.get("url") === headerUrl; });
headerToSelect.select();
links.trigger("reset");
}
};
});
38 changes: 38 additions & 0 deletions assets/js/apps/header/list/list_view.js
@@ -0,0 +1,38 @@
ContactManager.module("HeaderApp.List", function(List, ContactManager, Backbone, Marionette, $, _){
List.Header = Marionette.ItemView.extend({
template: "#header-link",
tagName: "li",

events: {
"click a": "navigate"
},

navigate: function(e){
e.preventDefault();
this.trigger("navigate", this.model);
},

onRender: function(){
if(this.model.selected){
// add class so Bootstrap will highlight the active entry in the navbar
this.$el.addClass("active");
};
}
});

List.Headers = Marionette.CompositeView.extend({
template: "#header-template",
className: "navbar navbar-inverse navbar-fixed-top",
itemView: List.Header,
itemViewContainer: "ul",

events: {
"click a.brand": "brandClicked"
},

brandClicked: function(e){
e.preventDefault();
this.trigger("brand:clicked");
}
});
});
37 changes: 37 additions & 0 deletions assets/js/entities/header.js
@@ -0,0 +1,37 @@
ContactManager.module("Entities", function(Entities, ContactManager, Backbone, Marionette, $, _){
Entities.Header = Backbone.Model.extend({
initialize: function(){
var selectable = new Backbone.Picky.Selectable(this);
_.extend(this, selectable);
}
});

Entities.HeaderCollection = Backbone.Collection.extend({
model: Entities.Header,

initialize: function(){
var singleSelect = new Backbone.Picky.SingleSelect(this);
_.extend(this, singleSelect);
}
});

var initializeHeaders = function(){
Entities.headers = new Entities.HeaderCollection([
{ name: "Contacts", url: "contacts", navigationTrigger: "contacts:list" },
{ name: "About", url: "about", navigationTrigger: "about:show" }
]);
};

var API = {
getHeaders: function(){
if(Entities.headers === undefined){
initializeHeaders();
}
return Entities.headers;
}
};

ContactManager.reqres.setHandler("header:entities", function(){
return API.getHeaders();
});
});
181 changes: 181 additions & 0 deletions assets/js/vendor/backbone.picky.js
@@ -0,0 +1,181 @@
Backbone.Picky = (function (Backbone, _) {
var Picky = {};

// Picky.SingleSelect
// ------------------
// A single-select mixin for Backbone.Collection, allowing a single
// model to be selected within a collection. Selection of another
// model within the collection causes the previous model to be
// deselected.

Picky.SingleSelect = function(collection){
this.collection = collection;
};

_.extend(Picky.SingleSelect.prototype, {

// Select a model, deselecting any previously
// select model
select: function(model){
if (model && this.selected === model) { return; }

this.deselect();

this.selected = model;
this.selected.select();
this.trigger("selected", model);
},

// Deselect a model, resulting in no model
// being selected
deselect: function(model){
if (!this.selected){ return; }

model = model || this.selected;
if (this.selected !== model){ return; }

this.selected.deselect();
this.trigger("deselected", this.selected);
delete this.selected;
}

});

// Picky.MultiSelect
// -----------------
// A mult-select mixin for Backbone.Collection, allowing a collection to
// have multiple items selected, including `selectAll` and `selectNone`
// capabilities.

Picky.MultiSelect = function (collection) {
this.collection = collection;
this.selected = {};
};

_.extend(Picky.MultiSelect.prototype, {

// Select a specified model, make sure the
// model knows it's selected, and hold on to
// the selected model.
select: function (model) {
if (this.selected[model.cid]) { return; }

this.selected[model.cid] = model;
model.select();
calculateSelectedLength(this);
},

// Deselect a specified model, make sure the
// model knows it has been deselected, and remove
// the model from the selected list.
deselect: function (model) {
if (!this.selected[model.cid]) { return; }

delete this.selected[model.cid];
model.deselect();
calculateSelectedLength(this);
},

// Select all models in this collection
selectAll: function () {
this.each(function (model) { model.select(); });
calculateSelectedLength(this);
},

// Deselect all models in this collection
selectNone: function () {
if (this.selectedLength === 0) { return; }
this.each(function (model) { model.deselect(); });
calculateSelectedLength(this);
},

// Toggle select all / none. If some are selected, it
// will select all. If all are selected, it will select
// none. If none are selected, it will select all.
toggleSelectAll: function () {
if (this.selectedLength === this.length) {
this.selectNone();
} else {
this.selectAll();
}
}
});

// Picky.Selectable
// ----------------
// A selectable mixin for Backbone.Model, allowing a model to be selected,
// enabling it to work with Picky.MultiSelect or on it's own

Picky.Selectable = function (model) {
this.model = model;
};

_.extend(Picky.Selectable.prototype, {

// Select this model, and tell our
// collection that we're selected
select: function () {
if (this.selected) { return; }

this.selected = true;
this.trigger("selected");

if (this.collection) {
this.collection.select(this);
}
},

// Deselect this model, and tell our
// collection that we're deselected
deselect: function () {
if (!this.selected) { return; }

this.selected = false;
this.trigger("deselected");

if (this.collection) {
this.collection.deselect(this);
}
},

// Change selected to the opposite of what
// it currently is
toggleSelected: function () {
if (this.selected) {
this.deselect();
} else {
this.select();
}
}
});

// Helper Methods
// --------------

// Calculate the number of selected items in a collection
// and update the collection with that length. Trigger events
// from the collection based on the number of selected items.
var calculateSelectedLength = function (collection) {
collection.selectedLength = _.size(collection.selected);

var selectedLength = collection.selectedLength;
var length = collection.length;

if (selectedLength === length) {
collection.trigger("select:all", collection);
return;
}

if (selectedLength === 0) {
collection.trigger("select:none", collection);
return;
}

if (selectedLength > 0 && selectedLength < length) {
collection.trigger("select:some", collection);
return;
}
};

return Picky;
})(Backbone, _);
29 changes: 22 additions & 7 deletions index.html
Expand Up @@ -10,20 +10,29 @@

<body>

<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<span class="brand">Contact manager</span>
</div>
</div>
</div>
<div id="header-region"></div>

<div id="main-region" class="container">
<p>Here is static content in the web page. You'll notice that it gets replaced by our app as soon as we start it.</p>
</div>

<div id="dialog-region"></div>

<script type="text/template" id="header-template">
<div class="navbar-inner">
<div class="container">
<a class="brand" href="#contacts">Contact manager</a>
<div class="nav-collapse collapse">
<ul class="nav"></ul>
</div>
</div>
</div>
</script>

<script type="text/template" id="header-link">
<a href="#<%= url %>"><%= name %></a>
</script>

<script type="text/template" id="contact-list">
<thead>
<tr>
Expand Down Expand Up @@ -122,6 +131,7 @@ <h1>About this application</h1>
<script src="./assets/js/vendor/json2.js"></script>
<script src="./assets/js/vendor/underscore.js"></script>
<script src="./assets/js/vendor/backbone.js"></script>
<script src="./assets/js/vendor/backbone.picky.js"></script>
<script src="./assets/js/vendor/backbone.syphon.js"></script>
<script src="./assets/js/vendor/backbone.localstorage.js"></script>
<script src="./assets/js/vendor/backbone.marionette.js"></script>
Expand All @@ -132,6 +142,7 @@ <h1>About this application</h1>
<script src="./assets/js/app.js"></script>
<script src="./assets/js/apps/config/storage/localstorage.js"></script>
<script src="./assets/js/entities/common.js"></script>
<script src="./assets/js/entities/header.js"></script>
<script src="./assets/js/entities/contact.js"></script>
<script src="./assets/js/common/views.js"></script>

Expand All @@ -149,6 +160,10 @@ <h1>About this application</h1>
<script src="./assets/js/apps/about/show/show_view.js"></script>
<script src="./assets/js/apps/about/show/show_controller.js"></script>

<script src="./assets/js/apps/header/header_app.js"></script>
<script src="./assets/js/apps/header/list/list_view.js"></script>
<script src="./assets/js/apps/header/list/list_controller.js"></script>

<script type="text/javascript">
ContactManager.start();
</script>
Expand Down

0 comments on commit fef637d

Please sign in to comment.