This patch is primarily concerned with getting associations to work, but all contains some refactorings:

Associations work for
hostgroups->hosts
groups->users

and continue to work for

users->groups

The name of the sampledata file is implied from the ipa command method.
Since a missing sampledata file means that the ipa command fails, this patch contains smaple data for the 'adds' and 'assign' calls that we could skip before.

Started pulling the various facets into an object structure to simplify and reduce the chances of bugs in dispatch.

Visual diff is here:

https://fedorahosted.org/freeipa/attachment/ticket/104/admiyo-freeipa-0019-associations.patch
>From f426c8386274223b93aa032ec34c8c0ef14eaf18 Mon Sep 17 00:00:00 2001
From: Adam Young <ayo...@redhat.com>
Date: Thu, 2 Sep 2010 13:11:09 -0400
Subject: [PATCH] associations

Below are the individual commit comments from most recent to oldest

    enabled filter for associate
    Association group->users works
    cleaned up some variable names so they no longer are specific to the group entity
    hostgroup to host associations working
    Group enrollment still workds, ready to fix hostgroups
    all forms for the hostgroup fall under a single object, making dispatching much cleaner and simpler.
    AssociationForm now general enough to do searches for multiple target types.
    Added associate.js to Makefile.am
    Cleaned up the association list form enough that it workds for both user->groups and hostgroups->hosts
    Refactored association list into its own object.
    sampledata for adds
    The new ipa_cmd/sampledata scheme insists on there being sample data for any commands or the ipa_command fails.
    Sampledata rename and simplification
    Sampledata is now performed by matching the method name to the file in the sampledata directory.
    assocation refactoring
    pulled the code for group enrollments out into an object.
    THis is the starting point for the next association, hosts to hostgroups
---
 install/static/Makefile.am                         |    1 +
 install/static/add.js                              |    2 +-
 install/static/associate.js                        |  251 ++++++++++++++++++++
 install/static/details.js                          |   12 +-
 install/static/group.js                            |   59 +++--
 install/static/host.js                             |   16 +-
 install/static/hostgroup.js                        |   80 +++++--
 install/static/index.xhtml                         |   31 ++--
 install/static/ipa.js                              |   14 +-
 install/static/navigation.js                       |   37 +++-
 install/static/netgroup.js                         |    5 +-
 install/static/sampledata/group_add.json           |   31 +++
 install/static/sampledata/group_add_member.json    |   25 ++
 .../sampledata/{grouplist.json => group_find.json} |    0
 .../sampledata/{groupshow.json => group_show.json} |    0
 install/static/sampledata/host_add.json            |   40 +++
 .../sampledata/{hostlist.json => host_find.json}   |    0
 .../sampledata/{hostshow.json => host_show.json}   |    0
 install/static/sampledata/hostgroup_add.json       |   27 ++
 .../{hostgrouplist.json => hostgroup_find.json}    |    0
 .../{hostgroupshow.json => hostgroup_show.json}    |    7 +-
 .../{metadata.json => json_metadata.json}          |    0
 install/static/sampledata/netgroup_add.json        |   28 +++
 .../{netgrouplist.json => netgroup_find.json}      |    0
 .../{netgroupshow.json => netgroup_show.json}      |    0
 install/static/sampledata/user_add.json            |   50 ++++
 .../sampledata/{userlist.json => user_find.json}   |    0
 .../sampledata/{usershow.json => user_show.json}   |    0
 install/static/search.js                           |  110 ++++-----
 install/static/user.js                             |  168 ++------------
 30 files changed, 702 insertions(+), 292 deletions(-)
 create mode 100644 install/static/associate.js
 create mode 100644 install/static/sampledata/group_add.json
 create mode 100644 install/static/sampledata/group_add_member.json
 rename install/static/sampledata/{grouplist.json => group_find.json} (100%)
 rename install/static/sampledata/{groupshow.json => group_show.json} (100%)
 create mode 100644 install/static/sampledata/host_add.json
 rename install/static/sampledata/{hostlist.json => host_find.json} (100%)
 rename install/static/sampledata/{hostshow.json => host_show.json} (100%)
 create mode 100644 install/static/sampledata/hostgroup_add.json
 rename install/static/sampledata/{hostgrouplist.json => hostgroup_find.json} (100%)
 rename install/static/sampledata/{hostgroupshow.json => hostgroup_show.json} (65%)
 rename install/static/sampledata/{metadata.json => json_metadata.json} (100%)
 create mode 100644 install/static/sampledata/netgroup_add.json
 rename install/static/sampledata/{netgrouplist.json => netgroup_find.json} (100%)
 rename install/static/sampledata/{netgroupshow.json => netgroup_show.json} (100%)
 create mode 100644 install/static/sampledata/user_add.json
 rename install/static/sampledata/{userlist.json => user_find.json} (100%)
 rename install/static/sampledata/{usershow.json => user_show.json} (100%)

diff --git a/install/static/Makefile.am b/install/static/Makefile.am
index 366975e..63e5894 100644
--- a/install/static/Makefile.am
+++ b/install/static/Makefile.am
@@ -3,6 +3,7 @@ NULL =
 appdir = $(IPA_DATA_DIR)/static
 app_DATA =                              \
 	add.js				\
+	associate.js			\
 	but-reset.png			\
 	but-update.png			\
 	but-selected.png		\
diff --git a/install/static/add.js b/install/static/add.js
index 4af6a8e..4f97165 100644
--- a/install/static/add.js
+++ b/install/static/add.js
@@ -91,7 +91,7 @@ function EntityBuilder(obj,addProperties,addOptionsFunction ){
         }
     }
     //register the new object with the associatev array of builders.
-    builders[obj] = this;
+    builders[obj] = this;3
 }
 
 
