2 column picker WIP
authorScott Baker <smbaker@gmail.com>
Wed, 17 Dec 2014 06:49:42 +0000 (22:49 -0800)
committerScott Baker <smbaker@gmail.com>
Wed, 17 Dec 2014 06:49:42 +0000 (22:49 -0800)
planetstack/core/xoslib/dashboards/xosAdminDashboard.html
planetstack/core/xoslib/static/css/xosAdminSite.css
planetstack/core/xoslib/static/js/picker.js [new file with mode: 0644]
planetstack/core/xoslib/static/js/xoslib/xos-backbone.js
planetstack/core/xoslib/static/js/xoslib/xos-util.js
planetstack/core/xoslib/static/js/xoslib/xosHelper.js
planetstack/core/xoslib/templates/xosAdmin.html

index af9a0f4..bf10240 100644 (file)
@@ -14,6 +14,7 @@
 <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/picker.js"></script>
 <script src="{{ STATIC_URL }}/js/xosAdminSite.js"></script>
 
 <script type="text/template" id="xos-log-template">
index ad9eb47..91d4928 100644 (file)
 #xos-confirm-dialog {
     display: none;
 }
+
+.picker_row {
+  display: table;
+}
+.picker_column {
+  display: table-cell;
+  padding: 10px;
+}
diff --git a/planetstack/core/xoslib/static/js/picker.js b/planetstack/core/xoslib/static/js/picker.js
new file mode 100644 (file)
index 0000000..0302cf4
--- /dev/null
@@ -0,0 +1,50 @@
+function init_picker(selector, ordered) {
+    //console.log("init_picker");
+    //console.log($(selector));
+
+    var addBtn = $(selector).find(".btn-picker-add");
+    var removeBtn = $(selector).find(".btn-picker-remove");
+    var upBtn = $(selector).find(".btn-picker-up");
+    var downBtn = $(selector).find(".btn-picker-down");
+    var from = $(selector).find(".select-picker-from");
+    var to = $(selector).find(".select-picker-to");
+
+    if (!ordered) {
+        upBtn.hide();
+        downBtn.hide();
+    }
+
+    addBtn.click(function(){
+        console.log("add");
+        from.find(":selected").each( function() {
+            to.append("<option value='"+$(this).val()+"'>"+$(this).text()+"</option>");\r
+            $(this).remove();\r
+        });\r
+    });\r
+    removeBtn.click(function(){\r
+        console.log("remove");\r
+        to.find(":selected").each( function() {\r
+            from.append("<option value='"+$(this).val()+"'>"+$(this).text()+"</option>");\r
+            $(this).remove();\r
+        });\r
+    });\r
+    upBtn.bind('click', function() {\r
+        to.find(":selected").each( function() {\r
+            var newPos = to.find('option').index(this) - 1;\r
+            if (newPos > -1) {\r
+                to.find("option").eq(newPos).before("<option value='"+$(this).val()+"' selected='selected'>"+$(this).text()+"</option>");\r
+                $(this).remove();\r
+            }\r
+        });\r
+    });\r
+    downBtn.bind('click', function() {\r
+        var countOptions = to.find("option").size();\r
+        to.find(":selected").each( function() {\r
+            var newPos = to.find("option").index(this) + 1;\r
+            if (newPos < countOptions) {\r
+                to.find("option").eq(newPos).after("<option value='"+$(this).val()+"' selected='selected'>"+$(this).text()+"</option>");\r
+                $(this).remove();\r
+            }\r
+        });\r
+    });\r
+};\r
index 554ff5f..b0ee0dc 100644 (file)
@@ -79,6 +79,20 @@ if (! window.XOSLIB_LOADED ) {
                 return Backbone.Model.prototype.save.call(this, attributes, options);
             },
 
