1 function assert(outcome, description) {
3 console.log(description);
7 function templateFromId(id) {
8 return _.template($(id).html());
11 function firstCharUpper(s) {
12 return s.charAt(0).toUpperCase() + s.slice(1);
15 HTMLView = Marionette.ItemView.extend({
17 this.$el.append(this.options.html);
21 XOSApplication = Marionette.Application.extend({
22 detailBoxId: "#detailBox",
23 errorBoxId: "#errorBox",
24 errorCloseButtonId: "#close-error-box",
25 successBoxId: "#successBox",
26 successCloseButtonId: "#close-success-box",
27 errorTemplate: "#xos-error-template",
28 successTemplate: "#xos-success-template",
31 hideError: function() {
32 if (this.logWindowId) {
34 $(this.errorBoxId).hide();
35 $(this.successBoxId).hide();
39 showSuccess: function(result) {
40 result["success"] = "success";
41 if (this.logTableId) {
42 this.appendLogWindow(result);
44 $(this.successBoxId).show();
45 $(this.successBoxId).html(_.template($(this.successTemplate).html())(result));
47 $(this.successCloseButtonId).unbind().bind('click', function() {
48 $(that.successBoxId).hide();
53 showError: function(result) {
54 result["success"] = "failure";
55 if (this.logTableId) {
56 this.appendLogWindow(result);
58 $(this.errorBoxId).show();
59 $(this.errorBoxId).html(_.template($(this.errorTemplate).html())(result));
61 $(this.errorCloseButtonId).unbind().bind('click', function() {
62 $(that.errorBoxId).hide();
67 showInformational: function(result) {
68 result["success"] = "information";
69 if (this.logTableId) {
70 return this.appendLogWindow(result);
76 appendLogWindow: function(result) {
77 // compute a new logMessageId for this log message
78 logMessageId = "logMessage" + this.logMessageCount;
79 this.logMessageCount = this.logMessageCount + 1;
80 result["logMessageId"] = logMessageId;
82 logMessageTemplate=$("#xos-log-template").html();
83 assert(logMessageTemplate != undefined, "logMessageTemplate is undefined");
84 newRow = _.template(logMessageTemplate, result);
85 assert(newRow != undefined, "newRow is undefined");
87 if (result["infoMsgId"] != undefined) {
88 // We were passed the logMessageId of an informational message,
89 // and the caller wants us to replace that message with our own.
90 // i.e. replace an informational message with a success or an error.
91 $("#"+result["infoMsgId"]).replaceWith(newRow);
93 // Create a brand new log message rather than replacing one.
94 logTableBody = $(this.logTableId + " tbody");
95 logTableBody.prepend(newRow);
98 if (this.statusMsgId) {
99 $(this.statusMsgId).html( templateFromId("#xos-status-template")(result) );
105 hideLinkedItems: function(result) {
108 this["linkedObjs" + (index+1)].empty();
\r
113 listViewShower: function(listViewName, collection_name, regionName, title) {
\r
115 return function() {
\r
116 app[regionName].show(new app[listViewName]);
\r
117 app.hideLinkedItems();
\r
118 $("#contentTitle").html(templateFromId("#xos-title-list")({"title": title}));
\r
119 $("#detail").show();
\r
120 $("#xos-listview-button-box").show();
\r
122 $("#xos-detail-button-box").hide();
\r
126 addShower: function(detailName, collection_name, regionName, title) {
\r
128 return function() {
\r
129 detailViewClass = app[detailName];
\r
130 detailView = new detailViewClass();
\r
131 app[regionName].show(detailView);
\r
135 detailShower: function(detailName, collection_name, regionName, title) {
\r
137 showModelId = function(model_id) {
\r
138 showModel = function(model) {
\r
139 detailViewClass = app[detailName];
\r
140 detailView = new detailViewClass({model: model});
\r
141 app[regionName].show(detailView);
\r
142 detailView.showLinkedItems();
\r
143 $("#xos-detail-button-box").show();
\r
144 $("#xos-listview-button-box").hide();
\r
147 $("#contentTitle").html(templateFromId("#xos-title-detail")({"title": title}));
\r
149 collection = xos[collection_name];
\r
150 model = collection.get(model_id);
\r
151 if (model == undefined) {
\r
152 if (!collection.isLoaded) {
\r
153 // If the model cannot be found, then maybe it's because
\r
154 // we haven't finished loading the collection yet. So wait for
\r
155 // the sort event to complete, then try again.
\r
156 collection.once("sort", function() {
\r
157 collection = xos[collection_name];
\r
158 model = collection.get(model_id);
\r
159 if (model == undefined) {
\r
160 // We tried. It's not here. Complain to the user.
\r
161 app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name}));
\r
167 // The collection was loaded, the user must just be asking for something we don't have.
\r
168 app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name}));
\r
174 return showModelId;
\r
180 app - MarionetteApplication
181 template - template (See XOSHelper.html)
184 XOSDetailView = Marionette.ItemView.extend({
187 events: {"click button.btn-xos-save-continue": "submitContinueClicked",
188 "click button.btn-xos-save-leave": "submitLeaveClicked",
189 "click button.btn-xos-save-another": "submitAddAnotherClicked",
190 "change input": "inputChanged"},
192 /* inputChanged is watching the onChange events of the input controls. We
193 do this to track when this view is 'dirty', so we can throw up a warning
\r
194 if the user tries to change his slices without saving first.
\r
197 inputChanged: function(e) {
\r
201 saveError: function(model, result, xhr, infoMsgId) {
\r
202 result["what"] = "save " + model.__proto__.modelName;
\r
203 result["infoMsgId"] = infoMsgId;
\r
204 this.app.showError(result);
\r
207 saveSuccess: function(model, result, xhr, infoMsgId) {
\r
208 result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};
\r
209 result["what"] = "save " + model.__proto__.modelName;
\r
210 result["infoMsgId"] = infoMsgId;
\r
211 this.app.showSuccess(result);
\r
214 submitContinueClicked: function(e) {
215 console.log("saveContinue");
220 submitLeaveClicked: function(e) {
221 console.log("saveLeave");
226 submitAddAnotherClicked: function(e) {
227 console.log("saveAnother");
233 this.app.hideError();
234 var infoMsgId = this.app.showInformational( {what: "save " + this.model.__proto__.modelName, status: "", statusText: "in progress..."} );
\r
235 var data = Backbone.Syphon.serialize(this);
\r
237 this.model.save(data, {error: function(model, result, xhr) { that.saveError(model,result,xhr,infoMsgId);},
\r
238 success: function(model, result, xhr) { that.saveSuccess(model,result,xhr,infoMsgId);}});
\r
239 this.dirty = false;
\r
242 tabClick: function(tabId, regionName) {
243 region = this.app[regionName];
\r
244 if (this.currentTabRegion != undefined) {
\r
245 this.currentTabRegion.$el.hide();
\r
247 if (this.currentTabId != undefined) {
\r
248 $(this.currentTabId).removeClass('active');
\r
250 this.currentTabRegion = region;
\r
251 this.currentTabRegion.$el.show();
\r
253 this.currentTabId = tabId;
\r
254 $(tabId).addClass('active');
\r
257 showTabs: function(tabs) {
258 template = templateFromId("#xos-tabs-template", {tabs: tabs});
259 $("#tabs").html(template(tabs));
262 _.each(tabs, function(tab) {
263 var regionName = tab["region"];
264 var tabId = '#xos-nav-'+regionName;
265 $(tabId).bind('click', function() { that.tabClick(tabId, regionName); });
271 showLinkedItems: function() {
274 tabs.push({name: "details", region: "detail"});
277 for (relatedName in this.model.collection.relatedCollections) {
\r
278 relatedField = this.model.collection.relatedCollections[relatedName];
\r
279 regionName = "linkedObjs" + (index+1);
\r
281 relatedListViewClassName = relatedName + "ListView";
\r
282 assert(this.app[relatedListViewClassName] != undefined, relatedListViewClassName + " not found");
\r
283 relatedListViewClass = this.app[relatedListViewClassName].extend({collection: xos[relatedName].filterBy(relatedField,this.model.id)});
\r
284 this.app[regionName].show(new relatedListViewClass());
\r
285 if (this.app.hideTabsByDefault) {
\r
286 this.app[regionName].$el.hide();
\r
288 tabs.push({name: relatedName, region: regionName});
\r
293 this.app["linkedObjs" + (index+1)].empty();
\r
297 this.showTabs(tabs);
\r
298 this.tabClick('#xos-nav-detail', 'detail');
\r
304 This is for items that will be displayed as table rows.
306 app - MarionetteApplication
307 template - template (See XOSHelper.html)
308 detailClass - class of detail view, probably an XOSDetailView
311 XOSItemView = Marionette.ItemView.extend({
313 className: 'test-tablerow',
315 events: {"click": "changeItem"},
317 changeItem: function(e) {
\r
318 this.app.hideError();
\r
319 e.preventDefault();
\r
320 e.stopPropagation();
\r
322 this.app.navigateToModel(this.app, this.detailClass, this.detailNavLink, this.model);
\r
328 app - MarionetteApplication
329 childView - class of ItemView, probably an XOSItemView
330 template - template (see xosHelper.html)
331 collection - collection that holds these objects
332 title - title to display in template
335 XOSListView = Marionette.CompositeView.extend({
336 childViewContainer: 'tbody',
\r
338 events: {"click button.btn-xos-add": "addClicked",
\r
341 addClicked: function(e) {
344 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName));
347 initialize: function() {
\r
348 this.listenTo(this.collection, 'change', this._renderChildren)
350 // Because many of the templates use idToName(), we need to
351 // listen to the collections that hold the names for the ids
352 // that we want to display.
353 for (i in this.collection.foreignCollections) {
354 foreignName = this.collection.foreignCollections[i];
355 if (xos[foreignName] == undefined) {
356 console.log("Failed to find xos class " + foreignName);
358 this.listenTo(xos[foreignName], 'change', this._renderChildren);
359 this.listenTo(xos[foreignName], 'sort', this._renderChildren);
363 templateHelpers: function() {
364 return { title: this.title };
368 /* Give an id, the name of a collection, and the name of a field for models
369 within that collection, lookup the id and return the value of the field.
372 idToName = function(id, collectionName, fieldName) {
373 linkedObject = xos[collectionName].get(id);
374 if (linkedObject == undefined) {
377 return linkedObject.attributes[fieldName];
381 /* Constructs lists of <option> html blocks for items in a collection.
383 selectedId = the id of an object that should be selected, if any
384 collectionName = name of collection
385 fieldName = name of field within models of collection that will be displayed
388 idToOptions = function(selectedId, collectionName, fieldName) {
390 for (index in xos[collectionName].models) {
391 linkedObject = xos[collectionName].models[index];
392 linkedId = linkedObject["id"];
393 linkedName = linkedObject.attributes[fieldName];
394 if (linkedId == selectedId) {
395 selected = " selected";
399 result = result + '<option value="' + linkedId + '"' + selected + '>' + linkedName + '</option>';
404 /* Constructs an html <select> and the <option>s to go with it.
406 variable = variable name to return to form
407 selectedId = the id of an object that should be selected, if any
408 collectionName = name of collection
409 fieldName = name of field within models of collection that will be displayed
412 idToSelect = function(variable, selectedId, collectionName, fieldName) {
413 result = '<select name="' + variable + '">' +
414 idToOptions(selectedId, collectionName, fieldName) +