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);
51 Backbone.Syphon.InputReaders.register('select', function(el) {
52 // Modify syphon so that if a select has "syphonall" in the class, then
53 // the value of every option will be returned, regardless of whether of
54 // not it is selected.
55 if (el.hasClass("syphonall")) {
57 _.each(el.find("option"), function(option) {
58 result.push($(option).val());
65 XOSApplication = Marionette.Application.extend({
66 detailBoxId: "#detailBox",
67 errorBoxId: "#errorBox",
68 errorCloseButtonId: "#close-error-box",
69 successBoxId: "#successBox",
70 successCloseButtonId: "#close-success-box",
71 errorTemplate: "#xos-error-template",
72 successTemplate: "#xos-success-template",
75 confirmDialog: function(view, event, callback) {
76 $("#xos-confirm-dialog").dialog({
80 "Confirm" : function() {
81 $(this).dialog("close");
89 "Cancel" : function() {
90 $(this).dialog("close");
94 $("#xos-confirm-dialog").dialog("open");
97 popupErrorDialog: function(responseText) {
99 parsed_error=$.parseJSON(responseText);
103 parsed_error=undefined;
104 width=640; // django stacktraces like wide width
107 $("#xos-error-dialog").html(templateFromId("#xos-error-response")(parsed_error));
109 $("#xos-error-dialog").html(templateFromId("#xos-error-rawresponse")({responseText: responseText}))
112 $("#xos-error-dialog").dialog({
116 Ok: function() { $(this).dialog("close"); }
121 hideLinkedItems: function(result) {
124 this["linkedObjs" + (index+1)].empty();
129 hideTabs: function() { $("#tabs").hide(); },
130 showTabs: function() { $("#tabs").show(); },
132 createListHandler: function(listViewName, collection_name, regionName, title) {
135 listView = new app[listViewName];
136 app[regionName].show(listView);
137 app.hideLinkedItems();
138 $("#contentTitle").html(templateFromId("#xos-title-list")({"title": title}));
142 listButtons = new XOSListButtonView({linkedView: listView});
143 app["rightButtonPanel"].show(listButtons);
147 createAddHandler: function(detailName, collection_name, regionName, title) {
150 console.log("addHandler");
152 app.hideLinkedItems();
155 model = new xos[collection_name].model();
156 detailViewClass = app[detailName];
157 detailView = new detailViewClass({model: model, collection:xos[collection_name]});
158 app[regionName].show(detailView);
160 detailButtons = new XOSDetailButtonView({linkedView: detailView});
161 app["rightButtonPanel"].show(detailButtons);
165 createAddChildHandler: function(addChildName, collection_name) {
167 return function(parent_modelName, parent_fieldName, parent_id) {
168 app.Router.showPreviousURL();
169 model = new xos[collection_name].model();
170 model.attributes[parent_fieldName] = parent_id;
171 model.readOnlyFields.push(parent_fieldName);
172 detailViewClass = app[addChildName];
173 var detailView = new detailViewClass({model: model, collection:xos[collection_name]});
174 detailView.dialog = $("xos-addchild-dialog");
175 app["addChildDetail"].show(detailView);
176 $("#xos-addchild-dialog").dialog({
181 "Save" : function() {
182 var addDialog = this;
183 detailView.synchronous = true;
184 detailView.afterSave = function() { console.log("addChild afterSave"); $(addDialog).dialog("close"); }
187 //$(this).dialog("close");
189 "Cancel" : function() {
190 $(this).dialog("close");
194 $("#xos-addchild-dialog").dialog("open");
198 createDeleteHandler: function(collection_name) {
200 return function(model_id) {
201 console.log("deleteCalled");
202 collection = xos[collection_name];
203 model = collection.get(model_id);
204 assert(model!=undefined, "failed to get model " + model_id + " from collection " + collection_name);
205 app.Router.showPreviousURL();
206 app.deleteDialog(model);
210 createDetailHandler: function(detailName, collection_name, regionName, title) {
212 showModelId = function(model_id) {
213 $("#contentTitle").html(templateFromId("#xos-title-detail")({"title": title}));
215 collection = xos[collection_name];
216 model = collection.get(model_id);
217 if (model == undefined) {
218 app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name}));
220 detailViewClass = app[detailName];
221 detailView = new detailViewClass({model: model});
222 app[regionName].show(detailView);
223 detailView.showLinkedItems();
225 detailButtons = new XOSDetailButtonView({linkedView: detailView});
226 app["rightButtonPanel"].show(detailButtons);
232 /* error handling callbacks */
234 hideError: function() {
235 if (this.logWindowId) {
237 $(this.errorBoxId).hide();
238 $(this.successBoxId).hide();
242 showSuccess: function(result) {
243 result["statusclass"] = "success";
244 if (this.logTableId) {
245 this.appendLogWindow(result);
247 $(this.successBoxId).show();
248 $(this.successBoxId).html(_.template($(this.successTemplate).html())(result));
250 $(this.successCloseButtonId).unbind().bind('click', function() {
251 $(that.successBoxId).hide();
256 showError: function(result) {
257 result["statusclass"] = "failure";
258 if (this.logTableId) {
259 this.appendLogWindow(result);
260 this.popupErrorDialog(result.responseText);
262 // this is really old stuff
263 $(this.errorBoxId).show();
264 $(this.errorBoxId).html(_.template($(this.errorTemplate).html())(result));
266 $(this.errorCloseButtonId).unbind().bind('click', function() {
267 $(that.errorBoxId).hide();
272 showInformational: function(result) {
273 result["statusclass"] = "inprog";
274 if (this.logTableId) {
275 return this.appendLogWindow(result);
281 appendLogWindow: function(result) {
282 // compute a new logMessageId for this log message
283 logMessageId = "logMessage" + this.logMessageCount;
284 this.logMessageCount = this.logMessageCount + 1;
285 result["logMessageId"] = logMessageId;
287 logMessageTemplate=$("#xos-log-template").html();
288 assert(logMessageTemplate != undefined, "logMessageTemplate is undefined");
289 newRow = _.template(logMessageTemplate, result);
290 assert(newRow != undefined, "newRow is undefined");
292 if (result["infoMsgId"] != undefined) {
293 // We were passed the logMessageId of an informational message,
294 // and the caller wants us to replace that message with our own.
295 // i.e. replace an informational message with a success or an error.
296 $("#"+result["infoMsgId"]).replaceWith(newRow);
298 // Create a brand new log message rather than replacing one.
299 logTableBody = $(this.logTableId + " tbody");
300 logTableBody.prepend(newRow);
303 if (this.statusMsgId) {
304 $(this.statusMsgId).html( templateFromId("#xos-status-template")(result) );
307 limitTableRows(this.logTableId, 5);
312 saveError: function(model, result, xhr, infoMsgId) {
313 console.log("saveError");
314 result["what"] = "save " + model.modelName + " " + model.attributes.humanReadableName;
315 result["infoMsgId"] = infoMsgId;
316 this.showError(result);
319 saveSuccess: function(model, result, xhr, infoMsgId, addToCollection) {
320 console.log("saveSuccess");
321 if (model.addToCollection) {
322 console.log("addToCollection");
323 console.log(model.addToCollection);
324 model.addToCollection.add(model);
325 model.addToCollection.sort();
326 model.addToCollection = undefined;
328 result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};
329 result["what"] = "save " + model.modelName + " " + model.attributes.humanReadableName;
330 result["infoMsgId"] = infoMsgId;
331 this.showSuccess(result);
334 destroyError: function(model, result, xhr, infoMsgId) {
335 result["what"] = "destroy " + model.modelName + " " + model.attributes.humanReadableName;
336 result["infoMsgId"] = infoMsgId;
337 this.showError(result);
340 destroySuccess: function(model, result, xhr, infoMsgId) {
341 result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};
342 result["what"] = "destroy " + model.modelName + " " + model.attributes.humanReadableName;
343 result["infoMsgId"] = infoMsgId;
344 this.showSuccess(result);
347 /* end error handling callbacks */
349 destroyModel: function(model) {
350 //console.log("destroyModel"); console.log(model);
352 var infoMsgId = this.showInformational( {what: "destroy " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
354 model.destroy({error: function(model, result, xhr) { that.destroyError(model,result,xhr,infoMsgId);},
355 success: function(model, result, xhr) { that.destroySuccess(model,result,xhr,infoMsgId);}});
358 deleteDialog: function(model, afterDelete) {
360 assert(model!=undefined, "deleteDialog's model is undefined");
361 //console.log("deleteDialog"); console.log(model);
362 this.confirmDialog(null, null, function() {
363 //console.log("deleteConfirm"); console.log(model);
364 modelName = model.modelName;
365 that.destroyModel(model);
366 if (afterDelete=="list") {
367 that.navigate("list", modelName);
373 XOSButtonView = Marionette.ItemView.extend({
374 events: {"click button.btn-xos-save-continue": "submitContinueClicked",
375 "click button.btn-xos-save-leave": "submitLeaveClicked",
376 "click button.btn-xos-save-another": "submitAddAnotherClicked",
377 "click button.btn-xos-delete": "deleteClicked",
378 "click button.btn-xos-add": "addClicked",
379 "click button.btn-xos-refresh": "refreshClicked",
382 submitLeaveClicked: function(e) {
383 this.options.linkedView.submitLeaveClicked.call(this.options.linkedView, e);
386 submitContinueClicked: function(e) {
387 this.options.linkedView.submitContinueClicked.call(this.options.linkedView, e);
390 submitAddAnotherClicked: function(e) {
391 this.options.linkedView.submitAddAnotherClicked.call(this.options.linkedView, e);
394 submitDeleteClicked: function(e) {
395 this.options.linkedView.submitDeleteClicked.call(this.options.linkedView, e);
398 addClicked: function(e) {
399 this.options.linkedView.addClicked.call(this.options.linkedView, e);
402 refreshClicked: function(e) {
403 this.options.linkedView.refreshClicked.call(this.options.linkedView, e);
407 XOSDetailButtonView = XOSButtonView.extend({ template: "#xos-savebuttons-template" });
408 XOSListButtonView = XOSButtonView.extend({ template: "#xos-listbuttons-template" });
412 app - MarionetteApplication
413 template - template (See XOSHelper.html)
416 XOSDetailView = Marionette.ItemView.extend({
419 viewInitializers: [],
421 events: {"click button.btn-xos-save-continue": "submitContinueClicked",
422 "click button.btn-xos-save-leave": "submitLeaveClicked",
423 "click button.btn-xos-save-another": "submitAddAnotherClicked",
424 "click button.btn-xos-delete": "deleteClicked",
425 "change input": "inputChanged"},
427 /* inputChanged is watching the onChange events of the input controls. We
428 do this to track when this view is 'dirty', so we can throw up a warning
429 if the user tries to change his slices without saving first.
432 initialize: function() {
433 this.on("saveSuccess", this.onAfterSave);
434 this.synchronous = false;
438 _.each(this.viewInitializers, function(initializer) {
443 afterSave: function(e) {
446 onAfterSave: function(e) {
450 inputChanged: function(e) {
454 submitContinueClicked: function(e) {
455 console.log("saveContinue");
457 this.afterSave = function() { };
461 submitLeaveClicked: function(e) {
462 console.log("saveLeave");
465 this.afterSave = function() {
466 that.app.navigate("list", that.model.modelName);
471 submitAddAnotherClicked: function(e) {
472 console.log("saveAnother");
476 this.afterSave = function() {
477 console.log("addAnother afterSave");
478 that.app.navigate("add", that.model.modelName);
484 this.app.hideError();
485 var data = Backbone.Syphon.serialize(this);
487 var isNew = !this.model.id;
491 this.$el.find(".help-inline").remove();
493 /* although model.validate() is called automatically by
494 model.save, we call it ourselves, so we can throw up our
495 validation error before creating the infoMsg in the log
497 errors = this.model.xosValidate(data);
499 this.onFormDataInvalid(errors);
504 this.model.attributes.humanReadableName = "new " + model.modelName;
505 this.model.addToCollection = this.collection;
507 this.model.addToCollection = undefined;
510 var infoMsgId = this.app.showInformational( {what: "save " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
512 this.model.save(data, {error: function(model, result, xhr) { that.app.saveError(model,result,xhr,infoMsgId);},
513 success: function(model, result, xhr) { that.app.saveSuccess(model,result,xhr,infoMsgId);
514 if (that.synchronous) {
515 that.trigger("saveSuccess");
520 if (!this.synchronous) {
525 deleteClicked: function(e) {
527 this.app.deleteDialog(this.model, "list");
530 tabClick: function(tabId, regionName) {
531 region = this.app[regionName];
532 if (this.currentTabRegion != undefined) {
533 this.currentTabRegion.$el.hide();
535 if (this.currentTabId != undefined) {
536 $(this.currentTabId).removeClass('active');
538 this.currentTabRegion = region;
539 this.currentTabRegion.$el.show();
541 this.currentTabId = tabId;
542 $(tabId).addClass('active');
545 showTabs: function(tabs) {
546 template = templateFromId("#xos-tabs-template", {tabs: tabs});
547 $("#tabs").html(template(tabs));
550 _.each(tabs, function(tab) {
551 var regionName = tab["region"];
552 var tabId = '#xos-nav-'+regionName;
553 $(tabId).bind('click', function() { that.tabClick(tabId, regionName); });
559 showLinkedItems: function() {
562 tabs.push({name: "details", region: "detail"});
564 makeFilter = function(relatedField, relatedId) {
565 return function(model) { return model.attributes[relatedField] == relatedId; }
569 for (relatedName in this.model.collection.relatedCollections) {
570 var relatedField = this.model.collection.relatedCollections[relatedName];
571 var relatedId = this.model.id;
572 regionName = "linkedObjs" + (index+1);
574 relatedListViewClassName = relatedName + "ListView";
575 assert(this.app[relatedListViewClassName] != undefined, relatedListViewClassName + " not found");
576 relatedListViewClass = this.app[relatedListViewClassName].extend({collection: xos[relatedName],
577 filter: makeFilter(relatedField, relatedId),
578 parentModel: this.model});
579 this.app[regionName].show(new relatedListViewClass());
580 if (this.app.hideTabsByDefault) {
581 this.app[regionName].$el.hide();
583 tabs.push({name: relatedName, region: regionName});
588 this.app["linkedObjs" + (index+1)].empty();
593 this.tabClick('#xos-nav-detail', 'detail');
596 onFormDataInvalid: function(errors) {
598 var markErrors = function(value, key) {
599 var $inputElement = self.$el.find("[name='" + key + "']");
600 var $inputContainer = $inputElement.parent();
601 //$inputContainer.find(".help-inline").remove();
602 var $errorEl = $("<span>", {class: "help-inline error", text: value});
603 $inputContainer.append($errorEl).addClass("error");
605 _.each(errors, markErrors);
608 templateHelpers: function() { return { modelName: this.model.modelName,
609 collectionName: this.model.collectionName,
610 addFields: this.model.addFields,
611 listFields: this.model.listFields,
612 detailFields: this.model.detailFields,
613 foreignFields: this.model.foreignFields,
614 detailLinkFields: this.model.detailLinkFields,
615 inputType: this.model.inputType,
621 XOSDetailView_sliver = XOSDetailView.extend( {
622 events: $.extend(XOSDetailView.events,
623 {"change #field_deploymentNetwork": "onDeploymentNetworkChange"}
627 // Note that this causes the selects to be updated a second time. The
628 // first time was when the template was originally invoked, and the
629 // selects will all have the full unfiltered set of candidates. Then
630 // onShow will fire, and we'll update them with the filtered values.
631 this.onDeploymentNetworkChange();
634 onDeploymentNetworkChange: function(e) {
635 var deploymentID = this.$el.find("#field_deploymentNetwork").val();
637 console.log("onDeploymentNetworkChange");
638 console.log(deploymentID);
640 filterFunc = function(model) { return (model.attributes.deployment==deploymentID); }
641 newSelect = idToSelect("node",
642 this.model.attributes.node,
643 this.model.foreignFields["node"],
647 this.$el.find("#field_node").html(newSelect);
649 filterFunc = function(model) { for (index in model.attributes.deployments) {
650 item=model.attributes.deployments[index];
651 if (item.toString()==deploymentID.toString()) return true;
655 newSelect = idToSelect("flavor",
656 this.model.attributes.flavor,
657 this.model.foreignFields["flavor"],
661 this.$el.find("#field_flavor").html(newSelect);
663 filterFunc = function(model) { for (index in xos.imageDeployments.models) {
664 imageDeployment = xos.imageDeployments.models[index];
665 if ((imageDeployment.attributes.deployment == deploymentID) && (imageDeployment.attributes.image == model.id)) {
671 newSelect = idToSelect("image",
672 this.model.attributes.image,
673 this.model.foreignFields["image"],
677 this.$el.find("#field_image").html(newSelect);
682 This is for items that will be displayed as table rows.
684 app - MarionetteApplication
685 template - template (See XOSHelper.html)
688 XOSItemView = Marionette.ItemView.extend({
690 className: 'test-tablerow',
692 templateHelpers: function() { return { modelName: this.model.modelName,
693 collectionName: this.model.collectionName,
694 listFields: this.model.listFields,
695 addFields: this.model.addFields,
696 detailFields: this.model.detailFields,
697 foreignFields: this.model.foreignFields,
698 detailLinkFields: this.model.detailLinkFields,
699 inputType: this.model.inputType,
706 app - MarionetteApplication
707 childView - class of ItemView, probably an XOSItemView
708 template - template (see xosHelper.html)
709 collection - collection that holds these objects
710 title - title to display in template
713 XOSListView = FilteredCompositeView.extend({
714 childViewContainer: 'tbody',
717 events: {"click button.btn-xos-add": "addClicked",
718 "click button.btn-xos-refresh": "refreshClicked",
721 _fetchStateChange: function() {
722 if (this.collection.fetching) {
723 $("#xos-list-title-spinner").show();
725 $("#xos-list-title-spinner").hide();
729 addClicked: function(e) {
731 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
734 refreshClicked: function(e) {
736 this.collection.refresh(refreshRelated=true);
739 initialize: function() {
740 this.listenTo(this.collection, 'change', this._renderChildren)
741 this.listenTo(this.collection, 'sort', function() { console.log("sort"); })
742 this.listenTo(this.collection, 'add', function() { console.log("add"); })
743 this.listenTo(this.collection, 'fetchStateChange', this._fetchStateChange);
745 // Because many of the templates use idToName(), we need to
746 // listen to the collections that hold the names for the ids
747 // that we want to display.
748 for (i in this.collection.foreignCollections) {
749 foreignName = this.collection.foreignCollections[i];
750 if (xos[foreignName] == undefined) {
751 console.log("Failed to find xos class " + foreignName);
753 this.listenTo(xos[foreignName], 'change', this._renderChildren);
754 this.listenTo(xos[foreignName], 'sort', this._renderChildren);
758 getAddChildHash: function() {
759 if (this.parentModel) {
760 parentFieldName = this.parentModel.relatedCollections[this.collection.collectionName];
761 parentFieldName = parentFieldName || "unknown";
763 /*parentFieldName = "unknown";
765 for (fieldName in this.collection.foreignFields) {
766 cname = this.collection.foreignFields[fieldName];
767 if (cname = this.collection.collectionName) {
768 parentFieldName = fieldName;
771 return "#addChild" + firstCharUpper(this.collection.modelName) + "/" + this.parentModel.modelName + "/" + parentFieldName + "/" + this.parentModel.id; // modelName, fieldName, id
777 templateHelpers: function() {
778 return { title: this.title,
779 addChildHash: this.getAddChildHash(),
780 foreignFields: this.collection.foreignFields,
781 listFields: this.collection.listFields,
782 detailLinkFields: this.collection.detailLinkFields, };
786 XOSDataTableView = Marionette.View.extend( {
787 el: '<div style="overflow: hidden">' +
788 '<h3 class="xos-list-title title_placeholder"></h3>' +
789 '<div class="header_placeholder"></div>' +
791 '<div class="footer_placeholder"></div>' +
796 events: {"click button.btn-xos-add": "addClicked",
797 "click button.btn-xos-refresh": "refreshClicked",
800 _fetchStateChange: function() {
801 if (this.collection.fetching) {
802 $("#xos-list-title-spinner").show();
804 $("#xos-list-title-spinner").hide();
808 addClicked: function(e) {
810 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
813 refreshClicked: function(e) {
815 this.collection.refresh(refreshRelated=true);
819 initialize: function() {
820 $(this.el).find(".footer_placeholder").html( xosListFooterTemplate({addChildHash: this.getAddChildHash()}) );
821 $(this.el).find(".header_placeholder").html( xosListHeaderTemplate() );
823 this.listenTo(this.collection, 'fetchStateChange', this._fetchStateChange);
829 view.columnsByIndex = [];
830 view.columnsByFieldName = {};
831 _.each(this.collection.listFields, function(fieldName) {
833 mSearchText = undefined;
834 sTitle = fieldNameToHumanReadable(fieldName);
836 if (fieldName=="backend_status") {
837 mRender = function(x,y,z) { return xosBackendStatusIconTemplate(z); };
840 } else if (fieldName in view.collection.foreignFields) {
841 var foreignCollection = view.collection.foreignFields[fieldName];
842 mSearchText = function(x) { return idToName(x, foreignCollection, "humanReadableName"); };
844 if ($.inArray(fieldName, view.collection.detailLinkFields)>=0) {
845 var collectionName = view.collection.collectionName;
846 mRender = function(x,y,z) { return '<a href="#' + collectionName + '/' + z.id + '">' + x + '</a>'; };
848 thisColumn = {sTitle: sTitle, bSortable: bSortable, mData: fieldName, mRender: mRender, mSearchText: mSearchText};
849 view.columnsByIndex.push( thisColumn );
850 view.columnsByFieldName[fieldName] = thisColumn;
853 deleteColumn = {sTitle: "", bSortable: false, mRender: function(x,y,z) { return xosDeleteButtonTemplate({modelName: view.collection.modelName, id: z.id}); }, mData: function() { return "delete"; }};
854 view.columnsByIndex.push(deleteColumn);
855 view.columnsByFieldName["delete"] = deleteColumn;
857 oTable = $(this.el).find("table").dataTable( {
861 "aoColumns": view.columnsByIndex,
863 fnServerData: function(sSource, aoData, fnCallback, settings) {
864 var compareColumns = function(sortCols, sortDirs, a, b) {
867 result = (a==b) ? 0 : ((a<b) ? -1 : 1);
868 if (sortDirs[0] == "desc") {
874 var searchMatch = function(row, sSearch) {
875 for (fieldName in row) {
876 if (fieldName in view.columnsByFieldName) {
878 value = row[fieldName].toString();
882 if (value.indexOf(sSearch) >= 0) {
890 //console.log(aoData);
892 // function used to populate the DataTable with the current
\r
893 // content of the collection
\r
894 var populateTable = function()
\r
896 console.log("populatetable!");
\r
898 // clear out old row views
\r
903 iDisplayLength = 1000;
\r
906 _.each(aoData, function(param) {
\r
907 if (param.name == "sSortDir_0") {
\r
908 sortDirs = [param.value];
\r
909 } else if (param.name == "iSortCol_0") {
\r
910 sortCols = [view.columnsByIndex[param.value].mData];
\r
911 } else if (param.name == "iDisplayStart") {
\r
912 iDisplayStart = param.value;
\r
913 } else if (param.name == "iDisplayLength") {
\r
914 iDisplayLength = param.value;
\r
915 } else if (param.name == "sSearch") {
\r
916 sSearch = param.value;
\r
920 aaData = view.collection.toJSON();
\r
922 // apply backbone filtering on the models
\r
924 aaData = aaData.filter( function(row) { model = {}; model.attributes = row; return view.filter(model); } );
\r
927 var totalSize = aaData.length;
\r
929 // turn the ForeignKey fields into human readable things
\r
930 for (rowIndex in aaData) {
\r
931 row = aaData[rowIndex];
\r
932 for (fieldName in row) {
\r
933 if (fieldName in view.columnsByFieldName) {
\r
934 mSearchText = view.columnsByFieldName[fieldName].mSearchText;
\r
936 row[fieldName] = mSearchText(row[fieldName]);
\r
942 // apply datatables search
\r
944 aaData = aaData.filter( function(row) { return searchMatch(row, sSearch); });
\r
947 var filteredSize = aaData.length;
\r
949 // apply datatables sort
\r
950 aaData.sort(function(a,b) { return compareColumns(sortCols, sortDirs, a, b); });
\r
952 // slice it for pagination
\r
953 aaData = aaData.slice(iDisplayStart, iDisplayStart+iDisplayLength);
\r
955 return fnCallback({iTotalRecords: totalSize,
\r
956 iTotalDisplayRecords: filteredSize,
\r
960 aoData.shift(); // ignore sEcho
963 view.listenTo(view.collection, 'change', populateTable);
964 view.listenTo(view.collection, 'add', populateTable);
965 view.listenTo(view.collection, 'remove', populateTable);
972 getAddChildHash: function() {
973 if (this.parentModel) {
974 parentFieldName = this.parentModel.relatedCollections[this.collection.collectionName];
975 parentFieldName = parentFieldName || "unknown";
977 /*parentFieldName = "unknown";
979 for (fieldName in this.collection.foreignFields) {
980 cname = this.collection.foreignFields[fieldName];
981 if (cname = this.collection.collectionName) {
982 parentFieldName = fieldName;
985 return "#addChild" + firstCharUpper(this.collection.modelName) + "/" + this.parentModel.modelName + "/" + parentFieldName + "/" + this.parentModel.id; // modelName, fieldName, id
993 idToName = function(id, collectionName, fieldName) {
994 return xos.idToName(id, collectionName, fieldName);
997 makeIdToName = function(collectionName, fieldName) {
998 return function(id) { return idToName(id, collectionName, fieldName); }
1001 /* Constructs lists of <option> html blocks for items in a collection.
1003 selectedId = the id of an object that should be selected, if any
1004 collectionName = name of collection
1005 fieldName = name of field within models of collection that will be displayed
1008 idToOptions = function(selectedId, collectionName, fieldName, filterFunc) {
1010 for (index in xos[collectionName].models) {
1011 linkedObject = xos[collectionName].models[index];
1012 linkedId = linkedObject["id"];
1013 linkedName = linkedObject.attributes[fieldName];
1014 if (linkedId == selectedId) {
1015 selected = " selected";
1019 if ((filterFunc) && (!filterFunc(linkedObject))) {
1022 result = result + '<option value="' + linkedId + '"' + selected + '>' + linkedName + '</option>';
1027 /* Constructs an html <select> and the <option>s to go with it.
1029 variable = variable name to return to form
1030 selectedId = the id of an object that should be selected, if any
1031 collectionName = name of collection
1032 fieldName = name of field within models of collection that will be displayed
1035 idToSelect = function(variable, selectedId, collectionName, fieldName, readOnly, filterFunc) {
1037 readOnly = " readonly";
1041 result = '<select name="' + variable + '" id="field_' + variable + '"' + readOnly + '>' +
1042 idToOptions(selectedId, collectionName, fieldName, filterFunc) +