popup.js 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069
  1. /**
  2. * Popup generator for Stock tools
  3. *
  4. * (c) 2009-2017 Sebastian Bochan
  5. *
  6. * License: www.highcharts.com/license
  7. */
  8. 'use strict';
  9. import H from '../parts/Globals.js';
  10. var addEvent = H.addEvent,
  11. createElement = H.createElement,
  12. objectEach = H.objectEach,
  13. pick = H.pick,
  14. wrap = H.wrap,
  15. isString = H.isString,
  16. isObject = H.isObject,
  17. isArray = H.isArray,
  18. indexFilter = /\d/g,
  19. PREFIX = 'highcharts-',
  20. DIV = 'div',
  21. INPUT = 'input',
  22. LABEL = 'label',
  23. BUTTON = 'button',
  24. SELECT = 'select',
  25. OPTION = 'option',
  26. SPAN = 'span',
  27. UL = 'ul',
  28. LI = 'li',
  29. H3 = 'h3';
  30. // onContainerMouseDown blocks internal popup events, due to e.preventDefault.
  31. // Related issue #4606
  32. wrap(H.Pointer.prototype, 'onContainerMouseDown', function (proceed, e) {
  33. var popupClass = e.target && e.target.className;
  34. // elements is not in popup
  35. if (!(isString(popupClass) &&
  36. popupClass.indexOf(PREFIX + 'popup-field') >= 0)
  37. ) {
  38. proceed.apply(this, Array.prototype.slice.call(arguments, 1));
  39. }
  40. });
  41. H.Popup = function (parentDiv) {
  42. this.init(parentDiv);
  43. };
  44. H.Popup.prototype = {
  45. /*
  46. * Initialize the popup. Create base div and add close button.
  47. *
  48. * @param {HTMLDOMElement} - container where popup should be placed
  49. *
  50. * @return {HTMLDOMElement} - return created popup's div
  51. *
  52. */
  53. init: function (parentDiv) {
  54. // create popup div
  55. this.container = createElement(DIV, {
  56. className: PREFIX + 'popup'
  57. }, null, parentDiv);
  58. this.lang = this.getLangpack();
  59. // add close button
  60. this.addCloseBtn();
  61. },
  62. /*
  63. * Create HTML element and attach click event (close popup).
  64. *
  65. */
  66. addCloseBtn: function () {
  67. var _self = this,
  68. closeBtn;
  69. // create close popup btn
  70. closeBtn = createElement(DIV, {
  71. className: PREFIX + 'popup-close'
  72. }, null, this.container);
  73. ['click', 'touchstart'].forEach(function (eventName) {
  74. addEvent(closeBtn, eventName, function () {
  75. _self.closePopup();
  76. });
  77. });
  78. },
  79. /*
  80. * Create two columns (divs) in HTML.
  81. *
  82. * @param {HTMLDOMElement} - container of columns
  83. *
  84. * @return {Object} - reference to two HTML columns
  85. *
  86. */
  87. addColsContainer: function (container) {
  88. var rhsCol,
  89. lhsCol;
  90. // left column
  91. lhsCol = createElement(DIV, {
  92. className: PREFIX + 'popup-lhs-col'
  93. }, null, container);
  94. // right column
  95. rhsCol = createElement(DIV, {
  96. className: PREFIX + 'popup-rhs-col'
  97. }, null, container);
  98. // wrapper content
  99. createElement(DIV, {
  100. className: PREFIX + 'popup-rhs-col-wrapper'
  101. }, null, rhsCol);
  102. return {
  103. lhsCol: lhsCol,
  104. rhsCol: rhsCol
  105. };
  106. },
  107. /*
  108. * Create input with label.
  109. *
  110. * @param {String} - chain of fields i.e params.styles.fontSize
  111. * @param {String} - indicator type
  112. * @param {HTMLDOMElement} - container where elements should be added
  113. * @param {String} - dafault value of input i.e period value is 14,
  114. * extracted from defaultOptions (ADD mode) or series options (EDIT mode)
  115. *
  116. */
  117. addInput: function (option, type, parentDiv, value) {
  118. var optionParamList = option.split('.'),
  119. optionName = optionParamList[optionParamList.length - 1],
  120. lang = this.lang,
  121. inputName = PREFIX + type + '-' + optionName;
  122. if (!inputName.match(indexFilter)) {
  123. // add label
  124. createElement(
  125. LABEL, {
  126. innerHTML: lang[optionName] || optionName,
  127. htmlFor: inputName
  128. },
  129. null,
  130. parentDiv
  131. );
  132. }
  133. // add input
  134. createElement(
  135. INPUT,
  136. {
  137. name: inputName,
  138. value: value[0],
  139. type: value[1],
  140. className: PREFIX + 'popup-field'
  141. },
  142. null,
  143. parentDiv
  144. ).setAttribute(PREFIX + 'data-name', option);
  145. },
  146. /*
  147. * Create button.
  148. *
  149. * @param {HTMLDOMElement} - container where elements should be added
  150. * @param {String} - text placed as button label
  151. * @param {String} - add | edit | remove
  152. * @param {Function} - on click callback
  153. * @param {HTMLDOMElement} - container where inputs are generated
  154. *
  155. * @return {HTMLDOMElement} - html button
  156. */
  157. addButton: function (parentDiv, label, type, callback, fieldsDiv) {
  158. var _self = this,
  159. closePopup = this.closePopup,
  160. getFields = this.getFields,
  161. button;
  162. button = createElement(BUTTON, {
  163. innerHTML: label
  164. }, null, parentDiv);
  165. ['click', 'touchstart'].forEach(function (eventName) {
  166. addEvent(button, eventName, function () {
  167. closePopup.call(_self);
  168. return callback(
  169. getFields(fieldsDiv, type)
  170. );
  171. });
  172. });
  173. return button;
  174. },
  175. /*
  176. * Get values from all inputs and create JSON.
  177. *
  178. * @param {HTMLDOMElement} - container where inputs are created
  179. * @param {String} - add | edit | remove
  180. *
  181. * @return {Object} - fields
  182. */
  183. getFields: function (parentDiv, type) {
  184. var inputList = parentDiv.querySelectorAll('input'),
  185. optionSeries = '#' + PREFIX + 'select-series > option:checked',
  186. optionVolume = '#' + PREFIX + 'select-volume > option:checked',
  187. linkedTo = parentDiv.querySelectorAll(optionSeries)[0],
  188. volumeTo = parentDiv.querySelectorAll(optionVolume)[0],
  189. seriesId,
  190. param,
  191. fieldsOutput;
  192. fieldsOutput = {
  193. actionType: type,
  194. linkedTo: linkedTo && linkedTo.getAttribute('value'),
  195. fields: { }
  196. };
  197. inputList.forEach(function (input) {
  198. param = input.getAttribute(PREFIX + 'data-name');
  199. seriesId = input.getAttribute(PREFIX + 'data-series-id');
  200. // params
  201. if (seriesId) {
  202. fieldsOutput.seriesId = input.value;
  203. } else if (param) {
  204. fieldsOutput.fields[param] = input.value;
  205. } else {
  206. // type like sma / ema
  207. fieldsOutput.type = input.value;
  208. }
  209. });
  210. if (volumeTo) {
  211. fieldsOutput.fields['params.volumeSeriesID'] = volumeTo
  212. .getAttribute('value');
  213. }
  214. return fieldsOutput;
  215. },
  216. /*
  217. * Reset content of the current popup and show.
  218. *
  219. * @param {Chart} - chart
  220. * @param {Function} - on click callback
  221. *
  222. * @return {Object} - fields
  223. */
  224. showPopup: function () {
  225. var popupDiv = this.container,
  226. toolbarClass = PREFIX + 'annotation-toolbar',
  227. popupCloseBtn = popupDiv
  228. .querySelectorAll('.' + PREFIX + 'popup-close')[0];
  229. // reset content
  230. popupDiv.innerHTML = '';
  231. // reset toolbar styles if exists
  232. if (popupDiv.className.indexOf(toolbarClass) >= 0) {
  233. popupDiv.classList.remove(toolbarClass);
  234. // reset toolbar inline styles
  235. popupDiv.removeAttribute('style');
  236. }
  237. // add close button
  238. popupDiv.appendChild(popupCloseBtn);
  239. popupDiv.style.display = 'block';
  240. },
  241. /*
  242. * Hide popup.
  243. *
  244. */
  245. closePopup: function () {
  246. this.popup.container.style.display = 'none';
  247. },
  248. /*
  249. * Create content and show popup.
  250. *
  251. * @param {String} - type of popup i.e indicators
  252. * @param {Chart} - chart
  253. * @param {Object} - options
  254. * @param {Function} - on click callback
  255. *
  256. */
  257. showForm: function (type, chart, options, callback) {
  258. this.popup = chart.navigationBindings.popup;
  259. // show blank popup
  260. this.showPopup();
  261. // indicator form
  262. if (type === 'indicators') {
  263. this.indicators.addForm.call(this, chart, options, callback);
  264. }
  265. // annotation small toolbar
  266. if (type === 'annotation-toolbar') {
  267. this.annotations.addToolbar.call(this, chart, options, callback);
  268. }
  269. // annotation edit form
  270. if (type === 'annotation-edit') {
  271. this.annotations.addForm.call(this, chart, options, callback);
  272. }
  273. // flags form - add / edit
  274. if (type === 'flag') {
  275. this.annotations.addForm.call(this, chart, options, callback, true);
  276. }
  277. },
  278. /*
  279. * Return lang definitions for popup.
  280. *
  281. * @return {Object} - elements translations.
  282. */
  283. getLangpack: function () {
  284. return H.getOptions().lang.navigation.popup;
  285. },
  286. annotations: {
  287. /*
  288. * Create annotation simple form. It contains two buttons
  289. * (edit / remove) and text label.
  290. *
  291. * @param {Chart} - chart
  292. * @param {Object} - options
  293. * @param {Function} - on click callback
  294. *
  295. */
  296. addToolbar: function (chart, options, callback) {
  297. var _self = this,
  298. lang = this.lang,
  299. popupDiv = this.popup.container,
  300. showForm = this.showForm,
  301. toolbarClass = PREFIX + 'annotation-toolbar',
  302. button;
  303. // set small size
  304. if (popupDiv.className.indexOf(toolbarClass) === -1) {
  305. popupDiv.className += ' ' + toolbarClass;
  306. }
  307. // set position
  308. popupDiv.style.top = chart.plotTop + 10 + 'px';
  309. // create label
  310. createElement(SPAN, {
  311. innerHTML: pick(
  312. // Advanced annotations:
  313. lang[options.langKey] || options.langKey,
  314. // Basic shapes:
  315. options.shapes && options.shapes[0].type
  316. )
  317. }, null, popupDiv);
  318. // add buttons
  319. button = this.addButton(
  320. popupDiv,
  321. lang.removeButton || 'remove',
  322. 'remove',
  323. callback,
  324. popupDiv
  325. );
  326. button.className += ' ' + PREFIX + 'annotation-remove-button';
  327. button = this.addButton(
  328. popupDiv,
  329. lang.editButton || 'edit',
  330. 'edit',
  331. function () {
  332. showForm.call(
  333. _self,
  334. 'annotation-edit',
  335. chart,
  336. options,
  337. callback
  338. );
  339. },
  340. popupDiv
  341. );
  342. button.className += ' ' + PREFIX + 'annotation-edit-button';
  343. },
  344. /*
  345. * Create annotation simple form.
  346. * It contains fields with param names.
  347. *
  348. * @param {Chart} - chart
  349. * @param {Object} - options
  350. * @param {Function} - on click callback
  351. * @param {Boolean} - if it is a form declared for init annotation
  352. *
  353. */
  354. addForm: function (chart, options, callback, isInit) {
  355. var popupDiv = this.popup.container,
  356. lang = this.lang,
  357. bottomRow,
  358. lhsCol;
  359. // create title of annotations
  360. lhsCol = createElement('h2', {
  361. innerHTML: lang[options.langKey] || options.langKey,
  362. className: PREFIX + 'popup-main-title'
  363. }, null, popupDiv);
  364. // left column
  365. lhsCol = createElement(DIV, {
  366. className: PREFIX + 'popup-lhs-col ' + PREFIX + 'popup-lhs-full'
  367. }, null, popupDiv);
  368. bottomRow = createElement(DIV, {
  369. className: PREFIX + 'popup-bottom-row'
  370. }, null, popupDiv);
  371. this.annotations.addFormFields.call(
  372. this,
  373. lhsCol,
  374. chart,
  375. '',
  376. options,
  377. [],
  378. true
  379. );
  380. this.addButton(
  381. bottomRow,
  382. isInit ?
  383. (lang.addButton || 'add') :
  384. (lang.saveButton || 'save'),
  385. isInit ? 'add' : 'save',
  386. callback,
  387. popupDiv
  388. );
  389. },
  390. /*
  391. * Create annotation's form fields.
  392. *
  393. * @param {HTMLDOMElement} - div where inputs are placed
  394. * @param {Chart} - chart
  395. * @param {String} - name of parent to create chain of names
  396. * @param {Object} - options
  397. * @param {Array} - storage - array where all items are stored
  398. * @param {Boolean} - isRoot - recursive flag for root
  399. *
  400. */
  401. addFormFields: function (
  402. parentDiv,
  403. chart,
  404. parentNode,
  405. options,
  406. storage,
  407. isRoot
  408. ) {
  409. var _self = this,
  410. addFormFields = this.annotations.addFormFields,
  411. addInput = this.addInput,
  412. lang = this.lang,
  413. parentFullName,
  414. titleName;
  415. objectEach(options, function (value, option) {
  416. // create name like params.styles.fontSize
  417. parentFullName = parentNode !== '' ?
  418. parentNode + '.' + option : option;
  419. if (isObject(value)) {
  420. if (
  421. // value is object of options
  422. !isArray(value) ||
  423. // array of objects with params. i.e labels in Fibonacci
  424. (isArray(value) && isObject(value[0]))
  425. ) {
  426. titleName = lang[option] || option;
  427. if (!titleName.match(indexFilter)) {
  428. storage.push([
  429. true,
  430. titleName,
  431. parentDiv
  432. ]);
  433. }
  434. addFormFields.call(
  435. _self,
  436. parentDiv,
  437. chart,
  438. parentFullName,
  439. value,
  440. storage,
  441. false
  442. );
  443. } else {
  444. storage.push([
  445. _self,
  446. parentFullName,
  447. 'annotation',
  448. parentDiv,
  449. value
  450. ]);
  451. }
  452. }
  453. });
  454. if (isRoot) {
  455. storage = storage.sort(function (a) {
  456. return a[1].match(/format/g) ? -1 : 1;
  457. });
  458. storage.forEach(function (genInput) {
  459. if (genInput[0] === true) {
  460. createElement(SPAN, {
  461. className: PREFIX + 'annotation-title',
  462. innerHTML: genInput[1]
  463. }, null, genInput[2]);
  464. } else {
  465. addInput.apply(genInput[0], genInput.splice(1));
  466. }
  467. });
  468. }
  469. }
  470. },
  471. indicators: {
  472. /*
  473. * Create indicator's form. It contains two tabs (ADD and EDIT) with
  474. * content.
  475. *
  476. * @param {Chart} - chart
  477. * @param {Object} - options
  478. * @param {Function} - on click callback
  479. *
  480. */
  481. addForm: function (chart, options, callback) {
  482. var tabsContainers,
  483. indicators = this.indicators,
  484. lang = this.lang,
  485. buttonParentDiv;
  486. // add tabs
  487. this.tabs.init.call(this, chart);
  488. // get all tabs content divs
  489. tabsContainers = this.popup.container
  490. .querySelectorAll('.' + PREFIX + 'tab-item-content');
  491. // ADD tab
  492. this.addColsContainer(tabsContainers[0]);
  493. indicators.addIndicatorList.call(
  494. this,
  495. chart,
  496. tabsContainers[0],
  497. 'add'
  498. );
  499. buttonParentDiv = tabsContainers[0]
  500. .querySelectorAll('.' + PREFIX + 'popup-rhs-col')[0];
  501. this.addButton(
  502. buttonParentDiv,
  503. lang.addButton || 'add',
  504. 'add',
  505. callback,
  506. buttonParentDiv
  507. );
  508. // EDIT tab
  509. this.addColsContainer(tabsContainers[1]);
  510. indicators.addIndicatorList.call(
  511. this,
  512. chart,
  513. tabsContainers[1],
  514. 'edit'
  515. );
  516. buttonParentDiv = tabsContainers[1]
  517. .querySelectorAll('.' + PREFIX + 'popup-rhs-col')[0];
  518. this.addButton(
  519. buttonParentDiv,
  520. lang.saveButton || 'save',
  521. 'edit',
  522. callback,
  523. buttonParentDiv
  524. );
  525. this.addButton(
  526. buttonParentDiv,
  527. lang.removeButton || 'remove',
  528. 'remove',
  529. callback,
  530. buttonParentDiv
  531. );
  532. },
  533. /*
  534. * Create HTML list of all indicators (ADD mode) or added indicators
  535. * (EDIT mode).
  536. *
  537. * @param {Chart} - chart
  538. * @param {HTMLDOMElement} - container where list is added
  539. * @param {String} - 'edit' or 'add' mode
  540. *
  541. */
  542. addIndicatorList: function (chart, parentDiv, listType) {
  543. var _self = this,
  544. lhsCol = parentDiv
  545. .querySelectorAll('.' + PREFIX + 'popup-lhs-col')[0],
  546. rhsCol = parentDiv
  547. .querySelectorAll('.' + PREFIX + 'popup-rhs-col')[0],
  548. defaultOptions = H.getOptions(),
  549. isEdit = listType === 'edit',
  550. series = isEdit ? chart.series : // EDIT mode
  551. defaultOptions.plotOptions, // ADD mode
  552. addFormFields = this.indicators.addFormFields,
  553. rhsColWrapper,
  554. indicatorList,
  555. item;
  556. // create wrapper for list
  557. indicatorList = createElement(UL, {
  558. className: PREFIX + 'indicator-list'
  559. }, null, lhsCol);
  560. rhsColWrapper = rhsCol
  561. .querySelectorAll('.' + PREFIX + 'popup-rhs-col-wrapper')[0];
  562. objectEach(series, function (serie, value) {
  563. var seriesOptions = serie.options;
  564. if (
  565. serie.params ||
  566. seriesOptions && seriesOptions.params
  567. ) {
  568. var indicatorNameType = _self.indicators
  569. .getNameType(serie, value),
  570. indicatorType = indicatorNameType.type;
  571. item = createElement(LI, {
  572. className: PREFIX + 'indicator-list',
  573. innerHTML: indicatorNameType.name
  574. }, null, indicatorList);
  575. ['click', 'touchstart'].forEach(function (eventName) {
  576. addEvent(item, eventName, function () {
  577. addFormFields.call(
  578. _self,
  579. chart,
  580. isEdit ? serie : series[indicatorType],
  581. indicatorNameType.type,
  582. rhsColWrapper
  583. );
  584. // add hidden input with series.id
  585. if (isEdit && serie.options) {
  586. createElement(INPUT, {
  587. type: 'hidden',
  588. name: PREFIX + 'id-' + indicatorType,
  589. value: serie.options.id
  590. }, null, rhsColWrapper)
  591. .setAttribute(
  592. PREFIX + 'data-series-id',
  593. serie.options.id
  594. );
  595. }
  596. });
  597. });
  598. }
  599. });
  600. // select first item from the list
  601. if (indicatorList.childNodes.length > 0) {
  602. indicatorList.childNodes[0].click();
  603. }
  604. },
  605. /*
  606. * Extract full name and type of requested indicator.
  607. *
  608. * @param {Series} - series which name is needed.
  609. * (EDIT mode - defaultOptions.series, ADD mode - indicator series).
  610. * @param {String} - indicator type like: sma, ema, etc.
  611. *
  612. * @return {Object} - series name and type like: sma, ema, etc.
  613. *
  614. */
  615. getNameType: function (series, type) {
  616. var options = series.options,
  617. seriesTypes = H.seriesTypes,
  618. // add mode
  619. seriesName = seriesTypes[type] &&
  620. seriesTypes[type].prototype.nameBase || type.toUpperCase(),
  621. seriesType = type;
  622. // edit
  623. if (options && options.type) {
  624. seriesType = series.options.type;
  625. seriesName = series.name;
  626. }
  627. return {
  628. name: seriesName,
  629. type: seriesType
  630. };
  631. },
  632. /*
  633. * List all series with unique ID. Its mandatory for indicators to set
  634. * correct linking.
  635. *
  636. * @param {String} - indicator type like: sma, ema, etc.
  637. * @param {String} - type of select i.e series or volume.
  638. * @param {Chart} - chart
  639. * @param {HTMLDOMElement} - element where created HTML list is added
  640. *
  641. */
  642. listAllSeries: function (type, optionName, chart, parentDiv) {
  643. var selectName = PREFIX + optionName + '-type-' + type,
  644. lang = this.lang,
  645. selectBox,
  646. seriesOptions;
  647. createElement(
  648. LABEL, {
  649. innerHTML: lang[optionName] || optionName,
  650. htmlFor: selectName
  651. },
  652. null,
  653. parentDiv
  654. );
  655. // select type
  656. selectBox = createElement(
  657. SELECT,
  658. {
  659. name: selectName,
  660. className: PREFIX + 'popup-field'
  661. },
  662. null,
  663. parentDiv
  664. );
  665. selectBox.setAttribute('id', PREFIX + 'select-' + optionName);
  666. // list all series which have id - mandatory for creating indicator
  667. chart.series.forEach(function (serie) {
  668. seriesOptions = serie.options;
  669. if (
  670. !seriesOptions.params &&
  671. seriesOptions.id &&
  672. seriesOptions.id !== PREFIX + 'navigator-series'
  673. ) {
  674. createElement(
  675. OPTION,
  676. {
  677. innerHTML: seriesOptions.name || seriesOptions.id,
  678. value: seriesOptions.id
  679. },
  680. null,
  681. selectBox
  682. );
  683. }
  684. });
  685. },
  686. /*
  687. * Create typical inputs for chosen indicator. Fields are extracted from
  688. * defaultOptions (ADD mode) or current indicator (ADD mode). Two extra
  689. * fields are added:
  690. * - hidden input - contains indicator type (required for callback)
  691. * - select - list of series which can be linked with indicator
  692. *
  693. * @param {Chart} - chart
  694. * @param {Series} - indicator
  695. * @param {String} - indicator type like: sma, ema, etc.
  696. * @param {HTMLDOMElement} - element where created HTML list is added
  697. *
  698. */
  699. addFormFields: function (chart, series, seriesType, rhsColWrapper) {
  700. var fields = series.params || series.options.params,
  701. getNameType = this.indicators.getNameType;
  702. // reset current content
  703. rhsColWrapper.innerHTML = '';
  704. // create title (indicator name in the right column)
  705. createElement(
  706. H3,
  707. {
  708. className: PREFIX + 'indicator-title',
  709. innerHTML: getNameType(series, seriesType).name
  710. },
  711. null,
  712. rhsColWrapper
  713. );
  714. // input type
  715. createElement(
  716. INPUT,
  717. {
  718. type: 'hidden',
  719. name: PREFIX + 'type-' + seriesType,
  720. value: seriesType
  721. },
  722. null,
  723. rhsColWrapper
  724. );
  725. // list all series with id
  726. this.indicators.listAllSeries.call(
  727. this,
  728. seriesType,
  729. 'series',
  730. chart,
  731. rhsColWrapper
  732. );
  733. if (fields.volumeSeriesID) {
  734. this.indicators.listAllSeries.call(
  735. this,
  736. seriesType,
  737. 'volume',
  738. chart,
  739. rhsColWrapper
  740. );
  741. }
  742. // add param fields
  743. this.indicators.addParamInputs.call(
  744. this,
  745. chart,
  746. 'params',
  747. fields,
  748. seriesType,
  749. rhsColWrapper
  750. );
  751. },
  752. /*
  753. * Recurent function which lists all fields, from params object and
  754. * create them as inputs. Each input has unique `data-name` attribute,
  755. * which keeps chain of fields i.e params.styles.fontSize.
  756. *
  757. * @param {Chart} - chart
  758. * @param {String} - name of parent to create chain of names
  759. * @param {Series} - fields - params which are based for input create
  760. * @param {String} - indicator type like: sma, ema, etc.
  761. * @param {HTMLDOMElement} - element where created HTML list is added
  762. *
  763. */
  764. addParamInputs: function (chart, parentNode, fields, type, parentDiv) {
  765. var _self = this,
  766. addParamInputs = this.indicators.addParamInputs,
  767. addInput = this.addInput,
  768. parentFullName;
  769. objectEach(fields, function (value, fieldName) {
  770. // create name like params.styles.fontSize
  771. parentFullName = parentNode + '.' + fieldName;
  772. if (isObject(value)) {
  773. addParamInputs.call(
  774. _self,
  775. chart,
  776. parentFullName,
  777. value,
  778. type,
  779. parentDiv
  780. );
  781. } else if (
  782. // skip volume field which is created by addFormFields
  783. parentFullName !== 'params.volumeSeriesID'
  784. ) {
  785. addInput.call(
  786. _self,
  787. parentFullName,
  788. type,
  789. parentDiv,
  790. [value, 'text'] // all inputs are text type
  791. );
  792. }
  793. });
  794. },
  795. /*
  796. * Get amount of indicators added to chart.
  797. *
  798. * @return {Number} - Amount of indicators
  799. */
  800. getAmount: function () {
  801. var series = this.series,
  802. counter = 0;
  803. objectEach(series, function (serie) {
  804. var seriesOptions = serie.options;
  805. if (
  806. serie.params ||
  807. seriesOptions && seriesOptions.params
  808. ) {
  809. counter++;
  810. }
  811. });
  812. return counter;
  813. }
  814. },
  815. tabs: {
  816. /*
  817. * Init tabs. Create tab menu items, tabs containers
  818. *
  819. * @param {Chart} - reference to current chart
  820. *
  821. */
  822. init: function (chart) {
  823. var tabs = this.tabs,
  824. indicatorsCount = this.indicators.getAmount.call(chart),
  825. firstTab; // run by default
  826. // create menu items
  827. firstTab = tabs.addMenuItem.call(this, 'add');
  828. tabs.addMenuItem.call(this, 'edit', indicatorsCount);
  829. // create tabs containers
  830. tabs.addContentItem.call(this, 'add');
  831. tabs.addContentItem.call(this, 'edit');
  832. tabs.switchTabs.call(this, indicatorsCount);
  833. // activate first tab
  834. tabs.selectTab.call(this, firstTab, 0);
  835. },
  836. /*
  837. * Create tab menu item
  838. *
  839. * @param {String} - `add` or `edit`
  840. * @param {Number} - Disable tab when 0
  841. *
  842. * @return {HTMLDOMElement} - created HTML tab-menu element
  843. */
  844. addMenuItem: function (tabName, disableTab) {
  845. var popupDiv = this.popup.container,
  846. className = PREFIX + 'tab-item',
  847. lang = this.lang,
  848. menuItem;
  849. if (disableTab === 0) {
  850. className += ' ' + PREFIX + 'tab-disabled';
  851. }
  852. // tab 1
  853. menuItem = createElement(
  854. SPAN,
  855. {
  856. innerHTML: lang[tabName + 'Button'] || tabName,
  857. className: className
  858. },
  859. null,
  860. popupDiv
  861. );
  862. menuItem.setAttribute(PREFIX + 'data-tab-type', tabName);
  863. return menuItem;
  864. },
  865. /*
  866. * Create tab content
  867. *
  868. * @return {HTMLDOMElement} - created HTML tab-content element
  869. *
  870. */
  871. addContentItem: function () {
  872. var popupDiv = this.popup.container;
  873. return createElement(
  874. DIV,
  875. {
  876. className: PREFIX + 'tab-item-content'
  877. },
  878. null,
  879. popupDiv
  880. );
  881. },
  882. /*
  883. * Add click event to each tab
  884. *
  885. * @param {Number} - Disable tab when 0
  886. *
  887. */
  888. switchTabs: function (disableTab) {
  889. var _self = this,
  890. popupDiv = this.popup.container,
  891. tabs = popupDiv.querySelectorAll('.' + PREFIX + 'tab-item'),
  892. dataParam;
  893. tabs.forEach(function (tab, i) {
  894. dataParam = tab.getAttribute(PREFIX + 'data-tab-type');
  895. if (dataParam === 'edit' && disableTab === 0) {
  896. return;
  897. }
  898. ['click', 'touchstart'].forEach(function (eventName) {
  899. addEvent(tab, eventName, function () {
  900. // reset class on other elements
  901. _self.tabs.deselectAll.call(_self);
  902. _self.tabs.selectTab.call(_self, this, i);
  903. });
  904. });
  905. });
  906. },
  907. /*
  908. * Set tab as visible
  909. *
  910. * @param {HTMLDOMElement} - current tab
  911. * @param {Number} - Index of tab in menu
  912. *
  913. */
  914. selectTab: function (tab, index) {
  915. var allTabs = this.popup.container
  916. .querySelectorAll('.' + PREFIX + 'tab-item-content');
  917. tab.className += ' ' + PREFIX + 'tab-item-active';
  918. allTabs[index].className += ' ' + PREFIX + 'tab-item-show';
  919. },
  920. /*
  921. * Set all tabs as invisible.
  922. *
  923. */
  924. deselectAll: function () {
  925. var popupDiv = this.popup.container,
  926. tabs = popupDiv
  927. .querySelectorAll('.' + PREFIX + 'tab-item'),
  928. tabsContent = popupDiv
  929. .querySelectorAll('.' + PREFIX + 'tab-item-content'),
  930. i;
  931. for (i = 0; i < tabs.length; i++) {
  932. tabs[i].classList.remove(PREFIX + 'tab-item-active');
  933. tabsContent[i].classList.remove(PREFIX + 'tab-item-show');
  934. }
  935. }
  936. }
  937. };
  938. addEvent(H.NavigationBindings, 'showPopup', function (config) {
  939. if (!this.popup) {
  940. // Add popup to main container
  941. this.popup = new H.Popup(this.chart.container);
  942. }
  943. this.popup.showForm(
  944. config.formType,
  945. this.chart,
  946. config.options,
  947. config.onSubmit
  948. );
  949. });
  950. addEvent(H.NavigationBindings, 'closePopup', function () {
  951. if (this.popup) {
  952. this.popup.closePopup();
  953. }
  954. });