By using this site, you agree to the Privacy Policy and Terms of Use.
Accept
World of SoftwareWorld of SoftwareWorld of Software
  • News
  • Software
  • Mobile
  • Computing
  • Gaming
  • Videos
  • More
    • Gadget
    • Web Stories
    • Trending
    • Press Release
Search
  • Privacy
  • Terms
  • Advertise
  • Contact
Copyright © All Rights Reserved. World of Software.
Reading: How to Build an AI Generated Calculator Without Custom JavaScript | HackerNoon
Share
Sign In
Notification Show More
Font ResizerAa
World of SoftwareWorld of Software
Font ResizerAa
  • Software
  • Mobile
  • Computing
  • Gadget
  • Gaming
  • Videos
Search
  • News
  • Software
  • Mobile
  • Computing
  • Gaming
  • Videos
  • More
    • Gadget
    • Web Stories
    • Trending
    • Press Release
Have an existing account? Sign In
Follow US
  • Privacy
  • Terms
  • Advertise
  • Contact
Copyright © All Rights Reserved. World of Software.
World of Software > Computing > How to Build an AI Generated Calculator Without Custom JavaScript | HackerNoon
Computing

How to Build an AI Generated Calculator Without Custom JavaScript | HackerNoon

News Room
Last updated: 2026/01/24 at 6:56 PM
News Room Published 24 January 2026
Share
How to Build an AI Generated Calculator Without Custom JavaScript | HackerNoon
SHARE

How a JSON or JSON-like language enables the next generation of safe human and AI-generated UIs

Introduction

In The Future of AI-Generated UI, I pointed out that the raw JavaScript generated by AI-generated code is a security problem, and the flexibility of JavaScript without a framework can result in hard-to-manage code. I argued that we need declarative, sandboxed formats like A2UI or a Computational DOM (cDOM) with JSON Pointer Regular Expressions (JPRX if), if we want to trust an LLM to build our interfaces.

It also helps if the approach is based on industry standards for which there is lots of documentation and examples that have probably been consumed by LLMs during training. cCOM and JPRX do this; they incorporate concepts and syntax from JSON Pointers, JSON Schema, and XPath.

In my previous article, to show how a cDOM and JPRX work, I used a reactive counter, but let’s be real: reactive counters and to-do lists are easy. Any framework looks elegant when the logic fits on a napkin. To prove a JSON-based approach actually holds up, you need a problem with messy state, edge cases, and distinct modes of operation. You need a calculator.

Calculators are inherently tricky:

  • Input Modes: Are we typing a fresh number or appending to an existing one?
  • Chaining: What happens when you hit `+` then `-` then `*` without hitting equals?-
  • DRY Logic: How do we minimize code differences between 10 handlers for buttons 0-9? n

So, I asked Claude Opus to build a fully functional, iOS-style calculator using zero custom JavaScript functions – just declarative cDOM and JPRX expressions. The fact that AI could produce a declarative calculator with little prompting purely from documentation demonstrates another point I made in my earlier article: cDOM and JPRX aren’t just new syntax. They can be a protocol for human-machine collaboration. n

n The Code

To reduce characters and quotation noise while allowing inline explanation, I am using cDOMC, a compressed version of a cDOM. A regular cDOM does not support comments and requires quotes around attributes and JPRX expressions. When represented with quotes and without comments, cDOM can be treated as regular JSON.

