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 events: {"click button.btn-xos-save-continue": "submitContinueClicked",
408 "click button.btn-xos-save-leave": "submitLeaveClicked",
409 "click button.btn-xos-save-another": "submitAddAnotherClicked",
410 "click button.btn-xos-delete": "deleteClicked",
411 "change input": "inputChanged"},
413 /* inputChanged is watching the onChange events of the input controls. We
414 do this to track when this view is 'dirty', so we can throw up a warning
415 if the user tries to change his slices without saving first.
418 initialize: function() {
419 this.on("saveSuccess", this.onAfterSave);
420 this.synchronous = false;
423 afterSave: function(e) {
426 onAfterSave: function(e) {
430 inputChanged: function(e) {
434 submitContinueClicked: function(e) {
435 console.log("saveContinue");
437 this.afterSave = function() { };
441 submitLeaveClicked: function(e) {
442 console.log("saveLeave");
445 this.afterSave = function() {
446 that.app.navigate("list", that.model.modelName);
451 submitAddAnotherClicked: function(e) {
452 console.log("saveAnother");
456 this.afterSave = function() {
457 console.log("addAnother afterSave");
458 that.app.navigate("add", that.model.modelName);
464 this.app.hideError();
465 var data = Backbone.Syphon.serialize(this);
467 var isNew = !this.model.id;
469 this.$el.find(".help-inline").remove();
471 /* although model.validate() is called automatically by
472 model.save, we call it ourselves, so we can throw up our
473 validation error before creating the infoMsg in the log
475 errors = this.model.xosValidate(data);
477 this.onFormDataInvalid(errors);
482 this.model.attributes.humanReadableName = "new " + model.modelName;
483 this.model.addToCollection = this.collection;
485 this.model.addToCollection = undefined;
488 var infoMsgId = this.app.showInformational( {what: "save " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
490 this.model.save(data, {error: function(model, result, xhr) { that.app.saveError(model,result,xhr,infoMsgId);},
491 success: function(model, result, xhr) { that.app.saveSuccess(model,result,xhr,infoMsgId);
492 if (that.synchronous) {
493 that.trigger("saveSuccess");
498 if (!this.synchronous) {
503 deleteClicked: function(e) {
505 this.app.deleteDialog(this.model, "list");
508 tabClick: function(tabId, regionName) {
509 region = this.app[regionName];
510 if (this.currentTabRegion != undefined) {
511 this.currentTabRegion.$el.hide();
513 if (this.currentTabId != undefined) {
514 $(this.currentTabId).removeClass('active');
516 this.currentTabRegion = region;
517 this.currentTabRegion.$el.show();
519 this.currentTabId = tabId;
520 $(tabId).addClass('active');
523 showTabs: function(tabs) {
524 template = templateFromId("#xos-tabs-template", {tabs: tabs});
525 $("#tabs").html(template(tabs));
528 _.each(tabs, function(tab) {
529 var regionName = tab["region"];
530 var tabId = '#xos-nav-'+regionName;
531 $(tabId).bind('click', function() { that.tabClick(tabId, regionName); });
537 showLinkedItems: function() {
540 tabs.push({name: "details", region: "detail"});
542 makeFilter = function(relatedField, relatedId) {
543 return function(model) { return model.attributes[relatedField] == relatedId; }
547 for (relatedName in this.model.collection.relatedCollections) {
548 var relatedField = this.model.collection.relatedCollections[relatedName];
549 var relatedId = this.model.id;
550 regionName = "linkedObjs" + (index+1);
552 relatedListViewClassName = relatedName + "ListView";
553 assert(this.app[relatedListViewClassName] != undefined, relatedListViewClassName + " not found");
554 relatedListViewClass = this.app[relatedListViewClassName].extend({collection: xos[relatedName],
555 filter: makeFilter(relatedField, relatedId),
556 parentModel: this.model});
557 this.app[regionName].show(new relatedListViewClass());
558 if (this.app.hideTabsByDefault) {
559 this.app[regionName].$el.hide();
561 tabs.push({name: relatedName, region: regionName});
566 this.app["linkedObjs" + (index+1)].empty();
571 this.tabClick('#xos-nav-detail', 'detail');
574 onFormDataInvalid: function(errors) {
576 var markErrors = function(value, key) {
577 console.log("name='" + key + "'");
578 var $inputElement = self.$el.find("[name='" + key + "']");
579 var $inputContainer = $inputElement.parent();
580 //$inputContainer.find(".help-inline").remove();
581 var $errorEl = $("<span>", {class: "help-inline error", text: value});
582 $inputContainer.append($errorEl).addClass("error");
584 _.each(errors, markErrors);
587 templateHelpers: function() { return { modelName: this.model.modelName,
588 collectionName: this.model.collectionName,
589 addFields: this.model.addFields,
590 listFields: this.model.listFields,
591 detailFields: this.model.detailFields,
592 foreignFields: this.model.foreignFields,
593 detailLinkFields: this.model.detailLinkFields,
594 inputType: this.model.inputType,
599 XOSDetailView_sliver = XOSDetailView.extend( {
600 events: $.extend(XOSDetailView.events,
601 {"change #field_deploymentNetwork": "onDeploymentNetworkChange"}
605 // Note that this causes the selects to be updated a second time. The
606 // first time was when the template was originally invoked, and the
607 // selects will all have the full unfiltered set of candidates. Then
608 // onShow will fire, and we'll update them with the filtered values.
609 this.onDeploymentNetworkChange();
612 onDeploymentNetworkChange: function(e) {
613 var deploymentID = this.$el.find("#field_deploymentNetwork").val();
615 console.log("onDeploymentNetworkChange");
616 console.log(deploymentID);
618 filterFunc = function(model) { return (model.attributes.deployment==deploymentID); }
619 newSelect = idToSelect("node",
620 this.model.attributes.node,
621 this.model.foreignFields["node"],
625 this.$el.find("#field_node").html(newSelect);
627 filterFunc = function(model) { for (index in model.attributes.deployments) {
628 item=model.attributes.deployments[index];
629 if (item.toString()==deploymentID.toString()) return true;
633 newSelect = idToSelect("flavor",
634 this.model.attributes.flavor,
635 this.model.foreignFields["flavor"],
639 this.$el.find("#field_flavor").html(newSelect);
641 filterFunc = function(model) { for (index in xos.imageDeployments.models) {
642 imageDeployment = xos.imageDeployments.models[index];
643 if ((imageDeployment.attributes.deployment == deploymentID) && (imageDeployment.attributes.image == model.id)) {
649 newSelect = idToSelect("image",
650 this.model.attributes.image,
651 this.model.foreignFields["image"],
655 this.$el.find("#field_image").html(newSelect);
660 This is for items that will be displayed as table rows.
662 app - MarionetteApplication
663 template - template (See XOSHelper.html)
666 XOSItemView = Marionette.ItemView.extend({
668 className: 'test-tablerow',
670 templateHelpers: function() { return { modelName: this.model.modelName,
671 collectionName: this.model.collectionName,
672 listFields: this.model.listFields,
673 addFields: this.model.addFields,
674 detailFields: this.model.detailFields,
675 foreignFields: this.model.foreignFields,
676 detailLinkFields: this.model.detailLinkFields,
677 inputType: this.model.inputType,
684 app - MarionetteApplication
685 childView - class of ItemView, probably an XOSItemView
686 template - template (see xosHelper.html)
687 collection - collection that holds these objects
688 title - title to display in template
691 XOSListView = FilteredCompositeView.extend({
692 childViewContainer: 'tbody',
695 events: {"click button.btn-xos-add": "addClicked",
696 "click button.btn-xos-refresh": "refreshClicked",
699 _fetchStateChange: function() {
700 if (this.collection.fetching) {
701 $("#xos-list-title-spinner").show();
703 $("#xos-list-title-spinner").hide();
707 addClicked: function(e) {
709 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
712 refreshClicked: function(e) {
714 this.collection.refresh(refreshRelated=true);
717 initialize: function() {
718 this.listenTo(this.collection, 'change', this._renderChildren)
719 this.listenTo(this.collection, 'sort', function() { console.log("sort"); })
720 this.listenTo(this.collection, 'add', function() { console.log("add"); })
721 this.listenTo(this.collection, 'fetchStateChange', this._fetchStateChange);
723 // Because many of the templates use idToName(), we need to
724 // listen to the collections that hold the names for the ids
725 // that we want to display.
726 for (i in this.collection.foreignCollections) {
727 foreignName = this.collection.foreignCollections[i];
728 if (xos[foreignName] == undefined) {
729 console.log("Failed to find xos class " + foreignName);
731 this.listenTo(xos[foreignName], 'change', this._renderChildren);
732 this.listenTo(xos[foreignName], 'sort', this._renderChildren);
736 getAddChildHash: function() {
737 if (this.parentModel) {
738 parentFieldName = this.parentModel.relatedCollections[this.collection.collectionName];
739 parentFieldName = parentFieldName || "unknown";
741 /*parentFieldName = "unknown";
743 for (fieldName in this.collection.foreignFields) {
744 cname = this.collection.foreignFields[fieldName];
745 if (cname = this.collection.collectionName) {
746 parentFieldName = fieldName;
749 return "#addChild" + firstCharUpper(this.collection.modelName) + "/" + this.parentModel.modelName + "/" + parentFieldName + "/" + this.parentModel.id; // modelName, fieldName, id
755 templateHelpers: function() {
756 return { title: this.title,
757 addChildHash: this.getAddChildHash(),
758 foreignFields: this.collection.foreignFields,
759 listFields: this.collection.listFields,
760 detailLinkFields: this.collection.detailLinkFields, };
764 XOSDataTableView = Marionette.View.extend( {
765 el: '<div style="overflow: hidden">' +
766 '<h3 class="xos-list-title title_placeholder"></h3>' +
767 '<div class="header_placeholder"></div>' +
769 '<div class="footer_placeholder"></div>' +
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);
797 initialize: function() {
798 $(this.el).find(".footer_placeholder").html( xosListFooterTemplate({addChildHash: this.getAddChildHash()}) );
799 $(this.el).find(".header_placeholder").html( xosListHeaderTemplate() );
801 this.listenTo(this.collection, 'fetchStateChange', this._fetchStateChange);
807 view.columnsByIndex = [];
808 view.columnsByFieldName = {};
809 _.each(this.collection.listFields, function(fieldName) {
811 mSearchText = undefined;
812 sTitle = fieldNameToHumanReadable(fieldName);
814 if (fieldName=="backend_status") {
815 mRender = function(x,y,z) { return xosBackendStatusIconTemplate(z); };
818 } else if (fieldName in view.collection.foreignFields) {
819 var foreignCollection = view.collection.foreignFields[fieldName];
820 mSearchText = function(x) { return idToName(x, foreignCollection, "humanReadableName"); };
822 if ($.inArray(fieldName, view.collection.detailLinkFields)>=0) {
823 var collectionName = view.collection.collectionName;
824 mRender = function(x,y,z) { return '<a href="#' + collectionName + '/' + z.id + '">' + x + '</a>'; };
826 thisColumn = {sTitle: sTitle, bSortable: bSortable, mData: fieldName, mRender: mRender, mSearchText: mSearchText};
827 view.columnsByIndex.push( thisColumn );
828 view.columnsByFieldName[fieldName] = thisColumn;
831 deleteColumn = {sTitle: "", bSortable: false, mRender: function(x,y,z) { return xosDeleteButtonTemplate({modelName: view.collection.modelName, id: z.id}); }, mData: function() { return "delete"; }};
832 view.columnsByIndex.push(deleteColumn);
833 view.columnsByFieldName["delete"] = deleteColumn;
835 oTable = $(this.el).find("table").dataTable( {
839 "aoColumns": view.columnsByIndex,
841 fnServerData: function(sSource, aoData, fnCallback, settings) {
842 var compareColumns = function(sortCols, sortDirs, a, b) {
845 result = (a==b) ? 0 : ((a<b) ? -1 : 1);
846 if (sortDirs[0] == "desc") {
852 var searchMatch = function(row, sSearch) {
853 for (fieldName in row) {
854 if (fieldName in view.columnsByFieldName) {
856 value = row[fieldName].toString();
860 if (value.indexOf(sSearch) >= 0) {
868 //console.log(aoData);
870 // function used to populate the DataTable with the current
\r
871 // content of the collection
\r
872 var populateTable = function()
\r
874 console.log("populatetable!");
\r
876 // clear out old row views
\r
881 iDisplayLength = 1000;
\r
884 _.each(aoData, function(param) {
\r
885 if (param.name == "sSortDir_0") {
\r
886 sortDirs = [param.value];
\r
887 } else if (param.name == "iSortCol_0") {
\r
888 sortCols = [view.columnsByIndex[param.value].mData];
\r
889 } else if (param.name == "iDisplayStart") {
\r
890 iDisplayStart = param.value;
\r
891 } else if (param.name == "iDisplayLength") {
\r
892 iDisplayLength = param.value;
\r
893 } else if (param.name == "sSearch") {
\r
894 sSearch = param.value;
\r
898 aaData = view.collection.toJSON();
\r
900 // apply backbone filtering on the models
\r
902 aaData = aaData.filter( function(row) { model = {}; model.attributes = row; return view.filter(model); } );
\r
905 var totalSize = aaData.length;
\r
907 // turn the ForeignKey fields into human readable things
\r
908 for (rowIndex in aaData) {
\r
909 row = aaData[rowIndex];
\r
910 for (fieldName in row) {
\r
911 if (fieldName in view.columnsByFieldName) {
\r
912 mSearchText = view.columnsByFieldName[fieldName].mSearchText;
\r
914 row[fieldName] = mSearchText(row[fieldName]);
\r
920 // apply datatables search
\r
922 aaData = aaData.filter( function(row) { return searchMatch(row, sSearch); });
\r
925 var filteredSize = aaData.length;
\r
927 // apply datatables sort
\r
928 aaData.sort(function(a,b) { return compareColumns(sortCols, sortDirs, a, b); });
\r
930 // slice it for pagination
\r
931 aaData = aaData.slice(iDisplayStart, iDisplayStart+iDisplayLength);
\r
933 return fnCallback({iTotalRecords: totalSize,
\r
934 iTotalDisplayRecords: filteredSize,
\r
938 aoData.shift(); // ignore sEcho
941 view.listenTo(view.collection, 'change', populateTable);
942 view.listenTo(view.collection, 'add', populateTable);
943 view.listenTo(view.collection, 'remove', populateTable);
950 getAddChildHash: function() {
951 if (this.parentModel) {
952 parentFieldName = this.parentModel.relatedCollections[this.collection.collectionName];
953 parentFieldName = parentFieldName || "unknown";
955 /*parentFieldName = "unknown";
957 for (fieldName in this.collection.foreignFields) {
958 cname = this.collection.foreignFields[fieldName];
959 if (cname = this.collection.collectionName) {
960 parentFieldName = fieldName;
963 return "#addChild" + firstCharUpper(this.collection.modelName) + "/" + this.parentModel.modelName + "/" + parentFieldName + "/" + this.parentModel.id; // modelName, fieldName, id
971 idToName = function(id, collectionName, fieldName) {
972 return xos.idToName(id, collectionName, fieldName);
975 /* Constructs lists of <option> html blocks for items in a collection.
977 selectedId = the id of an object that should be selected, if any
978 collectionName = name of collection
979 fieldName = name of field within models of collection that will be displayed
982 idToOptions = function(selectedId, collectionName, fieldName, filterFunc) {
984 for (index in xos[collectionName].models) {
985 linkedObject = xos[collectionName].models[index];
986 linkedId = linkedObject["id"];
987 linkedName = linkedObject.attributes[fieldName];
988 if (linkedId == selectedId) {
989 selected = " selected";
993 if ((filterFunc) && (!filterFunc(linkedObject))) {
996 result = result + '<option value="' + linkedId + '"' + selected + '>' + linkedName + '</option>';
1001 /* Constructs an html <select> and the <option>s to go with it.
1003 variable = variable name to return to form
1004 selectedId = the id of an object that should be selected, if any
1005 collectionName = name of collection
1006 fieldName = name of field within models of collection that will be displayed
1009 idToSelect = function(variable, selectedId, collectionName, fieldName, readOnly, filterFunc) {
1011 readOnly = " readonly";
1015 result = '<select name="' + variable + '" id="field_' + variable + '"' + readOnly + '>' +
1016 idToOptions(selectedId, collectionName, fieldName, filterFunc) +