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 // XXX - We import backbone multiple times (BAD!) since the import happens
\r
85 // inside of the view's html. The second time it's imported (developer
\r
86 // view), it wipes out Backbone.Syphon. So, save it as Backbone_Syphon for
\r
88 Backbone_Syphon = Backbone.Syphon
\r
89 Backbone_Syphon.InputReaders.register('select', function(el) {
\r
90 // Modify syphon so that if a select has "syphonall" in the class, then
91 // the value of every option will be returned, regardless of whether of
92 // not it is selected.
93 if (el.hasClass("syphonall")) {
95 _.each(el.find("option"), function(option) {
96 result.push($(option).val());
103 XOSApplication = Marionette.Application.extend({
104 detailBoxId: "#detailBox",
105 errorBoxId: "#errorBox",
106 errorCloseButtonId: "#close-error-box",
107 successBoxId: "#successBox",
108 successCloseButtonId: "#close-success-box",
109 errorTemplate: "#xos-error-template",
110 successTemplate: "#xos-success-template",
113 confirmDialog: function(view, event, callback) {
114 $("#xos-confirm-dialog").dialog({
118 "Confirm" : function() {
119 $(this).dialog("close");
127 "Cancel" : function() {
128 $(this).dialog("close");
132 $("#xos-confirm-dialog").dialog("open");
135 popupErrorDialog: function(responseText) {
137 parsed_error=$.parseJSON(responseText);
141 parsed_error=undefined;
142 width=640; // django stacktraces like wide width
144 console.log(responseText);
145 console.log(parsed_error);
147 $("#xos-error-dialog").html(templateFromId("#xos-error-response")(parsed_error));
149 $("#xos-error-dialog").html(templateFromId("#xos-error-rawresponse")({responseText: responseText}))
152 $("#xos-error-dialog").dialog({
156 Ok: function() { $(this).dialog("close"); }
161 hideLinkedItems: function(result) {
164 this["linkedObjs" + (index+1)].empty();
169 hideTabs: function() { $("#tabs").hide(); },
170 showTabs: function() { $("#tabs").show(); },
172 createListHandler: function(listViewName, collection_name, regionName, title) {
175 listView = new app[listViewName];
176 app[regionName].show(listView);
177 app.hideLinkedItems();
178 $("#contentTitle").html(templateFromId("#xos-title-list")({"title": title}));
182 listButtons = new XOSListButtonView({linkedView: listView});
183 app["rightButtonPanel"].show(listButtons);
187 createAddHandler: function(detailName, collection_name, regionName, title) {
190 console.log("addHandler");
192 app.hideLinkedItems();
195 model = new xos[collection_name].model();
196 detailViewClass = app[detailName];
197 detailView = new detailViewClass({model: model, collection:xos[collection_name]});
198 app[regionName].show(detailView);
200 detailButtons = new XOSDetailButtonView({linkedView: detailView});
201 app["rightButtonPanel"].show(detailButtons);
205 createAddChildHandler: function(addChildName, collection_name) {
207 return function(parent_modelName, parent_fieldName, parent_id) {
208 app.Router.showPreviousURL();
209 model = new xos[collection_name].model();
210 model.attributes[parent_fieldName] = parent_id;
211 model.readOnlyFields.push(parent_fieldName);
212 detailViewClass = app[addChildName];
213 var detailView = new detailViewClass({model: model, collection:xos[collection_name]});
214 detailView.dialog = $("xos-addchild-dialog");
215 app["addChildDetail"].show(detailView);
216 $("#xos-addchild-dialog").dialog({
221 "Save" : function() {
222 var addDialog = this;
223 detailView.synchronous = true;
224 detailView.afterSave = function() { console.log("addChild afterSave"); $(addDialog).dialog("close"); }
227 //$(this).dialog("close");
229 "Cancel" : function() {
230 $(this).dialog("close");
234 $("#xos-addchild-dialog").dialog("open");
238 createDeleteHandler: function(collection_name) {
240 return function(model_id) {
241 console.log("deleteCalled");
242 collection = xos[collection_name];
243 model = collection.get(model_id);
244 assert(model!=undefined, "failed to get model " + model_id + " from collection " + collection_name);
245 app.Router.showPreviousURL();
246 app.deleteDialog(model);
250 createDetailHandler: function(detailName, collection_name, regionName, title) {
252 showModelId = function(model_id) {
253 $("#contentTitle").html(templateFromId("#xos-title-detail")({"title": title}));
255 collection = xos[collection_name];
256 model = collection.get(model_id);
257 if (model == undefined) {
258 app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name}));
260 detailViewClass = app[detailName];
261 detailView = new detailViewClass({model: model});
262 app[regionName].show(detailView);
263 detailView.showLinkedItems();
265 detailButtons = new XOSDetailButtonView({linkedView: detailView});
266 app["rightButtonPanel"].show(detailButtons);
272 /* error handling callbacks */
274 hideError: function() {
275 if (this.logWindowId) {
277 $(this.errorBoxId).hide();
278 $(this.successBoxId).hide();
282 showSuccess: function(result) {
283 result["statusclass"] = "success";
284 if (this.logTableId) {
285 this.appendLogWindow(result);
287 $(this.successBoxId).show();
288 $(this.successBoxId).html(_.template($(this.successTemplate).html())(result));
290 $(this.successCloseButtonId).unbind().bind('click', function() {
291 $(that.successBoxId).hide();
296 showError: function(result) {
297 result["statusclass"] = "failure";
298 if (this.logTableId) {
299 this.appendLogWindow(result);
300 this.popupErrorDialog(result.responseText);
302 // this is really old stuff
303 $(this.errorBoxId).show();
304 $(this.errorBoxId).html(_.template($(this.errorTemplate).html())(result));
306 $(this.errorCloseButtonId).unbind().bind('click', function() {
307 $(that.errorBoxId).hide();
312 showInformational: function(result) {
313 result["statusclass"] = "inprog";
314 if (this.logTableId) {
315 return this.appendLogWindow(result);
321 appendLogWindow: function(result) {
322 // compute a new logMessageId for this log message
323 logMessageId = "logMessage" + this.logMessageCount;
324 this.logMessageCount = this.logMessageCount + 1;
325 result["logMessageId"] = logMessageId;
327 logMessageTemplate=$("#xos-log-template").html();
328 assert(logMessageTemplate != undefined, "logMessageTemplate is undefined");
329 newRow = _.template(logMessageTemplate, result);
330 assert(newRow != undefined, "newRow is undefined");
332 if (result["infoMsgId"] != undefined) {
333 // We were passed the logMessageId of an informational message,
334 // and the caller wants us to replace that message with our own.
335 // i.e. replace an informational message with a success or an error.
336 $("#"+result["infoMsgId"]).replaceWith(newRow);
338 // Create a brand new log message rather than replacing one.
339 logTableBody = $(this.logTableId + " tbody");
340 logTableBody.prepend(newRow);
343 if (this.statusMsgId) {
344 $(this.statusMsgId).html( templateFromId("#xos-status-template")(result) );
347 limitTableRows(this.logTableId, 5);
352 saveError: function(model, result, xhr, infoMsgId) {
353 console.log("saveError");
354 result["what"] = "save " + model.modelName + " " + model.attributes.humanReadableName;
355 result["infoMsgId"] = infoMsgId;
356 this.showError(result);
359 saveSuccess: function(model, result, xhr, infoMsgId, addToCollection) {
360 console.log("saveSuccess");
361 if (model.addToCollection) {
362 console.log("addToCollection");
363 console.log(model.addToCollection);
364 model.addToCollection.add(model);
365 model.addToCollection.sort();
366 model.addToCollection = undefined;
368 result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};
369 result["what"] = "save " + model.modelName + " " + model.attributes.humanReadableName;
370 result["infoMsgId"] = infoMsgId;
371 this.showSuccess(result);
374 destroyError: function(model, result, xhr, infoMsgId) {
375 result["what"] = "destroy " + model.modelName + " " + model.attributes.humanReadableName;
376 result["infoMsgId"] = infoMsgId;
377 this.showError(result);
380 destroySuccess: function(model, result, xhr, infoMsgId) {
381 result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};
382 result["what"] = "destroy " + model.modelName + " " + model.attributes.humanReadableName;
383 result["infoMsgId"] = infoMsgId;
384 this.showSuccess(result);
387 /* end error handling callbacks */
389 destroyModel: function(model) {
390 //console.log("destroyModel"); console.log(model);
392 var infoMsgId = this.showInformational( {what: "destroy " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
394 model.destroy({error: function(model, result, xhr) { that.destroyError(model,result,xhr,infoMsgId);},
395 success: function(model, result, xhr) { that.destroySuccess(model,result,xhr,infoMsgId);}});
398 deleteDialog: function(model, afterDelete) {
400 assert(model!=undefined, "deleteDialog's model is undefined");
401 //console.log("deleteDialog"); console.log(model);
402 this.confirmDialog(null, null, function() {
403 //console.log("deleteConfirm"); console.log(model);
404 modelName = model.modelName;
405 that.destroyModel(model);
406 if (afterDelete=="list") {
407 that.navigate("list", modelName);
408 } else if (afterDelete) {
415 XOSButtonView = Marionette.ItemView.extend({
416 events: {"click button.btn-xos-save-continue": "submitContinueClicked",
417 "click button.btn-xos-save-leave": "submitLeaveClicked",
418 "click button.btn-xos-save-another": "submitAddAnotherClicked",
419 "click button.btn-xos-delete": "deleteClicked",
420 "click button.btn-xos-add": "addClicked",
421 "click button.btn-xos-refresh": "refreshClicked",
424 submitLeaveClicked: function(e) {
425 this.options.linkedView.submitLeaveClicked.call(this.options.linkedView, e);
428 submitContinueClicked: function(e) {
429 this.options.linkedView.submitContinueClicked.call(this.options.linkedView, e);
432 submitAddAnotherClicked: function(e) {
433 this.options.linkedView.submitAddAnotherClicked.call(this.options.linkedView, e);
436 submitDeleteClicked: function(e) {
437 this.options.linkedView.deleteClicked.call(this.options.linkedView, e);
440 addClicked: function(e) {
441 this.options.linkedView.addClicked.call(this.options.linkedView, e);
444 refreshClicked: function(e) {
445 this.options.linkedView.refreshClicked.call(this.options.linkedView, e);
449 XOSDetailButtonView = XOSButtonView.extend({ template: "#xos-savebuttons-template" });
450 XOSListButtonView = XOSButtonView.extend({ template: "#xos-listbuttons-template" });
454 app - MarionetteApplication
455 template - template (See XOSHelper.html)
458 XOSDetailView = Marionette.ItemView.extend({
461 viewInitializers: [],
463 events: {"click button.btn-xos-save-continue": "submitContinueClicked",
464 "click button.btn-xos-save-leave": "submitLeaveClicked",
465 "click button.btn-xos-save-another": "submitAddAnotherClicked",
466 "click button.btn-xos-delete": "deleteClicked",
467 "change input": "inputChanged"},
469 /* inputChanged is watching the onChange events of the input controls. We
470 do this to track when this view is 'dirty', so we can throw up a warning
471 if the user tries to change his slices without saving first.
474 initialize: function() {
475 this.on("saveSuccess", this.onAfterSave);
476 this.synchronous = false;
480 _.each(this.viewInitializers, function(initializer) {
485 afterSave: function(e) {
488 onAfterSave: function(e) {
492 inputChanged: function(e) {
496 submitContinueClicked: function(e) {
497 console.log("saveContinue");
499 this.afterSave = function() { };
503 submitLeaveClicked: function(e) {
504 console.log("saveLeave");
506 if (this.options.noSubmitButton || this.noSubmitButton) {
510 this.afterSave = function() {
511 that.app.navigate("list", that.model.modelName);
516 submitAddAnotherClicked: function(e) {
517 console.log("saveAnother");
521 this.afterSave = function() {
522 console.log("addAnother afterSave");
523 that.app.navigate("add", that.model.modelName);
529 this.app.hideError();
530 var data = Backbone_Syphon.serialize(this);
532 var isNew = !this.model.id;
536 this.$el.find(".help-inline").remove();
538 /* although model.validate() is called automatically by
539 model.save, we call it ourselves, so we can throw up our
540 validation error before creating the infoMsg in the log
542 errors = this.model.xosValidate(data);
544 this.onFormDataInvalid(errors);
549 this.model.attributes.humanReadableName = "new " + this.model.modelName;
550 this.model.addToCollection = this.collection;
552 this.model.addToCollection = undefined;
555 var infoMsgId = this.app.showInformational( {what: "save " + this.model.modelName + " " + this.model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
557 this.model.save(data, {error: function(model, result, xhr) { that.app.saveError(model,result,xhr,infoMsgId);},
558 success: function(model, result, xhr) { that.app.saveSuccess(model,result,xhr,infoMsgId);
559 if (that.synchronous) {
560 that.trigger("saveSuccess");
565 if (!this.synchronous) {
570 deleteClicked: function(e) {
572 this.app.deleteDialog(this.model, "list");
575 tabClick: function(tabId, regionName) {
576 region = this.app[regionName];
577 if (this.currentTabRegion != undefined) {
578 this.currentTabRegion.$el.hide();
580 if (this.currentTabId != undefined) {
581 $(this.currentTabId).removeClass('active');
583 this.currentTabRegion = region;
584 this.currentTabRegion.$el.show();
586 this.currentTabId = tabId;
587 $(tabId).addClass('active');
590 showTabs: function(tabs) {
591 template = templateFromId("#xos-tabs-template", {tabs: tabs});
592 $("#tabs").html(template(tabs));
595 _.each(tabs, function(tab) {
596 var regionName = tab["region"];
597 var tabId = '#xos-nav-'+regionName;
598 $(tabId).bind('click', function() { that.tabClick(tabId, regionName); });
604 showLinkedItems: function() {
607 tabs.push({name: "details", region: "detail"});
609 makeFilter = function(relatedField, relatedId) {
610 return function(model) { return model.attributes[relatedField] == relatedId; }
614 for (relatedName in this.model.collection.relatedCollections) {
615 var relatedField = this.model.collection.relatedCollections[relatedName];
616 var relatedId = this.model.id;
617 regionName = "linkedObjs" + (index+1);
619 relatedListViewClassName = relatedName + "ListView";
620 assert(this.app[relatedListViewClassName] != undefined, relatedListViewClassName + " not found");
621 relatedListViewClass = this.app[relatedListViewClassName].extend({collection: xos[relatedName],
622 filter: makeFilter(relatedField, relatedId),
623 parentModel: this.model});
624 this.app[regionName].show(new relatedListViewClass());
625 if (this.app.hideTabsByDefault) {
626 this.app[regionName].$el.hide();
628 tabs.push({name: relatedName, region: regionName});
633 this.app["linkedObjs" + (index+1)].empty();
638 this.tabClick('#xos-nav-detail', 'detail');
641 onFormDataInvalid: function(errors) {
643 var markErrors = function(value, key) {
644 var $inputElement = self.$el.find("[name='" + key + "']");
645 var $inputContainer = $inputElement.parent();
646 //$inputContainer.find(".help-inline").remove();
647 var $errorEl = $("<span>", {class: "help-inline error", text: value});
648 $inputContainer.append($errorEl).addClass("error");
650 _.each(errors, markErrors);
653 templateHelpers: function() { return { modelName: this.model.modelName,
654 collectionName: this.model.collectionName,
655 addFields: this.model.addFields,
656 listFields: this.model.listFields,
657 detailFields: this.options.detailFields || this.detailFields || this.model.detailFields,
658 fieldDisplayNames: this.options.fieldDisplayNames || this.fieldDisplayNames || this.model.fieldDisplayNames || {},
659 foreignFields: this.model.foreignFields,
660 detailLinkFields: this.model.detailLinkFields,
661 inputType: this.model.inputType,
664 choices: this.options.choices || this.choices || this.model.choices || {},
668 XOSDetailView_sliver = XOSDetailView.extend( {
669 events: $.extend(XOSDetailView.events,
670 {"change #field_deploymentNetwork": "onDeploymentNetworkChange"}
674 // Note that this causes the selects to be updated a second time. The
675 // first time was when the template was originally invoked, and the
676 // selects will all have the full unfiltered set of candidates. Then
677 // onShow will fire, and we'll update them with the filtered values.
678 this.onDeploymentNetworkChange();
681 onDeploymentNetworkChange: function(e) {
682 var deploymentID = this.$el.find("#field_deploymentNetwork").val();
684 console.log("onDeploymentNetworkChange");
685 console.log(deploymentID);
687 filterFunc = function(model) { return (model.attributes.deployment==deploymentID); }
688 newSelect = idToSelect("node",
689 this.model.attributes.node,
690 this.model.foreignFields["node"],
694 this.$el.find("#field_node").html(newSelect);
696 filterFunc = function(model) { for (index in model.attributes.deployments) {
697 item=model.attributes.deployments[index];
698 if (item.toString()==deploymentID.toString()) return true;
702 newSelect = idToSelect("flavor",
703 this.model.attributes.flavor,
704 this.model.foreignFields["flavor"],
708 this.$el.find("#field_flavor").html(newSelect);
710 filterFunc = function(model) { for (index in xos.imageDeployments.models) {
711 imageDeployment = xos.imageDeployments.models[index];
712 if ((imageDeployment.attributes.deployment == deploymentID) && (imageDeployment.attributes.image == model.id)) {
718 newSelect = idToSelect("image",
719 this.model.attributes.image,
720 this.model.foreignFields["image"],
724 this.$el.find("#field_image").html(newSelect);
729 This is for items that will be displayed as table rows.
731 app - MarionetteApplication
732 template - template (See XOSHelper.html)
735 XOSItemView = Marionette.ItemView.extend({
737 className: 'test-tablerow',
739 templateHelpers: function() { return { modelName: this.model.modelName,
740 collectionName: this.model.collectionName,
741 listFields: this.model.listFields,
742 addFields: this.model.addFields,
743 detailFields: this.model.detailFields,
744 foreignFields: this.model.foreignFields,
745 detailLinkFields: this.model.detailLinkFields,
746 inputType: this.model.inputType,
753 app - MarionetteApplication
754 childView - class of ItemView, probably an XOSItemView
755 template - template (see xosHelper.html)
756 collection - collection that holds these objects
757 title - title to display in template
760 XOSListView = FilteredCompositeView.extend({
761 childViewContainer: 'tbody',
764 events: {"click button.btn-xos-add": "addClicked",
765 "click button.btn-xos-refresh": "refreshClicked",
768 _fetchStateChange: function() {
769 if (this.collection.fetching) {
770 $("#xos-list-title-spinner").show();
772 $("#xos-list-title-spinner").hide();
776 addClicked: function(e) {
778 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
781 refreshClicked: function(e) {
783 this.collection.refresh(refreshRelated=true);
786 initialize: function() {
787 this.listenTo(this.collection, 'change', this._renderChildren)
788 this.listenTo(this.collection, 'sort', function() { console.log("sort"); })
789 this.listenTo(this.collection, 'add', function() { console.log("add"); })
790 this.listenTo(this.collection, 'fetchStateChange', this._fetchStateChange);
792 // Because many of the templates use idToName(), we need to
793 // listen to the collections that hold the names for the ids
794 // that we want to display.
795 for (i in this.collection.foreignCollections) {
796 foreignName = this.collection.foreignCollections[i];
797 if (xos[foreignName] == undefined) {
798 console.log("Failed to find xos class " + foreignName);
800 this.listenTo(xos[foreignName], 'change', this._renderChildren);
801 this.listenTo(xos[foreignName], 'sort', this._renderChildren);
805 getAddChildHash: function() {
806 if (this.parentModel) {
807 parentFieldName = this.parentModel.relatedCollections[this.collection.collectionName];
808 parentFieldName = parentFieldName || "unknown";
810 /*parentFieldName = "unknown";
812 for (fieldName in this.collection.foreignFields) {
813 cname = this.collection.foreignFields[fieldName];
814 if (cname = this.collection.collectionName) {
815 parentFieldName = fieldName;
818 return "#addChild" + firstCharUpper(this.collection.modelName) + "/" + this.parentModel.modelName + "/" + parentFieldName + "/" + this.parentModel.id; // modelName, fieldName, id
824 templateHelpers: function() {
825 return { title: this.title,
826 addChildHash: this.getAddChildHash(),
827 foreignFields: this.collection.foreignFields,
828 listFields: this.collection.listFields,
829 detailLinkFields: this.collection.detailLinkFields, };
833 XOSDataTableView = Marionette.View.extend( {
834 el: '<div style="overflow: hidden">' +
835 '<h3 class="xos-list-title title_placeholder"></h3>' +
836 '<div class="header_placeholder"></div>' +
838 '<div class="footer_placeholder"></div>' +
843 events: {"click button.btn-xos-add": "addClicked",
844 "click button.btn-xos-refresh": "refreshClicked",
847 _fetchStateChange: function() {
848 if (this.collection.fetching) {
849 $("#xos-list-title-spinner").show();
851 $("#xos-list-title-spinner").hide();
855 addClicked: function(e) {
857 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
860 refreshClicked: function(e) {
862 this.collection.refresh(refreshRelated=true);
866 initialize: function() {
867 $(this.el).find(".footer_placeholder").html( xosListFooterTemplate({addChildHash: this.getAddChildHash()}) );
868 $(this.el).find(".header_placeholder").html( xosListHeaderTemplate() );
870 this.listenTo(this.collection, 'fetchStateChange', this._fetchStateChange);
876 view.columnsByIndex = [];
877 view.columnsByFieldName = {};
878 _.each(this.collection.listFields, function(fieldName) {
879 inputType = view.options.inputType || view.inputType || {};
881 mSearchText = undefined;
882 sTitle = fieldNameToHumanReadable(fieldName);
884 if (fieldName=="backend_status") {
885 mRender = function(x,y,z) { return xosBackendStatusIconTemplate(z); };
888 } else if (fieldName in view.collection.foreignFields) {
889 var foreignCollection = view.collection.foreignFields[fieldName];
890 mSearchText = function(x) { return idToName(x, foreignCollection, "humanReadableName"); };
891 } else if (inputType[fieldName] == "spinner") {
892 mRender = function(x,y,z) { return xosDataTableSpinnerTemplate( {value: x, collectionName: view.collection.collectionName, fieldName: fieldName, id: z.id, app: view.app} ); };
894 if ($.inArray(fieldName, view.collection.detailLinkFields)>=0) {
895 var collectionName = view.collection.collectionName;
896 mRender = function(x,y,z) { return '<a href="#' + collectionName + '/' + z.id + '">' + x + '</a>'; };
898 thisColumn = {sTitle: sTitle, bSortable: bSortable, mData: fieldName, mRender: mRender, mSearchText: mSearchText};
899 view.columnsByIndex.push( thisColumn );
900 view.columnsByFieldName[fieldName] = thisColumn;
903 if (!view.noDeleteColumn) {
904 deleteColumn = {sTitle: "", bSortable: false, mRender: function(x,y,z) { return xosDeleteButtonTemplate({modelName: view.collection.modelName, id: z.id}); }, mData: function() { return "delete"; }};
905 view.columnsByIndex.push(deleteColumn);
906 view.columnsByFieldName["delete"] = deleteColumn;
909 oTable = $(this.el).find("table").dataTable( {
913 "aoColumns": view.columnsByIndex,
915 fnServerData: function(sSource, aoData, fnCallback, settings) {
916 var compareColumns = function(sortCols, sortDirs, a, b) {
919 result = (a==b) ? 0 : ((a<b) ? -1 : 1);
920 if (sortDirs[0] == "desc") {
926 var searchMatch = function(row, sSearch) {
927 for (fieldName in row) {
928 if (fieldName in view.columnsByFieldName) {
930 value = row[fieldName].toString();
934 if (value.indexOf(sSearch) >= 0) {
942 //console.log(aoData);
944 // function used to populate the DataTable with the current
\r
945 // content of the collection
\r
946 var populateTable = function()
\r
948 //console.log("populatetable!");
\r
950 // clear out old row views
\r
955 iDisplayLength = 1000;
\r
958 _.each(aoData, function(param) {
\r
959 if (param.name == "sSortDir_0") {
\r
960 sortDirs = [param.value];
\r
961 } else if (param.name == "iSortCol_0") {
\r
962 sortCols = [view.columnsByIndex[param.value].mData];
\r
963 } else if (param.name == "iDisplayStart") {
\r
964 iDisplayStart = param.value;
\r
965 } else if (param.name == "iDisplayLength") {
\r
966 iDisplayLength = param.value;
\r
967 } else if (param.name == "sSearch") {
\r
968 sSearch = param.value;
\r
972 aaData = view.collection.toJSON();
\r
974 // apply backbone filtering on the models
\r
976 aaData = aaData.filter( function(row) { model = {}; model.attributes = row; return view.filter(model); } );
\r
979 var totalSize = aaData.length;
\r
981 // turn the ForeignKey fields into human readable things
\r
982 for (rowIndex in aaData) {
\r
983 row = aaData[rowIndex];
\r
984 for (fieldName in row) {
\r
985 if (fieldName in view.columnsByFieldName) {
\r
986 mSearchText = view.columnsByFieldName[fieldName].mSearchText;
\r
988 row[fieldName] = mSearchText(row[fieldName]);
\r
994 // apply datatables search
\r
996 aaData = aaData.filter( function(row) { return searchMatch(row, sSearch); });
\r
999 var filteredSize = aaData.length;
\r
1001 // apply datatables sort
\r
1002 aaData.sort(function(a,b) { return compareColumns(sortCols, sortDirs, a, b); });
\r
1004 // slice it for pagination
\r
1005 aaData = aaData.slice(iDisplayStart, iDisplayStart+iDisplayLength);
\r
1007 return fnCallback({iTotalRecords: totalSize,
\r
1008 iTotalDisplayRecords: filteredSize,
\r
1012 aoData.shift(); // ignore sEcho
1015 view.listenTo(view.collection, 'change', populateTable);
1016 view.listenTo(view.collection, 'add', populateTable);
1017 view.listenTo(view.collection, 'remove', populateTable);
1024 getAddChildHash: function() {
1025 if (this.parentModel) {
1026 parentFieldName = this.parentModel.relatedCollections[this.collection.collectionName];
1027 parentFieldName = parentFieldName || "unknown";
1029 /*parentFieldName = "unknown";
1031 for (fieldName in this.collection.foreignFields) {
1032 cname = this.collection.foreignFields[fieldName];
1033 if (cname = this.collection.collectionName) {
1034 parentFieldName = fieldName;
1037 return "#addChild" + firstCharUpper(this.collection.modelName) + "/" + this.parentModel.modelName + "/" + parentFieldName + "/" + this.parentModel.id; // modelName, fieldName, id
1045 idToName = function(id, collectionName, fieldName) {
1046 return xos.idToName(id, collectionName, fieldName);
1049 makeIdToName = function(collectionName, fieldName) {
1050 return function(id) { return idToName(id, collectionName, fieldName); }
1053 /* Constructs lists of <option> html blocks for items in a collection.
1055 selectedId = the id of an object that should be selected, if any
1056 collectionName = name of collection
1057 fieldName = name of field within models of collection that will be displayed
1060 idToOptions = function(selectedId, collectionName, fieldName, filterFunc) {
1062 for (index in xos[collectionName].models) {
1063 linkedObject = xos[collectionName].models[index];
1064 linkedId = linkedObject["id"];
1065 linkedName = linkedObject.attributes[fieldName];
1066 if (linkedId == selectedId) {
1067 selected = " selected";
1071 if ((filterFunc) && (!filterFunc(linkedObject))) {
1074 result = result + '<option value="' + linkedId + '"' + selected + '>' + linkedName + '</option>';
1079 /* Constructs an html <select> and the <option>s to go with it.
1081 variable = variable name to return to form
1082 selectedId = the id of an object that should be selected, if any
1083 collectionName = name of collection
1084 fieldName = name of field within models of collection that will be displayed
1087 idToSelect = function(variable, selectedId, collectionName, fieldName, readOnly, filterFunc) {
1089 readOnly = " readonly";
1093 result = '<select name="' + variable + '" id="field_' + variable + '"' + readOnly + '>' +
1094 idToOptions(selectedId, collectionName, fieldName, filterFunc) +
1099 choicesToOptions = function(selectedValue, choices) {
1101 for (index in choices) {
1102 choice = choices[index];
1103 displayName = choice[0];
1105 if (value == selectedValue) {
1106 selected = " selected";
1110 result = result + '<option value="' + value + '"' + selected + '>' + displayName + '</option>';
1115 choicesToSelect = function(variable, selectedValue, choices) {
1116 result = '<select name="' + variable + '" id="field_' + variable + '">' +
1117 choicesToOptions(selectedValue, choices) +