# **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.*