1 function openerp_pos_models(instance, module){ //module is instance.point_of_sale
2 var QWeb = instance.web.qweb;
4 module.LocalStorageDAO = instance.web.Class.extend({
5 add_operation: function(operation) {
7 return $.async_when().pipe(function() {
8 var tmp = self._get('oe_pos_operations', []);
9 var last_id = self._get('oe_pos_operations_sequence', 1);
10 tmp.push({'id': last_id, 'data': operation});
11 self._set('oe_pos_operations', tmp);
12 self._set('oe_pos_operations_sequence', last_id + 1);
15 remove_operation: function(id) {
17 return $.async_when().pipe(function() {
18 var tmp = self._get('oe_pos_operations', []);
19 tmp = _.filter(tmp, function(el) {
22 self._set('oe_pos_operations', tmp);
25 get_operations: function() {
27 return $.async_when().pipe(function() {
28 return self._get('oe_pos_operations', []);
31 _get: function(key, default_) {
32 var txt = localStorage['oe_pos_dao_'+key];
35 return JSON.parse(txt);
37 _set: function(key, value) {
38 localStorage['oe_pos_dao_'+key] = JSON.stringify(value);
40 reset_stored_data: function(){
41 for(key in localStorage){
42 if(key.indexOf('oe_pos_dao_') === 0){
43 delete localStorage[key];
49 var fetch = function(model, fields, domain, ctx){
50 return new instance.web.Model(model).query(fields).filter(domain).context(ctx).all()
53 // The PosModel contains the Point Of Sale's representation of the backend.
54 // Since the PoS must work in standalone ( Without connection to the server )
55 // it must contains a representation of the server's PoS backend.
56 // (taxes, product list, configuration options, etc.) this representation
57 // is fetched and stored by the PosModel at the initialisation.
58 // this is done asynchronously, a ready deferred alows the GUI to wait interactively
59 // for the loading to be completed
60 // There is a single instance of the PosModel for each Front-End instance, it is usually called
61 // 'pos' and is available to almost all widgets.
63 module.PosModel = Backbone.Model.extend({
64 initialize: function(session, attributes) {
65 Backbone.Model.prototype.initialize.call(this, attributes);
67 this.session = session;
68 this.dao = new module.LocalStorageDAO(); // used to store the order's data on the Hard Drive
69 this.ready = $.Deferred(); // used to notify the GUI that the PosModel has loaded all resources
70 this.flush_mutex = new $.Mutex(); // used to make sure the orders are sent to the server once at time
72 this.barcode_reader = new module.BarcodeReader({'pos': this}); // used to read barcodes
73 this.proxy = new module.ProxyDevice(); // used to communicate to the hardware devices via a local proxy
74 this.db = new module.PosLS(); // a database used to store the products and categories
79 this.use_scale = false;
80 this.use_proxy_printer = false;
81 this.use_virtual_keyboard = false;
82 this.use_websql = false;
83 this.use_barcode_scanner = false;
85 // default attributes values. If null, it will be loaded below.
87 'nbr_pending_operations': 0,
89 'currency': {symbol: '$', position: 'after'},
92 'user': null, // the user that loaded the pos
93 'user_list': null, // list of all users
94 'cashier': null, // the logged cashier, if different from user
96 'orders': new module.OrderCollection(),
97 //this is the product list as seen by the product list widgets, it will change based on the category filters
98 'products': new module.ProductCollection(),
99 'cashRegisters': null,
101 'product_list': null, // the list of all products, does not change.
102 'bank_statements': null,
108 'selectedOrder': undefined,
111 this.get('orders').bind('remove', function(){ self.on_removed_order(); });
113 // We fetch the backend data on the server asynchronously. this is done only when the pos user interface is launched,
114 // Any change on this data made on the server is thus not reflected on the point of sale until it is relaunched.
116 var user_def = fetch('res.users',['name','company_id'],[['id','=',this.session.uid]])
117 .pipe(function(users){
119 self.set('user',user);
121 return fetch('res.company',
127 //TODO contact_address
132 [['id','=',user.company_id[0]]])
133 }).pipe(function(companies){
134 var company = companies[0];
135 self.set('company',company);
137 return fetch('res.currency',['symbol','position'],[['id','=',company.currency_id[0]]]);
138 }).pipe(function (currencies){
139 self.set('currency',currencies[0]);
142 var cat_def = fetch('pos.category', ['id','name', 'parent_id', 'child_id', 'category_image_small'])
143 .pipe(function(result){
144 return self.set({'categories': result});
147 var uom_def = fetch( //unit of measure
151 ).then(function(result){
152 self.set({'units': result});
153 var units_by_id = {};
154 for(var i = 0, len = result.length; i < len; i++){
155 units_by_id[result[i].id] = result[i];
157 self.set({'units_by_id':units_by_id});
160 var pack_def = fetch(
164 ).then(function(packaging){
165 self.set('product.packaging',packaging);
168 var users_def = fetch(
171 [['ean13', '!=', false]]
172 ).then(function(result){
173 self.set({'user_list':result});
176 var tax_def = fetch('account.tax', ['amount','price_include','type'])
177 .then(function(result){
178 self.set({'taxes': result});
181 var session_def = fetch( // loading the PoS Session.
183 ['id', 'journal_ids','name','user_id','config_id','start_at','stop_at'],
184 [['state', '=', 'opened'], ['user_id', '=', this.session.uid]]
185 ).pipe(function(result) {
187 // some data are associated with the pos session, like the pos config and bank statements.
188 // we must have a valid session before we can read those.
190 var session_data_def = new $.Deferred();
192 if( result.length !== 0 ) {
193 var pos_session = result[0];
195 self.set({'pos_session': pos_session});
197 var pos_config_def = fetch(
199 ['name','journal_ids','shop_id','journal_id',
200 'iface_self_checkout', 'iface_websql', 'iface_led', 'iface_cashdrawer',
201 'iface_payment_terminal', 'iface_electronic_scale', 'iface_barscan', 'iface_vkeyboard',
202 'iface_print_via_proxy','iface_cashdrawer','state','sequence_id','session_ids'],
203 [['id','=', pos_session.config_id[0]]]
204 ).pipe(function(result){
205 var pos_config = result[0]
207 self.set({'pos_config': pos_config});
208 self.use_scale = pos_config.iface_electronic_scale || false;
209 self.use_proxy_printer = pos_config.iface_print_via_proxy || false;
210 self.use_virtual_keyboard = pos_config.iface_vkeyboard || false;
211 self.use_websql = pos_config.iface_websql || false;
212 self.use_barcode_scanner = pos_config.iface_barscan || false;
213 self.use_selfcheckout = pos_config.iface_self_checkout || false;
214 self.use_cashbox = pos_config.iface_cashdrawer || false;
216 return shop_def = fetch('sale.shop',[], [['id','=',pos_config.shop_id[0]]])
217 }).pipe(function(shops){
218 self.set('shop',shops[0]);
221 //context {pricelist: shop.pricelist_id[0]}
222 ['name', 'list_price','price','pos_categ_id', 'taxes_id','product_image_small', 'ean13', 'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type'],
223 [['pos_categ_id','!=', false]],
224 {pricelist: shops[0].pricelist_id[0]} // context for price
226 }).pipe( function(product_list){
227 self.set({'product_list': product_list});
230 var bank_def = fetch(
231 'account.bank.statement',
232 ['account_id','currency','journal_id','state','name','user_id','pos_session_id'],
233 [['state','=','open'],['pos_session_id', '=', pos_session.id]]
234 ).then(function(result){
235 self.set({'bank_statements':result});
238 var journal_def = fetch(
241 [['user_id','=',pos_session.user_id[0]]]
242 ).then(function(result){
243 self.set({'journals':result});
246 // associate the bank statements with their journals.
247 var bank_process_def = $.when(bank_def, journal_def)
249 var bank_statements = self.get('bank_statements');
250 var journals = self.get('journals');
251 for(var i = 0, ilen = bank_statements.length; i < ilen; i++){
252 for(var j = 0, jlen = journals.length; j < jlen; j++){
253 if(bank_statements[i].journal_id[0] === journals[j].id){
254 bank_statements[i].journal = journals[j];
255 bank_statements[i].self_checkout_payment_method = journals[j].self_checkout_payment_method;
261 session_data_def = $.when(pos_config_def,bank_def,journal_def,bank_process_def);
264 session_data_def.reject();
266 return session_data_def;
269 // when all the data has loaded, we compute some stuff, and declare the Pos ready to be used.
270 $.when(pack_def, cat_def, user_def, users_def, uom_def, session_def, tax_def, user_def, this.flush())
273 self.db.add_categories(self.get('categories'));
274 self.db.add_product(self.get('product_list'));
275 self.set({'cashRegisters' : new module.CashRegisterCollection(self.get('bank_statements'))});
276 self.log_loaded_data(); //Uncomment if you want to log the data to the console for easier debugging
277 self.ready.resolve();
279 //we failed to load some backend data, or the backend was badly configured.
280 //the error messages will be displayed in PosWidget
285 // logs the usefull posmodel data to the console for debug purposes
286 log_loaded_data: function(){
287 console.log('PosModel data has been loaded:');
288 console.log('PosModel: categories:',this.get('categories'));
289 console.log('PosModel: product_list:',this.get('product_list'));
290 console.log('PosModel: units:',this.get('units'));
291 console.log('PosModel: bank_statements:',this.get('bank_statements'));
292 console.log('PosModel: journals:',this.get('journals'));
293 console.log('PosModel: taxes:',this.get('taxes'));
294 console.log('PosModel: pos_session:',this.get('pos_session'));
295 console.log('PosModel: pos_config:',this.get('pos_config'));
296 console.log('PosModel: cashRegisters:',this.get('cashRegisters'));
297 console.log('PosModel: shop:',this.get('shop'));
298 console.log('PosModel: company:',this.get('company'));
299 console.log('PosModel: currency:',this.get('currency'));
300 console.log('PosModel: user_list:',this.get('user_list'));
301 console.log('PosModel: user:',this.get('user'));
302 console.log('PosModel.session:',this.session);
303 console.log('PosModel.categories:',this.categories);
304 console.log('PosModel end of data log.');
307 // this is called when an order is removed from the order collection. It ensures that there is always an existing
308 // order and a valid selected order
309 on_removed_order: function(removed_order){
310 if( this.get('orders').isEmpty()){
311 this.add_new_order();
313 if( this.get('selectedOrder') === removed_order){
314 this.set({ selectedOrder: this.get('orders').last() });
318 // saves the order locally and try to send it to the backend. 'record' is a bizzarely defined JSON version of the Order
319 push_order: function(record) {
321 return this.dao.add_operation(record).pipe(function(){
326 //creates a new empty order and sets it as the current order
327 add_new_order: function(){
328 var order = new module.Order({pos:this});
329 this.get('orders').add(order);
330 this.set('selectedOrder', order);
333 // attemps to send all pending orders ( stored in the DAO ) to the server.
334 // it will do it one by one, and remove the successfully sent ones from the DAO once
335 // it has been confirmed that they have been received.
337 //this makes sure only one _int_flush is called at the same time
338 return this.flush_mutex.exec(_.bind(function() {
339 return this._int_flush();
342 _int_flush : function() {
345 this.dao.get_operations().pipe(function(operations) {
346 // operations are really Orders that are converted to json.
347 // they are saved to disk and then we attempt to send them to the backend so that they can
349 // since the network is not reliable we potentially have many 'pending operations' that have not been sent.
350 self.set( {'nbr_pending_operations':operations.length} );
351 if(operations.length === 0){
354 var order = operations[0];
356 // we prevent the default error handler and assume errors
357 // are a normal use case, except we stop the current iteration
359 return (new instance.web.Model('pos.order')).get_func('create_from_ui')([order])
360 .fail(function(unused, event){
362 event.preventDefault();
365 // success: remove the successfully sent operation, and try to send the next one
366 self.dao.remove_operation(operations[0].id).pipe(function(){
367 return self._int_flush();
370 // in case of error we just sit there and do nothing. wtf ask niv
376 scan_product: function(parsed_ean){
378 var def = new $.Deferred();
379 this.db.get_product_by_ean13(parsed_ean.base_ean, function(product){
380 var selectedOrder = this.get('selectedOrder');
382 def.reject('product-not-found: '+parsed_ean.base_ean);
385 if(parsed_ean.type === 'price'){
386 selectedOrder.addProduct(new module.Product(product), {price:parsed_ean.value});
387 }else if(parsed_ean.type === 'weight'){
388 selectedOrder.addProduct(new module.Product(product), {quantity:parsed_ean.value, merge:false});
390 selectedOrder.addProduct(new module.Product(product));
398 module.CashRegister = Backbone.Model.extend({
401 module.CashRegisterCollection = Backbone.Collection.extend({
402 model: module.CashRegister,
405 module.Product = Backbone.Model.extend({
408 module.ProductCollection = Backbone.Collection.extend({
409 model: module.Product,
412 // An orderline represent one element of the content of a client's shopping cart.
413 // An orderline contains a product, its quantity, its price, discount. etc.
414 // An Order contains zero or more Orderlines.
415 module.Orderline = Backbone.Model.extend({
416 initialize: function(attr,options){
417 this.pos = options.pos;
418 this.order = options.order;
419 this.product = options.product;
420 this.price = options.product.get('list_price');
424 this.selected = false;
426 // sets a discount [0,100]%
427 set_discount: function(discount){
428 this.discount = Math.max(0,Math.min(100,discount));
429 this.trigger('change');
431 // returns the discount [0,100]%
432 get_discount: function(){
433 return this.discount;
436 get_product_type: function(){
439 // sets the quantity of the product. The quantity will be rounded according to the
440 // product's unity of measure properties. Quantities greater than zero will not get
442 set_quantity: function(quantity){
443 if(_.isNaN(quantity)){
444 this.order.removeOrderline(this);
445 }else if(quantity !== undefined){
446 this.quantity = Math.max(0,quantity);
447 var unit = this.get_unit();
448 if(unit && this.quantity > 0 ){
449 this.quantity = Math.max(unit.rounding, Math.round(quantity / unit.rounding) * unit.rounding);
452 this.trigger('change');
454 // return the quantity of product
455 get_quantity: function(){
456 return this.quantity;
458 // return the unit of measure of the product
459 get_unit: function(){
460 var unit_id = (this.product.get('uos_id') || this.product.get('uom_id'));
464 unit_id = unit_id[0];
468 return this.pos.get('units_by_id')[unit_id];
470 // return the product of this orderline
471 get_product: function(){
474 // return the base price of this product (for this orderline)
475 get_list_price: function(){
478 // changes the base price of the product for this orderline
479 set_list_price: function(price){
481 this.trigger('change');
483 // selects or deselects this orderline
484 set_selected: function(selected){
485 this.selected = selected;
486 this.trigger('change');
488 // returns true if this orderline is selected
489 is_selected: function(){
490 return this.selected;
492 // when we add an new orderline we want to merge it with the last line to see reduce the number of items
493 // in the orderline. This returns true if it makes sense to merge the two
494 can_be_merged_with: function(orderline){
495 if( this.get_product().get('id') !== orderline.get_product().get('id')){ //only orderline of the same product can be merged
497 }else if(this.get_product_type() !== orderline.get_product_type()){
499 }else if(this.get_discount() > 0){ // we don't merge discounted orderlines
501 }else if(this.price !== orderline.price){
507 merge: function(orderline){
508 this.set_quantity(this.get_quantity() + orderline.get_quantity());
510 export_as_JSON: function() {
512 qty: this.get_quantity(),
513 price_unit: this.get_list_price(),
514 discount: this.get_discount(),
515 product_id: this.get_product().get('id'),
518 //used to create a json of the ticket, to be sent to the printer
519 export_for_printing: function(){
521 quantity: this.get_quantity(),
522 unit_name: this.get_unit().name,
523 list_price: this.get_list_price(),
524 discount: this.get_discount(),
525 product_name: this.get_product().get('name'),
526 price_with_tax : this.get_price_with_tax(),
527 price_without_tax: this.get_price_without_tax(),
531 get_price_without_tax: function(){
532 return this.get_all_prices().priceWithoutTax;
534 get_price_with_tax: function(){
535 return this.get_all_prices().priceWithTax;
538 return this.get_all_prices().tax;
540 get_all_prices: function() {
542 var base = this.get_quantity() * this.price * (1 - (this.get_discount() / 100));
544 var totalNoTax = base;
546 var product_list = this.pos.get('product_list');
547 var product = this.get_product();
548 var taxes_ids = product.taxes_id;
549 var taxes = self.pos.get('taxes');
551 _.each(taxes_ids, function(el) {
552 var tax = _.detect(taxes, function(t) {return t.id === el;});
553 if (tax.price_include) {
555 if (tax.type === "percent") {
556 tmp = base - (base / (1 + tax.amount));
557 } else if (tax.type === "fixed") {
558 tmp = tax.amount * self.get_quantity();
560 throw "This type of tax is not supported by the point of sale: " + tax.type;
566 if (tax.type === "percent") {
567 tmp = tax.amount * base;
568 } else if (tax.type === "fixed") {
569 tmp = tax.amount * self.get_quantity();
571 throw "This type of tax is not supported by the point of sale: " + tax.type;
578 "priceWithTax": totalTax,
579 "priceWithoutTax": totalNoTax,
585 module.OrderlineCollection = Backbone.Collection.extend({
586 model: module.Orderline,
589 // Every PaymentLine contains a cashregister and an amount of money.
590 module.Paymentline = Backbone.Model.extend({
591 initialize: function(attributes, options) {
593 this.cashregister = options.cashRegister;
595 //sets the amount of money on this payment line
596 set_amount: function(value){
598 this.trigger('change');
600 // returns the amount of money on this paymentline
601 get_amount: function(){
604 // returns the associated cashRegister
605 get_cashregister: function(){
606 return this.cashregister;
608 //exports as JSON for server communication
609 export_as_JSON: function(){
611 name: instance.web.datetime_to_str(new Date()),
612 statement_id: this.cashregister.get('id'),
613 account_id: (this.cashregister.get('account_id'))[0],
614 journal_id: (this.cashregister.get('journal_id'))[0],
615 amount: this.get_amount()
618 //exports as JSON for receipt printing
619 export_for_printing: function(){
621 amount: this.get_amount(),
622 journal: this.cashregister.get('journal_id')[1],
627 module.PaymentlineCollection = Backbone.Collection.extend({
628 model: module.Paymentline,
632 // An order more or less represents the content of a client's shopping cart (the OrderLines)
633 // plus the associated payment information (the PaymentLines)
634 // there is always an active ('selected') order in the Pos, a new one is created
635 // automaticaly once an order is completed and sent to the server.
636 module.Order = Backbone.Model.extend({
637 initialize: function(attributes){
638 Backbone.Model.prototype.initialize.apply(this, arguments);
640 creationDate: new Date(),
641 orderLines: new module.OrderlineCollection(),
642 paymentLines: new module.PaymentlineCollection(),
643 name: "Order " + this.generateUniqueId(),
646 this.pos = attributes.pos;
647 this.selected_orderline = undefined;
648 this.screen_data = {}; // see ScreenSelector
651 generateUniqueId: function() {
652 return new Date().getTime();
654 addProduct: function(product, options){
655 options = options || {};
656 var attr = product.toJSON();
659 var line = new module.Orderline({}, {pos: this.pos, order: this, product: product});
661 if(options.quantity !== undefined){
662 line.set_quantity(options.quantity);
664 if(options.price !== undefined){
665 line.set_list_price(options.price);
668 var last_orderline = this.getLastOrderline();
669 if( last_orderline && last_orderline.can_be_merged_with(line) && options.merge !== false){
670 last_orderline.merge(line);
672 this.get('orderLines').add(line);
674 this.selectLine(this.getLastOrderline());
676 removeOrderline: function( line ){
677 this.get('orderLines').remove(line);
678 this.selectLine(this.getLastOrderline());
680 getLastOrderline: function(){
681 return this.get('orderLines').at(this.get('orderLines').length -1);
683 addPaymentLine: function(cashRegister) {
684 var paymentLines = this.get('paymentLines');
685 var newPaymentline = new module.Paymentline({},{cashRegister:cashRegister});
686 if(cashRegister.get('journal').type !== 'cash'){
687 newPaymentline.set_amount( this.getDueLeft() );
689 paymentLines.add(newPaymentline);
691 getName: function() {
692 return this.get('name');
694 getTotal: function() {
695 return (this.get('orderLines')).reduce((function(sum, orderLine) {
696 return sum + orderLine.get_price_with_tax();
699 getTotalTaxExcluded: function() {
700 return (this.get('orderLines')).reduce((function(sum, orderLine) {
701 return sum + orderLine.get_price_without_tax();
705 return (this.get('orderLines')).reduce((function(sum, orderLine) {
706 return sum + orderLine.get_tax();
709 getPaidTotal: function() {
710 return (this.get('paymentLines')).reduce((function(sum, paymentLine) {
711 return sum + paymentLine.get_amount();
714 getChange: function() {
715 return this.getPaidTotal() - this.getTotal();
717 getDueLeft: function() {
718 return this.getTotal() - this.getPaidTotal();
720 // the client related to the current order.
721 set_client: function(client){
722 this.set('client',client);
724 get_client: function(){
725 return this.get('client');
727 // the order also stores the screen status, as the PoS supports
728 // different active screens per order. This method is used to
729 // store the screen status.
730 set_screen_data: function(key,value){
731 if(arguments.length === 2){
732 this.screen_data[key] = value;
733 }else if(arguments.length === 1){
734 for(key in arguments[0]){
735 this.screen_data[key] = arguments[0][key];
739 //see set_screen_data
740 get_screen_data: function(key){
741 return this.screen_data[key];
743 // exports a JSON for receipt printing
744 export_for_printing: function(){
746 this.get('orderLines').each(function(orderline){
747 orderlines.push(orderline.export_for_printing());
750 var paymentlines = [];
751 this.get('paymentLines').each(function(paymentline){
752 paymentlines.push(paymentline.export_for_printing());
754 var client = this.get('client');
755 var cashier = this.pos.get('cashier') || this.pos.get('user');
756 var company = this.pos.get('company');
757 var shop = this.pos.get('shop');
758 var date = new Date();
761 orderlines: orderlines,
762 paymentlines: paymentlines,
763 total_with_tax: this.getTotal(),
764 total_without_tax: this.getTotalTaxExcluded(),
765 total_tax: this.getTax(),
766 total_paid: this.getPaidTotal(),
767 change: this.getChange(),
768 name : this.getName(),
769 client: client ? client.name : null ,
770 cashier: cashier ? cashier.name : null,
772 year: date.getFullYear(),
773 month: date.getMonth(),
774 date: date.getDate(), // day of the month
775 day: date.getDay(), // day of the week
776 hour: date.getHours(),
777 minute: date.getMinutes()
780 email: company.email,
781 website: company.website,
782 company_registry: company.company_registry,
783 contact_address: null, //TODO
786 phone: company.phone,
791 currency: this.pos.get('currency'),
794 exportAsJSON: function() {
795 var orderLines, paymentLines;
797 (this.get('orderLines')).each(_.bind( function(item) {
798 return orderLines.push([0, 0, item.export_as_JSON()]);
801 (this.get('paymentLines')).each(_.bind( function(item) {
802 return paymentLines.push([0, 0, item.export_as_JSON()]);
805 name: this.getName(),
806 amount_paid: this.getPaidTotal(),
807 amount_total: this.getTotal(),
808 amount_tax: this.getTax(),
809 amount_return: this.getChange(),
811 statement_ids: paymentLines,
812 pos_session_id: this.pos.get('pos_session').id,
813 partner_id: this.pos.get('client') ? this.pos.get('client').id : undefined,
814 user_id: this.pos.get('cashier') ? this.pos.get('cashier').id : this.pos.get('user').id,
817 getSelectedLine: function(){
818 return this.selected_orderline;
820 selectLine: function(line){
822 if(line !== this.selected_orderline){
823 if(this.selected_orderline){
824 this.selected_orderline.set_selected(false);
826 this.selected_orderline = line;
827 this.selected_orderline.set_selected(true);
830 this.selected_orderline = undefined;
835 module.OrderCollection = Backbone.Collection.extend({
840 The numpad handles both the choice of the property currently being modified
841 (quantity, price or discount) and the edition of the corresponding numeric value.
843 module.NumpadState = Backbone.Model.extend({
848 appendNewChar: function(newChar) {
850 oldBuffer = this.get('buffer');
851 if (oldBuffer === '0') {
855 } else if (oldBuffer === '-0') {
857 buffer: "-" + newChar
861 buffer: (this.get('buffer')) + newChar
866 deleteLastChar: function() {
867 var tempNewBuffer = this.get('buffer').slice(0, -1);
870 this.set({ buffer: "0" });
873 if (isNaN(tempNewBuffer)) {
876 this.set({ buffer: tempNewBuffer });
880 switchSign: function() {
882 oldBuffer = this.get('buffer');
884 buffer: oldBuffer[0] === '-' ? oldBuffer.substr(1) : "-" + oldBuffer
888 changeMode: function(newMode) {
900 updateTarget: function() {
901 var bufferContent, params;
902 bufferContent = this.get('buffer');
903 if (bufferContent && !isNaN(bufferContent)) {
904 this.trigger('set_value', parseFloat(bufferContent));
907 killTarget: function(){
908 this.trigger('set_value',Number.NaN);