addtohomescreen.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750
  1. /* Add to Homescreen v3.2.3 ~ (c) 2015 Matteo Spinelli ~ @license: http://cubiq.org/license */
  2. (function (window, document) {
  3. /*
  4. _ _ _____ _____
  5. ___ _| |_| |_ _|___| | |___ _____ ___ ___ ___ ___ ___ ___ ___
  6. | .'| . | . | | | | . | | . | | -_|_ -| _| _| -_| -_| |
  7. |__,|___|___| |_| |___|__|__|___|_|_|_|___|___|___|_| |___|___|_|_|
  8. by Matteo Spinelli ~ http://cubiq.org
  9. */
  10. // Check for addEventListener browser support (prevent errors in IE<9)
  11. var _eventListener = 'addEventListener' in window;
  12. // Check if document is loaded, needed by autostart
  13. var _DOMReady = false;
  14. if ( document.readyState === 'complete' ) {
  15. _DOMReady = true;
  16. } else if ( _eventListener ) {
  17. window.addEventListener('load', loaded, false);
  18. }
  19. function loaded () {
  20. window.removeEventListener('load', loaded, false);
  21. _DOMReady = true;
  22. }
  23. // regex used to detect if app has been added to the homescreen
  24. var _reSmartURL = /\/ath(\/)?$/;
  25. var _reQueryString = /([\?&]ath=[^&]*$|&ath=[^&]*(&))/;
  26. // singleton
  27. var _instance;
  28. function ath (options) {
  29. _instance = _instance || new ath.Class(options);
  30. return _instance;
  31. }
  32. // message in all supported languages
  33. ath.intl = {
  34. cs_cs: {
  35. ios: 'Pro přidáni této webové aplikace na úvodní obrazovku: stlačte %icon a pak <strong>Přidat na úvodní obrazovku</strong>.',
  36. android: 'Pro přidáni této webové aplikace na úvodní obrazovku otevřete menu nastavení prohlížeče a stlačte <strong>Přidat na úvodní obrazovku</strong>. <small>K menu se dostanete stlačením hardwaroveho tlačítka, když ho vaše zařízení má, nebo stlačením pravé horní menu ikony <span class="ath-action-icon">icon</span>.</small>'
  37. },
  38. de_de: {
  39. ios: 'Um diese Web-App zum Home-Bildschirm hinzuzufügen, tippen Sie auf %icon und dann <strong>Zum Home-Bildschirm</strong>.',
  40. android: 'Um diese Web-App zum Home-Bildschirm hinzuzufügen, öffnen Sie das Menü und tippen dann auf <strong>Zum Startbildschirm hinzufügen</strong>. <small>Wenn Ihr Gerät eine Menütaste hat, lässt sich das Browsermenü über diese öffnen. Ansonsten tippen Sie auf %icon.</small>'
  41. },
  42. da_dk: {
  43. ios: 'For at tilføje denne web app til hjemmeskærmen: Tryk %icon og derefter <strong>Føj til hjemmeskærm</strong>.',
  44. android: 'For at tilføje denne web app til hjemmeskærmen, åbn browser egenskaber menuen og tryk på <strong>Føj til hjemmeskærm</strong>. <small>Denne menu kan tilgås ved at trykke på menu knappen, hvis din enhed har en, eller ved at trykke på det øverste højre menu ikon %icon.</small>'
  45. },
  46. el_gr: {
  47. ios: 'Για να προσθέσετε την εφαρμογή στην αρχική οθόνη: πατήστε το %icon και μετά <strong>Πρόσθεσε στην αρχική οθόνη</strong>.',
  48. android: 'Για να προσθέσετε την εφαρμογή στην αρχική οθόνη, ανοίξτε τις επιλογές του browser σας και πατήστε το <strong>Προσθήκη στην αρχική οθόνη</strong>. <small>Μπορείτε να έχετε πρόσβαση στο μενού, πατώντας το κουμπί του μενού του κινητού σας ή το πάνω δεξιά κουμπί του μενού %icon.</small>'
  49. },
  50. en_us: {
  51. ios: 'To add this web app to the home screen: tap %icon and then <strong>Add to Home Screen</strong>.',
  52. android: 'To add this web app to the home screen open the browser option menu and tap on <strong>Add to homescreen</strong>. <small>The menu can be accessed by pressing the menu hardware button if your device has one, or by tapping the top right menu icon %icon.</small>'
  53. },
  54. es_es: {
  55. ios: 'Para añadir esta aplicación web a la pantalla de inicio: pulsa %icon y selecciona <strong>Añadir a pantalla de inicio</strong>.',
  56. android: 'Para añadir esta aplicación web a la pantalla de inicio, abre las opciones y pulsa <strong>Añadir a pantalla inicio</strong>. <small>El menú se puede acceder pulsando el botón táctil en caso de tenerlo, o bien el icono de la parte superior derecha de la pantalla %icon.</small>'
  57. },
  58. fi_fi: {
  59. ios: 'Liitä tämä sovellus kotivalikkoon: klikkaa %icon ja tämän jälkeen <strong>Lisää kotivalikkoon</strong>.',
  60. android: 'Lisätäksesi tämän sovelluksen aloitusnäytölle, avaa selaimen valikko ja klikkaa tähti -ikonia tai <strong>Lisää aloitusnäytölle tekstiä</strong>. <small>Valikkoon pääsee myös painamalla menuvalikkoa, jos laitteessasi on sellainen tai koskettamalla oikealla yläkulmassa menu ikonia %icon.</small>'
  61. },
  62. fr_fr: {
  63. ios: 'Pour ajouter cette application web sur l\'écran d\'accueil : Appuyez %icon et sélectionnez <strong>Ajouter sur l\'écran d\'accueil</strong>.',
  64. android: 'Pour ajouter cette application web sur l\'écran d\'accueil : Appuyez sur le bouton "menu", puis sur <strong>Ajouter sur l\'écran d\'accueil</strong>. <small>Le menu peut-être accessible en appuyant sur le bouton "menu" du téléphone s\'il en possède un <i class="fa fa-bars"></i>. Sinon, il se trouve probablement dans la coin supérieur droit du navigateur %icon.</small>'
  65. },
  66. he_il: {
  67. ios: '<span dir="rtl">להוספת האפליקציה למסך הבית: ללחוץ על %icon ואז <strong>הוסף למסך הבית</strong>.</span>',
  68. android: 'To add this web app to the home screen open the browser option menu and tap on <strong>Add to homescreen</strong>. <small>The menu can be accessed by pressing the menu hardware button if your device has one, or by tapping the top right menu icon %icon.</small>'
  69. },
  70. hu_hu: {
  71. ios: 'Ha hozzá szeretné adni ezt az alkalmazást a kezdőképernyőjéhez, érintse meg a következő ikont: %icon , majd a <strong>Hozzáadás a kezdőképernyőhöz</strong> menüpontot.',
  72. android: 'Ha hozzá szeretné adni ezt az alkalmazást a kezdőképernyőjéhez, a böngésző menüjében kattintson a <strong>Hozzáadás a kezdőképernyőhöz</strong> menüpontra. <small>A böngésző menüjét a következő ikon megérintésével tudja megnyitni: %icon.</small>'
  73. },
  74. it_it: {
  75. ios: 'Per aggiungere questa web app alla schermata iniziale: premi %icon e poi <strong>Aggiungi a Home</strong>.',
  76. android: 'Per aggiungere questa web app alla schermata iniziale, apri il menu opzioni del browser e premi su <strong>Aggiungi alla homescreen</strong>. <small>Puoi accedere al menu premendo il pulsante hardware delle opzioni se la tua device ne ha uno, oppure premendo l\'icona %icon in alto a destra.</small>'
  77. },
  78. ja_jp: {
  79. ios: 'このウェプアプリをホーム画面に追加するには、%iconをタップして<strong>ホーム画面に追加</strong>してください。',
  80. android: 'このウェプアプリをホーム画面に追加するには、ブラウザのオプションメニューから<strong>ホーム画面に追加</strong>をタップしてください。<small>オプションメニューは、一部の機種ではデバイスのメニューボタンから、それ以外では画面右上の%iconからアクセスできます。</small>'
  81. },
  82. ko_kr: {
  83. ios: '홈 화면에 바로가기 생성: %icon 을 클릭한 후 <strong>홈 화면에 추가</strong>.',
  84. android: '브라우저 옵션 메뉴의 <string>홈 화면에 추가</string>를 클릭하여 홈화면에 바로가기를 생성할 수 있습니다. <small>옵션 메뉴는 장치의 메뉴 버튼을 누르거나 오른쪽 상단의 메뉴 아이콘 %icon을 클릭하여 접근할 수 있습니다.</small>'
  85. },
  86. nb_no: {
  87. ios: 'For å installere denne appen på hjem-skjermen: trykk på %icon og deretter <strong>Legg til på Hjem-skjerm</strong>.',
  88. android: 'For å legge til denne webappen på startsiden åpner en nettlesermenyen og velger <strong>Legg til på startsiden</strong>. <small>Menyen åpnes ved å trykke på den fysiske menyknappen hvis enheten har det, eller ved å trykke på menyikonet øverst til høyre %icon.</small>'
  89. },
  90. pt_br: {
  91. ios: 'Para adicionar este app à tela de início: clique %icon e então <strong>Tela de início</strong>.',
  92. android: 'Para adicionar este app à tela de início, abra o menu de opções do navegador e selecione <strong>Adicionar à tela inicial</strong>. <small>O menu pode ser acessado pressionando o "menu" button se o seu dispositivo tiver um, ou selecionando o ícone %icon no canto superior direito.</small>'
  93. },
  94. pt_pt: {
  95. ios: 'Para adicionar esta app ao ecrã principal: clique %icon e depois <strong>Ecrã principal</strong>.',
  96. android: 'Para adicionar esta app web ecrã principal, abra o menu de opções do navegador e selecione <strong>Adicionar à tela inicial</strong>. <small>O menu pode ser acessado pressionando o "menu" button se o seu dispositivo tiver um, ou selecionando o ícone %icon no canto superior direito.</small>'
  97. },
  98. nl_nl: {
  99. ios: 'Om deze webapp aan je startscherm toe te voegen, klik op %icon en dan <strong>Zet in startscherm</strong>.',
  100. android: 'Om deze webapp aan je startscherm toe te voegen, open de browserinstellingen en tik op <strong>Toevoegen aan startscherm</strong>. <small>Gebruik de "menu" knop als je telefoon die heeft, anders het menu-icoon rechtsbovenin %icon.</small>'
  101. },
  102. ru_ru: {
  103. ios: 'Чтобы добавить этот сайт на свой домашний экран, нажмите на иконку %icon и затем <strong>На экран "Домой"</strong>.',
  104. android: 'Чтобы добавить сайт на свой домашний экран, откройте меню браузера и нажмите на <strong>Добавить на главный экран</strong>. <small>Меню можно вызвать, нажав на кнопку меню вашего телефона, если она есть. Или найдите иконку сверху справа %icon[иконка].</small>'
  105. },
  106. sk_sk: {
  107. ios: 'Pre pridanie tejto webovej aplikácie na úvodnú obrazovku: stlačte %icon a potom <strong>Pridať na úvodnú obrazovku</strong>.',
  108. android: 'Pre pridanie tejto webovej aplikácie na úvodnú obrazovku otvorte menu nastavenia prehliadača a stlačte <strong>Pridať na úvodnú obrazovku</strong>. <small>K menu sa dostanete stlačením hardwaroveho tlačidla, ak ho vaše zariadenie má, alebo stlačením pravej hornej menu ikony <span class="ath-action-icon">icon</span>.</small>'
  109. },
  110. sv_se: {
  111. ios: 'För att lägga till denna webbapplikation på hemskärmen: tryck på %icon och därefter <strong>Lägg till på hemskärmen</strong>.',
  112. android: 'För att lägga till den här webbappen på hemskärmen öppnar du webbläsarens alternativ-meny och väljer <strong>Lägg till på startskärmen</strong>. <small>Man hittar menyn genom att trycka på hårdvaruknappen om din enhet har en sådan, eller genom att trycka på menyikonen högst upp till höger %icon.</small>'
  113. },
  114. tr_tr: {
  115. ios: 'Uygulamayı ana ekrana eklemek için, %icon ve ardından <strong>ana ekrana ekle</strong> butonunu tıklayın.',
  116. android: 'Uygulamayı ana ekrana eklemek için, menüye girin ve <strong>ana ekrana ekle</strong> butonunu tıklayın. <small>Cihazınız menü tuşuna sahip ise menüye girmek için menü tuşunu tıklayın. Aksi takdirde %icon butonunu tıklayın.</small>'
  117. },
  118. uk_ua: {
  119. ios: 'Щоб додати цей сайт на початковий екран, натисніть %icon, а потім <strong>На початковий екран</strong>.',
  120. android: 'Щоб додати цей сайт на домашній екран, відкрийте меню браузера та виберіть <strong>Додати на головний екран</strong>. <small>Це можливо зробити, натиснувши кнопку меню на вашому смартфоні, якщо така є. Або ж на іконці зверху справа %icon.</small>'
  121. },
  122. zh_cn: {
  123. ios: '如要把应用程序加至主屏幕,请点击%icon, 然后<strong>添加到主屏幕</strong>',
  124. android: 'To add this web app to the home screen open the browser option menu and tap on <strong>Add to homescreen</strong>. <small>The menu can be accessed by pressing the menu hardware button if your device has one, or by tapping the top right menu icon %icon.</small>'
  125. },
  126. zh_tw: {
  127. ios: '如要把應用程式加至主屏幕, 請點擊%icon, 然後<strong>加至主屏幕</strong>.',
  128. android: 'To add this web app to the home screen open the browser option menu and tap on <strong>Add to homescreen</strong>. <small>The menu can be accessed by pressing the menu hardware button if your device has one, or by tapping the top right menu icon %icon.</small>'
  129. }
  130. };
  131. // Add 2 characters language support (Android mostly)
  132. for ( var lang in ath.intl ) {
  133. ath.intl[lang.substr(0, 2)] = ath.intl[lang];
  134. }
  135. // default options
  136. ath.defaults = {
  137. appID: 'org.cubiq.addtohome', // local storage name (no need to change)
  138. fontSize: 15, // base font size, used to properly resize the popup based on viewport scale factor
  139. debug: false, // override browser checks
  140. logging: false, // log reasons for showing or not showing to js console; defaults to true when debug is true
  141. modal: false, // prevent further actions until the message is closed
  142. mandatory: false, // you can't proceed if you don't add the app to the homescreen
  143. autostart: true, // show the message automatically
  144. skipFirstVisit: false, // show only to returning visitors (ie: skip the first time you visit)
  145. startDelay: 1, // display the message after that many seconds from page load
  146. lifespan: 15, // life of the message in seconds
  147. displayPace: 10880, // minutes before the message is shown again (0: display every time, default 24 hours)
  148. maxDisplayCount: 2, // absolute maximum number of times the message will be shown to the user (0: no limit)
  149. icon: true, // add touch icon to the message
  150. message: '', // the message can be customized
  151. validLocation: [], // list of pages where the message will be shown (array of regexes)
  152. onInit: null, // executed on instance creation
  153. onShow: null, // executed when the message is shown
  154. onRemove: null, // executed when the message is removed
  155. onAdd: null, // when the application is launched the first time from the homescreen (guesstimate)
  156. onPrivate: null, // executed if user is in private mode
  157. privateModeOverride: true, // show the message even in private mode (very rude)
  158. detectHomescreen: false // try to detect if the site has been added to the homescreen (false | true | 'hash' | 'queryString' | 'smartURL')
  159. };
  160. // browser info and capability
  161. var _ua = window.navigator.userAgent;
  162. var _nav = window.navigator;
  163. _extend(ath, {
  164. hasToken: document.location.hash == '#ath' || _reSmartURL.test(document.location.href) || _reQueryString.test(document.location.search),
  165. isRetina: window.devicePixelRatio && window.devicePixelRatio > 1,
  166. isIDevice: (/iphone|ipod|ipad/i).test(_ua),
  167. isMobileChrome: _ua.indexOf('Android') > -1 && (/Chrome\/[.0-9]*/).test(_ua) && _ua.indexOf("Version") == -1,
  168. isMobileIE: _ua.indexOf('Windows Phone') > -1,
  169. language: _nav.language && _nav.language.toLowerCase().replace('-', '_') || ''
  170. });
  171. // falls back to en_us if language is unsupported
  172. ath.language = ath.language && ath.language in ath.intl ? ath.language : 'en_us';
  173. ath.isMobileSafari = ath.isIDevice && _ua.indexOf('Safari') > -1 && _ua.indexOf('CriOS') < 0;
  174. ath.OS = ath.isIDevice ? 'ios' : ath.isMobileChrome ? 'android' : ath.isMobileIE ? 'windows' : 'unsupported';
  175. ath.OSVersion = _ua.match(/(OS|Android) (\d+[_\.]\d+)/);
  176. ath.OSVersion = ath.OSVersion && ath.OSVersion[2] ? +ath.OSVersion[2].replace('_', '.') : 0;
  177. ath.isStandalone = 'standalone' in window.navigator && window.navigator.standalone;
  178. ath.isTablet = (ath.isMobileSafari && _ua.indexOf('iPad') > -1) || (ath.isMobileChrome && _ua.indexOf('Mobile') < 0);
  179. ath.isCompatible = (ath.isMobileSafari && ath.OSVersion >= 6) || ath.isMobileChrome; // TODO: add winphone
  180. var _defaultSession = {
  181. lastDisplayTime: 0, // last time we displayed the message
  182. returningVisitor: false, // is this the first time you visit
  183. displayCount: 0, // number of times the message has been shown
  184. optedout: false, // has the user opted out
  185. added: false // has been actually added to the homescreen
  186. };
  187. ath.removeSession = function (appID) {
  188. try {
  189. if (!localStorage) {
  190. throw new Error('localStorage is not defined');
  191. }
  192. localStorage.removeItem(appID || ath.defaults.appID);
  193. } catch (e) {
  194. // we are most likely in private mode
  195. }
  196. };
  197. ath.doLog = function (logStr) {
  198. if ( this.options.logging ) {
  199. console.log(logStr);
  200. }
  201. };
  202. ath.Class = function (options) {
  203. // class methods
  204. this.doLog = ath.doLog;
  205. // merge default options with user config
  206. this.options = _extend({}, ath.defaults);
  207. _extend(this.options, options);
  208. // override defaults that are dependent on each other
  209. if ( this.options && this.options.debug && (typeof this.options.logging === "undefined") ) {
  210. this.options.logging = true;
  211. }
  212. // IE<9 so exit (I hate you, really)
  213. if ( !_eventListener ) {
  214. return;
  215. }
  216. // normalize some options
  217. this.options.mandatory = this.options.mandatory && ( 'standalone' in window.navigator || this.options.debug );
  218. this.options.modal = this.options.modal || this.options.mandatory;
  219. if ( this.options.mandatory ) {
  220. this.options.startDelay = -0.5; // make the popup hasty
  221. }
  222. this.options.detectHomescreen = this.options.detectHomescreen === true ? 'hash' : this.options.detectHomescreen;
  223. // setup the debug environment
  224. if ( this.options.debug ) {
  225. ath.isCompatible = true;
  226. ath.OS = typeof this.options.debug == 'string' ? this.options.debug : ath.OS == 'unsupported' ? 'android' : ath.OS;
  227. ath.OSVersion = ath.OS == 'ios' ? '8' : '4';
  228. }
  229. // the element the message will be appended to
  230. this.container = document.body;
  231. // load session
  232. this.session = this.getItem(this.options.appID);
  233. this.session = this.session ? JSON.parse(this.session) : undefined;
  234. // user most likely came from a direct link containing our token, we don't need it and we remove it
  235. if ( ath.hasToken && ( !ath.isCompatible || !this.session ) ) {
  236. ath.hasToken = false;
  237. _removeToken();
  238. }
  239. // the device is not supported
  240. if ( !ath.isCompatible ) {
  241. this.doLog("Add to homescreen: not displaying callout because device not supported");
  242. return;
  243. }
  244. this.session = this.session || _defaultSession;
  245. // check if we can use the local storage
  246. try {
  247. if (!localStorage) {
  248. throw new Error('localStorage is not defined');
  249. }
  250. localStorage.setItem(this.options.appID, JSON.stringify(this.session));
  251. ath.hasLocalStorage = true;
  252. } catch (e) {
  253. // we are most likely in private mode
  254. ath.hasLocalStorage = false;
  255. if ( this.options.onPrivate ) {
  256. this.options.onPrivate.call(this);
  257. }
  258. }
  259. // check if this is a valid location
  260. var isValidLocation = !this.options.validLocation.length;
  261. for ( var i = this.options.validLocation.length; i--; ) {
  262. if ( this.options.validLocation[i].test(document.location.href) ) {
  263. isValidLocation = true;
  264. break;
  265. }
  266. }
  267. // check compatibility with old versions of add to homescreen. Opt-out if an old session is found
  268. if ( this.getItem('addToHome') ) {
  269. this.optOut();
  270. }
  271. // critical errors:
  272. if ( this.session.optedout ) {
  273. this.doLog("Add to homescreen: not displaying callout because user opted out");
  274. return;
  275. }
  276. if ( this.session.added ) {
  277. this.doLog("Add to homescreen: not displaying callout because already added to the homescreen");
  278. return;
  279. }
  280. if ( !isValidLocation ) {
  281. this.doLog("Add to homescreen: not displaying callout because not a valid location");
  282. return;
  283. }
  284. // check if the app is in stand alone mode
  285. if ( ath.isStandalone ) {
  286. // execute the onAdd event if we haven't already
  287. if ( !this.session.added ) {
  288. this.session.added = true;
  289. this.updateSession();
  290. if ( this.options.onAdd && ath.hasLocalStorage ) { // double check on localstorage to avoid multiple calls to the custom event
  291. this.options.onAdd.call(this);
  292. }
  293. }
  294. this.doLog("Add to homescreen: not displaying callout because in standalone mode");
  295. return;
  296. }
  297. // (try to) check if the page has been added to the homescreen
  298. if ( this.options.detectHomescreen ) {
  299. // the URL has the token, we are likely coming from the homescreen
  300. if ( ath.hasToken ) {
  301. _removeToken(); // we don't actually need the token anymore, we remove it to prevent redistribution
  302. // this is called the first time the user opens the app from the homescreen
  303. if ( !this.session.added ) {
  304. this.session.added = true;
  305. this.updateSession();
  306. if ( this.options.onAdd && ath.hasLocalStorage ) { // double check on localstorage to avoid multiple calls to the custom event
  307. this.options.onAdd.call(this);
  308. }
  309. }
  310. this.doLog("Add to homescreen: not displaying callout because URL has token, so we are likely coming from homescreen");
  311. return;
  312. }
  313. // URL doesn't have the token, so add it
  314. if ( this.options.detectHomescreen == 'hash' ) {
  315. history.replaceState('', window.document.title, document.location.href + '#ath');
  316. } else if ( this.options.detectHomescreen == 'smartURL' ) {
  317. history.replaceState('', window.document.title, document.location.href.replace(/(\/)?$/, '/ath$1'));
  318. } else {
  319. history.replaceState('', window.document.title, document.location.href + (document.location.search ? '&' : '?' ) + 'ath=');
  320. }
  321. }
  322. // check if this is a returning visitor
  323. if ( !this.session.returningVisitor ) {
  324. this.session.returningVisitor = true;
  325. this.updateSession();
  326. // we do not show the message if this is your first visit
  327. if ( this.options.skipFirstVisit ) {
  328. this.doLog("Add to homescreen: not displaying callout because skipping first visit");
  329. return;
  330. }
  331. }
  332. // we do no show the message in private mode
  333. if ( !this.options.privateModeOverride && !ath.hasLocalStorage ) {
  334. this.doLog("Add to homescreen: not displaying callout because browser is in private mode");
  335. return;
  336. }
  337. // all checks passed, ready to display
  338. this.ready = true;
  339. if ( this.options.onInit ) {
  340. this.options.onInit.call(this);
  341. }
  342. if ( this.options.autostart ) {
  343. this.doLog("Add to homescreen: autostart displaying callout");
  344. this.show();
  345. }
  346. };
  347. ath.Class.prototype = {
  348. // event type to method conversion
  349. events: {
  350. load: '_delayedShow',
  351. error: '_delayedShow',
  352. orientationchange: 'resize',
  353. resize: 'resize',
  354. scroll: 'resize',
  355. click: 'remove',
  356. touchmove: '_preventDefault',
  357. transitionend: '_removeElements',
  358. webkitTransitionEnd: '_removeElements',
  359. MSTransitionEnd: '_removeElements'
  360. },
  361. handleEvent: function (e) {
  362. var type = this.events[e.type];
  363. if ( type ) {
  364. this[type](e);
  365. }
  366. },
  367. show: function (force) {
  368. // in autostart mode wait for the document to be ready
  369. if ( this.options.autostart && !_DOMReady ) {
  370. setTimeout(this.show.bind(this), 50);
  371. // we are not displaying callout because DOM not ready, but don't log that because
  372. // it would log too frequently
  373. return;
  374. }
  375. // message already on screen
  376. if ( this.shown ) {
  377. this.doLog("Add to homescreen: not displaying callout because already shown on screen");
  378. return;
  379. }
  380. var now = Date.now();
  381. var lastDisplayTime = this.session.lastDisplayTime;
  382. if ( force !== true ) {
  383. // this is needed if autostart is disabled and you programmatically call the show() method
  384. if ( !this.ready ) {
  385. this.doLog("Add to homescreen: not displaying callout because not ready");
  386. return;
  387. }
  388. // we obey the display pace (prevent the message to popup too often)
  389. if ( now - lastDisplayTime < this.options.displayPace * 60000 ) {
  390. this.doLog("Add to homescreen: not displaying callout because displayed recently");
  391. return;
  392. }
  393. // obey the maximum number of display count
  394. if ( this.options.maxDisplayCount && this.session.displayCount >= this.options.maxDisplayCount ) {
  395. this.doLog("Add to homescreen: not displaying callout because displayed too many times already");
  396. return;
  397. }
  398. }
  399. this.shown = true;
  400. // increment the display count
  401. this.session.lastDisplayTime = now;
  402. this.session.displayCount++;
  403. this.updateSession();
  404. // try to get the highest resolution application icon
  405. if ( !this.applicationIcon ) {
  406. if ( ath.OS == 'ios' ) {
  407. this.applicationIcon = document.querySelector('head link[rel^=apple-touch-icon][sizes="152x152"],head link[rel^=apple-touch-icon][sizes="144x144"],head link[rel^=apple-touch-icon][sizes="120x120"],head link[rel^=apple-touch-icon][sizes="114x114"],head link[rel^=apple-touch-icon]');
  408. } else {
  409. this.applicationIcon = document.querySelector('head link[rel^="shortcut icon"][sizes="196x196"],head link[rel^=apple-touch-icon]');
  410. }
  411. }
  412. var message = '';
  413. if ( typeof this.options.message == 'object' && ath.language in this.options.message ) { // use custom language message
  414. message = this.options.message[ath.language][ath.OS];
  415. } else if ( typeof this.options.message == 'object' && ath.OS in this.options.message ) { // use custom os message
  416. message = this.options.message[ath.OS];
  417. } else if ( this.options.message in ath.intl ) { // you can force the locale
  418. message = ath.intl[this.options.message][ath.OS];
  419. } else if ( this.options.message !== '' ) { // use a custom message
  420. message = this.options.message;
  421. } else if ( ath.OS in ath.intl[ath.language] ) { // otherwise we use our message
  422. message = ath.intl[ath.language][ath.OS];
  423. }
  424. // add the action icon
  425. message = '<p>' + message.replace(/%icon(?:\[([^\]]+)\])?/gi, function(matches, group1) {
  426. return '<span class="ath-action-icon">' + (!!group1 ? group1 : 'icon') + '</span>';
  427. }) + '</p>';
  428. // create the message container
  429. this.viewport = document.createElement('div');
  430. this.viewport.className = 'ath-viewport';
  431. if ( this.options.modal ) {
  432. this.viewport.className += ' ath-modal';
  433. }
  434. if ( this.options.mandatory ) {
  435. this.viewport.className += ' ath-mandatory';
  436. }
  437. this.viewport.style.position = 'absolute';
  438. // create the actual message element
  439. this.element = document.createElement('div');
  440. this.element.className = 'ath-container ath-' + ath.OS + ' ath-' + ath.OS + (parseInt(ath.OSVersion) || '') + ' ath-' + (ath.isTablet ? 'tablet' : 'phone');
  441. this.element.style.cssText = '-webkit-transition-property:-webkit-transform,opacity;-webkit-transition-duration:0s;-webkit-transition-timing-function:ease-out;transition-property:transform,opacity;transition-duration:0s;transition-timing-function:ease-out;';
  442. this.element.style.webkitTransform = 'translate3d(0,-' + window.innerHeight + 'px,0)';
  443. this.element.style.transform = 'translate3d(0,-' + window.innerHeight + 'px,0)';
  444. // add the application icon
  445. if ( this.options.icon && this.applicationIcon ) {
  446. this.element.className += ' ath-icon';
  447. this.img = document.createElement('img');
  448. this.img.className = 'ath-application-icon';
  449. this.img.addEventListener('load', this, false);
  450. this.img.addEventListener('error', this, false);
  451. this.img.src = this.applicationIcon.href;
  452. this.element.appendChild(this.img);
  453. }
  454. this.element.innerHTML += message;
  455. // we are not ready to show, place the message out of sight
  456. this.viewport.style.left = '-99999em';
  457. // attach all elements to the DOM
  458. this.viewport.appendChild(this.element);
  459. this.container.appendChild(this.viewport);
  460. // if we don't have to wait for an image to load, show the message right away
  461. if ( this.img ) {
  462. this.doLog("Add to homescreen: not displaying callout because waiting for img to load");
  463. } else {
  464. this._delayedShow();
  465. }
  466. },
  467. _delayedShow: function (e) {
  468. setTimeout(this._show.bind(this), this.options.startDelay * 1000 + 500);
  469. },
  470. _show: function () {
  471. var that = this;
  472. // update the viewport size and orientation
  473. this.updateViewport();
  474. // reposition/resize the message on orientation change
  475. window.addEventListener('resize', this, false);
  476. window.addEventListener('scroll', this, false);
  477. window.addEventListener('orientationchange', this, false);
  478. if ( this.options.modal ) {
  479. // lock any other interaction
  480. document.addEventListener('touchmove', this, true);
  481. }
  482. // Enable closing after 1 second
  483. if ( !this.options.mandatory ) {
  484. setTimeout(function () {
  485. that.element.addEventListener('click', that, true);
  486. }, 1000);
  487. }
  488. // kick the animation
  489. setTimeout(function () {
  490. that.element.style.webkitTransitionDuration = '1.2s';
  491. that.element.style.transitionDuration = '1.2s';
  492. that.element.style.webkitTransform = 'translate3d(0,0,0)';
  493. that.element.style.transform = 'translate3d(0,0,0)';
  494. }, 0);
  495. // set the destroy timer
  496. if ( this.options.lifespan ) {
  497. this.removeTimer = setTimeout(this.remove.bind(this), this.options.lifespan * 1000);
  498. }
  499. // fire the custom onShow event
  500. if ( this.options.onShow ) {
  501. this.options.onShow.call(this);
  502. }
  503. },
  504. remove: function () {
  505. clearTimeout(this.removeTimer);
  506. // clear up the event listeners
  507. if ( this.img ) {
  508. this.img.removeEventListener('load', this, false);
  509. this.img.removeEventListener('error', this, false);
  510. }
  511. window.removeEventListener('resize', this, false);
  512. window.removeEventListener('scroll', this, false);
  513. window.removeEventListener('orientationchange', this, false);
  514. document.removeEventListener('touchmove', this, true);
  515. this.element.removeEventListener('click', this, true);
  516. // remove the message element on transition end
  517. this.element.addEventListener('transitionend', this, false);
  518. this.element.addEventListener('webkitTransitionEnd', this, false);
  519. this.element.addEventListener('MSTransitionEnd', this, false);
  520. // start the fade out animation
  521. this.element.style.webkitTransitionDuration = '0.3s';
  522. this.element.style.opacity = '0';
  523. },
  524. _removeElements: function () {
  525. this.element.removeEventListener('transitionend', this, false);
  526. this.element.removeEventListener('webkitTransitionEnd', this, false);
  527. this.element.removeEventListener('MSTransitionEnd', this, false);
  528. // remove the message from the DOM
  529. this.container.removeChild(this.viewport);
  530. this.shown = false;
  531. // fire the custom onRemove event
  532. if ( this.options.onRemove ) {
  533. this.options.onRemove.call(this);
  534. }
  535. },
  536. updateViewport: function () {
  537. if ( !this.shown ) {
  538. return;
  539. }
  540. this.viewport.style.width = window.innerWidth + 'px';
  541. this.viewport.style.height = window.innerHeight + 'px';
  542. this.viewport.style.left = window.scrollX + 'px';
  543. this.viewport.style.top = window.scrollY + 'px';
  544. var clientWidth = document.documentElement.clientWidth;
  545. this.orientation = clientWidth > document.documentElement.clientHeight ? 'landscape' : 'portrait';
  546. var screenWidth = ath.OS == 'ios' ? this.orientation == 'portrait' ? screen.width : screen.height : screen.width;
  547. this.scale = screen.width > clientWidth ? 1 : screenWidth / window.innerWidth;
  548. this.element.style.fontSize = this.options.fontSize / this.scale + 'px';
  549. },
  550. resize: function () {
  551. clearTimeout(this.resizeTimer);
  552. this.resizeTimer = setTimeout(this.updateViewport.bind(this), 100);
  553. },
  554. updateSession: function () {
  555. if ( ath.hasLocalStorage === false ) {
  556. return;
  557. }
  558. if (localStorage) {
  559. localStorage.setItem(this.options.appID, JSON.stringify(this.session));
  560. }
  561. },
  562. clearSession: function () {
  563. this.session = _defaultSession;
  564. this.updateSession();
  565. },
  566. getItem: function(item) {
  567. try {
  568. if (!localStorage) {
  569. throw new Error('localStorage is not defined');
  570. }
  571. return localStorage.getItem(item);
  572. } catch(e) {
  573. // Preventing exception for some browsers when fetching localStorage key
  574. ath.hasLocalStorage = false;
  575. }
  576. },
  577. optOut: function () {
  578. this.session.optedout = true;
  579. this.updateSession();
  580. },
  581. optIn: function () {
  582. this.session.optedout = false;
  583. this.updateSession();
  584. },
  585. clearDisplayCount: function () {
  586. this.session.displayCount = 0;
  587. this.updateSession();
  588. },
  589. _preventDefault: function (e) {
  590. e.preventDefault();
  591. e.stopPropagation();
  592. }
  593. };
  594. // utility
  595. function _extend (target, obj) {
  596. for ( var i in obj ) {
  597. target[i] = obj[i];
  598. }
  599. return target;
  600. }
  601. function _removeToken () {
  602. if ( document.location.hash == '#ath' ) {
  603. history.replaceState('', window.document.title, document.location.href.split('#')[0]);
  604. }
  605. if ( _reSmartURL.test(document.location.href) ) {
  606. history.replaceState('', window.document.title, document.location.href.replace(_reSmartURL, '$1'));
  607. }
  608. if ( _reQueryString.test(document.location.search) ) {
  609. history.replaceState('', window.document.title, document.location.href.replace(_reQueryString, '$2'));
  610. }
  611. }
  612. // expose to the world
  613. window.addToHomescreen = ath;
  614. })(window, document);