Tamarack

Under Development Some features may be incomplete or contain bugs

A framework for building rich, cross-platform apps in pure JavaScript.

One codebase, multiple platforms

Tamarack provides a solid base that adapts to the platform it's running on and provides out of the box support for:

Responsive design: All tamarack views have been tested and display correctly on every screen, from large desktop monitors all the way down to the smallest of mobile displays.
Dark mode: All tamarack views have styling that automatically reflects the user's preferred color scheme. No extra code required.
Integrating with existing code: Though you can write your application entirely with tamarack, it is designed to be easily added into any codebase. Any TkView can be placed inside an existing HTML element like this:
                    
                            let colorChooser = new TkColorChooser({ parent: "#someExistingElement", color: "blue" });
                        
                    
Writing platform specific CSS: For parts of your app where you want to style an element differently depending on the platform, tamarack makes it easy to write platform-specific CSS styles. It also provides a few built-in styles to better match the UI design of each platform, such as moving toolbars to the bottom of the screen on iOS. For example, here's platform-specific CSS styles that change the text color of an <h1> element:
                    
                            /* The default setting */
                            h1 { color: orange; }
                            /* For iPhones, either in Cordova or in the browser */
                            [iphone] h1 { color: red; }                        
                            /* For Androids, only in Cordova */
                            [cordova][android] h1 { color: blue; }                         
                            /* For Electron apps, running on macOS */
                            [electron][macos] h1 { color: green; }
                        
                    

Structure

Tamarack has no external dependencies and is made up of these scripts:

  • core.js: Functionality used by all of the other scripts.
  • color.js: A class to manage nearly every type of CSS color and easily convert between them.
  • font.js: A class to simplify the management and manipulation of fonts.
  • view.js: Contains a collection of views, which are wrappers around HTML elements inheriting from a class called TkView and can be manipulated entirely through JavaScript, with no HTML boilerplate.
  • app.js: Sets up the environment for building cross-platform apps.
  • And many more...

Demos

Hello world

A simple hello world demo in Tamarack.

                
                        new TkText("h1", { parent: "body", text: "Hello World!" });
                
                

Coin toss

Create a button that tosses a coin and prints the result to the screen.

                
                        let tossCoinButton = new TkButton({ parent: "body", text: "Toss Coin" });
                        let results = new TkStack({ parent: "body", direction: TkStackDirection.VERTICAL });

                        tossCoinButton.on("click", () => {
                            let result = TkArray.random("Heads", "Tails");
                            let resultColor = (result == "Heads") ? "maroon" : "midnightblue";
                            let resultText = new TkText("h2", { 
                                parent: results, 
                                text: result,
                                style: `color: ${resultColor};`
                            });
                        });
                
                

Tabs

Managing a notebook with tabs is a fairly common task when developing an application, but it can quickly become a hassle using only plain HTML and JavaScript. With Tamarack, it can be done entirely in JavaScript, without any boilerplate.

A TkNotebook is a view containing pages that contains useful functions and properties for managing pages. It also includes events such as activechanged, addpage, and removepage that you can add event handlers to in order to deal with changes in the notebook.

Each page in a notebook is represented by a TkPage, which includes a tab button and a content panel.

                
                        // Declare views
                        let buttonStack = new TkStack({ parent: "body", direction: TkStackDirection.FLOW });
                        let previousPageButton = new TkButton({ parent: buttonStack, text: "<" });
                        let nextPageButton = new TkButton({ parent: buttonStack, text: ">" });
                        let addPageButton = new TkButton({ parent: buttonStack, text: "Add" });
                        let removePageButton = new TkButton({ parent: buttonStack, text: "Remove" });
                        let selectedPageText = new TkText("h2", { parent: "body", style: "margin: 1rem 0;" });
                        let notebook = new TkNotebook({ parent: "body" });

                        // Keep track of the pages added
                        let pageCount = 0;

                        // Attach button event handlers
                        previousPageButton.on("click", () => notebook.goToPrevious());
                        nextPageButton.on("click", () => notebook.goToNext());
                        addPageButton.on("click", () => {
                            notebook.add(new TkPage({
                                title: `Tab ${++pageCount}`,
                                content: [new TkText("h2", { text: `Content ${pageCount}` })]
                            }));
                        });
                        removePageButton.on("click", () => {
                            if (notebook.active !== null)
                                notebook.remove(notebook.active);
                        });

                        // Update the selected page text when the current page is changed
                        notebook.on("activechanged", () => {
                            selectedPageText.text = (notebook.active != null) ? notebook.active.title : "";
                        });
                    
                

Detect dark mode

Easily add event handlers to watch for switches between light and dark mode.

                
                        // Insert a text element into <body>
                        let modeText = new TkText("h1", { parent: "body" });

                        // Update the text and document colors when the system is 
                        // switched between dark and light mode
                        function updateMode() {
                            if (TkDocument.isInDarkMode()) {
                                document.body.style.backgroundColor = "black";
                                document.body.style.color = "white";
                                modeText.text = "Dark Mode";
                            } else {
                                document.body.style.backgroundColor = "white";
                                document.body.style.color = "black";
                                modeText.text = "Light Mode";
                            }
                        }

                        // Attach the handler to watch for the change
                        TkDocument.onChangeDarkMode(updateMode);

                        // Call to update for the current mode
                        updateMode();
                    
                

Stacks

Layout views in a stack.

A fundamental view in Tamarack is the TkStack view, which is a flexbox container that exposes useful functionality for laying out child views.

It creates two stacks, buttonStack and colorStack, adds a button to buttonStack for each TkStackDirection to change the stack direction of colorStack, then loops through each hue (0-360), and adds a TkText view representing it to the color stack.

                
                        // Declare the layout button stack and add it to the body
                        let buttonStack = new TkStack({
                            parent: "body",
                            direction: TkStackDirection.FLOW
                        });

                        // Declare the color stack and add it to the body
                        let colorStack = new TkStack({
                            parent: "body",
                            direction: TkStackDirection.VERTICAL
                        });

                        // Add a button for each stack direction
                        for (let direction in TkStackDirection) {
                            let button = new TkButton({
                                parent: buttonStack,
                                text: direction,
                                style: "margin: 0.25rem;"
                            });

                            button.on("click", () => colorStack.direction = TkStackDirection[direction]);
                        }

                        // Loop through hues and add text for each one to the colorStack
                        for (let i = 0; i <= 360; i++) {
                            let itemColor = new TkColor(`hsl(${i}, 100%, 50%)`);
                            let stackItem = new TkText("span", {
                                parent: colorStack,
                                style: "font-weight: bold; padding: 0.25rem;"
                            });

                            stackItem.style.backgroundColor = itemColor.asHex();
                            stackItem.style.color = itemColor.isLight() ? "black" : "white";
                            stackItem.text = itemColor.asHex();
                        }