DEV Community

Ahmad Wasfi
Ahmad Wasfi

Posted on

Building a Single-Page App with a New Paradigm Using TargetJ

React has become one of the most popular frameworks for building user interfaces. A typical React application looks like this:

React Application (App.js)

import React, { useState } from 'react';
import './App.css';

function App() {
  // State to hold the message
  const [message, setMessage] = useState('Welcome to My Simple React App');

  // Function to update the message
  const handleClick = () => {
    setMessage('Hello! You clicked the button.');
  };

  return (
    <div className="App">
      <header className="App-header">
        <h1>{message}</h1>
        <button onClick={handleClick}>Click Me!</button>
      </header>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Styling with CSS (App.css)

.App {
  text-align: center;
}

.App-header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

button {
  font-size: 1.2em;
  padding: 10px 20px;
  cursor: pointer;
  background-color: #61dafb;
  border: none;
  border-radius: 5px;
  margin-top: 20px;
}

button:hover {
  background-color: #21a1f1;
}
Enter fullscreen mode Exit fullscreen mode

The issue with this approach is that React doesn't provide a unified way to manage state, handle presentation, manage events, make API calls, or handle animations. Additionally, controlling program flow with time is challenging. Moreover, the HTML tree and CSS introduce an extra intermediary layer that is inherently static, making it difficult to control affecting design flexibility.

TargetJ is a new JavaScript framework that introduces a new paradigm for programming single-page sites, addressing all the issues mentioned above. It is open-source, and you can find it at https://github.com/livetrails/targetj. The core concept of the framework is that it gives life cycles to methods and variable assignments, allowing them to operate autonomously, much like living cells.

This is achieved by providing a unified interface wrapper for both methods and variable assignments, forming new constructs called targets. The interface wrapper doesn't necessarily add extra code, as targets can also be written as methods or variable assignments. Targets can also be written as objects, which is the most expressive format, allowing them to have callbacks to adjust to external changes.

In other words, targets are the fundamental building blocks of components. Every component in TargetJ is composed of a set of targets.

A brief explanation of what targets consist of will help in understanding how TargetJ works. Each target has the following:

1. Target Value and Actual Value
The target value is the value assigned to a variable or the result produced by a method, while the actual value is the one used by the rest of the application. When the target value differs from the actual value, TargetJ updates the actual value iteratively until it matches the target value. This process is controlled by two additional variables: Step, which defines the number of iterations, and Interval, which specifies the duration (in milliseconds) the system waits before executing each iteration.

2. State
Targets have four states that control their lifecycle: Active, Inactive, Updating, and Complete.

  • Active: This is the default state for all targets. It indicates that the target is ready to be executed, meaning the target value needs to be initialized from the variable it represents, or its value() method needs to be executed to calculate its output.
  • Inactive: Indicates that the target is not ready to be executed.
  • Updating: Indicates that the actual value is being adjusted to match the target value.
  • Complete: Indicates that the target execution is finished, and the actual value has successfully matched the target value.

3. Target Methods
All methods are optional. They are used to control the lifecycle of targets or serve as callbacks to reflect changes.

  • Controlling methods: enabledOn, loop, steps, cycles.
  • Callbacks: onValueChange, onStepsEnd, onImperativeStep, onImperativeEnd, onSuccess, onError.

TargetJ Solution

Let’s see how TargetJ tackles the issues mentioned earlier.

1. Handling presentation

Let’s start with the button as we work progressively to implement the React example above, allowing us to illustrate each issue more clearly.

In the example below, you can see that the HTML elements, styles, and attributes are all written as targets, each capable of operating independently. For instance, the background target has three values and will continuously morph between them over 50 steps. HTML nesting can also be dynamically changed by targets. This is achieved by declaring the domParent target from the element seeking a container or the domHolder target, declared by containers for elements without a domParent. In our example, the button has no domParent, so it will be nested in the application root, supporting a flat HTML structure.

You can view a live example here: https://targetj.io/examples/articlePresentation.html.

TargetJ Application (articlePresentation.js)

App(new TModel({
    width: 100,
    height: 100,
    fontSize: '1.2em',
    lineHeight: 100,
    cursor: 'pointer',
    border: 'none',
    borderRadius: 5,
    html: 'Click me',
    topMargin: 20,
    x() { return this.getCenterX(); },                      
    background: {
        loop: true,
        value() {
            return { list: [ '#21a1f1', '#61dafb' ] };
        },
        steps: 30
    }
}));
Enter fullscreen mode Exit fullscreen mode

presentation

2. Animation

In the next example, we add four sets of animation moves to our button by creating a new target called animate. We iterate through the array of moves using cycle, which only updates once all the moves in a set are completed. The setTarget defines an imperative target within the declarative target of animate. The onImperativeEnd callback is triggered when the actual value reaches the target value of each style attribute.

We also added a baseElement target, which alternates between <img> and <button> every second. The focus here is not on presentation, but rather to illustrate how HTML can be seen in a new light, reducing the design constraints typically imposed by it.

You can view a live example here: https://targetj.io/examples/articleAnimation.html.

TargetJ Application (articleAnimation.js)

App(new TModel({
    width: 100,
    height: 100,
    src: 'https://targetj.io/img/structure.jpg',
    fontSize: '1.2em',
    lineHeight: 100,
    cursor: 'pointer',
    border: 'none',
    borderRadius: 5,
    html: 'Click me',
    topMargin: 20,
    x() { return this.getCenterX(); },           
    baseElement: {
        loop: true,
        cycles: 1,
        interval: 1000,
        value(cycle) { return ['img', 'button' ][cycle]; }
    },            
    background: {
        loop: true,
        value() { return { list: [ '#21a1f1', '#61dafb' ] }; },
        steps: 30
    },
    animate: {
        cycles: 3,
        value(cycle) {
            return [
                { rotate: 0, scale: 1, background: 'blue', borderRadius: 0 },
                { rotate: 180, scale: 1.5, background: 'red', borderRadius: 75 },
                { rotate: 360, scale: 2, background: 'yellow', borderRadius: 50 },
                { rotate: 0, scale: 1, background: '#61dafb', borderRadius: 5  }
            ][cycle];
        },
        onValueChange(value) {
            this.setTarget("coolAnimation", value, 50); 
        },
        onImperativeEnd(key) {
            if (key === 'background' && this.getTargetCycle(this.key) === 3) {
                this.resetImperative(key);
            }
        }
    }      
}));
Enter fullscreen mode Exit fullscreen mode

animation

3. Handling events

In this example, we added three targets to handle three events: click, mouseenter, and mouseleave. The target events onClickEvent, onEnterEvent, and onLeaveEvent are special targets that the TargetJ task will call when the corresponding events are triggered. These targets can either activate other targets or handle the events directly as methods.

We also added a special target called canHandleEvents, which specifies that the button can handle touch events. Since canHandleEvents is a target, it can also be activated by other events, enabling or disabling touch event handling based on certain conditions.

Notice that the shake target has the active flag set to false. This indicates that the target will not be executed until it is activated by other targets.

You can view a live example here: https://targetj.io/examples/articleEvents.html.

TargetJ Application (articleEvents.js)

import { App, TModel, Easing } from "targetj";

App(new TModel({
    canHandleEvents: 'touch',
    width: 100,
    height: 100,
    fontSize: '1.2em',
    lineHeight: 100,
    cursor: 'pointer',
    border: 'none',
    textAlign: 'center',
    borderRadius: 5,
    html: 'Click me',
    background: '#61dafb',
    topMargin: 20,
    x() { return this.getCenterX(); },    
    shake: {
        active: false,
        cycles: 3,
        value(cycle) {
            const x = this.getX();
            this.setTarget('x', { list: [ x - 20, x, x + 20, x ] }, 10 - 2 * cycle, 0, Easing.inOut);
        }
    },
    onClickEvent: 'shake',
    onEnterEvent() { this.setTarget('background', '#21a1f1'); },
    onLeaveEvent() { this.setTarget('background', '#61dafb'); },     
}));
Enter fullscreen mode Exit fullscreen mode

animation

4. Calling API

In the example below, we define a target named load. Inside the value function, we make an API call using fetch(). The second argument specifies the API URL, and the third argument contains the query parameters passed to the API. A fourth optional parameter, omitted in this example, can specify a cache ID if we want to cache the result. This cache ID can also be used to retrieve the cached data. If it’s not specified, the result will always come from the API.

Once the API response is received, it triggers either onSuccess or onError, depending on the outcome.

You can view a live example here: https://targetj.io/examples/articleAPI.html.

TargetJ Application (articleAPI.js)

import { App, TModel, getLoader } from "targetj";

App(new TModel({
    canHandleEvents: 'touch',
    width: 100,
    height: 100,
    fontSize: '1.2em',
    display: 'flex',
    justifyContent: 'center',
    alignItems: 'center',
    textAlign: 'center',    
    cursor: 'pointer',
    border: 'none',
    borderRadius: 5,
    html: 'Click me',
    background: '#61dafb',
    topMargin: 20,
    x() { return this.getCenterX(); }, 
    load: {
        active: false,
        value() {
            return getLoader().fetch(this, 'https://targetj.io/api/randomUser', { id: 'user0' });  
        },
        onSuccess(res) {   
            this.setTarget('html', `My name is ${res.result.name}`);                     
        },
        onError() {
            this.setTarget('html', 'Click again!');
        }
    },    
    onClickEvent: 'load',
    onEnterEvent() { this.setTarget('background', '#21a1f1'); },
    onLeaveEvent() { this.setTarget('background', '#61dafb'); }    
}));
Enter fullscreen mode Exit fullscreen mode

animation

5. State Management

Each target manages its own state. Targets can be activated by certain events, such as user actions or when the page is closed, allowing them to refresh their states. TargetJ tasks execute all targets in the order they appear, from top to bottom. For more complex cases, enabledOn() can keep a target active until the condition defined within enabledOn() is met.

The getPager() function in TargetJ, used for switching between pages, maintains a cache of each page. Similarly, the getLoader(), as we saw above, can cache results to be used across different parts of the application.

6. Controlling the execution flow with time

TargetJ was designed from the ground up to handle complicated and intricate sequences. Here is a brief explanation of what it provides: Each target has an interval property, which can also be implemented as a method. The interval controls the execution rate of the value() of the target. The execution rate for each step, which updates the actual value to match the target value, can also be configured. Imperative targets can define their own interval as well. The onImperativeStep and onImperativeEnd callbacks allow you to listen for each step update or when all steps of the imperative targets are completed.

Here is the full implementation of the React example mentioned earlier, with the following added to demonstrate TargetJ’s ability to control different parts of the application over time:

  • Display the message first, then add the button after 1 second.
  • Scale the button and revert back after 1 second from showing it.

You can view a live example here: https://targetj.io/examples/articleTime.html.

TargetJ Application (articleTime.js)

import { App, TModel, getEvents } from "targetj";

App(new TModel({
    title() {
        return new TModel({
            topMargin: 20,
            html: 'Welcome to My Simple TargetJ App',
            x() { return this.getCenterX(); }
        });
    },
    button() {
        return new TModel({
            canHandleEvents: 'touch',
            fontSize: '1.2em',
            padding: '10px 20px',
            cursor: 'pointer',
            border: 'none',
            borderRadius: '5px',
            html: 'Click me',
            topMargin: 20,
            scale: {
                cycles: 1,
                interval: 1000,
                value(cycle) {
                    return [ 1, [ { list: [ 1, 2, 1 ] }, 30, 0 ] ][cycle];
                }
            },
            background() { return getEvents().isTouchHandler(this) ? '#21a1f1' : '#61dafb'; },            
            x() { return this.getCenterX(); },
            onClickEvent() { this.getParentValue('title').setTarget('html', 'Hello! You clicked the button.'); },
            onEnterEvent: 'background',
            onLeaveEvent: 'background'
        });
    },
    children: {
       cycles: 1,
       interval: 1000,
       value(cycle) {
           return [ this.val('title'), this.val('button') ][cycle];
       }
    }
}));
Enter fullscreen mode Exit fullscreen mode

animation

Conclusion

We believe TargetJ offers a fluid and flexible medium that enables a new kind of user experience that was previously difficult to achieve. It also simplifies development significantly, making it more enjoyable without sacrificing performance. You can find the framework at https://github.com/livetrails/targetj and explore its interactive documentation at www.targetj.io.

Top comments (0)