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 this.$el.find(".help-inline").remove();
\r
273 /* although model.validate() is called automatically by
\r
274 model.save, we call it ourselves, so we can throw up our
\r
275 validation error before creating the infoMsg in the log
\r
277 errors = this.model.validate(data);
\r
279 this.onFormDataInvalid(errors);
\r
283 var infoMsgId = this.app.showInformational( {what: "save " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
\r
285 this.model.save(data, {error: function(model, result, xhr) { that.saveError(model,result,xhr,infoMsgId);},
\r
286 success: function(model, result, xhr) { that.saveSuccess(model,result,xhr,infoMsgId);}});
\r
288 console.log(this.model);
\r
289 this.collection.add(this.model);
\r
290 this.collection.sort();
\r
292 this.dirty = false;
\r
295 destroyModel: function() {
296 this.app.hideError();
297 var infoMsgId = this.app.showInformational( {what: "destroy " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
299 this.model.destroy({error: function(model, result, xhr) { that.destroyError(model,result,xhr,infoMsgId);},
300 success: function(model, result, xhr) { that.destroySuccess(model,result,xhr,infoMsgId);}});
303 deleteClicked: function(e) {
305 \r this.app.confirmDialog(this, "deleteConfirmed");
308 \r deleteConfirmed: function() {
309 \r modelName = this.model.modelName;
310 \r this.destroyModel();
311 \r this.app.navigate("list", modelName);
314 tabClick: function(tabId, regionName) {
315 region = this.app[regionName];
\r
316 if (this.currentTabRegion != undefined) {
\r
317 this.currentTabRegion.$el.hide();
\r
319 if (this.currentTabId != undefined) {
\r
320 $(this.currentTabId).removeClass('active');
\r
322 this.currentTabRegion = region;
\r
323 this.currentTabRegion.$el.show();
\r
325 this.currentTabId = tabId;
\r
326 $(tabId).addClass('active');
\r
329 showTabs: function(tabs) {
330 template = templateFromId("#xos-tabs-template", {tabs: tabs});
331 $("#tabs").html(template(tabs));
334 _.each(tabs, function(tab) {
335 var regionName = tab["region"];
336 var tabId = '#xos-nav-'+regionName;
337 $(tabId).bind('click', function() { that.tabClick(tabId, regionName); });
343 showLinkedItems: function() {
346 tabs.push({name: "details", region: "detail"});
349 for (relatedName in this.model.collection.relatedCollections) {
\r
350 relatedField = this.model.collection.relatedCollections[relatedName];
\r
351 regionName = "linkedObjs" + (index+1);
\r
353 relatedListViewClassName = relatedName + "ListView";
\r
354 assert(this.app[relatedListViewClassName] != undefined, relatedListViewClassName + " not found");
\r
355 relatedListViewClass = this.app[relatedListViewClassName].extend({collection: xos[relatedName].filterBy(relatedField,this.model.id)});
\r
356 this.app[regionName].show(new relatedListViewClass());
\r
357 if (this.app.hideTabsByDefault) {
\r
358 this.app[regionName].$el.hide();
\r
360 tabs.push({name: relatedName, region: regionName});
\r
365 this.app["linkedObjs" + (index+1)].empty();
\r
369 this.showTabs(tabs);
\r
370 this.tabClick('#xos-nav-detail', 'detail');
\r
373 onFormDataInvalid: function(errors) {
\r
375 var markErrors = function(value, key) {
\r
376 console.log("name='" + key + "'");
\r
377 var $inputElement = self.$el.find("[name='" + key + "']");
\r
378 var $inputContainer = $inputElement.parent();
\r
379 //$inputContainer.find(".help-inline").remove();
\r
380 var $errorEl = $("<span>", {class: "help-inline error", text: value});
\r
381 $inputContainer.append($errorEl).addClass("error");
\r
383 _.each(errors, markErrors);
\r
389 This is for items that will be displayed as table rows.
391 app - MarionetteApplication
392 template - template (See XOSHelper.html)
393 detailClass - class of detail view, probably an XOSDetailView
396 XOSItemView = Marionette.ItemView.extend({
398 className: 'test-tablerow',
400 events: {"click": "changeItem"},
402 changeItem: function(e) {
\r
403 this.app.hideError();
\r
404 e.preventDefault();
\r
405 e.stopPropagation();
\r
407 this.app.navigateToModel(this.app, this.detailClass, this.detailNavLink, this.model);
\r
413 app - MarionetteApplication
414 childView - class of ItemView, probably an XOSItemView
415 template - template (see xosHelper.html)
416 collection - collection that holds these objects
417 title - title to display in template
420 XOSListView = Marionette.CompositeView.extend({
421 childViewContainer: 'tbody',
\r
423 events: {"click button.btn-xos-add": "addClicked",
\r
424 "click button.btn-xos-refresh": "refreshClicked",
\r
427 _fetchStateChange: function() {
\r
428 if (this.collection.fetching) {
\r
429 $("#xos-list-title-spinner").show();
\r
431 $("#xos-list-title-spinner").hide();
\r
435 addClicked: function(e) {
437 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
440 \r refreshClicked: function(e) {
441 \r e.preventDefault();
442 \r this.collection.refresh(refreshRelated=true);
445 \r initialize: function() {
446 \r this.listenTo(this.collection, 'change', this._renderChildren)
447 this.listenTo(this.collection, 'fetchStateChange', this._fetchStateChange);
449 // Because many of the templates use idToName(), we need to
450 // listen to the collections that hold the names for the ids
451 // that we want to display.
452 for (i in this.collection.foreignCollections) {
453 foreignName = this.collection.foreignCollections[i];
454 if (xos[foreignName] == undefined) {
455 console.log("Failed to find xos class " + foreignName);
457 this.listenTo(xos[foreignName], 'change', this._renderChildren);
458 this.listenTo(xos[foreignName], 'sort', this._renderChildren);
462 templateHelpers: function() {
463 return { title: this.title };
467 /* Give an id, the name of a collection, and the name of a field for models
468 within that collection, lookup the id and return the value of the field.
471 idToName = function(id, collectionName, fieldName) {
472 linkedObject = xos[collectionName].get(id);
473 if (linkedObject == undefined) {
476 return linkedObject.attributes[fieldName];
480 /* Constructs lists of <option> html blocks for items in a collection.
482 selectedId = the id of an object that should be selected, if any
483 collectionName = name of collection
484 fieldName = name of field within models of collection that will be displayed
487 idToOptions = function(selectedId, collectionName, fieldName) {
489 for (index in xos[collectionName].models) {
490 linkedObject = xos[collectionName].models[index];
491 linkedId = linkedObject["id"];
492 linkedName = linkedObject.attributes[fieldName];
493 if (linkedId == selectedId) {
494 selected = " selected";
498 result = result + '<option value="' + linkedId + '"' + selected + '>' + linkedName + '</option>';
503 /* Constructs an html <select> and the <option>s to go with it.
505 variable = variable name to return to form
506 selectedId = the id of an object that should be selected, if any
507 collectionName = name of collection
508 fieldName = name of field within models of collection that will be displayed
511 idToSelect = function(variable, selectedId, collectionName, fieldName) {
512 result = '<select name="' + variable + '">' +
513 idToOptions(selectedId, collectionName, fieldName) +