Complete Example and Summary
This page lists all six view files that complete the countries CRUD module. The controller and model were covered in the two preceding pages:
- Countries.php - Controller (see Building the Controller)
- Countries_model.php - Model (see Building the Model)
- create.php - Create/edit form view
- delete_conf.php - Delete confirmation view
- manage.php - Paginated list view
- not_found.php - Record not found view
- search_modal.php - Search modal form view
- show.php - Record detail view
Views
create.php
<h1><?= $headline ?></h1>
<?= validation_errors() ?>
<div class="card">
<div class="card-heading">
Country Details
</div>
<div class="card-body">
<?php
echo form_open($form_location);
echo form_label('Country Title');
$country_title_attr = [
'placeholder' => 'Enter Country Title',
'required' => true,
'minlength' => 2,
'maxlength' => 255
];
echo form_input('country_title', $country_title, $country_title_attr);
echo form_label('Country Code');
$country_code_attr = [
'placeholder' => 'Enter Country Code',
'required' => true,
'minlength' => 2,
'maxlength' => 2
];
echo form_input('country_code', $country_code, $country_code_attr);
echo '<div class="text-center">';
echo anchor($cancel_url, 'Cancel', ['class' => 'button alt']);
echo form_submit('submit', 'Submit');
echo '</div>';
echo form_close();
?>
</div>
</div>delete_conf.php
<h1><?= $headline ?></h1>
<div class="card">
<div class="card-heading">
Confirmation Required
</div>
<div class="card-body">
<p>Are you sure?</p>
<p>You are about to delete a country record. This cannot be undone. Do you really want to do this?</p>
<?php
echo form_open($form_location);
echo '<div class="text-center">';
echo anchor($cancel_url, 'Cancel', array('class' => 'button alt'));
echo form_submit('submit', 'Yes - Delete Now', array('class' => 'danger'));
echo form_close();
echo '</div>';
?>
</div>
</div>manage.php
<h1>Manage Countries</h1>
<?= flashdata() ?>
<?php if (!empty($search_query)): ?>
<p>Showing results for <strong><?= out($search_query) ?></strong></p>
<?php endif; ?>
<?php
echo '<p class="flex-row justify-between">';
echo anchor('countries/create', 'Create New Country Record', ['class' => 'button alt']);
if ((count($rows) > 9) || (!empty($search_query))) {
$btn_attr = [
'class' => 'alt',
'mx-get' => 'countries/search_modal',
'mx-build-modal' => json_encode([
'id' => 'search-modal',
'modalHeading' => 'Search Countries',
'modalFooter' => '<button class="alt" onclick="closeModal()">Cancel</button><button form="search-form">Search</button>'
])
];
echo form_button('search_btn', 'Search <i class="tg tg-search"></i>', $btn_attr);
}
echo '</p>';
if (empty($rows)) {
echo '<p>There are currently no records to display.</p>';
return;
}
echo Modules::run('pagination/display', $pagination_data);
?>
<div class="table-container">
<table class="records-table">
<thead>
<tr>
<th colspan="3">
<div>
<div> </div>
<div>Records Per Page: <?php
$dropdown_attr['onchange'] = 'setPerPage()';
echo form_dropdown('per_page', $per_page_options, $selected_per_page, $dropdown_attr);
?></div>
</div>
</th>
</tr>
<tr>
<th class="text-left">Country Title</th>
<th class="text-left">Country Code</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach($rows as $row):
$link_attr = [
'mx-get' => 'countries/show/'.$row->id,
'mx-select' => '.detail-grid',
'mx-build-modal' => json_encode([
'id' => 'record-preview-modal',
'width' => '640px',
'modalHeading' => 'Record Preview',
'modalFooter' => '<a href="countries/show/'.$row->id.'" class="button alt mt-0 xs">View Details</a>'
])
];
?>
<tr>
<td><?= anchor('#', out($row->country_title), $link_attr) ?></td>
<td><?= out($row->country_code) ?></td> <td>
<div class="actions">
<a href="countries/show/<?= $row->id ?>" class="button alt button-round"><i class="tg tg-eye"></i></a>
<a href="countries/create/<?= $row->id ?>" class="button alt button-round"><i class="tg tg-pencil"></i></a>
<a href="countries/delete_conf/<?= $row->id ?>" class="button alt button-round"><i class="tg tg-trash"></i></a>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php
if(count($rows)>9) {
unset($pagination_data['include_showing_statement']);
echo Modules::run('pagination/display', $pagination_data);
}
?>
<script>
function setPerPage() {
const selectedIndex = document.querySelector('select[name="per_page"]').value;
window.location.href = '<?= BASE_URL ?>countries/set_per_page/' + selectedIndex;
}
</script>not_found.php
<h1><?= $headline ?></h1>
<div class="card">
<div class="card-heading">
<?= $headline ?>
</div>
<div class="card-body">
<p><?= $message ?></p>
<div class="text-center">
<?= anchor($back_url, $back_label, array('class' => 'button alt')) ?>
</div>
</div>
</div>search_modal.php
<?php
/**
* Search modal form for filtering countries.
*
* Loaded via MX into a modal when the Search button is clicked.
*/
$searchable_columns = [
'country_title' => 'Country Title',
'country_code' => 'Country Code',
];
$form_attr = ['id' => 'search-form'];
echo form_open('countries/submit_search', $form_attr);
?>
<div class="form-group">
<?= form_label('Search Query') ?>
<?= form_input('search_query', '', ['placeholder' => 'Enter search term...', 'autocomplete' => 'off']) ?>
</div>
<div class="form-group">
<?= form_label('Search In') ?>
<?= form_dropdown('search_column', $searchable_columns) ?>
</div>
<?= form_close() ?>show.php
<h1><?= $headline ?></h1>
<?= flashdata() ?>
<div class="card">
<div class="card-heading">
Country Details
</div>
<div class="card-body">
<div class="text-right mb-3">
<?= anchor($back_url, 'Back', array('class' => 'button alt')) ?>
<?= anchor(BASE_URL.'countries/create/'.$update_id, 'Edit', array('class' => 'button')) ?>
<?= anchor('countries/delete_conf/'.$update_id, 'Delete', array('class' => 'button danger')) ?>
</div>
<div class="detail-grid">
<div class="detail-row">
<div class="detail-label">Country Title</div>
<div class="detail-value"><?= out($country_title) ?></div>
</div>
<div class="detail-row">
<div class="detail-label">Country Code</div>
<div class="detail-value"><?= out($country_code) ?></div>
</div>
</div>
</div>
</div>Summary
In this chapter you built a complete CRUD module for managing countries. Here is what you accomplished:
-
Database: Created a
countriestable withcountry_titleandcountry_codecolumns, and populated it with 23 sample countries. -
Model: Built
Countries_modelwith methods for fetching, inserting, updating, counting, searching, and preparing records for display. -
Controller: Built
Countrieswith methods for manage (pagination with search), create/submit (form with validation), show (detail view), and delete confirmation flow. - Manage page: A paginated list with two visible columns, search modal, per-page selector, and view/edit/delete action buttons. Country titles link to MX-powered record preview modals.
- Create and Edit: A single form that handles both creation and updates, with validation rules, client-side constraints, and error display.
-
Show: A detail view of individual records with navigation to edit or delete, plus a
.detail-gridclass that doubles as the MX preview target. - Delete: A two-step delete flow with confirmation screen and not-found handling for already-deleted records.
-
Security: Every public method protected by
$this->trongate_security->make_sure_allowed(). Private helper methods eliminate the need forblock_url(). -
Search: Column-whitelisted search with SQL injection protection via named parameters in
query_bind().
This same pattern - manage, show, create, submit, delete_conf, submit_delete - is used by every admin CRUD module in Trongate. Once you have built one, you have built them all. The only differences are the table schema, the form fields, and the validation rules.
In the next chapter, we will look at exposing these same records through a REST API, where the same make_sure_allowed() pattern takes on new significance for securing endpoints.
We're continually improving the Trongate documentation. If anything is incorrect, unclear, incomplete, or could be better, we'd genuinely appreciate your input.
Share your thoughts in the Documentation Feedback.