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
372 this.$el.find(".help-inline").remove();
\r
374 var markErrors = function(value, key) {
\r
375 console.log("name='" + key + "'");
\r
376 var $inputElement = self.$el.find("[name='" + key + "']");
\r
377 var $inputContainer = $inputElement.parent();
\r
378 //$inputContainer.find(".help-inline").remove();
\r
379 var $errorEl = $("<span>", {class: "help-inline error", text: value});
\r
380 $inputContainer.append($errorEl).addClass("error");
\r
382 _.each(errors, markErrors);
\r
388 This is for items that will be displayed as table rows.
390 app - MarionetteApplication
391 template - template (See XOSHelper.html)
392 detailClass - class of detail view, probably an XOSDetailView
395 XOSItemView = Marionette.ItemView.extend({
397 className: 'test-tablerow',
399 events: {"click": "changeItem"},
401 changeItem: function(e) {
\r
402 this.app.hideError();
\r
403 e.preventDefault();
\r
404 e.stopPropagation();
\r
406 this.app.navigateToModel(this.app, this.detailClass, this.detailNavLink, this.model);
\r
412 app - MarionetteApplication
413 childView - class of ItemView, probably an XOSItemView
414 template - template (see xosHelper.html)
415 collection - collection that holds these objects
416 title - title to display in template
419 XOSListView = Marionette.CompositeView.extend({
420 childViewContainer: 'tbody',
\r
422 events: {"click button.btn-xos-add": "addClicked",
\r
423 "click button.btn-xos-refresh": "refreshClicked",
\r
426 _fetchStateChange: function() {
\r
427 if (this.collection.fetching) {
\r
428 $("#xos-list-title-spinner").show();
\r
430 $("#xos-list-title-spinner").hide();
\r
434 addClicked: function(e) {
436 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
439 \r refreshClicked: function(e) {
440 \r e.preventDefault();
441 \r this.collection.refresh(refreshRelated=true);
444 \r initialize: function() {
445 \r this.listenTo(this.collection, 'change', this._renderChildren)
446 this.listenTo(this.collection, 'fetchStateChange', this._fetchStateChange);
448 // Because many of the templates use idToName(), we need to
449 // listen to the collections that hold the names for the ids
450 // that we want to display.
451 for (i in this.collection.foreignCollections) {
452 foreignName = this.collection.foreignCollections[i];
453 if (xos[foreignName] == undefined) {
454 console.log("Failed to find xos class " + foreignName);
456 this.listenTo(xos[foreignName], 'change', this._renderChildren);
457 this.listenTo(xos[foreignName], 'sort', this._renderChildren);
461 templateHelpers: function() {
462 return { title: this.title };
466 /* Give an id, the name of a collection, and the name of a field for models
467 within that collection, lookup the id and return the value of the field.
470 idToName = function(id, collectionName, fieldName) {
471 linkedObject = xos[collectionName].get(id);
472 if (linkedObject == undefined) {
475 return linkedObject.attributes[fieldName];
479 /* Constructs lists of <option> html blocks for items in a collection.
481 selectedId = the id of an object that should be selected, if any
482 collectionName = name of collection
483 fieldName = name of field within models of collection that will be displayed
486 idToOptions = function(selectedId, collectionName, fieldName) {
488 for (index in xos[collectionName].models) {
489 linkedObject = xos[collectionName].models[index];
490 linkedId = linkedObject["id"];
491 linkedName = linkedObject.attributes[fieldName];
492 if (linkedId == selectedId) {
493 selected = " selected";
497 result = result + '<option value="' + linkedId + '"' + selected + '>' + linkedName + '</option>';
502 /* Constructs an html <select> and the <option>s to go with it.
504 variable = variable name to return to form
505 selectedId = the id of an object that should be selected, if any
506 collectionName = name of collection
507 fieldName = name of field within models of collection that will be displayed
510 idToSelect = function(variable, selectedId, collectionName, fieldName) {
511 result = '<select name="' + variable + '">' +
512 idToOptions(selectedId, collectionName, fieldName) +