Introduction
Reactive methods, where one method runs automatically when another completes, whether synchronous or asynchronous, is a powerful idea. TargetJS introduces a distinctly innovative approach to this concept: it enables methods to react exclusively to their immediately preceding counterparts, fostering a declarative and simple code flow.
TargetJS also brings in a second key concept: it unifies both variables and methods into a new construct called “Targets”. Targets also provide state, loops, timing, and more, whether it's a variable or a function.
When these two ideas are combined: code-ordered reactivity and Targets, they unlock a fundamentally new way of coding that simplifies everything from animations and UI updates to API calls and state management. The result is code that is not only more intuitive to write but also significantly more compact.
Understanding TargetJS Syntax: Reactive Postfixes
TargetJS uses the postfixes $
and $$
appended to target names for defining reactive behaviors. While initially appearing a bit cryptic, this convention provides a compact syntax.
$
Postfix (Immediate Reactivity):
A target name ending with a single $
(e.g., height$
) indicates that this target will execute every time its immediately preceding target runs or emits a new value. If the preceding target involves an asynchronous operation like an API call, the reactive target activates when the response is received. If there are multiple API calls made, $
postfix ensures that the target reacts to the first API result when it becomes available, then the second, and so on, maintaining a strict, code-ordered sequence of operations.
$$
Postfix (Full Completion Reactivity):
A target name ending with a double $$
(e.g., fetch$$
) will activate only after its immediately preceding targets have fully and comprehensively completed all of their operations. This includes:
- The successful resolution of any timed sequences, such as animations.
- The completion and return of results from all associated API calls.
- The finalization of all tasks, animations, and API calls initiated by any dependent child targets that were themselves triggered by a preceding target.
Targets: The Building Blocks of TargetJS
Targets provide a unified interface for both variables and methods. Each Target comes equipped with a built-in set of capabilities:
- State Management: Targets are inherently stateful, enabling implicit state handling across your application.
- Iterations: They can iterate towards defined values, making them perfect for creating animations.
- Multiple or Conditional Execution: Targets can execute repeatedly or only under specific conditions.
- Execution timing: Targets enable fine-grained control over when they execute.
- Code-Ordered Execution: Targets execute sequentially and predictably in the order they are written within a JavaScript object, thanks to ES2015's guaranteed property order.
An All-in-One Solution
The elegance of TargetJS emerges when its two innovations: Reactivity and Targets, work in concert. The combination allows TargetJS to serve as a consistent, all-in-one solution for most front-end development needs:
- Declarative UI Rendering: Define your user interface using Targets, and let TargetJS automatically manage rendering updates.
- Animations: Leverage a target's built-in iteration, looping, and timing capabilities alongside reactivity to create complex animations with minimal effort.
- API Integration: Execute API calls as targets and utilize their reactivity features to automatically process responses.
- State Management: With targets inherently managing their own state, application state management becomes implicit.
- Event Handling: Attach event listeners directly as targets to work consistently with the rest of the application
Examples in Action
To demonstrate the power and simplicity of TargetJS, let's explore its concepts through practical examples. We'll begin with a simple animation and incrementally expand it to demonstrate API integration, event handling, and dynamic UI updates.
Growing and Shrinking Box: Declarative Animation
import { App } from 'targetj';
App({
background: 'mediumpurple',
width: [{ list: [100, 250, 100] }, 50, 10], // width animates through 100 → 250 → 100, over 50 steps with 10ms intervals.
height$() { // `$` creates a reactive target: the `height` updates each time `width` executes
return this.prevTargetValue / 2;
}
});
Explanation
Targets execute precisely in the order they are defined:
-
background
: This target runs first, setting the element's background color tomediumpurple
. Once the assignment is complete, its lifecycle ends. -
width
: Next, thewidth
target takes over. It's configured to animate through a list of values (100, 250, 100), performing 50 steps with a 10ms pause between each step, creating a grow-then-shrink effect. -
height$
: Finally, theheight$
target demonstrates TargetJS's reactivity. Because its name ends with a single$
postfix,height$
is explicitly declared to react whenever its immediately preceding target (width
) executes on every step. Aswidth
animates and changes its value,height$
automatically re-runs, setting its value to half of width's value.
The example above can also be implemented directly in HTML, utilizing tg- attributes that mirror the object literal keys used in JavaScript:
<div
tg-background="mediumpurple"
tg-width="[{ list: [100, 250, 100] }, 50, 10]"
tg-height$="return this.prevTargetValue / 2;">
</div>
Adding an API Call
Let's extend our previous example to demonstrate how TargetJS handles asynchronous operations. We'll fetch user details from an API, but we also want this API call to initiate only after the box animation has fully completed.
import { App } from 'targetj';
App({
background: 'mediumpurple',
width: [{ list: [100, 250, 100] }, 50, 10],
height$() {
return this.prevTargetValue / 2;
},
fetch$$: 'https://targetjs.io/api/randomUser?id=user0', // `$$` ensures this runs only after width and height animation is complete
html$() { // it runs when the API response is resolved
return this.prevTargetValue.name; // `prevTargetValue` holds the result from the previous target (i.e., the API response)
}
});
Explanation
This example introduces two new targets:
fetch$$
:fetch
target is a specialized target designed to retrieve data when given a URL string. Since its name ends with a$$
postfix, as previously discussed, this indicates thatfetch$$
will activate only after preceding targets fully completed all of its operations. This guarantees the API call is initiated just after the animation finishes.html$
: Followingfetch$$
, thehtml
target is responsible for setting the text content of thediv
element to the fetched user's name. The$
postfix here signifies thathtml$
is a reactive target that executes each time its immediately preceding target (fetch$$
) provides a result. In this context,this.prevTargetValue
will hold the resolved data from the API call, allowinghtml$
to dynamically display the user's name as soon as it's available.
Together, these targets orchestrate the flow: animation completes, then the API call happens, then the UI updates with the fetched data, all managed declaratively and in code order.
Attaching a Click Handler
Let's expand our box further by adding a click handler. The goal is to change the box's background color to orange when clicked, pause for two seconds, and then revert the background back to its original mediumpurple.
import { App } from 'targetj';
App({
background: 'mediumpurple',
width: [{ list: [100, 250, 100] }, 50, 10],
height$() {
return this.prevTargetValue / 2;
},
fetch$$: 'https://targetjs.io/api/randomUser?id=user0',
html$() {
return this.prevTargetValue.name;
},
onClick() { // Special target that runs when the element is clicked
this.setTarget('background', 'orange', 30, 10); // Animates background to orange over 30 steps
},
pause$$: { interval: 2000 }, // `$$` ensures this runs ONLY after the preceding 'onClick' animation is fully complete
purpleAgain$$() { // `$$` ensures this runs ONLY after `pause$$` completes (2-second interval)
this.setTarget('background', 'mediumpurple', 30, 10); // Animates background back to mediumpurple
}
});
Explanation:
-
onClick
: This is a special TargetJS function that automatically runs whenever the associated element is clicked. Inside,this.setTarget('background', 'orange', 30, 10)
imperatively triggers a new animation, changing the background color to orange over 30 steps. -
pause$$
: Notice the$$
postfix. Thispause$$
target is configured with an interval of 2000 milliseconds (2 seconds). Crucially, its$$
postfix means it will only begin its 2-second pause after its immediately preceding target (onClick) has fully completed its animation of changing the background to orange. -
purpleAgain$$
: Also ending with$$
, this target executes only after thepause$$
target has finished its 2-second execution. It then uses this.setTarget again to animate the background color back tomediumpurple
.
This sequence demonstrates how TargetJS allows you to define complex, timed flow with a guaranteeing execution order.
Let's Make it More Complicated :)
Let’s expand the previous example by creating 10 boxes instead of just one. Each box will be added with a slight delay and undergo its own task from the previous example. Once all these individual box processes (creation, animation, and API calls) are complete, we'll trigger a final collective action: changing all their backgrounds to green.
import { App } from 'targetj';
App({
children: { // A special target that generates a new list of child objects each time it executes
cycles: 9, // Creates 10 children (from cycle 0 to 9)
interval: 100, // Adds a new child every 100 milliseconds
value(cycle) {
return {
background: 'mediumpurple',
width: [{ list: [100, 250, 100] }, 50, 10],
height$() { return this.prevTargetValue / 2; },
fetch$$: `https://targetjs.io/api/randomUser?id=user${cycle}`, // Each child fetches a unique user
html$() { return this.prevTargetValue.name; },
onClick() { this.setTarget('background', 'orange', 30, 10); },
pause$$: { interval: 2000 },
purpleAgain$$() { this.setTarget('background', 'mediumpurple', 30, 10); }
};
}
},
greenify$$() { // `$$` ensures this runs ONLY after ALL children have completed ALL their tasks
this.getChildren().forEach(child => child.setTarget("background", "green", 30, 10)); // Iterates and animates each child's background
}
});
Explanation:
This advanced example demonstrates TargetJS's capability to manage complex, dynamic UI scenarios:
children
: A special target construct used to create a collection of child objects. Each time it executes, the returned object or objects are added to the current one. Thecycles
property specifies that the value function will run 10 times (fromcycle
0 to 9), thus creating 10 individual box elements.
Theinterval
property ensures that each new box is created and added to the UI every 100 milliseconds.
Thevalue(cycle)
function return the same object element from the previous example.greenify$$
: This target demonstrates the$$
postfix's full completion reactivity at a higher level. Thegreenify$$
target will execute only after the entire children target has comprehensively completed all of its work. This includes:
- The creation of all 10 child boxes.
- The completion of each individual child's width animation.
- The successful return of all 10 API calls (
fetch
) for each child. - The population of all user names (
html
) for each child.
Only when all tasks initiated by children are finished will greenify$$
runs. It then uses this.getChildren().forEach(child => child.setTarget("background", "green", 30, 10))
to iterate over all the created child boxes and animate their backgrounds to green.
What if greenify
ends with only one $
?
If greenify
ended with only one $
(i.e., greenify$
), it would exhibit immediate reactivity to its preceding target children
. Instead of waiting for all 10 children to be fully animated, etc, greenify$
would run immediately each time a new child is added.
This example highlights TargetJS's ability to orchestrate complex, dependent operations across multiple elements in a compact, declarative, and code-ordered fashion.
Ready to learn more?
🔗 Visit: GitHub Repo
Top comments (0)