Skip to content

Commit c60d6be

Browse files
author
av
committed
feat: Add theme toggle and refactor UI by extracting inline CSS and JavaScript into separate files.
1 parent 7d5ec02 commit c60d6be

3 files changed

Lines changed: 680 additions & 222 deletions

File tree

index.html

Lines changed: 48 additions & 222 deletions
Original file line numberDiff line numberDiff line change
@@ -1,241 +1,67 @@
11
<!DOCTYPE html>
2-
<html>
2+
<html lang="en">
3+
34
<head>
4-
<script async src="https://www.googletagmanager.com/gtag/js?id=G-CGWJKN5SH2"></script>
5-
<script>window.dataLayer = window.dataLayer || []; function gtag() { dataLayer.push(arguments); } gtag('js', new Date()); gtag('config', 'G-CGWJKN5SH2');</script>
6-
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🧮</text></svg>">
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
77
<title>Dataverse Record Counter</title>
8-
<meta itemprop="name" content="Dataverse Record Counter">
9-
<meta name="description" content="Dataverse Record Counter is a quick browser based alternative for XrmToolBox plugins like Fast Record Counter, FetchXml Record Counter for Developers">
10-
<meta name="keywords" content="Dataverse Row Counter,Dataverse Record Counter,Dynamics Record Counter,Fast Record Counter, XrmToolBox">
11-
<meta itemprop="image" content="https://www.ashishvishwakarma.com/DataverseRowCounter/DataverseRowCounter.webp">
8+
9+
<!-- Meta Tags -->
10+
<meta name="description"
11+
content="Dataverse Record Counter is a quick browser based alternative for XrmToolBox plugins like Fast Record Counter, FetchXml Record Counter for Developers">
12+
<meta name="keywords"
13+
content="Dataverse Row Counter,Dataverse Record Counter,Dynamics Record Counter,Fast Record Counter, XrmToolBox">
14+
<meta name="author" content="Ashish Vishwakarma">
1215
<meta property="og:url" content="https://www.ashishvishwakarma.com/DataverseRowCounter">
1316
<meta property="og:type" content="website">
1417
<meta property="og:title" content="Dataverse Record Counter">
15-
<meta property="og:description" content="Dataverse Record Counter is a quick browser based alternative for XrmToolBox plugins like Fast Record Counter, FetchXml Record Counter for Developers">
18+
<meta property="og:description"
19+
content="Dataverse Record Counter is a quick browser based alternative for XrmToolBox plugins like Fast Record Counter, FetchXml Record Counter for Developers">
1620
<meta property="og:image" content="https://www.ashishvishwakarma.com/DataverseRowCounter/DataverseRowCounter.webp">
17-
<meta name="twitter:card" content="summary_large_image">
18-
<meta name="twitter:title" content="Dataverse Record Counter">
19-
<meta name="twitter:description" content="Dataverse Record Counter is a quick browser based alternative for XrmToolBox plugins like Fast Record Counter, FetchXml Record Counter for Developers">
20-
<meta name="twitter:image" content="https://www.ashishvishwakarma.com/DataverseRowCounter/DataverseRowCounter.webp">
21-
<meta name="author" content="Ashish Vishwakarma">
22-
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic">
23-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.css">
24-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/milligram/1.4.1/milligram.css">
25-
<link rel="stylesheet" href="https://unpkg.com/balloon-css/balloon.min.css">
26-
<style>
27-
.tags-input ul {
28-
list-style: none;
29-
padding: 0;
30-
margin: 0;
31-
}
32-
33-
.tags-input li {
34-
display: inline-block;
35-
}
36-
37-
.tags-input input[type="text"] {
38-
border: none;
39-
outline: none;
40-
padding: 5px;
41-
font-size: 14px;
42-
}
4321

44-
.tags-input input[type="text"]:focus {
45-
outline: none;
46-
}
22+
<!-- Favicon -->
23+
<link rel="icon"
24+
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🧮</text></svg>">
4725

