← Back to team overview

openerp-dev-web team mailing list archive

lp:~openerp-dev/openobject-client-web/trunk-proto61-dashboard-vda into lp:~openerp-dev/openobject-client-web/trunk-proto61

 

vda(Open ERP) has proposed merging lp:~openerp-dev/openobject-client-web/trunk-proto61-dashboard-vda into lp:~openerp-dev/openobject-client-web/trunk-proto61.

Requested reviews:
  OpenERP R&D Team (openerp-dev)

For more details, see:
https://code.launchpad.net/~openerp-dev/openobject-client-web/trunk-proto61-dashboard-vda/+merge/59469

Dashboard using jquery.dashboard plugin.
-- 
https://code.launchpad.net/~openerp-dev/openobject-client-web/trunk-proto61-dashboard-vda/+merge/59469
Your team OpenERP R&D Team is requested to review the proposed merge of lp:~openerp-dev/openobject-client-web/trunk-proto61-dashboard-vda into lp:~openerp-dev/openobject-client-web/trunk-proto61.
=== modified file 'addons/base/static/src/js/views.js'
--- addons/base/static/src/js/views.js	2011-04-26 14:13:34 +0000
+++ addons/base/static/src/js/views.js	2011-04-29 08:50:52 +0000
@@ -42,7 +42,11 @@
                     if (this.viewmanager) {
                         this.viewmanager.stop();
                     }
-                    this.viewmanager = new openerp.base.ViewManagerAction(this.session, this.element_id, action, true);
+                    var sidebar = true;
+                    if(action.no_sidebar) {
+                        sidebar = false;
+                    }
+                    this.viewmanager = new openerp.base.ViewManagerAction(this.session, this.element_id, action, sidebar);
                     this.viewmanager.start();
                 }
                 break;

=== added directory 'addons/base_dashboard'
=== added file 'addons/base_dashboard/__init__.py'
--- addons/base_dashboard/__init__.py	1970-01-01 00:00:00 +0000
+++ addons/base_dashboard/__init__.py	2011-04-29 08:50:52 +0000
@@ -0,0 +1,1 @@
+import controllers
\ No newline at end of file

=== added file 'addons/base_dashboard/__openerp__.py'
--- addons/base_dashboard/__openerp__.py	1970-01-01 00:00:00 +0000
+++ addons/base_dashboard/__openerp__.py	2011-04-29 08:50:52 +0000
@@ -0,0 +1,11 @@
+{
+    "name": "Base Dashboard",
+    "version": "2.0",
+    "depends": ['base'],
+    "js": [
+        'static/lib/jquery.dashboard/js/jquery.dashboard.js',
+        'static/src/js/dashboard.js'
+    ],
+    "css": ['static/lib/jquery.dashboard/css/dashboardui.css'],
+    'active': True
+}

=== added directory 'addons/base_dashboard/controllers'
=== added file 'addons/base_dashboard/controllers/__init__.py'
--- addons/base_dashboard/controllers/__init__.py	1970-01-01 00:00:00 +0000
+++ addons/base_dashboard/controllers/__init__.py	2011-04-29 08:50:52 +0000
@@ -0,0 +1,1 @@
+import main
\ No newline at end of file

=== added file 'addons/base_dashboard/controllers/main.py'
--- addons/base_dashboard/controllers/main.py	1970-01-01 00:00:00 +0000
+++ addons/base_dashboard/controllers/main.py	2011-04-29 08:50:52 +0000
@@ -0,0 +1,17 @@
+from base.controllers.main import View, clean_action
+import openerpweb
+
+class Dashboard(View):
+    _cp_path = "/base_dashboard/dashboard"
+    
+    @openerpweb.jsonrequest
+    def load(self, req, node_attrs):
+        
+        action_id = int(node_attrs['name'])
+        actions = req.session.model('ir.actions.actions')
+        result = actions.read([action_id],['type'], req.session.context)
+        if not result:
+            raise _('Action not found!')
+        action = req.session.model(result[0]['type']).read([action_id], False, req.session.context)[0]
+        clean_action(action, req.session)
+        return {'action': action}
\ No newline at end of file

=== added directory 'addons/base_dashboard/static'
=== added file 'addons/base_dashboard/static/data.html'
--- addons/base_dashboard/static/data.html	1970-01-01 00:00:00 +0000
+++ addons/base_dashboard/static/data.html	2011-04-29 08:50:52 +0000
@@ -0,0 +1,1 @@
+content 4 content 4 content 4 content 4 content 4 content 4 content 4 content 4 content 4 content 4 content 4
\ No newline at end of file

