DeGiro improved filters

Adds missing sort options and amount of shares to buy calculator

  1. // ==UserScript==
  2. // @name DeGiro improved filters
  3. // @namespace https://yelidmod.com/degiro
  4. // @version 0.7
  5. // @description Adds missing sort options and amount of shares to buy calculator
  6. // @author DonNadie
  7. // @match https://trader.degiro.nl/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. // jshint esversion: 6
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. const section = {
  17. equity: 1,
  18. etf: 131
  19. };
  20.  
  21. let filtersIndex = {
  22. volume: 0,
  23. isin: null,
  24. };
  25. const sortButton = '<i role="img" data-name="icon" data-type="sort" aria-hidden="true" class="ife-sort-icon"><svg viewBox="0 0 24 24"><path d="M16.8 13.2L12 18l-4.8-4.8h9.6zM12 6l4.8 4.8H7.2L12 6z"></path></svg></i>';
  26.  
  27. const addStyle = (styleString) => {
  28. const style = document.createElement('style');
  29. style.textContent = styleString;
  30. document.head.append(style);
  31. };
  32.  
  33. const sort = () => {
  34. const trList = document.querySelectorAll('[data-name="productTypeSearch"] tr');
  35.  
  36. new Promise((resolve, reject) => {
  37. let list = [];
  38. let val;
  39. trList.forEach((tr, e) => {
  40. tr.querySelectorAll('td').forEach((td, i) => {
  41. if (i !== filtersIndex.volume) {
  42. return;
  43. }
  44. val = parseInt(td.innerText.replace(".", ""));
  45. list.push({
  46. tr: tr,
  47. value : isNaN(val) ? 0 : val
  48. });
  49. });
  50. if (e == (trList.length - 1)) {
  51. resolve(list);
  52. }
  53. });
  54. }).then(list => {
  55. list.sort((a, b) => (a.value < b.value) ? 1 : -1);
  56. list.forEach(entry => {
  57. document.querySelector('[data-name="productTypeSearch"] tbody').appendChild(entry.tr);
  58. });
  59. });
  60.  
  61. };
  62.  
  63. const onLoaded = () => {
  64. const url = new URL(location.href.replace("#", ''));
  65.  
  66. if (!url.searchParams.has('productType')) {
  67. return;
  68. }
  69.  
  70. const currentSection = parseInt(url.searchParams.get('productType'));
  71.  
  72. document.querySelectorAll('[data-name="productTypeSearch"] th').forEach((el, i) => {
  73. if (el.innerText == "Volumen") {
  74. filtersIndex.volume = i;
  75. el.classList.add('ife-container');
  76. el.innerHTML += sortButton;
  77. el.addEventListener("click", sort);
  78. } else if (el.innerText.includes("ISIN")) {
  79. filtersIndex.isin = i;
  80. }
  81. });
  82.  
  83. if (filtersIndex.isin == null || ![section.etf, section.equity].includes(currentSection)) {
  84. return;
  85. }
  86. const morningType = currentSection == section.etf ? 'ETF' : 'STOCK';
  87.  
  88. document.querySelectorAll('[data-name="productTypeSearch"] tr').forEach((tr, e) => {
  89. tr.querySelectorAll('td').forEach((td, i) => {
  90. if (i !== filtersIndex.isin) {
  91. return;
  92. }
  93. let isin;
  94.  
  95. if (td.innerText.includes("/")) {
  96. isin = td.innerText.split(" / ")[1];
  97. } else if (td.innerText.length > 5) {
  98. isin = td.innerText;
  99. }
  100.  
  101. td.querySelector("span").innerHTML = td.innerText.replace(isin, '<a href="https://www.morningstar.es/es/funds/SecuritySearchResults.aspx?type=' + morningType + '&search=' + isin + '" class="ife-link" target="_blank">' + isin + '</a>');
  102. });
  103. });
  104. };
  105.  
  106. const showCalculator = (mutationsList) => {
  107. let section = document.querySelector('[data-name="orderForm"] section');
  108.  
  109. if (section == null ||
  110. document.getElementById('simple-calculator') != null ||
  111. typeof window.calculatorInjected !== "undefined") {
  112. return;
  113. }
  114. window.calculatorInjected = true;
  115.  
  116. const calculatorContainer = document.createElement("div");
  117. calculatorContainer.id = "simple-calculator";
  118. calculatorContainer.classList.add("ife-calculator-container");
  119. calculatorContainer.innerHTML += '<div class="ife-input-container"><div class="ife-input-label"><input id="calculator-money" type="number" min="0" step="1" placeholder="0" class="ife-input"><span>€</span></div></div>'
  120. + "." +
  121. '<div class="ife-input-container"><div class="ife-input-label"><input id="calculator-shares" type="text" class="ife-input" placeholder="0" disabled><span>shares</span></div></div>';
  122.  
  123. const moneyInput = calculatorContainer.querySelector("#calculator-money");
  124. const sharesInput = calculatorContainer.querySelector("#calculator-shares");
  125.  
  126. moneyInput.addEventListener("input", () => {
  127. const sharePrice = parseFloat(document.querySelector('[data-field="CurrentPrice"]').title.replace(".", "").replace(",", "."));
  128. sharesInput.value = Math.floor(moneyInput.value / sharePrice);
  129. });
  130.  
  131. setTimeout(() => {
  132. // it seems like section is recreated in just a few secs :S
  133. section = document.querySelector('[data-name="orderForm"] section');
  134. section.parentNode.insertBefore(calculatorContainer, section);
  135. delete(window.calculatorInjected);
  136. }, 1000);
  137. };
  138.  
  139. addStyle(`
  140. .ife-container {
  141. align-items: center;
  142. display: flex;
  143. flex-direction: row;
  144. padding-right: 12px;
  145. position: relative;
  146. }
  147. .ife-sort-icon {
  148. contain: strict;
  149. display: inline-block;
  150. flex-shrink: 0;
  151. font-style: normal;
  152. font-weight: 400;
  153. line-height: 1;
  154. opacity: 1;
  155. overflow: hidden;
  156. width: 20px;
  157. height: 20px;
  158. text-align: center;
  159. -webkit-user-select: none;
  160. -moz-user-select: none;
  161. -ms-user-select: none;
  162. user-select: none;
  163. vertical-align: middle;
  164. position: absolute;
  165. right: -8px;
  166. top: 50%;
  167. transform: translateY(-50%);
  168. cursor: pointer;
  169. }
  170. .ife-link {
  171. color: #009fdf;
  172. }
  173. .ife-calculator-container {
  174. align-items: flex-start;
  175. display: flex;
  176. flex-direction: row;
  177. }
  178. .ife-input-container {
  179. display: flex;
  180. flex: 1 1 50%;
  181. flex-direction: column;
  182. max-width: 50%;
  183. margin-top: 10px;
  184. margin-bottom: 10px;
  185. }
  186. .ife-input-label {
  187. background-color: #f3f4f5;
  188. border: 1px solid transparent;
  189. display: inline-block;
  190. height: 32px;
  191. min-height: 32px;
  192. position: relative;
  193. }
  194. .ife-input {
  195. background-color: inherit;
  196. border: 0;
  197. border-radius: inherit;
  198. display: block;
  199. font-size: 1rem;
  200. height: 100%;
  201. line-height: 1.5;
  202. min-height: 100%;
  203. padding: 0 8px;
  204. width: 100%;
  205. }
  206. .ife-input[disabled], .ife-input[disabled]::placeholder {
  207. color: #00a658;
  208. }
  209. .ife-input-label span {
  210. color: #00a658;
  211. position: absolute;
  212. top: 30%;
  213. right: 10px;
  214. }
  215. `);
  216.  
  217. let observerTimeout = null;
  218. const observerCallback = (mutationsList, observer) => {
  219. showCalculator(mutationsList);
  220.  
  221. // since we can't track an specific id, we observe any body changes and
  222. // check if our classes are present.
  223. if (document.querySelectorAll('.ife-container').length > 0) {
  224. return;
  225. }
  226. if (observerTimeout != null) {
  227. clearTimeout(observerTimeout);
  228. }
  229. observerTimeout = setTimeout(onLoaded, 1000 * 2);
  230. };
  231.  
  232. (new MutationObserver(observerCallback)).observe(document.body, {childList: true, subtree: true });
  233. })();