function openerp_pos_widgets(instance, module){ //module is instance.point_of_sale
    var QWeb = instance.web.qweb;

    // 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({
        init: function(options){
            options = options || {};
            this.max_size = options.max_size || 500;

            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;
                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;
                        }
                    }
                    if(oldestUrl){
                        delete this.cache[oldestUrl];
                        delete this.access_time[oldestUrl];
                    }
                    this.size--;
                }
                this.cache[url] = img;
                this.access_time[url] = (new Date()).getTime();
                this.size++;
                return img;
            }
        },
    });

    module.NumpadWidget = module.PosBaseWidget.extend({
        template:'NumpadWidget',
        init: function(parent, options) {
            this._super(parent);
            this.state = new module.NumpadState();
        },
        start: function() {
            this.state.bind('change:mode', this.changedMode, this);
            this.changedMode();
            this.$el.find('button#numpad-backspace').click(_.bind(this.clickDeleteLastChar, this));
            this.$el.find('button#numpad-minus').click(_.bind(this.clickSwitchSign, this));
            this.$el.find('button.number-char').click(_.bind(this.clickAppendNewChar, this));
            this.$el.find('button.mode-button').click(_.bind(this.clickChangeMode, this));
        },
        clickDeleteLastChar: function() {
            return this.state.deleteLastChar();
        },
        clickSwitchSign: function() {
            return this.state.switchSign();
        },
        clickAppendNewChar: function(event) {
            var newChar;
            newChar = event.currentTarget.innerText || event.currentTarget.textContent;
            return this.state.appendNewChar(newChar);
        },
        clickChangeMode: function(event) {
            var newMode = event.currentTarget.attributes['data-mode'].nodeValue;
            return this.state.changeMode(newMode);
        },
        changedMode: function() {
            var mode = this.state.get('mode');
            $('.selected-mode').removeClass('selected-mode');
            $(_.str.sprintf('.mode-button[data-mode="%s"]', mode), this.$el).addClass('selected-mode');
        },
    });

    // The paypad allows to select the payment method (cashRegisters) 
    // used to pay the order.
    module.PaypadWidget = module.PosBaseWidget.extend({
        template: 'PaypadWidget',
        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);
            });
        }
    });

    module.PaypadButtonWidget = module.PosBaseWidget.extend({
        template: 'PaypadButtonWidget',
        init: function(parent, options){
            this._super(parent, options);
            this.cashRegister = options.cashRegister;
        },
        renderElement: function() {
            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...?');
                    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', _.bind( function() {
                this.refresh();
            }, this));
        },
        click_handler: function() {
            this.order.selectLine(this.model);
            this.trigger('order_line_selected');
        },
        renderElement: function() {
            this._super();
            this.$el.click(_.bind(this.click_handler, this));
            if(this.model.is_selected()){
                this.$el.addClass('selected');
            }
        },
        refresh: function(){
            this.renderElement();
            this.trigger('order_line_refreshed');
        },
    });
    
    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 = [];
        },
        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_value: function(val) {
        	var order = this.pos.get('selectedOrder');
        	if (order.get('orderLines').length !== 0) {
                var mode = this.numpadState.get('mode');
                if( mode === 'quantity'){
                    order.getSelectedLine().set_quantity(val);
                }else if( mode === 'discount'){
                    order.getSelectedLine().set_discount(val);
                }else if( mode === 'price'){
                    order.getSelectedLine().set_price(val);
                }
        	} else {
        	    this.pos.get('selectedOrder').destroy();
        	}
        },
        change_selected_order: function() {
            this.currentOrderLines.unbind();
            this.bind_orderline_events();
            this.renderElement();
        },
        bind_orderline_events: function() {
            this.currentOrderLines = (this.pos.get('selectedOrder')).get('orderLines');
            this.currentOrderLines.bind('add', this.renderElement, this);
            this.currentOrderLines.bind('remove', this.renderElement, this);
        },
        update_numpad: function() {
            this.selected_line = this.pos.get('selectedOrder').getSelectedLine();
            if (this.numpadState)
                this.numpadState.reset();
        },
        renderElement: function() {
            var self = this;
            this._super();

            // freeing subwidgets
            
            if(this.scrollbar){
                this.scrollbar.destroy();
            }
            for(var i = 0, len = this.orderlinewidgets.length; i < len; i++){
                this.orderlinewidgets[i].destroy();
            }
            this.orderlinewidgets = [];

            if(this.display_mode === 'maximized'){
                $('.point-of-sale .order-container').css({'bottom':'0px'});
            }else if(this.display_mode === 'actionbar'){
                $('.point-of-sale .order-container').css({'bottom':'105px'});
            }else if(this.display_mode !== 'numpad'){
                console.error('ERROR: OrderWidget renderElement(): wrong display_mode:',this.display_mode);
            }

            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();
            this.update_summary();

            var position = this.scrollbar ? this.scrollbar.get_position() : 0;
            var at_bottom = this.scrollbar ? this.scrollbar.is_at_bottom() : false;
            
            this.scrollbar = new module.ScrollbarWidget(this,{
                target_widget:   this,
                target_selector: '.order-scroller',
                name: 'order',
                track_bottom: true,
                on_show: function(){
                    self.$('.order-scroller').css({'width':'89%'},100);
                },
                on_hide: function(){
                    self.$('.order-scroller').css({'width':'100%'},100);
                },
            });

            this.scrollbar.replace(this.$('.placeholder-ScrollbarWidget'));
            this.scrollbar.set_position(position);

            if( at_bottom ){
                this.scrollbar.set_position(Number.MAX_VALUE, false);
            }

        },
        update_summary: function(){
            var order = this.pos.get('selectedOrder');
            var total = order ? order.getTotal() : 0;
            this.$('.summary .value.total').html(this.format_currency(total));
        },
        set_display_mode: function(mode){
            if(this.display_mode !== mode){
                this.display_mode = mode;
                this.renderElement();
            }
        },
    });

    module.ProductWidget = module.PosBaseWidget.extend({
        template: 'ProductWidget',
        init: function(parent, options) {
            this._super(parent,options);
            this.model = options.model;
            this.model.attributes.weight = options.weight;
            this.next_screen = options.next_screen; //when a product is clicked, this screen is set
            this.click_product_action = options.click_product_action; 
        },
        // returns the url of the product thumbnail
        renderElement: function() {
            this._super();
            this.$('img').replaceWith(this.pos_widget.image_cache.get_image(this.model.get_image_url()));
            var self = this;
            $("a", this.$el).click(function(e){
                if(self.click_product_action){
                    self.click_product_action(self.model);
                }
            });
        },
    });

    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;
            if (newAmount && !isNaN(newAmount)) {
            	this.amount = parseFloat(newAmount);
                this.payment_line.set_amount(this.amount);
            }
        },
        changedAmount: function() {
        	if (this.amount !== this.payment_line.get_amount())
        		this.renderElement();
        },
        renderElement: function() {
            var self = this;
            this.name =   this.payment_line.get_cashregister().get('journal_id')[1];
            this._super();
            this.$('input').keyup(_.bind(this.changeAmount, this));
            this.$('.delete-payment-line').click(function() {
                self.trigger('delete_payment_line', self);
            });
        },
    });

    module.OrderButtonWidget = module.PosBaseWidget.extend({
        template:'OrderButtonWidget',
        init: function(parent, options) {
            this._super(parent,options);
            var self = this;

            this.order = options.order;
            this.order.bind('destroy',function(){ self.destroy(); });
            this.order.bind('change', function(){ self.renderElement(); });
            this.pos.bind('change:selectedOrder', _.bind( function(pos) {
                var selectedOrder;
                selectedOrder = pos.get('selectedOrder');
                if (this.order === selectedOrder) {
                    this.setButtonSelected();
                }
            }, this));
        },
        renderElement:function(){
            this._super();
            this.$('button.select-order').off('click').click(_.bind(this.selectOrder, this));
            this.$('button.close-order').off('click').click(_.bind(this.closeOrder, this));
        },
        selectOrder: function(event) {
            this.pos.set({
                selectedOrder: this.order
            });
        },
        setButtonSelected: function() {
            $('.selected-order').removeClass('selected-order');
            this.$el.addClass('selected-order');
        },
        closeOrder: function(event) {
            this.order.destroy();
        },
    });

    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;
            }
        },
        set_disabled: function(disabled){
            if(this.disabled != disabled){
                this.disabled = !!disabled;
                this.renderElement();
            }
        },
        renderElement: function(){
            this._super();
            if(this.click_action && !this.disabled){
                this.$el.click(_.bind(this.click_action, this));
            }
        },
    });

    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).show();
                }else{
                    this.$('.'+element).hide();
                }
            }
            if(visible && action){
                this.action[element] = action;
                this.$('.'+element).off('click').click(action);
            }
        },
        set_button_disabled: function(name, disabled){
            var b = this.buttons[name];
            if(b){
                b.set_disabled(disabled);
            }
        },
        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;
            }
            button.appendTo(this.$('.pos-actionbar-button-list'));
            return button;
        },
        show:function(){
            this.$el.show();
        },
        hide:function(){
            this.$el.hide();
        },
    });

    module.CategoryButton = module.PosBaseWidget.extend({
    });
    module.ProductCategoriesWidget = module.PosBaseWidget.extend({
        template: 'ProductCategoriesWidget',
        init: function(parent, options){
            var self = this;
            this._super(parent,options);
            this.product_type = options.product_type || 'all';  // 'all' | 'weightable'
            this.onlyWeightable = options.onlyWeightable || false;
            this.category = this.pos.root_category;
            this.breadcrumb = [];
            this.subcategories = [];
            this.set_category();
        },

        // changes the category. if undefined, sets to root category
        set_category : function(category){
            var db = this.pos.db;
            if(!category){
                this.category = db.get_category_by_id(db.root_category_id);
            }else{
                this.category = category;
            }
            this.breadcrumb = [];
            var ancestors_ids = db.get_category_ancestors_ids(this.category.id);
            for(var i = 1; i < ancestors_ids.length; i++){
                this.breadcrumb.push(db.get_category_by_id(ancestors_ids[i]));
            }
            if(this.category.id !== db.root_category_id){
                this.breadcrumb.push(this.category);
            }
            this.subcategories = db.get_category_by_id(db.get_category_childs_ids(this.category.id));
        },

        get_image_url: function(category){
            return instance.session.url('/web/binary/image', {model: 'pos.category', field: 'image', id: category.id});
        },

        renderElement: function(){
            var self = this;
            this._super();

            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){
                    hasimages = true;
                }
            });

            _.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);
                }

                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();
                    self.search_and_categories(cat);
                });
            });
            // 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();
                self.search_and_categories(category);
            });
            this.search_and_categories();
        },
        
        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();
            this.renderElement();
            this.search_and_categories();
        },

        // 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);

            // filter the products according to the search string
            this.$('.searchbox input').keyup(function(){
                query = $(this).val().toLowerCase();
                if(query){
                    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();
                }
            });

            this.$('.searchbox input').click(function(){}); //Why ???

            //reset the search when clicking on reset
            this.$('.search-clear').click(function(){
                var products = self.pos.db.get_product_by_category(self.category.id);
                self.pos.get('products').reset(products);
                self.$('.searchbox input').val('').focus();
                self.$('.search-clear').fadeOut();
            });
        },
    });

    module.ProductListWidget = module.ScreenWidget.extend({
        template:'ProductListWidget',
        init: function(parent, options) {
            var self = this;
            this._super(parent,options);
            this.model = options.model;
            this.productwidgets = [];
            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();
            });
        },
        renderElement: function() {
            var self = this;
            this._super();

            // free subwidgets  memory from previous renders
            
            for(var i = 0, len = this.productwidgets.length; i < len; i++){
                this.productwidgets[i].destroy();
            }
            this.productwidgets = []; 
            if(this.scrollbar){
                this.scrollbar.destroy();
            }

            this.pos.get('products')
                .chain()
                .map(function(product) {
                    var product = new module.ProductWidget(self, {
                            model: product,
                            weight: self.weight,
                            click_product_action: self.click_product_action,
                    })
                    self.productwidgets.push(product);
                    return product;
                })
                .invoke('appendTo', this.$('.product-list'));

            this.scrollbar = new module.ScrollbarWidget(this,{
                target_widget:   this,
                target_selector: '.product-list-scroller',
                on_show: function(){
                    self.$('.product-list-scroller').css({'padding-right':'62px'},100);
                },
                on_hide: function(){
                    self.$('.product-list-scroller').css({'padding-right':'0px'},100);
                },
            });

            this.scrollbar.replace(this.$('.placeholder-ScrollbarWidget'));

        },
    });

    module.UsernameWidget = module.PosBaseWidget.extend({
        template: 'UsernameWidget',
        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();
        },
        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');
            }
            if(user){
                return user.name;
            }else{
                return "";
            }
        },
    });

    module.HeaderButtonWidget = module.PosBaseWidget.extend({
        template: 'HeaderButtonWidget',
        init: function(parent, options){
            options = options || {};
            this._super(parent, options);
            this.action = options.action;
            this.label   = options.label;
        },
        renderElement: function(){
            var self = this;
            this._super();
            if(this.action){
                this.$el.click(function(){ self.action(); });
            }
        },
        show: function(){ this.$el.show(); },
        hide: function(){ this.$el.hide(); },
    });

    // The debug widget lets the user control and monitor the hardware and software status
    // without the use of the proxy
    module.DebugWidget = module.PosBaseWidget.extend({
        template: "DebugWidget",
        eans:{
            admin_badge:  '0410100000006',
            client_badge: '0420200000004',
            invalid_ean:  '1232456',
            soda_33cl:    '5449000000996',
            oranges_kg:   '2100002031410',
            lemon_price:  '2301000001560',
            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',
        ],
        minimized: false,
        start: function(){
            var self = this;

            this.$el.draggable();
            this.$('.toggle').click(function(){
                var content = self.$('.content');
                var bg      = self.$el;
                if(!self.minimized){
                    content.animate({'height':'0'},200);
                }else{
                    content.css({'height':'auto'});
                }
                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)){
                    self.pos.proxy.debug_set_weight(kg);
                }
            });
            this.$('.button.reset_weight').click(function(){
                self.pos.proxy.debug_reset_weight();
            });
            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.on_ean(ean);
            });
            _.each(this.eans, function(ean, name){
                self.$('.button.'+name).click(function(){
                    self.$('input.ean').val(ean);
                    self.pos.barcode_reader.on_ean(ean);
                });
            });
            _.each(this.events, function(name){
                self.pos.proxy.add_notification(name,function(){
                    self.$('.event.'+name).stop().clearQueue().css({'background-color':'#6CD11D'}); 
                    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);
        },
        renderElement: function() {
            var self = this;
            this._super();
            this.$('.oe_pos_synch-notification-button').click(function(){
                self.pos.flush();
            });
        },
        start: function(){
            var self = this;
            this.pos.bind('change:nbr_pending_operations', function(){
                self.renderElement();
            });
        },
        get_nbr_pending: function(){
            return this.pos.get('nbr_pending_operations');
        },
    });


    // The PosWidget is the main widget that contains all other widgets in the PointOfSale.
    // It is mainly composed of :
    // - 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

    module.PosWidget = module.PosBaseWidget.extend({
        template: 'PosWidget',
        init: function() { 
            this._super(arguments[0],{});

            instance.web.blockUI(); 

            this.pos = new module.PosModel(this.session);
            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
        },
      
        start: function() {
            var self = this;
            return self.pos.ready.done(function() {
                self.build_currency_template();
                self.renderElement();
                
                self.$('.neworder-button').click(function(){
                    self.pos.add_new_order();
                });
                
                //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($('#orders'));
                    new_order_button.selectOrder();
                }, self);

                self.pos.get('orders').add(new module.Order({ pos: self.pos }));

                self.build_widgets();

                self.screen_selector.set_default_screen();

                window.screen_selector = self.screen_selector;

                self.pos.barcode_reader.connect();

                instance.webclient.set_content_full_screen(true);

                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');
                }
            
                instance.web.unblockUI();
                self.$('.loader').animate({opacity:0},1500,'swing',function(){self.$('.loader').hide();});

            }).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));
            });
        },
        
        // 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() {
            var self = this;

            // --------  Screens ---------

            this.product_screen = new module.ProductScreenWidget(this,{});
            this.product_screen.appendTo($('#rightpane'));

            this.receipt_screen = new module.ReceiptScreenWidget(this, {});
            this.receipt_screen.appendTo($('#rightpane'));

            this.payment_screen = new module.PaymentScreenWidget(this, {});
            this.payment_screen.appendTo($('#rightpane'));

            this.welcome_screen = new module.WelcomeScreenWidget(this,{});
            this.welcome_screen.appendTo($('#rightpane'));

            this.client_payment_screen = new module.ClientPaymentScreenWidget(this, {});
            this.client_payment_screen.appendTo($('#rightpane'));

            this.scale_invite_screen = new module.ScaleInviteScreenWidget(this, {});
            this.scale_invite_screen.appendTo($('#rightpane'));

            this.scale_screen = new module.ScaleScreenWidget(this,{});
            this.scale_screen.appendTo($('#rightpane'));

            // --------  Popups ---------

            this.help_popup = new module.HelpPopupWidget(this, {});
            this.help_popup.appendTo($('.point-of-sale'));

            this.error_popup = new module.ErrorPopupWidget(this, {});
            this.error_popup.appendTo($('.point-of-sale'));

            this.error_product_popup = new module.ProductErrorPopupWidget(this, {});
            this.error_product_popup.appendTo($('.point-of-sale'));

            this.error_session_popup = new module.ErrorSessionPopupWidget(this, {});
            this.error_session_popup.appendTo($('.point-of-sale'));

            this.choose_receipt_popup = new module.ChooseReceiptPopupWidget(this, {});
            this.choose_receipt_popup.appendTo($('.point-of-sale'));

            this.error_negative_price_popup = new module.ErrorNegativePricePopupWidget(this, {});
            this.error_negative_price_popup.appendTo($('.point-of-sale'));

            // --------  Misc ---------

            this.notification = new module.SynchNotificationWidget(this,{});
            this.notification.appendTo(this.$('#rightheader'));

            this.username   = new module.UsernameWidget(this,{});
            this.username.replace(this.$('.placeholder-UsernameWidget'));

            this.action_bar = new module.ActionBarWidget(this);
            this.action_bar.appendTo($(".point-of-sale #rightpane"));

            this.left_action_bar = new module.ActionBarWidget(this);
            this.left_action_bar.appendTo($(".point-of-sale #leftpane"));

            this.paypad = new module.PaypadWidget(this, {});
            this.paypad.replace($('#placeholder-PaypadWidget'));

            this.numpad = new module.NumpadWidget(this);
            this.numpad.replace($('#placeholder-NumpadWidget'));

            this.order_widget = new module.OrderWidget(this, {});
            this.order_widget.replace($('#placeholder-OrderWidget'));

            this.onscreen_keyboard = new module.OnscreenKeyboardWidget(this, {
                'keyboard_model': 'simple'
            });
            this.onscreen_keyboard.appendTo($(".point-of-sale #content")); 

            this.close_button = new module.HeaderButtonWidget(this,{
                label:'Close',
                action: function(){ self.try_close(); },
            });
            this.close_button.appendTo(this.$('#rightheader'));

            this.client_button = new module.HeaderButtonWidget(this,{
                label:'Self-Checkout',
                action: function(){ self.screen_selector.set_user_mode('client'); },
            });
            this.client_button.appendTo(this.$('#rightheader'));

            
            // --------  Screen Selector ---------

            this.screen_selector = new module.ScreenSelector({
                pos: this.pos,
                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,
                },
                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,
                },
                default_client_screen: 'welcome',
                default_cashier_screen: 'products',
                default_mode: this.pos.iface_self_checkout ?  'client' : 'cashier',
            });

            if(this.pos.debug){
                this.debug_widget = new module.DebugWidget(this);
                this.debug_widget.appendTo(this.$('#content'));
            }
        },

        changed_pending_operations: function () {
            var self = this;
            this.synch_notification.on_change_nbr_pending(self.pos.get('nbr_pending_operations').length);
        },
        // shows or hide the numpad and related controls like the paypad.
        set_numpad_visible: function(visible){
            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');
                }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');
                    }
                }
            }
        },

        //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){
                    $('#leftpane').show().animate({'width':this.leftpane_width},500,'swing');
                    $('#rightpane').animate({'left':this.leftpane_width},500,'swing');
                }else{
                    var leftpane = $('#leftpane');
                    $('#leftpane').animate({'width':'0px'},500,'swing', function(){ leftpane.hide(); });
                    $('#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){
                    $('#loggedas').show();
                    $('#rightheader').show();
                }else{
                    $('#loggedas').hide();
                    $('#rightheader').hide();
                }
            }
        },
        try_close: function() {
            var self = this;
            //TODO : do the close after the flush...
            self.pos.flush()
            self.close();
        },
        close: function() {
            var self = this;
            this.pos.barcode_reader.disconnect();
            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));
            }, this));
        },
        destroy: function() {
            instance.webclient.set_content_full_screen(false);
            self.pos = undefined;
            this._super();
        }
    });
}
