# **Chapter 7: Two Front Files Pattern - Pagination and Edit Form**
> **File Extension Reference:**
> - Gate Files: `*.gate.php` (e.g., `Index.gate.php`)
> - Front Files: `*.front` (e.g., `index_main.front`)
> - Master Files: `*.mast.php` (e.g., `default/master.mast.php`)
> - Front Places: `*.place.front` or `*.place.php`
---
## **7.1 The Two Front Files Pattern**
A common pattern in SartajPhp is using **two Front Files** in one Gate:
1. **List Front File** - Displays data with Pagination Component
2. **Edit Front File** - Form for inserting/updating records
This pattern provides:
- Clean separation between viewing and editing
- Reusable pagination for any table
- Automatic database operations
- Built-in form validation
## **7.2 When to Use This Pattern**
Use when your application needs:
- List view with pagination
- Add new record functionality
- Edit existing record functionality
- Delete functionality
## **7.3 Step 1: Register the Gate**
```php
<?php
// reg.php
registerGate("product", __DIR__ . "/apps/Product.gate.php");
```
## **7.4 Step 2: Create the Gate Class**
```php
<?php
// apps/Product.gate.php
use Sphp\tools\BasicGate;
class Product extends BasicGate
{
private $frtList; // For displaying list with pagination
private $frtEdit; // For add/edit form
public function onstart()
{
// Allow guests to view, members to edit
$this->getAuthenticate("GUEST,MEMBER,ADMIN");
// Create two FrontFile objects
$this->frtList = new \Sphp\tools\FrontFile($this->mypath . "/fronts/product_list.front");
$this->frtEdit = new \Sphp\tools\FrontFile($this->mypath . "/fronts/product_edit.front");
}
// Page Events will go here
}
```
## **7.5 Step 3: Create List Front File with Pagination**
Create `apps/fronts/product_list.front`:
```html
<!-- product_list.front - List View with Pagination -->
<title id="pageTitle" runat="server">Product List</title>
<div class="container">
<div class="row">
<div class="col">
<h1>Products</h1>
</div>
<div class="col-auto">
<!-- Add New Button -->
<a href="product-create.html" class="btn btn-primary">
Add New Product
</a>
</div>
</div>
<!-- Pagination Component - Handles listing and paging -->
<div id="productGrid" runat="server"
path="uikit/data/Pagination.php"
dtable="products"
fun-setFieldNames="name,price,category"
fun-setPerPageRows="10"
fun-setAJAX=""
on-init="true">
<!-- Template for each row - what shows in the table -->
<div class="row align-items-center">
<div class="col-md-4">
<!-- Display name from current row -->
<strong>##{$productGrid->getRow('name')}#</strong>
</div>
<div class="col-md-2">
$##{$productGrid->getRow('price')}#
</div>
<div class="col-md-3">
##{$productGrid->getRow('category')}#
</div>
<div class="col-md-3">
<!-- Action links -->
<a href="product-view-##{$productGrid->getRow('id')}#.html"
class="btn btn-sm btn-info">View</a>
<a href="product-edit-##{$productGrid->getRow('id')}#.html"
class="btn btn-sm btn-warning">Edit</a>
</div>
</div>
</div>
<!-- Page links (next, previous, page numbers) -->
<div id="pageLinks" runas="holder" sphp-comp="productGrid" sphp-comp-prop="page_links"></div>
</div>
```
### **Key Pagination Component Attributes:**
| Attribute | Purpose |
|-----------|---------|
| `dtable` | Database table name |
| `fun-setFieldNames` | Fields to display (comma-separated) |
| `fun-setPerPageRows` | Rows per page |
| `fun-setAJAX` | Enable AJAX pagination |
| `path` | Component path (built-in) |
## **7.6 Step 4: Create Edit Front File with Form**
Create `apps/fronts/product_edit.front`:
```html
<!-- product_edit.front - Add/Edit Form -->
<title id="pageTitle" runat="server">Product Form</title>
<div class="container">
<h1>##{$formTitle}#</h1>
<!-- Product Form -->
<form id="productForm" runat="server" action="product.html">
<!-- Product Name -->
<div class="mb-3">
<label class="form-label">Product Name *</label>
<input id="txtName" runat="server" type="text"
class="form-control"
dtable="products"
dfield="name"
placeholder="Enter product name"
fui-setRequired=""
fui-setMinLen="3"
fui-setMaxLen="100"
fui-setForm="productForm">
</div>
<!-- Price -->
<div class="mb-3">
<label class="form-label">Price *</label>
<input id="txtPrice" runat="server" type="number"
class="form-control"
dtable="products"
dfield="price"
placeholder="0.00"
fui-setRequired=""
fui-setNumeric=""
fui-setForm="productForm">
</div>
<!-- Category Dropdown -->
<div class="mb-3">
<label class="form-label">Category</label>
<select id="sltCategory" runat="server"
class="form-select"
dtable="products"
dfield="category"
fui-setForm="productForm">
<option value="">Select Category</option>
<option value="Electronics">Electronics</option>
<option value="Clothing">Clothing</option>
<option value="Books">Books</option>
<option value="Home">Home</option>
</select>
</div>
<!-- Description -->
<div class="mb-3">
<label class="form-label">Description</label>
<textarea id="txaDescription" runat="server"
class="form-control" rows="4"
dtable="products"
dfield="description"
placeholder="Product description"></textarea>
</div>
<!-- Alert for errors -->
<alert id="formErrors" runat="server" fun-setShowAll=""></alert>
<!-- Buttons -->
<div class="mb-3">
<button type="submit" class="btn btn-primary">Save</button>
<a href="product.html" class="btn btn-secondary">Cancel</a>
<!-- Delete button (only shows when editing) -->
#{if $isEditMode}#
<a href="product-delete-##{$recordId}#.html"
class="btn btn-danger float-end"
onclick="return confirm('Delete this product?')">Delete</a>
#{endif}#
</div>
</form>
</div>
```
### **Key Form Component Attributes:**
| Attribute | Purpose |
|-----------|---------|
| `dtable` | Database table to bind to |
| `dfield` | Database field name |
| `fui-setRequired` | Field is required |
| `fui-setForm` | Bind to form for validation |
## **7.7 Step 5: Add Page Events to Handle Everything**
Now add the Page Events in your Gate:
```php
<?php
// apps/Product.gate.php - Complete
use Sphp\tools\BasicGate;
class Product extends BasicGate
{
private $frtList;
private $frtEdit;
public function onstart()
{
$this->getAuthenticate("GUEST,MEMBER,ADMIN");
$this->frtList = new \Sphp\tools\FrontFile($this->mypath . "/fronts/product_list.front");
$this->frtEdit = new \Sphp\tools\FrontFile($this->mypath . "/fronts/product_edit.front");
}
/**
* URL: product.html
* Display list with pagination
*/
public function page_new()
{
$this->setFrontFile($this->frtList);
}
/**
* URL: product-create.html
* Show empty form for new record
*/
public function page_event_create($evtp)
{
// Set mode properties for FrontFile
$this->frtEdit->addProp("formTitle", "Add New Product");
$this->frtEdit->addProp("isEditMode", false);
$this->frtEdit->addProp("recordId", "");
$this->setFrontFile($this->frtEdit);
}
/**
* URL: product-view-5.html
* View existing record (same as edit but read-only)
*/
public function page_view()
{
$recordId = (int)$this->page->evtp;
// Load data into form
$this->loadRecordToForm($recordId, false);
$this->setFrontFile($this->frtEdit);
}
/**
* URL: product-edit-5.html
* Show form populated with existing data
*/
public function page_event_edit($evtp)
{
$recordId = (int)$evtp;
// Load data into form
$this->loadRecordToForm($recordId, true);
$this->setFrontFile($this->frtEdit);
}
/**
* URL: product.html (POST)
* Handle form submission - insert or update
*/
public function page_submit()
{
// Check validation errors
if (getCheckErr()) {
// Show form again with errors
$this->frtEdit->addProp("formTitle", "Please Fix Errors");
/**
* you can also use setErr to inform other about error in working. This is not
* PHP error so it will not stop PHP execution.
*/
$this->setFrontFile($this->frtEdit);
return;
}
// Determine insert or update based on record ID
if ($this->frtEdit->getComponent("txtId")->getValue() == "") {
// No ID = Insert
$this->page->insertData($this->frtEdit->getComponent("productForm"));
} else {
// Has ID = Update
$this->page->updateData($this->frtEdit->getComponent("productForm"));
}
// Redirect back to list
$this->page->forward(getGateURL("product"));
}
/**
* URL: product-delete-5.html
* Delete a record
*/
public function page_event_delete($evtp)
{
// Require higher permission
$this->getAuthenticate("MEMBER,ADMIN");
$recordId = (int)$evtp;
// Delete from database
$this->dbEngine->executeQuery("DELETE FROM products WHERE id = $recordId");
// Redirect to list
$this->page->forward(getGateURL("product"));
}
/**
* Helper: Load record into form
*/
private function loadRecordToForm($recordId, $isEditMode)
{
$this->dbEngine->connect();
// Fetch from database
$result = $this->dbEngine->executeQuery(
"SELECT * FROM products WHERE id = $recordId"
);
$row = $this->dbEngine->row_fetch_assoc($result);
$this->dbEngine->disconnect();
if (!$row) {
$this->page->forward(getGateURL("product"));
return;
}
// Set form values
$this->frtEdit->getComponent("txtId")->fi_setDefaultValue($row['id']);
$this->frtEdit->getComponent("txtName")->fi_setDefaultValue($row['name']);
$this->frtEdit->getComponent("txtPrice")->fi_setDefaultValue($row['price']);
$this->frtEdit->getComponent("sltCategory")->fi_setDefaultValue($row['category']);
$this->frtEdit->getComponent("txaDescription")->fi_setDefaultValue($row['description']);
// Set properties for display
$this->frtEdit->addProp("formTitle", $isEditMode ? "Edit Product" : "View Product");
$this->frtEdit->addProp("isEditMode", $isEditMode);
$this->frtEdit->addProp("recordId", $recordId);
}
}
```
## **7.8 URL Mapping Summary**
| URL | Event | Action |
|-----|-------|--------|
| `product.html` | page_new | Show list with pagination |
| `product.html` (POST) | page_submit | Save form data |
| `product-create.html` | page_event_create | Show empty form |
| `product-view-5.html` | page_view | Show record (read-only) |
| `product-edit-5.html` | page_event_edit | Show populated form |
| `product-delete-5.html` | page_event_delete | Delete record |
## **7.9 How Database Operations Work**
### **Automatic Insert**
```php
// Gets form Components with dtable/dfield and inserts
$this->page->insertData($this->frtEdit->getComponent("productForm"));
```
This looks at Components in the form:
- `txtName` with `dtable="products" dfield="name"`
- `txtPrice` with `dtable="products" dfield="price"`
- etc.
And generates: `INSERT INTO products (name, price, ...) VALUES (?, ?, ...)`
### **Automatic Update**
```php
// Gets form Components and updates by record ID
$this->page->updateData($this->frtEdit->getComponent("productForm"));
```
Generates: `UPDATE products SET name=?, price=? WHERE id=?`
### **Loading Data for Edit**
SartajPHP provides two approaches for loading data into form components:
#### **Approach 1: Using $this->page->viewData() (Recommended)**
```php
// Auto-populate form from record using database binding
// Components need dfield and dtable attributes
$this->page->viewData($this->frtEdit->getComponent("productForm"), $recordId);
```
**How viewData works internally:**
```php
// viewData iterates over all form children components
$compList = $form->getAllChildren();
foreach ($compList as $compid => $comp) {
// Only process components with dfield attribute and not marked blnDontFill
if ($comp->dfield != '' && !$comp->blnDontFill) {
// Get table from dtable attribute or use default tblName
$table = ($comp->dtable != '') ? $comp->dtable : $this->page->tblName;
// Build SELECT query and fetch data
// Set component value from database row
}
}
```
**Component flags for database binding:**
- `dfield`: Database field name to bind with
- `dtable`: Database table name (optional, uses `$this->page->tblName` by default)
- `blnDontFill`: When set, component won't be filled from database (use `fur-unsetDataFill="true"` in FrontFile)
#### **Approach 2: Manual fetch**
```php
// Manual approach - fetch then set each component
$this->dbEngine->connect();
$result = $this->dbEngine->executeQuery("SELECT * FROM products WHERE id = $id");
$row = $this->dbEngine->row_fetch_assoc($result);
$this->dbEngine->disconnect();
// Set each component manually
$this->frtEdit->getComponent("txtName")->fi_setDefaultValue($row['name']);
$this->frtEdit->getComponent("txtPrice")->fi_setDefaultValue($row['price']);
```
#### **FrontFile Component Database Binding**
```html
<form id="productForm" runat="server">
<!-- dfield = database field, dtable = database table -->
<input id="txtName" runat="server" dfield="name" dtable="products" />
<input id="txtPrice" runat="server" dfield="price" dtable="products" type="number" />
<textarea id="txaDesc" runat="server" dfield="description" dtable="products"></textarea>
<!-- Skip database fill for this component -->
<input id="txtCalculated" runat="server" dfield="total" fur-unsetDataFill="true" />
</form>
```
### **Using Dynamic URLs**
Always use `getThisGateURL()`,`getEventURL()`, `getGateURL()` for dynamic URLs instead of hardcoded paths:
In FrontFile:
```html
<a href="##{getThisGateURL()}#">Add New Product</a>
```
**Why use getThisGateURL()?**
- Generates domain-binded absolute URLs
- Works even if you change the Gate registration name in `registerGate()`
- Handles URL extensions dynamically (configured in comp.php)
## **7.10 Complete File Structure**
```
myproject/
├── reg.php
│
├── apps/
│ ├── Product.gate.php
│ │
│ └── fronts/
│ ├── product_list.front # Pagination view
│ └── product_edit.front # Add/Edit form
│
└── masters/
└── default/
├── master.mast.php
└── menu.place.front
```
## **7.11 Chapter Summary**
1. **Two Front Files Pattern** - List for viewing, Form for editing
2. **Pagination Component** - Use built-in `Pagination.php` with `dtable` and `dtable` attributes
3. **Form Components** - Use `dtable` and `dfield` for database binding
4. **Page Events**:
- `page_new()` - Show list
- `page_event_create()` - Empty form
- `page_event_edit(id)` - Populated form
- `page_view(id)` - Read-only view
- `page_submit()` - Save (auto insert/update)
- `page_event_delete(id)` - Delete
5. **Database Operations** - `page->insertData()`, `page->updateData()`, `page->viewData()`
## **7.12 What's Next?**
Next, learn about Master Files - how to create page layouts with headers, footers, and menus.
---
*Next: Chapter 8 - Master Files: Creating Page Layouts*