Files
Unify/Rendering.md
2025-12-25 11:16:59 +01:00

7.2 KiB
Raw Permalink Blame History

Rendering Data: Render Collections, Lists & Pagination

In Unify, data-driven interfaces, such as tables, lists, or collections, are rendered through a powerful concept: Render Collections.

A Render Collection is a UI component that displays rows of data, with a complete lifecycle from querying the backend to rendering the results, all while maintaining a declarative, JavaScript-centric approach.

1. Define a Render Collection

A render collection consists of two key components:

  • The Row Renderer: A component that defines how each row is visually rendered.
  • The Collection: A component that defines the data source and binds to a backend Table (like news, users, etc.).

Example: Defining a Render Collection

export default class newsListTable extends gridView {

    header = new newsListTableHeader();

    body = new newsListTableBody(
        newsListItem,               // Row Renderer
        new collection(news)        // Backend Data Binding
    );
}

In this example:

  • newsListItem is the row component used to render individual items.
  • new collection(news) binds to the news Table on the backend, providing the data to be displayed.

How it works:

  • The Collection defines the data.
  • The Row Renderer defines how to display each individual row.

2. Syncing Data — Client-Server Integration

Unify makes sure the data remains synced across the client and the backend. You can fetch data on-demand and refresh it easily with the .sync() method:

await this.body.sync();

When the data is synced, the following happens:

  1. The query is sent to the server (via node async filterSearch()).
  2. The server executes the query and returns the rows.
  3. The rows are parsed and displayed in the UI via the Row Renderer.

If the collection is already pre-bound to a table with an id, calling .sync() will also refresh that specific objects details.


3. Pagination — Developer Controlled

Pagination in Unify is developer-controlled. Unlike typical frameworks that handle pagination automatically, you have full control over how the limit, offset, and searching are applied.

Pagination Example:

async update(updatePagination = true) {
    if (updatePagination) {
        this.page = 0;
    }

    // Filtering and pagination logic
    this.numberOfPages = await this.filterSearch(
        this.searchType,
        this.searchTerm,
        this.sort,
        this.direction,
        this.limit,
        this.page
    );

    await this.sync();

    if (updatePagination) {
        this.parents("newsItemPage").tableControl.pagination.create();
    }
}

In this case:

  1. filterSearch() constructs the filter for the query and is called to fetch the rows.
  2. After filtering, pagination is managed manually.
  3. sync() then updates the UI with the results.

This gives you control over pagination logic and how the list is managed.


4. Filtering and Searching — Declarative Querying

In Unify, filtering and searching are handled using logical expressions that are parsed on the server side. You construct the queries using Unify's SQL helpers:

import OR from '/unify/sql/OR.js';
import LIKE from '/unify/sql/LIKE.js';
import GREATER from '/unify/sql/GREATER_OR_EQUAL.js';
import SMALLER from '/unify/sql/SMALLER_OR_EQUAL.js';

node async filterSearch(searchType, searchTerm, order, direction, limit, page) {
    const filter = this.getFilter();

    switch (searchType) {
        case 0:
            filter.search = OR(
                OR(LIKE(filter.title, searchTerm), LIKE(filter.comments.body, searchTerm)),
                LIKE(filter.body, searchTerm)
            );
            break;
        case 1:
            filter.search = GREATER(filter.price, searchTerm);
            break;
        case 2:
            filter.search = SMALLER(filter.price, searchTerm);
            break;
    }

    if (!searchTerm) filter.search = false;

    filter.order = filter[order];
    filter.direction = direction === "desc" ? "desc" : "asc";
    
    filter.limit = parseInt(limit);
    filter.from = parseInt(page * limit);

    await this.get(); // Fetch results

    const numberOfPages = Math.ceil(this.rows.length / limit);
    return numberOfPages;
}

Key Points:

  • Logical operators like OR, AND, and LIKE are used to build declarative filters.
  • Filters are executed directly on the server. The objects are parsed as-is, without the need for string-based queries or manual SQL building.
  • Pagination and sorting logic is fully under the developers control.

The query-building helpers (LIKE, GREATER, SMALLER) are parsed directly by the server. They allow you to declaratively specify complex queries in JavaScript, all while Unify handles the logic and results.


5. Permissions — Controlling Access to Data

Just like Unify Table objects, Render Collections have a permission system that controls who can read, write, or delete data. For example:

permission() {
    this.allow(groups.visitor, "READ");
    this.allow(groups.member, "READ");
    this.allow(groups.admin, "READ");
}

Permissions can control:

  • Which rows are visible to different user roles
  • Which fields inside those rows are visible or editable

Its important to note that filtering does not automatically enforce permissions. You must explicitly validate access rights to each table, column, or row.

For example, before executing an action like deleteRow(), you must check the users permissions:

node async deleteRow(client) {
    const user = client.user;

    if (!user.hasPermission("DELETE_NEWS")) {
        throw new Error("Permission denied");
    }

    this.delete();
}

6. Lifecycle Summary

Action Happens Where Implemented by
Configure UI of list Client Developer
Construct query logic Shared (Client + Server) Developer
Execute SQL on server Server Unify
Deserialize objects Server → Client Unify
Render rows in the UI Client Unify
Handle permissions for rows Server Developer

Unify abstracts away complex data handling. The UI remains declarative and directly synced with the backend, ensuring zero glue code between database and frontend.


Conclusion

Unifys Render Collections provide a simple, declarative way to build dynamic, paginated, filtered lists:

  1. The backend logic remains completely abstracted (no REST APIs, no ORM configuration).
  2. The client UI renders the data based on filter and query objects.
  3. The permissions and security of the data are automatically respected with clear separation between frontend and backend logic.

The system gives you total control over pagination, sorting, searching, and permissions, while Unify handles the syncing, UI updates, and backend query execution for you.