diff --git a/install/static/associate.js b/install/static/associate.js
new file mode 100644
index 0000000..65153ba
--- /dev/null
+++ b/install/static/associate.js
@@ -0,0 +1,251 @@
+/*  Authors:
+ *    Adam Young <ayo...@redhat.com>
+ *
+ * Copyright (C) 2010 Red Hat
+ * see file 'COPYING' for use and warranty information
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2 only
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/* IPA Object Add  - creating new instances of entities */
+
+/* REQUIRES: ipa.js */
+
+function keyForFacet(tab, facet){
+    qs = ipa_parse_qs();
+    var key = qs['tab'] +'-'+ qs['facet'];
+    return key;
+}
+
+/**
+*This associator is built for the case where each association requires a separate rpc
+*/
+function SerialAssociator(oneObj, manyObj, facet,manyObjPkeys,oneObjPkey){
+    this.manyObj = manyObj;
+    this.oneObj = oneObj;
+    this.manyObjPkeys =  manyObjPkeys;
+    this.oneObjPkey = oneObjPkey;
+
+    this.associateNext = function(){
+        //TODO assert pre-conditions
+        var  manyObjPkey =  this.manyObjPkeys.shift();
+        if (manyObjPkey){
+            var options = {};
+            options[this.oneObj] = this.oneObjPkey;
+            var args = [manyObjPkey];
+            var associator = this;
+
+            ipa_cmd( 'add_member',args, options ,
+                     function(){
+                         associator.associateNext();
+                     },
+                     function(response){
+                         alert("associateFailure");
+                     },
+                     this.manyObj );
+        }else{
+            location.hash="tab=" +oneObj+"&facet=details&pkey="+qs.pkey;
+        }
+    }
+
+}
+
+/**
+*This associator is for the common case where all the asociations can be sent 
+in a single rpc
+*/
+function BulkAssociator(oneObj, manyObj, facet,manyObjPkeys,pkey){
+    this.manyObj = manyObj;
+    this.oneObj = oneObj;
+    this.manyObjPkeys =  manyObjPkeys;
+    this.pkey =pkey;
+
+    this.associateNext = function(){
+
+        var option = this.manyObjPkeys.shift();
+        while(         this.manyObjPkeys.length > 0){
+            option += "," + this.manyObjPkeys.shift();
+        }
+
+        var options = {
+          "all":true
+        };
+        options[manyObj] = option;
+
+        var args = [this.pkey];
+        var associator = this;
+
+        ipa_cmd( 'add_member',args, options ,
+                 function(response){
+                     var qs = ipa_parse_qs();
+                     if (response.error){
+                         alert("error adding memeber");
+                     }else{
+                         location.hash="tab=" +oneObj
+                             +"&facet=details&pkey="+qs.pkey;
+                     }
+                 },
+                 function(response){
+                     alert("associateFailure");
+                 },
+                 this.oneObj );
+    }
+}
+
+/**
+ *  Create a form for a one to many association.
+ *
+ */
+function AssociationForm(oneObj, manyObj,facet,facets, searchColumn, headerText , associatorConstructor){
+    this.oneObj = oneObj;
+    this.manyObj = manyObj;
+    this.facet = facet;
+    this.facets = facets;
+    this.headerText = headerText;
+    this.searchColumn = searchColumn;
+
+    if (associatorConstructor){
+        this.associatorConstructor = associatorConstructor;
+    }else{
+        this.associatorConstructor = SerialAssociator;
+    }
+    this.associator;
+
+    this.setup = function(pkey){
+        showAssociations();
+        qs = ipa_parse_qs();
+        $("#availableList").html("");
+        $("#enrollments").html("");
+
+        setupFacetNavigation(this.oneObj,qs['pkey'],qs['facet'],this.facets);
+
+        this.currentUserToEnroll = qs['pkey'];
+        this.manyObjPkeys = [];
+        var form = this;
+
+        $('h1').text(this.headerText());
+
+
+        $("#enroll").click(function(){
+            form.associate();
+        });
+        $("#addToList").click(function(){
+            $('#availableList :selected').each(function(i, selected){
+                $("#enrollments").append(selected);
+            });
+            $('#availableList :selected').remove();
+        });
+        $("#removeFromList").click(function(){
+            $('#enrollments :selected').each(function(i, selected){
+                $("#availableList").append(selected);
+            });
+            $('#enrollments :selected').remove();
+        });
+        $("#find").click(function(){
+            form.search();
+        });
+    }
+    this.search = function(){
+
+        var queryFilter = $("#associateFilter").val();
+
+        var form = this;
+        ipa_cmd( 'find', [queryFilter], {}, 
+                 function(searchResults){
+                        form.populateSearch(searchResults);
+                 },
+                 function(){
+                     alert("associationSearchFailure");
+                 },
+                 this.manyObj);
+    }
+
+    this.associate = function(){
+        var manyObjPkeys =  [];
+        $('#enrollments').children().each(function(i, selected){
+            manyObjPkeys.push(selected.value);
+        });
+        var pkey = qs['pkey'];
+        this.associator = 
+            new this.associatorConstructor  (this.oneObj, this.manyObj,
+                                             this.facet,manyObjPkeys,pkey);
+        this.associator.associateNext();
+    }
+    this.populateSearch = function(searchResults){
+        var results = searchResults.result;
+        $("#availableList").html("");
+        for (var i =0; i != results.count; i++){
+            var result = results.result[i];
+            $("<option/>",{
+                value: result[this.searchColumn][0],
+                html:  result[this.searchColumn][0]
+            }).appendTo( $("#availableList"));
+        }
+    }
+}
+
+
+/** 
+    A modfied version of search. It shows the  associations for an object.
+*/
+function AssociationList(obj,facet,assignFacet,associationColumns,facets) {
+    this.obj = obj;
+    this.facet = facet;
+    this.assignFacet = assignFacet;
+    this.associationColumns = associationColumns;
+    this.facets = facets;
+
+
+    this.populate = function(userData){
+        var associationList = userData.result.result[this.associationColumns[0].column];
+        for (var j = 0; j < associationList.length; j++){
+            var row  = $("<tr/>").appendTo($('#searchResultsTable thead:last'));
+            for (var k = 0; k < associationColumns.length ;k++){
+                var column = this.associationColumns[k].column;
+                $("<td/>",{
+                    html: userData.result.result[column][j]
+                }).appendTo(row);
+            }
+        }
+    }
+    this.setup=function(){
+        qs = ipa_parse_qs();
+        showSearch();
+        buildFacetNavigation(facets);
+        $("#filter").css("display","none");
+        $("#searchButtons").html("");
+        $("<input/>",{
+            type:  'button',
+            value: 'enroll',
+            click: function(){
+                location.hash="tab="+obj+"&facet="+assignFacet+"&pkey="+qs['pkey'];
+            }
+        }).appendTo("#searchButtons");
+        var header = $("<tr/>").appendTo($('#searchResultsTable thead:last'));
+        for (var i =0 ; i != associationColumns.length ;i++){
+            $("<th/>",{
+                html: associationColumns[i].title
+            }).appendTo(header);
+        }
+        var form = this;
+        ipa_cmd( 'show', [qs['pkey']], {},
+                 function(result){
+                     form.populate(result);
+                 },
+                 function(){
+                     alert("associationListFailure");
+                 },
+                 this.obj);
+    }
+}
diff --git a/install/static/details.js b/install/static/details.js
index 40d36b6..e291b49 100644
--- a/install/static/details.js
+++ b/install/static/details.js
@@ -40,7 +40,7 @@ var _ipa_load_on_fail_callback = null;
 
 var ipa_details_cache = null;
 
-function ipa_details_load(pkey, on_win, on_fail,sampleData)
+function ipa_details_load(pkey, on_win, on_fail)
 {
     if (!pkey)
         return;
@@ -50,7 +50,7 @@ function ipa_details_load(pkey, on_win, on_fail,sampleData)
 
     ipa_cmd(
         'show', [pkey], {all: true}, _ipa_load_on_win, _ipa_load_on_fail,
-        _ipa_obj_name, sampleData );
+        _ipa_obj_name);
 }
 
 function _ipa_load_on_win(data, text_status, xhr)
