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["statusclass"] = "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["statusclass"] = "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["statusclass"] = "inprog";
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, collection:xos[collection_name]});
\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 $("#contentTitle").html(templateFromId("#xos-title-detail")({"title": title}));
\r
143 collection = xos[collection_name];
\r
144 model = collection.get(model_id);
\r
145 if (model == undefined) {
\r
146 app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name}));
\r
148 detailViewClass = app[detailName];
\r
149 detailView = new detailViewClass({model: model});
\r
150 app[regionName].show(detailView);
\r
151 detailView.showLinkedItems();
\r
152 $("#xos-detail-button-box").show();
\r
153 $("#xos-listview-button-box").hide();
\r
156 return showModelId;
\r
162 app - MarionetteApplication
163 template - template (See XOSHelper.html)
166 XOSDetailView = Marionette.ItemView.extend({
169 events: {"click button.btn-xos-save-continue": "submitContinueClicked",
170 "click button.btn-xos-save-leave": "submitLeaveClicked",
171 "click button.btn-xos-save-another": "submitAddAnotherClicked",
172 "change input": "inputChanged"},
174 /* inputChanged is watching the onChange events of the input controls. We
175 do this to track when this view is 'dirty', so we can throw up a warning
\r
176 if the user tries to change his slices without saving first.
\r
179 inputChanged: function(e) {
\r
183 saveError: function(model, result, xhr, infoMsgId) {
\r
184 result["what"] = "save " + model.__proto__.modelName;
\r
185 result["infoMsgId"] = infoMsgId;
\r
186 this.app.showError(result);
\r
189 saveSuccess: function(model, result, xhr, infoMsgId) {
\r
190 result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};
\r
191 result["what"] = "save " + model.__proto__.modelName;
\r
192 result["infoMsgId"] = infoMsgId;
\r
193 this.app.showSuccess(result);
\r
196 submitContinueClicked: function(e) {
197 console.log("saveContinue");
202 submitLeaveClicked: function(e) {
203 console.log("saveLeave");
206 this.app.navigate("list", this.model.modelName);
209 submitAddAnotherClicked: function(e) {
210 console.log("saveAnother");
213 this.app.navigate("add", this.model.modelName);
217 this.app.hideError();
218 var infoMsgId = this.app.showInformational( {what: "save " + this.model.__proto__.modelName, status: "", statusText: "in progress..."} );
\r
219 var data = Backbone.Syphon.serialize(this);
\r
221 var isNew = !this.model.id;
\r
222 this.model.save(data, {error: function(model, result, xhr) { that.saveError(model,result,xhr,infoMsgId);},
\r
223 success: function(model, result, xhr) { that.saveSuccess(model,result,xhr,infoMsgId);}});
\r
225 console.log(this.model);
\r
226 this.collection.add(this.model);
\r
227 this.collection.sort();
\r
229 this.dirty = false;
\r
232 tabClick: function(tabId, regionName) {
233 region = this.app[regionName];
\r
234 if (this.currentTabRegion != undefined) {
\r
235 this.currentTabRegion.$el.hide();
\r
237 if (this.currentTabId != undefined) {
\r
238 $(this.currentTabId).removeClass('active');
\r
240 this.currentTabRegion = region;
\r
241 this.currentTabRegion.$el.show();
\r
243 this.currentTabId = tabId;
\r
244 $(tabId).addClass('active');
\r
247 showTabs: function(tabs) {
248 template = templateFromId("#xos-tabs-template", {tabs: tabs});
249 $("#tabs").html(template(tabs));
252 _.each(tabs, function(tab) {
253 var regionName = tab["region"];
254 var tabId = '#xos-nav-'+regionName;
255 $(tabId).bind('click', function() { that.tabClick(tabId, regionName); });
261 showLinkedItems: function() {
264 tabs.push({name: "details", region: "detail"});
267 for (relatedName in this.model.collection.relatedCollections) {
\r
268 relatedField = this.model.collection.relatedCollections[relatedName];
\r
269 regionName = "linkedObjs" + (index+1);
\r
271 relatedListViewClassName = relatedName + "ListView";
\r
272 assert(this.app[relatedListViewClassName] != undefined, relatedListViewClassName + " not found");
\r
273 relatedListViewClass = this.app[relatedListViewClassName].extend({collection: xos[relatedName].filterBy(relatedField,this.model.id)});
\r
274 this.app[regionName].show(new relatedListViewClass());
\r
275 if (this.app.hideTabsByDefault) {
\r
276 this.app[regionName].$el.hide();
\r
278 tabs.push({name: relatedName, region: regionName});
\r
283 this.app["linkedObjs" + (index+1)].empty();
\r
287 this.showTabs(tabs);
\r
288 this.tabClick('#xos-nav-detail', 'detail');
\r
294 This is for items that will be displayed as table rows.
296 app - MarionetteApplication
297 template - template (See XOSHelper.html)
298 detailClass - class of detail view, probably an XOSDetailView
301 XOSItemView = Marionette.ItemView.extend({
303 className: 'test-tablerow',
305 events: {"click": "changeItem"},
307 changeItem: function(e) {
\r
308 this.app.hideError();
\r
309 e.preventDefault();
\r
310 e.stopPropagation();
\r
312 this.app.navigateToModel(this.app, this.detailClass, this.detailNavLink, this.model);
\r
318 app - MarionetteApplication
319 childView - class of ItemView, probably an XOSItemView
320 template - template (see xosHelper.html)
321 collection - collection that holds these objects
322 title - title to display in template
325 XOSListView = Marionette.CompositeView.extend({
326 childViewContainer: 'tbody',
\r
328 events: {"click button.btn-xos-add": "addClicked",
\r
329 "click button.btn-xos-refresh": "refreshClicked",
\r
332 _fetchStateChange: function() {
\r
333 if (this.collection.fetching) {
\r
334 $("#xos-list-title-spinner").show();
\r
336 $("#xos-list-title-spinner").hide();
\r
340 addClicked: function(e) {
342 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
345 \r refreshClicked: function(e) {
346 \r e.preventDefault();
347 \r this.collection.refresh(refreshRelated=true);
350 \r initialize: function() {
351 \r this.listenTo(this.collection, 'change', this._renderChildren)
352 this.listenTo(this.collection, 'fetchStateChange', this._fetchStateChange);
354 // Because many of the templates use idToName(), we need to
355 // listen to the collections that hold the names for the ids
356 // that we want to display.
357 for (i in this.collection.foreignCollections) {
358 foreignName = this.collection.foreignCollections[i];
359 if (xos[foreignName] == undefined) {
360 console.log("Failed to find xos class " + foreignName);
362 this.listenTo(xos[foreignName], 'change', this._renderChildren);
363 this.listenTo(xos[foreignName], 'sort', this._renderChildren);
367 templateHelpers: function() {
368 return { title: this.title };
372 /* Give an id, the name of a collection, and the name of a field for models
373 within that collection, lookup the id and return the value of the field.
376 idToName = function(id, collectionName, fieldName) {
377 linkedObject = xos[collectionName].get(id);
378 if (linkedObject == undefined) {
381 return linkedObject.attributes[fieldName];
385 /* Constructs lists of <option> html blocks for items in a collection.
387 selectedId = the id of an object that should be selected, if any
388 collectionName = name of collection
389 fieldName = name of field within models of collection that will be displayed
392 idToOptions = function(selectedId, collectionName, fieldName) {
394 for (index in xos[collectionName].models) {
395 linkedObject = xos[collectionName].models[index];
396 linkedId = linkedObject["id"];
397 linkedName = linkedObject.attributes[fieldName];
398 if (linkedId == selectedId) {
399 selected = " selected";
403 result = result + '<option value="' + linkedId + '"' + selected + '>' + linkedName + '</option>';
408 /* Constructs an html <select> and the <option>s to go with it.
410 variable = variable name to return to form
411 selectedId = the id of an object that should be selected, if any
412 collectionName = name of collection
413 fieldName = name of field within models of collection that will be displayed
416 idToSelect = function(variable, selectedId, collectionName, fieldName) {
417 result = '<select name="' + variable + '">' +
418 idToOptions(selectedId, collectionName, fieldName) +