# **Chapter 6: Components - Server-Side UI Building Blocks**
## **6.1 The Component Philosophy: Beyond Widgets**
In SartajPHP, Components are not mere UI widgets—they are **complete server-side objects** that bridge the gap between HTML presentation and PHP business logic. Unlike traditional frameworks where form elements are passive data containers, SartajPHP Components are active participants in the application lifecycle with their own state, behavior, and intelligence.
### **6.1.1 What Makes a Component Special?**
Consider a standard HTML input:
```html
<input type="text" name="username" value="John">
```
This element is:
- **Passive**: Just displays a value
- **Dumb**: No validation logic
- **Insecure**: Raw data exposure
- **Isolated**: No connection to server logic
Now see the same input as a SartajPHP Component:
```html
<input id="txtUsername" runat="server" type="text"
dtable="users"
dfield="username"
fui-setRequired=""
fui-setMinLen="3"
fui-setMaxLen="50"
fui-setForm="userForm"
on-init="true">
```
This Component is:
- **Active**: Participates in validation
- **Intelligent**: Knows its constraints
- **Secure**: Auto-sanitizes data
- **Connected**: Binds to database
- **Lifecycle-aware**: Has initialization events
### **6.1.2 The Component-NodeTag Partnership**
Every Component consists of two inseparable parts:
1. **Component Object** (PHP class)
- Server-side logic and state
- Validation rules and business logic
- Database interaction methods
- Lifecycle event handlers
2. **NodeTag Object** (HTML representation)
- HTML element properties
- DOM structure and relationships
- Rendering control flags
- Client-side attributes
These two objects work together through a well-defined interface:
```
Component (PHP) ↔ NodeTag (HTML)
│ │
├── Sets values ───┤
├── Controls rendering
└── Handles events
```
## **6.2 Component Types and Hierarchy**
SartajPHP provides a comprehensive hierarchy of Components for different purposes.
### **6.2.1 Built-in Component Classes**
```
Sphp\tools\Component (Base Class)
├── Sphp\Comp\Form\HTMLForm (Forms)
├── Sphp\Comp\Form\TextField (Text inputs)
│ ├── EmailField (Email inputs)
│ ├── PasswordField (Password inputs)
│ └── HiddenField (Hidden inputs)
├── Sphp\Comp\Form\TextArea (Textareas)
├── Sphp\Comp\Form\Select (Dropdowns)
├── Sphp\Comp\Form\CheckBox (Checkboxes)
├── Sphp\Comp\Form\Radio (Radio buttons)
├── Sphp\Comp\Form\DateField (Date pickers)
├── Sphp\Comp\Form\FileUploader (File uploads)
├── Sphp\Comp\Html\Alert (Alert/Message displays)
├── Sphp\Comp\Html\Img (Images)
├── Sphp\Comp\Html\Title (Page titles)
├── Sphp\Comp\Server\IncludeFront (Include other FrontFiles)
├── Sphp\Comp\Server\IncludePlace (Include placeholders)
├── \Pagination (Data pagination)
├── \DataGrid (Data tables)
├── Sphp\Comp\Server\ForLoop (Loop structures)
└── Sphp\Comp\Tag (Generic tag handler)
```
### **6.2.2 Component Categories**
**Form Components** (User input):
```html
<input id="txtName" runat="server" type="text">
<textarea id="txaBio" runat="server"></textarea>
<select id="sltCountry" runat="server" fun-setOptions="Name1,Name2"></select>
<input id="chkAgree" runat="server" type="checkbox">
<input id="radGender" runat="server" type="radio">
<input id="datBirth" runat="server" type="date">
<input id="fleAvatar" runat="server" type="file">
```
**Display Components** (Output):
```html
<alert id="msgSuccess" runat="server"></alert>
<title id="pageTitle" runat="server">Default</title>
<img id="imgLogo" runat="server" src="logo.png">
```
**Container Components** (Layout):
```html
<div id="contentArea" runat="server"></div>
<span id="statusLabel" runat="server"></span>
<include id="header" runat="server" fui-setFrontFile="mypath/address.front"></include>
```
**Data Components** (Collections):
```html
<div id="dataGrid" runat="server" path="uikit/data/Grid.php" fun-setFieldNames="userid,city">
<div runas="holder" sphp-comp="dataGrid" dfield="userid"></div>
<div>##{$dataGrid->getRow('id')}#</div>
</div>
<div id="pagination" runat="server" path="uikit/data/Pagination.php" fun-setFieldNames="customer_name,city" dtable="customer">
<div runas="holder" sphp-comp="pagination" dfield="customer_name"></div>
<div>##{$dataGrid->getRow('city')}#</div>
</div>
<div runas="holder" sphp-comp="pagination" sphp-comp-fun="getPageBar">display here page links</div>
<div id="forLoop" runat="server" path="slibpath/comp/server/ForLoop.php" fun-setLoopTo="5">
<span>##{'Counter:- ' . $forLoop->counter}#</span>
</div>
<div id="forEach" runat="server" path="slibpath/comp/server/ForEachLoop.php" fun-setObject="myarray">
<span>##{'Key Item:- ' . $foreach->key . ' : ' . $foreach->getItem()}#</span>
</div>
```
### **6.2.3 Component Validations**
#### Form Binding
* `fui-setForm="form_id"` binds Controls to a Form
* Enables automatic client-side validation
* Errors can be displayed using `<alert>` Controls
#### Example: Form with Alert frtMain Front File Object
```html
<alert id="form1_err" runat="server"></alert>
<form id="form1" runat="server" action="##{getThisGateURL()}#">
<input id="txtname" runat="server" type="text"
fui-setForm="form1"
fui-setRequired=""
fui-setMinLen="4"
fui-setMaxLen="20"
placeholder="Name" />
<input type="submit" value="Submit" />
</form>
```
#### Server-side Handling (BasicGate)
```php
public function page_insert(){
if(!getCheckErr()){
echo "Form Submitted Successfully<br>";
echo "Name: " . $this->Client->post("txtname");
// OR
echo "OR Name: " . $this->frtMain->getComponent("txtname")->getValue();
} else {
setErr("form1_err","Form Submission Failed");
$this->setTempFile($this->frtMain);
}
}
```
#### Form Submission Patterns
##### Standard HTTP POST
* Uses `page_insert`
* Validation handled automatically via `fuisetForm`
##### AJAX Submission
* Add `fun-setAJAX=""` to `<form>`
* Use `page_event_*`
* Response sent via `JSServer`
```php
public function page_insert(){
if(!getCheckErr()){
$this->JSServer
->addJSONHTMLBlock("form1_err","Form Submitted Successfully");
} else {
$this->JSServer
->addJSONHTMLBlock("form1_err","Form Submission Failed");
}
}
```
---
##### WebSocket Form Submission (NativeGate)
* `fun-setSocket="app,event,param"` binds the Form to a **NativeGate**
* Not supported in BasicGate
* Browser communicates via WebSocket
```html
<form id="form1" runat="server"
fun-setSocket="myapp1,event1,event_param">
...
</form>
```
**Server-side:**
* `onwscon`
* `onwsdiscon`
* `page_event_*`
---
### Form Controls Overview
SartajPHP provides multiple form controls:
* `TextField` → text, email, password, numeric
* `Textarea` → multi-line text input
* `Select` → dropdowns with options
* `Radio` → radio button groups
* `Checkbox` → single/multi-option
* `File` → file upload with validations
* `Date` → date picker with min/max
* `Alert` → display error messages
Controls can handle both client-side and server-side validation.
---
### TextField and Textarea
**Attributes:**
* `placeholder` → used in validation messages
* `fui-setForm` → bind to form for client-side validation
* `fui-setRequired`, `fui-setMinLen`, `fui-setMaxLen`
**Example – TextField**
```html
<input id="txtname" runat="server" type="text"
fun-setStyler="2" fui-setForm="form2"
placeholder="Name" fui-setRequired=""
fui-setMinLen="4" fui-setMaxLen="20" />
```
**Example – Textarea**
```html
<textarea id="txadetails" runat="server" fui-setForm="form2"
placeholder="Details" fui-setRequired="" fui-setMinLen="20" fui-setMaxLen="80"></textarea>
```
---
### File Control
**Attributes:**
* `fui-setFileTypesAllowed` → allowed MIME types
* `fui-setFileSavePath` → save path
* `fui-setFileMaxLen` / `fui-setFileMinLen` → file size limits
**Example – File Upload**
```html
<input id="flea1" type="file" runat="server"
fui-setFileTypesAllowed="image/pjpeg,image/jpeg,image/gif,image/png"
fui-setFileSavePath="masters/imgs/##{$flea1->getFileName() . '.' . $flea1->getFileExtension()}#"
placeholder="Profile Image" fui-setForm="form2"
fui-setFileMaxLen="2000000" fui-setFileMinLen="10000" />
```
---
### Radio and Checkbox
**Radio Button Group**
```html
<!-- Create Radio Buttons Group with fi_setOptions method -->
<input id="radbook" runat="server" type="radio" fui-setForm="form2"
fui-setOptions="Book1,Book2,Book3" checked="Book2" disabled="Book3"
placeholder="Choose Book" fui-setRequired="" />
```
**Checkbox**
```html
<input id="chkagree" type="checkbox" runat="server"
fui-setForm="form2" placeholder="I Agree Terms" fui-setRequired="" />
```
---
### Date Control
**Attributes:**
* `fui-setDateMin` / `fui-setDateMax` → date range
* `fui-setNumMonths` → months visible in calendar
* `fui-setAppendText` → display format
**Example – Correct Date Control**
```html
<input type="date" id="datshipped" runat="server" fui-setForm="form2"
placeholder="Shippment Date" fui-setDateMin="01-10-2025"
fui-setDateMax="31-12-2025" fui-setNumMonths="2"
fui-setAppendText="dd-mm-yyyy" />
```
---
### Select Control
**Options Binding:**
* `fun-setOptionsJSON` → JSON string `[["1","Clothes"],["2","Electronics"]]`
* `fun-setOptions` → comma-separated list
* `fu_setOptionsArray` / `fu_setOptionsFromTable` → server-side
**Example – AJAX-dependent Select**
```html
<select id="sltcountry" runat="server" fui-setForm="form2"
fun-submitAJAX="change" fui-setNotValue="__Select__"
fui-setFirstValue="__Select__" fun-setOptions="Country1,Country2,Country3"></select>
```
**Server-side – populate dependent Select**
```php
public function page_event_sltcountry_change($evtp){
$lst = "state1,state2";
switch($this->frtMain->getComponent('sltcountry')->getValue()){
case "Country2": $lst = "2state1"; break;
case "Country3": $lst = "3state1"; break;
}
$this->frtMain->getComponent('sltstate')->setOptions($lst);
$this->JSServer->addJSONCompChildren($this->frtMain->getComponent('sltstate'),"sltstate");
}
```
---
### Dynamic Control Validation
* `placeholder` used as label in validation
* `setMsgName` alternative for custom labels
* `fui-setForm` attribute required for client-side form validation
* Components Tag without `fui-setForm` attribute can still handle server-side validation
---
## **6.3 Component Lifecycle and Events**
Understanding the Component lifecycle is crucial for effective development.
### **6.3.1 Complete Lifecycle Sequence**
```
1. FrontFile Parsing
├── HTML parsed into NodeTags
├── runat="server" tags detected
├── Component objects instantiated
└── NodeTag bound to Component ($this->element)
2. Parse Phase Initialization
├── Component::oninit() event
├── App::comp_*_on_init() hook (if on-init="true")
├── Fusion attributes with fui- prefix executed
└── Component::oncreate() event
3. Request Processing
├── Component values populated from request
├── Validation executed (if form submitted)
├── Gate logic interacts with Components
└── Component state updated
4. Render Phase
├── Component::onprerender() event
├── Fusion attributes with fur- prefix executed
├── Component::onrender() event
├── Component generates HTML output
└── Component::onpostrender() event
5. Cleanup
└── Component resources released
```
### **6.3.2 Lifecycle Events in Detail**
**Component Internal Events:**
```php
class MyComponent extends \Sphp\tools\Component {
// Called during initialization
public function oninit() {
// Setup default values
// Initialize resources
}
// Called after Component is fully created
public function oncreate() {
// All attributes are parsed
// Fusion methods have been called
}
// Called before rendering starts
public function onprerender() {
// Last chance to modify state before output
}
// Called during HTML generation
public function onrender() {
// Generate Component-specific HTML
// Can modify NodeTag output
}
// Called after rendering completes
public function onpostrender() {
// Cleanup temporary resources
// Finalize output
}
}
```
**Gate Hook Events (from FrontFile):**
```html
<!-- FrontFile triggers Gate hooks -->
<input id="txtUser" runat="server" on-init="true" on-create="true">
```
```php
// In Gate class
public function comp_txtUser_on_init($evtp) {
// $evtp contains event data
$component = $evtp['obj']; // Component instance
$element = $evtp['element']; // NodeTag instance
// Modify Component before rendering
$component->fu_setDefaultValue("Guest");
}
public function comp_txtUser_on_create($evtp) {
// Component fully created
// All Fusion attributes processed
}
```
### **6.3.3 Lifecycle Timing Diagram**
```
Time ───────────────────────────────────────►
│ │
FrontFile │ Parse HTML │
Parsing │ Detect runat="server" │
│ Create Components │
│ Bind NodeTags │
├───────────────────────────────────────┤
Parse │ Component::oninit() │
Phase │ App::comp_*_on_init() │
│ fui-* Fusion execution │
│ Component::oncreate() │
│ App::comp_*_on_create() │
├───────────────────────────────────────┤
Request │ Populate values from $_POST/$_GET │
Processing│ Execute validation │
│ Gate PageEvent logic │
├───────────────────────────────────────┤
Render │ Component::onprerender() │
Phase │ fur-* Fusion execution │
│ Component::onrender() │
│ Generate HTML output │
│ Component::onpostrender() │
└───────────────────────────────────────┘
```
## **6.4 Component Properties and Methods**
### **6.4.1 Core Properties**
Every Component has these essential properties:
```php
// Component identity
$this->id; // "txtUsername" (from id attribute of NodeTag)
$this->name; // Same as id, used for name of Object
$this->tagName // use for HTML Tag Node name
$this->HTMLName // use for HTML Tag's name attribute
$this->HTMLID // use for HTML Tag's id attribute
$this->element; // Bound NodeTag object
$this->parentobj // Parent Component Object
$this->frontobj // parent Front File Object
$this->mypath // Component Directory Path
$this->myrespath // Component Directory URL
// State properties
$this->value; // Current value (posted or default)
$this->defvalue; // Initial value
$this->issubmit; // Browser Post this Component
// Database properties (if bound)
$this->dtable; // Database table name
$this->dfield; // Database field name
// Rendering properties
$this->styler // Integer number Change output Layout design
$this->renderMe; // Whether Component render or not
$this->renderTag; // Whether Component Tag render or not
$this->visible; // Whether Component hidden or visible
```
### **6.4.2 Common Methods**
**Value Management:**
```php
// Get/set value
$value = $component->getValue();
$component->value = "New Value"; // not safe, can over write Browser Post value
$component->fu_setValue("Via Fusion"); // Safe, ignore if Browser Post Value
// Default values
$component->fi_setDefaultValue("Via fui-*"); // set Initial value
// Value transformation not all component supported
$component->getSqlSafeValue(); // Manually escaped value for database
```
**Validation Methods:**
```php
// Check validation
if (!getCheckErr()) {
// Valid data
}
// Get validation errors
$errors = getErrMsg("");
// Validation rules (called via fui-* attributes)
$component->fi_setRequired(); // Field is required
$component->fi_setEmail(); // Must be valid email
$component->fi_setNumeric(); // Must be numeric
$component->fi_setMinLen(3); // Minimum length
$component->fi_setMaxLen(50); // Maximum length
$component->fi_setMatch("otherField"); // Match another field
```
**Rendering Control:**
```php
// Show/hide Component
$component->setVisible(); // Make visible
$component->unsetVisible(); // Make invisible
$component->fu_setrender(); // Make render
$component->fu_unsetrender(); // Make no output
// HTML manipulation
$component->setInnerHTML("Content");
$component->setOuterHTML("<div>Replacement</div>");
$component->setPreTag("<span>Before</span>");
$component->setPostTag("<span>After</span>");
// HTMLTag Manipulation through element property
// set pre component tag html
$component->setPreTag('<span>Info Page</span><div>');
// set post component tag html
$component->setPostTag('</div>');
// set html before children of component tag
$component->element->setInnerPreTag('<div>Title</div>');
// set html after children of component tag
$component->element->setInnerPostTag('<div>Info End</div>');
// set Inner html and replace all children of component tag
$component->setInnerHTML('<div>Info End</div>');
// append Inner html of component tag
$component->appendHTML('<div>Info End</div>');
// wrap component tag as child
$component->appendHTML('<div class="card"></div>');
// wrap Inner html of component tag
$component->appendHTML('<div class="card-body"></div>');
// Class and style through attributes
// set attribute of component tag
$component->setAttribute("style","color: red;");
// remove attribute of component tag
$component->removeAttribute('class');
// check exist attribute of component tag
$component->element->hasAttribute('class');
// set Initial attribute value of component tag
$component->element->setDefaultAttribute('class','card');
// append attribute value of component tag
$component->element->appendAttribute('class','btn-primary');
```
**Database Operations:**
```php
/** Database Bind Operations, Take Care by
* SphpBase::page()->insert_data, update_data and view_data methods
*/
// Manually bind
$component->bindToTable($table,$field);
$component->bindToTable("users", "username");
/** Forcefully bind with Database from take values in Front File
* Only available if dtable/dfield set
*/
$component->setDataBound();
// remove Database binding
$component->unsetDataBound();
// get Database Binding Status, true or false
$component->hasDatabaseBinding();
// Stop Insert to Database according to Permissions
$component->fi_setDontInsert("GUEST,MEMBER");
// Stop Update to Database according to Permissions
$component->fi_setDontUpdate("GUEST,MEMBER");
// In App, with page helper
$this->page->viewData($component); // Auto-populate all Children from DB
$this->page->insertData($component); // Insert All Children Component values
$this->page->updateData($component); // Update All Children Component values
```
## **6.5 Fusion Attributes: Component Control from FrontFile**
Fusion attributes are the bridge between FrontFile markup and Component methods.
### **6.5.1 Fusion Prefixes and Timing**
| Prefix | Execution Phase | Purpose |
|--------|----------------|---------|
| `fui-` | Parse Phase | Initial setup, validation rules |
| `fun-` | Auto-decided | fi_=Parse, fu_=Render |
| `fur-` | Render Phase | Output generation, dynamic values |
**Parse Phase (`fui-`)** - Runs once during initialization:
```html
<input runat="server"
fui-setRequired=""
fui-setEmail=""
fui-setDefaultValue="user@example.com">
```
**Render Phase (`fur-`)** - Runs every render cycle:
```html
<input runat="server"
fur-setValue="##{$currentUser->email}#">
```
**Auto (`fun-`)** - Framework decides based on type of method prefix fi_ or fu_:
```html
<!-- fi_ method call - runs in Parse Phase -->
<input runat="server" fun-setDefaultValue="Static Value">
<!-- fu_ method call - runs in Render Phase -->
<input runat="server" fun-setValue="other value">
```
### **6.5.2 Common Fusion Methods**
**TextField Component (`Sphp\Comp\Form\TextField`):**
```html
<input id="txtExample" runat="server"
type="text"
<!-- Validation -->
fui-setRequired=""
fui-setEmail=""
fui-setNumeric=""
fui-setMinLen="3"
fui-setMaxLen="50"
fui-setMatch="txtConfirm"
<!-- Configuration -->
placeholder="Email Address"
fui-setForm="myForm"
<!-- Values -->
fui-setDefaultValue="example@test.com"
fur-setValue="##{$userEmail}#"
<!-- Events -->
on-init="true"
on-create="true">
```
**Select Component (`Sphp\Comp\Form\Select`):**
```html
<select id="sltCountry" runat="server"
<!-- Options -->
fun-setOptions="USA,Canada,Mexico,UK"
fun-setOptions="##{$countryList}#"
fun-setOptionsFromTable="city_name,|state,|cities,|WHERE city_name LIKE '%missi%',|,|300"
fu_setOptionsJSON='[["0","Parent"], ["1", "Child"]]'
<!-- Selection -->
fui-setDefaultValue="us"
fur-setValue="##{$userCountry}#">
fur-setFirstValue="--SELECT--"
fur-enableSearch=""
<!-- Validation -->
fui-setNotValue="--SELECT--"
</select>
```
**CheckBox Component (`Sphp\Comp\Form\CheckBox`):**
```html
<input id="chkNewsletter" runat="server"
type="checkbox"
fui-setRequired=""
fui-setChecked="true"
fur-setChecked="##{$user->wantsNewsletter}#">
```
### **6.5.3 The Special `_` Fusion Method**
The `_` method is a generic setter for any attribute:
```html
<!-- Set the "class" attribute -->
<div id="myDiv" runat="server"
fur-_class="##{$statusClass}#">
</div>
<!-- Set the "style" attribute -->
<div runat="server"
fur-_style="color: ##{$textColor}#; font-size: ##{$fontSize}#px;">
</div>
<!-- Set custom data attributes -->
<div runat="server"
fur-_data-user-id="##{$userId}#"
fur-_data-role="admin">
</div>
```
This is equivalent to:
```php
$component->element->setAttribute("class", $value);
$component->element->setAttribute("style", $value);
$component->element->setAttribute("data-user-id", $value);
```
## **6.6 Database Binding and Operations**
One of Component's most powerful features is automatic database integration.
### **6.6.1 Basic Database Binding**
```html
<!-- Bind to users table, username field -->
<input id="txtUsername" runat="server"
dtable="users"
dfield="username"
fui-setRequired=""
fui-setMinLen="3">
<!-- Bind to multiple fields in same table -->
<input id="txtEmail" runat="server"
dtable="users"
dfield="email"
fui-setRequired=""
fui-setEmail="">
<input id="txtPassword" runat="server"
dtable="users"
dfield="password_hash"
fui-setRequired=""
fur-setPassword="">
```
### **6.6.2 Automatic CRUD Operations**
With database-bound Components, CRUD becomes trivial:
**In Gate class:**
```php
// Create (Insert)
public function page_insert() {
// Components automatically read posted values
// Values automatically sanitized
// pass form component to process database binding on all children
$this->page->insertData($this->frtMain->getComponent("userForm")); // Inserts into bound tables
// Redirect or show success
$this->page->forward(getEventURLSecure("view", $this->getInsertId()));
}
// Read (View)
public function page_view() {
$recordId = (int)$this->page->evtp;
// Auto-populate Components from database
// pass form component to process database binding on all children
$this->page->viewData($this->frtMain->getComponent("userForm"), $recordId);
$this->setFrontFile($this->frtMain);
}
// Update
public function page_update() {
// Components have values from form
// Update based on existing record detection
// pass form component to process database binding on all children
$this->page->updateData($this->frtMain->getComponent("userForm")); // Updates bound tables
$this->page->forward(getEventURLSecure("view", $this->getUpdateId()));
}
// Delete
public function page_delete() {
$recordId = (int)$this->page->evtp;
$this->page->deleteRec($recordId); // Deletes from bound tables
$this->page->forward(getGateURL("user"));
}
```
### **6.6.3 Manual Database Operations**
For more control, work with Components directly:
```php
// Get value formatted for database
$dbValue = $component->getSqlSafeValue();
// Set value from database result
$component->setFromDatabase($row);
// Check if Component has database binding
if ($component->hasDatabaseBinding()) {
$table = $component->dtable; // if empty then use SphpBase::page()->tblName property
$field = $component->dfield; // if empty then don't bind with database
// Build SQL manually
SphpBase::dbEngine()->connect(); // if not connected then connect
// component value are safe but still can break query
// so use SphpBase::dbEngine()->cleanQuery is more safer but need DB connection
$sql = "INSERT INTO $table ($field) VALUES ('" . SphpBase::dbEngine()->cleanQuery($component->getValue()) ."')";
SphpBase::dbEngine()->executeQuery($sql);
// or use easy other methods
// generate sql from inserSQL method
$sql = SphpBase::dbEngine()->insertSQL([$field=>$component->getValue()],$table);
SphpBase::dbEngine()->executeQuery($sql);
// or use single method runSQL
$inserted_id = SphpBase::dbEngine()->runSQL($table,[$field=>$component->getValue()]);
}
```
### **6.6.4 Complex Binding Scenarios**
**Multiple table binding:**
```html
<!-- Here we need frame component. Frame Component work like form container on server side
but client side it is just a parent HTML div tag.
User table wrap in parent tbluser frame component -->
<frame id="tbluser" runat="server">
<input id="txtUsername" type="text" runat="server" dtable="users" dfield="username">
</frame>
<!-- Profile table (same form) -->
<frame id="tblprofile" runat="server">
<input id="txtBio" type="text" runat="server" dtable="user_profiles" dfield="bio">
<input id="txtuser_id" type="hidden" runat="server" dtable="user_profiles" dfield="user_id">
</frame>
<!-- In App: -->
public function page_insert() {
/**
* We can also use manual execute query with SphpBase::dbEngine()->runSQL() but here we
* use SphpBase::page()->insertData() method.
*/
// Insert into users table
$userId = $this->page->insertData($this->frtMain->tbluser);
// Insert into profiles with user_id
$this->frtMain->getComponent("txtBio")->setValue($userId);
// Insert into tblprofile table
$this->page->insertData($this->frtMain->tblprofile);
}
```
**Conditional binding:**
```php
// In Gate lifecycle event
public function comp_txtRole_on_init($evtp) {
$component = $evtp['obj'];
// Only bind to database for admin users
if ($this->page->isAuthenticate("ADMIN")) {
// disable all DB operations (view,insert,update) if not ADMIN
$component->bindToTable("users", "role");
}
}
```
## **6.7 Creating Custom Components**
When built-in Components aren't enough, create your own.
### **6.7.1 Basic Custom Component**
```php
// components/StarRating.php
namespace MyApp\Components;
class StarRating extends \Sphp\tools\Component {
private $maxStars = 5;
private $currentRating = 0;
public function oncreate($element){
if($this->issubmit){
// when hidden field submit as Component Name then Component Class read value
// so restore currentRating from value submitted
$this->setValue($this->value);
}
}
// Parse Phase Fusion method
public function fi_setMaxStars($value) {
$this->maxStars = (int)$value;
}
// Render Phase Fusion method
public function fu_setRating($value) {
$this->currentRating = (float)$value;
}
// Override render method
public function onrender() {
$html = '<div class="star-rating">';
for ($i = 1; $i <= $this->maxStars; $i++) {
$filled = $i <= $this->currentRating;
$class = $filled ? 'star-filled' : 'star-empty';
$html .= "<span class='$class' data-rating='$i'>★</span>";
}
$html .= '</div><input type="hidden" name="'. $this->name .'" value="'. $this->currentRating .'" />';
// remove name from Component Tag becuase use in hidden field
$this->setHTMLName(""); // remove HTML Tag name attribute from output
// Set as Component output
$this->element->setInnerHTML($html);
// change Tag Name as Valid HTML Tag
$this->tagName = "div";
}
// Handle posted value in oncreate
public function getValue() {
return $this->currentRating;
}
public function setValue($value) {
$this->currentRating = min($this->maxStars, max(0, (float)$value));
}
}
```
**Usage in FrontFile:**
```html
<star-rating id="productRating" runat="server"
path="components/StarRating.php"
fui-setMaxStars="10"
fur-setRating="##{$product->rating}#">
</star-rating>
```
### **6.7.2 Component with JavaScript**
```php
// components/AutoComplete.php
class AutoComplete extends \Sphp\Comp\Form\TextField {
private $sourceUrl = '';
private $minChars = 2;
public function fi_setSource($url) {
$this->sourceUrl = $url;
}
public function fi_setMinChars($chars) {
$this->minChars = (int)$chars;
}
public function onrender() {
// Call parent to render input field
parent::onrender();
// always use $this->name on server side to create id and name attributes of HTML Tag
// Add autocomplete container
$container = '<div class="autocomplete-results"
id="' . $this->name . '_results"></div>';
$this->element->setPostTag($container);
// Add JavaScript initialization
$js = "
$('#{$this->name}').autocomplete({
source: '{$this->sourceUrl}',
minChars: {$this->minChars},
containerId: '{$this->name}_results'
});
";
addHeaderJSCode($this->name . '_autocomplete', $js);
}
}
```
### **6.7.3 Component Resource Management**
Components can manage their own CSS/JS resources:
```php
class DateRangePicker extends \Sphp\tools\Component {
public function onjsrender() {
// Load Component-specific CSS
addFileLink($this->myrespath . '/daterangepicker.css');
// Load Component-specific JS
addFileLink($this->myrespath . '/daterangepicker.js');
/** SphpBase::SphpJsM() manage only few required css,js libraries. Bootstrap 5, jQuery 3,
* fontawesome 6 are included for all projects. Other Library jQuery UI and jQuery-mobile we can add
* according to requirements. But For all other libraries it is Component responsibilities to use
* downloaded files or CDN links or Custom Master File responsibilities to download all required libraries.
* So SartajPHP don't provide all libraries sources, it is only provide management API. So you can
* use addFileLink function to inject js or css files.
*/
// Load dependency (jQuery UI)
SphpBase::SphpJsM()::addjQueryUI();
}
public function onrender() {
$startId = $this->name . '_start';
$endId = $this->name . '_end';
$html = "
<div class='date-range-picker'>
<input type='text' id='{$startId}' class='date-start'>
<span>to</span>
<input type='text' id='{$endId}' class='date-end'>
</div>
";
$this->element->setInnerHTML($html);
// Initialize picker
$js = "$('#{$startId}, #{$endId}').daterangepicker();";
addHeaderJSFunctionCode("ready",$this->name,$js);
}
}
```
## **6.8 Advanced Component Patterns**
### **6.8.1 Composite Components**
Components that contain other Components:
```php
class AddressForm extends \Sphp\tools\Component {
private $streetComponent;
private $cityComponent;
private $stateComponent;
/** @var \Sphp\tools\FrontFile */
private $front1 = null;
public function oninit() {
// Create child Components programmatically
$str1 = '<input type="text" id="street" runat="server" placeholder="Street Address" />';
$str1 .= '<input type="text" id="city" runat="server" placeholder="City" />';
$str1 .= '<input type="text" id="state" runat="server" placeholder="State" />';
$str1 .= '<p>Access Parent Front File Component: ##{$'. $this->name .'->getAttribute("class")}#</p>';
// private Front File, Components can't access in Parent FrontFile
//$this->front1 = $this->createFrontObjectPrivate($str1,true);
// share Front File, Components can access in Parent FrontFile
// and also parent components can access in child FrontFile
// database binding also works in shared FrontFile
// shared FrontFile use prefix as "name + _" of parent to avoid id/name conflicts
$this->front1 = $this->createFrontObjectShare($str1,true);
$this->streetComponent = $this->front1->getComponent($this->name. '_street');
$this->cityComponent = $this->front1->getComponent($this->name. '_city');
$this->stateComponent = $this->front1->getComponent($this->name. '_state');
// parent FrontFile can access child Front File Components with proper prefix
$this->frontobj->getComponent($this->name.'_street')->fi_setDefaultValue('84 Bell');
$this->stateComponent->fi_setDefaultValue('Ohio');
// ... more child Components
}
public function onrender() {
// Render child Components
$str1 =$this->front1->ProcessMe();
$this->setInnerHTML($str1);
}
public function getValue() {
// Return composite value
return [
'street' => $this->streetComponent->getValue(),
'city' => $this->cityComponent->getValue(),
'state' => $this->stateComponent->getValue()
];
}
public function setValue($data) {
// Distribute composite value
if (is_array($data)) {
$this->streetComponent->setValue($data['street'] ?? '');
$this->cityComponent->setValue($data['city'] ?? '');
$this->stateComponent->setValue($data['state'] ?? '');
}
}
}
```
### **6.8.2 Data-Aware Components**
Components that fetch their own data:
```php
class CategorySelect extends \Sphp\Comp\Form\Select {
// query execute on render time save useless processing when no rendering required
public function onprerender() {
// Fetch categories from database
$db = SphpBase::dbEngine();
$categories = $db->executeQuery(
"SELECT id, name FROM categories WHERE active = 1 ORDER BY name"
);
// Build options array
$options = [];
foreach ($categories as $cat) {
$options[] = [$cat['id'],$cat['name']];
}
// Set options
$this->setOptionsKeyArray();
$this->setOptionsArray($options);
parent::onprerender();
}
}
```
### **6.8.3 Stateful Components**
Components that maintain state across requests:
```php
class ShoppingCart extends \Sphp\tools\Component {
private $sessionKey;
public function oninit() {
$this->sessionKey = 'cart_' . $this->id;
// Initialize from session
if (!(SphpBase::sphp_request()->isSession($this->sessionKey))) {
SphpBase::sphp_request()->session($this->sessionKey, []);
}
}
public function addItem($productId, $quantity = 1) {
$cart = SphpBase::sphp_request()->session($this->sessionKey);
if (isset($cart[$productId])) {
$cart[$productId] += $quantity;
} else {
$cart[$productId] = $quantity;
}
SphpBase::sphp_request()->session($this->sessionKey, $cart);
}
public function getValue() {
return SphpBase::sphp_request()->isSession($this->sessionKey) ?? [];
}
public function setValue($cartData) {
SphpBase::sphp_request()->session($this->sessionKey, $cartData);
}
public function onrender() {
$cart = $this->getValue();
$itemCount = array_sum($cart);
$total = $this->calculateTotal($cart);
$html = "
<div class='shopping-cart'>
<span class='item-count'>{$itemCount} items</span>
<span class='cart-total'>\${$total}</span>
<a href='##{getGateURL('cart')}#'>View Cart</a>
</div>
";
$this->element->setInnerHTML($html);
}
}
```
## **6.9 Component Validation System**
### **6.9.1 Built-in Validation Rules**
```html
<!-- Required field -->
<input id="txt1" type="text" runat="server" fui-setRequired="">
<!-- Email validation -->
<input id="eml1" type="text" runat="server" fui-setEmail="">
<!-- Numeric validation -->
<input id="num1" type="text" runat="server" fui-setNumeric="">
<!-- Length constraints -->
<input id="txt1" type="text" runat="server" fui-setMinLen="3">
<input id="txt1" type="text" runat="server" fui-setMaxLen="50">
<!-- Match another field -->
<input id="pasPassword" type="password" runat="server">
<input id="pas1" type="password" runat="server" fui-setMatch="txtPassword">
<!-- Custom pattern -->
<input id="txt1" type="text" runat="server" fui-setPattern="[A-Za-z]{3,}" path="mycomp.php">
<!-- Date range -->
<input id="dat1" runat="server" type="date"
fui-setDateMin="2024-01-01"
fui-setDateMax="2024-12-31">
```
### **6.9.2 Custom Validation Methods**
```php
include_once(SphpBase::sphp_settings()->comp_uikit_path . "/form/TextField.php");
class CustomTextField extends \Sphp\Comp\Form\TextField {
private $usernameFormat = false;
private $uniqueUsername = false;
// User Name Format validation method
public function fi_setUsernameFormat() {
// true = User Name Format validation enabled
$this->usernameFormat = true;
// check server side validation when Component is submitted
if ($this->issubmit) {
// Must start with letter
// check server side validation
if (!preg_match('/^[a-zA-Z]/', $this->value)) {
// inform user and Framework about the Validation error
$this->setErrMsg( $this->getAttribute("msgname") . " Username must start with a letter");
}
// Only letters, numbers, underscore
// check server side validation
if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->value)) {
// inform user and Framework about the Validation error
$this->setErrMsg( $this->getAttribute("msgname") . " Username can only contain letters, numbers, and underscore");
}
}
}
/** User Name Existence validation (check against database)
* we will check database on client side validation with
* use AJAX call to server event and handle in this file
*/
public function fi_setUniqueUsername() {
// true = Unique User Name validation enabled
$this->uniqueUsername = true;
// check server side validation when Component is submitted
if ($this->issubmit) {
if ($this->findUniqueUsername()) {
// inform user and Framework about the Validation error
$this->setErrMsg($this->getAttribute("msgname") . " Username already taken");
}
}
}
private function findUniqueUsername() {
$db = SphpBase::dbEngine();
return $db->isRecordExist(
"SELECT COUNT(*) FROM ". $this->dtable ." WHERE ". $this->dfield ." = '". $this->value ."'",
);
}
protected function onprerender() {
// Add client side validation with javascript
if ($this->usernameFormat) {
// insert javascript code into form component submit event
addHeaderJSFunctionCode("{$this->formName}_submit", "{$this->name}usernameformat", '
var usernameformat = /^[a-zA-Z][a-zA-Z0-9_]*$/;
var blnusernameformat = usernameformat.test(document.getElementById("'. $this->name .'").value);
if(!blnusernameformat){
// show validation error message
displayValidationError(document.getElementById("'. $this->name .'"),"Value isn\'t match with User Name Format ");
// prevent form submit
blnSubmit = false ;
}');
}
// For AJAX based Unique User Name validation
// we call AJAX function on keyup event to server event to check username existence
if ($this->uniqueUsername) {
// insert javascript into jQuery ready event handler
addHeaderJSFunctionCode("ready", "{$this->name}uniqueusername", '
$("#'. $this->name .'").keyup(function(){
// check username existence with call getAJAX function to server event
var data = {};
data["'. $this->name .'"] = $("#'. $this->name .'").val();
getAJAX("'. getEventURL("checkusername") .'",data,true,function(response){
// process server response
if(response.exists){
// show validation error message
displayValidationError(document.getElementById("'. $this->name .'"),"Username already taken");
// disable form submit
blnSubmit = false ;
} else {
// clear validation error message
clearValidationError(document.getElementById("'. $this->name .'"));
}
});
});');
}
parent::onprerender();
}
/** now we handle server event and reply AJAX validation request.
* So we override onappevent event handler to handle server event.
*/
protected function onappevent() {
// check if it is validation request with use of Page Class: SphpBase::page()
if(SphpBase::page()->getEvent() == "checkusername") {
// check username existence with call findUniqueUsername function
$this->findUniqueUsername() ? SphpBase::JSServer()->addJSONReturnBlock(['exists' => true]) : SphpBase::JSServer()->addJSONReturnBlock(['exists' => false]);
}
}
}
```
**Usage:**
```html
<input id="txtUsername" type="text" runat="server"
path="mypath/comp/CustomTextField.php"
dfield="username" dtable="users"
fui-setForm="userForm"
fui-setRequired=""
fui-setUsernameFormat=""
fui-setUniqueUsername="">
```
### **6.9.3 Validation Groups**
```html
<form id="userForm" runat="server">
<!-- Display Error here -->
<p id="err"></p>
<!-- Personal info group -->
<fieldset data-validation-group="personal">
<input id="txtFirstName" type="text" runat="server" fui-setRequired="">
<input id="txtLastName" type="text" runat="server" fui-setRequired="">
</fieldset>
<!-- Contact info group -->
<fieldset data-validation-group="contact">
<input id="txtEmail" type="text" runat="server" fui-setRequired="" fui-setEmail="">
<input id="txtPhone" type="text" runat="server" fui-setRequired="">
</fieldset>
<!-- Validate specific group with AJAX use submitAJAX('click','url','txtFirstName,txtLastName') method
which also post other components value with request (txtFirstName,txtLastName)
-->
<button id="btnvalidate" runat="server" type="button"
fun-submitAJAX="click,|##{getEventURL('validate_group','personal')}#,|txtFirstName,txtLastName">
Validate Personal Info
</button>
</form>
```
```php
// In App
public function page_event_validate_group($evtp) {
$group = $evtp; // "personal" or "contact"
$personal = $this->frtMain->getComponent($group);
// Validate only Components in this group
$isValid = false;
// we validate name should not contain @
foreach($personal->getChildren() as $compname=>$compobj){
$isValid = $isValid || (strpos($compobj->value,'@') > 0) ? false : true ;
}
$strError = "";
if(! $isValid) $strError = "Error in Validation";
// send error to display in tag with id='err'
$this->JSServer->addJSONHTMLBlock('err',$strError);
}
```
## **6.10 Performance Optimization**
### **6.10.1 Component Caching**
```php
class CachedComponent extends \Sphp\tools\Component {
private $cacheKey;
private $cacheTtl = 300; // 5 minutes
public function oninit() {
$this->cacheKey = 'component_' . $this->name;
}
public function getCachedData() {
$sphp_api = SphpBase::sphp_api();
if ($data = $sphp_api->readFromCache($this->cacheKey,$this->cacheTtl)) {
return $data;
}
// Expensive operation
$data = $this->fetchDataFromExternalAPI();
// $data save to Cache
$sphp_api->saveToCache($this->cacheKey, $data);
return $data;
}
public function clearCache() {
SphpBase::sphp_api()->clearCache($this->cacheKey);
}
}
```
### **6.10.2 Lazy Loading Components**
```html
<!-- Component loads only when visible -->
<div id="heavyComponent" runat="server"
path="components/HeavyComponent.php"
fun-setLazyLoad="true">
<!-- Loading placeholder -->
<div class="loading-placeholder">
Loading component...
</div>
</div>
```
```php
class HeavyComponent extends \Sphp\tools\Component {
public function onrender() {
if ($this->shouldLoad()) {
// Expensive initialization
$this->initializeHeavyResources();
$html = $this->generateContent();
} else {
// Show placeholder
$html = $this->element->getInnerHTML();
}
$this->element->setInnerHTML($html);
}
private function shouldLoad() {
// Check if Component is in viewport
// Or if user has interacted with it
return $this->isVisible() || $this->hasInteraction();
}
}
```
## **6.11 Debugging Components**
### **6.11.1 Component Inspection**
```php
// In Gate during development
public function onready() {
if ($this->debug->debugmode > 1) {
// Dump all Components
foreach ($this->frtMain->getComponents() as $component) {
$this->debug->println(
"Component: " . $component->id .
" | Type: " . get_class($component) .
" | Value: " . $component->getValue()
);
}
}
}
```
### **6.11.2 Browser Console Debugging**
Components can send debug info to browser console:
```php
public function onrender() {
if ($this->debug->debugmode > 0) {
$debugInfo = [
'id' => $this->id,
'value' => $this->getValue(),
'valid' => $this->isValid(),
'bound' => $this->hasDatabaseBinding()
];
$js = "console.debug('Component Debug', " .
json_encode($debugInfo) . ");";
$this->element->setPreTag("<script>$js</script>");
}
}
```
### **6.11.3 Common Issues and Solutions**
**Issue: Component not updating**
```html
<!-- ❌ forget id attribute -->
<input type="text" runat="server">
<!-- ✅ id attribute use as Component Object Name -->
<input id="txt1" type="text" runat="server">
```
**Issue: Client Side Validation not working**
```html
<!-- ❌ Missing form association -->
<input id="txt1" type="text" runat="server" fui-setRequired="">
<!-- ✅ Associated with form Component pass id, So Component can insert JS Validation Code in form submit function -->
<input id="txt1" type="text" runat="server" fui-setRequired="" fui-setForm="myForm">
```
**Issue: Database binding good but value isn't inserted in Database**
```html
<!-- ❌ Component not inside form Tag -->
<form id="myform" runat="server">
</form>
<input id="txt1" type="text" dfield="username" runat="server" fui-setRequired="" fui-setForm="myForm">
<!-- ✅ All children Components should be inside form Component -->
<form id="myform" runat="server">
<input id="txt1" type="text" dfield="username" runat="server" fui-setRequired="" fui-setForm="myForm">
</form>
```
**Issue: Database binding failing**
```html
<!-- ❌ Component don't specified dfield -->
<input id="txt1" type="text" runat="server" fui-setRequired="" fui-setForm="myForm">
<!-- ✅ Component need specify atleast dfield. dtable attribute is optional and only required if Gate don't set default table with setTableName method or Component is using different table -->
<input id="txt1" type="text" dfield="username" runat="server" fui-setRequired="" fui-setForm="myForm">
```
## **6.12 Real-World Component Examples**
### **6.12.1 Image Upload with Preview**
```php
class ImageUploadWithPreview extends \Sphp\Comp\Form\FileUploader {
public function onrender() {
// Render file input
parent::onrender();
// Add preview container
$previewId = $this->name . '_preview';
$preview = "<div id='{$previewId}' class='image-preview'></div>";
// Add preview JavaScript
$js = "
$('#{$this->name}').on('change', function(e) {
var file = this.files[0];
if (file) {
var reader = new FileReader();
reader.onload = function(e) {
$('#{$previewId}').html(
'<img src=\"' + e.target.result + '\" style=\"max-width: 200px;\">'
);
}
reader.readAsDataURL(file);
}
});
";
$this->element->setPostTag($preview);
// jQuery code should be inside jQuery ready
// give name `$this->id . '_preview_js'` to code to use for update
SphpBase::sphp_api()->addHeaderJSFunctionCode("ready",$this->id . '_preview_js', $js);
}
}
```
### **6.12.2 Rich Text Editor**
```php
class RichTextEditor extends \Sphp\Comp\Form\TextArea {
private $toolbar = 'basic';
public function fi_setToolbar($type) {
$this->toolbar = $type;
}
public function onrender() {
// Get TextArea HTML
parent::onrender();
// Convert to rich editor
$editorId = $this->name . '_editor';
$js = "
tinymce.init({
selector: '#{$this->name}',
height: 300,
menubar: false,
toolbar: '{$this->getToolbarConfig()}',
plugins: 'link image code'
});
";
// Load TinyMCE if not already loaded
if (!SphpBase::sphp_api()->issetFileLink('tinymce')) {
// external url need permission in prerun.php file
SphpBase::sphp_api()->addFileLink(
'https://cdn.tiny.cloud/1/your-api-key/tinymce/6/tinymce.min.js',
true,
'tinymce',
'js'
);
}
SphpBase::sphp_api()->addHeaderJSFunctionCode("ready",$this->id . '_tinymce', $js);
}
private function getToolbarConfig() {
switch ($this->toolbar) {
case 'basic':
return 'undo redo | bold italic | link';
case 'full':
return 'undo redo | formatselect | bold italic underline | ' .
'alignleft aligncenter alignright | bullist numlist | ' .
'link image | code';
default:
return 'undo redo | bold italic';
}
}
}
```
## **6.13 Chapter Summary**
Components are the heart of SartajPHP's server-side UI architecture:
1. **Active Participants**: Components are intelligent objects with state, behavior, and lifecycle
2. **HTML-PHP Bridge**: They seamlessly connect FrontFile markup with server logic
3. **Database Integration**: Automatic CRUD operations through simple binding
4. **Validation System**: Built-in and extensible validation rules
5. **Customizable**: Create your own Components for specialized functionality
Key concepts to master:
- **Lifecycle Events**: `oninit()`, `oncreate()`, `onrender()`
- **Fusion Attributes**: `fui-*`, `fun-*`, `fur-*` for FrontFile control
- **Database Binding**: `dtable` and `dfield` for automatic CRUD
- **Value Management**: `getValue()`, `setValue()`, validation
- **Custom Components**: Extend `Component` class for specialized needs
Components transform SartajPHP from a simple templating system into a powerful application framework. With Components handling the heavy lifting of data binding, validation, and UI logic, you can focus on building your application's unique features.
In the next chapter, we'll explore Fusion Attributes in depth—understanding exactly how they control Component behavior and execution timing.
---
*Next: Chapter 7 dives deep into Fusion Attributes, explaining the precise timing and usage rules that make Components so powerful.*