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,
25 events: {"change select": "onSliceChanged"},
27 childViewOptions: function() {
28 return { selectedID: this.options.selectedID || this.selectedID || null };
31 onSliceChanged: function() {
32 this.sliceChanged(this.$el.find("select").val());
35 sliceChanged: function(id) {
36 console.log("sliceChanged " + id);
39 templateHelpers: function() { return {caption: this.options.caption || this.caption }; },
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 // XXX - We import backbone multiple times (BAD!) since the import happens
\r
88 // inside of the view's html. The second time it's imported (developer
\r
89 // view), it wipes out Backbone.Syphon. So, save it as Backbone_Syphon for
\r
91 Backbone_Syphon = Backbone.Syphon
\r
92 Backbone_Syphon.InputReaders.register('select', function(el) {
\r
93 // Modify syphon so that if a select has "syphonall" in the class, then
94 // the value of every option will be returned, regardless of whether of
95 // not it is selected.
96 if (el.hasClass("syphonall")) {
98 _.each(el.find("option"), function(option) {
99 result.push($(option).val());
106 XOSApplication = Marionette.Application.extend({
107 detailBoxId: "#detailBox",
108 errorBoxId: "#errorBox",
109 errorCloseButtonId: "#close-error-box",
110 successBoxId: "#successBox",
111 successCloseButtonId: "#close-success-box",
112 errorTemplate: "#xos-error-template",
113 successTemplate: "#xos-success-template",
116 confirmDialog: function(view, event, callback) {
117 $("#xos-confirm-dialog").dialog({
121 "Confirm" : function() {
122 $(this).dialog("close");
130 "Cancel" : function() {
131 $(this).dialog("close");
135 $("#xos-confirm-dialog").dialog("open");
138 popupErrorDialog: function(responseText) {
140 parsed_error=$.parseJSON(responseText);
144 parsed_error=undefined;
145 width=640; // django stacktraces like wide width
147 console.log(responseText);
148 console.log(parsed_error);
150 $("#xos-error-dialog").html(templateFromId("#xos-error-response")(parsed_error));
152 $("#xos-error-dialog").html(templateFromId("#xos-error-rawresponse")({responseText: responseText}))
155 $("#xos-error-dialog").dialog({
159 Ok: function() { $(this).dialog("close"); }
164 hideLinkedItems: function(result) {
167 this["linkedObjs" + (index+1)].empty();
172 hideTabs: function() { $("#tabs").hide(); },
173 showTabs: function() { $("#tabs").show(); },
175 createListHandler: function(listViewName, collection_name, regionName, title) {
178 listView = new app[listViewName];
179 app[regionName].show(listView);
180 app.hideLinkedItems();
181 $("#contentTitle").html(templateFromId("#xos-title-list")({"title": title}));
185 listButtons = new XOSListButtonView({linkedView: listView});
186 app["rightButtonPanel"].show(listButtons);
190 createAddHandler: function(detailName, collection_name, regionName, title) {
193 console.log("addHandler");
195 app.hideLinkedItems();
198 model = new xos[collection_name].model();
199 detailViewClass = app[detailName];
200 detailView = new detailViewClass({model: model, collection:xos[collection_name]});
201 app[regionName].show(detailView);
203 detailButtons = new XOSDetailButtonView({linkedView: detailView});
204 app["rightButtonPanel"].show(detailButtons);
208 createAddChildHandler: function(addChildName, collection_name) {
210 return function(parent_modelName, parent_fieldName, parent_id) {
211 app.Router.showPreviousURL();
212 model = new xos[collection_name].model();
213 model.attributes[parent_fieldName] = parent_id;
214 model.readOnlyFields.push(parent_fieldName);
215 detailViewClass = app[addChildName];
216 var detailView = new detailViewClass({model: model, collection:xos[collection_name]});
217 detailView.dialog = $("xos-addchild-dialog");
218 app["addChildDetail"].show(detailView);
219 $("#xos-addchild-dialog").dialog({
224 "Save" : function() {
225 var addDialog = this;
226 detailView.synchronous = true;
227 detailView.afterSave = function() { console.log("addChild afterSave"); $(addDialog).dialog("close"); }
230 //$(this).dialog("close");
232 "Cancel" : function() {
233 $(this).dialog("close");
237 $("#xos-addchild-dialog").dialog("open");
241 createDeleteHandler: function(collection_name) {
243 return function(model_id) {
244 console.log("deleteCalled");
245 collection = xos[collection_name];
246 model = collection.get(model_id);
247 assert(model!=undefined, "failed to get model " + model_id + " from collection " + collection_name);
248 app.Router.showPreviousURL();
249 app.deleteDialog(model);
253 createDetailHandler: function(detailName, collection_name, regionName, title) {
255 showModelId = function(model_id) {
256 $("#contentTitle").html(templateFromId("#xos-title-detail")({"title": title}));
258 collection = xos[collection_name];
259 model = collection.get(model_id);
260 if (model == undefined) {
261 app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name}));
263 detailViewClass = app[detailName];
264 detailView = new detailViewClass({model: model});
265 app[regionName].show(detailView);
266 detailView.showLinkedItems();
268 detailButtons = new XOSDetailButtonView({linkedView: detailView});
269 app["rightButtonPanel"].show(detailButtons);
275 /* error handling callbacks */
277 hideError: function() {
278 if (this.logWindowId) {
280 $(this.errorBoxId).hide();
281 $(this.successBoxId).hide();
285 showSuccess: function(result) {
286 result["statusclass"] = "success";
287 if (this.logTableId) {
288 this.appendLogWindow(result);
290 $(this.successBoxId).show();
291 $(this.successBoxId).html(_.template($(this.successTemplate).html())(result));
293 $(this.successCloseButtonId).unbind().bind('click', function() {
294 $(that.successBoxId).hide();
299 showError: function(result) {
300 result["statusclass"] = "failure";
301 if (this.logTableId) {
302 this.appendLogWindow(result);
303 this.popupErrorDialog(result.responseText);
305 // this is really old stuff
306 $(this.errorBoxId).show();
307 $(this.errorBoxId).html(_.template($(this.errorTemplate).html())(result));
309 $(this.errorCloseButtonId).unbind().bind('click', function() {
310 $(that.errorBoxId).hide();
315 showInformational: function(result) {
316 result["statusclass"] = "inprog";
317 if (this.logTableId) {
318 return this.appendLogWindow(result);
324 appendLogWindow: function(result) {
325 // compute a new logMessageId for this log message
326 logMessageId = "logMessage" + this.logMessageCount;
327 this.logMessageCount = this.logMessageCount + 1;
328 result["logMessageId"] = logMessageId;
330 logMessageTemplate=$("#xos-log-template").html();
331 assert(logMessageTemplate != undefined, "logMessageTemplate is undefined");
332 newRow = _.template(logMessageTemplate, result);
333 assert(newRow != undefined, "newRow is undefined");
335 if (result["infoMsgId"] != undefined) {
336 // We were passed the logMessageId of an informational message,
337 // and the caller wants us to replace that message with our own.
338 // i.e. replace an informational message with a success or an error.
339 $("#"+result["infoMsgId"]).replaceWith(newRow);
341 // Create a brand new log message rather than replacing one.
342 logTableBody = $(this.logTableId + " tbody");
343 logTableBody.prepend(newRow);
346 if (this.statusMsgId) {
347 $(this.statusMsgId).html( templateFromId("#xos-status-template")(result) );
350 limitTableRows(this.logTableId, 5);
355 saveError: function(model, result, xhr, infoMsgId) {
356 console.log("saveError");
357 result["what"] = "save " + model.modelName + " " + model.attributes.humanReadableName;
358 result["infoMsgId"] = infoMsgId;
359 this.showError(result);
362 saveSuccess: function(model, result, xhr, infoMsgId, addToCollection) {
363 console.log("saveSuccess");
364 if (model.addToCollection) {
365 console.log("addToCollection");
366 console.log(model.addToCollection);
367 model.addToCollection.add(model);
368 model.addToCollection.sort();
369 model.addToCollection = undefined;
371 result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};
372 result["what"] = "save " + model.modelName + " " + model.attributes.humanReadableName;
373 result["infoMsgId"] = infoMsgId;
374 this.showSuccess(result);
377 destroyError: function(model, result, xhr, infoMsgId) {
378 result["what"] = "destroy " + model.modelName + " " + model.attributes.humanReadableName;
379 result["infoMsgId"] = infoMsgId;
380 this.showError(result);
383 destroySuccess: function(model, result, xhr, infoMsgId) {
384 result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};
385 result["what"] = "destroy " + model.modelName + " " + model.attributes.humanReadableName;
386 result["infoMsgId"] = infoMsgId;
387 this.showSuccess(result);
390 /* end error handling callbacks */
392 destroyModel: function(model) {
393 //console.log("destroyModel"); console.log(model);
395 var infoMsgId = this.showInformational( {what: "destroy " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
397 model.destroy({error: function(model, result, xhr) { that.destroyError(model,result,xhr,infoMsgId);},
398 success: function(model, result, xhr) { that.destroySuccess(model,result,xhr,infoMsgId);}});
401 deleteDialog: function(model, afterDelete) {
403 assert(model!=undefined, "deleteDialog's model is undefined");
404 //console.log("deleteDialog"); console.log(model);
405 this.confirmDialog(null, null, function() {
406 //console.log("deleteConfirm"); console.log(model);
407 modelName = model.modelName;
408 that.destroyModel(model);
409 if (afterDelete=="list") {
410 that.navigate("list", modelName);
411 } else if (afterDelete) {
418 XOSButtonView = Marionette.ItemView.extend({
419 events: {"click button.btn-xos-save-continue": "submitContinueClicked",
420 "click button.btn-xos-save-leave": "submitLeaveClicked",
421 "click button.btn-xos-save-another": "submitAddAnotherClicked",
422 "click button.btn-xos-delete": "deleteClicked",
423 "click button.btn-xos-add": "addClicked",
424 "click button.btn-xos-refresh": "refreshClicked",
427 submitLeaveClicked: function(e) {
428 this.options.linkedView.submitLeaveClicked.call(this.options.linkedView, e);
431 submitContinueClicked: function(e) {
432 this.options.linkedView.submitContinueClicked.call(this.options.linkedView, e);
435 submitAddAnotherClicked: function(e) {
436 this.options.linkedView.submitAddAnotherClicked.call(this.options.linkedView, e);
439 submitDeleteClicked: function(e) {
440 this.options.linkedView.deleteClicked.call(this.options.linkedView, e);
443 addClicked: function(e) {
444 this.options.linkedView.addClicked.call(this.options.linkedView, e);
447 refreshClicked: function(e) {
448 this.options.linkedView.refreshClicked.call(this.options.linkedView, e);
452 XOSDetailButtonView = XOSButtonView.extend({ template: "#xos-savebuttons-template" });
453 XOSListButtonView = XOSButtonView.extend({ template: "#xos-listbuttons-template" });
457 app - MarionetteApplication
458 template - template (See XOSHelper.html)
461 XOSDetailView = Marionette.ItemView.extend({
464 viewInitializers: [],
466 events: {"click button.btn-xos-save-continue": "submitContinueClicked",
467 "click button.btn-xos-save-leave": "submitLeaveClicked",
468 "click button.btn-xos-save-another": "submitAddAnotherClicked",
469 "click button.btn-xos-delete": "deleteClicked",
470 "change input": "inputChanged"},
472 /* inputChanged is watching the onChange events of the input controls. We
473 do this to track when this view is 'dirty', so we can throw up a warning
474 if the user tries to change his slices without saving first.
477 initialize: function() {
478 this.on("saveSuccess", this.onSaveSuccess);
479 this.synchronous = false;
483 _.each(this.viewInitializers, function(initializer) {
488 saveSuccess: function(e) {
489 // always called after a save succeeds
492 afterSave: function(e) {
493 // if this.synchronous, then called after the save succeeds
494 // if !this.synchronous, then called after save is initiated
497 onSaveSuccess: function(e) {
499 if (this.synchronous) {
504 inputChanged: function(e) {
508 submitContinueClicked: function(e) {
509 console.log("saveContinue");
511 this.afterSave = function() { };
515 submitLeaveClicked: function(e) {
516 console.log("saveLeave");
518 if (this.options.noSubmitButton || this.noSubmitButton) {
522 this.afterSave = function() {
523 that.app.navigate("list", that.model.modelName);
528 submitAddAnotherClicked: function(e) {
529 console.log("saveAnother");
533 this.afterSave = function() {
534 console.log("addAnother afterSave");
535 that.app.navigate("add", that.model.modelName);
541 this.app.hideError();
542 var data = Backbone_Syphon.serialize(this);
544 var isNew = !this.model.id;
548 this.$el.find(".help-inline").remove();
550 /* although model.validate() is called automatically by
551 model.save, we call it ourselves, so we can throw up our
552 validation error before creating the infoMsg in the log
554 errors = this.model.xosValidate(data);
556 this.onFormDataInvalid(errors);
561 this.model.attributes.humanReadableName = "new " + this.model.modelName;
562 this.model.addToCollection = this.collection;
564 this.model.addToCollection = undefined;
567 var infoMsgId = this.app.showInformational( {what: "save " + this.model.modelName + " " + this.model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
569 this.model.save(data, {error: function(model, result, xhr) { that.app.saveError(model,result,xhr,infoMsgId);},
570 success: function(model, result, xhr) { that.app.saveSuccess(model,result,xhr,infoMsgId);
571 that.trigger("saveSuccess");
575 if (!this.synchronous) {
580 deleteClicked: function(e) {
582 this.app.deleteDialog(this.model, "list");
585 tabClick: function(tabId, regionName) {
586 region = this.app[regionName];
587 if (this.currentTabRegion != undefined) {
588 this.currentTabRegion.$el.hide();
590 if (this.currentTabId != undefined) {
591 $(this.currentTabId).removeClass('active');
593 this.currentTabRegion = region;
594 this.currentTabRegion.$el.show();
596 this.currentTabId = tabId;
597 $(tabId).addClass('active');
600 showTabs: function(tabs) {
601 template = templateFromId("#xos-tabs-template", {tabs: tabs});
602 $("#tabs").html(template(tabs));
605 _.each(tabs, function(tab) {
606 var regionName = tab["region"];
607 var tabId = '#xos-nav-'+regionName;
608 $(tabId).bind('click', function() { that.tabClick(tabId, regionName); });
614 showLinkedItems: function() {
617 tabs.push({name: "details", region: "detail"});
619 makeFilter = function(relatedField, relatedId) {
620 return function(model) { return model.attributes[relatedField] == relatedId; }
624 for (relatedName in this.model.collection.relatedCollections) {
625 var relatedField = this.model.collection.relatedCollections[relatedName];
626 var relatedId = this.model.id;
627 regionName = "linkedObjs" + (index+1);
629 relatedListViewClassName = relatedName + "ListView";
630 assert(this.app[relatedListViewClassName] != undefined, relatedListViewClassName + " not found");
631 relatedListViewClass = this.app[relatedListViewClassName].extend({collection: xos[relatedName],
632 filter: makeFilter(relatedField, relatedId),
633 parentModel: this.model});
634 this.app[regionName].show(new relatedListViewClass());
635 if (this.app.hideTabsByDefault) {
636 this.app[regionName].$el.hide();
638 tabs.push({name: relatedName, region: regionName});
643 this.app["linkedObjs" + (index+1)].empty();
648 this.tabClick('#xos-nav-detail', 'detail');
651 onFormDataInvalid: function(errors) {
653 var markErrors = function(value, key) {
654 var $inputElement = self.$el.find("[name='" + key + "']");
655 var $inputContainer = $inputElement.parent();
656 //$inputContainer.find(".help-inline").remove();
657 var $errorEl = $("<span>", {class: "help-inline error", text: value});
658 $inputContainer.append($errorEl).addClass("error");
660 _.each(errors, markErrors);
663 templateHelpers: function() { return { modelName: this.model.modelName,
664 collectionName: this.model.collectionName,
665 addFields: this.model.addFields,
666 listFields: this.model.listFields,
667 detailFields: this.options.detailFields || this.detailFields || this.model.detailFields,
668 fieldDisplayNames: this.options.fieldDisplayNames || this.fieldDisplayNames || this.model.fieldDisplayNames || {},
669 foreignFields: this.model.foreignFields,
670 detailLinkFields: this.model.detailLinkFields,
671 inputType: this.model.inputType,
674 choices: this.options.choices || this.choices || this.model.choices || {},
678 XOSDetailView_sliver = XOSDetailView.extend( {
679 events: $.extend(XOSDetailView.events,
680 {"change #field_deploymentNetwork": "onDeploymentNetworkChange"}
684 // Note that this causes the selects to be updated a second time. The
685 // first time was when the template was originally invoked, and the
686 // selects will all have the full unfiltered set of candidates. Then
687 // onShow will fire, and we'll update them with the filtered values.
688 this.onDeploymentNetworkChange();
691 onDeploymentNetworkChange: function(e) {
692 var deploymentID = this.$el.find("#field_deploymentNetwork").val();
694 console.log("onDeploymentNetworkChange");
695 console.log(deploymentID);
697 filterFunc = function(model) { return (model.attributes.deployment==deploymentID); }
698 newSelect = idToSelect("node",
699 this.model.attributes.node,
700 this.model.foreignFields["node"],
704 this.$el.find("#field_node").html(newSelect);
706 filterFunc = function(model) { for (index in model.attributes.deployments) {
707 item=model.attributes.deployments[index];
708 if (item.toString()==deploymentID.toString()) return true;
712 newSelect = idToSelect("flavor",
713 this.model.attributes.flavor,
714 this.model.foreignFields["flavor"],
718 this.$el.find("#field_flavor").html(newSelect);
720 filterFunc = function(model) { for (index in xos.imageDeployments.models) {
721 imageDeployment = xos.imageDeployments.models[index];
722 if ((imageDeployment.attributes.deployment == deploymentID) && (imageDeployment.attributes.image == model.id)) {
728 newSelect = idToSelect("image",
729 this.model.attributes.image,
730 this.model.foreignFields["image"],
734 this.$el.find("#field_image").html(newSelect);
739 This is for items that will be displayed as table rows.
741 app - MarionetteApplication
742 template - template (See XOSHelper.html)
745 XOSItemView = Marionette.ItemView.extend({
747 className: 'test-tablerow',
749 templateHelpers: function() { return { modelName: this.model.modelName,
750 collectionName: this.model.collectionName,
751 listFields: this.model.listFields,
752 addFields: this.model.addFields,
753 detailFields: this.model.detailFields,
754 foreignFields: this.model.foreignFields,
755 detailLinkFields: this.model.detailLinkFields,
756 inputType: this.model.inputType,
763 app - MarionetteApplication
764 childView - class of ItemView, probably an XOSItemView
765 template - template (see xosHelper.html)
766 collection - collection that holds these objects
767 title - title to display in template
770 XOSListView = FilteredCompositeView.extend({
771 childViewContainer: 'tbody',
774 events: {"click button.btn-xos-add": "addClicked",
775 "click button.btn-xos-refresh": "refreshClicked",
778 _fetchStateChange: function() {
779 if (this.collection.fetching) {
780 $("#xos-list-title-spinner").show();
782 $("#xos-list-title-spinner").hide();
786 addClicked: function(e) {
788 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
791 refreshClicked: function(e) {
793 this.collection.refresh(refreshRelated=true);
796 initialize: function() {
797 this.listenTo(this.collection, 'change', this._renderChildren)
798 this.listenTo(this.collection, 'sort', function() { console.log("sort"); })
799 this.listenTo(this.collection, 'add', function() { console.log("add"); })
800 this.listenTo(this.collection, 'fetchStateChange', this._fetchStateChange);
802 // Because many of the templates use idToName(), we need to
803 // listen to the collections that hold the names for the ids
804 // that we want to display.
805 for (i in this.collection.foreignCollections) {
806 foreignName = this.collection.foreignCollections[i];
807 if (xos[foreignName] == undefined) {
808 console.log("Failed to find xos class " + foreignName);
810 this.listenTo(xos[foreignName], 'change', this._renderChildren);
811 this.listenTo(xos[foreignName], 'sort', this._renderChildren);
815 getAddChildHash: function() {
816 if (this.parentModel) {
817 parentFieldName = this.parentModel.relatedCollections[this.collection.collectionName];
818 parentFieldName = parentFieldName || "unknown";
820 /*parentFieldName = "unknown";
822 for (fieldName in this.collection.foreignFields) {
823 cname = this.collection.foreignFields[fieldName];
824 if (cname = this.collection.collectionName) {
825 parentFieldName = fieldName;
828 return "#addChild" + firstCharUpper(this.collection.modelName) + "/" + this.parentModel.modelName + "/" + parentFieldName + "/" + this.parentModel.id; // modelName, fieldName, id
834 templateHelpers: function() {
835 return { title: this.title,
836 addChildHash: this.getAddChildHash(),
837 foreignFields: this.collection.foreignFields,
838 listFields: this.collection.listFields,
839 detailLinkFields: this.collection.detailLinkFields, };
843 XOSDataTableView = Marionette.View.extend( {
844 el: '<div style="overflow: hidden">' +
845 '<h3 class="xos-list-title title_placeholder"></h3>' +
846 '<div class="header_placeholder"></div>' +
848 '<div class="footer_placeholder"></div>' +
853 events: {"click button.btn-xos-add": "addClicked",
854 "click button.btn-xos-refresh": "refreshClicked",
857 _fetchStateChange: function() {
858 if (this.collection.fetching) {
859 $("#xos-list-title-spinner").show();
861 $("#xos-list-title-spinner").hide();
865 addClicked: function(e) {
867 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
870 refreshClicked: function(e) {
872 this.collection.refresh(refreshRelated=true);
876 initialize: function() {
877 $(this.el).find(".footer_placeholder").html( xosListFooterTemplate({addChildHash: this.getAddChildHash()}) );
878 $(this.el).find(".header_placeholder").html( xosListHeaderTemplate() );
880 this.listenTo(this.collection, 'fetchStateChange', this._fetchStateChange);
885 var fieldDisplayNames = view.options.fieldDisplayNames || view.fieldDisplayNames || {};
887 view.columnsByIndex = [];
888 view.columnsByFieldName = {};
889 _.each(this.collection.listFields, function(fieldName) {
890 inputType = view.options.inputType || view.inputType || {};
892 mSearchText = undefined;
893 sTitle = fieldName in fieldDisplayNames ? fieldDisplayNames[fieldName] : fieldNameToHumanReadable(fieldName);
895 if (fieldName=="backend_status") {
896 mRender = function(x,y,z) { return xosBackendStatusIconTemplate(z); };
899 } else if (fieldName in view.collection.foreignFields) {
900 var foreignCollection = view.collection.foreignFields[fieldName];
901 mSearchText = function(x) { return idToName(x, foreignCollection, "humanReadableName"); };
902 } else if (inputType[fieldName] == "spinner") {
903 mRender = function(x,y,z) { return xosDataTableSpinnerTemplate( {value: x, collectionName: view.collection.collectionName, fieldName: fieldName, id: z.id, app: view.app} ); };
905 if ($.inArray(fieldName, view.collection.detailLinkFields)>=0) {
906 var collectionName = view.collection.collectionName;
907 mRender = function(x,y,z) { return '<a href="#' + collectionName + '/' + z.id + '">' + x + '</a>'; };
909 thisColumn = {sTitle: sTitle, bSortable: bSortable, mData: fieldName, mRender: mRender, mSearchText: mSearchText};
910 view.columnsByIndex.push( thisColumn );
911 view.columnsByFieldName[fieldName] = thisColumn;
914 if (!view.noDeleteColumn) {
915 deleteColumn = {sTitle: "", bSortable: false, mRender: function(x,y,z) { return xosDeleteButtonTemplate({modelName: view.collection.modelName, id: z.id}); }, mData: function() { return "delete"; }};
916 view.columnsByIndex.push(deleteColumn);
917 view.columnsByFieldName["delete"] = deleteColumn;
920 oTable = $(this.el).find("table").dataTable( {
924 "bFilter": ! (view.options.disableFilter || view.disableFilter),
925 "bPaginate": ! (view.options.disablePaginate || view.disablePaginate),
926 "aoColumns": view.columnsByIndex,
928 fnServerData: function(sSource, aoData, fnCallback, settings) {
929 var compareColumns = function(sortCols, sortDirs, a, b) {
932 result = (a==b) ? 0 : ((a<b) ? -1 : 1);
933 if (sortDirs[0] == "desc") {
939 var searchMatch = function(row, sSearch) {
940 for (fieldName in row) {
941 if (fieldName in view.columnsByFieldName) {
943 value = row[fieldName].toString();
947 if (value.indexOf(sSearch) >= 0) {
955 //console.log(aoData);
957 // function used to populate the DataTable with the current
\r
958 // content of the collection
\r
959 var populateTable = function()
\r
961 //console.log("populatetable!");
\r
963 // clear out old row views
\r
968 iDisplayLength = 1000;
\r
971 _.each(aoData, function(param) {
\r
972 if (param.name == "sSortDir_0") {
\r
973 sortDirs = [param.value];
\r
974 } else if (param.name == "iSortCol_0") {
\r
975 sortCols = [view.columnsByIndex[param.value].mData];
\r
976 } else if (param.name == "iDisplayStart") {
\r
977 iDisplayStart = param.value;
\r
978 } else if (param.name == "iDisplayLength") {
\r
979 iDisplayLength = param.value;
\r
980 } else if (param.name == "sSearch") {
\r
981 sSearch = param.value;
\r
985 aaData = view.collection.toJSON();
\r
987 // apply backbone filtering on the models
\r
989 aaData = aaData.filter( function(row) { model = {}; model.attributes = row; return view.filter(model); } );
\r
992 var totalSize = aaData.length;
\r
994 // turn the ForeignKey fields into human readable things
\r
995 for (rowIndex in aaData) {
\r
996 row = aaData[rowIndex];
\r
997 for (fieldName in row) {
\r
998 if (fieldName in view.columnsByFieldName) {
\r
999 mSearchText = view.columnsByFieldName[fieldName].mSearchText;
\r
1000 if (mSearchText) {
\r
1001 row[fieldName] = mSearchText(row[fieldName]);
\r
1007 // apply datatables search
\r
1009 aaData = aaData.filter( function(row) { return searchMatch(row, sSearch); });
\r
1012 var filteredSize = aaData.length;
\r
1014 // apply datatables sort
\r
1015 aaData.sort(function(a,b) { return compareColumns(sortCols, sortDirs, a, b); });
\r
1017 // slice it for pagination
\r
1018 if (iDisplayLength >= 0) {
\r
1019 aaData = aaData.slice(iDisplayStart, iDisplayStart+iDisplayLength);
\r
1022 return fnCallback({iTotalRecords: totalSize,
\r
1023 iTotalDisplayRecords: filteredSize,
\r
1027 aoData.shift(); // ignore sEcho
1030 view.listenTo(view.collection, 'change', populateTable);
1031 view.listenTo(view.collection, 'add', populateTable);
1032 view.listenTo(view.collection, 'remove', populateTable);
1039 getAddChildHash: function() {
1040 if (this.parentModel) {
1041 parentFieldName = this.parentModel.relatedCollections[this.collection.collectionName];
1042 parentFieldName = parentFieldName || "unknown";
1044 /*parentFieldName = "unknown";
1046 for (fieldName in this.collection.foreignFields) {
1047 cname = this.collection.foreignFields[fieldName];
1048 if (cname = this.collection.collectionName) {
1049 parentFieldName = fieldName;
1052 return "#addChild" + firstCharUpper(this.collection.modelName) + "/" + this.parentModel.modelName + "/" + parentFieldName + "/" + this.parentModel.id; // modelName, fieldName, id
1060 idToName = function(id, collectionName, fieldName) {
1061 return xos.idToName(id, collectionName, fieldName);
1064 makeIdToName = function(collectionName, fieldName) {
1065 return function(id) { return idToName(id, collectionName, fieldName); }
1068 /* Constructs lists of <option> html blocks for items in a collection.
1070 selectedId = the id of an object that should be selected, if any
1071 collectionName = name of collection
1072 fieldName = name of field within models of collection that will be displayed
1075 idToOptions = function(selectedId, collectionName, fieldName, filterFunc) {
1077 for (index in xos[collectionName].models) {
1078 linkedObject = xos[collectionName].models[index];
1079 linkedId = linkedObject["id"];
1080 linkedName = linkedObject.attributes[fieldName];
1081 if (linkedId == selectedId) {
1082 selected = " selected";
1086 if ((filterFunc) && (!filterFunc(linkedObject))) {
1089 result = result + '<option value="' + linkedId + '"' + selected + '>' + linkedName + '</option>';
1094 /* Constructs an html <select> and the <option>s to go with it.
1096 variable = variable name to return to form
1097 selectedId = the id of an object that should be selected, if any
1098 collectionName = name of collection
1099 fieldName = name of field within models of collection that will be displayed
1102 idToSelect = function(variable, selectedId, collectionName, fieldName, readOnly, filterFunc) {
1104 readOnly = " readonly";
1108 result = '<select name="' + variable + '" id="field_' + variable + '"' + readOnly + '>' +
1109 idToOptions(selectedId, collectionName, fieldName, filterFunc) +
1114 choicesToOptions = function(selectedValue, choices) {
1116 for (index in choices) {
1117 choice = choices[index];
1118 displayName = choice[0];
1120 if (value == selectedValue) {
1121 selected = " selected";
1125 result = result + '<option value="' + value + '"' + selected + '>' + displayName + '</option>';
1130 choicesToSelect = function(variable, selectedValue, choices) {
1131 result = '<select name="' + variable + '" id="field_' + variable + '">' +
1132 choicesToOptions(selectedValue, choices) +