popup.js 30 KB

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