main.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  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. var db_name = "ModelePWA"
  28. var db_version = "3"
  29. var indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB || window.shimIndexedDB;
  30. var request;
  31. // Installe le service worker
  32. if ('serviceWorker' in navigator) {
  33. console.log("[ServiceWorker] Installe");
  34. navigator.serviceWorker.register('../sw.js');
  35. }
  36. var sectionId;
  37. var section;
  38. var objId;
  39. var load = function () {
  40. // Vide la section main
  41. $("#sync").hide();
  42. $("#main").empty();
  43. $("#main").show();
  44. // Page en cours
  45. var base = window.location.hash.split('?')[0];
  46. sectionId = base.length > 0 ? base : "#index";
  47. section = $("body").find(sectionId);
  48. console.log("Section: " + sectionId);
  49. var qry = {};
  50. var qrystr = window.location.hash.split('?')[1];
  51. if (qrystr) {
  52. var definitions = qrystr.split('&');
  53. definitions.forEach(function (val, key) {
  54. var parts = val.split('=', 2);
  55. qry[parts[0]] = parts[1];
  56. });
  57. }
  58. console.log("Query: " + qry);
  59. // compile la section avec handlebars
  60. if ($(section).attr("model")) {
  61. var model = $(section).attr("model");
  62. var template = Handlebars.compile(section.html());
  63. // Charge la base de données, et le stockage courant
  64. //var indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB || window.shimIndexedDB;
  65. request = indexedDB.open(db_name, db_version);
  66. var db;
  67. var txs;
  68. var store;
  69. // Cree les stockages necessaires si ceux ci sont manquants
  70. request.onupgradeneeded = function () {
  71. var db = request.result;
  72. db.createObjectStore(model, { keyPath: "guid" });
  73. console.log("[DB] Cree '" + model + "'");
  74. };
  75. request.onsuccess = function () {
  76. db = request.result;
  77. txs = db.transaction(model, "readonly");
  78. store = txs.objectStore(model);
  79. if (!qry["id"]) {
  80. store.getAll().onsuccess = function (event) {
  81. var data = { data: event.target.result };
  82. console.log(data);
  83. $("#main").html(template(data));
  84. };
  85. }
  86. else {
  87. store.get(qry["id"]).onsuccess = function (event) {
  88. var data = { data: event.target.result };
  89. $("#main").html(template(data));
  90. $("input, select, textarea").each(function () {
  91. var input = $(this);
  92. var fieldName = input.attr('id');
  93. console.log(fieldName + ': ' + data['data'][fieldName]);
  94. input.val(data['data'][fieldName]);
  95. });
  96. }
  97. }
  98. };
  99. request.onerror = function () {
  100. console.log("Error while loading the db '" + model + "'");
  101. $("#main").html(template({}));
  102. return;
  103. };
  104. // Intercepte la soumision de formulaires
  105. $("#main").off("submit", "form");
  106. $("#main").on("submit", "form", function (event) {
  107. console.log("handle submit");
  108. var form = $("#main").find("form")[0];
  109. // Stop the form from submitting since we’re handling that with AJAX.
  110. event.preventDefault();
  111. // Call our function to get the form data.
  112. var data = formToJSON(form.elements);
  113. data.guid = createGuid();
  114. //data.tstamp = Date.now();
  115. data.user = localStorage.hasOwnProperty("params") ? JSON.parse(localStorage.getItem("params")).user : "(unknown)";
  116. txs = db.transaction(model, "readwrite");
  117. store = txs.objectStore(model);
  118. store.put(data);
  119. store.getAll().onsuccess = function (event) {
  120. window.location = "";
  121. };
  122. });
  123. // Gere le clic sur un bouton supprimer
  124. $("#main").off("click", ".del");
  125. $("#main").on("click", ".del", function (event) {
  126. var del = $(this);
  127. if (confirm("Supprimer la selection?") == true) {
  128. $(del).prop("disabled", true);
  129. $(".ui-selected").each(function () {
  130. var elt = $(this);
  131. var id = $(elt).data("id");
  132. txs = db.transaction(model, "readwrite");
  133. store = txs.objectStore(model);
  134. store.delete(id).onsuccess = function (evt) {
  135. $(elt).remove();
  136. $(".del").prop('disabled', false);
  137. };
  138. });
  139. }
  140. });
  141. // Gere le clic sur un bouton editer
  142. $("#main").off("click", ".edit");
  143. $("#main").on("click", ".edit", 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. //template = Handlebars.compile(section.html());
  156. //$("#main").html(template({}));
  157. $("#main").html(section.html());
  158. }
  159. }
  160. load();
  161. // Recharge dynamiquement le contenu HTML à chaque changement d'url
  162. $(window).on('hashchange', function () {
  163. console.log("Trigger: hashchange");
  164. load();
  165. });
  166. // ############ MAIN ###############
  167. // ### Interactions
  168. // Affiche ou masque la sidebar
  169. $('.bt-menu').on('click', 'svg', function () {
  170. $(this).closest('nav').find('div:not(:first)').toggleClass('sidebar');
  171. });
  172. $(document).on('click', '.sidebar', function () {
  173. $(this).closest('nav').find('div:not(:first)').toggleClass('sidebar');
  174. });
  175. // Affiche ou masque le bouton de sync
  176. if (navigator.onLine) {
  177. $(".start-sync").removeAttr("disabled");
  178. }
  179. else {
  180. if (!$(".start-sync").is(":disabled"))
  181. {
  182. $(".start-sync").attr("disabled")
  183. }
  184. }
  185. /* retour haut de page*/
  186. window.onscroll = function (ev) {
  187. document.getElementById("back-top").className =(window.pageYOffset > 100) ? "": "hidden";
  188. };
  189. $('#back-top').on('click', function () {
  190. $('html, body').animate({ scrollTop: 0 }, 200);
  191. });
  192. // Rend les lignes des tables .selectable selectionnables
  193. mo = new MutationObserver(function (mutations, observer) {
  194. $(".selectable > tbody").bind("mousedown", function (e) {
  195. e.metaKey = true;
  196. }).selectable({
  197. filter: "tr",
  198. stop: function () {
  199. $(".del").prop('disabled', ($(".ui-selected").length == 0));
  200. $(".edit").prop('disabled', ($(".ui-selected").length != 1));
  201. },
  202. });
  203. })
  204. mo.observe(document.querySelector('#main'), { childList: true });
  205. // ### Synchronisation des données
  206. $(".start-sync").on("click", function () {
  207. infodiv = $("#sync")
  208. if (!request) {
  209. request = indexedDB.open(db_name, db_version);
  210. request.onerror = function () {
  211. console.log("Error while accessing the db");
  212. infodiv.html("<p>Erreur de synchronisation: impossible d'accéder à la base de données locale.</p>");
  213. return;
  214. };
  215. }
  216. $("#main").hide();
  217. $("#sync").show();
  218. var db = request.result;
  219. var txs = db.transaction("activites", "readonly");
  220. var stores = txs.objectStore("activites");
  221. console.log("post all");
  222. stores.openCursor().onsuccess = function (event) {
  223. var cursor = event.target.result;
  224. if (cursor) {
  225. $("#sync").find(".status").html("<p>Synchronisation en cours, veuillez patienter...</p>");
  226. cursor.value.model = "activites";
  227. var id = cursor.value.guid;
  228. var posting = $.post("/api/modelepwa", { data: JSON.stringify(cursor.value) });
  229. // Put the results in a div
  230. posting.done(function (data) {
  231. if (data == true) {
  232. var tx = db.transaction("activites", "readwrite");
  233. var store = tx.objectStore("activites");
  234. store.delete(id).onsuccess = function (evt) {
  235. };
  236. }
  237. });
  238. cursor.continue();
  239. }
  240. else {
  241. $("#sync").find(".status").html("<p>Synchronisation terminée.</p>");
  242. $("#sync").find(".end-sync").show();
  243. }
  244. };
  245. stores.openCursor().onerror = function (event) {
  246. $("#sync").find(".status").html("<p>Erreur de synchronisation: .</p>");
  247. console.log(event);
  248. }
  249. })
  250. $(".end-sync").click(function () {
  251. load();
  252. });
  253. //###### TOOLBOX ######
  254. function createGuid() {
  255. return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
  256. (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
  257. )
  258. }
  259. function getLocation() {
  260. try {
  261. if (navigator.geolocation) {
  262. navigator.geolocation.getCurrentPosition(showPosition);
  263. } else {
  264. console.log("Geolocation is not supported by this browser.");
  265. return 0;
  266. }
  267. }
  268. catch (e) {
  269. console.log("Geolocation: error");
  270. }
  271. }
  272. function showPosition(position) {
  273. $("input[name='coordinates']").val(position.coords.latitude + "," + position.coords.longitude);
  274. console.log(position.coords);
  275. }
  276. // ### Serialization
  277. /**
  278. * Checks that an element has a non-empty `name` and `value` property.
  279. * @param {Element} element the element to check
  280. * @return {Bool} true if the element is an input, false if not
  281. */
  282. var isValidElement = function isValidElement(element) {
  283. return element.name && element.value;
  284. };
  285. /**
  286. * Checks if an element’s value can be saved (e.g. not an unselected checkbox).
  287. * @param {Element} element the element to check
  288. * @return {Boolean} true if the value should be added, false if not
  289. */
  290. var isValidValue = function isValidValue(element) {
  291. return !['checkbox', 'radio'].includes(element.type) || element.checked;
  292. };
  293. /**
  294. * Checks if an input is a checkbox, because checkboxes allow multiple values.
  295. * @param {Element} element the element to check
  296. * @return {Boolean} true if the element is a checkbox, false if not
  297. */
  298. var isCheckbox = function isCheckbox(element) {
  299. return element.type === 'checkbox';
  300. };
  301. //var isHidden = function isHidden(element) {
  302. // return element.type === 'hidden';
  303. //};
  304. /**
  305. * Checks if an input is a `select` with the `multiple` attribute.
  306. * @param {Element} element the element to check
  307. * @return {Boolean} true if the element is a multiselect, false if not
  308. */
  309. var isMultiSelect = function isMultiSelect(element) {
  310. return element.options && element.multiple;
  311. };
  312. /**
  313. * Retrieves the selected options from a multi-select as an array.
  314. * @param {HTMLOptionsCollection} options the options for the select
  315. * @return {Array} an array of selected option values
  316. */
  317. var getSelectValues = function getSelectValues(options) {
  318. return [].reduce.call(options, function (values, option) {
  319. return option.selected ? values.concat(option.value) : values;
  320. }, []);
  321. };
  322. /**
  323. * A more verbose implementation of `formToJSON()` to explain how it works.
  324. *
  325. * NOTE: This function is unused, and is only here for the purpose of explaining how
  326. * reducing form elements works.
  327. *
  328. * @param {HTMLFormControlsCollection} elements the form elements
  329. * @return {Object} form data as an object literal
  330. */
  331. var formToJSON_deconstructed = function formToJSON_deconstructed(elements) {
  332. // This is the function that is called on each element of the array.
  333. var reducerFunction = function reducerFunction(data, element) {
  334. // Add the current field to the object.
  335. data[element.name] = element.value;
  336. // For the demo only: show each step in the reducer’s progress.
  337. console.log(JSON.stringify(data));
  338. return data;
  339. };
  340. // This is used as the initial value of `data` in `reducerFunction()`.
  341. var reducerInitialValue = {};
  342. // To help visualize what happens, log the inital value, which we know is `{}`.
  343. console.log('Initial `data` value:', JSON.stringify(reducerInitialValue));
  344. // Now we reduce by `call`-ing `Array.prototype.reduce()` on `elements`.
  345. var formData = [].reduce.call(elements, reducerFunction, reducerInitialValue);
  346. // The result is then returned for use elsewhere.
  347. return formData;
  348. };
  349. /**
  350. * Retrieves input data from a form and returns it as a JSON object.
  351. * @param {HTMLFormControlsCollection} elements the form elements
  352. * @return {Object} form data as an object literal
  353. */
  354. var formToJSON = function formToJSON(elements) {
  355. return [].reduce.call(elements, function (data, element) {
  356. // Make sure the element has the required properties and should be added.
  357. if (isValidElement(element) && isValidValue(element)) {
  358. /*
  359. * Some fields allow for more than one value, so we need to check if this
  360. * is one of those fields and, if so, store the values as an array.
  361. */
  362. if (isCheckbox(element)) {
  363. data[element.name] = (data[element.name] || []).concat(element.value);
  364. } else if (isMultiSelect(element)) {
  365. data[element.name] = getSelectValues(element);
  366. } else {
  367. data[element.name] = element.value;
  368. }
  369. }
  370. return data;
  371. }, {});
  372. };