# 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 ```js 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: ```js 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 object’s 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: ```js 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: ```js 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 developer’s 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: ```js 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 It’s 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 user’s permissions: ```js 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 Unify’s **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.