Dynamically changing the symbol

Once you’ve laid down the groundwork and embedded TradingView’s widgets into your webpage, you may want to dive deeper and gain more control over your page’s functionality. In this section, we’ll cover how we can dynamically change the stock symbol of interest and how to cleanly manage embed scripts with template elements.

So far, we have created a page for a single stock symbol (AAPL). However, we don’t want to restrict the fun to just one symbol. Let’s make it work for a wide range of symbols.

On the server-side, we can send different content depending on the page’s route (URL path) and modify the symbol names specified in the widget options.

For instance, if we want to display the details for the NVDA symbol instead of AAPL, we simply need to update the value of "symbol" in the options.

JS
{
/* ... the other options ...*/
"symbol": "NASDAQ:NVDA" /* ← changed from NASDAQ:AAPL */
}

Symbol names are defined using the syntax {EXCHANGE}:{NAME}.

In this example, we will retrieve the desired symbol name from the query portion of the URL (the part that comes after the ?). For instance, in the URL https://example.com/?symbol=NASDAQ:AAPL, the query string contains the property “symbol” with a value of “NASDAQ:AAPL”. Using JavaScript, we can extract the symbol name from the URL and dynamically set its value in the embed code before inserting it into the page.

We will use the default query parameter name of tvwidgetsymbol to define the symbol name. The Advanced Chart widget will automatically detect this value and display the correct symbol on the page. 🎉

To handle the remaining widgets, we’ll use JavaScript to dynamically modify the symbol option. This is necessary because these widgets may be used multiple times on a single page, each with a different symbol. Relying solely on the page’s URL isn’t always suitable in such cases. Therefore, we need to include additional code on the website to handle this situation.

To retrieve the symbol value from the query string, you can utilize the following code:

JS
function getQueryParam(param) {
let urlSearchParams = new URLSearchParams(window.location.search);
return urlSearchParams.get(param);
}
function readSymbolFromQueryString() {
return getQueryParam('tvwidgetsymbol');
}
const symbol = readSymbolFromQueryString() || 'NASDAQ:AAPL';

Changing symbol within the Widget options

So far, we have integrated the widgets by directly placing the embed code into the page. As the page loads, the widgets automatically create themselves and replace the contents of the container div element within the embed code. As a result, the widget will begin loading with the default or predefined symbol name before we have an opportunity to read and set the new symbol name from the query string.

One way to address this is by utilizing template elements to store the embed code. This code can be inserted into the document using JavaScript. By converting the embed codes into template elements, we can enhance the cleanliness and maintainability of the page code. These template elements can be cloned dynamically into the document. The content within a template element is not executed or treated as live page content, but it can be used to dynamically insert the content into the page where it will be displayed.

So the changes we are going to make to the HTML source code are as follows. We are going to move the embed code into template elements, and leave the widget container elements as empty placeholders.

HTML
<section id="symbol-info">
</section>
<template id="symbol-info-template">
<!-- TradingView Widget BEGIN -->
<div class="tradingview-widget-container">
<div class="tradingview-widget-container__widget"></div>
<script
type="text/javascript"
src="https://s3.tradingview.com/external-embedding/embed-widget-symbol-info.js"
async
>
{
"symbol": "NASDAQ:AAPL",
"width": "100%",
"locale": "en",
"colorTheme": "light",
"isTransparent": true
}
</script>
</div>
<!-- TradingView Widget END -->
</template>

We will use the following naming convention: the placeholder container for the widget will retain the existing id names (e.g., symbol-info), and the corresponding template element will have an id with the same name but a suffix of -template (e.g., symbol-info-template).

Using javascript, we can clone the template content into the page with the following code:

JS
function cloneTemplateInto(templateId, targetId, rewrites) {
const tmpl = document.querySelector(`#${templateId}`);
if (!tmpl) return;
const target = document.querySelector(`#${targetId}`);
if (!target) return;
target.innerText = '';
const clone = tmpl.content.cloneNode(true);
if (rewrites) {
const script = clone.querySelector('script');
script.textContent = rewrites(script.textContent);
}
target.appendChild(clone);
}

