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 confirmDialog: function(view, event) {
32 $("#xos-confirm-dialog").dialog({
36 "Confirm" : function() {
\r
37 $(this).dialog("close");
\r
38 view.trigger(event);
\r
40 "Cancel" : function() {
\r
41 $(this).dialog("close");
\r
45 $("#xos-confirm-dialog").dialog("open");
48 hideError: function() {
49 if (this.logWindowId) {
51 $(this.errorBoxId).hide();
52 $(this.successBoxId).hide();
56 showSuccess: function(result) {
57 result["statusclass"] = "success";
58 if (this.logTableId) {
59 this.appendLogWindow(result);
61 $(this.successBoxId).show();
62 $(this.successBoxId).html(_.template($(this.successTemplate).html())(result));
64 $(this.successCloseButtonId).unbind().bind('click', function() {
65 $(that.successBoxId).hide();
70 popupErrorDialog: function(responseText) {
71 $("#xos-error-dialog").html(templateFromId("#xos-error-response")($.parseJSON(responseText)));
72 $("#xos-error-dialog").dialog({
75 Ok: function() { $(this).dialog("close"); }
80 showError: function(result) {
81 result["statusclass"] = "failure";
82 if (this.logTableId) {
83 this.appendLogWindow(result);
84 this.popupErrorDialog(result.responseText);
86 // this is really old stuff
87 $(this.errorBoxId).show();
88 $(this.errorBoxId).html(_.template($(this.errorTemplate).html())(result));
90 $(this.errorCloseButtonId).unbind().bind('click', function() {
91 $(that.errorBoxId).hide();
96 showInformational: function(result) {
97 result["statusclass"] = "inprog";
98 if (this.logTableId) {
99 return this.appendLogWindow(result);
105 appendLogWindow: function(result) {
106 // compute a new logMessageId for this log message
107 logMessageId = "logMessage" + this.logMessageCount;
108 this.logMessageCount = this.logMessageCount + 1;
109 result["logMessageId"] = logMessageId;
111 logMessageTemplate=$("#xos-log-template").html();
112 assert(logMessageTemplate != undefined, "logMessageTemplate is undefined");
113 newRow = _.template(logMessageTemplate, result);
114 assert(newRow != undefined, "newRow is undefined");
116 if (result["infoMsgId"] != undefined) {
117 // We were passed the logMessageId of an informational message,
118 // and the caller wants us to replace that message with our own.
119 // i.e. replace an informational message with a success or an error.
120 $("#"+result["infoMsgId"]).replaceWith(newRow);
122 // Create a brand new log message rather than replacing one.
123 logTableBody = $(this.logTableId + " tbody");
124 logTableBody.prepend(newRow);
127 if (this.statusMsgId) {
128 $(this.statusMsgId).html( templateFromId("#xos-status-template")(result) );
131 limitTableRows(this.logTableId, 5);
136 hideLinkedItems: function(result) {
139 this["linkedObjs" + (index+1)].empty();
\r
144 listViewShower: function(listViewName, collection_name, regionName, title) {
\r
146 return function() {
\r
147 app[regionName].show(new app[listViewName]);
\r
148 app.hideLinkedItems();
\r
149 $("#contentTitle").html(templateFromId("#xos-title-list")({"title": title}));
\r
150 $("#detail").show();
\r
151 $("#xos-listview-button-box").show();
\r
153 $("#xos-detail-button-box").hide();
\r
157 addShower: function(detailName, collection_name, regionName, title) {
\r
159 return function() {
\r
160 model = new xos[collection_name].model();
\r
161 detailViewClass = app[detailName];
\r
162 detailView = new detailViewClass({model: model, collection:xos[collection_name]});
\r
163 app[regionName].show(detailView);
\r
164 $("#xos-detail-button-box").show();
\r
165 $("#xos-listview-button-box").hide();
\r
169 detailShower: function(detailName, collection_name, regionName, title) {
\r
171 showModelId = function(model_id) {
\r
172 $("#contentTitle").html(templateFromId("#xos-title-detail")({"title": title}));
\r
174 collection = xos[collection_name];
\r
175 model = collection.get(model_id);
\r
176 if (model == undefined) {
\r
177 app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name}));
\r
179 detailViewClass = app[detailName];
\r
180 detailView = new detailViewClass({model: model});
\r
181 app[regionName].show(detailView);
\r
182 detailView.showLinkedItems();
\r
183 $("#xos-detail-button-box").show();
\r
184 $("#xos-listview-button-box").hide();
\r
187 return showModelId;
\r
193 app - MarionetteApplication
194 template - template (See XOSHelper.html)
197 XOSDetailView = Marionette.ItemView.extend({
200 events: {"click button.btn-xos-save-continue": "submitContinueClicked",
201 "click button.btn-xos-save-leave": "submitLeaveClicked",
202 "click button.btn-xos-save-another": "submitAddAnotherClicked",
203 "click button.btn-xos-delete": "deleteClicked",
204 "change input": "inputChanged"},
206 initialize: function() {
207 this.on('deleteConfirmed', this.deleteConfirmed);
210 /* inputChanged is watching the onChange events of the input controls. We
211 do this to track when this view is 'dirty', so we can throw up a warning
\r
212 if the user tries to change his slices without saving first.
\r
215 inputChanged: function(e) {
\r
219 saveError: function(model, result, xhr, infoMsgId) {
\r
220 result["what"] = "save " + model.modelName + " " + model.attributes.humanReadableName;
\r
221 result["infoMsgId"] = infoMsgId;
\r
222 this.app.showError(result);
\r
225 saveSuccess: function(model, result, xhr, infoMsgId) {
\r
226 result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};
\r
227 result["what"] = "save " + model.modelName + " " + model.attributes.humanReadableName;
\r
228 result["infoMsgId"] = infoMsgId;
\r
229 this.app.showSuccess(result);
\r
232 destroyError: function(model, result, xhr, infoMsgId) {
233 result["what"] = "destroy " + model.modelName + " " + model.attributes.humanReadableName;
\r
234 result["infoMsgId"] = infoMsgId;
\r
235 this.app.showError(result);
\r
238 destroySuccess: function(model, result, xhr, infoMsgId) {
\r
239 result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};
\r
240 result["what"] = "destroy " + model.modelName + " " + model.attributes.humanReadableName;
\r
241 result["infoMsgId"] = infoMsgId;
\r
242 this.app.showSuccess(result);
\r
245 submitContinueClicked: function(e) {
246 console.log("saveContinue");
251 submitLeaveClicked: function(e) {
252 console.log("saveLeave");
255 this.app.navigate("list", this.model.modelName);
258 submitAddAnotherClicked: function(e) {
259 console.log("saveAnother");
262 this.app.navigate("add", this.model.modelName);
266 this.app.hideError();
267 var infoMsgId = this.app.showInformational( {what: "save " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
\r
268 var data = Backbone.Syphon.serialize(this);
\r
270 var isNew = !this.model.id;
\r
271 this.model.save(data, {error: function(model, result, xhr) { that.saveError(model,result,xhr,infoMsgId);},
\r
272 success: function(model, result, xhr) { that.saveSuccess(model,result,xhr,infoMsgId);}});
\r
274 console.log(this.model);
\r
275 this.collection.add(this.model);
\r
276 this.collection.sort();
\r
278 this.dirty = false;
\r
281 destroyModel: function() {
282 this.app.hideError();
283 var infoMsgId = this.app.showInformational( {what: "destroy " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
285 this.model.destroy({error: function(model, result, xhr) { that.destroyError(model,result,xhr,infoMsgId);},
286 success: function(model, result, xhr) { that.destroySuccess(model,result,xhr,infoMsgId);}});
289 deleteClicked: function(e) {
291 \r this.app.confirmDialog(this, "deleteConfirmed");
294 \r deleteConfirmed: function() {
295 \r modelName = this.model.modelName;
296 \r this.destroyModel();
297 \r this.app.navigate("list", modelName);
300 tabClick: function(tabId, regionName) {
301 region = this.app[regionName];
\r
302 if (this.currentTabRegion != undefined) {
\r
303 this.currentTabRegion.$el.hide();
\r
305 if (this.currentTabId != undefined) {
\r
306 $(this.currentTabId).removeClass('active');
\r
308 this.currentTabRegion = region;
\r
309 this.currentTabRegion.$el.show();
\r
311 this.currentTabId = tabId;
\r
312 $(tabId).addClass('active');
\r
315 showTabs: function(tabs) {
316 template = templateFromId("#xos-tabs-template", {tabs: tabs});
317 $("#tabs").html(template(tabs));
320 _.each(tabs, function(tab) {
321 var regionName = tab["region"];
322 var tabId = '#xos-nav-'+regionName;
323 $(tabId).bind('click', function() { that.tabClick(tabId, regionName); });
329 showLinkedItems: function() {
332 tabs.push({name: "details", region: "detail"});
335 for (relatedName in this.model.collection.relatedCollections) {
\r
336 relatedField = this.model.collection.relatedCollections[relatedName];
\r
337 regionName = "linkedObjs" + (index+1);
\r
339 relatedListViewClassName = relatedName + "ListView";
\r
340 assert(this.app[relatedListViewClassName] != undefined, relatedListViewClassName + " not found");
\r
341 relatedListViewClass = this.app[relatedListViewClassName].extend({collection: xos[relatedName].filterBy(relatedField,this.model.id)});
\r
342 this.app[regionName].show(new relatedListViewClass());
\r
343 if (this.app.hideTabsByDefault) {
\r
344 this.app[regionName].$el.hide();
\r
346 tabs.push({name: relatedName, region: regionName});
\r
351 this.app["linkedObjs" + (index+1)].empty();
\r
355 this.showTabs(tabs);
\r
356 this.tabClick('#xos-nav-detail', 'detail');
\r
362 This is for items that will be displayed as table rows.
364 app - MarionetteApplication
365 template - template (See XOSHelper.html)
366 detailClass - class of detail view, probably an XOSDetailView
369 XOSItemView = Marionette.ItemView.extend({
371 className: 'test-tablerow',
373 events: {"click": "changeItem"},
375 changeItem: function(e) {
\r
376 this.app.hideError();
\r
377 e.preventDefault();
\r
378 e.stopPropagation();
\r
380 this.app.navigateToModel(this.app, this.detailClass, this.detailNavLink, this.model);
\r
386 app - MarionetteApplication
387 childView - class of ItemView, probably an XOSItemView
388 template - template (see xosHelper.html)
389 collection - collection that holds these objects
390 title - title to display in template
393 XOSListView = Marionette.CompositeView.extend({
394 childViewContainer: 'tbody',
\r
396 events: {"click button.btn-xos-add": "addClicked",
\r
397 "click button.btn-xos-refresh": "refreshClicked",
\r
400 _fetchStateChange: function() {
\r
401 if (this.collection.fetching) {
\r
402 $("#xos-list-title-spinner").show();
\r
404 $("#xos-list-title-spinner").hide();
\r
408 addClicked: function(e) {
410 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
413 \r refreshClicked: function(e) {
414 \r e.preventDefault();
415 \r this.collection.refresh(refreshRelated=true);
418 \r initialize: function() {
419 \r this.listenTo(this.collection, 'change', this._renderChildren)
420 this.listenTo(this.collection, 'fetchStateChange', this._fetchStateChange);
422 // Because many of the templates use idToName(), we need to
423 // listen to the collections that hold the names for the ids
424 // that we want to display.
425 for (i in this.collection.foreignCollections) {
426 foreignName = this.collection.foreignCollections[i];
427 if (xos[foreignName] == undefined) {
428 console.log("Failed to find xos class " + foreignName);
430 this.listenTo(xos[foreignName], 'change', this._renderChildren);
431 this.listenTo(xos[foreignName], 'sort', this._renderChildren);
435 templateHelpers: function() {
436 return { title: this.title };
440 /* Give an id, the name of a collection, and the name of a field for models
441 within that collection, lookup the id and return the value of the field.
444 idToName = function(id, collectionName, fieldName) {
445 linkedObject = xos[collectionName].get(id);
446 if (linkedObject == undefined) {
449 return linkedObject.attributes[fieldName];
453 /* Constructs lists of <option> html blocks for items in a collection.
455 selectedId = the id of an object that should be selected, if any
456 collectionName = name of collection
457 fieldName = name of field within models of collection that will be displayed
460 idToOptions = function(selectedId, collectionName, fieldName) {
462 for (index in xos[collectionName].models) {
463 linkedObject = xos[collectionName].models[index];
464 linkedId = linkedObject["id"];
465 linkedName = linkedObject.attributes[fieldName];
466 if (linkedId == selectedId) {
467 selected = " selected";
471 result = result + '<option value="' + linkedId + '"' + selected + '>' + linkedName + '</option>';
476 /* Constructs an html <select> and the <option>s to go with it.
478 variable = variable name to return to form
479 selectedId = the id of an object that should be selected, if any
480 collectionName = name of collection
481 fieldName = name of field within models of collection that will be displayed
484 idToSelect = function(variable, selectedId, collectionName, fieldName) {
485 result = '<select name="' + variable + '">' +
486 idToOptions(selectedId, collectionName, fieldName) +