diff --git a/.travis.yml b/.travis.yml index 94e850d5a7..45872f8a6b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,9 +23,9 @@ env: matrix: - LINT_CHECK="1" - - TRANSIFEX="1" - TESTS="1" ODOO_REPO="odoo/odoo" - TESTS="1" ODOO_REPO="OCA/OCB"" + - TRANSIFEX="1" install: @@ -38,7 +38,7 @@ script: - travis_run_tests after_success: - coveralls + - travis_after_tests_success notifications: email: false diff --git a/hw_printer_network/README.rst b/hw_printer_network/README.rst index b9b6f6b9d9..9e979ef135 100644 --- a/hw_printer_network/README.rst +++ b/hw_printer_network/README.rst @@ -13,6 +13,7 @@ Credits Contributors ------------ * Dinar Gabbasov +* Tom Blauwendraat Sponsors -------- diff --git a/hw_printer_network/__openerp__.py b/hw_printer_network/__openerp__.py index 9e4e203eb4..840395a3ea 100644 --- a/hw_printer_network/__openerp__.py +++ b/hw_printer_network/__openerp__.py @@ -4,7 +4,7 @@ "summary": """Hardware Driver for Network Printers""", "category": "Point of Sale", "images": [], - "version": "1.0.0", + "version": "2.0.0", "application": False, "author": "IT-Projects LLC, Dinar Gabbasov", diff --git a/hw_printer_network/controllers/hw_printer_network_controller.py b/hw_printer_network/controllers/hw_printer_network_controller.py index 472219bdf2..0f10ab35df 100644 --- a/hw_printer_network/controllers/hw_printer_network_controller.py +++ b/hw_printer_network/controllers/hw_printer_network_controller.py @@ -2,8 +2,10 @@ from openerp import http import logging import time +import socket +import subprocess +import threading import traceback -from ast import literal_eval _logger = logging.getLogger(__name__) @@ -13,51 +15,91 @@ from openerp.addons.hw_escpos.escpos import escpos from openerp.addons.hw_escpos.controllers.main import EscposProxy from openerp.addons.hw_escpos.controllers.main import EscposDriver - from openerp.addons.hw_escpos.escpos.exceptions import NoDeviceError, HandleDeviceError, TicketNotPrinted, NoStatusError from openerp.addons.hw_escpos.escpos.printer import Network - import openerp.addons.hw_escpos.controllers.main as hw_escpos_main + import openerp.addons.hw_proxy.controllers.main as hw_proxy except ImportError: EscposProxy = object + EscposDriver = object -class UpdatedEscposDriver(EscposDriver): +class PingProcess(threading.Thread): + def __init__(self, ip): + self.stdout = None + self.stderr = None + threading.Thread.__init__(self) + self.status = 'offline' + self.ip = ip + self.stop = False + + def run(self): + while not self.stop: + child = subprocess.Popen( + ["ping", "-c1", "-w5", self.ip], + shell=False, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + child.communicate() + self.status = 'offline' if child.returncode else 'online' + time.sleep(1) + + def get_status(self): + return self.status + + def __del__(self): + self.stop = True + + +class EscposNetworkDriver(EscposDriver): def __init__(self): - self.network_printers = False - self.usb_printer_active = False - self.run_network_connection() - super(UpdatedEscposDriver, self).__init__() - - def run_network_connection(self): - while self.network_printers and len(self.network_printers) > 0: - driver.network_printers = self.connected_network_printers(self.network_printers) - time.sleep(5) - - def connected_network_printers(self, printers): - printer = False - if printers and len(printers) > 0: - network_printers = [] - for p in printers: - if type(p) is str: - p = literal_eval(p) - try: - printer = UpdatedNetwork(p['ip']) - printer.close() - p['status'] = 'online' - except: - p['status'] = 'offline' - if printer: - printer.close() - network_printers.append({ - 'ip': str(p['ip']), - 'status': str(p['status']), - 'name': str(p['name']) - }) - return network_printers - return printers + self.network_printers = [] + self.ping_processes = {} + self.printer_objects = {} + super(EscposNetworkDriver, self).__init__() + + def get_network_printer(self, ip, name=None): + found_printer = False + for printer in self.network_printers: + if printer['ip'] == ip: + found_printer = True + if name: + printer['name'] = name + if printer['status'] == 'online': + printer_object = self.printer_objects.get(ip, None) + if not printer_object: + try: + printer_object = Network(ip) + self.printer_objects[ip] = printer_object + except socket.error: + pass + return printer + if not found_printer: + self.add_network_printer(ip, name) + return None + + def add_network_printer(self, ip, name=None): + printer = dict( + ip=ip, + status='offline', + name=name or 'Unnamed printer' + ) + self.network_printers.append(printer) # dont return because offline + self.start_pinging(ip) + + def start_pinging(self, ip): + pinger = PingProcess(ip) + self.ping_processes[ip] = pinger + pinger.start() + + def update_driver_status(self): + count = len([p for p in self.network_printers if p.get('status', None) == 'online']) + if count: + self.set_status('connected', '{} printer(s) Connected'.format(count)) + else: + self.set_status('disconnected', 'Disconnected') def run(self): - printer = None if not escpos: _logger.error('ESC/POS cannot initialize, please verify system dependencies.') return @@ -65,56 +107,39 @@ def run(self): try: error = True timestamp, task, data = self.queue.get(True) - printer = None - if len(task) == 2: - if task[0] == 'network_xml_receipt': - if timestamp >= time.time() - 1 * 60 * 60: - network_printer_proxy = task[1] - # print in network printer - printer = UpdatedNetwork(network_printer_proxy) - printer.receipt(data) - else: - if self.usb_printer_active: - printer = self.get_escpos_printer() - elif self.network_printers: - self.set_status('connected', 'Connected') - if printer is None: - if self.usb_printer_active is False: - if self.network_printers: - self.set_status('connected', 'Connected') - else: - if task != 'status': - self.queue.put((timestamp, task, data)) - elif self.usb_printer_active is True: - if task != 'status': - self.queue.put((timestamp, task, data)) + if task == 'xml_receipt': error = False - time.sleep(5) - continue - elif task == 'receipt': - if timestamp >= time.time() - 1 * 60 * 60: - self.print_receipt_body(printer, data) - printer.cut() - elif task == 'xml_receipt': - if timestamp >= time.time() - 1 * 60 * 60: - printer.receipt(data) - elif task == 'cashbox': - if timestamp >= time.time() - 12: - self.open_cashbox(printer) + if timestamp >= (time.time() - 1 * 60 * 60): + receipt, network_printer_ip = data + printer_info = self.get_network_printer(network_printer_ip) + printer = self.printer_objects.get(network_printer_ip, None) + if printer_info and printer_info['status'] == 'online' and printer: + _logger.info('Printing XML receipt on printer %s...', network_printer_ip) + try: + printer.receipt(receipt) + except socket.error: + printer.open() + printer.receipt(receipt) + _logger.info('Done printing XML receipt on printer %s', network_printer_ip) + else: + _logger.error('xml_receipt: printer offline!') + # TODO: do something with the missed order? elif task == 'printstatus': - self.print_status(printer) - elif task == 'status': pass + elif task == 'status': + error = False + for printer in self.network_printers: + ip = printer['ip'] + pinger = self.ping_processes.get(ip, None) + if pinger and pinger.isAlive(): + status = pinger.get_status() + if status != printer['status']: + # todo: use a lock? + printer['status'] = status + self.update_driver_status() + else: + self.start_pinging(ip) error = False - - except NoDeviceError as e: - _logger.error("No device found %s" % str(e)) - except HandleDeviceError as e: - _logger.error("Impossible to handle the device due to previous error %s" % str(e)) - except TicketNotPrinted as e: - _logger.error("The ticket does not seems to have been fully printed %s" % str(e)) - except NoStatusError as e: - _logger.error("Impossible to get the status of the printer %s" % str(e)) except Exception as e: self.set_status('error', str(e)) errmsg = str(e) + '\n' + '-'*60+'\n' + traceback.format_exc() + '-'*60 + '\n' @@ -122,48 +147,36 @@ def run(self): finally: if error: self.queue.put((timestamp, task, data)) - if printer: - printer.close() -driver = UpdatedEscposDriver() -hw_escpos_main.driver = driver -driver.push_task('printstatus') +# Separate instance, mainloop and queue for network printers +# original driver runs in parallel and deals with USB printers +network_driver = EscposNetworkDriver() + +hw_proxy.drivers['escpos_network'] = network_driver + +# this will also start the message handling loop +network_driver.push_task('printstatus') class UpdatedEscposProxy(EscposProxy): @http.route('/hw_proxy/print_xml_receipt', type='json', auth='none', cors='*') def print_xml_receipt(self, receipt, proxy=None): if proxy: - driver.push_task(['network_xml_receipt', proxy], receipt) + network_driver.push_task('xml_receipt', (receipt, proxy)) else: super(UpdatedEscposProxy, self).print_xml_receipt(receipt) @http.route('/hw_proxy/network_printers', type='json', auth='none', cors='*') - def networ_printers(self, network_printers=None): - network_printers = list(set([str(e) for e in network_printers])) - for i in range(len(network_printers)): - network_printers[i] = literal_eval(network_printers[i]) - driver.network_printers = network_printers - driver.run_network_connection() + def network_printers(self, network_printers=None): + for printer in network_printers: + network_driver.get_network_printer(printer['ip'], name=printer['name']) @http.route('/hw_proxy/status_network_printers', type='json', auth='none', cors='*') - def networ_printers_status(self): - return driver.network_printers + def network_printers_status(self): + return network_driver.network_printers @http.route('/hw_proxy/without_usb', type='http', auth='none', cors='*') def without_usb(self): - driver.usb_printer_active = False + """ Old pos_printer_network module expects this to work """ return "ping" - - @http.route('/hw_proxy/hello', type='http', auth='none', cors='*') - def hello(self): - driver.usb_printer_active = True - return super(UpdatedEscposProxy, self).hello() - - -class UpdatedNetwork(Network): - def close(self): - """ Close TCP connection """ - if self.device: - self.device.close() diff --git a/hw_printer_network/doc/changelog.rst b/hw_printer_network/doc/changelog.rst index 9ee2b48b8e..9d65606cc4 100644 --- a/hw_printer_network/doc/changelog.rst +++ b/hw_printer_network/doc/changelog.rst @@ -1,3 +1,8 @@ +`2.0.0` +------- + +- **IMP:** Asynchroneous checking of network status to speed it up when there are more than one network printer in the system. Also, there is no need to modify code of posbox anymore. + `1.0.0` ------- diff --git a/hw_printer_network/doc/index.rst b/hw_printer_network/doc/index.rst index 88eb358b24..6b188f5fcf 100644 --- a/hw_printer_network/doc/index.rst +++ b/hw_printer_network/doc/index.rst @@ -8,15 +8,6 @@ Installation In PosBox --------- -* Comment out line 354 in hw_escpos/controllers/main.py, i.e. replace - - ``driver.push_task('printstatus')`` - - with - - ``# driver.push_task('printstatus')`` - - Read here how to edit files at PosBox: https://odoo-development.readthedocs.io/en/latest/admin/posbox/administrate-posbox.html#how-to-edit-odoo-source. * add ``hw_printer_network`` module to *server wide modules*. Detailed instruction is here: https://odoo-development.readthedocs.io/en/latest/admin/posbox/administrate-posbox.html#how-to-update-odoo-command-line-options Usage diff --git a/pos_debt_notebook/__manifest__.py b/pos_debt_notebook/__manifest__.py index d04bc8105c..b3cc554273 100644 --- a/pos_debt_notebook/__manifest__.py +++ b/pos_debt_notebook/__manifest__.py @@ -4,7 +4,7 @@ 'category': 'Point Of Sale', 'live_test_url': 'http://apps.it-projects.info/shop/product/pos-debt-notebook?version=10.0', "images": ['images/debt_notebook.png'], - 'version': '4.4.1', + 'version': '4.4.3', 'author': 'IT-Projects LLC, Ivan Yelizariev', "support": "apps@it-projects.info", diff --git a/pos_debt_notebook/doc/changelog.rst b/pos_debt_notebook/doc/changelog.rst index 2559e43747..c9387003f8 100644 --- a/pos_debt_notebook/doc/changelog.rst +++ b/pos_debt_notebook/doc/changelog.rst @@ -1,3 +1,13 @@ +`4.4.3` +------- + +**FIX:** Compatibility with Chinese localization + +`4.4.2` +------- + +**ADD:** Compatibility with Pos Mobile + `4.4.1` ------- diff --git a/pos_debt_notebook/doc/index.rst b/pos_debt_notebook/doc/index.rst index 9c42ee516e..9e4e25a7f9 100644 --- a/pos_debt_notebook/doc/index.rst +++ b/pos_debt_notebook/doc/index.rst @@ -19,6 +19,16 @@ Debt Journal * select Customer * on payment screen use "Debt Journal" to pay +Pay Full Debt button +-------------------- + +The button appears when you select Customer which has unpaid debt (red amount). You can see this button in 2 places: + +* At the top of the Customer's form +* On the payment page + +When you click ``[Pay Full Debt]`` button, the debt amount will be added to the payment list with negative sign. That negative amount has to be covered by normal payment (e.g. by cash). After that the Customer will have zero debt value. + Credit Products --------------- diff --git a/pos_debt_notebook/i18n/fr.po b/pos_debt_notebook/i18n/fr.po index ff77e32147..0186912e86 100644 --- a/pos_debt_notebook/i18n/fr.po +++ b/pos_debt_notebook/i18n/fr.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 10.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-07-28 07:32+0000\n" -"PO-Revision-Date: 2017-07-28 07:32+0000\n" +"POT-Creation-Date: 2017-12-31 00:07+0000\n" +"PO-Revision-Date: 2017-12-31 00:07+0000\n" "Last-Translator: Translation Bot , 2017\n" "Language-Team: French (https://www.transifex.com/it-projects-llc/teams/76080/fr/)\n" "MIME-Version: 1.0\n" @@ -40,11 +40,13 @@ msgstr "" #. module: pos_debt_notebook #: model:ir.model.fields,field_description:pos_debt_notebook.field_pos_credit_update_balance +#: selection:pos.credit.update,update_type:0 msgid "Balance Update" msgstr "" #. module: pos_debt_notebook #: model:ir.ui.view,arch_db:pos_debt_notebook.view_pos_credit_invoices_form +#: model:ir.ui.view,arch_db:pos_debt_notebook.view_pos_credit_update_form msgid "Cancel" msgstr "" @@ -77,6 +79,12 @@ msgstr "" msgid "Company" msgstr "" +#. module: pos_debt_notebook +#: model:ir.actions.server,name:pos_debt_notebook.server_action_pos_credit_update +#: model:ir.ui.view,arch_db:pos_debt_notebook.view_pos_credit_update_form +msgid "Confirm" +msgstr "" + #. module: pos_debt_notebook #: selection:pos.credit.update,state:0 msgid "Confirmed" @@ -163,7 +171,7 @@ msgstr "" #. module: pos_debt_notebook #. openerp-web -#: code:addons/pos_debt_notebook/static/src/js/pos.js:368 +#: code:addons/pos_debt_notebook/static/src/js/pos.js:389 #: model:ir.ui.view,arch_db:pos_debt_notebook.view_pos_credit_update_search #: model:ir.ui.view,arch_db:pos_debt_notebook.view_report_pos_debt_search #, python-format @@ -280,11 +288,17 @@ msgstr "" #. module: pos_debt_notebook #. openerp-web -#: code:addons/pos_debt_notebook/static/src/js/pos.js:273 +#: code:addons/pos_debt_notebook/static/src/js/pos.js:277 #, python-format msgid "Don't forget to specify Customer when sell Credits." msgstr "" +#. module: pos_debt_notebook +#: model:ir.ui.view,arch_db:pos_debt_notebook.view_pos_credit_update_form +#: selection:pos.credit.update,state:0 +msgid "Draft" +msgstr "" + #. module: pos_debt_notebook #: model:ir.model.fields,field_description:pos_debt_notebook.field_pos_config_debt_dummy_product_id msgid "Dummy Product for Debt" @@ -303,20 +317,14 @@ msgstr "" #. module: pos_debt_notebook #. openerp-web -#: code:addons/pos_debt_notebook/static/src/js/pos.js:279 +#: code:addons/pos_debt_notebook/static/src/js/pos.js:283 #, python-format msgid "Empty Order" msgstr "Commande vide" -#. module: pos_debt_notebook -#: code:addons/pos_debt_notebook/__init__.py:15 -#, python-format -msgid "Error!" -msgstr "" - #. module: pos_debt_notebook #. openerp-web -#: code:addons/pos_debt_notebook/static/src/js/pos.js:538 +#: code:addons/pos_debt_notebook/static/src/js/pos.js:559 #, python-format msgid "Error: No Debt" msgstr "Erreur: aucune dette" @@ -442,7 +450,7 @@ msgstr "" #. module: pos_debt_notebook #. openerp-web -#: code:addons/pos_debt_notebook/static/src/js/pos.js:286 +#: code:addons/pos_debt_notebook/static/src/js/pos.js:290 #, python-format msgid "Max Debt exceeded" msgstr "" @@ -472,13 +480,19 @@ msgstr "" msgid "New" msgstr "" +#. module: pos_debt_notebook +#: model:ir.model.fields,field_description:pos_debt_notebook.field_pos_credit_update_new_balance +#: selection:pos.credit.update,update_type:0 +msgid "New Balance" +msgstr "" + #. module: pos_debt_notebook #: model:ir.model.fields,field_description:pos_debt_notebook.field_pos_credit_update_note msgid "Note" msgstr "" #. module: pos_debt_notebook -#: code:addons/pos_debt_notebook/models.py:126 +#: code:addons/pos_debt_notebook/models.py:147 #, python-format msgid "Only POS managers can change a debt limit value!" msgstr "" @@ -554,7 +568,7 @@ msgstr "" #. module: pos_debt_notebook #. openerp-web -#: code:addons/pos_debt_notebook/static/src/js/pos.js:294 +#: code:addons/pos_debt_notebook/static/src/js/pos.js:298 #, python-format msgid "" "Please enter the exact or lower debt amount than the cost of the order." @@ -662,14 +676,14 @@ msgstr "" #. module: pos_debt_notebook #. openerp-web -#: code:addons/pos_debt_notebook/static/src/js/pos.js:539 +#: code:addons/pos_debt_notebook/static/src/js/pos.js:560 #, python-format msgid "The selected customer has no debt." msgstr "Le client sélectionné n'a pas de dette." #. module: pos_debt_notebook #. openerp-web -#: code:addons/pos_debt_notebook/static/src/js/pos.js:280 +#: code:addons/pos_debt_notebook/static/src/js/pos.js:284 #, python-format msgid "" "There must be at least one product in your order before it can be validated." @@ -734,24 +748,34 @@ msgstr "" #. module: pos_debt_notebook #. openerp-web -#: code:addons/pos_debt_notebook/static/src/js/pos.js:293 +#: code:addons/pos_debt_notebook/static/src/js/pos.js:297 #, python-format msgid "Unable to return the change with a debt payment method" msgstr "" #. module: pos_debt_notebook #. openerp-web -#: code:addons/pos_debt_notebook/static/src/js/pos.js:265 -#: code:addons/pos_debt_notebook/static/src/js/pos.js:272 +#: code:addons/pos_debt_notebook/static/src/js/pos.js:269 +#: code:addons/pos_debt_notebook/static/src/js/pos.js:276 #, python-format msgid "Unknown customer" msgstr "Client inconnu" +#. module: pos_debt_notebook +#: model:ir.model.fields,field_description:pos_debt_notebook.field_pos_credit_update_update_type +msgid "Update type" +msgstr "" + #. module: pos_debt_notebook #: selection:report.pos.debt,state:0 msgid "Validated" msgstr "" +#. module: pos_debt_notebook +#: model:ir.model.fields,help:pos_debt_notebook.field_pos_credit_update_new_balance +msgid "Value to set balance to. Used only in Draft state." +msgstr "" + #. module: pos_debt_notebook #: model:ir.model.fields,help:pos_debt_notebook.field_pos_config_settings_debt_type msgid "" @@ -772,7 +796,7 @@ msgstr "" #. module: pos_debt_notebook #. openerp-web -#: code:addons/pos_debt_notebook/static/src/js/pos.js:287 +#: code:addons/pos_debt_notebook/static/src/js/pos.js:291 #, python-format msgid "" "You cannot sell products on credit to the customer, because his max debt " @@ -781,7 +805,7 @@ msgstr "" #. module: pos_debt_notebook #. openerp-web -#: code:addons/pos_debt_notebook/static/src/js/pos.js:266 +#: code:addons/pos_debt_notebook/static/src/js/pos.js:270 #, python-format msgid "You cannot use Debt payment. Select customer first." msgstr "" diff --git a/pos_debt_notebook/models.py b/pos_debt_notebook/models.py index c604c36596..0ff057b6ee 100644 --- a/pos_debt_notebook/models.py +++ b/pos_debt_notebook/models.py @@ -132,7 +132,7 @@ def _get_date_formats(self, report): user_tz = timezone(self.env.user.tz) final = utc_tz.astimezone(user_tz) - return final.strftime(fmt) + return final.strftime(fmt.encode('utf-8')) def _compute_debt_type(self): debt_type = self.env["ir.config_parameter"].sudo().get_param("pos_debt_notebook.debt_type", default='debt') @@ -225,7 +225,6 @@ def init_debt_journal(self): 'code': 'XDEBT', 'user_type_id': self.env.ref('account.data_account_type_current_assets').id, 'company_id': user.company_id.id, - 'note': 'code "XDEBT" should not be modified as it is used to compute debt', }) self.env['ir.model.data'].create({ 'name': 'debt_account_for_company' + str(user.company_id.id), diff --git a/pos_debt_notebook/static/src/css/pos.css b/pos_debt_notebook/static/src/css/pos.css index f3d63474cd..ba15c32a48 100644 --- a/pos_debt_notebook/static/src/css/pos.css +++ b/pos_debt_notebook/static/src/css/pos.css @@ -71,3 +71,21 @@ padding: 3px 13px; transition: all 150ms linear; } +/* if pos_mobile was installed*/ +.pos.mobile .clientlist-screen .top-content .debt_notebook_button { + float: none; +} +.pos.mobile .clientlist-screen .top-content #client_list_header_buttons { + display: inline-block; + position: relative; + top: 0; + right: 0; + left: 0; + padding-left: 0; + padding-right: 0; + height: initial; +} +.pos.mobile .clientlist-screen .top-content #client_list_header_buttons span { + margin-left: 0; + margin-right: 0; +} diff --git a/pos_disable_payment/README.rst b/pos_disable_payment/README.rst index 26c67e3457..7aa6969430 100644 --- a/pos_disable_payment/README.rst +++ b/pos_disable_payment/README.rst @@ -18,8 +18,9 @@ Credits Contributors ------------ -* Ivan Yelizariev -* Ilmir Karamov +* `Ivan Yelizariev `__ +* `Ilmir Karamov `__ +* `Dinar Gabbasov `__ Sponsors -------- diff --git a/pos_disable_payment/__openerp__.py b/pos_disable_payment/__openerp__.py index b4cc9c9d61..351c4f7483 100644 --- a/pos_disable_payment/__openerp__.py +++ b/pos_disable_payment/__openerp__.py @@ -2,14 +2,16 @@ { 'name': "Disable payments in POS", 'summary': "Control access to the POS payments", - 'version': '2.3.0', + 'version': '10.0.2.5.0', 'author': 'IT-Projects LLC, Ivan Yelizariev', 'license': 'LGPL-3', 'category': 'Point Of Sale', 'live_test_url': 'http://apps.it-projects.info/shop/product/pos-multi-session?version=11.0', "support": "apps@it-projects.info", 'website': 'https://yelizariev.github.io', - 'depends': ['point_of_sale'], + 'depends': [ + 'point_of_sale' + ], 'images': ['images/pos_payment_access.png'], "price": 40.00, "currency": "EUR", diff --git a/pos_disable_payment/doc/changelog.rst b/pos_disable_payment/doc/changelog.rst index 27ad8fb227..5137e8589d 100644 --- a/pos_disable_payment/doc/changelog.rst +++ b/pos_disable_payment/doc/changelog.rst @@ -3,6 +3,19 @@ Updates ======= +`2.5.0` +------- +- NEW: New option "Allow manual customer selecting" on user access rights + +`2.4.1` +------- +- FIX: Compatibility with pos_discount +- IMP: When unchecked "Allow remove order line", the delete button is disabled if qty of the line < = 0 + +`2.4.0` +------- +- NEW: Set disabled button as non-clickable instead of hiding + `2.3.0` ------- - NEW: New option "Allow refunds" on user access rights diff --git a/pos_disable_payment/models.py b/pos_disable_payment/models.py index 7ce35f88c0..0b9128459f 100644 --- a/pos_disable_payment/models.py +++ b/pos_disable_payment/models.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- -from openerp import fields -from openerp import models +from odoo import fields, models, api -class PosConfig(models.Model): +class ResUsers(models.Model): _inherit = 'res.users' allow_payments = fields.Boolean('Allow payments', default=True) @@ -11,6 +10,17 @@ class PosConfig(models.Model): allow_discount = fields.Boolean('Allow discount', default=True) allow_edit_price = fields.Boolean('Allow edit price', default=True) allow_decrease_amount = fields.Boolean('Allow decrease order line', default=True) + allow_decrease_kitchen_only = fields.Boolean('Allow change Qty for kitchen orders', default=False) allow_delete_order_line = fields.Boolean('Allow remove order line', default=True) allow_create_order_line = fields.Boolean('Allow create order line', default=True) allow_refund = fields.Boolean('Allow refunds', default=True) + allow_manual_customer_selecting = fields.Boolean('Allow manual customer selecting', default=True) + is_restaurant_installed = fields.Boolean(compute='_compute_state') + + def _compute_state(self): + for r in self: + r.is_restaurant_installed = r.is_module_installed('pos_restaurant') + + @api.model + def is_module_installed(self, module_name=None): + return module_name in self.env['ir.module.module']._installed() diff --git a/pos_disable_payment/static/src/css/pos.css b/pos_disable_payment/static/src/css/pos.css new file mode 100644 index 0000000000..7d610282c5 --- /dev/null +++ b/pos_disable_payment/static/src/css/pos.css @@ -0,0 +1,4 @@ +.pos .disable { + background: #00000036!important; + pointer-events: none; +} diff --git a/pos_disable_payment/static/src/js/pos_disable_payment.js b/pos_disable_payment/static/src/js/pos_disable_payment.js index 9e29c5cdde..1413536835 100644 --- a/pos_disable_payment/static/src/js/pos_disable_payment.js +++ b/pos_disable_payment/static/src/js/pos_disable_payment.js @@ -1,4 +1,5 @@ odoo.define('pos_disable_payment', function(require){ + "use strict"; var chrome = require('point_of_sale.chrome'); @@ -9,42 +10,14 @@ odoo.define('pos_disable_payment', function(require){ var PosBaseWidget = require('point_of_sale.BaseWidget'); var _t = core._t; - models.load_models({ - model: 'res.users', - fields: ['allow_payments','allow_delete_order','allow_discount','allow_edit_price','allow_decrease_amount','allow_delete_order_line','allow_create_order_line','allow_refund'], - loaded: function(self,users){ - for (var i = 0; i < users.length; i++) { - var user = _.find(self.users, function(el){ - return el.id === users[i].id; - }); - if (user) { - _.extend(user,users[i]); - } - } - } - }); + models.load_fields("res.users", ['allow_payments','allow_delete_order','allow_discount','allow_edit_price','allow_decrease_amount','allow_decrease_kitchen_only','allow_delete_order_line','allow_create_order_line','allow_refund','allow_manual_customer_selecting']); + // Example of event binding and handling (triggering). Look up binding lower bind('change:cashier' ... // Example extending of class (method set_cashier), than was created using extend. // /odoo9/addons/point_of_sale/static/src/js/models.js // exports.PosModel = Backbone.Model.extend ... var PosModelSuper = models.PosModel; models.PosModel = models.PosModel.extend({ - initialize: function(){ - PosModelSuper.prototype.initialize.apply(this, arguments); - var self = this; - this.ready.then(function () { - if (!self.cashier){ - // it's possible in non-updated odoo that self.cashier if falsy here - return; - } - // At this point this.cashier has no rights settings added by module. - // Reset cashier to fix it - var current_cashier = _.find(self.users, function(user){ - return user.id === self.cashier.id; - }); - self.set_cashier(current_cashier); - }); - }, set_cashier: function(){ PosModelSuper.prototype.set_cashier.apply(this, arguments); this.trigger('change:cashier',this); @@ -63,9 +36,9 @@ odoo.define('pos_disable_payment', function(require){ if (order) { // User option calls "Allow remove non-empty order". So we got to check if its empty we can delete it. if (!user.allow_delete_order && order.orderlines.length > 0) { - this.$('.deleteorder-button').hide(); + this.$('.deleteorder-button').addClass('disable'); } else { - this.$('.deleteorder-button').show(); + this.$('.deleteorder-button').removeClass('disable'); } } }, @@ -87,26 +60,62 @@ odoo.define('pos_disable_payment', function(require){ this._super(); var order = this.pos.get('selectedOrder'); order.orderlines.bind('add remove', this.chrome.check_allow_delete_order, this.chrome); + }, + orderline_change: function(line) { + this._super(line); + var user = this.pos.cashier || this.pos.user; + if (line && line.quantity <= 0) { + if (user.allow_delete_order_line) { + $('.numpad-backspace').removeClass('disable'); + } else{ + $('.numpad-backspace').addClass('disable'); + } + } else { + $('.numpad-backspace').removeClass('disable'); + } + this.check_kitchen_access(line); + }, + click_line: function(orderline, event) { + this._super(orderline, event); + this.check_kitchen_access(orderline); + }, + check_kitchen_access: function(line){ + var user = this.pos.cashier || this.pos.user; + if (user.allow_decrease_amount || user.allow_decrease_kitchen_only) { + return true; + } + var state = this.getParent().numpad.state; + if (line.mp_dirty === false) { + $('.numpad').find("[data-mode='quantity']").addClass('disable'); + if (user.allow_discount) { + state.changeMode('discount'); + } else if (user.allow_edit_price) { + state.changeMode('price'); + } else { + state.changeMode(""); + } + } else { + $('.numpad').find("[data-mode='quantity']").removeClass('disable'); + if (state.get('mode') !== 'quantity') { + state.changeMode('quantity'); + } + } } }); // Here regular binding (in init) do not work for some reasons. We got to put binding method in renderElement. screens.ProductScreenWidget.include({ - init: function () { - var self = this; - this._super.apply(this, arguments); - }, start: function () { this._super(); - var user = this.pos.cashier || this.pos.user; - if (!user.allow_payments) { - this.actionpad.$('.pay').hide(); - } + this.checkPayAllowed(); + this.checkCreateOrderLine(); + this.checkDiscountButton(); }, renderElement: function () { this._super(); this.pos.bind('change:cashier', this.checkPayAllowed, this); this.pos.bind('change:cashier', this.checkCreateOrderLine, this); + this.pos.bind('change:cashier', this.checkDiscountButton, this); }, checkCreateOrderLine: function () { var user = this.pos.cashier || this.pos.user; @@ -121,9 +130,17 @@ odoo.define('pos_disable_payment', function(require){ checkPayAllowed: function () { var user = this.pos.cashier || this.pos.user; if (user.allow_payments) { - this.actionpad.$('.pay').show(); + this.actionpad.$('.pay').removeClass('disable'); }else{ - this.actionpad.$('.pay').hide(); + this.actionpad.$('.pay').addClass('disable'); + } + }, + checkDiscountButton: function() { + var user = this.pos.cashier || this.pos.user; + if (user.allow_discount) { + this.$('.control-buttons .js_discount').removeClass('disable'); + }else{ + this.$('.control-buttons .js_discount').addClass('disable'); } } }); @@ -132,9 +149,9 @@ odoo.define('pos_disable_payment', function(require){ this._super(); var user = this.pos.cashier || this.pos.user; if (user.allow_payments) { - $('.pay').show(); + $('.pay').removeClass('disable'); }else{ - $('.pay').hide(); + $('.pay').addClass('disable'); } if (user.allow_create_order_line) { $('.numpad').show(); @@ -146,14 +163,47 @@ odoo.define('pos_disable_payment', function(require){ } }); screens.ActionpadWidget.include({ + init: function(parent, options) { + var self = this; + this._super(parent, options); + this.pos.bind('change:cashier', this.checkManualCustomerSelecting, this); + }, + checkManualCustomerSelecting: function() { + var user = this.pos.cashier || this.pos.user; + if (user.allow_manual_customer_selecting) { + this.$('.set-customer').removeClass('disable'); + } else { + this.$('.set-customer').addClass('disable'); + } + }, renderElement: function () { this._super(); var user = this.pos.cashier || this.pos.user; if (user.allow_payments) { - $('.pay').show(); - }else{ - $('.pay').hide(); + $('.pay').removeClass('disable'); + } else{ + $('.pay').addClass('disable'); + } + this.checkManualCustomerSelecting(); + } + }); + screens.PaymentScreenWidget.include({ + init: function(parent, options) { + var self = this; + this._super(parent, options); + this.pos.bind('change:cashier', this.checkManualCustomerSelecting, this); + }, + checkManualCustomerSelecting: function() { + var user = this.pos.cashier || this.pos.user; + if (user.allow_manual_customer_selecting) { + this.$('.js_set_customer').removeClass('disable'); + } else { + this.$('.js_set_customer').addClass('disable'); } + }, + renderElement: function(){ + this._super(); + this.checkManualCustomerSelecting(); } }); screens.NumpadWidget.include({ @@ -167,25 +217,36 @@ odoo.define('pos_disable_payment', function(require){ }, check_access: function(){ var user = this.pos.cashier || this.pos.user; + var order = this.pos.get_order(); + var orderline = false; + if (order) { + orderline = order.get_selected_orderline(); + } if (user.allow_discount) { - this.$el.find("[data-mode='discount']").css('visibility', 'visible'); + this.$el.find("[data-mode='discount']").removeClass('disable'); }else{ - this.$el.find("[data-mode='discount']").css('visibility', 'hidden'); + this.$el.find("[data-mode='discount']").addClass('disable'); } if (user.allow_edit_price) { - this.$el.find("[data-mode='price']").css('visibility', 'visible'); + this.$el.find("[data-mode='price']").removeClass('disable'); }else{ - this.$el.find("[data-mode='price']").css('visibility', 'hidden'); + this.$el.find("[data-mode='price']").addClass('disable'); } if (user.allow_refund) { - this.$el.find('.numpad-minus').css('visibility', 'visible'); + this.$el.find('.numpad-minus').removeClass('disable'); }else{ - this.$el.find('.numpad-minus').css('visibility', 'hidden'); + this.$el.find('.numpad-minus').addClass('disable'); + } + if (orderline && orderline.quantity <= 0) { + if (user.allow_delete_order_liner) { + this.$el.find('.numpad-backspace').removeClass('disable'); + }else{ + this.$el.find('.numpad-backspace').addClass('disable'); + } } } }); - screens.NumpadWidget.include({ clickDeleteLastChar: function(){ var user = this.pos.cashier || this.pos.user; diff --git a/pos_disable_payment/views.xml b/pos_disable_payment/views.xml index 5172db3ec7..8fe74fc6b5 100644 --- a/pos_disable_payment/views.xml +++ b/pos_disable_payment/views.xml @@ -3,6 +3,7 @@ @@ -21,8 +22,11 @@ + + + diff --git a/pos_keyboard/__openerp__.py b/pos_keyboard/__openerp__.py index 33b16126c0..a387c92c05 100644 --- a/pos_keyboard/__openerp__.py +++ b/pos_keyboard/__openerp__.py @@ -10,7 +10,7 @@ 'license': 'LGPL-3', 'version': '1.0.2', 'depends': ['point_of_sale'], - "price": 100.00, + "price": 15.00, "currency": "EUR", 'data': [ 'data.xml', diff --git a/pos_longpolling/__manifest__.py b/pos_longpolling/__manifest__.py index 308a0ccb2c..ffe8bef194 100644 --- a/pos_longpolling/__manifest__.py +++ b/pos_longpolling/__manifest__.py @@ -3,7 +3,7 @@ "summary": """Technical module to implement instant updates in POS""", "category": "Point of Sale", "images": [], - "version": "1.1.1", + "version": "10.0.2.0.1", "application": False, "author": "IT-Projects LLC, Dinar Gabbasov", diff --git a/pos_longpolling/doc/changelog.rst b/pos_longpolling/doc/changelog.rst index 9567048fd0..598098f8bb 100644 --- a/pos_longpolling/doc/changelog.rst +++ b/pos_longpolling/doc/changelog.rst @@ -1,3 +1,9 @@ +`2.0.1` +------- + +- **IMP:** Sync icons for deactivated pollings are hidden +- **FIX:** Longpolling recovering on very bad internet connection is fixed + `2.0.0` ------- diff --git a/pos_longpolling/doc/index.rst b/pos_longpolling/doc/index.rst index 251d360eb7..ab1dd17bd9 100644 --- a/pos_longpolling/doc/index.rst +++ b/pos_longpolling/doc/index.rst @@ -23,3 +23,11 @@ and configure nginx: :: proxy_pass http://127.0.0.1:8069; } +Database connections limit +========================== + +It's highly recommended to `check odoo and postgresql setttings about connections limits `__. In short, it must satisfy following condition:: + + (1 + workers + max_cron_threads) * db_maxconn < max_connections + +Where ``max_connections`` is postgresql setting and the rest are from odoo. diff --git a/pos_longpolling/external_tests/run.js b/pos_longpolling/external_tests/run.js new file mode 100644 index 0000000000..c53baadf50 --- /dev/null +++ b/pos_longpolling/external_tests/run.js @@ -0,0 +1,61 @@ +/* +Script allows to run headless POS. It run POS scripts (e.g. sending longpolling requests) for TIMEOUT secs and then stops. + +API + + phantomjs run.js POS_URL SESSION_ID TIMEOUT + +e.g. + + phantomjs http://pos.10.local/pos/web 025590b63e43f9efe53d0096c5affbe69ddfc092 60 + + + +Usage: + +* Open browser dev tools, switch to Network tab +* Open Odoo in a normal way +* Find **Cookie** header at any request +* Open POS in a normal way +* run in terminal + +for i in `seq 100`; do /usr/local/bin/phantomjs run.js http://pos.10.local/pos/web 025590b63e43f9efe53d0096c5affbe69ddfc092 300 & ; done + +-- will run 100 instances for 300 seconds + + + +*/ + +var system = require('system'); +var pos_url = system.args[1]; +var session_id = system.args[2]; +var timeout = parseInt(system.args[3]); +var pages_num = 1; + +console.log("Open " + pages_num + " pages for " + timeout + " sec"); + + +var domain = pos_url.match(/^https?\:\/\/([^\/?#]+)(?:[\/?#]|$)/i)[1]; + +phantom.addCookie({ + 'name': 'session_id', + 'value': session_id, + 'domain': domain +}); + +var pages = []; +for (var i=0; i < pages_num; i++){ + var page = require('webpage').create(); + + page.open(pos_url, function(status) { + console.log("Page #" + i + ": " + status); + + }); + pages.push(page); +} + +setTimeout(function(){ + phantom.exit(); +}, timeout*1000); + diff --git a/pos_longpolling/i18n/de.po b/pos_longpolling/i18n/de.po index 412aeb0d49..c5d7512c16 100644 --- a/pos_longpolling/i18n/de.po +++ b/pos_longpolling/i18n/de.po @@ -4,13 +4,14 @@ # # Translators: # Translation Bot , 2017 +# Ermin Trevisan , 2017 msgid "" msgstr "" "Project-Id-Version: Odoo Server 10.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-10-27 14:43+0000\n" -"PO-Revision-Date: 2017-10-27 14:43+0000\n" -"Last-Translator: Translation Bot , 2017\n" +"POT-Creation-Date: 2017-12-31 00:08+0000\n" +"PO-Revision-Date: 2017-12-31 00:08+0000\n" +"Last-Translator: Ermin Trevisan , 2017\n" "Language-Team: German (https://www.transifex.com/it-projects-llc/teams/76080/de/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -20,7 +21,25 @@ msgstr "" #. module: pos_longpolling #. openerp-web -#: code:addons/pos_longpolling/static/src/js/pos_longpolling.js:95 +#: code:addons/pos_longpolling/static/src/js/test_longpoll_pos.js:14 +#, python-format +msgid "" +"

Click to start the point of sale interface. It runs on tablets, " +"laptops, or industrial hardware.

Once the session launched, the system" +" continues to run without an internet connection.

" +msgstr "" +"

Anklicken, um das POS-System zu starten. Es läuft auf Tablets, " +"Laptops, oder industrieller Hardware.

Sobald eine Sitzung gestartet " +"wurde, funktioniert das System auch ohne Internet-Verbindung.

" + +#. module: pos_longpolling +#: model:ir.model.fields,field_description:pos_longpolling.field_pos_config_autostart_longpolling +msgid "Autostart longpolling" +msgstr "" + +#. module: pos_longpolling +#. openerp-web +#: code:addons/pos_longpolling/static/src/js/pos_longpolling.js:128 #, python-format msgid "Error" msgstr "Fehler" @@ -35,6 +54,14 @@ msgstr "Longpolling" msgid "Query timeout" msgstr "Zeitüberschreitung Abfrage" +#. module: pos_longpolling +#. openerp-web +#: code:addons/pos_longpolling/static/src/js/test_longpoll_pos.js:10 +#, python-format +msgid "Ready to launch your point of sale? Click here." +msgstr "" +"Bereit, Ihren Point of Salezu starten? Klicken Sie hier." + #. module: pos_longpolling #: model:ir.model.fields,field_description:pos_longpolling.field_pos_config_response_timeout msgid "Response timeout" @@ -56,6 +83,12 @@ msgid "" "connection is restored, the icon changes its color back to green)" msgstr "" +#. module: pos_longpolling +#: model:ir.model.fields,help:pos_longpolling.field_pos_config_autostart_longpolling +msgid "" +"When switched off longpoling will start only when other module start it" +msgstr "" + #. module: pos_longpolling #: model:ir.model,name:pos_longpolling.model_pos_config msgid "pos.config" diff --git a/pos_longpolling/i18n/es.po b/pos_longpolling/i18n/es.po index c851428204..7c146bce6d 100644 --- a/pos_longpolling/i18n/es.po +++ b/pos_longpolling/i18n/es.po @@ -3,14 +3,14 @@ # * pos_longpolling # # Translators: -# Gustavo Valverde , 2017 +# Translation Bot , 2017 msgid "" msgstr "" "Project-Id-Version: Odoo Server 10.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-09-30 15:22+0000\n" -"PO-Revision-Date: 2017-09-30 15:22+0000\n" -"Last-Translator: Gustavo Valverde , 2017\n" +"POT-Creation-Date: 2017-12-31 00:08+0000\n" +"PO-Revision-Date: 2017-12-31 00:08+0000\n" +"Last-Translator: Translation Bot , 2017\n" "Language-Team: Spanish (https://www.transifex.com/it-projects-llc/teams/76080/es/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -20,7 +20,22 @@ msgstr "" #. module: pos_longpolling #. openerp-web -#: code:addons/pos_longpolling/static/src/js/pos_longpolling.js:93 +#: code:addons/pos_longpolling/static/src/js/test_longpoll_pos.js:14 +#, python-format +msgid "" +"

Click to start the point of sale interface. It runs on tablets, " +"laptops, or industrial hardware.

Once the session launched, the system" +" continues to run without an internet connection.

" +msgstr "" + +#. module: pos_longpolling +#: model:ir.model.fields,field_description:pos_longpolling.field_pos_config_autostart_longpolling +msgid "Autostart longpolling" +msgstr "" + +#. module: pos_longpolling +#. openerp-web +#: code:addons/pos_longpolling/static/src/js/pos_longpolling.js:128 #, python-format msgid "Error" msgstr "" @@ -35,6 +50,13 @@ msgstr "" msgid "Query timeout" msgstr "Timeout de Consulta" +#. module: pos_longpolling +#. openerp-web +#: code:addons/pos_longpolling/static/src/js/test_longpoll_pos.js:10 +#, python-format +msgid "Ready to launch your point of sale? Click here." +msgstr "" + #. module: pos_longpolling #: model:ir.model.fields,field_description:pos_longpolling.field_pos_config_response_timeout msgid "Response timeout" @@ -56,6 +78,12 @@ msgid "" "connection is restored, the icon changes its color back to green)" msgstr "" +#. module: pos_longpolling +#: model:ir.model.fields,help:pos_longpolling.field_pos_config_autostart_longpolling +msgid "" +"When switched off longpoling will start only when other module start it" +msgstr "" + #. module: pos_longpolling #: model:ir.model,name:pos_longpolling.model_pos_config msgid "pos.config" diff --git a/pos_longpolling/i18n/ru.po b/pos_longpolling/i18n/ru.po index 85121672e2..f2968b79fe 100644 --- a/pos_longpolling/i18n/ru.po +++ b/pos_longpolling/i18n/ru.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 10.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-11-24 09:02+0000\n" -"PO-Revision-Date: 2017-11-24 09:02+0000\n" +"POT-Creation-Date: 2017-12-31 00:08+0000\n" +"PO-Revision-Date: 2017-12-31 00:08+0000\n" "Last-Translator: Dinar , 2017\n" "Language-Team: Russian (https://www.transifex.com/it-projects-llc/teams/76080/ru/)\n" "MIME-Version: 1.0\n" @@ -20,7 +20,22 @@ msgstr "" #. module: pos_longpolling #. openerp-web -#: code:addons/pos_longpolling/static/src/js/pos_longpolling.js:95 +#: code:addons/pos_longpolling/static/src/js/test_longpoll_pos.js:14 +#, python-format +msgid "" +"

Click to start the point of sale interface. It runs on tablets, " +"laptops, or industrial hardware.

Once the session launched, the system" +" continues to run without an internet connection.

" +msgstr "" + +#. module: pos_longpolling +#: model:ir.model.fields,field_description:pos_longpolling.field_pos_config_autostart_longpolling +msgid "Autostart longpolling" +msgstr "" + +#. module: pos_longpolling +#. openerp-web +#: code:addons/pos_longpolling/static/src/js/pos_longpolling.js:128 #, python-format msgid "Error" msgstr "Ошибка" @@ -35,6 +50,13 @@ msgstr "Longpolling" msgid "Query timeout" msgstr "Время запроса" +#. module: pos_longpolling +#. openerp-web +#: code:addons/pos_longpolling/static/src/js/test_longpoll_pos.js:10 +#, python-format +msgid "Ready to launch your point of sale? Click here." +msgstr "" + #. module: pos_longpolling #: model:ir.model.fields,field_description:pos_longpolling.field_pos_config_response_timeout msgid "Response timeout" @@ -63,6 +85,12 @@ msgstr "" "сообщение не придет, тогда значок окраситься в красный, если сообщение " "придет, значит соединение восстановлена, значок изменит цвет на зеленый)" +#. module: pos_longpolling +#: model:ir.model.fields,help:pos_longpolling.field_pos_config_autostart_longpolling +msgid "" +"When switched off longpoling will start only when other module start it" +msgstr "" + #. module: pos_longpolling #: model:ir.model,name:pos_longpolling.model_pos_config msgid "pos.config" diff --git a/pos_longpolling/models/pos_longpolling_models.py b/pos_longpolling/models/pos_longpolling_models.py index eb426d79ba..7d5c1aa86b 100644 --- a/pos_longpolling/models/pos_longpolling_models.py +++ b/pos_longpolling/models/pos_longpolling_models.py @@ -8,18 +8,17 @@ class PosConfig(models.Model): _inherit = 'pos.config' # 5/60 = 0.0833 = 5 min - default value - query_timeout = fields.Float(string='Query timeout', default=0.0833, - help="Waiting period for any message from poll " - "(if we have not received a message at this period, " - "poll will send message ('PING') to check the connection)") + longpolling_max_silence_timeout = fields.Float(string='Max Silence timeout', default=0.0833, + help="Waiting period for any message from poll " + "(if we have not received a message at this period, " + "poll will send message ('PING') to check the connection)") # 1/60 = 0.01666 = 1 min - default value - response_timeout = fields.Float(string='Response timeout', default=0.01666, - help="Waiting period for response message (i.e. once message from " - "poll has been sent, it will be waiting for response message ('PONG') " - "at this period and if the message has not been received, the icon turns " - "color to red. Once the connection is restored, the icon changes its color " - "back to green)") + longpolling_pong_timeout = fields.Float(string='Pong timeout', default=0.01666, + help="Waiting period to receive PONG message after sending PING request." + "When this timeout occurs, the icon turns " + "color to red. Once the connection is restored, the icon changes its color " + "back to green)") autostart_longpolling = fields.Boolean('Autostart longpolling', default=True, help='When switched off longpoling will start only when other module start it') diff --git a/pos_longpolling/static/src/css/longpoll.css b/pos_longpolling/static/src/css/longpoll.css deleted file mode 100644 index 2c7851873f..0000000000 --- a/pos_longpolling/static/src/css/longpoll.css +++ /dev/null @@ -1,4 +0,0 @@ -.pos .oe_status .oe_gray, -.pos .oe_icon.oe_gray { - color: rgb(177, 179, 179); -} \ No newline at end of file diff --git a/pos_longpolling/static/src/js/pos_longpolling.js b/pos_longpolling/static/src/js/pos_longpolling.js index 79b7aadde9..43cfe4c579 100644 --- a/pos_longpolling/static/src/js/pos_longpolling.js +++ b/pos_longpolling/static/src/js/pos_longpolling.js @@ -25,14 +25,14 @@ odoo.define('pos_longpolling', function(require){ this.ERROR_DELAY = 10000; this.serv_adr = sync_server || ''; this.longpolling_connection = new exports.LongpollingConnection(this.pos); - this.activated = false; + this.set_activated(false); var callback = this.longpolling_connection.network_is_on; this.add_channel_callback("pos.longpolling", callback, this.longpolling_connection); }, poll: function(address) { var self = this; - this.activated = true; + this.set_activated(true); var now = new Date().getTime(); var options = _.extend({}, this.options, { bus_inactivity: now - this.get_last_presence(), @@ -42,7 +42,7 @@ odoo.define('pos_longpolling', function(require){ var serv_adr = address ? address.serv : this.serv_adr || ''; - session.rpc(serv_adr + '/longpolling/poll', data, {shadow : true}).then(function(result) { + session.rpc(serv_adr + '/longpolling/poll', data, {shadow : true, timeout: 60000}).then(function(result) { self.on_notification(result); if(!self.stop){ self.poll(); @@ -91,7 +91,7 @@ odoo.define('pos_longpolling', function(require){ this.lonpolling_activated = true; this.longpolling_connection.send_ping({serv: this.serv_adr}); this.start_polling(); - this.activated = true; + this.set_activated(true); }, activate_channel: function(channel_name){ var channel = this.get_full_channel_name(channel_name); @@ -134,6 +134,13 @@ odoo.define('pos_longpolling', function(require){ get_full_channel_name: function(channel_name){ return JSON.stringify([session.db,channel_name,String(this.pos.config.id)]); }, + set_activated: function(status) { + if (this.activated === status) { + return; + } + this.activated = status; + this.longpolling_connection.trigger("change:poll_connection", status); + }, }); var PosModelSuper = models.PosModel; @@ -193,7 +200,7 @@ odoo.define('pos_longpolling', function(require){ }, update_timer: function(){ this.stop_timer(); - this.start_timer(this.pos.config.query_timeout, 'query'); + this.start_timer(this.pos.config.longpolling_max_silence_timeout, 'query'); }, stop_timer: function(){ var self = this; @@ -218,7 +225,7 @@ odoo.define('pos_longpolling', function(require){ }, response_timer: function() { this.stop_timer(); - this.start_timer(this.pos.config.response_timeout, "response"); + this.start_timer(this.pos.config.longpolling_pong_timeout, "response"); }, send_ping: function(address) { var self = this; @@ -252,12 +259,12 @@ odoo.define('pos_longpolling', function(require){ this.set_icon_class(selector, 'oe_red'); } } else { - this.set_icon_class(selector, 'oe_gray'); + this.set_icon_class(selector, 'oe_hidden'); } }, set_icon_class: function(selector, new_class) { - var element = self.$(selector); - element.removeClass('oe_red oe_green oe_gray').addClass(new_class); + var element = this.$(selector); + element.removeClass('oe_hidden oe_red oe_green').addClass(new_class); }, }); @@ -266,7 +273,7 @@ odoo.define('pos_longpolling', function(require){ start: function(){ var self = this; var element = this.$('.serv_additional'); - if (this.pos.buses && Object.keys(this.pos.buses).length){ + if (this.pos.buses && _.keys(this.pos.buses).length){ for (var key in this.pos.buses){ if (_.has(this.pos.buses, key)){ bus = this.pos.buses[key]; @@ -293,7 +300,7 @@ odoo.define('pos_longpolling', function(require){ this.widgets.splice(index + 1, 0, { 'name': 'AdditionalSynchNotificationWidget', 'widget': AdditionalSynchNotificationWidget, - 'append': '.pos-rightheader', + 'append': '.oe_status.js_synch', }); } this._super(); diff --git a/pos_longpolling/static/src/xml/pos_longpolling_connection.xml b/pos_longpolling/static/src/xml/pos_longpolling_connection.xml index 685c5d921f..d95bab53e3 100644 --- a/pos_longpolling/static/src/xml/pos_longpolling_connection.xml +++ b/pos_longpolling/static/src/xml/pos_longpolling_connection.xml @@ -13,7 +13,7 @@ -
+
diff --git a/pos_mobile/README.rst b/pos_mobile/README.rst index 3188b14446..63c3c94082 100644 --- a/pos_mobile/README.rst +++ b/pos_mobile/README.rst @@ -13,7 +13,7 @@ Contributors Sponsors -------- -* `IT-Projects LLC `__ +* `Sinomate `__ Maintainers ----------- diff --git a/pos_mobile/__manifest__.py b/pos_mobile/__manifest__.py index e04719e0ef..53397cfdb6 100644 --- a/pos_mobile/__manifest__.py +++ b/pos_mobile/__manifest__.py @@ -5,7 +5,7 @@ "category": "Point of Sale", "live_test_url": "http://apps.it-projects.info/shop/product/pos-mobile-ui?version=11.0", "images": ["images/pos_mobile.png"], - "version": "1.0.0", + "version": "10.0.1.1.5", "application": False, "author": "IT-Projects LLC, Dinar Gabbasov", diff --git a/pos_mobile/doc/changelog.rst b/pos_mobile/doc/changelog.rst index 9ee2b48b8e..da66d1173d 100644 --- a/pos_mobile/doc/changelog.rst +++ b/pos_mobile/doc/changelog.rst @@ -1,3 +1,39 @@ +`1.1.5` +------- + +- **FIX:** Issue when user name has a space +- **IMP:** No need to move to category when it doesn't have subcategory +- **IMP:** Show categories from right to left + +`1.1.4` +------- + +- **NEW:** Touch scrolling for Android + +`1.1.3` +------- + +- **FIX:** Serachbox issue for iOS version 11.0 and higher + +`1.1.2` +------- + +- **FIX:** Correct scrolling on the Payment screen on IOS + +`1.1.1` +------- + +- **FIX:** Compatibility with IOS 11.2 +- **FIX:** Quantity indicator of products was not updated when quantity is changed via numpad +- **FIX:** Payment screen interface and ClientList screen interface + +`1.1.0` +------- + +- **NEW:** Payment screen interface +- **NEW:** Quantity indicator of products added to the current order +- **NEW:** Order list scrolling + `1.0.0` ------- diff --git a/pos_mobile/i18n/de.po b/pos_mobile/i18n/de.po new file mode 100644 index 0000000000..8d56503778 --- /dev/null +++ b/pos_mobile/i18n/de.po @@ -0,0 +1,33 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * pos_mobile +# +# Translators: +# Ermin Trevisan , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-12-27 09:32+0000\n" +"PO-Revision-Date: 2017-12-27 09:32+0000\n" +"Last-Translator: Ermin Trevisan , 2017\n" +"Language-Team: German (https://www.transifex.com/it-projects-llc/teams/76080/de/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: de\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: pos_mobile +#. openerp-web +#: code:addons/pos_mobile/static/src/xml/pos.xml:82 +#, python-format +msgid "Customer" +msgstr "Kunde" + +#. module: pos_mobile +#. openerp-web +#: code:addons/pos_mobile/static/src/xml/pos.xml:62 +#, python-format +msgid "Please select a payment method." +msgstr "" diff --git a/pos_mobile/i18n/es.po b/pos_mobile/i18n/es.po new file mode 100644 index 0000000000..59574a2dd3 --- /dev/null +++ b/pos_mobile/i18n/es.po @@ -0,0 +1,33 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * pos_mobile +# +# Translators: +# Gustavo Valverde , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-12-27 09:32+0000\n" +"PO-Revision-Date: 2017-12-27 09:32+0000\n" +"Last-Translator: Gustavo Valverde , 2017\n" +"Language-Team: Spanish (https://www.transifex.com/it-projects-llc/teams/76080/es/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: es\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: pos_mobile +#. openerp-web +#: code:addons/pos_mobile/static/src/xml/pos.xml:82 +#, python-format +msgid "Customer" +msgstr "Cliente" + +#. module: pos_mobile +#. openerp-web +#: code:addons/pos_mobile/static/src/xml/pos.xml:62 +#, python-format +msgid "Please select a payment method." +msgstr "" diff --git a/pos_mobile/static/lib/jquery.nicescroll.js b/pos_mobile/static/lib/jquery.nicescroll.js new file mode 100644 index 0000000000..bd6ebb3f2e --- /dev/null +++ b/pos_mobile/static/lib/jquery.nicescroll.js @@ -0,0 +1,3729 @@ +/* jquery.nicescroll +-- version 3.7.6 +-- copyright 2017-07-19 InuYaksa*2017 +-- licensed under the MIT +-- +-- https://nicescroll.areaaperta.com/ +-- https://github.com/inuyaksa/jquery.nicescroll +-- +*/ + +/* jshint expr: true */ + +(function (factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as anonymous module. + define(['jquery'], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS. + module.exports = factory(require('jquery')); + } else { + // Browser globals. + factory(jQuery); + } +}(function (jQuery) { + + "use strict"; + + // globals + var domfocus = false, + mousefocus = false, + tabindexcounter = 0, + ascrailcounter = 2000, + globalmaxzindex = 0; + + var $ = jQuery, // sandbox + _doc = document, + _win = window, + $window = $(_win); + + var delegatevents = []; + + // http://stackoverflow.com/questions/2161159/get-script-path + function getScriptPath() { + var scripts = _doc.currentScript || (function () { var s = _doc.getElementsByTagName('script'); return (s.length) ? s[s.length - 1] : false; })(); + var path = scripts ? scripts.src.split('?')[0] : ''; + return (path.split('/').length > 0) ? path.split('/').slice(0, -1).join('/') + '/' : ''; + } + + // based on code by Paul Irish https://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ + var setAnimationFrame = _win.requestAnimationFrame || _win.webkitRequestAnimationFrame || _win.mozRequestAnimationFrame || false; + var clearAnimationFrame = _win.cancelAnimationFrame || _win.webkitCancelAnimationFrame || _win.mozCancelAnimationFrame || false; + + if (!setAnimationFrame) { + var anilasttime = 0; + setAnimationFrame = function (callback, element) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - anilasttime)); + var id = _win.setTimeout(function () { callback(currTime + timeToCall); }, + timeToCall); + anilasttime = currTime + timeToCall; + return id; + }; + clearAnimationFrame = function (id) { + _win.clearTimeout(id); + }; + } else { + if (!_win.cancelAnimationFrame) clearAnimationFrame = function (id) { }; + } + + var ClsMutationObserver = _win.MutationObserver || _win.WebKitMutationObserver || false; + + var now = Date.now || function () { return new Date().getTime(); }; + + var _globaloptions = { + zindex: "auto", + cursoropacitymin: 0, + cursoropacitymax: 1, + cursorcolor: "#424242", + cursorwidth: "6px", + cursorborder: "0px solid #424242", + cursorborderradius: "5px", + scrollspeed: 40, + mousescrollstep: 9 * 3, + touchbehavior: false, // deprecated + emulatetouch: false, // replacing touchbehavior + hwacceleration: true, + usetransition: true, + boxzoom: false, + dblclickzoom: true, + gesturezoom: true, + grabcursorenabled: true, + autohidemode: true, + background: "", + iframeautoresize: true, + cursorminheight: 32, + preservenativescrolling: true, + railoffset: false, + railhoffset: false, + bouncescroll: true, + spacebarenabled: true, + railpadding: { + top: 0, + right: 0, + left: 0, + bottom: 0 + }, + disableoutline: true, + horizrailenabled: true, + railalign: "right", + railvalign: "bottom", + enabletranslate3d: true, + enablemousewheel: true, + enablekeyboard: true, + smoothscroll: true, + sensitiverail: true, + enablemouselockapi: true, + // cursormaxheight:false, + cursorfixedheight: false, + directionlockdeadzone: 6, + hidecursordelay: 400, + nativeparentscrolling: true, + enablescrollonselection: true, + overflowx: true, + overflowy: true, + cursordragspeed: 0.3, + rtlmode: "auto", + cursordragontouch: false, + oneaxismousemode: "auto", + scriptpath: getScriptPath(), + preventmultitouchscrolling: true, + disablemutationobserver: false, + enableobserver: true, + scrollbarid: false, + scrollCLass: false + }; + + var browserdetected = false; + + var getBrowserDetection = function () { + + if (browserdetected) return browserdetected; + + var _el = _doc.createElement('DIV'), + _style = _el.style, + _agent = navigator.userAgent, + _platform = navigator.platform, + d = {}; + + d.haspointerlock = "pointerLockElement" in _doc || "webkitPointerLockElement" in _doc || "mozPointerLockElement" in _doc; + + d.isopera = ("opera" in _win); // 12- + d.isopera12 = (d.isopera && ("getUserMedia" in navigator)); + d.isoperamini = (Object.prototype.toString.call(_win.operamini) === "[object OperaMini]"); + + d.isie = (("all" in _doc) && ("attachEvent" in _el) && !d.isopera); //IE10- + d.isieold = (d.isie && !("msInterpolationMode" in _style)); // IE6 and older + d.isie7 = d.isie && !d.isieold && (!("documentMode" in _doc) || (_doc.documentMode === 7)); + d.isie8 = d.isie && ("documentMode" in _doc) && (_doc.documentMode === 8); + d.isie9 = d.isie && ("performance" in _win) && (_doc.documentMode === 9); + d.isie10 = d.isie && ("performance" in _win) && (_doc.documentMode === 10); + d.isie11 = ("msRequestFullscreen" in _el) && (_doc.documentMode >= 11); // IE11+ + + d.ismsedge = ("msCredentials" in _win); // MS Edge 14+ + + d.ismozilla = ("MozAppearance" in _style); + + d.iswebkit = !d.ismsedge && ("WebkitAppearance" in _style); + + d.ischrome = d.iswebkit && ("chrome" in _win); + d.ischrome38 = (d.ischrome && ("touchAction" in _style)); // behavior changed in touch emulation + d.ischrome22 = (!d.ischrome38) && (d.ischrome && d.haspointerlock); + d.ischrome26 = (!d.ischrome38) && (d.ischrome && ("transition" in _style)); // issue with transform detection (maintain prefix) + + d.cantouch = ("ontouchstart" in _doc.documentElement) || ("ontouchstart" in _win); // with detection for Chrome Touch Emulation + d.hasw3ctouch = (_win.PointerEvent || false) && ((navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)); //IE11 pointer events, following W3C Pointer Events spec + d.hasmstouch = (!d.hasw3ctouch) && (_win.MSPointerEvent || false); // IE10 pointer events + + d.ismac = /^mac$/i.test(_platform); + + d.isios = d.cantouch && /iphone|ipad|ipod/i.test(_platform); + d.isios4 = d.isios && !("seal" in Object); + d.isios7 = d.isios && ("webkitHidden" in _doc); //iOS 7+ + d.isios8 = d.isios && ("hidden" in _doc); //iOS 8+ + d.isios10 = d.isios && _win.Proxy; //iOS 10+ + + d.isandroid = (/android/i.test(_agent)); + + d.haseventlistener = ("addEventListener" in _el); + + d.trstyle = false; + d.hastransform = false; + d.hastranslate3d = false; + d.transitionstyle = false; + d.hastransition = false; + d.transitionend = false; + + d.trstyle = "transform"; + d.hastransform = ("transform" in _style) || (function () { + var check = ['msTransform', 'webkitTransform', 'MozTransform', 'OTransform']; + for (var a = 0, c = check.length; a < c; a++) { + if (_style[check[a]] !== undefined) { + d.trstyle = check[a]; + break; + } + } + d.hastransform = (!!d.trstyle); + })(); + + if (d.hastransform) { + _style[d.trstyle] = "translate3d(1px,2px,3px)"; + d.hastranslate3d = /translate3d/.test(_style[d.trstyle]); + } + + d.transitionstyle = "transition"; + d.prefixstyle = ''; + d.transitionend = "transitionend"; + + d.hastransition = ("transition" in _style) || (function () { + + d.transitionend = false; + var check = ['webkitTransition', 'msTransition', 'MozTransition', 'OTransition', 'OTransition', 'KhtmlTransition']; + var prefix = ['-webkit-', '-ms-', '-moz-', '-o-', '-o', '-khtml-']; + var evs = ['webkitTransitionEnd', 'msTransitionEnd', 'transitionend', 'otransitionend', 'oTransitionEnd', 'KhtmlTransitionEnd']; + for (var a = 0, c = check.length; a < c; a++) { + if (check[a] in _style) { + d.transitionstyle = check[a]; + d.prefixstyle = prefix[a]; + d.transitionend = evs[a]; + break; + } + } + if (d.ischrome26) d.prefixstyle = prefix[1]; // always use prefix + + d.hastransition = (d.transitionstyle); + + })(); + + function detectCursorGrab() { + var lst = ['grab', '-webkit-grab', '-moz-grab']; + if ((d.ischrome && !d.ischrome38) || d.isie) lst = []; // force setting for IE returns false positive and chrome cursor bug + for (var a = 0, l = lst.length; a < l; a++) { + var p = lst[a]; + _style.cursor = p; + if (_style.cursor == p) return p; + } + return 'url(https://cdnjs.cloudflare.com/ajax/libs/slider-pro/1.3.0/css/images/openhand.cur),n-resize'; // thanks to https://cdnjs.com/ for the openhand cursor! + } + d.cursorgrabvalue = detectCursorGrab(); + + d.hasmousecapture = ("setCapture" in _el); + + d.hasMutationObserver = (ClsMutationObserver !== false); + + _el = null; //memory released + + browserdetected = d; + + return d; + }; + + var NiceScrollClass = function (myopt, me) { + + var self = this; + + this.version = '3.7.6'; + this.name = 'nicescroll'; + + this.me = me; + + var $body = $("body"); + + var opt = this.opt = { + doc: $body, + win: false + }; + + $.extend(opt, _globaloptions); // clone opts + + // Options for internal use + opt.snapbackspeed = 80; + + if (myopt || false) { + for (var a in opt) { + if (myopt[a] !== undefined) opt[a] = myopt[a]; + } + } + + if (opt.disablemutationobserver) ClsMutationObserver = false; + + this.doc = opt.doc; + this.iddoc = (this.doc && this.doc[0]) ? this.doc[0].id || '' : ''; + this.ispage = /^BODY|HTML/.test((opt.win) ? opt.win[0].nodeName : this.doc[0].nodeName); + this.haswrapper = (opt.win !== false); + this.win = opt.win || (this.ispage ? $window : this.doc); + this.docscroll = (this.ispage && !this.haswrapper) ? $window : this.win; + this.body = $body; + this.viewport = false; + + this.isfixed = false; + + this.iframe = false; + this.isiframe = ((this.doc[0].nodeName == 'IFRAME') && (this.win[0].nodeName == 'IFRAME')); + + this.istextarea = (this.win[0].nodeName == 'TEXTAREA'); + + this.forcescreen = false; //force to use screen position on events + + this.canshowonmouseevent = (opt.autohidemode != "scroll"); + + // Events jump table + this.onmousedown = false; + this.onmouseup = false; + this.onmousemove = false; + this.onmousewheel = false; + this.onkeypress = false; + this.ongesturezoom = false; + this.onclick = false; + + // Nicescroll custom events + this.onscrollstart = false; + this.onscrollend = false; + this.onscrollcancel = false; + + this.onzoomin = false; + this.onzoomout = false; + + // Let's start! + this.view = false; + this.page = false; + + this.scroll = { + x: 0, + y: 0 + }; + this.scrollratio = { + x: 0, + y: 0 + }; + this.cursorheight = 20; + this.scrollvaluemax = 0; + + // http://dev.w3.org/csswg/css-writing-modes-3/#logical-to-physical + // http://dev.w3.org/csswg/css-writing-modes-3/#svg-writing-mode + if (opt.rtlmode == "auto") { + var target = this.win[0] == _win ? this.body : this.win; + var writingMode = target.css("writing-mode") || target.css("-webkit-writing-mode") || target.css("-ms-writing-mode") || target.css("-moz-writing-mode"); + + if (writingMode == "horizontal-tb" || writingMode == "lr-tb" || writingMode === "") { + this.isrtlmode = (target.css("direction") == "rtl"); + this.isvertical = false; + } else { + this.isrtlmode = (writingMode == "vertical-rl" || writingMode == "tb" || writingMode == "tb-rl" || writingMode == "rl-tb"); + this.isvertical = (writingMode == "vertical-rl" || writingMode == "tb" || writingMode == "tb-rl"); + } + } else { + this.isrtlmode = (opt.rtlmode === true); + this.isvertical = false; + } + // this.checkrtlmode = false; + + this.scrollrunning = false; + + this.scrollmom = false; + + this.observer = false; // observer div changes + this.observerremover = false; // observer on parent for remove detection + this.observerbody = false; // observer on body for position change + + if (opt.scrollbarid !== false) { + this.id = opt.scrollbarid; + } else { + do { + this.id = "ascrail" + (ascrailcounter++); + } while (_doc.getElementById(this.id)); + } + + this.rail = false; + this.cursor = false; + this.cursorfreezed = false; + this.selectiondrag = false; + + this.zoom = false; + this.zoomactive = false; + + this.hasfocus = false; + this.hasmousefocus = false; + + //this.visibility = true; + this.railslocked = false; // locked by resize + this.locked = false; // prevent lost of locked status sets by user + this.hidden = false; // rails always hidden + this.cursoractive = true; // user can interact with cursors + + this.wheelprevented = false; //prevent mousewheel event + + this.overflowx = opt.overflowx; + this.overflowy = opt.overflowy; + + this.nativescrollingarea = false; + this.checkarea = 0; + + this.events = []; // event list for unbind + + this.saved = {}; // style saved + + this.delaylist = {}; + this.synclist = {}; + + this.lastdeltax = 0; + this.lastdeltay = 0; + + this.detected = getBrowserDetection(); + + var cap = $.extend({}, this.detected); + + this.canhwscroll = (cap.hastransform && opt.hwacceleration); + this.ishwscroll = (this.canhwscroll && self.haswrapper); + + if (!this.isrtlmode) { + this.hasreversehr = false; + } else if (this.isvertical) { // RTL mode with reverse horizontal axis + this.hasreversehr = !(cap.iswebkit || cap.isie || cap.isie11); + } else { + this.hasreversehr = !(cap.iswebkit || (cap.isie && !cap.isie10 && !cap.isie11)); + } + + this.istouchcapable = false; // desktop devices with touch screen support + + //## Check WebKit-based desktop with touch support + //## + Firefox 18 nightly build (desktop) false positive (or desktop with touch support) + + if (!cap.cantouch && (cap.hasw3ctouch || cap.hasmstouch)) { // desktop device with multiple input + this.istouchcapable = true; + } else if (cap.cantouch && !cap.isios && !cap.isandroid && (cap.iswebkit || cap.ismozilla)) { + this.istouchcapable = true; + } + + //## disable MouseLock API on user request + if (!opt.enablemouselockapi) { + cap.hasmousecapture = false; + cap.haspointerlock = false; + } + + this.debounced = function (name, fn, tm) { + if (!self) return; + var dd = self.delaylist[name] || false; + if (!dd) { + self.delaylist[name] = { + h: setAnimationFrame(function () { + self.delaylist[name].fn.call(self); + self.delaylist[name] = false; + }, tm) + }; + fn.call(self); + } + self.delaylist[name].fn = fn; + }; + + + this.synched = function (name, fn) { + if (self.synclist[name]) self.synclist[name] = fn; + else { + self.synclist[name] = fn; + setAnimationFrame(function () { + if (!self) return; + self.synclist[name] && self.synclist[name].call(self); + self.synclist[name] = null; + }); + } + }; + + this.unsynched = function (name) { + if (self.synclist[name]) self.synclist[name] = false; + }; + + this.css = function (el, pars) { // save & set + for (var n in pars) { + self.saved.css.push([el, n, el.css(n)]); + el.css(n, pars[n]); + } + }; + + this.scrollTop = function (val) { + return (val === undefined) ? self.getScrollTop() : self.setScrollTop(val); + }; + + this.scrollLeft = function (val) { + return (val === undefined) ? self.getScrollLeft() : self.setScrollLeft(val); + }; + + // derived by by Dan Pupius www.pupius.net + var BezierClass = function (st, ed, spd, p1, p2, p3, p4) { + + this.st = st; + this.ed = ed; + this.spd = spd; + + this.p1 = p1 || 0; + this.p2 = p2 || 1; + this.p3 = p3 || 0; + this.p4 = p4 || 1; + + this.ts = now(); + this.df = ed - st; + }; + BezierClass.prototype = { + B2: function (t) { + return 3 * (1 - t) * (1 - t) * t; + }, + B3: function (t) { + return 3 * (1 - t) * t * t; + }, + B4: function (t) { + return t * t * t; + }, + getPos: function () { + return (now() - this.ts) / this.spd; + }, + getNow: function () { + var pc = (now() - this.ts) / this.spd; + var bz = this.B2(pc) + this.B3(pc) + this.B4(pc); + return (pc >= 1) ? this.ed : this.st + (this.df * bz) | 0; + }, + update: function (ed, spd) { + this.st = this.getNow(); + this.ed = ed; + this.spd = spd; + this.ts = now(); + this.df = this.ed - this.st; + return this; + } + }; + + //derived from http://stackoverflow.com/questions/11236090/ + function getMatrixValues() { + var tr = self.doc.css(cap.trstyle); + if (tr && (tr.substr(0, 6) == "matrix")) { + return tr.replace(/^.*\((.*)\)$/g, "$1").replace(/px/g, '').split(/, +/); + } + return false; + } + + if (this.ishwscroll) { // hw accelerated scroll + + this.doc.translate = { + x: 0, + y: 0, + tx: "0px", + ty: "0px" + }; + + //this one can help to enable hw accel on ios6 http://indiegamr.com/ios6-html-hardware-acceleration-changes-and-how-to-fix-them/ + if (cap.hastranslate3d && cap.isios) this.doc.css("-webkit-backface-visibility", "hidden"); // prevent flickering http://stackoverflow.com/questions/3461441/ + + this.getScrollTop = function (last) { + if (!last) { + var mtx = getMatrixValues(); + if (mtx) return (mtx.length == 16) ? -mtx[13] : -mtx[5]; //matrix3d 16 on IE10 + if (self.timerscroll && self.timerscroll.bz) return self.timerscroll.bz.getNow(); + } + return self.doc.translate.y; + }; + + this.getScrollLeft = function (last) { + if (!last) { + var mtx = getMatrixValues(); + if (mtx) return (mtx.length == 16) ? -mtx[12] : -mtx[4]; //matrix3d 16 on IE10 + if (self.timerscroll && self.timerscroll.bh) return self.timerscroll.bh.getNow(); + } + return self.doc.translate.x; + }; + + this.notifyScrollEvent = function (el) { + var e = _doc.createEvent("UIEvents"); + e.initUIEvent("scroll", false, false, _win, 1); + e.niceevent = true; + el.dispatchEvent(e); + }; + + var cxscrollleft = (this.isrtlmode) ? 1 : -1; + + if (cap.hastranslate3d && opt.enabletranslate3d) { + this.setScrollTop = function (val, silent) { + self.doc.translate.y = val; + self.doc.translate.ty = (val * -1) + "px"; + self.doc.css(cap.trstyle, "translate3d(" + self.doc.translate.tx + "," + self.doc.translate.ty + ",0)"); + if (!silent) self.notifyScrollEvent(self.win[0]); + }; + this.setScrollLeft = function (val, silent) { + self.doc.translate.x = val; + self.doc.translate.tx = (val * cxscrollleft) + "px"; + self.doc.css(cap.trstyle, "translate3d(" + self.doc.translate.tx + "," + self.doc.translate.ty + ",0)"); + if (!silent) self.notifyScrollEvent(self.win[0]); + }; + } else { + this.setScrollTop = function (val, silent) { + self.doc.translate.y = val; + self.doc.translate.ty = (val * -1) + "px"; + self.doc.css(cap.trstyle, "translate(" + self.doc.translate.tx + "," + self.doc.translate.ty + ")"); + if (!silent) self.notifyScrollEvent(self.win[0]); + }; + this.setScrollLeft = function (val, silent) { + self.doc.translate.x = val; + self.doc.translate.tx = (val * cxscrollleft) + "px"; + self.doc.css(cap.trstyle, "translate(" + self.doc.translate.tx + "," + self.doc.translate.ty + ")"); + if (!silent) self.notifyScrollEvent(self.win[0]); + }; + } + } else { // native scroll + + this.getScrollTop = function () { + return self.docscroll.scrollTop(); + }; + this.setScrollTop = function (val) { + self.docscroll.scrollTop(val); + }; + + this.getScrollLeft = function () { + var val; + if (!self.hasreversehr) { + val = self.docscroll.scrollLeft(); + } else if (self.detected.ismozilla) { + val = self.page.maxw - Math.abs(self.docscroll.scrollLeft()); + } else { + val = self.page.maxw - self.docscroll.scrollLeft(); + } + return val; + }; + this.setScrollLeft = function (val) { + return setTimeout(function () { + if (!self) return; + if (self.hasreversehr) { + if (self.detected.ismozilla) { + val = -(self.page.maxw - val); + } else { + val = self.page.maxw - val; + } + } + return self.docscroll.scrollLeft(val); + }, 1); + }; + } + + this.getTarget = function (e) { + if (!e) return false; + if (e.target) return e.target; + if (e.srcElement) return e.srcElement; + return false; + }; + + this.hasParent = function (e, id) { + if (!e) return false; + var el = e.target || e.srcElement || e || false; + while (el && el.id != id) { + el = el.parentNode || false; + } + return (el !== false); + }; + + function getZIndex() { + var dom = self.win; + if ("zIndex" in dom) return dom.zIndex(); // use jQuery UI method when available + while (dom.length > 0) { + if (dom[0].nodeType == 9) return false; + var zi = dom.css('zIndex'); + if (!isNaN(zi) && zi !== 0) return parseInt(zi); + dom = dom.parent(); + } + return false; + } + + //inspired by http://forum.jquery.com/topic/width-includes-border-width-when-set-to-thin-medium-thick-in-ie + var _convertBorderWidth = { + "thin": 1, + "medium": 3, + "thick": 5 + }; + + function getWidthToPixel(dom, prop, chkheight) { + var wd = dom.css(prop); + var px = parseFloat(wd); + if (isNaN(px)) { + px = _convertBorderWidth[wd] || 0; + var brd = (px == 3) ? ((chkheight) ? (self.win.outerHeight() - self.win.innerHeight()) : (self.win.outerWidth() - self.win.innerWidth())) : 1; //DON'T TRUST CSS + if (self.isie8 && px) px += 1; + return (brd) ? px : 0; + } + return px; + } + + this.getDocumentScrollOffset = function () { + return { + top: _win.pageYOffset || _doc.documentElement.scrollTop, + left: _win.pageXOffset || _doc.documentElement.scrollLeft + }; + }; + + this.getOffset = function () { + if (self.isfixed) { + var ofs = self.win.offset(); // fix Chrome auto issue (when right/bottom props only) + var scrl = self.getDocumentScrollOffset(); + ofs.top -= scrl.top; + ofs.left -= scrl.left; + return ofs; + } + var ww = self.win.offset(); + if (!self.viewport) return ww; + var vp = self.viewport.offset(); + return { + top: ww.top - vp.top, + left: ww.left - vp.left + }; + }; + + this.updateScrollBar = function (len) { + var pos, off; + if (self.ishwscroll) { + self.rail.css({ + height: self.win.innerHeight() - (opt.railpadding.top + opt.railpadding.bottom) + }); + if (self.railh) self.railh.css({ + width: self.win.innerWidth() - (opt.railpadding.left + opt.railpadding.right) + }); + } else { + var wpos = self.getOffset(); + pos = { + top: wpos.top, + left: wpos.left - (opt.railpadding.left + opt.railpadding.right) + }; + pos.top += getWidthToPixel(self.win, 'border-top-width', true); + pos.left += (self.rail.align) ? self.win.outerWidth() - getWidthToPixel(self.win, 'border-right-width') - self.rail.width : getWidthToPixel(self.win, 'border-left-width'); + + off = opt.railoffset; + if (off) { + if (off.top) pos.top += off.top; + if (off.left) pos.left += off.left; + } + + if (!self.railslocked) self.rail.css({ + top: pos.top, + left: pos.left, + height: ((len) ? len.h : self.win.innerHeight()) - (opt.railpadding.top + opt.railpadding.bottom) + }); + + if (self.zoom) { + self.zoom.css({ + top: pos.top + 1, + left: (self.rail.align == 1) ? pos.left - 20 : pos.left + self.rail.width + 4 + }); + } + + if (self.railh && !self.railslocked) { + pos = { + top: wpos.top, + left: wpos.left + }; + off = opt.railhoffset; + if (off) { + if (off.top) pos.top += off.top; + if (off.left) pos.left += off.left; + } + var y = (self.railh.align) ? pos.top + getWidthToPixel(self.win, 'border-top-width', true) + self.win.innerHeight() - self.railh.height : pos.top + getWidthToPixel(self.win, 'border-top-width', true); + var x = pos.left + getWidthToPixel(self.win, 'border-left-width'); + self.railh.css({ + top: y - (opt.railpadding.top + opt.railpadding.bottom), + left: x, + width: self.railh.width + }); + } + + } + }; + + this.doRailClick = function (e, dbl, hr) { + var fn, pg, cur, pos; + + if (self.railslocked) return; + + self.cancelEvent(e); + + if (!("pageY" in e)) { + e.pageX = e.clientX + _doc.documentElement.scrollLeft; + e.pageY = e.clientY + _doc.documentElement.scrollTop; + } + + if (dbl) { + fn = (hr) ? self.doScrollLeft : self.doScrollTop; + cur = (hr) ? ((e.pageX - self.railh.offset().left - (self.cursorwidth / 2)) * self.scrollratio.x) : ((e.pageY - self.rail.offset().top - (self.cursorheight / 2)) * self.scrollratio.y); + self.unsynched("relativexy"); + fn(cur|0); + } else { + fn = (hr) ? self.doScrollLeftBy : self.doScrollBy; + cur = (hr) ? self.scroll.x : self.scroll.y; + pos = (hr) ? e.pageX - self.railh.offset().left : e.pageY - self.rail.offset().top; + pg = (hr) ? self.view.w : self.view.h; + fn((cur >= pos) ? pg : -pg); + } + + }; + + self.newscrolly = self.newscrollx = 0; + + self.hasanimationframe = ("requestAnimationFrame" in _win); + self.hascancelanimationframe = ("cancelAnimationFrame" in _win); + + self.hasborderbox = false; + + this.init = function () { + + self.saved.css = []; + + if (cap.isoperamini) return true; // SORRY, DO NOT WORK! + if (cap.isandroid && !("hidden" in _doc)) return true; // Android 3- SORRY, DO NOT WORK! + + opt.emulatetouch = opt.emulatetouch || opt.touchbehavior; // mantain compatibility with "touchbehavior" + + self.hasborderbox = _win.getComputedStyle && (_win.getComputedStyle(_doc.body)['box-sizing'] === "border-box"); + + var _scrollyhidden = { 'overflow-y': 'hidden' }; + if (cap.isie11 || cap.isie10) _scrollyhidden['-ms-overflow-style'] = 'none'; // IE 10 & 11 is always a world apart! + + if (self.ishwscroll) { + this.doc.css(cap.transitionstyle, cap.prefixstyle + 'transform 0ms ease-out'); + if (cap.transitionend) self.bind(self.doc, cap.transitionend, self.onScrollTransitionEnd, false); //I have got to do something usefull!! + } + + self.zindex = "auto"; + if (!self.ispage && opt.zindex == "auto") { + self.zindex = getZIndex() || "auto"; + } else { + self.zindex = opt.zindex; + } + + if (!self.ispage && self.zindex != "auto" && self.zindex > globalmaxzindex) { + globalmaxzindex = self.zindex; + } + + if (self.isie && self.zindex === 0 && opt.zindex == "auto") { // fix IE auto == 0 + self.zindex = "auto"; + } + + if (!self.ispage || !cap.isieold) { + + var cont = self.docscroll; + if (self.ispage) cont = (self.haswrapper) ? self.win : self.doc; + + self.css(cont, _scrollyhidden); + + if (self.ispage && (cap.isie11 || cap.isie)) { // IE 7-11 + self.css($("html"), _scrollyhidden); + } + + if (cap.isios && !self.ispage && !self.haswrapper) self.css($body, { + "-webkit-overflow-scrolling": "touch" + }); //force hw acceleration + + var cursor = $(_doc.createElement('div')); + cursor.css({ + position: "relative", + top: 0, + "float": "right", + width: opt.cursorwidth, + height: 0, + 'background-color': opt.cursorcolor, + border: opt.cursorborder, + 'background-clip': 'padding-box', + '-webkit-border-radius': opt.cursorborderradius, + '-moz-border-radius': opt.cursorborderradius, + 'border-radius': opt.cursorborderradius + }); + + cursor.addClass('nicescroll-cursors'); + + self.cursor = cursor; + + var rail = $(_doc.createElement('div')); + rail.attr('id', self.id); + rail.addClass('nicescroll-rails nicescroll-rails-vr'); + + if (opt.scrollCLass) { + rail.addClass(opt.scrollCLass); + } + + var v, a, kp = ["left", "right", "top", "bottom"]; //** + for (var n in kp) { + a = kp[n]; + v = opt.railpadding[a] || 0; + v && rail.css("padding-" + a, v + "px"); + } + + rail.append(cursor); + + rail.width = Math.max(parseFloat(opt.cursorwidth), cursor.outerWidth()); + rail.css({ + width: rail.width + "px", + zIndex: self.zindex, + background: opt.background, + cursor: "default" + }); + + rail.visibility = true; + rail.scrollable = true; + + rail.align = (opt.railalign == "left") ? 0 : 1; + + self.rail = rail; + + self.rail.drag = false; + + var zoom = false; + if (opt.boxzoom && !self.ispage && !cap.isieold) { + zoom = _doc.createElement('div'); + + self.bind(zoom, "click", self.doZoom); + self.bind(zoom, "mouseenter", function () { + self.zoom.css('opacity', opt.cursoropacitymax); + }); + self.bind(zoom, "mouseleave", function () { + self.zoom.css('opacity', opt.cursoropacitymin); + }); + + self.zoom = $(zoom); + self.zoom.css({ + cursor: "pointer", + zIndex: self.zindex, + backgroundImage: 'url(' + opt.scriptpath + 'zoomico.png)', + height: 18, + width: 18, + backgroundPosition: '0 0' + }); + if (opt.dblclickzoom) self.bind(self.win, "dblclick", self.doZoom); + if (cap.cantouch && opt.gesturezoom) { + self.ongesturezoom = function (e) { + if (e.scale > 1.5) self.doZoomIn(e); + if (e.scale < 0.8) self.doZoomOut(e); + return self.cancelEvent(e); + }; + self.bind(self.win, "gestureend", self.ongesturezoom); + } + } + + // init HORIZ + + self.railh = false; + var railh; + + if (opt.horizrailenabled) { + + self.css(cont, { + overflowX: 'hidden' + }); + + cursor = $(_doc.createElement('div')); + cursor.css({ + position: "absolute", + top: 0, + height: opt.cursorwidth, + width: 0, + backgroundColor: opt.cursorcolor, + border: opt.cursorborder, + backgroundClip: 'padding-box', + '-webkit-border-radius': opt.cursorborderradius, + '-moz-border-radius': opt.cursorborderradius, + 'border-radius': opt.cursorborderradius + }); + + if (cap.isieold) cursor.css('overflow', 'hidden'); //IE6 horiz scrollbar issue + + cursor.addClass('nicescroll-cursors'); + + self.cursorh = cursor; + + railh = $(_doc.createElement('div')); + railh.attr('id', self.id + '-hr'); + railh.addClass('nicescroll-rails nicescroll-rails-hr'); + if (opt.scrollCLass) { + rail.addClass(opt.scrollCLass); + } + + railh.height = Math.max(parseFloat(opt.cursorwidth), cursor.outerHeight()); + railh.css({ + height: railh.height + "px", + 'zIndex': self.zindex, + "background": opt.background + }); + + railh.append(cursor); + + railh.visibility = true; + railh.scrollable = true; + + railh.align = (opt.railvalign == "top") ? 0 : 1; + + self.railh = railh; + + self.railh.drag = false; + + } + + if (self.ispage) { + + rail.css({ + position: "fixed", + top: 0, + height: "100%" + }); + + rail.css((rail.align) ? { right: 0 } : { left: 0 }); + + self.body.append(rail); + if (self.railh) { + railh.css({ + position: "fixed", + left: 0, + width: "100%" + }); + + railh.css((railh.align) ? { bottom: 0 } : { top: 0 }); + + self.body.append(railh); + } + } else { + if (self.ishwscroll) { + if (self.win.css('position') == 'static') self.css(self.win, { 'position': 'relative' }); + var bd = (self.win[0].nodeName == 'HTML') ? self.body : self.win; + $(bd).scrollTop(0).scrollLeft(0); // fix rail position if content already scrolled + if (self.zoom) { + self.zoom.css({ + position: "absolute", + top: 1, + right: 0, + "margin-right": rail.width + 4 + }); + bd.append(self.zoom); + } + rail.css({ + position: "absolute", + top: 0 + }); + rail.css((rail.align) ? { right: 0 } : { left: 0 }); + bd.append(rail); + if (railh) { + railh.css({ + position: "absolute", + left: 0, + bottom: 0 + }); + railh.css((railh.align) ? { bottom: 0 } : { top: 0 }); + bd.append(railh); + } + } else { + self.isfixed = (self.win.css("position") == "fixed"); + var rlpos = (self.isfixed) ? "fixed" : "absolute"; + + if (!self.isfixed) self.viewport = self.getViewport(self.win[0]); + if (self.viewport) { + self.body = self.viewport; + if (!(/fixed|absolute/.test(self.viewport.css("position")))) self.css(self.viewport, { + "position": "relative" + }); + } + + rail.css({ + position: rlpos + }); + if (self.zoom) self.zoom.css({ + position: rlpos + }); + self.updateScrollBar(); + self.body.append(rail); + if (self.zoom) self.body.append(self.zoom); + if (self.railh) { + railh.css({ + position: rlpos + }); + self.body.append(railh); + } + } + + if (cap.isios) self.css(self.win, { + '-webkit-tap-highlight-color': 'rgba(0,0,0,0)', + '-webkit-touch-callout': 'none' + }); // prevent grey layer on click + + if (opt.disableoutline) { + if (cap.isie) self.win.attr("hideFocus", "true"); // IE, prevent dotted rectangle on focused div + if (cap.iswebkit) self.win.css('outline', 'none'); // Webkit outline + } + + } + + if (opt.autohidemode === false) { + self.autohidedom = false; + self.rail.css({ + opacity: opt.cursoropacitymax + }); + if (self.railh) self.railh.css({ + opacity: opt.cursoropacitymax + }); + } else if ((opt.autohidemode === true) || (opt.autohidemode === "leave")) { + self.autohidedom = $().add(self.rail); + if (cap.isie8) self.autohidedom = self.autohidedom.add(self.cursor); + if (self.railh) self.autohidedom = self.autohidedom.add(self.railh); + if (self.railh && cap.isie8) self.autohidedom = self.autohidedom.add(self.cursorh); + } else if (opt.autohidemode == "scroll") { + self.autohidedom = $().add(self.rail); + if (self.railh) self.autohidedom = self.autohidedom.add(self.railh); + } else if (opt.autohidemode == "cursor") { + self.autohidedom = $().add(self.cursor); + if (self.railh) self.autohidedom = self.autohidedom.add(self.cursorh); + } else if (opt.autohidemode == "hidden") { + self.autohidedom = false; + self.hide(); + self.railslocked = false; + } + + if (cap.cantouch || self.istouchcapable || opt.emulatetouch || cap.hasmstouch) { + + self.scrollmom = new ScrollMomentumClass2D(self); + + var delayedclick = null; + + self.ontouchstart = function (e) { + + if (self.locked) return false; + + //if (e.pointerType && e.pointerType != 2 && e.pointerType != "touch") return false; + if (e.pointerType && (e.pointerType === 'mouse' || e.pointerType === e.MSPOINTER_TYPE_MOUSE)) return false; // need test on surface!! + + self.hasmoving = false; + + if (self.scrollmom.timer) { + self.triggerScrollEnd(); + self.scrollmom.stop(); + } + + if (!self.railslocked) { + var tg = self.getTarget(e); + + if (tg) { + var skp = (/INPUT/i.test(tg.nodeName)) && (/range/i.test(tg.type)); + if (skp) return self.stopPropagation(e); + } + + var ismouse = (e.type === "mousedown"); + + if (!("clientX" in e) && ("changedTouches" in e)) { + e.clientX = e.changedTouches[0].clientX; + e.clientY = e.changedTouches[0].clientY; + } + + if (self.forcescreen) { + var le = e; + e = { + "original": (e.original) ? e.original : e + }; + e.clientX = le.screenX; + e.clientY = le.screenY; + } + + self.rail.drag = { + x: e.clientX, + y: e.clientY, + sx: self.scroll.x, + sy: self.scroll.y, + st: self.getScrollTop(), + sl: self.getScrollLeft(), + pt: 2, + dl: false, + tg: tg + }; + + if (self.ispage || !opt.directionlockdeadzone) { + + self.rail.drag.dl = "f"; + + } else { + + var view = { + w: $window.width(), + h: $window.height() + }; + + var page = self.getContentSize(); + + var maxh = page.h - view.h; + var maxw = page.w - view.w; + + if (self.rail.scrollable && !self.railh.scrollable) self.rail.drag.ck = (maxh > 0) ? "v" : false; + else if (!self.rail.scrollable && self.railh.scrollable) self.rail.drag.ck = (maxw > 0) ? "h" : false; + else self.rail.drag.ck = false; + + } + + if (opt.emulatetouch && self.isiframe && cap.isie) { + var wp = self.win.position(); + self.rail.drag.x += wp.left; + self.rail.drag.y += wp.top; + } + + self.hasmoving = false; + self.lastmouseup = false; + self.scrollmom.reset(e.clientX, e.clientY); + + if (tg&&ismouse) { + + var ip = /INPUT|SELECT|BUTTON|TEXTAREA/i.test(tg.nodeName); + if (!ip) { + if (cap.hasmousecapture) tg.setCapture(); + if (opt.emulatetouch) { + if (tg.onclick && !(tg._onclick || false)) { // intercept DOM0 onclick event + tg._onclick = tg.onclick; + tg.onclick = function (e) { + if (self.hasmoving) return false; + tg._onclick.call(this, e); + }; + } + return self.cancelEvent(e); + } + return self.stopPropagation(e); + } + + if (/SUBMIT|CANCEL|BUTTON/i.test($(tg).attr('type'))) { + self.preventclick = { + "tg": tg, + "click": false + }; + } + + } + } + + }; + + self.ontouchend = function (e) { + + if (!self.rail.drag) return true; + + if (self.rail.drag.pt == 2) { + //if (e.pointerType && e.pointerType != 2 && e.pointerType != "touch") return false; + if (e.pointerType && (e.pointerType === 'mouse' || e.pointerType === e.MSPOINTER_TYPE_MOUSE)) return false; + + self.rail.drag = false; + + var ismouse = (e.type === "mouseup"); + + if (self.hasmoving) { + self.scrollmom.doMomentum(); + self.lastmouseup = true; + self.hideCursor(); + if (cap.hasmousecapture) _doc.releaseCapture(); + if (ismouse) return self.cancelEvent(e); + } + + } + else if (self.rail.drag.pt == 1) { + return self.onmouseup(e); + } + + }; + + var moveneedoffset = (opt.emulatetouch && self.isiframe && !cap.hasmousecapture); + + var locktollerance = opt.directionlockdeadzone * 0.3 | 0; + + self.ontouchmove = function (e, byiframe) { + + if (!self.rail.drag) return true; + + if (e.targetTouches && opt.preventmultitouchscrolling) { + if (e.targetTouches.length > 1) return true; // multitouch + } + + //if (e.pointerType && e.pointerType != 2 && e.pointerType != "touch") return false; + if (e.pointerType && (e.pointerType === 'mouse' || e.pointerType === e.MSPOINTER_TYPE_MOUSE)) return true; + + if (self.rail.drag.pt == 2) { + + if (("changedTouches" in e)) { + e.clientX = e.changedTouches[0].clientX; + e.clientY = e.changedTouches[0].clientY; + } + + var ofy, ofx; + ofx = ofy = 0; + + if (moveneedoffset && !byiframe) { + var wp = self.win.position(); + ofx = -wp.left; + ofy = -wp.top; + } + + var fy = e.clientY + ofy; + var my = (fy - self.rail.drag.y); + var fx = e.clientX + ofx; + var mx = (fx - self.rail.drag.x); + + var ny = self.rail.drag.st - my; + + if (self.ishwscroll && opt.bouncescroll) { + if (ny < 0) { + ny = Math.round(ny / 2); + } else if (ny > self.page.maxh) { + ny = self.page.maxh + Math.round((ny - self.page.maxh) / 2); + } + } else { + if (ny < 0) { + ny = 0; + fy = 0; + } + else if (ny > self.page.maxh) { + ny = self.page.maxh; + fy = 0; + } + if (fy === 0 && !self.hasmoving) { + if (!self.ispage) self.rail.drag = false; + return true; + } + } + + var nx = self.getScrollLeft(); + + if (self.railh && self.railh.scrollable) { + nx = (self.isrtlmode) ? mx - self.rail.drag.sl : self.rail.drag.sl - mx; + + if (self.ishwscroll && opt.bouncescroll) { + if (nx < 0) { + nx = Math.round(nx / 2); + } else if (nx > self.page.maxw) { + nx = self.page.maxw + Math.round((nx - self.page.maxw) / 2); + } + } else { + if (nx < 0) { + nx = 0; + fx = 0; + } + if (nx > self.page.maxw) { + nx = self.page.maxw; + fx = 0; + } + } + + } + + + if (!self.hasmoving) { + + if (self.rail.drag.y === e.clientY && self.rail.drag.x === e.clientX) return self.cancelEvent(e); // prevent first useless move event + + var ay = Math.abs(my); + var ax = Math.abs(mx); + var dz = opt.directionlockdeadzone; + + if (!self.rail.drag.ck) { + if (ay > dz && ax > dz) self.rail.drag.dl = "f"; + else if (ay > dz) self.rail.drag.dl = (ax > locktollerance) ? "f" : "v"; + else if (ax > dz) self.rail.drag.dl = (ay > locktollerance) ? "f" : "h"; + } + else if (self.rail.drag.ck == "v") { + if (ax > dz && ay <= locktollerance) { + self.rail.drag = false; + } + else if (ay > dz) self.rail.drag.dl = "v"; + + } + else if (self.rail.drag.ck == "h") { + + if (ay > dz && ax <= locktollerance) { + self.rail.drag = false; + } + else if (ax > dz) self.rail.drag.dl = "h"; + + } + + if (!self.rail.drag.dl) return self.cancelEvent(e); + + self.triggerScrollStart(e.clientX, e.clientY, 0, 0, 0); + self.hasmoving = true; + } + + if (self.preventclick && !self.preventclick.click) { + self.preventclick.click = self.preventclick.tg.onclick || false; + self.preventclick.tg.onclick = self.onpreventclick; + } + + if (self.rail.drag.dl) { + if (self.rail.drag.dl == "v") nx = self.rail.drag.sl; + else if (self.rail.drag.dl == "h") ny = self.rail.drag.st; + } + + self.synched("touchmove", function () { + if (self.rail.drag && (self.rail.drag.pt == 2)) { + if (self.prepareTransition) self.resetTransition(); + if (self.rail.scrollable) self.setScrollTop(ny); + self.scrollmom.update(fx, fy); + if (self.railh && self.railh.scrollable) { + self.setScrollLeft(nx); + self.showCursor(ny, nx); + } else { + self.showCursor(ny); + } + if (cap.isie10) _doc.selection.clear(); + } + }); + + return self.cancelEvent(e); + + } + else if (self.rail.drag.pt == 1) { // drag on cursor + return self.onmousemove(e); + } + + }; + + self.ontouchstartCursor = function (e, hronly) { + if (self.rail.drag && self.rail.drag.pt != 3) return; + if (self.locked) return self.cancelEvent(e); + self.cancelScroll(); + self.rail.drag = { + x: e.touches[0].clientX, + y: e.touches[0].clientY, + sx: self.scroll.x, + sy: self.scroll.y, + pt: 3, + hr: (!!hronly) + }; + var tg = self.getTarget(e); + if (!self.ispage && cap.hasmousecapture) tg.setCapture(); + if (self.isiframe && !cap.hasmousecapture) { + self.saved.csspointerevents = self.doc.css("pointer-events"); + self.css(self.doc, { "pointer-events": "none" }); + } + return self.cancelEvent(e); + }; + + self.ontouchendCursor = function (e) { + if (self.rail.drag) { + if (cap.hasmousecapture) _doc.releaseCapture(); + if (self.isiframe && !cap.hasmousecapture) self.doc.css("pointer-events", self.saved.csspointerevents); + if (self.rail.drag.pt != 3) return; + self.rail.drag = false; + return self.cancelEvent(e); + } + }; + + self.ontouchmoveCursor = function (e) { + if (self.rail.drag) { + if (self.rail.drag.pt != 3) return; + + self.cursorfreezed = true; + + if (self.rail.drag.hr) { + self.scroll.x = self.rail.drag.sx + (e.touches[0].clientX - self.rail.drag.x); + if (self.scroll.x < 0) self.scroll.x = 0; + var mw = self.scrollvaluemaxw; + if (self.scroll.x > mw) self.scroll.x = mw; + } else { + self.scroll.y = self.rail.drag.sy + (e.touches[0].clientY - self.rail.drag.y); + if (self.scroll.y < 0) self.scroll.y = 0; + var my = self.scrollvaluemax; + if (self.scroll.y > my) self.scroll.y = my; + } + + self.synched('touchmove', function () { + if (self.rail.drag && (self.rail.drag.pt == 3)) { + self.showCursor(); + if (self.rail.drag.hr) self.doScrollLeft(Math.round(self.scroll.x * self.scrollratio.x), opt.cursordragspeed); + else self.doScrollTop(Math.round(self.scroll.y * self.scrollratio.y), opt.cursordragspeed); + } + }); + + return self.cancelEvent(e); + } + + }; + + } + + self.onmousedown = function (e, hronly) { + if (self.rail.drag && self.rail.drag.pt != 1) return; + if (self.railslocked) return self.cancelEvent(e); + self.cancelScroll(); + self.rail.drag = { + x: e.clientX, + y: e.clientY, + sx: self.scroll.x, + sy: self.scroll.y, + pt: 1, + hr: hronly || false + }; + var tg = self.getTarget(e); + + if (cap.hasmousecapture) tg.setCapture(); + if (self.isiframe && !cap.hasmousecapture) { + self.saved.csspointerevents = self.doc.css("pointer-events"); + self.css(self.doc, { + "pointer-events": "none" + }); + } + self.hasmoving = false; + return self.cancelEvent(e); + }; + + self.onmouseup = function (e) { + if (self.rail.drag) { + if (self.rail.drag.pt != 1) return true; + + if (cap.hasmousecapture) _doc.releaseCapture(); + if (self.isiframe && !cap.hasmousecapture) self.doc.css("pointer-events", self.saved.csspointerevents); + self.rail.drag = false; + self.cursorfreezed = false; + if (self.hasmoving) self.triggerScrollEnd(); + return self.cancelEvent(e); + } + }; + + self.onmousemove = function (e) { + if (self.rail.drag) { + if (self.rail.drag.pt !== 1) return; + + if (cap.ischrome && e.which === 0) return self.onmouseup(e); + + self.cursorfreezed = true; + + if (!self.hasmoving) self.triggerScrollStart(e.clientX, e.clientY, 0, 0, 0); + + self.hasmoving = true; + + if (self.rail.drag.hr) { + self.scroll.x = self.rail.drag.sx + (e.clientX - self.rail.drag.x); + if (self.scroll.x < 0) self.scroll.x = 0; + var mw = self.scrollvaluemaxw; + if (self.scroll.x > mw) self.scroll.x = mw; + } else { + self.scroll.y = self.rail.drag.sy + (e.clientY - self.rail.drag.y); + if (self.scroll.y < 0) self.scroll.y = 0; + var my = self.scrollvaluemax; + if (self.scroll.y > my) self.scroll.y = my; + } + + self.synched('mousemove', function () { + + if (self.cursorfreezed) { + self.showCursor(); + + if (self.rail.drag.hr) { + self.scrollLeft(Math.round(self.scroll.x * self.scrollratio.x)); + } else { + self.scrollTop(Math.round(self.scroll.y * self.scrollratio.y)); + } + + } + }); + + return self.cancelEvent(e); + } + else { + self.checkarea = 0; + } + }; + + if (cap.cantouch || opt.emulatetouch) { + + self.onpreventclick = function (e) { + if (self.preventclick) { + self.preventclick.tg.onclick = self.preventclick.click; + self.preventclick = false; + return self.cancelEvent(e); + } + }; + + self.onclick = (cap.isios) ? false : function (e) { // it needs to check IE11 ??? + if (self.lastmouseup) { + self.lastmouseup = false; + return self.cancelEvent(e); + } else { + return true; + } + }; + + if (opt.grabcursorenabled && cap.cursorgrabvalue) { + self.css((self.ispage) ? self.doc : self.win, { + 'cursor': cap.cursorgrabvalue + }); + self.css(self.rail, { + 'cursor': cap.cursorgrabvalue + }); + } + + } else { + + var checkSelectionScroll = function (e) { + if (!self.selectiondrag) return; + + if (e) { + var ww = self.win.outerHeight(); + var df = (e.pageY - self.selectiondrag.top); + if (df > 0 && df < ww) df = 0; + if (df >= ww) df -= ww; + self.selectiondrag.df = df; + } + if (self.selectiondrag.df === 0) return; + + var rt = -(self.selectiondrag.df*2/6)|0; + self.doScrollBy(rt); + + self.debounced("doselectionscroll", function () { + checkSelectionScroll(); + }, 50); + }; + + if ("getSelection" in _doc) { // A grade - Major browsers + self.hasTextSelected = function () { + return (_doc.getSelection().rangeCount > 0); + }; + } else if ("selection" in _doc) { //IE9- + self.hasTextSelected = function () { + return (_doc.selection.type != "None"); + }; + } else { + self.hasTextSelected = function () { // no support + return false; + }; + } + + self.onselectionstart = function (e) { + // More testing - severe chrome issues + /* + if (!self.haswrapper&&(e.which&&e.which==2)) { // fool browser to manage middle button scrolling + self.win.css({'overflow':'auto'}); + setTimeout(function(){ + self.win.css({'overflow':'hidden'}); + },10); + return true; + } + */ + if (self.ispage) return; + self.selectiondrag = self.win.offset(); + }; + + self.onselectionend = function (e) { + self.selectiondrag = false; + }; + self.onselectiondrag = function (e) { + if (!self.selectiondrag) return; + if (self.hasTextSelected()) self.debounced("selectionscroll", function () { + checkSelectionScroll(e); + }, 250); + }; + } + + if (cap.hasw3ctouch) { //IE11+ + self.css((self.ispage) ? $("html") : self.win, { 'touch-action': 'none' }); + self.css(self.rail, { + 'touch-action': 'none' + }); + self.css(self.cursor, { + 'touch-action': 'none' + }); + self.bind(self.win, "pointerdown", self.ontouchstart); + self.bind(_doc, "pointerup", self.ontouchend); + self.delegate(_doc, "pointermove", self.ontouchmove); + } else if (cap.hasmstouch) { //IE10 + self.css((self.ispage) ? $("html") : self.win, { '-ms-touch-action': 'none' }); + self.css(self.rail, { + '-ms-touch-action': 'none' + }); + self.css(self.cursor, { + '-ms-touch-action': 'none' + }); + self.bind(self.win, "MSPointerDown", self.ontouchstart); + self.bind(_doc, "MSPointerUp", self.ontouchend); + self.delegate(_doc, "MSPointerMove", self.ontouchmove); + self.bind(self.cursor, "MSGestureHold", function (e) { + e.preventDefault(); + }); + self.bind(self.cursor, "contextmenu", function (e) { + e.preventDefault(); + }); + } else if (cap.cantouch) { // smartphones/touch devices + self.bind(self.win, "touchstart", self.ontouchstart, false, true); + self.bind(_doc, "touchend", self.ontouchend, false, true); + self.bind(_doc, "touchcancel", self.ontouchend, false, true); + self.delegate(_doc, "touchmove", self.ontouchmove, false, true); + } + + if (opt.emulatetouch) { + self.bind(self.win, "mousedown", self.ontouchstart, false, true); + self.bind(_doc, "mouseup", self.ontouchend, false, true); + self.bind(_doc, "mousemove", self.ontouchmove, false, true); + } + + if (opt.cursordragontouch || (!cap.cantouch && !opt.emulatetouch)) { + + self.rail.css({ + cursor: "default" + }); + self.railh && self.railh.css({ + cursor: "default" + }); + + self.jqbind(self.rail, "mouseenter", function () { + if (!self.ispage && !self.win.is(":visible")) return false; + if (self.canshowonmouseevent) self.showCursor(); + self.rail.active = true; + }); + self.jqbind(self.rail, "mouseleave", function () { + self.rail.active = false; + if (!self.rail.drag) self.hideCursor(); + }); + + if (opt.sensitiverail) { + self.bind(self.rail, "click", function (e) { + self.doRailClick(e, false, false); + }); + self.bind(self.rail, "dblclick", function (e) { + self.doRailClick(e, true, false); + }); + self.bind(self.cursor, "click", function (e) { + self.cancelEvent(e); + }); + self.bind(self.cursor, "dblclick", function (e) { + self.cancelEvent(e); + }); + } + + if (self.railh) { + self.jqbind(self.railh, "mouseenter", function () { + if (!self.ispage && !self.win.is(":visible")) return false; + if (self.canshowonmouseevent) self.showCursor(); + self.rail.active = true; + }); + self.jqbind(self.railh, "mouseleave", function () { + self.rail.active = false; + if (!self.rail.drag) self.hideCursor(); + }); + + if (opt.sensitiverail) { + self.bind(self.railh, "click", function (e) { + self.doRailClick(e, false, true); + }); + self.bind(self.railh, "dblclick", function (e) { + self.doRailClick(e, true, true); + }); + self.bind(self.cursorh, "click", function (e) { + self.cancelEvent(e); + }); + self.bind(self.cursorh, "dblclick", function (e) { + self.cancelEvent(e); + }); + } + + } + + } + + if (opt.cursordragontouch && (this.istouchcapable || cap.cantouch)) { + self.bind(self.cursor, "touchstart", self.ontouchstartCursor); + self.bind(self.cursor, "touchmove", self.ontouchmoveCursor); + self.bind(self.cursor, "touchend", self.ontouchendCursor); + self.cursorh && self.bind(self.cursorh, "touchstart", function (e) { + self.ontouchstartCursor(e, true); + }); + self.cursorh && self.bind(self.cursorh, "touchmove", self.ontouchmoveCursor); + self.cursorh && self.bind(self.cursorh, "touchend", self.ontouchendCursor); + } + +// if (!cap.cantouch && !opt.emulatetouch) { + if (!opt.emulatetouch && !cap.isandroid && !cap.isios) { + + self.bind((cap.hasmousecapture) ? self.win : _doc, "mouseup", self.onmouseup); + self.bind(_doc, "mousemove", self.onmousemove); + if (self.onclick) self.bind(_doc, "click", self.onclick); + + self.bind(self.cursor, "mousedown", self.onmousedown); + self.bind(self.cursor, "mouseup", self.onmouseup); + + if (self.railh) { + self.bind(self.cursorh, "mousedown", function (e) { + self.onmousedown(e, true); + }); + self.bind(self.cursorh, "mouseup", self.onmouseup); + } + + if (!self.ispage && opt.enablescrollonselection) { + self.bind(self.win[0], "mousedown", self.onselectionstart); + self.bind(_doc, "mouseup", self.onselectionend); + self.bind(self.cursor, "mouseup", self.onselectionend); + if (self.cursorh) self.bind(self.cursorh, "mouseup", self.onselectionend); + self.bind(_doc, "mousemove", self.onselectiondrag); + } + + if (self.zoom) { + self.jqbind(self.zoom, "mouseenter", function () { + if (self.canshowonmouseevent) self.showCursor(); + self.rail.active = true; + }); + self.jqbind(self.zoom, "mouseleave", function () { + self.rail.active = false; + if (!self.rail.drag) self.hideCursor(); + }); + } + + } else { + + self.bind((cap.hasmousecapture) ? self.win : _doc, "mouseup", self.ontouchend); + if (self.onclick) self.bind(_doc, "click", self.onclick); + + if (opt.cursordragontouch) { + self.bind(self.cursor, "mousedown", self.onmousedown); + self.bind(self.cursor, "mouseup", self.onmouseup); + self.cursorh && self.bind(self.cursorh, "mousedown", function (e) { + self.onmousedown(e, true); + }); + self.cursorh && self.bind(self.cursorh, "mouseup", self.onmouseup); + } else { + self.bind(self.rail, "mousedown", function (e) { e.preventDefault(); }); // prevent text selection + self.railh && self.bind(self.railh, "mousedown", function (e) { e.preventDefault(); }); + } + + } + + + if (opt.enablemousewheel) { + if (!self.isiframe) self.mousewheel((cap.isie && self.ispage) ? _doc : self.win, self.onmousewheel); + self.mousewheel(self.rail, self.onmousewheel); + if (self.railh) self.mousewheel(self.railh, self.onmousewheelhr); + } + + if (!self.ispage && !cap.cantouch && !(/HTML|^BODY/.test(self.win[0].nodeName))) { + if (!self.win.attr("tabindex")) self.win.attr({ + "tabindex": ++tabindexcounter + }); + + self.bind(self.win, "focus", function (e) { // better using native events + domfocus = (self.getTarget(e)).id || self.getTarget(e) || false; + self.hasfocus = true; + if (self.canshowonmouseevent) self.noticeCursor(); + }); + self.bind(self.win, "blur", function (e) { // * + domfocus = false; + self.hasfocus = false; + }); + + self.bind(self.win, "mouseenter", function (e) { // * + mousefocus = (self.getTarget(e)).id || self.getTarget(e) || false; + self.hasmousefocus = true; + if (self.canshowonmouseevent) self.noticeCursor(); + }); + self.bind(self.win, "mouseleave", function (e) { // * + mousefocus = false; + self.hasmousefocus = false; + if (!self.rail.drag) self.hideCursor(); + }); + + } + + + //Thanks to http://www.quirksmode.org !! + self.onkeypress = function (e) { + if (self.railslocked && self.page.maxh === 0) return true; + + e = e || _win.event; + var tg = self.getTarget(e); + if (tg && /INPUT|TEXTAREA|SELECT|OPTION/.test(tg.nodeName)) { + var tp = tg.getAttribute('type') || tg.type || false; + if ((!tp) || !(/submit|button|cancel/i.tp)) return true; + } + + if ($(tg).attr('contenteditable')) return true; + + if (self.hasfocus || (self.hasmousefocus && !domfocus) || (self.ispage && !domfocus && !mousefocus)) { + var key = e.keyCode; + + if (self.railslocked && key != 27) return self.cancelEvent(e); + + var ctrl = e.ctrlKey || false; + var shift = e.shiftKey || false; + + var ret = false; + switch (key) { + case 38: + case 63233: //safari + self.doScrollBy(24 * 3); + ret = true; + break; + case 40: + case 63235: //safari + self.doScrollBy(-24 * 3); + ret = true; + break; + case 37: + case 63232: //safari + if (self.railh) { + (ctrl) ? self.doScrollLeft(0) : self.doScrollLeftBy(24 * 3); + ret = true; + } + break; + case 39: + case 63234: //safari + if (self.railh) { + (ctrl) ? self.doScrollLeft(self.page.maxw) : self.doScrollLeftBy(-24 * 3); + ret = true; + } + break; + case 33: + case 63276: // safari + self.doScrollBy(self.view.h); + ret = true; + break; + case 34: + case 63277: // safari + self.doScrollBy(-self.view.h); + ret = true; + break; + case 36: + case 63273: // safari + (self.railh && ctrl) ? self.doScrollPos(0, 0) : self.doScrollTo(0); + ret = true; + break; + case 35: + case 63275: // safari + (self.railh && ctrl) ? self.doScrollPos(self.page.maxw, self.page.maxh) : self.doScrollTo(self.page.maxh); + ret = true; + break; + case 32: + if (opt.spacebarenabled) { + (shift) ? self.doScrollBy(self.view.h) : self.doScrollBy(-self.view.h); + ret = true; + } + break; + case 27: // ESC + if (self.zoomactive) { + self.doZoom(); + ret = true; + } + break; + } + if (ret) return self.cancelEvent(e); + } + }; + + if (opt.enablekeyboard) self.bind(_doc, (cap.isopera && !cap.isopera12) ? "keypress" : "keydown", self.onkeypress); + + self.bind(_doc, "keydown", function (e) { + var ctrl = e.ctrlKey || false; + if (ctrl) self.wheelprevented = true; + }); + self.bind(_doc, "keyup", function (e) { + var ctrl = e.ctrlKey || false; + if (!ctrl) self.wheelprevented = false; + }); + self.bind(_win, "blur", function (e) { + self.wheelprevented = false; + }); + + self.bind(_win, 'resize', self.onscreenresize); + self.bind(_win, 'orientationchange', self.onscreenresize); + + self.bind(_win, "load", self.lazyResize); + + if (cap.ischrome && !self.ispage && !self.haswrapper) { //chrome void scrollbar bug - it persists in version 26 + var tmp = self.win.attr("style"); + var ww = parseFloat(self.win.css("width")) + 1; + self.win.css('width', ww); + self.synched("chromefix", function () { + self.win.attr("style", tmp); + }); + } + + + // Trying a cross-browser implementation - good luck! + + self.onAttributeChange = function (e) { + self.lazyResize(self.isieold ? 250 : 30); + }; + + if (opt.enableobserver) { + + if ((!self.isie11) && (ClsMutationObserver !== false)) { // IE11 crashes #568 + self.observerbody = new ClsMutationObserver(function (mutations) { + mutations.forEach(function (mut) { + if (mut.type == "attributes") { + return ($body.hasClass("modal-open") && $body.hasClass("modal-dialog") && !$.contains($('.modal-dialog')[0], self.doc[0])) ? self.hide() : self.show(); // Support for Bootstrap modal; Added check if the nice scroll element is inside a modal + } + }); + if (self.me.clientWidth != self.page.width || self.me.clientHeight != self.page.height) return self.lazyResize(30); + }); + self.observerbody.observe(_doc.body, { + childList: true, + subtree: true, + characterData: false, + attributes: true, + attributeFilter: ['class'] + }); + } + + if (!self.ispage && !self.haswrapper) { + + var _dom = self.win[0]; + + // redesigned MutationObserver for Chrome18+/Firefox14+/iOS6+ with support for: remove div, add/remove content + if (ClsMutationObserver !== false) { + self.observer = new ClsMutationObserver(function (mutations) { + mutations.forEach(self.onAttributeChange); + }); + self.observer.observe(_dom, { + childList: true, + characterData: false, + attributes: true, + subtree: false + }); + self.observerremover = new ClsMutationObserver(function (mutations) { + mutations.forEach(function (mo) { + if (mo.removedNodes.length > 0) { + for (var dd in mo.removedNodes) { + if (!!self && (mo.removedNodes[dd] === _dom)) return self.remove(); + } + } + }); + }); + self.observerremover.observe(_dom.parentNode, { + childList: true, + characterData: false, + attributes: false, + subtree: false + }); + } else { + self.bind(_dom, (cap.isie && !cap.isie9) ? "propertychange" : "DOMAttrModified", self.onAttributeChange); + if (cap.isie9) _dom.attachEvent("onpropertychange", self.onAttributeChange); //IE9 DOMAttrModified bug + self.bind(_dom, "DOMNodeRemoved", function (e) { + if (e.target === _dom) self.remove(); + }); + } + } + + } + + // + + if (!self.ispage && opt.boxzoom) self.bind(_win, "resize", self.resizeZoom); + if (self.istextarea) { + self.bind(self.win, "keydown", self.lazyResize); + self.bind(self.win, "mouseup", self.lazyResize); + } + + self.lazyResize(30); + + } + + if (this.doc[0].nodeName == 'IFRAME') { + var oniframeload = function () { + self.iframexd = false; + var doc; + try { + doc = 'contentDocument' in this ? this.contentDocument : this.contentWindow._doc; + var a = doc.domain; + } catch (e) { + self.iframexd = true; + doc = false; + } + + if (self.iframexd) { + if ("console" in _win) console.log('NiceScroll error: policy restriced iframe'); + return true; //cross-domain - I can't manage this + } + + self.forcescreen = true; + + if (self.isiframe) { + self.iframe = { + "doc": $(doc), + "html": self.doc.contents().find('html')[0], + "body": self.doc.contents().find('body')[0] + }; + self.getContentSize = function () { + return { + w: Math.max(self.iframe.html.scrollWidth, self.iframe.body.scrollWidth), + h: Math.max(self.iframe.html.scrollHeight, self.iframe.body.scrollHeight) + }; + }; + self.docscroll = $(self.iframe.body); + } + + if (!cap.isios && opt.iframeautoresize && !self.isiframe) { + self.win.scrollTop(0); // reset position + self.doc.height(""); //reset height to fix browser bug + var hh = Math.max(doc.getElementsByTagName('html')[0].scrollHeight, doc.body.scrollHeight); + self.doc.height(hh); + } + self.lazyResize(30); + + self.css($(self.iframe.body), _scrollyhidden); + + if (cap.isios && self.haswrapper) { + self.css($(doc.body), { + '-webkit-transform': 'translate3d(0,0,0)' + }); // avoid iFrame content clipping - thanks to http://blog.derraab.com/2012/04/02/avoid-iframe-content-clipping-with-css-transform-on-ios/ + } + + if ('contentWindow' in this) { + self.bind(this.contentWindow, "scroll", self.onscroll); //IE8 & minor + } else { + self.bind(doc, "scroll", self.onscroll); + } + + if (opt.enablemousewheel) { + self.mousewheel(doc, self.onmousewheel); + } + + if (opt.enablekeyboard) self.bind(doc, (cap.isopera) ? "keypress" : "keydown", self.onkeypress); + + if (cap.cantouch) { + self.bind(doc, "touchstart", self.ontouchstart); + self.bind(doc, "touchmove", self.ontouchmove); + } + else if (opt.emulatetouch) { + self.bind(doc, "mousedown", self.ontouchstart); + self.bind(doc, "mousemove", function (e) { + return self.ontouchmove(e, true); + }); + if (opt.grabcursorenabled && cap.cursorgrabvalue) self.css($(doc.body), { + 'cursor': cap.cursorgrabvalue + }); + } + + self.bind(doc, "mouseup", self.ontouchend); + + if (self.zoom) { + if (opt.dblclickzoom) self.bind(doc, 'dblclick', self.doZoom); + if (self.ongesturezoom) self.bind(doc, "gestureend", self.ongesturezoom); + } + }; + + if (this.doc[0].readyState && this.doc[0].readyState === "complete") { + setTimeout(function () { + oniframeload.call(self.doc[0], false); + }, 500); + } + self.bind(this.doc, "load", oniframeload); + + } + + }; + + this.showCursor = function (py, px) { + if (self.cursortimeout) { + clearTimeout(self.cursortimeout); + self.cursortimeout = 0; + } + if (!self.rail) return; + if (self.autohidedom) { + self.autohidedom.stop().css({ + opacity: opt.cursoropacitymax + }); + self.cursoractive = true; + } + + if (!self.rail.drag || self.rail.drag.pt != 1) { + if (py !== undefined && py !== false) { + self.scroll.y = (py / self.scrollratio.y) | 0; + } + if (px !== undefined) { + self.scroll.x = (px / self.scrollratio.x) | 0; + } + } + + self.cursor.css({ + height: self.cursorheight, + top: self.scroll.y + }); + if (self.cursorh) { + var lx = (self.hasreversehr) ? self.scrollvaluemaxw - self.scroll.x : self.scroll.x; + self.cursorh.css({ + width: self.cursorwidth, + left: (!self.rail.align && self.rail.visibility) ? lx + self.rail.width : lx + }); + self.cursoractive = true; + } + + if (self.zoom) self.zoom.stop().css({ + opacity: opt.cursoropacitymax + }); + }; + + this.hideCursor = function (tm) { + if (self.cursortimeout) return; + if (!self.rail) return; + if (!self.autohidedom) return; + + if (self.hasmousefocus && opt.autohidemode === "leave") return; + self.cursortimeout = setTimeout(function () { + if (!self.rail.active || !self.showonmouseevent) { + self.autohidedom.stop().animate({ + opacity: opt.cursoropacitymin + }); + if (self.zoom) self.zoom.stop().animate({ + opacity: opt.cursoropacitymin + }); + self.cursoractive = false; + } + self.cursortimeout = 0; + }, tm || opt.hidecursordelay); + }; + + this.noticeCursor = function (tm, py, px) { + self.showCursor(py, px); + if (!self.rail.active) self.hideCursor(tm); + }; + + this.getContentSize = + (self.ispage) ? + function () { + return { + w: Math.max(_doc.body.scrollWidth, _doc.documentElement.scrollWidth), + h: Math.max(_doc.body.scrollHeight, _doc.documentElement.scrollHeight) + }; + } : (self.haswrapper) ? + function () { + return { + w: self.doc[0].offsetWidth, + h: self.doc[0].offsetHeight + }; + } : function () { + return { + w: self.docscroll[0].scrollWidth, + h: self.docscroll[0].scrollHeight + }; + }; + + this.onResize = function (e, page) { + + if (!self || !self.win) return false; + + var premaxh = self.page.maxh, + premaxw = self.page.maxw, + previewh = self.view.h, + previeww = self.view.w; + + self.view = { + w: (self.ispage) ? self.win.width() : self.win[0].clientWidth, + h: (self.ispage) ? self.win.height() : self.win[0].clientHeight + }; + + self.page = (page) ? page : self.getContentSize(); + + self.page.maxh = Math.max(0, self.page.h - self.view.h); + self.page.maxw = Math.max(0, self.page.w - self.view.w); + + if ((self.page.maxh == premaxh) && (self.page.maxw == premaxw) && (self.view.w == previeww) && (self.view.h == previewh)) { + // test position + if (!self.ispage) { + var pos = self.win.offset(); + if (self.lastposition) { + var lst = self.lastposition; + if ((lst.top == pos.top) && (lst.left == pos.left)) return self; //nothing to do + } + self.lastposition = pos; + } else { + return self; //nothing to do + } + } + + if (self.page.maxh === 0) { + self.hideRail(); + self.scrollvaluemax = 0; + self.scroll.y = 0; + self.scrollratio.y = 0; + self.cursorheight = 0; + self.setScrollTop(0); + if (self.rail) self.rail.scrollable = false; + } else { + self.page.maxh -= (opt.railpadding.top + opt.railpadding.bottom); + self.rail.scrollable = true; + } + + if (self.page.maxw === 0) { + self.hideRailHr(); + self.scrollvaluemaxw = 0; + self.scroll.x = 0; + self.scrollratio.x = 0; + self.cursorwidth = 0; + self.setScrollLeft(0); + if (self.railh) { + self.railh.scrollable = false; + } + } else { + self.page.maxw -= (opt.railpadding.left + opt.railpadding.right); + if (self.railh) self.railh.scrollable = (opt.horizrailenabled); + } + + self.railslocked = (self.locked) || ((self.page.maxh === 0) && (self.page.maxw === 0)); + if (self.railslocked) { + if (!self.ispage) self.updateScrollBar(self.view); + return false; + } + + if (!self.hidden) { + if (!self.rail.visibility) self.showRail(); + if (self.railh && !self.railh.visibility) self.showRailHr(); + } + + if (self.istextarea && self.win.css('resize') && self.win.css('resize') != 'none') self.view.h -= 20; + + self.cursorheight = Math.min(self.view.h, Math.round(self.view.h * (self.view.h / self.page.h))); + self.cursorheight = (opt.cursorfixedheight) ? opt.cursorfixedheight : Math.max(opt.cursorminheight, self.cursorheight); + + self.cursorwidth = Math.min(self.view.w, Math.round(self.view.w * (self.view.w / self.page.w))); + self.cursorwidth = (opt.cursorfixedheight) ? opt.cursorfixedheight : Math.max(opt.cursorminheight, self.cursorwidth); + + self.scrollvaluemax = self.view.h - self.cursorheight - (opt.railpadding.top + opt.railpadding.bottom); + if (!self.hasborderbox) self.scrollvaluemax -= self.cursor[0].offsetHeight - self.cursor[0].clientHeight; + + if (self.railh) { + self.railh.width = (self.page.maxh > 0) ? (self.view.w - self.rail.width) : self.view.w; + self.scrollvaluemaxw = self.railh.width - self.cursorwidth - (opt.railpadding.left + opt.railpadding.right); + } + + if (!self.ispage) self.updateScrollBar(self.view); + + self.scrollratio = { + x: (self.page.maxw / self.scrollvaluemaxw), + y: (self.page.maxh / self.scrollvaluemax) + }; + + var sy = self.getScrollTop(); + if (sy > self.page.maxh) { + self.doScrollTop(self.page.maxh); + } else { + self.scroll.y = (self.getScrollTop() / self.scrollratio.y) | 0; + self.scroll.x = (self.getScrollLeft() / self.scrollratio.x) | 0; + if (self.cursoractive) self.noticeCursor(); + } + + if (self.scroll.y && (self.getScrollTop() === 0)) self.doScrollTo((self.scroll.y * self.scrollratio.y)|0); + + return self; + }; + + this.resize = self.onResize; + + var hlazyresize = 0; + + this.onscreenresize = function(e) { + clearTimeout(hlazyresize); + + var hiderails = (!self.ispage && !self.haswrapper); + if (hiderails) self.hideRails(); + + hlazyresize = setTimeout(function () { + if (self) { + if (hiderails) self.showRails(); + self.resize(); + } + hlazyresize=0; + }, 120); + }; + + this.lazyResize = function (tm) { // event debounce + + clearTimeout(hlazyresize); + + tm = isNaN(tm) ? 240 : tm; + + hlazyresize = setTimeout(function () { + self && self.resize(); + hlazyresize=0; + }, tm); + + return self; + + }; + + // derived by MDN https://developer.mozilla.org/en-US/docs/DOM/Mozilla_event_reference/wheel + function _modernWheelEvent(dom, name, fn, bubble) { + self._bind(dom, name, function (e) { + e = e || _win.event; + var event = { + original: e, + target: e.target || e.srcElement, + type: "wheel", + deltaMode: e.type == "MozMousePixelScroll" ? 0 : 1, + deltaX: 0, + deltaZ: 0, + preventDefault: function () { + e.preventDefault ? e.preventDefault() : e.returnValue = false; + return false; + }, + stopImmediatePropagation: function () { + (e.stopImmediatePropagation) ? e.stopImmediatePropagation() : e.cancelBubble = true; + } + }; + + if (name == "mousewheel") { + e.wheelDeltaX && (event.deltaX = -1 / 40 * e.wheelDeltaX); + e.wheelDeltaY && (event.deltaY = -1 / 40 * e.wheelDeltaY); + !event.deltaY && !event.deltaX && (event.deltaY = -1 / 40 * e.wheelDelta); + } else { + event.deltaY = e.detail; + } + + return fn.call(dom, event); + }, bubble); + } + + + + this.jqbind = function (dom, name, fn) { // use jquery bind for non-native events (mouseenter/mouseleave) + self.events.push({ + e: dom, + n: name, + f: fn, + q: true + }); + $(dom).on(name, fn); + }; + + this.mousewheel = function (dom, fn, bubble) { // bind mousewheel + var el = ("jquery" in dom) ? dom[0] : dom; + if ("onwheel" in _doc.createElement("div")) { // Modern browsers support "wheel" + self._bind(el, "wheel", fn, bubble || false); + } else { + var wname = (_doc.onmousewheel !== undefined) ? "mousewheel" : "DOMMouseScroll"; // older Webkit+IE support or older Firefox + _modernWheelEvent(el, wname, fn, bubble || false); + if (wname == "DOMMouseScroll") _modernWheelEvent(el, "MozMousePixelScroll", fn, bubble || false); // Firefox legacy + } + }; + + var passiveSupported = false; + + if (cap.haseventlistener) { // W3C standard event model + + // thanks to https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener + try { var options = Object.defineProperty({}, "passive", { get: function () { passiveSupported = !0; } }); _win.addEventListener("test", null, options); } catch (err) { } + + this.stopPropagation = function (e) { + if (!e) return false; + e = (e.original) ? e.original : e; + e.stopPropagation(); + return false; + }; + + this.cancelEvent = function(e) { + if (e.cancelable) e.preventDefault(); + e.stopImmediatePropagation(); + if (e.preventManipulation) e.preventManipulation(); // IE10+ + return false; + }; + + } else { + + // inspired from https://gist.github.com/jonathantneal/2415137 + + Event.prototype.preventDefault = function () { + this.returnValue = false; + }; + + Event.prototype.stopPropagation = function () { + this.cancelBubble = true; + }; + + _win.constructor.prototype.addEventListener = _doc.constructor.prototype.addEventListener = Element.prototype.addEventListener = function (type, listener, useCapture) { + this.attachEvent("on" + type, listener); + }; + _win.constructor.prototype.removeEventListener = _doc.constructor.prototype.removeEventListener = Element.prototype.removeEventListener = function (type, listener, useCapture) { + this.detachEvent("on" + type, listener); + }; + + // Thanks to http://www.switchonthecode.com !! + this.cancelEvent = function (e) { + e = e || _win.event; + if (e) { + e.cancelBubble = true; + e.cancel = true; + e.returnValue = false; + } + return false; + }; + + this.stopPropagation = function (e) { + e = e || _win.event; + if (e) e.cancelBubble = true; + return false; + }; + + } + + this.delegate = function (dom, name, fn, bubble, active) { + + var de = delegatevents[name] || false; + + if (!de) { + + de = { + a: [], + l: [], + f: function (e) { + var lst = de.l, l = lst.length - 1; + var r = false; + for (var a = l; a >= 0; a--) { + r = lst[a].call(e.target, e); + if (r === false) return false; + } + return r; + } + }; + + self.bind(dom, name, de.f, bubble, active); + + delegatevents[name] = de; + + } + + if (self.ispage) { + de.a = [self.id].concat(de.a); + de.l = [fn].concat(de.l); + } else { + de.a.push(self.id); + de.l.push(fn); + } + + }; + + this.undelegate = function (dom, name, fn, bubble, active) { + var de = delegatevents[name]||false; + if (de&&de.l) { // quick fix #683 + for (var a=0,l=de.l.length;a 0) return dd; + dom = (dom.parentNode) ? dom.parentNode : false; + } + return false; + }; + + this.triggerScrollStart = function (cx, cy, rx, ry, ms) { + + if (self.onscrollstart) { + var info = { + type: "scrollstart", + current: { + x: cx, + y: cy + }, + request: { + x: rx, + y: ry + }, + end: { + x: self.newscrollx, + y: self.newscrolly + }, + speed: ms + }; + self.onscrollstart.call(self, info); + } + + }; + + this.triggerScrollEnd = function () { + if (self.onscrollend) { + + var px = self.getScrollLeft(); + var py = self.getScrollTop(); + + var info = { + type: "scrollend", + current: { + x: px, + y: py + }, + end: { + x: px, + y: py + } + }; + + self.onscrollend.call(self, info); + + } + + }; + + var scrolldiry = 0, scrolldirx = 0, scrolltmr = 0, scrollspd = 1; + + function doScrollRelative(px, py, chkscroll, iswheel) { + + if (!self.scrollrunning) { + self.newscrolly = self.getScrollTop(); + self.newscrollx = self.getScrollLeft(); + scrolltmr = now(); + } + + var gap = (now() - scrolltmr); + scrolltmr = now(); + + if (gap > 350) { + scrollspd = 1; + } else { + scrollspd += (2 - scrollspd) / 10; + } + + px = px * scrollspd | 0; + py = py * scrollspd | 0; + + if (px) { + + if (iswheel) { // mouse-only + if (px < 0) { // fix apple magic mouse swipe back/forward + if (self.getScrollLeft() >= self.page.maxw) return true; + } else { + if (self.getScrollLeft() <= 0) return true; + } + } + + var dx = px > 0 ? 1 : -1; + + if (scrolldirx !== dx) { + if (self.scrollmom) self.scrollmom.stop(); + self.newscrollx = self.getScrollLeft(); + scrolldirx = dx; + } + + self.lastdeltax -= px; + + } + + if (py) { + + var chk = (function () { + var top = self.getScrollTop(); + if (py < 0) { + if (top >= self.page.maxh) return true; + } else { + if (top <= 0) return true; + } + })(); + + if (chk) { + if (opt.nativeparentscrolling && chkscroll && !self.ispage && !self.zoomactive) return true; + var ny = self.view.h >> 1; + if (self.newscrolly < -ny) { self.newscrolly = -ny; py = -1; } + else if (self.newscrolly > self.page.maxh + ny) { self.newscrolly = self.page.maxh + ny; py = 1; } + else py = 0; + } + + var dy = py > 0 ? 1 : -1; + + if (scrolldiry !== dy) { + if (self.scrollmom) self.scrollmom.stop(); + self.newscrolly = self.getScrollTop(); + scrolldiry = dy; + } + + self.lastdeltay -= py; + + } + + if (py || px) { + self.synched("relativexy", function () { + + var dty = self.lastdeltay + self.newscrolly; + self.lastdeltay = 0; + + var dtx = self.lastdeltax + self.newscrollx; + self.lastdeltax = 0; + + if (!self.rail.drag) self.doScrollPos(dtx, dty); + + }); + } + + } + + var hasparentscrollingphase = false; + + function execScrollWheel(e, hr, chkscroll) { + var px, py; + + if (!chkscroll && hasparentscrollingphase) return true; + + if (e.deltaMode === 0) { // PIXEL + px = -(e.deltaX * (opt.mousescrollstep / (18 * 3))) | 0; + py = -(e.deltaY * (opt.mousescrollstep / (18 * 3))) | 0; + } else if (e.deltaMode === 1) { // LINE + px = -(e.deltaX * opt.mousescrollstep * 50 / 80) | 0; + py = -(e.deltaY * opt.mousescrollstep * 50 / 80) | 0; + } + + if (hr && opt.oneaxismousemode && (px === 0) && py) { // classic vertical-only mousewheel + browser with x/y support + px = py; + py = 0; + + if (chkscroll) { + var hrend = (px < 0) ? (self.getScrollLeft() >= self.page.maxw) : (self.getScrollLeft() <= 0); + if (hrend) { // preserve vertical scrolling + py = px; + px = 0; + } + } + + } + + // invert horizontal direction for rtl mode + if (self.isrtlmode) px = -px; + + var chk = doScrollRelative(px, py, chkscroll, true); + + if (chk) { + if (chkscroll) hasparentscrollingphase = true; + } else { + hasparentscrollingphase = false; + e.stopImmediatePropagation(); + return e.preventDefault(); + } + + } + + this.onmousewheel = function (e) { + if (self.wheelprevented||self.locked) return false; + if (self.railslocked) { + self.debounced("checkunlock", self.resize, 250); + return false; + } + if (self.rail.drag) return self.cancelEvent(e); + + if (opt.oneaxismousemode === "auto" && e.deltaX !== 0) opt.oneaxismousemode = false; // check two-axis mouse support (not very elegant) + + if (opt.oneaxismousemode && e.deltaX === 0) { + if (!self.rail.scrollable) { + if (self.railh && self.railh.scrollable) { + return self.onmousewheelhr(e); + } else { + return true; + } + } + } + + var nw = now(); + var chk = false; + if (opt.preservenativescrolling && ((self.checkarea + 600) < nw)) { + self.nativescrollingarea = self.isScrollable(e); + chk = true; + } + self.checkarea = nw; + if (self.nativescrollingarea) return true; // this isn't my business + var ret = execScrollWheel(e, false, chk); + if (ret) self.checkarea = 0; + return ret; + }; + + this.onmousewheelhr = function (e) { + if (self.wheelprevented) return; + if (self.railslocked || !self.railh.scrollable) return true; + if (self.rail.drag) return self.cancelEvent(e); + + var nw = now(); + var chk = false; + if (opt.preservenativescrolling && ((self.checkarea + 600) < nw)) { + self.nativescrollingarea = self.isScrollable(e); + chk = true; + } + self.checkarea = nw; + if (self.nativescrollingarea) return true; // this is not my business + if (self.railslocked) return self.cancelEvent(e); + + return execScrollWheel(e, true, chk); + }; + + this.stop = function () { + self.cancelScroll(); + if (self.scrollmon) self.scrollmon.stop(); + self.cursorfreezed = false; + self.scroll.y = Math.round(self.getScrollTop() * (1 / self.scrollratio.y)); + self.noticeCursor(); + return self; + }; + + this.getTransitionSpeed = function (dif) { + + return 80 + (dif / 72) * opt.scrollspeed |0; + + }; + + if (!opt.smoothscroll) { + this.doScrollLeft = function (x, spd) { //direct + var y = self.getScrollTop(); + self.doScrollPos(x, y, spd); + }; + this.doScrollTop = function (y, spd) { //direct + var x = self.getScrollLeft(); + self.doScrollPos(x, y, spd); + }; + this.doScrollPos = function (x, y, spd) { //direct + var nx = (x > self.page.maxw) ? self.page.maxw : x; + if (nx < 0) nx = 0; + var ny = (y > self.page.maxh) ? self.page.maxh : y; + if (ny < 0) ny = 0; + self.synched('scroll', function () { + self.setScrollTop(ny); + self.setScrollLeft(nx); + }); + }; + this.cancelScroll = function () { }; // direct + + } else if (self.ishwscroll && cap.hastransition && opt.usetransition && !!opt.smoothscroll) { + + var lasttransitionstyle = ''; + + this.resetTransition = function () { + lasttransitionstyle = ''; + self.doc.css(cap.prefixstyle + 'transition-duration', '0ms'); + }; + + this.prepareTransition = function (dif, istime) { + var ex = (istime) ? dif : self.getTransitionSpeed(dif); + var trans = ex + 'ms'; + if (lasttransitionstyle !== trans) { + lasttransitionstyle = trans; + self.doc.css(cap.prefixstyle + 'transition-duration', trans); + } + return ex; + }; + + this.doScrollLeft = function (x, spd) { //trans + var y = (self.scrollrunning) ? self.newscrolly : self.getScrollTop(); + self.doScrollPos(x, y, spd); + }; + + this.doScrollTop = function (y, spd) { //trans + var x = (self.scrollrunning) ? self.newscrollx : self.getScrollLeft(); + self.doScrollPos(x, y, spd); + }; + + this.cursorupdate = { + running: false, + start: function () { + var m = this; + + if (m.running) return; + m.running = true; + + var loop = function () { + if (m.running) setAnimationFrame(loop); + self.showCursor(self.getScrollTop(), self.getScrollLeft()); + self.notifyScrollEvent(self.win[0]); + }; + + setAnimationFrame(loop); + }, + stop: function () { + this.running = false; + } + }; + + this.doScrollPos = function (x, y, spd) { //trans + + var py = self.getScrollTop(); + var px = self.getScrollLeft(); + + if (((self.newscrolly - py) * (y - py) < 0) || ((self.newscrollx - px) * (x - px) < 0)) self.cancelScroll(); //inverted movement detection + + if (!opt.bouncescroll) { + if (y < 0) y = 0; + else if (y > self.page.maxh) y = self.page.maxh; + if (x < 0) x = 0; + else if (x > self.page.maxw) x = self.page.maxw; + } else { + if (y < 0) y = y / 2 | 0; + else if (y > self.page.maxh) y = self.page.maxh + (y - self.page.maxh) / 2 | 0; + if (x < 0) x = x / 2 | 0; + else if (x > self.page.maxw) x = self.page.maxw + (x - self.page.maxw) / 2 | 0; + } + + if (self.scrollrunning && x == self.newscrollx && y == self.newscrolly) return false; + + self.newscrolly = y; + self.newscrollx = x; + + var top = self.getScrollTop(); + var lft = self.getScrollLeft(); + + var dst = {}; + dst.x = x - lft; + dst.y = y - top; + + var dd = Math.sqrt((dst.x * dst.x) + (dst.y * dst.y)) | 0; + + var ms = self.prepareTransition(dd); + + if (!self.scrollrunning) { + self.scrollrunning = true; + self.triggerScrollStart(lft, top, x, y, ms); + self.cursorupdate.start(); + } + + self.scrollendtrapped = true; + + if (!cap.transitionend) { + if (self.scrollendtrapped) clearTimeout(self.scrollendtrapped); + self.scrollendtrapped = setTimeout(self.onScrollTransitionEnd, ms); // simulate transitionend event + } + + self.setScrollTop(self.newscrolly); + self.setScrollLeft(self.newscrollx); + + }; + + this.cancelScroll = function () { + if (!self.scrollendtrapped) return true; + var py = self.getScrollTop(); + var px = self.getScrollLeft(); + self.scrollrunning = false; + if (!cap.transitionend) clearTimeout(cap.transitionend); + self.scrollendtrapped = false; + self.resetTransition(); + self.setScrollTop(py); // fire event onscroll + if (self.railh) self.setScrollLeft(px); + if (self.timerscroll && self.timerscroll.tm) clearInterval(self.timerscroll.tm); + self.timerscroll = false; + + self.cursorfreezed = false; + + self.cursorupdate.stop(); + self.showCursor(py, px); + return self; + }; + + this.onScrollTransitionEnd = function () { + + if (!self.scrollendtrapped) return; + + var py = self.getScrollTop(); + var px = self.getScrollLeft(); + + if (py < 0) py = 0; + else if (py > self.page.maxh) py = self.page.maxh; + if (px < 0) px = 0; + else if (px > self.page.maxw) px = self.page.maxw; + if ((py != self.newscrolly) || (px != self.newscrollx)) return self.doScrollPos(px, py, opt.snapbackspeed); + + if (self.scrollrunning) self.triggerScrollEnd(); + self.scrollrunning = false; + + self.scrollendtrapped = false; + self.resetTransition(); + self.timerscroll = false; + self.setScrollTop(py); // fire event onscroll + if (self.railh) self.setScrollLeft(px); // fire event onscroll left + + self.cursorupdate.stop(); + self.noticeCursor(false, py, px); + + self.cursorfreezed = false; + + }; + + } else { + + this.doScrollLeft = function (x, spd) { //no-trans + var y = (self.scrollrunning) ? self.newscrolly : self.getScrollTop(); + self.doScrollPos(x, y, spd); + }; + + this.doScrollTop = function (y, spd) { //no-trans + var x = (self.scrollrunning) ? self.newscrollx : self.getScrollLeft(); + self.doScrollPos(x, y, spd); + }; + + this.doScrollPos = function (x, y, spd) { //no-trans + + var py = self.getScrollTop(); + var px = self.getScrollLeft(); + + if (((self.newscrolly - py) * (y - py) < 0) || ((self.newscrollx - px) * (x - px) < 0)) self.cancelScroll(); //inverted movement detection + + var clipped = false; + + if (!self.bouncescroll || !self.rail.visibility) { + if (y < 0) { + y = 0; + clipped = true; + } else if (y > self.page.maxh) { + y = self.page.maxh; + clipped = true; + } + } + if (!self.bouncescroll || !self.railh.visibility) { + if (x < 0) { + x = 0; + clipped = true; + } else if (x > self.page.maxw) { + x = self.page.maxw; + clipped = true; + } + } + + if (self.scrollrunning && (self.newscrolly === y) && (self.newscrollx === x)) return true; + + self.newscrolly = y; + self.newscrollx = x; + + self.dst = {}; + self.dst.x = x - px; + self.dst.y = y - py; + self.dst.px = px; + self.dst.py = py; + + var dd = Math.sqrt((self.dst.x * self.dst.x) + (self.dst.y * self.dst.y)) | 0; + var ms = self.getTransitionSpeed(dd); + + self.bzscroll = {}; + + var p3 = (clipped) ? 1 : 0.58; + self.bzscroll.x = new BezierClass(px, self.newscrollx, ms, 0, 0, p3, 1); + self.bzscroll.y = new BezierClass(py, self.newscrolly, ms, 0, 0, p3, 1); + + var loopid = now(); + + var loop = function () { + + if (!self.scrollrunning) return; + var x = self.bzscroll.y.getPos(); + + self.setScrollLeft(self.bzscroll.x.getNow()); + self.setScrollTop(self.bzscroll.y.getNow()); + + if (x <= 1) { + self.timer = setAnimationFrame(loop); + } else { + self.scrollrunning = false; + self.timer = 0; + self.triggerScrollEnd(); + } + + }; + + if (!self.scrollrunning) { + self.triggerScrollStart(px, py, x, y, ms); + self.scrollrunning = true; + self.timer = setAnimationFrame(loop); + } + + }; + + this.cancelScroll = function () { + if (self.timer) clearAnimationFrame(self.timer); + self.timer = 0; + self.bzscroll = false; + self.scrollrunning = false; + return self; + }; + + } + + this.doScrollBy = function (stp, relative) { + doScrollRelative(0, stp); + }; + + this.doScrollLeftBy = function (stp, relative) { + doScrollRelative(stp, 0); + }; + + this.doScrollTo = function (pos, relative) { + var ny = (relative) ? Math.round(pos * self.scrollratio.y) : pos; + if (ny < 0) ny = 0; + else if (ny > self.page.maxh) ny = self.page.maxh; + self.cursorfreezed = false; + self.doScrollTop(pos); + }; + + this.checkContentSize = function () { + var pg = self.getContentSize(); + if ((pg.h != self.page.h) || (pg.w != self.page.w)) self.resize(false, pg); + }; + + self.onscroll = function (e) { + if (self.rail.drag) return; + if (!self.cursorfreezed) { + self.synched('scroll', function () { + self.scroll.y = Math.round(self.getScrollTop() / self.scrollratio.y); + if (self.railh) self.scroll.x = Math.round(self.getScrollLeft() / self.scrollratio.x); + self.noticeCursor(); + }); + } + }; + self.bind(self.docscroll, "scroll", self.onscroll); + + this.doZoomIn = function (e) { + if (self.zoomactive) return; + self.zoomactive = true; + + self.zoomrestore = { + style: {} + }; + var lst = ['position', 'top', 'left', 'zIndex', 'backgroundColor', 'marginTop', 'marginBottom', 'marginLeft', 'marginRight']; + var win = self.win[0].style; + for (var a in lst) { + var pp = lst[a]; + self.zoomrestore.style[pp] = (win[pp] !== undefined) ? win[pp] : ''; + } + + self.zoomrestore.style.width = self.win.css('width'); + self.zoomrestore.style.height = self.win.css('height'); + + self.zoomrestore.padding = { + w: self.win.outerWidth() - self.win.width(), + h: self.win.outerHeight() - self.win.height() + }; + + if (cap.isios4) { + self.zoomrestore.scrollTop = $window.scrollTop(); + $window.scrollTop(0); + } + + self.win.css({ + position: (cap.isios4) ? "absolute" : "fixed", + top: 0, + left: 0, + zIndex: globalmaxzindex + 100, + margin: 0 + }); + var bkg = self.win.css("backgroundColor"); + if ("" === bkg || /transparent|rgba\(0, 0, 0, 0\)|rgba\(0,0,0,0\)/.test(bkg)) self.win.css("backgroundColor", "#fff"); + self.rail.css({ + zIndex: globalmaxzindex + 101 + }); + self.zoom.css({ + zIndex: globalmaxzindex + 102 + }); + self.zoom.css('backgroundPosition', '0 -18px'); + self.resizeZoom(); + + if (self.onzoomin) self.onzoomin.call(self); + + return self.cancelEvent(e); + }; + + this.doZoomOut = function (e) { + if (!self.zoomactive) return; + self.zoomactive = false; + + self.win.css("margin", ""); + self.win.css(self.zoomrestore.style); + + if (cap.isios4) { + $window.scrollTop(self.zoomrestore.scrollTop); + } + + self.rail.css({ + "z-index": self.zindex + }); + self.zoom.css({ + "z-index": self.zindex + }); + self.zoomrestore = false; + self.zoom.css('backgroundPosition', '0 0'); + self.onResize(); + + if (self.onzoomout) self.onzoomout.call(self); + + return self.cancelEvent(e); + }; + + this.doZoom = function (e) { + return (self.zoomactive) ? self.doZoomOut(e) : self.doZoomIn(e); + }; + + this.resizeZoom = function () { + if (!self.zoomactive) return; + + var py = self.getScrollTop(); //preserve scrolling position + self.win.css({ + width: $window.width() - self.zoomrestore.padding.w + "px", + height: $window.height() - self.zoomrestore.padding.h + "px" + }); + self.onResize(); + + self.setScrollTop(Math.min(self.page.maxh, py)); + }; + + this.init(); + + $.nicescroll.push(this); + + }; + + // Inspired by the work of Kin Blas + // http://webpro.host.adobe.com/people/jblas/momentum/includes/jquery.momentum.0.7.js + var ScrollMomentumClass2D = function (nc) { + var self = this; + this.nc = nc; + + this.lastx = 0; + this.lasty = 0; + this.speedx = 0; + this.speedy = 0; + this.lasttime = 0; + this.steptime = 0; + this.snapx = false; + this.snapy = false; + this.demulx = 0; + this.demuly = 0; + + this.lastscrollx = -1; + this.lastscrolly = -1; + + this.chkx = 0; + this.chky = 0; + + this.timer = 0; + + this.reset = function (px, py) { + self.stop(); + self.steptime = 0; + self.lasttime = now(); + self.speedx = 0; + self.speedy = 0; + self.lastx = px; + self.lasty = py; + self.lastscrollx = -1; + self.lastscrolly = -1; + }; + + this.update = function (px, py) { + var tm = now(); + self.steptime = tm - self.lasttime; + self.lasttime = tm; + var dy = py - self.lasty; + var dx = px - self.lastx; + var sy = self.nc.getScrollTop(); + var sx = self.nc.getScrollLeft(); + var newy = sy + dy; + var newx = sx + dx; + self.snapx = (newx < 0) || (newx > self.nc.page.maxw); + self.snapy = (newy < 0) || (newy > self.nc.page.maxh); + self.speedx = dx; + self.speedy = dy; + self.lastx = px; + self.lasty = py; + }; + + this.stop = function () { + self.nc.unsynched("domomentum2d"); + if (self.timer) clearTimeout(self.timer); + self.timer = 0; + self.lastscrollx = -1; + self.lastscrolly = -1; + }; + + this.doSnapy = function (nx, ny) { + var snap = false; + + if (ny < 0) { + ny = 0; + snap = true; + } else if (ny > self.nc.page.maxh) { + ny = self.nc.page.maxh; + snap = true; + } + + if (nx < 0) { + nx = 0; + snap = true; + } else if (nx > self.nc.page.maxw) { + nx = self.nc.page.maxw; + snap = true; + } + + (snap) ? self.nc.doScrollPos(nx, ny, self.nc.opt.snapbackspeed) : self.nc.triggerScrollEnd(); + }; + + this.doMomentum = function (gp) { + var t = now(); + var l = (gp) ? t + gp : self.lasttime; + + var sl = self.nc.getScrollLeft(); + var st = self.nc.getScrollTop(); + + var pageh = self.nc.page.maxh; + var pagew = self.nc.page.maxw; + + self.speedx = (pagew > 0) ? Math.min(60, self.speedx) : 0; + self.speedy = (pageh > 0) ? Math.min(60, self.speedy) : 0; + + var chk = l && (t - l) <= 60; + + if ((st < 0) || (st > pageh) || (sl < 0) || (sl > pagew)) chk = false; + + var sy = (self.speedy && chk) ? self.speedy : false; + var sx = (self.speedx && chk) ? self.speedx : false; + + if (sy || sx) { + var tm = Math.max(16, self.steptime); //timeout granularity + + if (tm > 50) { // do smooth + var xm = tm / 50; + self.speedx *= xm; + self.speedy *= xm; + tm = 50; + } + + self.demulxy = 0; + + self.lastscrollx = self.nc.getScrollLeft(); + self.chkx = self.lastscrollx; + self.lastscrolly = self.nc.getScrollTop(); + self.chky = self.lastscrolly; + + var nx = self.lastscrollx; + var ny = self.lastscrolly; + + var onscroll = function () { + var df = ((now() - t) > 600) ? 0.04 : 0.02; + + if (self.speedx) { + nx = Math.floor(self.lastscrollx - (self.speedx * (1 - self.demulxy))); + self.lastscrollx = nx; + if ((nx < 0) || (nx > pagew)) df = 0.10; + } + + if (self.speedy) { + ny = Math.floor(self.lastscrolly - (self.speedy * (1 - self.demulxy))); + self.lastscrolly = ny; + if ((ny < 0) || (ny > pageh)) df = 0.10; + } + + self.demulxy = Math.min(1, self.demulxy + df); + + self.nc.synched("domomentum2d", function () { + + if (self.speedx) { + var scx = self.nc.getScrollLeft(); + // if (scx != self.chkx) self.stop(); + self.chkx = nx; + self.nc.setScrollLeft(nx); + } + + if (self.speedy) { + var scy = self.nc.getScrollTop(); + // if (scy != self.chky) self.stop(); + self.chky = ny; + self.nc.setScrollTop(ny); + } + + if (!self.timer) { + self.nc.hideCursor(); + self.doSnapy(nx, ny); + } + + }); + + if (self.demulxy < 1) { + self.timer = setTimeout(onscroll, tm); + } else { + self.stop(); + self.nc.hideCursor(); + self.doSnapy(nx, ny); + } + }; + + onscroll(); + + } else { + self.doSnapy(self.nc.getScrollLeft(), self.nc.getScrollTop()); + } + + }; + + }; + + + // override jQuery scrollTop + var _scrollTop = jQuery.fn.scrollTop; // preserve original function + + jQuery.cssHooks.pageYOffset = { + get: function (elem, computed, extra) { + var nice = $.data(elem, '__nicescroll') || false; + return (nice && nice.ishwscroll) ? nice.getScrollTop() : _scrollTop.call(elem); + }, + set: function (elem, value) { + var nice = $.data(elem, '__nicescroll') || false; + (nice && nice.ishwscroll) ? nice.setScrollTop(parseInt(value)) : _scrollTop.call(elem, value); + return this; + } + }; + + jQuery.fn.scrollTop = function (value) { + if (value === undefined) { + var nice = (this[0]) ? $.data(this[0], '__nicescroll') || false : false; + return (nice && nice.ishwscroll) ? nice.getScrollTop() : _scrollTop.call(this); + } else { + return this.each(function () { + var nice = $.data(this, '__nicescroll') || false; + (nice && nice.ishwscroll) ? nice.setScrollTop(parseInt(value)) : _scrollTop.call($(this), value); + }); + } + }; + + // override jQuery scrollLeft + var _scrollLeft = jQuery.fn.scrollLeft; // preserve original function + + $.cssHooks.pageXOffset = { + get: function (elem, computed, extra) { + var nice = $.data(elem, '__nicescroll') || false; + return (nice && nice.ishwscroll) ? nice.getScrollLeft() : _scrollLeft.call(elem); + }, + set: function (elem, value) { + var nice = $.data(elem, '__nicescroll') || false; + (nice && nice.ishwscroll) ? nice.setScrollLeft(parseInt(value)) : _scrollLeft.call(elem, value); + return this; + } + }; + + jQuery.fn.scrollLeft = function (value) { + if (value === undefined) { + var nice = (this[0]) ? $.data(this[0], '__nicescroll') || false : false; + return (nice && nice.ishwscroll) ? nice.getScrollLeft() : _scrollLeft.call(this); + } else { + return this.each(function () { + var nice = $.data(this, '__nicescroll') || false; + (nice && nice.ishwscroll) ? nice.setScrollLeft(parseInt(value)) : _scrollLeft.call($(this), value); + }); + } + }; + + var NiceScrollArray = function (doms) { + var self = this; + this.length = 0; + this.name = "nicescrollarray"; + + this.each = function (fn) { + $.each(self, fn); + return self; + }; + + this.push = function (nice) { + self[self.length] = nice; + self.length++; + }; + + this.eq = function (idx) { + return self[idx]; + }; + + if (doms) { + for (var a = 0; a < doms.length; a++) { + var nice = $.data(doms[a], '__nicescroll') || false; + if (nice) { + this[this.length] = nice; + this.length++; + } + } + } + + return this; + }; + + function mplex(el, lst, fn) { + for (var a = 0, l = lst.length; a < l; a++) fn(el, lst[a]); + } + mplex( + NiceScrollArray.prototype, ['show', 'hide', 'toggle', 'onResize', 'resize', 'remove', 'stop', 'doScrollPos'], + function (e, n) { + e[n] = function () { + var args = arguments; + return this.each(function () { + this[n].apply(this, args); + }); + }; + } + ); + + jQuery.fn.getNiceScroll = function (index) { + if (index === undefined) { + return new NiceScrollArray(this); + } else { + return this[index] && $.data(this[index], '__nicescroll') || false; + } + }; + + var pseudos = jQuery.expr.pseudos || jQuery.expr[':']; // jQuery 3 migration + pseudos.nicescroll = function (a) { + return $.data(a, '__nicescroll') !== undefined; + }; + + $.fn.niceScroll = function (wrapper, _opt) { + if (_opt === undefined && typeof wrapper == "object" && !("jquery" in wrapper)) { + _opt = wrapper; + wrapper = false; + } + + var ret = new NiceScrollArray(); + + this.each(function () { + var $this = $(this); + + var opt = $.extend({}, _opt); // cloning + + if (wrapper || false) { + var wrp = $(wrapper); + opt.doc = (wrp.length > 1) ? $(wrapper, $this) : wrp; + opt.win = $this; + } + var docundef = !("doc" in opt); + if (!docundef && !("win" in opt)) opt.win = $this; + + var nice = $this.data('__nicescroll') || false; + if (!nice) { + opt.doc = opt.doc || $this; + nice = new NiceScrollClass(opt, $this); + $this.data('__nicescroll', nice); + } + ret.push(nice); + }); + + return (ret.length === 1) ? ret[0] : ret; + }; + + _win.NiceScroll = { + getjQuery: function () { + return jQuery; + } + }; + + if (!$.nicescroll) { + $.nicescroll = new NiceScrollArray(); + $.nicescroll.options = _globaloptions; + } + +})); \ No newline at end of file diff --git a/pos_mobile/static/src/css/pos_mobile_styles.css b/pos_mobile/static/src/css/pos_mobile_styles.css index a8872eb82f..fe9caff7ad 100644 --- a/pos_mobile/static/src/css/pos_mobile_styles.css +++ b/pos_mobile/static/src/css/pos_mobile_styles.css @@ -1,4 +1,10 @@ /*===================Common styles for mobile version=============================*/ +.ios { + width: 100%; + height: 100%; + position: fixed; + margin: 0; +} .pos.mobile textarea { font-size: 38px; padding: 20px; @@ -61,6 +67,10 @@ height: 96px; background: white; border-bottom: solid 3px rgb(196, 196, 196); + display: -webkit-box; + display: -ms-flexbox; + display: -webkit-flex; + display: flex; } /* a) The left part of the top-bar */ @@ -69,23 +79,28 @@ width: initial; position: relative; vertical-align: top; + display: block; + float: left; } .pos.mobile .pos-branding .username{ - margin-right:0px; margin-left: 20px; - line-height: 22px; + margin-right: 20px; + line-height: 104px; float: left; color: #555555; - word-wrap: break-word; - width: 50px; - font-size: 22px; - padding-top: 15px; + /*word-wrap: break-word;*/ + width: initial; + font-size: 20px; + white-space: nowrap; } /* b) The right part of the top-bar */ .pos.mobile .pos-rightheader { - display: table-cell; + display: block; position: relative; - left: 20px; + left: 0px; + right: 0px; + float: right; + width: 100%; } .pos.mobile .pos-rightheader > * { border-right: 0px; @@ -154,7 +169,6 @@ /* c) The session buttons */ .pos.mobile .pos-rightheader .header-button{ display: inline-block; - float: left; height: 96px; padding-left: 32px; padding-right: 32px; @@ -162,6 +176,10 @@ border-left: 2px solid #292929; line-height: 96px; color: #555555; + position: absolute; + right: 0; + padding: 0px; + width: 80px; } .pos.mobile .pos-rightheader .header-button img{ padding: 10px; @@ -174,11 +192,13 @@ } /* c) The notifications indicator */ .pos.mobile .oe_status{ - display: inline-block; - float: left; padding: 28px; line-height: 40px; font-size: 40px; + float: right; + display: block; + padding-left: 15px; + padding-right: 15px; } .pos.mobile .oe_status .oe_icon{ width:40px; @@ -239,9 +259,12 @@ .pos.mobile .control-buttons { padding: 16px 32px 0px 22px; margin-bottom: -12px; + max-height: 500px; + overflow: hidden; + overflow-y: auto; } .pos.mobile .control-button { - font-size: 28px; + font-size: 48px; margin: 15px; padding: 10px 24px; line-height: 80px; @@ -258,11 +281,18 @@ margin-right: 8px; } .pos.mobile .control-button .control-button-number { - height: 56px; - line-height: 56px; - width: 56px; - margin-left: -32px; + height: 48px; + line-height: 48px; + width: 48px; + margin-left: 0px; margin-right: 8px; + margin-bottom: 13px; + border-radius: 10px; + border: 3px solid rgb(85, 85, 85); + background: initial; + color: rgb(85, 85, 85); + font-size: 40px; + font-weight: initial; } .pos.mobile .control-button.highlight, .pos.mobile .button.highlight { @@ -320,14 +350,14 @@ } .pos.mobile .actionpad .button.pay { height: 315px; + font-size: 48px; } .pos.mobile .actionpad .button.pay .pay-circle { - font-size: 64px; - line-height: 108px; + font-size: 47px; + line-height: 84px; padding-top: 12px; - width: 120px; - border-radius: 60px; - margin-bottom: 20px; + width: 96px; + border-radius: 50%; background: #dbdbdb; color: #555555; } @@ -336,15 +366,17 @@ left: 6px; } .pos.mobile .actionpad .button.set-customer{ - padding-left: 80px; + padding-left: 90px; padding-right: 80px; height: 98px; margin-bottom: 10px; position: relative; + font-size: 48px; } .pos .actionpad .button.set-customer.decentered { - padding-left: 40px; + padding-left: 5px; padding-right: 5px; + font-size: 35px; } .pos.mobile .actionpad .button .fa-user { left: 26px; @@ -357,7 +389,7 @@ .pos.mobile .actionpad .button.set-customer img{ width: 50px; position: absolute; - left: 30px; + left: 25px; top: 20px; } /* ********* The Numpad ********* */ @@ -385,10 +417,6 @@ background: #6EC89B; border-color: transparent; } -.pos.mobile .numpad-backspace img { - height: 42px; - width: 48px; -} .pos.mobile .numpad button:nth-child(4) { border-top-right-radius: 15px; } @@ -399,9 +427,9 @@ font-size: 48px; } .pos.mobile .mode-button { - font-size: 28px; + font-size: 48px; } -.pos.mobile .numpad button:hover { +.pos.mobile .numpad button:active { background: #6EC89B; color: white; border-color: transparent; @@ -449,11 +477,25 @@ padding: 20px; text-align: center; } +.pos.mobile .current-order-qty { + position: absolute; + right: 0; + bottom: 0; + background: red; + color: white; + line-height: 30px; + min-width: 30px; + border-radius: 24px; + margin-right: 8px; + margin-left: -8px; + margin-bottom: 5px; +} /* a) the product list navigation bar */ .pos.mobile .breadcrumb{ line-height: 96px; height: 96px; min-width: 96px; + float: right; } .pos.mobile .breadcrumb:last-child { padding-right: 6px; @@ -470,6 +512,7 @@ } .pos.mobile .breadcrumb-arrow{ width: 56px; + transform: scale(-1, 1); } .pos.mobile .breadcrumb-homeimg { width: 54px; @@ -527,7 +570,7 @@ text-align: center; } .pos .category-list-scroller { - max-height: 311px; + max-height: 342px; } /* d) the category button */ .pos.mobile .category-button { @@ -572,7 +615,17 @@ -webkit-transition-property: background, border; -webkit-transition-duration: 0.2s; -webkit-transition-timing-function: ease-out; - transition: 0.order-scroller2s; + transition: 0.2s; +} +.pos.mobile .category-simple-button.active { + color: white; + background: black; + border-color: black; + + -webkit-transition-property: background, border; + -webkit-transition-duration: 0.2s; + -webkit-transition-timing-function: ease-out; + transition: 0.2s; } .pos.mobile .category-button .category-img { width: 240px; @@ -626,17 +679,19 @@ .pos.mobile .product .product-img img { max-height: 200px; max-width: 220px; - min-width: 128px; + width: 185px; + height: 185px; + vertical-align: top; } .pos.mobile .product .price-tag { - top: 4px; - right: 4px; + top: 0px; + right: 0px; line-height: 26px; padding: 4px 10px; border-radius: 4px; } .pos.mobile .product .product-name { - line-height: 28px; + line-height: 20px; padding: 6px; padding-top:30px; text-align: left; @@ -659,18 +714,17 @@ /* a) Generic Screen Layout Constructs */ .pos.mobile .screen .screen-content{ max-width: 2048px; - background: #f7f7f7; + background: white; } - .pos.mobile .screen .top-content{ background: white; height: 128px; - border-bottom: dashed 2px rgb(215,215,215); + border-bottom: dashed 2px rgb(110,110,110); } .pos.mobile .screen .top-content .button { line-height: 64px; padding: 6px 26px; - font-size: 35px; + font-size: 35px !important; margin: 24px; border: 2px solid #e8e8e8; border-radius: 15px; @@ -694,6 +748,7 @@ } .pos.mobile .screen .top-content .button.next{ font-size: inherit; + } .pos.mobile .screen .left-content{ top: 128px; @@ -727,18 +782,25 @@ /* b) The payment screen */ .pos.mobile .payment-numpad { width: 100%; - max-width: 760px; + max-width: 635px; + padding: 0px; + margin: 0px; + float: right; } .pos.mobile .payment-numpad .numpad { border: none; border-radius: 8px; width: 100% !important; max-width: initial; - margin-left: 5px; + margin: 0px; + margin-top: 15px; } .pos.mobile .payment-numpad .numpad button { - width: 160px; - height: 160px; + width: 145px; + height: 135px; + font-size: 50px; + border-radius: 8px; + box-shadow: 0px 8px 22px -14px black; } .pos.mobile .payment-numpad .numpad button:first-child { border-top-left-radius: 8px; @@ -746,10 +808,21 @@ .pos.mobile .payment-numpad .numpad button:nth-child(16) { border-bottom-left-radius: 8px; } +.pos.mobile .paymentlines { + margin-bottom: 20px; +} .pos.mobile .paymentlines-container { padding: 0px; - border-bottom: dashed 2px gainsboro; - min-height: 308px; + border-bottom: dashed 2px rgb(110,110,110); + max-height: initial; + height: 562px; + overflow: hidden !important; + overflow-y: auto !important; + -webkit-overflow-scrolling: touch!important; +} +.pos.mobile .paymentlines-container .delete-button{ + padding: 0px; + line-height: 65px; } .pos.mobile .paymentlines .controls { width: 80px; @@ -762,15 +835,16 @@ border-radius: 6px; } .pos.mobile .paymentline{ - font-size: 44px; + font-size: 35px; } .pos.mobile .paymentline.selected { font-size: 35px; } .pos.mobile .paymentline.selected .edit { - outline: 6px blue; - box-shadow: 0px 0px 0px 6px #6EC89B; + outline: 1px blue; + box-shadow: 0px 0px 0px 1px #6EC89B; border-radius: 6px; + color: #555555; } .pos.mobile .paymentline > *{ padding: 16px 24px; @@ -792,21 +866,58 @@ width: 100%; display: block; } +.pos.mobile .payment-screen .left-content{ + width: 100%; + display: block; + position: relative; + max-height: 375px; +} +.pos.mobile .payment-screen .left-content.pc40{ + right: initial; + border-bottom: dashed 2px rgb(175,175,175); +} +.pos.mobile .payment-screen .right-content{ + width: 100%; + display: block; + position: relative; +} +.pos.mobile .payment-screen .right-content.pc60{ + left: initial; + height: calc(100% - 128px); +} +.pos.mobile .payment-screen .payment-buttons{ + padding: 0px; + display: block; + max-width: 380px; + width: 100%; + padding-right: 5px; +} .pos.mobile .payment-screen .payment-buttons .button { - line-height: 146px; + line-height: 130px; font-size: 32px; padding: 0px 16px; border: 2px solid #e8e8e8; - border-radius: 15px; - margin: 10px; + border-radius: 8px; box-shadow: 0px 9px 20px -14px black; background: #ededed; font-weight: inherit; + text-align: left; + padding-left: 135px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin: 10px; + margin-left: 20px; + margin-right: 0px; +} +.pos.mobile .payment-screen .payment-buttons i { + text-align: center; } .pos.mobile .payment-screen .payment-buttons .button:first-child { border-top-left-radius: 6px; border-top-right-radius: 6px; border-top-width: 2px; + margin-top: 20px; } .pos.mobile .payment-screen .payment-buttons .button:last-child { border-bottom-left-radius: 6px; @@ -831,6 +942,32 @@ font-size: 128px; text-shadow: 0px 4px white, 0px 4px 4px rgba(0, 0, 0, 0.27); } +.pos.mobile .payment-screen .paymentlines-empty { + margin-bottom: 20px; +} +.pos.mobile .paymentmethods-container { + border-bottom: dashed 3px rgb(188,188,188); + max-height: 375px; + width: 100%; + -webkit-overflow-scrolling: auto!important; +} +.pos.mobile .payment-screen .payment-numpad { + max-width: calc(100% - 380px); +} +.pos.mobile .payment-screen .left-content { + display: none; +} +.pos.mobile .payment-screen .paymentlines-empty .message { + font-weight: bold; + font-size: 30px; +} +.pos.mobile .payment-screen .paymentlines-empty .total { + padding-bottom: 0; + padding-top: 0; +} +.pos.mobile .payment-screen .touch-scrollable{ + overflow: hidden!important; +} /* c) The receipt screen */ .pos.mobile .receipt-screen .centered-content .button { line-height: 80px; @@ -888,9 +1025,19 @@ font-size: 30px; line-height: 50px; } -.pos.mobile .clientlist-screen .scrollable-y{ - overflow: auto !important; - overflow-y: auto !important; +.pos.mobile .clientlist-screen .top-content{ + text-align: left; + margin-left: 15px; + margin-right: 15px; + white-space: nowrap; + overflow: hidden; + overflow-x: auto; + -webkit-overflow-scrolling: touch !important; +} +pos.mobile .clientlist-screen .top-content .button { + margin: 0px; + position: relative; + display: inline-block; } .pos.mobile .clientlist-screen .client-details-left, .pos.mobile .clientlist-screen .client-details-right { width: 100%; @@ -903,6 +1050,10 @@ .pos.mobile .clientlist-screen .client-details{ padding: 32px; border-bottom: solid 10px rgb(110,200,155); + -webkit-transition: 0.5s; + -moz-transition: 0.5s; + -o-transition: 0.5s; + transition: 0.5s; } .pos.mobile .clientlist-screen .client-picture{ height: 128px; @@ -959,8 +1110,13 @@ margin-top: 40px; } .pos.mobile .clientlist-screen .searchbox{ - margin-left: -295px; + margin-left: 0px; margin-top:0px; + position: relative; + display: inline-block; + right: 0px; + float: initial; + left: 0; } .pos.mobile .clientlist-screen .searchbox input{ width: 275px; @@ -969,16 +1125,28 @@ font-size: 35px; } .pos.mobile .clientlist-screen .button.new-customer { - margin-left: 60px; + margin-left: 0px; + left: 0; +} +.pos.mobile .clientlist-screen .button.next { + margin-left: 0px; } .pos.mobile .clientlist-screen .subwindow:nth-child(2) .subwindow-container-fix { - /*height: 100%!important; - max-height: 1000px; - min-height: 700px;*/ border-bottom: 8px solid rgb(110,200,155) !important; + overflow: hidden!important; +} +.ios .pos.mobile .clientlist-screen .subwindow:nth-child(2) .subwindow-container-fix { + overflow: hidden!important; + overflow-y: auto!important; + -webkit-overflow-scrolling: touch!important; +} +.pos.mobile .clientlist-screen .button { + display: inline-block; + position: relative; } -.pos.mobile .clientlist-screen .subwindow-container-fix.touch-scrollable.scrollable-y { - min-height: 900px; + .pos.mobile .clientlist-screen .top-content .button.back { + margin-left: 0; + margin-right: 0; } /* ********* The OrderWidget ********* */ .pos.mobile .order{ @@ -1028,6 +1196,10 @@ .pos.mobile .summary .line .discount .value{ float: none; } +.pos.mobile .summary .line .fiscal{ + display: inline-block; + margin-left:10px; +} /*______________________________________________________*/ .pos.mobile .submit-kitchen-button { padding: 24px 40px; @@ -1055,7 +1227,7 @@ .pos.mobile .splitbill-screen .order-info { margin-bottom:40px; padding: 40px 0px; - font-size: 128px; + font-size: 100px; text-shadow: 0px 4px white, 0px 4px 4px rgba(0, 0, 0, 0.27); border-bottom: dashed 2px rgb(215,215,215); } @@ -1076,26 +1248,37 @@ } .pos.mobile .paymentmethods { margin: 15px; - margin-top: 55px; + display: -webkit-flex; + display: flex; + -webkit-flex-flow: row wrap; + flex-flow: row wrap; } .pos.mobile .paymentmethods .button { line-height: 90px; font-size: 32px; border: 2px solid #e8e8e8; - border-radius: 15px; + border-radius: 5px; margin: 10px; box-shadow: 0px 9px 20px -14px black; background: #ededed; font-weight: inherit; + -webkit-flex-grow: 1; + flex-grow: 1; + width: 400px; + margin-left:30px; + margin-right: 30px; } .pos.mobile .paymentmethods .button:first-child { border-top-width: 2px; - border-top-left-radius: 6px; - border-top-right-radius: 6px; + border-top-left-radius: 5px; + border-top-right-radius: 5px; } .pos.mobile .paymentmethods .button:last-child { - border-bottom-left-radius: 6px; - border-bottom-right-radius: 6px; + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; +} +.pos.mobile .splitbill-screen .left-content{ + border-right: 0px; } /* ********* The ActionBarWidget ********* */ @@ -1307,11 +1490,16 @@ font-size: 28px; } /* ********* Styles for pos with swiper ********* */ -.pos.mobile .screen, .swiper-container { - width: 100%; +.pos.mobile .mobile-order-container, .pos.mobile .swiper-container-order { height: 100%; } -.pos.mobile .order-selector, .pos.mobile .orders.touch-scrollable{ +.pos.mobile .order-selector { + display: flex; + float: left; + max-width: 674px; + white-space: nowrap; +} +.pos.mobile .orders.touch-scrollable{ display: inline-block; float: left; } @@ -1328,7 +1516,7 @@ margin: 0 10px; color: #808080; } -.pos.mobile .slide-categories { +.pos.mobile .mobile-categories { background: white; width: 100%; height: 100%; @@ -1339,12 +1527,16 @@ .pos.mobile .slide-numpad-button img, .pos.mobile .slide-categories-button img, .pos.mobile .slide-order-button img { - width: 86px; + width: 75px; +} +.pos.mobile .swiper-container-numpad { + background: white; } .pos.mobile .slide-numpad { overflow-y: auto; height: 100%; background: white; + border-bottom: 6px solid white; } .pos.mobile .rightpane-header .slide-order-button{ margin-left: 30px; @@ -1364,22 +1556,18 @@ -o-transition: 0.5s; transition: 0.5s; } -.pos.mobile .slide-search { +.pos.mobile .mobile-search-bar { height: 150px; border-top: 2px solid #e8e8e8; border-bottom: 2px solid #e8e8e8; } -.pos.mobile .under-search { +.pos.mobile .mobile-search-bar-menu { max-height:650px; height: 100%; } .pos.mobile .slide-buttons { background: white; } -.pos.mobile .swipe-is-open { - transform: translate3d(0px, 0px, 0px) !important; - transition: 0.3s !important; -} /* New size for order-and-products blocks */ .pos.mobile .swipe-is-open .order-and-products{ height: calc(100% - 654px)!important; @@ -1404,3 +1592,6 @@ .pos.mobile .mobile-active-screen { display: block; } +.pos.mobile .disable { + box-shadow: 0px 0px 0px 0px transparent!important; +} diff --git a/pos_mobile/static/src/img/backspace.png b/pos_mobile/static/src/img/backspace.png new file mode 100644 index 0000000000..ea0b669d80 Binary files /dev/null and b/pos_mobile/static/src/img/backspace.png differ diff --git a/pos_mobile/static/src/js/chrome.js b/pos_mobile/static/src/js/chrome.js index 18d1f42f97..ca15cc814a 100644 --- a/pos_mobile/static/src/js/chrome.js +++ b/pos_mobile/static/src/js/chrome.js @@ -14,21 +14,14 @@ odoo.define('pos_mobile.chrome', function (require) { $('.pos').addClass('mobile'); - // horizontal swiper - this.swiperH = new window.Swiper('.swiper-container-h', { + this.swiper_order = new window.Swiper(".swiper-container-order", { spaceBetween: 0, }); - // vertical swiper - this.swiperV = new window.Swiper('.swiper-container-v', { - direction: 'vertical', - slidesPerView: 'auto', + this.swiper_numpad = new window.Swiper(".swiper-container-numpad", { spaceBetween: 0, }); - // remove all events for vertical swiper - this.swiperV.destroy(false , false); - // move some widgets and screens from screen block to slide blocks var products = $('.rightpane .content-row'); products.detach(); @@ -48,64 +41,73 @@ odoo.define('pos_mobile.chrome', function (require) { var breadcrumbs = $('.breadcrumbs'); breadcrumbs.detach(); - $('.slide-categories').prepend(breadcrumbs); + $('.mobile-categories').prepend(breadcrumbs); var search = $('.rightpane-header'); search.detach(); - $('.slide-search').append(search); + $('.mobile-search-bar').append(search); var buttons = $('.control-buttons'); if (!buttons.hasClass('oe_hidden')) { - this.swiperH[1].appendSlide('
'); + this.swiper_numpad.appendSlide('
'); buttons.detach(); $('.slide-buttons').append(buttons); } - }, - }); - chrome.OrderSelectorWidget.include({ - order_click_handler: function(event,$el) { - this._super(event,$el); - var order = this.get_order_by_uid($el.data('uid')); - if (order) { - this.chrome.swiperH[0].slideTo(0, 0); - } - }, - neworder_click_handler: function(event, $el) { - this._super(event,$el); - this.chrome.swiperH[0].slideTo(0, 0); - }, - deleteorder_click_handler: function(event, $el) { - this._super(event,$el); - this.chrome.swiperH[0].slideTo(0, 0); + var payment_method = $(".payment-screen .paymentmethods-container"); + payment_method.detach(); + $('.payment-screen .paymentlines-container').after(payment_method); + + // element before the closing button in top header + $($('.pos-rightheader .oe_status')[0]).css({'margin-right': '70px'}); + + this.gui.screen_instances.products.order_widget.change_orderlist(); }, }); chrome.HeaderButtonWidget.include({ + confirm_img: '', + cancel_img: '', renderElement: function(){ var self = this; this._super(); if(this.action){ + this.$el.append(this.cancel_img); this.$el.click(function(){ self.change_action(); }); } }, change_action: function() { - var cancel_button = ''; - var confirm_cancel_button = ''; var self = this; if (!this.confirmed_change) { this.$el.text(''); - this.$el.append(confirm_cancel_button); + this.$el.append(self.confirm_img); this.confirmed_change = setTimeout(function(){ self.$el.text(''); - self.$el.append(cancel_button); + self.$el.append(self.cancel_img); self.confirmed_change = false; },2000); } }, }); + chrome.UsernameWidget.include({ + renderElement: function(){ + this._super(); + if (this.gui.screen_instances && this.gui.screen_instances.products && this.gui.screen_instances.products.order_widget) { + this.gui.screen_instances.products.order_widget.change_orderlist(); + } + }, + }); + + chrome.OrderSelectorWidget.include({ + renderElement: function(){ + this._super(); + $('.pos-topheader .nicescroll-rails').remove(); + this.$('.orders').niceScroll(); + } + }); + return chrome; }); diff --git a/pos_mobile/static/src/js/gui.js b/pos_mobile/static/src/js/gui.js index ab7478c1d7..77cb3a19e2 100644 --- a/pos_mobile/static/src/js/gui.js +++ b/pos_mobile/static/src/js/gui.js @@ -16,13 +16,13 @@ odoo.define('pos_mobile.gui', function (require) { this.change_screen_type(current_screen_name); }, change_screen_type: function(current_screen) { - var swiper_order_container = $('.swiper-order-container'); + var order_container = $('.mobile-order-container'); if (current_screen === "products") { - swiper_order_container.addClass('mobile-active-screen'); - swiper_order_container.css({display:''}); + order_container.addClass('mobile-active-screen'); + order_container.css({display:''}); } else { - swiper_order_container.removeClass('mobile-active-screen'); - swiper_order_container.css({display:'none'}); + order_container.removeClass('mobile-active-screen'); + order_container.css({display:'none'}); } if ($('.mobile-active-screen').length) { $('.pos.mobile .window').css({display: 'none'}); @@ -30,10 +30,33 @@ odoo.define('pos_mobile.gui', function (require) { $('.pos.mobile .window').css({display: 'table'}); } if (current_screen === 'clientlist') { - // automatic define max-height for correct calc the height for - // subwindow-container-fix block. The 2 - is height 2px border bottom - var new_height = $('.clientlist-screen .full-content').height() - 2; - $('.clientlist-screen .subwindow-container-fix').css({'max-height': new_height}); + /* + automatically define the height and max-height for the correct + height calculation for the subwindow-container-fix block. 2 - height of the border bottom in px + */ + + var client_detail_height = $('.clientlist-screen .subwindow.collapsed').height(); + var new_height = $('.clientlist-screen .full-content').height() - client_detail_height - 2; + var new_max_height = $('.clientlist-screen .full-content').height() - 2; + $('.clientlist-screen .subwindow-container-fix.touch-scrollable').css({ + 'height': new_height, + 'max-height': new_max_height + }); + // add custom scrolling + if (!this.pos.iOS) { + $('.clientlist-screen .nicescroll-rails').remove(); + $('.clientlist-screen .subwindow-container-fix.touch-scrollable.scrollable-y').niceScroll(); + } + } else if (current_screen === 'payment') { + var height = $('.payment-screen .right-content').height(); + var paymentmethods = $('.payment-screen .paymentmethods-container').height(); + var numpad = $('.payment-screen .payment-numpad').height(); + // automatic define height. 20 the size of the indentation from the bottom block + $('.paymentlines-container').css({height: height - paymentmethods - numpad - 20}); + // add custom scrolling + $('.payment-screen .touch-scrollable').niceScroll({ + horizrailenabled: false, + }); } } }); diff --git a/pos_mobile/static/src/js/mobile.js b/pos_mobile/static/src/js/mobile.js index d2007e62cb..03b405f6f3 100644 --- a/pos_mobile/static/src/js/mobile.js +++ b/pos_mobile/static/src/js/mobile.js @@ -7,6 +7,10 @@ odoo.define('pos_mobile.mobile', function (require) { models.PosModel = models.PosModel.extend({ initialize: function (session, attributes) { this.is_mobile = odoo.is_mobile; + this.iOS = (/(iPad|iPhone|iPod)/g).test(navigator.userAgent); + if (this.iOS) { + $("body").addClass("ios"); + } return PosModelSuper.initialize.call(this, session, attributes); }, }); diff --git a/pos_mobile/static/src/js/screens.js b/pos_mobile/static/src/js/screens.js index fce65c2c9b..7f62d99d53 100644 --- a/pos_mobile/static/src/js/screens.js +++ b/pos_mobile/static/src/js/screens.js @@ -11,6 +11,10 @@ odoo.define('pos_mobile.screens', function (require) { init: function(parent, options){ this._super(parent,options); var self = this; + + this.max_height = $('body').height(); + this.min_height = $('body').height(); + this.click_categories_slide = function(event){ self.change_categories_slide(); }; @@ -20,39 +24,73 @@ odoo.define('pos_mobile.screens', function (require) { this.switch_category_handler = function(event){ self.set_category(self.pos.db.get_category_by_id(Number(this.dataset.categoryId))); self.renderElement(); - self.chrome.swiperH[0].slideTo(1); + self.chrome.swiper_order.slideTo(1); + }; + this.touch_searchbox = function(event) { + if (self.current_bottom_slide) { + self.close_bottom_menu(); + if (self.pos.iOS) { + self.iOSkeyboard(); + } + } + // specific styles for the iOS platform + if (self.pos.iOS) { + if (event.type === "focusout") { + $('.pos.mobile').css({ + height: self.max_height + }); + } + } + }; + + var search_timeout = null; + this.search_handler = function(event){ + if(event.type === "keypress" || event.type === "keydown" || event.keyCode === 46 || event.keyCode === 8){ + // specific styles for the iOS platform + if (self.pos.iOS) { + self.iOSkeyboard(); + } + clearTimeout(search_timeout); + var searchbox = this; + search_timeout = setTimeout(function(){ + self.perform_search(self.category, searchbox.value, event.which === 13); + $('.slide-products .product-list').addClass('iOSkeyboard'); + },70); + } }; }, + iOSkeyboard: function() { + if (window.pageYOffset !== 0 && this.min_height > this.max_height - window.pageYOffset) { + this.min_height = this.max_height - window.pageYOffset; + } + $('body').scrollTop(0); + $('.pos.mobile').css({ + height: this.min_height + }); + }, open_bottom_menu: function() { if (this.current_bottom_slide === "numpad") { - $('.slide-categories').hide(); - $('.order-swiper').removeClass('open-categories-slide'); + $('.mobile-categories').hide(); + $('.mobile-order-container').removeClass('open-categories-slide'); - $('.under-search .swiper-wrapper').show(); - $('.order-swiper').addClass('open-numpad-slide'); + $('.mobile-search-bar-menu .swiper-container-numpad').show(); + $('.mobile-order-container').addClass('open-numpad-slide'); } else if (this.current_bottom_slide === "categories") { - $('.under-search .swiper-wrapper').hide(); - $('.order-swiper').removeClass('open-numpad-slide'); + $('.mobile-search-bar-menu .swiper-container-numpad').hide(); + $('.mobile-order-container').removeClass('open-numpad-slide'); - $('.slide-categories').show(); - $('.order-swiper').addClass('open-categories-slide'); + $('.mobile-categories').show(); + $('.mobile-order-container').addClass('open-categories-slide'); } - - // open under search block - $('.order-swiper').addClass('swipe-is-open'); - var slider = this.chrome.swiperV; - slider.slideNext(); + // open mobile-search-bar-menu + $('.mobile-order-container').addClass('swipe-is-open'); }, close_bottom_menu: function() { - // close under search block - var slider = this.chrome.swiperV; - slider.slidePrev(); - // remove class - $('.order-swiper').removeClass('open-numpad-slide'); - $('.order-swiper').removeClass('open-categories-slide'); - $('.order-swiper').removeClass('swipe-is-open'); + $('.mobile-order-container').removeClass('open-numpad-slide'); + $('.mobile-order-container').removeClass('open-categories-slide'); + $('.mobile-order-container').removeClass('swipe-is-open'); this.current_bottom_slide = false; }, change_categories_slide: function() { @@ -71,9 +109,8 @@ odoo.define('pos_mobile.screens', function (require) { this.close_bottom_menu(); this.current_bottom_slide = "numpad"; this.open_bottom_menu(); - // start slide is numpad slide - var slider = this.chrome.swiperH[1]; + var slider = this.chrome.swiper_numpad; slider.slideTo(0); } }, @@ -84,42 +121,70 @@ odoo.define('pos_mobile.screens', function (require) { this.el.querySelector('.slide-categories-button').addEventListener('click', this.click_categories_slide); this.el.querySelector('.slide-numpad-button').addEventListener('click', this.click_numpad_slide); + $('.searchbox input').on("focusout", self.touch_searchbox); + $('.searchbox input').on("focus input", self.touch_searchbox); + $('.searchbox').click(function(){ + self.reset_category(); + }); var breadcrumbs = $('.window .rightpane .breadcrumbs'); - if (breadcrumbs.length) { - breadcrumbs.detach(); - $(".slide-categories .breadcrumbs").detach(); - $(".slide-categories").prepend(breadcrumbs); + var active_category_id = $( ".categories .active").data( "category-id" ); + $('.categories span').removeClass('active'); + if (this.category && this.category.child_id && !this.category.child_id.length) { + if (active_category_id === this.category.id) { + if (this.category.parent_id) { + this.set_category(this.pos.db.get_category_by_id(this.category.parent_id[0])); + this.renderElement(); + } else { + this.reset_category(); + } + } else { + $('.categories span[data-category-id='+ this.category.id + ']').addClass('active'); + } + } else { + if (breadcrumbs.length) { + breadcrumbs.detach(); + $(".mobile-categories .breadcrumbs").detach(); + $(".mobile-categories").prepend(breadcrumbs); + } + var categories = $('.window .rightpane .categories'); + categories.detach(); + $(".mobile-categories .categories").detach(); + $(".mobile-categories").append(categories); } - var categories = $('.window .rightpane .categories'); - categories.detach(); - $(".slide-categories .categories").detach(); - $(".slide-categories").append(categories); + if (!this.pos.iOS) { + $('.product-list-scroller').niceScroll({ + horizrailenabled: false, + }); + } }, perform_search: function(category, query, buy_result){ this._super.apply(this, arguments); if (query) { - this.chrome.swiperH[0].slideTo(1); + this.chrome.swiper_order.slideTo(1); } }, clear_search: function(){ this._super(); - var parent = $(".pos.mobile .swiper-container .rightpane-header")[0]; + this.reset_category(); + var parent = $(".pos.mobile .mobile-order-container .rightpane-header")[0]; var input = parent.querySelector('.searchbox input'); input.value = ''; input.focus(); }, get_image_url: function(category){ return window.location.origin + '/web/image?model=pos.category&field=image&id='+category.id; - }, + } }); screens.ProductScreenWidget.include({ click_product: function(product) { this._super.apply(this, arguments); - // adds click effect - if ($('.product-count')) { - $('.product-count').remove(); + var $qty = $('span[data-product-id="'+product.id+'"] .current-order-qty'); + var order = this.pos.get_order(); + var qty = order.get_quantity_by_product_id(product.id); + if (qty) { + $qty.html(qty); } var $p = $('span[data-product-id="'+product.id+'"]'); $($p).animate({ @@ -131,24 +196,14 @@ odoo.define('pos_mobile.screens', function (require) { }); var $pi = $('span[data-product-id="'+product.id+'"] img'); $($pi).animate({ - 'max-height': '240px', - 'min-width': '200px', + 'height': '220px', + 'width': '220px', }, 200, function(){ $($pi).animate({ - 'max-height': '200px', - 'min-width': '128px', + 'height': '185px', + 'width': '185px', }, 400); }); - var order = this.pos.get_order(); - var qty = order.get_quantity_by_product_id(product.id); - $p.append(''+qty+''); - var count = $($p.children()[2]); - count.animate({ - 'font-size': '150px', - 'top': '-40%', - }, 400, function(){ - count.remove(); - }); }, }); @@ -156,6 +211,24 @@ odoo.define('pos_mobile.screens', function (require) { partner_icon_url: function(id){ return '/web/image?model=res.partner&id='+id+'&field=image_medium'; }, + show: function(){ + this._super(); + var self = this; + var search_timeout = null; + this.$('.searchbox input').on('keydown',function(event){ + clearTimeout(search_timeout); + + var searchbox = this; + + search_timeout = setTimeout(function(){ + self.perform_search(searchbox.value, event.which === 13); + },70); + }); + // new the 'next' button position + var next_button = this.$('.next'); + next_button.detach(); + this.$('.new-customer').after(next_button); + } }); screens.NumpadWidget.include({ @@ -171,13 +244,80 @@ odoo.define('pos_mobile.screens', function (require) { }, }); + screens.PaymentScreenWidget.include({ + renderElement: function(){ + this._super(); + var payment_method = $(".payment-screen .paymentmethods-container"); + payment_method.detach(); + $('.payment-screen .paymentlines-container').after(payment_method); + } + }); + screens.OrderWidget.include({ renderElement: function(scrollbottom){ this._super(scrollbottom); var summary = $('.pos.mobile .order-container .summary.clearfix'); summary.detach(); $('.pos.mobile .order-container').append(summary); + if (!this.pos.iOS) { + $('.order-scroller').niceScroll({ + horizrailenabled: false, + }); + } }, + change_selected_order: function() { + this._super(); + // go to slide of order + this.pos.chrome.swiper_order.slideTo(0, 0); + // close bottom menu after open new order + if (this.getParent() && this.getParent().product_categories_widget) { + this.getParent().product_categories_widget.close_bottom_menu(); + } + this.change_product_qty(); + this.scroll_to_selected_order(); + this.change_orderlist(); + }, + orderline_change: function(line) { + this._super(line); + this.change_product_qty(line.product.id); + }, + change_product_qty: function(product_id) { + var order = this.pos.get_order(); + if (order) { + // update the products qty for current order + var products = this.pos.gui.screen_instances.products.product_list_widget.product_list; + + // if the product_id is exist then update only this product + if (product_id) { + products = [this.pos.db.get_product_by_id(product_id)]; + } + products.forEach(function(product){ + var $qty = $('span[data-product-id="'+product.id+'"] .current-order-qty'); + var qty = order.get_quantity_by_product_id(product.id); + $qty.html(''); + if (qty) { + $qty.html(qty); + } + }); + } + }, + scroll_to_selected_order: function() { + var orders = this.pos.get('orders'); + var selected_order = this.pos.get_order(); + var width = orders.indexOf(selected_order); + $('.pos-rightheader .orders.touch-scrollable').scrollLeft(105 * width); + }, + change_orderlist: function() { + var width = 0; + var header_width = $('.pos.mobile .pos-rightheader').width(); + $('.pos.mobile .pos-rightheader').children().each(function(index, el) { + if (!$(el).hasClass('order-selector')) { + width += $(el).width(); + width += 3; + } + }); + $('.pos.mobile .order-selector').css({'max-width': header_width - width - 70}); + } }); return screens; }); diff --git a/pos_mobile/static/src/xml/pos.xml b/pos_mobile/static/src/xml/pos.xml index c5e630b525..fc96efadad 100644 --- a/pos_mobile/static/src/xml/pos.xml +++ b/pos_mobile/static/src/xml/pos.xml @@ -3,23 +3,21 @@ -
-
-
-
-
-
-
-
+
+
+
+
+
+
- -