2 column picker WIP
diff --git a/planetstack/core/xoslib/static/js/picker.js b/planetstack/core/xoslib/static/js/picker.js
new file mode 100644
index 0000000..0302cf4
--- /dev/null
+++ b/planetstack/core/xoslib/static/js/picker.js
@@ -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>");

+            $(this).remove();

+        });

+    });

+    removeBtn.click(function(){

+        console.log("remove");

+        to.find(":selected").each( function() {

+            from.append("<option value='"+$(this).val()+"'>"+$(this).text()+"</option>");

+            $(this).remove();

+        });

+    });

+    upBtn.bind('click', function() {

+        to.find(":selected").each( function() {

+            var newPos = to.find('option').index(this) - 1;

+            if (newPos > -1) {

+                to.find("option").eq(newPos).before("<option value='"+$(this).val()+"' selected='selected'>"+$(this).text()+"</option>");

+                $(this).remove();

+            }

+        });

+    });

+    downBtn.bind('click', function() {

+        var countOptions = to.find("option").size();

+        to.find(":selected").each( function() {

+            var newPos = to.find("option").index(this) + 1;

+            if (newPos < countOptions) {

+                to.find("option").eq(newPos).after("<option value='"+$(this).val()+"' selected='selected'>"+$(this).text()+"</option>");

+                $(this).remove();

+            }

+        });

+    });

+};

diff --git a/planetstack/core/xoslib/static/js/xoslib/xos-backbone.js b/planetstack/core/xoslib/static/js/xoslib/xos-backbone.js
index 554ff5f..b0ee0dc 100644
--- a/planetstack/core/xoslib/static/js/xoslib/xos-backbone.js
+++ b/planetstack/core/xoslib/static/js/xoslib/xos-backbone.js
@@ -79,6 +79,20 @@
                 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 @@
 
         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 @@
 
         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 @@
 
         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
diff --git a/planetstack/core/xoslib/static/js/xoslib/xos-util.js b/planetstack/core/xoslib/static/js/xoslib/xos-util.js
index 638ba9a..3fd597b 100644
--- a/planetstack/core/xoslib/static/js/xoslib/xos-util.js
+++ b/planetstack/core/xoslib/static/js/xoslib/xos-util.js
@@ -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);
diff --git a/planetstack/core/xoslib/static/js/xoslib/xosHelper.js b/planetstack/core/xoslib/static/js/xoslib/xosHelper.js
index 65ccdbd..fe975aa 100644
--- a/planetstack/core/xoslib/static/js/xoslib/xosHelper.js
+++ b/planetstack/core/xoslib/static/js/xoslib/xosHelper.js
@@ -404,6 +404,8 @@
 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 @@
                 this.synchronous = false;
             },
 
+            onShow: function() {
+                _.each(this.viewInitializers, function(initializer) {
+                    initializer();
+                });
+            },
+
             afterSave: function(e) {
             },
 
@@ -574,7 +582,6 @@
             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 @@
                                                     detailLinkFields: this.model.detailLinkFields,
                                                     inputType: this.model.inputType,
                                                     model: this.model,
+                                                    detailView: this,
                                          }},
 });
 
@@ -972,6 +980,10 @@
     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