1 HTMLView = Marionette.ItemView.extend({
3 this.$el.append(this.options.html);
7 FilteredCompositeView = Marionette.CompositeView.extend( {
8 showCollection: function() {
10 this.collection.each(function(child, index) {
11 filterFunc = this.options.filter || this.filter;
12 if (filterFunc && !filterFunc(child)) {
15 ChildView = this.getChildView(child);
16 this.addChild(child, ChildView, index);
22 SliceSelectorOption = Marionette.ItemView.extend({
23 template: "#xos-sliceselector-option",
25 attributes: function() {
26 if (this.options.selectedID == this.model.get("id")) {
27 return { value: this.model.get("id"), selected: 1 };
29 return { value: this.model.get("id") };
34 SliceSelectorView = FilteredCompositeView.extend({
35 template: "#xos-sliceselector-select",
36 childViewContainer: "select",
37 childView: SliceSelectorOption,
40 events: {"change select": "onSliceChanged"},
42 childViewOptions: function() {
43 return { selectedID: this.options.selectedID || this.selectedID || null };
46 onSliceChanged: function() {
47 this.sliceChanged(this.$el.find("select").val());
50 sliceChanged: function(id) {
51 console.log("sliceChanged " + id);
54 templateHelpers: function() { return {caption: this.options.caption || this.caption }; },
57 XOSRouter = Marionette.AppRouter.extend({
58 initialize: function() {
\r
62 onRoute: function(x,y,z) {
\r
63 this.routeStack.push(Backbone.history.fragment);
\r
64 this.routeStack = this.routeStack.slice(-32); // limit the size of routeStack to something reasonable
\r
67 prevPage: function() {
\r
68 return this.routeStack.slice(-1)[0];
71 showPreviousURL: function() {
72 prevPage = this.prevPage();
73 //console.log("showPreviousURL");
74 //console.log(this.routeStack);
76 this.navigate("#"+prevPage, {trigger: false, replace: true} );
80 navigate: function(href, options) {
82 Marionette.AppRouter.prototype.navigate.call(this, "nowhere", {trigger: false, replace: true});
84 Marionette.AppRouter.prototype.navigate.call(this, href, options);
88 // XXX - We import backbone multiple times (BAD!) since the import happens
\r
89 // inside of the view's html. The second time it's imported (developer
\r
90 // view), it wipes out Backbone.Syphon. So, save it as Backbone_Syphon for
\r
92 Backbone_Syphon = Backbone.Syphon
\r
93 Backbone_Syphon.InputReaders.register('select', function(el) {
\r
94 // Modify syphon so that if a select has "syphonall" in the class, then
95 // the value of every option will be returned, regardless of whether of
96 // not it is selected.
97 if (el.hasClass("syphonall")) {
99 _.each(el.find("option"), function(option) {
100 result.push($(option).val());
107 XOSApplication = Marionette.Application.extend({
108 detailBoxId: "#detailBox",
109 errorBoxId: "#errorBox",
110 errorCloseButtonId: "#close-error-box",
111 successBoxId: "#successBox",
112 successCloseButtonId: "#close-success-box",
113 errorTemplate: "#xos-error-template",
114 successTemplate: "#xos-success-template",
117 confirmDialog: function(view, event, callback) {
118 $("#xos-confirm-dialog").dialog({
122 "Confirm" : function() {
123 $(this).dialog("close");
131 "Cancel" : function() {
132 $(this).dialog("close");
136 $("#xos-confirm-dialog").dialog("open");
139 popupErrorDialog: function(responseText) {
141 parsed_error=$.parseJSON(responseText);
145 parsed_error=undefined;
146 width=640; // django stacktraces like wide width
148 console.log(responseText);
149 console.log(parsed_error);
151 if (parsed_error && ("error" in parsed_error)) {
152 // this error comes from genapi views
153 $("#xos-error-dialog").html(templateFromId("#xos-error-response")(parsed_error));
154 } else if (parsed_error && ("detail" in parsed_error)) {
155 // this error response comes from rest_framework APIException
156 parsed_error["error"] = "API Error";
157 parsed_error["specific_error"] = parsed_error["detail"];
158 parsed_error["reasons"] = [];
159 $("#xos-error-dialog").html(templateFromId("#xos-error-response")(parsed_error));
161 $("#xos-error-dialog").html(templateFromId("#xos-error-rawresponse")({responseText: strip_scripts(responseText)}))
164 $("#xos-error-dialog").dialog({
168 Ok: function() { $(this).dialog("close"); }
173 hideLinkedItems: function(result) {
176 this["linkedObjs" + (index+1)].empty();
181 hideTabs: function() { $("#tabs").hide(); },
182 showTabs: function() { $("#tabs").show(); },
184 createListHandler: function(listViewName, collection_name, regionName, title) {
187 listView = new app[listViewName];
188 app[regionName].show(listView);
189 app.hideLinkedItems();
190 $("#contentTitle").html(templateFromId("#xos-title-list")({"title": title}));
194 listButtons = new XOSListButtonView({linkedView: listView});
195 app["rightButtonPanel"].show(listButtons);
199 createAddHandler: function(detailName, collection_name, regionName, title) {
202 console.log("addHandler");
204 app.hideLinkedItems();
207 model = new xos[collection_name].model();
208 detailViewClass = app[detailName];
209 detailView = new detailViewClass({model: model, collection:xos[collection_name]});
210 app[regionName].show(detailView);
212 detailButtons = new XOSDetailButtonView({linkedView: detailView});
213 app["rightButtonPanel"].show(detailButtons);
217 createAddChildHandler: function(addChildName, collection_name) {
219 return function(parent_modelName, parent_fieldName, parent_id) {
220 app.Router.showPreviousURL();
221 model = new xos[collection_name].model();
222 model.attributes[parent_fieldName] = parent_id;
223 model.readOnlyFields.push(parent_fieldName);
224 detailViewClass = app[addChildName];
225 var detailView = new detailViewClass({model: model, collection:xos[collection_name]});
226 detailView.dialog = $("xos-addchild-dialog");
227 app["addChildDetail"].show(detailView);
228 $("#xos-addchild-dialog").dialog({
233 "Save" : function() {
234 var addDialog = this;
235 detailView.synchronous = true;
236 detailView.afterSave = function() { console.log("addChild afterSave"); $(addDialog).dialog("close"); }
239 //$(this).dialog("close");
241 "Cancel" : function() {
242 $(this).dialog("close");
246 $("#xos-addchild-dialog").dialog("open");
250 createDeleteHandler: function(collection_name) {
252 return function(model_id) {
253 console.log("deleteCalled");
254 collection = xos[collection_name];
255 model = collection.get(model_id);
256 assert(model!=undefined, "failed to get model " + model_id + " from collection " + collection_name);
257 app.Router.showPreviousURL();
258 app.deleteDialog(model);
262 createDetailHandler: function(detailName, collection_name, regionName, title) {
264 showModelId = function(model_id) {
265 $("#contentTitle").html(templateFromId("#xos-title-detail")({"title": title}));
267 collection = xos[collection_name];
268 model = collection.get(model_id);
269 if (model == undefined) {
270 app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name}));
272 detailViewClass = app[detailName];
273 detailView = new detailViewClass({model: model});
274 app[regionName].show(detailView);
275 detailView.showLinkedItems();
277 detailButtons = new XOSDetailButtonView({linkedView: detailView});
278 app["rightButtonPanel"].show(detailButtons);
284 /* error handling callbacks */
286 hideError: function() {
287 if (this.logWindowId) {
289 $(this.errorBoxId).hide();
290 $(this.successBoxId).hide();
294 showSuccess: function(result) {
295 result["statusclass"] = "success";
296 if (this.logTableId) {
297 this.appendLogWindow(result);
299 $(this.successBoxId).show();
300 $(this.successBoxId).html(_.template($(this.successTemplate).html())(result));
302 $(this.successCloseButtonId).unbind().bind('click', function() {
303 $(that.successBoxId).hide();
308 showError: function(result) {
309 result["statusclass"] = "failure";
310 if (this.logTableId) {
311 this.appendLogWindow(result);
312 this.popupErrorDialog(result.responseText);
314 // this is really old stuff
315 $(this.errorBoxId).show();
316 $(this.errorBoxId).html(_.template($(this.errorTemplate).html())(result));
318 $(this.errorCloseButtonId).unbind().bind('click', function() {
319 $(that.errorBoxId).hide();
324 showInformational: function(result) {
325 result["statusclass"] = "inprog";
326 if (this.logTableId) {
327 return this.appendLogWindow(result);
333 appendLogWindow: function(result) {
334 // compute a new logMessageId for this log message
335 logMessageId = "logMessage" + this.logMessageCount;
336 this.logMessageCount = this.logMessageCount + 1;
337 result["logMessageId"] = logMessageId;
339 logMessageTemplate=$("#xos-log-template").html();
340 assert(logMessageTemplate != undefined, "logMessageTemplate is undefined");
341 newRow = _.template(logMessageTemplate, result);
342 assert(newRow != undefined, "newRow is undefined");
344 if (result["infoMsgId"] != undefined) {
345 // We were passed the logMessageId of an informational message,
346 // and the caller wants us to replace that message with our own.
347 // i.e. replace an informational message with a success or an error.
348 $("#"+result["infoMsgId"]).replaceWith(newRow);
350 // Create a brand new log message rather than replacing one.
351 logTableBody = $(this.logTableId + " tbody");
352 logTableBody.prepend(newRow);
355 if (this.statusMsgId) {
356 $(this.statusMsgId).html( templateFromId("#xos-status-template")(result) );
359 limitTableRows(this.logTableId, 5);
364 saveError: function(model, result, xhr, infoMsgId) {
365 console.log("saveError");
366 result["what"] = "save " + model.modelName + " " + model.attributes.humanReadableName;
367 result["infoMsgId"] = infoMsgId;
368 this.showError(result);
371 saveSuccess: function(model, result, xhr, infoMsgId, addToCollection) {
372 console.log("saveSuccess");
373 if (model.addToCollection) {
374 console.log("addToCollection");
375 console.log(model.addToCollection);
376 model.addToCollection.add(model);
377 model.addToCollection.sort();
378 model.addToCollection = undefined;
380 result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};
381 result["what"] = "save " + model.modelName + " " + model.attributes.humanReadableName;
382 result["infoMsgId"] = infoMsgId;
383 this.showSuccess(result);
386 destroyError: function(model, result, xhr, infoMsgId) {
387 result["what"] = "destroy " + model.modelName + " " + model.attributes.humanReadableName;
388 result["infoMsgId"] = infoMsgId;
389 this.showError(result);
392 destroySuccess: function(model, result, xhr, infoMsgId) {
393 result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};
394 result["what"] = "destroy " + model.modelName + " " + model.attributes.humanReadableName;
395 result["infoMsgId"] = infoMsgId;
396 this.showSuccess(result);
399 /* end error handling callbacks */
401 destroyModel: function(model) {
402 //console.log("destroyModel"); console.log(model);
404 var infoMsgId = this.showInformational( {what: "destroy " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
406 model.destroy({error: function(model, result, xhr) { that.destroyError(model,result,xhr,infoMsgId);},
407 success: function(model, result, xhr) { that.destroySuccess(model,result,xhr,infoMsgId);}});
410 deleteDialog: function(model, afterDelete) {
412 assert(model!=undefined, "deleteDialog's model is undefined");
413 //console.log("deleteDialog"); console.log(model);
414 this.confirmDialog(null, null, function() {
415 //console.log("deleteConfirm"); console.log(model);
416 modelName = model.modelName;
417 that.destroyModel(model);
418 if (afterDelete=="list") {
419 that.navigate("list", modelName);
420 } else if (afterDelete) {
427 XOSButtonView = Marionette.ItemView.extend({
428 events: {"click button.btn-xos-save-continue": "submitContinueClicked",
429 "click button.btn-xos-save-leave": "submitLeaveClicked",
430 "click button.btn-xos-save-another": "submitAddAnotherClicked",
431 "click button.btn-xos-delete": "deleteClicked",
432 "click button.btn-xos-add": "addClicked",
433 "click button.btn-xos-refresh": "refreshClicked",
436 submitLeaveClicked: function(e) {
437 this.options.linkedView.submitLeaveClicked.call(this.options.linkedView, e);
440 submitContinueClicked: function(e) {
441 this.options.linkedView.submitContinueClicked.call(this.options.linkedView, e);
444 submitAddAnotherClicked: function(e) {
445 this.options.linkedView.submitAddAnotherClicked.call(this.options.linkedView, e);
448 submitDeleteClicked: function(e) {
449 this.options.linkedView.deleteClicked.call(this.options.linkedView, e);
452 addClicked: function(e) {
453 this.options.linkedView.addClicked.call(this.options.linkedView, e);
456 refreshClicked: function(e) {
457 this.options.linkedView.refreshClicked.call(this.options.linkedView, e);
461 XOSDetailButtonView = XOSButtonView.extend({ template: "#xos-savebuttons-template" });
462 XOSListButtonView = XOSButtonView.extend({ template: "#xos-listbuttons-template" });
466 app - MarionetteApplication
467 template - template (See XOSHelper.html)
470 XOSDetailView = Marionette.ItemView.extend({
473 viewInitializers: [],
475 events: {"click button.btn-xos-save-continue": "submitContinueClicked",
476 "click button.btn-xos-save-leave": "submitLeaveClicked",
477 "click button.btn-xos-save-another": "submitAddAnotherClicked",
478 "click button.btn-xos-delete": "deleteClicked",
479 "change input": "inputChanged"},
481 /* inputChanged is watching the onChange events of the input controls. We
482 do this to track when this view is 'dirty', so we can throw up a warning
483 if the user tries to change his slices without saving first.
486 initialize: function() {
487 this.on("saveSuccess", this.onSaveSuccess);
488 this.synchronous = false;
492 _.each(this.viewInitializers, function(initializer) {
497 saveSuccess: function(e) {
498 // always called after a save succeeds
501 afterSave: function(e) {
502 // if this.synchronous, then called after the save succeeds
503 // if !this.synchronous, then called after save is initiated
506 onSaveSuccess: function(e) {
508 if (this.synchronous) {
513 inputChanged: function(e) {
517 submitContinueClicked: function(e) {
518 console.log("saveContinue");
520 this.afterSave = function() { };
524 submitLeaveClicked: function(e) {
525 console.log("saveLeave");
527 if (this.options.noSubmitButton || this.noSubmitButton) {
531 this.afterSave = function() {
532 that.app.navigate("list", that.model.modelName);
537 submitAddAnotherClicked: function(e) {
538 console.log("saveAnother");
542 this.afterSave = function() {
543 console.log("addAnother afterSave");
544 that.app.navigate("add", that.model.modelName);
550 this.app.hideError();
551 var data = Backbone_Syphon.serialize(this);
553 var isNew = !this.model.id;
557 this.$el.find(".help-inline").remove();
559 /* although model.validate() is called automatically by
560 model.save, we call it ourselves, so we can throw up our
561 validation error before creating the infoMsg in the log
563 errors = this.model.xosValidate(data);
565 this.onFormDataInvalid(errors);
570 this.model.attributes.humanReadableName = "new " + this.model.modelName;
571 this.model.addToCollection = this.collection;
573 this.model.addToCollection = undefined;
576 var infoMsgId = this.app.showInformational( {what: "save " + this.model.modelName + " " + this.model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
578 this.model.save(data, {error: function(model, result, xhr) { that.app.saveError(model,result,xhr,infoMsgId);},
579 success: function(model, result, xhr) { that.app.saveSuccess(model,result,xhr,infoMsgId);
580 that.trigger("saveSuccess");
584 if (!this.synchronous) {
589 deleteClicked: function(e) {
591 this.app.deleteDialog(this.model, "list");
594 tabClick: function(tabId, regionName) {
595 region = this.app[regionName];
596 if (this.currentTabRegion != undefined) {
597 this.currentTabRegion.$el.hide();
599 if (this.currentTabId != undefined) {
600 $(this.currentTabId).removeClass('active');
602 this.currentTabRegion = region;
603 this.currentTabRegion.$el.show();
605 this.currentTabId = tabId;
606 $(tabId).addClass('active');
609 showTabs: function(tabs) {
610 template = templateFromId("#xos-tabs-template", {tabs: tabs});
611 $("#tabs").html(template(tabs));
614 _.each(tabs, function(tab) {
615 var regionName = tab["region"];
616 var tabId = '#xos-nav-'+regionName;
617 $(tabId).bind('click', function() { that.tabClick(tabId, regionName); });
623 showLinkedItems: function() {
626 tabs.push({name: "details", region: "detail"});
628 makeFilter = function(relatedField, relatedId) {
629 return function(model) { return model.attributes[relatedField] == relatedId; }
633 for (relatedName in this.model.collection.relatedCollections) {
634 var relatedField = this.model.collection.relatedCollections[relatedName];
635 var relatedId = this.model.id;
636 regionName = "linkedObjs" + (index+1);
638 relatedListViewClassName = relatedName + "ListView";
639 assert(this.app[relatedListViewClassName] != undefined, relatedListViewClassName + " not found");
640 relatedListViewClass = this.app[relatedListViewClassName].extend({collection: xos[relatedName],
641 filter: makeFilter(relatedField, relatedId),
642 parentModel: this.model});
643 this.app[regionName].show(new relatedListViewClass());
644 if (this.app.hideTabsByDefault) {
645 this.app[regionName].$el.hide();
647 tabs.push({name: relatedName, region: regionName});
652 this.app["linkedObjs" + (index+1)].empty();
657 this.tabClick('#xos-nav-detail', 'detail');
660 onFormDataInvalid: function(errors) {
662 var markErrors = function(value, key) {
663 var $inputElement = self.$el.find("[name='" + key + "']");
664 var $inputContainer = $inputElement.parent();
665 //$inputContainer.find(".help-inline").remove();
666 var $errorEl = $("<span>", {class: "help-inline error", text: value});
667 $inputContainer.append($errorEl).addClass("error");
669 _.each(errors, markErrors);
672 templateHelpers: function() { return { modelName: this.model.modelName,
673 collectionName: this.model.collectionName,
674 addFields: this.model.addFields,
675 listFields: this.model.listFields,
676 detailFields: this.options.detailFields || this.detailFields || this.model.detailFields,
677 fieldDisplayNames: this.options.fieldDisplayNames || this.fieldDisplayNames || this.model.fieldDisplayNames || {},
678 foreignFields: this.model.foreignFields,
679 detailLinkFields: this.model.detailLinkFields,
680 inputType: this.model.inputType,
683 choices: this.options.choices || this.choices || this.model.choices || {},
684 helpText: this.options.helpText || this.helpText || this.model.helpText || {},
688 XOSDetailView_sliver = XOSDetailView.extend( {
689 events: $.extend(XOSDetailView.events,
690 {"change #field_deploymentNetwork": "onDeploymentNetworkChange"}
694 // Note that this causes the selects to be updated a second time. The
695 // first time was when the template was originally invoked, and the
696 // selects will all have the full unfiltered set of candidates. Then
697 // onShow will fire, and we'll update them with the filtered values.
698 this.onDeploymentNetworkChange();
701 onDeploymentNetworkChange: function(e) {
702 var deploymentID = this.$el.find("#field_deploymentNetwork").val();
704 console.log("onDeploymentNetworkChange");
705 console.log(deploymentID);
707 filterFunc = function(model) { return (model.attributes.deployment==deploymentID); }
708 newSelect = idToSelect("node",
709 this.model.attributes.node,
710 this.model.foreignFields["node"],
714 this.$el.find("#field_node").html(newSelect);
716 filterFunc = function(model) { for (index in model.attributes.deployments) {
717 item=model.attributes.deployments[index];
718 if (item.toString()==deploymentID.toString()) return true;
722 newSelect = idToSelect("flavor",
723 this.model.attributes.flavor,
724 this.model.foreignFields["flavor"],
728 this.$el.find("#field_flavor").html(newSelect);
730 filterFunc = function(model) { for (index in xos.imageDeployments.models) {
731 imageDeployment = xos.imageDeployments.models[index];
732 if ((imageDeployment.attributes.deployment == deploymentID) && (imageDeployment.attributes.image == model.id)) {
738 newSelect = idToSelect("image",
739 this.model.attributes.image,
740 this.model.foreignFields["image"],
744 this.$el.find("#field_image").html(newSelect);
749 This is for items that will be displayed as table rows.
751 app - MarionetteApplication
752 template - template (See XOSHelper.html)
755 XOSItemView = Marionette.ItemView.extend({
757 className: 'test-tablerow',
759 templateHelpers: function() { return { modelName: this.model.modelName,
760 collectionName: this.model.collectionName,
761 listFields: this.model.listFields,
762 addFields: this.model.addFields,
763 detailFields: this.model.detailFields,
764 foreignFields: this.model.foreignFields,
765 detailLinkFields: this.model.detailLinkFields,
766 inputType: this.model.inputType,
773 app - MarionetteApplication
774 childView - class of ItemView, probably an XOSItemView
775 template - template (see xosHelper.html)
776 collection - collection that holds these objects
777 title - title to display in template
780 XOSListView = FilteredCompositeView.extend({
781 childViewContainer: 'tbody',
784 events: {"click button.btn-xos-add": "addClicked",
785 "click button.btn-xos-refresh": "refreshClicked",
788 _fetchStateChange: function() {
789 if (this.collection.fetching) {
790 $("#xos-list-title-spinner").show();
792 $("#xos-list-title-spinner").hide();
796 addClicked: function(e) {
798 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
801 refreshClicked: function(e) {
803 this.collection.refresh(refreshRelated=true);
806 initialize: function() {
807 this.listenTo(this.collection, 'change', this._renderChildren)
808 this.listenTo(this.collection, 'sort', function() { console.log("sort"); })
809 this.listenTo(this.collection, 'add', function() { console.log("add"); })
810 this.listenTo(this.collection, 'fetchStateChange', this._fetchStateChange);
812 // Because many of the templates use idToName(), we need to
813 // listen to the collections that hold the names for the ids
814 // that we want to display.
815 for (i in this.collection.foreignCollections) {
816 foreignName = this.collection.foreignCollections[i];
817 if (xos[foreignName] == undefined) {
818 console.log("Failed to find xos class " + foreignName);
820 this.listenTo(xos[foreignName], 'change', this._renderChildren);
821 this.listenTo(xos[foreignName], 'sort', this._renderChildren);
825 getAddChildHash: function() {
826 if (this.parentModel) {
827 parentFieldName = this.parentModel.relatedCollections[this.collection.collectionName];
828 parentFieldName = parentFieldName || "unknown";
830 /*parentFieldName = "unknown";
832 for (fieldName in this.collection.foreignFields) {
833 cname = this.collection.foreignFields[fieldName];
834 if (cname = this.collection.collectionName) {
835 parentFieldName = fieldName;
838 return "#addChild" + firstCharUpper(this.collection.modelName) + "/" + this.parentModel.modelName + "/" + parentFieldName + "/" + this.parentModel.id; // modelName, fieldName, id
844 templateHelpers: function() {
845 return { title: this.title,
846 addChildHash: this.getAddChildHash(),
847 foreignFields: this.collection.foreignFields,
848 listFields: this.collection.listFields,
849 detailLinkFields: this.collection.detailLinkFields, };
853 XOSDataTableView = Marionette.View.extend( {
854 el: '<div style="overflow: hidden">' +
855 '<h3 class="xos-list-title title_placeholder"></h3>' +
856 '<div class="header_placeholder"></div>' +
858 '<div class="footer_placeholder"></div>' +
863 events: {"click button.btn-xos-add": "addClicked",
864 "click button.btn-xos-refresh": "refreshClicked",
867 _fetchStateChange: function() {
868 if (this.collection.fetching) {
869 $("#xos-list-title-spinner").show();
871 $("#xos-list-title-spinner").hide();
875 addClicked: function(e) {
877 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
880 refreshClicked: function(e) {
882 this.collection.refresh(refreshRelated=true);
886 initialize: function() {
887 $(this.el).find(".footer_placeholder").html( xosListFooterTemplate({addChildHash: this.getAddChildHash()}) );
888 $(this.el).find(".header_placeholder").html( xosListHeaderTemplate() );
890 this.listenTo(this.collection, 'fetchStateChange', this._fetchStateChange);
895 var fieldDisplayNames = view.options.fieldDisplayNames || view.fieldDisplayNames || {};
897 view.columnsByIndex = [];
898 view.columnsByFieldName = {};
899 _.each(this.collection.listFields, function(fieldName) {
900 inputType = view.options.inputType || view.inputType || {};
902 mSearchText = undefined;
903 sTitle = fieldName in fieldDisplayNames ? fieldDisplayNames[fieldName] : fieldNameToHumanReadable(fieldName);
905 if (fieldName=="backend_status") {
906 mRender = function(x,y,z) { return xosBackendStatusIconTemplate(z); };
909 } else if (fieldName in view.collection.foreignFields) {
910 var foreignCollection = view.collection.foreignFields[fieldName];
911 mSearchText = function(x) { return idToName(x, foreignCollection, "humanReadableName"); };
912 } else if (inputType[fieldName] == "spinner") {
913 mRender = function(x,y,z) { return xosDataTableSpinnerTemplate( {value: x, collectionName: view.collection.collectionName, fieldName: fieldName, id: z.id, app: view.app} ); };
915 if ($.inArray(fieldName, view.collection.detailLinkFields)>=0) {
916 var collectionName = view.collection.collectionName;
917 mRender = function(x,y,z) { return '<a href="#' + collectionName + '/' + z.id + '">' + x + '</a>'; };
919 thisColumn = {sTitle: sTitle, bSortable: bSortable, mData: fieldName, mRender: mRender, mSearchText: mSearchText};
920 view.columnsByIndex.push( thisColumn );
921 view.columnsByFieldName[fieldName] = thisColumn;
924 if (!view.noDeleteColumn) {
925 deleteColumn = {sTitle: "", bSortable: false, mRender: function(x,y,z) { return xosDeleteButtonTemplate({modelName: view.collection.modelName, id: z.id}); }, mData: function() { return "delete"; }};
926 view.columnsByIndex.push(deleteColumn);
927 view.columnsByFieldName["delete"] = deleteColumn;
930 oTable = $(this.el).find("table").dataTable( {
934 "bFilter": ! (view.options.disableFilter || view.disableFilter),
935 "bPaginate": ! (view.options.disablePaginate || view.disablePaginate),
936 "aoColumns": view.columnsByIndex,
938 fnServerData: function(sSource, aoData, fnCallback, settings) {
939 var compareColumns = function(sortCols, sortDirs, a, b) {
942 result = (a==b) ? 0 : ((a<b) ? -1 : 1);
943 if (sortDirs[0] == "desc") {
949 var searchMatch = function(row, sSearch) {
950 for (fieldName in row) {
951 if (fieldName in view.columnsByFieldName) {
953 value = row[fieldName].toString();
957 if (value.indexOf(sSearch) >= 0) {
965 //console.log(aoData);
967 // function used to populate the DataTable with the current
\r
968 // content of the collection
\r
969 var populateTable = function()
\r
971 //console.log("populatetable!");
\r
973 // clear out old row views
\r
978 iDisplayLength = 1000;
\r
981 _.each(aoData, function(param) {
\r
982 if (param.name == "sSortDir_0") {
\r
983 sortDirs = [param.value];
\r
984 } else if (param.name == "iSortCol_0") {
\r
985 sortCols = [view.columnsByIndex[param.value].mData];
\r
986 } else if (param.name == "iDisplayStart") {
\r
987 iDisplayStart = param.value;
\r
988 } else if (param.name == "iDisplayLength") {
\r
989 iDisplayLength = param.value;
\r
990 } else if (param.name == "sSearch") {
\r
991 sSearch = param.value;
\r
995 aaData = view.collection.toJSON();
\r
997 // apply backbone filtering on the models
\r
999 aaData = aaData.filter( function(row) { model = {}; model.attributes = row; return view.filter(model); } );
\r
1002 var totalSize = aaData.length;
\r
1004 // turn the ForeignKey fields into human readable things
\r
1005 for (rowIndex in aaData) {
\r
1006 row = aaData[rowIndex];
\r
1007 for (fieldName in row) {
\r
1008 if (fieldName in view.columnsByFieldName) {
\r
1009 mSearchText = view.columnsByFieldName[fieldName].mSearchText;
\r
1010 if (mSearchText) {
\r
1011 row[fieldName] = mSearchText(row[fieldName]);
\r
1017 // apply datatables search
\r
1019 aaData = aaData.filter( function(row) { return searchMatch(row, sSearch); });
\r
1022 var filteredSize = aaData.length;
\r
1024 // apply datatables sort
\r
1025 aaData.sort(function(a,b) { return compareColumns(sortCols, sortDirs, a, b); });
\r
1027 // slice it for pagination
\r
1028 if (iDisplayLength >= 0) {
\r
1029 aaData = aaData.slice(iDisplayStart, iDisplayStart+iDisplayLength);
\r
1032 return fnCallback({iTotalRecords: totalSize,
\r
1033 iTotalDisplayRecords: filteredSize,
\r
1037 aoData.shift(); // ignore sEcho
1040 view.listenTo(view.collection, 'change', populateTable);
1041 view.listenTo(view.collection, 'add', populateTable);
1042 view.listenTo(view.collection, 'remove', populateTable);
1049 getAddChildHash: function() {
1050 if (this.parentModel) {
1051 parentFieldName = this.parentModel.relatedCollections[this.collection.collectionName];
1052 parentFieldName = parentFieldName || "unknown";
1054 /*parentFieldName = "unknown";
1056 for (fieldName in this.collection.foreignFields) {
1057 cname = this.collection.foreignFields[fieldName];
1058 if (cname = this.collection.collectionName) {
1059 parentFieldName = fieldName;
1062 return "#addChild" + firstCharUpper(this.collection.modelName) + "/" + this.parentModel.modelName + "/" + parentFieldName + "/" + this.parentModel.id; // modelName, fieldName, id
1070 idToName = function(id, collectionName, fieldName) {
1071 return xos.idToName(id, collectionName, fieldName);
1074 makeIdToName = function(collectionName, fieldName) {
1075 return function(id) { return idToName(id, collectionName, fieldName); }
1078 /* Constructs lists of <option> html blocks for items in a collection.
1080 selectedId = the id of an object that should be selected, if any
1081 collectionName = name of collection
1082 fieldName = name of field within models of collection that will be displayed
1085 idToOptions = function(selectedId, collectionName, fieldName, filterFunc) {
1087 for (index in xos[collectionName].models) {
1088 linkedObject = xos[collectionName].models[index];
1089 linkedId = linkedObject["id"];
1090 linkedName = linkedObject.attributes[fieldName];
1091 if (linkedId == selectedId) {
1092 selected = " selected";
1096 if ((filterFunc) && (!filterFunc(linkedObject))) {
1099 result = result + '<option value="' + linkedId + '"' + selected + '>' + linkedName + '</option>';
1104 /* Constructs an html <select> and the <option>s to go with it.
1106 variable = variable name to return to form
1107 selectedId = the id of an object that should be selected, if any
1108 collectionName = name of collection
1109 fieldName = name of field within models of collection that will be displayed
1112 idToSelect = function(variable, selectedId, collectionName, fieldName, readOnly, filterFunc) {
1114 readOnly = " readonly";
1118 result = '<select name="' + variable + '" id="field_' + variable + '"' + readOnly + '>' +
1119 idToOptions(selectedId, collectionName, fieldName, filterFunc) +
1124 choicesToOptions = function(selectedValue, choices) {
1126 for (index in choices) {
1127 choice = choices[index];
1128 displayName = choice[0];
1130 if (value == selectedValue) {
1131 selected = " selected";
1135 result = result + '<option value="' + value + '"' + selected + '>' + displayName + '</option>';
1140 choicesToSelect = function(variable, selectedValue, choices) {
1141 result = '<select name="' + variable + '" id="field_' + variable + '">' +
1142 choicesToOptions(selectedValue, choices) +