Popup.js 31 KB

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