tenant view, WIP
diff --git a/planetstack/core/xoslib/dashboards/xosTenant.html b/planetstack/core/xoslib/dashboards/xosTenant.html
new file mode 100644
index 0000000..49236d4
--- /dev/null
+++ b/planetstack/core/xoslib/dashboards/xosTenant.html
@@ -0,0 +1,72 @@
+<script src="{{ STATIC_URL }}/js/vendor/underscore-min.js"></script>
+<script src="{{ STATIC_URL }}/js/vendor/backbone.js"></script>
+<script src="{{ STATIC_URL }}/js/vendor/backbone.syphon.js"></script>
+<script src="{{ STATIC_URL }}/js/vendor/backbone.wreqr.js"></script>
+<script src="{{ STATIC_URL }}/js/vendor/backbone.babysitter.js"></script>
+<script src="{{ STATIC_URL }}/js/vendor/backbone.marionette.js"></script>
+
+<link rel="stylesheet" href="//code.jquery.com/ui/1.11.2/themes/smoothness/jquery-ui.css">
+<link rel="stylesheet" type="text/css" href="{% static 'css/xosAdminDashboard.css' %}" media="all" >
+<link rel="stylesheet" type="text/css" href="{% static 'css/xosAdminSite.css' %}" media="all" >
+
+<script src="{{ STATIC_URL }}/js/xoslib/xos-util.js"></script>
+<script src="{{ STATIC_URL }}/js/xoslib/xos-defaults.js"></script>
+<script src="{{ STATIC_URL }}/js/xoslib/xos-validators.js"></script>
+<script src="{{ STATIC_URL }}/js/xoslib/xos-backbone.js"></script>
+<script src="{{ STATIC_URL }}/js/xoslib/xosHelper.js"></script>
+<script src="{{ STATIC_URL }}/js/xosTenant.js"></script>
+
+<script type="text/template" id="xos-tenant-buttons-template">
+ <div class="box save-box">
+ <button class="btn btn-high btn-tenant-create">Create New Slice</button>
+ <button class="btn btn-high btn-tenant-delete">Delete Slice</button>
+ <button class="btn btn-high btn-tenant-add-user">Add User</button>
+ <button class="btn btn-high btn-tenant-save">Save</button>
+ </div>
+</script>
+
+<script type="text/template" id="xos-log-template">
+ <tr id="<%= logMessageId %>" class="xos-log xos-<%= statusclass %>">
+ <td><%= what %><br>
+ <%= status %> <%= statusText %>
+ </td>
+ </tr>
+</script>
+
+<div id="xos-confirm-dialog" title="Confirmation Required">
+ Are you sure about this?
+</div>
+
+<div id="xos-error-dialog" title="Error Message">
+</div>
+
+<div id="contentPanel">
+<div id="contentTitle">
+</div>
+<div id="contentButtonPanel">
+
+<div id="rightButtonPanel"></div>
+
+<div class="box" id="logPanel">
+<table id="logTable">
+<tbody>
+</tbody>
+</table> <!-- end logTable -->
+</div> <!-- end logPanel -->
+</div> <!-- end contentButtonPanel -->
+
+<div id="contentInner">
+
+<div id="tenantSliceSelector">
+</div>
+<div id="tenantSummary">
+</div>
+<div id="tenantSiteList">
+</div>
+<div id="tenantButtons">
+</div>
+
+</div> <!-- end contentInner -->
+</div> <!-- end contentPanel -->
+
+{% include 'xosAdmin.html' %}
diff --git a/planetstack/core/xoslib/static/js/picker.js b/planetstack/core/xoslib/static/js/picker.js
index 0302cf4..075bdc5 100644
--- a/planetstack/core/xoslib/static/js/picker.js
+++ b/planetstack/core/xoslib/static/js/picker.js
@@ -48,3 +48,7 @@
});
});
};
+
+function init_spinner(selector, value) {
+ var spinner = $(selector).spinner( "value", value);
+};
diff --git a/planetstack/core/xoslib/static/js/xosTenant.js b/planetstack/core/xoslib/static/js/xosTenant.js
new file mode 100644
index 0000000..5095505
--- /dev/null
+++ b/planetstack/core/xoslib/static/js/xosTenant.js
@@ -0,0 +1,218 @@
+XOSTenantSite = XOSModel.extend( {
+ listFields: ["name", "allocated"],
+ modelName: "tenantSite",
+ collectionName: "tenantSites"
+});
+
+XOSTenantSiteCollection = XOSCollection.extend( {
+ listFields: ["name", "allocated"],
+ modelName: "tenantSite",
+ collectionName: "tenantSites",
+
+ updateFromSlice: function(slice) {
+ var tenantSites = [];
+ var id = 0;
+ for (siteName in slice.attributes.site_allocation) {
+ allocated = slice.attributes.site_allocation[siteName];
+ tenantSites.push(new XOSTenantSite( { name: siteName, allocated: allocated, id: id} ));
+ id = id + 1;
+ }
+ for (index in xos.tenantview.models[0].attributes.blessed_site_names) {
+ siteName = xos.tenantview.models[0].attributes.blessed_site_names[index];
+ if (! (siteName in slice.attributes.site_allocation)) {
+ tenantSites.push(new XOSTenantSite( { name: siteName, allocated: 0, id: id} ));
+ id = id + 1;
+ }
+ }
+ this.set(tenantSites);
+ },
+});
+
+XOSTenantButtonView = Marionette.ItemView.extend({
+ template: "#xos-tenant-buttons-template",
+
+ events: {"click button.btn-tenant-create": "createClicked",
+ "click button.btn-tenant-delete": "deleteClicked",
+ "click button.btn-tenant-add-user": "addUserClicked",
+ "click button.btn-tenant-save": "saveClicked",
+ },
+
+ createClicked: function(e) {
+ },
+
+ deleteClicked: function(e) {
+ this.options.linkedView.deleteClicked.call(this.options.linkedView, e);
+ },
+
+ addUserClicked: function(e) {
+ },
+
+ saveClicked: function(e) {
+ this.options.linkedView.submitContinueClicked.call(this.options.linkedView, e);
+ },
+ });
+
+XOSTenantApp = new XOSApplication({
+ logTableId: "#logTable",
+ statusMsgId: "#statusMsg",
+ hideTabsByDefault: true
+});
+
+XOSTenantApp.addRegions({
+ tenantSliceSelector: "#tenantSliceSelector",
+ tenantSummary: "#tenantSummary",
+ tenantSiteList: "#tenantSiteList",
+ tenantButtons: "#tenantButtons",
+});
+
+XOSTenantApp.buildViews = function() {
+ XOSTenantApp.tenantSites = new XOSTenantSiteCollection();
+
+ tenantSummaryClass = XOSDetailView.extend({template: "#xos-detail-template",
+ app: XOSTenantApp,
+ detailFields: ["serviceClass", "image_preference", "network_ports", "mount_data_sets"]});
+
+ XOSTenantApp.tenantSummaryView = tenantSummaryClass;
+
+ tenantSiteItemClass = XOSItemView.extend({template: "#xos-listitem-template",
+ app: XOSTenantApp});
+
+ XOSTenantApp.tenantSiteItemView = tenantSiteItemClass;
+
+ tenantSiteListClass = XOSDataTableView.extend({template: "#xos-list-template",
+ app: XOSTenantApp,
+ childView: tenantSiteItemClass,
+ collection: XOSTenantApp.tenantSites,
+ title: "sites",
+ inputType: {allocated: "spinner"},
+ noDeleteColumn: true,
+ });
+
+ XOSTenantApp.tenantSiteListView = tenantSiteListClass;
+
+ XOSTenantApp.tenantSliceSelectorView = SliceSelectorView.extend( {
+ sliceChanged: function(id) {
+ //console.log("navigate to " + id);
+ XOSTenantApp.Router.navigate("slice/" + id, {trigger: true});
+ },
+ });
+
+ xos.sites.fetch();
+ xos.slicesPlus.fetch();
+ xos.tenantview.fetch();
+};
+
+make_choices = function(list_of_names, list_of_values) {
+ result = [];
+ if (!list_of_values) {
+ for (index in list_of_names) {
+ displayName = list_of_names[index];
+ result.push( [displayName, displayName] );
+ }
+ }
+ return result;
+};
+
+XOSTenantApp.viewSlice = function(model) {
+ if (!model && xos.slicesPlus.models.length > 0) {
+ model = xos.slicesPlus.models[0];
+ }
+
+ sliceSelector = new XOSTenantApp.tenantSliceSelectorView({collection: xos.slicesPlus,
+ selectedID: model.id,
+ } );
+ XOSTenantApp.sliceSelector = sliceSelector;
+ XOSTenantApp.tenantSliceSelector.show(sliceSelector);
+
+ tenantSummary = new XOSTenantApp.tenantSummaryView({model: model,
+ choices: {mount_data_sets: make_choices(xos.tenantview.models[0].attributes.public_volume_names, null),
+ image_preference: make_choices(xos.tenantview.models[0].attributes.blessed_image_names, null)},
+ });
+ XOSTenantApp.tenantSummary.show(tenantSummary);
+
+ tenantSites = new XOSTenantSiteCollection();
+ tenantSites.updateFromSlice(model);
+ XOSTenantApp.tenantSites = tenantSites;
+
+ tenantSiteList = new XOSTenantApp.tenantSiteListView({collection: tenantSites });
+ XOSTenantApp.tenantSiteList.show(tenantSiteList);
+ // on xos.slicePlus.sort, need to update xostenantapp.tenantSites
+
+ XOSTenantApp.tenantButtons.show( new XOSTenantButtonView( { app: XOSTenantApp,
+ linkedView: tenantSummary } ) );
+};
+
+XOSTenantApp.initRouter = function() {
+ router = XOSRouter;
+ var api = {};
+ var routes = {};
+
+ nav_url = "slice/:id";
+ api_command = "viewSlice";
+ api[api_command] = function(id) { XOSTenantApp.viewSlice(xos.slicesPlus.get(id)); };
+ routes[nav_url] = api_command;
+
+ nav_url = "increase/:collectionName/:id/:fieldName";
+ api_command = "increase";
+ api[api_command] = function(collectionName, id, fieldName) {
+ XOSTenantApp.Router.showPreviousURL();
+ model = XOSTenantApp[collectionName].get(id);
+ model.set(fieldName, model.get(fieldName) + 1);
+ };
+ routes[nav_url] = api_command;
+
+ nav_url = "decrease/:collectionName/:id/:fieldName";
+ api_command = "decrease";
+ api[api_command] = function(collectionName, id, fieldName) {
+ XOSTenantApp.Router.showPreviousURL();
+ model = XOSTenantApp[collectionName].get(id);
+ model.set(fieldName, Math.max(0, model.get(fieldName) - 1));
+ };
+ routes[nav_url] = api_command;
+
+ nav_url = "*path";
+ api_command = "defaultRoute";
+ api[api_command] = function() { XOSTenantApp.viewSlice(undefined); };
+ routes[nav_url] = api_command;
+
+ XOSTenantApp.Router = new router({ appRoutes: routes, controller: api });
+};
+
+XOSTenantApp.startNavigation = function() {
+ Backbone.history.start();
+ XOSTenantApp.navigationStarted = true;
+}
+
+XOSTenantApp.collectionLoadChange = function() {
+ stats = xos.getCollectionStatus();
+
+ if (!XOSTenantApp.navigationStarted) {
+ if (stats["isLoaded"] + stats["failedLoad"] >= stats["startedLoad"]) {
+ XOSTenantApp.startNavigation();
+
+ //if (xos.slicesPlus.models.length > 0) {
+ // XOSTenantApp.Router.navigate("slice/" + xos.slicesPlus.models[0].id, {trigger:true});
+ //}
+ } else {
+ $("#tenantSummary").html("<h3>Loading...</h3><div id='xos-startup-progress'></div>");
+ $("#xos-startup-progress").progressbar({value: stats["completedLoad"], max: stats["startedLoad"]});
+ }
+ }
+};
+
+XOSTenantApp.on("start", function() {
+ XOSTenantApp.buildViews();
+
+ XOSTenantApp.initRouter();
+
+ // fire it once to initially show the progress bar
+ XOSTenantApp.collectionLoadChange();
+
+ // fire it each time the collection load status is updated
+ Backbone.on("xoslib:collectionLoadChange", XOSTenantApp.collectionLoadChange);
+});
+
+$(document).ready(function(){
+ XOSTenantApp.start();
+});
+
diff --git a/planetstack/core/xoslib/static/js/xoslib/xos-backbone.js b/planetstack/core/xoslib/static/js/xoslib/xos-backbone.js
index 47ea66a..762a2b5 100644
--- a/planetstack/core/xoslib/static/js/xoslib/xos-backbone.js
+++ b/planetstack/core/xoslib/static/js/xoslib/xos-backbone.js
@@ -34,8 +34,16 @@
USERDEPLOYMENT_API = "/plstackapi/userdeployments/";
SLICEPLUS_API = "/xoslib/slicesplus/";
+ TENANTVIEW_API = "/xoslib/tenantview/"
XOSModel = Backbone.Model.extend({
+ relatedCollections: [],
+ foreignCollections: [],
+ foreignFields: {},
+ m2mFields: {},
+ readonlyFields: [],
+ detailLinkFields: [],
+
/* from backbone-tastypie.js */
//idAttribute: 'resource_uri',
@@ -152,6 +160,10 @@
relatedCollections: [],
foreignCollections: [],
+ foreignFields: {},
+ m2mFields: {},
+ readonlyFields: [],
+ detailLinkFields: [],
sorted: function() {
//console.log("sorted " + this.modelName);
@@ -333,6 +345,22 @@
},
});
+ function get_defaults(modelName) {
+ if ((typeof xosdefaults !== "undefined") && xosdefaults[modelName]) {
+ return xosdefaults[modelName];
+ }
+ return undefined;
+ }
+
+ function extend_defaults(modelName, stuff) {
+ defaults = get_defaults(modelName);
+ if (defaults) {
+ return $.extend({}, defaults, stuff);
+ } else {
+ return stuff;
+ }
+ }
+
function define_model(lib, attrs) {
modelName = attrs.modelName;
modelClassName = modelName;
@@ -358,7 +386,7 @@
for (key in attrs) {
value = attrs[key];
- if ($.inArray(key, ["urlRoot", "modelName", "collectionName", "listFields", "addFields", "detailFields", "detailLinkFields", "foreignFields", "inputType", "relatedCollections", "foreignCollections"])>=0) {
+ if ($.inArray(key, ["urlRoot", "modelName", "collectionName", "listFields", "addFields", "detailFields", "detailLinkFields", "foreignFields", "inputType", "relatedCollections", "foreignCollections", "defaults"])>=0) {
modelAttrs[key] = value;
collectionAttrs[key] = value;
}
@@ -367,9 +395,15 @@
}
}
- if ((typeof xosdefaults !== "undefined") && xosdefaults[modelName]) {
- modelAttrs["defaults"] = xosdefaults[modelName];
+ if (!modelAttrs.defaults) {
+ modelAttrs.defaults = get_defaults(modelName);
}
+ console.log(modelName);
+ console.log(modelAttrs);
+
+// if ((typeof xosdefaults !== "undefined") && xosdefaults[modelName]) {
+// modelAttrs["defaults"] = xosdefaults[modelName];
+// }
if ((typeof xosvalidators !== "undefined") && xosvalidators[modelName]) {
modelAttrs["validators"] = xosvalidators[modelName];
@@ -629,9 +663,36 @@
// enhanced REST
// XXX this really needs to somehow be combined with Slice, to avoid duplication
define_model(this, {urlRoot: SLICEPLUS_API,
- relatedCollections: {'slivers': "slice"},
- modelName: "slicePlus",
- collectionName: "slicesPlus"});
+ relatedCollections: {"slivers": "slice", "slicePrivileges": "slice", "networks": "owner"},
+ foreignCollections: ["services", "sites"],
+ foreignFields: {"service": "services", "site": "sites"},
+ listFields: ["backend_status", "id", "name", "enabled", "description", "slice_url", "site", "max_slivers", "service"],
+ detailFields: ["backend_status", "name", "site", "enabled", "description", "slice_url", "max_slivers"],
+ inputType: {"enabled": "checkbox"},
+ modelName: "slicePlus",
+ collectionName: "slicesPlus",
+ defaults: extend_defaults("slice", {"network_ports": "", "site_allocation": []}),
+ xosValidate: function(attrs, options) {
+ errors = XOSModel.prototype.xosValidate(this, attrs, options);
+ // validate that slice.name starts with site.login_base
+ site = attrs.site || this.site;
+ if ((site!=undefined) && (attrs.name!=undefined)) {
+ site = xos.sites.get(site);
+ if (attrs.name.indexOf(site.attributes.login_base+"_") != 0) {
+ errors = errors || {};
+ errors["name"] = "must start with " + site.attributes.login_base + "_";
+ }
+ }
+ return errors;
+ },
+ });
+
+ define_model(this, {urlRoot: TENANTVIEW_API,
+ modelName: "tenantview",
+ collectionName: "tenantview",
+ listFields: [],
+ detailFields: [],
+ });
this.listObjects = function() { return this.allCollectionNames; };
diff --git a/planetstack/core/xoslib/static/js/xoslib/xosHelper.js b/planetstack/core/xoslib/static/js/xoslib/xosHelper.js
index 6c0c2f3..0cadf79 100644
--- a/planetstack/core/xoslib/static/js/xoslib/xosHelper.js
+++ b/planetstack/core/xoslib/static/js/xoslib/xosHelper.js
@@ -4,6 +4,41 @@
},
});
+SliceSelectorOption = Marionette.ItemView.extend({
+ template: "#xos-sliceselector-option",
+ tagName: "option",
+ attributes: function() {
+ console.log("XXX");
+ console.log(this.options.selectedID);
+ console.log(this.model.get("id"));
+ if (this.options.selectedID == this.model.get("id")) {
+ return { value: this.model.get("id"), selected: 1 };
+ } else {
+ return { value: this.model.get("id") };
+ }
+ },
+});
+
+SliceSelectorView = Marionette.CompositeView.extend({
+ template: "#xos-sliceselector-select",
+ childViewContainer: "select",
+ childView: SliceSelectorOption,
+
+ events: {"change select": "onSliceChanged"},
+
+ childViewOptions: function() {
+ return { selectedID: this.options.selectedID || this.selectedID || null };
+ },
+
+ onSliceChanged: function() {
+ this.sliceChanged(this.$el.find("select").val());
+ },
+
+ sliceChanged: function(id) {
+ console.log("sliceChanged " + id);
+ },
+});
+
FilteredCompositeView = Marionette.CompositeView.extend( {
showCollection: function() {
var ChildView;
@@ -25,6 +60,7 @@
onRoute: function(x,y,z) {
this.routeStack.push(Backbone.history.fragment);
+ this.routeStack = this.routeStack.slice(-32); // limit the size of routeStack to something reasonable
},
prevPage: function() {
@@ -33,8 +69,8 @@
showPreviousURL: function() {
prevPage = this.prevPage();
- console.log("showPreviousURL");
- console.log(this.routeStack);
+ //console.log("showPreviousURL");
+ //console.log(this.routeStack);
if (prevPage) {
this.navigate("#"+prevPage, {trigger: false, replace: true} );
}
@@ -103,6 +139,8 @@
parsed_error=undefined;
width=640; // django stacktraces like wide width
}
+ console.log(responseText);
+ console.log(parsed_error);
if (parsed_error) {
$("#xos-error-dialog").html(templateFromId("#xos-error-response")(parsed_error));
} else {
@@ -392,7 +430,7 @@
},
submitDeleteClicked: function(e) {
- this.options.linkedView.submitDeleteClicked.call(this.options.linkedView, e);
+ this.options.linkedView.deleteClicked.call(this.options.linkedView, e);
},
addClicked: function(e) {
@@ -501,13 +539,13 @@
}
if (isNew) {
- this.model.attributes.humanReadableName = "new " + model.modelName;
+ this.model.attributes.humanReadableName = "new " + this.model.modelName;
this.model.addToCollection = this.collection;
} else {
this.model.addToCollection = undefined;
}
- var infoMsgId = this.app.showInformational( {what: "save " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
+ var infoMsgId = this.app.showInformational( {what: "save " + this.model.modelName + " " + this.model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
this.model.save(data, {error: function(model, result, xhr) { that.app.saveError(model,result,xhr,infoMsgId);},
success: function(model, result, xhr) { that.app.saveSuccess(model,result,xhr,infoMsgId);
@@ -609,12 +647,13 @@
collectionName: this.model.collectionName,
addFields: this.model.addFields,
listFields: this.model.listFields,
- detailFields: this.model.detailFields,
+ detailFields: this.options.detailFields || this.detailFields || this.model.detailFields,
foreignFields: this.model.foreignFields,
detailLinkFields: this.model.detailLinkFields,
inputType: this.model.inputType,
model: this.model,
detailView: this,
+ choices: this.options.choices || this.choices || this.model.choices || {},
}},
});
@@ -829,6 +868,7 @@
view.columnsByIndex = [];
view.columnsByFieldName = {};
_.each(this.collection.listFields, function(fieldName) {
+ inputType = view.options.inputType || view.inputType || {};
mRender = undefined;
mSearchText = undefined;
sTitle = fieldNameToHumanReadable(fieldName);
@@ -840,6 +880,8 @@
} else if (fieldName in view.collection.foreignFields) {
var foreignCollection = view.collection.foreignFields[fieldName];
mSearchText = function(x) { return idToName(x, foreignCollection, "humanReadableName"); };
+ } else if (inputType[fieldName] == "spinner") {
+ mRender = function(x,y,z) { return xosDataTableSpinnerTemplate( {value: x, collectionName: view.collection.collectionName, fieldName: fieldName, id: z.id} ); };
}
if ($.inArray(fieldName, view.collection.detailLinkFields)>=0) {
var collectionName = view.collection.collectionName;
@@ -850,9 +892,11 @@
view.columnsByFieldName[fieldName] = thisColumn;
});
- deleteColumn = {sTitle: "", bSortable: false, mRender: function(x,y,z) { return xosDeleteButtonTemplate({modelName: view.collection.modelName, id: z.id}); }, mData: function() { return "delete"; }};
- view.columnsByIndex.push(deleteColumn);
- view.columnsByFieldName["delete"] = deleteColumn;
+ if (!view.noDeleteColumn) {
+ deleteColumn = {sTitle: "", bSortable: false, mRender: function(x,y,z) { return xosDeleteButtonTemplate({modelName: view.collection.modelName, id: z.id}); }, mData: function() { return "delete"; }};
+ view.columnsByIndex.push(deleteColumn);
+ view.columnsByFieldName["delete"] = deleteColumn;
+ };
oTable = $(this.el).find("table").dataTable( {
"bJQueryUI": true,
@@ -893,7 +937,7 @@
// content of the collection
var populateTable = function()
{
- console.log("populatetable!");
+ //console.log("populatetable!");
// clear out old row views
rows = [];
@@ -1044,3 +1088,25 @@
return result;
}
+choicesToOptions = function(selectedValue, choices) {
+ result="";
+ for (index in choices) {
+ choice = choices[index];
+ displayName = choice[0];
+ value = choice[1];
+ if (value == selectedValue) {
+ selected = " selected";
+ } else {
+ selected = "";
+ }
+ result = result + '<option value="' + value + '"' + selected + '>' + displayName + '</option>';
+ }
+ return result;
+}
+
+choicesToSelect = function(variable, selectedValue, choices) {
+ result = '<select name="' + variable + '" id="field_' + variable + '">' +
+ choicesToOptions(selectedValue, choices) +
+ '</select>';
+ return result;
+}
diff --git a/planetstack/core/xoslib/templates/xosAdmin.html b/planetstack/core/xoslib/templates/xosAdmin.html
index 4862cea..ac90e94 100644
--- a/planetstack/core/xoslib/templates/xosAdmin.html
+++ b/planetstack/core/xoslib/templates/xosAdmin.html
@@ -158,9 +158,14 @@
<% args = arguments; %>
<% _.each(detailFields, function(fieldName) { %>
<tr><td><%= fieldNameToHumanReadable(fieldName) %>:</td>
- <% readOnly = $.inArray(fieldName, model.readOnlyFields)>=0 ? " readonly" : ""; console.log(fieldName + " " + readOnly); console.log(model.readOnlyFields); %>
- <% if (fieldName in foreignFields) { %>
+ <% readOnly = $.inArray(fieldName, model.readOnlyFields)>=0 ? " readonly" : ""; %>
+ <% if (fieldName in choices) { %>
+ <td><%= choicesToSelect(fieldName, model.attributes[fieldName], choices[fieldName]) %></td>
+ <% } else if (fieldName in foreignFields) { %>
<td><%= idToSelect(fieldName, model.attributes[fieldName], foreignFields[fieldName], "humanReadableName", readOnly) %></td>
+ <% } else if (inputType[fieldName] == "spinner") { %>
+ <!-- note: I never finished working on this spinner stuff! -->
+ <td><%= xosSpinnerTemplate({id: "picker_" + fieldName, fieldName: fieldName, value: model.attributes[fieldName]}) %></td>
<% } else if (inputType[fieldName] == "checkbox") { %>
<td><input type="checkbox" name="<%= fieldName %>" <% if (model.attributes[fieldName]) print("checked"); %><%= readOnly %>></td>
<% } else if (inputType[fieldName] == "picker") { %>
@@ -223,9 +228,19 @@
</div>
</script>
+<script type="text/template" id="xos-datatable-spinner-template">
+ <!-- arguments: value, id, collectionName, fieldName -->
+ <%= value %> <a href='#increase/<%= collectionName %>/<%= id %>/<%= fieldName %>'>more</a> <a href='#decrease/<%= collectionName %>/<%= id %>/<%= fieldName %>'>less</a>
+</script>
+
+<script type="text/template" id="xos-spinner-template">
+ <!-- arguments: fieldName, id, value -->
+ <input name="<%= fieldName %>" class="xos-spinner" id="<%= id %>">
+ <% detailView.viewInitializers.push( function() { init_spinner("#" + id, value); } ); %>
+</script>
<script type="text/template" id="xos-picker-template">
- <!-- arguments: unpickedItems, pickedItems -->
+ <!-- arguments: unpickedItems, pickedItems, fieldName, id -->
<div id="<%= id %>">
<div class="picker_row">
<div class="picker_column">
@@ -259,6 +274,15 @@
<% detailView.viewInitializers.push( function() { init_picker("#" + id); } ); %>
</script>
+<script type="text/template" id="xos-sliceselector-option">
+ <%= name %>
+</script>
+
+<script type="text/template" id="xos-sliceselector-select">
+ <select>
+ </select>
+</script>
+
<script>
xosInlineDetailButtonsTemplate = _.template($("#xos-inline-detail-buttons-template").html());
xosListHeaderTemplate = _.template($("#xos-list-header-template").html());
@@ -268,5 +292,7 @@
xosBackendStatusIconTemplate = _.template($("#xos-backend-status-icon-template").html());
xosBackendStatusTextTemplate = _.template($("#xos-backend-status-text-template").html());
xosPickerTemplate = _.template($("#xos-picker-template").html());
+xosSpinnerTemplate = _.template($("#xos-spinner-template").html());
+xosDataTableSpinnerTemplate = _.template($("#xos-datatable-spinner-template").html());
</script>