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.onAfterSave);
479 this.synchronous = false;
483 _.each(this.viewInitializers, function(initializer) {
488 afterSave: function(e) {
491 onAfterSave: function(e) {
495 inputChanged: function(e) {
499 submitContinueClicked: function(e) {
500 console.log("saveContinue");
502 this.afterSave = function() { };
506 submitLeaveClicked: function(e) {
507 console.log("saveLeave");
509 if (this.options.noSubmitButton || this.noSubmitButton) {
513 this.afterSave = function() {
514 that.app.navigate("list", that.model.modelName);
519 submitAddAnotherClicked: function(e) {
520 console.log("saveAnother");
524 this.afterSave = function() {
525 console.log("addAnother afterSave");
526 that.app.navigate("add", that.model.modelName);
532 this.app.hideError();
533 var data = Backbone_Syphon.serialize(this);
535 var isNew = !this.model.id;
539 this.$el.find(".help-inline").remove();
541 /* although model.validate() is called automatically by
542 model.save, we call it ourselves, so we can throw up our
543 validation error before creating the infoMsg in the log
545 errors = this.model.xosValidate(data);
547 this.onFormDataInvalid(errors);
552 this.model.attributes.humanReadableName = "new " + this.model.modelName;
553 this.model.addToCollection = this.collection;
555 this.model.addToCollection = undefined;
558 var infoMsgId = this.app.showInformational( {what: "save " + this.model.modelName + " " + this.model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
560 this.model.save(data, {error: function(model, result, xhr) { that.app.saveError(model,result,xhr,infoMsgId);},
561 success: function(model, result, xhr) { that.app.saveSuccess(model,result,xhr,infoMsgId);
562 if (that.synchronous) {
563 that.trigger("saveSuccess");
568 if (!this.synchronous) {
573 deleteClicked: function(e) {
575 this.app.deleteDialog(this.model, "list");
578 tabClick: function(tabId, regionName) {
579 region = this.app[regionName];
580 if (this.currentTabRegion != undefined) {
581 this.currentTabRegion.$el.hide();
583 if (this.currentTabId != undefined) {
584 $(this.currentTabId).removeClass('active');
586 this.currentTabRegion = region;
587 this.currentTabRegion.$el.show();
589 this.currentTabId = tabId;
590 $(tabId).addClass('active');
593 showTabs: function(tabs) {
594 template = templateFromId("#xos-tabs-template", {tabs: tabs});
595 $("#tabs").html(template(tabs));
598 _.each(tabs, function(tab) {
599 var regionName = tab["region"];
600 var tabId = '#xos-nav-'+regionName;
601 $(tabId).bind('click', function() { that.tabClick(tabId, regionName); });
607 showLinkedItems: function() {
610 tabs.push({name: "details", region: "detail"});
612 makeFilter = function(relatedField, relatedId) {
613 return function(model) { return model.attributes[relatedField] == relatedId; }
617 for (relatedName in this.model.collection.relatedCollections) {
618 var relatedField = this.model.collection.relatedCollections[relatedName];
619 var relatedId = this.model.id;
620 regionName = "linkedObjs" + (index+1);
622 relatedListViewClassName = relatedName + "ListView";
623 assert(this.app[relatedListViewClassName] != undefined, relatedListViewClassName + " not found");
624 relatedListViewClass = this.app[relatedListViewClassName].extend({collection: xos[relatedName],
625 filter: makeFilter(relatedField, relatedId),
626 parentModel: this.model});
627 this.app[regionName].show(new relatedListViewClass());
628 if (this.app.hideTabsByDefault) {
629 this.app[regionName].$el.hide();
631 tabs.push({name: relatedName, region: regionName});
636 this.app["linkedObjs" + (index+1)].empty();
641 this.tabClick('#xos-nav-detail', 'detail');
644 onFormDataInvalid: function(errors) {
646 var markErrors = function(value, key) {
647 var $inputElement = self.$el.find("[name='" + key + "']");
648 var $inputContainer = $inputElement.parent();
649 //$inputContainer.find(".help-inline").remove();
650 var $errorEl = $("<span>", {class: "help-inline error", text: value});
651 $inputContainer.append($errorEl).addClass("error");
653 _.each(errors, markErrors);
656 templateHelpers: function() { return { modelName: this.model.modelName,
657 collectionName: this.model.collectionName,
658 addFields: this.model.addFields,
659 listFields: this.model.listFields,
660 detailFields: this.options.detailFields || this.detailFields || this.model.detailFields,
661 fieldDisplayNames: this.options.fieldDisplayNames || this.fieldDisplayNames || this.model.fieldDisplayNames || {},
662 foreignFields: this.model.foreignFields,
663 detailLinkFields: this.model.detailLinkFields,
664 inputType: this.model.inputType,
667 choices: this.options.choices || this.choices || this.model.choices || {},
671 XOSDetailView_sliver = XOSDetailView.extend( {
672 events: $.extend(XOSDetailView.events,
673 {"change #field_deploymentNetwork": "onDeploymentNetworkChange"}
677 // Note that this causes the selects to be updated a second time. The
678 // first time was when the template was originally invoked, and the
679 // selects will all have the full unfiltered set of candidates. Then
680 // onShow will fire, and we'll update them with the filtered values.
681 this.onDeploymentNetworkChange();
684 onDeploymentNetworkChange: function(e) {
685 var deploymentID = this.$el.find("#field_deploymentNetwork").val();
687 console.log("onDeploymentNetworkChange");
688 console.log(deploymentID);
690 filterFunc = function(model) { return (model.attributes.deployment==deploymentID); }
691 newSelect = idToSelect("node",
692 this.model.attributes.node,
693 this.model.foreignFields["node"],
697 this.$el.find("#field_node").html(newSelect);
699 filterFunc = function(model) { for (index in model.attributes.deployments) {
700 item=model.attributes.deployments[index];
701 if (item.toString()==deploymentID.toString()) return true;
705 newSelect = idToSelect("flavor",
706 this.model.attributes.flavor,
707 this.model.foreignFields["flavor"],
711 this.$el.find("#field_flavor").html(newSelect);
713 filterFunc = function(model) { for (index in xos.imageDeployments.models) {
714 imageDeployment = xos.imageDeployments.models[index];
715 if ((imageDeployment.attributes.deployment == deploymentID) && (imageDeployment.attributes.image == model.id)) {
721 newSelect = idToSelect("image",
722 this.model.attributes.image,
723 this.model.foreignFields["image"],
727 this.$el.find("#field_image").html(newSelect);
732 This is for items that will be displayed as table rows.
734 app - MarionetteApplication
735 template - template (See XOSHelper.html)
738 XOSItemView = Marionette.ItemView.extend({
740 className: 'test-tablerow',
742 templateHelpers: function() { return { modelName: this.model.modelName,
743 collectionName: this.model.collectionName,
744 listFields: this.model.listFields,
745 addFields: this.model.addFields,
746 detailFields: this.model.detailFields,
747 foreignFields: this.model.foreignFields,
748 detailLinkFields: this.model.detailLinkFields,
749 inputType: this.model.inputType,
756 app - MarionetteApplication
757 childView - class of ItemView, probably an XOSItemView
758 template - template (see xosHelper.html)
759 collection - collection that holds these objects
760 title - title to display in template
763 XOSListView = FilteredCompositeView.extend({
764 childViewContainer: 'tbody',
767 events: {"click button.btn-xos-add": "addClicked",
768 "click button.btn-xos-refresh": "refreshClicked",
771 _fetchStateChange: function() {
772 if (this.collection.fetching) {
773 $("#xos-list-title-spinner").show();
775 $("#xos-list-title-spinner").hide();
779 addClicked: function(e) {
781 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
784 refreshClicked: function(e) {
786 this.collection.refresh(refreshRelated=true);
789 initialize: function() {
790 this.listenTo(this.collection, 'change', this._renderChildren)
791 this.listenTo(this.collection, 'sort', function() { console.log("sort"); })
792 this.listenTo(this.collection, 'add', function() { console.log("add"); })
793 this.listenTo(this.collection, 'fetchStateChange', this._fetchStateChange);
795 // Because many of the templates use idToName(), we need to
796 // listen to the collections that hold the names for the ids
797 // that we want to display.
798 for (i in this.collection.foreignCollections) {
799 foreignName = this.collection.foreignCollections[i];
800 if (xos[foreignName] == undefined) {
801 console.log("Failed to find xos class " + foreignName);
803 this.listenTo(xos[foreignName], 'change', this._renderChildren);
804 this.listenTo(xos[foreignName], 'sort', this._renderChildren);
808 getAddChildHash: function() {
809 if (this.parentModel) {
810 parentFieldName = this.parentModel.relatedCollections[this.collection.collectionName];
811 parentFieldName = parentFieldName || "unknown";
813 /*parentFieldName = "unknown";
815 for (fieldName in this.collection.foreignFields) {
816 cname = this.collection.foreignFields[fieldName];
817 if (cname = this.collection.collectionName) {
818 parentFieldName = fieldName;
821 return "#addChild" + firstCharUpper(this.collection.modelName) + "/" + this.parentModel.modelName + "/" + parentFieldName + "/" + this.parentModel.id; // modelName, fieldName, id
827 templateHelpers: function() {
828 return { title: this.title,
829 addChildHash: this.getAddChildHash(),
830 foreignFields: this.collection.foreignFields,
831 listFields: this.collection.listFields,
832 detailLinkFields: this.collection.detailLinkFields, };
836 XOSDataTableView = Marionette.View.extend( {
837 el: '<div style="overflow: hidden">' +
838 '<h3 class="xos-list-title title_placeholder"></h3>' +
839 '<div class="header_placeholder"></div>' +
841 '<div class="footer_placeholder"></div>' +
846 events: {"click button.btn-xos-add": "addClicked",
847 "click button.btn-xos-refresh": "refreshClicked",
850 _fetchStateChange: function() {
851 if (this.collection.fetching) {
852 $("#xos-list-title-spinner").show();
854 $("#xos-list-title-spinner").hide();
858 addClicked: function(e) {
860 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
863 refreshClicked: function(e) {
865 this.collection.refresh(refreshRelated=true);
869 initialize: function() {
870 $(this.el).find(".footer_placeholder").html( xosListFooterTemplate({addChildHash: this.getAddChildHash()}) );
871 $(this.el).find(".header_placeholder").html( xosListHeaderTemplate() );
873 this.listenTo(this.collection, 'fetchStateChange', this._fetchStateChange);
878 var fieldDisplayNames = view.options.fieldDisplayNames || view.fieldDisplayNames || {};
880 view.columnsByIndex = [];
881 view.columnsByFieldName = {};
882 _.each(this.collection.listFields, function(fieldName) {
883 inputType = view.options.inputType || view.inputType || {};
885 mSearchText = undefined;
886 sTitle = fieldName in fieldDisplayNames ? fieldDisplayNames[fieldName] : fieldNameToHumanReadable(fieldName);
888 if (fieldName=="backend_status") {
889 mRender = function(x,y,z) { return xosBackendStatusIconTemplate(z); };
892 } else if (fieldName in view.collection.foreignFields) {
893 var foreignCollection = view.collection.foreignFields[fieldName];
894 mSearchText = function(x) { return idToName(x, foreignCollection, "humanReadableName"); };
895 } else if (inputType[fieldName] == "spinner") {
896 mRender = function(x,y,z) { return xosDataTableSpinnerTemplate( {value: x, collectionName: view.collection.collectionName, fieldName: fieldName, id: z.id, app: view.app} ); };
898 if ($.inArray(fieldName, view.collection.detailLinkFields)>=0) {
899 var collectionName = view.collection.collectionName;
900 mRender = function(x,y,z) { return '<a href="#' + collectionName + '/' + z.id + '">' + x + '</a>'; };
902 thisColumn = {sTitle: sTitle, bSortable: bSortable, mData: fieldName, mRender: mRender, mSearchText: mSearchText};
903 view.columnsByIndex.push( thisColumn );
904 view.columnsByFieldName[fieldName] = thisColumn;
907 if (!view.noDeleteColumn) {
908 deleteColumn = {sTitle: "", bSortable: false, mRender: function(x,y,z) { return xosDeleteButtonTemplate({modelName: view.collection.modelName, id: z.id}); }, mData: function() { return "delete"; }};
909 view.columnsByIndex.push(deleteColumn);
910 view.columnsByFieldName["delete"] = deleteColumn;
913 oTable = $(this.el).find("table").dataTable( {
917 "bFilter": ! (view.options.disableFilter || view.disableFilter),
918 "bPaginate": ! (view.options.disablePaginate || view.disablePaginate),
919 "aoColumns": view.columnsByIndex,
921 fnServerData: function(sSource, aoData, fnCallback, settings) {
922 var compareColumns = function(sortCols, sortDirs, a, b) {
925 result = (a==b) ? 0 : ((a<b) ? -1 : 1);
926 if (sortDirs[0] == "desc") {
932 var searchMatch = function(row, sSearch) {
933 for (fieldName in row) {
934 if (fieldName in view.columnsByFieldName) {
936 value = row[fieldName].toString();
940 if (value.indexOf(sSearch) >= 0) {
948 //console.log(aoData);
950 // function used to populate the DataTable with the current
\r
951 // content of the collection
\r
952 var populateTable = function()
\r
954 //console.log("populatetable!");
\r
956 // clear out old row views
\r
961 iDisplayLength = 1000;
\r
964 _.each(aoData, function(param) {
\r
965 if (param.name == "sSortDir_0") {
\r
966 sortDirs = [param.value];
\r
967 } else if (param.name == "iSortCol_0") {
\r
968 sortCols = [view.columnsByIndex[param.value].mData];
\r
969 } else if (param.name == "iDisplayStart") {
\r
970 iDisplayStart = param.value;
\r
971 } else if (param.name == "iDisplayLength") {
\r
972 iDisplayLength = param.value;
\r
973 } else if (param.name == "sSearch") {
\r
974 sSearch = param.value;
\r
978 aaData = view.collection.toJSON();
\r
980 // apply backbone filtering on the models
\r
982 aaData = aaData.filter( function(row) { model = {}; model.attributes = row; return view.filter(model); } );
\r
985 var totalSize = aaData.length;
\r
987 // turn the ForeignKey fields into human readable things
\r
988 for (rowIndex in aaData) {
\r
989 row = aaData[rowIndex];
\r
990 for (fieldName in row) {
\r
991 if (fieldName in view.columnsByFieldName) {
\r
992 mSearchText = view.columnsByFieldName[fieldName].mSearchText;
\r
994 row[fieldName] = mSearchText(row[fieldName]);
\r
1000 // apply datatables search
\r
1002 aaData = aaData.filter( function(row) { return searchMatch(row, sSearch); });
\r
1005 var filteredSize = aaData.length;
\r
1007 // apply datatables sort
\r
1008 aaData.sort(function(a,b) { return compareColumns(sortCols, sortDirs, a, b); });
\r
1010 // slice it for pagination
\r
1011 if (iDisplayLength >= 0) {
\r
1012 aaData = aaData.slice(iDisplayStart, iDisplayStart+iDisplayLength);
\r
1015 return fnCallback({iTotalRecords: totalSize,
\r
1016 iTotalDisplayRecords: filteredSize,
\r
1020 aoData.shift(); // ignore sEcho
1023 view.listenTo(view.collection, 'change', populateTable);
1024 view.listenTo(view.collection, 'add', populateTable);
1025 view.listenTo(view.collection, 'remove', populateTable);
1032 getAddChildHash: function() {
1033 if (this.parentModel) {
1034 parentFieldName = this.parentModel.relatedCollections[this.collection.collectionName];
1035 parentFieldName = parentFieldName || "unknown";
1037 /*parentFieldName = "unknown";
1039 for (fieldName in this.collection.foreignFields) {
1040 cname = this.collection.foreignFields[fieldName];
1041 if (cname = this.collection.collectionName) {
1042 parentFieldName = fieldName;
1045 return "#addChild" + firstCharUpper(this.collection.modelName) + "/" + this.parentModel.modelName + "/" + parentFieldName + "/" + this.parentModel.id; // modelName, fieldName, id
1053 idToName = function(id, collectionName, fieldName) {
1054 return xos.idToName(id, collectionName, fieldName);
1057 makeIdToName = function(collectionName, fieldName) {
1058 return function(id) { return idToName(id, collectionName, fieldName); }
1061 /* Constructs lists of <option> html blocks for items in a collection.
1063 selectedId = the id of an object that should be selected, if any
1064 collectionName = name of collection
1065 fieldName = name of field within models of collection that will be displayed
1068 idToOptions = function(selectedId, collectionName, fieldName, filterFunc) {
1070 for (index in xos[collectionName].models) {
1071 linkedObject = xos[collectionName].models[index];
1072 linkedId = linkedObject["id"];
1073 linkedName = linkedObject.attributes[fieldName];
1074 if (linkedId == selectedId) {
1075 selected = " selected";
1079 if ((filterFunc) && (!filterFunc(linkedObject))) {
1082 result = result + '<option value="' + linkedId + '"' + selected + '>' + linkedName + '</option>';
1087 /* Constructs an html <select> and the <option>s to go with it.
1089 variable = variable name to return to form
1090 selectedId = the id of an object that should be selected, if any
1091 collectionName = name of collection
1092 fieldName = name of field within models of collection that will be displayed
1095 idToSelect = function(variable, selectedId, collectionName, fieldName, readOnly, filterFunc) {
1097 readOnly = " readonly";
1101 result = '<select name="' + variable + '" id="field_' + variable + '"' + readOnly + '>' +
1102 idToOptions(selectedId, collectionName, fieldName, filterFunc) +
1107 choicesToOptions = function(selectedValue, choices) {
1109 for (index in choices) {
1110 choice = choices[index];
1111 displayName = choice[0];
1113 if (value == selectedValue) {
1114 selected = " selected";
1118 result = result + '<option value="' + value + '"' + selected + '>' + displayName + '</option>';
1123 choicesToSelect = function(variable, selectedValue, choices) {
1124 result = '<select name="' + variable + '" id="field_' + variable + '">' +
1125 choicesToOptions(selectedValue, choices) +