=== added directory 'addons/base_dashboard/static/lib'
=== added directory 'addons/base_dashboard/static/lib/jquery.dashboard'
=== added directory 'addons/base_dashboard/static/lib/jquery.dashboard/css'
=== added file 'addons/base_dashboard/static/lib/jquery.dashboard/css/dashboardui.css'
--- addons/base_dashboard/static/lib/jquery.dashboard/css/dashboardui.css	1970-01-01 00:00:00 +0000
+++ addons/base_dashboard/static/lib/jquery.dashboard/css/dashboardui.css	2011-04-29 08:50:52 +0000
@@ -0,0 +1,261 @@
+.ui-icon {
+  cursor:default;
+}
+.widgetheader {
+  cursor:move;
+}
+
+#layout-dialog .selected {
+  background: no-repeat scroll 0px -51px transparent;
+}
+#layout-dialog .layoutchoice {
+  width: 82px; 
+  height: 51px; 
+  float:left;
+  list-style-type:none;
+  margin:5px;
+  padding:0;
+}
+.selectedcolumn {
+  border: 3px dashed #aaaaaa;
+}
+.emptycolumn {
+  font-size:20px;
+  font-weight:bold;
+  color: #aaaaaa;
+  padding: 5px 5px 5px 5px;
+}
+
+.dashboard {
+  margin-top: 5px;
+}
+.right {
+  float:right;
+}
+.widgetheader {
+  padding:2px 2px 5px 5px;
+}
+.ui-icon {
+  float:left;
+}
+.widgetcontent {
+  padding:2px 2px 5px 5px;
+}
+.widget {
+  margin-bottom:10px;
+}
+.column  {
+  float:left;
+  margin:0 1% -1.5em;
+  padding:0;
+  width:47.5%;
+}
+
+.layout-a .column {
+width:98%;
+}
+.layout-a .column.second, .layout-a .column.third {
+display:none;
+}
+.layout-aa .column {
+width:47.5%;
+}
+.layout-aa .third {
+display:none;
+}
+.layout-ba .column {
+width:68%;
+}
+.layout-ba .first {
+width:27%;
+}
+.layout-ba .third {
+display:none;
+}
+.layout-ab .column {
+width:27%;
+}
+.layout-ab .first {
+width:68%;
+}
+.layout-ab .third {
+display:none;
+}
+.layout-aaa .column {
+  width:30.9%;
+}
+
+body {
+  margin:0;
+  color:#333333;
+  font:12px/1.4 arial,FreeSans,Helvetica,sans-serif;
+}
+
+.headerlink {
+  color:#ffffff;
+}
+
+.headerlinks {
+  text-align:right;
+  margin-right:20px;
+  line-height:24px;
+  text-decoration:underline;
+  font-weight:bold;
+}
+
+.headerbox {
+  height:148px;
+}
+
+.ui-widget-overlay {
+  opacity:0.5;
+}
+
+/*.loading {
+  padding: 50px;
+  text-align: center;
+  background-image:loading.gif;
+}*/
+
+
+#layout-dialog ul {
+  margin:0;
+  padding:0;
+}
+
+#layout-dialog ul li a, #layout-dialog ul li a:link, #layout-dialog ul li a:visited {
+  border:1px solid #BBBBBB;
+  display:block;
+  float:left;
+  margin:0 1em 1em 0;
+  outline:medium none;
+  padding:0.35em;
+  width:auto;
+}
+
+.hidden {
+  display:none;
+}
+
+.controls  {
+  border:1px solid #BBBBBB;
+  float:none;
+  margin:0;
+  padding:4px 0;
+}
+
+.controls  {
+  width:100px;
+  background:none repeat scroll 0 0 #dddddd;
+  border:1px solid #6A8EB3;
+  color:#000000;
+  margin-top:-1px;
+  padding:4px 0;
+  position:absolute;
+  right:0;
+  z-index:2003;
+}
+
+.controls li {
+  float:none;
+  margin:0;
+  padding:0;
+  list-style-type:none;
+  margin:0 0 0 0.2em;
+  width:auto;
+  position:static;
+}
+
+.controls li a {
+  color:#000000;
+  font-weight:normal;
+  float:none;
+  margin:0;
+  text-decoration:none;
+  width:auto;
+  padding-left:5px;
+}
+
+.hiddenmenu {
+  position:relative;
+}
+
+.dialog .categories {
+  list-style:none outside none;
+  height: 414px;
+}
+
+.dialog .categories li.selected button {
+  color:#FFFFFF;
+  font-weight:800;
+}
+.dialog .categories li button {
+  background:none repeat scroll 0 0 transparent;
+  border:medium none;
+  color:#666666;
+  font-family:"segoe ui",helvetica,arial,sans-serif;
+  font-size:0.8em;
+  padding:0.4em 1.2em;
+  text-align:left;
+  width:100%;
+}
+ul.categories button {
+  cursor:pointer;
+}
+
+.widgetitem {
+  border:2px none white;
+  float:left;
+  font-size:0.77em;
+  height:142px;
+  margin:0;
+  overflow:hidden;
+  padding:0 20px 0 142px;
+  width:152px;
+}
+
+ol.widgets {
+  float:left;
+  list-style:none outside none;
+  margin:-10px;
+  padding:0;
+  width:auto;
+}
+
+.dialog .panel-body {
+  overflow:auto;
+  padding:10px;
+}
+
+.dialog .categories {
+  background:none repeat scroll 0 0 #FFFFFF;
+  border-right:1px solid #F0F0F0;
+  float:left;
+  height:100%;
+  list-style:none outside none;
+  margin:0 1.17em 0 0;
+  padding:10px 0 0;
+  width:25%;
+}
+
+.dialog .categories li.selected {
+  background:none repeat scroll 0 0 #6699CC;
+  color:#FFFFFF;
+}
+
+.widgetitem .add-button {
+  float:left;
+  margin:81px 0 0 -131px;
+  width:auto;
+}
+
+.widgetitem h3 {
+  margin:11px 0 0;
+  padding:0;
+}
+
+.widgetitem img {
+  border:1px solid #999999;
+  float:left;
+  margin:10px 0 0 -132px;
+}
\ No newline at end of file