@@ -439,12 +439,12 @@ function _h2_on_click(obj)
     }
 }
 
-function DetailsForm(obj, details_list, pkeyCol, sampleData   ){
+function DetailsForm(obj, details_list, pkeyCol,  facets ){
 
     this.obj = obj;
     this.details_list = details_list;
-    this.sampleData = sampleData;
     this.pkeyCol = pkeyCol;
+    this.facets = facets;
 
     this.setup= function(key){
         //re initialize global parse of parameters
@@ -452,9 +452,11 @@ function DetailsForm(obj, details_list, pkeyCol, sampleData   ){
 
         showDetails();
         $('h1').text("Managing " + this.obj +": " +qs['pkey'] );
+        setupFacetNavigation(this.obj,qs.pkey,qs.facet,this.facets);
+
 
         ipa_details_init(this.obj);
         ipa_details_create(this.details_list, $('#details'));
-        ipa_details_load(qs.pkey, on_win, null, this.sampleData);
+        ipa_details_load(qs.pkey, on_win, null);
     }
 }
diff --git a/install/static/group.js b/install/static/group.js
index 981e048..8c5491a 100644
--- a/install/static/group.js
+++ b/install/static/group.js
@@ -1,10 +1,13 @@
 function setupGroup(facet){
-    if (facet == "details"){
-    setupGroupDetails();
-    }else  if (facet == "add"){
-    setupAddGroup();
+
+    if (groupForms[facet]){
+        groupForms[facet].setup();
+    }else if (facet == "details"){
+        setupGroupDetails();
+    }else if (facet == "add"){
+        setupAddGroup();
     }else{
-    groupSearchForm.setup();
+        groupForms.search.setup();
     }
 }
 
@@ -74,33 +77,26 @@ var group_details_list =
     ['description', 'Description'],
     ['gidnumber', 'Group ID']]]];
 
+var groupFacets=['details','users'];
+
 function setupGroupDetails(group){
 
     //re initialize global parse of parameters
     qs = ipa_parse_qs();
 
     showDetails();
-
+    setupFacetNavigation('group',qs['pkey'],qs['facet'],groupFacets);
     ipa_details_init('group');
     ipa_details_create(group_details_list, $('#details'));
-    ipa_details_load(qs['pkey'], on_win, null, "sampledata/groupshow.json");
+    ipa_details_load(qs['pkey'], on_win, null);
     $('h1').text('Managing group: ' + group);
 }
 
-
-
-function renderGroupDetails(group)
-{
-
-}
-
-
 function renderGroupDetailColumn(current,cell){
 
     $("<a/>",{
-    href:"#tab=group&facet=details&pkey="+current.cn,
-    html:  ""+ current[this.column],
-    //click: function(){ setupGroupDetails(current.cn)},
+        href:"#tab=group&facet=details&pkey="+current.cn,
+        html:  ""+ current[this.column],
     }).appendTo(cell);
 }
 
@@ -112,7 +108,32 @@ var groupSearchColumns = [
     {title:"Description",  column:"description",render: renderSimpleColumn}
 ];
 
-var groupSearchForm = new SearchForm("group", "find", groupSearchColumns ,"sampledata/grouplist.json");
+var groupForms = new GroupForms();
+
+function GroupForms(){
+
+    this.userListColumns = [ {title:"user",column:"member_user" }];
+    this.obj="group";
+    this.users = new AssociationList(
+        this.obj,
+        "users",
+        "assignusers",
+        this.userListColumns, groupFacets );
+
+    this.assignusers = new AssociationForm(
+        this.obj,
+        "user",
+        "assignusers",
+        groupFacets,
+        "uid",
+        function(){
+            return 'Add Users to group : '  + qs['pkey'] ;
+        },
+        BulkAssociator);
 
 
+    this.search =  new SearchForm("group", "find", groupSearchColumns );
+
+
+}
 
diff --git a/install/static/host.js b/install/static/host.js
index b249c9b..dba4f35 100644
--- a/install/static/host.js
+++ b/install/static/host.js
@@ -1,10 +1,10 @@
 function setupHost(facet){
     if (facet == "details"){
-    hostDetailsForm.setup();
+        hostDetailsForm.setup();
     }else if (facet == "add"){
-    hostBuilder.setup();
+        hostBuilder.setup();
     }else{
-    hostSearchForm.setup();
+        hostSearchForm.setup();
     }
 }
 
@@ -17,11 +17,13 @@ var host_details_list =  [['host', 'Host Details', [
     ['krbprincipalname', 'Kerberos Principal'],
     ['serverhostname', 'Server Host Name']
 ]]];
- 
-var hostDetailsForm = new DetailsForm("host",host_details_list,"fqdn","sampledata/hostshow.json") ;
 
+var hostFacets = ["details","hostgroup", "hostgroupmembership"];
 
-var hostDetailsColumns = [
+var hostDetailsForm = new DetailsForm("host",host_details_list,"fqdn",
+                                      hostFacets ) ;
+
+var hostSearchColumns = [
     {title:"Host",column:"fqdn",render: function(current,cell){
     renderPkeyColumn(hostDetailsForm,current,cell);
     }},
@@ -29,4 +31,4 @@ var hostDetailsColumns = [
     {title:"Enrolled?",  render: renderUnknownColumn},
     {title:"Manages?",   render: renderUnknownColumn}
 ];
-var hostSearchForm = new SearchForm("host", "find", hostDetailsColumns,"sampledata/hostlist.json");
+var hostSearchForm = new SearchForm("host", "find", hostSearchColumns);
diff --git a/install/static/hostgroup.js b/install/static/hostgroup.js
index 290460d..65732a1 100644
--- a/install/static/hostgroup.js
+++ b/install/static/hostgroup.js
@@ -1,11 +1,6 @@
+
 function setupHostgroup(facet){
-    if (facet == "details"){
-        hostgroupDetailsForm.setup();
-    }else if (facet == "add"){
-        hostgroupBuilder.setup();
-    }else{
-        hostgroupSearchForm.setup();
-    }
+        hostgroupForms.setup(facet);
 }
 
 var hostgroup_details_list =
@@ -13,8 +8,7 @@ var hostgroup_details_list =
         ['cn', 'Hostgroup Name'],
         ['description', 'Description']]]];
 
-
-var hostgroupDetailsForm = new DetailsForm("hostgroup",hostgroup_details_list,"cn","sampledata/hostgroupshow.json") ;
+var hostgroupFacets = ["details","hosts","assignhosts"];
 
 
 
@@ -26,23 +20,63 @@ function hostgroupAddOptionsFunction (){
     return options;
 }
 
