# Classic ASP / VBScript Coding Rules _Live from BN_CodingRules. Follow these when writing new .asp files._ ## VBScript Syntax ### 1.1 — Identifiers cannot start with an underscore _[critical]_ **Symptom:** Compilation error 800a0408 "Invalid character" at the line where the identifier appears. **Why:** VBScript rejects any identifier whose first character is an underscore. This applies to variables, functions, parameters, and constants. **DON'T:** ```vbs Dim _ansRaw Function _fmtPrice(p) : ... : End Function ``` **DO:** ```vbs Dim ansRaw Function fmtTicketPrice(p) : ... : End Function ``` _Hit in:_ Hit in _event_placeholders.asp (renamed _fmtPrice -> fmtTicketPrice and _ordinalSuffix -> ordinalSuffix) and earlier in workshop.asp (_ansRaw). ### 1.2 — Single-line If/Then cannot be chained with ElseIf _[critical]_ **Symptom:** Compilation error 800a03f6 "Expected End" at the ElseIf line. **Why:** A single-line "If x Then y" closes the If at end of line. The ElseIf that follows is orphaned. To use ElseIf, every branch must be on its own line with Then at end-of-line, terminated by End If. **DON'T:** ```vbs If h = 0 Then h = 12 : ap = "AM" ElseIf h = 12 Then ap = "PM" Else ap = "AM" End If ``` **DO:** ```vbs If h = 0 Then h = 12 ap = "AM" ElseIf h = 12 Then ap = "PM" Else ap = "AM" End If ``` _Hit in:_ Hit in AIWorkshopOffer.asp FmtTime helper. Note: single-line "If x Then y Else z" WITHOUT ElseIf IS legal. ### 1.3 — Functions return by assigning to the function name _[critical]_ **Symptom:** Either a runtime error or the function returns Empty. **Why:** VBScript has no Return keyword. A function returns by assigning its result to a variable with the same name as the function. **DON'T:** ```vbs Function Double(n) Return n * 2 End Function ``` **DO:** ```vbs Function Double(n) Double = n * 2 End Function ``` _Hit in:_ Common gotcha when coming from JS or other languages. Every helper in our codebase follows the assign-to-name pattern. ### 1.4 — VBScript has no native IIf — define your own _[critical]_ **Symptom:** Runtime error 800a000d "Type mismatch: IIf". **Why:** Unlike VB/VBA, VBScript does not ship with an IIf function. Calling IIf without a local definition throws Type mismatch at runtime. **DON'T:** ```vbs x = IIf(cond, valTrue, valFalse) 'without a local IIf definition ``` **DO:** ```vbs Function IIf(cond, valT, valF) If cond Then IIf = valT Else IIf = valF End Function ``` _Hit in:_ Hit in Board.asp. Defined a local IIf helper at the top of the file to fix it. ### 1.5 — Never Dim the same variable twice in one Sub/Function _[critical]_ **Symptom:** Compilation error 800a0411 "Name redefined" at the second Dim line. The whole file fails to compile so the page 500s. **Why:** VBScript treats a duplicate Dim inside the same procedure scope as a hard compile error. Most other languages let you re-declare; VBScript does not. Easy to hit when you copy-paste a block from elsewhere in the file and the variable was already Dim'd earlier in the same Sub. **DON'T:** ```vbs Sub sendThemedConfirmation() Dim host : host = Request.ServerVariables("HTTP_HOST") ' ... 30 lines later ... Dim host ' second Dim — compile error End Sub ``` **DO:** ```vbs Sub sendThemedConfirmation() Dim host : host = Request.ServerVariables("HTTP_HOST") ' ... 30 lines later ... host = Request.ServerVariables("HTTP_HOST") ' just assign, do not re-Dim End Sub ``` _Hit in:_ Hit in request_submit.asp on 2026-05 — Dim host appeared at lines 542 and 574 inside the same Sub. Removed the second Dim and the file compiled. ### 1.6 — Use CDbl/CCur for large numbers — CLng overflows at ~2.1 billion _[critical]_ **Symptom:** Runtime error 800a0006 "Overflow" on CLng() when the input number is above 2,147,483,647 (or below -2,147,483,648). **Why:** CLng casts to a 32-bit signed integer. Phone numbers, Stripe amounts in cents, Twilio IDs, large timestamps and many other "numeric-looking" strings exceed Long range and overflow. CDbl handles any size; CCur is correct for currency math. **DON'T:** ```vbs amount = CLng(Request.Form("amount_cents")) ' overflows on anything >= $21.47M phoneInt = CLng(rawPhone) ' overflows on most full-format phone numbers ``` **DO:** ```vbs If IsNumeric(rawAmount) Then amount = CDbl(rawAmount) ' arbitrary magnitude, OK for IDs ' or, for money math: amount = CCur(rawAmount) ' fixed 4-decimal currency type End If ``` _Hit in:_ Hit in CPG property edit pricing on 2026-05. Default to CDbl unless you specifically need 32-bit truncation. ### 1.7 — Don't colon-pack If/Else across a function body _[critical]_ **Symptom:** Compilation error 800a03f4 'Expected If' or 800a0401 'Expected end of statement' when the page loads — sometimes only on IIS, not when you eyeball the file. **Why:** VBScript's colon separator only works for the SINGLE-LINE If syntax (If x Then a = 1 Else a = 2). The moment you try to wrap a multi-keyword block (Function, Sub, Do, For) with colons, the parser can't tell where the block ends. Pasting from one-line examples is the common trigger. **DON'T:** ```vbs Function HE(s) : If IsNull(s) Then HE = "" Else HE = Server.HTMLEncode(s & "") : End Function ``` **DO:** ```vbs Function HE(s) If IsNull(s) Then HE = "" Else HE = Server.HTMLEncode(s & "") End If End Function ``` _Hit in:_ ManageCodingRules.asp top-of-file helpers. Multiple ASP files across BN + freestylerslegacy. ## Null Handling ### 2.1 — Null & "" = Null (not "") _[critical]_ **Symptom:** Downstream string operations like Len, Left, Mid, InStr throw Type mismatch. **Why:** VBScript preserves Null through string concatenation. Concatenating Null with "" does NOT coerce to "". You must explicitly check with IsNull. **DON'T:** ```vbs Dim x : x = dict(key) If Len(x) > 0 Then ... 'throws Type mismatch if x is Null ``` **DO:** ```vbs Dim x : x = dict(key) & "" If IsNull(x) Then x = "" ``` _Hit in:_ Hit multiple times in workshop.asp (pills_multi block) and in _event_placeholders.asp. ### 2.2 — Always coerce recordset field reads with & "" _[critical]_ **Symptom:** Any NULL column poisons downstream concat/string ops. **Why:** Reading rs("FieldName") returns a Variant. NULL columns come back as the Null value, not an empty string. Append & "" to coerce to String. **DON'T:** ```vbs evName = rs("EventName") 'fragile — breaks if EventName is NULL ``` **DO:** ```vbs evName = rs("EventName") & "" ``` _Hit in:_ Standard pattern across all our .asp files. Every single rs(...) read in GetEventDetail.asp, SaveEvent.asp, etc. uses & "". ### 2.3 — Mid/Left/InStr on non-string Variants throws Type mismatch _[critical]_ **Symptom:** Runtime error 800a000d "Type mismatch" on the Mid/Left/InStr call. **Why:** Dictionary returns from NTEXT columns or certain Variant types pass IsNull/IsEmpty but still blow up on string ops. Coerce defensively with & "" AND wrap in On Error Resume Next. **DON'T:** ```vbs pipeP = InStr(1, existing, "|", 1) 'throws if existing is a non-string Variant ``` **DO:** ```vbs On Error Resume Next Dim raw : raw = existing & "" If IsNull(raw) Then raw = "" If Len(raw) > 0 Then pipeP = InStr(1, raw, "|", 1) If Err.Number <> 0 Then Err.Clear : raw = "" On Error GoTo 0 ``` _Hit in:_ Hit multiple times in workshop.asp pills_multi block (line 556). The dictionary returned NTEXT content as an odd Variant. ### 2.4 — CDate can throw Type mismatch even when IsDate is True _[critical]_ **Symptom:** Runtime error 800a000d "Type mismatch" on WeekdayName/Weekday/MonthName after a CDate. **Why:** Some DB date formats pass IsDate() but fail CDate() downstream (localization quirks, odd formats). Wrap casts in On Error Resume Next. **DON'T:** ```vbs dateLong = WeekdayName(Weekday(CDate(evDate)), False) & " " & ... 'no guard ``` **DO:** ```vbs Dim dCast On Error Resume Next If IsDate(evDate) Then dCast = CDate(evDate) If Err.Number = 0 Then dateLong = WeekdayName(Weekday(dCast), False) & ", " & MonthName(Month(dCast), False) & " " & Day(dCast) & ", " & Year(dCast) End If End If If Err.Number <> 0 Then Err.Clear On Error GoTo 0 ``` _Hit in:_ Hit in AIWorkshopOffer.asp line 105. IsDate(evDate) returned True but the chained CDate/Weekday call threw Type mismatch anyway. ### 2.5 — Read migration-added columns defensively _[warning]_ **Symptom:** Runtime error "Item cannot be found in the collection corresponding to the requested name" — often crashes the page in dev/staging where the migration hasn't run yet. **Why:** When a new column is added via migration, other environments may not have it yet. Always wrap reads in On Error Resume Next until the migration is universally deployed. **DON'T:** ```vbs evSub = rs("Subtitle") & "" 'crashes if the Subtitle column migration hasn't run yet ``` **DO:** ```vbs Dim evSub : evSub = "" On Error Resume Next evSub = rs("Subtitle") & "" If Err.Number <> 0 Then evSub = "" : Err.Clear On Error GoTo 0 ``` _Hit in:_ Pattern used in GetEventDetail.asp for the new Subtitle column (added 2026-04-29). ### 2.6 — Always wrap recordset values with HE() or HA() before output _[critical]_ **Symptom:** Type mismatch 'CStr' (800a000d) when a column is NULL. Or worse — silent 'any quote in the name kills the page ``` **DO:** ```vbs Function JSReady(s) If IsNull(s) Then JSReady = "" : Exit Function Dim t : t = s & "" t = Replace(t, "\", "\\") t = Replace(t, """", "\""") t = Replace(t, Chr(10), " ") t = Replace(t, Chr(13), " ") JSReady = t End Function ``` _Hit in:_ Used in EventSignup.asp for prefilling name from cookie. ### 7.4 — Inline form values must be HTMLEncoded — including value="" attributes _[warning]_ **Symptom:** Form fields render broken (closing quotes early) or empty when the loaded value contains a double-quote, ampersand, or angle bracket. XSS surface if the value came from user input. **Why:** Server.HTMLEncode handles element text; HA() (attribute-safe escape) is needed inside value="...", placeholder="...", and other attributes. Easy to forget when round-tripping edit forms. **DON'T:** ```vbs "> ' breaks if Title contains " & < or anything HTML-significant ``` **DO:** ```vbs "> ' HA escapes & " < and treats Null as "" ``` _Hit in:_ Pattern used in ManageCodingRules.asp, EditEvent.asp, edituser.asp. HE for element text, HA for attribute values. ### 7.5 — Don't scope base text/background rules to #editor only _[critical]_ **Symptom:** Page looks fine inside the CMS preview/editor, then ships dark/cream/blank on the live site — headlines and body copy go invisible against the section background. **Why:** CMS page builders wrap content in #editor while editing but render it inside .page-wrapper (or no wrapper) when live. Any rule scoped only to #editor stops matching the moment the page leaves the editor, so the cascade never delivers your base color or background. **DON'T:** ```vbs #editor { color: #fff; background: var(--navy-deep); font-family: 'DM Sans',sans-serif; } ``` **DO:** ```vbs #editor, .page-wrapper { color: #fff !important; background: var(--navy-deep) !important; font-family: 'DM Sans', sans-serif; } ``` _Hit in:_ rankingmastery/clients-html/frank.html. Same pattern hits every CMS-embedded page. ### 7.6 — Default state of every element must be visible (no JS-dependent reveals) _[critical]_ **Symptom:** Entire sections (reviews, cards, panels, banners) are blank on the live page. Inspector shows the text is there with the right color — it's just opacity:0 and never animates in. **Why:** The page builder strips inline ``` **DO:** ```vbs .fl-sr { opacity: 1; transform: none; /* If you really want a reveal, use pure CSS @keyframes, never a JS class-adder: */ animation: fadeUp .7s ease both; } @keyframes fadeUp { from { opacity:0; transform:translateY(20px); } to { opacity:1; transform:none; } } ``` _Hit in:_ rankingmastery/clients-html/frank.html — the .fl-sr opacity:0 bug. Same root cause as embed-page-prompt.md rule 2. ### 7.7 — Override the CMS .widget-container background, not just the inner form fields _[warning]_ **Symptom:** A form (or any widget) renders as a bright white panel on top of your dark card. The labels and inputs you styled for a dark theme are invisible against the white. **Why:** The CMS injects a parent