FELIX-919 : Use new table layout for components panel, fix some layout problems and add new configure option.

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@742728 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ComponentsServlet.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ComponentsServlet.java
index e9ec667..c1e7312 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ComponentsServlet.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ComponentsServlet.java
@@ -19,24 +19,17 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Dictionary;
-import java.util.Iterator;
-import java.util.TreeMap;
-import java.util.TreeSet;
+import java.util.*;
 
+import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.apache.felix.scr.Component;
-import org.apache.felix.scr.Reference;
-import org.apache.felix.scr.ScrService;
+import org.apache.felix.scr.*;
 import org.apache.felix.webconsole.internal.BaseWebConsolePlugin;
 import org.apache.felix.webconsole.internal.Util;
 import org.apache.felix.webconsole.internal.servlet.OsgiManager;
-import org.json.JSONException;
-import org.json.JSONWriter;
+import org.json.*;
 import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceReference;
 import org.osgi.service.component.ComponentConstants;
@@ -57,6 +50,8 @@
 
     public static final String OPERATION_DISABLE = "disable";
 
+    public static final String OPERATION_EDIT = "edit";
+
     private static final String SCR_SERVICE = ScrService.class.getName();
 
 
@@ -74,92 +69,99 @@
 
     protected void doPost( HttpServletRequest request, HttpServletResponse response ) throws IOException
     {
-        ScrService scrService = getScrService();
-        if ( scrService != null )
-        {
-
-            long componentId = getComponentId( request );
-            Component component = scrService.getComponent( componentId );
-
-            if ( component != null )
-            {
-                String op = request.getParameter( OPERATION );
-                if ( OPERATION_ENABLE.equals( op ) )
-                {
-                    component.enable();
-                }
-                else if ( OPERATION_DISABLE.equals( op ) )
-                {
-                    component.disable();
-                }
-
-                sendAjaxDetails( component, response );
-            }
-
+        final RequestInfo reqInfo = new RequestInfo(request);
+        if ( reqInfo.component == null && reqInfo.componentRequested ) {
+            response.setStatus(404);
+            return;
         }
+        if ( !reqInfo.componentRequested ) {
+            response.setStatus(500);
+            return;
+        }
+        String op = request.getParameter( OPERATION );
+        if ( OPERATION_ENABLE.equals( op ) )
+        {
+            reqInfo.component.enable();
+        }
+        else if ( OPERATION_DISABLE.equals( op ) )
+        {
+            reqInfo.component.disable();
+        }
+
+        final PrintWriter pw = response.getWriter();
+        response.setContentType( "application/json" );
+        response.setCharacterEncoding( "UTF-8" );
+        renderResult( pw, null);
     }
 
 
+    protected void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException,
+    IOException {
+        final RequestInfo reqInfo = new RequestInfo(request);
+        if ( reqInfo.component == null && reqInfo.componentRequested ) {
+            response.setStatus(404);
+            return;
+        }
+        if ( reqInfo.extension.equals("json")  )
+        {
+            response.setContentType( "application/json" );
+            response.setCharacterEncoding( "UTF-8" );
+
+            this.renderResult(response.getWriter(), reqInfo.component);
+
+            // nothing more to do
+            return;
+        }
+        super.doGet( request, response );
+    }
+
     protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws IOException
     {
-        PrintWriter pw = response.getWriter();
+        // get request info from request attribute
+        final RequestInfo reqInfo = getRequestInfo(request);
+        final PrintWriter pw = response.getWriter();
 
-        String appRoot = ( String ) request.getAttribute( OsgiManager.ATTR_APP_ROOT );
-        pw.println( "<script src='" + appRoot + "/res/ui/datatable.js' language='JavaScript'></script>" );
+        final String appRoot = ( String ) request.getAttribute( OsgiManager.ATTR_APP_ROOT );
+        Util.script(pw, appRoot, "jquery-1.3.1.min.js");
+        Util.script(pw, appRoot, "jquery.tablesorter-2.0.3.min.js");
 
         Util.startScript( pw );
+        pw.println( "var imgRoot = '" + appRoot + "/res/imgs';");
+        pw.println( "var drawDetails = " + reqInfo.componentRequested + ";");
+        Util.endScript( pw );
 
-        pw.print( "var components = " );
-        renderResult( request, pw );
-        pw.println( ";" );
+        Util.script(pw, appRoot, "components.js");
 
-        pw.println( "renderDataTable( components );" );
-
+        pw.println( "<div id='plugin_content'/>");
+        Util.startScript( pw );
+        pw.print( "renderComponents(");
+        renderResult( pw, reqInfo.component );
+        pw.println(");" );
         Util.endScript( pw );
     }
 
 
-    private void renderResult( HttpServletRequest request, PrintWriter pw ) throws IOException
+    private void renderResult( final PrintWriter pw,
+                               final Component component) throws IOException
     {
-        JSONWriter jw = new JSONWriter( pw );
+        final JSONWriter jw = new JSONWriter( pw );
         try
         {
             jw.object();
 
-            jw.key( "numActions" );
-            jw.value( 2 );
-
-            ScrService scrService = getScrService();
+            final ScrService scrService = getScrService();
             if ( scrService == null )
             {
-                jw.key( "error" );
+                jw.key( "status" );
                 jw.value( "Apache Felix Declarative Service required for this function" );
             }
             else
             {
-                Component[] components = null;
-                boolean details = false;
-
-                long componentId = getComponentId( request );
-                if ( componentId >= 0 )
-                {
-                    Component component = scrService.getComponent( componentId );
-                    if ( component != null )
-                    {
-                        components = new Component[]
-                            { component };
-                        details = true;
-                    }
-                }
-
-                if ( components == null )
-                {
-                    components = scrService.getComponents();
-                }
+                final Component[] components = scrService.getComponents();
 
                 if ( components == null || components.length == 0 )
                 {
-                    jw.key( "error" );
+                    jw.key( "status" );
                     jw.value( "No components installed currently" );
                 }
                 else
@@ -168,16 +170,33 @@
                     TreeMap componentMap = new TreeMap();
                     for ( int i = 0; i < components.length; i++ )
                     {
-                        Component component = components[i];
-                        componentMap.put( component.getName(), component );
+                        Component c = components[i];
+                        componentMap.put( c.getName(), c );
                     }
 
+                    final StringBuffer buffer = new StringBuffer();
+                    buffer.append(componentMap.size());
+                    buffer.append(" component");
+                    if ( componentMap.size() != 1 ) {
+                        buffer.append('s');
+                    }
+                    buffer.append(" installed.");
+                    jw.key("status");
+                    jw.value(buffer.toString());
+
                     // render components
                     jw.key( "data" );
                     jw.array();
-                    for ( Iterator ci = componentMap.values().iterator(); ci.hasNext(); )
+                    if ( component != null )
                     {
-                        component( jw, ( Component ) ci.next(), details );
+                        component( jw, component, true );
+                    }
+                    else
+                    {
+                        for ( Iterator ci = componentMap.values().iterator(); ci.hasNext(); )
+                        {
+                            component( jw, ( Component ) ci.next(), false );
+                        }
                     }
                     jw.endArray();
                 }
@@ -191,28 +210,6 @@
         }
     }
 
-
-    private void sendAjaxDetails( Component component, HttpServletResponse response ) throws IOException
-    {
-
-        // send the result
-        response.setContentType( "text/javascript" );
-
-        JSONWriter jw = new JSONWriter( response.getWriter() );
-        try
-        {
-            if ( component != null )
-            {
-                component( jw, component, true );
-            }
-        }
-        catch ( JSONException je )
-        {
-            throw new IOException( je.toString() );
-        }
-    }
-
-
     private void component( JSONWriter jw, Component component, boolean details ) throws JSONException
     {
         String id = String.valueOf( component.getId() );
@@ -229,27 +226,28 @@
         jw.key( "state" );
         jw.value( toStateString( state ) );
 
+        final String pid = ( String ) component.getProperties().get( Constants.SERVICE_PID );
+        if ( pid != null )
+        {
+            jw.key("pid");
+            jw.value(pid);
+        }
         // component actions
         jw.key( "actions" );
         jw.array();
 
-        jw.object();
-        jw.key( "name" );
-        jw.value( "Enable" );
-        jw.key( "link" );
-        jw.value( OPERATION_ENABLE );
-        jw.key( "enabled" );
-        jw.value( state == Component.STATE_DISABLED );
-        jw.endObject();
-
-        jw.object();
-        jw.key( "name" );
-        jw.value( "Disable" );
-        jw.key( "link" );
-        jw.value( OPERATION_DISABLE );
-        jw.key( "enabled" );
-        jw.value( state != Component.STATE_DISABLED && state != Component.STATE_DESTROYED );
-        jw.endObject();
+        if ( state == Component.STATE_DISABLED )
+        {
+            action(jw, true, OPERATION_ENABLE, "Enable", "enable" );
+        }
+        if ( state != Component.STATE_DISABLED && state != Component.STATE_DESTROYED )
+        {
+            action(jw, true, OPERATION_DISABLE, "Disable", "disable" );
+        }
+        if ( pid != null )
+        {
+            action(jw, true, OPERATION_EDIT, "Edit", "edit" );
+        }
 
         jw.endArray();
 
@@ -262,6 +260,15 @@
         jw.endObject();
     }
 
+    private void action( JSONWriter jw, boolean enabled, String op, String opLabel, String image ) throws JSONException
+    {
+        jw.object();
+        jw.key( "enabled" ).value( enabled );
+        jw.key( "name" ).value( opLabel );
+        jw.key( "link" ).value( op );
+        jw.key( "image" ).value( image );
+        jw.endObject();
+    }
 
     private void gatherComponentDetails( JSONWriter jw, Component component ) throws JSONException
     {
@@ -291,17 +298,13 @@
 
         keyVal( jw, "Service Type", component.isServiceFactory() ? "service factory" : "service" );
 
-        StringBuffer buf = new StringBuffer();
+        JSONArray buf = new JSONArray();
         for ( int i = 0; i < services.length; i++ )
         {
-            if ( i > 0 )
-            {
-                buf.append( "<br />" );
-            }
-            buf.append( services[i] );
+            buf.put( services[i] );
         }
 
-        keyVal( jw, "Services", buf.toString() );
+        keyVal( jw, "Services", buf );
     }
 
 
@@ -312,16 +315,16 @@
         {
             for ( int i = 0; i < refs.length; i++ )
             {
-                StringBuffer buf = new StringBuffer();
-                buf.append( refs[i].isSatisfied() ? "Satisfied" : "Unsatisfied" ).append( "<br />" );
-                buf.append( "Service Name: " ).append( refs[i].getServiceName() ).append( "<br />" );
+                JSONArray buf = new JSONArray();
+                buf.put( refs[i].isSatisfied() ? "Satisfied" : "Unsatisfied" );
+                buf.put( "Service Name: " + refs[i].getServiceName());
                 if ( refs[i].getTarget() != null )
                 {
-                    buf.append( "Target Filter: " ).append( refs[i].getTarget() ).append( "<br />" );
+                    buf.put( "Target Filter: " + refs[i].getTarget() );
                 }
-                buf.append( "Multiple: " ).append( refs[i].isMultiple() ? "multiple" : "single" ).append( "<br />" );
-                buf.append( "Optional: " ).append( refs[i].isOptional() ? "optional" : "mandatory" ).append( "<br />" );
-                buf.append( "Policy: " ).append( refs[i].isStatic() ? "static" : "dynamic" ).append( "<br />" );
+                buf.put( "Multiple: " + (refs[i].isMultiple() ? "multiple" : "single" ));
+                buf.put( "Optional: " + (refs[i].isOptional() ? "optional" : "mandatory" ));
+                buf.put( "Policy: " + (refs[i].isStatic() ? "static" : "dynamic" ));
 
                 // list bound services
                 ServiceReference[] boundRefs = refs[i].getServiceReferences();
@@ -329,13 +332,9 @@
                 {
                     for ( int j = 0; j < boundRefs.length; j++ )
                     {
-                        if ( j > 0 )
-                        {
-                            buf.append( "<br />" );
-                        }
-
-                        buf.append( "Bound Service ID " );
-                        buf.append( boundRefs[j].getProperty( Constants.SERVICE_ID ) );
+                        final StringBuffer b = new StringBuffer();
+                        b.append( "Bound Service ID " );
+                        b.append( boundRefs[j].getProperty( Constants.SERVICE_ID ) );
 
                         String name = ( String ) boundRefs[j].getProperty( ComponentConstants.COMPONENT_NAME );
                         if ( name == null )
@@ -348,17 +347,17 @@
                         }
                         if ( name != null )
                         {
-                            buf.append( " (" );
-                            buf.append( name );
-                            buf.append( ")" );
+                            b.append( " (" );
+                            b.append( name );
+                            b.append( ")" );
                         }
+                        buf.put(b.toString());
                     }
                 }
                 else
                 {
-                    buf.append( "No Services bound" );
+                    buf.put( "No Services bound" );
                 }
-                buf.append( "<br />" );
 
                 keyVal( jw, "Reference " + refs[i].getName(), buf.toString() );
             }
@@ -371,26 +370,24 @@
         Dictionary props = component.getProperties();
         if ( props != null )
         {
-            StringBuffer buf = new StringBuffer();
+            JSONArray buf = new JSONArray();
             TreeSet keys = new TreeSet( Collections.list( props.keys() ) );
             for ( Iterator ki = keys.iterator(); ki.hasNext(); )
             {
-                String key = ( String ) ki.next();
-                buf.append( key ).append( " = " );
+                final String key = ( String ) ki.next();
+                final StringBuffer b = new StringBuffer();
+                b.append( key ).append( " = " );
 
                 Object prop = props.get( key );
                 if ( prop.getClass().isArray() )
                 {
                     prop = Arrays.asList( ( Object[] ) prop );
                 }
-                buf.append( prop );
-                if ( ki.hasNext() )
-                {
-                    buf.append( "<br />" );
-                }
+                b.append( prop );
+                buf.put(b.toString());
             }
 
-            keyVal( jw, "Properties", buf.toString() );
+            keyVal( jw, "Properties", buf );
         }
 
     }
