1 HTMLView = Marionette.ItemView.extend({
3 this.$el.append(this.options.html);
7 SliceSelectorOption = Marionette.ItemView.extend({
8 template: "#xos-sliceselector-option",
10 attributes: function() {
12 console.log(this.options.selectedID);
13 console.log(this.model.get("id"));
14 if (this.options.selectedID == this.model.get("id")) {
15 return { value: this.model.get("id"), selected: 1 };
17 return { value: this.model.get("id") };
22 SliceSelectorView = Marionette.CompositeView.extend({
23 template: "#xos-sliceselector-select",
24 childViewContainer: "select",
25 childView: SliceSelectorOption,
27 events: {"change select": "onSliceChanged"},
29 childViewOptions: function() {
30 return { selectedID: this.options.selectedID || this.selectedID || null };
33 onSliceChanged: function() {
34 this.sliceChanged(this.$el.find("select").val());
37 sliceChanged: function(id) {
38 console.log("sliceChanged " + id);
42 FilteredCompositeView = Marionette.CompositeView.extend( {
43 showCollection: function() {
45 this.collection.each(function(child, index) {
46 if (this.filter && !this.filter(child)) {
49 ChildView = this.getChildView(child);
50 this.addChild(child, ChildView, index);
56 XOSRouter = Marionette.AppRouter.extend({
57 initialize: function() {
\r
61 onRoute: function(x,y,z) {
\r
62 this.routeStack.push(Backbone.history.fragment);
\r
63 this.routeStack = this.routeStack.slice(-32); // limit the size of routeStack to something reasonable
\r
66 prevPage: function() {
\r
67 return this.routeStack.slice(-1)[0];
70 showPreviousURL: function() {
71 prevPage = this.prevPage();
72 //console.log("showPreviousURL");
73 //console.log(this.routeStack);
75 this.navigate("#"+prevPage, {trigger: false, replace: true} );
79 navigate: function(href, options) {
81 Marionette.AppRouter.prototype.navigate.call(this, "nowhere", {trigger: false, replace: true});
83 Marionette.AppRouter.prototype.navigate.call(this, href, options);
87 Backbone.Syphon.InputReaders.register('select', function(el) {
88 // Modify syphon so that if a select has "syphonall" in the class, then
89 // the value of every option will be returned, regardless of whether of
90 // not it is selected.
91 if (el.hasClass("syphonall")) {
93 _.each(el.find("option"), function(option) {
94 result.push($(option).val());
101 XOSApplication = Marionette.Application.extend({
102 detailBoxId: "#detailBox",
103 errorBoxId: "#errorBox",
104 errorCloseButtonId: "#close-error-box",
105 successBoxId: "#successBox",
106 successCloseButtonId: "#close-success-box",
107 errorTemplate: "#xos-error-template",
108 successTemplate: "#xos-success-template",
111 confirmDialog: function(view, event, callback) {
112 $("#xos-confirm-dialog").dialog({
116 "Confirm" : function() {
117 $(this).dialog("close");
125 "Cancel" : function() {
126 $(this).dialog("close");
130 $("#xos-confirm-dialog").dialog("open");
133 popupErrorDialog: function(responseText) {
135 parsed_error=$.parseJSON(responseText);
139 parsed_error=undefined;
140 width=640; // django stacktraces like wide width
142 console.log(responseText);
143 console.log(parsed_error);
145 $("#xos-error-dialog").html(templateFromId("#xos-error-response")(parsed_error));
147 $("#xos-error-dialog").html(templateFromId("#xos-error-rawresponse")({responseText: responseText}))
150 $("#xos-error-dialog").dialog({
154 Ok: function() { $(this).dialog("close"); }
159 hideLinkedItems: function(result) {
162 this["linkedObjs" + (index+1)].empty();
167 hideTabs: function() { $("#tabs").hide(); },
168 showTabs: function() { $("#tabs").show(); },
170 createListHandler: function(listViewName, collection_name, regionName, title) {
173 listView = new app[listViewName];
174 app[regionName].show(listView);
175 app.hideLinkedItems();
176 $("#contentTitle").html(templateFromId("#xos-title-list")({"title": title}));
180 listButtons = new XOSListButtonView({linkedView: listView});
181 app["rightButtonPanel"].show(listButtons);
185 createAddHandler: function(detailName, collection_name, regionName, title) {
188 console.log("addHandler");
190 app.hideLinkedItems();
193 model = new xos[collection_name].model();
194 detailViewClass = app[detailName];
195 detailView = new detailViewClass({model: model, collection:xos[collection_name]});
196 app[regionName].show(detailView);
198 detailButtons = new XOSDetailButtonView({linkedView: detailView});
199 app["rightButtonPanel"].show(detailButtons);
203 createAddChildHandler: function(addChildName, collection_name) {
205 return function(parent_modelName, parent_fieldName, parent_id) {
206 app.Router.showPreviousURL();
207 model = new xos[collection_name].model();
208 model.attributes[parent_fieldName] = parent_id;
209 model.readOnlyFields.push(parent_fieldName);
210 detailViewClass = app[addChildName];
211 var detailView = new detailViewClass({model: model, collection:xos[collection_name]});
212 detailView.dialog = $("xos-addchild-dialog");
213 app["addChildDetail"].show(detailView);
214 $("#xos-addchild-dialog").dialog({
219 "Save" : function() {
220 var addDialog = this;
221 detailView.synchronous = true;
222 detailView.afterSave = function() { console.log("addChild afterSave"); $(addDialog).dialog("close"); }
225 //$(this).dialog("close");
227 "Cancel" : function() {
228 $(this).dialog("close");
232 $("#xos-addchild-dialog").dialog("open");
236 createDeleteHandler: function(collection_name) {
238 return function(model_id) {
239 console.log("deleteCalled");
240 collection = xos[collection_name];
241 model = collection.get(model_id);
242 assert(model!=undefined, "failed to get model " + model_id + " from collection " + collection_name);
243 app.Router.showPreviousURL();
244 app.deleteDialog(model);
248 createDetailHandler: function(detailName, collection_name, regionName, title) {
250 showModelId = function(model_id) {
251 $("#contentTitle").html(templateFromId("#xos-title-detail")({"title": title}));
253 collection = xos[collection_name];
254 model = collection.get(model_id);
255 if (model == undefined) {
256 app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name}));
258 detailViewClass = app[detailName];
259 detailView = new detailViewClass({model: model});
260 app[regionName].show(detailView);
261 detailView.showLinkedItems();
263 detailButtons = new XOSDetailButtonView({linkedView: detailView});
264 app["rightButtonPanel"].show(detailButtons);
270 /* error handling callbacks */
272 hideError: function() {
273 if (this.logWindowId) {
275 $(this.errorBoxId).hide();
276 $(this.successBoxId).hide();
280 showSuccess: function(result) {
281 result["statusclass"] = "success";
282 if (this.logTableId) {
283 this.appendLogWindow(result);
285 $(this.successBoxId).show();
286 $(this.successBoxId).html(_.template($(this.successTemplate).html())(result));
288 $(this.successCloseButtonId).unbind().bind('click', function() {
289 $(that.successBoxId).hide();
294 showError: function(result) {
295 result["statusclass"] = "failure";
296 if (this.logTableId) {
297 this.appendLogWindow(result);
298 this.popupErrorDialog(result.responseText);
300 // this is really old stuff
301 $(this.errorBoxId).show();
302 $(this.errorBoxId).html(_.template($(this.errorTemplate).html())(result));
304 $(this.errorCloseButtonId).unbind().bind('click', function() {
305 $(that.errorBoxId).hide();
310 showInformational: function(result) {
311 result["statusclass"] = "inprog";
312 if (this.logTableId) {
313 return this.appendLogWindow(result);
319 appendLogWindow: function(result) {
320 // compute a new logMessageId for this log message
321 logMessageId = "logMessage" + this.logMessageCount;
322 this.logMessageCount = this.logMessageCount + 1;
323 result["logMessageId"] = logMessageId;
325 logMessageTemplate=$("#xos-log-template").html();
326 assert(logMessageTemplate != undefined, "logMessageTemplate is undefined");
327 newRow = _.template(logMessageTemplate, result);
328 assert(newRow != undefined, "newRow is undefined");
330 if (result["infoMsgId"] != undefined) {
331 // We were passed the logMessageId of an informational message,
332 // and the caller wants us to replace that message with our own.
333 // i.e. replace an informational message with a success or an error.
334 $("#"+result["infoMsgId"]).replaceWith(newRow);
336 // Create a brand new log message rather than replacing one.
337 logTableBody = $(this.logTableId + " tbody");
338 logTableBody.prepend(newRow);
341 if (this.statusMsgId) {
342 $(this.statusMsgId).html( templateFromId("#xos-status-template")(result) );
345 limitTableRows(this.logTableId, 5);
350 saveError: function(model, result, xhr, infoMsgId) {
351 console.log("saveError");
352 result["what"] = "save " + model.modelName + " " + model.attributes.humanReadableName;
353 result["infoMsgId"] = infoMsgId;
354 this.showError(result);
357 saveSuccess: function(model, result, xhr, infoMsgId, addToCollection) {
358 console.log("saveSuccess");
359 if (model.addToCollection) {
360 console.log("addToCollection");
361 console.log(model.addToCollection);
362 model.addToCollection.add(model);
363 model.addToCollection.sort();
364 model.addToCollection = undefined;
366 result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};
367 result["what"] = "save " + model.modelName + " " + model.attributes.humanReadableName;
368 result["infoMsgId"] = infoMsgId;
369 this.showSuccess(result);
372 destroyError: function(model, result, xhr, infoMsgId) {
373 result["what"] = "destroy " + model.modelName + " " + model.attributes.humanReadableName;
374 result["infoMsgId"] = infoMsgId;
375 this.showError(result);
378 destroySuccess: function(model, result, xhr, infoMsgId) {
379 result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};
380 result["what"] = "destroy " + model.modelName + " " + model.attributes.humanReadableName;
381 result["infoMsgId"] = infoMsgId;
382 this.showSuccess(result);
385 /* end error handling callbacks */
387 destroyModel: function(model) {
388 //console.log("destroyModel"); console.log(model);
390 var infoMsgId = this.showInformational( {what: "destroy " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
392 model.destroy({error: function(model, result, xhr) { that.destroyError(model,result,xhr,infoMsgId);},
393 success: function(model, result, xhr) { that.destroySuccess(model,result,xhr,infoMsgId);}});
396 deleteDialog: function(model, afterDelete) {
398 assert(model!=undefined, "deleteDialog's model is undefined");
399 //console.log("deleteDialog"); console.log(model);
400 this.confirmDialog(null, null, function() {
401 //console.log("deleteConfirm"); console.log(model);
402 modelName = model.modelName;
403 that.destroyModel(model);
404 if (afterDelete=="list") {
405 that.navigate("list", modelName);
411 XOSButtonView = Marionette.ItemView.extend({
412 events: {"click button.btn-xos-save-continue": "submitContinueClicked",
413 "click button.btn-xos-save-leave": "submitLeaveClicked",
414 "click button.btn-xos-save-another": "submitAddAnotherClicked",
415 "click button.btn-xos-delete": "deleteClicked",
416 "click button.btn-xos-add": "addClicked",
417 "click button.btn-xos-refresh": "refreshClicked",
420 submitLeaveClicked: function(e) {
421 this.options.linkedView.submitLeaveClicked.call(this.options.linkedView, e);
424 submitContinueClicked: function(e) {
425 this.options.linkedView.submitContinueClicked.call(this.options.linkedView, e);
428 submitAddAnotherClicked: function(e) {
429 this.options.linkedView.submitAddAnotherClicked.call(this.options.linkedView, e);
432 submitDeleteClicked: function(e) {
433 this.options.linkedView.deleteClicked.call(this.options.linkedView, e);
436 addClicked: function(e) {
437 this.options.linkedView.addClicked.call(this.options.linkedView, e);
440 refreshClicked: function(e) {
441 this.options.linkedView.refreshClicked.call(this.options.linkedView, e);
445 XOSDetailButtonView = XOSButtonView.extend({ template: "#xos-savebuttons-template" });
446 XOSListButtonView = XOSButtonView.extend({ template: "#xos-listbuttons-template" });
450 app - MarionetteApplication
451 template - template (See XOSHelper.html)
454 XOSDetailView = Marionette.ItemView.extend({
457 viewInitializers: [],
459 events: {"click button.btn-xos-save-continue": "submitContinueClicked",
460 "click button.btn-xos-save-leave": "submitLeaveClicked",
461 "click button.btn-xos-save-another": "submitAddAnotherClicked",
462 "click button.btn-xos-delete": "deleteClicked",
463 "change input": "inputChanged"},
465 /* inputChanged is watching the onChange events of the input controls. We
466 do this to track when this view is 'dirty', so we can throw up a warning
467 if the user tries to change his slices without saving first.
470 initialize: function() {
471 this.on("saveSuccess", this.onAfterSave);
472 this.synchronous = false;
476 _.each(this.viewInitializers, function(initializer) {
481 afterSave: function(e) {
484 onAfterSave: function(e) {
488 inputChanged: function(e) {
492 submitContinueClicked: function(e) {
493 console.log("saveContinue");
495 this.afterSave = function() { };
499 submitLeaveClicked: function(e) {
500 console.log("saveLeave");
503 this.afterSave = function() {
504 that.app.navigate("list", that.model.modelName);
509 submitAddAnotherClicked: function(e) {
510 console.log("saveAnother");
514 this.afterSave = function() {
515 console.log("addAnother afterSave");
516 that.app.navigate("add", that.model.modelName);
522 this.app.hideError();
523 var data = Backbone.Syphon.serialize(this);
525 var isNew = !this.model.id;
529 this.$el.find(".help-inline").remove();
531 /* although model.validate() is called automatically by
532 model.save, we call it ourselves, so we can throw up our
533 validation error before creating the infoMsg in the log
535 errors = this.model.xosValidate(data);
537 this.onFormDataInvalid(errors);
542 this.model.attributes.humanReadableName = "new " + this.model.modelName;
543 this.model.addToCollection = this.collection;
545 this.model.addToCollection = undefined;
548 var infoMsgId = this.app.showInformational( {what: "save " + this.model.modelName + " " + this.model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
550 this.model.save(data, {error: function(model, result, xhr) { that.app.saveError(model,result,xhr,infoMsgId);},
551 success: function(model, result, xhr) { that.app.saveSuccess(model,result,xhr,infoMsgId);
552 if (that.synchronous) {
553 that.trigger("saveSuccess");
558 if (!this.synchronous) {
563 deleteClicked: function(e) {
565 this.app.deleteDialog(this.model, "list");
568 tabClick: function(tabId, regionName) {
569 region = this.app[regionName];
570 if (this.currentTabRegion != undefined) {
571 this.currentTabRegion.$el.hide();
573 if (this.currentTabId != undefined) {
574 $(this.currentTabId).removeClass('active');
576 this.currentTabRegion = region;
577 this.currentTabRegion.$el.show();
579 this.currentTabId = tabId;
580 $(tabId).addClass('active');
583 showTabs: function(tabs) {
584 template = templateFromId("#xos-tabs-template", {tabs: tabs});
585 $("#tabs").html(template(tabs));
588 _.each(tabs, function(tab) {
589 var regionName = tab["region"];
590 var tabId = '#xos-nav-'+regionName;
591 $(tabId).bind('click', function() { that.tabClick(tabId, regionName); });
597 showLinkedItems: function() {
600 tabs.push({name: "details", region: "detail"});
602 makeFilter = function(relatedField, relatedId) {
603 return function(model) { return model.attributes[relatedField] == relatedId; }
607 for (relatedName in this.model.collection.relatedCollections) {
608 var relatedField = this.model.collection.relatedCollections[relatedName];
609 var relatedId = this.model.id;
610 regionName = "linkedObjs" + (index+1);
612 relatedListViewClassName = relatedName + "ListView";
613 assert(this.app[relatedListViewClassName] != undefined, relatedListViewClassName + " not found");
614 relatedListViewClass = this.app[relatedListViewClassName].extend({collection: xos[relatedName],
615 filter: makeFilter(relatedField, relatedId),
616 parentModel: this.model});
617 this.app[regionName].show(new relatedListViewClass());
618 if (this.app.hideTabsByDefault) {
619 this.app[regionName].$el.hide();
621 tabs.push({name: relatedName, region: regionName});
626 this.app["linkedObjs" + (index+1)].empty();
631 this.tabClick('#xos-nav-detail', 'detail');
634 onFormDataInvalid: function(errors) {
636 var markErrors = function(value, key) {
637 var $inputElement = self.$el.find("[name='" + key + "']");
638 var $inputContainer = $inputElement.parent();
639 //$inputContainer.find(".help-inline").remove();
640 var $errorEl = $("<span>", {class: "help-inline error", text: value});
641 $inputContainer.append($errorEl).addClass("error");
643 _.each(errors, markErrors);
646 templateHelpers: function() { return { modelName: this.model.modelName,
647 collectionName: this.model.collectionName,
648 addFields: this.model.addFields,
649 listFields: this.model.listFields,
650 detailFields: this.options.detailFields || this.detailFields || this.model.detailFields,
651 foreignFields: this.model.foreignFields,
652 detailLinkFields: this.model.detailLinkFields,
653 inputType: this.model.inputType,
656 choices: this.options.choices || this.choices || this.model.choices || {},
660 XOSDetailView_sliver = XOSDetailView.extend( {
661 events: $.extend(XOSDetailView.events,
662 {"change #field_deploymentNetwork": "onDeploymentNetworkChange"}
666 // Note that this causes the selects to be updated a second time. The
667 // first time was when the template was originally invoked, and the
668 // selects will all have the full unfiltered set of candidates. Then
669 // onShow will fire, and we'll update them with the filtered values.
670 this.onDeploymentNetworkChange();
673 onDeploymentNetworkChange: function(e) {
674 var deploymentID = this.$el.find("#field_deploymentNetwork").val();
676 console.log("onDeploymentNetworkChange");
677 console.log(deploymentID);
679 filterFunc = function(model) { return (model.attributes.deployment==deploymentID); }
680 newSelect = idToSelect("node",
681 this.model.attributes.node,
682 this.model.foreignFields["node"],
686 this.$el.find("#field_node").html(newSelect);
688 filterFunc = function(model) { for (index in model.attributes.deployments) {
689 item=model.attributes.deployments[index];
690 if (item.toString()==deploymentID.toString()) return true;
694 newSelect = idToSelect("flavor",
695 this.model.attributes.flavor,
696 this.model.foreignFields["flavor"],
700 this.$el.find("#field_flavor").html(newSelect);
702 filterFunc = function(model) { for (index in xos.imageDeployments.models) {
703 imageDeployment = xos.imageDeployments.models[index];
704 if ((imageDeployment.attributes.deployment == deploymentID) && (imageDeployment.attributes.image == model.id)) {
710 newSelect = idToSelect("image",
711 this.model.attributes.image,
712 this.model.foreignFields["image"],
716 this.$el.find("#field_image").html(newSelect);
721 This is for items that will be displayed as table rows.
723 app - MarionetteApplication
724 template - template (See XOSHelper.html)
727 XOSItemView = Marionette.ItemView.extend({
729 className: 'test-tablerow',
731 templateHelpers: function() { return { modelName: this.model.modelName,
732 collectionName: this.model.collectionName,
733 listFields: this.model.listFields,
734 addFields: this.model.addFields,
735 detailFields: this.model.detailFields,
736 foreignFields: this.model.foreignFields,
737 detailLinkFields: this.model.detailLinkFields,
738 inputType: this.model.inputType,
745 app - MarionetteApplication
746 childView - class of ItemView, probably an XOSItemView
747 template - template (see xosHelper.html)
748 collection - collection that holds these objects
749 title - title to display in template
752 XOSListView = FilteredCompositeView.extend({
753 childViewContainer: 'tbody',
756 events: {"click button.btn-xos-add": "addClicked",
757 "click button.btn-xos-refresh": "refreshClicked",
760 _fetchStateChange: function() {
761 if (this.collection.fetching) {
762 $("#xos-list-title-spinner").show();
764 $("#xos-list-title-spinner").hide();
768 addClicked: function(e) {
770 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
773 refreshClicked: function(e) {
775 this.collection.refresh(refreshRelated=true);
778 initialize: function() {
779 this.listenTo(this.collection, 'change', this._renderChildren)
780 this.listenTo(this.collection, 'sort', function() { console.log("sort"); })
781 this.listenTo(this.collection, 'add', function() { console.log("add"); })
782 this.listenTo(this.collection, 'fetchStateChange', this._fetchStateChange);
784 // Because many of the templates use idToName(), we need to
785 // listen to the collections that hold the names for the ids
786 // that we want to display.
787 for (i in this.collection.foreignCollections) {
788 foreignName = this.collection.foreignCollections[i];
789 if (xos[foreignName] == undefined) {
790 console.log("Failed to find xos class " + foreignName);
792 this.listenTo(xos[foreignName], 'change', this._renderChildren);
793 this.listenTo(xos[foreignName], 'sort', this._renderChildren);
797 getAddChildHash: function() {
798 if (this.parentModel) {
799 parentFieldName = this.parentModel.relatedCollections[this.collection.collectionName];
800 parentFieldName = parentFieldName || "unknown";
802 /*parentFieldName = "unknown";
804 for (fieldName in this.collection.foreignFields) {
805 cname = this.collection.foreignFields[fieldName];
806 if (cname = this.collection.collectionName) {
807 parentFieldName = fieldName;
810 return "#addChild" + firstCharUpper(this.collection.modelName) + "/" + this.parentModel.modelName + "/" + parentFieldName + "/" + this.parentModel.id; // modelName, fieldName, id
816 templateHelpers: function() {
817 return { title: this.title,
818 addChildHash: this.getAddChildHash(),
819 foreignFields: this.collection.foreignFields,
820 listFields: this.collection.listFields,
821 detailLinkFields: this.collection.detailLinkFields, };
825 XOSDataTableView = Marionette.View.extend( {
826 el: '<div style="overflow: hidden">' +
827 '<h3 class="xos-list-title title_placeholder"></h3>' +
828 '<div class="header_placeholder"></div>' +
830 '<div class="footer_placeholder"></div>' +
835 events: {"click button.btn-xos-add": "addClicked",
836 "click button.btn-xos-refresh": "refreshClicked",
839 _fetchStateChange: function() {
840 if (this.collection.fetching) {
841 $("#xos-list-title-spinner").show();
843 $("#xos-list-title-spinner").hide();
847 addClicked: function(e) {
849 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
852 refreshClicked: function(e) {
854 this.collection.refresh(refreshRelated=true);
858 initialize: function() {
859 $(this.el).find(".footer_placeholder").html( xosListFooterTemplate({addChildHash: this.getAddChildHash()}) );
860 $(this.el).find(".header_placeholder").html( xosListHeaderTemplate() );
862 this.listenTo(this.collection, 'fetchStateChange', this._fetchStateChange);
868 view.columnsByIndex = [];
869 view.columnsByFieldName = {};
870 _.each(this.collection.listFields, function(fieldName) {
871 inputType = view.options.inputType || view.inputType || {};
873 mSearchText = undefined;
874 sTitle = fieldNameToHumanReadable(fieldName);
876 if (fieldName=="backend_status") {
877 mRender = function(x,y,z) { return xosBackendStatusIconTemplate(z); };
880 } else if (fieldName in view.collection.foreignFields) {
881 var foreignCollection = view.collection.foreignFields[fieldName];
882 mSearchText = function(x) { return idToName(x, foreignCollection, "humanReadableName"); };
883 } else if (inputType[fieldName] == "spinner") {
884 mRender = function(x,y,z) { return xosDataTableSpinnerTemplate( {value: x, collectionName: view.collection.collectionName, fieldName: fieldName, id: z.id} ); };
886 if ($.inArray(fieldName, view.collection.detailLinkFields)>=0) {
887 var collectionName = view.collection.collectionName;
888 mRender = function(x,y,z) { return '<a href="#' + collectionName + '/' + z.id + '">' + x + '</a>'; };
890 thisColumn = {sTitle: sTitle, bSortable: bSortable, mData: fieldName, mRender: mRender, mSearchText: mSearchText};
891 view.columnsByIndex.push( thisColumn );
892 view.columnsByFieldName[fieldName] = thisColumn;
895 if (!view.noDeleteColumn) {
896 deleteColumn = {sTitle: "", bSortable: false, mRender: function(x,y,z) { return xosDeleteButtonTemplate({modelName: view.collection.modelName, id: z.id}); }, mData: function() { return "delete"; }};
897 view.columnsByIndex.push(deleteColumn);
898 view.columnsByFieldName["delete"] = deleteColumn;
901 oTable = $(this.el).find("table").dataTable( {
905 "aoColumns": view.columnsByIndex,
907 fnServerData: function(sSource, aoData, fnCallback, settings) {
908 var compareColumns = function(sortCols, sortDirs, a, b) {
911 result = (a==b) ? 0 : ((a<b) ? -1 : 1);
912 if (sortDirs[0] == "desc") {
918 var searchMatch = function(row, sSearch) {
919 for (fieldName in row) {
920 if (fieldName in view.columnsByFieldName) {
922 value = row[fieldName].toString();
926 if (value.indexOf(sSearch) >= 0) {
934 //console.log(aoData);
936 // function used to populate the DataTable with the current
\r
937 // content of the collection
\r
938 var populateTable = function()
\r
940 //console.log("populatetable!");
\r
942 // clear out old row views
\r
947 iDisplayLength = 1000;
\r
950 _.each(aoData, function(param) {
\r
951 if (param.name == "sSortDir_0") {
\r
952 sortDirs = [param.value];
\r
953 } else if (param.name == "iSortCol_0") {
\r
954 sortCols = [view.columnsByIndex[param.value].mData];
\r
955 } else if (param.name == "iDisplayStart") {
\r
956 iDisplayStart = param.value;
\r
957 } else if (param.name == "iDisplayLength") {
\r
958 iDisplayLength = param.value;
\r
959 } else if (param.name == "sSearch") {
\r
960 sSearch = param.value;
\r
964 aaData = view.collection.toJSON();
\r
966 // apply backbone filtering on the models
\r
968 aaData = aaData.filter( function(row) { model = {}; model.attributes = row; return view.filter(model); } );
\r
971 var totalSize = aaData.length;
\r
973 // turn the ForeignKey fields into human readable things
\r
974 for (rowIndex in aaData) {
\r
975 row = aaData[rowIndex];
\r
976 for (fieldName in row) {
\r
977 if (fieldName in view.columnsByFieldName) {
\r
978 mSearchText = view.columnsByFieldName[fieldName].mSearchText;
\r
980 row[fieldName] = mSearchText(row[fieldName]);
\r
986 // apply datatables search
\r
988 aaData = aaData.filter( function(row) { return searchMatch(row, sSearch); });
\r
991 var filteredSize = aaData.length;
\r
993 // apply datatables sort
\r
994 aaData.sort(function(a,b) { return compareColumns(sortCols, sortDirs, a, b); });
\r
996 // slice it for pagination
\r
997 aaData = aaData.slice(iDisplayStart, iDisplayStart+iDisplayLength);
\r
999 return fnCallback({iTotalRecords: totalSize,
\r
1000 iTotalDisplayRecords: filteredSize,
\r
1004 aoData.shift(); // ignore sEcho
1007 view.listenTo(view.collection, 'change', populateTable);
1008 view.listenTo(view.collection, 'add', populateTable);
1009 view.listenTo(view.collection, 'remove', populateTable);
1016 getAddChildHash: function() {
1017 if (this.parentModel) {
1018 parentFieldName = this.parentModel.relatedCollections[this.collection.collectionName];
1019 parentFieldName = parentFieldName || "unknown";
1021 /*parentFieldName = "unknown";
1023 for (fieldName in this.collection.foreignFields) {
1024 cname = this.collection.foreignFields[fieldName];
1025 if (cname = this.collection.collectionName) {
1026 parentFieldName = fieldName;
1029 return "#addChild" + firstCharUpper(this.collection.modelName) + "/" + this.parentModel.modelName + "/" + parentFieldName + "/" + this.parentModel.id; // modelName, fieldName, id
1037 idToName = function(id, collectionName, fieldName) {
1038 return xos.idToName(id, collectionName, fieldName);
1041 makeIdToName = function(collectionName, fieldName) {
1042 return function(id) { return idToName(id, collectionName, fieldName); }
1045 /* Constructs lists of <option> html blocks for items in a collection.
1047 selectedId = the id of an object that should be selected, if any
1048 collectionName = name of collection
1049 fieldName = name of field within models of collection that will be displayed
1052 idToOptions = function(selectedId, collectionName, fieldName, filterFunc) {
1054 for (index in xos[collectionName].models) {
1055 linkedObject = xos[collectionName].models[index];
1056 linkedId = linkedObject["id"];
1057 linkedName = linkedObject.attributes[fieldName];
1058 if (linkedId == selectedId) {
1059 selected = " selected";
1063 if ((filterFunc) && (!filterFunc(linkedObject))) {
1066 result = result + '<option value="' + linkedId + '"' + selected + '>' + linkedName + '</option>';
1071 /* Constructs an html <select> and the <option>s to go with it.
1073 variable = variable name to return to form
1074 selectedId = the id of an object that should be selected, if any
1075 collectionName = name of collection
1076 fieldName = name of field within models of collection that will be displayed
1079 idToSelect = function(variable, selectedId, collectionName, fieldName, readOnly, filterFunc) {
1081 readOnly = " readonly";
1085 result = '<select name="' + variable + '" id="field_' + variable + '"' + readOnly + '>' +
1086 idToOptions(selectedId, collectionName, fieldName, filterFunc) +
1091 choicesToOptions = function(selectedValue, choices) {
1093 for (index in choices) {
1094 choice = choices[index];
1095 displayName = choice[0];
1097 if (value == selectedValue) {
1098 selected = " selected";
1102 result = result + '<option value="' + value + '"' + selected + '>' + displayName + '</option>';
1107 choicesToSelect = function(variable, selectedValue, choices) {
1108 result = '<select name="' + variable + '" id="field_' + variable + '">' +
1109 choicesToOptions(selectedValue, choices) +