Source: smartdom.js

  1. let alertElement = null
  2. let alertTimeout = null
  3. /**
  4. * alert<br>
  5. * <props-opt></props-opt>
  6. * <table class="classtable">
  7. * <tr><td>msg</td><td>message [ default: "Alert!"]</td>
  8. * <tr><td>delay</td><td>delay ms</td>
  9. * <tr><td>kind</td><td>kind [ info / success / error ]</td>
  10. * </table>
  11. * @param props {object} props <opt-param />
  12. */
  13. function alert(propsOpt){
  14. let props = propsOpt || {}
  15. if(!alertElement) alertElement = div().poa().ac("alertelement")
  16. let de = document.documentElement
  17. de.style.position = "relative"
  18. de.appendChild(alertElement.e)
  19. alertElement.html(props.msg || "Alert!").dispi().rc("info success error")
  20. if(props.kind) alertElement.ac(props.kind)
  21. if(alertTimeout) clearTimeout(alertTimeout)
  22. alertTimeout = setTimeout(_ => {
  23. alertElement.x().dispn()
  24. }, props.delay || 3000)
  25. }
  26. /**
  27. * get a value from localStorage
  28. * @param path {string} path
  29. * @param defaultValue {any} default value ( returned if stored value is not available or non parsable )
  30. */
  31. function getLocal(path, defaultValue){
  32. let stored = localStorage.getItem(path)
  33. if(stored){
  34. try{
  35. let value = JSON.parse(stored)
  36. return value
  37. }catch(err){}
  38. }
  39. return defaultValue
  40. }
  41. /**
  42. * store a value in localStorage
  43. * @param path {string} path
  44. * @param value {any} value ( should be JSON serializable )
  45. * @returns true on success, false otherwise
  46. */
  47. function storeLocal(path, value){
  48. try{
  49. localStorage.setItem(path, JSON.stringify(value))
  50. return true
  51. }catch(err){}
  52. return false
  53. }
  54. /**
  55. * translate option initializer
  56. * @param obj {*} dicionary / array / string
  57. */
  58. function translateOption(obj){
  59. if(obj instanceof Array){
  60. return ({
  61. value: obj[0],
  62. display: obj[1]
  63. })
  64. }
  65. if(typeof obj == "string"){
  66. return ({
  67. value: obj,
  68. display: obj
  69. })
  70. }
  71. return obj
  72. }
  73. /**
  74. * Classes
  75. */
  76. /**
  77. * base class of smartdom wrapper
  78. * <props-opt></props-opt>
  79. * @param props {object} props <opt-param />
  80. */
  81. class SmartDomElement_{
  82. constructor(props){
  83. this.props = props || {}
  84. let tag = this.props.tag || "div"
  85. this.id = this.props.id
  86. this.childs = []
  87. this.e = document.createElement(tag)
  88. this.state = {}
  89. }
  90. /**
  91. * append childs
  92. * @param childs {...any} child elements, either single elements or arrays of elements
  93. * @example
  94. * div().a(
  95. * div().html("Single div appended."),
  96. * [
  97. * div().html("Div appended as array element 0."),
  98. * div().html("Div appended as array element 1."),
  99. * ]
  100. * )
  101. */
  102. a(...childs){
  103. let childList = []
  104. for(let child of childs){
  105. if(child instanceof Array) childList = childList.concat(child)
  106. else childList.push(child)
  107. }
  108. let index = 0
  109. for(let child of childList){
  110. child.parent = this
  111. child.index = index++
  112. this.childs.push(child)
  113. this.e.appendChild(child.e)
  114. }
  115. return this
  116. }
  117. /**
  118. * add style and return the instance
  119. * @param name {string} style name
  120. * @param value {string} style value
  121. */
  122. addStyle(name, value){
  123. this.e.style[name] = value
  124. return this
  125. }
  126. /**
  127. * add classes
  128. * @param classes {string} space separated list of classes
  129. */
  130. ac(classes){
  131. for(let klass of classes.split(" ")) this.e.classList.add(klass)
  132. return this
  133. }
  134. /**
  135. * remove classes
  136. * @param classes {string} space separated list of classes
  137. */
  138. rc(classes){
  139. for(let klass of classes.split(" ")) this.e.classList.remove(klass)
  140. return this
  141. }
  142. /**
  143. * set HTML element attribute
  144. * @param name {string} name
  145. * @param value {any} value
  146. */
  147. setAttribute(name, value){
  148. this.e.setAttribute(name, value)
  149. return this
  150. }
  151. /**
  152. * get HTML element attribute
  153. * @param name {string} name
  154. */
  155. getAttribute(name){
  156. return this.e.getAttribute(name)
  157. }
  158. /**
  159. * return element value
  160. */
  161. value(){
  162. return this.e.value
  163. }
  164. /**
  165. * set element value
  166. * @param value {any} value
  167. */
  168. setValue(value){
  169. this.e.value = value
  170. }
  171. /**
  172. * add event listeners with a callback
  173. * @param events {string} events separated by space
  174. * @param callback {function} callback
  175. */
  176. ae(events, callback){
  177. for(let event of events.split(" ")){
  178. this.e.addEventListener(event, callback)
  179. }
  180. }
  181. /**
  182. * delete content of element
  183. */
  184. x() {this.e.innerHTML="";return this}
  185. /**
  186. * set display
  187. * @param x {string} display
  188. */
  189. disp(x) {return this.addStyle("display", x)}
  190. /**
  191. * display none
  192. */
  193. dispn() {return this.disp("none")}
  194. /**
  195. * display initial
  196. */
  197. dispi() {return this.disp("initial")}
  198. /**
  199. * set width
  200. * @param x {number} width in pixels
  201. */
  202. w(x) {return this.addStyle("width", x + "px")}
  203. /**
  204. * set height
  205. * @param x {number} height in pixels
  206. */
  207. h(x) {return this.addStyle("height", x + "px")}
  208. /**
  209. * set padding
  210. * @param x {number} padding in pixels
  211. */
  212. pad(x) {return this.addStyle("padding", x + "px")}
  213. /**
  214. * set color
  215. * @param x {string} color
  216. */
  217. c(x) {return this.addStyle("color", x)}
  218. /**
  219. * set background-color
  220. * @param x {string} background color
  221. */
  222. bc(x) {return this.addStyle("backgroundColor", x)}
  223. /**
  224. * set position
  225. * @param x {string} position
  226. */
  227. pos(x) {return this.addStyle("position", x)}
  228. /**
  229. * position relative
  230. */
  231. por() {return this.pos("relative")}
  232. /**
  233. * position absolute
  234. */
  235. poa() {return this.pos("absolute")}
  236. /**
  237. * set inner html
  238. * @param x {string} HTML string
  239. */
  240. html(x) {this.e.innerHTML = x;return this}
  241. /**
  242. * the id used as a path element
  243. */
  244. pathId(){
  245. return this.id
  246. }
  247. /**
  248. * list of path ids leading to the element
  249. */
  250. pathList(){
  251. let pathList = []
  252. let current = this
  253. while(current){
  254. if(current.pathId()) pathList.unshift(current.pathId())
  255. current = current.parent
  256. }
  257. return pathList
  258. }
  259. /**
  260. * path to the element
  261. */
  262. path(){
  263. let pathList = this.pathList()
  264. if(!pathList.length) return null
  265. return pathList.join("/")
  266. }
  267. /**
  268. * store path of the element, by default it is the element path,
  269. * but this can be overridden with props.forceStorePath
  270. */
  271. storePath(){
  272. if(this.props.forceStorePath) return this.props.forceStorePath
  273. return this.path()
  274. }
  275. /**
  276. * store the element state in localStorage if it has a path
  277. */
  278. storeState(){
  279. if(this.storePath()){
  280. storeLocal(this.storePath(), this.state)
  281. }
  282. }
  283. /**
  284. * retrieve the element state from localStorage if it has a path
  285. */
  286. retrieveState(){
  287. if(this.storePath()){
  288. this.state = getLocal(this.storePath(), this.state)
  289. }
  290. }
  291. /**
  292. * initialize state from props, should be implemented by derived classes
  293. */
  294. initState(){
  295. // abstract
  296. }
  297. /**
  298. * build element from scratch, should be implemented by derived classes
  299. */
  300. build(){
  301. // abstract
  302. }
  303. /**
  304. * mount element
  305. */
  306. mount(){
  307. this.retrieveState()
  308. this.initState()
  309. this.build()
  310. this.mountChilds()
  311. }
  312. /**
  313. * mount childs of the element
  314. */
  315. mountChilds(){
  316. for(let child of this.childs){
  317. child.mount()
  318. }
  319. }
  320. }
  321. /**
  322. * wrapper class for HTML div element
  323. */
  324. class div_ extends SmartDomElement_{
  325. constructor(props){
  326. super(props)
  327. }
  328. }
  329. /**
  330. * returns a new div_ instance
  331. * @param props {object} props <opt-param />
  332. * @example
  333. * // creates a div with content "I'm a div."
  334. * div().html("I'm a div.")
  335. */
  336. function div(props){return new div_(props)}
  337. /**
  338. * wrapper class for HTML button element
  339. */
  340. class Button_ extends SmartDomElement_{
  341. /**
  342. * @param caption {string} caption
  343. * @param callback {function} callback
  344. * @param props {object} props <opt-param />
  345. */
  346. constructor(caption, callback, props){
  347. super({...props, ...{
  348. tag: "button"
  349. }})
  350. this.html(caption)
  351. if(callback) this.ae("click", callback)
  352. }
  353. }
  354. /**
  355. * returns a new Button_ instance
  356. * @param caption {string} caption
  357. * @param callback {function} callback
  358. * @param props {object} props <opt-param />
  359. */
  360. function Button(caption, callback, props){return new Button_(caption, callback, props)}
  361. /**
  362. * wrapper for HTML input element
  363. * @param props (object) props <opt-param />
  364. */
  365. class input_ extends SmartDomElement_{
  366. /**
  367. * <props-opt></props-opt>
  368. * <table class="classtable">
  369. * <tr><td>type</td><td>input type [ default: "text" ]</td>
  370. * </table>
  371. */
  372. constructor(props){
  373. super({...props, ...{
  374. tag: "input"
  375. }})
  376. this.setAttribute("type", props.type || "text")
  377. }
  378. }
  379. /**
  380. * returns a new input_ instance
  381. * @param props {object} props <opt-param />
  382. */
  383. function input(props){return input_(props)}
  384. /**
  385. * wrapper class for HTML table element
  386. * @param props {object} props <opt-param />
  387. */
  388. class table_ extends SmartDomElement_{
  389. /**
  390. * <props-opt></props-opt>
  391. * <table class="classtable">
  392. * <tr><td>cellpadding</td><td>cell padding</td>
  393. * <tr><td>cellspacing</td><td>cell spacing</td>
  394. * <tr><td>border</td><td>border width</td>
  395. * </table>
  396. */
  397. constructor(props){
  398. super({...props, ...{
  399. tag: "table"
  400. }})
  401. if(typeof this.props.cellpadding != "undefined") this.setAttribute("cellpadding", this.props.cellpadding)
  402. if(typeof this.props.cellspacing != "undefined") this.setAttribute("cellspacing", this.props.cellspacing)
  403. if(typeof this.props.border != "undefined") this.setAttribute("border", this.props.border)
  404. }
  405. }
  406. /**
  407. * returns a new table_ instance
  408. * @param props {object} props <opt-param />
  409. */
  410. function table(props){return new table_(props)}
  411. /**
  412. * wrapper class for HTML table head element
  413. */
  414. class thead_ extends SmartDomElement_{
  415. constructor(props){
  416. super({...props, ...{
  417. tag: "thead"
  418. }})
  419. }
  420. }
  421. /**
  422. * returns a new thead_ instance
  423. * @param props {object} props <opt-param />
  424. */
  425. function thead(props){return new thead_(props)}
  426. /**
  427. * wrapper class for HTML table body element
  428. */
  429. class tbody_ extends SmartDomElement_{
  430. constructor(props){
  431. super({...props, ...{
  432. tag: "tbody"
  433. }})
  434. }
  435. }
  436. /**
  437. * returns a new tbody_ instance
  438. * @param props {object} props <opt-param />
  439. */
  440. function tbody(props){return new tbody_(props)}
  441. /**
  442. * wrapper class for HTML table row element
  443. */
  444. class tr_ extends SmartDomElement_{
  445. constructor(props){
  446. super({...props, ...{
  447. tag: "tr"
  448. }})
  449. }
  450. }
  451. /**
  452. * returns a new tr_ instance
  453. * @param props {object} props <opt-param />
  454. */
  455. function tr(props){return new tr_(props)}
  456. /**
  457. * wrapper class for HTML select element
  458. */
  459. class select_ extends SmartDomElement_{
  460. constructor(props){
  461. super({...props, ...{
  462. tag: "select"
  463. }})
  464. }
  465. }
  466. /**
  467. * returns a new select_ instance
  468. * @param props {object} props <opt-param />
  469. */
  470. function select(props){return new select_(props)}
  471. /**
  472. * wrapper class for HTML option element
  473. * @param props {object} props <opt-param />, see class constructor
  474. */
  475. class option_ extends SmartDomElement_{
  476. /**
  477. * <props-opt></props-opt>
  478. * <table class="classtable">
  479. * <tr><td>value</td><td>option value</td>
  480. * <tr><td>display</td><td>option display</td>
  481. * </table>
  482. */
  483. constructor(props){
  484. super({...props, ...{
  485. tag: "option"
  486. }})
  487. if(this.props.value) this.setAttribute("value", this.props.value)
  488. if(this.props.display) this.html(this.props.display)
  489. }
  490. }
  491. /**
  492. * returns a new option_ instance
  493. * @param props {object} props <opt-param />, see class constructor
  494. */
  495. function option(props){return new option_(props)}
  496. /**
  497. * wrapper class for HTML checkbox input element
  498. * @param props {object} props <opt-param />, see class constructor
  499. */
  500. class CheckBoxInput_ extends input_{
  501. /**
  502. * <props-opt></props-opt>
  503. * <table class="classtable">
  504. * <tr><td>forceChecked</td><td>boolean, force checked status to true or false</td>
  505. * <tr><td>changeCallback</td><td>change callback</td>
  506. * </table>
  507. */
  508. constructor(props){
  509. super({...props, ...{
  510. type: "checkbox"
  511. }})
  512. this.ae("change", this.changed.bind(this))
  513. }
  514. /**
  515. * handle change event
  516. */
  517. changed(){
  518. this.state.checked = this.e.checked
  519. this.storeState()
  520. if(this.props.changeCallback) this.props.changeCallback(this.state.checked)
  521. }
  522. /**
  523. * init state
  524. */
  525. initState(){
  526. if(typeof this.props.forceChecked != "undefined") this.state.checked = this.props.forceChecked
  527. }
  528. /**
  529. * build
  530. */
  531. build(){
  532. this.e.checked = this.state.checked
  533. }
  534. }
  535. /**
  536. * returns a new CheckBoxInput_ instance
  537. * @param props {object} props <opt-param />, see class constructor
  538. */
  539. function CheckBoxInput(props){return new CheckBoxInput_(props)}
  540. /**
  541. * wrapper class for HTML table cell element
  542. */
  543. class td_ extends SmartDomElement_{
  544. constructor(props){
  545. super({...props, ...{
  546. tag: "td"
  547. }})
  548. }
  549. }
  550. /**
  551. * returns a new td_ instance
  552. * @param props {object} props <opt-param />
  553. */
  554. function td(props){return new td_(props)}
  555. /**
  556. * combo
  557. * @param props {object} props <opt-param />, see class constructor
  558. */
  559. class Combo_ extends select_{
  560. /**
  561. * <props-opt></props-opt>
  562. * <table class="classtable">
  563. * <tr><td>forceOptions</td><td>list of options, allowed option formats {value: "foo", display: "bar"} / ["foo", "bar"] / "foo" ( display will also be "foo")</td>
  564. * <tr><td>forceSelected</td><td>selected option value</td>
  565. * <tr><td>changeCallback</td><td>change callback</td>
  566. * </table>
  567. */
  568. constructor(props){
  569. super({...props, ...{
  570. }})
  571. this.ae("change", this.changed.bind(this))
  572. }
  573. /**
  574. * handle change event
  575. */
  576. changed(){
  577. this.state.selected = this.value()
  578. this.storeState()
  579. if(this.props.changeCallback) this.props.changeCallback(this.state.selected)
  580. }
  581. /**
  582. * init state
  583. */
  584. initState(){
  585. if(!this.state.options) this.state.options = []
  586. if(this.props.forceOptions) this.state.options = this.props.forceOptions
  587. if(this.props.forceSelected) this.state.selected = this.props.forceSelected
  588. this.translateOptions()
  589. this.storeState()
  590. }
  591. /**
  592. * translate options
  593. */
  594. translateOptions(){
  595. this.state.options = this.state.options.map(opt => translateOption(opt))
  596. }
  597. /**
  598. * build
  599. */
  600. build(){
  601. this.translateOptions()
  602. this.x().a(
  603. this.state.options.map(
  604. opt => {
  605. let o = option(opt)
  606. if(opt.value == this.state.selected) o.setAttribute("selected", true)
  607. return o
  608. }
  609. )
  610. )
  611. }
  612. }
  613. /**
  614. * returns a new Combo_ instance
  615. * @param props {object} props <opt-param />, see class constructor
  616. */
  617. function Combo(props){return new Combo_(props)}
  618. /**
  619. * options table
  620. * @param props {object} see class constructor
  621. */
  622. class OptionsTable_ extends table_{
  623. /**
  624. * props should have an options field
  625. * <table class="classtable">
  626. * <tr><td>options</td><td>array of input elements,
  627. * each input element should have a display field in its props, telling the name of the option</td>
  628. * </table>
  629. */
  630. constructor(props){
  631. super({...props, ...{
  632. }})
  633. }
  634. /**
  635. * build
  636. */
  637. build(){
  638. this.a(
  639. thead().a(
  640. tr().a(
  641. td().html("Option Name"),
  642. td().html("Option Value"),
  643. )
  644. ),
  645. tbody().a(
  646. this.props.options.map(option => tr().a(
  647. td().html(option.props.display),
  648. td().a(option),
  649. ))
  650. )
  651. )
  652. }
  653. }
  654. /**
  655. * returns a new OptionsTable_ instance
  656. * @param props {object} props, see class constructor
  657. */
  658. function OptionsTable(props){return new OptionsTable_(props)}
  659. module.exports = {
  660. div: div,
  661. input: input,
  662. CheckBoxInput: CheckBoxInput,
  663. table: table,
  664. thead: thead,
  665. tbody: tbody,
  666. tr: tr,
  667. td: td,
  668. OptionsTable: OptionsTable,
  669. select: select,
  670. option: option,
  671. Combo: Combo,
  672. Button: Button,
  673. alert: alert,
  674. }