-var hostgroupAddProperties =
+var hostgroupForms = new HostgroupsForms();
+
+function HostgroupsForms(){
+
+    this.setup = function(facet){
+        if (this[facet]){
+            this[facet].setup();
+        }else{
+            this.unspecified.setup();
+        }
+    }
+
+
+/**
+*  used to initialize the search
+*/
+    this.hostgroupSearchColumns = [
+        {
+            title:"Hostgroup",
+            column:"cn",
+            render:  function(current,cell){
+                renderPkeyColumn2('hostgroup', 'cn', current,cell);
+            }
+        },
+        {title:"Description", column:"description",render: renderSimpleColumn}];
+
+    this.hostgroupAddProperties =
     [{title: 'Hostgroup Name', id: 'pkey', type: 'text'},
      {title: 'Description', id: 'description', type: 'text'}];
 
-var hostgroupBuilder = new EntityBuilder("hostgroup",hostgroupAddProperties,hostgroupAddOptionsFunction);
 
+    /**
+       Facets
+    */
+    this.hostListColumns = [ {title:"Host",column:"member_host" }];
+    this.obj="hostgroup";
+    this.hosts = new AssociationList(
+        this.obj,
+        "hosts",
+        "assignhosts", 
+        this.hostListColumns, hostgroupFacets );
 
-var hostgroupSearchColumns = [
-    {
-        title:"Hostgroup", 
-        column:"cn", 
-        render:  function(current,cell){
-            renderPkeyColumn(hostgroupDetailsForm, current,cell);
-        }
-    },
-    {title:"Description", column:"description",render: renderSimpleColumn}];
+    this.assignhosts = new AssociationForm(
+        this.obj,
+        "host",
+        "assignhosts",
+        hostgroupFacets,
+        "fqdn",
+        function(){
+            return 'Add Hosts to to  hostgroup : '  + qs['pkey'] ;
+        },
+        BulkAssociator);
 
-var hostgroupSearchForm = 
-    new SearchForm("hostgroup", "find", hostgroupSearchColumns,
-                   "sampledata/hostgrouplist.json");
+    this.details = new DetailsForm("hostgroup",hostgroup_details_list,"cn",hostgroupFacets) ;
+
+    this.add = new EntityBuilder("hostgroup",this.hostgroupAddProperties,hostgroupAddOptionsFunction);
+
+    this.search = new SearchForm("hostgroup", "find", this.hostgroupSearchColumns);
+    this.unspecified = this.search;
+}
diff --git a/install/static/index.xhtml b/install/static/index.xhtml
index 8e231f4..4077d4f 100644
--- a/install/static/index.xhtml
+++ b/install/static/index.xhtml
@@ -12,6 +12,7 @@
     <script type="text/javascript" src="search.js"></script>
     <script type="text/javascript" src="details.js"></script>
     <script type="text/javascript" src="add.js"></script>
+    <script type="text/javascript" src="associate.js"></script>
     <script type="text/javascript" src="user.js"></script>
     <script type="text/javascript" src="usermeta.js"></script>
     <script type="text/javascript" src="group.js"></script>
@@ -23,10 +24,7 @@
     <script type="text/javascript">
 
       $(document).ready(function(){
-      if (useSampleData)
-        ipa_init("sampledata/metadata.json", buildNavigation);
-      else
-        ipa_init(null, buildNavigation);
+        ipa_init( buildNavigation);
       });
 
     </script>
@@ -46,11 +44,16 @@
       <div id="sub-nav">Edit: </div>
     </div>
 
+    <h1></h1>
+    <ul id="viewtype" style="display:none" >
+      <li id="viewcaption"></li>
+    </ul>
+
+
     <div id="content" border="1"  style="display:none" >
     </div>
 
     <div id="details" style="display:none" >
-      <h1>Managing user:</h1>
       <div id="buttons">
 	<a href="jslink" onclick="return (reset_on_click(this));">
           <img id="butreset" src="but-reset.png" alt="Reset" />
@@ -60,9 +63,6 @@
 	</a>
       </div>
 
-      <ul id="viewtype">
-	<li id="viewcaption">View:</li>
-      </ul>
       <div id="detail-lists">
 	<hr />
       </div>
@@ -89,24 +89,23 @@
     </div>
 
     <div id="associations" style="display:none">
-      <h1>Enroll in Groups</h1>
       <ul id="viewtype">
 	<li id="viewcaption">View:</li>
       </ul>
       <form>
 	<div style="border-width:1px">
 	  <div >
-	    <input type="text"/>
-	    <input id="find" type="button" value="Find Groups"/>
+	    <input id="associateFilter" type="text"/>
+	    <input id="find" type="button" value="Find"/>
 	    <span style="float:right">
-	      <input id="cancelEnrollGroups" type="button" value="Cancel"/>
-	      <input id="enrollGroups" type="button" value="Enroll"/>
+	      <input id="cancel" type="button" value="Cancel"/>
+	      <input id="enroll" type="button" value="Enroll"/>
 	    </span>
 	  </div>
 	  <div  id="results"  style="border: 2px solid rgb(0, 0, 0); position:relative;  height:200px;" >
 	    <div style="float:left;">
-	      <div>Groups</div>
-	      <select id="grouplist" width="150px" size="10" multiple="true" >
+	      <div>Available</div>
+	      <select id="availableList" width="150px" size="10" multiple="true" >
 	      </select>
 	    </div>
 	    <div style="float:left;">
@@ -114,7 +113,7 @@
 	      <p><input id="addToList" type="button" value="&gt;&gt;"/></p>
 	    </div>
 	    <div style="float:left;">
-	      <div>Prospective Enrollments</div>
+	      <div>Prospective</div>
 	      <select id="enrollments" width="150px"  size="10" multiple="true" >
 	      </select>
 	    </div>
diff --git a/install/static/ipa.js b/install/static/ipa.js
index 63101a7..d5a2dbd 100644
--- a/install/static/ipa.js
+++ b/install/static/ipa.js
@@ -38,10 +38,9 @@ var qs;
 var useSampleData = (window.location.protocol == "file:");
 
 
-function ipa_init(url, on_win)
+function ipa_init(on_win)
 {
-    if (!url)
-	url = '/ipa/json';
+    var url = '/ipa/json';
 
     _ipa_init_on_win_callback = on_win;
 
@@ -55,7 +54,10 @@ function ipa_init(url, on_win)
 
     $.ajaxSetup(options);
 
-    ipa_cmd('json_metadata', [], {}, _ipa_load_objs);
+    ipa_cmd('json_metadata', [], {}, _ipa_load_objs,
+            function(response){
+                alert('init failed');
+            });
 }
 
 function _ipa_load_objs(data, textStatus, xhr)
