What is Structive?

Structive is a framework built on single-file Web Components that offers a structure-driven template syntax designed to eliminate as much boilerplate and state-hook overhead as possible, while still providing a fully declarative UI and reactive state management.

Learn more:
https://github.com/mogera551/Structive
https://github.com/mogera551/Structive-Example

Entry Point

Your entry point is an HTML file, and you’ll need to use an import map to alias your modules:

<span class="na">type="importmap">
{
  "imports": {
    "structive": "path/to/cdn/structive.js",
    "main": "./components/main.st.html"
  }
}




<span class="na">type="module">
import { config, defineComponents } from "structive";

config.enableMainWrapper = false;
config.enableShadowDom   = false;
config.enableRouter      = false;

defineComponents({ "app-main": "main" });

Script

Register your components and tweak framework settings inside a block.

Component Registration & Root Tag

Use defineComponents to map your custom element tag names to their .st.html files. Then include the root component tag (here ) in your .

Configuration Options

Currently Structive does not ship with a built-in router or wrapper, so set:

config.enableMainWrapper = false;
config.enableRouter      = false;
config.enableShadowDom   = false; // or true if you prefer Shadow DOM

Roadmap

Routing and automatic component-loading (“autoload”) support are coming soon.


Components

Each component lives in its own single file, with three sections:

<span class="na">type="module">

We’ll focus on the UI template and the state class.

Structural Paths

Both your template and your state class use structural paths to bind data. Paths use dot notation and support a wildcard * to represent array elements:

  • Full path: user.profile.name
  • Wildcard path: products.*.name

In the template, * refers to the current item inside a for block. In your state class, you can also declare derived state getters using wildcards.


UI Template

Your can include:

  • for blocks for iteration
  • if blocks for conditional rendering
  • Interpolation for embedding values
  • Attribute binding for linking state to DOM properties, classes, attributes, and events

You can also chain filters onto paths (e.g. |locale to format numbers).

{{ for:products }}
      {{ products.*.name }} — {{ products.*.price|locale }}
    {{ endfor: }}
  

  {{ if:user.isLoggedIn }}
    Welcome, {{ user.profile.nickName }}!
  {{ else: }}
    Please log in.
  {{ endif: }}

  Enter your name: 
   type="text" data-bind="value:user.profile.name">

   data-bind="onclick.popup">Click me

for Block

Start with {{ for:LIST_PATH }}, end with {{ endfor: }}. The path must resolve to an array. You don’t declare your own loop variable—inside, use structural paths. An implicit index variable $1 is provided; deeper loops get $2, $3, etc.

{{ for:users }}
  {{ users.*.profile.name }}
{{ endfor: }}

{{ for:makers }}
  
    No: {{ $1|inc,1 }} Maker: {{ makers.*.name }}
  
  {{ for:makers.*.products }}
    
      Product No: {{ $2|inc,1 }} —  
      {{ makers.*.products.name }}  
      ({{ makers.*.products.*.price|locale }})
    
  {{ endfor: }}
{{ endfor: }}

if Block

Use {{ if:CONDITION_PATH }}{{ endif: }}, with optional {{ else: }}. The path must return a boolean. You may apply filters, but not raw expressions.

{{ if:user.isLoggedIn }}
  Hello, {{ user.profile.nickName }}!
{{ else: }}
  Please log in.
{{ endif: }}


{{ if:user.age > 18 }}     
{{ if:user.age|gt,18 }}

Interpolation

Embed state values directly in text with {{ PATH }}. Filters can be chained.

{{ user.profile.nickName }}
{{ user.profile.nickName|uc }} 

{{ for:states }}
  
    {{ states.*.name }}, {{ states.*.population|locale }}
  
{{ endfor: }}

Attribute Binding

Link state paths to DOM element properties, classes, attributes, and events—all via data-bind. Supports two-way binding for inputs.

Property Binding

Certain DOM props are auto two-way: value, checked, etc.

type="text" data-bind="value:user.profile.name">

{{ for:products }}
  
    {{ products.*.name }}
     type="text" data-bind="value:products.*.inventory">
  
{{ endfor: }}

Conditional Class Binding

Toggle a CSS class based on a boolean path.