The rewrites function will be used to modify the contents of the script so we can change the value of the symbol property in the widget options.

An example of a rewrite function would be:

JS
const symbol = readSymbolFromQueryString() || 'NASDAQ:AAPL';
function setSymbol(scriptContent) {
return scriptContent.replace(/"symbol": "([^"]*)"/g, () => {
return `"symbol": "${symbol}"`;
});
}

and finally, the code to clone a specific template into the page would be:

JS
cloneTemplateInto('symbol-info-template', 'symbol-info', setSymbol);

We can repeat this process for all the other widgets (except the Ticker tape) as follows:

JS
cloneTemplateInto('symbol-info-template', 'symbol-info', setSymbol);
cloneTemplateInto('advanced-chart-template', 'advanced-chart');
cloneTemplateInto('company-profile-template', 'company-profile', setSymbol);
cloneTemplateInto('fundamental-data-template', 'fundamental-data', setSymbol);
cloneTemplateInto('technical-analysis-template', 'technical-analysis', setSymbol);
cloneTemplateInto('top-stories-template', 'top-stories', setSymbol);

We are excluding Ticker Tape from this template cloning because it isn’t specific to a single symbol but rather is used to show the prices for multiple symbols.

Complete code

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Stock Details</title>
<style>
:root {
--gap-size: 32px;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, 'Trebuchet MS', Roboto,
Ubuntu, sans-serif;
color: #000;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
align-items: center;
background: #fff;
}
header,
footer {
display: flex;
width: 100%;
align-items: center;
background: rgba(0, 0, 0, 0.05);
gap: 12px;
}
header {
justify-content: space-between;
padding: 0 var(--gap-size);
gap: calc(var(--gap-size) * 2);
box-shadow: rgba(0, 0, 0, 0.05) 0 2px 6px 0;
flex-direction: row;
z-index: 1;
}
header #site-logo {
font-weight: 600;
font-size: 32px;
padding: 16px;
background: var(
--18-promo-gradient-02,
linear-gradient(90deg, #00bce5 0%, #2962ff 100%)
);
-webkit-text-fill-color: transparent;
-webkit-background-clip: text;
background-clip: text;
}
header input[type='search'] {
padding: 10px;
width: 100%;
height: 32px;
max-width: 400px;
border: 1px solid #ccc;
border-radius: 20px;
}
footer {
flex-direction: column;
padding: calc(var(--gap-size) * 0.5) var(--gap-size);
margin-top: var(--gap-size);
border-top: solid 2px rgba(0, 0, 0, 0.1);
justify-content: center;
}
footer p,
#powered-by-tv p {
margin: 0;
font-size: 12px;
color: rgba(0, 0, 0, 0.6);
}
main {
display: grid;
width: 100%;
padding: 0 calc(var(--gap-size) * 0.5);
max-width: 960px;
grid-template-columns: 1fr 1fr;
grid-gap: var(--gap-size);
}
.span-full-grid,
#symbol-info,
#advanced-chart,
#company-profile,
#fundamental-data {
grid-column: span 2;
}
.span-one-column,
#technical-analysis,
#top-stories,
#powered-by-tv {
grid-column: span 1;
}
#ticker-tape {
width: 100%;
margin-bottom: var(--gap-size);
}
#advanced-chart {
height: 500px;
}
#company-profile {
height:390px;
}
#fundamental-data {
height: 490px;
}
#technical-analysis,
#top-stories {
height: 425px;
}
#powered-by-tv {
display: flex;
background: #f8f9fd;
border: solid 1px #e0e3eb;
text-align: justify;
flex-direction: column;
gap: 8px;
font-size: 14px;
padding: 16px;
border-radius: 6px;
}
#powered-by-tv a, #powered-by-tv a:visited {
color: #2962ff;
}
@media (max-width: 800px) {
main > section,
.span-full-grid,
#technical-analysis,
#top-stories,
#powered-by-tv {
grid-column: span 2;
}
}
</style>
</head>
<body>
<header>
<a id="site-logo" href="#">TradingVista</a>
<input type="search" placeholder="Search..." />
</header>
<nav id="ticker-tape"></nav>
<main>
<section id="symbol-info">
</section>
<section id="advanced-chart">
</section>
<section id="company-profile">
</section>
<section id="fundamental-data">
</section>
<section id="technical-analysis">
</section>
<section id="top-stories">
</section>
<section id="powered-by-tv">
<svg xmlns="http://www.w3.org/2000/svg" width="157" height="21">
<use href="/widget-docs/tradingview-logo.svg#tradingview-logo"></use>
</svg>
<p>
Charts and financial information provided by TradingView, a popular
charting & trading platform. Check out even more
<a href="https://www.tradingview.com/features/">advanced features</a>
or <a href="https://www.tradingview.com/widget/">grab charts</a> for
your website.
</p>
</section>
</main>
<footer>
<p>
This example page is part of a tutorial for integrating TradingView
widgets into your website.
</p>
<p><a href="https://www.tradingview.com/widget-docs/tutorials/build-page/#build-a-page">View the tutorial</a></p>
</footer>
</body>
<template id="ticker-tape-widget-template">
<!-- TradingView Widget BEGIN -->
<div class="tradingview-widget-container">
<div class="tradingview-widget-container__widget"></div>
<script
type="text/javascript"
src="https://s3.tradingview.com/external-embedding/embed-widget-ticker-tape.js"
async
>
{
"symbols": [
{
"description": "",
"proName": "NASDAQ:TSLA"
},
{
"description": "",
"proName": "NASDAQ:AAPL"
},
{
"description": "",
"proName": "NASDAQ:NVDA"
},
{
"description": "",
"proName": "NASDAQ:MSFT"
},
{
"description": "",
"proName": "NASDAQ:AMZN"
},
{
"description": "",
"proName": "NASDAQ:GOOGL"
},
{
"description": "",
"proName": "NASDAQ:META"
},
{
"description": "",
"proName": "NYSE:BRK.B"
},
{
"description": "",
"proName": "NYSE:LLY"
},
{
"description": "",
"proName": "NYSE:UNH"
},
{
"description": "",
"proName": "NYSE:V"
},
{
"description": "",
"proName": "NYSE:WMT"
}
],
"showSymbolLogo": true,
"colorTheme": "light",
"isTransparent": false,
"displayMode": "adaptive",
"locale": "en",
"largeChartUrl": "#"
}
</script>
</div>
<!-- TradingView Widget END -->
</template>
<template id="symbol-info-template">
<!-- TradingView Widget BEGIN -->
<div class="tradingview-widget-container">
<div class="tradingview-widget-container__widget"></div>
<script
type="text/javascript"
src="https://s3.tradingview.com/external-embedding/embed-widget-symbol-info.js"
async
>
{
"symbol": "NASDAQ:AAPL",
"width": "100%",
"locale": "en",
"colorTheme": "light",
"isTransparent": true
}
</script>
</div>
<!-- TradingView Widget END -->
</template>
<script
type="text/javascript"
src="https://s3.tradingview.com/tv.js"
></script>
<template id="advanced-chart-template">
<!-- TradingView Widget BEGIN -->
<div
class="tradingview-widget-container"
style="height: 100%; width: 100%"
>
<div
id="tradingview_ae7da"
style="height: calc(100% - 32px); width: 100%"
></div>
<script type="text/javascript">
new TradingView.widget({
autosize: true,
symbol: 'NASDAQ:AAPL',
interval: 'D',
timezone: 'Etc/UTC',
theme: 'light',
style: '1',
locale: 'en',
hide_side_toolbar: false,
allow_symbol_change: true,
studies: ['STD;MACD'],
container_id: 'tradingview_ae7da',
});
</script>
</div>
<!-- TradingView Widget END -->
</template>
<template id="company-profile-template">
<!-- TradingView Widget BEGIN -->
<div class="tradingview-widget-container">
<div class="tradingview-widget-container__widget"></div>
<script
type="text/javascript"
src="https://s3.tradingview.com/external-embedding/embed-widget-symbol-profile.js"
async
>
{
"width": "100%",
"height": "100%",
"colorTheme": "light",
"isTransparent": true,
"symbol": "NASDAQ:AAPL",
"locale": "en"
}
</script>
</div>
<!-- TradingView Widget END -->
</template>
<template id="fundamental-data-template">
<!-- TradingView Widget BEGIN -->
<div class="tradingview-widget-container">
<div class="tradingview-widget-container__widget"></div>
<script
type="text/javascript"
src="https://s3.tradingview.com/external-embedding/embed-widget-financials.js"
async
>
{
"colorTheme": "light",
"isTransparent": true,
"largeChartUrl": "",
"displayMode": "adaptive",
"width": "100%",
"height": "100%",
"symbol": "NASDAQ:AAPL",
"locale": "en"
}
</script>
</div>
<!-- TradingView Widget END -->
</template>
<template id="technical-analysis-template">
<!-- TradingView Widget BEGIN -->
<div class="tradingview-widget-container">
<div class="tradingview-widget-container__widget"></div>
<script
type="text/javascript"
src="https://s3.tradingview.com/external-embedding/embed-widget-technical-analysis.js"
async
>
{
"interval": "15m",
"width": "100%",
"isTransparent": true,
"height": "100%",
"symbol": "NASDAQ:AAPL",
"showIntervalTabs": true,
"displayMode": "single",
"locale": "en",
"colorTheme": "light"
}
</script>
</div>
<!-- TradingView Widget END -->
</template>
<template id="top-stories-template">
<!-- TradingView Widget BEGIN -->
<div class="tradingview-widget-container">
<div class="tradingview-widget-container__widget"></div>
<script
type="text/javascript"
src="https://s3.tradingview.com/external-embedding/embed-widget-timeline.js"
async
>
{
"feedMode": "symbol",
"symbol": "NASDAQ:AAPL",
"colorTheme": "light",
"isTransparent": true,
"displayMode": "regular",
"width": "100%",
"height": "100%",
"locale": "en"
}
</script>
</div>
<!-- TradingView Widget END -->
</template>
<script>
function getQueryParam(param) {
let urlSearchParams = new URLSearchParams(window.location.search);
return urlSearchParams.get(param);
}
function readSymbolFromQueryString() {
return getQueryParam('tvwidgetsymbol');
}
function cloneTemplateInto(templateId, targetId, rewrites) {
const tmpl = document.querySelector(`#${templateId}`);
if (!tmpl) return;
const target = document.querySelector(`#${targetId}`);
if (!target) return;
target.innerText = '';
const clone = tmpl.content.cloneNode(true);
if (rewrites) {
const script = clone.querySelector('script');
script.textContent = rewrites(script.textContent);
}
target.appendChild(clone);
}
const symbol = readSymbolFromQueryString() || 'NASDAQ:AAPL';
function setSymbol(scriptContent) {
return scriptContent.replace(/"symbol": "([^"]*)"/g, () => {
return `"symbol": "${symbol}"`;
});
}
cloneTemplateInto('symbol-info-template', 'symbol-info', setSymbol);
cloneTemplateInto('advanced-chart-template', 'advanced-chart');
cloneTemplateInto('company-profile-template', 'company-profile', setSymbol);
cloneTemplateInto('fundamental-data-template', 'fundamental-data', setSymbol);
cloneTemplateInto('technical-analysis-template', 'technical-analysis', setSymbol);
cloneTemplateInto('top-stories-template', 'top-stories', setSymbol);
if (symbol) {
document.title = `Stock Details - ${symbol}`;
}
</script>
</html>