*! version 1.5.2 02aug2026 Eric A. Booth *! - also escape the double quote. A value or label containing one, e.g. a *! 6" pipe or a label like the "quoted" column, aborted -build- with r(198) *! and left a truncated .html on disk. It is emitted as the JS escape *! backslash-u-0022, so no quote character survives into the Stata string *! that -file write- has to parse. *! version 1.5.1 02aug2026 Eric A. Booth *! - escape ' and \ in string values, variable labels and value labels before *! they reach the generated JavaScript. A value like O'Brien, or a label like *! "Client's score", used to close the JS string early: the whole data array *! then failed to parse and the table rendered empty. *! - build reports how many controls and panels it wrote, so a dashboard built *! without -replace- no longer silently inherits the previous one's panels. *! - build explains itself when a table asks for variables that are not in *! memory, instead of stopping on a bare "variable ... not found". *! version 1.5.0 26may2026 Eric A. Booth *! - output(table) now emits value-label text for labeled numeric variables *! (e.g. "Foreign" instead of 1); falls back to the number when unlabeled. program define statashiny version 16 * 1. Backward Compatibility Check local first : word 1 of `0' if inlist("`first'", "init", "input_num", "input_check", "output_val", "output_table", "output_raw", "data_from_memory", "calc", "build") { gettoken subcmd 0 : 0, parse(" ,") _statashiny_`subcmd' `0' exit } * 2. Raw HTML / Calc Check (Bypass syntax for complex strings) local check : copy local 0 if ustrpos(`"`macval(check)'"', "output(raw)") > 0 { _statashiny_handle_raw `0' exit } if ustrpos(`"`macval(check)'"', "calc(") > 0 { _statashiny_handle_calc `0' exit } * 3. Consolidated Syntax syntax [anything(name=id)] [, /// Input(string) Output(string) Calc(string) Build /// Title(string) Subtitle(string) Replace Open /// Label(string) Val(string) Min(real -1000) Max(real 1000) Step(real 1) /// Vars(varlist) Checked Toggles /// Prefix(string) Suffix(string) /// EXPort(string) ] * Initialization if "`title'" != "" | "`replace'" != "" { _statashiny_init, title(`"`title'"') subtitle(`"`subtitle'"') `replace' } * Inputs if "`input'" != "" { if inlist("`input'", "num", "number") { _statashiny_input_num, var("`id'") label("`label'") val("`val'") min(`min') max(`max') step(`step') } else if inlist("`input'", "check", "checkbox") { _statashiny_input_check, var("`id'") label("`label'") `checked' } } * Outputs if "`output'" != "" { if inlist("`output'", "val", "value") { _statashiny_output_val, id("`id'") label("`label'") prefix("`prefix'") suffix("`suffix'") } else if "`output'" == "table" { _statashiny_output_table, id("`id'") label("`label'") _statashiny_data_from_memory, id("`id'") vars(`vars') if "`toggles'" != "" { local i = 0 foreach v of varlist `vars' { local lab : var label `v' if "`lab'" == "" local lab "`v'" _statashiny_input_check, var("toggle_`id'_`i'") label("Show `lab'") checked _statashiny_calc "if (window.table_`id') table_`id'.column(`i').visible(document.getElementById('toggle_`id'_`i'').checked);" local ++i } } } } * Build if "`build'" != "" { _statashiny_build, `open' export(`"`export'"') } * Help fallback if "`id'`input'`output'`calc'`build'`title'" == "" { help statashiny } end program define _statashiny_handle_raw local js : copy local 0 local start = ustrpos(`"`macval(js)'"', "label(") + 6 local rest = usubstr(`"`macval(js)'"', `start', .) local end = ustrrpos(`"`macval(rest)'"', ")") local html = usubstr(`"`macval(rest)'"', 1, `end'-1) local html = strtrim(`"`macval(html)'"') if usubstr(`"`macval(html)'"', 1, 2) == `"`""' & usubstr(`"`macval(html)'"', -2, 2) == `""'"' { local html = usubstr(`"`macval(html)'"', 3, ustrlen(`"`macval(html)'"')-4) } else if usubstr(`"`macval(html)'"', 1, 1) == `"""' & usubstr(`"`macval(html)'"', -1, 1) == `"""' { local html = usubstr(`"`macval(html)'"', 2, ustrlen(`"`macval(html)'"')-2) } _statashiny_output_raw `macval(html)' end program define _statashiny_handle_calc local js : copy local 0 local start = ustrpos(`"`macval(js)'"', "calc(") + 5 local rest = usubstr(`"`macval(js)'"', `start', .) local end = ustrrpos(`"`macval(rest)'"', ")") local code = usubstr(`"`macval(rest)'"', 1, `end'-1) local code = strtrim(`"`macval(code)'"') if usubstr(`"`macval(code)'"', 1, 2) == `"`""' & usubstr(`"`macval(code)'"', -2, 2) == `""'"' { local code = usubstr(`"`macval(code)'"', 3, ustrlen(`"`macval(code)'"')-4) } else if usubstr(`"`macval(code)'"', 1, 1) == `"""' & usubstr(`"`macval(code)'"', -1, 1) == `"""' { local code = usubstr(`"`macval(code)'"', 2, ustrlen(`"`macval(code)'"')-2) } _statashiny_calc `macval(code)' end program define _statashiny_init syntax [, Title(string) Subtitle(string) Replace] if "`title'" == "" local title "StataShiny Dashboard" global S_title `"`title'"' global S_subtitle `"`subtitle'"' global S_inputs "" global S_outputs "" global S_calc_js "" global S_table_list "" di as txt "StataShiny initialized: `title'" end program define _statashiny_input_num syntax, Var(string) Label(string) [Val(string) Min(real -1000) Max(real 1000) Step(real 1)] if "`val'" == "" local val 0 local html `"
"' local html `"`html'"' local html `"`html'"' local html `"`html'
"' global S_inputs `"$S_inputs `html'"' end program define _statashiny_input_check syntax, Var(string) Label(string) [Checked] local chk = cond("`checked'"!="", "checked", "") local html `"
"' local html `"`html'"' local html `"`html'"' local html `"`html'
"' global S_inputs `"$S_inputs `html'"' end program define _statashiny_output_val syntax, Id(string) Label(string) [Prefix(string) Suffix(string)] local html `"
"' local html `"`html'
`label'
"' local html `"`html'
`prefix'---`suffix'
"' local html `"`html'
"' global S_outputs `"$S_outputs `html'"' end program define _statashiny_output_raw local html : copy local 0 global S_outputs `"$S_outputs `macval(html)'"' end program define _statashiny_output_table syntax, Id(string) Label(string) local html `"
"' local html `"`html'
`label'
"' local html `"`html'
"' local html `"`html'
"' global S_outputs `"$S_outputs `html'"' end program define _statashiny_data_from_memory syntax, Id(string) [Vars(varlist)] global S_table_list "$S_table_list `id':`vars'|" end program define _statashiny_calc local js : copy local 0 local js = strtrim(`"`macval(js)'"') if usubstr(`"`macval(js)'"', 1, 2) == `"`""' & usubstr(`"`macval(js)'"', -2, 2) == `""'"' { local js = usubstr(`"`macval(js)'"', 3, ustrlen(`"`macval(js)'"')-4) } else if usubstr(`"`macval(js)'"', 1, 1) == `"""' & usubstr(`"`macval(js)'"', -1, 1) == `"""' { local js = usubstr(`"`macval(js)'"', 2, ustrlen(`"`macval(js)'"')-2) } global S_calc_js `"$S_calc_js `macval(js)' "' end program define _statashiny_build syntax [, Open EXPort(string)] * Resolve output path: * - No export() → "statashiny_dashboard.html" in current working directory * - export("name.html") → CWD/name.html (relative) * - export("/abs/path/file.html") → absolute path used verbatim * - export("rel/dir/file.html") → CWD/rel/dir/file.html if `"`export'"' == "" { local path `"`c(pwd)'/statashiny_dashboard.html"' } else { local path `"`export'"' * Treat as relative if it doesn't start with "/" (Unix) or a drive letter (Windows). if substr(`"`path'"', 1, 1) != "/" & substr(`"`path'"', 2, 1) != ":" { local path `"`c(pwd)'/`path'"' } } tempname fh cap file open `fh' using `"`path'"', write replace if _rc { di as error `"statashiny build: cannot write to "`path'""' di as error `" (check that the directory exists and is writable)"' exit 603 } local title : copy global S_title local subtitle : copy global S_subtitle local inputs : copy global S_inputs local outputs : copy global S_outputs local scripts : copy global S_calc_js * A dashboard is assembled in global macros, and those are only cleared when * -replace- (or title()) runs an init. Building a second dashboard without * -replace- therefore silently carries the first one's panels along with it. * Rather than add an option, count what is about to be written and say so: * "3 panels" when the user expected 1 is the clue that -replace- was missed. * NB: -: subinstr local- returns the SUBSTITUTED STRING; the tally goes to * the macro named in count(). They must be different macros. local n_ctrl = 0 local n_panel = 0 if `"`macval(inputs)'"' != "" { local _junk : subinstr local inputs "updateDashboard()" "", all count(local n_ctrl) } if `"`macval(outputs)'"' != "" { local _junk : subinstr local outputs "statashiny-component" "", all count(local n_panel) } if `"`macval(title)'"' == "" { local title "StataShiny Dashboard" di as txt "statashiny build: no title() was set; using the default." } file write `fh' "`macval(title)'" _n file write `fh' "" _n file write `fh' "" _n file write `fh' "" _n file write `fh' "" _n file write `fh' "" _n file write `fh' "" _n file write `fh' "" _n file write `fh' "

`macval(title)'

" _n if `"`macval(subtitle)'"' != "" { file write `fh' `"
`macval(subtitle)'
"' _n } file write `fh' "
" _n if trim(`"`macval(inputs)'"') != "" { file write `fh' `"
"' _n file write `fh' `"
`macval(outputs)'
"' _n } else { file write `fh' `"
`macval(outputs)'
"' _n } file write `fh' "" _n file close `fh' di as txt "StataShiny built: `n_ctrl' control(s), `n_panel' panel(s)." /// _n as txt " (start a new dashboard with -statashiny, title(...) replace-;" /// _n as txt " without it, panels accumulate from the previous one.)" /// _n as smcl `"File: {browse `"`path'"'}"' if "`open'" != "" { cap file close _ss_fh tempfile _ss_sh if ("`c(os)'" == "MacOSX") | regexm("`c(machine_type)'", "Mac") { // Apple-Silicon Stata reports c(os)=="Unix" file open _ss_fh using "`_ss_sh'", write text replace file write _ss_fh "/usr/bin/open " _char(34) `"`path'"' _char(34) _n file close _ss_fh shell sh "`_ss_sh'" } else if "`c(os)'" == "Windows" { file open _ss_fh using "`_ss_sh'.cmd", write text replace file write _ss_fh "start " _char(34) _char(34) " " _char(34) `"`path'"' _char(34) _n file close _ss_fh shell "`_ss_sh'.cmd" } else { file open _ss_fh using "`_ss_sh'", write text replace file write _ss_fh "xdg-open " _char(34) `"`path'"' _char(34) _n file close _ss_fh shell sh "`_ss_sh'" } } end