@@ -79,7 +81,9 @@ function ipa_cmd(name, args, options, win_callback, fail_callback, objname,sampl
     if (objname)
 	name = objname + '_' + name;
 
-    if (useSampleData && sampleData){
+    if (useSampleData){
+        var sampleData ="sampledata/"+name+".json";
+
 	var ajax_options = {
 	    url: sampleData,
 	    type: 'POST',
diff --git a/install/static/navigation.js b/install/static/navigation.js
index ff21fdf..94fe47a 100644
--- a/install/static/navigation.js
+++ b/install/static/navigation.js
@@ -119,14 +119,14 @@ function buildNavigation(){
         selectedSub.setup(facet);
     }
     }else if (currentMain && currentMain.setup){
-    currentMain.setup(facet);
+        currentMain.setup(facet);
     }
 
     var whoami = $.cookie("whoami");
     if (whoami == null){
-    ipa_cmd( 'whoami', [], {}, whoamiSuccess, null,null, "sampledata/whoami.json");
+        ipa_cmd( 'whoami', [], {}, whoamiSuccess, null,null);
     }else{
-    setLoggedInText(whoami);
+        setLoggedInText(whoami);
     }
 }
 
@@ -168,12 +168,41 @@ function setActiveTab(tabName){
     $(activeTab).removeClass('main-nav-off').addClass("main-nav-on")
 }
 
+
+function buildFacetNavigation(facets){
+        setupFacetNavigation(qs['tab'], qs['pkey'],qs['facet'], facets);
+}
+
+function setupFacetNavigation(tab, pkey,facet,facets){
+    $('#viewtype').css("display","block");
+    $("#viewtype").html("");
+
+    for (var i =0; i < facets.length; i++){
+        var li = $('<li>').appendTo($("#viewtype"));
+        if (facets[i] == facet){
+            $('<img src="but-selected.png" alt="" />');
+            li.html(facets[i]);
+        }else{
+            $('<img src="but-unselected.png" alt="" />').appendTo(li);
+            $('<a/>',{
+                href: "#tab="+tab+"&facet="+facets[i]+"&pkey="+pkey,
+                html: facets[i]
+            }).appendTo(li);
+        }
+    }
+}
+
 function clearOld(){
+
+    $('h1').html("");
+    $('#viewtype').css("display","none");
+
     $('#search').css("display","none");
     $('#details').css("display","none");
     $('#content').css("display","none");
     $('#associations').css("display","none");
 
+
     $('#searchResultsTable thead').html("");
     $('#searchResultsTable tfoot').html("");
     $('#searchResultsTable tbody').find("tr").remove();
@@ -194,11 +223,13 @@ function showSearch(){
 
 function showContent(){
     clearOld();
+    $('#viewtype').css("display","block");
     $('#content').css("display","block");
 }
 
 function showDetails(){
     clearOld();
+    $('#viewtype').css("display","block");
     $('#details').css("display","block");
 }
 
diff --git a/install/static/netgroup.js b/install/static/netgroup.js
index 5659fbd..88338b1 100644
--- a/install/static/netgroup.js
+++ b/install/static/netgroup.js
@@ -16,7 +16,7 @@ var netgroup_details_list =
         ['nisdomainname', 'NIS Domain']]]];
 
 
-var netgroupDetailsForm = new DetailsForm("netgroup",netgroup_details_list,"cn","sampledata/netgroupshow.json") ;
+var netgroupDetailsForm = new DetailsForm("netgroup",netgroup_details_list,"cn", ["details","hosts","groups","users"]) ;
 
 
 var netgroupAddProperties =
@@ -32,7 +32,6 @@ function netgroupAddOptionsFunction (){
     return options;
 }
 
-
 var netgroupBuilder = new EntityBuilder("netgroup",netgroupAddProperties,netgroupAddOptionsFunction);
 
 
@@ -43,4 +42,4 @@ var netgroupSearchColumns = [
     {title:"Description", column:"description",render: renderSimpleColumn}];
 
 var netgroupSearchForm =
-    new SearchForm("netgroup", "find", netgroupSearchColumns,"sampledata/netgrouplist.json");
+    new SearchForm("netgroup", "find", netgroupSearchColumns);
diff --git a/install/static/sampledata/group_add.json b/install/static/sampledata/group_add.json
new file mode 100644
index 0000000..180f5f1
--- /dev/null
+++ b/install/static/sampledata/group_add.json
@@ -0,0 +1,31 @@
+{
+    "error": null,
+    "id": 12,
+    "result": {
+        "result": {
+            "cn": [
+                "neighbors"
+            ],
+            "description": [
+                "The People in Your Neighborhood"
+            ],
+            "dn": "cn=neighbors,cn=groups,cn=accounts,dc=ayoung,dc=boston,dc=devel,dc=redhat,dc=com",
+            "gidnumber": [
+                "123456"
+            ],
+            "ipauniqueid": [
+                "0dbae87a-b43a-11df-ac8e-525400674dcd"
+            ],
+            "objectclass": [
+                "top",
+                "groupofnames",
+                "nestedgroup",
+                "ipausergroup",
+                "ipaobject",
+                "posixgroup"
+            ]
+        },
+        "summary": null,
+        "value": "neighbors"
+    }
+}
\ No newline at end of file
diff --git a/install/static/sampledata/group_add_member.json b/install/static/sampledata/group_add_member.json
new file mode 100644
index 0000000..7f10d10
--- /dev/null
+++ b/install/static/sampledata/group_add_member.json
@@ -0,0 +1,25 @@
+{
+    "error": null,
+    "id": 0,
+    "result": {
+        "completed": 1,
+        "failed": {
+            "member": {
+                "group": [],
+                "user": []
+            }
+        },
+        "result": {
+            "cn": [
+                "testgroup"
+            ],
+            "description": [
+                "testgroup"
+            ],
+            "dn": "cn=testgroup,cn=groups,cn=accounts,dc=ayoung,dc=boston,dc=devel,dc=redhat,dc=com",
+            "member_user": [
+                "kfrog"
+            ]
+        }
+    }
+}
\ No newline at end of file
diff --git a/install/static/sampledata/grouplist.json b/install/static/sampledata/group_find.json
similarity index 100%
rename from install/static/sampledata/grouplist.json
rename to install/static/sampledata/group_find.json
diff --git a/install/static/sampledata/groupshow.json b/install/static/sampledata/group_show.json
similarity index 100%
rename from install/static/sampledata/groupshow.json
rename to install/static/sampledata/group_show.json
diff --git a/install/static/sampledata/host_add.json b/install/static/sampledata/host_add.json
new file mode 100644
index 0000000..8b4d8b9
--- /dev/null
+++ b/install/static/sampledata/host_add.json
@@ -0,0 +1,40 @@
+{
+    "error": null,
+    "id": 14,
+    "result": {
+        "result": {
+            "cn": [
+                "www.pbs.org"
+            ],
+            "dn": "fqdn=www.pbs.org,cn=computers,cn=accounts,dc=ayoung,dc=boston,dc=devel,dc=redhat,dc=com",
+            "fqdn": [
+                "www.pbs.org"
+            ],
+            "has_keytab": false,
+            "ipauniqueid": [
+                "3ad2e63d-b43a-11df-8dd1-525400674dcd"
+            ],
+            "krbprincipalname": [
+                "host/www.pbs....@ayoung.boston.devel.redhat.com"
+            ],
+            "managedby": [
+                "fqdn=www.pbs.org,cn=computers,cn=accounts,dc=ayoung,dc=boston,dc=devel,dc=redhat,dc=com"
+            ],
+            "objectclass": [
+                "ipaobject",
+                "nshost",
+                "ipahost",
+                "pkiuser",
+                "ipaservice",
+                "krbprincipalaux",
+                "krbprincipal",
+                "top"
+            ],
+            "serverhostname": [
+                "www"
+            ]
+        },
+        "summary": null,
+        "value": "www.pbs.org"
+    }
+}
\ No newline at end of file
diff --git a/install/static/sampledata/hostlist.json b/install/static/sampledata/host_find.json
similarity index 100%
rename from install/static/sampledata/hostlist.json
rename to install/static/sampledata/host_find.json
diff --git a/install/static/sampledata/hostshow.json b/install/static/sampledata/host_show.json
similarity index 100%
rename from install/static/sampledata/hostshow.json
rename to install/static/sampledata/host_show.json
diff --git a/install/static/sampledata/hostgroup_add.json b/install/static/sampledata/hostgroup_add.json
new file mode 100644
index 0000000..bc41ee7
--- /dev/null
+++ b/install/static/sampledata/hostgroup_add.json
@@ -0,0 +1,27 @@
+{
+    "error": null,
+    "id": 15,
+    "result": {
+        "result": {
+            "cn": [
+                "guest_hosts"
+            ],
+            "description": [
+                "Special Guest Stars"
+            ],
+            "dn": "cn=guest_hosts,cn=hostgroups,cn=accounts,dc=ayoung,dc=boston,dc=devel,dc=redhat,dc=com",
+            "ipauniqueid": [
+                "7f8c57ae-b43a-11df-8016-525400674dcd"
+            ],
+            "objectclass": [
+                "ipaobject",
+                "ipahostgroup",
+                "nestedGroup",
+                "groupOfNames",
+                "top"
+            ]
+        },
+        "summary": "Added hostgroup \"guest_hosts\"",
+        "value": "guest_hosts"
+    }
+}
\ No newline at end of file
diff --git a/install/static/sampledata/hostgrouplist.json b/install/static/sampledata/hostgroup_find.json
similarity index 100%
rename from install/static/sampledata/hostgrouplist.json
rename to install/static/sampledata/hostgroup_find.json
diff --git a/install/static/sampledata/hostgroupshow.json b/install/static/sampledata/hostgroup_show.json
similarity index 65%
rename from install/static/sampledata/hostgroupshow.json
rename to install/static/sampledata/hostgroup_show.json
index cddc41f..776d446 100644
--- a/install/static/sampledata/hostgroupshow.json
+++ b/install/static/sampledata/hostgroup_show.json
@@ -9,7 +9,12 @@
             "description": [
                 "Live servers"
             ],
-            "dn": "cn=host-live,cn=hostgroups,cn=accounts,dc=ayoung,dc=boston,dc=devel,dc=redhat,dc=com"
+            "dn": "cn=host-live,cn=hostgroups,cn=accounts,dc=ayoung,dc=boston,dc=devel,dc=redhat,dc=com",
+            "member_host": [
+                "live3.pbs.org",
+                "live2.pbs.org",
+                "live1.pbs.org"
+            ]
         },
         "summary": null,
         "value": "host-live"
