gluon-web.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. /*
  2. Copyright 2008 Steven Barth <steven@midlink.org>
  3. Copyright 2008-2012 Jo-Philipp Wich <jow@openwrt.org>
  4. Copyright 2017 Matthias Schiffer <mschiffer@universe-factory.net>
  5. Licensed under the Apache License, Version 2.0 (the "License");
  6. you may not use this file except in compliance with the License.
  7. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. */
  10. /*
  11. Build using:
  12. uglifyjs javascript/gluon-web.js -o files/lib/gluon/web/www/static/resources/gluon-web.js -c -m --support-ie8
  13. */
  14. (function() {
  15. var dep_entries = {};
  16. function Int(x) {
  17. return (/^-?\d+$/.test(x) ? +x : NaN);
  18. }
  19. function Dec(x) {
  20. return (/^-?\d*\.?\d+?$/.test(x) ? +x : NaN);
  21. }
  22. var validators = {
  23. 'integer': function() {
  24. return !isNaN(Int(this));
  25. },
  26. 'uinteger': function() {
  27. return (Int(this) >= 0);
  28. },
  29. 'float': function() {
  30. return !isNaN(Dec(this));
  31. },
  32. 'ufloat': function() {
  33. return (Dec(this) >= 0);
  34. },
  35. 'ipaddr': function() {
  36. return validators.ip4addr.apply(this) ||
  37. validators.ip6addr.apply(this);
  38. },
  39. 'ip4addr': function() {
  40. var match;
  41. if ((match = this.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/))) {
  42. return (match[1] >= 0) && (match[1] <= 255) &&
  43. (match[2] >= 0) && (match[2] <= 255) &&
  44. (match[3] >= 0) && (match[3] <= 255) &&
  45. (match[4] >= 0) && (match[4] <= 255);
  46. }
  47. return false;
  48. },
  49. 'ip6addr': function() {
  50. if (this.indexOf('::') < 0)
  51. return (this.match(/^(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}$/i) != null);
  52. if (
  53. (this.indexOf(':::') >= 0) || this.match(/::.+::/) ||
  54. this.match(/^:[^:]/) || this.match(/[^:]:$/)
  55. )
  56. return false;
  57. if (this.match(/^(?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}$/i))
  58. return true;
  59. if (this.match(/^(?:[a-f0-9]{1,4}:){7}:$/i))
  60. return true;
  61. if (this.match(/^:(?::[a-f0-9]{1,4}){7}$/i))
  62. return true;
  63. return false;
  64. },
  65. 'wpakey': function() {
  66. var v = this;
  67. if (v.length == 64)
  68. return (v.match(/^[a-f0-9]{64}$/i) != null);
  69. else
  70. return (v.length >= 8) && (v.length <= 63);
  71. },
  72. 'range': function(min, max) {
  73. var val = Dec(this);
  74. return (val >= +min && val <= +max);
  75. },
  76. 'min': function(min) {
  77. return (Dec(this) >= +min);
  78. },
  79. 'max': function(max) {
  80. return (Dec(this) <= +max);
  81. },
  82. 'irange': function(min, max) {
  83. var val = Int(this);
  84. return (val >= +min && val <= +max);
  85. },
  86. 'imin': function(min) {
  87. return (Int(this) >= +min);
  88. },
  89. 'imax': function(max) {
  90. return (Int(this) <= +max);
  91. },
  92. 'minlength': function(min) {
  93. return ((''+this).length >= +min);
  94. },
  95. 'maxlength': function(max) {
  96. return ((''+this).length <= +max);
  97. },
  98. };
  99. function compile(type) {
  100. var v, match;
  101. if ((match = type.match(/^([^\(]+)\(([^,]+),([^\)]+)\)$/)) && (v = validators[match[1]]) !== undefined) {
  102. return function() {
  103. return v.apply(this, [match[2], match[3]]);
  104. }
  105. } else if ((match = type.match(/^([^\(]+)\(([^,\)]+)\)$/)) && (v = validators[match[1]]) !== undefined) {
  106. return function() {
  107. return v.apply(this, [match[2]]);
  108. }
  109. } else {
  110. return validators[type];
  111. }
  112. }
  113. function checkvalue(target, ref) {
  114. var t = document.getElementById(target);
  115. var value;
  116. if (t) {
  117. if (t.type == "checkbox") {
  118. value = t.checked;
  119. } else if (t.value) {
  120. value = t.value;
  121. } else {
  122. value = "";
  123. }
  124. return (value == ref);
  125. } else {
  126. t = document.getElementById(target + '.' + ref);
  127. if (t)
  128. return (t.type == "radio" && t.checked);
  129. }
  130. return false;
  131. }
  132. function check(deps) {
  133. for (var i=0; i < deps.length; i++) {
  134. var stat = true;
  135. for (var j in deps[i]) {
  136. stat = (stat && checkvalue(j, deps[i][j]));
  137. }
  138. if (stat)
  139. return true;
  140. }
  141. return false;
  142. }
  143. function update() {
  144. var state = false;
  145. for (var id in dep_entries) {
  146. var entry = dep_entries[id];
  147. var node = document.getElementById(id);
  148. var parent = document.getElementById(entry.parent);
  149. if (node && node.parentNode && !check(entry.deps)) {
  150. node.parentNode.removeChild(node);
  151. state = true;
  152. } else if (parent && (!node || !node.parentNode) && check(entry.deps)) {
  153. var next = undefined;
  154. for (next = parent.firstChild; next; next = next.nextSibling) {
  155. if (next.getAttribute && parseInt(next.getAttribute('data-index'), 10) > entry.index) {
  156. break;
  157. }
  158. }
  159. if (!next) {
  160. parent.appendChild(entry.node);
  161. } else {
  162. parent.insertBefore(entry.node, next);
  163. }
  164. state = true;
  165. }
  166. // hide optionals widget if no choices remaining
  167. if (parent && parent.parentNode && parent.getAttribute('data-optionals'))
  168. parent.parentNode.style.display = (parent.options.length <= 1) ? 'none' : '';
  169. }
  170. if (state) {
  171. update();
  172. }
  173. }
  174. function bind(obj, type, callback, mode) {
  175. if (!obj.addEventListener) {
  176. obj.attachEvent('on' + type,
  177. function() {
  178. var e = window.event;
  179. if (!e.target && e.srcElement)
  180. e.target = e.srcElement;
  181. return !!callback(e);
  182. }
  183. );
  184. } else {
  185. obj.addEventListener(type, callback, !!mode);
  186. }
  187. return obj;
  188. }
  189. function init_dynlist(parent, attr) {
  190. var prefix = attr.prefix;
  191. function dynlist_redraw(focus, add, del) {
  192. var values = [];
  193. while (parent.firstChild) {
  194. var n = parent.firstChild;
  195. var i = +n.index;
  196. if (i != del) {
  197. if (n.nodeName.toLowerCase() == 'input')
  198. values.push(n.value || '');
  199. else if (n.nodeName.toLowerCase() == 'select')
  200. values[values.length-1] = n.options[n.selectedIndex].value;
  201. }
  202. parent.removeChild(n);
  203. }
  204. if (add >= 0) {
  205. focus = add + 1;
  206. values.splice(add, 0, '');
  207. } else if (!attr.optional && values.length == 0) {
  208. values.push('');
  209. }
  210. for (var i = 1; i <= values.length; i++) {
  211. var t = document.createElement('input');
  212. t.id = prefix + '.' + i;
  213. t.name = prefix;
  214. t.value = values[i-1];
  215. t.type = 'text';
  216. t.index = i;
  217. t.className = 'gluon-input-text';
  218. if (attr.size)
  219. t.size = attr.size;
  220. if (attr.placeholder)
  221. t.placeholder = attr.placeholder;
  222. parent.appendChild(t);
  223. if (attr.type)
  224. validate_field(t, false, attr.type);
  225. bind(t, 'keydown', dynlist_keydown);
  226. bind(t, 'keypress', dynlist_keypress);
  227. if (i == focus) {
  228. t.focus();
  229. } else if (-i == focus) {
  230. t.focus();
  231. /* force cursor to end */
  232. var v = t.value;
  233. t.value = ' '
  234. t.value = v;
  235. }
  236. if (attr.optional || values.length > 1) {
  237. var b = document.createElement('span');
  238. b.className = 'gluon-remove';
  239. parent.appendChild(b);
  240. bind(b, 'click', dynlist_btnclick(false));
  241. parent.appendChild(document.createElement('br'));
  242. }
  243. }
  244. var b = document.createElement('span');
  245. b.className = 'gluon-add';
  246. parent.appendChild(b);
  247. bind(b, 'click', dynlist_btnclick(true));
  248. }
  249. function dynlist_keypress(ev) {
  250. ev = ev ? ev : window.event;
  251. var se = ev.target ? ev.target : ev.srcElement;
  252. if (se.nodeType == 3)
  253. se = se.parentNode;
  254. switch (ev.keyCode) {
  255. /* backspace, delete */
  256. case 8:
  257. case 46:
  258. if (se.value.length == 0) {
  259. if (ev.preventDefault)
  260. ev.preventDefault();
  261. return false;
  262. }
  263. return true;
  264. /* enter, arrow up, arrow down */
  265. case 13:
  266. case 38:
  267. case 40:
  268. if (ev.preventDefault)
  269. ev.preventDefault();
  270. return false;
  271. }
  272. return true;
  273. }
  274. function dynlist_keydown(ev) {
  275. ev = ev ? ev : window.event;
  276. var se = ev.target ? ev.target : ev.srcElement;
  277. var index = 0;
  278. var prev, next;
  279. if (se) {
  280. if (se.nodeType == 3)
  281. se = se.parentNode;
  282. index = se.index;
  283. prev = se.previousSibling;
  284. while (prev && prev.name != prefix)
  285. prev = prev.previousSibling;
  286. next = se.nextSibling;
  287. while (next && next.name != prefix)
  288. next = next.nextSibling;
  289. }
  290. switch (ev.keyCode) {
  291. /* backspace, delete */
  292. case 8:
  293. case 46:
  294. var del = (se.nodeName.toLowerCase() == 'select')
  295. ? true : (se.value.length == 0);
  296. if (del) {
  297. if (ev.preventDefault)
  298. ev.preventDefault();
  299. var focus = se.index;
  300. if (ev.keyCode == 8)
  301. focus = -focus+1;
  302. dynlist_redraw(focus, -1, index);
  303. return false;
  304. }
  305. break;
  306. /* enter */
  307. case 13:
  308. dynlist_redraw(-1, index, -1);
  309. break;
  310. /* arrow up */
  311. case 38:
  312. if (prev)
  313. prev.focus();
  314. break;
  315. /* arrow down */
  316. case 40:
  317. if (next)
  318. next.focus();
  319. break;
  320. }
  321. return true;
  322. }
  323. function dynlist_btnclick(add) {
  324. return function(ev) {
  325. ev = ev ? ev : window.event;
  326. var se = ev.target ? ev.target : ev.srcElement;
  327. var input = se.previousSibling;
  328. while (input && input.name != prefix) {
  329. input = input.previousSibling;
  330. }
  331. if (add) {
  332. dynlist_keydown({
  333. target: input,
  334. keyCode: 13
  335. });
  336. } else {
  337. input.value = '';
  338. dynlist_keydown({
  339. target: input,
  340. keyCode: 8
  341. });
  342. }
  343. return false;
  344. }
  345. }
  346. dynlist_redraw(NaN, -1, -1);
  347. }
  348. function validate_field(field, optional, type) {
  349. var check = compile(type);
  350. if (!check)
  351. return;
  352. var validator = function() {
  353. if (!field.form)
  354. return;
  355. field.className = field.className.replace(/ gluon-input-invalid/g, '');
  356. var value = (field.options && field.options.selectedIndex > -1)
  357. ? field.options[field.options.selectedIndex].value : field.value;
  358. if (!(((value.length == 0) && optional) || check.apply(value)))
  359. field.className += ' gluon-input-invalid';
  360. };
  361. bind(field, "blur", validator);
  362. bind(field, "keyup", validator);
  363. if (field.nodeName.toLowerCase() == 'select') {
  364. bind(field, "change", validator);
  365. bind(field, "click", validator);
  366. }
  367. validator();
  368. }
  369. function add(obj, dep, index) {
  370. var entry = dep_entries[obj.id];
  371. if (!entry) {
  372. entry = {
  373. "node": obj,
  374. "parent": obj.parentNode.id,
  375. "deps": [],
  376. "index": index
  377. };
  378. dep_entries[obj.id] = entry;
  379. }
  380. entry.deps.push(dep)
  381. }
  382. (function() {
  383. var nodes;
  384. nodes = document.querySelectorAll('[data-depends]');
  385. for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
  386. var index = parseInt(node.getAttribute('data-index'), 10);
  387. var depends = JSON.parse(node.getAttribute('data-depends'));
  388. if (!isNaN(index) && depends.length > 0) {
  389. for (var alt = 0; alt < depends.length; alt++) {
  390. add(node, depends[alt], index);
  391. }
  392. }
  393. }
  394. nodes = document.querySelectorAll('[data-update]');
  395. for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
  396. var events = node.getAttribute('data-update').split(' ');
  397. for (var j = 0, event; (event = events[j]) !== undefined; j++) {
  398. bind(node, event, update);
  399. }
  400. }
  401. nodes = document.querySelectorAll('[data-type]');
  402. for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
  403. validate_field(node, node.getAttribute('data-optional') === 'true',
  404. node.getAttribute('data-type'));
  405. }
  406. nodes = document.querySelectorAll('[data-dynlist]');
  407. for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
  408. var attr = JSON.parse(node.getAttribute('data-dynlist'));
  409. init_dynlist(node, attr);
  410. }
  411. update();
  412. })();
  413. })();