@@ -445,31 +442,75 @@
     }
 
 
-    protected long getComponentId( HttpServletRequest request )
-    {
-        String componentIdPar = request.getParameter( ComponentsServlet.COMPONENT_ID );
-        if ( componentIdPar == null )
-        {
-            String info = request.getPathInfo();
-            componentIdPar = info.substring( info.lastIndexOf( '/' ) + 1 );
-        }
-
-        try
-        {
-            return Long.parseLong( componentIdPar );
-        }
-        catch ( NumberFormatException nfe )
-        {
-            // TODO: log
-        }
-
-        // no bundleId or wrong format
-        return -1;
-    }
-
-
     private ScrService getScrService()
     {
         return ( ScrService ) getService( SCR_SERVICE );
     }
+
+    private final class RequestInfo
+    {
+        public final String extension;
+        public final Component component;
+        public final boolean componentRequested;
+
+        protected long getComponentId( final String componentIdPar )
+        {
+            try
+            {
+                return Long.parseLong( componentIdPar );
+            }
+            catch ( NumberFormatException nfe )
+            {
+                // TODO: log
+            }
+
+            // no bundleId or wrong format
+            return -1;
+        }
+
+        protected RequestInfo( final HttpServletRequest request )
+        {
+            String info = request.getPathInfo();
+            // remove label and starting slash
+            info = info.substring(getLabel().length() + 1);
+
+            // get extension
+            if ( info.endsWith(".json") )
+            {
+                extension = "json";
+                info = info.substring(0, info.length() - 5);
+            }
+            else
+            {
+                extension = "html";
+            }
+
+            long componentId = getComponentId(info.substring(info.lastIndexOf('/') + 1));
+            if ( componentId == -1 )
+            {
+                componentRequested = false;
+                component = null;
+            }
+            else
+            {
+                componentRequested = true;
+                final ScrService scrService = getScrService();
+                if ( scrService != null )
+                {
+                    component = scrService.getComponent( componentId );
+                }
+                else
+                {
+                    component = null;
+                }
+            }
+            request.setAttribute(ComponentsServlet.class.getName(), this);
+        }
+
+    }
+
+    public static RequestInfo getRequestInfo(final HttpServletRequest request)
+    {
+        return (RequestInfo)request.getAttribute(ComponentsServlet.class.getName());
+    }
 }
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java
index 4133ed7..f8968f3 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java
@@ -114,7 +114,6 @@
             return;
         }
 