diff --git a/install/static/sampledata/metadata.json b/install/static/sampledata/json_metadata.json
similarity index 100%
rename from install/static/sampledata/metadata.json
rename to install/static/sampledata/json_metadata.json
diff --git a/install/static/sampledata/netgroup_add.json b/install/static/sampledata/netgroup_add.json
new file mode 100644
index 0000000..989bb5d
--- /dev/null
+++ b/install/static/sampledata/netgroup_add.json
@@ -0,0 +1,28 @@
+{
+    "error": null,
+    "id": 17,
+    "result": {
+        "result": {
+            "cn": [
+                "butterfly_net"
+            ],
+            "description": [
+                "Butterfly Networks"
+            ],
+            "dn": "ipauniqueid=b16f7240-b43a-11df-89fa-525400674dcd,cn=ng,cn=alt,dc=ayoung,dc=boston,dc=devel,dc=redhat,dc=com",
+            "ipauniqueid": [
+                "b16f7240-b43a-11df-89fa-525400674dcd"
+            ],
+            "nisdomainname": [
+                "ayoung.boston.devel.redhat.com"
+            ],
+            "objectclass": [
+                "ipaobject",
+                "ipaassociation",
+                "ipanisnetgroup"
+            ]
+        },
+        "summary": null,
+        "value": "butterfly_net"
+    }
+}
\ No newline at end of file
diff --git a/install/static/sampledata/netgrouplist.json b/install/static/sampledata/netgroup_find.json
similarity index 100%
rename from install/static/sampledata/netgrouplist.json
rename to install/static/sampledata/netgroup_find.json
diff --git a/install/static/sampledata/netgroupshow.json b/install/static/sampledata/netgroup_show.json
similarity index 100%
rename from install/static/sampledata/netgroupshow.json
rename to install/static/sampledata/netgroup_show.json
diff --git a/install/static/sampledata/user_add.json b/install/static/sampledata/user_add.json
new file mode 100644
index 0000000..b2ede8d
--- /dev/null
+++ b/install/static/sampledata/user_add.json
@@ -0,0 +1,50 @@
+{
+    "error": null,
+    "id": 10,
+    "result": {
+        "result": {
+            "dn": "uid=snuffy,cn=users,cn=accounts,dc=ayoung,dc=boston,dc=devel,dc=redhat,dc=com",
+            "gecos": [
+                "snuffy"
+            ],
+            "givenname": [
+                "Aloysius"
+            ],
+            "homedirectory": [
+                "/home/snuffy"
+            ],
+            "ipauniqueid": [
+                "a4ce7d19-b439-11df-a9ae-525400674dcd"
+            ],
+            "krbprincipalname": [
+                "snu...@ayoung.boston.devel.redhat.com"
+            ],
+            "loginshell": [
+                "/bin/sh"
+            ],
+            "objectclass": [
+                "top",
+                "person",
+                "organizationalperson",
+                "inetorgperson",
+                "inetuser",
+                "posixaccount",
+                "krbprincipalaux",
+                "krbticketpolicyaux",
+                "radiusprofile",
+                "ipaobject"
+            ],
+            "sn": [
+                "Snuffleupagus"
+            ],
+            "uid": [
+                "snuffy"
+            ],
+            "uidnumber": [
+                "1869788865"
+            ]
+        },
+        "summary": "Added user \"snuffy\"",
+        "value": "snuffy"
+    }
+}
\ No newline at end of file
diff --git a/install/static/sampledata/userlist.json b/install/static/sampledata/user_find.json
similarity index 100%
rename from install/static/sampledata/userlist.json
rename to install/static/sampledata/user_find.json
diff --git a/install/static/sampledata/usershow.json b/install/static/sampledata/user_show.json
similarity index 100%
rename from install/static/sampledata/usershow.json
rename to install/static/sampledata/user_show.json
diff --git a/install/static/search.js b/install/static/search.js
index 95ac482..c1f99ba 100644
--- a/install/static/search.js
+++ b/install/static/search.js
@@ -1,6 +1,3 @@
-//useSampleData is defined in index.xhtml.  Work around for development
-var sampleData;
-
 
 //Columns is an array of items in the form
 // {title, column,  render}