.adult { color: red; }
data-bind="class.adult:user.isAdult">
  



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  Event Binding
Map element events to methods on your state class. Use on-prefixed method names for clarity.

 data-bind="onclick:onAdd">Add



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  Custom Attribute Binding
Use attr. prefix when you need to set arbitrary HTML/SVG attributes.

 data-bind="attr.points:points">



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  State Class
Your state lives in a default-exported JS class. Define all your state as class properties.

<span class="na">type="module">
export default class {
  fruits = [
    { name: "apple" },
    { name: "banana" },
    { name: "cherry" }
  ];
  count = 0;
  user = {
    profile: {
      name: "Alice",
      age: 30
    }
  };
}




    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  Event Handling
Define methods on your class to handle events. Prefix them with on to distinguish them from utility methods.

{{ count }}
 data-bind="onclick:onIncrement">Increment



    Enter fullscreen mode
    


    Exit fullscreen mode
    





export default class {
  count = 0;
  onIncrement() {
    this.count++;
  }
}



    Enter fullscreen mode
    


    Exit fullscreen mode
    




Inside a for loop, handlers receive the index as a second argument:

{{ for:users }}
  {{ users.*.name }}
   data-bind="onclick:onClick">Click
{{ endfor: }}



    Enter fullscreen mode
    


    Exit fullscreen mode
    





export default class {
  users = [
    { name: "Alice" },
    { name: "Bob" },
    { name: "Charlie" }
  ];
  onClick(e, $1) {
    alert("Clicked index = " + $1);
  }
}



    Enter fullscreen mode
    


    Exit fullscreen mode
    




You can also update the looped item’s state by using a wildcard path:

{{ for:users }}
  
    {{ users.*.name }}
     data-bind="onclick:onToggle">Select
  
{{ endfor: }}



    Enter fullscreen mode
    


    Exit fullscreen mode
    





export default class {
  users = [
    { name: "Alice", selected: false },
    { name: "Bob",   selected: false },
    { name: "Charlie", selected: false }
  ];
  onToggle(e, $1) {
    this["users.*.selected"] = !this["users.*.selected"];
  }
}



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  Update Triggers
Any assignment to a class property via a structural path automatically re-renders the bound DOM. For arrays, use immutable methods (concat, toSpliced, etc.) rather than push/pop.

{{ count }}
 data-bind="onclick:onIncrement">Increment

{{ for:users }}
  
    {{ users.*.name }}
     data-bind="onclick:onDelete">Delete
  
{{ endfor: }}



    Enter fullscreen mode
    


    Exit fullscreen mode
    





export default class {
  count = 0;
  onIncrement() {
    this.count += 1;
  }

  users = [
    { name: "Alice",   selected: false },
    { name: "Bob",     selected: false },
    { name: "Charlie", selected: false }
  ];
  onDelete(e, $1) {
    this.users = this.users.toSpliced($1, 1);
  }
}



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  Derived State Creation
Use getters named with structural paths to compute derived values. Whenever their dependencies change, the UI updates automatically via dependency tracking.

{{ user.profile.name }}  
{{ user.profile.ucName }}
 type="text" data-bind="value:user.profile.name">



    Enter fullscreen mode
    


    Exit fullscreen mode
    





export default class {
  user = {
    profile: {
      name: "Alice",
      age: 30
    }
  };
  get "user.profile.ucName"() {
    return this["user.profile.name"].toUpperCase();
  }
}



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  Derived Structural Paths with Wildcards
You can also create a wildcard-based derived state, as if each array element had a virtual property:

{{ for:users }}
  {{ users.*.ucName }},
   type="text" data-bind="value:users.*.name">
{{ endfor: }}



    Enter fullscreen mode
    


    Exit fullscreen mode
    





export default class {
  users = [
    { name: "Alice", selected: false },
    { name: "Bob",   selected: false },
    { name: "Charlie", selected: false }
  ];
  get "users.*.ucName"() {
    return this["users.*.name"].toUpperCase();
  }
}



    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  Summary of Development

Use the same structural paths in your state class and UI template
Perform all updates via structural paths
Create derived state with getters named as paths
Inside loops, use implicit indices ($1, $2, …)

  
  
  Finally
Any feedback or messages would be greatly appreciated!