main.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  1. // requires jquery 1.7+
  2. // JS s'execute: Retire l'avertissement 'Javascript est requis' (de fait, on sait que JS est actif...)
  3. document.body.classList.remove("nojs");
  4. //### Helpers handlebars personnalisés
  5. Handlebars.registerHelper('lower', function (options) {
  6. return options.fn(this).toLowerCase();
  7. });
  8. Handlebars.registerHelper('repuri', function (find, replace, options) {
  9. return encodeURI(options.fn(this).replace(find, replace).toString());
  10. });
  11. Handlebars.registerHelper('todate', function (options) {
  12. var d = options.fn(this);
  13. var dt = new Date(Number.parseInt(d) * 1);
  14. return dt.toLocaleString();
  15. });
  16. Handlebars.registerHelper('timestamp', function (options) {
  17. return Number.parseInt(options.fn(this));
  18. });
  19. Handlebars.registerHelper('if_eq', function (a, opts) {
  20. var b = localStorage.hasOwnProperty("contact") ? localStorage.getItem("contact") : "";
  21. if (a === b) // Or === depending on your needs
  22. return opts.fn(this);
  23. else
  24. return opts.inverse(this);
  25. });
  26. //### Initialisation
  27. // ************** CONFIGURATION *****************
  28. // Nom et version de la base de données locale à créer/utiliser
  29. var db_name = "ModelePWA"
  30. var db_version = "3"
  31. // Si deleteOnSuccess est vrai, les données synchronisées seront supprimées de la base locale.
  32. var deleteOnSync = true;
  33. // Les stores de la liste doNotSync ne seront pas synchronisés.
  34. var doNotSync = ["params"];
  35. var postUrl = "/api/modelepwa";
  36. // **********************************************
  37. var indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB || window.shimIndexedDB;
  38. var request;
  39. // Installe le service worker
  40. if ('serviceWorker' in navigator) {
  41. console.log("[ServiceWorker] Installe");
  42. navigator.serviceWorker.register('../sw.js');
  43. }
  44. var sectionId;
  45. var section;
  46. var objId;
  47. var submitHandler;
  48. var deleteHandler;
  49. var editHandler;
  50. // Charge la page en fonction de l'url actuelle
  51. var load = function () {
  52. // Reinitialise l'affichage des sections
  53. $("#sync").hide();
  54. $("#main").empty();
  55. $("#main").show();
  56. // Parse l'url actuelle
  57. var base = window.location.hash.split('?')[0];
  58. sectionId = base.length > 0 ? base : "#index";
  59. section = $("body").find(sectionId);
  60. var qry = {};
  61. var qrystr = window.location.hash.split('?')[1];
  62. if (qrystr) {
  63. var definitions = qrystr.split('&');
  64. definitions.forEach(function (val, key) {
  65. var parts = val.split('=', 2);
  66. qry[parts[0]] = parts[1];
  67. });
  68. }
  69. // Met à jour le contenu de #main avec la section demandée dans l'url/
  70. // Si l'attribut 'model' existe pour cette section, charge les données depuis la localDb
  71. // et compile la section avec handlebars.
  72. if ($(section).attr("model")) {
  73. var model = $(section).attr("model");
  74. var template = Handlebars.compile(section.html());
  75. // Charge la base de données, et le stockage courant
  76. request = indexedDB.open(db_name, db_version);
  77. var db;
  78. var txs;
  79. var store;
  80. // Cree les stockages necessaires si ceux ci sont manquants
  81. request.onupgradeneeded = function () {
  82. var db = request.result;
  83. db.createObjectStore(model, { keyPath: "guid" });
  84. console.log("[DB] Cree '" + model + "'");
  85. };
  86. // Si l'accès a la db s'est bien passé:
  87. request.onsuccess = function () {
  88. var data;
  89. db = request.result;
  90. txs = db.transaction(model, "readonly");
  91. store = txs.objectStore(model);
  92. // Si un id specifique a été demandé, on charge specifiquement cet objet.
  93. // Sinon: on les charge tous en memoire.
  94. if (qry["id"]) {
  95. store.get(qry["id"]).onsuccess = function (event) {
  96. data = { data: event.target.result };
  97. $("#main").html(template(data));
  98. populateForm(data['data']);
  99. }
  100. }
  101. else {
  102. store.getAll().onsuccess = function (event) {
  103. data = { data: event.target.result };
  104. $("#main").html(template(data));
  105. };
  106. }
  107. };
  108. request.onerror = function () {
  109. console.log("Error while loading the db '" + model + "'");
  110. $("#main").html(template({}));
  111. return;
  112. };
  113. // (Re)définit la fonction d'interception de la soumission de formulaires
  114. submitHandler = function (event) {
  115. var form = $("#main").find("form")[0];
  116. // Stop the form from submitting since we’re handling that with AJAX.
  117. event.preventDefault();
  118. // Call our function to get the form data.
  119. var data = formToJSON(form.elements);
  120. data.guid = createGuid();
  121. txs = db.transaction(model, "readwrite");
  122. store = txs.objectStore(model);
  123. store.put(data);
  124. };
  125. // (Re)définit la fonction de suppression des lignes de l'index
  126. deleteHandler = function(event) {
  127. var del = $(this);
  128. if (confirm("Supprimer la selection?") == true) {
  129. $(del).prop("disabled", true);
  130. $(".ui-selected").each(function () {
  131. var elt = $(this);
  132. var id = $(elt).data("id");
  133. txs = db.transaction(model, "readwrite");
  134. store = txs.objectStore(model);
  135. store.delete(id).onsuccess = function (evt) {
  136. $(elt).remove();
  137. $(".del").prop('disabled', false);
  138. };
  139. });
  140. }
  141. };
  142. // (Re)définit la fonction de suppression d'edition
  143. editHandler = function(event) {
  144. var edit = $(this);
  145. var id = $(".ui-selected:first").data("id")
  146. txs = db.transaction(model, "readonly");
  147. store = txs.objectStore(model);
  148. var obj = store.get(id);
  149. obj.onsuccess = function (event) {
  150. window.location = "/#activites?id=" + id;
  151. };
  152. };
  153. }
  154. else {
  155. // Pas de modele, on charge simplement le html de la section.
  156. $("#main").html(section.html());
  157. }
  158. }
  159. load();
  160. // Recharge dynamiquement le contenu HTML à chaque changement d'url
  161. $(window).on('hashchange', function () {
  162. if (window.location.hash == "#menu") { return; }
  163. load();
  164. });
  165. // Met a jour le contenu des champs d'un formulaire
  166. // Le binbding se fait via l'id du champs
  167. var populateForm = function (data) {
  168. if ($("form").length) {
  169. $("input, select, textarea").each(function () {
  170. var input = $(this);
  171. var fieldName = input.attr('id');
  172. input.val(data[fieldName]);
  173. });
  174. }
  175. }
  176. // ###########################
  177. // ### Interactions
  178. // Intercepte la soumission de formulaires
  179. $("#main").on("submit", "form", function (event) {
  180. submitHandler(event);
  181. window.location = "/#index";
  182. });
  183. // Gere le clic sur un bouton Supprimer
  184. $("#main").on("click", ".del", function (event) {
  185. deleteHandler(event);
  186. });
  187. // Gere le clic sur un bouton Editer
  188. $("#main").on("click", ".edit", function (event) {
  189. editHandler(event);
  190. });
  191. // Gere le clic sur le bouton de menu pour afficher ou masquer la sidebar
  192. $('.bt-menu').on('click', 'svg', function () {
  193. $(this).closest('nav').find('div:not(:first)').toggleClass('sidebar');
  194. });
  195. $(document).on('click', '.sidebar', function () {
  196. $(this).closest('nav').find('div:not(:first)').toggleClass('sidebar');
  197. });
  198. // Affiche ou masque le bouton de sync selon que le poste est en ligne ou non
  199. if (navigator.onLine) {
  200. $(".start-sync").removeAttr("disabled");
  201. }
  202. else {
  203. if (!$(".start-sync").is(":disabled"))
  204. {
  205. $(".start-sync").attr("disabled")
  206. }
  207. }
  208. // Affiche ou masque, et gere le clic sur le bouton de retour au haut de page.
  209. window.onscroll = function (ev) {
  210. document.getElementById("back-top").className =(window.pageYOffset > 100) ? "": "hidden";
  211. };
  212. $('#back-top').on('click', function () {
  213. $('html, body').animate({ scrollTop: 0 }, 200);
  214. });
  215. // Rend les lignes des tables .selectable selectionnables
  216. mo = new MutationObserver(function (mutations, observer) {
  217. $(".selectable > tbody").bind("mousedown", function (e) {
  218. e.metaKey = true;
  219. }).selectable({
  220. filter: "tr",
  221. stop: function () {
  222. $(".del").prop('disabled', ($(".ui-selected").length == 0));
  223. $(".edit").prop('disabled', ($(".ui-selected").length != 1));
  224. },
  225. });
  226. })
  227. mo.observe(document.querySelector('#main'), { childList: true });
  228. // ### Synchronisation des données
  229. $(".start-sync").on("click", function () {
  230. // (!) voir en debut de fichier pour la configuration de la synchro (cf. les variables doNotSync et deleteOnSync)
  231. // Affiche la page de synchro
  232. $("#main").hide();
  233. $("#sync").show();
  234. // Verifie l'acces à la LocalDb
  235. if (!request) {
  236. request = indexedDB.open(db_name, db_version);
  237. request.onerror = function () {
  238. console.log("Error while accessing the db");
  239. $("#sync").find(".status").html("<p>Erreur de synchronisation: impossible d'accéder à la base de données locale.</p>");
  240. return;
  241. };
  242. }
  243. var db = request.result;
  244. $("#sync").find(".status").html("<p>Synchronisation en cours, veuillez patienter...</p>");
  245. var model;
  246. // Parcourt les differents stores
  247. for (var i = 0; i < db.objectStoreNames.length; i++) {
  248. model = db.objectStoreNames[i];
  249. if (doNotSync.indexOf(model) >= 0) {
  250. console.log("Ignored: " + model);
  251. continue;
  252. }
  253. console.log("Sync: " + model);
  254. var txs = db.transaction(model, "readonly");
  255. var stores = txs.objectStore(model);
  256. stores.openCursor().onsuccess = function (event) {
  257. var cursor = event.target.result;
  258. if (cursor) {
  259. cursor.value.model = model;
  260. var id = cursor.value.guid;
  261. var posting = $.post(postUrl, { data: JSON.stringify(cursor.value) });
  262. // Put the results in a div
  263. posting.done(function (data) {
  264. if (deleteOnSync & data) {
  265. var tx = db.transaction(model, "readwrite");
  266. var store = tx.objectStore(model);
  267. store.delete(id).onsuccess = function (evt) {
  268. };
  269. }
  270. });
  271. cursor.continue();
  272. }
  273. };
  274. stores.openCursor().onerror = function (event) {
  275. console.log(event);
  276. }
  277. }
  278. console.log("Synchro ok");
  279. $("#sync").find(".end-sync").show();
  280. $("#sync").find(".status").html("<p>Synchronisation terminée.</p>");
  281. });
  282. $(".end-sync").click(function () {
  283. load();
  284. });
  285. //###### TOOLBOX ######
  286. function createGuid() {
  287. return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
  288. (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
  289. )
  290. }
  291. function getLocation() {
  292. try {
  293. if (navigator.geolocation) {
  294. navigator.geolocation.getCurrentPosition(showPosition);
  295. } else {
  296. console.log("Geolocation is not supported by this browser.");
  297. return 0;
  298. }
  299. }
  300. catch (e) {
  301. console.log("Geolocation: error");
  302. }
  303. }
  304. function showPosition(position) {
  305. $("input[name='coordinates']").val(position.coords.latitude + "," + position.coords.longitude);
  306. console.log(position.coords);
  307. }
  308. // ### Serialization
  309. /**
  310. * Checks that an element has a non-empty `name` and `value` property.
  311. * @param {Element} element the element to check
  312. * @return {Bool} true if the element is an input, false if not
  313. */
  314. var isValidElement = function isValidElement(element) {
  315. return element.name && element.value;
  316. };
  317. /**
  318. * Checks if an element’s value can be saved (e.g. not an unselected checkbox).
  319. * @param {Element} element the element to check
  320. * @return {Boolean} true if the value should be added, false if not
  321. */
  322. var isValidValue = function isValidValue(element) {
  323. return !['checkbox', 'radio'].includes(element.type) || element.checked;
  324. };
  325. /**
  326. * Checks if an input is a checkbox, because checkboxes allow multiple values.
  327. * @param {Element} element the element to check
  328. * @return {Boolean} true if the element is a checkbox, false if not
  329. */
  330. var isCheckbox = function isCheckbox(element) {
  331. return element.type === 'checkbox';
  332. };
  333. //var isHidden = function isHidden(element) {
  334. // return element.type === 'hidden';
  335. //};
  336. /**
  337. * Checks if an input is a `select` with the `multiple` attribute.
  338. * @param {Element} element the element to check
  339. * @return {Boolean} true if the element is a multiselect, false if not
  340. */
  341. var isMultiSelect = function isMultiSelect(element) {
  342. return element.options && element.multiple;
  343. };
  344. /**
  345. * Retrieves the selected options from a multi-select as an array.
  346. * @param {HTMLOptionsCollection} options the options for the select
  347. * @return {Array} an array of selected option values
  348. */
  349. var getSelectValues = function getSelectValues(options) {
  350. return [].reduce.call(options, function (values, option) {
  351. return option.selected ? values.concat(option.value) : values;
  352. }, []);
  353. };
  354. /**
  355. * A more verbose implementation of `formToJSON()` to explain how it works.
  356. *
  357. * NOTE: This function is unused, and is only here for the purpose of explaining how
  358. * reducing form elements works.
  359. *
  360. * @param {HTMLFormControlsCollection} elements the form elements
  361. * @return {Object} form data as an object literal
  362. */
  363. var formToJSON_deconstructed = function formToJSON_deconstructed(elements) {
  364. // This is the function that is called on each element of the array.
  365. var reducerFunction = function reducerFunction(data, element) {
  366. // Add the current field to the object.
  367. data[element.name] = element.value;
  368. // For the demo only: show each step in the reducer’s progress.
  369. console.log(JSON.stringify(data));
  370. return data;
  371. };
  372. // This is used as the initial value of `data` in `reducerFunction()`.
  373. var reducerInitialValue = {};
  374. // To help visualize what happens, log the inital value, which we know is `{}`.
  375. console.log('Initial `data` value:', JSON.stringify(reducerInitialValue));
  376. // Now we reduce by `call`-ing `Array.prototype.reduce()` on `elements`.
  377. var formData = [].reduce.call(elements, reducerFunction, reducerInitialValue);
  378. // The result is then returned for use elsewhere.
  379. return formData;
  380. };
  381. /**
  382. * Retrieves input data from a form and returns it as a JSON object.
  383. * @param {HTMLFormControlsCollection} elements the form elements
  384. * @return {Object} form data as an object literal
  385. */
  386. var formToJSON = function formToJSON(elements) {
  387. return [].reduce.call(elements, function (data, element) {
  388. // Make sure the element has the required properties and should be added.
  389. if (isValidElement(element) && isValidValue(element)) {
  390. /*
  391. * Some fields allow for more than one value, so we need to check if this
  392. * is one of those fields and, if so, store the values as an array.
  393. */
  394. if (isCheckbox(element)) {
  395. data[element.name] = (data[element.name] || []).concat(element.value);
  396. } else if (isMultiSelect(element)) {
  397. data[element.name] = getSelectValues(element);
  398. } else {
  399. data[element.name] = element.value;
  400. }
  401. }
  402. return data;
  403. }, {});
  404. };
  405. /*
  406. Intensify by TEMPLATED
  407. templated.co @templatedco
  408. Released for free under the Creative Commons Attribution 3.0 license (templated.co/license)
  409. */
  410. (function ($) {
  411. skel.breakpoints({
  412. xlarge: '(max-width: 1680px)',
  413. large: '(max-width: 1280px)',
  414. medium: '(max-width: 980px)',
  415. small: '(max-width: 736px)',
  416. xsmall: '(max-width: 480px)'
  417. });
  418. $(function () {
  419. var $window = $(window),
  420. $body = $('body'),
  421. $header = $('#header');
  422. // Disable animations/transitions until the page has loaded.
  423. $body.addClass('is-loading');
  424. $window.on('load', function () {
  425. window.setTimeout(function () {
  426. $body.removeClass('is-loading');
  427. }, 100);
  428. });
  429. // Fix: Placeholder polyfill.
  430. $('form').placeholder();
  431. // Prioritize "important" elements on medium.
  432. skel.on('+medium -medium', function () {
  433. $.prioritize(
  434. '.important\\28 medium\\29',
  435. skel.breakpoint('medium').active
  436. );
  437. });
  438. // Scrolly.
  439. $('.scrolly').scrolly({
  440. offset: function () {
  441. return $header.height();
  442. }
  443. });
  444. // Menu.
  445. $('#menu')
  446. .append('<a href="#menu" class="close"></a>')
  447. .appendTo($body)
  448. .panel({
  449. delay: 500,
  450. hideOnClick: true,
  451. hideOnSwipe: true,
  452. resetScroll: true,
  453. resetForms: true,
  454. side: 'right'
  455. });
  456. });
  457. })(jQuery);