=== added directory 'addons/base_dashboard/static/lib/jquery.dashboard/js'
=== added file 'addons/base_dashboard/static/lib/jquery.dashboard/js/jquery.dashboard.js'
--- addons/base_dashboard/static/lib/jquery.dashboard/js/jquery.dashboard.js	1970-01-01 00:00:00 +0000
+++ addons/base_dashboard/static/lib/jquery.dashboard/js/jquery.dashboard.js	2011-04-29 08:50:52 +0000
@@ -0,0 +1,936 @@
+/*
+ * dashboard 1.0
+ * http://www.gxdeveloperweb.com/dashboard/
+ *
+ * Copyright (c) 2010 Mark Machielsen
+ *
+ * Dual licensed under the MIT and GPL licenses (same as jQuery):
+ *   http://www.opensource.org/licenses/mit-license.php
+ *   http://www.gnu.org/licenses/gpl.html
+ *
+ */
+ 
+(function($) { // Create closure.
+
+  // Constructor for dashboard object.
+  $.fn.dashboard = function(options) {
+    // Public properties of dashboard.
+    var dashboard = {};
+    var loading;
+    var widgetDirectoryUrl;
+    dashboard.layout;
+    dashboard.element = this;
+    dashboard.id = this.attr("id");
+    dashboard.widgets = {};
+    dashboard.widgetsToAdd = {};
+    dashboard.widgetCategories = {};
+    dashboard.initialized = false;
+
+    // Public methods
+    dashboard.serialize = function() { 
+      dashboard.log('entering serialize function',1);
+      var r = '{"layout": "' + dashboard.layout.id + '", "data" : [';
+      // add al widgets in the right order
+      var i=0;
+      if ($('.' + opts.columnClass).length == 0) dashboard.log(opts.columnClass  + ' class not found',5);
+      $('.' + opts.columnClass).each(function() {
+        $(this).children().each(function() {
+          if ($(this).hasClass(opts.widgetClass)) {
+            if (i > 0) { r+= ','; }
+            r+= (dashboard.getWidget($(this).attr("id"))).serialize();
+            i++;
+          }
+        });    
+      });
+      r+= ']}';
+      return r;    
+    }
+
+    dashboard.log = function(msg, level) {
+      if (level >= opts.debuglevel && typeof console != 'undefined') {
+        var l = '';
+        if (level == 1) l = 'INFO';
+        if (level == 2) l = 'EVENT';
+        if (level == 3) l = 'WARNING';
+        if (level == 5) l = 'ERROR';
+        console.log(l + ' - ' + msg);      
+      }    
+    }
+        
+    dashboard.setLayout = function(layout) {   
+      if (layout != null) {
+        dashboard.log('entering setLayout function with layout ' + layout.id,1);
+      } else {
+        dashboard.log('entering setLayout function with layout null',1);
+      }      
+      dashboard.layout = layout;
+
+      loading.remove();
+      if (dashboard.layout != null) {
+        if (typeof opts.layoutClass != 'undefined') {
+          this.element.find('.' + opts.layoutClass).addClass(dashboard.layout.classname);
+        } else {
+          this.element.html(dashboard.layout.html);
+        }
+      }
+      
+      // make the columns sortable, see http://jqueryui.com/demos/sortable/ for explaination
+      $('.' + opts.columnClass).sortable({
+        connectWith: $('.' + opts.columnClass),
+        opacity: opts.opacity,
+        handle: '.' + opts.widgetHeaderClass,
+        over: function(event, ui) { 
+          $(this).addClass("selectedcolumn");
+        },
+        out: function(event, ui) { 
+          $(this).removeClass("selectedcolumn");
+        },
+        receive: function(event, ui) {
+          // update the column attribute for the widget
+          var w = dashboard.getWidget(ui.item.attr("id"));
+          w.column = getColumnIdentifier($(this).attr("class"));
+
+          dashboard.log('dashboardStateChange event thrown for widget ' + w.id,2);        
+          dashboard.element.trigger("dashboardStateChange",{"stateChange":"widgetMoved","widget":w});        
+
+          dashboard.log('widgetDropped event thrown for widget ' + w.id,2);        
+          w.element.trigger("widgetDropped",{"widget":w});
+        },
+        deactivate: function(event, ui) {  
+          // This event is called for each column
+          dashboard.log('Widget is dropped: check if the column is now empty.',1);        
+          var childLength = $(this).children().length;
+          if (childLength == 0) {
+            dashboard.log('adding the empty text to the column',1);        
+            $(this).html('<div class="emptycolumn">' + opts.emptyColumnHtml + '</div>');            
+          } else {
+            if (childLength == 2) {
+              // remove the empty column HTML
+              $(this).find('.emptycolumn').remove();          
+            }
+          }
+        },
+        start: function(event, ui) {
+          ui.item.find('.' + opts.widgetTitleClass).addClass('noclick');
+        },
+        stop: function(event, ui) {
+            //sorting changed (within one list)
+            setTimeout(function(){
+              ui.item.find('.' + opts.widgetTitleClass).removeClass('noclick');
+             }, 300);
+        }
+
+      });
+      
+      fixSortableColumns();
+      
+      // trigger the dashboardLayoutLoaded event
+      dashboard.log('dashboardLayoutLoaded event thrown',2);        
+      dashboard.element.trigger("dashboardLayoutLoaded");      
+    }
+    
+    // This is a workaround for the following problem: when I drag a widget from column2 to column1, sometimes the widget is
+    // moved to column3, which is not visible
+    function fixSortableColumns() {
+      dashboard.log('entering fixSortableColumns function',1);
+      $('.nonsortablecolumn').removeClass('nonsortablecolumn').addClass(opts.columnClass);            
+      $('.' + opts.columnClass).filter(function() {return $(this).css("display") == 'none'}).addClass('nonsortablecolumn').removeClass(opts.columnClass);              
+    }
+    
+    function getColumnIdentifier(classes) {
+      dashboard.log('entering getColumnIdentifier function',1);
+      var r;
+      var s = classes.split(" ");
+      for (var i = 0;i < s.length;i++) {
+        if (s[i].indexOf(opts.columnPrefix) === 0) { r = s[i] };
+      };
+      return r.replace(opts.columnPrefix,'');
+    }
+
+    dashboard.loadLayout = function() {    
+      dashboard.log('entering loadLayout function',1);
+      if (typeof opts.json_data.url != 'undefined' && opts.json_data.url.length > 0) {
+        // ajax option
+        dashboard.log('Getting JSON feed : ' + opts.json_data.url,1);
+        $.getJSON(opts.json_data.url, function(json) {
+          if (json == null) {
+            alert('Unable to get json. If you are using chrome: there is an issue with loading json with local files. It works on a server :-)',5);
+            return;
+          }
+          // set the layout
+          var currentLayout = (typeof dashboard.layout != 'undefined') ? dashboard.layout : getLayout(json.layout);
+          dashboard.setLayout(currentLayout);         
+          dashboard.loadWidgets(json.data);
+        });      
+      } else {
+        // set the layout
+        var currentLayout = (typeof dashboard.layout != 'undefined') ? dashboard.layout : getLayout(opts.json_data.layout);
+        dashboard.setLayout(currentLayout);
+        dashboard.loadWidgets(opts.json_data.data);
+      }
+    };
+    
+    dashboard.addWidget = function(obj, column) {
+      dashboard.log('entering addWidget function',1);
+      // add the widget to the column
+      var wid = obj.id;
+
+      // check if the widget is already registered and available in the dom
+      if (typeof dashboard.widgets[wid] != 'undefined' && $('#' + wid).length > 0) {
+        var wi = $('#' + wid);
+        column = dashboard.widgets[wid].column;
+
+        // add it to the column
+        wi.appendTo(column);    
+
+      } else {
+        // build the widget    
+        dashboard.log('Applying template : ' + opts.widgetTemplate,1);
+        if ($('#' + opts.widgetTemplate).length == 0) dashboard.log('Template "' + opts.widgetTemplate + ' not found',5);
+        var widgetStr = tmpl($('#' + opts.widgetTemplate).html(), obj);
+        var wi = $(widgetStr);
+
+        // add it to the column
+        wi.appendTo(column);    
+        
+        if(column.find('.emptycolumn').length)
+            column.find('.emptycolumn').remove();
+        
+        dashboard.widgets[wid] = widget({
+          id: wid,
+          element: wi,
+          column: obj.column,
+          url: (typeof obj.url != 'undefined' ? obj.url : null),
+          editurl: obj.editurl,
+          title: obj.title,
+          open: obj.open,
+          metadata: obj.metadata
+        });
+      }
+
+      dashboard.log('widgetAdded event thrown for widget ' + wid,2);        
+      dashboard.widgets[wid].element.trigger("widgetAdded", {"widget":dashboard.widgets[wid]});
+
+      if (dashboard.initialized) {
+        dashboard.log('dashboardStateChange event thrown for widget ' + wid,2);        
+        dashboard.element.trigger("dashboardStateChange",{"stateChange":"widgetAdded","widget":wi});
+      }
+    }        
+
+    dashboard.loadWidgets = function(data) {
+      dashboard.log('entering loadWidgets function',1);
+      dashboard.element.find('.' + opts.columnClass).empty();
+
+
+      // This is for the manual feed
+      $(data).each(function() {
+        var column = this.column;        
+        dashboard.addWidget(this, dashboard.element.find('.' + opts.columnPrefix + column));
+      }); // end loop for widgets
+      
+      // check if there are widgets in the temp dashboard which needs to be moved
+      // this is not the correct place, but otherwise we are too late
+      
+      // check if there are still widgets in the temp
+      $('#tempdashboard').find('.' + opts.widgetClass).each(function() {
+        // append it to the first column
+        var firstCol = dashboard.element.find('.' + opts.columnClass + ':first');
+        $(this).appendTo(firstCol);
+        
+        // set the new column
+        dashboard.getWidget($(this).attr("id")).column = firstCol.attr("id");
+      });
+      $('#tempdashboard').remove();
+
+      // add the text to the empty columns
+      $('.' + opts.columnClass).each(function() {
+        if ($(this).children().length == 0) {
+          $(this).html('<div class="emptycolumn">' + opts.emptyColumnHtml + '</div>');
+        }
+      });            
+      
+      dashboard.initialized = true;
+    };
+    
+    dashboard.init = function() {
+      dashboard.log('entering init function',1);
+      // load the widgets as fast as we can. After that add the binding
+      dashboard.loadLayout();    
+    }
+    
+    dashboard.getWidget = function(id) {
+      dashboard.log('entering getWidget function',1);
+      var wi = dashboard.widgets[id];
+      if (typeof wi != 'undefined') {
+        return wi;
+      } else {
+        return null;
+      }
+    }
+    
+    
+    // Merge in the caller's options with the defaults.
+    var opts = $.extend({}, $.fn.dashboard.defaults, options);
+    var addOpts = $.extend({}, $.fn.dashboard.defaults.addWidgetSettings, options.addWidgetSettings);
+    var layoutOpts = $.extend({}, $.fn.dashboard.defaults.editLayoutSettings, options.editLayoutSettings);
+    
+    // Execution 'forks' here and restarts in init().  Tell the user we're busy with a loading.
+    var loading = $(opts.loadingHtml).appendTo(dashboard.element);
+    
+    /**
+     * widget object
+     *    Private sub-class of dashboard
+     * Constructor starts
+     */
+    function widget(widget) {
+    
+      dashboard.log('entering widget constructor',1);
+      // Merge default options with the options defined for this widget.
+      widget = $.extend({}, $.fn.dashboard.widget.defaults, widget);
+      
+      // public functions
+      widget.openContent = function() {
+        // hide the open link, show the close link
+        widget.element.find('.widgetOpen').hide();
+        widget.element.find('.widgetClose').show();
+
+        dashboard.log('entering openContent function',1);
+        widget.open = true;
+        if (!widget.loaded) {
+          // load the content in the widget if the state == open
+          if (this.url != '' && this.url != null && typeof this.url != 'undefined') {
+            // add the loading
+            $(opts.loadingHtml).appendTo(widget.element.find('.' + opts.widgetContentClass));
+
+            dashboard.log('widgetShow event thrown for widget ' + widget.id,2);        
+            widget.element.trigger("widgetShow", {"widget":widget});
+            
+            widget.element.find('.' + opts.widgetContentClass).load(this.url, function(response, status, xhr) {
+              if (status == "error") {
+                widget.element.find('.' + opts.widgetContentClass).html(opts.widgetNotFoundHtml);
+              }
+              widget.loaded = true;
+              dashboard.log('widgetLoaded event thrown for widget ' + widget.id,2);        
+              widget.element.trigger("widgetLoaded", {"widget":widget});
+            });
+          } else {          
+            dashboard.log('widgetShow event thrown for widget ' + widget.id,2);        
+            widget.element.trigger("widgetShow", {"widget":widget});
+
+            dashboard.log('widgetLoaded event thrown',2);                  
+            widget.element.trigger("widgetLoaded", {"widget":widget});
+          }
+        } else {
+          dashboard.log('widgetShow event thrown for widget ' + widget.id,2);        
+          widget.element.trigger("widgetShow", {"widget":widget});
+        }
+        if (dashboard.initialized) {
+          dashboard.log('dashboardStateChange event thrown for widget ' + widget.id,2);        
+          dashboard.element.trigger("dashboardStateChange",{"stateChange":"widgetOpened","widget":widget});        
+        }
+      };
+      widget.refreshContent = function() {
+        dashboard.log('entering refreshContent function',1);
+        widget.loaded = false;
+        if (widget.open) {
+          widget.openContent();
+        }
+      }
+      widget.setTitle = function(newTitle){
+        dashboard.log('entering setTitle function',1);
+        widget.title=newTitle;
+        widget.element.find('.' + opts.widgetTitleClass).html(newTitle);
+        if (dashboard.initialized) {
+          dashboard.log('dashboardStateChange event thrown for widget ' + widget.id,2);        
+          dashboard.element.trigger("dashboardStateChange",{"stateChange":"titleChanged","widget":widget}); 
+        }          
+      }
+      widget.closeContent = function() {
+        dashboard.log('entering closeContent function',1);
+        widget.open = false;
+
+        dashboard.log('widgetHide event thrown for widget ' + widget.id,2);        
+        widget.element.trigger("widgetHide", {"widget":widget});
+
+        // show the open link, hide the close link
+        widget.element.find('.widgetOpen').show();
+        widget.element.find('.widgetClose').hide();
+
+        dashboard.log('dashboardStateChange event thrown for widget ' + widget.id,2);        
+        dashboard.element.trigger("dashboardStateChange",{"stateChange":"widgetClosed","widget":widget});
+      };
+      widget.addMetadataValue = function(name, value) {
+        dashboard.log('entering addMetadataValue function',1);
+        if (typeof widget.metadata == 'undefined') {
+          widget.metadata = {};
+        }
+        widget.metadata[name] = value;
+        dashboard.log('dashboardStateChange event thrown for widget ' + widget.id,2);        
+        dashboard.element.trigger("dashboardStateChange",{"stateChange":"metadataChanged","widget":widget});
+      };
+      widget.openMenu = function() {
+        dashboard.log('entering openMenu function',1);
+        widget.element.find('.' + opts.menuClass).show();
+      };
+      widget.closeMenu = function() {
+        dashboard.log('entering closeMenu function',1);
+        widget.element.find('.' + opts.menuClass).hide();
+      };
+      widget.remove = function() {
+        dashboard.log('entering remove function',1);
+        widget.element.remove();      
+        dashboard.log('widgetDeleted event thrown for widget ' + widget.id,2);        
+        widget.element.trigger('widgetDeleted', {"widget":widget});        
+
+        dashboard.log('dashboardStateChange event thrown for widget ' + widget.id,2);        
+        dashboard.element.trigger("dashboardStateChange",{"stateChange":"widgetRemoved","widget":widget});
+      };
+      widget.serialize = function() {
+        dashboard.log('entering serialize function',1);
+        var r = '{"title" : "' + widget.title + '", "id" : "' + widget.id + '", "column" : "' + widget.column + '","editurl" : "' + widget.editurl + '","open" : ' + widget.open + ',"url" : "' + widget.url + '"';
+        
+        if (typeof widget.metadata != 'undefined') {
+          r+= ',"metadata":{'
+          var obj = widget.metadata;
+          var i=0;
+          for(var item in obj) {
+            if (i > 0) { r+= ',' };
+            
+            // FIXME: support for more than string, eg numbers subobjects
+            r+= '"' + item + '":"' + obj[item] + '"';
+            i++;
+          }
+          r+= '}'
+          
+        }
+        
+        r += '}';
+        return r;      
+      };
+      widget.openFullscreen = function() {
+        dashboard.log('entering openFullscreen function',1);
+        widget.fullscreen = true;
+        
+        // create a clone
+        var clone = widget.element.clone();
+
+        // move the dashboard 
+        var temp = $('<div style="display:none" id="tempdashboard_' + dashboard.id + '"></div>');
+        temp.appendTo($("body"));
+        dashboard.element.children().appendTo(temp);
+
+        // add the clone to the dashboard
+        var fs = $('<ul id="fullscreen_' + dashboard.id + '"></ul>');
+        fs.appendTo(dashboard.element);
+        clone.appendTo(fs);
+        
+      };
+      widget.closeFullscreen = function() {
+        dashboard.log('entering closeFullscreen function',1);
+        widget.fullscreen = false;
+
+        // remove the fullscreen
+        $('#fullscreen_' + dashboard.id).remove();
+
+        // move the content from the tempdashboard back
+        $('#tempdashboard_' + dashboard.id).children().appendTo(dashboard.element);
+        $('#tempdashboard_' + dashboard.id).remove();
+        
+      };      
+      widget.openSettings = function() {
+        dashboard.log('entering openSettings function',1);
+        widget.element.find('.' + opts.widgetContentClass).load(widget.editurl);
+      };
+      
+      // called when widget is initialized
+      if (widget.open) {
+        widget.openContent();
+      }
+
+      widget.initialized = true;
+
+      dashboard.log('widgetInitialized event thrown',2);                  
+      widget.element.trigger("widgetInitialized", {"widget":widget});
+
+      return widget;
+    };
+    
+
+    // FIXME: can this be done easier??
+    function getLayout(id) {
+      dashboard.log('entering getLayout function',1);
+      var r = null;
+      var first = null;
+      if (typeof opts.layouts != 'undefined') {
+
+        $.each(opts.layouts,function(i, item) {
+          if (i == 0) { first = item; }
+          if (item.id == id) {
+            r = item;
+          }
+        });
+      }
+      if (r == null) { r = first }
+      return r;
+    }
+    
+    
+    $('#' + dashboard.id + ' .menutrigger').live('click', function() {
+      dashboard.log('widgetOpenMenu event thrown for widget ' + widget.id,2);
+      var wi = dashboard.getWidget($(this).closest('.' + opts.widgetClass).attr("id"));
+      
+      wi.element.trigger('widgetOpenMenu', {"widget":wi});      
+      return false;
+    });
+        
+    // add event handlers to the menu
+    $('#' + dashboard.id + ' .' + opts.widgetFullScreenClass).live('click',function(e) {        
+      // close the menu
+      dashboard.log('widgetCloseMenu event thrown for widget ' + widget.id,2);        
+      var wi = dashboard.getWidget($(this).closest('.' + opts.widgetClass).attr("id"));
+      wi.element.trigger('widgetCloseMenu', {"widget":wi});      
+            
+      if (wi.fullscreen) {
+        dashboard.log('widgetCloseFullScreen event thrown for widget ' + wi.id,2);        
+        wi.element.trigger('widgetCloseFullScreen', {"widget":wi});
+      } else {
+        dashboard.log('widgetOpenFullScreen event thrown for widget ' + wi.id,2);        
+        wi.element.trigger('widgetOpenFullScreen', {"widget":wi});
+      }
+      return false;
+    });
+
+    $('#' + dashboard.id + ' .controls li').live('click',function(e) {        
+      // close the menu
+      dashboard.log('widgetCloseMenu event thrown for widget ' + widget.id,2);        
+
+      var wi = dashboard.getWidget($(this).closest('.' + opts.widgetClass).attr("id"));
+      wi.element.trigger('widgetCloseMenu', {"widget":wi});      
+      
+      // use the class on the li to determine what action to trigger
+      dashboard.log($(this).attr('class') + ' event thrown for widget ' + widget.id,2);        
+      
+      var wi = dashboard.getWidget($(this).closest('.' + opts.widgetClass).attr("id"));
+      wi.element.trigger($(this).attr('class'), {"widget":wi});
+      return false;
+    });
+    
+    // add the menu events (by default triggers are connected in dashboard_jsonfeed)
+    $('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetCloseMenu',function(e,o) {
+      dashboard.log("Closing menu " + $(this).attr("id"),1);
+      o.widget.closeMenu();
+    });
+
+    $('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetOpenMenu',function(e,o) {
+      dashboard.log("Opening menu " + $(this).attr("id"),1);
+      o.widget.openMenu();
+    });
+
+    $('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetDelete',function(e,o) {
+      if (confirm(opts.deleteConfirmMessage)) {
+        dashboard.log("Removing widget " + $(this).attr("id"),1);
+        o.widget.remove();
+      }      
+    });
+
+    $('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetRefresh',function(e,o) {
+      o.widget.refreshContent();
+    });
+    
+    $('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetSetTitle',function(event, o) {
+      o.widget.setTitle(o.title);
+    });
+
+    $('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetClose',function(e,o) {
+      dashboard.log("Closing widget " + $(this).attr("id"),1);
+      o.widget.closeContent();
+    });
+
+    $('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetOpen',function(e,o) {    
+      dashboard.log("Opening widget " + $(this).attr("id"),1);
+      o.widget.openContent();
+    });
+
+    $('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetShow',function() {    
+      $(this).find('.' + opts.widgetContentClass).show();
+    });
+
+    $('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetHide',function() {    
+      $(this).find('.' + opts.widgetContentClass).hide();
+    });
+    
+    $('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetAddMetadataValue',function(e,o) {
+      dashboard.log("Changing metadata for widget " + $(this).attr("id") + ", metadata name: " + o.name + ", value: " + o.value, 1);
+      o.widget.addMetadataValue(o.name, o.value);
+    });
+    
+    // Define a toggle event when clicking at the header
+    $('#' + dashboard.id + ' .' + opts.widgetTitleClass).live('click',function(e) {
+      dashboard.log("Click on the header detected for widget " + $(this).attr("id"),1);
+      if (!$(this).hasClass('noclick')) {
+        var wi = dashboard.getWidget($(this).closest('.' + opts.widgetClass).attr("id"));
+        if (wi.open) {
+          dashboard.log('widgetClose event thrown for widget ' + wi,2);        
+          wi.element.trigger('widgetClose', {"widget":wi});      
+        } else {
+          dashboard.log('widgetOpen event thrown for widget ' + wi,2);        
+          wi.element.trigger('widgetOpen', {"widget":wi});
+        }
+      }
+      return false;
+    });
+    
+    $('#' + dashboard.id + ' .' + opts.widgetHeaderClass).live('mouseover',function () {
+      $(this).find('.' + opts.iconsClass).removeClass("hidden");
+    });
+        
+    $('#' + dashboard.id + ' .' + opts.widgetHeaderClass).live('mouseout', function () {
+      $(this).find('.' + opts.iconsClass).addClass("hidden");
+    });
+
+    $('body').click(function() {
+      $('.' + opts.menuClass).hide();
+    });
+
+    $('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetOpenFullScreen',function(e,o) {
+      o.widget.openFullscreen();
+    });
+
+    $('.' + opts.widgetClass).live('widgetCloseFullScreen',function(e,o) {
+      o.widget.closeFullscreen();      
+    });
+
+    $('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetEdit',function(e,o) {
+      o.widget.openSettings();      
+    });
+        
+    if ($('#' + addOpts.dialogId).length == 0) dashboard.log('Unable to find ' + addOpts.dialogId,5);
+    $('#' + addOpts.dialogId).dialog({
+      autoOpen: false,
+      height: 414,
+      width: 550,
+      modal: true,
+      buttons: {
+        Cancel: function() {
+          $(this).dialog('close');
+        }
+      },
+      close: function() {
+        //close
+      }
+    });
+
+    if ($('#' + layoutOpts.dialogId).length == 0) dashboard.log('Unable to find ' + layoutOpts.dialogId,5);
+    $('#' + layoutOpts.dialogId).dialog({
+      autoOpen: false,
+      height: 300,
+      width: 600,
+      modal: true
+    });
+
+    $('.' + layoutOpts.openDialogClass).live('click', function(){
+      dashboard.log('dashboardOpenLayoutDialog event thrown',2);        
+      dashboard.element.trigger("dashboardOpenLayoutDialog");
+      return false;
+    });
+    
+    dashboard.element.live('dashboardOpenLayoutDialog', function(){        
+      dashboard.log('Opening dialog ' + layoutOpts.dialogId,1);
+      $('#' + layoutOpts.dialogId).dialog('open');
+
+      // add the layout images
+      var h = $('#' + layoutOpts.dialogId).find('.' + layoutOpts.layoutClass);
+      h.empty();
+      if (h.children().length == 0) {
+        dashboard.log('Number of layouts : ' + opts.layouts.length,1);
+        $.each(opts.layouts,function(i, item) {
+          dashboard.log('Applying template : ' + layoutOpts.layoutTemplate,1);
+          if ($('#' + layoutOpts.layoutTemplate).length == 0) dashboard.log('Template "' + layoutOpts.layoutTemplate + ' not found',5);
+          h.append(tmpl($('#' + layoutOpts.layoutTemplate).html(), item));
+        });
+      }
+      
+      // set the selected class for the selected layout
+      $('.' + layoutOpts.selectLayoutClass).removeClass(layoutOpts.selectedLayoutClass);
+      $('#' + dashboard.layout.id).addClass(layoutOpts.selectedLayoutClass);
+      
+      bindSelectLayout();
+    });
+
+
+    dashboard.element.live('dashboardStateChange', function(){   
+       if (typeof opts.stateChangeUrl != 'undefined' && opts.stateChangeUrl != null && opts.stateChangeUrl != '') {
+        $.ajax({type: 'POST',
+          url: opts.stateChangeUrl, 
+          data: { dashboard: dashboard.element.attr("id"), settings: dashboard.serialize() }, 
+          success: function(data){ 
+            if (data == "NOK" || data.indexOf('<response>NOK</response>') != -1){ 
+              dashboard.log('dashboardSaveFailed event thrown',2);        
+              dashboard.element.trigger("dashboardSaveFailed");      
+            } else {
+              dashboard.log('dashboardSuccessfulSaved event thrown',2);        
+              dashboard.element.trigger("dashboardSuccessfulSaved");      
+            }
+          },
+          error: function(XMLHttpRequest, textStatus, errorThrown){ 
+            dashboard.log('dashboardSaveFailed event thrown',2);        
+            dashboard.element.trigger("dashboardSaveFailed");      
+          },
+          dataType: "text"
+        });
+      }
+    });
+    
+
+    dashboard.element.live('dashboardCloseLayoutDialog', function(){   
+      // close the dialog
+      $('#' + layoutOpts.dialogId).dialog('close');
+    });
+
+    // FIXME: why doesn't the live construct work in this case
+    function bindSelectLayout() {
+      if ($('.' + layoutOpts.selectLayoutClass).length == 0) dashboard.log('Unable to find ' + layoutOpts.selectLayoutClass,5);
+      $('.' + layoutOpts.selectLayoutClass).bind('click', function(e){    
+        var currentLayout = dashboard.layout;
+        
+        dashboard.log('dashboardCloseLayoutDialog event thrown',2);        
+        dashboard.element.trigger('dashboardCloseLayoutDialog');
+
+        // Now set the new layout
+        var newLayout = getLayout($(this).attr("id"));
+        dashboard.layout = newLayout;
+
+        // remove the class of the old layout
+        if (typeof opts.layoutClass != 'undefined') {
+          dashboard.element.find('.' + opts.layoutClass).removeClass(currentLayout.classname).addClass(newLayout.classname);
+          
+          fixSortableColumns();
+          
+          // check if there are widgets in hidden columns, move them to the first column
+          if ($('.' + opts.columnClass).length == 0) dashboard.log('Unable to find ' + opts.columnClass,5);
+          dashboard.element.find('.' + opts.columnClass).each(function() {
+            if ($(this).css("display") == "none") {
+              // move the widgets to the first column
+              $(this).children().appendTo(dashboard.element.find('.' + opts.columnClass + ':first'));
+            }
+            
+            $('.emptycolumn').remove();
+            // add the text to the empty columns
+            $('.' + opts.columnClass).each(function() {
+              if ($(this).children().length == 0) {
+                $(this).html('<div class="emptycolumn">' + opts.emptyColumnHtml + '</div>');
+              }
+            });            
+            
+            
+          });
+          
+        } else {
+          // set the new layout, but first move the dashboard to a temp
+          var temp = $('<div style="display:none" id="tempdashboard"></div>');
+          temp.appendTo($("body"));
+
+          dashboard.element.children().appendTo(temp);
+
+          // reload the dashboard
+          dashboard.init();
+        }
+
+        // throw an event upon changing the layout. 
+        dashboard.log('dashboardChangeLayout event thrown',2);        
+        dashboard.element.trigger('dashboardLayoutChanged');
+
+      });
+      return false;
+    }    
+
+    $('.' + addOpts.selectCategoryClass).live('click', function(){     
+      dashboard.log('addWidgetDialogSelectCategory event thrown',2);        
+      dashboard.element.trigger('addWidgetDialogSelectCategory', {"category":$(this)});          
+      return false;
+    });
+
+    dashboard.element.live('addWidgetDialogSelectCategory', function(e, obj){
+      // remove the category selection
+      $('.' + addOpts.selectCategoryClass).removeClass(addOpts.selectedCategoryClass);
+      
+      // empty the widgets div
+      $('#' + addOpts.dialogId).find('.' + addOpts.widgetClass).empty();
+    
+      // select the category
+      $(obj.category).addClass(addOpts.selectedCategoryClass);
+      
+      // get the widgets
+      url = dashboard.widgetCategories[$(obj.category).attr("id")];
+
+      dashboard.log('Getting JSON feed : ' + url,1);
+      $.getJSON(url, {"cache":true}, function(json) {      
+        // load the widgets from the category        
+        if (json.data == 0) dashboard.log('Empty data returned',3);
+        $.each(json.data, function(i,item){
+          dashboard.widgetsToAdd[item.id] = item;
+
+          dashboard.log('Applying template : ' + addOpts.widgetTemplate,1);
+          if ($('#' + addOpts.widgetTemplate).length == 0) dashboard.log('Template "' + addOpts.widgetTemplate + ' not found',5);
+          var html = tmpl($('#' + addOpts.widgetTemplate).html(), item);
+          $('#' + addOpts.dialogId).find('.' + addOpts.widgetClass).append(html);        
+        });
+      });
+      
+      dashboard.log('addWidgetDialogWidgetsLoaded event thrown',2);        
+      dashboard.element.trigger('addWidgetDialogWidgetsLoaded');    
+    });
+
+
+    $('.' + addOpts.addWidgetClass).live('click', function(){      
+      var widget = dashboard.widgetsToAdd[$(this).attr("id").replace('addwidget','')];
+      dashboard.log('dashboardAddWidget event thrown',2);        
+      dashboard.element.trigger('dashboardAddWidget', {"widget":widget});    
+
+      dashboard.log('dashboardCloseWidgetDialog event thrown',2);        
+      dashboard.element.trigger('dashboardCloseWidgetDialog');
+      return false;
+    });
+
+    $('.' + addOpts.openDialogClass).live('click', function(){
+      dashboard.log('dashboardOpenWidgetDialog event thrown',2);        
+      dashboard.element.trigger('dashboardOpenWidgetDialog');
+      return false;
+    });
+    
+    dashboard.element.live('dashboardCloseWidgetDialog', function(){   
+      // close the dialog
+      $('#' + addOpts.dialogId).dialog('close');
+    });
+        
+    dashboard.element.live('dashboardOpenWidgetDialog', function(){
+    
+      //remove existing categories/widgets from the DOM, to prevent duplications
+      $('#' + addOpts.dialogId).find('.' + addOpts.categoryClass).empty();
+      $('#' + addOpts.dialogId).find('.' + addOpts.widgetClass).empty();
+
+      dashboard.log('Opening dialog ' + addOpts.dialogId,1);
+      $('#' + addOpts.dialogId).dialog('open');
+
+      dashboard.log('Getting JSON feed : ' + addOpts.widgetDirectoryUrl,1);
+      $.getJSON(addOpts.widgetDirectoryUrl, function(json) {      
+        if (json.category == 0) dashboard.log('Empty data returned',3);
+        $.each(json.category, function(i,item){
+          // Add the categories to the dashboard
+          dashboard.widgetCategories[item.id] = item.url;
+        
+          dashboard.log('Applying template : ' + addOpts.categoryTemplate,1);
+          if ($('#' + addOpts.categoryTemplate).length == 0) dashboard.log('Template "' + addOpts.categoryTemplate + ' not found',5);
+          var html = tmpl($('#' + addOpts.categoryTemplate).html(),item);
+          $('#' + addOpts.dialogId).find('.' + addOpts.categoryClass).append(html);        
+        });        
+        dashboard.log('addWidgetDialogCategoriesLoaded event thrown',2);        
+        dashboard.element.trigger('addWidgetDialogCategoriesLoaded');    
+
+        dashboard.log('addWidgetDialogSelectCategory event thrown',2);        
+        dashboard.element.trigger('addWidgetDialogSelectCategory', {"category":$('#' + addOpts.dialogId).find('.' + addOpts.categoryClass + '>li:first')});    
+        
+      });
+            
+    });
+     
+    return dashboard;
+  };
+
+    
+  // Public static properties of dashboard.  Default settings.
+  $.fn.dashboard.defaults = {
+    debuglevel:3,
+    json_data: {},
+    loadingHtml: '<div class="loading"><img alt="Loading, please wait" src="../themes/default/loading.gif" /><p>Loading...</p></div>',
+    emptyColumnHtml: 'Drag your widgets here',
+    widgetTemplate: 'widgettemplate',
+    columnPrefix: 'column-',
+    opacity:"0.2",
+    deleteConfirmMessage: "Are you sure you want to delete this widget?",
+    widgetNotFoundHtml: "The content of this widget is not available anymore. You may remove this widget.",
+    columnClass: 'column',
+    widgetClass: 'widget',
+    menuClass: 'controls',
+    widgetContentClass: 'widgetcontent',
+    widgetTitleClass: 'widgettitle',
+    widgetHeaderClass: 'widgetheader',
+    widgetFullScreenClass: 'widgetopenfullscreen',
+    iconsClass: 'icons',
+    stateChangeUrl: '',
+    
+    addWidgetSettings: {
+      openDialogClass: 'openaddwidgetdialog',
+      addWidgetClass: 'addwidget',
+      selectCategoryClass: 'selectcategory',
+      selectedCategoryClass: 'selected',
+      categoryClass: 'categories',
+      widgetClass: 'widgets',
+      
+      dialogId: 'addwidgetdialog',
+      
+      categoryTemplate: 'categorytemplate',
+      widgetTemplate: 'addwidgettemplate'
+    },
+    editLayoutSettings: {
+      dialogId: 'editLayout',
+      layoutClass: 'layoutselection',
+      selectLayoutClass: 'layoutchoice',
+      selectedLayoutClass: 'selected',
+      openDialogClass: 'editlayout',
+      layoutTemplate: 'selectlayouttemplate'
+    }
+    
+
+  };
+
+  // Default widget settings.
+  $.fn.dashboard.widget = {
+    defaults: {
+      open: true,
+      fullscreen: false,
+      loaded: false,
+      url: '',
+      metadata: {}
+    }
+  };
+
+})(jQuery); // end of closure
+
+
+// Simple JavaScript Templating
+// John Resig - http://ejohn.org/ - MIT Licensed
+(function(){
+  var cache = {};
+ 
+  this.tmpl = function tmpl(str, data){
+    // Figure out if we're getting a template, or if we need to
+    // load the template - and be sure to cache the result.
+
+    var fn = !/\W/.test(str) ?
+      cache[str] = cache[str] ||
+        tmpl(document.getElementById(str).innerHTML) :
+     
+      // Generate a reusable function that will serve as a template
+      // generator (and which will be cached).
+      new Function("obj",
+        "var p=[],print=function(){p.push.apply(p,arguments);};" +
+       
+        // Introduce the data as local variables using with(){}
+        "with(obj){p.push('" +
+       
+        // Convert the template into pure JavaScript
+        str
+          .replace(/[\r\t\n]/g, " ")
+          .split("<%").join("\t")
+          .replace(/((^|%>)[^\t]*)'/g, "$1\r")
+          .replace(/\t=(.*?)%>/g, "',$1,'")
+          .split("\t").join("');")
+          .split("%>").join("p.push('")
+          .split("\r").join("\\'")
+      + "');}return p.join('');");
+   
+    // Provide some basic currying to the user
+    return data ? fn( data ) : fn;
+  };
+})();
\ No newline at end of file

=== added directory 'addons/base_dashboard/static/src'
=== added file 'addons/base_dashboard/static/src/dashboard_template.html'
--- addons/base_dashboard/static/src/dashboard_template.html	1970-01-01 00:00:00 +0000
+++ addons/base_dashboard/static/src/dashboard_template.html	2011-04-29 08:50:52 +0000
@@ -0,0 +1,85 @@
+<script type="text/html" id="categorytemplate">
+	<li id="<%= id %>" class="selectcategory"><button><%= title %> (<%= amount %>)</button></li>
+</script>
+
+
+<script type="text/html" id="widgettemplate">
+	<div class="ui-widget ui-corner-all ui-widget-content widget" id="<%= id %>" title="<%= title %>">
+		<div class="ui-widget-header ui-corner-all widgetheader">
+			<span class="widgettitle"><%= title %></span>
+			<span class="right icons hidden">
+				<span class="ui-icon ui-icon-newwin widgetopenfullscreen"></span>
+				<span class="ui-icon ui-icon-arrowthickstop-1-s menutrigger"></span>
+				<span class="hiddenmenu">
+					<ul style="top: 13px;" class="hidden controls ui-widget-header">
+						<li class="widgetClose">
+							<span class="ui-icon ui-icon-minus"></span>
+							<a class="minimization" href="#">Minimize</a>
+						</li>
+						<li class="widgetOpen">
+							<span class="ui-icon ui-icon-extlink"></span>
+							<a class="minimization" href="#">Maximize</a>
+						</li>
+						<li class="widgetDelete">
+							<span class="ui-icon ui-icon-close"></span>
+							<a class="delete" href="#">Delete</a>
+						</li>
+						<!-- This could be implemented -->
+						<!--
+						<li class="widgetEdit">
+							<span class="ui-icon ui-icon-tag"></span>
+							<a class="no_target" href="#">Edit</a>
+						</li>
+						-->
+						<li class="widgetRefresh">
+							<span class="ui-icon ui-icon-arrowrefresh-1-w"></span>
+							<a class="no_target" href="#">Refresh</a>
+						</li>
+					</ul>
+				</span>
+			</span>
+		</div>
+		<div class="widgetcontent">
+		</div>
+	</div>
+</script>
+
+<script type="text/html" id="selectlayouttemplate">
+	<li class="layoutchoice" id="<%= id %>" style="background-image: url('<%= image %>')"></li>
+</script>
+
+<script type="text/html" id="addwidgettemplate">
+
+	<li class="widgetitem">
+		<img src="<%= image %>" alt="" height="60" width="120">
+		<div class="add-button">
+				<input class="macro-button-add addwidget" id="addwidget<%= id %>" value="Add it Now" type="button"><br>
+				<input class="macro-hidden-uri" value="<%= url %>" type="hidden">
+		</div>
+		<!-- // .add-button -->
+		<h3><a href=""><%= title %></a></h3>
+
+		<p>By <%= creator %></p>
+		<p><%= description %></p>
+	</li>
+
+</script>
+
+<div class="dialog" id="addwidgetdialog" title="Widget Directory">
+	<ul class="categories">
+	</ul>
+
+	<div class="panel-body">
+		<ol id="category-all" class="widgets">
+		</ol>
+	</div>
+</div>
+
+
+<div class="dialog" id="editLayout" title="Edit layout">
+	<div class="panel-body" id="layout-dialog">
+			<p><strong>Choose dashboard layout</strong></p>
+			<ul class="layoutselection">
+			</ul>
+	</div>
+</div>
\ No newline at end of file

=== added directory 'addons/base_dashboard/static/src/js'
=== added file 'addons/base_dashboard/static/src/js/dashboard.js'
--- addons/base_dashboard/static/src/js/dashboard.js	1970-01-01 00:00:00 +0000
+++ addons/base_dashboard/static/src/js/dashboard.js	2011-04-29 08:50:52 +0000
@@ -0,0 +1,104 @@
+openerp.base_dashboard = function(openerp){
+
+QWeb.add_template('/base_dashboard/static/src/xml/base_dashboard.xml');
+
+openerp.base.form.Board = openerp.base.form.Widget.extend({
+    init: function(view, node) {
+        
+        this._super(view, node);
+        this.template = "Board";
+    },
+    start: function() {
+        this._super.apply(this, arguments);
+        this.$element.html(QWeb.render(this.template));  
+    },
+    
+    render: function() {
+        var self = this;
+        jQuery('body').append(
+            jQuery('<div>', {'id': 'dashboard_template'}).load('/base_dashboard/static/src/dashboard_template.html',self.on_loaded).hide()
+        )
+    },
+    
+    on_loaded: function() {
+        var children = this.node.children;
+        var board = jQuery('#dashboard').dashboard({
+            layoutClass:'layout'
+        });
+        board.init();
+        for(var ch = 0; ch < children.length; ch++) {
+            var ch_widgets = children[ch].children;
+            for(var chld = 0; chld < ch_widgets.length; chld++) {
+                var widget_type = ch_widgets[chld].tag;
+                var child_index = widget_type == 'action' ? chld : ch;
+                var widget = new (openerp.base.form.widgets.get_object(widget_type)) (this.view, ch_widgets[chld], board, child_index);
+                widget.start();
+            }
+        }
+    }
+});
+
+openerp.base.form.Action = openerp.base.form.Widget.extend({
+    init: function(view, node, board, child_index) {
+        this._super(view, node, board, child_index);
+        this.board = board;
+        this.child_index = child_index;
+    },
+    start: function() {
+        this._super.apply(this, arguments);
+        this.rpc('/base_dashboard/dashboard/load',{
+            node_attrs: this.node.attrs
+        },
+        this.on_load_action);
+    },
+    
+    on_load_action: function(result) {
+        var action = result.action;
+        
+        action.search_view = false;
+        action.no_sidebar = true;
+        action.search_view_id = false;
+        
+        var node_attrs = this.node.attrs;
+        var get_column = ['first', 'second', 'third'];
+        var board_element = this.board.element.find('[id=column-'+get_column[this.child_index]+']');
+        
+        this.board.addWidget({
+                    'id': node_attrs.name,
+                    'title': node_attrs.string,
+                }, board_element);
+                
+        var content_id = node_attrs.name+'-widgetcontent';
+        this.board.getWidget(node_attrs.name).element.find('.widgetcontent').attr('id',content_id)
+        
+        action_manager = new openerp.base.ActionManager(this.session, content_id);
+        action_manager.start();
+        this.board.getWidget(node_attrs.name).url = action_manager.do_action(action);
+    }
+})
+
+openerp.base.form.Vpaned = openerp.base.form.Widget.extend({
+    init: function(view, node, board, child_index) {
+        
+        this._super(view, node, board, child_index);
+        this.board = board;
+        this.child_index = child_index;
+    },
+    start: function() {
+        this._super.apply(this, arguments);
+        var children = this.node.children;
+        for(var chld=0; chld<children.length; chld++) {
+            var ch_widget = children[chld].children;
+            for(var ch=0; ch<ch_widget.length; ch++) {
+                var widget_type = ch_widget[ch].tag;
+                var widget = new (openerp.base.form.widgets.get_object(widget_type)) (this.view, ch_widget[ch], this.board, this.child_index);
+                widget.start();
+            }
+        }
+    },
+})
+
+openerp.base.form.widgets.add('hpaned', 'openerp.base.form.Board');
+openerp.base.form.widgets.add('vpaned', 'openerp.base.form.Vpaned');
+openerp.base.form.widgets.add('action', 'openerp.base.form.Action');
+}
\ No newline at end of file

=== added directory 'addons/base_dashboard/static/src/xml'
=== added file 'addons/base_dashboard/static/src/xml/base_dashboard.xml'
--- addons/base_dashboard/static/src/xml/base_dashboard.xml	1970-01-01 00:00:00 +0000
+++ addons/base_dashboard/static/src/xml/base_dashboard.xml	2011-04-29 08:50:52 +0000
@@ -0,0 +1,12 @@
+<template>
+    <t t-name="Board">
+        <div id="dashboard" class="dashboard">
+            <!-- this HTML covers all layouts. The 5 different layouts are handled by setting another layout classname -->
+            <div class="layout">
+              <div id="column-first" class="column first column-first"></div>
+              <div id="column-second" class="column second column-second"></div>
+              <div id="column-third" class="column third column-third"></div>
+            </div>
+        </div>
+    </t>
+</template>
\ No newline at end of file


Follow ups