Popup.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733
  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 doc = H.doc, isFirefox = H.isFirefox;
  14. import NavigationBindings from './NavigationBindings.js';
  15. import O from '../../Core/Options.js';
  16. var getOptions = O.getOptions;
  17. import Pointer from '../../Core/Pointer.js';
  18. import U from '../../Core/Utilities.js';
  19. var addEvent = U.addEvent, createElement = U.createElement, defined = U.defined, fireEvent = U.fireEvent, isArray = U.isArray, isObject = U.isObject, isString = U.isString, objectEach = U.objectEach, pick = U.pick, stableSort = U.stableSort, wrap = U.wrap;
  20. 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';
  21. /* eslint-disable no-invalid-this, valid-jsdoc */
  22. // onContainerMouseDown blocks internal popup events, due to e.preventDefault.
  23. // Related issue #4606
  24. wrap(Pointer.prototype, 'onContainerMouseDown', function (proceed, e) {
  25. var popupClass = e.target && e.target.className;
  26. // elements is not in popup
  27. if (!(isString(popupClass) &&
  28. popupClass.indexOf(PREFIX + 'popup-field') >= 0)) {
  29. proceed.apply(this, Array.prototype.slice.call(arguments, 1));
  30. }
  31. });
  32. H.Popup = function (parentDiv, iconsURL, chart) {
  33. this.init(parentDiv, iconsURL, chart);
  34. };
  35. H.Popup.prototype = {
  36. /**
  37. * Initialize the popup. Create base div and add close button.
  38. * @private
  39. * @param {Highcharts.HTMLDOMElement} parentDiv
  40. * Container where popup should be placed
  41. * @param {string} iconsURL
  42. * Icon URL
  43. */
  44. init: function (parentDiv, iconsURL, chart) {
  45. this.chart = chart;
  46. // create popup div
  47. this.container = createElement(DIV, {
  48. className: PREFIX + 'popup'
  49. }, null, parentDiv);
  50. this.lang = this.getLangpack();
  51. this.iconsURL = iconsURL;
  52. // add close button
  53. this.addCloseBtn();
  54. },
  55. /**
  56. * Create HTML element and attach click event (close popup).
  57. * @private
  58. */
  59. addCloseBtn: function () {
  60. var _self = this, closeBtn;
  61. // create close popup btn
  62. closeBtn = createElement(DIV, {
  63. className: PREFIX + 'popup-close'
  64. }, null, this.container);
  65. closeBtn.style['background-image'] = 'url(' +
  66. this.iconsURL + 'close.svg)';
  67. ['click', 'touchstart'].forEach(function (eventName) {
  68. addEvent(closeBtn, eventName, function () {
  69. fireEvent(_self.chart.navigationBindings, 'closePopup');
  70. });
  71. });
  72. },
  73. /**
  74. * Create two columns (divs) in HTML.
  75. * @private
  76. * @param {Highcharts.HTMLDOMElement} container
  77. * Container of columns
  78. * @return {Highcharts.Dictionary<Highcharts.HTMLDOMElement>}
  79. * Reference to two HTML columns (lhsCol, rhsCol)
  80. */
  81. addColsContainer: function (container) {
  82. var rhsCol, lhsCol;
  83. // left column
  84. lhsCol = createElement(DIV, {
  85. className: PREFIX + 'popup-lhs-col'
  86. }, null, container);
  87. // right column
  88. rhsCol = createElement(DIV, {
  89. className: PREFIX + 'popup-rhs-col'
  90. }, null, container);
  91. // wrapper content
  92. createElement(DIV, {
  93. className: PREFIX + 'popup-rhs-col-wrapper'
  94. }, null, rhsCol);
  95. return {
  96. lhsCol: lhsCol,
  97. rhsCol: rhsCol
  98. };
  99. },
  100. /**
  101. * Create input with label.
  102. * @private
  103. * @param {string} option
  104. * Chain of fields i.e params.styles.fontSize
  105. * @param {string} type
  106. * Indicator type
  107. * @param {Highhcharts.HTMLDOMElement}
  108. * Container where elements should be added
  109. * @param {string} value
  110. * Default value of input i.e period value is 14, extracted from
  111. * defaultOptions (ADD mode) or series options (EDIT mode)
  112. */
  113. addInput: function (option, type, parentDiv, value) {
  114. var optionParamList = option.split('.'), optionName = optionParamList[optionParamList.length - 1], lang = this.lang, inputName = PREFIX + type + '-' + optionName;
  115. if (!inputName.match(indexFilter)) {
  116. // add label
  117. createElement(LABEL, {
  118. htmlFor: inputName
  119. }, void 0, parentDiv).appendChild(doc.createTextNode(lang[optionName] || optionName));
  120. }
  121. // add input
  122. createElement(INPUT, {
  123. name: inputName,
  124. value: value[0],
  125. type: value[1],
  126. className: PREFIX + 'popup-field'
  127. }, void 0, parentDiv).setAttribute(PREFIX + 'data-name', option);
  128. },
  129. /**
  130. * Create button.
  131. * @private
  132. * @param {Highcharts.HTMLDOMElement} parentDiv
  133. * Container where elements should be added
  134. * @param {string} label
  135. * Text placed as button label
  136. * @param {string} type
  137. * add | edit | remove
  138. * @param {Function} callback
  139. * On click callback
  140. * @param {Highcharts.HTMLDOMElement} fieldsDiv
  141. * Container where inputs are generated
  142. * @return {Highcharts.HTMLDOMElement}
  143. * HTML button
  144. */
  145. addButton: function (parentDiv, label, type, callback, fieldsDiv) {
  146. var _self = this, closePopup = this.closePopup, getFields = this.getFields, button;
  147. button = createElement(BUTTON, void 0, void 0, parentDiv);
  148. button.appendChild(doc.createTextNode(label));
  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, void 0, void 0, popupDiv).appendChild(doc.createTextNode(pick(
  273. // Advanced annotations:
  274. lang[options.langKey] || options.langKey,
  275. // Basic shapes:
  276. options.shapes && options.shapes[0].type)));
  277. // add buttons
  278. button = this.addButton(popupDiv, lang.removeButton || 'remove', 'remove', callback, popupDiv);
  279. button.className += ' ' + PREFIX + 'annotation-remove-button';
  280. button.style['background-image'] = 'url(' +
  281. this.iconsURL + 'destroy.svg)';
  282. button = this.addButton(popupDiv, lang.editButton || 'edit', 'edit', function () {
  283. showForm.call(_self, 'annotation-edit', chart, options, callback);
  284. }, popupDiv);
  285. button.className += ' ' + PREFIX + 'annotation-edit-button';
  286. button.style['background-image'] = 'url(' +
  287. this.iconsURL + 'edit.svg)';
  288. },
  289. /**
  290. * Create annotation simple form.
  291. * It contains fields with param names.
  292. * @private
  293. * @param {Highcharts.Chart} chart
  294. * Chart
  295. * @param {Object} options
  296. * Options
  297. * @param {Function} callback
  298. * On click callback
  299. * @param {boolean} [isInit]
  300. * If it is a form declared for init annotation
  301. */
  302. addForm: function (chart, options, callback, isInit) {
  303. var popupDiv = this.popup.container, lang = this.lang, bottomRow, lhsCol;
  304. // create title of annotations
  305. lhsCol = createElement('h2', {
  306. className: PREFIX + 'popup-main-title'
  307. }, void 0, popupDiv);
  308. lhsCol.appendChild(doc.createTextNode(lang[options.langKey] || options.langKey || ''));
  309. // left column
  310. lhsCol = createElement(DIV, {
  311. className: PREFIX + 'popup-lhs-col ' + PREFIX + 'popup-lhs-full'
  312. }, null, popupDiv);
  313. bottomRow = createElement(DIV, {
  314. className: PREFIX + 'popup-bottom-row'
  315. }, null, popupDiv);
  316. this.annotations.addFormFields.call(this, lhsCol, chart, '', options, [], true);
  317. this.addButton(bottomRow, isInit ?
  318. (lang.addButton || 'add') :
  319. (lang.saveButton || 'save'), isInit ? 'add' : 'save', callback, popupDiv);
  320. },
  321. /**
  322. * Create annotation's form fields.
  323. * @private
  324. * @param {Highcharts.HTMLDOMElement} parentDiv
  325. * Div where inputs are placed
  326. * @param {Highcharts.Chart} chart
  327. * Chart
  328. * @param {string} parentNode
  329. * Name of parent to create chain of names
  330. * @param {Highcharts.AnnotationsOptions} options
  331. * Options
  332. * @param {Array<unknown>} storage
  333. * Array where all items are stored
  334. * @param {boolean} [isRoot]
  335. * Recursive flag for root
  336. */
  337. addFormFields: function (parentDiv, chart, parentNode, options, storage, isRoot) {
  338. var _self = this, addFormFields = this.annotations.addFormFields, addInput = this.addInput, lang = this.lang, parentFullName, titleName;
  339. objectEach(options, function (value, option) {
  340. // create name like params.styles.fontSize
  341. parentFullName = parentNode !== '' ?
  342. parentNode + '.' + option : option;
  343. if (isObject(value)) {
  344. if (
  345. // value is object of options
  346. !isArray(value) ||
  347. // array of objects with params. i.e labels in Fibonacci
  348. (isArray(value) && isObject(value[0]))) {
  349. titleName = lang[option] || option;
  350. if (!titleName.match(indexFilter)) {
  351. storage.push([
  352. true,
  353. titleName,
  354. parentDiv
  355. ]);
  356. }
  357. addFormFields.call(_self, parentDiv, chart, parentFullName, value, storage, false);
  358. }
  359. else {
  360. storage.push([
  361. _self,
  362. parentFullName,
  363. 'annotation',
  364. parentDiv,
  365. value
  366. ]);
  367. }
  368. }
  369. });
  370. if (isRoot) {
  371. stableSort(storage, function (a) {
  372. return a[1].match(/format/g) ? -1 : 1;
  373. });
  374. if (isFirefox) {
  375. storage.reverse(); // (#14691)
  376. }
  377. storage.forEach(function (genInput) {
  378. if (genInput[0] === true) {
  379. createElement(SPAN, {
  380. className: PREFIX + 'annotation-title'
  381. }, void 0, genInput[2]).appendChild(doc.createTextNode(genInput[1]));
  382. }
  383. else {
  384. addInput.apply(genInput[0], genInput.splice(1));
  385. }
  386. });
  387. }
  388. }
  389. },
  390. indicators: {
  391. /**
  392. * Create indicator's form. It contains two tabs (ADD and EDIT) with
  393. * content.
  394. * @private
  395. */
  396. addForm: function (chart, _options, callback) {
  397. var tabsContainers, indicators = this.indicators, lang = this.lang, buttonParentDiv;
  398. // add tabs
  399. this.tabs.init.call(this, chart);
  400. // get all tabs content divs
  401. tabsContainers = this.popup.container
  402. .querySelectorAll('.' + PREFIX + 'tab-item-content');
  403. // ADD tab
  404. this.addColsContainer(tabsContainers[0]);
  405. indicators.addIndicatorList.call(this, chart, tabsContainers[0], 'add');
  406. buttonParentDiv = tabsContainers[0]
  407. .querySelectorAll('.' + PREFIX + 'popup-rhs-col')[0];
  408. this.addButton(buttonParentDiv, lang.addButton || 'add', 'add', callback, buttonParentDiv);
  409. // EDIT tab
  410. this.addColsContainer(tabsContainers[1]);
  411. indicators.addIndicatorList.call(this, chart, tabsContainers[1], 'edit');
  412. buttonParentDiv = tabsContainers[1]
  413. .querySelectorAll('.' + PREFIX + 'popup-rhs-col')[0];
  414. this.addButton(buttonParentDiv, lang.saveButton || 'save', 'edit', callback, buttonParentDiv);
  415. this.addButton(buttonParentDiv, lang.removeButton || 'remove', 'remove', callback, buttonParentDiv);
  416. },
  417. /**
  418. * Create HTML list of all indicators (ADD mode) or added indicators
  419. * (EDIT mode).
  420. * @private
  421. */
  422. addIndicatorList: function (chart, parentDiv, listType) {
  423. var _self = this, lhsCol = parentDiv.querySelectorAll('.' + PREFIX + 'popup-lhs-col')[0], rhsCol = parentDiv.querySelectorAll('.' + PREFIX + 'popup-rhs-col')[0], isEdit = listType === 'edit', series = (isEdit ?
  424. chart.series : // EDIT mode
  425. chart.options.plotOptions // ADD mode
  426. ), addFormFields = this.indicators.addFormFields, rhsColWrapper, indicatorList, item;
  427. // create wrapper for list
  428. indicatorList = createElement(UL, {
  429. className: PREFIX + 'indicator-list'
  430. }, null, lhsCol);
  431. rhsColWrapper = rhsCol
  432. .querySelectorAll('.' + PREFIX + 'popup-rhs-col-wrapper')[0];
  433. objectEach(series, function (serie, value) {
  434. var seriesOptions = serie.options;
  435. if (serie.params ||
  436. seriesOptions && seriesOptions.params) {
  437. var indicatorNameType_1 = _self.indicators.getNameType(serie, value), indicatorType_1 = indicatorNameType_1.type;
  438. item = createElement(LI, {
  439. className: PREFIX + 'indicator-list'
  440. }, void 0, indicatorList);
  441. item.appendChild(doc.createTextNode(indicatorNameType_1.name));
  442. ['click', 'touchstart'].forEach(function (eventName) {
  443. addEvent(item, eventName, function () {
  444. addFormFields.call(_self, chart, isEdit ? serie : series[indicatorType_1], indicatorNameType_1.type, rhsColWrapper);
  445. // add hidden input with series.id
  446. if (isEdit && serie.options) {
  447. createElement(INPUT, {
  448. type: 'hidden',
  449. name: PREFIX + 'id-' + indicatorType_1,
  450. value: serie.options.id
  451. }, null, rhsColWrapper)
  452. .setAttribute(PREFIX + 'data-series-id', serie.options.id);
  453. }
  454. });
  455. });
  456. }
  457. });
  458. // select first item from the list
  459. if (indicatorList.childNodes.length > 0) {
  460. indicatorList.childNodes[0].click();
  461. }
  462. },
  463. /**
  464. * Extract full name and type of requested indicator.
  465. * @private
  466. * @param {Highcharts.Series} series
  467. * Series which name is needed. (EDIT mode - defaultOptions.series, ADD
  468. * mode - indicator series).
  469. * @param {string} - indicator type like: sma, ema, etc.
  470. * @return {Object} - series name and type like: sma, ema, etc.
  471. */
  472. getNameType: function (series, type) {
  473. var options = series.options, seriesTypes = H.seriesTypes,
  474. // add mode
  475. seriesName = seriesTypes[type] &&
  476. seriesTypes[type].prototype.nameBase || type.toUpperCase(), seriesType = type;
  477. // edit
  478. if (options && options.type) {
  479. seriesType = series.options.type;
  480. seriesName = series.name;
  481. }
  482. return {
  483. name: seriesName,
  484. type: seriesType
  485. };
  486. },
  487. /**
  488. * List all series with unique ID. Its mandatory for indicators to set
  489. * correct linking.
  490. * @private
  491. * @param {string} type
  492. * Indicator type like: sma, ema, etc.
  493. * @param {string} optionName
  494. * Type of select i.e series or volume.
  495. * @param {Highcharts.Chart} chart
  496. * Chart
  497. * @param {Highcharts.HTMLDOMElement} parentDiv
  498. * Element where created HTML list is added
  499. * @param {string} selectedOption
  500. * optional param for default value in dropdown
  501. */
  502. listAllSeries: function (type, optionName, chart, parentDiv, selectedOption) {
  503. var selectName = PREFIX + optionName + '-type-' + type, lang = this.lang, selectBox, seriesOptions;
  504. createElement(LABEL, {
  505. htmlFor: selectName
  506. }, null, parentDiv).appendChild(doc.createTextNode(lang[optionName] || optionName));
  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. value: seriesOptions.id
  521. }, null, selectBox).appendChild(doc.createTextNode(seriesOptions.name || seriesOptions.id));
  522. }
  523. });
  524. if (defined(selectedOption)) {
  525. selectBox.value = selectedOption;
  526. }
  527. },
  528. /**
  529. * Create typical inputs for chosen indicator. Fields are extracted from
  530. * defaultOptions (ADD mode) or current indicator (ADD mode). Two extra
  531. * fields are added:
  532. * - hidden input - contains indicator type (required for callback)
  533. * - select - list of series which can be linked with indicator
  534. * @private
  535. * @param {Highcharts.Chart} chart
  536. * Chart
  537. * @param {Highcharts.Series} series
  538. * Indicator
  539. * @param {string} seriesType
  540. * Indicator type like: sma, ema, etc.
  541. * @param {Highcharts.HTMLDOMElement} rhsColWrapper
  542. * Element where created HTML list is added
  543. */
  544. addFormFields: function (chart, series, seriesType, rhsColWrapper) {
  545. var fields = series.params || series.options.params, getNameType = this.indicators.getNameType;
  546. // reset current content
  547. rhsColWrapper.innerHTML = '';
  548. // create title (indicator name in the right column)
  549. createElement(H3, {
  550. className: PREFIX + 'indicator-title'
  551. }, void 0, rhsColWrapper).appendChild(doc.createTextNode(getNameType(series, seriesType).name));
  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 (value !== void 0) { // skip if field is unnecessary, #15362
  588. if (isObject(value)) {
  589. addParamInputs.call(_self, chart, parentFullName, value, type, parentDiv);
  590. }
  591. else if (
  592. // skip volume field which is created by addFormFields
  593. parentFullName !== 'params.volumeSeriesID') {
  594. addInput.call(_self, parentFullName, type, parentDiv, [value, 'text'] // all inputs are text type
  595. );
  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. className: className
  654. }, void 0, popupDiv);
  655. menuItem.appendChild(doc.createTextNode(lang[tabName + 'Button'] || tabName));
  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 ' + PREFIX + 'no-mousewheel' // #12100
  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/9.1.0/gfx/stock-icons/'), this.chart);
  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. });