Mini is a library extension for HTML which lets you add interactivity to your app without needing a full blown frontend framework.
- HTML is great because it's easy to learn and extremely accessible. But HTML has shortcomings when it comes to building interfaces with interactivity.
- Lots of libraries have emerged to address these shortcomings - react, vue etc. These libraries are great but they:
- Have a high learning curve when it comes to code patterns and tooling.
- Are primarily suited for interfaces with lots of interactivity.
- Mini JS lets you build interfaces with moderate amounts of interactivity without needing a heavyweight, javascript-centered library. Because it follows the same patterns as html, it doesn't require learning lots of new concepts. It's designed to be extremely minimal and learnable within an afternoon.
- The key idea is that if we have
- A way to set state when an interaction happens (e.g a user clicks a button or types in an input), and
- A way to update other parts of the UI when those variables change, we can now easily do a range of things we previously couldn't do.
- Technically vanilla HTML can already do (1), but it can't do (2).
State
are variables that changes the UI or the DOM that uses it when they get updated.
Note: Only non-objects are supported for reactive state.
You can set the initial state of the variables using vanilla JS:
<script type="text/javascript">
firstName = 'Tony'
lastName = 'Ennis'
</script>
These are the following dynamic attributes that you can use to sync the DOM with your state:
:value
- Set the value of a form input to the result of the evaluated JS code.
:class
- Set the class of a DOM element to the result of the evaluated JS code.
:text
- Set the text content of a DOM element to the result of the evaluated JS code.
<script type="text/javascript">
firstName = 'Tony'
</script>
<input type="text" :change="firstName = this.value" />
<!-- The innerText of this paragraph changes based on the firstName variable -->
<p :text="firstName"></p>
A DOM update or a re-render happens when the state variable is re-assigned in dynamic events.
<input type="text" :change="firstName = this.value" />
<!-- the re-assignment of firstName will trigger DOM updates that uses that variable -->
When re-assignment happens in dynamic attributes, it will not trigger a re-render to avoid infinite loops.
<p :text="firstName = 'Tony'"></p>
<!-- the re-assignment of firstName will not trigger DOM updates -->
There are special variables that you can use inside dynamic attributes and events:
this
Β - the current element$
- equal to thedocument.querySelector
.
Besides :value
, :class
, and :text
, you can also make any attribute dynamic by renaming it from attribute
to :attribute
. Values set to dynamic attributes are evaluated as JavaScript:
<script>
pStyle = 'color: red'
</script>
<p :style="pStyle">My style is changing</p>
<button
:click="if (pStyle === 'color: red')
pStyle = 'color: blue';
else
pStyle = 'color: red'"
>
Toggle Style
</button>
You can make your class names reactive by using the :class
attribute:
<script type="text/javascript">
isActive = false
</script>
<button :click="isActive = !isActive" :class="isActive ? 'active' : ''">
Click Me
</button>
To set default classes, you can use the class
attribute:
<div class="hidden" :class="shouldShow ? 'visible' : 'hidden'"></div>
To set multiple reactive classes, you can use the :class
attribute:
- Use multiple ternary operators enclosed in parentheses:
<div
:class="(selectedTab === 'When' ? 'bg-white shadow-lg' : 'hover:bg-gray-300')
(whenSelectedTab === 'Dates' ? 'hidden' : '')"
></div>
- Use if-else statements:
<div
:class="if (selectedTab === 'When') {
return 'bg-white shadow-lg'
} else {
return 'hover:bg-gray-300'
}
if (whenSelectedTab === 'Dates') {
return 'hidden'
} else {
return ''
}"
></div>
You can create, use, and update state variables inside DOM events.
In events, you can get the current event using the event
variable:
<button :click="console.log(event)">Click Me</button>
All native events are supported. You can use them like this:
<button :click="console.log('click')">Click Me</button>
You can access the current element in the event via this
:
<button :click="this.classList.toggle('active')">Click Me</button>
<input :change="this.value = this.value.toUpperCase()" />
These are the events added in by MiniJS:
:clickout
- This will trigger when the user clicks outside of the current element.:clickme
- This will trigger when the user clicks the current element.:change
- This will trigger when the user changes the value of a form input.:press
- This will trigger when the user:- triggers the
click
event. - triggers the
keyup.enter
andkeyup.space
events. - triggers the
touchstart
event.
- triggers the
For keyboard events, you can listen to them using :keyup
, :keydown
, and :keypress
:
<input type="text" :keyup="console.log(event)" />
You can also use key modifiers to listen to specific keys. Modifiers are appended to the event name using a dot:
<input
type="text"
:keyup.up="console.log('keyup.up')"
:keydown.enter="console.log('keydown.enter')"
/>
You can chain multiple key modifiers together:
<input type="text" :keydown.slash.k="console.log('keydown.slash.k')" />
For key values that have multiple words like BracketLeft
, except for arrow keys, kebab case is used:
<input
type="text"
:keydown.bracket-left="console.log('keydown.bracket-left')"
/>
The following are the available key modifiers:
Type | Key Value | Modifier | Usage |
---|---|---|---|
Digits (0-9) | Digit1, Digit2 | 1, 2 | :keyup.1, :keyup.2 |
Letters (A-Z, a-z) | KeyA, KeyB | a, b | :keyup.a, :keyup.b |
Numpad (0-9) | Numpad1, Numpad2 | 1, 2 | :keyup.1, :keyup.2 |
Arrow Keys (up, down, left, right) | ArrowLeft, ArrowRight | left, right | :keyup.left, :keyup.right |
Meta (left, right) | Meta, MetaLeft, MetaRight | meta, meta-left, meta-right | :keyup.meta, :keyup.meta-left, :keyup.meta-right |
Alt (left, right) | Alt, AltLeft, AltRight | alt, alt-left, alt-right | :keyup.alt, :keyup.alt-left, :keyup.alt-right |
Control (left, right) | Control, ControlLeft, ControlRight | ctrl, ctrl-left, ctrl-right | :keyup.ctrl, :keyup.ctrl-left, :keyup.ctrl-right |
Shift (left, right) | Shift, ShiftLeft, ShiftRight | shift, shift-left, shift-right | :keyup.shift, :keyup.shift-left, :keyup.shift-right |
Symbols (., /, =, etc.) | Period, BracketLeft, Slash | period, bracket-left, slash | :keyup.period, :keyup.bracket-left, :keyup.slash |
Note: If you don't know the "name" of a symbol key, you can use the
console.log(event.code)
to see the key value. Example for the "Enter" key::keyup="console.log(event.code)"
will log "Enter". So you can use:keyup.enter
to listen to the "Enter" key.
The :each
statement is used to loop through an array and render a template for each item.
<script>
items = ['Tag 1', 'Tag 2', 'Tag 3', 'Tag 4']
</script>
<ul :each="item in items">
<li :text="item"></li>
</ul>
<ul :each="item, index in items">
<li :text=" `The item is ${item}. The index is ${index}` "></li>
</ul>
You can also use complex variables for the :each
statement:
<script>
items = [
{ name: 'Tag 1', id: 1 },
{ name: 'Tag 2', id: 2 },
{ name: 'Tag 3', id: 3 },
{ name: 'Tag 4', id: 4 },
]
</script>
<ul :each="item in items">
<li :text="item.name"></li>
</ul>
Note: Each item variables are read-only, which means you can't re-assign them like:
<ul :each="item in items">
<li :load="item = 'new value'" :text="item"></li>
</ul>
Appending $
to the variable name will save the variable in the local storage:
<script type="text/javascript">
$firstName = 'Tony'
</script>
<input type="text" :change="$firstName = this.value" />
Note: Currently, this is only available for globally declared variables.
Whenever you create a variable, it will automatically be added to the global scope. This means that you can access it anywhere in your code.
<script type="text/javascript">
firstName = 'Tony'
</script>
<button :click="console.log(firstName)">Click Me</button>
To use variables only in a current event, you can create a local variable using const
, and let
:
<button
:click="const time = new Date();
window.alert(time.toLocaleTimeString())"
>
Click Me
</button>
If you want to use the variable across an element's attributes and events, you can use el.
:
<script>
items = ['Tag 1', 'Tag 2', 'Tag 3', 'Tag 4']
</script>
<button
:load="el.selectedItem = items.pop()"
:click="el.selectedItem = items.pop()"
:text="`Last Item: ${el.selectedItem}`"
>
Click Me
</button>
Like the example above, :load
can be used to set the initial value of the variable.
Adding a :scope
attribute to an element will allow you to access its variables from its children using scope.
variables.
<!-- Scope Element -->
<div id="accordion" class="accordion" :scope>
<!-- Children Elements -->
<section
class="grid transition-all border-gray-300 border border-b-0 rounded hover:bg-gray-100"
>
<button
:click="scope.activeSection = 'about'"
class="cursor-pointer font-bold p-4"
>
About Us
</button>
<div
class="p-4 pt-2 overflow-hidden hidden"
:class="scope.activeSection =='about' ? 'block' : 'hidden'"
>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
eirmod.
</div>
</section>
<section
class="grid transition-all border-gray-300 border border-b-0 rounded hover:bg-gray-100"
>
<button
:click="scope.activeSection = 'contact'"
class="cursor-pointer font-bold p-4"
>
Contact Us
</button>
<div
class="p-4 pt-2 overflow-hidden"
:class="scope.activeSection =='contact' ? 'block' : 'hidden'"
>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
eirmod.
</div>
</section>
<section
class="grid transition-all border-gray-300 border rounded hover:bg-gray-100"
:class="scope.activeSection =='team' ? 'active' : ''"
>
<button
:click="scope.activeSection = 'team'"
class="cursor-pointer font-bold p-4"
>
Team 3
</button>
<div
class="p-4 pt-2 overflow-hidden"
:class="scope.activeSection =='team' ? 'block' : 'hidden'"
>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
eirmod.
</div>
</section>
</div>
You can set the default value of the scope variables in the :scope
directive:
<div id="accordion" class="accordion" :scope="activeSection = 'about'">
<!-- ... -->
</div>
MiniJS added some commonly-used custom methods to variables.
Here are the custom array methods which are available for you to use:
-
first
- returns the first item in the array. Usage:array.first
array = ['Cherries', 'Chocolate', 'Blueberry', 'Vanilla'] array.first // returns 'Cherries'
-
last
- returns the last item in the array. Usage:array.last
array = ['Cherries', 'Chocolate', 'Blueberry', 'Vanilla'] array.last // returns 'Vanilla'
-
search
- returns a new array of items that match the query. Usage:array.search('query')
array = ['Cherries', 'Chocolate', 'Blueberry', 'Vanilla'] array.search('c') // returns ['Cherries', 'Chocolate']
-
subtract
- removes a list of items from the array if they exist. Usage:array.subtract(['item1', 'item2'])
array = ['Tag 1', 'Tag 2', 'Tag 3', 'Tag 4'] array.subtract(['Tag 2', 'Tag 3']) // returns ['Tag 1', 'Tag 4']
-
nextItem
- gets the next item based on the given item in the array. Usage:array.nextItem('item')
array = ['Tag 1', 'Tag 2', 'Tag 3', 'Tag 4'] array.nextItem('Tag 2') // returns 'Tag 3'
-
previousItem
- gets the next item based on the given item in the array. Usage:array.previousItem('item')
array = ['Tag 1', 'Tag 2', 'Tag 3', 'Tag 4'] array.previousItem('Tag 2') // returns 'Tag 1'
-
add
- adds an item to the original array if it doesn't exist.- This mutates the original array.
Usage:
array.add('item')
array = ['Tag 1', 'Tag 2', 'Tag 3', 'Tag 4'] array.add('Tag 5') // returns ['Tag 1', 'Tag 2', 'Tag 3', 'Tag 4', 'Tag 5']
- This mutates the original array.
Usage:
-
remove
- removes an item from the original array if it exists.- This mutates the original array.
Usage:
array.remove('item')
array = ['Tag 1', 'Tag 2', 'Tag 3', 'Tag 4'] array.remove('Tag 2') // returns ['Tag 1', 'Tag 3', 'Tag 4']
- This mutates the original array.
Usage:
-
toggle
- removes / adds the item in the original array- This mutates the original array.
Usage:
array.toggle('item')
array = ['Cherries', 'Chocolate', 'Blueberry', 'Vanilla'] array.toggle('Cherries') // removes 'Cherries' // returns ['Chocolate', 'Blueberry', 'Vanilla'] array.toggle('Cherries') // re-adds 'Cherries' // returns ['Cherries', 'Chocolate', 'Blueberry', 'Vanilla']
- This mutates the original array.
Usage:
-
replaceAt
- replaces the item at the given index with the new item.- This mutates the original array.
Usage:
array.replaceAt(index, 'newItem')
array = ['Tag 1', 'Tag 2', 'Tag 3', 'Tag 4'] array.replaceAt(1, 'Tag 5') // returns ['Tag 1', 'Tag 5', 'Tag 3', 'Tag 4']
- This mutates the original array.
Usage:
Jen π» π π |
tonyennis145 π π» |
Joeylene π π» π |
Jen π π» |
To setup MiniJS in your local machine, you can do the following:
- Clone the repository.
- Run
yarn
to install dependencies. - Run
yarn build
to create thedist
folder -> output for MiniJS. - Run
yarn dev
to run the demo page locally. - Run
yarn build-watch
on another terminal to build the code whenever the Mini.js code changes. - Run
yarn test
to run the tests.