Leaf API

defineScope

defineScope is the most important API because it provides isolation between scopes, thus maintaining a clean code structure. DefineScope also creates the logic for your application’s UI.

Example

//scope_greeter.js
import { defineScope } from "https://cdn.jsdelivr.net/gh/Rahmad2830/Leaf@v1.0.0/dist/Leaf.min.js"

defineScope("greeter", () => {
  //your code here
})

Context Object

Leaf have 2 main context object, first at defineScope level, second is in the function level.

Context in scope level

This context is in the defineScope params function.

//context placed in here
defineScope("any", (context) => {
  //code goes here
})

This context scope has three main properties: root, which retrieves the element where the data-scope is defined. targets, which marks (retrieves) the element. values, which retrieves the value of other data-* elements within the root element.

You can also use object destruction to invoke the context. Example

//context placed in here
defineScope("any", ({ root, targets, values }) => {
  //code goes here
})
  • targets

Targets is useful for marking elements to be retrieved. You can mark multiple elements with the same target data using targets.all.[targets_name]. However, remember that using targets.all.[targets_name] will result in an array of elements. To process it, it must be looped first. Example

<div data-scope="any">
  <div data-target="hello">Something</div>
  
  <p data-target="world">Something</p>
  <div data-target="world">Something</div>
</div>
defineScope("any", ({ targets }) => {
  const greet = targets.hello //take element with data-target="hello"
  console.log(greet)
  
  const allWorld = targets.all.world //take all element with data-target="world"
  allWorld.forEach(element => console.log(element))
})

You can use all methods for elements. For example: greet.id to get the element ID, greet.hidden = true to set the element’s visibility, etc.

  • root

root is useful for retrieving elements that have the data-scope attribute attached. Example: HTML

<div data-scope="any">
  <!-- your code here -->
</div>

javascript

defineScope("any", ({ root }) => {
  root.hidden = true
})

You can use all methods for elements. For example: root.id to get the element ID, root.hidden = true to set the element’s visibility, etc.

  • values

Imagine if you needed other data in the scope, then you could put it in the data-* attribute. values is used to retrieve the value from the data- attribute in the scope element. If you write data-status=“pending”, you can call it with the prefix values.status. Example: HTML

<div data-scope="any" data-status="pending">
  <!-- your code here -->
</div>

javascript

defineScope("any", ({ values }) => {
  console.log(values.status)
  //output "pending"
})

Always remember that values always return strings. If you pass a number, for example, data-id=“5,” then the number 5 will default to a string. To change this, you must manually change Number(values.id)

Context in function level

The context object at the function level is located in the function parameters that you create in defineScope. Example:

defineScope("any", () => {
  //context in function level placed here
  function greet(context) {
    console.log(context)
  }
})

You can also use object destruction to call this context. Example:

defineScope("any", () => {
  //context in function level placed here
  function greet({ element, event, params }) {
    //do something
  }
})

The context at this level also has three main properties: element, used to select the element to which this function is attached. event, which is the event in a regular listener (e.g., event.target.closest). params used to retrieve data-* values within this element. Since the element to which this function is attached must be a data-action, it is more accurately called an action context.

  • element

element is useful for selecting elements that have functions attached to them, in this case elements that have data-action attached to them. Maybe you need element.id or something similar, then this will be very useful.

Example:

<div data-scope="any">
  <div data-target="hello">Something</div>
  
  <button data-action="click->toggle">hide</button>
</div>
defineScope("any", ({ targets }) => {
  const hello = targets.hello
  
  function toggle({ element }) {
    hello.hidden = !hello.hidden
    //set the button inner text
    element.textContent = hello.hidden === true ? "show" : "hide"
  }
  
  return { toggle }
})

The code above will change the inner text of the button to show or hide according to the condition of the hello element.

  • event

context event does not need to be explained in too much detail, it is the same as the event in the listener parameter.

Example:

<div data-scope="any">
  <label for="name">Your name</label>
  <input type="text" name="name" id="name" data-action="input->run" />
  
  <div data-target="output"></div>
</div>
defineScope("any", ({ targets }) => {
  const output = targets.output
  
  function run({ event }) {
    output.textContent = event.target.value
  }
  
  return { run }
})

The code above will do something like two-way binding, where when you write your name in the input, the output will be updated automatically.

  • params

params is used to retrieve the value from data-* within an element that has a data-action. Think of it like a function parameter. To retrieve it, use the prefix params.[data-name]. For example, if you’re looking for the ID data-id="5", you can retrieve it using params.id. Example:

<div data-scope="any">
  <button data-action="click->getUser" data-id="5">hide</button>
</div>
defineScope("any", () => {
  function getUser({ params }) {
    console.log(Number(params.id))
    //output "5" (string)
  }
})

Note that the resulting value of params.id is a string, and will always be a string. You must manually change it to Number(params.id) as well as the boolean value.