diff --git a/etemplate/inc/class.etemplate_et2_widgets.inc.php b/etemplate/inc/class.etemplate_et2_widgets.inc.php
new file mode 100644
index 0000000000..de1fe48429
--- /dev/null
+++ b/etemplate/inc/class.etemplate_et2_widgets.inc.php
@@ -0,0 +1,67 @@
+ true
+ );
+
+ public static function index($content = array())
+ {
+ $GLOBALS['egw_info']['flags']['currentapp'] = 'etemplate';
+ $GLOBALS['egw_info']['flags']['app_header'] = 'et2 Widgets';
+ //'js_link_registry' => True,
+
+ // Widget browser code
+ egw_framework::validate_file('/etemplate/js/widget_browser.js');
+
+ // Include the etemplate2 javascript code
+ egw_framework::validate_file('.', 'etemplate2', 'etemplate');
+
+ egw_framework::includeCSS('/etemplate/templates/default/etemplate2.css');
+
+ // Include the jQuery-UI CSS - many more complex widgets use it
+ $theme = 'redmond';
+ egw_framework::includeCSS("/phpgwapi/js/jquery/jquery-ui/$theme/jquery-ui-1.8.21.custom.css");
+
+ egw_framework::includeCSS('etemplate','widget_browser',false);
+
+ // load translations
+ translation::add_app('etemplate');
+
+ common::egw_header();
+ parse_navbar();
+
+ echo '
+
+
+ ';
+ common::egw_footer();
+ }
+}
diff --git a/etemplate/inc/hook_sidebox_menu.inc.php b/etemplate/inc/hook_sidebox_menu.inc.php
index 243572a364..aad504eb66 100644
--- a/etemplate/inc/hook_sidebox_menu.inc.php
+++ b/etemplate/inc/hook_sidebox_menu.inc.php
@@ -1,6 +1,6 @@
$docs.'reference.html',
'target' => 'docs'
),
+ array(
+ 'text' => 'eTemplate2 Reference',
+ 'link' => egw_framework::link('/index.php','menuaction=etemplate.etemplate_et2_widgets.index'),
+ 'target' => '_blank'
+ ),
array(
'text' => 'eGroupWare '.lang('Documentation'),
'no_lang' => True,
diff --git a/etemplate/js/widget_browser.js b/etemplate/js/widget_browser.js
new file mode 100644
index 0000000000..ff76631b3d
--- /dev/null
+++ b/etemplate/js/widget_browser.js
@@ -0,0 +1,185 @@
+/**
+ * EGroupware eTemplate2 widget browser
+ * View & play with et2 widgets - javascript
+ *
+ * @link http://www.egroupware.org
+ * @author Nathan Gray
+ * @copyright 2013 Nathan Gray
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @package etemplate
+ * @subpackage tools
+ * @version $Id$
+ */
+
+
+/**
+ * widget_browser shows a list of widgets, and allows you to view them one at a time.
+ * You can view and edit defined properties to see the effect.
+ */
+function widget_browser(list_div, widget_div)
+{
+
+ // Initialize etemplate2
+ this.et2 = new etemplate2(widget_div, "etemplate::ajax_process_content");
+
+ // Normally this would be passed from app
+ var _data = {}
+
+ // Create the basic widget container and attach it to the DOM
+ // Not really needed, but let's be consitent with et2
+ this.et2.widgetContainer = new et2_container(null);
+ this.et2.widgetContainer.setApiInstance(egw('etemplate', egw.elemWindow(this.et2.DOMContainer)));
+ this.et2.widgetContainer.setInstanceManager(this.et2);
+ this.et2.widgetContainer.setParentDOMNode(this.et2.DOMContainer);
+ this.et2.widgetContainer.setArrayMgrs(this.et2._createArrayManagers(_data));
+
+ // Set up UI
+ this.list_div = $j(list_div);
+ this.widget_div = $j(widget_div);
+ this.attribute_list = null;
+
+ // Create and popuplate the widget list
+ this._init_list();
+
+}
+
+/**
+ * Read the widget registry and create a list.
+ * The user can pick a widget, and we'll instanciate it.
+ */
+widget_browser.prototype._init_list = function()
+{
+ var self = this;
+
+ // Create list
+ var list = $j(document.createElement('ul'))
+ .attr('id', 'widgets')
+ .click(function(e) {self.select_widget(e);})
+ .appendTo(this.list_div);
+ for(var type in et2_registry)
+ {
+ var class_name = et2_registry[type];
+ list.append(''+type+'');
+ }
+
+ // Build attribute table
+ attribute_table = $j(document.createElement('table'));
+ attribute_table.append('");
+ this.attribute_list = $j(document.createElement('tbody'))
+ .appendTo(attribute_table);
+
+ this.list_div.append(
+ $j(document.createElement('div'))
+ .attr('id', 'widget_attributes')
+ .append(attribute_table)
+ );
+}
+
+/**
+ * User selected a widget from the list
+ *
+ * Create an instance of the widget, get its attributes, and display it.
+ */
+widget_browser.prototype.select_widget = function(e,f)
+{
+ // UI prettyness - clear selected
+ $j(e.target).parent().children().removeClass("ui-state-active");
+
+ // Clear previous widget
+ if(this.widget)
+ {
+ this.widget.free();
+ }
+
+ // Get the type of widget
+ var type = $j(e.target).text();
+ if(!type || e.target.nodeName != 'LI')
+ {
+ return;
+ }
+
+ // UI prettyness - show item as selected
+ $j(e.target).addClass('ui-state-active');
+
+ // Widget attributes
+ var attrs = {};
+
+
+ this.widget = et2_createWidget(type, attrs, this.et2.widgetContainer);
+ this.widget.loadingFinished();
+
+ // Attribute list
+ this.attribute_list.empty();
+ if(this.widget !== null && this.widget.attributes)
+ {
+ for(var attr in this.widget.attributes)
+ {
+ if(this.widget.attributes[attr].ignore) continue;
+ this.create_attribute(attr, this.widget.attributes[attr])
+ .appendTo(this.attribute_list);
+ }
+ }
+
+}
+
+
+
+/**
+ * Create the UI (DOM) elements for a single widget attribute
+ *
+ * @param name Name of the attribute
+ * @param settings attribute attributes (Human name, description, etc)
+ */
+widget_browser.prototype.create_attribute = function(name, settings)
+{
+ var set_function_name = "set_"+name;
+ var row = $j(document.createElement("tr"))
+ .addClass(typeof this.widget[set_function_name] == 'function' ? 'ui-state-default':'ui-state-disabled')
+ // Human Name
+ .append($j(document.createElement('td'))
+ .text(settings.name)
+ )
+ // Data type
+ .append($j(document.createElement('td'))
+ .text(settings.type)
+ );
+ // Add attribute name & description as a tooltip
+ if(settings.description)
+ {
+ egw().tooltipBind(row,settings.description);
+ }
+
+ // Value
+ var value = $j(document.createElement('td')).appendTo(row);
+ if(row.hasClass('ui-state-disabled'))
+ {
+ // No setter - just use text
+ value.text(this.widget.options[name]);
+ return row;
+ }
+
+ // Setter function - maybe editable?
+ var self = this;
+ switch(settings.type)
+ {
+ case 'string':
+ value.append('')
+ .change(function(e) {
+ self.widget[set_function_name].apply(self.widget, [$j(e.target).val()]);
+ });
+ break;
+ case 'boolean':
+ value.append('')
+ .change(function(e) {
+ self.widget[set_function_name].apply(self.widget, [$j(e.target).val()]);
+ });
+ break;
+ default:
+ value.text(this.widget.options[name]);
+ return row;
+ }
+ value.val(this.widget.options[name]);
+
+ return row;
+}
diff --git a/etemplate/templates/default/widget_browser.css b/etemplate/templates/default/widget_browser.css
new file mode 100644
index 0000000000..fc1e53eff6
--- /dev/null
+++ b/etemplate/templates/default/widget_browser.css
@@ -0,0 +1,46 @@
+/**
+ * CSS for the widget browser
+ * etemplate.etemplate_et2_widgets.index
+ */
+
+/* Basic layout */
+#widget_list {
+ position: fixed;
+ width: 260px;
+}
+#widget_list ul {
+ position: fixed;
+ top: 0px;
+ overflow-y: scroll;
+ height: 55%;
+ width: inherit;
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+}
+#widget_attributes {
+ position: fixed;
+ bottom: 0px;
+ height: 44%;
+ width: inherit;
+ overflow-y: auto;
+ margin: 0;
+ padding: 0;
+}
+#widget_attributes table{
+ width: 100%;
+}
+#widget_container{
+ position: fixed;
+ left: 260px;
+ padding: 20px;
+}
+
+/* Nicer looks */
+#widget_list li {
+ cursor:pointer;
+ padding: 0.4em;
+ font-size: 1.4em;
+ height: 16px;
+ width: 50%;
+}