Popup.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733
  1. /* *
  2. *
  3. * Popup generator for Stock tools
  4. *
  5. * (c) 2009-2017 Sebastian Bochan
  6. *
  7. * License: www.highcharts.com/license
  8. *
  9. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  10. *
  11. * */
  12. 'use strict';
  13. import H from '../../Core/Globals.js';
  14. import NavigationBindings from './NavigationBindings.js';
  15. import Pointer from '../../Core/Pointer.js';
  16. import U from '../../Core/Utilities.js';
  17. var addEvent = U.addEvent, createElement = U.createElement, defined = U.defined, getOptions = U.getOptions, isArray = U.isArray, isObject = U.isObject, isString = U.isString, objectEach = U.objectEach, pick = U.pick, wrap = U.wrap;
  18. var indexFilter = /\d/g, PREFIX = 'highcharts-', DIV = 'div', INPUT = 'input', LABEL = 'label', BUTTON = 'button', SELECT = 'select', OPTION = 'option', SPAN = 'span', UL = 'ul', LI = 'li', H3 = 'h3';
  19. /* eslint-disable no-invalid-this, valid-jsdoc */
  20. // onContainerMouseDown blocks internal popup events, due to e.preventDefault.
  21. // Related issue #4606
  22. wrap(Pointer.prototype, 'onContainerMouseDown', function (proceed, e) {
  23. var popupClass = e.target && e.target.className;
  24. // elements is not in popup
  25. if (!(isString(popupClass) &&
  26. popupClass.indexOf(PREFIX + 'popup-field') >= 0)) {
  27. proceed.apply(this, Array.prototype.slice.call(arguments, 1));
  28. }
  29. });
  30. H.Popup = function (parentDiv, iconsURL) {
  31. this.init(parentDiv, iconsURL);
  32. };
  33. H.Popup.prototype = {
  34. /**
  35. * Initialize the popup. Create base div and add close button.
  36. * @private
  37. * @param {Highcharts.HTMLDOMElement} parentDiv
  38. * Container where popup should be placed
  39. * @param {string} iconsURL
  40. * Icon URL
  41. */
  42. init: function (parentDiv, iconsURL) {
  43. // create popup div
  44. this.container = createElement(DIV, {
  45. className: PREFIX + 'popup'
  46. }, null, parentDiv);
  47. this.lang = this.getLangpack();
  48. this.iconsURL = iconsURL;
  49. // add close button
  50. this.addCloseBtn();
  51. },
  52. /**
  53. * Create HTML element and attach click event (close popup).
  54. * @private
  55. */
  56. addCloseBtn: function () {
  57. var _self = this, closeBtn;
  58. // create close popup btn
  59. closeBtn = createElement(DIV, {
  60. className: PREFIX + 'popup-close'
  61. }, null, this.container);
  62. closeBtn.style['background-image'] = 'url(' +
  63. this.iconsURL + 'close.svg)';
  64. ['click', 'touchstart'].forEach(function (eventName) {
  65. addEvent(closeBtn, eventName, function () {
  66. _self.closePopup();
  67. });
  68. });
  69. },
  70. /**
  71. * Create two columns (divs) in HTML.
  72. * @private
  73. * @param {Highcharts.HTMLDOMElement} container
  74. * Container of columns
  75. * @return {Highcharts.Dictionary<Highcharts.HTMLDOMElement>}
  76. * Reference to two HTML columns (lhsCol, rhsCol)
  77. */
  78. addColsContainer: function (container) {
  79. var rhsCol, lhsCol;
  80. // left column
  81. lhsCol = createElement(DIV, {
  82. className: PREFIX + 'popup-lhs-col'
  83. }, null, container);
  84. // right column
  85. rhsCol = createElement(DIV, {
  86. className: PREFIX + 'popup-rhs-col'
  87. }, null, container);
  88. // wrapper content
  89. createElement(DIV, {
  90. className: PREFIX + 'popup-rhs-col-wrapper'
  91. }, null, rhsCol);
  92. return {
  93. lhsCol: lhsCol,
  94. rhsCol: rhsCol
  95. };
  96. },
  97. /**
  98. * Create input with label.
  99. * @private
  100. * @param {string} option
  101. * Chain of fields i.e params.styles.fontSize
  102. * @param {string} type
  103. * Indicator type
  104. * @param {Highhcharts.HTMLDOMElement}
  105. * Container where elements should be added
  106. * @param {string} value
  107. * Default value of input i.e period value is 14, extracted from
  108. * defaultOptions (ADD mode) or series options (EDIT mode)
  109. */
  110. addInput: function (option, type, parentDiv, value) {
  111. var optionParamList = option.split('.'), optionName = optionParamList[optionParamList.length - 1], lang = this.lang, inputName = PREFIX + type + '-' + optionName;
  112. if (!inputName.match(indexFilter)) {
  113. // add label
  114. createElement(LABEL, {
  115. innerHTML: lang[optionName] || optionName,
  116. htmlFor: inputName
  117. }, null, parentDiv);
  118. }
  119. // add input
  120. createElement(INPUT, {
  121. name: inputName,
  122. value: value[0],
  123. type: value[1],
  124. className: PREFIX + 'popup-field'
  125. }, null, parentDiv).setAttribute(PREFIX + 'data-name', option);
  126. },
  127. /**
  128. * Create button.
  129. * @private
  130. * @param {Highcharts.HTMLDOMElement} parentDiv
  131. * Container where elements should be added
  132. * @param {string} label
  133. * Text placed as button label
  134. * @param {string} type
  135. * add | edit | remove
  136. * @param {Function} callback
  137. * On click callback
  138. * @param {Highcharts.HTMLDOMElement} fieldsDiv
  139. * Container where inputs are generated
  140. * @return {Highcharts.HTMLDOMElement}
  141. * HTML button
  142. */
  143. addButton: function (parentDiv, label, type, callback, fieldsDiv) {
  144. var _self = this, closePopup = this.closePopup, getFields = this.getFields, button;
  145. button = createElement(BUTTON, {
  146. innerHTML: label
  147. }, null, parentDiv);
  148. ['click', 'touchstart'].forEach(function (eventName) {
  149. addEvent(button, eventName, function () {
  150. closePopup.call(_self);
  151. return callback(getFields(fieldsDiv, type));
  152. });
  153. });
  154. return button;
  155. },
  156. /**
  157. * Get values from all inputs and create JSON.
  158. * @private
  159. * @param {Highcharts.HTMLDOMElement} - container where inputs are created
  160. * @param {string} - add | edit | remove
  161. * @return {Highcharts.PopupFieldsObject} - fields
  162. */
  163. getFields: function (parentDiv, type) {
  164. var inputList = parentDiv.querySelectorAll('input'), optionSeries = '#' + PREFIX + 'select-series > option:checked', optionVolume = '#' + PREFIX + 'select-volume > option:checked', linkedTo = parentDiv.querySelectorAll(optionSeries)[0], volumeTo = parentDiv.querySelectorAll(optionVolume)[0], seriesId, param, fieldsOutput;
  165. fieldsOutput = {
  166. actionType: type,
  167. linkedTo: linkedTo && linkedTo.getAttribute('value'),
  168. fields: {}
  169. };
  170. [].forEach.call(inputList, function (input) {
  171. param = input.getAttribute(PREFIX + 'data-name');
  172. seriesId = input.getAttribute(PREFIX + 'data-series-id');
  173. // params
  174. if (seriesId) {
  175. fieldsOutput.seriesId = input.value;
  176. }
  177. else if (param) {
  178. fieldsOutput.fields[param] = input.value;
  179. }
  180. else {
  181. // type like sma / ema
  182. fieldsOutput.type = input.value;
  183. }
  184. });
  185. if (volumeTo) {
  186. fieldsOutput.fields['params.volumeSeriesID'] = volumeTo.getAttribute('value');
  187. }
  188. return fieldsOutput;
  189. },
  190. /**
  191. * Reset content of the current popup and show.
  192. * @private
  193. */
  194. showPopup: function () {
  195. var popupDiv = this.container, toolbarClass = PREFIX + 'annotation-toolbar', popupCloseBtn = popupDiv
  196. .querySelectorAll('.' + PREFIX + 'popup-close')[0];
  197. // reset content
  198. popupDiv.innerHTML = '';
  199. // reset toolbar styles if exists
  200. if (popupDiv.className.indexOf(toolbarClass) >= 0) {
  201. popupDiv.classList.remove(toolbarClass);
  202. // reset toolbar inline styles
  203. popupDiv.removeAttribute('style');
  204. }
  205. // add close button
  206. popupDiv.appendChild(popupCloseBtn);
  207. popupDiv.style.display = 'block';
  208. },
  209. /**
  210. * Hide popup.
  211. * @private
  212. */
  213. closePopup: function () {
  214. this.popup.container.style.display = 'none';
  215. },
  216. /**
  217. * Create content and show popup.
  218. * @private
  219. * @param {string} - type of popup i.e indicators
  220. * @param {Highcharts.Chart} - chart
  221. * @param {Highcharts.AnnotationsOptions} - options
  222. * @param {Function} - on click callback
  223. */
  224. showForm: function (type, chart, options, callback) {
  225. this.popup = chart.navigationBindings.popup;
  226. // show blank popup
  227. this.showPopup();
  228. // indicator form
  229. if (type === 'indicators') {
  230. this.indicators.addForm.call(this, chart, options, callback);
  231. }
  232. // annotation small toolbar
  233. if (type === 'annotation-toolbar') {
  234. this.annotations.addToolbar.call(this, chart, options, callback);
  235. }
  236. // annotation edit form
  237. if (type === 'annotation-edit') {
  238. this.annotations.addForm.call(this, chart, options, callback);
  239. }
  240. // flags form - add / edit
  241. if (type === 'flag') {
  242. this.annotations.addForm.call(this, chart, options, callback, true);
  243. }
  244. },
  245. /**
  246. * Return lang definitions for popup.
  247. * @private
  248. * @return {Highcharts.Dictionary<string>} - elements translations.
  249. */
  250. getLangpack: function () {
  251. return getOptions().lang.navigation.popup;
  252. },
  253. annotations: {
  254. /**
  255. * Create annotation simple form. It contains two buttons
  256. * (edit / remove) and text label.
  257. * @private
  258. * @param {Highcharts.Chart} - chart
  259. * @param {Highcharts.AnnotationsOptions} - options
  260. * @param {Function} - on click callback
  261. */
  262. addToolbar: function (chart, options, callback) {
  263. var _self = this, lang = this.lang, popupDiv = this.popup.container, showForm = this.showForm, toolbarClass = PREFIX + 'annotation-toolbar', button;
  264. // set small size
  265. if (popupDiv.className.indexOf(toolbarClass) === -1) {
  266. popupDiv.className += ' ' + toolbarClass;
  267. }
  268. // set position
  269. popupDiv.style.top = chart.plotTop + 10 + 'px';
  270. // create label
  271. createElement(SPAN, {
  272. innerHTML: pick(
  273. // Advanced annotations:
  274. lang[options.langKey] || options.langKey,
  275. // Basic shapes:
  276. options.shapes && options.shapes[0].type)
  277. }, null, popupDiv);
  278. // add buttons
  279. button = this.addButton(popupDiv, lang.removeButton || 'remove', 'remove', callback, popupDiv);
  280. button.className += ' ' + PREFIX + 'annotation-remove-button';
  281. button.style['background-image'] = 'url(' +
  282. this.iconsURL + 'destroy.svg)';
  283. button = this.addButton(popupDiv, lang.editButton || 'edit', 'edit', function () {
  284. showForm.call(_self, 'annotation-edit', chart, options, callback);
  285. }, popupDiv);
  286. button.className += ' ' + PREFIX + 'annotation-edit-button';
  287. button.style['background-image'] = 'url(' +
  288. this.iconsURL + 'edit.svg)';
  289. },
  290. /**
  291. * Create annotation simple form.
  292. * It contains fields with param names.
  293. * @private
  294. * @param {Highcharts.Chart} chart
  295. * Chart
  296. * @param {Object} options
  297. * Options
  298. * @param {Function} callback
  299. * On click callback
  300. * @param {boolean} [isInit]
  301. * If it is a form declared for init annotation
  302. */
  303. addForm: function (chart, options, callback, isInit) {
  304. var popupDiv = this.popup.container, lang = this.lang, bottomRow, lhsCol;
  305. // create title of annotations
  306. lhsCol = createElement('h2', {
  307. innerHTML: lang[options.langKey] || options.langKey,
  308. className: PREFIX + 'popup-main-title'
  309. }, null, popupDiv);
  310. // left column
  311. lhsCol = createElement(DIV, {
  312. className: PREFIX + 'popup-lhs-col ' + PREFIX + 'popup-lhs-full'
  313. }, null, popupDiv);
  314. bottomRow = createElement(DIV, {
  315. className: PREFIX + 'popup-bottom-row'
  316. }, null, popupDiv);
  317. this.annotations.addFormFields.call(this, lhsCol, chart, '', options, [], true);
  318. this.addButton(bottomRow, isInit ?
  319. (lang.addButton || 'add') :
  320. (lang.saveButton || 'save'), isInit ? 'add' : 'save', callback, popupDiv);
  321. },
  322. /**
  323. * Create annotation's form fields.
  324. * @private
  325. * @param {Highcharts.HTMLDOMElement} parentDiv
  326. * Div where inputs are placed
  327. * @param {Highcharts.Chart} chart
  328. * Chart
  329. * @param {string} parentNode
  330. * Name of parent to create chain of names
  331. * @param {Highcharts.AnnotationsOptions} options
  332. * Options
  333. * @param {Array<unknown>} storage
  334. * Array where all items are stored
  335. * @param {boolean} [isRoot]
  336. * Recursive flag for root
  337. */
  338. addFormFields: function (parentDiv, chart, parentNode, options, storage, isRoot) {
  339. var _self = this, addFormFields = this.annotations.addFormFields, addInput = this.addInput, lang = this.lang, parentFullName, titleName;
  340. objectEach(options, function (value, option) {
  341. // create name like params.styles.fontSize
  342. parentFullName = parentNode !== '' ?
  343. parentNode + '.' + option : option;
  344. if (isObject(value)) {
  345. if (
  346. // value is object of options
  347. !isArray(value) ||
  348. // array of objects with params. i.e labels in Fibonacci
  349. (isArray(value) && isObject(value[0]))) {
  350. titleName = lang[option] || option;
  351. if (!titleName.match(indexFilter)) {
  352. storage.push([
  353. true,
  354. titleName,
  355. parentDiv
  356. ]);
  357. }
  358. addFormFields.call(_self, parentDiv, chart, parentFullName, value, storage, false);
  359. }
  360. else {
  361. storage.push([
  362. _self,
  363. parentFullName,
  364. 'annotation',
  365. parentDiv,
  366. value
  367. ]);
  368. }
  369. }
  370. });
  371. if (isRoot) {
  372. storage = storage.sort(function (a) {
  373. return a[1].match(/format/g) ? -1 : 1;
  374. });
  375. storage.forEach(function (genInput) {
  376. if (genInput[0] === true) {
  377. createElement(SPAN, {
  378. className: PREFIX + 'annotation-title',
  379. innerHTML: genInput[1]
  380. }, null, genInput[2]);
  381. }
  382. else {
  383. addInput.apply(genInput[0], genInput.splice(1));
  384. }
  385. });
  386. }
  387. }
  388. },
  389. indicators: {
  390. /**
  391. * Create indicator's form. It contains two tabs (ADD and EDIT) with
  392. * content.
  393. * @private
  394. */
  395. addForm: function (chart, _options, callback) {
  396. var tabsContainers, indicators = this.indicators, lang = this.lang, buttonParentDiv;
  397. // add tabs
  398. this.tabs.init.call(this, chart);
  399. // get all tabs content divs
  400. tabsContainers = this.popup.container
  401. .querySelectorAll('.' + PREFIX + 'tab-item-content');
  402. // ADD tab
  403. this.addColsContainer(tabsContainers[0]);
  404. indicators.addIndicatorList.call(this, chart, tabsContainers[0], 'add');
  405. buttonParentDiv = tabsContainers[0]
  406. .querySelectorAll('.' + PREFIX + 'popup-rhs-col')[0];
  407. this.addButton(buttonParentDiv, lang.addButton || 'add', 'add', callback, buttonParentDiv);
  408. // EDIT tab
  409. this.addColsContainer(tabsContainers[1]);
  410. indicators.addIndicatorList.call(this, chart, tabsContainers[1], 'edit');
  411. buttonParentDiv = tabsContainers[1]
  412. .querySelectorAll('.' + PREFIX + 'popup-rhs-col')[0];
  413. this.addButton(buttonParentDiv, lang.saveButton || 'save', 'edit', callback, buttonParentDiv);
  414. this.addButton(buttonParentDiv, lang.removeButton || 'remove', 'remove', callback, buttonParentDiv);
  415. },
  416. /**
  417. * Create HTML list of all indicators (ADD mode) or added indicators
  418. * (EDIT mode).
  419. * @private
  420. */
  421. addIndicatorList: function (chart, parentDiv, listType) {
  422. var _self = this, lhsCol = parentDiv.querySelectorAll('.' + PREFIX + 'popup-lhs-col')[0], rhsCol = parentDiv.querySelectorAll('.' + PREFIX + 'popup-rhs-col')[0], isEdit = listType === 'edit', series = (isEdit ?
  423. chart.series : // EDIT mode
  424. chart.options.plotOptions // ADD mode
  425. ), addFormFields = this.indicators.addFormFields, rhsColWrapper, indicatorList, item;
  426. // create wrapper for list
  427. indicatorList = createElement(UL, {
  428. className: PREFIX + 'indicator-list'
  429. }, null, lhsCol);
  430. rhsColWrapper = rhsCol
  431. .querySelectorAll('.' + PREFIX + 'popup-rhs-col-wrapper')[0];
  432. objectEach(series, function (serie, value) {
  433. var seriesOptions = serie.options;
  434. if (serie.params ||
  435. seriesOptions && seriesOptions.params) {
  436. var indicatorNameType = _self.indicators.getNameType(serie, value), indicatorType = indicatorNameType.type;
  437. item = createElement(LI, {
  438. className: PREFIX + 'indicator-list',
  439. innerHTML: indicatorNameType.name
  440. }, null, indicatorList);
  441. ['click', 'touchstart'].forEach(function (eventName) {
  442. addEvent(item, eventName, function () {
  443. addFormFields.call(_self, chart, isEdit ? serie : series[indicatorType], indicatorNameType.type, rhsColWrapper);
  444. // add hidden input with series.id
  445. if (isEdit && serie.options) {
  446. createElement(INPUT, {
  447. type: 'hidden',
  448. name: PREFIX + 'id-' + indicatorType,
  449. value: serie.options.id
  450. }, null, rhsColWrapper)
  451. .setAttribute(PREFIX + 'data-series-id', serie.options.id);
  452. }
  453. });
  454. });
  455. }
  456. });
  457. // select first item from the list
  458. if (indicatorList.childNodes.length > 0) {
  459. indicatorList.childNodes[0].click();
  460. }
  461. },
  462. /**
  463. * Extract full name and type of requested indicator.
  464. * @private
  465. * @param {Highcharts.Series} series
  466. * Series which name is needed. (EDIT mode - defaultOptions.series, ADD
  467. * mode - indicator series).
  468. * @param {string} - indicator type like: sma, ema, etc.
  469. * @return {Object} - series name and type like: sma, ema, etc.
  470. */
  471. getNameType: function (series, type) {
  472. var options = series.options, seriesTypes = H.seriesTypes,
  473. // add mode
  474. seriesName = seriesTypes[type] &&
  475. seriesTypes[type].prototype.nameBase || type.toUpperCase(), seriesType = type;
  476. // edit
  477. if (options && options.type) {
  478. seriesType = series.options.type;
  479. seriesName = series.name;
  480. }
  481. return {
  482. name: seriesName,
  483. type: seriesType
  484. };
  485. },
  486. /**
  487. * List all series with unique ID. Its mandatory for indicators to set
  488. * correct linking.
  489. * @private
  490. * @param {string} type
  491. * Indicator type like: sma, ema, etc.
  492. * @param {string} optionName
  493. * Type of select i.e series or volume.
  494. * @param {Highcharts.Chart} chart
  495. * Chart
  496. * @param {Highcharts.HTMLDOMElement} parentDiv
  497. * Element where created HTML list is added
  498. * @param {string} selectedOption
  499. * optional param for default value in dropdown
  500. */
  501. listAllSeries: function (type, optionName, chart, parentDiv, selectedOption) {
  502. var selectName = PREFIX + optionName + '-type-' + type, lang = this.lang, selectBox, seriesOptions;
  503. createElement(LABEL, {
  504. innerHTML: lang[optionName] || optionName,
  505. htmlFor: selectName
  506. }, null, parentDiv);
  507. // select type
  508. selectBox = createElement(SELECT, {
  509. name: selectName,
  510. className: PREFIX + 'popup-field'
  511. }, null, parentDiv);
  512. selectBox.setAttribute('id', PREFIX + 'select-' + optionName);
  513. // list all series which have id - mandatory for creating indicator
  514. chart.series.forEach(function (serie) {
  515. seriesOptions = serie.options;
  516. if (!seriesOptions.params &&
  517. seriesOptions.id &&
  518. seriesOptions.id !== PREFIX + 'navigator-series') {
  519. createElement(OPTION, {
  520. innerHTML: seriesOptions.name || seriesOptions.id,
  521. value: seriesOptions.id
  522. }, null, selectBox);
  523. }
  524. });
  525. if (defined(selectedOption)) {
  526. selectBox.value = selectedOption;
  527. }
  528. },
  529. /**
  530. * Create typical inputs for chosen indicator. Fields are extracted from
  531. * defaultOptions (ADD mode) or current indicator (ADD mode). Two extra
  532. * fields are added:
  533. * - hidden input - contains indicator type (required for callback)
  534. * - select - list of series which can be linked with indicator
  535. * @private
  536. * @param {Highcharts.Chart} chart
  537. * Chart
  538. * @param {Highcharts.Series} series
  539. * Indicator
  540. * @param {string} seriesType
  541. * Indicator type like: sma, ema, etc.
  542. * @param {Highcharts.HTMLDOMElement} rhsColWrapper
  543. * Element where created HTML list is added
  544. */
  545. addFormFields: function (chart, series, seriesType, rhsColWrapper) {
  546. var fields = series.params || series.options.params, getNameType = this.indicators.getNameType;
  547. // reset current content
  548. rhsColWrapper.innerHTML = '';
  549. // create title (indicator name in the right column)
  550. createElement(H3, {
  551. className: PREFIX + 'indicator-title',
  552. innerHTML: getNameType(series, seriesType).name
  553. }, null, rhsColWrapper);
  554. // input type
  555. createElement(INPUT, {
  556. type: 'hidden',
  557. name: PREFIX + 'type-' + seriesType,
  558. value: seriesType
  559. }, null, rhsColWrapper);
  560. // list all series with id
  561. this.indicators.listAllSeries.call(this, seriesType, 'series', chart, rhsColWrapper, series.linkedParent && fields.volumeSeriesID);
  562. if (fields.volumeSeriesID) {
  563. this.indicators.listAllSeries.call(this, seriesType, 'volume', chart, rhsColWrapper, series.linkedParent && series.linkedParent.options.id);
  564. }
  565. // add param fields
  566. this.indicators.addParamInputs.call(this, chart, 'params', fields, seriesType, rhsColWrapper);
  567. },
  568. /**
  569. * Recurent function which lists all fields, from params object and
  570. * create them as inputs. Each input has unique `data-name` attribute,
  571. * which keeps chain of fields i.e params.styles.fontSize.
  572. * @private
  573. * @param {Highcharts.Chart} chart
  574. * Chart
  575. * @param {string} parentNode
  576. * Name of parent to create chain of names
  577. * @param {Highcharts.PopupFieldsDictionary<string>} fields
  578. * Params which are based for input create
  579. * @param {string} type
  580. * Indicator type like: sma, ema, etc.
  581. * @param {Highcharts.HTMLDOMElement} parentDiv
  582. * Element where created HTML list is added
  583. */
  584. addParamInputs: function (chart, parentNode, fields, type, parentDiv) {
  585. var _self = this, addParamInputs = this.indicators.addParamInputs, addInput = this.addInput, parentFullName;
  586. objectEach(fields, function (value, fieldName) {
  587. // create name like params.styles.fontSize
  588. parentFullName = parentNode + '.' + fieldName;
  589. if (isObject(value)) {
  590. addParamInputs.call(_self, chart, parentFullName, value, type, parentDiv);
  591. }
  592. else if (
  593. // skip volume field which is created by addFormFields
  594. parentFullName !== 'params.volumeSeriesID') {
  595. addInput.call(_self, parentFullName, type, parentDiv, [value, 'text'] // all inputs are text type
  596. );
  597. }
  598. });
  599. },
  600. /**
  601. * Get amount of indicators added to chart.
  602. * @private
  603. * @return {number} - Amount of indicators
  604. */
  605. getAmount: function () {
  606. var series = this.series, counter = 0;
  607. series.forEach(function (serie) {
  608. var seriesOptions = serie.options;
  609. if (serie.params ||
  610. seriesOptions && seriesOptions.params) {
  611. counter++;
  612. }
  613. });
  614. return counter;
  615. }
  616. },
  617. tabs: {
  618. /**
  619. * Init tabs. Create tab menu items, tabs containers
  620. * @private
  621. * @param {Highcharts.Chart} chart
  622. * Reference to current chart
  623. */
  624. init: function (chart) {
  625. var tabs = this.tabs, indicatorsCount = this.indicators.getAmount.call(chart), firstTab; // run by default
  626. // create menu items
  627. firstTab = tabs.addMenuItem.call(this, 'add');
  628. tabs.addMenuItem.call(this, 'edit', indicatorsCount);
  629. // create tabs containers
  630. tabs.addContentItem.call(this, 'add');
  631. tabs.addContentItem.call(this, 'edit');
  632. tabs.switchTabs.call(this, indicatorsCount);
  633. // activate first tab
  634. tabs.selectTab.call(this, firstTab, 0);
  635. },
  636. /**
  637. * Create tab menu item
  638. * @private
  639. * @param {string} tabName
  640. * `add` or `edit`
  641. * @param {number} [disableTab]
  642. * Disable tab when 0
  643. * @return {Highcharts.HTMLDOMElement}
  644. * Created HTML tab-menu element
  645. */
  646. addMenuItem: function (tabName, disableTab) {
  647. var popupDiv = this.popup.container, className = PREFIX + 'tab-item', lang = this.lang, menuItem;
  648. if (disableTab === 0) {
  649. className += ' ' + PREFIX + 'tab-disabled';
  650. }
  651. // tab 1
  652. menuItem = createElement(SPAN, {
  653. innerHTML: lang[tabName + 'Button'] || tabName,
  654. className: className
  655. }, null, popupDiv);
  656. menuItem.setAttribute(PREFIX + 'data-tab-type', tabName);
  657. return menuItem;
  658. },
  659. /**
  660. * Create tab content
  661. * @private
  662. * @return {HTMLDOMElement} - created HTML tab-content element
  663. */
  664. addContentItem: function () {
  665. var popupDiv = this.popup.container;
  666. return createElement(DIV, {
  667. className: PREFIX + 'tab-item-content'
  668. }, null, popupDiv);
  669. },
  670. /**
  671. * Add click event to each tab
  672. * @private
  673. * @param {number} disableTab
  674. * Disable tab when 0
  675. */
  676. switchTabs: function (disableTab) {
  677. var _self = this, popupDiv = this.popup.container, tabs = popupDiv.querySelectorAll('.' + PREFIX + 'tab-item'), dataParam;
  678. tabs.forEach(function (tab, i) {
  679. dataParam = tab.getAttribute(PREFIX + 'data-tab-type');
  680. if (dataParam === 'edit' && disableTab === 0) {
  681. return;
  682. }
  683. ['click', 'touchstart'].forEach(function (eventName) {
  684. addEvent(tab, eventName, function () {
  685. // reset class on other elements
  686. _self.tabs.deselectAll.call(_self);
  687. _self.tabs.selectTab.call(_self, this, i);
  688. });
  689. });
  690. });
  691. },
  692. /**
  693. * Set tab as visible
  694. * @private
  695. * @param {globals.Element} - current tab
  696. * @param {number} - Index of tab in menu
  697. */
  698. selectTab: function (tab, index) {
  699. var allTabs = this.popup.container
  700. .querySelectorAll('.' + PREFIX + 'tab-item-content');
  701. tab.className += ' ' + PREFIX + 'tab-item-active';
  702. allTabs[index].className += ' ' + PREFIX + 'tab-item-show';
  703. },
  704. /**
  705. * Set all tabs as invisible.
  706. * @private
  707. */
  708. deselectAll: function () {
  709. var popupDiv = this.popup.container, tabs = popupDiv
  710. .querySelectorAll('.' + PREFIX + 'tab-item'), tabsContent = popupDiv
  711. .querySelectorAll('.' + PREFIX + 'tab-item-content'), i;
  712. for (i = 0; i < tabs.length; i++) {
  713. tabs[i].classList.remove(PREFIX + 'tab-item-active');
  714. tabsContent[i].classList.remove(PREFIX + 'tab-item-show');
  715. }
  716. }
  717. }
  718. };
  719. addEvent(NavigationBindings, 'showPopup', function (config) {
  720. if (!this.popup) {
  721. // Add popup to main container
  722. this.popup = new H.Popup(this.chart.container, (this.chart.options.navigation.iconsURL ||
  723. (this.chart.options.stockTools &&
  724. this.chart.options.stockTools.gui.iconsURL) ||
  725. 'https://code.highcharts.com/8.2.0/gfx/stock-icons/'));
  726. }
  727. this.popup.showForm(config.formType, this.chart, config.options, config.onSubmit);
  728. });
  729. addEvent(NavigationBindings, 'closePopup', function () {
  730. if (this.popup) {
  731. this.popup.closePopup();
  732. }
  733. });