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() {
11 if (this.options.selectedID == this.model.get("id")) {
12 return { value: this.model.get("id"), selected: 1 };
14 return { value: this.model.get("id") };
19 SliceSelectorView = Marionette.CompositeView.extend({
20 template: "#xos-sliceselector-select",
21 childViewContainer: "select",
22 childView: SliceSelectorOption,
24 events: {"change select": "onSliceChanged"},
26 childViewOptions: function() {
27 return { selectedID: this.options.selectedID || this.selectedID || null };
30 onSliceChanged: function() {
31 this.sliceChanged(this.$el.find("select").val());
34 sliceChanged: function(id) {
35 console.log("sliceChanged " + id);
39 FilteredCompositeView = Marionette.CompositeView.extend( {
40 showCollection: function() {
42 this.collection.each(function(child, index) {
43 if (this.filter && !this.filter(child)) {
46 ChildView = this.getChildView(child);
47 this.addChild(child, ChildView, index);
53 XOSRouter = Marionette.AppRouter.extend({
54 initialize: function() {
\r
58 onRoute: function(x,y,z) {
\r
59 this.routeStack.push(Backbone.history.fragment);
\r
60 this.routeStack = this.routeStack.slice(-32); // limit the size of routeStack to something reasonable
\r
63 prevPage: function() {
\r
64 return this.routeStack.slice(-1)[0];
67 showPreviousURL: function() {
68 prevPage = this.prevPage();
69 //console.log("showPreviousURL");
70 //console.log(this.routeStack);
72 this.navigate("#"+prevPage, {trigger: false, replace: true} );
76 navigate: function(href, options) {
78 Marionette.AppRouter.prototype.navigate.call(this, "nowhere", {trigger: false, replace: true});
80 Marionette.AppRouter.prototype.navigate.call(this, href, options);
84 Backbone.Syphon.InputReaders.register('select', function(el) {
85 // Modify syphon so that if a select has "syphonall" in the class, then
86 // the value of every option will be returned, regardless of whether of
87 // not it is selected.
88 if (el.hasClass("syphonall")) {
90 _.each(el.find("option"), function(option) {
91 result.push($(option).val());
98 XOSApplication = Marionette.Application.extend({
99 detailBoxId: "#detailBox",
100 errorBoxId: "#errorBox",
101 errorCloseButtonId: "#close-error-box",
102 successBoxId: "#successBox",
103 successCloseButtonId: "#close-success-box",
104 errorTemplate: "#xos-error-template",
105 successTemplate: "#xos-success-template",
108 confirmDialog: function(view, event, callback) {
109 $("#xos-confirm-dialog").dialog({
113 "Confirm" : function() {
114 $(this).dialog("close");
122 "Cancel" : function() {
123 $(this).dialog("close");
127 $("#xos-confirm-dialog").dialog("open");
130 popupErrorDialog: function(responseText) {
132 parsed_error=$.parseJSON(responseText);
136 parsed_error=undefined;
137 width=640; // django stacktraces like wide width
139 console.log(responseText);
140 console.log(parsed_error);
142 $("#xos-error-dialog").html(templateFromId("#xos-error-response")(parsed_error));
144 $("#xos-error-dialog").html(templateFromId("#xos-error-rawresponse")({responseText: responseText}))
147 $("#xos-error-dialog").dialog({
151 Ok: function() { $(this).dialog("close"); }
156 hideLinkedItems: function(result) {
159 this["linkedObjs" + (index+1)].empty();
164 hideTabs: function() { $("#tabs").hide(); },
165 showTabs: function() { $("#tabs").show(); },
167 createListHandler: function(listViewName, collection_name, regionName, title) {
170 listView = new app[listViewName];
171 app[regionName].show(listView);
172 app.hideLinkedItems();
173 $("#contentTitle").html(templateFromId("#xos-title-list")({"title": title}));
177 listButtons = new XOSListButtonView({linkedView: listView});
178 app["rightButtonPanel"].show(listButtons);
182 createAddHandler: function(detailName, collection_name, regionName, title) {
185 console.log("addHandler");
187 app.hideLinkedItems();
190 model = new xos[collection_name].model();
191 detailViewClass = app[detailName];
192 detailView = new detailViewClass({model: model, collection:xos[collection_name]});
193 app[regionName].show(detailView);
195 detailButtons = new XOSDetailButtonView({linkedView: detailView});
196 app["rightButtonPanel"].show(detailButtons);
200 createAddChildHandler: function(addChildName, collection_name) {
202 return function(parent_modelName, parent_fieldName, parent_id) {
203 app.Router.showPreviousURL();
204 model = new xos[collection_name].model();
205 model.attributes[parent_fieldName] = parent_id;
206 model.readOnlyFields.push(parent_fieldName);
207 detailViewClass = app[addChildName];
208 var detailView = new detailViewClass({model: model, collection:xos[collection_name]});
209 detailView.dialog = $("xos-addchild-dialog");
210 app["addChildDetail"].show(detailView);
211 $("#xos-addchild-dialog").dialog({
216 "Save" : function() {
217 var addDialog = this;
218 detailView.synchronous = true;
219 detailView.afterSave = function() { console.log("addChild afterSave"); $(addDialog).dialog("close"); }
222 //$(this).dialog("close");
224 "Cancel" : function() {
225 $(this).dialog("close");
229 $("#xos-addchild-dialog").dialog("open");
233 createDeleteHandler: function(collection_name) {
235 return function(model_id) {
236 console.log("deleteCalled");
237 collection = xos[collection_name];
238 model = collection.get(model_id);
239 assert(model!=undefined, "failed to get model " + model_id + " from collection " + collection_name);
240 app.Router.showPreviousURL();
241 app.deleteDialog(model);
245 createDetailHandler: function(detailName, collection_name, regionName, title) {
247 showModelId = function(model_id) {
248 $("#contentTitle").html(templateFromId("#xos-title-detail")({"title": title}));
250 collection = xos[collection_name];
251 model = collection.get(model_id);
252 if (model == undefined) {
253 app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name}));
255 detailViewClass = app[detailName];
256 detailView = new detailViewClass({model: model});
257 app[regionName].show(detailView);
258 detailView.showLinkedItems();
260 detailButtons = new XOSDetailButtonView({linkedView: detailView});
261 app["rightButtonPanel"].show(detailButtons);
267 /* error handling callbacks */
269 hideError: function() {
270 if (this.logWindowId) {
272 $(this.errorBoxId).hide();
273 $(this.successBoxId).hide();
277 showSuccess: function(result) {
278 result["statusclass"] = "success";
279 if (this.logTableId) {
280 this.appendLogWindow(result);
282 $(this.successBoxId).show();
283 $(this.successBoxId).html(_.template($(this.successTemplate).html())(result));
285 $(this.successCloseButtonId).unbind().bind('click', function() {
286 $(that.successBoxId).hide();
291 showError: function(result) {
292 result["statusclass"] = "failure";
293 if (this.logTableId) {
294 this.appendLogWindow(result);
295 this.popupErrorDialog(result.responseText);
297 // this is really old stuff
298 $(this.errorBoxId).show();
299 $(this.errorBoxId).html(_.template($(this.errorTemplate).html())(result));
301 $(this.errorCloseButtonId).unbind().bind('click', function() {
302 $(that.errorBoxId).hide();
307 showInformational: function(result) {
308 result["statusclass"] = "inprog";
309 if (this.logTableId) {
310 return this.appendLogWindow(result);
316 appendLogWindow: function(result) {
317 // compute a new logMessageId for this log message
318 logMessageId = "logMessage" + this.logMessageCount;
319 this.logMessageCount = this.logMessageCount + 1;
320 result["logMessageId"] = logMessageId;
322 logMessageTemplate=$("#xos-log-template").html();
323 assert(logMessageTemplate != undefined, "logMessageTemplate is undefined");
324 newRow = _.template(logMessageTemplate, result);
325 assert(newRow != undefined, "newRow is undefined");
327 if (result["infoMsgId"] != undefined) {
328 // We were passed the logMessageId of an informational message,
329 // and the caller wants us to replace that message with our own.
330 // i.e. replace an informational message with a success or an error.
331 $("#"+result["infoMsgId"]).replaceWith(newRow);
333 // Create a brand new log message rather than replacing one.
334 logTableBody = $(this.logTableId + " tbody");
335 logTableBody.prepend(newRow);
338 if (this.statusMsgId) {
339 $(this.statusMsgId).html( templateFromId("#xos-status-template")(result) );
342 limitTableRows(this.logTableId, 5);
347 saveError: function(model, result, xhr, infoMsgId) {
348 console.log("saveError");
349 result["what"] = "save " + model.modelName + " " + model.attributes.humanReadableName;
350 result["infoMsgId"] = infoMsgId;
351 this.showError(result);
354 saveSuccess: function(model, result, xhr, infoMsgId, addToCollection) {
355 console.log("saveSuccess");
356 if (model.addToCollection) {
357 console.log("addToCollection");
358 console.log(model.addToCollection);
359 model.addToCollection.add(model);
360 model.addToCollection.sort();
361 model.addToCollection = undefined;
363 result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};
364 result["what"] = "save " + model.modelName + " " + model.attributes.humanReadableName;
365 result["infoMsgId"] = infoMsgId;
366 this.showSuccess(result);
369 destroyError: function(model, result, xhr, infoMsgId) {
370 result["what"] = "destroy " + model.modelName + " " + model.attributes.humanReadableName;
371 result["infoMsgId"] = infoMsgId;
372 this.showError(result);
375 destroySuccess: function(model, result, xhr, infoMsgId) {
376 result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};
377 result["what"] = "destroy " + model.modelName + " " + model.attributes.humanReadableName;
378 result["infoMsgId"] = infoMsgId;
379 this.showSuccess(result);
382 /* end error handling callbacks */
384 destroyModel: function(model) {
385 //console.log("destroyModel"); console.log(model);
387 var infoMsgId = this.showInformational( {what: "destroy " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
389 model.destroy({error: function(model, result, xhr) { that.destroyError(model,result,xhr,infoMsgId);},
390 success: function(model, result, xhr) { that.destroySuccess(model,result,xhr,infoMsgId);}});
393 deleteDialog: function(model, afterDelete) {
395 assert(model!=undefined, "deleteDialog's model is undefined");
396 //console.log("deleteDialog"); console.log(model);
397 this.confirmDialog(null, null, function() {
398 //console.log("deleteConfirm"); console.log(model);
399 modelName = model.modelName;
400 that.destroyModel(model);
401 if (afterDelete=="list") {
402 that.navigate("list", modelName);
408 XOSButtonView = Marionette.ItemView.extend({
409 events: {"click button.btn-xos-save-continue": "submitContinueClicked",
410 "click button.btn-xos-save-leave": "submitLeaveClicked",
411 "click button.btn-xos-save-another": "submitAddAnotherClicked",
412 "click button.btn-xos-delete": "deleteClicked",
413 "click button.btn-xos-add": "addClicked",
414 "click button.btn-xos-refresh": "refreshClicked",
417 submitLeaveClicked: function(e) {
418 this.options.linkedView.submitLeaveClicked.call(this.options.linkedView, e);
421 submitContinueClicked: function(e) {
422 this.options.linkedView.submitContinueClicked.call(this.options.linkedView, e);
425 submitAddAnotherClicked: function(e) {
426 this.options.linkedView.submitAddAnotherClicked.call(this.options.linkedView, e);
429 submitDeleteClicked: function(e) {
430 this.options.linkedView.deleteClicked.call(this.options.linkedView, e);
433 addClicked: function(e) {
434 this.options.linkedView.addClicked.call(this.options.linkedView, e);
437 refreshClicked: function(e) {
438 this.options.linkedView.refreshClicked.call(this.options.linkedView, e);
442 XOSDetailButtonView = XOSButtonView.extend({ template: "#xos-savebuttons-template" });
443 XOSListButtonView = XOSButtonView.extend({ template: "#xos-listbuttons-template" });
447 app - MarionetteApplication
448 template - template (See XOSHelper.html)
451 XOSDetailView = Marionette.ItemView.extend({
454 viewInitializers: [],
456 events: {"click button.btn-xos-save-continue": "submitContinueClicked",
457 "click button.btn-xos-save-leave": "submitLeaveClicked",
458 "click button.btn-xos-save-another": "submitAddAnotherClicked",
459 "click button.btn-xos-delete": "deleteClicked",
460 "change input": "inputChanged"},
462 /* inputChanged is watching the onChange events of the input controls. We
463 do this to track when this view is 'dirty', so we can throw up a warning
464 if the user tries to change his slices without saving first.
467 initialize: function() {
468 this.on("saveSuccess", this.onAfterSave);
469 this.synchronous = false;
473 _.each(this.viewInitializers, function(initializer) {
478 afterSave: function(e) {
481 onAfterSave: function(e) {
485 inputChanged: function(e) {
489 submitContinueClicked: function(e) {
490 console.log("saveContinue");
492 this.afterSave = function() { };
496 submitLeaveClicked: function(e) {
497 console.log("saveLeave");
500 this.afterSave = function() {
501 that.app.navigate("list", that.model.modelName);
506 submitAddAnotherClicked: function(e) {
507 console.log("saveAnother");
511 this.afterSave = function() {
512 console.log("addAnother afterSave");
513 that.app.navigate("add", that.model.modelName);
519 this.app.hideError();
520 var data = Backbone.Syphon.serialize(this);
522 var isNew = !this.model.id;
526 this.$el.find(".help-inline").remove();
528 /* although model.validate() is called automatically by
529 model.save, we call it ourselves, so we can throw up our
530 validation error before creating the infoMsg in the log
532 errors = this.model.xosValidate(data);
534 this.onFormDataInvalid(errors);
539 this.model.attributes.humanReadableName = "new " + this.model.modelName;
540 this.model.addToCollection = this.collection;
542 this.model.addToCollection = undefined;
545 var infoMsgId = this.app.showInformational( {what: "save " + this.model.modelName + " " + this.model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
547 this.model.save(data, {error: function(model, result, xhr) { that.app.saveError(model,result,xhr,infoMsgId);},
548 success: function(model, result, xhr) { that.app.saveSuccess(model,result,xhr,infoMsgId);
549 if (that.synchronous) {
550 that.trigger("saveSuccess");
555 if (!this.synchronous) {
560 deleteClicked: function(e) {
562 this.app.deleteDialog(this.model, "list");
565 tabClick: function(tabId, regionName) {
566 region = this.app[regionName];
567 if (this.currentTabRegion != undefined) {
568 this.currentTabRegion.$el.hide();
570 if (this.currentTabId != undefined) {
571 $(this.currentTabId).removeClass('active');
573 this.currentTabRegion = region;
574 this.currentTabRegion.$el.show();
576 this.currentTabId = tabId;
577 $(tabId).addClass('active');
580 showTabs: function(tabs) {
581 template = templateFromId("#xos-tabs-template", {tabs: tabs});
582 $("#tabs").html(template(tabs));
585 _.each(tabs, function(tab) {
586 var regionName = tab["region"];
587 var tabId = '#xos-nav-'+regionName;
588 $(tabId).bind('click', function() { that.tabClick(tabId, regionName); });
594 showLinkedItems: function() {
597 tabs.push({name: "details", region: "detail"});
599 makeFilter = function(relatedField, relatedId) {
600 return function(model) { return model.attributes[relatedField] == relatedId; }
604 for (relatedName in this.model.collection.relatedCollections) {
605 var relatedField = this.model.collection.relatedCollections[relatedName];
606 var relatedId = this.model.id;
607 regionName = "linkedObjs" + (index+1);
609 relatedListViewClassName = relatedName + "ListView";
610 assert(this.app[relatedListViewClassName] != undefined, relatedListViewClassName + " not found");
611 relatedListViewClass = this.app[relatedListViewClassName].extend({collection: xos[relatedName],
612 filter: makeFilter(relatedField, relatedId),
613 parentModel: this.model});
614 this.app[regionName].show(new relatedListViewClass());
615 if (this.app.hideTabsByDefault) {
616 this.app[regionName].$el.hide();
618 tabs.push({name: relatedName, region: regionName});
623 this.app["linkedObjs" + (index+1)].empty();
628 this.tabClick('#xos-nav-detail', 'detail');
631 onFormDataInvalid: function(errors) {
633 var markErrors = function(value, key) {
634 var $inputElement = self.$el.find("[name='" + key + "']");
635 var $inputContainer = $inputElement.parent();
636 //$inputContainer.find(".help-inline").remove();
637 var $errorEl = $("<span>", {class: "help-inline error", text: value});
638 $inputContainer.append($errorEl).addClass("error");
640 _.each(errors, markErrors);
643 templateHelpers: function() { return { modelName: this.model.modelName,
644 collectionName: this.model.collectionName,
645 addFields: this.model.addFields,
646 listFields: this.model.listFields,
647 detailFields: this.options.detailFields || this.detailFields || this.model.detailFields,
648 foreignFields: this.model.foreignFields,
649 detailLinkFields: this.model.detailLinkFields,
650 inputType: this.model.inputType,
653 choices: this.options.choices || this.choices || this.model.choices || {},
657 XOSDetailView_sliver = XOSDetailView.extend( {
658 events: $.extend(XOSDetailView.events,
659 {"change #field_deploymentNetwork": "onDeploymentNetworkChange"}
663 // Note that this causes the selects to be updated a second time. The
664 // first time was when the template was originally invoked, and the
665 // selects will all have the full unfiltered set of candidates. Then
666 // onShow will fire, and we'll update them with the filtered values.
667 this.onDeploymentNetworkChange();
670 onDeploymentNetworkChange: function(e) {
671 var deploymentID = this.$el.find("#field_deploymentNetwork").val();
673 console.log("onDeploymentNetworkChange");
674 console.log(deploymentID);
676 filterFunc = function(model) { return (model.attributes.deployment==deploymentID); }
677 newSelect = idToSelect("node",
678 this.model.attributes.node,
679 this.model.foreignFields["node"],
683 this.$el.find("#field_node").html(newSelect);
685 filterFunc = function(model) { for (index in model.attributes.deployments) {
686 item=model.attributes.deployments[index];
687 if (item.toString()==deploymentID.toString()) return true;
691 newSelect = idToSelect("flavor",
692 this.model.attributes.flavor,
693 this.model.foreignFields["flavor"],
697 this.$el.find("#field_flavor").html(newSelect);
699 filterFunc = function(model) { for (index in xos.imageDeployments.models) {
700 imageDeployment = xos.imageDeployments.models[index];
701 if ((imageDeployment.attributes.deployment == deploymentID) && (imageDeployment.attributes.image == model.id)) {
707 newSelect = idToSelect("image",
708 this.model.attributes.image,
709 this.model.foreignFields["image"],
713 this.$el.find("#field_image").html(newSelect);
718 This is for items that will be displayed as table rows.
720 app - MarionetteApplication
721 template - template (See XOSHelper.html)
724 XOSItemView = Marionette.ItemView.extend({
726 className: 'test-tablerow',
728 templateHelpers: function() { return { modelName: this.model.modelName,
729 collectionName: this.model.collectionName,
730 listFields: this.model.listFields,
731 addFields: this.model.addFields,
732 detailFields: this.model.detailFields,
733 foreignFields: this.model.foreignFields,
734 detailLinkFields: this.model.detailLinkFields,
735 inputType: this.model.inputType,
742 app - MarionetteApplication
743 childView - class of ItemView, probably an XOSItemView
744 template - template (see xosHelper.html)
745 collection - collection that holds these objects
746 title - title to display in template
749 XOSListView = FilteredCompositeView.extend({
750 childViewContainer: 'tbody',
753 events: {"click button.btn-xos-add": "addClicked",
754 "click button.btn-xos-refresh": "refreshClicked",
757 _fetchStateChange: function() {
758 if (this.collection.fetching) {
759 $("#xos-list-title-spinner").show();
761 $("#xos-list-title-spinner").hide();
765 addClicked: function(e) {
767 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
770 refreshClicked: function(e) {
772 this.collection.refresh(refreshRelated=true);
775 initialize: function() {
776 this.listenTo(this.collection, 'change', this._renderChildren)
777 this.listenTo(this.collection, 'sort', function() { console.log("sort"); })
778 this.listenTo(this.collection, 'add', function() { console.log("add"); })
779 this.listenTo(this.collection, 'fetchStateChange', this._fetchStateChange);
781 // Because many of the templates use idToName(), we need to
782 // listen to the collections that hold the names for the ids
783 // that we want to display.
784 for (i in this.collection.foreignCollections) {
785 foreignName = this.collection.foreignCollections[i];
786 if (xos[foreignName] == undefined) {
787 console.log("Failed to find xos class " + foreignName);
789 this.listenTo(xos[foreignName], 'change', this._renderChildren);
790 this.listenTo(xos[foreignName], 'sort', this._renderChildren);
794 getAddChildHash: function() {
795 if (this.parentModel) {
796 parentFieldName = this.parentModel.relatedCollections[this.collection.collectionName];
797 parentFieldName = parentFieldName || "unknown";
799 /*parentFieldName = "unknown";
801 for (fieldName in this.collection.foreignFields) {
802 cname = this.collection.foreignFields[fieldName];
803 if (cname = this.collection.collectionName) {
804 parentFieldName = fieldName;
807 return "#addChild" + firstCharUpper(this.collection.modelName) + "/" + this.parentModel.modelName + "/" + parentFieldName + "/" + this.parentModel.id; // modelName, fieldName, id
813 templateHelpers: function() {
814 return { title: this.title,
815 addChildHash: this.getAddChildHash(),
816 foreignFields: this.collection.foreignFields,
817 listFields: this.collection.listFields,
818 detailLinkFields: this.collection.detailLinkFields, };
822 XOSDataTableView = Marionette.View.extend( {
823 el: '<div style="overflow: hidden">' +
824 '<h3 class="xos-list-title title_placeholder"></h3>' +
825 '<div class="header_placeholder"></div>' +
827 '<div class="footer_placeholder"></div>' +
832 events: {"click button.btn-xos-add": "addClicked",
833 "click button.btn-xos-refresh": "refreshClicked",
836 _fetchStateChange: function() {
837 if (this.collection.fetching) {
838 $("#xos-list-title-spinner").show();
840 $("#xos-list-title-spinner").hide();
844 addClicked: function(e) {
846 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
849 refreshClicked: function(e) {
851 this.collection.refresh(refreshRelated=true);
855 initialize: function() {
856 $(this.el).find(".footer_placeholder").html( xosListFooterTemplate({addChildHash: this.getAddChildHash()}) );
857 $(this.el).find(".header_placeholder").html( xosListHeaderTemplate() );
859 this.listenTo(this.collection, 'fetchStateChange', this._fetchStateChange);
865 view.columnsByIndex = [];
866 view.columnsByFieldName = {};
867 _.each(this.collection.listFields, function(fieldName) {
868 inputType = view.options.inputType || view.inputType || {};
870 mSearchText = undefined;
871 sTitle = fieldNameToHumanReadable(fieldName);
873 if (fieldName=="backend_status") {
874 mRender = function(x,y,z) { return xosBackendStatusIconTemplate(z); };
877 } else if (fieldName in view.collection.foreignFields) {
878 var foreignCollection = view.collection.foreignFields[fieldName];
879 mSearchText = function(x) { return idToName(x, foreignCollection, "humanReadableName"); };
880 } else if (inputType[fieldName] == "spinner") {
881 mRender = function(x,y,z) { return xosDataTableSpinnerTemplate( {value: x, collectionName: view.collection.collectionName, fieldName: fieldName, id: z.id, app: view.app} ); };
883 if ($.inArray(fieldName, view.collection.detailLinkFields)>=0) {
884 var collectionName = view.collection.collectionName;
885 mRender = function(x,y,z) { return '<a href="#' + collectionName + '/' + z.id + '">' + x + '</a>'; };
887 thisColumn = {sTitle: sTitle, bSortable: bSortable, mData: fieldName, mRender: mRender, mSearchText: mSearchText};
888 view.columnsByIndex.push( thisColumn );
889 view.columnsByFieldName[fieldName] = thisColumn;
892 if (!view.noDeleteColumn) {
893 deleteColumn = {sTitle: "", bSortable: false, mRender: function(x,y,z) { return xosDeleteButtonTemplate({modelName: view.collection.modelName, id: z.id}); }, mData: function() { return "delete"; }};
894 view.columnsByIndex.push(deleteColumn);
895 view.columnsByFieldName["delete"] = deleteColumn;
898 oTable = $(this.el).find("table").dataTable( {
902 "aoColumns": view.columnsByIndex,
904 fnServerData: function(sSource, aoData, fnCallback, settings) {
905 var compareColumns = function(sortCols, sortDirs, a, b) {
908 result = (a==b) ? 0 : ((a<b) ? -1 : 1);
909 if (sortDirs[0] == "desc") {
915 var searchMatch = function(row, sSearch) {
916 for (fieldName in row) {
917 if (fieldName in view.columnsByFieldName) {
919 value = row[fieldName].toString();
923 if (value.indexOf(sSearch) >= 0) {
931 //console.log(aoData);
933 // function used to populate the DataTable with the current
\r
934 // content of the collection
\r
935 var populateTable = function()
\r
937 //console.log("populatetable!");
\r
939 // clear out old row views
\r
944 iDisplayLength = 1000;
\r
947 _.each(aoData, function(param) {
\r
948 if (param.name == "sSortDir_0") {
\r
949 sortDirs = [param.value];
\r
950 } else if (param.name == "iSortCol_0") {
\r
951 sortCols = [view.columnsByIndex[param.value].mData];
\r
952 } else if (param.name == "iDisplayStart") {
\r
953 iDisplayStart = param.value;
\r
954 } else if (param.name == "iDisplayLength") {
\r
955 iDisplayLength = param.value;
\r
956 } else if (param.name == "sSearch") {
\r
957 sSearch = param.value;
\r
961 aaData = view.collection.toJSON();
\r
963 // apply backbone filtering on the models
\r
965 aaData = aaData.filter( function(row) { model = {}; model.attributes = row; return view.filter(model); } );
\r
968 var totalSize = aaData.length;
\r
970 // turn the ForeignKey fields into human readable things
\r
971 for (rowIndex in aaData) {
\r
972 row = aaData[rowIndex];
\r
973 for (fieldName in row) {
\r
974 if (fieldName in view.columnsByFieldName) {
\r
975 mSearchText = view.columnsByFieldName[fieldName].mSearchText;
\r
977 row[fieldName] = mSearchText(row[fieldName]);
\r
983 // apply datatables search
\r
985 aaData = aaData.filter( function(row) { return searchMatch(row, sSearch); });
\r
988 var filteredSize = aaData.length;
\r
990 // apply datatables sort
\r
991 aaData.sort(function(a,b) { return compareColumns(sortCols, sortDirs, a, b); });
\r
993 // slice it for pagination
\r
994 aaData = aaData.slice(iDisplayStart, iDisplayStart+iDisplayLength);
\r
996 return fnCallback({iTotalRecords: totalSize,
\r
997 iTotalDisplayRecords: filteredSize,
\r
1001 aoData.shift(); // ignore sEcho
1004 view.listenTo(view.collection, 'change', populateTable);
1005 view.listenTo(view.collection, 'add', populateTable);
1006 view.listenTo(view.collection, 'remove', populateTable);
1013 getAddChildHash: function() {
1014 if (this.parentModel) {
1015 parentFieldName = this.parentModel.relatedCollections[this.collection.collectionName];
1016 parentFieldName = parentFieldName || "unknown";
1018 /*parentFieldName = "unknown";
1020 for (fieldName in this.collection.foreignFields) {
1021 cname = this.collection.foreignFields[fieldName];
1022 if (cname = this.collection.collectionName) {
1023 parentFieldName = fieldName;
1026 return "#addChild" + firstCharUpper(this.collection.modelName) + "/" + this.parentModel.modelName + "/" + parentFieldName + "/" + this.parentModel.id; // modelName, fieldName, id
1034 idToName = function(id, collectionName, fieldName) {
1035 return xos.idToName(id, collectionName, fieldName);
1038 makeIdToName = function(collectionName, fieldName) {
1039 return function(id) { return idToName(id, collectionName, fieldName); }
1042 /* Constructs lists of <option> html blocks for items in a collection.
1044 selectedId = the id of an object that should be selected, if any
1045 collectionName = name of collection
1046 fieldName = name of field within models of collection that will be displayed
1049 idToOptions = function(selectedId, collectionName, fieldName, filterFunc) {
1051 for (index in xos[collectionName].models) {
1052 linkedObject = xos[collectionName].models[index];
1053 linkedId = linkedObject["id"];
1054 linkedName = linkedObject.attributes[fieldName];
1055 if (linkedId == selectedId) {
1056 selected = " selected";
1060 if ((filterFunc) && (!filterFunc(linkedObject))) {
1063 result = result + '<option value="' + linkedId + '"' + selected + '>' + linkedName + '</option>';
1068 /* Constructs an html <select> and the <option>s to go with it.
1070 variable = variable name to return to form
1071 selectedId = the id of an object that should be selected, if any
1072 collectionName = name of collection
1073 fieldName = name of field within models of collection that will be displayed
1076 idToSelect = function(variable, selectedId, collectionName, fieldName, readOnly, filterFunc) {
1078 readOnly = " readonly";
1082 result = '<select name="' + variable + '" id="field_' + variable + '"' + readOnly + '>' +
1083 idToOptions(selectedId, collectionName, fieldName, filterFunc) +
1088 choicesToOptions = function(selectedValue, choices) {
1090 for (index in choices) {
1091 choice = choices[index];
1092 displayName = choice[0];
1094 if (value == selectedValue) {
1095 selected = " selected";
1099 result = result + '<option value="' + value + '"' + selected + '>' + displayName + '</option>';
1104 choicesToSelect = function(variable, selectedValue, choices) {
1105 result = '<select name="' + variable + '" id="field_' + variable + '">' +
1106 choicesToOptions(selectedValue, choices) +