From a8497e07c97fc89ba2bc1296616dfeb301f80b81 Mon Sep 17 00:00:00 2001 From: Dharmraj Jhala Date: Mon, 16 Jun 2014 16:00:06 +0530 Subject: [PATCH] [ADD] added instantclick library to load data faster in anonymous mode --- addons/website_instantclick/__init__.py | 0 addons/website_instantclick/__openerp__.py | 17 + .../static/lib/instantclick/instantclick.js | 430 ++++++++++++++++++ .../views/website_instantclick.xml | 18 + addons/website_sale/views/templates.xml | 4 +- 5 files changed, 467 insertions(+), 2 deletions(-) create mode 100644 addons/website_instantclick/__init__.py create mode 100644 addons/website_instantclick/__openerp__.py create mode 100644 addons/website_instantclick/static/lib/instantclick/instantclick.js create mode 100644 addons/website_instantclick/views/website_instantclick.xml diff --git a/addons/website_instantclick/__init__.py b/addons/website_instantclick/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/addons/website_instantclick/__openerp__.py b/addons/website_instantclick/__openerp__.py new file mode 100644 index 00000000000..5047bdd0979 --- /dev/null +++ b/addons/website_instantclick/__openerp__.py @@ -0,0 +1,17 @@ +{ + 'name': 'Website Instantclick', + 'category': 'Website', + 'summary': 'Preloads and speeds up website on public browsing of the website using Instantclick.', + 'version': '1.0', + 'description': """ +Preloads data on anonymous mode of website +========================================== + + """, + 'author': 'OpenERP SA', + 'depends': ['website'], + 'installable': True, + 'data': [ + 'views/website_instantclick.xml', + ], +} diff --git a/addons/website_instantclick/static/lib/instantclick/instantclick.js b/addons/website_instantclick/static/lib/instantclick/instantclick.js new file mode 100644 index 00000000000..e5e4e3447d3 --- /dev/null +++ b/addons/website_instantclick/static/lib/instantclick/instantclick.js @@ -0,0 +1,430 @@ +/* InstantClick 2.1 | (C) 2014 Alexandre Dieulot | http://instantclick.io/license.html */ +var InstantClick = function(document, location) { + // Internal variables + var $currentLocationWithoutHash + var $urlToPreload + var $preloadTimer + + // Preloading-related variables + var $history = {} + var $xhr + var $url = false + var $title = false + var $hasBody = true + var $body = false + var $timing = {} + var $isPreloading = false + var $isWaitingForCompletion = false + + // Variables defined by public functions + var $useWhitelist + var $preloadOnMousedown + var $delayBeforePreload + var $eventsCallbacks = { + change: [] + } + + + ////////// HELPERS ////////// + + + function removeHash(url) { + var index = url.indexOf('#') + if (index < 0) { + return url + } + return url.substr(0, index) + } + + function getLinkTarget(target) { + while (target.nodeName != 'A') { + target = target.parentNode + } + return target + } + + function triggerPageEvent(eventType) { + for (var i = 0; i < $eventsCallbacks[eventType].length; i++) { + $eventsCallbacks[eventType][i]() + } + } + + function changePage(title, body, newUrl, scrollY_) { + var doc = document.implementation.createHTMLDocument('') + doc.documentElement.innerHTML = body + document.documentElement.replaceChild(doc.body, document.body) + /* We cannot just use `document.body = doc.body` as it causes Safari 5.1, 6.0, + and Mobile 7.0 to execute script tags directly. + */ + + var elem = document.createElement('i') + elem.innerHTML = title + document.title = elem.textContent + + if (newUrl) { + history.pushState(null, null, newUrl) + + var hashIndex = newUrl.indexOf('#') + var hashElem = hashIndex > -1 && document.getElementById(newUrl.substr(hashIndex + 1)) + var offset = 0 + if (hashElem) { + for (; hashElem.offsetParent; hashElem = hashElem.offsetParent) { + offset += hashElem.offsetTop + } + } + scrollTo(0, offset) + + $currentLocationWithoutHash = removeHash(newUrl) + } + else { + scrollTo(0, scrollY_) + } + + instantanize() + + triggerPageEvent('change') + } + + function setPreloadingAsHalted() { + $isPreloading = false + $isWaitingForCompletion = false + } + + + ////////// EVENT HANDLERS ////////// + + + function mousedown(e) { + preload(getLinkTarget(e.target).href) + } + + function mouseover(e) { + var a = getLinkTarget(e.target) + a.addEventListener('mouseout', mouseout) + if (!$delayBeforePreload) { + preload(a.href) + } + else { + $urlToPreload = a.href + $preloadTimer = setTimeout(preload, $delayBeforePreload) + } + } + + function click(e) { + if (e.which > 1 || e.metaKey || e.ctrlKey) { // Opening in new tab + return + } + e.preventDefault() + display(getLinkTarget(e.target).href) + } + + function mouseout() { + if ($preloadTimer) { + clearTimeout($preloadTimer) + $preloadTimer = false + return + } + + if (!$isPreloading || $isWaitingForCompletion) { + return + } + $xhr.abort() + setPreloadingAsHalted() + } + + function readystatechange() { + if ($xhr.readyState < 4) { + return + } + if ($xhr.status == 0) { + /* Request aborted */ + return + } + + $timing.ready = +new Date - $timing.start + + var text = $xhr.responseText + + var titleIndex = text.indexOf(' -1) { + $title = text.substr(text.indexOf('>', titleIndex) + 1) + $title = $title.substr(0, $title.indexOf(' -1) { + $body = text.substr(bodyIndex) + var closingIndex = $body.indexOf(' -1) { + $body = $body.substr(0, closingIndex) + } + + var urlWithoutHash = removeHash($url) + $history[urlWithoutHash] = { + body: $body, + title: $title, + scrollY: urlWithoutHash in $history ? $history[urlWithoutHash].scrollY : 0 + } + } + else { + $hasBody = false + } + + if ($isWaitingForCompletion) { + $isWaitingForCompletion = false + display($url) + } + } + + + ////////// MAIN FUNCTIONS ////////// + + + function instantanize(isInitializing) { + var as = document.getElementsByTagName('a'), a, domain = location.protocol + '//' + location.host + for (var i = as.length - 1; i >= 0; i--) { + a = as[i] + if (a.target || // target="_blank" etc. + a.hasAttribute('download') || + a.href.indexOf(domain + '/') != 0 || // another domain (or no href attribute) + a.href.indexOf('#') > -1 && removeHash(a.href) == $currentLocationWithoutHash || // link to an anchor + ($useWhitelist ? !a.hasAttribute('data-instant') : a.hasAttribute('data-no-instant'))) { + continue + } + if ($preloadOnMousedown) { + a.addEventListener('mousedown', mousedown) + } + else { + a.addEventListener('mouseover', mouseover) + } + a.addEventListener('click', click) + } + if (!isInitializing) { + var scripts = document.getElementsByTagName('script'), script, copy, parentNode, nextSibling + for (i = 0, j = scripts.length; i < j; i++) { + script = scripts[i] + if (script.hasAttribute('data-no-instant')) { + continue + } + copy = document.createElement('script') + if (script.src) { + copy.src = script.src + } + if (script.innerHTML) { + copy.innerHTML = script.innerHTML + } + parentNode = script.parentNode + nextSibling = script.nextSibling + parentNode.removeChild(script) + parentNode.insertBefore(copy, nextSibling) + } + } + } + + function preload(url) { + if (!$preloadOnMousedown && 'display' in $timing && +new Date - ($timing.start + $timing.display) < 100) { + /* After a page is displayed, if the user's cursor happens to be above a link + a mouseover event will be in most browsers triggered automatically, and in + other browsers it will be triggered when the user moves his mouse by 1px. + + Here are the behavior I noticed, all on Windows: + - Safari 5.1: auto-triggers after 0 ms + - IE 11: auto-triggers after 30-80 ms (looks like it depends on page's size) + - Firefox: auto-triggers after 10 ms + - Opera 18: auto-triggers after 10 ms + + - Chrome: triggers when cursor moved + - Opera 12.16: triggers when cursor moved + + To remedy to this, we do not start preloading if last display occurred less than + 100 ms ago. If they happen to click on the link, they will be redirected. + */ + + return + } + if ($preloadTimer) { + $clearTimeout($preloadTimer) + $preloadTimer = false + } + + if (!url) { + url = $urlToPreload + } + + if ($isPreloading && (url == $url || $isWaitingForCompletion)) { + return + } + $isPreloading = true + $isWaitingForCompletion = false + + $url = url + $body = false + $hasBody = true + $timing = { + start: +new Date + } + $xhr.open('GET', url) + $xhr.send() + } + + function display(url) { + if (!('display' in $timing)) { + $timing.display = +new Date - $timing.start + } + if ($preloadTimer) { + /* Happens when there’s a delay before preloading and that delay + hasn't expired (preloading didn't kick in). + */ + + if ($url && $url != url) { + /* Happens when the user clicks on a link before preloading + kicks in while another link is already preloading. + */ + + location.href = url + return + } + preload(url) + $isWaitingForCompletion = true + return + } + if (!$isPreloading || $isWaitingForCompletion) { + /* If the page isn't preloaded, it likely means + the user has focused on a link (with his Tab + key) and then pressed Return, which triggered a click. + Because very few people do this, it isn't worth handling this + case and preloading on focus (also, focusing on a link + doesn't mean it's likely that you'll "click" on it), so we just + redirect them when they "click". + It could also mean the user hovered over a link less than 100 ms + after a page display, thus we didn't start the preload (see + comments in `preload()` for the rationale behind this.) + + If the page is waiting for completion, the user clicked twice + while the page was preloading. + Two possibilities: + 1) He clicks on the same link again, either because it's slow + to load (there's no browser loading indicator with + InstantClick, so he might think his click hasn't registered + if the page isn't loading fast enough) or because he has + a habit of double clicking on the web; + 2) He clicks on another link. + + In the first case, we redirect him (send him to the page the old + way) so that he can have the browser's loading indicator back. + In the second case, we redirect him because we haven't preloaded + that link, since we were already preloading the last one. + + Determining if it's a double click might be overkill as there is + (hopefully) not that many people that double click on the web. + Fighting against the perception that the page is stuck is + interesting though, a seemingly good way to do that would be to + later incorporate a progress bar. + */ + + location.href = url + return + } + if (!$hasBody) { + location.href = $url + return + } + if (!$body) { + $isWaitingForCompletion = true + return + } + $history[$currentLocationWithoutHash].scrollY = pageYOffset + setPreloadingAsHalted() + changePage($title, $body, $url) + } + + + ////////// PUBLIC VARIABLE AND FUNCTIONS ////////// + + + var supported = 'pushState' in history + + function init() { + if ($currentLocationWithoutHash) { + /* Already initialized */ + return + } + if (!supported) { + triggerPageEvent('change') + return + } + for (var i = arguments.length - 1; i >= 0; i--) { + var arg = arguments[i] + if (arg === true) { + $useWhitelist = true + } + else if (arg == 'mousedown') { + $preloadOnMousedown = true + } + else if (typeof arg == 'number') { + $delayBeforePreload = arg + } + } + $currentLocationWithoutHash = removeHash(location.href) + $history[$currentLocationWithoutHash] = { + body: document.body.outerHTML, + title: document.title, + scrollY: pageYOffset + } + $xhr = new XMLHttpRequest() + $xhr.addEventListener('readystatechange', readystatechange) + + instantanize(true) + + triggerPageEvent('change') + + addEventListener('popstate', function() { + var loc = removeHash(location.href) + if (loc == $currentLocationWithoutHash) { + return + } + if (!(loc in $history)) { + location.href = location.href // Reloads the page and makes use of cache for assets, unlike location.reload() + return + } + $history[$currentLocationWithoutHash].scrollY = pageYOffset + $currentLocationWithoutHash = loc + changePage($history[loc].title, $history[loc].body, false, $history[loc].scrollY) + }) + } + + function on(eventType, callback) { + $eventsCallbacks[eventType].push(callback) + } + + /* The debug function isn't included by default to reduce file size. + To enable it, add a slash at the beginning of the comment englobing + the debug function, and uncomment "debug: debug," in the return + statement below the function. */ + + /* + function debug() { + return { + currentLocationWithoutHash: $currentLocationWithoutHash, + history: $history, + xhr: $xhr, + url: $url, + title: $title, + hasBody: $hasBody, + body: $body, + timing: $timing, + isPreloading: $isPreloading, + isWaitingForCompletion: $isWaitingForCompletion + } + } + //*/ + + + return { + // debug: debug, + supported: supported, + init: init, + on: on + } + +}(document, location); diff --git a/addons/website_instantclick/views/website_instantclick.xml b/addons/website_instantclick/views/website_instantclick.xml new file mode 100644 index 00000000000..39afa93a323 --- /dev/null +++ b/addons/website_instantclick/views/website_instantclick.xml @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml index 470ca8383f4..c43363b863e 100644 --- a/addons/website_sale/views/templates.xml +++ b/addons/website_sale/views/templates.xml @@ -664,7 +664,7 @@
- + @@ -673,7 +673,7 @@ t-att-data-product-id="line.product_id.id" t-att-value="int(line.product_uom_qty)"/> - +