{
  div: {
    class: "calculator",
    // A calculator feels stateless, but it's actually a strict state machine. 
    // You're never just "typing a number"; you're either entering the first operand, 
    // waiting for an operator, or entering the next operand.
    onmount: =state({
      display: "0", // What you see on the screen
      expr: "", // History string, (e.g. "8 + 5 =")
      prev: "", // value stored before an operation
      op: "", // the active operator
      waiting: false // true when expecting a new number vs operator
    },{
      name: "c", // the root name of our state, so we can express things like: /c/display
      schema: "polymorphic", // allow type changes, e.g. "0" or 0
      scope: $this // scope the path to the current element
    }),
    children: [
      // Display area
      {
        div: {
          class: "display",
          children: [
            { div: { class: "expression",children[=/c/expr] }},
            { div: { class: "result",children[=/c/display] }}
          ]
        }
      },
      // Button grid
      {
        div: {
          class: "buttons",
          children: [
            // Row 1: AC, ±, %, ÷
            {
              button: { 
                class: "btn btn-clear", 
                onclick: =/c = { display: "0", expr: "", prev: "", op: "", waiting: false }, 
                children: ["AC"] 
              } 
            },
            { 
              button: { 
                class: "btn btn-function", 
                onclick: =/c = { display: negate(/c/display), waiting: true, expr: "" }, 
                children: ["±"] 
              } 
            },
            {
              button: { 
                class: "btn btn-function", 
                onclick: =/c = { display: toPercent(/c/display), waiting: true, expr: "" }, 
                children: ["%"] 
              } 
            },
            // Divison is our first operator. This is where it gets tricky. 
            // When you click `+`, you can't just link `prev` to `display`. 
            // If you did, `prev` would update every time you selected a new digit for the**second**number, 
            // breaking the math. We need a snapshot of the value at that exact moment.
            // Excel solves this with INDIRECT, effectively dereferencing a cell. JPRX borrows the same concept:
            { 
              button: { 
                class: "btn btn-operator", 
                onclick: =/c = { 
                  prev: indirect(/c/display), // Capture the value right now
                  expr: concat(/c/display, " ÷"), 
                  op: "/", waiting: true 
                  }, 
                children: ["÷"] 
              } 
            },          
            // Row 2: 7, 8, 9, ×
            // I have 10 number buttons. Do I write 10 handlers? Do I write a loop? In React or Vue, 
            // you'd probably map over an array. With JPRX, the DOM is the data key and although map is available, 
            // I represent the calculator using literals in this example. In a future article I will cover map. 
            // By giving each button an `id` (e.g., `id: "7"`), we write a uniform logic expression that adapts 
            // to whichever element triggered it. We just reference $this.id in JPRX and use an xpath to get the text
            // content for the child node, #../@id. In cDOM (not JPRX) '#' delimits the start of an xpath expression
            { 
              button: { 
                id: "7", 
                class: "btn btn-number", 
                onclick: =/c = { 
                  display: if(/c/waiting, $this.id, if(/c/display==0, $this.id, concat(/c/display, $this.id))), waiting: false
                }, 
                children: [#../@id] // use xpath (starting char #) to get the text for the button from parent id
              } 
            },
            // Here's what is happening:
            // Waiting for input? (e.g., just hit `+`) → Replace the display with the button's ID.
            // Displaying "0"? → Replace it (avoids "07").
            // Otherwise: → Append the button's ID.
            // This is replicated identically for every number button. No loops, no external helper functions.
            {
              button: { 
                id: "8", 
                class: "btn btn-number", 
                onclick: =/c = { 
                  display: if(/c/waiting, $this.id, if(/c/display==0), $this.id, concat(/c/display, $this.id))), waiting: false 
                }, 
                children: [#../@id] 
              } 
            },
            { 
              button: { 
                id: "9", 
                class: "btn btn-number", 
                onclick: =/c = { 
                  display: if(/c/waiting, $this.id, if(/c/display==0, $this.id, concat(/c/display, $this.id))), waiting: false 
                }, 
                children: [#../@id] 
              } 
            },
            { 
              button: { 
                class: "btn btn-operator", 
                onclick: =/c = { 
                  prev: indirect(/c/display), expr: concat(/c/display, " ×"), op: "*", waiting: true 
                }, 
                children: ["×"] 
              } 
            },

            // Row 3: 4, 5, 6, −
            { 
              button: { 
                id: "4", 
                class: "btn btn-number", 
                onclick: =/c = { 
                  display: if(/c/waiting, $this.id, if(/c/display==0, $this.id, concat(/c/display, $this.id))), waiting: false 
                }, 
                children: [#../@id] 
              } 
            },
            { 
              button: { 
                id: "5", 
                class: "btn btn-number", 
                onclick: =/c = { 
                  display: if(/c/waiting, $this.id, if(/c/display==0, $this.id, concat(/c/display, $this.id))), waiting: false 
                }, 
                children: [#../@id]
              }  
            },
            { 
              button: { 
                id: "6", 
                class: "btn btn-number", 
                onclick: =/c = { 
                  display: if(/c/waiting, $this.id, if(/c/display==0, $this.id, concat(/c/display, $this.id))), waiting: false 
                }, 
                children: [#../@id] 
              } 
            },
            { 
              button: { 
                class: "btn btn-operator", 
                onclick: =/c = { 
                  prev: indirect(/c/display), expr: concat(/c/display, " −"), op: "-", waiting: true 
                  }, 
                children: ["−"] 
              } 
            },

            // Row 4: 1, 2, 3, +, use set and eq just to demonstrate equivalence with = and ==
            // the buttons below use 'set' in place of the infix operator '=', just to show a different way of doing things
            { 
              button: { 
                id: "1", 
                class: "btn btn-number", 
                onclick: =set(/c, { 
                              display: if(/c/waiting, $this.id, 
                                if(eq(/c/display, "0"), $this.id, concat(/c/display, $this.id))), 
                              waiting: false 
                         }), 
                children: [#../@id] 
              } 
            },
            { 
              button: { 
                id: "2", 
                class: "btn btn-number", 
                onclick: =set(/c, { 
                            display: if(/c/waiting, $this.id, 
                              if(eq(/c/display, "0"), $this.id, concat(/c/display, $this.id))), 
                            waiting: false 
                          }), 
                children: [#../@id] 
              } 
            },
            {
              button: { 
                id: "3", 
                class: "btn btn-number", 
                onclick: =set(/c, { 
                          display: if(/c/waiting, $this.id, 
                            if(eq(/c/display, "0"), $this.id, concat(/c/display, $this.id))),
                          waiting: false 
                        }), 
                children: [#../@id] 
              } 
            },
            { 
              button: { 
                class: "btn btn-operator", 
                onclick: =set(/c, { 
                            prev: indirect(/c/display), 
                            expr: concat(/c/display, " +"), 
                            op: "+", waiting: true }), 
                children: ["+"] 
              } 
            },

            // Row 5: 0, ., =
            { 
              button: { 
                id: "0",
                class: "btn btn-number btn-wide", 
                onclick: =set(/c, { 
                            display: if(/c/waiting, $this.id,   
                              if(eq(/c/display, "0"), "0", concat(/c/display, $this.id))), 
                            waiting: false }), 
                children: [#../@id] 
              } 
            },
            {
              button: 
              { 
                class: "btn btn-number", 
                onclick: =set(/c, { 
                            display: if(/c/waiting, "0.", 
                              if(contains(/c/display, "."), /c/display, concat(/c/display, "."))), 
                            waiting: false }), 
                children: ["."] 
              } 
            },
            // Finally, the math. We need to say:
            // 1. Take the snapshot we stored
            // 2. Apply the current operator
            // 3. combine it with what's on screen now
            // This is the job of calc(). If prev == 8 and op == * and display = 5, then calc would be evaluated as calc("8 * 5")
            // To keep the syntax a little cleaner we also use $(<path>) as a shorthand for indirect.
            { 
              button: 
              { 
                class: "btn btn-equals", 
                onclick: =set(/c, { 
                            display: if(eq(/c/op, ""), /c/display, calc(concat("$('/c/prev') ", /c/op, " $('/c/display')"))), 
                            expr: concat(/c/expr, " ", /c/display, " ="), 
                            prev: "", op: "", 
                            waiting: true }), 
                children: ["="] 
              } 
            }
          ]
         }
     },
     // Branding
     {
       div: {
         class: "branding",
         children: [
           { 
              span: { 
                children: [
                  "Built with ", 
                  { 
                    a: { 
                      href: "https://github.com/anywhichway/lightview", target: "_blank", 
                      children: ["Lightview"] 
                    } 
                  }, 
                  " cDOM • No custom JS!" 
                ] 
            } 
          }
        ]
       }
     }
   ]
  }
}

Loading cDOM via Lightview Hypermedia

Lightview supports hypermedia capability similar to HTMX by allowing the use of the src attribute on almost any element.

Simply reference a cDOM file using src:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">    
    <meta name="description"
      content="A beautiful calculator built with Lightview cDOM and JPRX reactive expressions - no custom JavaScript!">
    <title>Calculator | Lightview cDOM</title>    
    <link rel="stylesheet" href="calculator.css">    
    <!-- Load Lightview scripts -->    
    <script src="/lightview.js"></script>  <!-- DOM as JSON and reactivity support -->    
    <script src="/lightview-x.js"></script> <!-- hypermedia support -->    
    <script src="/lightview-cdom.js"></script> <-- cDOM/JPRX support -->
    </head>
  <body>    
    <!-- The calculator cDOM is loaded via Lightview's hypermedia src attribute -->    
    <div id="app" src="./calculator.cdomc"></div>
  </body>
</html>

The src attribute works like an HTML <img> or <script> tag – Lightview automatically fetches the .cdomc file, parses it, and renders the reactive content into the target element. This approach:

Why Build This Way?

You might look at concat("$('/c/prev') ...") and ask: Why in the world wouldn’t you just write parseFloat(prev) + parseFloat(curr)?

If you are a human writing code for yourself? You probably would. Lightview supports standard JS handlers for exactly that reason.

But if you are building infrastructure for AI Agents, the calculus changes. Sticking to a declarative, JSON-based path offers things raw code can’t:

  • Sandboxing: It executes in a controlled environment. The logic can’t access `window`, make global fetch requests, or execute arbitrary secondary code. This makes it safe to “hot swap” UI logic generated by an LLM in real-time.

  • Portability: This entire UI—logic and all—is just data. It can be sent from a server, stored in a database, or streamed from an AI model.

  • Mental Model: It forces a clear separation between state transformations and view structure, which is exactly how LLMs reason best.

    n This calculator proves that “declarative” doesn’t have to mean “dumb.” With the right primitives – state, conditionals, and path-based referencing—you can build rich, complex interactions without ever leaving the data structure.

The Bigger Picture

This series isn’t just about a new library. It’s about finding the right abstraction layer for the AI age.

In The Future of AI-Generated UI, we looked at the risks of letting LLMs write raw scripts and introduced the “Data as UI” philosophy.

n In this article, we showed that “Data as UI” doesn’t mean “dumb UI.” We handled state, context, data snapshots, math, and DOM navigation with ‘xpath’ without executing a single line of custom JavaScript.

n cDOM defines structure. JPRX defines behavior. It’s reactivity without the compilation and UI without the security risks.

Try It Yourself

The complete calculator is available at:

  • Live Demo: https://lightview.dev/docs/calculator.html
  • Source Code: https://github.com/anywhichway/lightview/blob/main/docs/calculator.html

Sign Up For Daily Newsletter

Be keep up! Get the latest breaking news delivered straight to your inbox.
By signing up, you agree to our Terms of Use and acknowledge the data practices in our Privacy Policy. You may unsubscribe at any time.
Share This Article
Facebook Twitter Email Print
Share
What do you think?
Love0
Sad0
Happy0
Sleepy0
Angry0
Dead0
Wink0
Previous Article If OnePlus is in trouble, the OnePlus 13 and 15 show exactly why If OnePlus is in trouble, the OnePlus 13 and 15 show exactly why
Next Article Best Apple Deals of the Week: Apple Studio Display Hits Lowest Prices in Months, Plus Accessory Discounts From Satechi and More Best Apple Deals of the Week: Apple Studio Display Hits Lowest Prices in Months, Plus Accessory Discounts From Satechi and More
Leave a comment

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Stay Connected

248.1k Like
69.1k Follow
134k Pin
54.3k Follow

Latest News

Forget tempered glass: The Galaxy S26 Ultra may debut a tougher new Gorilla Glass
Forget tempered glass: The Galaxy S26 Ultra may debut a tougher new Gorilla Glass
News
I&apos;m a Dad, and Here&apos;s Why I&apos;m Not Posting About My Kid Online
I'm a Dad, and Here's Why I'm Not Posting About My Kid Online
News
Apple Pay fees that don't cost the consumer a dime at center of  billion lawsuit
Apple Pay fees that don't cost the consumer a dime at center of $2 billion lawsuit
News
Europe’s passenger car industry, in a revealing map that makes it clear who is the real “engine” of the EU
Europe’s passenger car industry, in a revealing map that makes it clear who is the real “engine” of the EU
Mobile

You Might also Like

GIMP 3.0.8 Released In Advance Of GIMP 3.2
Computing

GIMP 3.0.8 Released In Advance Of GIMP 3.2

1 Min Read
Wine-Staging 11.1 Adds Patches For Enabling Recent Adobe Photoshop Versions On Linux
Computing

Wine-Staging 11.1 Adds Patches For Enabling Recent Adobe Photoshop Versions On Linux

2 Min Read
What’s in Rust 1.77.2? | HackerNoon
Computing

What’s in Rust 1.77.2? | HackerNoon

1 Min Read
Go 1.25 is released – The Go Programming Language | HackerNoon
Computing

Go 1.25 is released – The Go Programming Language | HackerNoon

2 Min Read
//

World of Software is your one-stop website for the latest tech news and updates, follow us now to get the news that matters to you.

Quick Link

  • Privacy Policy
  • Terms of use
  • Advertise
  • Contact

Topics

  • Computing
  • Software
  • Press Release
  • Trending

Sign Up for Our Newsletter

Subscribe to our newsletter to get our newest articles instantly!

World of SoftwareWorld of Software
Follow US
Copyright © All Rights Reserved. World of Software.
Welcome Back!

Sign in to your account

Lost your password?