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 data = Backbone.Syphon.serialize(this);
\r
269 var isNew = !this.model.id;
\r
271 /* although model.validate() is called automatically by
\r
272 model.save, we call it ourselves, so we can throw up our
\r
273 validation error before creating the infoMsg in the log
\r
275 errors = this.model.validate(data);
\r
277 this.onFormDataInvalid(errors);
\r
281 var infoMsgId = this.app.showInformational( {what: "save " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
\r
283 this.model.save(data, {error: function(model, result, xhr) { that.saveError(model,result,xhr,infoMsgId);},
\r
284 success: function(model, result, xhr) { that.saveSuccess(model,result,xhr,infoMsgId);}});
\r
286 console.log(this.model);
\r
287 this.collection.add(this.model);
\r
288 this.collection.sort();
\r
290 this.dirty = false;
\r
293 destroyModel: function() {
294 this.app.hideError();
295 var infoMsgId = this.app.showInformational( {what: "destroy " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
297 this.model.destroy({error: function(model, result, xhr) { that.destroyError(model,result,xhr,infoMsgId);},
298 success: function(model, result, xhr) { that.destroySuccess(model,result,xhr,infoMsgId);}});
301 deleteClicked: function(e) {
303 \r this.app.confirmDialog(this, "deleteConfirmed");
306 \r deleteConfirmed: function() {
307 \r modelName = this.model.modelName;
308 \r this.destroyModel();
309 \r this.app.navigate("list", modelName);
312 tabClick: function(tabId, regionName) {
313 region = this.app[regionName];
\r
314 if (this.currentTabRegion != undefined) {
\r
315 this.currentTabRegion.$el.hide();
\r
317 if (this.currentTabId != undefined) {
\r
318 $(this.currentTabId).removeClass('active');
\r
320 this.currentTabRegion = region;
\r
321 this.currentTabRegion.$el.show();
\r
323 this.currentTabId = tabId;
\r
324 $(tabId).addClass('active');
\r
327 showTabs: function(tabs) {
328 template = templateFromId("#xos-tabs-template", {tabs: tabs});
329 $("#tabs").html(template(tabs));
332 _.each(tabs, function(tab) {
333 var regionName = tab["region"];
334 var tabId = '#xos-nav-'+regionName;
335 $(tabId).bind('click', function() { that.tabClick(tabId, regionName); });
341 showLinkedItems: function() {
344 tabs.push({name: "details", region: "detail"});
347 for (relatedName in this.model.collection.relatedCollections) {
\r
348 relatedField = this.model.collection.relatedCollections[relatedName];
\r
349 regionName = "linkedObjs" + (index+1);
\r
351 relatedListViewClassName = relatedName + "ListView";
\r
352 assert(this.app[relatedListViewClassName] != undefined, relatedListViewClassName + " not found");
\r
353 relatedListViewClass = this.app[relatedListViewClassName].extend({collection: xos[relatedName].filterBy(relatedField,this.model.id)});
\r
354 this.app[regionName].show(new relatedListViewClass());
\r
355 if (this.app.hideTabsByDefault) {
\r
356 this.app[regionName].$el.hide();
\r
358 tabs.push({name: relatedName, region: regionName});
\r
363 this.app["linkedObjs" + (index+1)].empty();
\r
367 this.showTabs(tabs);
\r
368 this.tabClick('#xos-nav-detail', 'detail');
\r
371 onFormDataInvalid: function(errors) {
\r
373 var markErrors = function(value, key) {
\r
374 console.log("name='" + key + "'");
\r
375 var $inputElement = self.$el.find("[name='" + key + "']");
\r
376 var $inputContainer = $inputElement.parent();
\r
377 $inputContainer.find(".help-inline").remove();
\r
378 var $errorEl = $("<span>", {class: "help-inline error", text: value});
\r
379 $inputContainer.append($errorEl).addClass("error");
\r
381 _.each(errors, markErrors);
\r
387 This is for items that will be displayed as table rows.
389 app - MarionetteApplication
390 template - template (See XOSHelper.html)
391 detailClass - class of detail view, probably an XOSDetailView
394 XOSItemView = Marionette.ItemView.extend({
396 className: 'test-tablerow',
398 events: {"click": "changeItem"},
400 changeItem: function(e) {
\r
401 this.app.hideError();
\r
402 e.preventDefault();
\r
403 e.stopPropagation();
\r
405 this.app.navigateToModel(this.app, this.detailClass, this.detailNavLink, this.model);
\r
411 app - MarionetteApplication
412 childView - class of ItemView, probably an XOSItemView
413 template - template (see xosHelper.html)
414 collection - collection that holds these objects
415 title - title to display in template
418 XOSListView = Marionette.CompositeView.extend({
419 childViewContainer: 'tbody',
\r
421 events: {"click button.btn-xos-add": "addClicked",
\r
422 "click button.btn-xos-refresh": "refreshClicked",
\r
425 _fetchStateChange: function() {
\r
426 if (this.collection.fetching) {
\r
427 $("#xos-list-title-spinner").show();
\r
429 $("#xos-list-title-spinner").hide();
\r
433 addClicked: function(e) {
435 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
438 \r refreshClicked: function(e) {
439 \r e.preventDefault();
440 \r this.collection.refresh(refreshRelated=true);
443 \r initialize: function() {
444 \r this.listenTo(this.collection, 'change', this._renderChildren)
445 this.listenTo(this.collection, 'fetchStateChange', this._fetchStateChange);
447 // Because many of the templates use idToName(), we need to
448 // listen to the collections that hold the names for the ids
449 // that we want to display.
450 for (i in this.collection.foreignCollections) {
451 foreignName = this.collection.foreignCollections[i];
452 if (xos[foreignName] == undefined) {
453 console.log("Failed to find xos class " + foreignName);
455 this.listenTo(xos[foreignName], 'change', this._renderChildren);
456 this.listenTo(xos[foreignName], 'sort', this._renderChildren);
460 templateHelpers: function() {
461 return { title: this.title };
465 /* Give an id, the name of a collection, and the name of a field for models
466 within that collection, lookup the id and return the value of the field.
469 idToName = function(id, collectionName, fieldName) {
470 linkedObject = xos[collectionName].get(id);
471 if (linkedObject == undefined) {
474 return linkedObject.attributes[fieldName];
478 /* Constructs lists of <option> html blocks for items in a collection.
480 selectedId = the id of an object that should be selected, if any
481 collectionName = name of collection
482 fieldName = name of field within models of collection that will be displayed
485 idToOptions = function(selectedId, collectionName, fieldName) {
487 for (index in xos[collectionName].models) {
488 linkedObject = xos[collectionName].models[index];
489 linkedId = linkedObject["id"];
490 linkedName = linkedObject.attributes[fieldName];
491 if (linkedId == selectedId) {
492 selected = " selected";
496 result = result + '<option value="' + linkedId + '"' + selected + '>' + linkedName + '</option>';
501 /* Constructs an html <select> and the <option>s to go with it.
503 variable = variable name to return to form
504 selectedId = the id of an object that should be selected, if any
505 collectionName = name of collection
506 fieldName = name of field within models of collection that will be displayed
509 idToSelect = function(variable, selectedId, collectionName, fieldName) {
510 result = '<select name="' + variable + '">' +
511 idToOptions(selectedId, collectionName, fieldName) +