[MERGE] from master
[odoo/odoo.git] / addons / point_of_sale / static / src / js / widgets.js
index 6a222cd..51f7d19 100644 (file)
@@ -1,55 +1,49 @@
-function openerp_pos_widgets(instance, module){ //module is instance.point_of_sale
-    var QWeb = instance.web.qweb,
-       _t = instance.web._t;
-
-    // The ImageCache is used to hide the latency of the application cache on-disk access in chrome 
-    // that causes annoying flickering on product pictures. Why the hell a simple access to
-    // the application cache involves such latency is beyond me, hopefully one day this can be
-    // removed.
-    module.ImageCache   = instance.web.Class.extend({
+openerp.point_of_sale.load_widgets = function load_widgets(instance, module){ //module is instance.point_of_sale
+    "use strict";
+
+    var QWeb = instance.web.qweb;
+       var _t = instance.web._t;
+
+    module.DomCache = instance.web.Class.extend({
         init: function(options){
             options = options || {};
-            this.max_size = options.max_size || 500;
+            this.max_size = options.max_size || 2000;
 
             this.cache = {};
             this.access_time = {};
             this.size = 0;
         },
-        get_image_uncached: function(url){
-            var img =  new Image();
-            img.src = url;
-            return img;
-        },
-        // returns a DOM Image object from an url, and cache the last 500 (by default) results
-        get_image: function(url){
-            var cached = this.cache[url];
-            if(cached){
-                this.access_time[url] = (new Date()).getTime();
-                return cached;
-            }else{
-                var img = new Image();
-                img.src = url;
+        cache_node: function(key,node){
+            var cached = this.cache[key];
+            this.cache[key] = node;
+            this.access_time[key] = new Date().getTime();
+            if(!cached){
+                this.size++;
                 while(this.size >= this.max_size){
-                    var oldestUrl = null;
-                    var oldestTime = (new Date()).getTime();
-                    for(var url in this.cache){
-                        var time = this.access_time[url];
-                        if(time <= oldestTime){
-                            oldestTime = time;
-                            oldestUrl  = url;
+                    var oldest_key = null;
+                    var oldest_time = new Date().getTime();
+                    for(var key in this.cache){
+                        var time = this.access_time[key];
+                        if(time <= oldest_time){
+                            oldest_time = time;
+                            oldest_key  = key;
                         }
                     }
-                    if(oldestUrl){
-                        delete this.cache[oldestUrl];
-                        delete this.access_time[oldestUrl];
+                    if(oldest_key){
+                        delete this.cache[oldest_key];
+                        delete this.access_time[oldest_key];
                     }
                     this.size--;
                 }
-                this.cache[url] = img;
-                this.access_time[url] = (new Date()).getTime();
-                this.size++;
-                return img;
             }
+            return node;
+        },
+        get_node: function(key){
+            var cached = this.cache[key];
+            if(cached){
+                this.access_time[key] = new Date().getTime();
+            }
+            return cached;
         },
     });
 
@@ -58,6 +52,8 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
         init: function(parent, options) {
             this._super(parent);
             this.state = new module.NumpadState();
+            window.numpadstate = this.state;
+            var self = this;
         },
         start: function() {
             this.state.bind('change:mode', this.changedMode, this);
@@ -89,355 +85,244 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
         },
     });
 
-    // The paypad allows to select the payment method (cashRegisters) 
-    // used to pay the order.
-    module.PaypadWidget = module.PosBaseWidget.extend({
-        template: 'PaypadWidget',
+    // The action pad contains the payment button and the customer selection button
+    module.ActionpadWidget = module.PosBaseWidget.extend({
+        template: 'ActionpadWidget',
         renderElement: function() {
             var self = this;
             this._super();
-
-            this.pos.get('cashRegisters').each(function(cashRegister) {
-                var button = new module.PaypadButtonWidget(self,{
-                    pos: self.pos,
-                    pos_widget : self.pos_widget,
-                    cashRegister: cashRegister,
-                });
-                button.appendTo(self.$el);
+            this.$('.pay').click(function(){
+                self.pos.pos_widget.screen_selector.set_current_screen('payment');
+            });
+            this.$('.set-customer').click(function(){
+                self.pos.pos_widget.screen_selector.set_current_screen('clientlist');
             });
         }
     });
 
-    module.PaypadButtonWidget = module.PosBaseWidget.extend({
-        template: 'PaypadButtonWidget',
-        init: function(parent, options){
-            this._super(parent, options);
-            this.cashRegister = options.cashRegister;
-        },
-        renderElement: function() {
+    module.OrderWidget = module.PosBaseWidget.extend({
+        template:'OrderWidget',
+        init: function(parent, options) {
             var self = this;
-            this._super();
-
-            this.$el.click(function(){
-                if (self.pos.get('selectedOrder').get('screen') === 'receipt'){  //TODO Why ?
-                    console.warn('TODO should not get there...?');
+            this._super(parent,options);
+            this.editable = false;
+            this.pos.bind('change:selectedOrder', this.change_selected_order, this);
+            this.line_click_handler = function(event){
+                if(!self.editable){
                     return;
                 }
-                self.pos.get('selectedOrder').addPaymentLine(self.cashRegister);
-                self.pos_widget.screen_selector.set_current_screen('payment');
-            });
-        },
-    });
-
-    module.OrderlineWidget = module.PosBaseWidget.extend({
-        template: 'OrderlineWidget',
-        init: function(parent, options) {
-            this._super(parent,options);
-
-            this.model = options.model;
-            this.order = options.order;
-
-            this.model.bind('change', this.refresh, this);
-        },
-        renderElement: function() {
-            var self = this;
-            this._super();
-            this.$el.click(function(){
-                self.order.selectLine(self.model);
-                self.trigger('order_line_selected');
-            });
-            if(this.model.is_selected()){
-                this.$el.addClass('selected');
+                self.pos.get_order().select_orderline(this.orderline);
+                self.pos_widget.numpad.state.reset();
+            };
+            this.client_change_handler = function(event){
+                self.update_summary();
+            }
+            if (this.pos.get_order()) {
+                this.bind_order_events();
             }
         },
-        refresh: function(){
-            this.renderElement();
-            this.trigger('order_line_refreshed');
-        },
-        destroy: function(){
-            this.model.unbind('change',this.refresh,this);
-            this._super();
+        enable_numpad: function(){
+            this.disable_numpad();  //ensure we don't register the callbacks twice
+            this.numpad_state = this.pos_widget.numpad.state;
+            if(this.numpad_state){
+                this.numpad_state.reset();
+                this.numpad_state.bind('set_value',   this.set_value, this);
+            }
+                    
         },
-    });
-    
-    module.OrderWidget = module.PosBaseWidget.extend({
-        template:'OrderWidget',
-        init: function(parent, options) {
-            this._super(parent,options);
-            this.display_mode = options.display_mode || 'numpad';   // 'maximized' | 'actionbar' | 'numpad'
-            this.set_numpad_state(options.numpadState);
-            this.pos.bind('change:selectedOrder', this.change_selected_order, this);
-            this.bind_orderline_events();
-            this.orderlinewidgets = [];
+        disable_numpad: function(){
+            if(this.numpad_state){
+                this.numpad_state.unbind('set_value',  this.set_value);
+                this.numpad_state.reset();
+            }
         },
-        set_numpad_state: function(numpadState) {
-               if (this.numpadState) {
-                       this.numpadState.unbind('set_value', this.set_value);
-               }
-               this.numpadState = numpadState;
-               if (this.numpadState) {
-                       this.numpadState.bind('set_value', this.set_value, this);
-                       this.numpadState.reset();
-               }
+        set_editable: function(editable){
+            var order = this.pos.get_order();
+            if (order) {
+                this.editable = editable;
+                if (editable) {
+                    this.enable_numpad();
+                }else{
+                    this.disable_numpad();
+                    order.deselect_orderline();
+                }
+            }
         },
         set_value: function(val) {
-               var order = this.pos.get('selectedOrder');
-               if (order.get('orderLines').length !== 0) {
-                var mode = this.numpadState.get('mode');
+               var order = this.pos.get_order();
+               if (this.editable && order.get_selected_orderline()) {
+                var mode = this.numpad_state.get('mode');
                 if( mode === 'quantity'){
-                    order.getSelectedLine().set_quantity(val);
+                    order.get_selected_orderline().set_quantity(val);
                 }else if( mode === 'discount'){
-                    order.getSelectedLine().set_discount(val);
+                    order.get_selected_orderline().set_discount(val);
                 }else if( mode === 'price'){
-                    order.getSelectedLine().set_unit_price(val);
+                    order.get_selected_orderline().set_unit_price(val);
                 }
                }
         },
         change_selected_order: function() {
-            this.currentOrderLines.unbind();
-            this.bind_orderline_events();
-            this.renderElement();
+            if (this.pos.get_order()) {
+                this.bind_order_events();
+                this.renderElement();
+            }
         },
-        bind_orderline_events: function() {
-            this.currentOrderLines = (this.pos.get('selectedOrder')).get('orderLines');
-            this.currentOrderLines.bind('add', function(){ this.renderElement(true);}, this);
-            this.currentOrderLines.bind('remove', this.renderElement, this);
+        orderline_add: function(){
+            this.numpad_state.reset();
+            this.renderElement('and_scroll_to_bottom');
         },
-        update_numpad: function() {
-            this.selected_line = this.pos.get('selectedOrder').getSelectedLine();
-            if (this.numpadState)
-                this.numpadState.reset();
+        orderline_remove: function(line){
+            this.remove_orderline(line);
+            this.numpad_state.reset();
+            this.update_summary();
         },
-        renderElement: function(goto_bottom) {
-            var self = this;
-            var scroller = this.$('.order-scroller')[0];
-            var scrollbottom = true;
-            var scrollTop = 0;
-            /*if(scroller){
-                var overflow_bottom = scroller.scrollHeight - scroller.clientHeight;
-                scrollTop = scroller.scrollTop;
-                if( !goto_bottom && scrollTop < 0.9 * overflow_bottom){
-                    scrollbottom = false;
-                }
-            }*/
-            this._super();
-
-            // freeing subwidgets
-            
-            for(var i = 0, len = this.orderlinewidgets.length; i < len; i++){
-                this.orderlinewidgets[i].destroy();
-            }
-            this.orderlinewidgets = [];
-
-            var $content = this.$('.orderlines');
-            this.currentOrderLines.each(_.bind( function(orderLine) {
-                var line = new module.OrderlineWidget(this, {
-                        model: orderLine,
-                        order: this.pos.get('selectedOrder'),
-                });
-               line.on('order_line_selected', self, self.update_numpad);
-                line.on('order_line_refreshed', self, self.update_summary);
-                line.appendTo($content);
-                self.orderlinewidgets.push(line);
-            }, this));
-            this.update_numpad();
+        orderline_change: function(line){
+            this.rerender_orderline(line);
             this.update_summary();
-
-            scroller = this.$('.order-scroller')[0];
-            if(scroller){
-                //scroller.scrollTop = 1000000;
-                /*
-                if(scrollbottom){
-                    scroller.scrollTop = scroller.scrollHeight - scroller.clientHeight;
-                }else{
-                    scroller.scrollTop = scrollTop;
-                }*/
-            }
         },
-        update_summary: function(){
-            var order = this.pos.get('selectedOrder');
-            var total     = order ? order.getTotalTaxIncluded() : 0;
-            var taxes     = order ? total - order.getTotalTaxExcluded() : 0;
-            this.$('.summary .total > .value').html(this.format_currency(total));
-            this.$('.summary .total .subentry .value').html(this.format_currency(taxes));
-        },
-        set_display_mode: function(mode){
-            if(this.display_mode !== mode){
-                this.display_mode = mode;
+        bind_order_events: function() {
+            var order = this.pos.get_order();
+                order.unbind('change:client', this.client_change_handler);
+                order.bind('change:client', this.client_change_handler);
+
+            var lines = order.orderlines;
+                lines.unbind('add',     this.orderline_add,    this);
+                lines.bind('add',       this.orderline_add,    this);
+                lines.unbind('remove',  this.orderline_remove, this);
+                lines.bind('remove',    this.orderline_remove, this); 
+                lines.unbind('change',  this.orderline_change, this);
+                lines.bind('change',    this.orderline_change, this);
+
+        },
+        render_orderline: function(orderline){
+            var el_str  = openerp.qweb.render('Orderline',{widget:this, line:orderline}); 
+            var el_node = document.createElement('div');
+                el_node.innerHTML = _.str.trim(el_str);
+                el_node = el_node.childNodes[0];
+                el_node.orderline = orderline;
+                el_node.addEventListener('click',this.line_click_handler);
+
+            orderline.node = el_node;
+            return el_node;
+        },
+        remove_orderline: function(order_line){
+            if(this.pos.get_order().get_orderlines().length === 0){
                 this.renderElement();
+            }else{
+                order_line.node.parentNode.removeChild(order_line.node);
             }
         },
-    });
-
-
-    module.PaymentlineWidget = module.PosBaseWidget.extend({
-        template: 'PaymentlineWidget',
-        init: function(parent, options) {
-            this._super(parent,options);
-            this.payment_line = options.payment_line;
-            this.payment_line.bind('change', this.changedAmount, this);
-        },
-        changeAmount: function(event) {
-            var newAmount = event.currentTarget.value;
-            var amount = parseFloat(newAmount);
-            if(!isNaN(amount)){
-                this.amount = amount;
-                this.payment_line.set_amount(amount);
-            }
+        rerender_orderline: function(order_line){
+            var node = order_line.node;
+            var replacement_line = this.render_orderline(order_line);
+            node.parentNode.replaceChild(replacement_line,node);
         },
-        checkAmount: function(e){
-            if (e.which !== 0 && e.charCode !== 0) {
-                if(isNaN(String.fromCharCode(e.charCode))){
-                    return (String.fromCharCode(e.charCode) === "." && e.currentTarget.value.toString().split(".").length < 2)?true:false;
-                }
-            }
-            return true
+        // overriding the openerp framework replace method for performance reasons
+        replace: function($target){
+            this.renderElement();
+            var target = $target[0];
+            target.parentNode.replaceChild(this.el,target);
         },
-        changedAmount: function() {
-               if (this.amount !== this.payment_line.get_amount()){
-                       this.renderElement();
+        renderElement: function(scrollbottom){
+            this.pos_widget.numpad.state.reset();
+
+            var order  = this.pos.get_order();
+            if (!order) {
+                return;
             }
-        },
-        renderElement: function() {
-            var self = this;
-            this.name =   this.payment_line.get_cashregister().get('journal_id')[1];
-            this._super();
-            this.$('input').keypress(_.bind(this.checkAmount, this))
-                       .keyup(function(event){
-                self.changeAmount(event);
-            });
-            this.$('.delete-payment-line').click(function() {
-                self.trigger('delete_payment_line', self);
-            });
-        },
-        focus: function(){
-            var val = this.$('input')[0].value;
-            this.$('input')[0].focus();
-            this.$('input')[0].value = val;
-            this.$('input')[0].select();
-        },
-    });
+            var orderlines = order.get_orderlines();
 
-    module.OrderButtonWidget = module.PosBaseWidget.extend({
-        template:'OrderButtonWidget',
-        init: function(parent, options) {
-            this._super(parent,options);
-            var self = this;
+            var el_str  = openerp.qweb.render('OrderWidget',{widget:this, order:order, orderlines:orderlines});
 
-            this.order = options.order;
-            this.order.bind('destroy',this.destroy, this );
-            this.order.bind('change', this.renderElement, this );
-            this.pos.bind('change:selectedOrder', this.renderElement,this );
-        },
-        renderElement:function(){
-            this._super();
-            var self = this;
-            this.$el.click(function(){ 
-                self.selectOrder();
-            });
-            if( this.order === this.pos.get('selectedOrder') ){
-                this.$el.addClass('selected-order');
+            var el_node = document.createElement('div');
+                el_node.innerHTML = _.str.trim(el_str);
+                el_node = el_node.childNodes[0];
+
+
+            var list_container = el_node.querySelector('.orderlines');
+            for(var i = 0, len = orderlines.length; i < len; i++){
+                var orderline = this.render_orderline(orderlines[i]);
+                list_container.appendChild(orderline);
             }
-        },
-        selectOrder: function(event) {
-            this.pos.set({
-                selectedOrder: this.order
-            });
-        },
-        destroy: function(){
-            this.order.unbind('destroy', this.destroy, this);
-            this.order.unbind('change',  this.renderElement, this);
-            this.pos.unbind('change:selectedOrder', this.renderElement, this);
-            this._super();
-        },
-    });
 
-    module.ActionButtonWidget = instance.web.Widget.extend({
-        template:'ActionButtonWidget',
-        icon_template:'ActionButtonWidgetWithIcon',
-        init: function(parent, options){
-            this._super(parent, options);
-            this.label = options.label || 'button';
-            this.rightalign = options.rightalign || false;
-            this.click_action = options.click;
-            this.disabled = options.disabled || false;
-            if(options.icon){
-                this.icon = options.icon;
-                this.template = this.icon_template;
+            if(this.el && this.el.parentNode){
+                this.el.parentNode.replaceChild(el_node,this.el);
             }
-        },
-        set_disabled: function(disabled){
-            if(this.disabled != disabled){
-                this.disabled = !!disabled;
-                this.renderElement();
+            this.el = el_node;
+            this.update_summary();
+
+            if(scrollbottom){
+                this.el.querySelector('.order-scroller').scrollTop = 100 * orderlines.length;
             }
         },
-        renderElement: function(){
-            this._super();
-            if(this.click_action && !this.disabled){
-                this.$el.click(_.bind(this.click_action, this));
-            }
+        update_summary: function(){
+            var order = this.pos.get_order();
+            var total     = order ? order.get_total_with_tax() : 0;
+            var taxes     = order ? total - order.get_total_without_tax() : 0;
+
+            this.el.querySelector('.summary .total > .value').textContent = this.format_currency(total);
+            this.el.querySelector('.summary .total .subentry .value').textContent = this.format_currency(taxes);
+
         },
     });
 
-    module.ActionBarWidget = instance.web.Widget.extend({
-        template:'ActionBarWidget',
-        init: function(parent, options){
-            this._super(parent,options);
-            this.button_list = [];
-            this.buttons = {};
-            this.visibility = {};
-        },
-        set_element_visible: function(element, visible, action){
-            if(visible != this.visibility[element]){
-                this.visibility[element] = !!visible;
-                if(visible){
-                    this.$('.'+element).removeClass('oe_hidden');
-                }else{
-                    this.$('.'+element).addClass('oe_hidden');
+    module.OrderSelectorWidget = module.PosBaseWidget.extend({
+        template: 'OrderSelectorWidget',
+        init: function(parent, options) {
+            this._super(parent, options);
+            this.pos.get('orders').bind('add remove change',this.renderElement,this);
+            this.pos.bind('change:selectedOrder',this.renderElement,this);
+        },
+        get_order_by_uid: function(uid) {
+            var orders = this.pos.get_order_list();
+            for (var i = 0; i < orders.length; i++) {
+                if (orders[i].uid === uid) {
+                    return orders[i];
                 }
             }
-            if(visible && action){
-                this.action[element] = action;
-                this.$('.'+element).off('click').click(action);
-            }
+            return undefined;
         },
-        set_button_disabled: function(name, disabled){
-            var b = this.buttons[name];
-            if(b){
-                b.set_disabled(disabled);
+        order_click_handler: function(event,$el) {
+            var order = this.get_order_by_uid($el.data('uid'));
+            if (order) {
+                this.pos.set_order(order);
             }
         },
-        destroy_buttons:function(){
-            for(var i = 0; i < this.button_list.length; i++){
-                this.button_list[i].destroy();
-            }
-            this.button_list = [];
-            this.buttons = {};
-            return this;
-        },
-        get_button_count: function(){
-            return this.button_list.length;
-        },
-        add_new_button: function(button_options){
-            var button = new module.ActionButtonWidget(this,button_options);
-            this.button_list.push(button);
-            if(button_options.name){
-                this.buttons[button_options.name] = button;
+        neworder_click_handler: function(event, $el) {
+            this.pos.add_new_order();
+        },
+        deleteorder_click_handler: function(event, $el) {
+            var self  = this;
+            var order = this.pos.get_order(); 
+            if (!order) {
+                return;
+            } else if ( !order.is_empty() ){
+                this.pos_widget.screen_selector.show_popup('confirm',{
+                    message: _t('Destroy Current Order ?'),
+                    comment: _t('You will lose any data associated with the current order'),
+                    confirm: function(){
+                        self.pos.delete_current_order();
+                    },
+                });
+            } else {
+                this.pos.delete_current_order();
             }
-            button.appendTo(this.$('.pos-actionbar-button-list'));
-            return button;
         },
-        show:function(){
-            this.$el.removeClass('oe_hidden');
-        },
-        hide:function(){
-            this.$el.addClass('oe_hidden');
+        renderElement: function(){
+            var self = this;
+            this._super();
+            this.$('.order-button.select-order').click(function(event){
+                self.order_click_handler(event,$(this));
+            });
+            this.$('.neworder-button').click(function(event){
+                self.neworder_click_handler(event,$(this));
+            });
+            this.$('.deleteorder-button').click(function(event){
+                self.deleteorder_click_handler(event,$(this));
+            });
         },
     });
 
-    module.CategoryButton = module.PosBaseWidget.extend({
-    });
     module.ProductCategoriesWidget = module.PosBaseWidget.extend({
         template: 'ProductCategoriesWidget',
         init: function(parent, options){
@@ -448,7 +333,29 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
             this.category = this.pos.root_category;
             this.breadcrumb = [];
             this.subcategories = [];
+            this.product_list_widget = options.product_list_widget || null;
+            this.category_cache = new module.DomCache();
             this.set_category();
+            
+            this.switch_category_handler = function(event){
+                self.set_category(self.pos.db.get_category_by_id(Number(this.dataset['categoryId'])));
+                self.renderElement();
+            };
+            
+            this.clear_search_handler = function(event){
+                self.clear_search();
+            };
+
+            var search_timeout  = null;
+            this.search_handler = function(event){
+                clearTimeout(search_timeout);
+
+                var query = this.value;
+
+                search_timeout = setTimeout(function(){
+                    self.perform_search(self.category, query, event.which === 13);
+                },70);
+            };
         },
 
         // changes the category. if undefined, sets to root category
@@ -471,59 +378,96 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
         },
 
         get_image_url: function(category){
-            return instance.session.url('/web/binary/image', {model: 'pos.category', field: 'image_medium', id: category.id});
+            return window.location.origin + '/web/binary/image?model=pos.category&field=image_medium&id='+category.id;
+        },
+
+        render_category: function( category, with_image ){
+            var cached = this.category_cache.get_node(category.id);
+            if(!cached){
+                if(with_image){
+                    var image_url = this.get_image_url(category);
+                    var category_html = QWeb.render('CategoryButton',{ 
+                            widget:  this, 
+                            category: category, 
+                            image_url: this.get_image_url(category),
+                        });
+                        category_html = _.str.trim(category_html);
+                    var category_node = document.createElement('div');
+                        category_node.innerHTML = category_html;
+                        category_node = category_node.childNodes[0];
+                }else{
+                    var category_html = QWeb.render('CategorySimpleButton',{ 
+                            widget:  this, 
+                            category: category, 
+                        });
+                        category_html = _.str.trim(category_html);
+                    var category_node = document.createElement('div');
+                        category_node.innerHTML = category_html;
+                        category_node = category_node.childNodes[0];
+                }
+                this.category_cache.cache_node(category.id,category_node);
+                return category_node;
+            }
+            return cached; 
+        },
+
+        replace: function($target){
+            this.renderElement();
+            var target = $target[0];
+            target.parentNode.replaceChild(this.el,target);
         },
 
         renderElement: function(){
             var self = this;
-            this._super();
+
+            var el_str  = openerp.qweb.render(this.template, {widget: this});
+            var el_node = document.createElement('div');
+                el_node.innerHTML = el_str;
+                el_node = el_node.childNodes[1];
+
+            if(this.el && this.el.parentNode){
+                this.el.parentNode.replaceChild(el_node,this.el);
+            }
+
+            this.el = el_node;
 
             var hasimages = false;  //if none of the subcategories have images, we don't display buttons with icons
-            _.each(this.subcategories, function(category){
-                if(category.image){
+            for(var i = 0; i < this.subcategories.length; i++){
+                if(this.subcategories[i].image){
                     hasimages = true;
+                    break;
                 }
-            });
+            }
 
-            _.each(this.subcategories, function(category){
-                if(hasimages){
-                    var button = QWeb.render('CategoryButton',{category:category});
-                    var button = _.str.trim(button);
-                    var button = $(button);
-                    button.find('img').replaceWith(self.pos_widget.image_cache.get_image(self.get_image_url(category)));
-                }else{
-                    var button = QWeb.render('CategorySimpleButton',{category:category});
-                    button = _.str.trim(button);    // we remove whitespace between buttons to fix spacing
-                    var button = $(button);
+            var list_container = el_node.querySelector('.category-list');
+            if (list_container) { 
+                if (!hasimages) {
+                    list_container.classList.add('simple');
+                } else {
+                    list_container.classList.remove('simple');
                 }
+                for(var i = 0, len = this.subcategories.length; i < len; i++){
+                    list_container.appendChild(this.render_category(this.subcategories[i],hasimages));
+                };
+            }
 
-                button.appendTo(this.$('.category-list')).click(function(event){
-                    var id = category.id;
-                    var cat = self.pos.db.get_category_by_id(id);
-                    self.set_category(cat);
-                    self.renderElement();
-                });
-            });
-            // breadcrumb click actions
-            this.$(".oe-pos-categories-list a").click(function(event){
-                var id = $(event.target).data("category-id");
-                var category = self.pos.db.get_category_by_id(id);
-                self.set_category(category);
-                self.renderElement();
-            });
+            var buttons = el_node.querySelectorAll('.js-category-switch');
+            for(var i = 0; i < buttons.length; i++){
+                buttons[i].addEventListener('click',this.switch_category_handler);
+            }
+
+            var products = this.pos.db.get_product_by_category(this.category.id);
+            this.product_list_widget.set_product_list(products);
 
-            this.search_and_categories();
+            this.el.querySelector('.searchbox input').addEventListener('keyup',this.search_handler);
 
-            if(this.pos.iface_vkeyboard && this.pos_widget.onscreen_keyboard){
-                this.pos_widget.onscreen_keyboard.connect(this.$('.searchbox input'));
+            this.el.querySelector('.search-clear').addEventListener('click',this.clear_search_handler);
+
+            if(this.pos.config.iface_vkeyboard && this.pos_widget.onscreen_keyboard){
+                this.pos_widget.onscreen_keyboard.connect($(this.el.querySelector('.searchbox input')));
             }
         },
         
-        set_product_type: function(type){       // 'all' | 'weightable'
-            this.product_type = type;
-            this.reset_category();
-        },
-
         // resets the current category to the root category
         reset_category: function(){
             this.set_category();
@@ -533,56 +477,29 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
         // empties the content of the search box
         clear_search: function(){
             var products = this.pos.db.get_product_by_category(this.category.id);
-            this.pos.get('products').reset(products);
-            this.$('.searchbox input').val('').focus();
-            this.$('.search-clear').fadeOut();
+            this.product_list_widget.set_product_list(products);
+            var input = this.el.querySelector('.searchbox input');
+                input.value = '';
+                input.focus();
+        },
+        perform_search: function(category, query, buy_result){
+            if(query){
+                var products = this.pos.db.search_product_in_category(category.id,query)
+                if(buy_result && products.length === 1){
+                        this.pos.get_order().add_product(products[0]);
+                        this.clear_search();
+                }else{
+                    this.product_list_widget.set_product_list(products);
+                }
+            }else{
+                var products = this.pos.db.get_product_by_category(this.category.id);
+                this.product_list_widget.set_product_list(products);
+            }
         },
 
-        // filters the products, and sets up the search callbacks
-        search_and_categories: function(category){
-            var self = this;
-
-            // find all products belonging to the current category
-            var products = this.pos.db.get_product_by_category(this.category.id);
-            self.pos.get('products').reset(products);
-
-
-            var searchtimeout = null;
-            // filter the products according to the search string
-            this.$('.searchbox input').keyup(function(event){
-
-                clearTimeout(searchtimeout);
-
-                var query = $(this).val().toLowerCase();
-                
-                searchtimeout = setTimeout(function(){
-                    if(query){
-                        if(event.which === 13){
-                            if( self.pos.get('products').size() === 1 ){
-                                self.pos.get('selectedOrder').addProduct(self.pos.get('products').at(0));
-                                self.clear_search();
-                            }
-                        }else{
-                            var products = self.pos.db.search_product_in_category(self.category.id, query);
-                            self.pos.get('products').reset(products);
-                            self.$('.search-clear').fadeIn();
-                        }
-                    }else{
-                        var products = self.pos.db.get_product_by_category(self.category.id);
-                        self.pos.get('products').reset(products);
-                        self.$('.search-clear').fadeOut();
-                    }
-                },200);
-            });
-
-            //reset the search when clicking on reset
-            this.$('.search-clear').click(function(){
-                self.clear_search();
-            });
-        },
     });
 
-    module.ProductListWidget = module.ScreenWidget.extend({
+    module.ProductListWidget = module.PosBaseWidget.extend({
         template:'ProductListWidget',
         init: function(parent, options) {
             var self = this;
@@ -592,27 +509,66 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
             this.weight = options.weight || 0;
             this.show_scale = options.show_scale || false;
             this.next_screen = options.next_screen || false;
-            this.click_product_action = options.click_product_action;
 
-            this.pos.get('products').bind('reset', function(){
-                self.renderElement();
-            });
+            this.click_product_handler = function(event){
+                var product = self.pos.db.get_product_by_id(this.dataset['productId']);
+                options.click_product_action(product);
+            };
+
+            this.product_list = options.product_list || [];
+            this.product_cache = new module.DomCache();
+        },
+        set_product_list: function(product_list){
+            this.product_list = product_list;
+            this.renderElement();
+        },
+        get_product_image_url: function(product){
+            return window.location.origin + '/web/binary/image?model=product.product&field=image_medium&id='+product.id;
         },
+        replace: function($target){
+            this.renderElement();
+            var target = $target[0];
+            target.parentNode.replaceChild(this.el,target);
+        },
+
+        render_product: function(product){
+            var cached = this.product_cache.get_node(product.id);
+            if(!cached){
+                var image_url = this.get_product_image_url(product);
+                var product_html = QWeb.render('Product',{ 
+                        widget:  this, 
+                        product: product, 
+                        image_url: this.get_product_image_url(product),
+                    });
+                var product_node = document.createElement('div');
+                product_node.innerHTML = product_html;
+                product_node = product_node.childNodes[1];
+                this.product_cache.cache_node(product.id,product_node);
+                return product_node;
+            }
+            return cached;
+        },
+
         renderElement: function() {
             var self = this;
-            this._super();
 
-            var products = this.pos.get('products').models || [];
+            // this._super()
+            var el_str  = openerp.qweb.render(this.template, {widget: this});
+            var el_node = document.createElement('div');
+                el_node.innerHTML = el_str;
+                el_node = el_node.childNodes[1];
 
-            _.each(products,function(product,i){
-                var $product = $(QWeb.render('Product',{ widget:self, product: products[i] }));
-                $product.find('img').replaceWith(self.pos_widget.image_cache.get_image(products[i].get_image_url()));
-                $product.appendTo(self.$('.product-list'));
-            });
-            this.$el.delegate('a','click',function(){ 
-                self.click_product_action(new module.Product(self.pos.db.get_product_by_id(+$(this).data('product-id')))); 
-            });
+            if(this.el && this.el.parentNode){
+                this.el.parentNode.replaceChild(el_node,this.el);
+            }
+            this.el = el_node;
 
+            var list_container = el_node.querySelector('.product-list');
+            for(var i = 0, len = this.product_list.length; i < len; i++){
+                var product_node = this.render_product(this.product_list[i]);
+                product_node.addEventListener('click',this.click_product_handler);
+                list_container.appendChild(product_node);
+            };
         },
     });
 
@@ -621,22 +577,32 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
         init: function(parent, options){
             var options = options || {};
             this._super(parent,options);
-            this.mode = options.mode || 'cashier';
         },
         set_user_mode: function(mode){
             this.mode = mode;
-            this.refresh();
-        },
-        refresh: function(){
             this.renderElement();
         },
+        renderElement: function(){
+            var self = this;
+            this._super();
+
+            this.$el.click(function(){
+                self.click_username();
+            });
+        },
+        click_username: function(){
+            var self = this;
+            this.pos_widget.select_user({
+                'security':     true,
+                'current_user': this.pos.get_cashier(),
+                'message':      _t('Change Cashier'),
+            }).then(function(user){
+                self.pos.set_cashier(user);
+                self.renderElement();
+            });
+        },
         get_name: function(){
-            var user;
-            if(this.mode === 'cashier'){
-                user = this.pos.get('cashier') || this.pos.get('user');
-            }else{
-                user = this.pos.get('selectedOrder').get_client()  || this.pos.get('user');
-            }
+            var user = this.pos.cashier || this.pos.user;
             if(user){
                 return user.name;
             }else{
@@ -680,20 +646,67 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
             unknown_product: '9900000000004',
         },
         events:[
-            'scan_item_success',
-            'scan_item_error_unrecognized',
-            'payment_request',
             'open_cashbox',
             'print_receipt',
-            'print_pdf_invoice',
-            'weighting_read_kg',
-            'payment_status',
+            'scale_read',
         ],
         minimized: false,
+        init: function(parent,options){
+            this._super(parent,options);
+            var self = this;
+            
+            this.minimized = false;
+
+            // for dragging the debug widget around
+            this.dragging  = false;
+            this.dragpos = {x:0, y:0};
+
+            function eventpos(event){
+                if(event.touches && event.touches[0]){
+                    return {x: event.touches[0].screenX, y: event.touches[0].screenY};
+                }else{
+                    return {x: event.screenX, y: event.screenY};
+                }
+            }
+
+            this.dragend_handler = function(event){
+                self.dragging = false;
+            };
+            this.dragstart_handler = function(event){
+                self.dragging = true;
+                self.dragpos = eventpos(event);
+            };
+            this.dragmove_handler = function(event){
+                if(self.dragging){
+                    var top = this.offsetTop;
+                    var left = this.offsetLeft;
+                    var pos  = eventpos(event);
+                    var dx   = pos.x - self.dragpos.x; 
+                    var dy   = pos.y - self.dragpos.y; 
+
+                    self.dragpos = pos;
+
+                    this.style.right = 'auto';
+                    this.style.bottom = 'auto';
+                    this.style.left = left + dx + 'px';
+                    this.style.top  = top  + dy + 'px';
+                }
+                event.preventDefault();
+                event.stopPropagation();
+            };
+        },
         start: function(){
             var self = this;
 
-            this.$el.draggable();
+            this.el.addEventListener('mouseleave', this.dragend_handler);
+            this.el.addEventListener('mouseup',    this.dragend_handler);
+            this.el.addEventListener('touchend',   this.dragend_handler);
+            this.el.addEventListener('touchcancel',this.dragend_handler);
+            this.el.addEventListener('mousedown',  this.dragstart_handler);
+            this.el.addEventListener('touchstart', this.dragstart_handler);
+            this.el.addEventListener('mousemove',  this.dragmove_handler);
+            this.el.addEventListener('touchmove',  this.dragmove_handler);
+
             this.$('.toggle').click(function(){
                 var content = self.$('.content');
                 var bg      = self.$el;
@@ -704,12 +717,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
                 }
                 self.minimized = !self.minimized;
             });
-            this.$('.button.accept_payment').click(function(){
-                self.pos.proxy.debug_accept_payment();
-            });
-            this.$('.button.reject_payment').click(function(){
-                self.pos.proxy.debug_reject_payment();
-            });
             this.$('.button.set_weight').click(function(){
                 var kg = Number(self.$('input.weight').val());
                 if(!isNaN(kg)){
@@ -723,15 +730,41 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
             this.$('.button.custom_ean').click(function(){
                 var ean = self.pos.barcode_reader.sanitize_ean(self.$('input.ean').val() || '0');
                 self.$('input.ean').val(ean);
-                self.pos.barcode_reader.scan('ean13',ean);
+                self.pos.barcode_reader.scan(ean);
             });
             this.$('.button.reference').click(function(){
-                self.pos.barcode_reader.scan('reference',self.$('input.ean').val());
+                self.pos.barcode_reader.scan(self.$('input.ean').val());
+            });
+            this.$('.button.show_orders').click(function(){
+                self.pos.pos_widget.screen_selector.show_popup('unsent-orders');
+            });
+            this.$('.button.delete_orders').click(function(){
+                self.pos.pos_widget.screen_selector.show_popup('confirm',{
+                    message: _t('Delete Unsent Orders ?'),
+                    comment: _t('This operation will permanently destroy all unsent orders from the local storage. You will lose all the data. This operation cannot be undone.'),
+                    confirm: function(){
+                        self.pos.db.remove_all_orders();
+                        self.pos.set({synch: { state:'connected', pending: 0 }});
+                    },
+                });
+            });
+            this.$('.button.show_unpaid_orders').click(function(){
+                self.pos.pos_widget.screen_selector.show_popup('unpaid-orders');
+            });
+            this.$('.button.delete_unpaid_orders').click(function(){
+                self.pos.pos_widget.screen_selector.show_popup('confirm',{
+                    message: _t('Delete Unpaid Orders ?'),
+                    comment: _t('This operation will permanently destroy all unpaid orders from all sessions that have been put in the local storage. You will lose all the data and exit the point of sale. This operation cannot be undone.'),
+                    confirm: function(){
+                        self.pos.db.remove_all_unpaid_orders();
+                        window.location = '/';
+                    },
+                });
             });
             _.each(this.eans, function(ean, name){
                 self.$('.button.'+name).click(function(){
                     self.$('input.ean').val(ean);
-                    self.pos.barcode_reader.scan('ean13',ean);
+                    self.pos.barcode_reader.scan(ean);
                 });
             });
             _.each(this.events, function(name){
@@ -740,51 +773,91 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
                     self.$('.event.'+name).animate({'background-color':'#1E1E1E'},2000);
                 });
             });
-            self.pos.proxy.add_notification('help_needed',function(){
-                self.$('.status.help_needed').addClass('on');
-            });
-            self.pos.proxy.add_notification('help_canceled',function(){
-                self.$('.status.help_needed').removeClass('on');
-            });
-            self.pos.proxy.add_notification('transaction_start',function(){
-                self.$('.status.transaction').addClass('on');
-            });
-            self.pos.proxy.add_notification('transaction_end',function(){
-                self.$('.status.transaction').removeClass('on');
-            });
-            self.pos.proxy.add_notification('weighting_start',function(){
-                self.$('.status.weighting').addClass('on');
-            });
-            self.pos.proxy.add_notification('weighting_end',function(){
-                self.$('.status.weighting').removeClass('on');
-            });
         },
     });
 
 // ---------- Main Point of Sale Widget ----------
 
-    // this is used to notify the user that data is being synchronized on the network
-    module.SynchNotificationWidget = module.PosBaseWidget.extend({
-        template: "SynchNotificationWidget",
-        init: function(parent, options){
-            options = options || {};
-            this._super(parent, options);
+    module.StatusWidget = module.PosBaseWidget.extend({
+        status: ['connected','connecting','disconnected','warning'],
+        set_status: function(status,msg){
+            var self = this;
+            for(var i = 0; i < this.status.length; i++){
+                this.$('.js_'+this.status[i]).addClass('oe_hidden');
+            }
+            this.$('.js_'+status).removeClass('oe_hidden');
+            
+            if(msg){
+                this.$('.js_msg').removeClass('oe_hidden').html(msg);
+            }else{
+                this.$('.js_msg').addClass('oe_hidden').html('');
+            }
         },
-        renderElement: function() {
+    });
+
+    // this is used to notify the user that data is being synchronized on the network
+    module.SynchNotificationWidget = module.StatusWidget.extend({
+        template: 'SynchNotificationWidget',
+        start: function(){
             var self = this;
-            this._super();
+            this.pos.bind('change:synch', function(pos,synch){
+                self.set_status(synch.state, synch.pending);
+            });
             this.$el.click(function(){
-                self.pos.flush();
+                self.pos.push_order();
             });
         },
+    });
+
+    // this is used to notify the user if the pos is connected to the proxy
+    module.ProxyStatusWidget = module.StatusWidget.extend({
+        template: 'ProxyStatusWidget',
+        set_smart_status: function(status){
+            if(status.status === 'connected'){
+                var warning = false;
+                var msg = ''
+                if(this.pos.config.iface_scan_via_proxy){
+                    var scanner = status.drivers.scanner ? status.drivers.scanner.status : false;
+                    if( scanner != 'connected' && scanner != 'connecting'){
+                        warning = true;
+                        msg += _t('Scanner');
+                    }
+                }
+                if( this.pos.config.iface_print_via_proxy || 
+                    this.pos.config.iface_cashdrawer ){
+                    var printer = status.drivers.escpos ? status.drivers.escpos.status : false;
+                    if( printer != 'connected' && printer != 'connecting'){
+                        warning = true;
+                        msg = msg ? msg + ' & ' : msg;
+                        msg += _t('Printer');
+                    }
+                }
+                if( this.pos.config.iface_electronic_scale ){
+                    var scale = status.drivers.scale ? status.drivers.scale.status : false;
+                    if( scale != 'connected' && scale != 'connecting' ){
+                        warning = true;
+                        msg = msg ? msg + ' & ' : msg;
+                        msg += _t('Scale');
+                    }
+                }
+                msg = msg ? msg + ' ' + _t('Offline') : msg;
+                this.set_status(warning ? 'warning' : 'connected', msg);
+            }else{
+                this.set_status(status.status,'');
+            }
+        },
         start: function(){
             var self = this;
-            this.pos.bind('change:nbr_pending_operations', function(){
-                self.renderElement();
+            
+            this.set_smart_status(this.pos.proxy.get('status'));
+
+            this.pos.proxy.on('change:status',this,function(eh,status){ //FIXME remove duplicate changes 
+                self.set_smart_status(status.newValue);
+            });
+
+            this.$el.click(function(){
+                self.pos.connect_to_proxy();
             });
-        },
-        get_nbr_pending: function(){
-            return this.pos.get('nbr_pending_operations');
         },
     });
 
@@ -794,7 +867,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
     // - a header, containing the list of orders
     // - a leftpane, containing the list of bought products (orderlines) 
     // - a rightpane, containing the screens (see pos_screens.js)
-    // - an actionbar on the bottom, containing various action buttons
     // - popups
     // - an onscreen keyboard
     // a screen_selector which controls the switching between screens and the showing/closing of popups
@@ -804,101 +876,212 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
         init: function() { 
             this._super(arguments[0],{});
 
-            instance.web.blockUI(); 
-
-            this.pos = new module.PosModel(this.session);
-            this.pos.pos_widget = this;
+            this.pos = new module.PosModel(this.session,{pos_widget:this});
             this.pos_widget = this; //So that pos_widget's childs have pos_widget set automatically
 
             this.numpad_visible = true;
-            this.left_action_bar_visible = true;
             this.leftpane_visible = true;
             this.leftpane_width   = '440px';
             this.cashier_controls_visible = true;
-            this.image_cache = new module.ImageCache(); // for faster products image display
 
             FastClick.attach(document.body);
 
         },
-      
+
+        disable_rubberbanding: function(){
+            // prevent the pos body from being scrollable. 
+            document.body.addEventListener('touchmove',function(event){
+                var node = event.target;
+                while(node){
+                    if(node.classList && node.classList.contains('touch-scrollable')){
+                        return;
+                    }
+                    node = node.parentNode;
+                }
+                event.preventDefault();
+            });
+        },
+
         start: function() {
             var self = this;
             return self.pos.ready.done(function() {
-                $('.oe_tooltip').remove();  // remove tooltip from the start session button
+                // remove default webclient handlers that induce click delay
+                $(document).off();
+                $(window).off();
+                $('html').off();
+                $('body').off();
+                $(self.$el).parent().off();
+                $('document').off();
+                $('.oe_web_client').off();
+                $('.openerp_webclient_container').off();
 
-                self.build_currency_template();
                 self.renderElement();
                 
-                self.$('.neworder-button').click(function(){
-                    self.pos.add_new_order();
-                });
-
-                self.$('.deleteorder-button').click(function(){
-                    self.pos.delete_current_order();
-                });
-                
-                $('body').on('keyup',function(event){
-                    if(event.which === 13){
-                        self.set_fullscreen();
-                    }
-                });
-                
-                //when a new order is created, add an order button widget
-                self.pos.get('orders').bind('add', function(new_order){
-                    var new_order_button = new module.OrderButtonWidget(null, {
-                        order: new_order,
-                        pos: self.pos
-                    });
-                    new_order_button.appendTo(this.$('.orders'));
-                    new_order_button.selectOrder();
-                }, self);
-
-                self.pos.add_new_order();
+                self.pos.load_orders();
+                self.pos.set_start_order();
 
                 self.build_widgets();
 
-                if(self.pos.iface_big_scrollbars){
+                if(self.pos.config.iface_big_scrollbars){
                     self.$el.addClass('big-scrollbars');
                 }
 
                 self.screen_selector.set_default_screen();
 
-
                 self.pos.barcode_reader.connect();
 
-                if(!$('#oe-fullscreenwidget-viewport').length){
-                    $('head').append('<meta id="oe-pos-viewport" name="viewport" content=" width=1024px; user-scalable=no;">');
-                    $('head').append('<meta id="oe-pos-apple-mobile" name="apple-mobile-web-capable" content="yes">');
+                instance.webclient.set_content_full_screen(true);
+
+                if(self.pos.config.iface_fullscreen && document.body.webkitRequestFullscreen && (
+                    window.screen.availWidth  > window.innerWidth ||
+                    window.screen.availHeight > window.innerHeight    )){
+                    self.screen_selector.show_popup('fullscreen');
                 }
+                self.loading_hide();
 
-                $('.oe_leftbar').addClass('oe_hidden');
+                self.pos.push_order();
 
-                instance.webclient.set_content_full_screen(true);
+            }).fail(function(err){   // error when loading models data from the backend
+                self.loading_error(err);
+            });
+        },
+        loading_error: function(err){
+            var self = this;
+
+            var message = err.message;
+            var comment = err.stack;
+
+            if(err.message === 'XmlHttpRequestError '){
+                message = 'Network Failure (XmlHttpRequestError)';
+                comment = 'The Point of Sale could not be loaded due to a network problem.\n Please check your internet connection.';
+            }else if(err.message === 'OpenERP Server Error'){
+                message = err.data.message;
+                comment = err.data.debug;
+            }
+
+            if( typeof comment !== 'string' ){
+                comment = 'Traceback not available.';
+            }
+
+            var popup = $(QWeb.render('ErrorTracebackPopupWidget',{
+                widget: { message: message, comment: comment },
+            }));
+
+            popup.find('.button').click(function(){
+                self.close();
+            });
+
+            popup.css({ zindex: 9001 });
+
+            popup.appendTo(this.$el);
+        },
+        loading_progress: function(fac){
+            this.$('.loader .loader-feedback').removeClass('oe_hidden');
+            this.$('.loader .progress').removeClass('oe_hidden').css({'width': ''+Math.floor(fac*100)+'%'});
+        },
+        loading_message: function(msg,progress){
+            this.$('.loader .loader-feedback').removeClass('oe_hidden');
+            this.$('.loader .message').text(msg);
+            if (typeof progress !== 'undefined') {
+                this.loading_progress(progress);
+            } else {
+                this.$('.loader .progress').addClass('oe_hidden');
+            }
+        },
+        loading_skip: function(callback){
+            if(callback){
+                this.$('.loader .loader-feedback').removeClass('oe_hidden');
+                this.$('.loader .button.skip').removeClass('oe_hidden');
+                this.$('.loader .button.skip').off('click');
+                this.$('.loader .button.skip').click(callback);
+            }else{
+                this.$('.loader .button.skip').addClass('oe_hidden');
+            }
+        },
+        loading_hide: function(){
+            this.$('.loader').animate({opacity:0},1500,'swing',function(){self.$('.loader').addClass('oe_hidden');});
+        },
+        loading_show: function(){
+            this.$('.loader').removeClass('oe_hidden').animate({opacity:1},150,'swing');
+        },
 
-                if (!self.pos.get('pos_session')) {
-                    self.screen_selector.show_popup('error', 'Sorry, we could not create a user session');
-                }else if(!self.pos.get('pos_config')){
-                    self.screen_selector.show_popup('error', 'Sorry, we could not find any PoS Configuration for this session');
+        // A Generic UI that allow to select a user from a list.
+        // It returns a deferred that resolves with the selected user 
+        // upon success. Several options are available :
+        // - security: passwords will be asked
+        // - only_managers: restricts the list to managers
+        // - current_user: password will not be asked if this 
+        //                 user is selected.
+        // - message: The title of the user selection list. 
+        select_user: function(options){
+            options = options || {};
+            var self = this;
+            var def  = new $.Deferred();
+
+            var list = [];
+            for (var i = 0; i < this.pos.users.length; i++) {
+                var user = this.pos.users[i];
+                if (!options.only_managers || user.role === 'manager') {
+                    list.push({
+                        'label': user.name,
+                        'item':  user,
+                    });
+                }
+            }
+
+            this.pos_widget.screen_selector.show_popup('selection',{
+                'message': options.message || _t('Select User'),
+                list: list,
+                confirm: function(user){ def.resolve(user); },
+                cancel:  function(){ def.reject(); },
+            });
+
+            return def.then(function(user){
+                if (options.security && user !== options.current_user && user.pos_security_pin) {
+                    var ret = new $.Deferred();
+
+                    self.pos_widget.screen_selector.show_popup('password',{
+                        'message': _t('Password'),
+                        confirm: function(password) {
+                            if (password !== user.pos_security_pin) {
+                                this.pos_widget.screen_selector.show_popup('error',{
+                                    'message':_t('Password Incorrect'),
+                                });
+                                ret.reject();
+                            } else {
+                                ret.resolve(user);
+                            }
+                        },
+                        cancel: function(){ ret.reject(); },
+                    });
+
+                    return ret;
+                } else {
+                    return user;
                 }
-            
-                instance.web.unblockUI();
-                self.$('.loader').animate({opacity:0},1500,'swing',function(){self.$('.loader').addClass('oe_hidden');});
-
-                self.pos.flush();
-
-            }).fail(function(){   // error when loading models data from the backend
-                instance.web.unblockUI();
-                return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_pos_session_opening']], ['res_id'])
-                    .pipe( _.bind(function(res){
-                        return instance.session.rpc('/web/action/load', {'action_id': res[0]['res_id']})
-                            .pipe(_.bind(function(result){
-                                var action = result.result;
-                                this.do_action(action);
-                            }, this));
-                    }, self));
             });
         },
-        
+
+        // checks if the current user (or the user provided) has manager
+        // access rights. If not, a popup is shown allowing the user to
+        // temporarily login as an administrator. 
+        // This method returns a defferred, that succeeds with the 
+        // manager user when the login is successfull.
+        sudo: function(user){
+            var def = new $.Deferred();
+            user = user || this.pos.get_cashier();
+
+            if (user.role === 'manager') {
+                return new $.Deferred().resolve(user);
+            } else {
+                return this.select_user({
+                    security:       true, 
+                    only_managers:  true,
+                    message:       _t('Login as a Manager'),
+                })
+            }
+        },
+
         // This method instantiates all the screens, widgets, etc. If you want to add new screens change the
         // startup screen, etc, override this method.
         build_widgets: function() {
@@ -915,60 +1098,91 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
             this.payment_screen = new module.PaymentScreenWidget(this, {});
             this.payment_screen.appendTo(this.$('.screens'));
 
-            this.welcome_screen = new module.WelcomeScreenWidget(this,{});
-            this.welcome_screen.appendTo(this.$('.screens'));
-
-            this.client_payment_screen = new module.ClientPaymentScreenWidget(this, {});
-            this.client_payment_screen.appendTo(this.$('.screens'));
-
-            this.scale_invite_screen = new module.ScaleInviteScreenWidget(this, {});
-            this.scale_invite_screen.appendTo(this.$('.screens'));
+            this.clientlist_screen = new module.ClientListScreenWidget(this, {});
+            this.clientlist_screen.appendTo(this.$('.screens'));
 
             this.scale_screen = new module.ScaleScreenWidget(this,{});
             this.scale_screen.appendTo(this.$('.screens'));
 
-            // --------  Popups ---------
 
-            this.help_popup = new module.HelpPopupWidget(this, {});
-            this.help_popup.appendTo(this.$el);
+            // --------  Popups ---------
 
             this.error_popup = new module.ErrorPopupWidget(this, {});
             this.error_popup.appendTo(this.$el);
 
-            this.error_product_popup = new module.ProductErrorPopupWidget(this, {});
-            this.error_product_popup.appendTo(this.$el);
+            this.error_barcode_popup = new module.ErrorBarcodePopupWidget(this, {});
+            this.error_barcode_popup.appendTo(this.$el);
+
+            this.error_traceback_popup = new module.ErrorTracebackPopupWidget(this,{});
+            this.error_traceback_popup.appendTo(this.$el);
+
+            this.confirm_popup = new module.ConfirmPopupWidget(this,{});
+            this.confirm_popup.appendTo(this.$el);
+
+            this.fullscreen_popup = new module.FullscreenPopup(this,{});
+            this.fullscreen_popup.appendTo(this.$el);
 
-            this.error_session_popup = new module.ErrorSessionPopupWidget(this, {});
-            this.error_session_popup.appendTo(this.$el);
+            this.selection_popup = new module.SelectionPopupWidget(this,{});
+            this.selection_popup.appendTo(this.$el);
 
-            this.choose_receipt_popup = new module.ChooseReceiptPopupWidget(this, {});
-            this.choose_receipt_popup.appendTo(this.$el);
+            this.textinput_popup = new module.TextInputPopupWidget(this,{});
+            this.textinput_popup.appendTo(this.$el);
 
-            this.error_negative_price_popup = new module.ErrorNegativePricePopupWidget(this, {});
-            this.error_negative_price_popup.appendTo(this.$el);
+            this.textarea_popup = new module.TextAreaPopupWidget(this,{});
+            this.textarea_popup.appendTo(this.$el);
 
-            this.error_no_client_popup = new module.ErrorNoClientPopupWidget(this, {});
-            this.error_no_client_popup.appendTo(this.$el);
+            this.number_popup = new module.NumberPopupWidget(this,{});
+            this.number_popup.appendTo(this.$el);
 
-            this.error_invoice_transfer_popup = new module.ErrorInvoiceTransferPopupWidget(this, {});
-            this.error_invoice_transfer_popup.appendTo(this.$el);
+            this.password_popup = new module.PasswordPopupWidget(this,{});
+            this.password_popup.appendTo(this.$el);
+
+            this.unsent_orders_popup = new module.UnsentOrdersPopupWidget(this,{});
+            this.unsent_orders_popup.appendTo(this.$el);
+
+            this.unpaid_orders_popup = new module.UnpaidOrdersPopupWidget(this,{});
+            this.unpaid_orders_popup.appendTo(this.$el);
 
             // --------  Misc ---------
 
+            this.order_selector = new module.OrderSelectorWidget(this,{});
+            this.order_selector.replace(this.$('.placeholder-OrderSelectorWidget'));
+
+            if(this.pos.config.use_proxy){
+                this.proxy_status = new module.ProxyStatusWidget(this,{});
+                this.proxy_status.appendTo(this.$('.pos-rightheader'));
+            }
+
             this.notification = new module.SynchNotificationWidget(this,{});
             this.notification.appendTo(this.$('.pos-rightheader'));
 
-            this.username   = new module.UsernameWidget(this,{});
-            this.username.replace(this.$('.placeholder-UsernameWidget'));
+            this.close_button = new module.HeaderButtonWidget(this,{
+                label: _t('Close'),
+                action: function(){ 
+                    var self = this;
+                    if (!this.confirmed) {
+                        this.$el.addClass('confirm');
+                        this.$el.text(_t('Confirm'));
+                        this.confirmed = setTimeout(function(){
+                            self.$el.removeClass('confirm');
+                            self.$el.text(_t('Close'));
+                            self.confirmed = false;
+                        },2000);
+                    } else {
+                        clearTimeout(this.confirmed);
+                        this.pos_widget.close();
+                    }
+                },
+            });
+            this.close_button.appendTo(this.$('.pos-rightheader'));
 
-            this.action_bar = new module.ActionBarWidget(this);
-            this.action_bar.replace(this.$(".placeholder-RightActionBar"));
 
-            this.left_action_bar = new module.ActionBarWidget(this);
-            this.left_action_bar.replace(this.$('.placeholder-LeftActionBar'));
 
-            this.paypad = new module.PaypadWidget(this, {});
-            this.paypad.replace(this.$('.placeholder-PaypadWidget'));
+            this.username   = new module.UsernameWidget(this,{});
+            this.username.replace(this.$('.placeholder-UsernameWidget'));
+
+            this.actionpad = new module.ActionpadWidget(this, {});
+            this.actionpad.replace(this.$('.placeholder-ActionpadWidget'));
 
             this.numpad = new module.NumpadWidget(this);
             this.numpad.replace(this.$('.placeholder-NumpadWidget'));
@@ -981,19 +1195,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
             });
             this.onscreen_keyboard.replace(this.$('.placeholder-OnscreenKeyboardWidget'));
 
-            this.close_button = new module.HeaderButtonWidget(this,{
-                label: _t('Close'),
-                action: function(){ self.close(); },
-            });
-            this.close_button.appendTo(this.$('.pos-rightheader'));
-
-            this.client_button = new module.HeaderButtonWidget(this,{
-                label: _t('Self-Checkout'),
-                action: function(){ self.screen_selector.set_user_mode('client'); },
-            });
-            this.client_button.appendTo(this.$('.pos-rightheader'));
-
-            
             // --------  Screen Selector ---------
 
             this.screen_selector = new module.ScreenSelector({
@@ -1001,31 +1202,35 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
                 screen_set:{
                     'products': this.product_screen,
                     'payment' : this.payment_screen,
-                    'client_payment' : this.client_payment_screen,
-                    'scale_invite' : this.scale_invite_screen,
                     'scale':    this.scale_screen,
                     'receipt' : this.receipt_screen,
-                    'welcome' : this.welcome_screen,
+                    'clientlist': this.clientlist_screen,
                 },
                 popup_set:{
-                    'help': this.help_popup,
-                    'error': this.error_popup,
-                    'error-product': this.error_product_popup,
-                    'error-session': this.error_session_popup,
-                    'error-negative-price': this.error_negative_price_popup,
-                    'choose-receipt': this.choose_receipt_popup,
-                    'error-no-client': this.error_no_client_popup,
-                    'error-invoice-transfer': this.error_invoice_transfer_popup,
+                    'error':            this.error_popup,
+                    'error-barcode':    this.error_barcode_popup,
+                    'error-traceback':  this.error_traceback_popup,
+                    'textinput':        this.textinput_popup,
+                    'textarea':         this.textarea_popup,
+                    'number':           this.number_popup,
+                    'password':         this.password_popup,
+                    'confirm':          this.confirm_popup,
+                    'fullscreen':       this.fullscreen_popup,
+                    'selection':        this.selection_popup,
+                    'unsent-orders':    this.unsent_orders_popup,
+                    'unpaid-orders':    this.unpaid_orders_popup,
                 },
-                default_client_screen: 'welcome',
-                default_cashier_screen: 'products',
-                default_mode: this.pos.iface_self_checkout ?  'client' : 'cashier',
+                default_screen: 'products',
+                default_mode: 'cashier',
             });
 
             if(this.pos.debug){
                 this.debug_widget = new module.DebugWidget(this);
                 this.debug_widget.appendTo(this.$('.pos-content'));
             }
+
+            this.disable_rubberbanding();
+
         },
 
         changed_pending_operations: function () {
@@ -1037,96 +1242,50 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
             if(visible !== this.numpad_visible){
                 this.numpad_visible = visible;
                 if(visible){
-                    this.set_left_action_bar_visible(false);
                     this.numpad.show();
-                    this.paypad.show();
-                    this.order_widget.set_display_mode('numpad');
+                    this.actionpad.show();
                 }else{
                     this.numpad.hide();
-                    this.paypad.hide();
-                    if(this.order_widget.display_mode === 'numpad'){
-                        this.order_widget.set_display_mode('maximized');
-                    }
-                }
-            }
-        },
-        set_left_action_bar_visible: function(visible){
-            if(visible !== this.left_action_bar_visible){
-                this.left_action_bar_visible = visible;
-                if(visible){
-                    this.set_numpad_visible(false);
-                    this.left_action_bar.show();
-                    this.order_widget.set_display_mode('actionbar');
-                }else{
-                    this.left_action_bar.hide();
-                    if(this.order_widget.display_mode === 'actionbar'){
-                        this.order_widget.set_display_mode('maximized');
-                    }
+                    this.actionpad.hide();
                 }
             }
         },
-        set_fullscreen: function(){
-            if(this.el.webkitRequestFullscreen){
-                this.el.webkitRequestFullscreen();
-            }
-        },
         //shows or hide the leftpane (contains the list of orderlines, the numpad, the paypad, etc.)
         set_leftpane_visible: function(visible){
             if(visible !== this.leftpane_visible){
                 this.leftpane_visible = visible;
                 if(visible){
-                    this.$('.pos-leftpane').removeClass('oe_hidden').animate({'width':this.leftpane_width},500,'swing');
-                    this.$('.pos-rightpane').animate({'left':this.leftpane_width},500,'swing');
-                }else{
-                    var leftpane = this.$('.pos-leftpane');
-                    leftpane.animate({'width':'0px'},500,'swing', function(){ leftpane.addClass('oe_hidden'); });
-                    this.$('.pos-rightpane').animate({'left':'0px'},500,'swing');
-                }
-            }
-        },
-        //shows or hide the controls in the PosWidget that are specific to the cashier ( Orders, close button, etc. ) 
-        set_cashier_controls_visible: function(visible){
-            if(visible !== this.cashier_controls_visible){
-                this.cashier_controls_visible = visible;
-                if(visible){
-                    this.$('.pos-rightheader').removeClass('oe_hidden');
+                    this.$('.pos-leftpane').removeClass('oe_hidden');
+                    this.$('.rightpane').css({'left':this.leftpane_width});
                 }else{
-                    this.$('.pos-rightheader').addClass('oe_hidden');
+                    this.$('.pos-leftpane').addClass('oe_hidden');
+                    this.$('.rightpane').css({'left':'0px'});
                 }
             }
         },
         close: function() {
             var self = this;
-
-            function close(){
-                return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_client_pos_menu']], ['res_id']).pipe(
-                        _.bind(function(res) {
-                    return this.rpc('/web/action/load', {'action_id': res[0]['res_id']}).pipe(_.bind(function(result) {
-                        var action = result;
-                        action.context = _.extend(action.context || {}, {'cancel_action': {type: 'ir.actions.client', tag: 'reload'}});
-                        //self.destroy();
-                        this.do_action(action);
-                    }, this));
-                }, self));
-            }
-
-            var draft_order = _.find( self.pos.get('orders').models, function(order){
-                return order.get('orderLines').length !== 0 && order.get('paymentLines').length === 0;
+            self.loading_show();
+            self.loading_message(_t('Closing ...'));
+
+            self.pos.push_order().then(function(){
+                return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_client_pos_menu']], ['res_id'])
+                .pipe(function(res) {
+                    window.location = '/web#action=' + res[0]['res_id'];
+                },function(err,event) {
+                    event.preventDefault();
+                    self.screen_selector.show_popup('error',{
+                        'message': _t('Could not close the point of sale.'),
+                        'comment': _t('Your internet connection is probably down.'),
+                    });
+                    self.close_button.renderElement();
+                });
             });
-            if(draft_order){
-                if (confirm(_t("Pending orders will be lost.\nAre you sure you want to leave this session?"))) {
-                    return close();
-                }
-            }else{
-                return close();
-            }
+
         },
         destroy: function() {
             this.pos.destroy();
             instance.webclient.set_content_full_screen(false);
-            $('.oe_leftbar').removeClass('oe_hidden');
-            $('#oe-pos-viewport').remove();
-            $('#oe-pos-apple-mobile').remove();
             this._super();
         }
     });