Howto / FAQ
How do I …
Override or expand a default model
If you put a model, view or control inside your app/mvc folder, it will be used instead default in bpfw/core/mcv.
The app will look for that files at
1. %yourchildapp%/mvc
2. %yourparentapp%/mvc
3. bpfw/core/mvc
If existing this also works with your parent application.
So the parent files override the bpfw files, the child files override the parent and bpfw files.
Add a custom action / filter
// where the action should be executed:
$context = $model->GetTableName(); // only execute if context matches. can also be null.
bpfw_do_action("customaction", $context, array(1,2,3));
// add a callback
$priority = 10;
accepted_args = 3;
$context = $model->GetTableName(); // only execute if context matches. can also be null.
$bpfw_actions->add_action("customaction", $context, "somefunction", $priority, $accepted_args);
Add a custom page
From the example files:
From the example files:
From the example files:
Model:
class ExamplecustomcontentModel extends BpfwEmptyModel
{
function __construct()
{
parent::__construct();
$this->minUserrankForShow = USERTYPE_ADMIN;
}
public function GetTitle(): string
{
return "An empty content page. Use the view file to edit the content.";
}
}
View:
class ExamplecustomcontentView extends DefaultView
{
public function __construct(BpfwModel $model)
{
parent::__construct($model);
}
function renderView(): void
{
$model = $this->model;
echo "<div id='customcontent'>Output custom content for this page here</div>";
}
}
Control:
class ExamplecustomcontentControl extends DefaultControl
{
function handleAjaxCommand(string $command): void
{
// add ajax commands here
parent::handleAjaxCommand($command);
}
}
Add a custom component
You can add them in apps/%yourapp%/components
Example for a colored text component (included in examples):
/**
* An example to demonstrate that you can add custom components into your app folder
*
*
* @version 1.0
* @author torst
*/
class RedtextexampleComponent extends TextComponent
{
/**
* you can define custom attributes here
* @return string
*/
function getCustomAttributes(): array
{
return array("colortext_color" => "red", "colortext_bgcolor"=>"#770000");
}
/**
* get html of displaying this element as a label (for example in a list)
* @param mixed $value
* @param string $fieldName
* @param BpfwModelFormfield $fieldDbModel
* @param int|string $rowKey
* @param BpfwModel $model
* @return string
* @throws Exception
*/
public function GetDisplayLabelHtml(mixed $value, string $fieldName, BpfwModelFormfield $fieldDbModel, int|string $rowKey, BpfwModel $model): string
{
if ($fieldDbModel->type->type == BpfwDbFieldType::TYPE_DECIMAL) {
$value = bpfw_fromUsNumberFormat($value);
}
return parent::GetDisplayLabelHtml("<div style='color:".$fieldDbModel->colortext_color."'>".$value."</div>", $fieldName, $fieldDbModel, $rowKey, $model);
}
/**
* Summary of displayAsEdit
* @param mixed $value
* @param string $fieldName
* @param BpfwModelFormfield $fieldDbModel
* @param BpfwModel $model
* @param string|int $rowKey
* @return string
*/
protected function displayAsEdit(mixed $value, string $fieldName, BpfwModelFormField $fieldDbModel, BpfwModel $model, string|int $rowKey): string
{
ob_start();
$defaultValueAdded = false;
if ($rowKey === 0) { // add
if (empty($value)) {
$value = $fieldDbModel->default;
$defaultValueAdded = true;
}
}
$required = isset($fieldDbModel->required) && $fieldDbModel->required == true;
$requiredTxt = ($required ? "required" : "");
$xtraFormClass = isset($fieldDbModel->xtraFormClass) ? $fieldDbModel->display . " " . $fieldDbModel->xtraFormClass : $fieldDbModel->display;
$disabled = ($fieldDbModel->disabled);
$autocomplate = (isset ($fieldDbModel->autocomplete) && $fieldDbModel->autocomplete == true);
$autocomplatehtml = "";
$multipleComponents = false;
$drawIntervalSelection = false;
if ($fieldDbModel->display == "datepicker" && !empty($fieldDbModel->datetimeIntervalCombobox)) {
$multipleComponents = true;
$drawIntervalSelection = true;
}
if ($autocomplate) {
$autocomplatehtml = "autocomplete='field-" . $fieldName . "' ";
} else {
$autocomplatehtml = 'autocomplete="off" '; // latest chrome is ignoring autocomplete off ... so also use js
$xtraFormClass = trim($xtraFormClass) . " removeAutocomplete";
}
$step = 1;
// no keyboard on mobile
$readonly = "";
if ($fieldDbModel->display == "datepicker" || $fieldDbModel->display == "timepicker" || $fieldDbModel->display == "datetimepicker") {
$readonly = " readonly='true' ";
}
$type = "text";
$stephtml = "";
$pattern = "pattern=\"[0-9]+([,][0-9]+)?\"";
if (!empty($fieldDbModel->pattern)) {
$pattern = "pattern=" . $fieldDbModel->pattern;
if ($fieldDbModel->pattern == "default") $pattern = "";
}
if ($fieldDbModel->type->type == BpfwDbFieldType::TYPE_INT) {
$type = "number";
if (!empty($fieldDbModel->step)) {
$step = (int)$fieldDbModel->step;
$stephtml .= " step='$step' $pattern ";
}
if (!empty($fieldDbModel->min) || $fieldDbModel->min == "0" || $fieldDbModel->min == 0) {
$step = (int)$fieldDbModel->min;
$stephtml .= " min='$step' ";
}
if (!empty($fieldDbModel->max) || $fieldDbModel->max == "0" || $fieldDbModel->max == 0) {
$step = (int)$fieldDbModel->max;
$stephtml .= " max='$step' ";
}
}
if ($fieldDbModel->type->type == BpfwDbFieldType::TYPE_DECIMAL) {
//
$type = "number";
$step = $fieldDbModel->step;
$stephtml = " step='$step' $pattern ";
//if(!isset($_POST[$hkey])){
if ($defaultValueAdded) {
$value = bpfw_toUsNumberFormat($value);
}
}
if (!empty($fieldDbModel->textfield_type)) {
$type = $fieldDbModel->textfield_type;
}
if ($drawIntervalSelection) {
$xtraFormClass .= " hasintervalSelection";
}
if (!$disabled) {
echo '<input style="background-color:'.$fieldDbModel->colortext_bgcolor.';color:white;"' . $readonly . $this->getDataHtml($fieldDbModel->data) . ' ' . $autocomplatehtml . ' id="' . $fieldName . '" name="' . $fieldName . '" class = "' . $xtraFormClass . ' normal_admin_form_element admin_form_element" type="' . $type . '" ' . $stephtml . ' placeholder="' . $fieldDbModel->getPlaceholder() . '" ';
echo ' value="' . bpfw_htmlentities($value) . '" ';
echo '>';
if ($drawIntervalSelection) {
?>
<select data-baseformfield="<?php echo $fieldDbModel->baseformfield; ?>" data-live-search="false"
data-picker_for="<?php echo $fieldName; ?>"
class="combobox admin_form_element intervalSelection selectpicker" name="customerId" size="1">
<?php
foreach ($fieldDbModel->datetimeIntervalCombobox as $key => $value) {
echo "<option value='$value'>$key</option>";
}
?>
</select>
<?php
}
} else {
echo '<input style="background-color:'.$fieldDbModel->colortext_bgcolor.';color:white;" ' . $readonly . $this->getDataHtml($fieldDbModel->data) . ' ' . $autocomplatehtml . ' id="' . $fieldName . '_display" name="' . $fieldName . '_display" class = "' . $xtraFormClass . ' normal_admin_form_element admin_form_element" type="' . $type . '" placeholder="' . $fieldDbModel->getPlaceholder() . '" ';
echo ' value="' . bpfw_htmlentities($value) . '" ';
echo " disabled ";
echo '>';
echo '<input style="background-color:'.$fieldDbModel->colortext_bgcolor.';color:white;" ' . $readonly . $this->getDataHtml($fieldDbModel->data) . ' ' . $autocomplatehtml . ' id="' . $fieldName . '" name="' . $fieldName . '" type="hidden" placeholder="' . $fieldDbModel->getPlaceholder() . '" ';
echo ' value="' . bpfw_htmlentities($value) . '" ';
echo '>';
}
return ob_get_clean();
}
}
Create a nested dialog
You can nest as many dialogues as you want to have a more comfortable experience.
From the Demodata:
Control:
class ExamplecomplexModel extends BpfwModel
{
/**
* return the db model with all the fields
* @return array
* @throws Exception
*/
protected function loadDbModel(): array
{
if (empty($this->dbModel)) {
$this->addPrimaryKey("examplecomplexId");
$this->addModellistFiltered("test_modellistfiltered", "test_modellistfiltered", "examplecomplexsub", array( "firstname", "lastname", "anothervalue" ), array(LISTSETTING::AJAX_SORTABLE=>false, LISTSETTING::LABEL_LIST=>"test modelllist filtered", FORMSETTING::PAGE=>3, LISTSETTING::HIDDENONLIST=> true));
}
return $this->dbModel;
}
/**
* Define your constraints here. They have to be in the parent model. So if your uses has addresses, define in user etc.
* @return DatabaseFKConstraint[]
*/
public function getConstraints(): array
{
return array(
// FK_CONTRAINT_CASCADE: delete entries of submodel database when deleted
new DatabaseFKConstraint("deleteSubIfParentDeleted", "examplecomplexsub", "examplecomplexsubId", $this->getKeyName(), FK_CONTRAINT_CASCADE),
);
}
}
Submodel:
<?php /** @noinspection PhpUnused */
require_once(BPFW_MVC_PATH."bpfwModelFormField.inc.php");
class ExamplecomplexsubModel extends BpfwModel
{
// enable if you want all the labels in loadDbModel to be translated
// var bool $translateDbModelLabels = true;
// stores the current filter for the entries
// can also be "formid" to use value of parent form, so has to be string
private $examplecomplexId;
function __construct($examplecomplexId = null){
$this->examplecomplexId = $examplecomplexId;
// autoadds a FILTER_ENTRY_SELECT_WHERE filter
$this->filteredField = "examplecomplexId";
$this->filteredModel = "examplecomplex";
$filter = getorpost("filter");
if(!empty($filter) && $this->examplecomplexId === null){
$this->examplecomplexId = getorpost("filter");
}
$this->showdata = true;
$this->minUserrankForEdit = USERTYPE_ADMIN;
$this->minUserrankForShow = USERTYPE_CONSULTANT;
$this->minUserrankForAdd = USERTYPE_ADMIN;
$this->sortColumn = 1;
$this->sortOrder = "desc";
parent::__construct();
}
/**
* tablename
* @return string
*/
public function GetTableName(): string
{
return "examplecomplexsub";
}
public function GetTitle(): string
{
return "Submodel of examplecomplex";
}
/**
* @return array
* @throws Exception
*/
protected function loadDbModel(): array
{
if (empty($this->dbModel)) {
$this->addPrimaryKey("examplecomplexsubId");
// this will automatically add the Filtervalue of the parent to the database
$this->addHiddenField("examplecomplexId", "examplecomplexId", BpfwDbFieldType::TYPE_INT, $this->examplecomplexId, array());
$this->addTextField("firstname", "Firstname");
$this->addTextField("lastname", "Lastname");
$this->addTextFieldNumeric("anothervalue", "Another value");
}
return $this->dbModel;
}
}
Use constraints
In the Parent model define:
/**
* Define your constraints here. They have to be in the parent model. So if your uses has addresses, define in user etc.
* @return DatabaseFKConstraint[]
*/
public function getConstraints(): array
{
return array(
// FK_CONTRAINT_RESTRICT: show an error dialog if you want to delete an entry that still has uploaded attachments
new DatabaseFKConstraint("preventdeleteifstillattachments", "exampleattachments", "exampleattachmentsId", $this->getKeyName(), FK_CONTRAINT_RESTRICT),
// FK_CONTRAINT_CASCADE: delete entries of submodel database when deleted
new DatabaseFKConstraint("deleteSubIfParentDeleted", "examplecomplexsub", "examplecomplexsubId", $this->getKeyName(), FK_CONTRAINT_CASCADE),
);
}
Hint: the constrains are only existing as PHP code and are not applied to the database.
Create multiple Apps in one bpfw source base
If you put another app in your /apps/ folder you can switch between apps with /?app=demo1 /?app=demo2 /?app=demo3 etc. . The value in app represents the app and foldername.
You can also install another app with the installer.
For security reasons, the installer disables after an app is found, but you can install as many as you want.
You can change that by setting this variables before loading bpfw:
const ALLOW_MULTIPLE_APP_CREATION = true;
const MULTIPLE_APP_CREATION_PASSWORD = "somepassword";
require_once("vendor/bpfw/bpfw/use_bpfw.inc.php");
Now you can go to (yoururl)/?createApp ( for example: https://localhost?createApp ) to open wizard again. This time you have to input the password you just defined in MULTIPLE_APP_CREATION_PASSWORD.
Create a Parent and multiple derived Child Application
Parent apps can be useful if you have a complex base program and a lot of customers that should have their own database but not an instance of a sourcecode.
For example you have a timetracking app for different companies and each of them does things a bitt different. But you don’t want to copy the base sourcecode for every company all the time.
In a child/parent app, bpfw looks for a file in the child folder first, then in the parent app and finally in the bpfw folder. So you can place all your common files here.
To set this up:
Create an app the usual way
set in the config.inc.php of the app. This will be the parent app:
defined("TEMPLATE_THEME") || define('TEMPLATE_THEME', true);
Now create another app in inside the config:
define('PARENT_NAME', "wws45"); define('TEMPLATE_THEME', false);
Thats it, You could delete the parent database now since it is no longer used.
load BPFW from a different location
in the index.php (this example uses bpfw in /bpfw_dev/ ):
define("BPFW_BASE_PATH", getcwd() . DIRECTORY_SEPARATOR . 'bpfw_dev'.DIRECTORY_SEPARATOR);
const BPFW_BASE_URI = "/".'bpfw_dev'."/";
require_once("bpfw_dev/use_bpfw.inc.php");