48-
.tags-input .delete-button {
49-
background-color: transparent;
50-
border: none;
51-
color: #999;
52-
cursor: pointer;
53-
padding-left: 5px;
54-
}
55-
56-
.button-large {
57-
font-size: 1.4rem;
58-
height: 4.5rem;
59-
line-height: 4.5rem;
60-
padding: 0 2rem;
61-
}
26+
<!-- Analytics -->
27+
<script async src="https://www.googletagmanager.com/gtag/js?id=G-CGWJKN5SH2"></script>
28+
<script>window.dataLayer = window.dataLayer || []; function gtag() { dataLayer.push(arguments); } gtag('js', new Date()); gtag('config', 'G-CGWJKN5SH2');</script>
6229

63-
.entity-name {
64-
padding-left: 8px;
65-
padding-right: 10px;
66-
margin-right: 10px;
67-
text-transform: none;
68-
}
69-
</style>
30+
<!-- Styles -->
31+
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,700&display=swap">
32+
<link rel="stylesheet" href="style.css">
33+
<link rel="stylesheet" href="https://unpkg.com/balloon-css/balloon.min.css">
7034
</head>
35+
7136
<body>
7237
<div class="container">
73-
<br /> <br />
74-
<h1 class="title" style="text-align:center"><font color="#9b4dca">(</font>Dataverse Row Counter<font color="#9b4dca">)</font></h1>
75-
<br />
76-
<div style="text-align:center">
77-
<input id="orgURL" type="url" placeholder="enter dataverse environment url here eg. https://myenv.crm8.dynamics.com" />
38+
<button id="theme-toggle" class="theme-toggle" title="Toggle Dark/Light Mode"
39+
aria-label="Toggle Theme">🌓</button>
40+
<h1 class="title"><span class="highlight">(</span>Dataverse Row Counter<span class="highlight">)</span></h1>
41+
42+
<div class="input-group">
43+
<input id="orgURL" type="url"
44+
placeholder="Enter Dataverse environment URL (e.g., https://myenv.crm.dynamics.com)"
45+
aria-label="Organization URL" />
46+
<div id="url-validation-msg" class="validation-msg"></div>
7847
</div>
48+
7949
<div class="tags-input">
80-
<ul id="tags"></ul>
81-
<input type="text" id="input-tag" placeholder="enter table schema names here & enter to add more tables" />
50+
<ul id="tags" class="tags-list">
51+
<!-- Tags will be injected here by JS -->
52+
</ul>
53+
<input type="text" id="input-tag" placeholder="Type table name & press Enter..." aria-label="Add Entity"
54+
style="margin-top: 10px;" />
8255
</div>
83-
<hr />
84-
<p style="text-align:center">Built with ❤️ by <a href="https://www.linkedin.com/in/ashishvishwakarma-ashv/">AshV</a></p>
85-
</div>
86-
<script>
87-
var orgURL = document.querySelector("#orgURL");
88-
var entities = new Set();
89-
[
90-
"account",
91-
"contact",
92-
"email",
93-
"annotation",
94-
"team",
95-
"phonecall",
96-
"template",
97-
"workflow",
98-
"savedquery",
99-
"systemuser",
100-
"annotation",
101-
"asyncoperation",
102-
"opportunity",
103-
"lead",
104-
"incident",
105-
].forEach(e => { entities.add(e) });
106-
107-
const tags = document.getElementById('tags');
108-
const input = document.getElementById('input-tag');
109-
110-
if (localStorage.getItem("orgURL") !== null)
111-
orgURL.value = localStorage.getItem("orgURL");
112-
113-
if (localStorage.getItem("counterEntities") === null) {
114-
localStorage.setItem("counterEntities", JSON.stringify(Array.from(entities)));
115-
}
116-
117-
let entityList = JSON.parse(localStorage.getItem("counterEntities"));
118-
entityList.forEach(e => (restoreEntity(e)));
119-
12056