-
         super.doGet( request, response );
     }
 
diff --git a/webconsole/src/main/resources/res/imgs/component_disable.png b/webconsole/src/main/resources/res/imgs/component_disable.png
new file mode 100644
index 0000000..e6f75d2
--- /dev/null
+++ b/webconsole/src/main/resources/res/imgs/component_disable.png
Binary files differ
diff --git a/webconsole/src/main/resources/res/imgs/component_edit.png b/webconsole/src/main/resources/res/imgs/component_edit.png
new file mode 100644
index 0000000..67817e6
--- /dev/null
+++ b/webconsole/src/main/resources/res/imgs/component_edit.png
Binary files differ
diff --git a/webconsole/src/main/resources/res/imgs/component_enable.png b/webconsole/src/main/resources/res/imgs/component_enable.png
new file mode 100644
index 0000000..f8c8ec6
--- /dev/null
+++ b/webconsole/src/main/resources/res/imgs/component_enable.png
Binary files differ
diff --git a/webconsole/src/main/resources/res/ui/admin.css b/webconsole/src/main/resources/res/ui/admin.css
index 8e7a2c0..37f201c 100644
--- a/webconsole/src/main/resources/res/ui/admin.css
+++ b/webconsole/src/main/resources/res/ui/admin.css
@@ -568,11 +568,14 @@
 div.buttons {
     background-color: #6181A9;
     height: 30px;
+    text-align: right;
+    vertical-align: middle;
+    padding-right: 10px;
+    padding-top: 8px;
 }
 div.button {
-	float:right;
-    margin-top: 5px;
-	margin-right: 10px;
+    display: inline-block;
+    margin-left: 10px;
 }
 div.fullwidth {
     width: 100%;
@@ -582,7 +585,9 @@
     background-color: #F5F5F5;
     padding-left: 10px;
     border: 1px solid #CCCCCC;
-    font-size: 13px;
+    font-family: Verdana, Arial, Helvetica, san-serif;
+    font-size: 11px;
+    font-weight: normal;
     color: #222222;
     line-height: 19px;
     margin-bottom: 10px;
diff --git a/webconsole/src/main/resources/res/ui/bundles.js b/webconsole/src/main/resources/res/ui/bundles.js
index 830af0d..75ec6dd 100644
--- a/webconsole/src/main/resources/res/ui/bundles.js
+++ b/webconsole/src/main/resources/res/ui/bundles.js
@@ -32,9 +32,8 @@
 }
 
 function renderButtons( buttons ) {
-	$("#plugin_content").append( "<div class='fullwidth'><div class='buttons'>" +
-	                             "<form method='post' enctype='multipart/form-data'><div style='padding-top:5px;'>" +
-	                             buttons + "</div></form></div></div>" );
+	$("#plugin_content").append( "<form method='post' enctype='multipart/form-data'><div class='fullwidth'><div class='buttons'>" +
+	                             buttons + "</div></div></form>" );
 }
 
 function renderData( eventData )  {
@@ -87,8 +86,8 @@
     	src: appRoot + "/res/imgs/arrow_right.png",
     	border: "none",
     	id: 'img' + id,
-    	title: "Back",
-    	alt: "Back",
+    	title: "Details",
+    	alt: "Details",
     	width: 14,
     	height: 14,
         onClick: 'showDetails(' + id + ');'
diff --git a/webconsole/src/main/resources/res/ui/components.js b/webconsole/src/main/resources/res/ui/components.js
new file mode 100644
index 0000000..c6b1fac
--- /dev/null
+++ b/webconsole/src/main/resources/res/ui/components.js
@@ -0,0 +1,226 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+function renderStatusLine() {
+	$("#plugin_content").append( "<div class='fullwidth'><div class='statusline'/></div>" );
+}
+
+function renderView( /* Array of String */ columns, /* Array of String */ buttons ) {
+    renderStatusLine();
+    renderButtons(buttons);
+    var txt = "<div class='table'><table id='plugin_table' class='tablelayout'><thead><tr>";
+    for ( var name in columns ) {
+    	txt = txt + "<th class='col_" + columns[name] + "'>" + columns[name] + "</th>";
+    }
+    txt = txt + "</tr></thead><tbody></tbody></table></div>";
+    $("#plugin_content").append( txt );
+    renderButtons(buttons);
+    renderStatusLine();	
+}
+
+function renderButtons( buttons ) {
+	$("#plugin_content").append( "<form method='post' enctype='multipart/form-data'><div class='fullwidth'><div class='buttons'>" +
+	                             buttons + "</div></div></form>" );
+}
+
+function renderData( eventData )  {
+	$(".statusline").empty().append(eventData.status);
+	$("#plugin_table > tbody > tr").remove();
+    for ( var idx in eventData.data ) {
+        entry( eventData.data[idx] );
+    }
+    $("#plugin_table").trigger("update");
+    if ( drawDetails ) {
+	    renderDetails(eventData);
+    }
+}
+
+function entry( /* Object */ dataEntry ) {
+    var trElement = tr( null, { id: "entry" + dataEntry.id } );
+    entryInternal( trElement,  dataEntry );
+	$("#plugin_table > tbody").append(trElement);	
+}
+
+function actionButton( /* Element */ parent, /* string */ id, /* Obj */ action, /* string */ pid ) {
+	var enabled = action.enabled;
+	var op = action.link;
+	var opLabel = action.name;
+	var img = action.image;
+	
+	var arg = id;
+	if ( op == "edit" ) {
+		arg = pid
+	}
+	var input = createElement( "input", null, {
+            type: 'image',
+            title: opLabel,
+            alt: opLabel,
+            src: imgRoot + '/component_' + img + '.png',
+            onClick: 'changeDataEntryState("' + arg + '", "' + op + '");'
+        });
+		
+    if (!enabled) {
+        input.setAttribute( "disabled", true );
+    }
+    var div = createElement("div");
+    div.setAttribute("style", "float:left; margin-left:10px;");
+    div.appendChild(input);
+    parent.appendChild( div );
+}
+
+function entryInternal( /* Element */ parent, /* Object */ dataEntry ) {
+    var id = dataEntry.id;
+    var name = dataEntry.name;
+    var state = dataEntry.state;
+    
+    var inputElement = createElement("img", "rightButton", {
+    	src: appRoot + "/res/imgs/arrow_right.png",
+    	border: "none",
+    	id: 'img' + id,
+    	title: "Details",
+    	alt: "Details",
+    	width: 14,
+    	height: 14,
+        onClick: 'showDetails(' + id + ');'
+    });
+    var titleElement;
+    if ( drawDetails ) {
+    	titleElement = text(name);
+    } else {
+        titleElement = createElement ("a", null, {
+    	    href: window.location.pathname + "/" + id
+        });
+        titleElement.appendChild(text(name));
+    }
+    
+    parent.appendChild( td( null, null, [ text( id ) ] ) );
+    parent.appendChild( td( null, null, [ inputElement, text(" "), titleElement ] ) );
+    parent.appendChild( td( null, null, [ text( state ) ] ) );
+    var actionsTd = td( null, null );
+    
+    for ( var a in dataEntry.actions ) {
+    	actionButton( actionsTd, id, dataEntry.actions[a], dataEntry.pid );
+    }
+    parent.appendChild( actionsTd );
+}
+
+function changeDataEntryState(/* long */ id, /* String */ action) {
+	if ( action == "edit") {
+		window.location = appRoot + "/configMgr/" + id;
+		return;
+	}
+	$.post(pluginRoot + "/" + id, {"action":action}, function(data) {
+	    renderData(data);
+	}, "json");	
+}
+
+function showDetails( id ) {
+    $.get(pluginRoot + "/" + id + ".json", null, function(data) {
+    	renderDetails(data);
+    }, "json");
+}
+
+function loadData() {
+	$.get(pluginRoot + "/.json", null, function(data) {
+	    renderData(data);
+	}, "json");	
+}
+
+function hideDetails( id ) {
+	$("#img" + id).each(function() {
+		$("#pluginInlineDetails").remove();
+        this.setAttribute("src", appRoot + "/res/imgs/arrow_right.png");
+        this.setAttribute("onClick", "showDetails('" + id + "')");
+        this.setAttribute("title", "Details");
+        this.setAttribute("alt", "Details");
+	});
+}
+
+function renderDetails( data ) {
+	data = data.data[0];
+	$("#entry" + data.id + " > td").eq(1).append("<div id='pluginInlineDetails'/>");
+	$("#img" + data.id).each(function() {
+		if ( drawDetails ) {
+            this.setAttribute("src", appRoot + "/res/imgs/arrow_left.png");
+    	    var ref = window.location.pathname;
+    	    ref = ref.substring(0, ref.lastIndexOf('/'));
+            this.setAttribute("onClick", "window.location = '" + ref + "';");
+            this.setAttribute("title", "Back");
+            this.setAttribute("alt", "Back");
+		} else {
+            this.setAttribute("src", appRoot + "/res/imgs/arrow_down.png");
+            this.setAttribute("onClick", "hideDetails('" + data.id + "')");
+            this.setAttribute("title", "Hide Details");
+            this.setAttribute("alt", "Hide Details");
+		}
+	});
+	$("#pluginInlineDetails").append("<table border='0'><tbody></tbody></table>");
+    var details = data.props;
+    for (var idx in details) {
+        var prop = details[idx];
+        
+        var txt = "<tr><td class='aligntop' noWrap='true' style='border:0px none'>" + prop.key + "</td><td class='aligntop' style='border:0px none'>";	        
+        if (prop.value) {
+    		if ( $.isArray(prop.value) ) {
+        		var i = 0;
+        		for(var pi in prop.value) {
+        			var value = prop.value[pi];
+	                if (i > 0) { txt = txt + "<br/>"; }
+	                var span;
+	                if (value.substring(0, 2) == "!!") {
+	                	txt = txt + "<span style='color: red;'>" + value + "</span>";
+	                } else {
+	                	txt = txt + value;
+	                }
+	                i++;
+        		}
+    		} else {
+    			txt = txt + prop.value;
+    		}
+        } else {
+        	txt = txt + "\u00a0";
+        }
+        txt = txt + "</td></tr>";
+        $("#pluginInlineDetails > table > tbody").append(txt);
+	}
+}
+
+function renderComponents(data) {
+	$(document).ready(function(){
+    	renderView( ["Id", "Name", "Status", "Actions"],
+        		"<div class='button'><button class='reloadButton' type='button' name='reload'>Reload</button></div>");
+        renderData(data);
+        
+        $(".reloadButton").click(loadData);
+
+        var extractMethod = function(node) {
+        	var link = node.getElementsByTagName("a");
+            if ( link && link.length == 1 ) {
+            	return link[0].innerHTML;
+            }
+            return node.innerHTML;
+        };
+        $("#plugin_table").tablesorter({
+            headers: {
+        	    0: { sorter:"digit"},
+                3: { sorter: false }
+            },
+            sortList: [[1,0]],
+            textExtraction:extractMethod 
+        });
+    });
+}
+ 
diff --git a/webconsole/src/main/resources/res/ui/datatable.js b/webconsole/src/main/resources/res/ui/datatable.js
deleted file mode 100644
index 9158026..0000000
--- a/webconsole/src/main/resources/res/ui/datatable.js
+++ /dev/null
@@ -1,334 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-function renderDataTable( /* Array of Data Objects */ components )
-{
-    // number of actions plus 3 -- id, name and state
-    var columns = components.numActions + 3;
-    
-    header( columns );
-
-    if (components.error)
-    {
-        error( columns, components.error );
-    }
-    else
-    {
-        data ( components.data );
-    }
-
-    footer( columns );
-}
-
-
-function header( /* int */ columns )
-{
-    document.write( "<table class='content' cellpadding='0' cellspacing='0' width='100%'>" );
-
-    document.write( "<tr class='content'>" );
-    document.write( "<td colspan='" + columns + "' class='content'>&nbsp;</th>" );
-    document.write( "</tr>" );
-
-    document.write( "<tr class='content'>" );
-    document.write( "<th class='content'>ID</th>" );
-    document.write( "<th class='content' width='100%'>Name</th>" );
-    document.write( "<th class='content'>Status</th>" );
-    document.write( "<th class='content' colspan='" + (columns - 3) + "'>Actions</th>" );
-    document.write( "</tr>" );
-
-}
-
-
-function error( /* int */ columns, /* String */ message )
-{
-    document.write( "<tr class='content'>" );
-    document.write( "<td class='content'>&nbsp;</td>" );
-    document.write( "<td class='content' colspan='" + (columns - 1) + "'>" + message + "</td>" );
-    document.write( "</tr>" );
-}
-
-
-function data( /* Array of Object */ dataArray )
-{
-    // render components
-    if (dataArray.length == 1)
-    {
-        entry( dataArray[0], true );
-    }
-    else {
-        for ( var idx in dataArray )
-        {
-            entry( dataArray[idx] );
-        }
-    }
-}
-
-
-function footer( /* int */ columns )
-{
-    document.write( "<tr class='content'>" );
-    document.write( "<td colspan='" + columns + "' class='content'>&nbsp;</th>" );
-    document.write( "</tr>" );
-
-    document.write( "</table>" );
-}
-
-
-function entry( /* Object */ dataEntry, /* boolean */ singleEntry )
-{
-    var trElement = tr( null, { id: "entry" + dataEntry.id } );
-    entryInternal( trElement,  dataEntry, singleEntry );
-    document.write( serialize( trElement ) );
-
-    // dataEntry detailed properties
-    trElement = tr( null, { id: "entry" + dataEntry.id + "_details" } );
-    if (dataEntry.props)
-    {
-        getDataEntryDetails( trElement, dataEntry.props );
-    }
-    document.write( serialize( trElement ) );
-}
-
-
-function entryInternal( /* Element */ parent, /* Object */ dataEntry, /* boolean */ singleEntry )
-{
-
-    var id = dataEntry.id;
-    var name = dataEntry.name;
-    var state = dataEntry.state;
-    var icon = singleEntry ? "left" : (dataEntry.props ? "down" : "right");
-    var event = singleEntry ? "history.back()" : "showDataEntryDetails(" + id + ")"; 
-
-    parent.appendChild( td( "content right", null, [ text( id ) ] ) );
-    
-    parent.appendChild( td( "content", null, [
-            createElement( "img", null, {
-                src: appRoot + "/res/imgs/" + icon + ".gif",
-                onClick: event,
-                id: "entry" + id + "_inline"
-            } ),
-            text( "\u00a0" ),
-            createElement( "a", null, {
-                href: pluginRoot + "/" + id
-            }, [ text( name ) ]
-            )]
-        )
-    );
-
-    parent.appendChild( td( "content center", null, [ text( state ) ] ) );
-
-    for ( var aidx in dataEntry.actions )
-    {
-        var action = dataEntry.actions[aidx];
-        parent.appendChild( actionButton( action.enabled, id, action.link, action.name, action.title ) );
-    }
-}
-
-
-/* Element */ function actionButton( /* boolean */ enabled, /* long */ id, /* String */ op, /* String */ opLabel, /* String */ title )
-{
-    var buttonTd = td( "content", { align: "right" } );
-    if ( op )
-    {
-    	var input;
-    	if ( title ) {
-	        input = createElement( "input", null, {
-                type: 'image',
-                alt: opLabel,
-                src: imgRoot + '/bundle_' + title + '.gif', 
-                onClick: 'changeDataEntryState(' + id + ', "' + op + '");'
-            });
-    		
-    	} else {
-	        var input = createElement( "input", "submit", {
-	                type: 'button',
-	                value: opLabel,
-	                onClick: 'changeDataEntryState(' + id + ', "' + op + '");'
-	            });
-    	}
-        if (!enabled)
-        {
-            input.setAttribute( "disabled", true );
-        }
-        buttonTd.appendChild( input );
-    }
-    else
-    {
-        addText( buttonTd, "\u00a0" );
-    }
-    
-    return buttonTd;
-}
-
-
-function getDataEntryDetails( /* Element */ parent, /* Array of Object */ details )
-{
-    parent.appendChild( addText( td( "content"), "\u00a0" ) );
-    
-    var tdEl = td( "content", { colspan: 4 } );
-    parent.appendChild( tdEl );
-    
-    var tableEl = createElement( "table", null, { border: 0 } );
-    tdEl.appendChild( tableEl );
-    
-    var tbody = createElement( "tbody" );
-    tableEl.appendChild( tbody );
-    for (var idx in details)
-    {
-        var prop = details[idx];
-        
-        
-        var trEl = tr();
-        trEl.appendChild( addText( td( "aligntop", { noWrap: true } ), prop.key ) );
-
-        var proptd = td( "aligntop" );
-        trEl.appendChild( proptd );
-        
-        if (prop.value)
-        {
-            var values = new String( prop.value ).split( "<br />" );
-            for (var i=0; i < values.length; i++)
-            {
-                if (i > 0) { proptd.appendChild( createElement( "br" ) ); }
-                
-                var span;
-                if (values[i].substring(0, 2) == "!!") {
-                    span = createElement( "span", null, { style: { color: "red" } } );
-                    proptd.appendChild( span );
-                } else {
-                    span = proptd;
-                }
-                
-                addText( span, values[i] );
-            }
-        }
-        else
-        {
-            addText( proptd, "\u00a0" );
-        }
-
-        tbody.appendChild( trEl );
-    }
- }
-
- 
-function showDetails(bundleId)
-{
-    var span = document.getElementById('bundle' + bundleId + '_details');
-}
-
-
-function showDataEntryDetails( id )
-{
-    var span = document.getElementById( 'entry' + id + '_details' );
-    if (span)
-    {
-        if (span.firstChild)
-        {
-            clearChildren( span );
-            newLinkValue( id, appRoot + "/res/imgs/right.gif" );
-        }
-        else
-        {
-            sendRequest( 'POST', pluginRoot + '/' + id, displayDataEntryDetails );
-            newLinkValue( id, appRoot + "/res/imgs/down.gif" );
-        }
-    }
-}
-
-
-function newLinkValue( /* long */ id, /* String */ newLinkValue )
-{
-    
-    var link = document.getElementById( "entry" + id + "_inline" );
-    if (link)
-    {
-        link.src = newLinkValue;
-    }
-}
-
-
-function displayDataEntryDetails( obj )
-{
-    var span = document.getElementById('entry' + obj.id + '_details');
-    if (span)
-    {
-        clearChildren( span );
-        getDataEntryDetails( span, obj.props );
-    }
-    
-}
-
-
-function changeDataEntryState(/* long */ id, /* String */ action)
-{
-    var parm = pluginRoot + "/" + id + "?action=" + action;
-    sendRequest('POST', parm, dataEntryStateChanged);
-}
-
-    
-function dataEntryStateChanged(obj)
-{
-    if (obj.reload)
-    {
-        document.location = document.location;
-    }
-    else
-    {
-        var id = obj.id;
-        if (obj.state)
-        {
-            // has status, so draw the line
-            if (obj.props)
-            {
-                var span = document.getElementById('entry' + id + '_details');
-                if (span && span.firstChild)
-                {
-                    clearChildren( span );
-                    getDataEntryDetails( span, obj.props );
-                }
-                else
-                {
-                    obj.props = false;
-                }
-            }
-
-            var span = document.getElementById('entry' + id);
-            if (span)
-            {
-                clearChildren( span );
-                entryInternal( span, obj );
-            }
-        }
-        else
-        {
-            // no status, dataEntry has been removed/uninstalled 
-            var span = document.getElementById('entry' + id);
-            if (span)
-            {
-                span.parentNode.removeChild(span);
-            }
-            var span = document.getElementById('entry' + id + '_details');
-            if (span)
-            {
-                span.parentNode.removeChild(span);
-            }
-        }
-    }    
-}
-
-    
diff --git a/webconsole/src/main/resources/res/ui/events.js b/webconsole/src/main/resources/res/ui/events.js
index b888d88..62764c1 100644
--- a/webconsole/src/main/resources/res/ui/events.js
+++ b/webconsole/src/main/resources/res/ui/events.js
@@ -108,14 +108,14 @@
 
 function renderEvents() {
     renderView( ["Received", "Topic", "Properties"],
-    		["<button id='reloadButton' type='button' name='reload'>Reload</button>",
-    		 "<button id='clearButton' type='button' name='clear'>Clear List</button>"]);
+    		["<button class='clearButton' type='button' name='clear'>Clear List</button>",
+    		 "<button class='reloadButton' type='button' name='reload'>Reload</button>"]);
 	
     loadData();
     
     $("#events").tablesorter();
-    $("#reloadButton").click(loadData);
-    $("#clearButton").click(function () {
+    $(".reloadButton").click(loadData);
+    $(".clearButton").click(function () {
     	$("#events > tbody > tr").remove();
     	$.post(pluginRoot, { "action":"clear" }, function(data) {
     	    renderData(data);