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 model = new xos[collection_name].model();
\r
130 detailViewClass = app[detailName];
\r
131 detailView = new detailViewClass({model: model});
\r
132 app[regionName].show(detailView);
\r
133 $("#xos-detail-button-box").show();
\r
134 $("#xos-listview-button-box").hide();
\r
138 detailShower: function(detailName, collection_name, regionName, title) {
\r
140 showModelId = function(model_id) {
\r
141 showModel = function(model) {
\r
142 detailViewClass = app[detailName];
\r
143 detailView = new detailViewClass({model: model});
\r
144 app[regionName].show(detailView);
\r
145 detailView.showLinkedItems();
\r
146 $("#xos-detail-button-box").show();
\r
147 $("#xos-listview-button-box").hide();
\r
150 $("#contentTitle").html(templateFromId("#xos-title-detail")({"title": title}));
\r
152 collection = xos[collection_name];
\r
153 model = collection.get(model_id);
\r
154 if (model == undefined) {
\r
155 if (!collection.isLoaded) {
\r
156 // If the model cannot be found, then maybe it's because
\r
157 // we haven't finished loading the collection yet. So wait for
\r
158 // the sort event to complete, then try again.
\r
159 collection.once("sort", function() {
\r
160 collection = xos[collection_name];
\r
161 model = collection.get(model_id);
\r
162 if (model == undefined) {
\r
163 // We tried. It's not here. Complain to the user.
\r
164 app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name}));
\r
170 // The collection was loaded, the user must just be asking for something we don't have.
\r
171 app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name}));
\r
177 return showModelId;
\r
183 app - MarionetteApplication
184 template - template (See XOSHelper.html)
187 XOSDetailView = Marionette.ItemView.extend({
190 events: {"click button.btn-xos-save-continue": "submitContinueClicked",
191 "click button.btn-xos-save-leave": "submitLeaveClicked",
192 "click button.btn-xos-save-another": "submitAddAnotherClicked",
193 "change input": "inputChanged"},
195 /* inputChanged is watching the onChange events of the input controls. We
196 do this to track when this view is 'dirty', so we can throw up a warning
\r
197 if the user tries to change his slices without saving first.
\r
200 inputChanged: function(e) {
\r
204 saveError: function(model, result, xhr, infoMsgId) {
\r
205 result["what"] = "save " + model.__proto__.modelName;
\r
206 result["infoMsgId"] = infoMsgId;
\r
207 this.app.showError(result);
\r
210 saveSuccess: function(model, result, xhr, infoMsgId) {
\r
211 result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};
\r
212 result["what"] = "save " + model.__proto__.modelName;
\r
213 result["infoMsgId"] = infoMsgId;
\r
214 this.app.showSuccess(result);
\r
217 submitContinueClicked: function(e) {
218 console.log("saveContinue");
223 submitLeaveClicked: function(e) {
224 console.log("saveLeave");
229 submitAddAnotherClicked: function(e) {
230 console.log("saveAnother");
236 this.app.hideError();
237 var infoMsgId = this.app.showInformational( {what: "save " + this.model.__proto__.modelName, status: "", statusText: "in progress..."} );
\r
238 var data = Backbone.Syphon.serialize(this);
\r
240 this.model.save(data, {error: function(model, result, xhr) { that.saveError(model,result,xhr,infoMsgId);},
\r
241 success: function(model, result, xhr) { that.saveSuccess(model,result,xhr,infoMsgId);}});
\r
242 this.dirty = false;
\r
245 tabClick: function(tabId, regionName) {
246 region = this.app[regionName];
\r
247 if (this.currentTabRegion != undefined) {
\r
248 this.currentTabRegion.$el.hide();
\r
250 if (this.currentTabId != undefined) {
\r
251 $(this.currentTabId).removeClass('active');
\r
253 this.currentTabRegion = region;
\r
254 this.currentTabRegion.$el.show();
\r
256 this.currentTabId = tabId;
\r
257 $(tabId).addClass('active');
\r
260 showTabs: function(tabs) {
261 template = templateFromId("#xos-tabs-template", {tabs: tabs});
262 $("#tabs").html(template(tabs));
265 _.each(tabs, function(tab) {
266 var regionName = tab["region"];
267 var tabId = '#xos-nav-'+regionName;
268 $(tabId).bind('click', function() { that.tabClick(tabId, regionName); });
274 showLinkedItems: function() {
277 tabs.push({name: "details", region: "detail"});
280 for (relatedName in this.model.collection.relatedCollections) {
\r
281 relatedField = this.model.collection.relatedCollections[relatedName];
\r
282 regionName = "linkedObjs" + (index+1);
\r
284 relatedListViewClassName = relatedName + "ListView";
\r
285 assert(this.app[relatedListViewClassName] != undefined, relatedListViewClassName + " not found");
\r
286 relatedListViewClass = this.app[relatedListViewClassName].extend({collection: xos[relatedName].filterBy(relatedField,this.model.id)});
\r
287 this.app[regionName].show(new relatedListViewClass());
\r
288 if (this.app.hideTabsByDefault) {
\r
289 this.app[regionName].$el.hide();
\r
291 tabs.push({name: relatedName, region: regionName});
\r
296 this.app["linkedObjs" + (index+1)].empty();
\r
300 this.showTabs(tabs);
\r
301 this.tabClick('#xos-nav-detail', 'detail');
\r
307 This is for items that will be displayed as table rows.
309 app - MarionetteApplication
310 template - template (See XOSHelper.html)
311 detailClass - class of detail view, probably an XOSDetailView
314 XOSItemView = Marionette.ItemView.extend({
316 className: 'test-tablerow',
318 events: {"click": "changeItem"},
320 changeItem: function(e) {
\r
321 this.app.hideError();
\r
322 e.preventDefault();
\r
323 e.stopPropagation();
\r
325 this.app.navigateToModel(this.app, this.detailClass, this.detailNavLink, this.model);
\r
331 app - MarionetteApplication
332 childView - class of ItemView, probably an XOSItemView
333 template - template (see xosHelper.html)
334 collection - collection that holds these objects
335 title - title to display in template
338 XOSListView = Marionette.CompositeView.extend({
339 childViewContainer: 'tbody',
\r
341 events: {"click button.btn-xos-add": "addClicked",
\r
344 addClicked: function(e) {
347 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
350 initialize: function() {
\r
351 this.listenTo(this.collection, 'change', this._renderChildren)
353 // Because many of the templates use idToName(), we need to
354 // listen to the collections that hold the names for the ids
355 // that we want to display.
356 for (i in this.collection.foreignCollections) {
357 foreignName = this.collection.foreignCollections[i];
358 if (xos[foreignName] == undefined) {
359 console.log("Failed to find xos class " + foreignName);
361 this.listenTo(xos[foreignName], 'change', this._renderChildren);
362 this.listenTo(xos[foreignName], 'sort', this._renderChildren);
366 templateHelpers: function() {
367 return { title: this.title };
371 /* Give an id, the name of a collection, and the name of a field for models
372 within that collection, lookup the id and return the value of the field.
375 idToName = function(id, collectionName, fieldName) {
376 linkedObject = xos[collectionName].get(id);
377 if (linkedObject == undefined) {
380 return linkedObject.attributes[fieldName];
384 /* Constructs lists of <option> html blocks for items in a collection.
386 selectedId = the id of an object that should be selected, if any
387 collectionName = name of collection
388 fieldName = name of field within models of collection that will be displayed
391 idToOptions = function(selectedId, collectionName, fieldName) {
393 for (index in xos[collectionName].models) {
394 linkedObject = xos[collectionName].models[index];
395 linkedId = linkedObject["id"];
396 linkedName = linkedObject.attributes[fieldName];
397 if (linkedId == selectedId) {
398 selected = " selected";
402 result = result + '<option value="' + linkedId + '"' + selected + '>' + linkedName + '</option>';
407 /* Constructs an html <select> and the <option>s to go with it.
409 variable = variable name to return to form
410 selectedId = the id of an object that should be selected, if any
411 collectionName = name of collection
412 fieldName = name of field within models of collection that will be displayed
415 idToSelect = function(variable, selectedId, collectionName, fieldName) {
416 result = '<select name="' + variable + '">' +
417 idToOptions(selectedId, collectionName, fieldName) +