var TeamMember = function (options) { var options = options || {}; this.teamMemberName = ko.observable(options.teamMemberName || ""); this.startDate = ko.observable(options.startDate || moment().format("MM/DD/YYYY")); this.id = 0; }; var BudgetViewModel = function(){ var _developmentBudget = ko.observable(); var _serverBudget = ko.observable(); var _softwareBudget = ko.observable(); return { developmentBudget: _developmentBudget, serverBudget: _serverBudget, softwareBudget: _softwareBudget }; }; var ProjectViewModel = function () { var _projectName = ko.observable(); // We need to trigger PostalJS and send a message. We'll use Knockout for that _projectName.subscribe(function(newValue){ // Now use PostalJS to push message out postal.publish({ channel: "project", topic: "edit.projectNameChange", data: { projectName: _projectName() } }); }); var _projectDescription = ko.observable(); // teamSize has changed to a KnockoutJS observable. This will be updated // by our PostalJS subscription now var _teamSize = ko.observable(0); // PostalJS Subscriptions. Respond to changes in projectTeam, update teamSize var teamUpdateSubscription = postal.subscribe({ channel: "project", topic: "edit.teamUpdate", callback: function(data, envelope) { _teamSize(data.projectTeam.length); } }); return { // We also have eliminated our public functions in ProjectViewModel! // Just have our observables now projectName: _projectName, projectDescription: _projectDescription, teamSize: _teamSize }; }; var TeamViewModel = function(){ var _projectName = ko.observable(); var _projectTeam = ko.observableArray(); // This is a Knockout subscription - NOT a Postaljs. // We use Knockout to trigger the postaljs publish event _projectTeam.subscribe(function(){ // this is how postaljs publishes events postal.publish({ channel: "project", topic: "edit.teamUpdate", data: { projectTeam: ko.toJS(_projectTeam) } }); }); var _currentTeamMember = new TeamMember({ teamMemberName: "Jethro", startDate: moment().format("MM/DD/YYYY") }); var addTeamMember = function () { var newbie = new TeamMember({ teamMemberName: _currentTeamMember.teamMemberName(), startDate: _currentTeamMember.startDate() }); newbie.id = _projectTeam().length + 1; console.log(newbie); _projectTeam.push(newbie); _currentTeamMember.teamMemberName(""); }; // PostalJS subscriptions var projectNameChangeSubscription = postal.subscribe({ channel: "project", topic: "edit.projectNameChange", callback: function(data, envelope) { _projectName(data.projectName); } }); return { projectTeam: _projectTeam, projectName: _projectName, currentTeamMember: _currentTeamMember, // public functions addTeamMember: addTeamMember }; }; var TimelineViewModel = function(){ // Look mom - no observables! var _calendar; var initCalendar = function () { if (!_calendar) { _calendar = $('#calendar').fullCalendar({ allDayText: 'Items', defaultView: "month", defaultEventMinutes: 30, header: { left: 'prev,next today', center: 'title', ignoreTimeZone: false, }, events: [{title: "test", start: moment().format()}] }); } }; var populateCalendar = function (entries) { var length = entries.length; _calendar.fullCalendar("removeEvents"); for (var i = 0; i < length; i++) { var event = {}; var entry = entries[i]; event.title = entry.teamMemberName + " starts"; event.start = moment(entry.startDate); event.allDay = true; _calendar.fullCalendar("renderEvent", event, true); console.log(event); } }; var refreshCalendar = function () { initCalendar(); // Bootstrap tab hinders calendar rendering - need to nudge it to render $('.fc-today-button').click(); $("#calendar").fullCalendar('rerenderEvents'); }; // PostalJS subscriptions var teamUpdateSubscription = postal.subscribe({ channel: "project", topic: "edit.teamUpdate", callback: function(data, envelope) { populateCalendar(data.projectTeam); } }); // public functions return { refreshCalendar: refreshCalendar }; }; $(document).ready(function () { var projectViewModel = new ProjectViewModel(); var budgetViewModel = new BudgetViewModel(); var teamViewModel = new TeamViewModel(); var timelineViewModel = new TimelineViewModel(); ko.applyBindings(projectViewModel, $("#project-info")[0]); ko.applyBindings(budgetViewModel, $("#budget")[0]); ko.applyBindings(teamViewModel, $("#team")[0]); timelineViewModel.refreshCalendar(); $("#apparea").on('shown.bs.tab', 'a[data-toggle="tab"]', function (e) { timelineViewModel.refreshCalendar(); }); });
<div style="padding: 8px;"> <div id="apparea" role="tabpanel"> <!-- Nav tabs --> <ul class="nav nav-tabs" role="tablist"> <li role="presentation" class="active"><a href="#project-info" aria-controls="project-info" role="tab" data-toggle="tab">Project Info</a> </li> <li role="presentation"><a href="#budget" aria-controls="budget" role="tab" data-toggle="tab">Budget</a> </li> <li role="presentation"><a href="#team" aria-controls="team" role="tab" data-toggle="tab">Team</a> </li> <li role="presentation"><a href="#timeline" aria-controls="timeline" role="tab" data-toggle="tab">Timeline</a> </li> </ul> <!-- Tab panes --> <div class="tab-content demo"> <div role="tabpanel" class="tab-pane active" id="project-info"> <p> <label>Project Name</label> <br/> <input data-bind="value: $root.projectName"/> </p> <p> <label>Project Description</label> <br/> <textarea rows="3" cols="50" data-bind="value: projectDescription"></textarea> </p> <p> <label>Team Size: </label> <span data-bind="text: $root.teamSize"></span> </p> </div> <div role="tabpanel" class="tab-pane" id="budget"> <p> <label>Development Budget</label> <br/> <input data-bind="value: $root.developmentBudget"/> </p> <p> <label>Server Budget</label> <br/> <input data-bind="value: $root.serverBudget" /> </p> <label>Software Budget</label> <br/> <input data-bind="value: $root.softwareBudget" /> </p> </div> <div role="tabpanel" class="tab-pane" id="team"> <p> <span class="team-members">Team Members for Project: <span data-bind="text: $root.projectName" class="project-name"></span></span> </p> <p> <label>Team Member</label> <input data-bind="value: $root.currentTeamMember.teamMemberName"></input> <label>Start Date</label> <input data-bind="value: $root.currentTeamMember.startDate"></input> <button type="button" data-bind="click: $root.addTeamMember">Add Team Member</button </p> <table id="team-roster" class="table table-striped"> <thead> <tr> <th>Team Member</th> <th>Start Date</th> </tr> </thead> <tbody data-bind="foreach: $root.projectTeam"> <tr> <td><span data-bind="text: $data.teamMemberName"></span> </td> <td><span data-bind="text: $data.startDate"></span> </td> </tr> </tbody> </table> </div> <div role="tabpanel" class="tab-pane" id="timeline"> <div id="calendar"></div> </div> </div> </div> </div>
div.demo { margin-top: 8px; } span.team-members { font-size: 14px; font-weight: bold; } span.project-name { font-style: italic; }