1 HTMLView = Marionette.ItemView.extend({
3 this.$el.append(this.options.html);
7 FilteredCompositeView = Marionette.CompositeView.extend( {
8 showCollection: function() {
10 this.collection.each(function(child, index) {
11 if (this.filter && !this.filter(child)) {
14 ChildView = this.getChildView(child);
15 this.addChild(child, ChildView, index);
21 XOSRouter = Marionette.AppRouter.extend({
22 initialize: function() {
\r
26 onRoute: function(x,y,z) {
\r
27 this.routeStack.push(Backbone.history.fragment);
\r
30 prevPage: function() {
\r
31 return this.routeStack.slice(-1)[0];
34 showPreviousURL: function() {
35 prevPage = this.prevPage();
36 console.log("showPreviousURL");
37 console.log(this.routeStack);
39 this.navigate("#"+prevPage, {trigger: false, replace: true} );
43 navigate: function(href, options) {
45 Marionette.AppRouter.prototype.navigate.call(this, "nowhere", {trigger: false, replace: true});
47 Marionette.AppRouter.prototype.navigate.call(this, href, options);
53 XOSApplication = Marionette.Application.extend({
54 detailBoxId: "#detailBox",
55 errorBoxId: "#errorBox",
56 errorCloseButtonId: "#close-error-box",
57 successBoxId: "#successBox",
58 successCloseButtonId: "#close-success-box",
59 errorTemplate: "#xos-error-template",
60 successTemplate: "#xos-success-template",
63 confirmDialog: function(view, event, callback) {
64 $("#xos-confirm-dialog").dialog({
68 "Confirm" : function() {
69 $(this).dialog("close");
77 "Cancel" : function() {
78 $(this).dialog("close");
82 $("#xos-confirm-dialog").dialog("open");
85 popupErrorDialog: function(responseText) {
87 parsed_error=$.parseJSON(responseText);
91 parsed_error=undefined;
92 width=640; // django stacktraces like wide width
95 $("#xos-error-dialog").html(templateFromId("#xos-error-response")(parsed_error));
97 $("#xos-error-dialog").html(templateFromId("#xos-error-rawresponse")({responseText: responseText}))
100 $("#xos-error-dialog").dialog({
104 Ok: function() { $(this).dialog("close"); }
109 hideLinkedItems: function(result) {
112 this["linkedObjs" + (index+1)].empty();
117 hideTabs: function() { $("#tabs").hide(); },
118 showTabs: function() { $("#tabs").show(); },
120 createListHandler: function(listViewName, collection_name, regionName, title) {
123 listView = new app[listViewName];
124 app[regionName].show(listView);
125 app.hideLinkedItems();
126 $("#contentTitle").html(templateFromId("#xos-title-list")({"title": title}));
130 listButtons = new XOSListButtonView({linkedView: listView});
131 app["rightButtonPanel"].show(listButtons);
135 createAddHandler: function(detailName, collection_name, regionName, title) {
138 console.log("addHandler");
140 app.hideLinkedItems();
143 model = new xos[collection_name].model();
144 detailViewClass = app[detailName];
145 detailView = new detailViewClass({model: model, collection:xos[collection_name]});
146 app[regionName].show(detailView);
148 detailButtons = new XOSDetailButtonView({linkedView: detailView});
149 app["rightButtonPanel"].show(detailButtons);
153 createAddChildHandler: function(addChildName, collection_name) {
155 return function(parent_modelName, parent_fieldName, parent_id) {
156 app.Router.showPreviousURL();
157 model = new xos[collection_name].model();
158 model.attributes[parent_fieldName] = parent_id;
159 model.readOnlyFields.push(parent_fieldName);
160 detailViewClass = app[addChildName];
161 var detailView = new detailViewClass({model: model, collection:xos[collection_name]});
162 detailView.dialog = $("xos-addchild-dialog");
163 app["addChildDetail"].show(detailView);
164 $("#xos-addchild-dialog").dialog({
169 "Save" : function() {
170 var addDialog = this;
171 detailView.synchronous = true;
172 detailView.afterSave = function() { console.log("addChild afterSave"); $(addDialog).dialog("close"); }
175 //$(this).dialog("close");
177 "Cancel" : function() {
178 $(this).dialog("close");
182 $("#xos-addchild-dialog").dialog("open");
186 createDeleteHandler: function(collection_name) {
188 return function(model_id) {
189 console.log("deleteCalled");
190 collection = xos[collection_name];
191 model = collection.get(model_id);
192 assert(model!=undefined, "failed to get model " + model_id + " from collection " + collection_name);
193 app.Router.showPreviousURL();
194 app.deleteDialog(model);
198 createDetailHandler: function(detailName, collection_name, regionName, title) {
200 showModelId = function(model_id) {
201 $("#contentTitle").html(templateFromId("#xos-title-detail")({"title": title}));
203 collection = xos[collection_name];
204 model = collection.get(model_id);
205 if (model == undefined) {
206 app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name}));
208 detailViewClass = app[detailName];
209 detailView = new detailViewClass({model: model});
210 app[regionName].show(detailView);
211 detailView.showLinkedItems();
213 detailButtons = new XOSDetailButtonView({linkedView: detailView});
214 app["rightButtonPanel"].show(detailButtons);
220 /* error handling callbacks */
222 hideError: function() {
223 if (this.logWindowId) {
225 $(this.errorBoxId).hide();
226 $(this.successBoxId).hide();
230 showSuccess: function(result) {
231 result["statusclass"] = "success";
232 if (this.logTableId) {
233 this.appendLogWindow(result);
235 $(this.successBoxId).show();
236 $(this.successBoxId).html(_.template($(this.successTemplate).html())(result));
238 $(this.successCloseButtonId).unbind().bind('click', function() {
239 $(that.successBoxId).hide();
244 showError: function(result) {
245 result["statusclass"] = "failure";
246 if (this.logTableId) {
247 this.appendLogWindow(result);
248 this.popupErrorDialog(result.responseText);
250 // this is really old stuff
251 $(this.errorBoxId).show();
252 $(this.errorBoxId).html(_.template($(this.errorTemplate).html())(result));
254 $(this.errorCloseButtonId).unbind().bind('click', function() {
255 $(that.errorBoxId).hide();
260 showInformational: function(result) {
261 result["statusclass"] = "inprog";
262 if (this.logTableId) {
263 return this.appendLogWindow(result);
269 appendLogWindow: function(result) {
270 // compute a new logMessageId for this log message
271 logMessageId = "logMessage" + this.logMessageCount;
272 this.logMessageCount = this.logMessageCount + 1;
273 result["logMessageId"] = logMessageId;
275 logMessageTemplate=$("#xos-log-template").html();
276 assert(logMessageTemplate != undefined, "logMessageTemplate is undefined");
277 newRow = _.template(logMessageTemplate, result);
278 assert(newRow != undefined, "newRow is undefined");
280 if (result["infoMsgId"] != undefined) {
281 // We were passed the logMessageId of an informational message,
282 // and the caller wants us to replace that message with our own.
283 // i.e. replace an informational message with a success or an error.
284 $("#"+result["infoMsgId"]).replaceWith(newRow);
286 // Create a brand new log message rather than replacing one.
287 logTableBody = $(this.logTableId + " tbody");
288 logTableBody.prepend(newRow);
291 if (this.statusMsgId) {
292 $(this.statusMsgId).html( templateFromId("#xos-status-template")(result) );
295 limitTableRows(this.logTableId, 5);
300 saveError: function(model, result, xhr, infoMsgId) {
301 console.log("saveError");
302 result["what"] = "save " + model.modelName + " " + model.attributes.humanReadableName;
303 result["infoMsgId"] = infoMsgId;
304 this.showError(result);
307 saveSuccess: function(model, result, xhr, infoMsgId, addToCollection) {
308 console.log("saveSuccess");
309 if (model.addToCollection) {
310 console.log("addToCollection");
311 console.log(model.addToCollection);
312 model.addToCollection.add(model);
313 model.addToCollection.sort();
314 model.addToCollection = undefined;
316 result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};
317 result["what"] = "save " + model.modelName + " " + model.attributes.humanReadableName;
318 result["infoMsgId"] = infoMsgId;
319 this.showSuccess(result);
322 destroyError: function(model, result, xhr, infoMsgId) {
323 result["what"] = "destroy " + model.modelName + " " + model.attributes.humanReadableName;
324 result["infoMsgId"] = infoMsgId;
325 this.showError(result);
328 destroySuccess: function(model, result, xhr, infoMsgId) {
329 result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};
330 result["what"] = "destroy " + model.modelName + " " + model.attributes.humanReadableName;
331 result["infoMsgId"] = infoMsgId;
332 this.showSuccess(result);
335 /* end error handling callbacks */
337 destroyModel: function(model) {
338 //console.log("destroyModel"); console.log(model);
340 var infoMsgId = this.showInformational( {what: "destroy " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
342 model.destroy({error: function(model, result, xhr) { that.destroyError(model,result,xhr,infoMsgId);},
343 success: function(model, result, xhr) { that.destroySuccess(model,result,xhr,infoMsgId);}});
346 deleteDialog: function(model, afterDelete) {
348 assert(model!=undefined, "deleteDialog's model is undefined");
349 //console.log("deleteDialog"); console.log(model);
350 this.confirmDialog(null, null, function() {
351 //console.log("deleteConfirm"); console.log(model);
352 modelName = model.modelName;
353 that.destroyModel(model);
354 if (afterDelete=="list") {
355 that.navigate("list", modelName);
361 XOSButtonView = Marionette.ItemView.extend({
362 events: {"click button.btn-xos-save-continue": "submitContinueClicked",
363 "click button.btn-xos-save-leave": "submitLeaveClicked",
364 "click button.btn-xos-save-another": "submitAddAnotherClicked",
365 "click button.btn-xos-delete": "deleteClicked",
366 "click button.btn-xos-add": "addClicked",
367 "click button.btn-xos-refresh": "refreshClicked",
370 submitLeaveClicked: function(e) {
371 this.options.linkedView.submitLeaveClicked.call(this.options.linkedView, e);
374 submitContinueClicked: function(e) {
375 this.options.linkedView.submitContinueClicked.call(this.options.linkedView, e);
378 submitAddAnotherClicked: function(e) {
379 this.options.linkedView.submitAddAnotherClicked.call(this.options.linkedView, e);
382 submitDeleteClicked: function(e) {
383 this.options.linkedView.submitDeleteClicked.call(this.options.linkedView, e);
386 addClicked: function(e) {
387 this.options.linkedView.addClicked.call(this.options.linkedView, e);
390 refreshClicked: function(e) {
391 this.options.linkedView.refreshClicked.call(this.options.linkedView, e);
395 XOSDetailButtonView = XOSButtonView.extend({ template: "#xos-savebuttons-template" });
396 XOSListButtonView = XOSButtonView.extend({ template: "#xos-listbuttons-template" });
400 app - MarionetteApplication
401 template - template (See XOSHelper.html)
404 XOSDetailView = Marionette.ItemView.extend({
407 viewInitializers: [],
409 events: {"click button.btn-xos-save-continue": "submitContinueClicked",
410 "click button.btn-xos-save-leave": "submitLeaveClicked",
411 "click button.btn-xos-save-another": "submitAddAnotherClicked",
412 "click button.btn-xos-delete": "deleteClicked",
413 "change input": "inputChanged"},
415 /* inputChanged is watching the onChange events of the input controls. We
416 do this to track when this view is 'dirty', so we can throw up a warning
417 if the user tries to change his slices without saving first.
420 initialize: function() {
421 this.on("saveSuccess", this.onAfterSave);
422 this.synchronous = false;
426 _.each(this.viewInitializers, function(initializer) {
431 afterSave: function(e) {
434 onAfterSave: function(e) {
438 inputChanged: function(e) {
442 submitContinueClicked: function(e) {
443 console.log("saveContinue");
445 this.afterSave = function() { };
449 submitLeaveClicked: function(e) {
450 console.log("saveLeave");
453 this.afterSave = function() {
454 that.app.navigate("list", that.model.modelName);
459 submitAddAnotherClicked: function(e) {
460 console.log("saveAnother");
464 this.afterSave = function() {
465 console.log("addAnother afterSave");
466 that.app.navigate("add", that.model.modelName);
472 this.app.hideError();
473 var data = Backbone.Syphon.serialize(this);
475 var isNew = !this.model.id;
477 this.$el.find(".help-inline").remove();
479 /* although model.validate() is called automatically by
480 model.save, we call it ourselves, so we can throw up our
481 validation error before creating the infoMsg in the log
483 errors = this.model.xosValidate(data);
485 this.onFormDataInvalid(errors);
490 this.model.attributes.humanReadableName = "new " + model.modelName;
491 this.model.addToCollection = this.collection;
493 this.model.addToCollection = undefined;
496 var infoMsgId = this.app.showInformational( {what: "save " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
498 this.model.save(data, {error: function(model, result, xhr) { that.app.saveError(model,result,xhr,infoMsgId);},
499 success: function(model, result, xhr) { that.app.saveSuccess(model,result,xhr,infoMsgId);
500 if (that.synchronous) {
501 that.trigger("saveSuccess");
506 if (!this.synchronous) {
511 deleteClicked: function(e) {
513 this.app.deleteDialog(this.model, "list");
516 tabClick: function(tabId, regionName) {
517 region = this.app[regionName];
518 if (this.currentTabRegion != undefined) {
519 this.currentTabRegion.$el.hide();
521 if (this.currentTabId != undefined) {
522 $(this.currentTabId).removeClass('active');
524 this.currentTabRegion = region;
525 this.currentTabRegion.$el.show();
527 this.currentTabId = tabId;
528 $(tabId).addClass('active');
531 showTabs: function(tabs) {
532 template = templateFromId("#xos-tabs-template", {tabs: tabs});
533 $("#tabs").html(template(tabs));
536 _.each(tabs, function(tab) {
537 var regionName = tab["region"];
538 var tabId = '#xos-nav-'+regionName;
539 $(tabId).bind('click', function() { that.tabClick(tabId, regionName); });
545 showLinkedItems: function() {
548 tabs.push({name: "details", region: "detail"});
550 makeFilter = function(relatedField, relatedId) {
551 return function(model) { return model.attributes[relatedField] == relatedId; }
555 for (relatedName in this.model.collection.relatedCollections) {
556 var relatedField = this.model.collection.relatedCollections[relatedName];
557 var relatedId = this.model.id;
558 regionName = "linkedObjs" + (index+1);
560 relatedListViewClassName = relatedName + "ListView";
561 assert(this.app[relatedListViewClassName] != undefined, relatedListViewClassName + " not found");
562 relatedListViewClass = this.app[relatedListViewClassName].extend({collection: xos[relatedName],
563 filter: makeFilter(relatedField, relatedId),
564 parentModel: this.model});
565 this.app[regionName].show(new relatedListViewClass());
566 if (this.app.hideTabsByDefault) {
567 this.app[regionName].$el.hide();
569 tabs.push({name: relatedName, region: regionName});
574 this.app["linkedObjs" + (index+1)].empty();
579 this.tabClick('#xos-nav-detail', 'detail');
582 onFormDataInvalid: function(errors) {
584 var markErrors = function(value, key) {
585 var $inputElement = self.$el.find("[name='" + key + "']");
586 var $inputContainer = $inputElement.parent();
587 //$inputContainer.find(".help-inline").remove();
588 var $errorEl = $("<span>", {class: "help-inline error", text: value});
589 $inputContainer.append($errorEl).addClass("error");
591 _.each(errors, markErrors);
594 templateHelpers: function() { return { modelName: this.model.modelName,
595 collectionName: this.model.collectionName,
596 addFields: this.model.addFields,
597 listFields: this.model.listFields,
598 detailFields: this.model.detailFields,
599 foreignFields: this.model.foreignFields,
600 detailLinkFields: this.model.detailLinkFields,
601 inputType: this.model.inputType,
607 XOSDetailView_sliver = XOSDetailView.extend( {
608 events: $.extend(XOSDetailView.events,
609 {"change #field_deploymentNetwork": "onDeploymentNetworkChange"}
613 // Note that this causes the selects to be updated a second time. The
614 // first time was when the template was originally invoked, and the
615 // selects will all have the full unfiltered set of candidates. Then
616 // onShow will fire, and we'll update them with the filtered values.
617 this.onDeploymentNetworkChange();
620 onDeploymentNetworkChange: function(e) {
621 var deploymentID = this.$el.find("#field_deploymentNetwork").val();
623 console.log("onDeploymentNetworkChange");
624 console.log(deploymentID);
626 filterFunc = function(model) { return (model.attributes.deployment==deploymentID); }
627 newSelect = idToSelect("node",
628 this.model.attributes.node,
629 this.model.foreignFields["node"],
633 this.$el.find("#field_node").html(newSelect);
635 filterFunc = function(model) { for (index in model.attributes.deployments) {
636 item=model.attributes.deployments[index];
637 if (item.toString()==deploymentID.toString()) return true;
641 newSelect = idToSelect("flavor",
642 this.model.attributes.flavor,
643 this.model.foreignFields["flavor"],
647 this.$el.find("#field_flavor").html(newSelect);
649 filterFunc = function(model) { for (index in xos.imageDeployments.models) {
650 imageDeployment = xos.imageDeployments.models[index];
651 if ((imageDeployment.attributes.deployment == deploymentID) && (imageDeployment.attributes.image == model.id)) {
657 newSelect = idToSelect("image",
658 this.model.attributes.image,
659 this.model.foreignFields["image"],
663 this.$el.find("#field_image").html(newSelect);
668 This is for items that will be displayed as table rows.
670 app - MarionetteApplication
671 template - template (See XOSHelper.html)
674 XOSItemView = Marionette.ItemView.extend({
676 className: 'test-tablerow',
678 templateHelpers: function() { return { modelName: this.model.modelName,
679 collectionName: this.model.collectionName,
680 listFields: this.model.listFields,
681 addFields: this.model.addFields,
682 detailFields: this.model.detailFields,
683 foreignFields: this.model.foreignFields,
684 detailLinkFields: this.model.detailLinkFields,
685 inputType: this.model.inputType,
692 app - MarionetteApplication
693 childView - class of ItemView, probably an XOSItemView
694 template - template (see xosHelper.html)
695 collection - collection that holds these objects
696 title - title to display in template
699 XOSListView = FilteredCompositeView.extend({
700 childViewContainer: 'tbody',
703 events: {"click button.btn-xos-add": "addClicked",
704 "click button.btn-xos-refresh": "refreshClicked",
707 _fetchStateChange: function() {
708 if (this.collection.fetching) {
709 $("#xos-list-title-spinner").show();
711 $("#xos-list-title-spinner").hide();
715 addClicked: function(e) {
717 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
720 refreshClicked: function(e) {
722 this.collection.refresh(refreshRelated=true);
725 initialize: function() {
726 this.listenTo(this.collection, 'change', this._renderChildren)
727 this.listenTo(this.collection, 'sort', function() { console.log("sort"); })
728 this.listenTo(this.collection, 'add', function() { console.log("add"); })
729 this.listenTo(this.collection, 'fetchStateChange', this._fetchStateChange);
731 // Because many of the templates use idToName(), we need to
732 // listen to the collections that hold the names for the ids
733 // that we want to display.
734 for (i in this.collection.foreignCollections) {
735 foreignName = this.collection.foreignCollections[i];
736 if (xos[foreignName] == undefined) {
737 console.log("Failed to find xos class " + foreignName);
739 this.listenTo(xos[foreignName], 'change', this._renderChildren);
740 this.listenTo(xos[foreignName], 'sort', this._renderChildren);
744 getAddChildHash: function() {
745 if (this.parentModel) {
746 parentFieldName = this.parentModel.relatedCollections[this.collection.collectionName];
747 parentFieldName = parentFieldName || "unknown";
749 /*parentFieldName = "unknown";
751 for (fieldName in this.collection.foreignFields) {
752 cname = this.collection.foreignFields[fieldName];
753 if (cname = this.collection.collectionName) {
754 parentFieldName = fieldName;
757 return "#addChild" + firstCharUpper(this.collection.modelName) + "/" + this.parentModel.modelName + "/" + parentFieldName + "/" + this.parentModel.id; // modelName, fieldName, id
763 templateHelpers: function() {
764 return { title: this.title,
765 addChildHash: this.getAddChildHash(),
766 foreignFields: this.collection.foreignFields,
767 listFields: this.collection.listFields,
768 detailLinkFields: this.collection.detailLinkFields, };
772 XOSDataTableView = Marionette.View.extend( {
773 el: '<div style="overflow: hidden">' +
774 '<h3 class="xos-list-title title_placeholder"></h3>' +
775 '<div class="header_placeholder"></div>' +
777 '<div class="footer_placeholder"></div>' +
782 events: {"click button.btn-xos-add": "addClicked",
783 "click button.btn-xos-refresh": "refreshClicked",
786 _fetchStateChange: function() {
787 if (this.collection.fetching) {
788 $("#xos-list-title-spinner").show();
790 $("#xos-list-title-spinner").hide();
794 addClicked: function(e) {
796 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
799 refreshClicked: function(e) {
801 this.collection.refresh(refreshRelated=true);
805 initialize: function() {
806 $(this.el).find(".footer_placeholder").html( xosListFooterTemplate({addChildHash: this.getAddChildHash()}) );
807 $(this.el).find(".header_placeholder").html( xosListHeaderTemplate() );
809 this.listenTo(this.collection, 'fetchStateChange', this._fetchStateChange);
815 view.columnsByIndex = [];
816 view.columnsByFieldName = {};
817 _.each(this.collection.listFields, function(fieldName) {
819 mSearchText = undefined;
820 sTitle = fieldNameToHumanReadable(fieldName);
822 if (fieldName=="backend_status") {
823 mRender = function(x,y,z) { return xosBackendStatusIconTemplate(z); };
826 } else if (fieldName in view.collection.foreignFields) {
827 var foreignCollection = view.collection.foreignFields[fieldName];
828 mSearchText = function(x) { return idToName(x, foreignCollection, "humanReadableName"); };
830 if ($.inArray(fieldName, view.collection.detailLinkFields)>=0) {
831 var collectionName = view.collection.collectionName;
832 mRender = function(x,y,z) { return '<a href="#' + collectionName + '/' + z.id + '">' + x + '</a>'; };
834 thisColumn = {sTitle: sTitle, bSortable: bSortable, mData: fieldName, mRender: mRender, mSearchText: mSearchText};
835 view.columnsByIndex.push( thisColumn );
836 view.columnsByFieldName[fieldName] = thisColumn;
839 deleteColumn = {sTitle: "", bSortable: false, mRender: function(x,y,z) { return xosDeleteButtonTemplate({modelName: view.collection.modelName, id: z.id}); }, mData: function() { return "delete"; }};
840 view.columnsByIndex.push(deleteColumn);
841 view.columnsByFieldName["delete"] = deleteColumn;
843 oTable = $(this.el).find("table").dataTable( {
847 "aoColumns": view.columnsByIndex,
849 fnServerData: function(sSource, aoData, fnCallback, settings) {
850 var compareColumns = function(sortCols, sortDirs, a, b) {
853 result = (a==b) ? 0 : ((a<b) ? -1 : 1);
854 if (sortDirs[0] == "desc") {
860 var searchMatch = function(row, sSearch) {
861 for (fieldName in row) {
862 if (fieldName in view.columnsByFieldName) {
864 value = row[fieldName].toString();
868 if (value.indexOf(sSearch) >= 0) {
876 //console.log(aoData);
878 // function used to populate the DataTable with the current
\r
879 // content of the collection
\r
880 var populateTable = function()
\r
882 console.log("populatetable!");
\r
884 // clear out old row views
\r
889 iDisplayLength = 1000;
\r
892 _.each(aoData, function(param) {
\r
893 if (param.name == "sSortDir_0") {
\r
894 sortDirs = [param.value];
\r
895 } else if (param.name == "iSortCol_0") {
\r
896 sortCols = [view.columnsByIndex[param.value].mData];
\r
897 } else if (param.name == "iDisplayStart") {
\r
898 iDisplayStart = param.value;
\r
899 } else if (param.name == "iDisplayLength") {
\r
900 iDisplayLength = param.value;
\r
901 } else if (param.name == "sSearch") {
\r
902 sSearch = param.value;
\r
906 aaData = view.collection.toJSON();
\r
908 // apply backbone filtering on the models
\r
910 aaData = aaData.filter( function(row) { model = {}; model.attributes = row; return view.filter(model); } );
\r
913 var totalSize = aaData.length;
\r
915 // turn the ForeignKey fields into human readable things
\r
916 for (rowIndex in aaData) {
\r
917 row = aaData[rowIndex];
\r
918 for (fieldName in row) {
\r
919 if (fieldName in view.columnsByFieldName) {
\r
920 mSearchText = view.columnsByFieldName[fieldName].mSearchText;
\r
922 row[fieldName] = mSearchText(row[fieldName]);
\r
928 // apply datatables search
\r
930 aaData = aaData.filter( function(row) { return searchMatch(row, sSearch); });
\r
933 var filteredSize = aaData.length;
\r
935 // apply datatables sort
\r
936 aaData.sort(function(a,b) { return compareColumns(sortCols, sortDirs, a, b); });
\r
938 // slice it for pagination
\r
939 aaData = aaData.slice(iDisplayStart, iDisplayStart+iDisplayLength);
\r
941 return fnCallback({iTotalRecords: totalSize,
\r
942 iTotalDisplayRecords: filteredSize,
\r
946 aoData.shift(); // ignore sEcho
949 view.listenTo(view.collection, 'change', populateTable);
950 view.listenTo(view.collection, 'add', populateTable);
951 view.listenTo(view.collection, 'remove', populateTable);
958 getAddChildHash: function() {
959 if (this.parentModel) {
960 parentFieldName = this.parentModel.relatedCollections[this.collection.collectionName];
961 parentFieldName = parentFieldName || "unknown";
963 /*parentFieldName = "unknown";
965 for (fieldName in this.collection.foreignFields) {
966 cname = this.collection.foreignFields[fieldName];
967 if (cname = this.collection.collectionName) {
968 parentFieldName = fieldName;
971 return "#addChild" + firstCharUpper(this.collection.modelName) + "/" + this.parentModel.modelName + "/" + parentFieldName + "/" + this.parentModel.id; // modelName, fieldName, id
979 idToName = function(id, collectionName, fieldName) {
980 return xos.idToName(id, collectionName, fieldName);
983 makeIdToName = function(collectionName, fieldName) {
984 return function(id) { return idToName(id, collectionName, fieldName); }
987 /* Constructs lists of <option> html blocks for items in a collection.
989 selectedId = the id of an object that should be selected, if any
990 collectionName = name of collection
991 fieldName = name of field within models of collection that will be displayed
994 idToOptions = function(selectedId, collectionName, fieldName, filterFunc) {
996 for (index in xos[collectionName].models) {
997 linkedObject = xos[collectionName].models[index];
998 linkedId = linkedObject["id"];
999 linkedName = linkedObject.attributes[fieldName];
1000 if (linkedId == selectedId) {
1001 selected = " selected";
1005 if ((filterFunc) && (!filterFunc(linkedObject))) {
1008 result = result + '<option value="' + linkedId + '"' + selected + '>' + linkedName + '</option>';
1013 /* Constructs an html <select> and the <option>s to go with it.
1015 variable = variable name to return to form
1016 selectedId = the id of an object that should be selected, if any
1017 collectionName = name of collection
1018 fieldName = name of field within models of collection that will be displayed
1021 idToSelect = function(variable, selectedId, collectionName, fieldName, readOnly, filterFunc) {
1023 readOnly = " readonly";
1027 result = '<select name="' + variable + '" id="field_' + variable + '"' + readOnly + '>' +
1028 idToOptions(selectedId, collectionName, fieldName, filterFunc) +