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 = / ^ (?: (?: h t t p s ? | f t p ) : \/ \/ ) ? (?: (? ! (?: 1 0 | 1 2 7 ) (?: \. \d { 1 , 3 } ) { 3 } ) (? ! (?: 1 6 9 \. 2 5 4 | 1 9 2 \. 1 6 8 ) (?: \. \d { 1 , 3 } ) { 2 } ) (? ! 1 7 2 \. (?: 1 [ 6 - 9 ] | 2 \d | 3 [ 0 - 1 ] ) (?: \. \d { 1 , 3 } ) { 2 } ) (?: [ 1 - 9 ] \d ? | 1 \d \d | 2 [ 0 1 ] \d | 2 2 [ 0 - 3 ] ) (?: \. (?: 1 ? \d { 1 , 2 } | 2 [ 0 - 4 ] \d | 2 5 [ 0 - 5 ] ) ) { 2 } (?: \. (?: [ 1 - 9 ] \d ? | 1 \d \d | 2 [ 0 - 4 ] \d | 2 5 [ 0 - 4 ] ) ) | (?: (?: [ a - z \u00a1 - \uffff 0 - 9 ] - * ) * [ a - z \u00a1 - \uffff 0 - 9 ] + ) (?: \. (?: [ a - z \u00a1 - \uffff 0 - 9 ] - * ) * [ a - z \u00a1 - \uffff 0 - 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