+            getChoices: function(fieldName, excludeChosen) {
+                choices=[];
+                if (fieldName in this.m2mFields) {
+                    for (index in xos[this.m2mFields[fieldName]].models) {
+                        candidate = xos[this.m2mFields[fieldName]].models[index];
+                        if (excludeChosen && idInArray(candidate.id, this.attributes[fieldName])) {
+                            continue;
+                        }
+                        choices.push(candidate.id);
+                    }
+                }
+                return choices;
+            },
+
             /* If a 'validate' method is supplied, then it will be called
                automatically on save. Unfortunately, save calls neither the
                'error' nor the 'success' callback if the validator fails.
@@ -324,6 +338,7 @@ if (! window.XOSLIB_LOADED ) {
 
         attrs.inputType = attrs.inputType || {};
         attrs.foreignFields = attrs.foreignFields || {};
+        attrs.m2mFields = attrs.m2mFields || {};
         attrs.readOnlyFields = attrs.readOnlyFields || [];
         attrs.detailLinkFields = attrs.detailLinkFields || ["id","name"];
 
@@ -473,9 +488,11 @@ if (! window.XOSLIB_LOADED ) {
 
         define_model(this, { urlRoot: DEPLOYMENT_API,
                              relatedCollections: {"nodes": "deployment", "slivers": "deploymentNetwork", "networkDeployments": "deployment", "userDeployments": "deployment"},
+                             m2mFields: {"flavors": "flavors", "sites": "sites", "images": "images"},
                              modelName: "deployment",
                              listFields: ["backend_status", "id", "name", "backend_type", "admin_tenant"],
-                             detailFields: ["backend_status", "name", "backend_type", "admin_tenant"],
+                             detailFields: ["backend_status", "name", "backend_type", "admin_tenant", "flavors", "sites", "images"],
+                             inputType: {"flavors": "picker", "sites": "picker", "images": "picker"},
                              });
 
         define_model(this, {urlRoot: IMAGE_API,
@@ -529,9 +546,10 @@ if (! window.XOSLIB_LOADED ) {
 
         define_model(this, {urlRoot: FLAVOR_API,
                             modelName: "flavor",
+                            m2mFields: {"deployments": "deployments"},
                             listFields: ["backend_status", "id", "name", "flavor", "order", "default"],
-                            detailFields: ["backend_status", "name", "description", "flavor", "order", "default"],
-                            inputType: {"default": "checkbox"},
+                            detailFields: ["backend_status", "name", "description", "flavor", "order", "default", "deployments"],
+                            inputType: {"default": "checkbox", "deployments": "picker"},
                             });
 
         // enhanced REST
index 638ba9a..3fd597b 100644 (file)
@@ -1,5 +1,15 @@
 // misc utility functions
 
+function idInArray(id, arr) {
+    // because sometimes ids are strings and sometimes they're integers
+    for (index in arr) {
+        if (id.toString() == arr[index].toString()) {
+            return true;
+        }
+    }
+    return false;
+}
+
 function assert(outcome, description) {
     if (!outcome) {
         console.log(description);
index 65ccdbd..fe975aa 100644 (file)
@@ -404,6 +404,8 @@ XOSListButtonView = XOSButtonView.extend({ template: "#xos-listbuttons-template"
 XOSDetailView = Marionette.ItemView.extend({
             tagName: "div",
 
+            viewInitializers: [],
+
             events: {"click button.btn-xos-save-continue": "submitContinueClicked",
                      "click button.btn-xos-save-leave": "submitLeaveClicked",
                      "click button.btn-xos-save-another": "submitAddAnotherClicked",
@@ -420,6 +422,12 @@ XOSDetailView = Marionette.ItemView.extend({
                 this.synchronous = false;
             },
 
+            onShow: function() {
+                _.each(this.viewInitializers, function(initializer) {
+                    initializer();
+                });
+            },
+
             afterSave: function(e) {
             },
 
@@ -574,7 +582,6 @@ XOSDetailView = Marionette.ItemView.extend({
             onFormDataInvalid: function(errors) {
                 var self=this;
                 var markErrors = function(value, key) {
-                    console.log("name='" + key + "'");
                     var $inputElement = self.$el.find("[name='" + key + "']");
                     var $inputContainer = $inputElement.parent();
                     //$inputContainer.find(".help-inline").remove();
@@ -593,6 +600,7 @@ XOSDetailView = Marionette.ItemView.extend({
                                                     detailLinkFields: this.model.detailLinkFields,
                                                     inputType: this.model.inputType,
                                                     model: this.model,
+                                                    detailView: this,
                                          }},
 });
 
@@ -972,6 +980,10 @@ idToName = function(id, collectionName, fieldName) {
     return xos.idToName(id, collectionName, fieldName);
 };
 
+makeIdToName = function(collectionName, fieldName) {
+    return function(id) { return idToName(id, collectionName, fieldName); }
+};
+
 /* Constructs lists of <option> html blocks for items in a collection.
 
    selectedId = the id of an object that should be selected, if any
index acc318b..92b5046 100644 (file)
             <td><%= idToSelect(fieldName, model.attributes[fieldName], foreignFields[fieldName], "humanReadableName", readOnly) %></td>\r
         <% } else if (inputType[fieldName] == "checkbox") { %>\r
             <td><input type="checkbox" name="<%= fieldName %>" <% if (model.attributes[fieldName]) print("checked"); %><%= readOnly %>></td>\r
+        <% } else if (inputType[fieldName] == "picker") { %>\r
+            <% lookupFunc = makeIdToName(model.m2mFields[fieldName], "humanReadableName"); %>\r
+            <td><%= xosPickerTemplate({pickedItems: model.attributes[fieldName], unpickedItems: model.getChoices(fieldName,true), id: "picker_" + fieldName, detailView: detailView, lookupFunc: lookupFunc}) %></td>\r
         <% } else if (fieldName=="backend_status") { %>\r
             <td><%= xosBackendStatusTextTemplate.apply(this, args) %></td>\r
         <% } else { %>\r
   </div>
 </script>
 
+
+<script type="text/template" id="xos-picker-template">
+    <!-- arguments: unpickedItems, pickedItems -->
+    <div id="<%= id %>">
+    <div class="picker_row">
+    <div class="picker_column">\r
+    <div>Available</div>\r
+    <select name="pickerfrom" class="select-picker-from" multiple size="5">\r
+        <% _.each(unpickedItems, function(item) { %>\r
+           <option value="<%= item %>"><%= lookupFunc? lookupFunc(item) : item %></option>
+        <% });%>\r
+    </select>\r
+    </div>\r
+    <div class="picker_column">\r
+    <br>\r
+    <div class="btn btn-success btn-picker-add">Add &raquo;</div><br><br>\r
+    <div class="btn btn-success btn-picker-remove">&laquo; Remove</div>\r
+    </div>\r
+    <div class="picker_column">\r
+    <div>Selected</div>\r
+    <select name="pickerto" class="select-picker-to" multiple size="5">\r
+        <% _.each(pickedItems, function(item) { %>\r
+           <option value="<%= item %>"><%= lookupFunc ? lookupFunc(item) : item %></option>
+        <% }); %>\r
+    </select>\r
+    </div>\r
+    <div class="picker_column">\r
+    <br>\r
+    <div class="btn btn-success btn-picker-up">Up</div><br><br>\r
+    <div class="btn btn-success btn-picker-down">Down</div>\r
+    </div>\r
+    </div>
+    </div>
+    <% detailView.viewInitializers.push( function() { init_picker("#" + id); } ); %>
+</script>
+
 <script>
 xosInlineDetailButtonsTemplate = _.template($("#xos-inline-detail-buttons-template").html());
 xosListHeaderTemplate = _.template($("#xos-list-header-template").html());
@@ -224,5 +263,6 @@ xosDeleteButtonTemplate = _.template($("#xos-delete-button-template").html());
 xosDetailLinkTemplate = _.template($("#xos-detail-link-template").html());
 xosBackendStatusIconTemplate = _.template($("#xos-backend-status-icon-template").html());
 xosBackendStatusTextTemplate = _.template($("#xos-backend-status-text-template").html());
+xosPickerTemplate = _.template($("#xos-picker-template").html());
 </script>