121-
input.addEventListener('keydown', function (event) {
122-
if (event.key === 'Enter') {
123-
event.preventDefault();
124-
const tagContent = input.value.trim();
125-
input.value = '';
126-
if (tagContent !== '') addEntity(tagContent);
127-
}
128-
});
129-
130-
tags.addEventListener('click', function (event) {
131-
if (event.target.classList.contains('entity-name')) {
132-
runCount(event.target.dataset.name);
133-
}
134-
if (event.target.classList.contains('snapshot-count')) {
135-
runSnapshotCount(event.target.parentNode.dataset.name);
136-
}
137-
if (event.target.classList.contains('delete-button')) {
138-
deleteEntity(event.target.parentNode.dataset.name);
139-
event.target.parentNode.remove();
140-
}
141-
});
142-
143-
orgURL.onchange = function () {
144-
if (orgURL.value.length === 0)
145-
return;
146-
if (isValidURL(orgURL.value)) {
147-
var url = new URL(orgURL.value);
148-
orgURL.value = url.origin;
149-
localStorage.setItem("orgURL", orgURL.value);
150-
} else {
151-
alert("Please enter a valid orgnization URL.");
152-
}
153-
};
154-
155-
function prepareEntityButton(entityName) {
156-
return `<button class="button-large button-outline entity-name" data-name="${entityName}"><span style="padding-right: 5px" class="delete-button">✖</span>${entityName}<span style="padding-left: 2px" class="snapshot-count" aria-label="In case of 50K+ rows click here to retrieve count from last snapshot." data-balloon-pos="down">📷</span></button>`;
157-
}
158-
159-
function addEntity(entityName) {
160-
if (!entities.has(entityName)) {
161-
const tag = document.createElement('li');
162-
tag.innerHTML += prepareEntityButton(entityName);
163-
tags.appendChild(tag);
164-
entities.add(entityName);
165-
localStorage.setItem("counterEntities", JSON.stringify(Array.from(entities)));
166-
}
167-
}
168-
169-
function restoreEntity(entityName) {
170-
const tag = document.createElement('li');
171-
tag.innerHTML += prepareEntityButton(entityName);
172-
tags.appendChild(tag);
173-
}
174-
175-
function deleteEntity(entityName) {
176-
entities.delete(entityName);
177-
localStorage.setItem("counterEntities", JSON.stringify(Array.from(entities)));
178-
}
179-
180-
function isValidURL(str) {
181-
regexp = /^(?:(?:https?|ftp):\/\/)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?$/;
182-
return regexp.test(str);
183-
}
184-
185-
function getPlural(entityName) {
186-
let pluralName = entityName;
187-
188-
if (entityName.endsWith("s") || entityName.endsWith("x")) {
189-
pluralName = entityName + "es";
190-
} else if (entityName.endsWith("y")) {
191-
entityName = entityName.slice(0, -1);
192-
pluralName = entityName + "ies";
193-
} else {
194-
pluralName = entityName + "s";
195-
}
196-
197-
return pluralName;
198-
}
199-
200-
function getSubdomain(url) {
201-
let domain = url;
202-
if (url.includes("://")) {
203-
domain = url.split('://')[1];
204-
}
205-
let subdomain = domain.split('.')[0];
206-
return subdomain.replaceAll('-', '_');
207-
}
208-
209-
function getCountColumn(entityName) {
210-
if (["email", "letter", "fax", "phonecall", "appointment"].includes(entityName))
211-
return "createdon";
212-
213-
return entityName + "id";
214-
}
215-
216-
function runCount(entityName) {
217-
if (!isValidURL(orgURL.value) || orgURL.value === null)
218-
alert("Please enter a valid organization URL then retry.");
219-
else {
220-
let pluralName = getPlural(entityName);
221-
let countColumn = getCountColumn(entityName);
222-
let orgName = getSubdomain(orgURL.value);
223-
224-
let query = `/api/data/v9.2/${pluralName}?fetchXml=<fetch mapping="logical" distinct="false" aggregate="true"><entity name="${entityName}"><attribute name="${countColumn}" alias="${pluralName}_count_in_${orgName}_instance" aggregate="count"/></entity></fetch>`;
225-
226-
window.open(orgURL.value + query, '_blank');
227-
}
228-
}
229-
230-
function runSnapshotCount(entityName) {
231-
if (!isValidURL(orgURL.value) || orgURL.value === null)
232-
alert("Please enter a valid organization URL then retry.");
233-
else {
234-
let query = `/api/data/v9.2//RetrieveTotalRecordCount(EntityNames=['${entityName}'])`;
57+
<div class="footer">
58+
<hr />
59+
<p>Built with ❤️ by <a href="https://www.linkedin.com/in/ashishvishwakarma-ashv/" target="_blank">AshV</a>
60+
</p>
61+
</div>
62+
</div>
23563

236-
window.open(orgURL.value + query, '_blank');
237-
}
238-
}
239-
</script>
64+
<script src="script.js"></script>
24065
</body>
241-
</html>
66+
67+
</html>

0 commit comments

Comments
 (0)