1String.prototype.nf = function( decimals, min_decimals ) { 2 if( typeof min_decimals != 'number' ){ min_decimals = decimals } 3 4 var number = parseFloat(this); 5 if( isNaN(number) ){ 6 return ""; 7 } 8 9 return number.nf(decimals, min_decimals); 10 11} 12Number.prototype.nf = function( decimals, min_decimals ) { 13 if( typeof min_decimals != 'number' ){ min_decimals = decimals } 14 15 var number = this; 16 17 decimals = Math.round(decimals); 18 decimals = Math.min(10,decimals); 19 decimals = Math.max(0,decimals); 20 21 if( isNaN(number) || number == null ){ 22 return ""; 23 } 24 25 var nStr = number.toFixed( decimals ) + ''; 26 27 var x = nStr.split('.'); 28 var x1 = x[0]; 29 30 if( x.length > 1 && min_decimals < decimals ){ 31 while( x[1].charAt(x[1].length - 1) == '0' && min_decimals < decimals ){ 32 x[1] = x[1].slice(0,-1); 33 decimals--; 34 } 35 } 36 37 var x2 = (x.length > 1 && x[1])? (','+x[1]) : ''; 38 var rgx = /(\d+)(\d{3})/; 39 while (rgx.test(x1)) { 40 x1 = x1.replace(rgx, '$1' + '.' + '$2'); 41 } 42 return x1 + x2; 43 44} 45 46String.prototype.parse_nf = function( ){ 47 // get the numeric value 48 var signum = (this.indexOf('-') > -1)? '-' : ''; 49 var value = this.split('.').join(""); 50 var value_parts = value.match( /[0-9,]+/gi ); 51 if( value_parts && value_parts.length ){ 52 value = value_parts[0]; 53 }else{ 54 return null; 55 } 56 value_parts = value.split(','); 57 if(value_parts.length > 2){ 58 // multiple commas 59 var last = value_parts.pop(); 60 value = signum+value_parts.join('',last)+'.'+last; 61 }else if(value_parts.length == 2){ 62 value = signum+value_parts[0]+'.'+value_parts[1]; 63 }else{ 64 value = signum+value_parts[0]; 65 } 66 var value_num = parseFloat(value); 67 if( isNaN(value_num) ){ 68 value_num = null; 69 } 70 return value_num; 71} 72Number.prototype.parse_nf = function(){ 73 return this; 74} 75 76String.prototype.nf_financial = function( decimals, unit ) { 77 78 var number = parseFloat(this); 79 if( isNaN(number) ){ 80 return ""; 81 } 82 83 return number.nf_financial(decimals, unit); 84 85} 86Number.prototype.nf_financial = function( decimals, unit ){ 87 var units_zeroes = [ 0, 3, 6, 9, 12, 15]; 88 var units_names = [ '', 'Tsd.', 'Mio.', 'Mrd.', 'Bio.', 'Brd.']; 89 90 if( unit ){ 91 var unit_index = units_names.indexOf(unit); 92 if( unit_index < 0 ){ 93 unit_index = units_zeroes.indexOf(unit); 94 } 95 if( unit_index < 0 ){ 96 unit_index = 0; 97 unit = ''; 98 }else{ 99 unit = ' '+units_names[unit_index]; 100 } 101 }else{ 102 unit_index = 0; 103 unit = ''; 104 } 105 106 var number = this; 107 if( isNaN(number) ){ 108 return ""; 109 } 110 111 while( number > 1000 ){ 112 if( units_zeroes.length <= unit_index+1 ){ 113 break; 114 } 115 number = number/1000; 116 unit_index = unit_index+1; 117 unit = ' '+units_names[unit_index]; 118 } 119 return number.nf(decimals)+unit; 120} 121 122function createToast(options) { 123 if (!options || typeof options != 'object') { throw new IllegalArgument('options expects Object'); } 124 if (!options.hasOwnProperty('header') || typeof options.header != 'string') { throw new IllegalArgument('options.header expects String'); } 125 if (!options.hasOwnProperty('message') || typeof options.message != 'string') { throw new IllegalArgument('options.message expects String'); } 126 127 let className = (options.hasOwnProperty('className') && typeof options.className == 'string')? options.className : ''; 128 let fa = (options.hasOwnProperty('fa') && typeof options.fa == 'string')? options.fa : 'fas fa-info-circle'; 129 let small = (options.hasOwnProperty('small') && typeof options.small == 'string')? options.small : ''; 130 131 let parts1 = options.header.split(' '); 132 let parts2 = options.message.split(' '); 133 134 let delay = 0; 135 if (options.hasOwnProperty('delay') && typeof options.delay == 'number') { 136 delay = options.delay; 137 } else { 138 delay = Math.ceil(parts1.length + parts2.length / 4) * 1000; 139 delay = Math.max(delay,2000); 140 } 141 142 let toastElem = document.createElement('div'); 143 toastElem.className = 'toast '+className; 144 toastElem.setAttribute('data-bs-delay',delay); 145 toastElem.setAttribute('data-bs-autohide','true'); 146 147 toastElem.innerHTML = 148 '<div class="toast-header">'+ 149 '<i class="'+fa+' me-2"></i>'+ 150 '<strong class="me-auto">'+options.header+'</strong>'+ 151 '<small class="text-body-secondary">
151'+small+'</small>'+ 152 '<button type="button" class="btn-close" data-bs-dismiss="toast"></button>'+ 153 '</div>'+ 154 '<div class="toast-body">'+options.message+'</div>'; 155 156 let toastContainer = document.getElementById('toast-container'); 157 toastContainer.appendChild(toastElem); 158 159 let toast = bootstrap.Toast.getOrCreateInstance(toastElem); 160 toastElem.addEventListener('hidden.bs.toast',disposeToast); 161 toast.show(); 162 163 return toast; 164} 165function disposeToast(event) { 166 let toastElem = event.target; 167 let toast = bootstrap.Toast.getInstance(toastElem); 168 if (toast) { 169 toast.dispose(); 170 toast = null; 171 } 172 toastElem.removeEventListener('hidden.bs.toast',disposeToast); 173 toastElem.remove(); 174 toastElem = null; 175} 176 177function initUpdateElements(updateHtmlElements){ 178 for (let i = 0; i < updateHtmlElements.length; i++) { 179 let id = updateHtmlElements[i].getAttribute('data-id'); 180 let type = updateHtmlElements[i].getAttribute('data-type'); 181 if (type == 'last') { 182 let prefix = updateHtmlElements[i].getAttribute('data-prefix'); 183 if (!prefix) {prefix = '';} 184 let postfix = updateHtmlElements[i].getAttribute('data-postfix'); 185 if (!postfix) {postfix = '';} 186 let perfElement = new StockElement({ 187 element : updateHtmlElements[i], 188 language : language, 189 stocksManager : stocksManager, 190 updateValues : ['last'], 191 configuration : { 192 key: 'last', 193 dtype: 'float', 194 defaultValue: '-', 195 precision: 2, 196 showSignum: false, 197 isBlinking: true, 198 isColored: false, 199 prefix: prefix, 200 postfix: postfix 201 }, 202 data : { 203 id : id, 204 stock_id : id, 205 change : null 206 } 207 }); 208 updateElements.push(perfElement); 209 } 210 else if (type == 'change') { 211 let perfElement = new StockElement({ 212 element : updateHtmlElements[i], 213 language : language, 214 stocksManager : stocksManager, 215 updateValues : ['change'], 216 configuration : { 217 key: 'change', 218 dtype: 'float', 219 defaultValue: '-', 220 precision: 2, 221 showSignum: true, 222 isBlinking: true, 223 isColored: true, 224 prefix: '', 225 postfix: ' % <small>1T</small>' 226 }, 227 data : { 228 id : id, 229 stock_id : id, 230 change : null 231 } 232 }); 233 updateElements.push(perfElement); 234 } 235 else if (type == 'perf_1w') { 236 let perf1wElement = new StockDataElement({ 237 element : updateHtmlElements[i], 238 language : language, 239 stockDataManager : stockDataManager, 240 updateValues : ['perf_1w'], 241 configuration : { 242 key: 'perf_1w', 243 dtype: 'float', 244 defaultValue: '-', 245 precision: 2, 246 showSignum: true, 247 isBlinking: true, 248 isColored: true, 249 prefix: '', 250 postfix: ' % <small>1W</small>' 251 }, 252 data : { 253 id : id, 254 stock_id : id, 255 perf_1w : null 256 } 257 }); 258 } 259 else if (type == 'date') { 260 let dateElement = new StockElement({ 261 element : updateHtmlElements[i], 262 language : language, 263 stocksManager : stocksManager, 264 updateValues : ['lasttime'], 265 configuration : { 266 key: 'lasttime', 267 dtype: 'date', 268 defaultValue: '-', 269 formatOptions: {year: "numeric",month: "long",day:"2-digit",hour: '2-digit',minute:'2-digit'}, 270 prefix: '', 271 postfix: '' 272 }, 273 data : { 274 id : id, 275 stock_id : id, 276 lasttime : null 277 } 278 }); 279 updateElements.push(dateElement); 280 } 281 } 282} 283 284class Game { 285 constructor(options) { 286 if ( !options || typeof options != 'object' ) { throw new IllegalArgument('options expects Object'); } 287 if ( !options.hasOwnProperty('element') || !(options.element instanceof Element) ) { throw new IllegalArgument('options.element expects DOM Element'); } 288 if ( !options.hasOwnProperty('baseStock') || typeof options.baseStock != 'object' ) { throw new IllegalArgument('options.baseStock expects Object'); } 289 if ( !options.hasOwnProperty('baseStockContainer') || !(options.baseStockContainer instanceof Element) ) { throw new IllegalArgument('options.baseStockContainer expects DOM Element'); } 290 if ( !options.hasOwnProperty('baseStockCardData') || typeof options.baseStock != 'object' ) { throw new IllegalArgument('options.baseStockCardData expects Object'); } 291 292 this._getSuggestionsBind = this._getSuggestions.bind(this); 293 this._resetBind = this._reset.bind(this); 294 this._selectCardBind = this._selectCard.bind(this); 295 296 this.element = options.element; 297 298 this.typeControls = this.element.querySelectorAll('input[name="type"]'); 299 for (let i = 0; i < this.typeControls.length; i++) { 300 this.typeControls[i].addEventListener('change',this._getSuggestionsBind); 301 } 302 303 this.buttonGo = this.element.querySelector('button[name="go"]'); 304 this.buttonReset = this.element.querySelector('button[name="reset"]'); 305 306 this.buttonGo.addEventListener('click',this._getSuggestionsBind); 307 this.buttonReset.addEventListener('click',this._resetBind); 308 309 this._xhr = null; 310 311 // stored for reset. 312 // if the data of the current baseCard is needed, get it from the baseCard 313 this.baseStock = options.baseStock; 314 this.baseStockContainer = options.baseStockContainer; 315 this.baseStockCardData = options.baseStockCardData; 316 317 this.baseCard = new GameCard({ 318 'element' : (this.baseStockContainer.children.length ? this.baseStockContainer.children[0] : null),
319 'container' : this.baseStockContainer, 320 'stock' : this.baseStock, 321 'cardData' : this.baseStockCardData, 322 }); 323 324 this.suggestionContainers = this.element.querySelectorAll('.cardgame-card-container.cardgame-suggestion'); 325 this.suggestionCards = []; 326 327 this.suggestionTitles = this.element.querySelectorAll('.cardgame-suggestion-title'); 328 } 329 _reset() { 330 let baseStockId = this.baseCard.getStockId(); 331 if (baseStockId == this.baseStock.id) { 332 this.buttonReset.classList.add('disabled'); 333 return; 334 } 335 336 for(let i = 0; i < this.suggestionCards.length; i++) { 337 this.suggestionCards[i].remove(); 338 } 339 this.suggestionCards = []; 340 341 this.baseCard.remove(); 342 this.baseCard = new GameCard({ 343 'element' : null, 344 'container' : this.baseStockContainer, 345 'stock' : this.baseStock, 346 'cardData' : this.baseStockCardData, 347 }); 348 349 this.buttonReset.classList.add('disabled'); 350 } 351 _selectCard(card) { 352 let baseStockId = this.baseCard.getStockId(); 353 let stockId = card.getStockId(); 354 if (baseStockId == stockId) { 355 return; 356 } 357 358 this.baseCard.remove(); 359 this.baseCard = new GameCard({ 360 'element' : null, 361 'container' : this.baseStockContainer, 362 'stock' : card.stock, 363 'cardData' : card.cardData, 364 }); 365 366 for(let i = 0; i < this.suggestionCards.length; i++) { 367 let iStockId = this.suggestionCards[i].getStockId(); 368 if (iStockId == stockId) { 369 this.suggestionCards.splice(i,1); 370 break; 371 } 372 } 373 card.remove(); 374 375 this.buttonReset.classList.remove('disabled'); 376 377 this._getSuggestions(); 378 } 379 380 _getFilters() { 381 let filters = []; 382 for (let i = 0; i < this.typeControls.length; i++) { 383 if (this.typeControls[i].checked) { 384 let filter = { 385 id : this.typeControls[i].value, 386 min : this.baseCard.getValue(this.typeControls[i].value), 387 max : null, 388 } 389 filters.push(filter); 390 } 391 } 392 return filters; 393 } 394 _getFilterTitle(id) { 395 if (id == 'quality') { 396 return 'mit besserem Qualitäts-Check'; 397 } else if (id == 'dividend_yield') { 398 return 'mit höherer Dividendenrendite'; 399 } else if (id == 'dauerlaeufer_score') { 400 return 'mit höherer Dauerläufer-Score'; 401 } else if (id == 'relative_strength') { 402 return 'mit höherer Relativen Stärke'; 403 } else if (id == 'geo_performance_10y') { 404 return 'mit höherer durchschnittlichen Jahresperformance'; 405 } else if (id == 'revenue_growth_ttm') { 406 return 'mit höherem Umsatzwachstum'; 407 } else if (id == 'eps_growth_ttm') { 408 return 'mit höherem Gewinnwachstum'; 409 } else { 410 return ''; 411 } 412 } 413 _getFilterLink(id) { // TODO 414 if (id == 'quality') { 415 return ''; 416 } else if (id == 'dividend_yield') { 417 return ''; 418 } else if (id == 'dauerlaeufer_score') { 419 return ''; 420 } else if (id == 'relative_strength') { 421 return ''; 422 } else if (id == 'geo_performance_10y') { 423 return ''; 424 } else if (id == 'revenue_growth_ttm') { 425 return ''; 426 } else if (id == 'eps_growth_ttm') { 427 return ''; 428 } else { 429 return ''; 430 } 431 } 432 433 _getSuggestions() { 434 435 let filters = this._getFilters(); 436 437 let title = 'Wähle ein Kriterium, um bessere Aktien angezeigt zu bekommen.'; 438 if (filters.length > 1) { 439 title = 440 '<div class="text-start d-inline-block">'+ 441 '<div class="mb-3">Zufällig ausgewählte Aktien</div>'+ 442 '<ul class="fa-ul fs-110 fw-medium">'; 443 for(let i = 0; i < filters.length; i++) { 444 let filterTitle = this._getFilterTitle(filters[i].id); 445 let filterLink = this._getFilterLink(filters[i].id); 446 if (filterLink) { 447 filterTitle = 448 '<a href="'+filterLink+'" title="Alle Aktien '+filterTitle+' anzeigen" class="text-reset">'+filterTitle+'</a>'; 449 } 450 title = title + 451 '<li class="my-2"><i class="fa-li fas fa-check text-data"></i>'+filterTitle+'</li>'; 452 } 453 title = title+ 454 '</ul>'+ 455 '</div>'; 456 } 457 else if (filters.length > 0) { 458 let filterTitle = this._getFilterTitle(filters[0].id); 459 let filterLink = this._getFilterLink(filters[0].id); 460 title = 461 '<div class="row align-items-center justify-content-end">'+ 462 '<div class="col">Aktien '+filterTitle+'<small class="d-block">Zufallsauswahl</small></div>'; 463 if (filterLink) { 464 title = title+ 465 '<div class="col-auto"><a href="'+filterLink+'" class="text-reset">alle anzeigen</a></div>'; 466 } 467 title = title + 468 '</div>'; 469 } 470 for (let i = 0; i < this.suggestionTitles.length; i++) { 471 this.suggestionTitles[i].innerHTML = title; 472 } 473 474 if (!filters.length) { 475 createToast({ 476 header : 'Achtung', 477 message : 'Bitte wählen Sie mindestens einen Filter aus.', 478 className : 'toast-warning', 479 fa : 'fas fa-exclamation-triangle' 480 }); 481 return; 482 } 483 484 if( this._xhr ){ 485 createToast({ 486 header : 'Achtung', 487 message : 'Bitte haben Sie einen Moment Geduld, während die Vorschläge geladen werden.', 488 className : 'toast-warning', 489 fa : 'fas fa-exclamation-triangle' 490 }); 491 return; 492 } 493 494 this.disableEditing(); 495 496 let data = "num="+(this.suggestionContainers.length+1); 497 for(let i = 0; i < filters.length; i++) { 498 let num = i+1; 499 data = data+'&type'+num+'='+filters[i].id; 500 if (filters[i].min !== null) { 501 data = data+'&min'+num+'='+filters[i].min; 502 } 503 if (filters[i].max !== null) { 504 data = data+'&min'+num+'='+filters[i].max; 505 } 506 } 507 508 this._xhr = new XMLHttpRequest(); 509 this._xhr.open("POST", "/ajax/getCardGameSuggestions.php",true); 510 this._xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); 511 this._xhr.onreadystatechange = function() { 512 if (this._xhr.readyState == 4) { 513
514 for(let i = 0; i < this.suggestionCards.length; i++) { 515 this.suggestionCards[i].remove(); 516 } 517 this.suggestionCards = []; 518 519 if (this._xhr.status == 200) { 520 let data = JSON.parse(this._xhr.responseText); 521 522 if ( 523 data.hasOwnProperty('data') && 524 data.data.hasOwnProperty('stocks') && data.data.hasOwnProperty('cards') && 525 !TraderFoxUtils.isObjectEmpty(data.data.stocks) && !TraderFoxUtils.isObjectEmpty(data.data.cards) 526 ) { 527 let baseStockId = this.baseCard.getStockId(); 528 let i = 0; 529 for (let stockId in data.data.cards) { 530 if (i >= this.suggestionContainers.length) { 531 break; 532 } 533 if (stockId == baseStockId) { 534 continue; 535 } 536 if (data.data.stocks.hasOwnProperty(stockId)) { 537 let card = new GameCard({ 538 'element' : null, 539 'container' : this.suggestionContainers[i], 540 'stock' : data.data.stocks[stockId], 541 'cardData' : data.data.cards[stockId], 542 'onSelect' : this._selectCardBind 543 }); 544 this.suggestionCards.push(card); 545 i++; 546 } 547 } 548 549 if (i < this.suggestionContainers.length - 1) { 550 for (let j = i; j < this.suggestionContainers.length; j++) { 551 this.suggestionContainers[j].innerHTML = '<div class="card"><div class="card-header py-5">Keine weiteren Aktien erfüllen alle Kriterien</div></div>'; 552 } 553 } 554 } else { 555 createToast({ 556 header : 'Achtung', 557 message : 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es später noc
557h einmal', 558 className : 'toast-warning', 559 fa : 'fas fa-exclamation-triangle' 560 }); 561 } 562 } 563 564 this._xhr = null; 565 this.enableEditing(); 566 } 567 }.bind(this); 568 this._xhr.send(data); 569 } 570 571 disableEditing () { 572 for(let i = 0; i < this.suggestionCards.length; i++) { 573 this.suggestionCards[i].disableEditing(); 574 } 575 for (let i = 0; i < this.typeControls.length; i++) { 576 this.typeControls[i].disabled = true; 577 } 578 } 579 enableEditing () { 580 for(let i = 0; i < this.suggestionCards.length; i++) { 581 this.suggestionCards[i].enableEditing(); 582 } 583 for (let i = 0; i < this.typeControls.length; i++) { 584 this.typeControls[i].removeAttribute('disabled'); 585 } 586 } 587} 588class GameCard { 589 constructor(options) { 590 if ( !options || typeof options != 'object' ) { throw new IllegalArgument('options expects Object'); } 591 if ( !options.hasOwnProperty('container') || !(options.container instanceof Element) ) { throw new IllegalArgument('options.container expects DOM Element'); } 592 if ( !options.hasOwnProperty('stock') || typeof options.stock != 'object' ) { throw new IllegalArgument('options.stock expects Object'); } 593 if ( !options.hasOwnProperty('cardData') || typeof options.cardData != 'object' ) { throw new IllegalArgument('options.cardData expects Object'); } 594 595 this._onFlipBind = this._onFlip.bind(this); 596 this._onSelectBind = this._onSelect.bind(this); 597 598 this.stock = options.stock; 599 this.cardData = options.cardData; 600 601 this.onSelectCallback = null; 602 if (options.hasOwnProperty('onSelect') && typeof options.onSelect == 'function') { 603 this.onSelectCallback = options.onSelect; 604 } 605 606 if (options.hasOwnProperty('element') && options.element instanceof Element) { 607 this.element = options.element; 608 } else { 609 this.element = this._createElement(); 610 options.container.innerHTML = ''; 611 options.container.appendChild(this.element); 612 } 613 614 this.buttonSelect = null; 615 if (this.onSelectCallback) { 616 this.buttonSelect = this.element.querySelector('button[name="select"]'); 617 if (this.buttonSelect) { 618 this.buttonSelect.addEventListener('click',this._onSelectBind); 619 } 620 } 621 622 this.fliptoggles = this.element.querySelectorAll('.flip-card-toggle'); 623 for(let i = 0; i < this.fliptoggles.length; i++) { 624 this.fliptoggles[i].addEventListener('click',this._onFlipBind); 625 } 626 627 } 628 getStockId() { 629 return this.stock.id; 630 } 631 destroy() { 632 if (this.buttonSelect) { 633 this.buttonSelect.removeEventListener('click',this._onSelectBind); 634 } 635 for(let i = 0; i < this.fliptoggles.length; i++) { 636 this.fliptoggles[i].removeEventListener('click',this._onFlipBind); 637 } 638 } 639 remove() { 640 this.destroy(); 641 642 let parent = this.element.parentNode; 643 644 this.element = null; 645 this.stock = null; 646 this.cardData = null; 647 this.fliptoggles = null; 648 this.buttonSelect = null; 649 650 parent.innerHTML = '<div class="card cover"></div>'; 651 } 652 disableEditing () { 653 if (this.buttonSelect) { 654 this.buttonSelect.disabled = true; 655 } 656 } 657 enableEditing () { 658 if (this.buttonSelect) { 659 this.buttonSelect.removeAttribute('disabled'); 660 } 661 } 662 getValue(field) { 663 let value = null; 664 if (this.cardData.hasOwnProperty(field)) { 665 value = parseFloat(this.cardData[field]); 666 if (isNaN(value)) { 667 value = null; 668 } 669 } 670 return value; 671 } 672 673 _createElement () { 674 let element = document.createElement('div'); 675 element.className = 'flip-card cardgame-flip-card mx-auto'; 676 677 let qualityHtml = '-'; 678 if (this.cardData.hasOwnProperty('quality')) { 679 let value = parseFloat(this.cardData.quality); 680 if (!isNaN(value)) { 681 qualityHtml = value.nf(0); 682 } 683 } 684 let dividendYieldHtml = '-'; 685 if (this.cardData.hasOwnProperty('dividend_yield')) { 686 let value = parseFloat(this.cardData.dividend_yield); 687 if (!isNaN(value)) { 688 value = value * 100; 689 dividendYieldHtml = value.nf(0)+' %'; 690 } 691 } 692 let dauerlaeuferScoreHtml = '-'; 693 if (this.cardData.hasOwnProperty('dauerlaeufer_score')) { 694 let value = parseFloat(this.cardData.dauerlaeufer_score); 695 if (!isNaN(value)) { 696 value = value * 100; 697 dauerlaeuferScoreHtml = value.nf(0); 698 } 699 } 700 let RelativeStrengthHtml = '-'; 701 if (this.cardData.hasOwnProperty('relative_strength')) { 702 let value = parseFloat(this.cardData.relative_strength); 703 if (!isNaN(value)) { 704 value = value * 100; 705 RelativeStrengthHtml = value.nf(0); 706 } 707 } 708 let geoPerformance10yHtml = '-'; 709 if (this.cardData.hasOwnProperty('geo_performance_10y')) { 710 let value = parseFloat(this.cardData.geo_performance_10y); 711 if (!isNaN(value)) { 712 value = value * 100; 713 geoPerformance10yHtml = value.nf(0)+' %'; 714 } 715 } 716 let revenueGrowthTTMHtml = '-'; 717 if (this.cardData.hasOwnProperty('revenue_growth_ttm')) { 718 let value = parseFloat(this.cardData.revenue_growth_ttm); 719 if (!isNaN(value)) { 720 value = value * 100; 721 revenueGrowthTTMHtml = value.nf(0)+' %'; 722 } 723 } 724 let epsGrowthTTMHtml = '-'; 725 if (this.cardData.hasOwnProperty('eps_growth_ttm')) { 726 let value = parseFloat(this.cardData.eps_growth_ttm); 727 if (!isNaN(value)) { 728 value = value * 100; 729 epsGrowthTTMHtml = value.nf(0)+' %'; 730 } 731 } 732 let descriptionCardHtml = ''; 733 let flipCardMenuHtml = ''; 734 if (this.cardData.hasOwnProperty('description') && this.cardData.description) { 735 flipCardMenuHtml = 736 '<div class="flip-card-menu">'+ 737 '<button type="button" class="flip-card-toggle clickable" data-card="front"><i></i><i></i></button>'+ 738 '</div>'; 739 descriptionCardHtml = 740 '<div class="flip-card-back">'+ 741 '<div class="card">'+ 742 '<div class="card-body">'+this.cardData.description+'</div>'+ 743 '</div>'+ 744 flipCardMenuHtml+ 745 '</div>'; 746 } 747 748 let header = ''; 749 if (this.onSelectCallback) { 750 header = 751 '<div class="card-header has-button">'+ 752 '<strong class="title">'+this.stock.nice_name+'</strong>'+ 753 '<button name="select" type="button" class="btn btn-card-game-icon" title="Suche bessere Aktien als '+this.stock.nice_name+'"><img src="/img/cardgame/click.png"></button>'+ 754 '</div>'; 755 } else { 756 header = '<div class="card-header"><strong class="title">'+this.stock.nice_name+'</strong></div>'; 757 } 758 759 element.innerHTML = 760 '<div class="flip-card-inner shadow">'+ 761 '<div class="flip-card-front">'+ 762 '<div class="card">'+ 763 header+ 764 '<img src="https://charts.traderfox.com/chart/time_range-3650/adjustment-full/stock_id-'+this.stock.id+'/width-410/height-205/class-aktienquartett" class="card-img">'+ 765 '<div class="card-body p-0 flex-grow-0"><div>'+ 766 '<table class="table table-sm table-light"><tbody>'+ 767 '<tr><th>Qualitätscheck</th><td>'+qualityHtml+'</td></tr>'+ 768 '<tr><th>Dividendenrendite</th><td>'+dividendYieldHtml+'</td></tr>'+ 769 '<tr><th>Dauerläufer Score</th><td>'+dauerlaeuferScoreHtml+'</td></tr>'+ 770 '<tr><th>Relative Stärke Score</th><td>'+RelativeStrengthHtml+'</td></tr>'+ 771 '<tr><th>Performance 10 J</th><td>'+geoPerformance10yHtml+'</td></tr>'+ 772 '<tr><th>Umsatzw. (TTM)</th><td>'+revenueGrowthTTMHtml+'</td></tr>'+ 773 '<tr><th>Gewinnw. (TTM)</th><td>'+epsGrowthTTMHtml+'</td></tr>'+ 774 '</tbody></table>'+ 775 '</div></div>'+ 776 '</div>'+ 777 flipCardMenuHtml+ 778 '</div>'+ 779 descriptionCardHtml 780 '</div>'; 781 782 return element; 783 } 784 785 _onFlip (event) { 786 this.element.classList.toggle('flipped'); 787 } 788 _onSelect(event) { 789 if (!this.onSelectCallback) { 790 return; 791 } 792 this.onSelectCallback(this); 793 } 794} 795 796 797class TFSearch { 798 constructor (element, options) { 799 if (!element || !(element instanceof Element)) { throw new IllegalArgument('element expects DOM Element'); } 800 if (!options || typeof options != 'object') { options = {}; } 801
802 this.id = element.id; 803 if (!this.id) { 804 this.id = (''+Math.random()).replace(/\D/g,""); 805 element.id = this.id; 806 } 807 808 if (!options.hasOwnProperty('callback')) { 809 options.callback = TFSearch.onSelected; 810 } 811 if (!options.hasOwnProperty('request')) { 812 options.request = 'full'; 813 } 814 815 options.dataSrc = this._dataSrc.bind(this); 816 options.displayField = 'display'; 817 options.valueField = 'value'; 818 819 this.request = options.request; 820 this.xhr = null; 821 822 this.autocomplete = new Autocomplete(element,options); 823 824 } 825 destroy () { 826 if( this.xhr ){ 827 this.xhr.abort(); 828 this.xhr = null; 829 } 830 this.autocomplete.destroy(); 831 this.autocomplete = null; 832 } 833 834 async _dataSrc (term) { 835 if( this.xhr ){ 836 this.xhr.abort(); 837 this.xhr = null; 838 } 839 let items = await new Promise(resolve => { 840 841 this.xhr = new XMLHttpRequest(); 842 if (this.request == 'stock') { 843 this.xhr.open('GET', '/ajax/searchStocks.php?query='+term, true); 844 } else { 845 this.xhr.open('GET', '/ajax/searchTerms.php?query='+term, true); 846 } 847 this.xhr.onreadystatechange = function() { 848 if (this.xhr.readyState === XMLHttpRequest.DONE){ 849 if (this.xhr.status === 200) { 850 let data = JSON.parse(this.xhr.responseText); 851 let items = []; 852 if( data && data.length ){ 853 for( var i = 0; i < data.length; i++ ){ 854 855 let display = data[i].hasOwnProperty('display') ? data[i].display : data[i].value; 856 857 if (data[i].type == 'title') { 858 display = '<div class="search-results-title">'+display+'</div>'; 859 } 860 data[i]['display'] = display; 861 items.push(data[i]); 862 } 863 } 864 this.xhr = null; 865 resolve(items); 866 }else{ 867 resolve([]); 868 } 869 } 870 }.bind(this) 871 872 this.xhr.send(); 873 874 }); 875 876 return items 877 } 878 879 static onSelected (selected) { 880 if (selected && typeof selected == 'object') { 881 if (selected.hasOwnProperty('link')) { 882 location.href = selected.link; 883 } else if (selected.hasOwnProperty('stock') && typeof selected.stock == 'object' && selected.stock.hasOwnProperty('link')) { 884 location.href = selected.stock.link; 885 } 886 } 887 } 888}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.