#### Overview The Ballot Format Converter is a client-side web application designed to convert ballot data between two distinct formats: * **Simple Ballot Format (SBF):** A human-friendly, compact format designed for readability and ease of use in plain text environments. * **Aggregated Ballot Information Format (ABIF):** A more formal, standardized format for expressing aggregated ballot sets. This tool provides a two-panel interface for real-time conversion, the ability to load preset election data, and functionality to save custom ballot sets in the browser's local storage. #### Features * **Bidirectional Conversion:** Convert from SBF to ABIF and vice-versa. * **Ballot Aggregation:** Automatically combines and counts identical ballots during conversion, ensuring a canonical output. * **Preset Loader:** Load real-world election data from files stored in the /ballots/ directory via a dropdown menu. * **Local Storage:** Save custom or edited ballot sets with a "Save As..." button. Saved sets are automatically added to the loader dropdown. * **JSON Data Viewer:** After a successful conversion, view the parsed data structure as a clean, formatted JSON object in a resizable dialog box powered by CodeMirror. * **Self-Contained Design:** The application is written in vanilla JavaScript with no external frameworks, relying only on a few purpose-built internal helper libraries. (Note: The JSON viewer feature does pull the CodeMirror library from a CDN). #### Project Structure The project is organized to separate the main application logic, helper libraries, and static assets. Generated code ``` . ├── ballotFormatConverter.html # The main HTML entry point. Contains the app's DOM root and explanation template. ├── readme.html # This file. ├── ballots/ # Directory for preset ballot data files (.txt). │ ├── alaskaspecial2022.txt │ └── ... ├── css/ │ └── ballotFormatConverter.css # Contains minimal base styles. Most styling is applied dynamically. └── js/ ├── BallotFormatConverter.js # The core application class. All logic resides here. └── lib/ # Self-contained, internal helper libraries. ├── DialogBox.js # A component for creating draggable, resizable dialog boxes. └── UIHelpers.js # Contains helper functions for DOM manipulation and dynamic CSS. ``` Core Concepts & Coding Style The application is built with a specific set of conventions to ensure the code remains modular, understandable, and easy to modify. **1\. Single Class Architecture** All application logic is encapsulated within the BallotFormatConverter class in js/BallotFormatConverter.js. There are no global functions, IIFEs, or loose scripts. This makes the code's scope clear and prevents pollution of the global namespace. The application is instantiated and started in a single script block in ballotFormatConverter.html. **2\. Programmatic UI Construction** The ballotFormatConverter.html file is intentionally minimal. The entire user interface is built programmatically within the buildUI() method of the BallotFormatConverter class. This is accomplished using the makeElement() helper function. makeElement(type, ...args) from js/lib/UIHelpers.js is used to create all DOM elements. This keeps the UI structure co-located with the logic that controls it, making components easier to manage. Example: // Creates a button with a class and an onclick handler const myButton = makeElement( 'button', { className: 'my-button', onclick: () => this.handleButtonClick() }, 'Click Me' ); **3\. Dynamic Styling with applyCss** Instead of relying on a large, static CSS file, most of the application's styles are injected dynamically using the applyCss() helper from js/lib/UIHelpers.js. This function creates or updates a style tag in the document head This approach keeps component-specific styles directly within the JavaScript file, making components truly self-contained. Example: // In the init() method, this injects the core styles for the app. injectCss(.app-title { color: var(--title-color); font-size: 3.5em; } .converter-panel { background-color: var(--panel-bg); border: 1px solid var(--border-color); }, 'app-styles'); // The ID 'app-styles' prevents duplicate style blocks. **4\. Event Handling** Event listeners are attached directly to elements, typically during their creation in buildUI(). Arrow functions (() => this.method()) are used exclusively for these handlers to ensure the this context correctly refers to the BallotFormatConverter class instance, not the DOM element that was clicked. **5\. Self-Contained Libraries** The project relies on two internal helper libraries found in js/lib/: * UIHelpers.js: Contains makeElement and applyCss. * DialogBox.js: A reusable, standalone class for creating draggable and resizable dialogs. These are not external frameworks but are part of the project's codebase, reinforcing its self-contained nature. #### Application Flow (A Basic Run-Through) * **Initialization:** * ballotFormatConverter.html is loaded. * A new instance of BallotFormatConverter is created. * The init(document.body) method is called. * init() calls injectCss() to apply styles, buildUI() to create the interface, and loadBallotList() to populate the dropdown menu. * loadBallotList() triggers loadSelectedBallot() which fetches the default election data (alaskaspecial2022.txt). * **Conversion Process:** * A user clicks one of the "Convert" buttons. * The corresponding onclick handler fires (e.g., this.convertToAbif()). * The method reads the .value from the source textarea. * It calls the appropriate parser (parseSbf() or parseAbif()). * The parser processes the text line by line, identifies candidates and ballots, and uses the \_aggregateBallots() helper to consolidate duplicates. * The parser returns a structured data object: { candidates: {...}, ballots: \[...\] }. * The conversion method formats this object into the target string format. * Finally, it sets the .value of the destination textarea to the newly generated string. * **Saving a Ballot Set:** * The user clicks the "Save As..." button, which calls saveAs(). * A prompt() asks for a name. * If a name is provided, the current content of the SBF textarea is saved to localStorage under a prefixed key (e.g., sbf\_custom\_My Election). * loadBallotList() is called again to refresh the dropdown with the newly saved item.