@@ -25,13 +22,19 @@ function  renderUnknownColumn(current,cell){
 }
 
 
-function renderPkeyColumn(form,current,cell){
+function renderPkeyColumn2(obj,pkeyCol,current,cell){
     $("<a/>",{
-    href:"#tab="+form.obj+"&facet=details&pkey="+current[form.pkeyCol],
-    html:  "" + current[form.pkeyCol],
+    href:"#tab="+obj+"&facet=details&pkey="+current[pkeyCol],
+    html:  "" + current[pkeyCol],
     }).appendTo(cell);
 }
 
+function renderPkeyColumn(form,current,cell){
+    renderPkeyColumn2(form.obj, form.pkeyCol,current, cell);
+}
+
+
+
 
 function renderDetailColumn(current,cell,pkey,obj){
     $("<a/>",{
@@ -42,7 +45,7 @@ function renderDetailColumn(current,cell,pkey,obj){
 
 
 
-function SearchForm(obj, method, cols, searchSampleData){
+function SearchForm(obj, method, cols){
 
     this.buildColumnHeaders =  function (){
     var columnHeaders  = document.createElement("tr");
@@ -66,67 +69,60 @@ function SearchForm(obj, method, cols, searchSampleData){
     }
 
     this.searchSuccess = function (json){
-    if (json.result.truncated){
-        $("#searchResultsTable tfoot").html("More than "+sizelimit+" results returned.  First "+ sizelimit+" results shown." );
-    }else{
-        $("#searchResultsTable tfoot").html(json.result.summary);
-    }
-    $("#searchResultsTable tbody").find("tr").remove();
-    for (var index = 0; index !=  json.result.result.length; index++){
-    var current = json.result.result[index];
-        $('#searchResultsTable tbody:last').append(this.renderResultRow(current));
-    }
+        if (json.result.truncated){
+            $("#searchResultsTable tfoot").html("More than "+sizelimit+" results returned.  First "+ sizelimit+" results shown." );
+        }else{
+            $("#searchResultsTable tfoot").html(json.result.summary);
+        }
+        $("#searchResultsTable tbody").find("tr").remove();
+        for (var index = 0; index !=  json.result.result.length; index++){
+            var current = json.result.result[index];
+            $('#searchResultsTable tbody:last').append(this.renderResultRow(current));
+        }
     }
 
     this.searchWithFilter = function(queryFilter){
-    var form = this;
-
-    $('#searchResultsTable tbody').html("");
-    $('#searchResultsTable tbody').html("");
-    $('#searchResultsTable tfoot').html("");
-
-    ipa_cmd(this.method,
-        [queryFilter],
-        {"all":"true"},
-        function(json){
-            form.searchSuccess(json);
-        },
-        function(json){
-            alert("Search Failed");
-        },form.obj, form.searchSampleData);
-
+        var form = this;
+
+        $('#searchResultsTable tbody').html("");
+        $('#searchResultsTable tbody').html("");
+        $('#searchResultsTable tfoot').html("");
+
+        ipa_cmd(this.method,
+                [queryFilter],
+                {"all":"true"},
+                function(json){
+                    form.searchSuccess(json);
+                },
+                function(json){
+                    alert("Search Failed");
+                },
+                form.obj);
     }
 
     this.setup = function(){
-    showSearch();
-
-    $('#searchResultsTable thead').html("");
-    $('#searchResultsTable tbody').html("");
-    $('#searchResultsTable tfoot').html("");
-
-    $("#new").click(function(){
-        location.hash="tab="+obj+"&facet=add";
-    });
-
-    $("#query").click(executeSearch);
-
-    this.buildColumnHeaders();
-
-    var params = ipa_parse_qs();
-
-    qs = location.hash.substring(1);
-    //TODO fix this hack.  since parse returns an object, I don't know how to see if that object has a"critia" property if criteria is null.
-    if (qs.indexOf("criteria") > 0)
-    {
-        this.searchWithFilter(params["criteria"]);
-    }
+        showSearch();
+
+        $('#searchResultsTable thead').html("");
+        $('#searchResultsTable tbody').html("");
+        $('#searchResultsTable tfoot').html("");
+        $("#new").click(function(){
+            location.hash="tab="+obj+"&facet=add";
+        });
+        $("#query").click(executeSearch);
+        this.buildColumnHeaders();
+        var params = ipa_parse_qs();
+        qs = location.hash.substring(1);
+        //TODO fix this hack.  since parse returns an object, I don't know how to see if that object has a"critia" property if criteria is null.
+        if (qs.indexOf("criteria") > 0)
+        {
+            this.searchWithFilter(params["criteria"]);
+        }
     }
 
     this.obj = obj;
     this.method = method;
     this.columns = cols;
-    this.searchSampleData = searchSampleData;
-
     this.setup();
 }
 executeSearch = function(){
diff --git a/install/static/user.js b/install/static/user.js
index 71bf3e5..190afca 100644
--- a/install/static/user.js
+++ b/install/static/user.js
@@ -49,7 +49,7 @@ function setupUser(facet){
     }else  if (facet == "group"){
         setupUserGroupList();
     }else  if (facet == "groupmembership"){
-        setupUserGroupMembership();
+        userGroupMembershipForm.setup();
     }else{
         userSearchForm.setup();
     }
@@ -94,29 +94,13 @@ var userBuilder =
         });
 
 
-function setupFacetNavigation(pkey,facet){
-    $("#viewtype").html("");
-    var facets = ["details","group", "groupmembership"];
-
-    for (var i =0; i < facets.length; i++){
-        var li = $('<li>').appendTo($("#viewtype"));
-        if (facets[i] == facet){
-            $('<img src="but-selected.png" alt="" />');
-            li.html(facets[i]);
-        }else{
-            $('<img src="but-unselected.png" alt="" />').appendTo(li);
-            $('<a/>',{
-                href: "#tab=user&facet="+facets[i]+"&pkey="+pkey,
-                html: facets[i]
-            }).appendTo(li);
-        }
-    }
-}
+var userFacets = ["details","group", "groupmembership"];
+
 
 function setupUserDetails(user){
     qs = ipa_parse_qs();
-    setupFacetNavigation(qs.pkey,qs.facet);
     showDetails();
+    setupFacetNavigation('user',qs.pkey,qs.facet,userFacets);
     renderUserDetails();
 }
 
@@ -128,7 +112,7 @@ function renderUserDetails()
     if (qs['principal']) {
         ipa_cmd(
             'find', [], {'krbprincipalname': qs['principal']},
-            on_win_find, null, 'user', "sampledata/usershow.json");
+            on_win_find, null, 'user');
 
         return;
     }
@@ -136,7 +120,7 @@ function renderUserDetails()
     if (!qs['pkey'])
         return;
 
-    ipa_details_load(qs['pkey'], on_win, null, "sampledata/usershow.json");
+    ipa_details_load(qs['pkey'], on_win, null);
     $('h1').text('Managing user: ' + qs['pkey']);
 }
 
@@ -188,7 +172,7 @@ var userSearchColumns  = [
     {title:"Actions",  column:"none",           render: renderUserLinks}
 ];
 
-var userSearchForm = new SearchForm("user", "find", userSearchColumns,  "sampledata/userlist.json");
+var userSearchForm = new SearchForm("user", "find", userSearchColumns);
 
 /*Usr group enrollement:
   given a user, manage the groups in which they are enrolled */
@@ -196,146 +180,26 @@ function populateUserGroupFailure(){
     alert("Can't find user");
 }
 
-function setupUserGroupMembership(pkey){
-    sampleData = "sampledata/usershow.json";
-    showAssociations();
-    qs = ipa_parse_qs();
-    setupFacetNavigation(qs['pkey'],qs['facet']);
-
-    $('h1').text('Enroll user ' + qs['pkey'] + ' in groups');
-
-    $("#enrollGroups").click(function(){
-        groupsToEnroll =  [];
-        $('#enrollments').children().each(function(i, selected){
-            groupsToEnroll.push(selected.value);
-        });
-
-        currentUserToEnroll = qs['pkey'];
-        enrollUserInNextGroup();
-    });
-
-    $("#addToList").click(function(){
-        $('#grouplist :selected').each(function(i, selected){
-            $("#enrollments").append(selected);
-        });
-        $('#grouplist :selected').remove();
-    });
-
-    $("#removeFromList").click(function(){
-        $('#enrollments :selected').each(function(i, selected){
-            $("#grouplist").append(selected);
-        });
-        $('#enrollments :selected').remove();
-    });
-
-    $("#find").click(function(){
-        ipa_cmd( 'find', [], {}, populateUserGroupSearch, populateUserGroupFailure, 'group', "sampledata/grouplist.json" );
-
-    });
-}
-
-function populateUserGroupSearch(searchResults){
-    results = searchResults.result;
-    $("#grouplist").html("");
-    for (var i =0; i != searchResults.result.count; i++){
-        var li = document.createElement("option");
-        li.value = searchResults.result.result[i].cn;
-        li.innerHTML = searchResults.result.result[i].cn;
-        $("#grouplist").append(li);
-    }
-}
 
-var currentUserToEnroll;
-var groupsToEnroll;
 
-function enrollUserInGroupSuccess(response){
-    enrollUserInNextGroup();
-}
 
-function enrollUserInGroupFailure(response){
-    alert("enrollUserInGroupFailure");
-}
-
-function enrollUserInNextGroup(){
-    var  currentGroupToEnroll =     groupsToEnroll.shift();
-
-    if (currentGroupToEnroll){
-        var options = {"user":currentUserToEnroll};
-        var args = [currentGroupToEnroll];
-
-        ipa_cmd( 'add_member',args, options ,
-                 enrollUserInGroupSuccess,
-                 enrollUserInGroupFailure,'group' );
-    }else{
-        location.hash="tab=user&facet=group&pkey="+qs.pkey;
-    }
-}
-
-function renderUserGroupColumn(){
-}
-
-/*Group Membership&*/
-
-function  renderUserGroupColumn(current,cell){
-    cell.innerHTML = "Nothing to see here";
+var userGroupMembershipForm = new AssociationForm("user","group","groupmembership",userFacets, "cn", function(){
+    return 'Enroll user '  + qs['pkey'] + ' in groups';
 }
+);
 
+/*Group Membership*/
 var groupMembershipColumns  = [
-    {title:"Group",       column:"cn",        render: renderUserGroupColumn},
-    {title:"GID",         column:"gid",       render: renderUserGroupColumn},
-    {title:"Description", column:"uidnumber", render: renderUserGroupColumn},
-
+    {title:"Group", column:"memberof_group"},
 ];
 
-
 function populateUserEnrollments(userData){
-
-    var memberof_group = userData.result.result.memberof_group
-    for (var j = 0; j < memberof_group.length; j++){
-        var row  = document.createElement("tr");
-
-        var td = document.createElement("td");
-        td.innerHTML = memberof_group[j];
-        row.appendChild(td);
-
-        td = document.createElement("td");
-        td.innerHTML = "TBD";
-        row.appendChild(td);
-
-        var td = document.createElement("td");
-        td.innerHTML = "TBD";
-        row.appendChild(td);
-
-        $('#searchResultsTable thead:last').append(row);
-    }
+    userEnrollmentList.populate(userData);
 }
-
-
-function setupUserGroupList(){
-    qs = ipa_parse_qs();
-    setupFacetNavigation(qs['pkey'],qs['facet']);
-    showSearch();
-
-    $("#filter").css("display","none");
-
-    $("#searchButtons").html("");
-    $("<input/>",{
-        type:  'button',
-        value: 'enroll',
-        click: function(){
-            location.hash="tab=user&facet=groupmembership&pkey="+qs['pkey'];
-        }
-    }).appendTo("#searchButtons");
-    var columnHeaders  = document.createElement("tr");
-    for (var i =0 ; i != groupMembershipColumns.length ;i++){
-        var th = document.createElement("th");
-        th.innerHTML = groupMembershipColumns[i].title;
-        columnHeaders.appendChild(th);
-    }
-    $('#searchResultsTable thead:last').append(columnHeaders);
-
-    ipa_cmd( 'show', [qs['pkey']], {}, populateUserEnrollments, populateUserGroupFailure, 'user',"sampledata/usershow.json" );
+function  setupUserGroupList(){
+    userEnrollmentList.setup();
 }
+var userEnrollmentList = new AssociationList('user', 'group','groupmembership',groupMembershipColumns,userFacets);
 
 
 function on_win(data, textStatus, xhr)
-- 
1.7.1

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to