diff --git a/docs/accordion.md b/docs/accordion.md index 64cef6d5c0..e4297a42a4 100644 --- a/docs/accordion.md +++ b/docs/accordion.md @@ -1,29 +1,28 @@ - .. php:namespace:: Atk4\Ui .. php:class:: Accordion -========= -Accordion -========= +# Accordion Accordion implement another way to organize your data. The implementation is based on: https://fomantic-ui.com/modules/accordion.html. Demo: https://ui.agiletoolkit.org/demos/accordion.php +## Basic Usage -Basic Usage -=========== - -Once you create an Accordion container you can then mix and match static and dynamic accodion section:: +Once you create an Accordion container you can then mix and match static and dynamic accodion section: - $acc = Accordion::addTo($app); +``` +$acc = Accordion::addTo($app); +``` -Adding a static content section is pretty simple:: +Adding a static content section is pretty simple: - LoremIpsum::addTo($acc->addSection('Static Tab')); +``` +LoremIpsum::addTo($acc->addSection('Static Tab')); +``` You can add multiple elements into a single accordion section, like any other view. @@ -34,33 +33,35 @@ Use addSection() method to add more section in an Accordion view. First paramete Section can be static or dynamic. Dynamic sections use :php:class:`VirtualPage` implementation mentioned above. You should pass Closure action as a second parameter. -Example:: +Example: - $t = Accordion::addTo($layout); +``` +$t = Accordion::addTo($layout); - // add static section - HelloWorld::addTo($t->addSection('Static Content')); +// add static section +HelloWorld::addTo($t->addSection('Static Content')); - // add dynamic section - $t->addSection('Dynamically Loading', function (VirtualPage $vp) { - LoremIpsum::addTo($vp); - }); +// add dynamic section +$t->addSection('Dynamically Loading', function (VirtualPage $vp) { + LoremIpsum::addTo($vp); +}); +``` -Dynamic Accordion Section -========================= +## Dynamic Accordion Section Dynamic sections are based around implementation of :php:class:`VirtualPage` and allow you -to pass a callback which will be triggered when user clicks on the section title.:: +to pass a callback which will be triggered when user clicks on the section title.: - $acc = Accordion::addTo($app); +``` +$acc = Accordion::addTo($app); - // dynamic section - $acc->addSection('Dynamic Lorem Ipsum', function (VirtualPage $vp) { - LoremIpsum::addTo($vp, ['size' => 2]); - }); +// dynamic section +$acc->addSection('Dynamic Lorem Ipsum', function (VirtualPage $vp) { + LoremIpsum::addTo($vp, ['size' => 2]); +}); +``` -Controlling Accordion Section via Javascript -============================================ +## Controlling Accordion Section via Javascript Accordion class has some wrapper method in order to control the accordion module behavior. @@ -68,21 +69,24 @@ Accordion class has some wrapper method in order to control the accordion module .. php:method:: jsToggle($section, $action = null) .. php:method:: jsClose($section, $action = null) -For example, you can set a button that, when clicked, will toggle an accordion section:: +For example, you can set a button that, when clicked, will toggle an accordion section: - $button = Button::addTo($bar, ['Toggle Section 1']); +``` +$button = Button::addTo($bar, ['Toggle Section 1']); - $acc = Accordion::addTo($app, ['type' => ['styled', 'fluid']]); - $section1 = LoremIpsum::addTo($acc->addSection('Static Text')); - $section2 = LoremIpsum::addTo($acc->addSection('Static Text')); +$acc = Accordion::addTo($app, ['type' => ['styled', 'fluid']]); +$section1 = LoremIpsum::addTo($acc->addSection('Static Text')); +$section2 = LoremIpsum::addTo($acc->addSection('Static Text')); - $button->on('click', $acc->jsToggle($section1)); +$button->on('click', $acc->jsToggle($section1)); +``` -Accordion Module settings -========================= +## Accordion Module settings -It is possible to change Accordion module settings via the settings property.:: +It is possible to change Accordion module settings via the settings property.: - Accordion::addTo($app, ['settings' => []]); +``` +Accordion::addTo($app, ['settings' => []]); +``` For a complete list of all settings for the Accordion module, please visit: https://fomantic-ui.com/modules/accordion.html#/settings diff --git a/docs/advanced.md b/docs/advanced.md index 310e416b17..3f371871c5 100644 --- a/docs/advanced.md +++ b/docs/advanced.md @@ -1,12 +1,8 @@ - .. _advanced: -=============== -Advanced Topics -=============== +# Advanced Topics -Agile Data -========== +## Agile Data Agile Data is a business logic and data persistence framework. It's a separate library that has been specifically designed and developed @@ -37,8 +33,7 @@ Agile Data is distributed under same open-source license as Agile UI and the rest of this documentation will assume you are using Agile Data for the purpose of overall clarity. -Interface Stability -=================== +## Interface Stability Agile UI is based on Agile Toolkit 4.3 which has been a maintained UI framework that can trace it's roots back to 2003. As a result, the @@ -51,8 +46,7 @@ compatible for a period of a few years. We expect you to extend base classes to build your UI as it is a best practice to use Agile UI. -Testing and Enterprise Use -========================== +## Testing and Enterprise Use Agile UI is designed with corporate use in mind. The main aim of the framework is to make your application consistent, modern and @@ -65,16 +59,14 @@ browser compatibility is defined by the underlying CSS framework. With Agile UI we will provide you with a guide how to test your own components. -Unit Tests ----------- +### Unit Tests You only need to unit-test you own classes and controllers. For example if your application creates a separate class that deals with APR calculation, you need to include unit-test for that specific class. -Business Logic Unit Tests -------------------------- +### Business Logic Unit Tests Those tests are most suitable for testing your business logic, that is included in Agile Data. Use "array" persistence to @@ -89,8 +81,7 @@ logic with mock objects. In most cases the Integration tests are easier to make, and give you equal testability. -Integration Database Tests --------------------------- +### Integration Database Tests This test-suite will operate with SQL database by executing various database operations in Agile Data and then asserting @@ -101,8 +92,7 @@ business logic changes. 3. perform changes such as adding new invoice 4. assert through other models e.g. by running client report model. -Component Tests ---------------- +### Component Tests All of the basic components are tested for you using UI tests, but you should test your own components. This test will place @@ -112,8 +102,7 @@ that it continues to work. If your component relies on a model, this can also attempt various model combinations for an extensive test. -User Testing ------------- +### User Testing Once you place your components on your pages and associate them with your actual data you can perform user tests. diff --git a/docs/app.md b/docs/app.md index 5f01e9a562..f2e6f7dbf0 100644 --- a/docs/app.md +++ b/docs/app.md @@ -1,20 +1,18 @@ - - .. _app: - -Purpose of App class -==================== +## Purpose of App class .. php:namespace:: Atk4\Ui .. php:class:: App App is a mandatory object that's essential for Agile UI to operate. You should create instance -of an App class yourself before other components:: +of an App class yourself before other components: - $app = new \Atk4\Ui\App('My App'); - $app->initLayout([\Atk4\Ui\Layout\Centered::class]); - LoremIpsum::addTo($app); +``` +$app = new \Atk4\Ui\App('My App'); +$app->initLayout([\Atk4\Ui\Layout\Centered::class]); +LoremIpsum::addTo($app); +``` As you add one component into another, they will automatically inherit reference to App class. App class is an ideal place to have all your environment configured and all the dependencies defined that @@ -28,21 +26,23 @@ executing unit-tests, you may want to create new App instance. If your applicati exception, it will catch it and create a new App instance to display error message ensuring that the error is not repeated. -Using App for Injecting Dependencies ------------------------------------- +### Using App for Injecting Dependencies + Since App class becomes available for all objects and components of Agile Toolkit, you may add -properties into the App class:: +properties into the App class: - $app->db = new \Atk4\Data\Persistence\Sql($dsn); +``` +$app->db = new \Atk4\Data\Persistence\Sql($dsn); - // later anywhere in the code: +// later anywhere in the code: - $m = new MyModel($this->getApp()->db); +$m = new MyModel($this->getApp()->db); +``` .. IMPORTANT:: $app->db is NOT a standard property. If you use this property, that's your own convention. -Using App for Injecting Behavior --------------------------------- +### Using App for Injecting Behavior + You may use App class hook to impact behavior of your application: - using hooks to globally impact object initialization @@ -52,74 +52,77 @@ You may use App class hook to impact behavior of your application: - load templates from different files - use a different CDN settings for static files +### Using App as Initializer Object -Using App as Initializer Object -------------------------------- App class may initialize some resources for you including user authentication and work with session. My next example defines property `$user` and `$system` for the app class to indicate a system which is currently -active. (See :ref:`system_pattern`):: +active. (See :ref:`system_pattern`): - class Warehouse extends \Atk4\Ui\App - { - public $user; - public $company; +``` +class Warehouse extends \Atk4\Ui\App +{ + public $user; + public $company; - public function __construct(bool $auth = true) - { - parent::__construct('Warehouse App v0.4'); + public function __construct(bool $auth = true) + { + parent::__construct('Warehouse App v0.4'); - // My App class will establish database connection - $this->db = new \Atk4\Data\Persistence\Sql($_CLEARDB_DATABASE_URL['DSN']); - $this->db->setApp($this); + // My App class will establish database connection + $this->db = new \Atk4\Data\Persistence\Sql($_CLEARDB_DATABASE_URL['DSN']); + $this->db->setApp($this); -           // My App class provides access to a currently logged user and currently selected system. - session_start(); +        // My App class provides access to a currently logged user and currently selected system. + session_start(); - // App class may be used for pages that do not require authentication - if (!$auth) { - $this->initLayout([\Atk4\Ui\Layout\Centered::class]); + // App class may be used for pages that do not require authentication + if (!$auth) { + $this->initLayout([\Atk4\Ui\Layout\Centered::class]); - return; - } + return; + } - // Load user from database based on session data - if (isset($_SESSION['user_id'])) { - $user = new User($this->db); - $this->user = $user->tryLoad($_SESSION['user_id']); - } + // Load user from database based on session data + if (isset($_SESSION['user_id'])) { + $user = new User($this->db); + $this->user = $user->tryLoad($_SESSION['user_id']); + } -           // Make sure user is valid - if ($this->user === null) { - $this->initLayout([\Atk4\Ui\Layout\Centered::class]); - Message::addTo($this, ['Login Required', 'type' => 'error']); - Button::addTo($this, ['Login', 'class.primary' => true])->link('index.php'); - exit; - } +       // Make sure user is valid + if ($this->user === null) { + $this->initLayout([\Atk4\Ui\Layout\Centered::class]); + Message::addTo($this, ['Login Required', 'type' => 'error']); + Button::addTo($this, ['Login', 'class.primary' => true])->link('index.php'); + exit; + } - // Load company data (System) for present user - $this->company = $this->user->ref('company_id'); + // Load company data (System) for present user + $this->company = $this->user->ref('company_id'); - $this->initLayout([\Atk4\Ui\Layout\Admin::class]); + $this->initLayout([\Atk4\Ui\Layout\Admin::class]); -           // Add more initialization here, such as a populating menu. - } +       // Add more initialization here, such as a populating menu. } +} +``` -After declaring your Application class like this, you can use it conveniently anywhere:: +After declaring your Application class like this, you can use it conveniently anywhere: - include'vendor/autoload.php'; - $app = new Warehouse(); - Crud::addTo($app) - ->setModel($app->system->ref('Order')); +``` +include'vendor/autoload.php'; +$app = new Warehouse(); +Crud::addTo($app) + ->setModel($app->system->ref('Order')); +``` - -Quick Usage and Page pattern ----------------------------- +### Quick Usage and Page pattern A lot of the documentation for Agile UI uses a principle of initializing App object first, then, manually -add the UI elements using a procedural approach:: +add the UI elements using a procedural approach: - HelloWorld::addTo($app); +``` +HelloWorld::addTo($app); +``` There is another approach in which your application will determine which Page class should be used for executing the request, subsequently creating setting it up and letting it populate UI (This behavior is @@ -128,8 +131,7 @@ similar to Agile Toolkit prior to 4.3). In Agile UI this pattern is implemented through a 3rd party add-on for :ref:`page_manager` and routing. See also :php:meth:`App::url()` -Clean-up and simplification ---------------------------- +### Clean-up and simplification .. php:method:: run() .. php:attr:: runCalled @@ -138,18 +140,19 @@ Clean-up and simplification App also does certain actions to simplify handling of the application. For instance, App class will render itself automatically at the end of the application, so you can safely add objects into the `App` -without actually triggering a global execution process:: +without actually triggering a global execution process: - HelloWorld::addTo($app); +``` +HelloWorld::addTo($app); - // Next line is optional - $app->run(); +// Next line is optional +$app->run(); +``` If you do not want the application to automatically execute `run()` you can either set `$alwaysRun` to false or use :php:meth:`terminate()` to the app with desired output. -Exception handling ------------------- +### Exception handling .. php:method:: caugthException .. php:attr:: catch_exception @@ -160,23 +163,27 @@ better plan for exception, place your code inside a try-catch block. When Exception is caught, it's displayed using a Layout\Centered layout and execution of original application is terminated. -Integration with other Frameworks ---------------------------------- +### Integration with other Frameworks + If you use Agile UI in conjunction with another framework, then you may be using a framework-specific App class, that implements tighter integration with the host application or full-stack framework. .. php:method:: requireJs() -Method to include additional JavaScript file in page:: +Method to include additional JavaScript file in page: - $app->requireJs('https://example.com/file.min.js'); +``` +$app->requireJs('https://example.com/file.min.js'); +``` .. php:method:: requireCss($url) -Method to include additional CSS style sheet in page:: +Method to include additional CSS style sheet in page: - $app->requireCss('https://example.com/file.min.css'); +``` +$app->requireCss('https://example.com/file.min.css'); +``` .. php:method:: initIncludes() @@ -187,8 +194,7 @@ Initializes all includes required by Agile UI. You may extend this class to add Decodes current request without any arguments. If you are changing URL generation pattern, you probably need to change this method to properly identify the current page. See :php:class:`App::url()` -Loading Templates for Views ---------------------------- +### Loading Templates for Views .. php:method:: loadTemplate($name) @@ -198,9 +204,7 @@ you can specify extended logic. You may override this method if you are using a different CSS framework. - -Utilities by App -================ +## Utilities by App App provides various utilities that are used by other components. @@ -209,8 +213,7 @@ App provides various utilities that are used by other components. Apart from basic utility, App class provides several mechanisms that are helpful for components. -Sticky GET Arguments --------------------- +### Sticky GET Arguments .. php:method:: stickyGet() .. php:method:: stickyForget() @@ -219,10 +222,12 @@ Problem: sometimes certain PHP code will only be executed when GET arguments are you may have a file `detail.php` which expects `order_id` parameter and would contain a `Crud` component. Since the `Crud` component is interactive, it may want to generate requests to itself, but it must also -include `order_id` otherwise the scope will be incomplete. Agile UI solves that with StickyGet arguments:: +include `order_id` otherwise the scope will be incomplete. Agile UI solves that with StickyGet arguments: - $orderId = $app->stickyGet('order_id'); - $crud->setModel($order->load($orderId)->ref('Payment')); +``` +$orderId = $app->stickyGet('order_id'); +$crud->setModel($order->load($orderId)->ref('Payment')); +``` This make sure that pagination, editing, addition or any other operation that Crud implements will always address same model scope. @@ -231,66 +236,68 @@ If you need to generate URL that respects stickyGet arguments, use :php:meth:`Ap See also :php:meth:`View::stickyGet` -Redirects ---------- +### Redirects .. php:method:: redirect(page) .. php:method:: jsRedirect(page) App implements two handy methods for handling redirects between pages. The main purpose for those is to provide a simple way to redirect for users who are not familiar with JavaScript and HTTP headers -so well. Example:: +so well. Example: - if (!isset($_GET['age'])) { - $app->redirect(['age' => 18]); - } +``` +if (!isset($_GET['age'])) { + $app->redirect(['age' => 18]); +} - Button::addTo($app, ['Increase age']) - ->on('click', $app->jsRedirect(['age' => $_GET['age'] + 1])); +Button::addTo($app, ['Increase age']) + ->on('click', $app->jsRedirect(['age' => $_GET['age'] + 1])); +``` No much magic in these methods. -Database Connection -------------------- +### Database Connection .. php:property:: db If your `App` needs a DB connection, set this property to an instance of `Persistence`. - Example: +Example: - $app->db = \Atk4\Data\Persistence::connect('mysql://user:pass@localhost/atk'); +``` +$app->db = \Atk4\Data\Persistence::connect('mysql://user:pass@localhost/atk'); +``` See `Persistence::connect ` -Execution Termination ---------------------- +### Execution Termination .. php:method:: terminate(output) Used when application flow needs to be terminated preemptively. For example when callback is triggered and need to respond with some JSON. - -Execution state ---------------- +### Execution state .. php:attr:: isRendering Will be true if the application is currently rendering recursively through the Render Tree. -Links ------ +### Links .. php:method:: url(page) -Method to generate links between pages. Specified with associative array:: +Method to generate links between pages. Specified with associative array: - $url = $app->url(['contact', 'from' => 'John Smith']); +``` +$url = $app->url(['contact', 'from' => 'John Smith']); +``` -This method must respond with a properly formatted URL, such as:: +This method must respond with a properly formatted URL, such as: - contact.php?from=John+Smith +``` +contact.php?from=John+Smith +``` If value with key 0 is specified ('contact') it will be used as the name of the page. By default url() will use page as "contact.php?.." however you can define different behavior @@ -304,18 +311,14 @@ but if you need URL to drop any sticky value, specify value explicitly as `false Use jsUrl for creating callback, which return non-HTML output. This may be routed differently by a host framework (https://github.com/atk4/ui/issues/369). - - -Includes --------- +### Includes .. php:method:: requireJs($url) Includes header into the class that will load JavaScript file from a specified URL. This will be used by components that rely on external JavaScript libraries. -Hooks ------ +### Hooks Application implements HookTrait (https://agile-core.readthedocs.io/en/develop/hook.html) and the following hooks are available: @@ -331,36 +334,37 @@ after output was sent. ATK will avoid calling this hook multiple times. .. note:: beforeOutput and beforeRender are not executed if $app->terminate() is called, even if parameter is passed. - -Application and Layout -====================== +## Application and Layout When writing an application that uses Agile UI you can either select to use individual components or make them part of a bigger layout. If you use the component individually, then it will at some point initialize internal 'App' class that will assist with various tasks. -Having composition of multiple components will allow them to share the app object:: +Having composition of multiple components will allow them to share the app object: - $grid = new \Atk4\Ui\Grid(); - $grid->setModel($user); - $grid->addPaginator(); // initialize and populate paginator - $grid->addButton('Test'); // initialize and populate toolbar +``` +$grid = new \Atk4\Ui\Grid(); +$grid->setModel($user); +$grid->addPaginator(); // initialize and populate paginator +$grid->addButton('Test'); // initialize and populate toolbar - echo $grid->render(); +echo $grid->render(); +``` All of the objects created above - button, grid, toolbar and paginator will share the same value for the 'app' property. This value is carried into new objects through AppScopeTrait (https://agile-core.readthedocs.io/en/develop/appscope.html). -Adding the App --------------- +### Adding the App -You can create App object on your own then add elements into it:: +You can create App object on your own then add elements into it: - $app = new App('My App'); - $app->add($grid); +``` +$app = new App('My App'); +$app->add($grid); - echo $grid->render(); +echo $grid->render(); +``` This does not change the output, but you can use the 'App' class to your advantage as a "Property Bag" pattern to inject your configuration. You can even use a different "App" @@ -369,17 +373,20 @@ of GET/POST data and more. We are still not using the layout, however. -Adding the Layout ------------------ +### Adding the Layout -Layout can be initialized through the app like this:: +Layout can be initialized through the app like this: - $app->initLayout([\Atk4\Ui\Layout\Centered::class]); +``` +$app->initLayout([\Atk4\Ui\Layout\Centered::class]); +``` -This will initialize two new views inside the app:: +This will initialize two new views inside the app: - $app->html - $app->layout +``` +$app->html +$app->layout +``` The first view is a HTML boilerplate - containing head / body tags but not the body contents. It is a standard html5 doctype template. @@ -389,8 +396,8 @@ not only change the overall page outline, but will also introduce some additiona Each layout, depending on it's content, may come with several views that you can populate. -Admin Layout ------------- +### Admin Layout + .. php:namespace:: Atk4\Ui\Layout .. php:class:: Admin @@ -399,25 +406,29 @@ with top, left and right menu objects. .. php:attr:: menuLeft -Populating the left menu object is simply a matter of adding the right menu items to the layout menu:: +Populating the left menu object is simply a matter of adding the right menu items to the layout menu: - $app->initLayout([\Atk4\Ui\Layout\Admin::class]); - $layout = $app->layout; +``` +$app->initLayout([\Atk4\Ui\Layout\Admin::class]); +$layout = $app->layout; - // Add item into menu - $layout->menuLeft->addItem(['Welcome Page', 'icon' => 'gift'], ['index']); - $layout->menuLeft->addItem(['Layouts', 'icon' => 'object group'], ['layouts']); +// Add item into menu +$layout->menuLeft->addItem(['Welcome Page', 'icon' => 'gift'], ['index']); +$layout->menuLeft->addItem(['Layouts', 'icon' => 'object group'], ['layouts']); - $EditGroup = $layout->menuLeft->addGroup(['Edit', 'icon' => 'edit']); - $EditGroup->addItem('Basics', ['edit/basic']); +$EditGroup = $layout->menuLeft->addGroup(['Edit', 'icon' => 'edit']); +$EditGroup->addItem('Basics', ['edit/basic']); +``` .. php:attr:: menu -This is the top menu of the admin layout. You can add other item to the top menu using:: +This is the top menu of the admin layout. You can add other item to the top menu using: - Button::addTo($layout->menu->addItem(), ['View Source', 'class.teal' => true, 'icon' => 'github']) - ->setAttr('target', '_blank') - ->on('click', new \Atk4\Ui\Js\JsExpression('document.location = [];', [$url . $f])); +``` +Button::addTo($layout->menu->addItem(), ['View Source', 'class.teal' => true, 'icon' => 'github']) + ->setAttr('target', '_blank') + ->on('click', new \Atk4\Ui\Js\JsExpression('document.location = [];', [$url . $f])); +``` .. php:attr:: menuRight @@ -427,9 +438,7 @@ The top right dropdown menu. Whether or not the left menu is open on page load or not. Default is true. - -Integration with Legacy Apps ----------------------------- +### Integration with Legacy Apps If you use Agile UI inside a legacy application, then you may already have layout and some patterns or limitations may be imposed on the app. Your first job would be to properly @@ -438,8 +447,7 @@ implement the "App" and either modification of your existing class or a new clas Having a healthy "App" class will ensure that all of Agile UI components will perform properly. -3rd party Layouts ------------------ +### 3rd party Layouts You should be able to find 3rd party Layout implementations that may even be coming with some custom templates and views. The concept of a "Theme" in Agile UI consists of diff --git a/docs/autocomplete.md b/docs/autocomplete.md index 99c0c12ee6..e78360330c 100644 --- a/docs/autocomplete.md +++ b/docs/autocomplete.md @@ -1,9 +1,6 @@ - .. _autocomplete: -========================= -AutoComplete Form Control -========================= +# AutoComplete Form Control .. php:namespace:: Atk4\Ui\Form\Control .. php:class:: AutoComplete @@ -21,62 +18,64 @@ for AutoComplete form control. Although they look similar, there are some differ AutoComplete can be a drop-in replacement for Dropdown. -Using Plus mode ---------------- +### Using Plus mode In your application, it is handy if you can automatically add a missing "client" from the form where you add an invoice. AutoComplete implements "Plus" mode which will automatically open a modal form where you can enter new record details. The form save will re-use the model of your auto-complete, so be sure to set() defaults and -addCondition()s:: - - $form->addControl('test', [\Atk4\Ui\Form\Control\AutoComplete::class, 'plus' => true]) - ->setModel(new Country($db)); +addCondition()s: -Specifying in Model -------------------- +``` +$form->addControl('test', [\Atk4\Ui\Form\Control\AutoComplete::class, 'plus' => true]) + ->setModel(new Country($db)); +``` -You can also specify that you prefer to use AutoComplete inside your model definition:: +### Specifying in Model - $model->hasOne('country_id', ['model' => [Country::class], 'ui' => ['form' => [\Atk4\Ui\Form\Control\AutoComplete::class]]]); +You can also specify that you prefer to use AutoComplete inside your model definition: -Advanced Usage --------------- +``` +$model->hasOne('country_id', ['model' => [Country::class], 'ui' => ['form' => [\Atk4\Ui\Form\Control\AutoComplete::class]]]); +``` -You can do much more with AutoComplete form control by passing dropdown settings:: +### Advanced Usage - $form->addControl('test', [ - \Atk4\Ui\Form\Control\AutoComplete::class, - 'settings' => [ - 'allowReselection' => true, - 'selectOnKeydown' => false, - 'onChange' => new \Atk4\Ui\Js\JsExpression('function (value, t, c) { - if ($(this).data(\'value\') !== value) { - $(this).parents(\'.form\').form(\'submit\'); - $(this).data(\'value\', value); - }}'), - ], - ])->setModel(new Country($db)); +You can do much more with AutoComplete form control by passing dropdown settings: +``` +$form->addControl('test', [ + \Atk4\Ui\Form\Control\AutoComplete::class, + 'settings' => [ + 'allowReselection' => true, + 'selectOnKeydown' => false, + 'onChange' => new \Atk4\Ui\Js\JsExpression('function (value, t, c) { + if ($(this).data(\'value\') !== value) { + $(this).parents(\'.form\').form(\'submit\'); + $(this).data(\'value\', value); + }}'), + ], +])->setModel(new Country($db)); +``` -Lookup Form Control -=================== +## Lookup Form Control In 1.6 we have introduced Lookup form control, which is identical to AutoComplete but additionally allows -use of Filters:: - +use of Filters: - $form = \Atk4\Ui\Form::addTo($app, ['class.segment' => true]); - \Atk4\Ui\Label::addTo($form, ['Add city', 'class.top attached' => true], ['AboveControls']); +``` +$form = \Atk4\Ui\Form::addTo($app, ['class.segment' => true]); +\Atk4\Ui\Label::addTo($form, ['Add city', 'class.top attached' => true], ['AboveControls']); - $l = $form->addControl('city', [\Atk4\Ui\Form\Control\Lookup::class]); +$l = $form->addControl('city', [\Atk4\Ui\Form\Control\Lookup::class]); - // will restraint possible city value in droddown base on country and/or language. - $l->addFilter('country', 'Country'); - $l->addFilter('language', 'Lang'); +// will restraint possible city value in droddown base on country and/or language. +$l->addFilter('country', 'Country'); +$l->addFilter('language', 'Lang'); - // make sure country and language belong to your model. - $l->setModel(new City($db)); +// make sure country and language belong to your model. +$l->setModel(new City($db)); +``` Possibly this feature will be introduced into "AutoComplete" class. diff --git a/docs/breadcrumb.md b/docs/breadcrumb.md index debbb47b38..36d0887f1a 100644 --- a/docs/breadcrumb.md +++ b/docs/breadcrumb.md @@ -1,43 +1,38 @@ - .. _breadcrumb: -========== -Breadcrumb -========== +# Breadcrumb .. php:namespace:: Atk4\Ui .. php:class:: Breadcrumb Implement navigational Breadcrumb, by using https://fomantic-ui.com/collections/breadcrumb.html -Basic Usage -=========== +## Basic Usage .. php:method:: addCrumb() .. php:method:: set() -Here is a simple usage:: +Here is a simple usage: - $crumb = Breadcrumb::addTo($app); - $crumb->addCrumb('User', ['user']); - $crumb->addCrumb('Preferences', ['user_preferences']); - $crumb->set('Change Password'); +``` +$crumb = Breadcrumb::addTo($app); +$crumb->addCrumb('User', ['user']); +$crumb->addCrumb('Preferences', ['user_preferences']); +$crumb->set('Change Password'); +``` Every time you call addCrumb a new one is added. With set() you can specify the name of the current page. addCrumb also requires a URL passed as second argument which can be either a string or array (which would then be passed to url() (:php:meth:`View::url`). -Changing Divider -================ +## Changing Divider .. php:attr:: dividerClass By default value `right angle icon` is used, but you can change it to `right chevron icon` or simply set to empty string `""` to use slash. - -Working with Path -================= +## Working with Path .. php:attr:: path .. php:method: popTitle() @@ -49,31 +44,33 @@ Calling addCrumb adds more elements into the $path property. Each element there - divider - which divider to use after the crumb By default `divider` is set to :php:attr:`Breadcrumb::dividerClass`. You may also manipulate $path array yourself. -For example the next code will use some logic:: +For example the next code will use some logic: - $crumb = Breadcrumb::addTo($app); - $crumb->addCrumb('Users', []); +``` +$crumb = Breadcrumb::addTo($app); +$crumb->addCrumb('Users', []); - $model = new User($app->db); +$model = new User($app->db); - $id = $app->stickyGet('user_id'); - if ($id) { - // perhaps we edit individual user? - $model = $model->load($id); - $crumb->addCrumb($model->get('name'), []); +$id = $app->stickyGet('user_id'); +if ($id) { + // perhaps we edit individual user? + $model = $model->load($id); + $crumb->addCrumb($model->get('name'), []); - // here we can check for additional criteria and display a deeper level on the crumb + // here we can check for additional criteria and display a deeper level on the crumb - Form::addTo($app)->setModel($model); - } else { - // display list of users - $table = Table::addTo($app); - $table->setModel($model); - $table->addDecorator(['name', [\Atk4\Ui\Table\Column\Link::class, [], ['user_id' => 'id']); - } + Form::addTo($app)->setModel($model); +} else { + // display list of users + $table = Table::addTo($app); + $table->setModel($model); + $table->addDecorator(['name', [\Atk4\Ui\Table\Column\Link::class, [], ['user_id' => 'id']); +} - $crumb->popTitle(); +$crumb->popTitle(); +``` diff --git a/docs/button.md b/docs/button.md index af5abc370f..7e19b87fdd 100644 --- a/docs/button.md +++ b/docs/button.md @@ -1,126 +1,137 @@ - .. _button: -====== -Button -====== +# Button .. php:namespace:: Atk4\Ui .. php:class:: Button -Implements a clickable button:: +Implements a clickable button: - $button = Button::addTo($app, ['Click me']); +``` +$button = Button::addTo($app, ['Click me']); +``` The Button will typically inherit all same properties of a :php:class:`View`. The base class "View" -implements many useful methods already, such as:: +implements many useful methods already, such as: - $button->addClass('big red'); +``` +$button->addClass('big red'); +``` -Alternatvie syntax if you wish to initialize object yourself:: +Alternatvie syntax if you wish to initialize object yourself: - $button = new Button('Click me'); - $button->addClass('big red'); +``` +$button = new Button('Click me'); +$button->addClass('big red'); - $app->add($button); +$app->add($button); +``` You can refer to the Fomantic-UI documentation for Button to find out more about available classes: https://fomantic-ui.com/elements/button.html. Demo: https://ui.agiletoolkit.org/demos/button.php -Button Icon ------------ +### Button Icon .. php:attr:: icon -Property $icon will place icon on your button and can be specified in one of the following two ways:: +Property $icon will place icon on your button and can be specified in one of the following two ways: - $button = Button::addTo($app, ['Like', 'class.blue' => true, 'icon' => 'thumbs up']); +``` +$button = Button::addTo($app, ['Like', 'class.blue' => true, 'icon' => 'thumbs up']); - // or +// or - $button = Button::addTo($app, ['Like', 'class.blue' => true, 'icon' => new Icon('thumbs up')]); +$button = Button::addTo($app, ['Like', 'class.blue' => true, 'icon' => new Icon('thumbs up')]); +``` -or if you prefer initializing objects:: +or if you prefer initializing objects: - $button = new Button('Like'); - $button->addClass('blue'); - $button->icon = new Icon('thumbs u'); +``` +$button = new Button('Like'); +$button->addClass('blue'); +$button->icon = new Icon('thumbs u'); - $app->add($button); +$app->add($button); +``` .. php:attr:: iconRight -Setting this will display icon on the right of the button:: - +Setting this will display icon on the right of the button: - $button = Button::addTo($app, ['Next', 'iconRight' => 'right arrow']); +``` +$button = Button::addTo($app, ['Next', 'iconRight' => 'right arrow']); +``` Apart from being on the right, the same rules apply as :php:attr:`Button::$icon`. Both icons cannot be specified simultaneously. -Button Bar ----------- +### Button Bar Buttons can be arranged into a bar. You would need to create a :php:class:`View` component -with property ``ui='buttons'`` and add your other buttons inside:: - - $bar = View::addTo($app, ['ui' => 'vertical buttons']); +with property ``ui='buttons'`` and add your other buttons inside: - Button::addTo($bar, ['Play', 'icon' => 'play']); - Button::addTo($bar, ['Pause', 'icon' => 'pause']); - Button::addTo($bar, ['Shuffle', 'icon' => 'shuffle']); +``` +$bar = View::addTo($app, ['ui' => 'vertical buttons']); -At this point using alternative syntax where you initialize objects yourself becomes a bit too complex and lengthy:: +Button::addTo($bar, ['Play', 'icon' => 'play']); +Button::addTo($bar, ['Pause', 'icon' => 'pause']); +Button::addTo($bar, ['Shuffle', 'icon' => 'shuffle']); +``` - $bar = new View(); - $bar->ui = 'buttons'; - $bar->addClass('vertical'); +At this point using alternative syntax where you initialize objects yourself becomes a bit too complex and lengthy: - $button = new Button('Play'); - $button->icon = 'play'; - $bar->add($button); +``` +$bar = new View(); +$bar->ui = 'buttons'; +$bar->addClass('vertical'); - $button = new Button('Pause'); - $button->icon = 'pause'; - $bar->add($button); +$button = new Button('Play'); +$button->icon = 'play'; +$bar->add($button); - $button = new Button('Shuffle'); - $button->icon = 'shuffle'; - $bar->add($button); +$button = new Button('Pause'); +$button->icon = 'pause'; +$bar->add($button); - $app->add($bar); +$button = new Button('Shuffle'); +$button->icon = 'shuffle'; +$bar->add($button); +$app->add($bar); +``` -Linking -------- +### Linking .. php:method:: link -Will link button to a destination URL or page:: +Will link button to a destination URL or page: - $button->link('https://google.com/'); - // or - $button->link(['details', 'id' => 123]); +``` +$button->link('https://google.com/'); +// or +$button->link(['details', 'id' => 123]); +``` If array is used, it's routed to :php:meth:`App::url` -For other JavaScript actions you can use :ref:`js`:: - - $button->on('click', new JsExpression('document.location.reload()')); - -Complex Buttons ---------------- +For other JavaScript actions you can use :ref:`js`: +``` +$button->on('click', new JsExpression('document.location.reload()')); +``` +### Complex Buttons Knowledge of the Fomantic-UI button (https://fomantic-ui.com/elements/button.html) can help you -in creating more complex buttons:: - - $forks = new Button(['labeled' => true]); // Button, not Buttons! - Icon::addTo(Button::addTo($forks, ['Forks', 'class.blue' => true]), ['fork']); - Label::addTo($forks, ['1,048', 'class.basic blue left pointing' => true]); - $app->add($forks); +in creating more complex buttons: + +``` +$forks = new Button(['labeled' => true]); // Button, not Buttons! +Icon::addTo(Button::addTo($forks, ['Forks', 'class.blue' => true]), ['fork']); +Label::addTo($forks, ['1,048', 'class.basic blue left pointing' => true]); +$app->add($forks); +``` diff --git a/docs/callbacks.md b/docs/callbacks.md index b0bdb1925f..ca383f21e4 100644 --- a/docs/callbacks.md +++ b/docs/callbacks.md @@ -1,6 +1,4 @@ - -Callback Introduction ---------------------- +### Callback Introduction Agile UI pursues a goal of creating a full-featured, interactive, user interface. Part of that relies on abstraction of Browser/Server communication. @@ -10,21 +8,22 @@ through a unique route and not worry about accidentally affecting or triggering component. One example of this behavior is the format of :php:meth:`View::on` where you pass 2nd argument as a -PHP callback:: +PHP callback: - $button = new Button(); +``` +$button = new Button(); - // clicking button generates random number every time - $button->on('click', function (Jquery $j) { - return $j->text(rand(1, 100)); - }); +// clicking button generates random number every time +$button->on('click', function (Jquery $j) { + return $j->text(rand(1, 100)); +}); +``` This creates callback route transparently which is triggered automatically during the 'click' event. To make this work seamlessly there are several classes at play. This documentation chapter will walk you through the callback mechanisms of Agile UI. -The Callback class ------------------- +### The Callback class .. php:class:: Callback @@ -35,10 +34,12 @@ traits: - `AppScopeTrait `_ - `DiContainerTrait `_ -To create a new callback, do this:: +To create a new callback, do this: - $c = new \Atk4\Ui\Callback(); - $app->add($c); +``` +$c = new \Atk4\Ui\Callback(); +$app->add($c); +``` Because 'Callback' is not a View, it won't be rendered. The reason we are adding into :ref:`render_tree` is for it to establish a unique name which will be used to generate callback URL: @@ -47,23 +48,27 @@ is for it to establish a unique name which will be used to generate callback URL .. php:method:: set -The following example code generates unique URL:: +The following example code generates unique URL: - $label = \Atk4\Ui\Label::addTo($app, ['Callback URL:']); - $cb = \Atk4\Ui\Callback::addTo($label); - $label->detail = $cb->getUrl(); - $label->link($cb->getUrl()); +``` +$label = \Atk4\Ui\Label::addTo($app, ['Callback URL:']); +$cb = \Atk4\Ui\Callback::addTo($label); +$label->detail = $cb->getUrl(); +$label->link($cb->getUrl()); +``` I have assigned generated URL to the label, so that if you click it, your browser will visit callback URL triggering a special action. We haven't set that action yet, so I'll do it next with -:php:meth::`Callback::set()`:: +:php:meth::`Callback::set()`: + +``` +$cb->set(function () use ($app) { + $app->terminate('in callback'); +}); +``` - $cb->set(function () use ($app) { - $app->terminate('in callback'); - }); +### Callback Triggering -Callback Triggering -------------------- To illustrate how callbacks work, let's imagine the following workflow: - your application with the above code resides in file 'test.php` @@ -81,20 +86,21 @@ another request to the server: Calling :php:meth:`App::terminate()` will prevent the default behaviour (of rendering UI) and will output specified string instead, stopping further execution of your application. -Return value of set() ---------------------- +### Return value of set() The callback verifies trigger condition when you call :php:meth:`Callback::set()`. If your callback -returns any value, the set() will return it too:: +returns any value, the set() will return it too: - $label = \Atk4\Ui\Label::addTo($app, ['Callback URL:']); - $cb = \Atk4\Ui\Callback::addTo($label); - $label->detail = $cb->getUrl(); - $label->link($cb->getUrl()); +``` +$label = \Atk4\Ui\Label::addTo($app, ['Callback URL:']); +$cb = \Atk4\Ui\Callback::addTo($label); +$label->detail = $cb->getUrl(); +$label->link($cb->getUrl()); - if ($cb->set(function () { return true; })) { - $label->addClass('red'); - } +if ($cb->set(function () { return true; })) { + $label->addClass('red'); +} +``` This example uses return of the :php:meth:`Callback::set()` to add class to a label, however a much more preferred way is to use :php:attr:`$triggered`. @@ -104,20 +110,22 @@ much more preferred way is to use :php:attr:`$triggered`. You use property `triggered` to detect if callback was executed or not, without short-circuting the execution with set() and terminate(). This can be helpful sometimes when you need to affect the rendering of the page through a special callback link. The next example will change color of -the label regardless of the callback function:: +the label regardless of the callback function: - $label = \Atk4\Ui\Label::addTo($app, ['Callback URL:']); - $cb = \Atk4\Ui\Callback::addTo($label); - $label->detail = $cb->getUrl(); - $label->link($cb->getUrl()); +``` +$label = \Atk4\Ui\Label::addTo($app, ['Callback URL:']); +$cb = \Atk4\Ui\Callback::addTo($label); +$label->detail = $cb->getUrl(); +$label->link($cb->getUrl()); - $cb->set(function () { - echo 123; - }); +$cb->set(function () { + echo 123; +}); - if ($cb->triggered) { - $label->addClass('red'); - } +if ($cb->triggered) { + $label->addClass('red'); +} +``` .. php:attr:: postTrigger @@ -132,8 +140,7 @@ derived classes. Specifies which GET parameter to use for triggering. Normally it's same as `$callback->name`, but you can set it to anything you want. As long as you keep it unique on a current page, you should be OK. -CallbackLater -------------- +### CallbackLater .. php:class:: CallbackLater @@ -141,17 +148,19 @@ This class is very similar to Callback, but it will not execute immediately. Ins either at the end at beforeRender or beforeOutput hook from inside App, whichever comes first. In other words this won't break the flow of your code logic, it simply won't render it. In the next example -the $label->detail is assigned at the very end, yet callback is able to access the property:: +the $label->detail is assigned at the very end, yet callback is able to access the property: - $label = \Atk4\Ui\Label::addTo($app, ['Callback URL:']); - $cb = \Atk4\Ui\CallbackLater::addTo($label); +``` +$label = \Atk4\Ui\Label::addTo($app, ['Callback URL:']); +$cb = \Atk4\Ui\CallbackLater::addTo($label); - $cb->set(function () use ($app, $label) { - $app->terminate('Label detail is ' . $label->detail); - }); +$cb->set(function () use ($app, $label) { + $app->terminate('Label detail is ' . $label->detail); +}); - $label->detail = $cb->getUrl(); - $label->link($cb->getUrl()); +$label->detail = $cb->getUrl(); +$label->link($cb->getUrl()); +``` CallbackLater is used by several actions in Agile UI, such as JsReload(), and ensures that the component you are reloading are fully rendered by the time callback is executed. @@ -163,14 +172,16 @@ know about :php:class:`JsReload` already? - you must specify a view to JsReload - when triggered, the view will refresh itself on the screen. -Here is example of JsReload:: +Here is example of JsReload: - $view = \Atk4\Ui\View::addTo($app, ['ui' => 'tertiary green inverted segment']); - $button = \Atk4\Ui\Button::addTo($app, ['Reload Lorem']); +``` +$view = \Atk4\Ui\View::addTo($app, ['ui' => 'tertiary green inverted segment']); +$button = \Atk4\Ui\Button::addTo($app, ['Reload Lorem']); - $button->on('click', new \Atk4\Ui\Js\JsReload($view)); +$button->on('click', new \Atk4\Ui\Js\JsReload($view)); - \Atk4\Ui\LoremIpsum::addTo($view); +\Atk4\Ui\LoremIpsum::addTo($view); +``` NOTE: that we can't perform JsReload on LoremIpsum directly, because it's a text, it needs to be inside @@ -182,9 +193,7 @@ Should JsReload use regular 'Callback', then it wouldn't know that $view must co JsReload existence is only possible thanks to CallbackLater implementation. - -JsCallback ----------- +### JsCallback .. php:class:: JsCallback @@ -193,21 +202,25 @@ value was meaningful in some way? JsCallback implements exactly that. When you specify a handler for JsCallback, it can return one or multiple :ref:`js_action` which will be rendered into JavaScript in response to triggering callback's URL. Let's bring up our older example, but will -use JsCallback class now:: +use JsCallback class now: - $label = \Atk4\Ui\Label::addTo($app, ['Callback URL:']); - $cb = \Atk4\Ui\JsCallback::addTo($label); +``` +$label = \Atk4\Ui\Label::addTo($app, ['Callback URL:']); +$cb = \Atk4\Ui\JsCallback::addTo($label); - $cb->set(function () { - return 'ok'; - }); +$cb->set(function () { + return 'ok'; +}); - $label->detail = $cb->getUrl(); - $label->link($cb->getUrl()); +$label->detail = $cb->getUrl(); +$label->link($cb->getUrl()); +``` -When you trigger callback, you'll see the output:: +When you trigger callback, you'll see the output: - {"success": true, "message": "Success", "eval": "alert(\"ok\")"} +``` +{"success": true, "message": "Success", "eval": "alert(\"ok\")"} +``` This is how JsCallback renders actions and sends them back to the browser. In order to retrieve and execute actions, you'll need a JavaScript routine. Luckily JsCallback can be passed to :php:meth:`View::on()` as a JS action. @@ -215,25 +228,29 @@ you'll need a JavaScript routine. Luckily JsCallback can be passed to :php:meth: Let me try this again. JsCallback is an :ref:`js_action` which will execute request towards a callback-URL that will execute PHP method returning one or more :ref:`js_action` which will be received and executed by the original action. -To fully use jsAction above, here is a modified code:: +To fully use jsAction above, here is a modified code: - $label = \Atk4\Ui\Label::addTo($app, ['Callback URL:']); - $cb = \Atk4\Ui\JsCallback::addTo($label); +``` +$label = \Atk4\Ui\Label::addTo($app, ['Callback URL:']); +$cb = \Atk4\Ui\JsCallback::addTo($label); - $cb->set(function () { - return 'ok'; - }); +$cb->set(function () { + return 'ok'; +}); - $label->detail = $cb->getUrl(); - $label->on('click', $cb); +$label->detail = $cb->getUrl(); +$label->on('click', $cb); +``` -Now, that is pretty long. For your convenience, there is a shorter mechanism:: +Now, that is pretty long. For your convenience, there is a shorter mechanism: - $label = \Atk4\Ui\Label::addTo($app, ['Callback test']); +``` +$label = \Atk4\Ui\Label::addTo($app, ['Callback test']); - $label->on('click', function () { - return 'ok'; - }); +$label->on('click', function () { + return 'ok'; +}); +``` User Confirmation ^^^^^^^^^^^^^^^^^ @@ -243,28 +260,32 @@ is based on 'Callback' therefore code after :php:meth:`View::on()` will not be e .. php:attr:: confirm -If you set `confirm` property action will ask for user's confirmation before sending a callback:: +If you set `confirm` property action will ask for user's confirmation before sending a callback: - $label = \Atk4\Ui\Label::addTo($app, ['Callback URL:']); - $cb = \Atk4\Ui\JsCallback::addTo($label); +``` +$label = \Atk4\Ui\Label::addTo($app, ['Callback URL:']); +$cb = \Atk4\Ui\JsCallback::addTo($label); - $cb->confirm = 'sure?'; +$cb->confirm = 'sure?'; - $cb->set(function () { - return 'ok'; - }); +$cb->set(function () { + return 'ok'; +}); - $label->detail = $cb->getUrl(); - $label->on('click', $cb); +$label->detail = $cb->getUrl(); +$label->on('click', $cb); +``` This is used with delete operations. When using :php:meth:`View::on()` you can pass extra argument to set the 'confirm' -property:: +property: - $label = \Atk4\Ui\Label::addTo($app, ['Callback test']); +``` +$label = \Atk4\Ui\Label::addTo($app, ['Callback test']); - $label->on('click', function () { - return 'ok'; - }, ['confirm' => 'sure?']); +$label->on('click', function () { + return 'ok'; +}, ['confirm' => 'sure?']); +``` JavaScript arguments ^^^^^^^^^^^^^^^^^^^^ @@ -272,45 +293,53 @@ JavaScript arguments .. php:method:: set($callback, $arguments = []) It is possible to modify expression of JsCallback to pass additional arguments to it's callback. The next example -will send browser screen width back to the callback:: +will send browser screen width back to the callback: - $label = \Atk4\Ui\Label::addTo($app); - $cb = \Atk4\Ui\JsCallback::addTo($label); +``` +$label = \Atk4\Ui\Label::addTo($app); +$cb = \Atk4\Ui\JsCallback::addTo($label); - $cb->set(function (\Atk4\Ui\Js\Jquery $j, $arg1) { - return 'width is ' . $arg1; - }, [new \Atk4\Ui\Js\JsExpression('$(window).width()')]); +$cb->set(function (\Atk4\Ui\Js\Jquery $j, $arg1) { + return 'width is ' . $arg1; +}, [new \Atk4\Ui\Js\JsExpression('$(window).width()')]); - $label->detail = $cb->getUrl(); - $label->on('click', $cb); +$label->detail = $cb->getUrl(); +$label->on('click', $cb); +``` In here you see that I'm using a 2nd argument to $cb->set() to specify arguments, which, I'd like to fetch from the browser. Those arguments are passed to the callback and eventually arrive as $arg1 inside my callback. The :php:meth:`View::on()` -also supports argument passing:: +also supports argument passing: - $label = \Atk4\Ui\Label::addTo($app, ['Callback test']); +``` +$label = \Atk4\Ui\Label::addTo($app, ['Callback test']); - $label->on('click', function (Jquery $j, $arg1) { - return 'width is ' . $arg1; - }, ['confirm' => 'sure?', 'args' => [new \Atk4\Ui\Js\JsExpression('$(window).width()')]]); +$label->on('click', function (Jquery $j, $arg1) { + return 'width is ' . $arg1; +}, ['confirm' => 'sure?', 'args' => [new \Atk4\Ui\Js\JsExpression('$(window).width()')]]); +``` -If you do not need to specify confirm, you can actually pass arguments in a key-less array too:: +If you do not need to specify confirm, you can actually pass arguments in a key-less array too: - $label = \Atk4\Ui\Label::addTo($app, ['Callback test']); +``` +$label = \Atk4\Ui\Label::addTo($app, ['Callback test']); - $label->on('click', function (Jquery $j, $arg1) { - return 'width is ' . $arg1; - }, [new \Atk4\Ui\Js\JsExpression('$(window).width()')]); +$label->on('click', function (Jquery $j, $arg1) { + return 'width is ' . $arg1; +}, [new \Atk4\Ui\Js\JsExpression('$(window).width()')]); +``` Referring to event origin -^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^ You might have noticed that JsCallback now passes first argument ($j) which so far, we have ignored. This argument is a jQuery chain for the element which received the event. We can change the response to do something with this element. -Instead of `return` use:: +Instead of `return` use: - $j->text('width is ' . $arg1); +``` +$j->text('width is ' . $arg1); +``` Now instead of showing an alert box, label content will be changed to display window width. diff --git a/docs/components.md b/docs/components.md index f420ce02a7..5a7763b3ff 100644 --- a/docs/components.md +++ b/docs/components.md @@ -1,12 +1,8 @@ -========== -Components -========== +# Components Classes that extend from :php:class:`View` are called `Components` and inherit abilities to render themselves (see :ref:`render`) - -Core Components -=============== +## Core Components Some components serve as a foundation of entire set of other components. A lot of qualities implemented by a core component is inherited by its descendants. @@ -21,9 +17,7 @@ inherited by its descendants. fileupload tablecolumn -Simple components -================= - +## Simple components .. figure:: images/simple.png :scale: 30% @@ -50,9 +44,7 @@ control over even the small elements. accordion helloworld - -Interactive components -======================= +## Interactive components .. figure:: images/interactive.png :scale: 30% @@ -81,9 +73,7 @@ other code. Here is how interactive components will typically communicate: rightpanel dataexecutor - -Composite components -==================== +## Composite components .. figure:: images/composite.png :scale: 30% diff --git a/docs/conf.py b/docs/conf.py index 8daae9523b..f2c8f31feb 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -24,8 +24,6 @@ project = u'Agile UI' copyright = u'2016-2019, Agile Toolkit' -version = 'v1.x' - exclude_patterns = ['_build'] # If true, '()' will be appended to :func: etc. cross-reference text. @@ -56,11 +54,6 @@ # a list of builtin themes. html_theme = 'sphinx_rtd_theme' -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. diff --git a/docs/console.md b/docs/console.md index 84d1745cbf..a20d4b522e 100644 --- a/docs/console.md +++ b/docs/console.md @@ -2,9 +2,7 @@ .. php:class:: Console -======= -Console -======= +# Console .. figure:: images/console.png @@ -14,25 +12,25 @@ be used do direct output from slow method or even execute commands on the server Demo: https://ui.agiletoolkit.org/demos/console.php - -Basic Usage -=========== +## Basic Usage .. php:method:: set($callback) .. php:method:: send($callback) -After adding a console to your :ref:`render_tree`, you just need to set a callback:: +After adding a console to your :ref:`render_tree`, you just need to set a callback: - $console = Console::addTo($app); - $console->set(function (Console $console) { - // This will be executed through SSE request - $console->output('hello'); - echo 'world'; // also will be redirected to console - sleep(2); - $console->send(new \Atk4\Ui\Js\JsExpression('alert([])', ['The wait is over'])); - }); +``` +$console = Console::addTo($app); +$console->set(function (Console $console) { + // This will be executed through SSE request + $console->output('hello'); + echo 'world'; // also will be redirected to console + sleep(2); + $console->send(new \Atk4\Ui\Js\JsExpression('alert([])', ['The wait is over'])); +}); +``` Console uses :ref:`sse` which works pretty much out-of-the-box with the modern browsers and unlike websockets do not require you to set up additional ports on the server. JavaScript in a browser captures real-time @@ -41,70 +39,81 @@ events and displays it on a black background. Console integrates nicely with DebugTrait (https://agile-core.readthedocs.io/en/develop/debug.html?highlight=debug), and also allows you to execute shell process on the server while redirecting output in real-time. -Using With Object -================= +## Using With Object .. php:method:: runMethod($callback); We recommend that you pack up your busineess logic into your Model methods. When it's time to call your method, -you could either do this:: +you could either do this: - $user->generateReport(30); +``` +$user->generateReport(30); +``` Which would execute your own routine for some report generation, but doing it though a normal request will look like -your site is slow and is unable to load page quick. Alternative is to run it through a console:: - - $console->runMethod($user, 'generateReport', [30]); +your site is slow and is unable to load page quick. Alternative is to run it through a console: -This will display console to the user and will even output information from inside the model:: +``` +$console->runMethod($user, 'generateReport', [30]); +``` +This will display console to the user and will even output information from inside the model: - use \Atk4\Core\DebugTrait(); +``` +use \Atk4\Core\DebugTrait(); - public function generateReport($pages) - { - $this->info('converting report to PDF'); +public function generateReport($pages) +{ + $this->info('converting report to PDF'); - // slow stuff - $this->info('almost done, be patient..'); + // slow stuff + $this->info('almost done, be patient..'); - // more slow stuff - return true; - } + // more slow stuff + return true; +} +``` -You can also execute static methods:: +You can also execute static methods: - $console->runMethod('StaticLib', 'myStaticMethod'); +``` +$console->runMethod('StaticLib', 'myStaticMethod'); +``` -Executing Commands -================== +## Executing Commands .. php:method:: exec($cmd, $args); .. php:argument:: lastExitCode -To execute a command, use:: +To execute a command, use: - $console->exec('/sbin/ping', ['-c', '5', '-i', '1', '192.168.0.1']); +``` +$console->exec('/sbin/ping', ['-c', '5', '-i', '1', '192.168.0.1']); +``` This will run a command, and will stream command output to you. Console is implemented to capture both STDOUT and STDERR in real-time then display it on the console using color. Console does not support ANSI output. -Method exec can be executed directly on the $console or inside the callback:: +Method exec can be executed directly on the $console or inside the callback: - $console->set(function (Console $console) { - $console->eval(); - }); +``` +$console->set(function (Console $console) { + $console->eval(); +}); +``` Without callback, eval will wrap itself into a callback but you can only execute a single command. When using callback -form, you can execute multiple commands:: +form, you can execute multiple commands: - Console::addTo($app)->set(function (Console $c) { - $c - ->exec('/sbin/ping', ['-c', '5', '-i', '1', '192.168.0.1']) - ->exec('/sbin/ping', ['-c', '5', '-i', '2', '8.8.8.8']) - ->exec('/bin/no-such-command'); - }); +``` +Console::addTo($app)->set(function (Console $c) { + $c + ->exec('/sbin/ping', ['-c', '5', '-i', '1', '192.168.0.1']) + ->exec('/sbin/ping', ['-c', '5', '-i', '2', '8.8.8.8']) + ->exec('/bin/no-such-command'); +}); +``` Method exec() will return `$this` if command was run inside callback and was successful. It will return `false` on error and will return `null` if called outside of callback. You may also refer to ::php:attr:`Console::lastExitCode` which @@ -114,11 +123,13 @@ Normally it's safe to chain `exec` which ensures that execution will stack. Shou `exec` won't be performed. NOTE that for each invocation `exec` will spawn a new process, but if you want to execute multiple processes, you -can wrap them into `bash -c`:: - - Console::addTo($app)->exec('bash', [ - '-c', - 'cd ..; echo \'Running "composer update" in `pwd`\'; composer --no-ansi update; echo \'Self-updated. OK to refresh now!\'', - ]); +can wrap them into `bash -c`: + +``` +Console::addTo($app)->exec('bash', [ + '-c', + 'cd ..; echo \'Running "composer update" in `pwd`\'; composer --no-ansi update; echo \'Self-updated. OK to refresh now!\'', +]); +``` This also demonstrates argument escaping. diff --git a/docs/core.md b/docs/core.md index bc9c2de937..e102b7ff27 100644 --- a/docs/core.md +++ b/docs/core.md @@ -1,6 +1,4 @@ -============= -Core Concepts -============= +# Core Concepts .. php:namespace:: Atk4\Ui @@ -8,37 +6,42 @@ Agile Toolkit and Agile UI are built upon specific core concepts. Understanding concepts is very important especially if you plan to write and distribute your own add-ons. -App -=== +## App In any Agile UI application you will always need to have an App class. Even if you do not create this class explicitly, components generally will do it for you. The common pattern -is:: +is: - $app = new \Atk4\Ui\App('My App'); - $app->initLayout([\Atk4\Ui\Layout\Centered::class]); - LoremIpsum::addTo($app); +``` +$app = new \Atk4\Ui\App('My App'); +$app->initLayout([\Atk4\Ui\Layout\Centered::class]); +LoremIpsum::addTo($app); +``` .. toctree:: app .. _seed: -Seed -==== +## Seed + Agile UI is developed to be easy to read and with simple and concise syntax. We make use of -PHP's dynamic nature, therefore two syntax patterns are supported everywhere:: +PHP's dynamic nature, therefore two syntax patterns are supported everywhere: - Button::addTo($app, ['Hello']); +``` +Button::addTo($app, ['Hello']); - and +and - Button::addTo($app, ['Hello']); +Button::addTo($app, ['Hello']); +``` Method add() supports arguments in various formats and we call that "Seed". The same format -can be used elsewhere, for example:: +can be used elsewhere, for example: - $button->icon = 'book'; +``` +$button->icon = 'book'; +``` We call this format 'Seed'. This section will explain how and where it is used. @@ -48,45 +51,51 @@ We call this format 'Seed'. This section will explain how and where it is used. .. _render: .. _render_tree: -Render Tree -=========== +## Render Tree + Agile Toolkit allows you to create components hierarchically. Once complete, the component hierarchy will render itself and will present HTML output that would appear to user. -You can create and link multiple UI objects together before linking them with other chunks of your UI:: +You can create and link multiple UI objects together before linking them with other chunks of your UI: - $msg = new \Atk4\Ui\Message('Hey There'); - Button::addTo($msg, ['Button']); +``` +$msg = new \Atk4\Ui\Message('Hey There'); +Button::addTo($msg, ['Button']); - $app->add($msg); +$app->add($msg); +``` To find out more about how components are linked up together and rendered, see: .. toctree:: render -Sticky GET -========== +## Sticky GET + Agile UI implements advanced approach allowing any View object that you add into Render Tree to -declare "sticky GET arguments". Here is example:: +declare "sticky GET arguments". Here is example: - if (isset($_GET['message'])) { - Message::addTo($app)->set($_GET['message']); - } +``` +if (isset($_GET['message'])) { + Message::addTo($app)->set($_GET['message']); +} - Button::addTo($app, ['Trigger message'])->link(['message' => 'Hello World']); +Button::addTo($app, ['Trigger message'])->link(['message' => 'Hello World']); +``` The code is simple - if you click the button, page will appear with the message just above, however there is a potential problem here. What if "Message" wanted to perform a :ref:`Callback`? What if we use :php:class:`Console` instead, which must display an interactive data stream? -In Agile UI you can request that some $_GET arguments are preserved and included into callback urls:: +In Agile UI you can request that some $_GET arguments are preserved and included into callback urls: - if ($this->getApp()->stickyGet('message')) { - Message::addTo($app)->set($_GET['message']); - } +``` +if ($this->getApp()->stickyGet('message')) { + Message::addTo($app)->set($_GET['message']); +} - Button::addTo($app, ['Trigger message'])->link(['message' => 'Hello World']); +Button::addTo($app, ['Trigger message'])->link(['message' => 'Hello World']); +``` There are two types of "sticky" parameters, application-wide and view-specific. @@ -94,8 +103,7 @@ There are two types of "sticky" parameters, application-wide and view-specific. sticky -Type Presentation -================= +## Type Presentation Several components are too complex to be implemented in a single class. :php:class:`Table`, for example, has the ability to format columns by utilizing type-specific column classes. Another example is :php:class:`Form` @@ -107,10 +115,8 @@ by having the ability to introduce new types with consistent support throughout .. toctree:: type-presentation +## Templates - -Templates -========= Agile UI components store their HTML inside `*.html` template files. Those files are loaded and manipulated by a Template class. @@ -120,22 +126,21 @@ behavior see: .. toctree:: template - - -Agile Data -========== +## Agile Data Agile UI framework is focused on building User Interfaces, but quite often interface must present data values to the user or even receive data values from user's input. Agile UI uses various techniques to present data formats, so that as a developer you wouldn't -have to worry over the details:: +have to worry over the details: - $user = new User($db); - $user = $user->load(1); +``` +$user = new User($db); +$user = $user->load(1); - $view = View::addTo($app, ['template' => 'Hello, {$name}, your balance is {$balance}']); - $view->setModel($user); +$view = View::addTo($app, ['template' => 'Hello, {$name}, your balance is {$balance}']); +$view->setModel($user); +``` Next section will explain you how the Agile UI interacts with the data layer and how it outputs or inputs user data. @@ -145,8 +150,7 @@ inputs user data. .. _callback: -Callbacks -========= +## Callbacks By relying on the ability of generating :ref:`unique_name`, it's possible to create several classes for implementing PHP callbacks. They follow the pattern: @@ -155,11 +159,13 @@ for implementing PHP callbacks. They follow the pattern: - generate URL with unique parameter - if unique parameter is passed back, behave differently -Once the concept is established, it can even be used on a higher level, for example:: +Once the concept is established, it can even be used on a higher level, for example: - $button->on('click', function () { - return 'clicked button'; - }); +``` +$button->on('click', function () { + return 'clicked button'; +}); +``` .. toctree:: :maxdepth: 4 @@ -169,18 +175,19 @@ Once the concept is established, it can even be used on a higher level, for exam .. _virtualpage: -VirtualPage -=========== +## VirtualPage Building on the foundation of :ref:`callback`, components :php:class:`VirtualPage` and :php:class:`Loader` -exist to enhance other Components with dynamically loadable content. Here is example for :php:class:`Tabs`:: +exist to enhance other Components with dynamically loadable content. Here is example for :php:class:`Tabs`: - $tabs = Tabs::addTo($app); - LoremIpsum::addTo($tabs->addTab('First tab is static')); +``` +$tabs = Tabs::addTo($app); +LoremIpsum::addTo($tabs->addTab('First tab is static')); - $tabs->addTab('Second tab is dynamic', function (VirtualPage $vp) { - LoremIpsum::addTo($vp); - }); +$tabs->addTab('Second tab is dynamic', function (VirtualPage $vp) { + LoremIpsum::addTo($vp); +}); +``` As you switch between those two tabs, you'll notice that the :php:class:`Button` label on the "Second tab" reloads every time. :php:class:`Tabs` implements this by using :php:class:`VirtualPage`, read further to @@ -192,10 +199,7 @@ find out how: virtualpage - - -Documentation is coming soon. -============================= +## Documentation is coming soon. .. toctree:: :maxdepth: 4 diff --git a/docs/crud.md b/docs/crud.md index 1faf35b5be..950e103fff 100644 --- a/docs/crud.md +++ b/docs/crud.md @@ -1,9 +1,6 @@ - .. _crud: -==== -Crud -==== +# Crud .. php:namespace:: Atk4\Ui .. php:class:: Crud @@ -19,43 +16,48 @@ updating and adding records as well as linking them with corresponding Model act .. important:: ATK Addon - MasterCrud implements a higher-level multi-model management solution, that takes advantage of model relations and traversal to create multiple levels of Cruds: https://github.com/atk4/mastercrud -Using Crud -========== +## Using Crud -The basic usage of Crud is:: +The basic usage of Crud is: - Crud::addTo($app)->setModel(new Country($app->db)); +``` +Crud::addTo($app)->setModel(new Country($app->db)); +``` Users are now able to fully interact with the table. There are ways to restrict which "rows" and which "columns" user -can access. First we can only allow user to read, manage and delete only countries that are part of European Union:: +can access. First we can only allow user to read, manage and delete only countries that are part of European Union: - $euCountries = new Country($app->db); - $euCountries->addCondition('is_eu', true); +``` +$euCountries = new Country($app->db); +$euCountries->addCondition('is_eu', true); - Crud::addTo($app)->setModel($euCountries); +Crud::addTo($app)->setModel($euCountries); +``` After that column `is_eu` will not be editable to the user anymore as it will be marked `system` by `addCondition`. -You can also specify which columns you would like to see on the grid:: +You can also specify which columns you would like to see on the grid: - $crud->setModel($euCountries, ['name']); +``` +$crud->setModel($euCountries, ['name']); +``` This restriction will apply to both viewing and editing, but you can fine-tune that by specifying one of many parameters to Crud. -Disabling Actions -================= +## Disabling Actions By default Crud allows all four operations - creating, reading, updating and deleting. These action is set by default in model -action. It is possible to disable these default actions by setting their system property to true in your model:: +action. It is possible to disable these default actions by setting their system property to true in your model: - $euCountries->getUserAction('edit')->system = true; +``` +$euCountries->getUserAction('edit')->system = true; +``` Model action using system property set to true, will not be display in Crud. Note that action must be setup prior to use `$crud->setModel($euCountries)` -Specifying Fields (for different views) -======================================= +## Specifying Fields (for different views) .. php:attr:: displayFields @@ -68,12 +70,14 @@ you can choose here the fields that are available in the editing modal window. .. important:: Both views (overview and editing view) refer to the same model, just the fields shown in either of them differ -Example:: +Example: - $crud = \Atk4\Ui\Crud::addTo($app); - $model = new \Atk4\Data\Model($app->db); - $crud->displayFields(['field1, field2']); - $crud->editFields(['field1, field2, field3, field4']); +``` +$crud = \Atk4\Ui\Crud::addTo($app); +$model = new \Atk4\Data\Model($app->db); +$crud->displayFields(['field1, field2']); +$crud->editFields(['field1, field2, field3, field4']); +``` .. php:attr:: addFields @@ -81,55 +85,52 @@ Through those properties you can specify which fields to use when form is displa Field name add here will have priorities over the action fields properties. When set to null, the action fields property will be used. - -Custom Form Behavior -==================== +## Custom Form Behavior :php:class:`Form` in Agile UI allows you to use many different things, such as custom layouts. With Crud you can -specify your own form behavior using a callback for action:: - - // callback for model action add form. - $g->onFormAdd(function (Form $form, ModalExecutor $ex) { - $form->js(true, $form->getControl('name')->jsInput()->val('Entering value via javascript')); - }); - - // callback for model action edit form. - $g->onFormEdit(function (Form $form, ModalExecutor $ex) { - $form->js(true, $form->getControl('name')->jsInput()->attr('readonly', true)); - }); - - // callback for both model action edit and add. - $g->onFormAddEdit(function (Form $form, ModalExecutor $ex) { - $form->onSubmit(function (Form $form) use ($ex) { - return new \Atk4\Ui\Js\JsBlock([ - $ex->hide(), - new \Atk4\Ui\Js\JsToast('Submit all right! This demo does not saved data.'), - ]); - }); +specify your own form behavior using a callback for action: + +``` +// callback for model action add form. +$g->onFormAdd(function (Form $form, ModalExecutor $ex) { + $form->js(true, $form->getControl('name')->jsInput()->val('Entering value via javascript')); +}); + +// callback for model action edit form. +$g->onFormEdit(function (Form $form, ModalExecutor $ex) { + $form->js(true, $form->getControl('name')->jsInput()->attr('readonly', true)); +}); + +// callback for both model action edit and add. +$g->onFormAddEdit(function (Form $form, ModalExecutor $ex) { + $form->onSubmit(function (Form $form) use ($ex) { + return new \Atk4\Ui\Js\JsBlock([ + $ex->hide(), + new \Atk4\Ui\Js\JsToast('Submit all right! This demo does not saved data.'), + ]); }); +}); +``` Callback function will receive the Form and ActionExecutor as arguments. - -Changing titles -=============== +## Changing titles .. important:: Changing the title of the CRUD's grid view must be done before setting the model. Changing the title of the modal of a CRUD's modal window must be done after loading the model. Otherwise the changes will have no effect. -Here's an example:: - - $crud = \Atk4\Ui\Crud::addTo($app); - $model = new \Atk4\Data\Model($app->db); - $model->getUserAction('add')->description = 'New title for adding a record'; // the button of the overview - must be loaded before setting model - $crud->setModel($model); - $model->getUserAction('add')->ui['executor']->title = 'New title for modal'; // the button of the modal - must be rendered after setting model - +Here's an example: +``` +$crud = \Atk4\Ui\Crud::addTo($app); +$model = new \Atk4\Data\Model($app->db); +$model->getUserAction('add')->description = 'New title for adding a record'; // the button of the overview - must be loaded before setting model +$crud->setModel($model); +$model->getUserAction('add')->ui['executor']->title = 'New title for modal'; // the button of the modal - must be rendered after setting model +``` -Notification -============ +## Notification .. php:attr:: notifyDefault .. php:attr:: saveMsg diff --git a/docs/data.md b/docs/data.md index bdc832b4f5..edf0e00b8d 100644 --- a/docs/data.md +++ b/docs/data.md @@ -1,9 +1,6 @@ - - .. _data: -Integration ------------ +### Integration Agile UI relies on Agile Data library for flexible access to user defined data sources. The purpose of this integration is to relieve a developer from manually creating data fetching and storing code. @@ -15,17 +12,18 @@ bigger code footprint. There are no way to use Agile UI without Agile Data, however Agile Data is flexible enough to work with your own data sources. The rest of this chapter will explain how you can map various data structures. -Static Data Arrays ------------------- +### Static Data Arrays Agile Data contains Persistence\Array_ (https://agile-data.readthedocs.io/en/develop/design.html?highlight=array#domain-model-actions) implementation that load and store data in a regular PHP arrays. For the "quick and easy" solution Agile UI Views provide a -method :php:meth:`View::setSource` which will work-around complexities and give you a syntax:: +method :php:meth:`View::setSource` which will work-around complexities and give you a syntax: - $grid->setSource([ - 1 => ['name' => 'John', 'surname' => 'Smith', 'age' => 10], - 2 => ['name' => 'Sarah', 'surname' => 'Kelly', 'age' => 20], - ]); +``` +$grid->setSource([ + 1 => ['name' => 'John', 'surname' => 'Smith', 'age' => 10], + 2 => ['name' => 'Sarah', 'surname' => 'Kelly', 'age' => 20], +]); +``` .. note::   Dynamic views will not be able to identify that you are working with static data, and some features may not work properly. @@ -33,13 +31,14 @@ method :php:meth:`View::setSource` which will work-around complexities and give for expressing your native data source. Agile UI is not optimized for setSource so its performance will generally be slower too. -Raw SQL Queries ---------------- +### Raw SQL Queries Writing raw SQL queries is source of many errors, both with a business logic and security. Agile Data provides great ways -for abstracting your SQL queries, but if you have to use a raw query:: +for abstracting your SQL queries, but if you have to use a raw query: - // not sure how TODO - write this section. +``` +// not sure how TODO - write this section. +``` .. note:: The above way to using raw queries has a performance implications, because Agile UI is optimised to work with Agile diff --git a/docs/dataexecutor.md b/docs/dataexecutor.md index 2ce93bff9d..f696625855 100644 --- a/docs/dataexecutor.md +++ b/docs/dataexecutor.md @@ -1,9 +1,6 @@ - .. _dataexecutor: -==================== -Data Action Executor -==================== +# Data Action Executor Data action executor in UI is parts of interactive components that can execute a Data model defined user action. For more details on Data Model User Action please visit: https://agile-data.readthedocs.io/en/develop/model.html#actions @@ -15,8 +12,7 @@ an ArgumentFormExecutor. Or actions that can run using a single button can use a Demo: https://ui.agiletoolkit.org/demos/data-action/actions.php -Executor Interface -================== +## Executor Interface .. php:namespace:: Atk4\Ui\UserAction @@ -25,8 +21,7 @@ All executors must implement the ExecutorInterface or JsExecutorInterface interf .. php:interface:: ExecutorInterface .. php:interface:: JsExecutorInterface -Basic Executor -============== +## Basic Executor .. php:class:: BasicExecutor @@ -38,24 +33,21 @@ BasicExecutor will display: - a header where action name and description are displayed; - an error message if an action argument is missing; -Preview Executor -================ +## Preview Executor .. php:class:: PreviewExecutor This executor is specifically set in order to display the $preview property of the current model UserAction. You can select to display the preview using regular console type container, regular text or using HTML content. -Form Executor -============= +## Form Executor .. php:class:: FormExecutor This executor will display a form where user is required to fill in either all model fields or certain model fields depending on the model UserAction $field property. Form control will depend on model field type. -Argument Form Executor -====================== +## Argument Form Executor .. php:class:: ArgumentFormExecutor @@ -63,16 +55,14 @@ This executor will display a form but instead of filling form control with model $args property. This is used when you need to ask user about an argument value prior to execute the action. The type of form control type to be used in form will depend on how $args is setup within the model UserAction. -JS Callaback Executor -===================== +## JS Callaback Executor .. php:class:: JsCallbackExecutor This type of executor will output proper javascript that you can assign to a view event using View::on() method. It is also possible to pass the UserAction argument via $_POST argument. -Modal Executor -============== +## Modal Executor .. php:class:: ModalExecutor @@ -91,8 +81,7 @@ base on the action definition within a modal view: The modal title default is set from the UserAction::getDescription() method but can be override using the Modal::$title property. -Confirmation Executor -===================== +## Confirmation Executor .. php:class:: ConfirmationExecutor @@ -100,23 +89,24 @@ Like ModalExecutor, Confirmation executor is also based on a Modal view. It allo execute the action. Since UserAction::confirmation property may be set with a Closure function, this give a chance to return specific record information to be displayed to user prior to execute the action. -Here is an example of an user action returning specific record information in the confirmation message:: +Here is an example of an user action returning specific record information in the confirmation message: - $country->addUserAction('delete_country', [ - 'caption' => 'Delete', - 'description' => 'Delete Country', - 'ui' => ['executor' => [\Atk4\Ui\UserAction\ConfirmationExecutor::class]], - 'confirmation' => function (Model\UserAction $action) { - return 'Are you sure you want to delete this country: $action->getModel()->getTitle(); - }, - 'callback' => 'delete', - ]); +``` + $country->addUserAction('delete_country', [ + 'caption' => 'Delete', + 'description' => 'Delete Country', + 'ui' => ['executor' => [\Atk4\Ui\UserAction\ConfirmationExecutor::class]], + 'confirmation' => function (Model\UserAction $action) { + return 'Are you sure you want to delete this country: $action->getModel()->getTitle(); + }, + 'callback' => 'delete', + ]); +``` The modal title default is set from the UserAction::getDescription() method but can be override using the Modal::$title property. -Executor HOOK_AFTER_EXECUTE -============================ +## Executor HOOK_AFTER_EXECUTE Executors can use the HOOK_AFTER_EXECUTE hook in order to return javascript action after the model UserAction finish executing. It is use in Crud for example in order to display users of successful model UserAction execution. Either by displaying @@ -126,8 +116,7 @@ Some Ui View component, like Crud for example, will also set javascript action t For example it the modifier property is set to MODIFIER_DELETE then Crud will know it has to delete a table row on the other hand, if MODIFIER_UPDATE is set, then Table needs to be reloaded. -The Executor Factory -==================== +## The Executor Factory .. php:class:: ExecutorFactory @@ -136,9 +125,11 @@ The Executor Factory Executor factory is responsible for creating proper executor type in regards to the model user action being used. -The factory createExecutor method:: +The factory createExecutor method: - ExecutorFactory::createExecutor(UserAction $action, View $owner, $requiredType = null) +``` +ExecutorFactory::createExecutor(UserAction $action, View $owner, $requiredType = null) +``` Based on parameter passed to the method, it will return proper executor for the model user action. @@ -157,108 +148,118 @@ The createExecutor method also add the executor to the View passed as argument. class is of type Modal, then it will be attached to the $app->html view instead. This is because Modal view in ui needs to be added to $app->html view in order to work correctly on reload. +### Changing or adding Executor type -Changing or adding Executor type --------------------------------- - -Existing executor type can be change or added globally for all your user model actions via this method:: +Existing executor type can be change or added globally for all your user model actions via this method: - ExecutorFactory::registerTypeExecutor(string $type, array $seed): void +``` +ExecutorFactory::registerTypeExecutor(string $type, array $seed): void +``` This will set a type to your own executor class. For example, a custom executor class can be set as a MODAL_EXECUTOR type and all model user action that use this type will be executed using this custom executor instance. -Type may also be registered per specific model user action via this method:: +Type may also be registered per specific model user action via this method: - ExecutorFactory::registerExecutor(UserAction $action, array $seed): void +``` +ExecutorFactory::registerExecutor(UserAction $action, array $seed): void +``` -For example, you need a custom executor to be created when using a specific model user action:: +For example, you need a custom executor to be created when using a specific model user action: - class MySpecialFormExecutor extends \Atk4\Ui\UserAction\ModalExecutor +``` +class MySpecialFormExecutor extends \Atk4\Ui\UserAction\ModalExecutor +{ + public function addFormTo(\Atk4\Ui\View $view): \Atk4\Ui\Form { - public function addFormTo(\Atk4\Ui\View $view): \Atk4\Ui\Form - { - $myView = MySpecialView::addTo($view); + $myView = MySpecialView::addTo($view); - return parent::addFormTo($myView); - } + return parent::addFormTo($myView); } +} - //... - ExecutorFactory::registerExecutor($action, [MySpecialFormExecutor::class]); +//... +ExecutorFactory::registerExecutor($action, [MySpecialFormExecutor::class]); +``` Then, when ExecutorFactory::createExecutor method is called for this $action, MySpecialExecutor instance will be create in order to run this user model action. -Triggering model user action ----------------------------- +### Triggering model user action The Executor factory is also responsible for creating the UI view element, like regular, table or card button or menu item that will fire the model user action execution. -The method is:: +The method is: - ExecutorFactory::createTrigger(UserAction $action, string $type = null): View +``` +ExecutorFactory::createTrigger(UserAction $action, string $type = null): View +``` This method return an instance object for the proper type. When no type is supply, a default View Button object is returned. -As per executor type, it is also possible to add or change already register type via the registerTrigger method:: +As per executor type, it is also possible to add or change already register type via the registerTrigger method: - ExecutorFactory::registerTrigger(string $type, $seed, UserAction $action, bool $isSpecific = false): void +``` +ExecutorFactory::registerTrigger(string $type, $seed, UserAction $action, bool $isSpecific = false): void +``` Again, the type can be apply globally to all action using the same name or specifically for a certain model/action. -For example, changing default Table button for a specific model user action when this action is used inside a crud table:: +For example, changing default Table button for a specific model user action when this action is used inside a crud table: - ExecutorFactory::registerTrigger( - ExecutorFactory::TABLE_BUTTON, - [Button::class, null, 'icon' => 'mail'], - $m->getUserAction('mail') - ); +``` +ExecutorFactory::registerTrigger( + ExecutorFactory::TABLE_BUTTON, + [Button::class, null, 'icon' => 'mail'], + $m->getUserAction('mail') +); +``` This button view will then be display in Crud when it use a model containing 'mail' user action. -Overriding ExecutorFactory --------------------------- +### Overriding ExecutorFactory Overriding the ExecutorFactory class is a good way of changing the look of all trigger element within your app or within a specific view instance. -Example of changing button for Card, Crud and Modal executor globally within your app:: - - class MyFactory extends \Atk4\Ui\UserAction\ExecutorFactory - { - protected static $actionTriggerSeed = [ - self::MODAL_BUTTON => [ - 'edit' => [Button::class, 'Save', 'class.green' => true], - 'add' => [Button::class, 'Save', 'class.green' => true], - ], - self::TABLE_BUTTON => [ - 'edit' => [Button::class, null, 'icon' => 'pencil'], - 'delete' => [Button::class, null, 'icon' => 'times red'], - ], - self::CARD_BUTTON => [ - 'edit' => [Button::class, 'Edit', 'icon' => 'pencil', 'ui' => 'tiny button'], - 'delete' => [Button::class, 'Remove', 'icon' => 'times', 'ui' => 'tiny button'], - ], - ]; - - protected static $actionCaption = [ - 'add' => 'Add New Record', - ]; - } - - //... - $app->defaultExecutorFactory = $myFactory; - - -Model UserAction assignment to View -=================================== - -It is possible to assign a model UserAction to the View::on() method directly:: - - $button->on('click', $model->getUserAction('my_action')); +Example of changing button for Card, Crud and Modal executor globally within your app: + +``` +class MyFactory extends \Atk4\Ui\UserAction\ExecutorFactory +{ + protected static $actionTriggerSeed = [ + self::MODAL_BUTTON => [ + 'edit' => [Button::class, 'Save', 'class.green' => true], + 'add' => [Button::class, 'Save', 'class.green' => true], + ], + self::TABLE_BUTTON => [ + 'edit' => [Button::class, null, 'icon' => 'pencil'], + 'delete' => [Button::class, null, 'icon' => 'times red'], + ], + self::CARD_BUTTON => [ + 'edit' => [Button::class, 'Edit', 'icon' => 'pencil', 'ui' => 'tiny button'], + 'delete' => [Button::class, 'Remove', 'icon' => 'times', 'ui' => 'tiny button'], + ], + ]; + + protected static $actionCaption = [ + 'add' => 'Add New Record', + ]; +} + +//... +$app->defaultExecutorFactory = $myFactory; +``` + +## Model UserAction assignment to View + +It is possible to assign a model UserAction to the View::on() method directly: + +``` +$button->on('click', $model->getUserAction('my_action')); +``` By doing so, the View::on() method will automatically determine which executor is required to properly run the action. If the model UserAction contains has either $fields, $args or $preview property set, then the ModalExecutor will be @@ -267,15 +268,16 @@ used, JsCallback will be used otherwise. It is possible to override this behavior by setting the $ui['executor'] property of the model UserAction, since View::on() method will first look for that property prior to determine which executor to use. -Example of overriding executor assign to a button.:: +Example of overriding executor assign to a button.: - $myAction = $model->getUserAction('my_action'); - $myAction->ui['executor'] = $myExecutor; +``` +$myAction = $model->getUserAction('my_action'); +$myAction->ui['executor'] = $myExecutor; - $button->on('click', $myAction); +$button->on('click', $myAction); +``` -Demo ----- +### Demo For more information on how Model UserAction are assign to button and interact with user according to their definition, please visit: `Assign action to button event `_ diff --git a/docs/filestructure.md b/docs/filestructure.md index ac19c3d483..cbb8029ae8 100644 --- a/docs/filestructure.md +++ b/docs/filestructure.md @@ -1,16 +1,12 @@ .. _filestructure: -================================== -File structure example & first app -================================== +# File structure example & first app We will deal here with a suggestion how you could structure your files and folders for your individual atk4 project. Let's assume you are planning to create at least several pages with some models and views. This example can be expanded and modified to your needs and shows just one concept of how to setup an atk4 project. - -File structure example -====================== +## File structure example This file structure is a recommendation and no must. It is a best practice example. Feel free to experiment with it and find the ideal file structure for your project. @@ -67,28 +63,26 @@ Feel free to experiment with it and find the ideal file structure for your proje * composer.json - - -Composer configuration -====================== +## Composer configuration Configure your composer.json to load the atk4 AND your project folder. -Your composer.json could look like this:: - - { - "require":{ - "atk4/ui": "*" - }, - "autoload": { - "psr-4": { - "MyProject\\": "projectfolder/" - } +Your composer.json could look like this: + +``` +{ + "require":{ + "atk4/ui": "*" + }, + "autoload": { + "psr-4": { + "MyProject\\": "projectfolder/" } } +} +``` +### What does that mean? -What does that mean? --------------------- As soon as you start a "composer update" or "composer dump-autoload" in the public_html directory, all needed atk4 files and all your project files from the subdirectory "projectfolder" are processed by Composer and the autoload.php file is generated. Read below how to load the autoload.php into your project. @@ -98,9 +92,7 @@ The "require" section within composer.json loads publicly available composer pac The "autoload" section within composer.json loads your individual project files (that are saved locally on your computer). "MyProject" defines the namespace you are using in your classes later on. - -Why "public_html"? ------------------- +### Why "public_html"? It is a good idea to keep away sensible configuration files that contain passwords (like database connection setups etc.) from the public and make it only available to your application. @@ -111,10 +103,7 @@ If you call www.myexampledomain.com it should show the content of public_html. Within a php file from public_html you are still able to access and include files from config. But you can't call it directly through the domain (that means in our case "db.php" can't be accessed through the domain). - - -Create your application -======================= +## Create your application To initialize your application we need to do the following steps: #) Create db.php for database @@ -127,63 +116,67 @@ To initialize your application we need to do the following steps: #) Create index.php and admin.php - -Create db.php for database --------------------------- +### Create db.php for database We initialize a reusable database connection in db.php through a mysql persistence. -Create a file called "db.php" in the directory "config":: +Create a file called "db.php" in the directory "config": - db = $db; // defines our database for reuse in other classes +``` +$app = new \Atk4\Ui\App('Welcome to my first app'); // initialization of our app +$app->db = $db; // defines our database for reuse in other classes +``` -Create index.php and admin.php ------------------------------- +### Create index.php and admin.php -If you want to write an app with a backend, create a file called "admin.php":: +If you want to write an app with a backend, create a file called "admin.php": - initLayout([\Atk4\Ui\Layout\Admin::class]); +``` +initLayout([\Atk4\Ui\Layout\Admin::class]); +``` -If you want to write an app with a frontend, create a file called "index.php":: +If you want to write an app with a frontend, create a file called "index.php": - initLayout([\Atk4\Ui\Layout\Centered::class]); +``` +initLayout([\Atk4\Ui\Layout\Centered::class]); +``` - -Create your own classes -======================= +## Create your own classes Now as your basic app is set up and running, we start implementing our own classes that build the core of our app. Following the PSR-4 specifications all class names and file names have to correspond to each other. @@ -197,20 +190,22 @@ defined in your composer.json. Do you remember? - If no, take a look at the beginning of this document. We defined there "MyProject" as our namespace for the directory "projectfolder". -Open the created file "View1.php" in your editor and add the following lines:: +Open the created file "View1.php" in your editor and add the following lines: - getApp(), ['here goes some text']); - } + \Atk4\Ui\Text::addTo($this->getApp(), ['here goes some text']); } +} +``` "namespace MyProject\\Views;" defines the namespace to use. It reflects the folder structure of the app. The file located in "projectfolder/Views/View1.php" becomes "MyProject\\Views\\View1" in the namespace. @@ -220,18 +215,20 @@ files will be autoloaded by Composer. .. warning:: Keep in mind that as soon as you have created one or more new file(s) within the projectfolder you have to run "composer dump-autoload"!!! Otherwise the newly generated file(s) and classes will not be autoloaded and are therefore unavailable in your application. - -Load your class in index.php -============================ +## Load your class in index.php To use our class in our app, we have to include it into our app. This can be done either through index.php or admin.php. -Please add the following lines into your index.php:: +Please add the following lines into your index.php: - \MyProject\Views\View1::addTo($app); +``` +\MyProject\Views\View1::addTo($app); +``` -or if you have added at the beginning of your index.php "use MyProject\\Views\\View1;" you can write:: +or if you have added at the beginning of your index.php "use MyProject\\Views\\View1;" you can write: - View1::addTo($app); +``` +View1::addTo($app); +``` See also :ref:`using-namespaces` on this topic... diff --git a/docs/fileupload.md b/docs/fileupload.md index 4e827d8984..8c34064b7d 100644 --- a/docs/fileupload.md +++ b/docs/fileupload.md @@ -1,7 +1,4 @@ - -=========== -File Upload -=========== +# File Upload .. figure:: images/fileupload.png @@ -27,8 +24,7 @@ During upload, a progress bar will appear. .. php:class:: Upload -Attributes -========== +## Attributes Upload control has the following properties: @@ -41,25 +37,23 @@ Example would be: `['application/pdf', 'images/*']`. The button view to use for displaying the file open dialog. A default action button is used if omitted. +## Callbacks -Callbacks -========= - -When adding an Upload or UploadImage field to a form, onUpload and onDelete callback must be defined:: +When adding an Upload or UploadImage field to a form, onUpload and onDelete callback must be defined: - $img = $form->addControl('img', [\Atk4\Ui\Form\Control\UploadImage::class, ['defaultSrc' => './images/default.png', 'placeholder' => 'Click to add an image.']]); +``` +$img = $form->addControl('img', [\Atk4\Ui\Form\Control\UploadImage::class, ['defaultSrc' => './images/default.png', 'placeholder' => 'Click to add an image.']]); - $img->onUpload(function (array $postFile) { - // callback action here... - }); +$img->onUpload(function (array $postFile) { + // callback action here... +}); - $img->onDelete(function (string $fileId) { - // callback action here... - }); +$img->onDelete(function (string $fileId) { + // callback action here... +}); +``` - -onUpload --------- +### onUpload The onUpload callback get called as soon as the upload process is finished. This callback function will receive the `$_FILES['upfile']` array as function parameter (see https://php.net/manual/en/features.file-upload.php). @@ -73,35 +67,38 @@ The onUpload callback function is a good place to: - setup a file preview to display back to user, - notify your user of the file upload process, -Example showing the onUpload callback on the UploadImage field:: +Example showing the onUpload callback on the UploadImage field: - $img->onUpload(function (array $postFile) use ($form, $img) { - if ($postFile['error'] !== 0) { - return $form->jsError('img', 'Error uploading image.'); - } +``` +$img->onUpload(function (array $postFile) use ($form, $img) { + if ($postFile['error'] !== 0) { + return $form->jsError('img', 'Error uploading image.'); + } - // Do file processing here... + // Do file processing here... - $img->setThumbnailSrc('./images/' . $fileName); - $img->setFileId('123456'); + $img->setThumbnailSrc('./images/' . $fileName); + $img->setFileId('123456'); - // can also return a notifier. - return new \Atk4\Ui\Js\JsToast([ - 'message' => 'File is uploaded!', - 'class' => 'success', - ]); - }); + // can also return a notifier. + return new \Atk4\Ui\Js\JsToast([ + 'message' => 'File is uploaded!', + 'class' => 'success', + ]); +}); +``` When user submit the form, the form control data value that will be submitted is the fileId set during the onUpload callback. -The fileId is set to file name by default if omitted:: +The fileId is set to file name by default if omitted: - $form->onSubmit(function (Form $form) { - // implement submission here - return $form->jsSuccess('Thanks for submitting file: ' . $form->model->get('img')); - }); +``` +$form->onSubmit(function (Form $form) { + // implement submission here + return $form->jsSuccess('Thanks for submitting file: ' . $form->model->get('img')); +}); +``` -onDelete --------- +### onDelete The onDelete callback get called when user click the delete button. This callback function receive the same fileId set during the onUpload callback as function parameter. @@ -114,21 +111,21 @@ The onDelete callback function is a good place to: - delete db entry according to the fileId, - reset thumbnail preview, -Example showing the onDelete callback on the UploadImage field:: - - $img->onDelete(function (string $fileId) use ($img) { - // reset thumbanil - $img->clearThumbnail('./images/default.png'); +Example showing the onDelete callback on the UploadImage field: - return new \Atk4\Ui\Js\JsToast([ - 'message' => $fileId . ' has been removed!', - 'class' => 'success', - ]); - }); +``` +$img->onDelete(function (string $fileId) use ($img) { + // reset thumbanil + $img->clearThumbnail('./images/default.png'); + return new \Atk4\Ui\Js\JsToast([ + 'message' => $fileId . ' has been removed!', + 'class' => 'success', + ]); +}); +``` -UploadImage -=========== +## UploadImage Similar to Upload, this is a control implementation for uploading images. Here are additional properties: diff --git a/docs/form-control.md b/docs/form-control.md index e3af41d56f..aa07cc35f1 100644 --- a/docs/form-control.md +++ b/docs/form-control.md @@ -1,9 +1,6 @@ - .. _form-control: -============= -Form Controls -============= +# Form Controls .. php:namespace:: Atk4\Ui\Form @@ -12,80 +9,87 @@ Form Controls Agile UI dedicates a separate namespace for the Form Controls. Those are quite simple components that present themselves as input controls: line, select, checkbox. -Relationship with Form -====================== +## Relationship with Form All Form Control Decorators can be integrated with :php:class:`Atk4\\Ui\\Form` which will facilitate collection and processing of data in a form. Form Control decorators can also be used as stand-alone controls. -Stand-alone use ---------------- +### Stand-alone use .. php:method:: set() .. php:method:: jsInput() -Add any form control to your application like this:: - - $control = Line::addTo($app); +Add any form control to your application like this: -You can set default value and interact with a form control using JavaScript:: +``` +$control = Line::addTo($app); +``` - $control->set('hello world'); +You can set default value and interact with a form control using JavaScript: +``` +$control->set('hello world'); - $button = \Atk4\Ui\Button::addTo($app, ['check value']); - $button->on('click', new \Atk4\Ui\Js\JsExpression('alert(\'control value is: \' + [])', [$control->jsInput()->val()])); +$button = \Atk4\Ui\Button::addTo($app, ['check value']); +$button->on('click', new \Atk4\Ui\Js\JsExpression('alert(\'control value is: \' + [])', [$control->jsInput()->val()])); +``` -When used stand-alone, Form\Controls will produce a basic HTML (I have omitted id=):: -
- -
+When used stand-alone, Form\Controls will produce a basic HTML (I have omitted id=): +``` +
+ +
+``` -Using in-form -------------- +### Using in-form -Form Control can also be used inside a form like this:: +Form Control can also be used inside a form like this: - $form = \Atk4\Ui\Form::addTo($app); - $control = $form->addControl('name', new \Atk4\Ui\Form\Control\Line()); +``` +$form = \Atk4\Ui\Form::addTo($app); +$control = $form->addControl('name', new \Atk4\Ui\Form\Control\Line()); +``` If you execute this example, you'll notice that Field now has a label, it uses full width of the -page and the following HTML is now produced:: +page and the following HTML is now produced: -
- -
- -
+``` +
+ +
+
+
+``` The markup that surronds the button which includes Label and formatting is produced by :php:class:`Atk4\\Ui\\Form\\Layout`, which does draw some of the information from the Form Control itself. -Using in Form Layouts ---------------------- +### Using in Form Layouts Form may have multiple Form Layouts and that's very useful if you need to split up form -into multiple Tabs or detach form control groups or even create nested layouts:: +into multiple Tabs or detach form control groups or even create nested layouts: - $form = \Atk4\Ui\Form::addTo($app); - $tabs = \Atk4\Ui\Tabs::addTo($form, [], ['AboveControls']); - \Atk4\Ui\View::addTo($form, ['ui' => 'divider'], ['AboveControls']); +``` +$form = \Atk4\Ui\Form::addTo($app); +$tabs = \Atk4\Ui\Tabs::addTo($form, [], ['AboveControls']); +\Atk4\Ui\View::addTo($form, ['ui' => 'divider'], ['AboveControls']); - $formPage = Form\Layout::addTo($tabs->addTab('Basic Info'), ['form' => $form]); - $formPage->addControl('name', new \Atk4\Ui\Form\Control\Line()); +$formPage = Form\Layout::addTo($tabs->addTab('Basic Info'), ['form' => $form]); +$formPage->addControl('name', new \Atk4\Ui\Form\Control\Line()); - $formPage = Form\Layout::addTo($tabs->addTab('Other Info'), ['form' => $form]); - $formPage->addControl('age', new \Atk4\Ui\Form\Control\Line()); +$formPage = Form\Layout::addTo($tabs->addTab('Other Info'), ['form' => $form]); +$formPage->addControl('age', new \Atk4\Ui\Form\Control\Line()); - $form->onSubmit(function (Form $form) { - return $form->model->get('name') . ' has age ' . $form->model->get('age'); - }); +$form->onSubmit(function (Form $form) { + return $form->model->get('name') . ' has age ' . $form->model->get('age'); +}); +``` This is further explained in documentation for :php:class:`Atk4\\Ui\\Form\\Layout` class, however if you do plan on adding your own form control types, it's important that you extend it @@ -95,8 +99,7 @@ properly: - Input (abstract, extends Generic) - Easiest since it already implements `` and various ways to attach button to the input with markup of Fomantic-UI form control. -Hints ------ +### Hints .. php:attr:: hint @@ -105,27 +108,31 @@ although it intends to be "extra info" or "extra help" due to current limitation the only way we can display hint is using a gray bubble. In the future version of Agile UI we will update to use a more suitable form control. -Hint can be specified either inside Form Control decorator seed or inside the Field::ui attribute:: +Hint can be specified either inside Form Control decorator seed or inside the Field::ui attribute: +``` +$form->addControl('title', [], ['values' => ['Mr', 'Mrs', 'Miss'], 'hint' => 'select one']); - $form->addControl('title', [], ['values' => ['Mr', 'Mrs', 'Miss'], 'hint' => 'select one']); +$form->addControl('name', ['hint' => 'Full Name Only']); +``` - $form->addControl('name', ['hint' => 'Full Name Only']); +Text will have HTML characters escaped. You may also specify hint value as an object: -Text will have HTML characters escaped. You may also specify hint value as an object:: +``` +$form->addControl('name', ['hint' => new \Atk4\Ui\Text( + 'Click here' +)]); +``` - $form->addControl('name', ['hint' => new \Atk4\Ui\Text( - 'Click here' - )]); +or you can inject a view with a custom template: -or you can inject a view with a custom template:: +``` +$form->addControl('name', ['hint' => ['template' => new \Atk4\Ui\Template( + 'Click here' +)]]); +``` - $form->addControl('name', ['hint' => ['template' => new \Atk4\Ui\Template( - 'Click here' - )]]); - -Read only and disabled form controls ------------------------------------- +### Read only and disabled form controls .. php:attr:: readOnly @@ -137,37 +144,39 @@ change their value. Disabled form controls can be seen in form, cannot be focused and will not be submitted. And of course we don't allow to change their value. Disabled form controls are used for read only model fields for example. - -Relationship with Model -======================= +## Relationship with Model In the examples above, we looked at how to create Form Control Decorator object explicitly. The most common use-case in large application is the use with Models. You would need a model, such as `Country` model as well as -`Persistence $db `_:: +`Persistence $db `_: - class Country extends \Atk4\Data\Model - { - public $table = 'country'; +``` +class Country extends \Atk4\Data\Model +{ + public $table = 'country'; - protected function init(): void - { - parent::init(); + protected function init(): void + { + parent::init(); - $this->addField('name', ['actual' => 'nicename', 'required' => true, 'type' => 'string']); - $this->addField('sys_name', ['actual' => 'name', 'system' => true]); + $this->addField('name', ['actual' => 'nicename', 'required' => true, 'type' => 'string']); + $this->addField('sys_name', ['actual' => 'name', 'system' => true]); - $this->addField('iso', ['caption' => 'ISO', 'required' => true, 'type' => 'string']); - $this->addField('iso3', ['caption' => 'ISO3', 'required' => true, 'type' => 'string']); - $this->addField('numcode', ['caption' => 'ISO Numeric Code', 'type' => 'integer', 'required' => true]); - $this->addField('phonecode', ['caption' => 'Phone Prefix', 'type' => 'integer']); - } + $this->addField('iso', ['caption' => 'ISO', 'required' => true, 'type' => 'string']); + $this->addField('iso3', ['caption' => 'ISO3', 'required' => true, 'type' => 'string']); + $this->addField('numcode', ['caption' => 'ISO Numeric Code', 'type' => 'integer', 'required' => true]); + $this->addField('phonecode', ['caption' => 'Phone Prefix', 'type' => 'integer']); } +} +``` -To create a form, the following is sufficient:: +To create a form, the following is sufficient: - $form = \Atk4\Ui\Form::addTo($app); - $form->setModel(new Country($db); +``` +$form = \Atk4\Ui\Form::addTo($app); +$form->setModel(new Country($db); +``` The above will populate fields from model into the form automatically. You can use second argument to :php:meth:`\Atk4\Ui\Form::setModel()` to indicate which fields to display @@ -184,21 +193,24 @@ The rules are rather straightforward but may change in future versions of Agile - consult :php:attr:`\Atk4\Ui\Form::$typeToDecorator` property for type-to-seed association - type=password will use :php:class:`Password` -You always have an option to explicitly specify which field you would like to use:: +You always have an option to explicitly specify which field you would like to use: - $model->addField('long_text', ['ui' => ['rorm' => \Atk4\Ui\Form\Control\TextArea::class]]); +``` +$model->addField('long_text', ['ui' => ['rorm' => \Atk4\Ui\Form\Control\TextArea::class]]); +``` It is recommended however, that you use type when possible, because types will be universally supported -by all components:: +by all components: - $model->addField('long_text', ['type' => 'text']); +``` +$model->addField('long_text', ['type' => 'text']); +``` .. note:: All forms will be associated with a model. If form is not explicitly linked with a model, it will create a ProxyModel and all form controls will be created automatically in that model. As a result, all Form Control Decorators will be linked with Model Fields. -Link to Model Field -------------------- +### Link to Model Field .. php:attr:: field @@ -207,8 +219,7 @@ the value of the field would be read from `$decorator->entityField->get()`. .. php:namespace:: Atk4\Ui\Form\Control -Line Input Form control -======================= +## Line Input Form control .. php:class:: Input @@ -224,29 +235,33 @@ element. For example, `icon` property: Adds icon into the input form control. Default - `icon` will appear on the right, while `leftIcon` will display icon on the left. -Here are few ways to specify `icon` to an Input/Line:: +Here are few ways to specify `icon` to an Input/Line: - // compact - Line::addTo($page, ['icon' => 'search']); +``` +// compact +Line::addTo($page, ['icon' => 'search']); - // Type-hinting friendly - $line = new \Atk4\Ui\Form\Control\Line(); - $line->icon = 'search'; - $page->add($line); +// Type-hinting friendly +$line = new \Atk4\Ui\Form\Control\Line(); +$line->icon = 'search'; +$page->add($line); - // using class factory - Line::addTo($page, ['icon' => 'search']); +// using class factory +Line::addTo($page, ['icon' => 'search']); +``` The 'icon' property can be either string or a View. The string is for convenience and will be automatically substituted with `new Icon($icon)`. If you wish to be more specific -and pass some arguments to the icon, there are two options:: +and pass some arguments to the icon, there are two options: - // compact - $line->icon = ['search', 'class.big' => true]; +``` +// compact +$line->icon = ['search', 'class.big' => true]; - // Type-hinting friendly - $line->icon = new Icon('search'); - $line->icon->addClass('big'); +// Type-hinting friendly +$line->icon = new Icon('search'); +$line->icon->addClass('big'); +``` To see how Icon interprets `new Icon(['search', 'class.big' => true])`, refer to :php:class:`Icon`. @@ -275,46 +290,45 @@ To see how Icon interprets `new Icon(['search', 'class.big' => true])`, refer to To see various examples of form controls and their attributes see `demos/form-control/`. -Integration with Form ---------------------- +### Integration with Form When you use :php:class:`form::addControl()` it will create 'Form Control Decorator' -JavaScript on Input -------------------- +### JavaScript on Input .. php:method:: jsInput([$event, [$otherChain]]) Input class implements method jsInput which is identical to :php:meth:`View::js`, except -that it would target the INPUT element rather then the whole form control:: +that it would target the INPUT element rather then the whole form control: - $control->jsInput(true)->val(123); +``` +$control->jsInput(true)->val(123); +``` -onChange event --------------- +### onChange event .. php:method:: onChange($expression) It's preferable to use this short-hand version of on('change', 'input', $expression) method. $expression argument can be JS expression or PHP callback function. - // simple string - $f1 = $form->addControl('f1'); - $f1->onChange(\Atk4\Ui\Js\JsExpression('console.log(\'f1 changed\')')); - - // callback - $f2 = $form->addControl('f2'); - $f2->onChange(function () { - return new \Atk4\Ui\Js\JsExpression('console.log(\'f2 changed\')'); - }); +``` +// simple string +$f1 = $form->addControl('f1'); +$f1->onChange(\Atk4\Ui\Js\JsExpression('console.log(\'f1 changed\')')); - // Calendar form control - wraps in function call with arguments date, text and mode - $c1 = $form->addControl('c1', new \Atk4\Ui\Form\Control\Calendar(['type' => 'date'])); - $c1->onChange(\Atk4\Ui\Js\JsExpression('console.log(\'c1 changed: \' + date + \', \' + text + \', \' + mode)')); +// callback +$f2 = $form->addControl('f2'); +$f2->onChange(function () { + return new \Atk4\Ui\Js\JsExpression('console.log(\'f2 changed\')'); +}); +// Calendar form control - wraps in function call with arguments date, text and mode +$c1 = $form->addControl('c1', new \Atk4\Ui\Form\Control\Calendar(['type' => 'date'])); +$c1->onChange(\Atk4\Ui\Js\JsExpression('console.log(\'c1 changed: \' + date + \', \' + text + \', \' + mode)')); +``` -Dropdown -======== +## Dropdown .. php:class:: Dropdown @@ -322,109 +336,123 @@ Dropdown uses Fomantic-UI Dropdown (https://fomantic-ui.com/modules/dropdown.htm 1) Set a Model to $model property. The Dropdown will render all records of the model that matches the model's conditions. 2) You can define $values property to create custom Dropdown items. -Usage with a Model ------------------- +### Usage with a Model + A Dropdown is not used as default Form Control decorator (`$model->hasOne()` uses :php:class:`Lookup`), but in your Model, you can define that UI should render a Field as Dropdown. For example, this makes sense when a `hasOne()` relationship only has a very limited amount (like 20) of records to display. Dropdown renders all records when the paged is rendered, while Lookup always sends an additional request to the server. :php:class:`Lookup` on the other hand is the better choice if there is lots of records (like more than 50). -To render a model field as Dropdown, use the ui property of the field:: +To render a model field as Dropdown, use the ui property of the field: - $model->addField('someField', ['ui' => ['form' => [\Atk4\Ui\Form\Control\Dropdown::class]]]); +``` +$model->addField('someField', ['ui' => ['form' => [\Atk4\Ui\Form\Control\Dropdown::class]]]); +``` .. Customizing how a Model's records are displayed in Dropdown As default, Dropdown will use the `$model->idField` as value, and `$model->titleField` as title for each menu item. If you want to customize how a record is displayed and/or add an icon, Dropdown has the :php:meth:`Form::renderRowFunction()` to do this. -This function is called with each model record and needs to return an array:: +This function is called with each model record and needs to return an array: - $dropdown->renderRowFunction = function (Model $record) { - return [ - 'value' => $record->idField, - 'title' => $record->getTitle() . ' (' . $record->get('subtitle') . ')', - ]; - } +``` +$dropdown->renderRowFunction = function (Model $record) { + return [ + 'value' => $record->idField, + 'title' => $record->getTitle() . ' (' . $record->get('subtitle') . ')', + ]; +} +``` -You can also use this function to add an Icon to a record:: +You can also use this function to add an Icon to a record: - $dropdown->renderRowFunction = function (Model $record) { - return [ - 'value' => $record->idField, - 'title' => $record->getTitle() . ' (' . $record->get('subtitle') . ')', - 'icon' => $record->get('value') > 100 ? 'money' : 'coins', - ]; - } +``` +$dropdown->renderRowFunction = function (Model $record) { + return [ + 'value' => $record->idField, + 'title' => $record->getTitle() . ' (' . $record->get('subtitle') . ')', + 'icon' => $record->get('value') > 100 ? 'money' : 'coins', + ]; +} +``` + +If you'd like to even further adjust How each item is displayed (e.g. complex HTML and more model fields), you can extend the Dropdown class and create your own template with the complex HTML: -If you'd like to even further adjust How each item is displayed (e.g. complex HTML and more model fields), you can extend the Dropdown class and create your own template with the complex HTML:: +``` +class MyDropdown extends \Atk4\Ui\Dropdown +{ + public $defaultTemplate = 'my_dropdown.html'; - class MyDropdown extends \Atk4\Ui\Dropdown + /** + * used when a custom callback is defined for row rendering. Sets + * values to item template and appends it to main template + */ + protected function _addCallBackRow($row, $key = null) { - public $defaultTemplate = 'my_dropdown.html'; - - /** - * used when a custom callback is defined for row rendering. Sets - * values to item template and appends it to main template - */ - protected function _addCallBackRow($row, $key = null) - { - $res = ($this->renderRowFunction)($row, $key); - $this->_tItem->set('value', (string) $res['value']); - $this->_tItem->set('title', $res['title']); - $this->_tItem->set('someOtherField', $res['someOtherField]); - $this->_tItem->set('someOtherField2', $res['someOtherField2]); - // add item to template - $this->template->dangerouslyAppendHtml('Item', $this->_tItem->render()); - } + $res = ($this->renderRowFunction)($row, $key); + $this->_tItem->set('value', (string) $res['value']); + $this->_tItem->set('title', $res['title']); + $this->_tItem->set('someOtherField', $res['someOtherField]); + $this->_tItem->set('someOtherField2', $res['someOtherField2]); + // add item to template + $this->template->dangerouslyAppendHtml('Item', $this->_tItem->render()); } +} +``` -With the according renderRowFunction:: +With the according renderRowFunction: - function (Model $record) { - return [ - 'value' => $record->getId(), - 'title' => $record->getTitle, - 'icon' => $record->value > 100 ? 'money' : 'coins', - 'someOtherField' => $record->get('SomeOtherField'), - 'someOtherField2' => $record->get('SomeOtherField2'), - ]; - } +``` +function (Model $record) { + return [ + 'value' => $record->getId(), + 'title' => $record->getTitle, + 'icon' => $record->value > 100 ? 'money' : 'coins', + 'someOtherField' => $record->get('SomeOtherField'), + 'someOtherField2' => $record->get('SomeOtherField2'), + ]; +} +``` Of course, the tags `value`, `title`, `icon`, `someOtherField` and `someOtherField2` need to be set in my_dropdown.html. +### Usage with $values property -Usage with $values property ---------------------------- -If not used with a model, you can define the Dropdown values in $values array. The pattern is value => title:: +If not used with a model, you can define the Dropdown values in $values array. The pattern is value => title: - $dropdown->values = [ - 'decline' => 'No thanks', - 'postprone' => 'Maybe later', - 'accept' => 'Yes, I want to!', - ]; +``` +$dropdown->values = [ + 'decline' => 'No thanks', + 'postprone' => 'Maybe later', + 'accept' => 'Yes, I want to!', +]; +``` -You can also define an Icon right away:: +You can also define an Icon right away: - $dropdown->values = [ - 'tag' => ['Tag', 'icon' => 'tag'], - 'globe' => ['Globe', 'icon' => 'globe'], - 'registered' => ['Registered', 'icon' => 'registered'], - 'file' => ['File', 'icon' => 'file'], - ]; +``` +$dropdown->values = [ + 'tag' => ['Tag', 'icon' => 'tag'], + 'globe' => ['Globe', 'icon' => 'globe'], + 'registered' => ['Registered', 'icon' => 'registered'], + 'file' => ['File', 'icon' => 'file'], +]; +``` If using $values property, you can also use the :php:meth:`Form::renderRowFunction()`, though there usually is no need for it. -If you use it, use the second parameter as well, its the array key:: +If you use it, use the second parameter as well, its the array key: - function (string $value, $key) { - return [ - 'value' => $key, - 'title' => strtoupper($value), - ]; - } +``` +function (string $value, $key) { + return [ + 'value' => $key, + 'title' => strtoupper($value), + ]; +} +``` +### Dropdown Settings -Dropdown Settings ------------------ There's a bunch of settings to influence Dropdown behaviour. .. php:attr:: empty @@ -433,38 +461,41 @@ Define a string for the empty option (no selection). Standard is non-breaking sp .. php:attr:: dropdownOptions -Here you can pass an array of Fomantic-UI dropdown options (https://fomantic-ui.com/modules/dropdown.html#/settings) e.g. :: +Here you can pass an array of Fomantic-UI dropdown options (https://fomantic-ui.com/modules/dropdown.html#/settings) e.g. : - $dropdown = new Dropdown(['dropdownOptions' => [ - 'selectOnKeydown' => false, - ]]); +``` +$dropdown = new Dropdown(['dropdownOptions' => [ + 'selectOnKeydown' => false, +]]); +``` .. php:attr:: multiple If set to true, multiple items can be selected in Dropdown. They will be sent comma separated (value1,value2,value3) on form submit. By default Dropdown will save values as comma-separated string value in data model, but it also supports model fields with array type. -See this example from Model class init method:: - $exprModel = $this->ref('Expressions'); - $this->addField('expressions', [ - 'type' => 'json', - 'required' => true, - 'ui' => [ - 'form' => [ - \Atk4\Ui\Form\Control\Dropdown::class, - 'multiple' => true, - 'model' => $exprModel, - ], - 'table' => [ - 'Labels', - 'values' => $exprModel->getTitles(), - ], +See this example from Model class init method: + +``` +$exprModel = $this->ref('Expressions'); +$this->addField('expressions', [ + 'type' => 'json', + 'required' => true, + 'ui' => [ + 'form' => [ + \Atk4\Ui\Form\Control\Dropdown::class, + 'multiple' => true, + 'model' => $exprModel, ], - ]); - + 'table' => [ + 'Labels', + 'values' => $exprModel->getTitles(), + ], + ], +]); +``` -DropdownCascade -=============== +## DropdownCascade .. php:class:: DropdownCascade @@ -484,16 +515,16 @@ This property represent a model hasMany reference and should be an hasMany refer In other word, the model that will generated list value for this dropdown input is an hasMany reference of the cascadeFrom input model. -Assume that each data model are defined and model Category has many Sub-Category and Sub-Category has many Product:: - - $form = \Atk4\Ui\Form::addTo($app); - $form->addControl('category_id', [Dropdown::class, 'model' => new Category($db)]); - $form->addControl('sub_category_id', [DropdownCascade::class, 'cascadeFrom' => 'category_id', 'reference' => Category::hinting()->fieldName()->SubCategories]); - $form->addControl('product_id', [DropdownCascade::class, 'cascadeFrom' => 'sub_category_id', 'reference' => SubCategory::hinting()->fieldName()->Products]); +Assume that each data model are defined and model Category has many Sub-Category and Sub-Category has many Product: +``` +$form = \Atk4\Ui\Form::addTo($app); +$form->addControl('category_id', [Dropdown::class, 'model' => new Category($db)]); +$form->addControl('sub_category_id', [DropdownCascade::class, 'cascadeFrom' => 'category_id', 'reference' => Category::hinting()->fieldName()->SubCategories]); +$form->addControl('product_id', [DropdownCascade::class, 'cascadeFrom' => 'sub_category_id', 'reference' => SubCategory::hinting()->fieldName()->Products]); +``` -Lookup -====== +## Lookup .. php:class:: Lookup diff --git a/docs/form.md b/docs/form.md index 45d8062895..7932189258 100644 --- a/docs/form.md +++ b/docs/form.md @@ -1,10 +1,6 @@ - - .. _form: -===== -Forms -===== +# Forms .. php:namespace:: Atk4\Ui @@ -30,44 +26,53 @@ So if looking for a PHP Form class, ATK Form has the most complete implementatio not require to fall-back into HTML / JS, perform any data conversion, load / store data and implement any advanced interactions such as file uploads. -Basic Usage -=========== +## Basic Usage -It only takes 2 PHP lines to create a fully working form:: +It only takes 2 PHP lines to create a fully working form: - $form = Form::addTo($app); - $form->addControl('email'); +``` +$form = Form::addTo($app); +$form->addControl('email'); +``` The form component can be further tweaked by setting a custom callback handler -directly in PHP:: +directly in PHP: - $form->onSubmit(function (Form $form) { - // implement subscribe here +``` +$form->onSubmit(function (Form $form) { + // implement subscribe here - return "Subscribed " . $form->model->get('email') . " to newsletter."; - }); + return "Subscribed " . $form->model->get('email') . " to newsletter."; +}); +``` Form is a composite component and it relies on other components to render parts -of it. Form uses :php:class:`Button` that you can tweak to your liking:: +of it. Form uses :php:class:`Button` that you can tweak to your liking: - $form->buttonSave->set('Subscribe'); - $form->buttonSave->icon = 'mail'; +``` +$form->buttonSave->set('Subscribe'); +$form->buttonSave->icon = 'mail'; +``` -or you can tweak it when you create form like this:: +or you can tweak it when you create form like this: - $form = Form::addTo($app, ['buttonSave' => [null, 'Subscribe', 'icon' => 'mail']]); +``` +$form = Form::addTo($app, ['buttonSave' => [null, 'Subscribe', 'icon' => 'mail']]); +``` To set the default values in the form controls you can use the model property of the form. -Even if model not explicitly set (see section below) each form has an underlying model which is automatically generated:: +Even if model not explicitly set (see section below) each form has an underlying model which is automatically generated: - // single field - $form->model->set('email', 'some@email.com'); +``` +// single field +$form->model->set('email', 'some@email.com'); - // or multiple fields - $form->model->set([ - 'name' => 'John', - 'email' => 'some@email.com', - ]); +// or multiple fields +$form->model->set([ + 'name' => 'John', + 'email' => 'some@email.com', +]); +``` Form also relies on a ``\Atk4\Ui\Form::Layout`` class and displays form controls through decorators defined at ``\Atk4\Ui\Form::Control``. See dedicated documentation for: @@ -75,23 +80,26 @@ decorators defined at ``\Atk4\Ui\Form::Control``. See dedicated documentation fo - :php:class:`Form::Layout` - :php:class:`Form::Control` -To tweak the UI properties of an form control input use ``setInputAttr()`` (and not the surrounding
as ``setAttr()`` would do). Here is how to set the HTML "maxlength" attribute on the generated input field:: +To tweak the UI properties of an form control input use ``setInputAttr()`` (and not the surrounding
as ``setAttr()`` would do). Here is how to set the HTML "maxlength" attribute on the generated input field: - $form = \Atk4\Ui\Form::addTo($this); - $form->setModel($model); - $form->getControl('name')->setInputAttr('maxlength', 20); +``` +$form = \Atk4\Ui\Form::addTo($this); +$form->setModel($model); +$form->getControl('name')->setInputAttr('maxlength', 20); +``` The rest of this chapter will focus on Form mechanics, such as submission, integration with front-end, integration with Model, error handling etc. -Usage with Model ----------------- +### Usage with Model -A most common use of form is if you have a working Model (https://agile-data.readthedocs.io/en/develop/model.html):: +A most common use of form is if you have a working Model (https://agile-data.readthedocs.io/en/develop/model.html): - // Form will automatically add a new user and save into the database - $form = Form::addTo($app); - $form->setModel(new User($db)); +``` +// Form will automatically add a new user and save into the database +$form = Form::addTo($app); +$form->setModel(new User($db)); +``` The basic 2-line syntax will extract all the required logic from the Model including: @@ -121,8 +129,7 @@ All of the above works auto-magically, but you can tweak it even more: If your form is NOT associated with a model, then Form will automatically create a :php:class:`ProxyModel` and associate it with your Form. As you add form controls respective fields will also be added into ProxyModel. -Extensions ----------- +### Extensions Starting with Agile UI 1.3 Form has a stable API and we expect to introduce some extensions like: @@ -133,8 +140,7 @@ Starting with Agile UI 1.3 Form has a stable API and we expect to introduce some If you develop such a feature please let me know so that I can include it in the documentation and give you credit. -Layout and Form Controls -======================== +## Layout and Form Controls Although Form extends the View class, controls are not added into Form directly but rather use a View layout for it in order to create their HTML element. In other words, layout attached to the form @@ -152,33 +158,37 @@ Each sub layout may also contain specific section layout like Accordion, Columns More on Form layout and sub layout below. -Adding Controls -=============== +## Adding Controls .. php:method:: addControl($name, $decorator = [], $field = []) -Create a new control on a form:: +Create a new control on a form: - $form = Form::addTo($app); - $form->addControl('email'); - $form->addControl('gender', [\Atk4\Ui\Form\Control\Dropdown::class, 'values' => ['Female', 'Male']]); - $form->addControl('terms', [], ['type' => 'boolean', 'caption' => 'Agree to Terms & Conditions']); +``` +$form = Form::addTo($app); +$form->addControl('email'); +$form->addControl('gender', [\Atk4\Ui\Form\Control\Dropdown::class, 'values' => ['Female', 'Male']]); +$form->addControl('terms', [], ['type' => 'boolean', 'caption' => 'Agree to Terms & Conditions']); +``` Create a new control on a form using Model does not require you to describe each control. Form will rely on Model Field Definition and UI meta-values to decide on the best way to handle -specific field type:: +specific field type: - $form = Form::addTo($app); - $form->setModel(new User($db), ['email', 'gender', 'terms']); +``` +$form = Form::addTo($app); +$form->setModel(new User($db), ['email', 'gender', 'terms']); +``` Form control does not have to be added directly into the form. You can use a separate -:php:class:`Form\\Layout` or even a regular view. Simply specify property :php:meth:`Form\\Control::$form`:: +:php:class:`Form\\Layout` or even a regular view. Simply specify property :php:meth:`Form\\Control::$form`: - $myview = View::addTo($form, ['defaultTemplate' => './mytemplate.html']); - Form\Control\Dropdown::addTo($myview, ['form' => $form]); +``` +$myview = View::addTo($form, ['defaultTemplate' => './mytemplate.html']); +Form\Control\Dropdown::addTo($myview, ['form' => $form]); +``` -Adding new controls -------------------- +### Adding new controls First argument to addControl is the name of the form control. You cannot have multiple controls with the same name. @@ -192,8 +202,7 @@ association with field. This will not work with regular fields, but you can add custom control such as CAPTCHA, which does not really need association with a field. -Form Control ------------- +### Form Control To avoid term miss-use, we use "Field" to refer to ``\Atk4\Data\Field``. This class is documented here: https://agile-data.readthedocs.io/en/develop/fields.html @@ -214,15 +223,17 @@ Agile UI comes with at least the following form controls: For some examples see: https://ui.agiletoolkit.org/demos/form3.php -Field Decorator can be passed to ``addControl`` using 'string', :php:ref:`seed` or 'object':: +Field Decorator can be passed to ``addControl`` using 'string', :php:ref:`seed` or 'object': - $form->addControl('accept_terms', [\Atk4\Ui\Form\Control\Checkbox::class]); - $form->addControl('gender', [\Atk4\Ui\Form\Control\Dropdown::class, 'values' => ['Female', 'Male']]); +``` +$form->addControl('accept_terms', [\Atk4\Ui\Form\Control\Checkbox::class]); +$form->addControl('gender', [\Atk4\Ui\Form\Control\Dropdown::class, 'values' => ['Female', 'Male']]); - $calendar = new \Atk4\Ui\Form\Control\Calendar(); - $calendar->type = 'tyme'; - $calendar->options['ampm'] = true; - $form->addControl('time', $calendar); +$calendar = new \Atk4\Ui\Form\Control\Calendar(); +$calendar->type = 'tyme'; +$calendar->options['ampm'] = true; +$form->addControl('time', $calendar); +``` For more information on default form controls as well as examples on how to create your own see documentation on :php:class:`Form::Control`. @@ -232,22 +243,23 @@ your own see documentation on :php:class:`Form::Control`. If form control class is not specified (``null``) then it will be determined from the type of the Data control with ``controlFactory`` method. -Data Field ----------- +### Data Field Data field is the 3rd argument to ``Form::addControl()``. -There are 3 ways to define Data form control using 'string', 'json' or 'object':: +There are 3 ways to define Data form control using 'string', 'json' or 'object': - $form->addControl('accept_terms', [\Atk4\Ui\Form\Control\Checkbox::class], 'Accept Terms & Conditions'); - $form->addControl('gender', [], ['enum' => ['Female', 'Male']]); +``` +$form->addControl('accept_terms', [\Atk4\Ui\Form\Control\Checkbox::class], 'Accept Terms & Conditions'); +$form->addControl('gender', [], ['enum' => ['Female', 'Male']]); - class MyBoolean extends \Atk4\Data\Field - { - public string $type = 'boolean'; - public ?array $enum = ['N', 'Y']; - } - $form->addControl('test2', [], new MyBoolean()); +class MyBoolean extends \Atk4\Data\Field +{ + public string $type = 'boolean'; + public ?array $enum = ['N', 'Y']; +} +$form->addControl('test2', [], new MyBoolean()); +``` String will be converted into ``['caption' => $string]`` a short way to give field a custom label. Without a custom label, Form will clean up the name (1st @@ -258,73 +270,77 @@ Specifying array will use the same syntax as the 2nd argument for ``\Atk4\Data\M (https://agile-data.readthedocs.io/en/develop/model.html#Model::addField) If field already exist inside model, then values of $field will be merged into -existing field properties. This example make email field mandatory for the form:: +existing field properties. This example make email field mandatory for the form: - $form = Form::addTo($app); - $form->setModel(new User($db), []); +``` +$form = Form::addTo($app); +$form->setModel(new User($db), []); - $form->addControl('email', [], ['required' => true]); +$form->addControl('email', [], ['required' => true]); +``` -addControl into Form with Existing Model ----------------------------------------- +### addControl into Form with Existing Model If your form is using a model and you add an additional control, then the underlying model field will be created but it will be set as "neverPersist" (https://agile-data.readthedocs.io/en/develop/fields.html#Field::$neverPersist). This is to make sure that data from custom form controls wouldn't go directly into the database. Next -example displays a registration form for a User:: +example displays a registration form for a User: - class User extends \Atk4\Data\Model - { - public $table = 'user'; +``` +class User extends \Atk4\Data\Model +{ + public $table = 'user'; - protected function init(): void - { - parent::init(); + protected function init(): void + { + parent::init(); - $this->addField('email'); - $this->addFiled('password'); - } + $this->addField('email'); + $this->addFiled('password'); } +} - $form = Form::addTo($app); - $form->setModel(new User($db)); +$form = Form::addTo($app); +$form->setModel(new User($db)); - // add password verification field - $form->addControl('password_verify', [\Atk4\Ui\Form\Control\Password::class], 'Type password again'); - $form->addControl('accept_terms', [], ['type' => 'boolean']); +// add password verification field +$form->addControl('password_verify', [\Atk4\Ui\Form\Control\Password::class], 'Type password again'); +$form->addControl('accept_terms', [], ['type' => 'boolean']); - // submit event - $form->onSubmit(function (Form $form) { - if ($form->model->get('password') != $form->model->get('password_verify')) { - return $form->jsError('password_verify', 'Passwords do not match'); - } +// submit event +$form->onSubmit(function (Form $form) { + if ($form->model->get('password') != $form->model->get('password_verify')) { + return $form->jsError('password_verify', 'Passwords do not match'); + } - if (!$form->model->get('accept_terms')) { - return $form->jsError('accept_terms', 'Read and accept terms'); - } + if (!$form->model->get('accept_terms')) { + return $form->jsError('accept_terms', 'Read and accept terms'); + } - $form->model->save(); // will only store email / password + $form->model->save(); // will only store email / password - return $form->jsSuccess('Thank you. Check your email now'); - }); + return $form->jsSuccess('Thank you. Check your email now'); +}); +``` -Field Type vs Form Control --------------------------- +### Field Type vs Form Control Sometimes you may wonder - should you pass form control class (Form\Control\Checkbox) or a data field type (['type' => 'boolean']); It is always recommended to use data field type, because it will take care of type-casting -for you. Here is an example with date:: +for you. Here is an example with date: - $form = Form::addTo($app); - $form->addControl('date1', [], ['type' => 'date']); - $form->addControl('date2', [\Atk4\Ui\Form\Control\Calendar::class, 'type' => 'date']); +``` +$form = Form::addTo($app); +$form->addControl('date1', [], ['type' => 'date']); +$form->addControl('date2', [\Atk4\Ui\Form\Control\Calendar::class, 'type' => 'date']); - $form->onSubmit(function (Form $form) { - echo 'date1 = ' . print_r($form->model->get('date1'), true) . ' and date2 = ' . print_r($form->model->get('date2'), true); - }); +$form->onSubmit(function (Form $form) { + echo 'date1 = ' . print_r($form->model->get('date1'), true) . ' and date2 = ' . print_r($form->model->get('date2'), true); +}); +``` Field ``date1`` is defined inside a :php:class:`ProxyModel` as a date field and will be automatically converted into DateTime object by Persistence typecasting. @@ -332,12 +348,13 @@ be automatically converted into DateTime object by Persistence typecasting. Field ``date2`` has no data type, do not confuse with ui type => date pass as second argument for Calendar field, and therefore Persistence typecasting will not modify it's value and it's stored inside model as a string. -The above code result in the following output:: +The above code result in the following output: - date1 = DateTime Object ( [date] => 2017-09-03 00:00:00 .. ) and date2 = September 3, 2017 +``` +date1 = DateTime Object ( [date] => 2017-09-03 00:00:00 .. ) and date2 = September 3, 2017 +``` -Seeding Form Control from Model -------------------------------- +### Seeding Form Control from Model In large projects you most likely won't be setting individual form controls for each Form. Instead you can simply use ``setModel()`` to populate all form controls from fields defined inside a model. Form does @@ -346,33 +363,36 @@ use a custom decorator? This is where ``$field->ui`` comes in (https://agile-data.readthedocs.io/en/develop/fields.html#Field::$ui). -You can specify ``'ui' => ['form' => $decoratorSeed]`` when defining your model field inside your Model:: +You can specify ``'ui' => ['form' => $decoratorSeed]`` when defining your model field inside your Model: - class User extends \Atk4\Data\Model - { - public $table = 'user'; +``` +class User extends \Atk4\Data\Model +{ + public $table = 'user'; - protected function init(): void - { - parent::init(); + protected function init(): void + { + parent::init(); - $this->addField('email'); - $this->addField('password'); + $this->addField('email'); + $this->addField('password'); - $this->addField('birth_year', ['type' => 'date', 'ui' => ['type' => 'month']); - } + $this->addField('birth_year', ['type' => 'date', 'ui' => ['type' => 'month']); } +} +``` The seed for the UI will be combined with the default overriding :php:attr:`Form\\Control\\Calendar::type` to allow month/year entry by the Calendar extension, which will then be saved and -stored as a regular date. Obviously you can also specify decorator class:: +stored as a regular date. Obviously you can also specify decorator class: - $this->addField('birth_year', ['ui' => [\Atk4\Ui\Form\Control\Calendar::class, 'type' => 'month']); +``` +$this->addField('birth_year', ['ui' => [\Atk4\Ui\Form\Control\Calendar::class, 'type' => 'month']); +``` Without the data 'type' property, now the calendar selection will be stored as text. -Using setModel() ----------------- +### Using setModel() Although there were many examples above for the use of setModel() this method needs a bit more info: @@ -395,16 +415,17 @@ by using `Form->layout->setModel()` internally. See also: https://agile-data.readthedocs.io/en/develop/fields.html#Field::isEditable -Using setModel() on a sub layout --------------------------------- +### Using setModel() on a sub layout -You may add form controls to sub layout directly using setModel method on the sub layout itself.:: +You may add form controls to sub layout directly using setModel method on the sub layout itself.: - $form = Form::addTo($app); - $form->setModel($model, []); +``` +$form = Form::addTo($app); +$form->setModel($model, []); - $subLayout = $form->layout->addSubLayout(); - $subLayout->setModel($model, ['first_name', 'last_name']); +$subLayout = $form->layout->addSubLayout(); +$subLayout->setModel($model, ['first_name', 'last_name']); +``` When using setModel() on a sub layout to add controls per sub layout instead of entire layout, @@ -412,36 +433,38 @@ make sure you pass false as second argument when setting the model on the Form i Otherwise all model fields will be automatically added in Forms main layout and you will not be able to add them again in sub-layouts. -Loading Values --------------- +### Loading Values Although you can set form control values individually using ``$form->model->set('field', $value)`` it's always nicer to load values for the database. Given a ``User`` model this is how -you can create a form to change profile of a currently logged user:: +you can create a form to change profile of a currently logged user: - $user = new User($db); - $user->getField('password')->neverPersist = true; // ignore password field - $user = $user->load($currentUserId); +``` +$user = new User($db); +$user->getField('password')->neverPersist = true; // ignore password field +$user = $user->load($currentUserId); - // Display all fields (except password) and values - $form = Form::addTo($app); - $form->setModel($user); +// Display all fields (except password) and values +$form = Form::addTo($app); +$form->setModel($user); +``` Submitting this form will automatically store values back to the database. Form uses POST data to submit itself and will re-use the query string, so you can also safely use any GET arguments for passing record $id. You may also perform model load after record association. This gives the benefit of not loading any other fields, unless they're marked as System (https://agile-data.readthedocs.io/en/develop/fields.html#Field::$system), -see https://agile-data.readthedocs.io/en/develop/model.html?highlight=onlyfields#Model::setOnlyFields:: +see https://agile-data.readthedocs.io/en/develop/model.html?highlight=onlyfields#Model::setOnlyFields: - $form = Form::addTo($app); - $form->setModel((new User($db))->load($currentUserId), ['email', 'name']); +``` +$form = Form::addTo($app); +$form->setModel((new User($db))->load($currentUserId), ['email', 'name']); +``` As before, field ``password`` will not be loaded from the database, but this time using onlyFields restriction rather then `neverPersist`. -Validating ----------- +### Validating The topic of validation in web apps is quite extensive. You should start by reading what Agile Data has to say about validation: @@ -474,45 +497,50 @@ As far as form is concerned: - Form submit handler will also interpret use of :php:meth:`Form::jsError` by displaying errors that do not originate inside Model save logic. -Example use of Model's validate() method:: - - class Person extends \Atk4\Data\Model - { - public $table = 'person'; +Example use of Model's validate() method: - protected function init(): void - { - parent::init(); +``` +class Person extends \Atk4\Data\Model +{ + public $table = 'person'; - $this->addField('name', ['required' => true]); - $this->addField('surname'); - $this->addField('gender', ['enum' => ['M', 'F']]); - } + protected function init(): void + { + parent::init(); - public function validate(): array - { - $errors = parent::validate(); + $this->addField('name', ['required' => true]); + $this->addField('surname'); + $this->addField('gender', ['enum' => ['M', 'F']]); + } - if ($this->get('name') === $this->get('surname')) { - $errors['surname'] = 'Your surname cannot be same as the name'; - } + public function validate(): array + { + $errors = parent::validate(); - return $errors; + if ($this->get('name') === $this->get('surname')) { + $errors['surname'] = 'Your surname cannot be same as the name'; } + + return $errors; } +} +``` -We can now populate form controls based around the data fields defined in the model:: +We can now populate form controls based around the data fields defined in the model: - Form::addTo($app) - ->setModel(new Person($db)); +``` +Form::addTo($app) + ->setModel(new Person($db)); +``` -This should display a following form:: +This should display a following form: - $form->addControl('terms', ['type' => 'boolean', 'ui' => ['caption' => 'Accept Terms and Conditions']]); +``` +$form->addControl('terms', ['type' => 'boolean', 'ui' => ['caption' => 'Accept Terms and Conditions']]); +``` -Form Submit Handling --------------------- +### Form Submit Handling .. php:method:: onSubmit($callback) @@ -529,9 +557,12 @@ Form Submit Handling .. php:method:: setApiConfig($config) Add additional parameters to Fomantic-UI .api function which does the AJAX submission of the form. -For example, if you want the loading overlay at a different HTML element, you can define it with:: - $form->setApiConfig(['stateContext' => 'my-JQuery-selector']); +For example, if you want the loading overlay at a different HTML element, you can define it with: + +``` +$form->setApiConfig(['stateContext' => 'my-JQuery-selector']); +``` All available parameters can be found here: https://fomantic-ui.com/behaviors/api.html#/settings @@ -541,64 +572,68 @@ All available parameters can be found here: https://fomantic-ui.com/behaviors/ap To continue with the example, a new Person record can be added into the database but only if they have also accepted terms and conditions. An onSubmit handler -that would perform the check can be defined displaying error or success messages:: +that would perform the check can be defined displaying error or success messages: - $form->onSubmit(function (Form $form) { - if (!$form->model->get('terms')) { - return $form->jsError('terms', 'You must accept terms and conditions'); - } +``` +$form->onSubmit(function (Form $form) { + if (!$form->model->get('terms')) { + return $form->jsError('terms', 'You must accept terms and conditions'); + } - $form->model->save(); + $form->model->save(); - return $form->jsSuccess('Registration Successful', 'We will call you soon.'); - }); + return $form->jsSuccess('Registration Successful', 'We will call you soon.'); +}); +``` Callback function can return one or multiple JavaScript actions. Methods such as :php:meth:`jsError()` or :php:meth:`jsSuccess()` will help initialize those actions for your form. Here is a code that can be used to output multiple errors at once. Errors were intentionally not grouped -with a message about failure to accept of terms and conditions:: +with a message about failure to accept of terms and conditions: - $form->onSubmit(function (Form $form) { - $errors = []; +``` +$form->onSubmit(function (Form $form) { + $errors = []; - if (!$form->model->get('name')) { - $errors[] = $form->jsError('name', 'Name must be specified'); - } + if (!$form->model->get('name')) { + $errors[] = $form->jsError('name', 'Name must be specified'); + } - if (!$form->model->get('surname')) { - $errors[] = $form->jsError('surname', 'Surname must be specified'); - } + if (!$form->model->get('surname')) { + $errors[] = $form->jsError('surname', 'Surname must be specified'); + } - if ($errors) { - return new \Atk4\Ui\Js\JsBlock($errors); - } + if ($errors) { + return new \Atk4\Ui\Js\JsBlock($errors); + } - if (!$form->model->get('terms')) { - return $form->jsError('terms', 'You must accept terms and conditions'); - } + if (!$form->model->get('terms')) { + return $form->jsError('terms', 'You must accept terms and conditions'); + } - $form->model->save(); + $form->model->save(); - return $form->jsSuccess('Registration Successful', 'We will call you soon.'); - }); + return $form->jsSuccess('Registration Successful', 'We will call you soon.'); +}); +``` So far Agile UI / Agile Data does not come with a validation library but it supports usage of 3rd party validation libraries. Callback function may raise exception. If Exception is based on ``\Atk4\Core\Exception``, -then the parameter "field" can be used to associate error with specific field:: +then the parameter "field" can be used to associate error with specific field: - throw (new \Atk4\Core\Exception('Sample Exception')) - ->addMoreInfo('field', 'surname'); +``` +throw (new \Atk4\Core\Exception('Sample Exception')) + ->addMoreInfo('field', 'surname'); +``` If 'field' parameter is not set or any other exception is generated, then error will not be associated with a field. Only the main Exception message will be delivered to the user. Core Exceptions may contain some sensitive information in parameters or back-trace, but those will not be included in response for security reasons. - -Form Layout and Sub-layout --------------------------- +### Form Layout and Sub-layout As stated above, when a Form object is created and form controls are added through either :php:meth:`addControl()` or :php:meth:`setModel()`, the form controls will appear one under each-other. This arrangement of form controls as @@ -624,54 +659,58 @@ of labels etc. Creates a sub-layout, returning new instance of a :php:class:`Form\\Layout` object. You can also specify a header. - -Form Control Group Layout and Sub-layout ----------------------------------------- +### Form Control Group Layout and Sub-layout Controls can be organized in groups, using method `Form::addGroup()` or as sub section using `Form\\Layout::addSubLayout()` method. -Using Group ------------ +### Using Group Group will create a sub layout for you where form controls added to the group will be placed side by side in one line and where you can setup specific width for each field. -My next example will add multiple controls on the same line:: +My next example will add multiple controls on the same line: - $form->setModel(new User($db), []); // will not populate any form controls automatically +``` +$form->setModel(new User($db), []); // will not populate any form controls automatically - $group = $form->addGroup('Customer'); - $group->addControl('name'); - $group->addControl('surname'); +$group = $form->addGroup('Customer'); +$group->addControl('name'); +$group->addControl('surname'); - $group = $form->addGroup('Address'); - $group->addControl('street'); - $group->addControl('city'); - $group->addControl('country'); +$group = $form->addGroup('Address'); +$group->addControl('street'); +$group->addControl('city'); +$group->addControl('country'); +``` By default grouped form controls will appear with fixed width. To distribute space you can either specify -proportions manually:: +proportions manually: - $group = $form->addGroup('Address'); - $group->addControl('address', ['width' => 'twelve']); - $group->addControl('code', ['Post Code', 'width' => 'four']); +``` +$group = $form->addGroup('Address'); +$group->addControl('address', ['width' => 'twelve']); +$group->addControl('code', ['Post Code', 'width' => 'four']); +``` -or you can divide space equally between form controls. Header is also omitted for this group:: +or you can divide space equally between form controls. Header is also omitted for this group: - $group = $form->addGroup(['width' => 'two']); - $group->addControl('city'); - $group->addControl('country'); +``` +$group = $form->addGroup(['width' => 'two']); +$group->addControl('city'); +$group->addControl('country'); +``` You can also use in-line form groups. Controls in such a group will display header on the left and -the error messages appearing on the right from the control:: +the error messages appearing on the right from the control: - $group = $form->addGroup(['Name', 'inline' => true]); - $group->addControl('first_name', ['width' => 'eight']); - $group->addControl('middle_name', ['width' => 'three', 'disabled' => true]); - $group->addControl('last_name', ['width' => 'five']); +``` +$group = $form->addGroup(['Name', 'inline' => true]); +$group->addControl('first_name', ['width' => 'eight']); +$group->addControl('middle_name', ['width' => 'three', 'disabled' => true]); +$group->addControl('last_name', ['width' => 'five']); +``` -Using Sub-layout ----------------- +### Using Sub-layout There are four specific sub layout views that you can add to your existing form layout: Generic, Accordion, Tabs and Columns. @@ -681,48 +720,52 @@ the same way as you would do for :php:class:`Form\\Layout`. Sub layout section like Accordion, Tabs or Columns will create layout specific section where you can organize fields in either accordion, tabs or columns. -The following example will show how to organize fields using regular sub layout and accordion sections:: +The following example will show how to organize fields using regular sub layout and accordion sections: - $form = Form::addTo($app); - $form->setModel($model, []); +``` +$form = Form::addTo($app); +$form->setModel($model, []); - $subLayout = $form->layout->addSubLayout([\Atk4\Ui\Form\Layout\Section::class]); +$subLayout = $form->layout->addSubLayout([\Atk4\Ui\Form\Layout\Section::class]); - Header::addTo($subLayout, ['Accordion Section in Form']); - $subLayout->setModel($model, ['name']); +Header::addTo($subLayout, ['Accordion Section in Form']); +$subLayout->setModel($model, ['name']); - $accordionLayout = $form->layout->addSubLayout([\Atk4\Ui\Form\Layout\Section\Accordion::class]); +$accordionLayout = $form->layout->addSubLayout([\Atk4\Ui\Form\Layout\Section\Accordion::class]); - $a1 = $accordionLayout->addSection('Section 1'); - $a1->setModel($model, ['iso', 'iso3']); +$a1 = $accordionLayout->addSection('Section 1'); +$a1->setModel($model, ['iso', 'iso3']); - $a2 = $accordionLayout->addSection('Section 2'); - $a2->setModel($model, ['numcode', 'phonecode']); +$a2 = $accordionLayout->addSection('Section 2'); +$a2->setModel($model, ['numcode', 'phonecode']); +``` In the example above, we first add a Generic sub layout to the existing layout of the form where one form control ('name') is added to this sub layout. Then we add another layout to the form layout. In this case it's specific Accordion layout. This sub layout -is further separated in two accordion sections and form controls are added to each section:: +is further separated in two accordion sections and form controls are added to each section: - $a1->setModel($model, ['iso', 'iso3']); - $a2->setModel($model, ['numcode', 'phonecode']); +``` +$a1->setModel($model, ['iso', 'iso3']); +$a2->setModel($model, ['numcode', 'phonecode']); +``` Sub layout gives you greater control on how to display form controls within your form. For more examples on sub layouts please visit demo page: https://github.com/atk4/ui/blob/develop/demos/form-section.php -Fomantic-UI Modifiers ---------------------- +### Fomantic-UI Modifiers There are many other classes Fomantic-UI allow you to use on a form. The next code will produce -form inside a segment (outline) and will make form controls appear smaller:: +form inside a segment (outline) and will make form controls appear smaller: - $form = new \Atk4\Ui\Form(['class.small segment' => true])); +``` +$form = new \Atk4\Ui\Form(['class.small segment' => true])); +``` For further styling see documentation on :php:class:`View`. -Not-Nullable and Required Fields -============================= +## Not-Nullable and Required Fields ATK Data has two field flags - "nullable" and "required". Because ATK Data works with PHP values, the values are defined like this: @@ -740,102 +783,106 @@ a valid number (or date) and therefore will be converted to NULL. So in most cases you'd want "required=true" flag set on your ATK Data fields. For numeric field, if zero must be a permitted entry, use "nullable=false" instead. - -Conditional Form -================ +## Conditional Form .. php:method:: setControlsDisplayRules() So far we had to present form with a set of form controls while initializing. Sometimes you would want to hide/display controls while user enters the data. -The logic is based around passing a declarative array:: +The logic is based around passing a declarative array: - $form = Form::addTo($app); - $form->addControl('phone1'); - $form->addControl('phone2'); - $form->addControl('phone3'); - $form->addControl('phone4'); +``` +$form = Form::addTo($app); +$form->addControl('phone1'); +$form->addControl('phone2'); +$form->addControl('phone3'); +$form->addControl('phone4'); - $form->setControlsDisplayRules([ - 'phone2' => ['phone1' => 'empty'], - 'phone3' => ['phone1' => 'empty', 'phone2' => 'empty'], - 'phone4' => ['phone1' => 'empty', 'phone2' => 'empty', 'phone3' => 'empty'], - ]); +$form->setControlsDisplayRules([ + 'phone2' => ['phone1' => 'empty'], + 'phone3' => ['phone1' => 'empty', 'phone2' => 'empty'], + 'phone4' => ['phone1' => 'empty', 'phone2' => 'empty', 'phone3' => 'empty'], +]); +``` The only catch here is that "empty" means "not empty". ATK UI relies on rules defined by Fomantic-UI https://fomantic-ui.com/behaviors/form.html, so you can use any of the conditions there. -Here is a more advanced example:: - - $form = Form::addTo($app); - $form->addControl('name'); - $form->addControl('subscribe', [\Atk4\Ui\Form\Control\Checkbox::class, 'Subscribe to weekly newsletter', 'class.toggle' => true]); - $form->addControl('email'); - $form->addControl('gender', [\Atk4\Ui\Form\Control\Radio::class], ['enum' => ['Female', 'Male']])->set('Female'); - $form->addControl('m_gift', [\Atk4\Ui\Form\Control\Dropdown::class, 'caption' => 'Gift for Men', 'values' => ['Beer Glass', 'Swiss Knife']]); - $form->addControl('f_gift', [\Atk4\Ui\Form\Control\Dropdown::class, 'caption' => 'Gift for Women', 'values' => ['Wine Glass', 'Lipstick']]); - - // Show email and gender when subscribe is checked. - - // Show m_gift when gender = 'male' and subscribe is checked. - // Show f_gift when gender = 'female' and subscribe is checked. - - $form->setControlsDisplayRules([ - 'email' => ['subscribe' => 'checked'], - 'gender' => ['subscribe' => 'checked'], - 'm_gift' => ['gender' => 'isExactly[Male]', 'subscribe' => 'checked'], - 'f_gift' => ['gender' => 'isExactly[Female]', 'subscribe' => 'checked'], - ]); - -You may also define multiple conditions for the form control to be visible if you wrap them inside and array:: - - - $form = Form::addTo($app); - $form->addControl('race', [\Atk4\Ui\Form\Control\Line::class]); - $form->addControl('age'); - $form->addControl('hair_cut', [\Atk4\Ui\Form\Control\Dropdown::class, 'values' => ['Short', 'Long']]); - - // Show 'hair_cut' when race contains the word 'poodle' AND age is between 1 and 5 - // OR - // Show 'hair_cut' when race contains exactly the word 'bichon' - $form->setControlsDisplayRules([ - 'hair_cut' => [['race' => 'contains[poodle]', 'age' => 'integer[1..5]'], ['race' => 'isExactly[bichon]']], - ]); - -Hiding / Showing group of field -------------------------------- - -Instead of defining rules for form controls individually you can hide/show entire group:: - - $form = Form::addTo($app, ['class.segment' => true]); - Label::addTo($form, ['Work on form group too.', 'class.top attached' => true], ['AboveControls']); - - $groupBasic = $form->addGroup(['Basic Information']); - $groupBasic->addControl('first_name', ['width' => 'eight']); - $groupBasic->addControl('middle_name', ['width' => 'three']); - $groupBasic->addControl('last_name', ['width' => 'five']); - - $form->addControl('dev', [\Atk4\Ui\Form\Control\Checkbox::class, 'caption' => 'I am a developer']); - - $groupCode = $form->addGroup(['Check all language that apply']); - $groupCode->addControl('php', [\Atk4\Ui\Form\Control\Checkbox::class]); - $groupCode->addControl('js', [\Atk4\Ui\Form\Control\Checkbox::class]); - $groupCode->addControl('html', [\Atk4\Ui\Form\Control\Checkbox::class]); - $groupCode->addControl('css', [\Atk4\Ui\Form\Control\Checkbox::class]); - - $groupOther = $form->addGroup(['Others']); - $groupOther->addControl('language', ['width' => 'eight']); - $groupOther->addControl('favorite_pet', ['width' => 'four']); - - // To hide-show group simply select a field in that group. - // Show group where 'php' belong when dev is checked. - // Show group where 'language' belong when dev is checked. - - $form->setGroupDisplayRules([ - 'php' => ['dev' => 'checked'], - 'language' => ['dev' => 'checked'], - ]); +Here is a more advanced example: + +``` +$form = Form::addTo($app); +$form->addControl('name'); +$form->addControl('subscribe', [\Atk4\Ui\Form\Control\Checkbox::class, 'Subscribe to weekly newsletter', 'class.toggle' => true]); +$form->addControl('email'); +$form->addControl('gender', [\Atk4\Ui\Form\Control\Radio::class], ['enum' => ['Female', 'Male']])->set('Female'); +$form->addControl('m_gift', [\Atk4\Ui\Form\Control\Dropdown::class, 'caption' => 'Gift for Men', 'values' => ['Beer Glass', 'Swiss Knife']]); +$form->addControl('f_gift', [\Atk4\Ui\Form\Control\Dropdown::class, 'caption' => 'Gift for Women', 'values' => ['Wine Glass', 'Lipstick']]); + +// Show email and gender when subscribe is checked. + +// Show m_gift when gender = 'male' and subscribe is checked. +// Show f_gift when gender = 'female' and subscribe is checked. + +$form->setControlsDisplayRules([ + 'email' => ['subscribe' => 'checked'], + 'gender' => ['subscribe' => 'checked'], + 'm_gift' => ['gender' => 'isExactly[Male]', 'subscribe' => 'checked'], + 'f_gift' => ['gender' => 'isExactly[Female]', 'subscribe' => 'checked'], +]); +``` + +You may also define multiple conditions for the form control to be visible if you wrap them inside and array: + +``` +$form = Form::addTo($app); +$form->addControl('race', [\Atk4\Ui\Form\Control\Line::class]); +$form->addControl('age'); +$form->addControl('hair_cut', [\Atk4\Ui\Form\Control\Dropdown::class, 'values' => ['Short', 'Long']]); + +// Show 'hair_cut' when race contains the word 'poodle' AND age is between 1 and 5 +// OR +// Show 'hair_cut' when race contains exactly the word 'bichon' +$form->setControlsDisplayRules([ + 'hair_cut' => [['race' => 'contains[poodle]', 'age' => 'integer[1..5]'], ['race' => 'isExactly[bichon]']], +]); +``` + +### Hiding / Showing group of field + +Instead of defining rules for form controls individually you can hide/show entire group: + +``` +$form = Form::addTo($app, ['class.segment' => true]); +Label::addTo($form, ['Work on form group too.', 'class.top attached' => true], ['AboveControls']); + +$groupBasic = $form->addGroup(['Basic Information']); +$groupBasic->addControl('first_name', ['width' => 'eight']); +$groupBasic->addControl('middle_name', ['width' => 'three']); +$groupBasic->addControl('last_name', ['width' => 'five']); + +$form->addControl('dev', [\Atk4\Ui\Form\Control\Checkbox::class, 'caption' => 'I am a developer']); + +$groupCode = $form->addGroup(['Check all language that apply']); +$groupCode->addControl('php', [\Atk4\Ui\Form\Control\Checkbox::class]); +$groupCode->addControl('js', [\Atk4\Ui\Form\Control\Checkbox::class]); +$groupCode->addControl('html', [\Atk4\Ui\Form\Control\Checkbox::class]); +$groupCode->addControl('css', [\Atk4\Ui\Form\Control\Checkbox::class]); + +$groupOther = $form->addGroup(['Others']); +$groupOther->addControl('language', ['width' => 'eight']); +$groupOther->addControl('favorite_pet', ['width' => 'four']); + +// To hide-show group simply select a field in that group. +// Show group where 'php' belong when dev is checked. +// Show group where 'language' belong when dev is checked. + +$form->setGroupDisplayRules([ + 'php' => ['dev' => 'checked'], + 'language' => ['dev' => 'checked'], +]); +``` .. todo:: MOVE THIS TO SEPARATE FILE diff --git a/docs/grid.md b/docs/grid.md index e3c17a367a..0c8f8089a8 100644 --- a/docs/grid.md +++ b/docs/grid.md @@ -1,9 +1,6 @@ - .. _grid: -==== -Grid -==== +# Grid .. php:namespace:: Atk4\Ui .. php:class:: Grid @@ -12,87 +9,102 @@ If you didn't read documentation on :ref:`table` you should start with that. Whi data rendering, Grid component supplies various enhancements around it, such as paginator, quick-search, toolbar and others by relying on other components. -Using Grid -========== +## Using Grid -Here is a simple usage:: +Here is a simple usage: - Grid::addTo($app)->setModel(new Country($db)); +``` +Grid::addTo($app)->setModel(new Country($db)); +``` -To make your grid look nicer, you might want to add some buttons and enable quicksearch:: +To make your grid look nicer, you might want to add some buttons and enable quicksearch: - $grid = Grid::addTo($app); - $grid->setModel(new Country($db)); +``` +$grid = Grid::addTo($app); +$grid->setModel(new Country($db)); - $grid->addQuickSearch(); - $grid->menu->addItem('Reload Grid', new \Atk4\Ui\Js\JsReload($grid)); +$grid->addQuickSearch(); +$grid->menu->addItem('Reload Grid', new \Atk4\Ui\Js\JsReload($grid)); +``` -Adding Menu Items -================= +## Adding Menu Items .. php:attr:: menu .. php:method: addButton($label) Grid top-bar which contains QuickSearch is implemented using Fomantic-UI "ui menu". With that -you can add additional items and use all features of a regular :php:class:`Menu`:: +you can add additional items and use all features of a regular :php:class:`Menu`: - $sub = $grid->menu->addMenu('Drop-down'); - $sub->addItem('Test123'); +``` +$sub = $grid->menu->addMenu('Drop-down'); +$sub->addItem('Test123'); +``` For compatibility grid supports addition of the buttons to the menu, but there are several -Fomantic-UI limitations that wouldn't allow to format buttons nicely:: +Fomantic-UI limitations that wouldn't allow to format buttons nicely: - $grid->addButton('Hello'); +``` +$grid->addButton('Hello'); +``` -If you don't need menu, you can disable menu bar entirely:: +If you don't need menu, you can disable menu bar entirely: - $grid = Grid::addTo($app, ['menu' => false]); +``` +$grid = Grid::addTo($app, ['menu' => false]); +``` -Adding Quick Search -=================== +## Adding Quick Search .. php:attr:: quickSearch .. php:method: addQuickSearch($fields = [], $hasAutoQuery = false) After you have associated grid with a model using :php:class:`View::setModel()` you can -include quick-search component:: +include quick-search component: - $grid->addQuickSearch(['name', 'surname']); +``` +$grid->addQuickSearch(['name', 'surname']); +``` If you don't specify argument, then search will be done by a models title field. (https://agile-data.readthedocs.io/en/develop/model.html#title-field) By default, quick search input field will query server when user press the Enter key. However, it is possible to make it -querying the server automatically, i.e. after the user has finished typing, by setting the auto query parameter:: +querying the server automatically, i.e. after the user has finished typing, by setting the auto query parameter: - $grid->addQuickSearch(['name', 'surname'], true); +``` +$grid->addQuickSearch(['name', 'surname'], true); +``` -Paginator -========= +## Paginator .. php:attr:: paginator .. php:attr:: ipp Grid comes with a paginator already. You can disable it by setting $paginator property to false. Alternatively you -can provide seed for the paginator or even entire object:: +can provide seed for the paginator or even entire object: - $grid = Grid::addTo($app, ['paginator' => ['range' => 2]]); +``` +$grid = Grid::addTo($app, ['paginator' => ['range' => 2]]); +``` -You can use $ipp property to specify different number of items per page:: +You can use $ipp property to specify different number of items per page: - $grid->ipp = 10; +``` +$grid->ipp = 10; +``` -JsPaginator ------------ +### JsPaginator .. php:method:: addJsPaginator($ipp, $options = [], $container = null, $scrollRegion = 'Body') JsPaginator will load table content dynamically when user scroll down the table window on screen. - $table->addJsPaginator(30); +``` +$table->addJsPaginator(30); +``` See :php:meth:`Table::addJsPaginator` @@ -101,8 +113,7 @@ See :php:meth:`Table::addJsPaginator` Use this method if you want fixed table header when scrolling down table. In this case you have to set fixed height of your table container. -Actions -======= +## Actions .. php:attr:: actions @@ -122,22 +133,22 @@ See :php:meth:`Table\\Column\\\Actions::addAction` .. php:method:: addModalAction($button, $title, $callback) Similar to addAction, but when clicking a button, will open a modal dialog and execute $callback -to populate a content:: +to populate a content: - $grid->addModalAction('Details', 'Additional Details', function (View $p, $id) use ($grid) { - // $id of the record which was clicked - // $grid->model = $grid->model->load($id); +``` +$grid->addModalAction('Details', 'Additional Details', function (View $p, $id) use ($grid) { + // $id of the record which was clicked + // $grid->model = $grid->model->load($id); - LoremIpsum::addTo($p); - }); + LoremIpsum::addTo($p); +}); +``` Calling this method multiple times will add button into same action column. See :php:meth:`Atk4\\Ui\\Table\\Column\\Actions::addModal` - -Column Menus -============ +## Column Menus .. php:method:: addDropdown($columnName, $items, $fx, $icon = 'caret square down', $menuId = null) @@ -146,21 +157,21 @@ Column Menus Methods addDropdown and addPopup provide a wrapper for :php:meth:`Atk4\\Ui\\Table\\Column::addDropdown` and :php:meth:`Atk4\\Ui\\\Table\\Column::addPopup` methods. -Selection -========= +## Selection Grid can have a checkbox column for you to select elements. It relies on :php:class:`Table\\Column\\Checkbox`, but will additionally place this column before any other column inside a grid. You can use :php:meth:`Table\\Column\\Checkbox::jsChecked()` -method to reference value of selected checkboxes inside any :ref:`js_action`:: +method to reference value of selected checkboxes inside any :ref:`js_action`: - $sel = $grid->addSelection(); - $grid->menu->addItem('show selection') - ->on('click', new \Atk4\Ui\Js\JsExpression( - 'alert(\'Selected: \' + [])', [$sel->jsChecked()] - )); +``` +$sel = $grid->addSelection(); +$grid->menu->addItem('show selection') + ->on('click', new \Atk4\Ui\Js\JsExpression( + 'alert(\'Selected: \' + [])', [$sel->jsChecked()] + )); +``` -Sorting -======= +## Sorting .. php:attr:: sortable @@ -172,9 +183,7 @@ of particular columns. See also :php:attr:`Table::$sortable`. - -Advanced Usage -============== +## Advanced Usage .. php:attr:: table diff --git a/docs/header.md b/docs/header.md index e8c2f11f74..85287efe98 100644 --- a/docs/header.md +++ b/docs/header.md @@ -1,8 +1,6 @@ .. php:namespace:: Atk4\Ui -====== -Header -====== +# Header Can be used for page or section headers. @@ -10,59 +8,64 @@ Based around: https://fomantic-ui.com/elements/header.html. Demo: https://ui.agiletoolkit.org/demos/header.php -Basic Usage -=========== +## Basic Usage -By default header size will depend on where you add it:: +By default header size will depend on where you add it: - Header::addTo($this, ['Hello, Header']); +``` +Header::addTo($this, ['Hello, Header']); +``` -Attributes -========== +## Attributes .. php:attr:: size .. php:attr:: subHeader -Specify size and sub-header content:: +Specify size and sub-header content: - Header::addTo($seg, [ - 'H1 header', - 'size' => 1, - 'subHeader' => 'H1 subheader', - ]); +``` +Header::addTo($seg, [ + 'H1 header', + 'size' => 1, + 'subHeader' => 'H1 subheader', +]); - // or +// or - Header::addTo($seg, [ - 'Small header', - 'size' => 'small', - 'subHeader' => 'small subheader', - ]); +Header::addTo($seg, [ + 'Small header', + 'size' => 'small', + 'subHeader' => 'small subheader', +]); +``` -Icon and Image -=============== +## Icon and Image .. php:attr:: icon .. php:attr:: image -Header may specify icon or image:: +Header may specify icon or image: - Header::addTo($seg, [ - 'Header with icon', - 'icon' => 'settings', - 'subHeader' => 'and with sub-header', - ]); +``` +Header::addTo($seg, [ + 'Header with icon', + 'icon' => 'settings', + 'subHeader' => 'and with sub-header', +]); +``` -Here you can also specify seed for the image:: +Here you can also specify seed for the image: - $img = $app->cdn['atk'] . '/logo.png'; - Header::addTo($seg, [ - 'Center-aligned header', - 'aligned' => 'center', - 'image' => [$img, 'class.disabled' => true], - 'subHeader' => 'header with image', - ]); +``` +$img = $app->cdn['atk'] . '/logo.png'; +Header::addTo($seg, [ + 'Center-aligned header', + 'aligned' => 'center', + 'image' => [$img, 'class.disabled' => true], + 'subHeader' => 'header with image', +]); +``` diff --git a/docs/helloworld.md b/docs/helloworld.md index c914757294..05fb60bcae 100644 --- a/docs/helloworld.md +++ b/docs/helloworld.md @@ -1,10 +1,6 @@ - - .. _helloworld: -========== -HelloWorld -========== +# HelloWorld .. php:namespace:: Atk4\Ui @@ -13,12 +9,13 @@ HelloWorld Presence of the "Hello World" component in the standard distribution is just us saying "The best way to create a Hello World app is around a HelloWorld component". -Basic Usage -=========== +## Basic Usage -To add a "Hello, World" message:: +To add a "Hello, World" message: - HelloWorld::addTo($app); +``` +HelloWorld::addTo($app); +``` There are no additional features on this component, it is intentionally left simple. For a more sophisticated "Hello, World" implementation look into Hello World add-on. diff --git a/docs/icon.md b/docs/icon.md index dac1ebd211..4780a7fbe9 100644 --- a/docs/icon.md +++ b/docs/icon.md @@ -1,108 +1,118 @@ - .. _icon: -==== -Icon -==== +# Icon .. php:namespace:: Atk4\Ui .. php:class:: Icon -Implements basic icon:: +Implements basic icon: - $icon = Icon::addTo($app, ['book']); +``` +$icon = Icon::addTo($app, ['book']); +``` -Alternatively:: +Alternatively: - $icon = Icon::addTo($app, [], ['flag'])->addClass('outline'); +``` +$icon = Icon::addTo($app, [], ['flag'])->addClass('outline'); +``` Most commonly icon class is used for embedded icons on a :php:class:`Button` -or inside other components (see :ref:`icon_other_comp`):: +or inside other components (see :ref:`icon_other_comp`): - $b1 = new \Atk4\Ui\Button(['Click Me', 'icon' => 'book']); +``` +$b1 = new \Atk4\Ui\Button(['Click Me', 'icon' => 'book']); +``` -You can, of course, create instance of an Icon yourself:: +You can, of course, create instance of an Icon yourself: - $icon = new \Atk4\Ui\Icon('book'); - $b1 = new \Atk4\Ui\Button(['Click Me', 'icon' => $icon]); +``` +$icon = new \Atk4\Ui\Icon('book'); +$b1 = new \Atk4\Ui\Button(['Click Me', 'icon' => $icon]); +``` You do not need to add an icon into the render tree when specifying like that. The icon is selected through class. To find out what icons are available, refer to Fomantic-UI icon documentation: https://fomantic-ui.com/elements/icon.html -You can also use States, Variations by passing classes to your button:: +You can also use States, Variations by passing classes to your button: - Button::addTo($app, ['Click Me', 'class.red' => true, 'icon' => 'flipped big question']); +``` +Button::addTo($app, ['Click Me', 'class.red' => true, 'icon' => 'flipped big question']); - Label::addTo($app, ['Battery Low', 'class.green' => true, 'icon' => 'battery low']); +Label::addTo($app, ['Battery Low', 'class.green' => true, 'icon' => 'battery low']); +``` .. _icon_other_comp: -Using on other Components -========================= +## Using on other Components You can use icon on the following components: :php:class:`Button`, :php:class:`Label`, :php:class:`Header` -:php:class:`Message`, :php:class:`Menu` and possibly some others. Here are some examples:: - - - Header::addTo($app, ['Header', 'class.red' => true, 'icon' => 'flipped question']); - Button::addTo($app, ['Button', 'class.red' => true, 'icon' => 'flipped question']); - - $menu = Menu::addTo($app); - $menu->addItem(['Menu Item', 'icon' => 'flipped question']); - $subMenu = $menu->addMenu(['Sub-menu', 'icon' => 'flipped question']); - $subMenu->addItem(['Sub Item', 'icon' => 'flipped question']); +:php:class:`Message`, :php:class:`Menu` and possibly some others. Here are some examples: - Label::addTo($app, ['Label', 'class.right ribbon red' => true, 'icon' => 'flipped question']); +``` +Header::addTo($app, ['Header', 'class.red' => true, 'icon' => 'flipped question']); +Button::addTo($app, ['Button', 'class.red' => true, 'icon' => 'flipped question']); +$menu = Menu::addTo($app); +$menu->addItem(['Menu Item', 'icon' => 'flipped question']); +$subMenu = $menu->addMenu(['Sub-menu', 'icon' => 'flipped question']); +$subMenu->addItem(['Sub Item', 'icon' => 'flipped question']); +Label::addTo($app, ['Label', 'class.right ribbon red' => true, 'icon' => 'flipped question']); +``` -Groups -====== +## Groups Fomantic-UI support icon groups. The best way to implement is to supply :php:class:`Template` to an -icon:: +icon: - Icon::addTo($app, ['template' => new \Atk4\Ui\Template(' - - - '), false]); +``` +Icon::addTo($app, ['template' => new \Atk4\Ui\Template(' + + +'), false]); +``` However there are several other options you can use when working with your custom HTML. This is not exclusive to Icon, but I'm adding a few examples here, just for your convenience. -Let's start with a View that contains your custom HTML loaded from file or embedded like this:: +Let's start with a View that contains your custom HTML loaded from file or embedded like this: - $view = View::addTo($app, ['template' => new \Atk4\Ui\Template('
Hello my {Icon} - - - {/}, It is me
')]); +``` +$view = View::addTo($app, ['template' => new \Atk4\Ui\Template('
Hello my {Icon} + + +{/}, It is me
')]); +``` Looking at the template it has a region `{Icon}..{/}`. Try by executing the code above, and you'll see a text message with a user icon in a circle. You can replace this region by passing it as a template into Icon class. For that you need to disable a standard Icon template and specify a correct Spot -when adding:: +when adding: - $icon = Icon::addTo($view, ['red book', 'template' => false], ['Icon']); +``` +$icon = Icon::addTo($view, ['red book', 'template' => false], ['Icon']); +``` This technique may be helpful for you if you are creating re-usable elements and you wish to store Icon object in one of your public properties. -Composing ---------- +### Composing -Composing offers you another way to deal with Group icons:: +Composing offers you another way to deal with Group icons: - $noUsers = new \Atk4\Ui\View(['class.huge icons' => true, 'element' => 'i']); - Icon::addTo($noUsers, ['big red dont']); - Icon::addTo($noUsers, ['black user']); +``` +$noUsers = new \Atk4\Ui\View(['class.huge icons' => true, 'element' => 'i']); +Icon::addTo($noUsers, ['big red dont']); +Icon::addTo($noUsers, ['black user']); - $app->add($noUsers); +$app->add($noUsers); +``` -Icon in Your Component -====================== +## Icon in Your Component Sometimes you want to build a component that will contain user-defined icon. Here you can find an implementation for ``SocialAdd`` component that implements a friendly social button with @@ -112,68 +122,74 @@ the following features: - allow to customize icon by specifying it as string, object or injecting properties - allow to customize label -Here is the code with comments:: +Here is the code with comments: - /** +``` +/** +```     * Implements a social network add button. You can initialize the button by passing - * social network as a parameter: new SocialAdd('facebook') - * or alternatively you can specify $social, $icon and content individually: - * new SocialAdd(['Follow on Facebook', 'social' => 'facebook', 'icon' => 'facebook f']); - * - * For convenience use this with link(), which will automatically open a new window - * too. - */ - class SocialAdd extends \Atk4\Ui\View +``` + * social network as a parameter: new SocialAdd('facebook') + * or alternatively you can specify $social, $icon and content individually: + * new SocialAdd(['Follow on Facebook', 'social' => 'facebook', 'icon' => 'facebook f']); + * + * For convenience use this with link(), which will automatically open a new window + * too. + */ +class SocialAdd extends \Atk4\Ui\View +{ + public $social = null; + public $icon = null; + public $defaultTemplate = null; // __DIR__ . '../templates/socialadd.html' + + protected function init(): void { - public $social = null; - public $icon = null; - public $defaultTemplate = null; // __DIR__ . '../templates/socialadd.html' - - protected function init(): void - { - parent::init(); - - if (is_null($this->social)) { - $this->social = $this->content; - $this->content = 'Add on ' . ucwords($this->content); - } - - if (!$this->social) { - throw new Exception('Specify social network to use'); - } - - if (is_null($this->icon)) { - $this->icon = $this->social; - } - - if (!$this->template) { - // TODO: Place template into file and set defaultTemplate instead - $this->template = new \Atk4\Ui\Template( - '<{_element}button{/} class="ui ' . $this->social . ' button" {$attributes}> - - {$Icon} - - - {$Content} - '); - } - - // Initialize icon - if (!is_object($this->icon)) { - $this->icon = new \Atk4\Ui\Icon($this->icon); - } - - // Add icon into render tree - $this->add($this->icon, 'Icon'); + parent::init(); + + if (is_null($this->social)) { + $this->social = $this->content; + $this->content = 'Add on ' . ucwords($this->content); + } + + if (!$this->social) { + throw new Exception('Specify social network to use'); } + + if (is_null($this->icon)) { + $this->icon = $this->social; + } + + if (!$this->template) { + // TODO: Place template into file and set defaultTemplate instead + $this->template = new \Atk4\Ui\Template( +'<{_element}button{/} class="ui ' . $this->social . ' button" {$attributes}> + + {$Icon} + + + {$Content} +'); + } + + // Initialize icon + if (!is_object($this->icon)) { + $this->icon = new \Atk4\Ui\Icon($this->icon); + } + + // Add icon into render tree + $this->add($this->icon, 'Icon'); } +} +```   // Usage Examples. Start with the most basic usage - SocialAdd::addTo($app, ['instagram']); +``` +SocialAdd::addTo($app, ['instagram']); - // Next specify label and separately name of social network - SocialAdd::addTo($app, ['Follow on Twitter', 'social' => 'twitter']); +// Next specify label and separately name of social network +SocialAdd::addTo($app, ['Follow on Twitter', 'social' => 'twitter']); - // Finally provide custom icon and make the button clickable. - SocialAdd::addTo($app, ['facebook', 'icon' => 'facebook f']) - ->link('https://facebook.com', '_blank'); +// Finally provide custom icon and make the button clickable. +SocialAdd::addTo($app, ['facebook', 'icon' => 'facebook f']) + ->link('https://facebook.com', '_blank'); +``` diff --git a/docs/image.md b/docs/image.md index dfc859d1c0..17d573bb95 100644 --- a/docs/image.md +++ b/docs/image.md @@ -1,9 +1,6 @@ - .. _image: -===== -Image -===== +# Image .. php:namespace:: Atk4\Ui @@ -11,20 +8,22 @@ Image Implements Image around https://fomantic-ui.com/elements/image.html. -Basic Usage -=========== +## Basic Usage -Implements basic image:: +Implements basic image: - $icon = Image::addTo($app, ['image.gif']); +``` +$icon = Image::addTo($app, ['image.gif']); +``` You need to make sure that argument specified to Image is a valid URL to an image. -Specify classes -=============== +## Specify classes -You can pass additional classes to an image:: +You can pass additional classes to an image: - $img = $app->cdn['atk'] . '/logo.png'; - $icon = Image::addTo($app, [$img, 'class.disabled' => true]); +``` +$img = $app->cdn['atk'] . '/logo.png'; +$icon = Image::addTo($app, [$img, 'class.disabled' => true]); +``` diff --git a/docs/index.md b/docs/index.md index da136130da..52a53b51e1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,6 +1,4 @@ - -Agile UI Documentation -======================== +## Agile UI Documentation Contents: @@ -33,10 +31,7 @@ Contents: virtual-page console - - -Indices and tables -================== +## Indices and tables * :ref:`genindex` * :ref:`search` diff --git a/docs/js.md b/docs/js.md index edb28d65ad..9ae560387a 100644 --- a/docs/js.md +++ b/docs/js.md @@ -2,19 +2,18 @@ .. _js: -================== -JavaScript Mapping -================== +# JavaScript Mapping A modern user interface cannot exist without JavaScript. Agile UI provides you assistance with generating and executing events directly from PHP and the context of your Views. The most basic -example of such integration would be a button, that hides itself when clicked:: +example of such integration would be a button, that hides itself when clicked: - $b = new Button(); - $b->js('click')->hide(); +``` +$b = new Button(); +$b->js('click')->hide(); +``` -Introduction -============ +## Introduction Agile UI does not replace JavaScript. It encourages you to keep JavaScript routines as generic as possible, then associate them with your UI through actions and events. @@ -24,39 +23,48 @@ by specifying selector, you can perform certain actions: .. code-block:: js - $('#my-long-id').hide(); +``` +$('#my-long-id').hide(); +``` Agile UI provides a built-in integration for jQuery. To use jQuery and any other JavaScript library in Agile UI you need to understand how Actions and Events work. .. _js_action: -Actions -------- +### Actions An action is represented by a PHP object that can map itself into a JavaScript code. For instance -the code for hiding a view can be generated by calling:: +the code for hiding a view can be generated by calling: - $action = $view->js()->hide(); +``` +$action = $view->js()->hide(); +``` -There are other ways to generate an action, such as using :php:meth:`JsExpression`:: +There are other ways to generate an action, such as using :php:meth:`JsExpression`: - $action = new JsExpression('alert([])', ['Hello world']); +``` +$action = new JsExpression('alert([])', ['Hello world']); +``` -Finally, actions can be used inside other actions:: +Finally, actions can be used inside other actions: - $action = new JsExpression('alert([])', [ - $view->js()->text(), - ]); +``` +$action = new JsExpression('alert([])', [ + $view->js()->text(), +]); - // will produce alert($('#button-id').text()); +// will produce alert($('#button-id').text()); +``` -or:: +or: - $action = $view->js()->text(new JsExpression('[] + []', [ - 5, - 10, - ])); +``` +$action = $view->js()->text(new JsExpression('[] + []', [ + 5, + 10, +])); +``` All of the above examples will produce a valid "Action" object that can be used further. @@ -65,40 +73,46 @@ All of the above examples will produce a valid "Action" object that can be used We never encourage writing JavaScript logic in PHP. The purpose of JS layer is for binding events and actions with your generic JavaScript routines. -Events ------- +### Events Agile UI also offers a great way to associate actions with certain client-side events. Those events can be triggered by the user or by other JavaScript code. There are several ways to bind `$action`. -To execute actions instantly on page load, use `true` as first argument to :php:meth:`View::js()`:: +To execute actions instantly on page load, use `true` as first argument to :php:meth:`View::js()`: - $view->js(true, new JsExpression('alert([])', ['Hello world'])); +``` +$view->js(true, new JsExpression('alert([])', ['Hello world'])); +``` -You can also combine both forms:: +You can also combine both forms: - $view->js(true)->hide(); +``` +$view->js(true)->hide(); +``` -Finally, you can specify the name of the JavaScript event:: +Finally, you can specify the name of the JavaScript event: - $view->js('click')->hide(); +``` +$view->js('click')->hide(); +``` Agile UI also provides support for an `on` event binding. This allows to apply events on -multiple elements:: +multiple elements: - $buttons = View::addTo($app, ['ui' => 'basic buttons']); +``` +$buttons = View::addTo($app, ['ui' => 'basic buttons']); - \Atk4\Ui\Button::addTo($buttons, ['One']); - \Atk4\Ui\Button::addTo($buttons, ['Two']); - \Atk4\Ui\Button::addTo($buttons, ['Three']); +\Atk4\Ui\Button::addTo($buttons, ['One']); +\Atk4\Ui\Button::addTo($buttons, ['Two']); +\Atk4\Ui\Button::addTo($buttons, ['Three']); - $buttons->on('click', '.button')->hide(); +$buttons->on('click', '.button')->hide(); +``` All the above examples will map themselves into a simple and readable JavaScript code. -Extending ---------- +### Extending Agile UI builds upon the concepts of actions and events in the following ways: @@ -120,16 +134,13 @@ Agile UI builds upon the concepts of actions and events in the following ways: - closure can respond with additional actions, - various UI elements (such as Form) extend this concept further. -Including JS/CSS ----------------- +### Including JS/CSS Sometimes you need to include an additional .js or .css file for your code to work. See :php:meth:`App:requireJs()` and :php:meth:`App::requireCss()` for details. - -Building actions with JsExpressionable -====================================== +## Building actions with JsExpressionable .. php:interface:: JsExpressionable @@ -141,19 +152,22 @@ Building actions with JsExpressionable Express object as a string containing valid JavaScript statement or expression. :php:class:`View` class is supported as JsExpression argument natively and will present -itself as a valid selector. Example:: +itself as a valid selector. Example: - $frame = new View(); - $button->js(true)->appendTo($frame); +``` +$frame = new View(); +$button->js(true)->appendTo($frame); +``` The resulting Javascript will be: .. code-block:: js - $('#button-id').appendTo('#frame-id'); +``` +$('#button-id').appendTo('#frame-id'); +``` -JavaScript Chain Building -------------------------- +### JavaScript Chain Building .. php:class:: JsChain @@ -162,11 +176,13 @@ JavaScript Chain Building Chain is a PHP object that represents one or several actions that are to be executed on the client side. The JsChain objects themselves are generic, so in these examples we'll be using Jquery which -is a descendant of JsChain:: +is a descendant of JsChain: - $chain = new Jquery('#the-box-id'); +``` +$chain = new Jquery('#the-box-id'); - $chain->dropdown(); +$chain->dropdown(); +``` The calls to the chain are stored in the object and can be converted into JavaScript by calling :php:meth:`JsChain::jsRender()` @@ -174,15 +190,19 @@ The calls to the chain are stored in the object and can be converted into JavaSc Converts actions recorded in JsChain into string of JavaScript code. -Executing:: +Executing: - echo $chain->jsRender(); +``` +echo $chain->jsRender(); +``` will output: .. code-block:: js - $('#the-box-id').dropdown(); +``` +$('#the-box-id').dropdown(); +``` .. important:: @@ -197,16 +217,17 @@ will output: :php:meth:`JsExpressionable::jsRender`. The result will not be escaped and any object implementing :php:interface:`JsExpressionable` interface is responsible for safe JavaScript generation. -The following code is safe:: +The following code is safe: - $b = new Button(); - $b->js(true)->text($_GET['button_text']); +``` +$b = new Button(); +$b->js(true)->text($_GET['button_text']); +``` Any malicious input through the GET arguments will be encoded as JS string before being included as an argument to `text()`. -View to JS integration ----------------------- +### View to JS integration We are not building JavaScript code just for the exercise. Our whole point is ability to link that code between actual views. All views support JavaScript binding through two methods: :php:meth:`View::js()` and :php:meth:`View::on()`. @@ -221,59 +242,63 @@ between actual views. All views support JavaScript binding through two methods: If `$otherChain` is specified together with event, it will also be bound to said event. `$otherChain` can also be a PHP closure. -Several usage cases for plain `js()` method. The most basic scenario is to perform action on the view when event happens:: +Several usage cases for plain `js()` method. The most basic scenario is to perform action on the view when event happens: - $b1 = new Button('One'); - $b1->js('click')->hide(); +``` +$b1 = new Button('One'); +$b1->js('click')->hide(); - $b2 = new Button('Two'); - $b2->js('click', $b1->js()->hide()); +$b2 = new Button('Two'); +$b2->js('click', $b1->js()->hide()); +``` .. php:method:: on(String $event, [String selector], $callback = null) Returns chain that will be automatically executed if $event occurs. If $callback is specified, it will also be executed on event. -The following code will show three buttons and clicking any one will hide it. Only a single action is created:: +The following code will show three buttons and clicking any one will hide it. Only a single action is created: - $buttons = View::addTo($app, ['ui' => 'basic buttons']); +``` +$buttons = View::addTo($app, ['ui' => 'basic buttons']); - \Atk4\Ui\Button::addTo($buttons, ['One']); - \Atk4\Ui\Button::addTo($buttons, ['Two']); - \Atk4\Ui\Button::addTo($buttons, ['Three']); +\Atk4\Ui\Button::addTo($buttons, ['One']); +\Atk4\Ui\Button::addTo($buttons, ['Two']); +\Atk4\Ui\Button::addTo($buttons, ['Three']); - $buttons->on('click', '.button')->hide(); +$buttons->on('click', '.button')->hide(); - // Generates: - // $('#top-element-id').on('click', '.button', function (event) { - // event.preventDefault(); - // event.stopPropagation(); - // $(this).hide(); - // }); +// Generates: +// $('#top-element-id').on('click', '.button', function (event) { +// event.preventDefault(); +// event.stopPropagation(); +// $(this).hide(); +// }); +``` :php:meth:`View::on()` is handy when multiple elements exist inside a view which you want to trigger individually. The best example would be a :php:class:`Lister` with interactive elements. -You can use both actions together. The next example will allow only one button to be active:: +You can use both actions together. The next example will allow only one button to be active: - $buttons = View::addTo($app, ['ui' => 'basic buttons']); +``` +$buttons = View::addTo($app, ['ui' => 'basic buttons']); - \Atk4\Ui\Button::addTo($buttons, ['One']); - \Atk4\Ui\Button::addTo($buttons, ['Two']); - \Atk4\Ui\Button::addTo($buttons, ['Three']); +\Atk4\Ui\Button::addTo($buttons, ['One']); +\Atk4\Ui\Button::addTo($buttons, ['Two']); +\Atk4\Ui\Button::addTo($buttons, ['Three']); - $buttons->on('click', '.button', $b3->js()->hide()); +$buttons->on('click', '.button', $b3->js()->hide()); - // Generates: - // $('#top-element-id').on('click', '.button', function (event) { - // event.preventDefault(); - // event.stopPropagation(); - // $('#b3-element-id').hide(); - // }); +// Generates: +// $('#top-element-id').on('click', '.button', function (event) { +// event.preventDefault(); +// event.stopPropagation(); +// $('#b3-element-id').hide(); +// }); +``` - -JsExpression -============ +## JsExpression .. php:class:: JsExpression .. php:method:: __construct(template, args) @@ -281,51 +306,60 @@ JsExpression Returns object that renders into template by substituting args into it. Sometimes you want to execute action by calling a global JavaScript method. For this -and other cases you can use JsExpression:: +and other cases you can use JsExpression: - $action = new JsExpression('alert([])', [ - $view->js()->text(), - ]); +``` +$action = new JsExpression('alert([])', [ + $view->js()->text(), +]); +``` Because :php:class:`JsChain` will typically wrap all the arguments through -:php:meth:`JsChain::_jsonEncode()`, it prevents you from accidentally injecting JavaScript code:: +:php:meth:`JsChain::_jsonEncode()`, it prevents you from accidentally injecting JavaScript code: - $b = new Button(); - $b->js(true)->text('2 + 2'); +``` +$b = new Button(); +$b->js(true)->text('2 + 2'); +``` This will result in a button having a label `2 + 2` instead of having a label `4`. To -get around this, you can use JsExpression:: +get around this, you can use JsExpression: - $b = new Button(); - $b->js(true)->text(new JsExpression('2 + 2')); +``` +$b = new Button(); +$b->js(true)->text(new JsExpression('2 + 2')); +``` This time `2 + 2` is no longer escaped and will be used as plain JavaScript code. Another example -shows how you can use global variables:: +shows how you can use global variables: - echo (new Jquery('document'))->find('h1')->hide()->jsRender(); +``` +echo (new Jquery('document'))->find('h1')->hide()->jsRender(); - // produces $('document').find('h1').hide(); - // does not hide anything because document is treated as string selector! +// produces $('document').find('h1').hide(); +// does not hide anything because document is treated as string selector! - $expr = new JsExpression('document'); - echo (new Jquery($expr))->find('h1')->hide()->jsRender(); +$expr = new JsExpression('document'); +echo (new Jquery($expr))->find('h1')->hide()->jsRender(); - // produces $(document).find('h1').hide(); - // works correctly!! +// produces $(document).find('h1').hide(); +// works correctly!! +``` -Template of JsExpression ------------------------- +### Template of JsExpression The JsExpression class provides the most simple implementation that can be useful for providing any JavaScript expressions. My next example will set height of right container to the sum of 2 -boxes on the left:: +boxes on the left: - $h1 = $leftBox1->js()->height(); - $h2 = $leftBox2->js()->height(); +``` +$h1 = $leftBox1->js()->height(); +$h2 = $leftBox2->js()->height(); - $sum = new JsExpression('[] + []', [$h1, $h2]); +$sum = new JsExpression('[] + []', [$h1, $h2]); - $rightBoxContainer->js(true)->height( $sum ); +$rightBoxContainer->js(true)->height( $sum ); +``` It is important to remember that the height of an element is a browser-side property and you must operate with it in your browser by passing expressions into chain. @@ -335,20 +369,20 @@ The template language for JsExpression is super-simple: - [] will be mapped to next argument in the argument array - [foo] will be mapped to named argument in argument array -So the following lines are identical:: +So the following lines are identical: - $sum = new JsExpression('[] + []', [$h1, $h2]); - $sum = new JsExpression('[0] + [1]', [$h1, $h2]); - $sum = new JsExpression('[a] + [b]', ['a' => $h1, 'b' => $h2]); +``` +$sum = new JsExpression('[] + []', [$h1, $h2]); +$sum = new JsExpression('[0] + [1]', [$h1, $h2]); +$sum = new JsExpression('[a] + [b]', ['a' => $h1, 'b' => $h2]); +``` .. important:: We have specifically selected a very simple tag format as a reminder not to write any code as part of JsExpression. You must not use JsExpression() for anything complex. - -Writing JavaScript code ------------------------ +### Writing JavaScript code If you know JavaScript you are likely to write more extensive methods to provide extended functionality for your user browsers. Agile UI does not attempt to stop you from doing that, @@ -367,14 +401,16 @@ Create a file `test.js` containing: Then load this JavaScript dependency on your page (see :php:meth:`App::includeJS()` and :php:meth:`App::includeCSS()`). Finally use UI code as a "glue" between your routine and the actual View objects. For example, to match the size of `$rightContainer` -with the size of `$leftContainer`:: +with the size of `$leftContainer`: - $heights = []; - foreach ($leftContainer->elements as $leftBox) { - $heights[] = $leftBox->js()->height(); - } +``` +$heights = []; +foreach ($leftContainer->elements as $leftBox) { + $heights[] = $leftBox->js()->height(); +} - $rightContainer->js(true)->height(new JsExpression('mySum([])', [$heights])); +$rightContainer->js(true)->height(new JsExpression('mySum([])', [$heights])); +``` This will map into the following JavaScript code: @@ -387,8 +423,7 @@ This will map into the following JavaScript code: You can further simplify JavaScript code yourself, but keep the JavaScript logic inside the `.js` files and leave PHP only for binding. -Modals -====== +## Modals There are two modal implementations in ATK: @@ -398,8 +433,7 @@ There are two modal implementations in ATK: In contrast to :php:class:`Modal`, the HTML `
` element generated by :php:class:`JsModal` is always destroyed when the modal is closed instead of only hiding it. -Modal ------ +### Modal .. php:class:: Modal @@ -412,48 +446,53 @@ Modal This class allows you to open modal dialogs and close them easily. It's based around Fomantic-UI `.modal(), `_ but integrates PHP callback for dynamically -producing content of your dialog:: +producing content of your dialog: - $modal = \Atk4\Ui\Modal::addTo($app, ['Modal Title']); - $modal->set(function (View $p) use ($modal) { - \Atk4\Ui\LoremIpsum::addTo($p); - \Atk4\Ui\Button::addTo($p, ['Hide']) - ->on('click', $modal->jsHide()); - }); +``` +$modal = \Atk4\Ui\Modal::addTo($app, ['Modal Title']); +$modal->set(function (View $p) use ($modal) { + \Atk4\Ui\LoremIpsum::addTo($p); + \Atk4\Ui\Button::addTo($p, ['Hide']) + ->on('click', $modal->jsHide()); +}); - \Atk4\Ui\Button::addTo($app, ['Show']) - ->on('click', $modal->jsShow()); +\Atk4\Ui\Button::addTo($app, ['Show']) + ->on('click', $modal->jsShow()); +``` -Modal will render as a HTML `
` block but will be hidden. Alternatively you can use Modal without loadable content:: +Modal will render as a HTML `
` block but will be hidden. Alternatively you can use Modal without loadable content: - $modal = \Atk4\Ui\Modal::addTo($app, ['Modal Title']); - \Atk4\Ui\LoremIpsum::addTo($modal); - \Atk4\Ui\Button::addTo($modal, ['Hide']) - ->on('click', $modal->jsHide()); +``` +$modal = \Atk4\Ui\Modal::addTo($app, ['Modal Title']); +\Atk4\Ui\LoremIpsum::addTo($modal); +\Atk4\Ui\Button::addTo($modal, ['Hide']) + ->on('click', $modal->jsHide()); - \Atk4\Ui\Button::addTo($app, ['Show']) - ->on('click', $modal->jsShow()); +\Atk4\Ui\Button::addTo($app, ['Show']) + ->on('click', $modal->jsShow()); +``` The second way is more convenient for creating static content, such as Terms of Service. You can customize the CSS classes of both header and content section of the modal using the properties `headerCss` or `contentCss` or use the method `addContentCss()`. See the Fomantic-UI modal documentation for further information. -JsModal -------- +### JsModal .. php:class:: JsModal This alternative implementation to :php:class:`Modal` is convenient for situations when the need to open a dialog box is not known in advance. This class is not a component, but rather an Action so you **must not** add it to the Render Tree. -To accomplish that, use a :ref:`virtualpage`:: +To accomplish that, use a :ref:`virtualpage`: - $vp = \Atk4\Ui\VirtualPage::addTo($app); - \Atk4\Ui\LoremIpsum::addTo($vp, ['size' => 2]); +``` +$vp = \Atk4\Ui\VirtualPage::addTo($app); +\Atk4\Ui\LoremIpsum::addTo($vp, ['size' => 2]); - \Atk4\Ui\Button::addTo($app, ['Dynamic Modal']) - ->on('click', new \Atk4\Ui\Js\JsModal('My Popup Title', $vp->getUrl('cut'))); +\Atk4\Ui\Button::addTo($app, ['Dynamic Modal']) + ->on('click', new \Atk4\Ui\Js\JsModal('My Popup Title', $vp->getUrl('cut'))); +``` Note that this element is always destroyed as opposed to :php:class:`Modal`, where it is only hidden. @@ -462,38 +501,39 @@ where it is only hidden. See `Modals and reloading`_ concerning the intricacies between jsMmodals and callbacks. - -Reloading -========= +## Reloading .. php:class:: JsReload -JsReload is a JavaScript action that performs reload of a certain object:: +JsReload is a JavaScript action that performs reload of a certain object: - $jsReload = new JsReload($table); +``` +$jsReload = new JsReload($table); +``` This action can be used similarly to any other JsExpression. For instance submitting a form can reload some -other view:: +other view: - $bookModel = new Book($db); +``` +$bookModel = new Book($db); - $form = \Atk4\Ui\Form::addTo($app); - $table = \Atk4\Ui\Table::addTo($app); +$form = \Atk4\Ui\Form::addTo($app); +$table = \Atk4\Ui\Table::addTo($app); - $form->setModel($bookModel); +$form->setModel($bookModel); - $form->onSubmit(function (Form $form) use ($table) { - $form->model->save(); +$form->onSubmit(function (Form $form) use ($table) { + $form->model->save(); - return new \Atk4\Ui\Js\JsReload($table); - }); + return new \Atk4\Ui\Js\JsReload($table); +}); - $t->setModel($bookModel); +$t->setModel($bookModel); +``` In this example, filling out and submitting the form will result in table contents being refreshed using AJAX. -Modals and reloading --------------------- +### Modals and reloading Care needs to be taken when attempting to combine the above with a `JsModal`_ which requires a :ref:`virtualpage` to store its contents. In that case, the order in which declarations are made matters because of the way the @@ -508,47 +548,73 @@ needed: * a button that opens the modal in order to add data, and * the form's callback on submit. -The following will **not** work:: +The following will **not** work: - $app = new MyApp(); - $model = new MyModel(); +``` +$app = new MyApp(); +$model = new MyModel(); - // JsModal requires its contents to be put into a Virtual Page - $vp = \Atk4\Ui\VirtualPage::addTo($app); - $form = \Atk4\Ui\Form::addTo($vp); - $form->setModel($model); +// JsModal requires its contents to be put into a Virtual Page +$vp = \Atk4\Ui\VirtualPage::addTo($app); +$form = \Atk4\Ui\Form::addTo($vp); +$form->setModel($model); - $table = \Atk4\Ui\Table::addTo($app); - $table->setModel($model)); +$table = \Atk4\Ui\Table::addTo($app); +$table->setModel($model)); - $button = \Atk4\Ui\Button::addTo($app, ['Add Item', 'icon' => 'plus']); - $button->on('click', new \Atk4\Ui\Js\JsModal('JSModal Title', $vp)); +$button = \Atk4\Ui\Button::addTo($app, ['Add Item', 'icon' => 'plus']); +$button->on('click', new \Atk4\Ui\Js\JsModal('JSModal Title', $vp)); - $form->onSubmit(function (Form $form) use ($table) { - $form->model->save(); +$form->onSubmit(function (Form $form) use ($table) { + $form->model->save(); - return new \Atk4\Ui\Js\JsBlock([ - $table->jsReload(), - $form->jsSuccess('ok'), - ]); - }); + return new \Atk4\Ui\Js\JsBlock([ + $table->jsReload(), + $form->jsSuccess('ok'), + ]); +}); +``` -Table needs to be first! The following works:: +Table needs to be first! The following works: - $app = new MyApp(); - $model = new MyModel(); +``` +$app = new MyApp(); +$model = new MyModel(); - // This needs to be first - $table = \Atk4\Ui\Table::addTo($app); - $table->setModel($model)); +// This needs to be first +$table = \Atk4\Ui\Table::addTo($app); +$table->setModel($model)); - $vp = \Atk4\Ui\VirtualPage::addTo($app); - $form = \Atk4\Ui\Form::addTo($vp); - $form->setModel($model); +$vp = \Atk4\Ui\VirtualPage::addTo($app); +$form = \Atk4\Ui\Form::addTo($vp); +$form->setModel($model); + +$button = \Atk4\Ui\Button::addTo($app, ['Add Item', 'icon' => 'plus']); +$button->on('click', new \Atk4\Ui\Js\JsModal('JSModal Title', $vp)); + +$form->onSubmit(function (Form $form) use ($table) { + $form->model->save(); + + return new \Atk4\Ui\Js\JsBlock([ + $table->jsReload(), + $form->jsSuccess('ok'), + ]); +}); +``` + +The first will not work because of how the render tree is called and because VirtualPage is special. +While rendering, if a reload is caught, the rendering process stops and only renders what was asked to be reloaded. +Since VirtualPage is special, when asked to be rendered and it gets triggered, rendering stops and only the +VirtualPage content is rendered. To force yourself to put things in order you can write the above like this: - $button = \Atk4\Ui\Button::addTo($app, ['Add Item', 'icon' => 'plus']); - $button->on('click', new \Atk4\Ui\Js\JsModal('JSModal Title', $vp)); +``` +$table = \Atk4\Ui\Table::addTo($app); +$table->setModel($model); +$vp = \Atk4\Ui\VirtualPage::addTo($app); +$vp->set(function (\Atk4\Ui\VirtualPage $p) use ($table, $model) { + $form = \Atk4\Ui\Form::addTo($p); + $form->setModel($model); $form->onSubmit(function (Form $form) use ($table) { $form->model->save(); @@ -557,38 +623,16 @@ Table needs to be first! The following works:: $form->jsSuccess('ok'), ]); }); +}); -The first will not work because of how the render tree is called and because VirtualPage is special. -While rendering, if a reload is caught, the rendering process stops and only renders what was asked to be reloaded. -Since VirtualPage is special, when asked to be rendered and it gets triggered, rendering stops and only the -VirtualPage content is rendered. To force yourself to put things in order you can write the above like this:: - - $table = \Atk4\Ui\Table::addTo($app); - $table->setModel($model); - - $vp = \Atk4\Ui\VirtualPage::addTo($app); - $vp->set(function (\Atk4\Ui\VirtualPage $p) use ($table, $model) { - $form = \Atk4\Ui\Form::addTo($p); - $form->setModel($model); - $form->onSubmit(function (Form $form) use ($table) { - $form->model->save(); - - return new \Atk4\Ui\Js\JsBlock([ - $table->jsReload(), - $form->jsSuccess('ok'), - ]); - }); - }); - - $button = \Atk4\Ui\Button::addTo($app, ['Add Item', 'icon' => 'plus']); - $button->on('click', new \Atk4\Ui\Js\JsModal('JSModal Title', $vp)); +$button = \Atk4\Ui\Button::addTo($app, ['Add Item', 'icon' => 'plus']); +$button->on('click', new \Atk4\Ui\Js\JsModal('JSModal Title', $vp)); +``` Note that in no case you will be able to render the button *above* the table (because the button needs a reference to `$vp` which references `$table` for reload), so `$button` must be last. - -Background Tasks -================ +## Background Tasks Agile UI has addressed one of the big shortcomings of the PHP language: the ability to execute running / background processes. It is best illustrated with an example: @@ -596,50 +640,53 @@ processes. It is best illustrated with an example: Processing a large image, resize, find face, watermark, create thumbnails and store externally can take an average of 5-10 seconds, so you'd like to user updated about the process. There are various ways to do so. -The most basic approach is:: +The most basic approach is: - $button = \Atk4\Ui\Button::addTo($app, ['Process Image']); - $button->on('click', function () use ($button, $image) { - sleep(1); // $image->resize(); - sleep(1); // $image->findFace(); - sleep(1); // $image->watermark(); - sleep(1); // $image->createThumbnails(); +``` +$button = \Atk4\Ui\Button::addTo($app, ['Process Image']); +$button->on('click', function () use ($button, $image) { + sleep(1); // $image->resize(); + sleep(1); // $image->findFace(); + sleep(1); // $image->watermark(); + sleep(1); // $image->createThumbnails(); - return $button->js()->text('Success')->addClass('disabled'); - }); + return $button->js()->text('Success')->addClass('disabled'); +}); +``` However, it would be nice if the user was aware of the progress of your process, which is when `Server Sent Event (JsSse)`_ comes into play. .. _sse: -Server Sent Event (JsSse) -------------------------- +### Server Sent Event (JsSse) .. php:class:: JsSse .. php:method:: send(action) -This class implements ability for your PHP code to send messages to the browser during process execution:: +This class implements ability for your PHP code to send messages to the browser during process execution: - $button = \Atk4\Ui\Button::addTo($app, ['Process Image']); +``` +$button = \Atk4\Ui\Button::addTo($app, ['Process Image']); - $sse = \Atk4\Ui\JsSse::addTo($button); +$sse = \Atk4\Ui\JsSse::addTo($button); - $button->on('click', $sse->set(function () use ($sse, $button, $image) { - $sse->send($button->js()->text('Processing')); - sleep(1); // $image->resize(); +$button->on('click', $sse->set(function () use ($sse, $button, $image) { + $sse->send($button->js()->text('Processing')); + sleep(1); // $image->resize(); - $sse->send($button->js()->text('Looking for face')); - sleep(1); // $image->findFace(); + $sse->send($button->js()->text('Looking for face')); + sleep(1); // $image->findFace(); - $sse->send($button->js()->text('Adding watermark')); - sleep(1); // $image->watermark(); + $sse->send($button->js()->text('Adding watermark')); + sleep(1); // $image->watermark(); - $sse->send($button->js()->text('Creating thumbnail')); - sleep(1); // $image->createThumbnails(); + $sse->send($button->js()->text('Creating thumbnail')); + sleep(1); // $image->createThumbnails(); - return $button->js()->text('Success')->addClass('disabled'); - })); + return $button->js()->text('Success')->addClass('disabled'); +})); +``` The JsSse component plays a crucial role in some high-level components such as :php:class:`Console` and :php:class:`ProgressBar`. diff --git a/docs/label.md b/docs/label.md index 7a80eb3d4e..9a6390ce57 100644 --- a/docs/label.md +++ b/docs/label.md @@ -1,10 +1,6 @@ - - .. _label: -===== -Label -===== +# Label .. php:namespace:: Atk4\Ui @@ -17,18 +13,19 @@ To see what possible classes you can use on the Label, see: https://fomantic-ui. Demo: https://ui.agiletoolkit.org/demos/label.php -Basic Usage -=========== +## Basic Usage First argument of constructor or first element in array passed to constructor will be the text that will -appear on the label:: +appear on the label: - $label = Label::addTo($app, ['hello world']); +``` +$label = Label::addTo($app, ['hello world']); - // or +// or - $label = new \Atk4\Ui\Label('hello world'); - $app->add($label); +$label = new \Atk4\Ui\Label('hello world'); +$app->add($label); +``` Label has the following properties: @@ -45,8 +42,7 @@ Label has the following properties: All the above can be string, array (passed to Icon, Image or View class) or an object. -Icons -===== +## Icons There are two properties (icon, iconRight) but you can set only one at a time:: @@ -59,76 +55,81 @@ You can also specify icon as an object:: For more information, see: :php:class:`Icon` -Image -===== +## Image -Image cannot be specified at the same time with the icon, but you can use PNG/GIF/JPG image on your label:: +Image cannot be specified at the same time with the icon, but you can use PNG/GIF/JPG image on your label: - $img = $app->cdn['atk'] . '/logo.png'; - Label::addTo($app, ['Coded in PHP', 'image' => $img]); +``` +$img = $app->cdn['atk'] . '/logo.png'; +Label::addTo($app, ['Coded in PHP', 'image' => $img]); +``` -Detail -====== +## Detail -You can specify "detail" component to your label:: +You can specify "detail" component to your label: - Label::addTo($app, ['Number of lines', 'detail' => '33']); +``` +Label::addTo($app, ['Number of lines', 'detail' => '33']); +``` -Groups -====== +## Groups Label can be part of the group, but you would need to either use custom HTML template or -composition:: +composition: - $group = View::addTo($app, ['class.blue tag' => true, 'ui' => 'labels']); - Label::addTo($group, ['$9.99']); - Label::addTo($group, ['$19.99', 'class.red tag' => true]); - Label::addTo($group, ['$24.99']); +``` +$group = View::addTo($app, ['class.blue tag' => true, 'ui' => 'labels']); +Label::addTo($group, ['$9.99']); +Label::addTo($group, ['$19.99', 'class.red tag' => true]); +Label::addTo($group, ['$24.99']); +``` -Combining classes -================= +## Combining classes -Based on Fomantic-UI documentation, you can add more classes to your labels:: +Based on Fomantic-UI documentation, you can add more classes to your labels: - $columns = Columns::addTo($app); +``` +$columns = Columns::addTo($app); - $c = $columns->addColumn(); - $col = View::addTo($c, ['ui' => 'raised segment']); +$c = $columns->addColumn(); +$col = View::addTo($c, ['ui' => 'raised segment']); - // attach label to the top of left column - Label::addTo($col, ['Left Column', 'class.top attached' => true, 'icon' => 'book']); +// attach label to the top of left column +Label::addTo($col, ['Left Column', 'class.top attached' => true, 'icon' => 'book']); - // ribbon around left column - Label::addTo($col, ['Lorem', 'class.red ribbon' => true, 'icon' => 'cut']); +// ribbon around left column +Label::addTo($col, ['Lorem', 'class.red ribbon' => true, 'icon' => 'cut']); - // add some content inside column - LoremIpsum::addTo($col, ['size' => 1]); +// add some content inside column +LoremIpsum::addTo($col, ['size' => 1]); - $c = $columns->addColumn(); - $col = View::addTo($c, ['ui' => 'raised segment']); +$c = $columns->addColumn(); +$col = View::addTo($c, ['ui' => 'raised segment']); - // attach label to the top of right column - Label::addTo($col, ['Right Column', 'class.top attached' => true, 'icon' => 'book']); +// attach label to the top of right column +Label::addTo($col, ['Right Column', 'class.top attached' => true, 'icon' => 'book']); - // some content - LoremIpsum::addTo($col, ['size' => 1]); +// some content +LoremIpsum::addTo($col, ['size' => 1]); - // right bottom corner label - Label::addTo($col, ['Ipsum', 'class.orange bottom right attached' => true, 'icon' => 'cut']); +// right bottom corner label +Label::addTo($col, ['Ipsum', 'class.orange bottom right attached' => true, 'icon' => 'cut']); +``` -Added labels into Table -======================= +## Added labels into Table You can even use label inside a table, but because table renders itself by repeating periodically, then -the following code is needed:: - - $table->onHook(\Atk4\Ui\Table\Column::HOOK_GET_HTML_TAGS, function (Table $table, Model $row) { - if ($row->getId() == 1) { - return [ - 'name' => $table->getApp()->getTag('div', ['class' => 'ui ribbon label'], $row->get('name')), - ]; - } - }); +the following code is needed: + +``` +$table->onHook(\Atk4\Ui\Table\Column::HOOK_GET_HTML_TAGS, function (Table $table, Model $row) { + if ($row->getId() == 1) { + return [ + 'name' => $table->getApp()->getTag('div', ['class' => 'ui ribbon label'], $row->get('name')), + ]; + } +}); +``` Now while $table will be rendered, if it finds a record with id=1, it will replace $name value with a HTML tag. You need to make sure that 'name' column appears first on the left. diff --git a/docs/lister.md b/docs/lister.md index adc8970241..1d351bd861 100644 --- a/docs/lister.md +++ b/docs/lister.md @@ -1,9 +1,6 @@ - .. _Lister: -====== -Lister -====== +# Lister .. php:namespace:: Atk4\Ui @@ -13,46 +10,53 @@ Lister can be used to output unstructured data with your own HTML template. If y data in a table, see :php:class:`Table`. Lister is also the fastest way to render large amount of output and will probably give you most flexibility. -Basic Usage -=========== +## Basic Usage The most common use is when you need to implement a certain HTML and if that HTML contains list of -items. If your HTML looks like this:: +items. If your HTML looks like this: -
Top 20 countries (alphabetically)
-
Andorra
-
Camerroon
-
Canada
-
+``` +
Top 20 countries (alphabetically)
+
Andorra
+
Camerroon
+
Canada
+
+``` -you should put that into file `myview.html` then use it with a view:: +you should put that into file `myview.html` then use it with a view: - $view = View::addTo($app, ['template' => 'myview.html']); +``` +$view = View::addTo($app, ['template' => 'myview.html']); +``` Now your application should contain list of 3 sample countries as you have specified in HTML, but next -we need to add some tags into your template:: - -
Top {limit}20{/limit} countries (alphabetically)
- {Countries} - {rows} - {row} -
Andorra
- {/row} -
Camerroon
-
Canada
- {/rows} - {/Countries} -
+we need to add some tags into your template: + +``` +
Top {limit}20{/limit} countries (alphabetically)
+ {Countries} + {rows} + {row} +
Andorra
+ {/row} +
Camerroon
+
Canada
+ {/rows} + {/Countries} +
+``` Here the `{Countries}` region will be replaced with the lister, but the contents of this region will be re-used as the list template. Refresh your page and your output should not be affected at all, because View clears out all extra template tags. -Next I'll add Lister:: +Next I'll add Lister: - Lister::addTo($view, [], ['Countries']) - ->setModel(new Country($db)) - ->setLimit(20); +``` +Lister::addTo($view, [], ['Countries']) + ->setModel(new Country($db)) + ->setLimit(20); +``` While most other objects in Agile UI come with their own templates, lister will prefer to use template inside your region. It will look for "row" and "rows" tag: @@ -63,55 +67,62 @@ to use template inside your region. It will look for "row" and "rows" tag: 4. Render {row} and append into {rows} If you refresh your page now, you should see "Andorra" duplicated 20 times. This is because -the {row} did not contain any field tags. Lets set them up:: +the {row} did not contain any field tags. Lets set them up: - {row} -
{name}Andorra{/name}
- {/row} +``` +{row} +
{name}Andorra{/name}
+{/row} +``` Refresh your page and you should see list of countries as expected. The flags are not showing yet, -but I'll deal with in next section. For now, lets clean up the template by removing unnecessary tag content:: +but I'll deal with in next section. For now, lets clean up the template by removing unnecessary tag content: -
Top {limit}20{/limit} countries (alphabetically)
- {Countries} - {rows} - {row} -
{$name}
- {/row} - {/rows} - {/Countries} -
+``` +
Top {limit}20{/limit} countries (alphabetically)
+ {Countries} + {rows} + {row} +
{$name}
+ {/row} + {/rows} + {/Countries} +
+``` -Finally, Lister permits you not to use {rows} and {row} tags if entire region can be considered as a row:: +Finally, Lister permits you not to use {rows} and {row} tags if entire region can be considered as a row: -
Top {limit}20{/limit} countries (alphabetically)
- {Countries} -
{$name}
- {/Countries} - +``` +
Top {limit}20{/limit} countries (alphabetically)
+ {Countries} +
{$name}
+ {/Countries} + +``` -Tweaking the output -=================== +## Tweaking the output Output is formatted using the standard :ref:`uiPersistence` routine, but you can also fine-tune the content -of your tags like this:: +of your tags like this: - $lister->onHook(\Atk4\Ui\Lister::HOOK_BEFORE_ROW, function (\Atk4\Ui\Lister $lister) { - $lister->currentRow->set('iso', mb_strtolower($lister->currentRow->get('iso'))); - }); +``` +$lister->onHook(\Atk4\Ui\Lister::HOOK_BEFORE_ROW, function (\Atk4\Ui\Lister $lister) { + $lister->currentRow->set('iso', mb_strtolower($lister->currentRow->get('iso'))); +}); +``` -Model vs Static Source -====================== +## Model vs Static Source -Since Lister is non-interactive, you can also set a static source for your lister to avoid hassle:: +Since Lister is non-interactive, you can also set a static source for your lister to avoid hassle: - $lister->setSource([ - ['flag' => 'ca', 'name' => 'Canada'], - ['flag' => 'uk', 'name' => 'UK'], - ]); +``` +$lister->setSource([ + ['flag' => 'ca', 'name' => 'Canada'], + ['flag' => 'uk', 'name' => 'UK'], +]); +``` -Special template tags -===================== +## Special template tags Your {row} template may contain few special tags: @@ -119,26 +130,27 @@ Your {row} template may contain few special tags: - {$_title} - will be set to the title of your record (see $model->$titleField) - {$_href} - will point to current page but with ?id=123 extra GET argument. - -Load page content dynamically when scrolling -============================================ +## Load page content dynamically when scrolling You can make lister load page content dynamically when user is scrolling down page. - $lister->addJsPaginator(20, $options = [], $container = null, $scrollRegion = null); +``` +$lister->addJsPaginator(20, $options = [], $container = null, $scrollRegion = null); +``` The first parameter is the number of item you wish to load per page. The second parameter is options you want to pass to respective JS widget. The third parameter is the $container view holding the lister and where scrolling is applicable. And last parameter is CSS selector of element in which you want to do scrolling. -Using without Template -====================== +## Using without Template Agile UI comes with a one sample template for your lister, although it's not set by default, -you can specify it explicitly:: +you can specify it explicitly: - Lister::addTo($app, ['defaultTemplate' => 'lister.html']); +``` +Lister::addTo($app, ['defaultTemplate' => 'lister.html']); +``` This should display a list nicely formatted by Fomantic-UI, with header, links, icons and description area. diff --git a/docs/loremipsum.md b/docs/loremipsum.md index a4d0e592f5..6df5330bef 100644 --- a/docs/loremipsum.md +++ b/docs/loremipsum.md @@ -1,9 +1,6 @@ - .. _text: -========== -LoremIpsum -========== +# LoremIpsum .. php:namespace:: Atk4\Ui @@ -12,27 +9,31 @@ LoremIpsum This class implements a standard filler-text which is so popular amongst web developers and designers. LoremIpsum will generate a dynamic filler text which should help you test :ref:`reloading` or layouts. -Basic Usage -=========== +## Basic Usage - LoremIpsum::addTo($app); +``` +LoremIpsum::addTo($app); +``` -Resizing -======== +## Resizing -You can define the length of the LoremIpsum text:: +You can define the length of the LoremIpsum text: - $text = Text::addTo($app) - ->addParagraph('First Paragraph') - ->addParagraph('Second Paragraph'); +``` +$text = Text::addTo($app) + ->addParagraph('First Paragraph') + ->addParagraph('Second Paragraph'); +``` -You may specify amount of text to be generated with lorem:: +You may specify amount of text to be generated with lorem: - LoremIpsum::addTo($app, [1]); // just add a little one +``` +LoremIpsum::addTo($app, [1]); // just add a little one - // or +// or - LoremIpsum::addTo($app, [5]); // adds a lot of text +LoremIpsum::addTo($app, [5]); // adds a lot of text +``` diff --git a/docs/menu.md b/docs/menu.md index f536d11e31..883fc6575c 100644 --- a/docs/menu.md +++ b/docs/menu.md @@ -1,48 +1,48 @@ - .. _menu: -==== -Menu -==== +# Menu .. php:namespace:: Atk4\Ui .. php:class:: Menu Menu implements horizontal or vertical multi-level menu by using Fomantic-UI 'menu'. -Using Menu -========== +## Using Menu .. php:method: addItem($label, $action) -Here is a simple usage:: - - $menu = Menu::addTo($app); - $menu->addItem('foo'); - $menu->addItem('bar'); +Here is a simple usage: -to make menu vertical:: +``` +$menu = Menu::addTo($app); +$menu->addItem('foo'); +$menu->addItem('bar'); +``` - $menu->addClass('vertical'); +to make menu vertical: +``` +$menu->addClass('vertical'); +``` -Decorating Menu Items -===================== +## Decorating Menu Items -See :php:class:`MenuItem` for more options:: +See :php:class:`MenuItem` for more options: - $menu->addItem(['foo', 'icon' => 'book']); +``` +$menu->addItem(['foo', 'icon' => 'book']); +``` -Specifying Links and Actions -============================ +## Specifying Links and Actions -Menu items can use links and actions:: +Menu items can use links and actions: - $menu->addItem('foo', 'test.php'); - $menu->addItem('bar', new JsModal('Test')); +``` +$menu->addItem('foo', 'test.php'); +$menu->addItem('bar', new JsModal('Test')); +``` -Creating sub-menus -================== +## Creating sub-menus .. php:method: addMenu($label) .. php:method: addGroup($label) @@ -51,26 +51,25 @@ Creating sub-menus You can create sub-menu for either vertical or horizontal menu. For a vertical menu you can also use groups. For horizontal menu, you can use addRightMenu. -:: +: - $menu = Menu::addTo($app); - $menu->addItem('foo'); - $sub = $menu->addMenu('Some Bars'); - $sub->addItem('bar 1'); - $sub->addItem('bar 2'); +``` +$menu = Menu::addTo($app); +$menu->addItem('foo'); +$sub = $menu->addMenu('Some Bars'); +$sub->addItem('bar 1'); +$sub->addItem('bar 2'); +``` -Headers -======= +## Headers .. php:method: addHeader($label) -Advanced Use -============ +## Advanced Use You can add other elements inside menu. Refer to demos/menu.php. -MenuItem -==== +## MenuItem .. php:class:: MenuItem diff --git a/docs/message.md b/docs/message.md index db97707b6a..ee4d2f370f 100644 --- a/docs/message.md +++ b/docs/message.md @@ -1,9 +1,6 @@ - .. _message: -======= -Message -======= +# Message .. php:namespace:: Atk4\Ui @@ -13,48 +10,54 @@ Outputs a rectangular segment with a distinctive color to convey message to the Demo: https://ui.agiletoolkit.org/demos/message.php -Basic Usage -=========== +## Basic Usage -Implements basic image:: +Implements basic image: - $message = new \Atk4\Ui\Message('Message Title'); - $app->add($message); +``` +$message = new \Atk4\Ui\Message('Message Title'); +$app->add($message); +``` -Although typically you would want to specify what type of message is that:: +Although typically you would want to specify what type of message is that: - $message = new \Atk4\Ui\Message(['Warning Message Title', 'type' => 'warning']); - $app->add($message); +``` +$message = new \Atk4\Ui\Message(['Warning Message Title', 'type' => 'warning']); +$app->add($message); +``` -Here is the alternative syntax:: +Here is the alternative syntax: - $message = Message::addTo($app, ['Warning Message Title', 'type' => 'warning']); +``` +$message = Message::addTo($app, ['Warning Message Title', 'type' => 'warning']); +``` -Adding message text -=================== +## Adding message text .. php:attr:: text Property $text is automatically initialized to :php:class:`Text` so you can call :php:meth:`Text::addParagraph` -to add more text inside your message:: - - $message = Message::addTo($app, ['Message Title']); - $message->addClass('warning'); - $message->text->addParagraph('First para'); - $message->text->addParagraph('Second para'); +to add more text inside your message: +``` +$message = Message::addTo($app, ['Message Title']); +$message->addClass('warning'); +$message->text->addParagraph('First para'); +$message->text->addParagraph('Second para'); +``` -Message Icon -============ +## Message Icon .. php:attr:: icon -You can specify icon also:: +You can specify icon also: - $message = Message::addTo($app, [ - 'Battery low', - 'class.red' => true, - 'icon' => 'battery low', - ])->text->addParagraph('Your battery is getting low. Re-charge your Web App'); +``` +$message = Message::addTo($app, [ + 'Battery low', + 'class.red' => true, + 'icon' => 'battery low', +])->text->addParagraph('Your battery is getting low. Re-charge your Web App'); +``` diff --git a/docs/misc.md b/docs/misc.md index 7dc8e54a35..bfe8ef7b54 100644 --- a/docs/misc.md +++ b/docs/misc.md @@ -1,10 +1,6 @@ - - .. php:namespace:: Atk4\Ui - -Columns -======= +## Columns This class implements CSS Grid or ability to divide your elements into columns. If you are an expert designer with knowledge of HTML/CSS we recommend you to create your own layouts and templates, but @@ -14,48 +10,54 @@ basic content arrangements. .. php:method:: addColumn() When you add new component to the page it will typically consume 100% width of its container. Columns -will break down width into chunks that can be used by other elements:: +will break down width into chunks that can be used by other elements: - $c = Columns::addTo($page); - LoremIpsum::addTo($c->addColumn(), [1]); - LoremIpsum::addTo($c->addColumn(), [1]); +``` +$c = Columns::addTo($page); +LoremIpsum::addTo($c->addColumn(), [1]); +LoremIpsum::addTo($c->addColumn(), [1]); +``` -By default width is equally divided by columns. You may specify a custom width expressed as fraction of 16:: +By default width is equally divided by columns. You may specify a custom width expressed as fraction of 16: - $c = Columns::addTo($page); - LoremIpsum::addTo($c->addColumn(6), [1]); - LoremIpsum::addTo($c->addColumn(10), [2]); // wider column, more filler +``` +$c = Columns::addTo($page); +LoremIpsum::addTo($c->addColumn(6), [1]); +LoremIpsum::addTo($c->addColumn(10), [2]); // wider column, more filler +``` You can specify how many columns are expected in a grid, but if you do you can't specify widths of individual -columns. This seem like a limitation of Fomantic-UI:: +columns. This seem like a limitation of Fomantic-UI: - $c = Columns::addTo($page, ['width' => 4]); - Box::addTo($c->addColumn(), ['red']); - Box::addTo($c->addColumn(['class.right floated' => true]), ['blue']); +``` +$c = Columns::addTo($page, ['width' => 4]); +Box::addTo($c->addColumn(), ['red']); +Box::addTo($c->addColumn(['class.right floated' => true]), ['blue']); +``` -Rows ----- +### Rows When you add columns for a total width which is more than permitted, columns will stack below and form a second -row. To improve and control the flow of rows better, you can specify addRow():: +row. To improve and control the flow of rows better, you can specify addRow(): - $c = Columns::addTo($page, ['class.internally celled' => true]); +``` +$c = Columns::addTo($page, ['class.internally celled' => true]); - $r = $c->addRow(); - Icon::addTo($r->addColumn([2, 'class.right aligned' => true]), ['huge home']); - LoremIpsum::addTo($r->addColumn(12), [1]); - Icon::addTo($r->addColumn(2), ['huge trash']); +$r = $c->addRow(); +Icon::addTo($r->addColumn([2, 'class.right aligned' => true]), ['huge home']); +LoremIpsum::addTo($r->addColumn(12), [1]); +Icon::addTo($r->addColumn(2), ['huge trash']); - $r = $c->addRow(); - Icon::addTo($r->addColumn([2, 'class.right aligned' => true]), ['huge home']); - LoremIpsum::addTo($r->addColumn(12), [1]); - Icon::addTo($r->addColumn(2), ['huge trash']); +$r = $c->addRow(); +Icon::addTo($r->addColumn([2, 'class.right aligned' => true]), ['huge home']); +LoremIpsum::addTo($r->addColumn(12), [1]); +Icon::addTo($r->addColumn(2), ['huge trash']); +``` This example also uses custom class for Columns ('internally celled') that adds dividers between columns and rows. For more information on available classes, see https://fomantic-ui.com/collections/grid.html. -Responsiveness and Performance ------------------------------- +### Responsiveness and Performance Although you can use responsiveness with the Column class to some degree, we recommend that you create your own component template where you can have greater control over all classes. diff --git a/docs/multiline.md b/docs/multiline.md index e21806599f..579476e4a0 100644 --- a/docs/multiline.md +++ b/docs/multiline.md @@ -1,11 +1,8 @@ - .. php:namespace:: Atk4\Ui\Form\Control .. php:class:: Multiline -====================== -Multiline Form Control -====================== +# Multiline Form Control The Multiline form control is not simply a single control, but will add multiple control in order to be able to edit multiple Model fields related to a single record reference into another Model. @@ -14,43 +11,45 @@ A good example is a user who can have many addresses. In this example, the Model `User` containsMany `Addresses`. Since the Model field addresses is defined with containsMany() inside the main model, Multiline will store addresses content as JSON value inside the table blobl addresses field. -For example:: +For example: - /** - * User model - */ - class User extends \Atk4\Data\Model - { - public $table = 'user'; +``` +/** + * User model + */ +class User extends \Atk4\Data\Model +{ + public $table = 'user'; - protected function init(): void - { - parent:: init(); + protected function init(): void + { + parent:: init(); - $this->addField('firstname', ['type' => 'string']); - $this->addField('lastname', ['type' => 'string']); + $this->addField('firstname', ['type' => 'string']); + $this->addField('lastname', ['type' => 'string']); - $this->containsMany('addresses', [Address::class, 'system' => false]); - } + $this->containsMany('addresses', [Address::class, 'system' => false]); } - - /** - * Address Model - */ - class Address extends \Atk4\Data\Model +} + +/** + * Address Model + */ +class Address extends \Atk4\Data\Model +{ + protected function init(): void { - protected function init(): void - { - parent::init(); - - $this->addField('street_and_number', ['type' => 'string']); - $this->addField('zip', ['type' => 'string']); - $this->addField('city', ['type' => 'string']); - $this->addField('country', ['type' => 'string']); - } + parent::init(); + + $this->addField('street_and_number', ['type' => 'string']); + $this->addField('zip', ['type' => 'string']); + $this->addField('city', ['type' => 'string']); + $this->addField('country', ['type' => 'string']); } +} - \Atk4\Ui\Crud::addTo($app)->setModel(new User($app->db)); +\Atk4\Ui\Crud::addTo($app)->setModel(new User($app->db)); +``` This leads to a Multiline component automatically rendered for adding, editing and deleting Addresses of the current user record: @@ -58,77 +57,79 @@ This leads to a Multiline component automatically rendered for adding, editing a You can also check LINK_TO_DEMO/multiline.php for this example -Using Multiline with HasMany relation -======================================= +## Using Multiline with HasMany relation Multiline form control is used by default when a Model field used `containsMany()` or `containsOne()`, but you can set up the multiline component to be used with hasMany() relation and edit related record accordingly. -Lets say a User can have many email addresses, but you want to store them in a separate table.:: +Lets say a User can have many email addresses, but you want to store them in a separate table.: - /** - * Email Model - */ - class Email extends \Atk4\Data\Model - { - public $table = 'email'; +``` +/** + * Email Model + */ +class Email extends \Atk4\Data\Model +{ + public $table = 'email'; - protected function init(): void - { - parent::init(); + protected function init(): void + { + parent::init(); - $this->addField('email_address', ['type' => 'string']); + $this->addField('email_address', ['type' => 'string']); - $this->hasOne('user_id', [User::class]); - } + $this->hasOne('user_id', [User::class]); } +} - /** - * User model - */ - class User extends \Atk4\Data\Model - { - public $table = 'user'; +/** + * User model + */ +class User extends \Atk4\Data\Model +{ + public $table = 'user'; - protected function init(): void - { - parent:: init(); + protected function init(): void + { + parent:: init(); - $this->addField('firstname', ['type' => 'string']); - $this->addField('lastname', ['type' => 'string']); + $this->addField('firstname', ['type' => 'string']); + $this->addField('lastname', ['type' => 'string']); - $this->hasMany('Emails', [Email::class]); - } + $this->hasMany('Emails', [Email::class]); } +} +``` Using a form with User model won't automatically add a Multiline to edit the related email addresses. .. php:method:: setReferenceModel(string $refModelName, Model $entity = null, array $fieldNames = []): Model -If you want to edit them along with the user, Multiline need to be set up accordingly using the setReferenceModel method:: +If you want to edit them along with the user, Multiline need to be set up accordingly using the setReferenceModel method: - // Add a form to Ui in order to edit User record. - $userForm = \Atk4\Ui\Form::addTo($app); - $userForm->setModel($user->load($userId)); +``` +// Add a form to Ui in order to edit User record. +$userForm = \Atk4\Ui\Form::addTo($app); +$userForm->setModel($user->load($userId)); - $ml = $userForm->addControl('emails', [\Atk4\Ui\Form\Control\Multiline::class]); - $ml->setReferenceModel('Emails'); +$ml = $userForm->addControl('emails', [\Atk4\Ui\Form\Control\Multiline::class]); +$ml->setReferenceModel('Emails'); - // set up saving of Email on Form submit - $userForm->onSubmit(function (Form $form) use ($ml) { - $form->model->save(); - // save emails record related to current user. - $ml->saveRows(); +// set up saving of Email on Form submit +$userForm->onSubmit(function (Form $form) use ($ml) { + $form->model->save(); + // save emails record related to current user. + $ml->saveRows(); - return new JsToast(var_export($ml->model->export(), true)); - }); + return new JsToast(var_export($ml->model->export(), true)); +}); +``` Using the example above will create a form with control from the User model as well as a Multiline control for editing the Email model's field. -Important ---------- +### Important It is important to note that for Email's record to be properly saved, in relation to their User record, the User model needs to be loaded prior to call Multiline::setReferenceModel() method. @@ -136,54 +137,56 @@ needs to be loaded prior to call Multiline::setReferenceModel() method. Also note that Multiline::saveRows() method need to be called for related record to be saved in related table. You would normally call this method in your form onSubmit handler method. -Multiline and Expressions -========================= +## Multiline and Expressions + If a Model contains Expressions, there resulting values will automatically get updated when one of the form control value is changed. A loading icon on the ``+`` button will indicates that the expression values are being update. -Lets use the example of demos/multiline.php:: +Lets use the example of demos/multiline.php: - class InventoryItem extends \Atk4\Data\Model +``` +class InventoryItem extends \Atk4\Data\Model +{ + protected function init(): void { - protected function init(): void - { - parent::init(); - - $this->addField('item', ['required' => true, 'default' => 'item']); - $this->addField('qty', ['type' => 'integer', 'caption' => 'Qty / Box', 'required' => true, 'ui' => ['multiline' => [Form\Control\Multiline::TABLE_CELL => ['width' => 2]]]]); - $this->addField('box', ['type' => 'integer', 'caption' => '# of Boxes', 'required' => true, 'ui' => ['multiline' => [Form\Control\Multiline::TABLE_CELL => ['width' => 2]]]]); - $this->addExpression('total', ['expr' => function (Model $row) { - return $row->get('qty') * $row->get('box'); - }, 'type' => 'integer']); - } + parent::init(); + + $this->addField('item', ['required' => true, 'default' => 'item']); + $this->addField('qty', ['type' => 'integer', 'caption' => 'Qty / Box', 'required' => true, 'ui' => ['multiline' => [Form\Control\Multiline::TABLE_CELL => ['width' => 2]]]]); + $this->addField('box', ['type' => 'integer', 'caption' => '# of Boxes', 'required' => true, 'ui' => ['multiline' => [Form\Control\Multiline::TABLE_CELL => ['width' => 2]]]]); + $this->addExpression('total', ['expr' => function (Model $row) { + return $row->get('qty') * $row->get('box'); + }, 'type' => 'integer']); } +} +``` The 'total' expression will get updated on each field change automatically. -OnLineChange Callback -===================== +## OnLineChange Callback + If you want to define a callback which gets executed when a field value in a Multiline row is changed, you can do so using the ``onLineChange()`` method. The first parameter is the callback function, the second one is an array containing field names that will trigger the callback when values are changed. You can return a single JsExpressionable or an array of JsExpressionables which then will be sent to the browser. -In this case we display a message when any of the control value for 'qty' and 'box' are changed:: +In this case we display a message when any of the control value for 'qty' and 'box' are changed: - $multiline->onLineChange(function (array $rows, Form $form) { - $total = 0; - foreach ($rows as $row => $cols) { - $qty = $cols['qty'] ?? 0; - $box = $cols['box'] ?? 0; - $total += $qty * $box; - } - - return new JsToast('The new Total is ' . $app->uiPersistence->typecastSaveField(new Field(['type' => 'atk4_money']), $total)); - }, ['qty', 'box']); +``` +$multiline->onLineChange(function (array $rows, Form $form) { + $total = 0; + foreach ($rows as $row => $cols) { + $qty = $cols['qty'] ?? 0; + $box = $cols['box'] ?? 0; + $total += $qty * $box; + } + return new JsToast('The new Total is ' . $app->uiPersistence->typecastSaveField(new Field(['type' => 'atk4_money']), $total)); +}, ['qty', 'box']); +``` -Multiline Vue Component -======================= +## Multiline Vue Component Multiline is a Vue component by itself and rely on many others Vue components to render itself. Each control is render via a Vue component and the Vue component used will depend on the model @@ -197,52 +200,58 @@ Each control being a Vue component means that they accept 'Props' that may chang Props on each component may be applied globally, i.e. to all control within Multiline that use that control, or per component. -Setting component Props globally ---------------------------------- +### Setting component Props globally + Use the $componentProps property of Multiline in order to apply 'Props' to component globally. .. php:attr:: componentProps -Example of changing all Dropdown(SuiDropdown) within Multiline:: +Example of changing all Dropdown(SuiDropdown) within Multiline: - $ml = $form->addControl('ml', [Multiline::class, 'compponentProps' => [Multiline::SELECT => ['floating' => true]]]); +``` +$ml = $form->addControl('ml', [Multiline::class, 'compponentProps' => [Multiline::SELECT => ['floating' => true]]]); +``` -Setting component Props per field ---------------------------------- +### Setting component Props per field -Specific field components Props may be applied using the 'ui' field property when adding field to your model:: +Specific field components Props may be applied using the 'ui' field property when adding field to your model: - $this->addField('email', [ - 'required' => true, - 'ui' => ['multiline' => [Multiline::INPUT => ['icon' => 'envelope', 'type' => 'email']]], - ]); - $this->addField('password', [ - 'required' => true, - 'ui' => ['multiline' => [Multiline::INPUT => ['icon' => 'key', 'type' => 'password']]], - ]); +``` +$this->addField('email', [ + 'required' => true, + 'ui' => ['multiline' => [Multiline::INPUT => ['icon' => 'envelope', 'type' => 'email']]], +]); +$this->addField('password', [ + 'required' => true, + 'ui' => ['multiline' => [Multiline::INPUT => ['icon' => 'key', 'type' => 'password']]], +]); +``` -Note on Multiline control -------------------------- +### Note on Multiline control Each control inside Multiline is wrap within a table cell(SuiTableCell) component and this component can be customize as -well using the 'ui' property of the model's field:: +well using the 'ui' property of the model's field: - $this->addExpression('total', [ - 'expr' => function (Model $row) { - return $row->get('qty') * $row->get('box'); - }, - 'type' => 'integer', - 'ui' => ['multiline' => [Multiline::TABLE_CELL => ['width' => 1, 'class' => 'blue']]], - ]); +``` +$this->addExpression('total', [ + 'expr' => function (Model $row) { + return $row->get('qty') * $row->get('box'); + }, + 'type' => 'integer', + 'ui' => ['multiline' => [Multiline::TABLE_CELL => ['width' => 1, 'class' => 'blue']]], +]); +``` + +### Table appearance within Multiline + +Table(SuiTable) Props can be set using $tableProps property of Multiline: -Table appearance within Multiline ---------------------------------- -Table(SuiTable) Props can be set using $tableProps property of Multiline:: +``` +$ml = $form->addControl('ml', [Multiline::class, 'tableProps' => ['color' => 'blue']]); +``` - $ml = $form->addControl('ml', [Multiline::class, 'tableProps' => ['color' => 'blue']]); +### Header -Header ------- - The header uses the field's caption by default. - You can edit it by setting the ``$caption`` property. - If you want to hide the header, set the ``$caption`` property to an empty string ``''``. diff --git a/docs/overview.md b/docs/overview.md index b577ed6cfd..5d35590c0b 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -1,9 +1,6 @@ - .. _overview: -==================== -Overview of Agile UI -==================== +# Overview of Agile UI Agile UI is a PHP component framework for building User Interfaces entirely in PHP. Although the components of Agile UI will typically use HTML, JavaScript, jQuery and @@ -21,14 +18,12 @@ Agile UI is designed and built for the Agile Toolkit (https://agiletoolkit.org/) with the goal of providing a user-friendly experience when creating data-heavy API / UI backends. -Agile UI Design Goals -===================== +## Agile UI Design Goals Our goal is to offer a free UI framework which can be used to easily develop the most complex business application UI in just a few hours, without diving deep into HTML/JS specifics. -1. Out of the box experience ----------------------------- +### 1. Out of the box experience Sample scenario: @@ -40,8 +35,7 @@ Sample scenario: Agile UI is ideal for such a scenario. By simply describing your data model, relations, and operations you will get a fully working UI and API with minimal setup. -2. Compact and easy to integrate --------------------------------- +### 2. Compact and easy to integrate Simple scenario: @@ -52,8 +46,7 @@ Simple scenario: Agile UI contains a Form component which you can integrate into your existing app. More importantly, it can securely access your offer database. -3. Compatible with RestAPI --------------------------- +### 3. Compatible with RestAPI Simple scenario: @@ -62,8 +55,7 @@ Simple scenario: You can set up an API end-point for authorized access to your offer database, that follows the same business rules and has access to the same operations. -4. Deploy and Scale -------------------- +### 4. Deploy and Scale Simple scenario: @@ -76,8 +68,7 @@ architecture providers such as: Heroku, Docker, or even AWS Lambdas. Agile UI / PHP application has a minimum "start-up" time, has the best CPU usage, and gives you the highest efficiency and best scaling.   -5. High-level Solution ----------------------- +### 5. High-level Solution Simple scenario: @@ -92,39 +83,40 @@ Overview Example ^^^^^^^^^^^^^^^^ Agile UI / Agile Data code for your app can fit into a single file. See below for -clarifications:: +clarifications: +``` +addField('domain_name'); - $this->addField('contact_email'); - $this->addField('contact_phone'); - $this->addField('date', ['type' => 'date']); - $this->addField('offer', ['type' => 'atk4_money']); - $this->addField('is_accepted', ['type' => 'boolean']); - } + parent::init(); + + // Persistence may not have structure, so we define here + $this->addField('domain_name'); + $this->addField('contact_email'); + $this->addField('contact_phone'); + $this->addField('date', ['type' => 'date']); + $this->addField('offer', ['type' => 'atk4_money']); + $this->addField('is_accepted', ['type' => 'boolean']); } +} - // Create Application object and initialize Admin Layout - $app = new \Atk4\Ui\App('Offer tracking system'); - $app->initLayout([\Atk4\Ui\Layout\Admin::class]); +// Create Application object and initialize Admin Layout +$app = new \Atk4\Ui\App('Offer tracking system'); +$app->initLayout([\Atk4\Ui\Layout\Admin::class]); - // Connect to database and place a fully-interactive Crud - $db = new \Atk4\Data\Persistence\Sql($dsn); - \Atk4\Ui\Crud::addTo($app) - ->setModel(new Offer($db)); +// Connect to database and place a fully-interactive Crud +$db = new \Atk4\Data\Persistence\Sql($dsn); +\Atk4\Ui\Crud::addTo($app) + ->setModel(new Offer($db)); +``` Through the course of this example, We are performing several core actions: @@ -181,9 +173,7 @@ To sum up Agile UI in more technical terms: - Abstains from duplicating field names, types, or validation logic outside of Model class. - -Best use of Agile UI --------------------- +### Best use of Agile UI - Creating admin backend UI for data entry and dashboards in shortest time and with minimum amount of code. @@ -196,64 +186,72 @@ Best use of Agile UI .. _component: -Component -========= +## Component The component is a fundamental building block of Agile UI. Each component is fully self-sufficient and creating a class instance is enough to make a component work. That means that components may rely on each other and even though some may appear very basic to you, they are relied on by some other components for maximum -flexibility. The next example adds a "Cancel" button to a form:: +flexibility. The next example adds a "Cancel" button to a form: - $button = \Atk4\Ui\Button::addTo($form, [ - 'Cancel', - 'icon' => new \Atk4\Ui\Icon('pencil'), - ])->link('dashboard.php'); +``` +$button = \Atk4\Ui\Button::addTo($form, [ + 'Cancel', + 'icon' => new \Atk4\Ui\Icon('pencil'), +])->link('dashboard.php'); +``` :php:class:`Button` and :php:class:`Icon` are some of the most basic components in Agile UI. You will find Crud / Form / Grid components much more useful: .. figure:: images/all-atk-classes.png +### Using Components -Using Components ----------------- Look above at the :ref:`overview_example`, component `GRID` was made part -of application layout with a line:: +of application layout with a line: - \Atk4\Ui\Crud::addTo($app); +``` +\Atk4\Ui\Crud::addTo($app); +``` -To render a component individually and get the HTML and JavaScript use this format:: +To render a component individually and get the HTML and JavaScript use this format: - $form = new Form(); - $form->setApp($app); - $form->invokeInit(); - $form->setModel(new User($db)); +``` +$form = new Form(); +$form->setApp($app); +$form->invokeInit(); +$form->setModel(new User($db)); - $html = $form->render(); +$html = $form->render(); +``` -This would render an individual component and will return HTML:: +This would render an individual component and will return HTML: -
-
- ... fields - ... buttons -
-
+``` +
+
+ ... fields + ... buttons +
+
+``` For other use-cases please look into :php:meth:`View::render()` -Factory -------- +### Factory + Factory is a mechanism which allow you to use shorter syntax for creating objects. The goal of Agile UI is to be simple to read and use; so taking advantage of loose types -in PHP language allows us to use an alternative shorter syntax:: +in PHP language allows us to use an alternative shorter syntax: - \Atk4\Ui\Button::addTo($form, ['Cancel', 'icon' => 'pencil']) - ->link('dashboard.php'); +``` +\Atk4\Ui\Button::addTo($form, ['Cancel', 'icon' => 'pencil']) + ->link('dashboard.php'); +``` By default, class names specified as the first array elements passed to the add() method are resolved to namespace `Atk4\\Ui`; however the application class can fine-tune the @@ -262,15 +260,14 @@ search. Using a factory is optional. For more information see: https://agile-core.readthedocs.io/en/develop/factory.html -Templates ---------- +### Templates + Components rely on :php:class:`Template` class for parsing and rendering their HTML. The default template is written for Fomantic-UI framework, which makes sure that elements will look good and be consistent. +### Layouts -Layouts -------- .. image:: images/layout-hierarchy.png :width: 40% :align: right @@ -291,9 +288,8 @@ If you are extending your Admin Layout, be sure to maintain the same property na to allow other components to make use of them. For example, an authentication controller will automatically populate a user-menu with the name of the user and log-out button. +## Advanced techniques -Advanced techniques -=================== By design we make sure that adding a component into a Render Tree (See :ref:`view`) is enough, so App provides a mechanism for components to: @@ -301,21 +297,20 @@ is enough, so App provides a mechanism for components to: - Define event handlers and actions - Handle callbacks -Non-PHP dependencies --------------------- +### Non-PHP dependencies + Your component may depend on additional JavaScript libraries, CSS, or other files. At the present time you have to make them available through a CDN and HTTPS. See: :php:meth:`App::requireJs` +### Events and Actions -Events and Actions ------------------- Agile UI allows you to initiate some JavaScript actions from within PHP. The amount of code involvement is quite narrow and is only intended for binding events inside your component without involving developers who use and implement your component. -Callbacks ---------- +### Callbacks + Some actions can be done only on the server side. For example, adding a new record into the database. @@ -325,8 +320,8 @@ must be able to use unique URLs which will trigger the callback. To see how this is implemented, read about :ref:`callback` -Virtual Pages -------------- +### Virtual Pages + .. image:: images/ui-component-diagram.png :width: 30% :align: right @@ -340,16 +335,15 @@ everything else, virtual pages can be contained within the components, so that no extra effort from you is required when a component wishes to use a dynamic modal dialog. -Extending with Add-ons ----------------------- +### Extending with Add-ons + Agile UI is designed for data-agnostic UI components which you can add inside your application with a single line of code. However, Agile Toolkit goes one step further by offering you a directory of published add-ons and installs them by using a simple wizard. +## Using Agile UI -Using Agile UI -============== Technologies advance forward to make it simpler and faster to build web apps. In some cases you can use ReactJS + Firebase but in most cases you will need to have a backend. @@ -366,8 +360,7 @@ endpoint provided by Agile Toolkit. .. warning:: information on setting up API endpoints is coming soon. -Learning Agile Toolkit ----------------------- +### Learning Agile Toolkit We recommend that you start looking at Agile UI first. Continue reading through the :ref:`quickstart` section and try building some of the basic apps. You will need to @@ -400,29 +393,24 @@ If you are not interested in UI and only need the Rest API, we recommend that yo into documentation for Agile Data (https://agile-data.readthedocs.io) and the Rest API extension (https://github.com/atk4/api) which is a work in progress. -Application Tutorials ---------------------- +### Application Tutorials We have written a few working cloud applications ourselves with Agile Toolkit and are offering you to view their code. Some of them come with tutorials that teach you how to build an application step-by-step. -Education ---------- +### Education If you represent a group of students that wish to learn Agile Toolkit contact us about our education materials. We offer special support for those that want to learn how to develop Web Apps using Agile Toolkit. -Commercial Project Strategy ---------------------------- +### Commercial Project Strategy If you maintain a legacy PHP application, and would like to have a free chat with us about some support and assistance, please do not hesitate to reach out. - -Things Agile UI simplifies -========================== +## Things Agile UI simplifies Some technologies are "pre-requirements" in other PHP frameworks, but Agile Toolkit lets you develop a perfectly functional web application even if you are NOT familiar @@ -436,24 +424,20 @@ with technologies such as: We do recommend that you come back and learn those technologies **after** you have mastered Agile Toolkit. -Database abstraction --------------------- +### Database abstraction Agile Data offers abstraction of database servers and will use appropriate query language to fetch your data. You may need to use SQL/NoSQL language of your database for some more advanced use cases. -Cloud deployment ----------------- +### Cloud deployment There are also ways to deploy your application into the cloud without knowledge of infrastructure, Linux and SSH. A good place to start is Heroku (https://www.heroku.com/). We reference Heroku in our tutorials, but Agile Toolkit can work with any cloud hosting that runs PHP apps. - -Hosted Demo showing many functions -================================== +## Hosted Demo showing many functions There's a demo available of atk4/ui which shows many of the modules and functions available in atk4. @@ -463,13 +447,12 @@ how to use atk4 in certain application cases. You can find the demo here: https://ui.agiletoolkit.org/demos/ -Local Demo/Sandpit -===================================== +## Local Demo/Sandpit + When you download and install atk4 you will find a subdirectory called "demos" in the atk4 repository which also could be locally executed. -Setup the demo --------------- +### Setup the demo To run the demo: - Create a directory called "atk4" and create a separate folder for each repo (ui, data, etc.), in this case "ui" @@ -480,11 +463,12 @@ To run the demo: - Setup an Sqlite file database using a provided script (see below) - Open the demos from your browser (e.g. https://localhost/atk4/ui/demos/) -Setup database with example data ---------------------- +### Setup database with example data + The demo also includes a script that let's you setup a Sqlite file database with an example data. You will find this script in the subdirectory "atk4/ui/demos/_demo-data/". To run this script, use the following command: - ``` - php atk4/ui/demos/_demo-data/create-db.php; - ``` + +``` +php atk4/ui/demos/_demo-data/create-db.php; +``` diff --git a/docs/paginator.md b/docs/paginator.md index a6d84b3222..5ac0ab6c84 100644 --- a/docs/paginator.md +++ b/docs/paginator.md @@ -1,9 +1,6 @@ - .. _paginator: -========= -Paginator -========= +# Paginator .. php:namespace:: Atk4\Ui .. php:class:: Paginator @@ -11,39 +8,43 @@ Paginator Paginator displays a horizontal UI menu providing links to pages when all of the content does not fit on a page. Paginator is a stand-alone component but you can use it in conjunction with other components. -Adding and Using -================ +## Adding and Using .. php:attr:: total .. php:attr:: page Place paginator in a designated spot on your page. You also should specify what's the total number of pages -paginator should have:: +paginator should have: - $paginator = Paginator::addTo($app); - $paginator->total = 20; +``` +$paginator = Paginator::addTo($app); +$paginator->total = 20; +``` Paginator will not display links to all the 20 pages, instead it will show first, last, current page and few pages around the current page. Paginator will automatically place links back to your current page through :php:meth:`App::url()`. After initializing paginator you can use it's properties to determine current page. Quite often you'll need -to display current page BEFORE the paginator on your page:: +to display current page BEFORE the paginator on your page: - $h = Header::addTo($page); - LoremIpsum::addTo($page); // some content here +``` +$h = Header::addTo($page); +LoremIpsum::addTo($page); // some content here - $p = Paginator::addTo($page); - $h->set('Page ' . $p->page . ' from ' . $p->total); +$p = Paginator::addTo($page); +$h->set('Page ' . $p->page . ' from ' . $p->total); +``` -Remember that values of 'page' and 'total' are integers, so you may need to do type-casting:: +Remember that values of 'page' and 'total' are integers, so you may need to do type-casting: - $label->set($p->page); // will not work - $label->set((string) $p->page); // works fine +``` +$label->set($p->page); // will not work +$label->set((string) $p->page); // works fine +``` -Range and Logic -=============== +## Range and Logic You can configure Paginator through properties. @@ -61,8 +62,7 @@ the current and total pages. Returns number of current page. -Template -======== +## Template Paginator uses Fomantic-UI `ui pagination menu` so if you are unhappy with the styling (e.g: active element is not sufficiently highlighted), you should refer to Fomantic-UI or use alternative theme. @@ -80,8 +80,7 @@ Each of the above (except Spacer) may have `active`, `link` and `page` tags. .. php:method:: renderItem($t, $page = null) -Dynamic Reloading -================= +## Dynamic Reloading .. php:attr:: reload diff --git a/docs/popup.md b/docs/popup.md index ca6f9ac6be..201cf7c14a 100644 --- a/docs/popup.md +++ b/docs/popup.md @@ -1,26 +1,27 @@ - .. _popup: -===== -Popup -===== +# Popup .. php:namespace:: Atk4\Ui .. php:class:: Popup -Implements a popup:: +Implements a popup: - $button = Button::addTo($app, ['Click me']); - HelloWorld::addTo(Popup::addTo($app, [$button])); +``` +$button = Button::addTo($app, ['Click me']); +HelloWorld::addTo(Popup::addTo($app, [$button])); +``` .. php:method:: set($callback) -Popup can also operate with dynamic content:: +Popup can also operate with dynamic content: - $button = Button::addTo($app, ['Click me']); - Popup::addTo($app, [$button]) - ->set('hello world with rand=' . rand(1, 100)); +``` +$button = Button::addTo($app, ['Click me']); +Popup::addTo($app, [$button]) + ->set('hello world with rand=' . rand(1, 100)); +``` Pop-up should be added into a viewport which will define boundaries of a pop-up, but it will be positioned relative to the $button. Popup remains invisible until it's triggered by event of $button. @@ -29,20 +30,24 @@ If second argument in the :ref:`seed` is of class :php:class:`Button`, :php:clas :php:class:`MenuItem` or :php:class:`Dropdown` (note - NOT Form\Control!), pop-up will also bind itself to that element. The above example will automatically bind "click" event of a button to open a pop-up. -When added into a menu, pop-up will appear on hover:: +When added into a menu, pop-up will appear on hover: - $menu = Menu::addTo($app); - $item = $menu->addItem('HoverMe') - Text::addTo(Popup::addTo($app, [$item]))->set('Appears when you hover a menu item'); +``` +$menu = Menu::addTo($app); +$item = $menu->addItem('HoverMe') +Text::addTo(Popup::addTo($app, [$item]))->set('Appears when you hover a menu item'); +``` -Like many other Views of ATK, popup is an interactive element. It can load it's contents when opened:: +Like many other Views of ATK, popup is an interactive element. It can load it's contents when opened: - $menu = Menu::addTo($app); - $item = $menu->addItem('HoverMe'); - Popup::addTo($app, [$item])->set(function (View $p) { - Text::addTo($p)->set('Appears when you hover a menu item'); - Label::addTo($p, ['Random value', 'detail' => rand(1, 100)]); - }); +``` +$menu = Menu::addTo($app); +$item = $menu->addItem('HoverMe'); +Popup::addTo($app, [$item])->set(function (View $p) { + Text::addTo($p)->set('Appears when you hover a menu item'); + Label::addTo($p, ['Random value', 'detail' => rand(1, 100)]); +}); +``` Demo: https://ui.agiletoolkit.org/demos/popup.php diff --git a/docs/progressbar.md b/docs/progressbar.md index 28e3c78585..da9074be79 100644 --- a/docs/progressbar.md +++ b/docs/progressbar.md @@ -1,41 +1,39 @@ - .. php:namespace:: Atk4\Ui .. php:class:: ProgressBar -=========== -ProgressBar -=========== +# ProgressBar ProgressBar is actually a quite simple element, but it can be made quite interactive along with :php:class:`JsSse`. Demo: https://ui.agiletoolkit.org/demos/progressbar.php - -Basic Usage -=========== +## Basic Usage .. php:method:: jsValue($value) -After adding a console to your :ref:`render_tree`, you just need to set a callback:: +After adding a console to your :ref:`render_tree`, you just need to set a callback: - // Add progressbar showing 0 (out of 100) - $bar = ProgressBar::addTo($app); +``` +// Add progressbar showing 0 (out of 100) +$bar = ProgressBar::addTo($app); - // Add with some other value of 20% and label - $bar2 = ProgressBar::addTo($app, [20, '% Battery']); +// Add with some other value of 20% and label +$bar2 = ProgressBar::addTo($app, [20, '% Battery']); +``` The value of the progress bar can be changed either before rendering, inside PHP, or after rendering -with JavaScript:: +with JavaScript: - $bar->value = 5; // sets this value instead of 0 +``` +$bar->value = 5; // sets this value instead of 0 - Button::addTo($app, ['charge up the battery']) - ->on('click', $bar2->jsValue(100)); +Button::addTo($app, ['charge up the battery']) + ->on('click', $bar2->jsValue(100)); +``` -Updating Progress in RealTime -============================= +## Updating Progress in RealTime You can use real-time element such as JsSse or Console (which relies on JsSse) to execute jsValue() of your progress bar and adjust the display value. diff --git a/docs/quickstart.md b/docs/quickstart.md index b5e06ee5ff..d0724bbabd 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -1,9 +1,6 @@ - .. _quickstart: -========== -Quickstart -========== +# Quickstart In this section we will demonstrate how to build a very simple web application with just under 50 lines of PHP code. The important consideration here is that those are the ONLY @@ -13,34 +10,34 @@ At this point you might not understand some concept, so I will provide reference into the documentation, but I suggest you to come back to this QuickStart to finish this simple tutorial. -Requirements -============ +## Requirements Agile Toolkit will work anywhere where PHP can. Find a suitable guide on how to set up PHP on your platform. Having a local database is a plus, but our initial application will work without persistent database. -Installing -========== +## Installing Create a directory which is accessible by you web server. Start your command-line, -enter this directory and execute composer command:: - - composer require atk4/ui +enter this directory and execute composer command: +``` +composer require atk4/ui +``` -Coding "Hello, World" -===================== +## Coding "Hello, World" -Open a new file `index.php` and enter the following code:: +Open a new file `index.php` and enter the following code: - initLayout([\Atk4\Ui\Layout\Centered::class]); +$app = new \Atk4\Ui\App('My First App'); +$app->initLayout([\Atk4\Ui\Layout\Centered::class]); - \Atk4\Ui\HelloWorld::addTo($app); +\Atk4\Ui\HelloWorld::addTo($app); +``` .. rubric:: Clarifications @@ -66,94 +63,104 @@ components that do things for you. .. _using-namespaces: -Using namespaces -================ +## Using namespaces By using namespaces you will be able to write less code for classes you use more often by using namespace references and writing clearer code. -By using namespaces you will make out of this:: +By using namespaces you will make out of this: - initLayout([\Atk4\Ui\Layout\Centered::class]); +$app = new \Atk4\Ui\App('ToDo List'); +$app->initLayout([\Atk4\Ui\Layout\Centered::class]); +``` All components of Agile Data are database-agnostic and will not concern themselves with the way how you store data. I will start the session and connect `persistence `_ -with it:: +with it: - db class:: +you can define it in the $app->db class: - "Erp v." . ERP_VER, - "db" => $db, - "callExit" => false, - ]); +$app = new App([ + "title" => "Erp v." . ERP_VER, + "db" => $db, + "callExit" => false, +]); +``` -Data Model -========== +## Data Model We need a class `Task` which describes `data model `_ for the -single ToDo item:: +single ToDo item: +``` +class ToDoItem extends \Atk4\Data\Model +{ + public $table = 'todo_item'; - class ToDoItem extends \Atk4\Data\Model + protected function init(): void { - public $table = 'todo_item'; - - protected function init(): void - { - parent::init(); + parent::init(); - $this->addField('name', ['caption' => 'Task Name', 'required' => true]); + $this->addField('name', ['caption' => 'Task Name', 'required' => true]); - $this->addField('due', [ - 'type' => 'date', - 'caption' => 'Due Date', - 'default' => new \DateTime('+1 week'), - ]); - } + $this->addField('due', [ + 'type' => 'date', + 'caption' => 'Due Date', + 'default' => new \DateTime('+1 week'), + ]); } +} +``` .. rubric:: Clarifications @@ -167,48 +174,48 @@ single ToDo item:: As you might have noted already, Persistence and Model are defined independently from each-other. -Instantiate App using DiContainerTrait (Dependency Injection) -============================================================= +## Instantiate App using DiContainerTrait (Dependency Injection) -Class App use `DiContainerTrait` which allow us to inject dependency directly in constructor:: +Class App use `DiContainerTrait` which allow us to inject dependency directly in constructor: - use Monolog\Logger; - use Monolog\Handler\StreamHandler; +``` +use Monolog\Logger; +use Monolog\Handler\StreamHandler; - // create a log channel - $logger = new Logger('name'); - $logger->pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING)); +// create a log channel +$logger = new Logger('name'); +$logger->pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING)); - use Atk4\Data\Persistence; - use Atk4\Ui\App; - $db = Persistence::connect("mysql://localhost:3306/database_name", "user", "password"); +use Atk4\Data\Persistence; +use Atk4\Ui\App; +$db = Persistence::connect("mysql://localhost:3306/database_name", "user", "password"); - $app = new App([ - "title" => "Your application title", - "db" => $db, - "logger" => $logger, - ]); +$app = new App([ + "title" => "Your application title", + "db" => $db, + "logger" => $logger, +]); +``` +## Form and Crud Components +Next we need to add Components that are capable of manipulating the data: -Form and Crud Components -======================== +``` +$col = \Atk4\Ui\Columns::addTo($app, ['divided']); +$colReload = new \Atk4\Ui\Js\JsReload($col); -Next we need to add Components that are capable of manipulating the data:: +$form = \Atk4\Ui\Form::addTo($col->addColumn()); +$form->setModel(new ToDoItem($s)); +$form->onSubmit(function (Form $form) use ($colReload) { + $form->model->save(); - $col = \Atk4\Ui\Columns::addTo($app, ['divided']); - $colReload = new \Atk4\Ui\Js\JsReload($col); + return $colReload; +}); - $form = \Atk4\Ui\Form::addTo($col->addColumn()); - $form->setModel(new ToDoItem($s)); - $form->onSubmit(function (Form $form) use ($colReload) { - $form->model->save(); - - return $colReload; - }); - - \Atk4\Ui\Table::addTo($col->addColumn()) - ->setModel(new ToDoItem($s)); +\Atk4\Ui\Table::addTo($col->addColumn()) + ->setModel(new ToDoItem($s)); +``` .. rubric:: Clarifications @@ -238,30 +245,31 @@ Next we need to add Components that are capable of manipulating the data:: It is time to test our application in action. Use the form to add new record data. Saving the form will cause table to also reload revealing new records. -Grid and Crud -============= +## Grid and Crud As mentioned before, UI Components in Agile Toolkit are often interchangeable, you can swap one for -another. In our example replace right column (label 17) with the following code:: - - $grid = \Atk4\Ui\Crud::addTo($col->addColumn(), [ - 'paginator' => false, - 'canCreate' => false, - 'canDelete' => false, - ]); - $grid->setModel(new ToDoItem($s)); - - $grid->menu->addItem('Complete Selected', - new \Atk4\Ui\Js\JsReload($grid->table, [ - 'delete' => $grid->addSelection()->jsChecked(), - ]) - ); - - if (isset($_GET['delete'])) { - foreach (explode(',', $_GET['delete']) as $id) { - $grid->model->delete($id); - } +another. In our example replace right column (label 17) with the following code: + +``` +$grid = \Atk4\Ui\Crud::addTo($col->addColumn(), [ + 'paginator' => false, + 'canCreate' => false, + 'canDelete' => false, +]); +$grid->setModel(new ToDoItem($s)); + +$grid->menu->addItem('Complete Selected', + new \Atk4\Ui\Js\JsReload($grid->table, [ + 'delete' => $grid->addSelection()->jsChecked(), + ]) +); + +if (isset($_GET['delete'])) { + foreach (explode(',', $_GET['delete']) as $id) { + $grid->model->delete($id); } +} +``` .. rubric:: Clarifications @@ -287,9 +295,7 @@ another. In our example replace right column (label 17) with the following code: App class will carry on with triggering the necessary code to render new HTML for the $grid->table, so it will reflect removal of the items. - -Conclusion -========== +## Conclusion We have just implemented a full-stack application with a stunning UI, advanced use of JavaScript, Form validation and reasonable defaults, calendar picker, multi-item selection in the grid with ability to @@ -297,15 +303,16 @@ also edit records through a dynamically loaded dialog. All of that in about 50 lines of PHP code. More importantly, this code is portable, can be used anywhere and does not have any complex requirements. In fact, we could wrap it up into an individual Component -that can be invoked with just one line of code:: +that can be invoked with just one line of code: - ToDoManager::addTo($app)->setModel(new ToDoItem()); +``` +ToDoManager::addTo($app)->setModel(new ToDoItem()); +``` Just like that you could be developing more components and re-using existing ones in your current or next web application. -More Tutorials -============== +## More Tutorials If you have enjoyed this tutorial, we have prepared another one for you, that builds a multi-page and multi-user application and takes advantage of database expressions, authentication and introduces diff --git a/docs/render.md b/docs/render.md index 4ae32514e3..b10c6d2eb1 100644 --- a/docs/render.md +++ b/docs/render.md @@ -1,15 +1,15 @@ - -Introduction ------------- +### Introduction Agile UI allows you to create and combine various objects into a single Render Tree for unified rendering. Tree represents -all the UI components that will contribute to the HTML generation. Render tree is automatically created and maintained:: +all the UI components that will contribute to the HTML generation. Render tree is automatically created and maintained: - $view = new \Atk4\Ui\View(); +``` +$view = new \Atk4\Ui\View(); - \Atk4\Ui\Button::addTo($view, ['test']); +\Atk4\Ui\Button::addTo($view, ['test']); - echo $view->render(); +echo $view->render(); +``` When render on the $view is executed, it will render button first then incorporate HTML into it's own template before rendering. @@ -33,8 +33,7 @@ simply set a $model property and does not really need to rely on $api etc. Next, lets look at what Initialization really is and why is it important. -Initialization --------------- +### Initialization Calling the init() method of a view is essential before any meaningful work can be done with it. This is important, because the following actions are performed: @@ -47,43 +46,48 @@ the following actions are performed: Many UI components rely on the above to function properly. For example, Grid will look for certain regions in its template to clone them into separate objects. This cloning can only take place inside init() method. -Late initialization -------------------- +### Late initialization -When you create an application and select a Layout, the layout is automatically initialized:: +When you create an application and select a Layout, the layout is automatically initialized: - $app = new \Atk4\Ui\App(); - $app->initLayout([\Atk4\Ui\Layout\Centered::class]); +``` +$app = new \Atk4\Ui\App(); +$app->initLayout([\Atk4\Ui\Layout\Centered::class]); - echo $app->layout->name; // present, because layout is initialized! +echo $app->layout->name; // present, because layout is initialized! +``` -After that, adding any objects into app (into layout) will initialize those objects too:: +After that, adding any objects into app (into layout) will initialize those objects too: - $b = \Atk4\Ui\Button::addTo($app, ['Test1']); +``` +$b = \Atk4\Ui\Button::addTo($app, ['Test1']); - echo $b->name; // present, because button was added into initialized object. +echo $b->name; // present, because button was added into initialized object. +``` If object cannot determine the path to the application, then it will remain uninitialized for some time. This is called -"Late initialization":: +"Late initialization": - $v = new Buttons(); - $b2 = \Atk4\Ui\Button::addTo($v, ['Test2']); +``` +$v = new Buttons(); +$b2 = \Atk4\Ui\Button::addTo($v, ['Test2']); - echo $b2->name; // not set!! Not part of render tree +echo $b2->name; // not set!! Not part of render tree +``` At this point, if you execute $v->render() it will create it's own App and will create its own render tree. On the other -hand, if you add $v inside layout, trees will merge and the same $app will be used:: +hand, if you add $v inside layout, trees will merge and the same $app will be used: - $app->add($v); +``` +$app->add($v); - echo $b2->name; // fully set now and unique. +echo $b2->name; // fully set now and unique. +``` Agile UI will attempt to always initialize objects as soon as possible, so that you can get the most meaningful stack traces should there be any problems with the initialization. - -Rendering outside ------------------ +### Rendering outside It's possible for some views to be rendered outside of the app. In the previous section I speculated that calling $v->render() will create its own tree independent from the main one. @@ -107,31 +111,34 @@ with static components. .. _unique_name: -Unique Name ------------ +### Unique Name Through adding objects into render tree (even if those are not Views) objects can assume unique names. When you create -your application, then any object you add into your app will have a unique `name` property:: +your application, then any object you add into your app will have a unique `name` property: - $b = \Atk4\Ui\Button::addTo($app); - echo $b->name; +``` +$b = \Atk4\Ui\Button::addTo($app); +echo $b->name; +``` The other property of the name is that it's also "permanent". Refreshing the page guarantees your object to have the same -name. Ultimately, you can create a View that uses it's name to store some information:: +name. Ultimately, you can create a View that uses it's name to store some information: - class MyView extends View +``` +class MyView extends View +{ + protected function init(): void { - protected function init(): void - { - parent::init(); + parent::init(); - if ($_GET[$this->name]) { - \Atk4\Ui\Label::addTo($this, ['Secret info is', 'class.big red' => true, 'detail' => $_GET[$this->name]]); - } - - \Atk4\Ui\Button::addTo($this, ['Send info to ourselves']) - ->link([$this->name => 'secret_info']); + if ($_GET[$this->name]) { + \Atk4\Ui\Label::addTo($this, ['Secret info is', 'class.big red' => true, 'detail' => $_GET[$this->name]]); } + + \Atk4\Ui\Button::addTo($this, ['Send info to ourselves']) + ->link([$this->name => 'secret_info']); } +} +``` This quality of Agile UI objects is further explored through :php:class:`Callback` and :php:class:`VirtualPage` diff --git a/docs/rightpanel.md b/docs/rightpanel.md index 37b3707481..4cb46a2b26 100644 --- a/docs/rightpanel.md +++ b/docs/rightpanel.md @@ -1,9 +1,6 @@ - .. _rightpanel: -=========== -Right Panel -=========== +# Right Panel .. php:namespace:: Atk4\Ui\Panel @@ -14,43 +11,47 @@ and can display content statically or dynamically using Loadable Content. Demo: https://ui.agiletoolkit.org/demos/layout/layout-panel.php -Basic Usage -=========== +## Basic Usage -Adding a right panel to the app layout and adding content to it:: +Adding a right panel to the app layout and adding content to it: - $panel = $app->layout->addRightPanel(new \Atk4\Ui\Panel\Right(['dynamic' => false])); - Message::addTo($panel, ['This panel contains only static content.']); +``` +$panel = $app->layout->addRightPanel(new \Atk4\Ui\Panel\Right(['dynamic' => false])); +Message::addTo($panel, ['This panel contains only static content.']); +``` By default, panel content are loaded dynamically. If you want to only add static content, you need to specify the :ref:`dynamic` property and set it to false. Opening of the panel is done via a javascript event. Here, we simply register a click event on a button that will open -the panel:: +the panel: - $button = Button::addTo($app, ['Open Static']); - $button->on('click', $panel->jsOpen()); +``` +$button = Button::addTo($app, ['Open Static']); +$button->on('click', $panel->jsOpen()); +``` -Loading content dynamically ---------------------------- +### Loading content dynamically Loading dynamic content within panel is done via the onOpen method .. php:method:: onOpen($callback) -Initializing a panel with onOpen callback:: - - $panel = $app->layout->addRightPanel(new \Atk4\Ui\Panel\Right()); - Message::addTo($panel, ['This panel will load content dynamically below according to button select on the right.']); - $button = Button::addTo($app, ['Button 1']); - $button->js(true)->data('btn', '1'); - $button->on('click', $panel->jsOpen(['btn'], 'orange')); - - $panel->onOpen(function (Panel\Content $p) { - $buttonNumber = $_GET['btn'] ?? null; - $text = 'You loaded panel content using button #' . $buttonNumber; - Message::addTo($p, ['Panel 1', 'text' => $text]); - }); +Initializing a panel with onOpen callback: + +``` +$panel = $app->layout->addRightPanel(new \Atk4\Ui\Panel\Right()); +Message::addTo($panel, ['This panel will load content dynamically below according to button select on the right.']); +$button = Button::addTo($app, ['Button 1']); +$button->js(true)->data('btn', '1'); +$button->on('click', $panel->jsOpen(['btn'], 'orange')); + +$panel->onOpen(function (Panel\Content $p) { + $buttonNumber = $_GET['btn'] ?? null; + $text = 'You loaded panel content using button #' . $buttonNumber; + Message::addTo($p, ['Panel 1', 'text' => $text]); +}); +``` .. php:method:: jsOpen diff --git a/docs/seed.md b/docs/seed.md index 6810019292..8bc74c8874 100644 --- a/docs/seed.md +++ b/docs/seed.md @@ -1,22 +1,24 @@ - -Purpose of the Seed -=================== +## Purpose of the Seed .. php:namespace:: Atk4\Ui Agile UI relies on wide variety of objects. For example :php:class:`Button` relies on :php:class:`Icon` object for its rendering. As a developer can create Icon object first, -then pass it to the button:: +then pass it to the button: - $icon = new Iron('book'); - $button = new Button('Hello'); - $button->icon = $icon; +``` +$icon = new Iron('book'); +$button = new Button('Hello'); +$button->icon = $icon; +``` or you can divert icon creation until later by using Array / String for :php:attr:`Button::$icon` -property:: +property: - $button = new Button('Hello'); - $button->icon = 'book'; +``` +$button = new Button('Hello'); +$button->icon = 'book'; +``` When you don't provide an object - string/array value is called "Seed" and will be used to locate and load class dynamically just when it's needed. @@ -30,34 +32,37 @@ Seed has many advantages: - recursive syntax with property and constructor argument injection - allow App logic to further enhance mechanics -Growing Seed ------------- +### Growing Seed To grow a seed you need a factory. Factory is a trait implemented in atk4/core and used by all -ATK UI classes:: +ATK UI classes: - $object = Factory::factory($seed); +``` +$object = Factory::factory($seed); +``` In most cases you don't need to call factory yourself, methods which accept object/seed combinations -will do it for you:: +will do it for you: - Button::addTo($app); - // app will create instance of class \Atk4\Ui\Button +``` +Button::addTo($app); +// app will create instance of class \Atk4\Ui\Button +``` -Seed, Object and Render Tree ----------------------------- +### Seed, Object and Render Tree When calling :php:meth:`View::add()` not only your seed becomes an object, but it is also added to the :ref:`render tree`. -Seed Components -=============== +## Seed Components For more information about seeds, merging seeds, factories and namespaces, see https://agile-core.readthedocs.io/. -The most important points of a seed such as this one:: +The most important points of a seed such as this one: - $seed = [Button::class, 'hello', 'class.big red' => true, 'icon' => ['book', 'red']]; +``` +$seed = [Button::class, 'hello', 'class.big red' => true, 'icon' => ['book', 'red']]; +``` are: @@ -65,39 +70,43 @@ are: - Elements with numeric indexes 'hello' and 'big red' are passed to constructor of Button - Elements with named arguments are assigned to properties after invocation of constructor -Alternative ways to use Seed ----------------------------- +### Alternative ways to use Seed Some constructors may accept array as the first argument. It is also treated as a seed -but without class (because class is already set):: +but without class (because class is already set): - $button = new Button(['hello', 'class.big red' => true, 'icon' => ['book', 'class.red' => true]]); +``` +$button = new Button(['hello', 'class.big red' => true, 'icon' => ['book', 'class.red' => true]]); +``` It is alternatively possible to pass object as index 0 of the seed. In this case constructor is already invoked, so passing numeric values is not possible, but -you still can pass some property values:: +you still can pass some property values: - $seed = [new Button('hello', 'class.big red' => true), 'icon' => ['book', 'class.red' => true]]; +``` +$seed = [new Button('hello', 'class.big red' => true), 'icon' => ['book', 'class.red' => true]]; +``` -Additional cases ----------------- +### Additional cases An individual object may add more ways to deal with seed. For example, when adding columns -to your Table you can specify seed for the decorator: :php:class:`Atk4\\Ui\\\Table\\Column`:: +to your Table you can specify seed for the decorator: :php:class:`Atk4\\Ui\\\Table\\Column`: - $table->addColumn('salary', [\Atk4\Ui\Table\Column\Money::class]); +``` +$table->addColumn('salary', [\Atk4\Ui\Table\Column\Money::class]); - // or +// or - $table->addColumn('salary', [\Atk4\Ui\Table\Column\Money::class]); +$table->addColumn('salary', [\Atk4\Ui\Table\Column\Money::class]); - // or +// or - $table->addColumn('salary', new \Atk4\Ui\Table\Column\Money()); +$table->addColumn('salary', new \Atk4\Ui\Table\Column\Money()); - // or +// or - $table->addColumn('salary', [new \Atk4\Ui\Table\Column\Money()]); +$table->addColumn('salary', [new \Atk4\Ui\Table\Column\Money()]); +``` Note that addColumn uses default namespace of `\\Atk4\\Ui\\Table\\Column` when seeding objects. Some other methods that use seeds are: diff --git a/docs/session.md b/docs/session.md index b8161df6bd..8eb9b03aab 100644 --- a/docs/session.md +++ b/docs/session.md @@ -1,39 +1,38 @@ -============= -Session Trait -============= +# Session Trait .. php:trait:: SessionTrait - -Introduction -============ +## Introduction SessionTrait is a simple way to let object store relevant data in the session. Specifically used in ATK UI some objects want to memorize data. (see https://github.com/atk4/ui/blob/develop/src/Wizard.php#L12) -You would need 3 things. First make use of session trait:: - - use \Atk4\Ui\SessionTrait; +You would need 3 things. First make use of session trait: -next you may memorize any value, which will be stored independently from any other object (even of a same class):: +``` +use \Atk4\Ui\SessionTrait; +``` - $this->memorize('dsn', $dsn); +next you may memorize any value, which will be stored independently from any other object (even of a same class): -Later when you need the value, you can simply recall it:: +``` +$this->memorize('dsn', $dsn); +``` - $dsn = $this->recall('dsn'); +Later when you need the value, you can simply recall it: +``` +$dsn = $this->recall('dsn'); +``` -Properties -========== +## Properties .. php:attr:: rootNamespace Internal property to make sure that all session data will be stored in one "container" (array key). -Methods -======= +## Methods .. php:method:: startSession() diff --git a/docs/sticky.md b/docs/sticky.md index 2699003c75..267fd7f0d3 100644 --- a/docs/sticky.md +++ b/docs/sticky.md @@ -1,7 +1,4 @@ - - -Introduction ------------- +### Introduction Ability to automatically generate callback URLs is one of the unique features in Agile UI. With most UI widgets they would rely on a specific URL to be available or would require @@ -11,26 +8,30 @@ With Agile UI the backend URLs are created dynamically by using unique names and There is one problem, however. What if View (and the callbacks too) are created conditionally? -The next code creates Loader area which will display a console. Result is - nested callback:: +The next code creates Loader area which will display a console. Result is - nested callback: - Loader::addTo($app)->set(function (Loader $p) { - Console::addTo($p)->set(function (Console $console) { - $console->output('success!'); - }); +``` +Loader::addTo($app)->set(function (Loader $p) { + Console::addTo($p)->set(function (Console $console) { + $console->output('success!'); }); +}); +``` What if you need to pass a variable `client_id` to display on console output? Technically you would need to tweak the callback URL of "Loader" and also callback URL of "Console". -Sticky GET is a better approach. It works like this:: +Sticky GET is a better approach. It works like this: - $app->stickyGet('client_id'); +``` +$app->stickyGet('client_id'); - Loader::addTo($app)->set(function (Loader $p) { - Console::addTo($p)->set(function (Console $console) { - $console->output('client_id = !' . $_GET['client_id']); - }); +Loader::addTo($app)->set(function (Loader $p) { + Console::addTo($p)->set(function (Console $console) { + $console->output('client_id = !' . $_GET['client_id']); }); +}); +``` Whenever Loader, Console or any other component generatens a URL, it will now include value of `$_GET['client_id']` and it will transparently arrive inside your code even if it takes @@ -49,13 +50,17 @@ when Loader wishes to load content dynamically, it must pass extra _GET paramete the SAME get argument to trigger a callback for the Loader, otherwise Console wouldn't be initialized at all. -Loader sets a local stickyGet on the $p before it's passed inside your function:: +Loader sets a local stickyGet on the $p before it's passed inside your function: - $p->stickyGet('trigger_name'); +``` +$p->stickyGet('trigger_name'); +``` -This way - all the views added into this $p will carry an extra get argument:: +This way - all the views added into this $p will carry an extra get argument: - $p->url(); // includes "trigger_name=callback" +``` +$p->url(); // includes "trigger_name=callback" +``` If you call `$app->url()` it will contain `client_id` but won't contain the callbacks triggers. @@ -66,18 +71,20 @@ Agile UI views have a method View::url() which will return URL that is guarantee method. This is regardless of the placement of your View and also it honors all the arguments that are defined as sticky globally. -Consider this code:: +Consider this code: - $b1 = \Atk4\Ui\Button::addTo($app); - $b1->set($b1->url()); +``` +$b1 = \Atk4\Ui\Button::addTo($app); +$b1->set($b1->url()); - Loader::addTo($app)->set(function (Loader $p) { - $b2 = \Atk4\Ui\Button::addTo($p); - $b2->set($b2->url()); - }); +Loader::addTo($app)->set(function (Loader $p) { + $b2 = \Atk4\Ui\Button::addTo($p); + $b2->set($b2->url()); +}); - $b3 = \Atk4\Ui\Button::addTo($app); - $b3->set($b3->url()); +$b3 = \Atk4\Ui\Button::addTo($app); +$b3->set($b3->url()); +``` This will display 3 buttons and each button will contain a URL which needs to be opened in order for corresponding button to be initialized. Because middle button is inside a callback the URL for that diff --git a/docs/table.md b/docs/table.md index 88886ae14e..d4e93b2618 100644 --- a/docs/table.md +++ b/docs/table.md @@ -1,9 +1,6 @@ - .. _table: -===== -Table -===== +# Table .. php:namespace:: Atk4\Ui @@ -27,69 +24,73 @@ Main features of Table class are: - Can use Agile Data source or Static data. - Custom HTML, Format hooks -Basic Usage -=========== +## Basic Usage -The simplest way to create a table is when you use it with Agile Data model:: +The simplest way to create a table is when you use it with Agile Data model: - $table = Table::addTo($app); - $table->setModel(new Order($db)); +``` +$table = Table::addTo($app); +$table->setModel(new Order($db)); +``` The table will be able to automatically determine all the fields defined in your "Order" model, map them to appropriate column types, implement type-casting and also connect your model with the appropriate data source (database) $db. -Using with Array Data ---------------------- +### Using with Array Data -You can also use Table with Array data source like this:: +You can also use Table with Array data source like this: - $myArray = [ - ['name' => 'Vinny', 'surname' => 'Sihra', 'birthdate' => new \DateTime('1973-02-03')], - ['name' => 'Zoe', 'surname' => 'Shatwell', 'birthdate' => new \DateTime('1958-08-21')], - ['name' => 'Darcy', 'surname' => 'Wild', 'birthdate' => new \DateTime('1968-11-01')], - ['name' => 'Brett', 'surname' => 'Bird', 'birthdate' => new \DateTime('1988-12-20')], - ]; +``` +$myArray = [ + ['name' => 'Vinny', 'surname' => 'Sihra', 'birthdate' => new \DateTime('1973-02-03')], + ['name' => 'Zoe', 'surname' => 'Shatwell', 'birthdate' => new \DateTime('1958-08-21')], + ['name' => 'Darcy', 'surname' => 'Wild', 'birthdate' => new \DateTime('1968-11-01')], + ['name' => 'Brett', 'surname' => 'Bird', 'birthdate' => new \DateTime('1988-12-20')], +]; - $table = Table::addTo($app); - $table->setSource($myArray); +$table = Table::addTo($app); +$table->setSource($myArray); - $table->addColumn('name'); - $table->addColumn('surname', [\Atk4\Ui\Table\Column\Link::class, 'url' => 'details.php?surname={$surname}']); - $table->addColumn('birthdate', null, ['type' => 'date']); +$table->addColumn('name'); +$table->addColumn('surname', [\Atk4\Ui\Table\Column\Link::class, 'url' => 'details.php?surname={$surname}']); +$table->addColumn('birthdate', null, ['type' => 'date']); +``` .. warning:: I encourage you to seek appropriate Agile Data persistence instead of handling data like this. The implementation of :php:meth:`View::setSource` will create a model for you with Array persistence for you anyways. -Adding Columns --------------- +### Adding Columns .. php:method:: setModel(\Atk4\Data\Model $model, $fields = null) .. php:method:: addColumn($name, $columnDecorator = [], $field = null) To change the order or explicitly specify which field columns must appear, if you pass list of those -fields as second argument to setModel:: +fields as second argument to setModel: - $table = Table::addTo($app); - $table->setModel(new Order($db), ['name', 'price', 'amount', 'status']); +``` +$table = Table::addTo($app); +$table->setModel(new Order($db), ['name', 'price', 'amount', 'status']); +``` Table will make use of "Only Fields" feature in Agile Data to adjust query for fetching only the necessary columns. See also :ref:`field_visibility`. -You can also add individual column to your table:: +You can also add individual column to your table: - $table->setModel(new Order($db), []); // [] here means - don't add any fields by default - $table->addColumn('name'); - $table->addColumn('price'); +``` +$table->setModel(new Order($db), []); // [] here means - don't add any fields by default +$table->addColumn('name'); +$table->addColumn('price'); +``` When invoking addColumn, you have a great control over the field properties and decoration. The format of addColumn() is very similar to :php:meth:`Form::addControl`. -Calculations -============ +## Calculations Apart from adding columns that reflect current values of your database, there are several ways how you can calculate additional values. You must know the capabilities of your database server @@ -97,14 +98,16 @@ if you want to execute some calculation there. (See https://agile-data.readthedo It's always a good idea to calculate column inside database. Lets create "total" column which will multiply "price" and "amount" values. Use ``addExpression`` to provide in-line definition for this -field if it's not already defined in ``Order::init()``:: +field if it's not already defined in ``Order::init()``: - $table = Table::addTo($app); - $order = new Order($db); +``` +$table = Table::addTo($app); +$order = new Order($db); - $order->addExpression('total', '[price] * [amount]')->type = 'atk4_money'; +$order->addExpression('total', '[price] * [amount]')->type = 'atk4_money'; - $table->setModel($order, ['name', 'price', 'amount', 'total', 'status']); +$table->setModel($order, ['name', 'price', 'amount', 'total', 'status']); +``` The type of the Model Field determines the way how value is presented in the table. I've specified value to be 'atk4_money' which makes column align values to the right, format it with 2 decimal signs @@ -114,26 +117,26 @@ To learn about value formatting, read documentation on :ref:`uiPersistence`. Table object does not contain any information about your fields (such as captions) but instead it will consult your Model for the necessary field information. If you are willing to define the type but also -specify the caption, you can use code like this:: +specify the caption, you can use code like this: - $table = Table::addTo($app); - $order = new Order($db); +``` +$table = Table::addTo($app); +$order = new Order($db); - $order->addExpression('total', [ - '[price]*[amount]', - 'type' => 'atk4_money', - 'caption' => 'Total Price', - ]); +$order->addExpression('total', [ + '[price]*[amount]', + 'type' => 'atk4_money', + 'caption' => 'Total Price', +]); - $table->setModel($order, ['name', 'price', 'amount', 'total', 'status']); +$table->setModel($order, ['name', 'price', 'amount', 'total', 'status']); +``` -Column Objects --------------- +### Column Objects To read more about column objects, see :ref:`tablecolumn` -Advanced Column Denifitions ---------------------------- +### Advanced Column Denifitions Table defines a method `columnFactory`, which returns Column object which is to be used to display values of specific model Field. @@ -149,12 +152,14 @@ then to save memory Table will re-use the same objects for all generic fields. Contains array of defined columns. `addColumn` adds a new column to the table. This method was explained above but can also be -used to add columns without field:: +used to add columns without field: - $action = $this->addColumn(null, [Table\Column\ActionButtons::class]); - $action->addButton('Delete', function () { - return 'ok'; - }); +``` +$action = $this->addColumn(null, [Table\Column\ActionButtons::class]); +$action->addButton('Delete', function () { + return 'ok'; +}); +``` The above code will add a new extra column that will only contain 'delete' icon. When clicked it will automatically delete the corresponding record. @@ -172,27 +177,31 @@ just fine if column is not passed. If you do specify a string as a $name for addColumn, but no such field exist in the model, the method will rely on 3rd argument to create a new field for you. Here is example that calculates -the "total" column value (as above) but using PHP math instead of doing it inside database:: +the "total" column value (as above) but using PHP math instead of doing it inside database: - $table = Table::addTo($app); - $order = new Order($db); +``` +$table = Table::addTo($app); +$order = new Order($db); - $table->setModel($order, ['name', 'price', 'amount', 'status']); - $table->addColumn('total', new \Atk4\Data\Field\Calculated(function (Model $row) { - return $row->get('price') * $row->get('amount'); - })); +$table->setModel($order, ['name', 'price', 'amount', 'status']); +$table->addColumn('total', new \Atk4\Data\Field\Calculated(function (Model $row) { + return $row->get('price') * $row->get('amount'); +})); +``` If you execute this code, you'll notice that the "total" column is now displayed last. If you -wish to position it before status, you can use the final format of addColumn():: +wish to position it before status, you can use the final format of addColumn(): - $table = Table::addTo($app); - $order = new Order($db); +``` +$table = Table::addTo($app); +$order = new Order($db); - $table->setModel($order, ['name', 'price', 'amount']); - $table->addColumn('total', new \Atk4\Data\Field\Calculated(function (Model $row) { - return $row->get('price') * $row->get('amount'); - })); - $table->addColumn('status'); +$table->setModel($order, ['name', 'price', 'amount']); +$table->addColumn('total', new \Atk4\Data\Field\Calculated(function (Model $row) { + return $row->get('price') * $row->get('amount'); +})); +$table->addColumn('status'); +``` This way we don't populate the column through setModel() and instead populate it manually later through addColumn(). This will use an identical logic (see :php:meth:`Table::columnFactory`). For @@ -203,43 +212,47 @@ your convenience there is a way to add multiple columns efficiently. Here, names can be an array of strings (['status', 'price']) or contain array that will be passed as argument sto the addColumn method ([['total', $fieldDef], ['delete', $deleteColumn]); -As a final note in this section - you can re-use column objects multiple times:: +As a final note in this section - you can re-use column objects multiple times: - $colGap = new \Atk4\Ui\Table\Column\Template(' ... '); +``` +$colGap = new \Atk4\Ui\Table\Column\Template(' ... '); - $table->addColumn($colGap); - $table->setModel(new Order($db), ['name', 'price', 'amount']); - $table->addColumn($colGap); - $table->addColumns(['total', 'status']) - $table->addColumn($colGap); +$table->addColumn($colGap); +$table->setModel(new Order($db), ['name', 'price', 'amount']); +$table->addColumn($colGap); +$table->addColumns(['total', 'status']) +$table->addColumn($colGap); +``` This will result in 3 gap columns rendered to the left, middle and right of your Table. -Table sorting -============= +## Table sorting .. php:attr:: sortable .. php:attr:: sortBy .. php:attr:: sortDirection Table does not support an interactive sorting on it's own, (but :php:class:`Grid` does), however -you can designate columns to display headers as if table were sorted:: +you can designate columns to display headers as if table were sorted: - $table->sortable = true; - $table->sortBy = 'name'; - $table->sortDirection = 'asc'; +``` +$table->sortable = true; +$table->sortBy = 'name'; +$table->sortDirection = 'asc'; +``` This will highlight the column "name" header and will also display a sorting indicator as per sort order. -JavaScript Sorting ------------------- +### JavaScript Sorting You can make your table sortable through JavaScript inside your browser. This won't work well if -your data is paginated, because only the current page will be sorted:: +your data is paginated, because only the current page will be sorted: - $table->getApp()->includeJS('https://fomantic-ui.com/javascript/library/tablesort.js'); - $table->js(true)->tablesort(); +``` +$table->getApp()->includeJS('https://fomantic-ui.com/javascript/library/tablesort.js'); +$table->js(true)->tablesort(); +``` For more information see https://github.com/kylefox/jquery-tablesort @@ -247,54 +260,57 @@ For more information see https://github.com/kylefox/jquery-tablesort .. _table_html: -Injecting HTML --------------- - -The tag will override model value. Here is example usage of :php:meth:`Table\\Column::getHtmlTags`:: +### Injecting HTML +The tag will override model value. Here is example usage of :php:meth:`Table\\Column::getHtmlTags`: - class ExpiredColumn extends \Atk4\Ui\Table\Column - public function getDataCellHtml(): string - { - return '{$_expired}'; - } +``` +class ExpiredColumn extends \Atk4\Ui\Table\Column + public function getDataCellHtml(): string + { + return '{$_expired}'; + } - public function getHtmlTags(\Atk4\Data\Model $row, ?\Atk4\Data\Field $field): array - { - return [ - '_expired' => $field->get($row) < new \DateTime() - ? 'EXPIRED' - : '', - ]; - } + public function getHtmlTags(\Atk4\Data\Model $row, ?\Atk4\Data\Field $field): array + { + return [ + '_expired' => $field->get($row) < new \DateTime() + ? 'EXPIRED' + : '', + ]; } +} +``` -Your column now can be added to any table:: +Your column now can be added to any table: - $table->addColumn(new ExpiredColumn()); +``` +$table->addColumn(new ExpiredColumn()); +``` IMPORTANT: HTML injection will work unless :php:attr:`Table::useHtmlTags` property is disabled (for performance). -Table Data Handling -=================== +## Table Data Handling Table is very similar to :php:class:`Lister` in the way how it loads and displays data. To control which data Table will be displaying you need to properly specify the model and persistence. The following two examples will show you how to display list of "files" inside your Dropbox folder and how to display list -of issues from your Github repository:: +of issues from your Github repository: - // Show contents of dropbox - $dropbox = \Atk4\Dropbox\Persistence($dbConfig); - $files = new \Atk4\Dropbox\Model\File($dropbox); +``` +// Show contents of dropbox +$dropbox = \Atk4\Dropbox\Persistence($dbConfig); +$files = new \Atk4\Dropbox\Model\File($dropbox); - Table::addTo($app)->setModel($files); +Table::addTo($app)->setModel($files); - // Show contents of dropbox - $github = \Atk4\Github\IssuePersistence($githubApiConfig); - $issues = new \Atk4\Github\Model\Issue($github); +// Show contents of dropbox +$github = \Atk4\Github\IssuePersistence($githubApiConfig); +$issues = new \Atk4\Github\Model\Issue($github); - Table::addTo($app)->setModel($issues); +Table::addTo($app)->setModel($issues); +``` This example demonstrates that by selecting a 3rd party persistence implementation, you can access virtually any API, Database or SQL resource and it will always take care of formatting for you as well @@ -309,8 +325,7 @@ all the different data persitences. (see :php:ref:`universal_data_access`) For most applications, however, you would be probably using internally defined models that rely on data stored inside your own database. Either way, several principles apply to the way how Table works. -Table Rendering Steps ---------------------- +### Table Rendering Steps Once model is specified to the Table it will keep the object until render process will begin. Table columns can be defined any time and will be stored in the :php:attr:`Table::columns` property. Columns @@ -334,8 +349,7 @@ During the render process (see :php:meth:`View::renderView`) Table will perform 4. If no rows were displayed, then "empty message" will be shown (see :php:attr:`Table::tEmpty`). 5. If :php:meth:`addTotals` was used, append totals row to table footer. -Dealing with Multiple decorators -================================ +## Dealing with Multiple decorators .. php:method:: addDecorator($name, $columnDecorator) @@ -350,10 +364,12 @@ nicer especially inside a table. is not limited to the table columns. Decorators may add an icon, change cell style, align cell or hide overflowing text to make table output look better. -One column may have several decorators:: +One column may have several decorators: - $table->addColumn('salary', new \Atk4\Ui\Table\Column\Money()); - $table->addDecorator('salary', new \Atk4\Ui\Table\Column\Link(['page2'])); +``` +$table->addColumn('salary', new \Atk4\Ui\Table\Column\Money()); +$table->addDecorator('salary', new \Atk4\Ui\Table\Column\Link(['page2'])); +``` In this case the first decorator will take care of tr/td tags but second decorator will compliment it. Result is that table will output 'salary' as a currency (align and red ink) and also decorate @@ -375,9 +391,11 @@ There are a few things to note: :php:meth:`Table\\Column\\\Money::getDataCellTemplate` is called, which returns ONLY the HTML value, without the cell itself. Subsequently :php:meth:`Table\\Column\\\Link::getDataCellTemplate` is called and the '{$salary}' tag from this link is replaced by output from Money column resulting in this -template:: +template: - £ {$salary} +``` +£ {$salary} +``` To calculate which tag should be used, a different approach is done. Attributes for tag from Money are collected then merged with attributes of a Link class. The money column wishes @@ -385,16 +403,20 @@ to add class "right aligned single line" to the tag but sometimes it may al class "negative". The way how it's done is by defining `class="{$f_name_money}"` as one of the TD properties. -The link does add any TD properties so the resulting "td" tag would be:: +The link does add any TD properties so the resulting "td" tag would be: - ['class' => ['{$f_name_money}'] ] +``` +['class' => ['{$f_name_money}'] ] - // would produce .. +// would produce .. +``` Combined with the field template generated above it provides us with a full cell -template:: +template: - £ {$salary} +``` +£ {$salary} +``` Which is concatenated with other table columns just before rendering starts. The actual template is formed by calling. This may be too much detail, so if you need @@ -405,76 +427,78 @@ to make a note on how template caching works then, - properties are stacked - last decorator will convert array with td properties into an actual tag. -Header and Footer ------------------ +### Header and Footer + When using with multiple decorators, the last decorator gets to render Header cell. The footer (totals) uses the same approach for generating template, however a different methods are called from the columns: getTotalsCellTemplate -Redefining ----------- +### Redefining If you are defining your own column, you may want to re-define getDataCellTemplate. The getDataCellHtml can be left as-is and will be handled correctly. If you have overridden getDataCellHtml only, then your column will still work OK provided that it's used as a last decorator. -Advanced Usage -============== +## Advanced Usage Table is a very flexible object and can be extended through various means. This chapter will focus on various requirements and will provide a way how to achieve that. -Toolbar, Quick-search and Paginator ------------------------------------ +### Toolbar, Quick-search and Paginator See :php:class:`Grid` -JsPaginator ------------ +### JsPaginator .. php:method:: addJsPaginator($ipp, $options = [], $container = null, $scrollRegion = 'Body') JsPaginator will load table content dynamically when user scroll down the table window on screen. - $table->addJsPaginator(30); +``` +$table->addJsPaginator(30); +``` See also :php:meth:`Lister::addJsPaginator` -Resizable Columns ------------------ +### Resizable Columns .. php:method:: resizableColumn($fx = null, $widths = null, $resizerOptions = []) -Each table's column width can be resize by dragging the column right border:: +Each table's column width can be resize by dragging the column right border: - $table->resizableColumn(); +``` +$table->resizableColumn(); +``` You may specify a callback function to the method. The callback will return an array containing each -column name in table with their new width in pixel.:: +column name in table with their new width in pixel.: - $table->resizableColumn(function (Jquery $j, array $columnWidths) { - // do something with new column widths - }, [200, 300, 100, 100, 100]); +``` +$table->resizableColumn(function (Jquery $j, array $columnWidths) { + // do something with new column widths +}, [200, 300, 100, 100, 100]); +``` Note that you may specify an array of integer representing the initial width value in pixel for each column in your table. Finally you may also specify some of the resizer options - https://github.com/Bayer-Group/column-resizer#options -Column attributes and classes -============================= +## Column attributes and classes + By default Table will include ID for each row: ``. The following code example -demonstrates how various standard column types are relying on this property:: +demonstrates how various standard column types are relying on this property: - $table->on('click', 'td', new JsExpression( - 'document.location = \'page.php?id=\' + []', - [(new Jquery())->closest('tr')->data('id')] - )); +``` +$table->on('click', 'td', new JsExpression( + 'document.location = \'page.php?id=\' + []', + [(new Jquery())->closest('tr')->data('id')] +)); +``` See also :ref:`js`. -Static Attributes and classes ------------------------------ +### Static Attributes and classes .. php:class:: Table\\Column @@ -484,28 +508,38 @@ Static Attributes and classes The following code will make sure that contents of the column appear on a single line by -adding class "single line" to all body cells:: +adding class "single line" to all body cells: - $table->addColumn('name', (new \Atk4\Ui\Table\Column()->addClass('single line'))); +``` +$table->addColumn('name', (new \Atk4\Ui\Table\Column()->addClass('single line'))); +``` -If you wish to add a class to 'head' or 'foot' or 'all' cells, you can pass 2nd argument to addClass:: +If you wish to add a class to 'head' or 'foot' or 'all' cells, you can pass 2nd argument to addClass: - $table->addColumn('name', (new \Atk4\Ui\Table\Column()->addClass('right aligned', 'all'))); +``` +$table->addColumn('name', (new \Atk4\Ui\Table\Column()->addClass('right aligned', 'all'))); +``` -There are several ways to make your code more readable:: +There are several ways to make your code more readable: - $table->addColumn('name', new \Atk4\Ui\Table\Column()) - ->addClass('right aligned', 'all'); +``` +$table->addColumn('name', new \Atk4\Ui\Table\Column()) + ->addClass('right aligned', 'all'); +``` -Or if you wish to use factory, the syntax is:: +Or if you wish to use factory, the syntax is: - $table->addColumn('name', [\Atk4\Ui\Table\Column::class]) - ->addClass('right aligned', 'all'); +``` +$table->addColumn('name', [\Atk4\Ui\Table\Column::class]) + ->addClass('right aligned', 'all'); +``` -For setting an attribute you can use setAttr() method:: +For setting an attribute you can use setAttr() method: - $table->addColumn('name', [\Atk4\Ui\Table\Column::class]) - ->setAttr('colspan', 2, 'all'); +``` +$table->addColumn('name', [\Atk4\Ui\Table\Column::class]) + ->setAttr('colspan', 2, 'all'); +``` Setting a new value to the attribute will override previous value. @@ -519,16 +553,15 @@ classes, you should generate your TD/TH tag through getTag method. Will apply cell-based attributes or classes then use :php:meth:`App::getTag` to generate HTML tag and encode it's content. -Columns without fields ----------------------- - -You can add column to a table that does not link with field:: +### Columns without fields - $cb = $table->addColumn('CheckBox'); +You can add column to a table that does not link with field: +``` +$cb = $table->addColumn('CheckBox'); +``` -Using dynamic values --------------------- +### Using dynamic values Body attributes will be embedded into the template by the default :php:meth:`Table\\Column::getDataCellHtml`, but if you specify attribute (or class) value as a tag, then it will be auto-filled diff --git a/docs/tablecolumn.md b/docs/tablecolumn.md index 80f077e30c..147e46a968 100644 --- a/docs/tablecolumn.md +++ b/docs/tablecolumn.md @@ -1,11 +1,8 @@ - .. _tablecolumn: .. php:namespace:: Atk4\Ui -======================= -Table Column Decorators -======================= +# Table Column Decorators Classes like :php:class:`Table` and :php:class:`Card` do not render their cell contents themselves. Instead they rely on Column Decorator class to position content within the @@ -28,8 +25,7 @@ A final mention is about :php:class:`Multiformat`, which is a column decorator t any other decorator based on condition. This allows you to change button [Archive] for active records, but if record is already archived, use a template "Archived on {$archive_date}". -Generic Column Decorator -======================== +## Generic Column Decorator .. php:class:: Table\\Column @@ -63,14 +59,18 @@ fields once. When iterating, a combined template will be used to display the val The template must not incorporate field values (simply because related model will not be loaded just yet), but instead should resort to tags and syntax compatible with :php:class:`Template`. -A sample template could be:: +A sample template could be: - {$name} +``` +{$name} +``` Note that the "name" here must correspond with the field name inside the Model. You may use -multiple field names to format the column:: +multiple field names to format the column: - {$year}-{$month}-{$day} +``` +{$year}-{$month}-{$day} +``` The above 3 methods define first argument as a field, however it's possible to define column without a physical field. This makes sense for situations when column contains multiple field @@ -83,97 +83,100 @@ Sometimes you do want to inject HTML instead of using row values: Return array of HTML tags that will be injected into the row template. See :php:ref:`table_html` for further example. -Column Menus and Popups -======================= +## Column Menus and Popups Table column may have a menu as seen in https://ui.agiletoolkit.org/demos/tablecolumnmenu.php. Menu is added into table column and can be linked with Popup or Menu. -Basic Use ---------- +### Basic Use -The simplest way to use Menus and Popups is through a wrappers: :php:meth:`Atk4\\Ui\\Grid::addDropdown` and :php:meth:`Atk4\\Ui\\Grid::addPopup`:: +The simplest way to use Menus and Popups is through a wrappers: :php:meth:`Atk4\\Ui\\Grid::addDropdown` and :php:meth:`Atk4\\Ui\\Grid::addPopup`: - View::addTo($grid->addPopup('iso')) - ->set('Grid column popup text'); +``` +View::addTo($grid->addPopup('iso')) + ->set('Grid column popup text'); - // OR +// OR - $grid->addDropdown('name', ['Sort A-Z', 'Sort by Relevance'], function (string $item) { - return $item; - }); +$grid->addDropdown('name', ['Sort A-Z', 'Sort by Relevance'], function (string $item) { + return $item; +}); +``` Those wrappers will invoke methods :php:meth:`Table\\Column::addDropdown` and :php:meth:`Table\\Colmun::addPopup` for a specified column, which are documented below. -Popups ------- +### Popups .. php:method:: addPopup() To create a popup, you need to get the column decorator object. This must be the first decorator, which is responsible for rendering of the TH box. If you are adding column manually, :php:meth:`Atk4\\Ui\\Table::addColumn()` -will return it. When using model, use :php:meth:`Atk4\\Ui\\Table::getColumnDecorators`:: - +will return it. When using model, use :php:meth:`Atk4\\Ui\\Table::getColumnDecorators`: - $table = Table::addTo($app, ['class.celled' => true]); - $table->setModel(new Country($app->db)); +``` +$table = Table::addTo($app, ['class.celled' => true]); +$table->setModel(new Country($app->db)); - $nameColumn = $table->getColumnDecorators('name'); - LoremIpsum::addTo($nameColumn[0]->addPopup()); +$nameColumn = $table->getColumnDecorators('name'); +LoremIpsum::addTo($nameColumn[0]->addPopup()); +``` .. important:: If content of a pop-up is too large, it may not be possible to display it on-screen. Watch for warning. -You may also use :php:meth:`Atk4\\Ui\\Popup::set` method to dynamically load the content:: +You may also use :php:meth:`Atk4\\Ui\\Popup::set` method to dynamically load the content: +``` +$table = Table::addTo($app, ['class.celled' => true]); +$table->setModel(new Country($app->db)); - $table = Table::addTo($app, ['class.celled' => true]); - $table->setModel(new Country($app->db)); +$nameColumn = $table->getColumnDecorators('name'); +$nameColumn[0]->addPopup()->set(function (View $p) { + HelloWorld::addTo($p); +}); +``` - $nameColumn = $table->getColumnDecorators('name'); - $nameColumn[0]->addPopup()->set(function (View $p) { - HelloWorld::addTo($p); - }); - -Dropdown Menus --------------- +### Dropdown Menus .. php:method:: addDropdown() -Menus will show item selection and will trigger a callback when user selects one of them:: - - $someColumn->addDropdown(['Change', 'Reorder', 'Update'], function (string $item) { - return 'Title item: ' . $item; - }); +Menus will show item selection and will trigger a callback when user selects one of them: +``` +$someColumn->addDropdown(['Change', 'Reorder', 'Update'], function (string $item) { + return 'Title item: ' . $item; +}); +``` -Decorators for data types -========================= +## Decorators for data types In addition to :php:class:`Table\\Column`, Agile UI includes several column implementations. -Link ----- +### Link .. php:class:: Table\\Column\\Link Put `addColumn('name', [\Atk4\Ui\Table\Column\Link::class, 'https://google.com/?q={$name}']); +``` +$table->addColumn('name', [\Atk4\Ui\Table\Column\Link::class, 'https://google.com/?q={$name}']); +``` -The URL may also be specified as an array. It will be passed to App::url() which will encode arguments:: +The URL may also be specified as an array. It will be passed to App::url() which will encode arguments: - $table->addColumn('name', [\Atk4\Ui\Table\Column\Link::class, ['details', 'id' => 123, 'q' => $anything]]); +``` +$table->addColumn('name', [\Atk4\Ui\Table\Column\Link::class, ['details', 'id' => 123, 'q' => $anything]]); +``` In this case even if `$anything = '{$name}'` the substitution will not take place for safety reasons. To -pass on some values from your model, use second argument to constructor:: +pass on some values from your model, use second argument to constructor: - $table->addColumn('name', [\Atk4\Ui\Table\Column\Link::class, ['details', 'id' => 123], ['q' => 'name']]); +``` +$table->addColumn('name', [\Atk4\Ui\Table\Column\Link::class, ['details', 'id' => 123], ['q' => 'name']]); +``` - -Money ------ +### Money .. php:class:: Table\\Column\\Money @@ -182,8 +185,7 @@ use red text (td class "negative" for Fomantic-UI). The money cells are not wrap For the actual number formatting, see :ref:`uiPersistence` -Status ------- +### Status .. php:class:: Table\\Column\\Status @@ -191,15 +193,16 @@ Allow you to set highlight class and icon based on column value. This is most su contain pre-defined values. If your column "status" can be one of the following "pending", "declined", "archived" and "paid" and you would like -to use different icons and colors to emphasise status:: - +to use different icons and colors to emphasise status: - $states = [ - 'positive' => ['paid', 'archived'], - 'negative' => ['declined'], - ]; +``` +$states = [ + 'positive' => ['paid', 'archived'], + 'negative' => ['declined'], +]; - $table->addColumn('status', new \Atk4\Ui\Table\Column\Status($states)); +$table->addColumn('status', new \Atk4\Ui\Table\Column\Status($states)); +``` Current list of states supported: @@ -209,75 +212,83 @@ Current list of states supported: (list of states may be expanded further) -Template --------- +### Template .. php:class:: Table\\Column\\Template This column is suitable if you wish to have custom cell formatting but do not wish to go through the trouble of setting up your own class. -If you wish to display movie rating "4 out of 10" based around the column "rating", you can use:: +If you wish to display movie rating "4 out of 10" based around the column "rating", you can use: - $table->addColumn('rating', new \Atk4\Ui\Table\Column\Template('{$rating} out of 10')); +``` +$table->addColumn('rating', new \Atk4\Ui\Table\Column\Template('{$rating} out of 10')); +``` Template may incorporate values from multiple fields in a data row, but current implementation will only work if you assign it to a primary column (by passing 1st argument to addColumn). (In the future it may be optional with the ability to specify caption). -Image ------ +### Image .. php:class:: Table\\Column\\Image -This column is suitable if you wish to have image in your table cell:: +This column is suitable if you wish to have image in your table cell: - $table->addColumn('image_url', new \Atk4\Ui\Table\Column\Image); +``` +$table->addColumn('image_url', new \Atk4\Ui\Table\Column\Image); +``` +## Interactive Decorators -Interactive Decorators -====================== - -ActionButtons -------------- +### ActionButtons .. php:class:: Table\\Column\\ActionButtons -Can be used to add "action buttons" column to your table:: +Can be used to add "action buttons" column to your table: - $action = $table->addColumn(null, [Table\Column\ActionButtons::class]); +``` +$action = $table->addColumn(null, [Table\Column\ActionButtons::class]); +``` -If you want to have label above the action column, then:: +If you want to have label above the action column, then: - $action = $table->addColumn(null, [Table\Column\ActionButtons::class, 'caption' => 'User Actions']); +``` +$action = $table->addColumn(null, [Table\Column\ActionButtons::class, 'caption' => 'User Actions']); +``` .. php:method:: addButton($button, $action, $confirm = false) Adds another button into "Actions" column which will perform a certain JavaScript action when clicked. -See also :php:meth:`Atk4\\Ui\\Grid::addActionButton()`:: +See also :php:meth:`Atk4\\Ui\\Grid::addActionButton()`: - $button = $action->addButton('Reload Table', $table->jsReload()); +``` +$button = $action->addButton('Reload Table', $table->jsReload()); +``` Normally you would also want to pass the ID of the row which was clicked. You can use :php:meth:`Atk4\\Ui\\Table:jsRow()` -and jQuery's data() method to reference it:: +and jQuery's data() method to reference it: - $button = $action->addButton('Reload Table', $table->jsReload(['clicked' => $table->jsRow()->data('id')])); +``` +$button = $action->addButton('Reload Table', $table->jsReload(['clicked' => $table->jsRow()->data('id')])); +``` Moreover you may pass $action argument as a PHP callback. .. php:method:: addModal($button, $title, $callback) -Triggers a modal dialog when you click on the button. See description on :php:meth:`Atk4\\Ui\\Grid::addModalAction()`:: +Triggers a modal dialog when you click on the button. See description on :php:meth:`Atk4\\Ui\\Grid::addModalAction()`: - $action->addButton(['Say HI'], function (Jquery $j, $id) use ($g) { - return 'Loaded "' . $g->model->load($id)->get('name') . '" from ID=' . $id; - }); +``` +$action->addButton(['Say HI'], function (Jquery $j, $id) use ($g) { + return 'Loaded "' . $g->model->load($id)->get('name') . '" from ID=' . $id; +}); +``` Note that in this case ID is automatically passed to your callback. -Checkbox --------- +### Checkbox .. php:class:: Table\\Column\\Checkbox @@ -286,31 +297,33 @@ Checkbox Adding this column will render checkbox for each row. This column must not be used on a field. CheckBox column provides you with a handy jsChecked() method, which you can use to reference current item selection. The next code will allow you to select the checkboxes, and when you -click on the button, it will reload $segment component while passing all the id's:: +click on the button, it will reload $segment component while passing all the id's: - $box = $table->addColumn(new \Atk4\Ui\Table\Column\CheckBox()); +``` +$box = $table->addColumn(new \Atk4\Ui\Table\Column\CheckBox()); - $button->on('click', new JsReload($segment, ['ids' => $box->jsChecked()])); +$button->on('click', new JsReload($segment, ['ids' => $box->jsChecked()])); +``` jsChecked expression represents a JavaScript string which you can place inside a form control, use as argument etc. - -Multiformat ------------ +### Multiformat Sometimes your formatting may change depending on value. For example you may want to place link -only on certain rows. For this you can use an `\\Atk4\Ui\\Table\\Column\\Multiformat` decorator:: +only on certain rows. For this you can use an `\\Atk4\Ui\\Table\\Column\\Multiformat` decorator: - $table->addColumn('amount', [\Atk4\Ui\Table\Column\Multiformat::class, function (Model $model) { - if ($model->get('is_invoiced') > 0) { - return [\Atk4\Ui\Table\Column\Money::class, [\Atk4\Ui\Table\Column\Link::class, 'invoice', ['invoice_id' => 'id']]]; - } elseif (abs($model->get('is_refunded')) < 50) { - return [[\Atk4\Ui\Table\Column\Template::class, 'Amount was refunded']]; - } +``` +$table->addColumn('amount', [\Atk4\Ui\Table\Column\Multiformat::class, function (Model $model) { + if ($model->get('is_invoiced') > 0) { + return [\Atk4\Ui\Table\Column\Money::class, [\Atk4\Ui\Table\Column\Link::class, 'invoice', ['invoice_id' => 'id']]]; + } elseif (abs($model->get('is_refunded')) < 50) { + return [[\Atk4\Ui\Table\Column\Template::class, 'Amount was refunded']]; + } - return [[\Atk4\Ui\Table\Column\Money::class]]; - }]); + return [[\Atk4\Ui\Table\Column\Money::class]]; +}]); +``` You supply a callback to the Multiformat decorator, which will then be used to determine the actual set of decorators to be used on a given row. The example above will look at various @@ -318,7 +331,9 @@ fields of your models and will conditionally add Link on top of Money formatting The callback must return array of seeds like: - [[\Atk4\Ui\Table\Column\Link::class], \Atk4\Ui\Table\Column\Money::class] +``` +[[\Atk4\Ui\Table\Column\Link::class], \Atk4\Ui\Table\Column\Money::class] +``` Multiple decorators will be created and merged. diff --git a/docs/tabs.md b/docs/tabs.md index e39eef3b10..38f8d17032 100644 --- a/docs/tabs.md +++ b/docs/tabs.md @@ -1,29 +1,28 @@ - .. php:namespace:: Atk4\Ui .. php:class:: Tabs -==== -Tabs -==== +# Tabs Tabs implement a yet another way to organize your data. The implementation is based on: https://fomantic-ui.com/elements/icon.html. Demo: https://ui.agiletoolkit.org/demos/tabs.php +## Basic Usage -Basic Usage -=========== - -Once you create Tabs container you can then mix and match static and dynamic tabs:: +Once you create Tabs container you can then mix and match static and dynamic tabs: - $tabs = Tabs::addTo($app); +``` +$tabs = Tabs::addTo($app); +``` -Adding a static content is pretty simple:: +Adding a static content is pretty simple: - LoremIpsum::addTo($tabs->addTab('Static Tab')); +``` +LoremIpsum::addTo($tabs->addTab('Static Tab')); +``` You can add multiple elements into a single tab, like any other view. @@ -34,56 +33,59 @@ Use addTab() method to add more tabs in Tabs view. First parameter is a title of Tabs can be static or dynamic. Dynamic tabs use :php:class:`VirtualPage` implementation mentioned above. You should pass Closure action as a second parameter. -Example:: +Example: - $tabs = Tabs::addTo($layout); +``` +$tabs = Tabs::addTo($layout); - // add static tab - HelloWorld::addTo($tabs->addTab('Static Tab')); +// add static tab +HelloWorld::addTo($tabs->addTab('Static Tab')); - // add dynamic tab - $tabs->addTab('Dynamically Loading', function (VirtualPage $vp) { - LoremIpsum::addTo($vp); - }); +// add dynamic tab +$tabs->addTab('Dynamically Loading', function (VirtualPage $vp) { + LoremIpsum::addTo($vp); +}); +``` -Dynamic Tabs -============ +## Dynamic Tabs Dynamic tabs are based around implementation of :php:class:`VirtualPage` and allow you to pass a callback which will be triggered when user clicks on the tab. -Note that tab contents are refreshed including any values you put on the form:: +Note that tab contents are refreshed including any values you put on the form: - $tabs = Tabs::addTo($app); +``` +$tabs = Tabs::addTo($app); - // dynamic tab - $tabs->addTab('Dynamic Lorem Ipsum', function (VirtualPage $vp) { - LoremIpsum::addTo($vp, ['size' => 2]); - }); +// dynamic tab +$tabs->addTab('Dynamic Lorem Ipsum', function (VirtualPage $vp) { + LoremIpsum::addTo($vp, ['size' => 2]); +}); - // dynamic tab - $tabs->addTab('Dynamic Form', function (VirtualPage $vp) { - $mRegister = new \Atk4\Data\Model(new \Atk4\Data\Persistence\Array_($a)); - $mRegister->addField('name', ['caption' => 'Please enter your name (John)']); - - $form = Form::addTo($vp, ['class.segment' => true]); - $form->setModel($mRegister); - $form->onSubmit(function (Form $form) { - if ($form->model->get('name') !== 'John') { - return $form->jsError('name', 'Your name is not John! It is "' . $form->model->get('name') . '". It should be John. Pleeease!'); - } - }); - }); +// dynamic tab +$tabs->addTab('Dynamic Form', function (VirtualPage $vp) { + $mRegister = new \Atk4\Data\Model(new \Atk4\Data\Persistence\Array_($a)); + $mRegister->addField('name', ['caption' => 'Please enter your name (John)']); + $form = Form::addTo($vp, ['class.segment' => true]); + $form->setModel($mRegister); + $form->onSubmit(function (Form $form) { + if ($form->model->get('name') !== 'John') { + return $form->jsError('name', 'Your name is not John! It is "' . $form->model->get('name') . '". It should be John. Pleeease!'); + } + }); +}); +``` -URL Tabs -======== +## URL Tabs .. php:method:: addTabUrl($name, $url) -Tab can load external URL or a different page if you prefer that instead of VirtualPage. This works similar to iframe:: +Tab can load external URL or a different page if you prefer that instead of VirtualPage. This works similar to iframe: - $tabs = Tabs::addTo($app); +``` +$tabs = Tabs::addTo($app); - $tabs->addTabUrl('Terms and Condition', 'terms.html'); +$tabs->addTabUrl('Terms and Condition', 'terms.html'); +``` diff --git a/docs/template.md b/docs/template.md index 56ff238810..26f730e486 100644 --- a/docs/template.md +++ b/docs/template.md @@ -1,9 +1,6 @@ - - .. _Template: -Introduction -============ +## Introduction Agile UI relies on a lightweight built-in template engine to manipulate templates. The design goals of a template engine are: @@ -14,50 +11,58 @@ The design goals of a template engine are: - Allow preserving template content as much as possible -Example Template -================ +## Example Template -Assuming that you have the following template:: +Assuming that you have the following template: - Hello, {mytag}world{/} +``` +Hello, {mytag}world{/} +``` -Tags ----- +### Tags the usage of `{` denotes a "tag" inside your HTML, which must be followed by alpha-numeric identifier and a closing `}`. Tag needs to be closed with either `{/mytag}` or `{/}`. -The following code will initialize template inside a PHP code:: +The following code will initialize template inside a PHP code: - $t = new Template('Hello, {mytag}world{/}'); +``` +$t = new Template('Hello, {mytag}world{/}'); +``` Once template is initialized you can `renderToHtml()` it any-time to get string -"Hello, world". You can also change tag value:: +"Hello, world". You can also change tag value: - $t->set('mytag', 'Agile UI'); +``` +$t->set('mytag', 'Agile UI'); - echo $t->renderToHtml(); // "Hello, Agile UI". +echo $t->renderToHtml(); // "Hello, Agile UI". +``` -Tags may also be self-closing:: +Tags may also be self-closing: - Hello, {$mytag} +``` +Hello, {$mytag} +``` -is idetnical to:: +is idetnical to: - Hello, {mytag}{/} +``` +Hello, {mytag}{/} +``` +### Regions -Regions -------- +We call region a tag, that may contain other tags. Example: -We call region a tag, that may contain other tags. Example:: +``` +Hello, {$name} - Hello, {$name} - - {Content} - User {$user} has sent you {$amount} dollars. - {/Content} +{Content} +User {$user} has sent you {$amount} dollars. +{/Content} +``` When this template is parsed, region 'Content' will contain tags $user and $amount. Although technically you can still use `set()` @@ -65,32 +70,34 @@ to change value of a tag even if it's inside a region, we often use Region to delegate rendering to another View (more about this in section for Views). -There are some operations you can do with a region, such as:: +There are some operations you can do with a region, such as: - $content = $mainTemplate->cloneRegion('Content'); +``` +$content = $mainTemplate->cloneRegion('Content'); - $mainTemplate->del('Content'); +$mainTemplate->del('Content'); - $content->set('user', 'Joe')->set('amount', 100); - $mainTemplate->dangerouslyAppendHtml('Content', $content->renderToHtml()); +$content->set('user', 'Joe')->set('amount', 100); +$mainTemplate->dangerouslyAppendHtml('Content', $content->renderToHtml()); - $content->set('user', 'Billy')->set('amount', 50); - $mainTemplate->dangerouslyAppendHtml('Content', $content->renderToHtml()); +$content->set('user', 'Billy')->set('amount', 50); +$mainTemplate->dangerouslyAppendHtml('Content', $content->renderToHtml()); +``` -Usage in Agile UI ------------------ +### Usage in Agile UI In practice, however, you will rarely have to work with the template -engine directly, but you would be able to use it through views:: - +engine directly, but you would be able to use it through views: - $v = new View('my_template.html'); - $v->template->set('name', 'Mr. Boss'); +``` +$v = new View('my_template.html'); +$v->template->set('name', 'Mr. Boss'); - $lister = new Lister($v, 'Content'); - $lister->setModel($userlist); +$lister = new Lister($v, 'Content'); +$lister->setModel($userlist); - echo $v->renderToHtml(); +echo $v->renderToHtml(); +``` The code above will work like this: @@ -106,9 +113,7 @@ The code above will work like this: appending value of the rendered region back to $v. Finally the $v will render itself and echo result. - -Detailed Template Manipulation -============================== +## Detailed Template Manipulation As I have mentioned, most Views will handle template for you. You need to learn about template manipulations if you are designing custom view that @@ -116,9 +121,7 @@ needs to follow some advanced patterns. .. php:class:: Template -Template Loading ----------------- - +### Template Loading Array containing a structural representation of the template. When you create new template object, you can pass template as an argument to a @@ -146,8 +149,7 @@ Alternatively, if you wish to load template from a file: If the template is already loaded, you can load another template from another source which will override the existing one. -Template Parsing ----------------- +### Template Parsing .. note:: Older documentation...... @@ -166,15 +168,17 @@ same way as empty tag. (``{$elephant}``) Region — typically a multiple lines HTML and text between opening and closing tag which can contain a nested tags. Regions are typically named -with PascalCase, while other tags are named using ``snake_case``:: +with PascalCase, while other tags are named using ``snake_case``: - some text before - {ElephantBlock} - Hello, {$name}. +``` +some text before +{ElephantBlock} + Hello, {$name}. - by {sender}John Smith{/} - {/ElephantBlock} - some text after + by {sender}John Smith{/} +{/ElephantBlock} +some text after +``` In the example above, ``sender`` and ``name`` are nested tags. @@ -184,8 +188,7 @@ all of it's nested tags are also preserved. Top Tag - a tag representing a Region containing all of the template. Typically is called _top. -Manually template usage pattern -------------------------------- +### Manually template usage pattern Template engine in Agile Toolkit can be used independently, without views if you require so. A typical workflow would be: @@ -196,9 +199,7 @@ if you require so. A typical workflow would be: 2. Set tag and region values with :php:meth:`GiTemplate::set`. 3. Render template with :php:meth:`GiTemplate::renderToHtml`. - -Template use together with Views --------------------------------- +### Template use together with Views A UI Framework such as Agile Toolkit puts quite specific requirements on template system. In case with Agile Toolkit, the following pattern @@ -237,15 +238,12 @@ of a Form. | | other filters are not supported (yet) | +---------------------------------------------------+-------------------------------------------------------+ -Using Template Engine directly -============================== +## Using Template Engine directly Although you might never need to use template engine, understanding how it's done is important to completely grasp Agile Toolkit underpinnings. - -Loading template ----------------- +### Loading template .. php:method:: loadFromString(string) @@ -278,26 +276,30 @@ Loading template Template can be loaded from either file or string by using one of -following commands:: - +following commands: - $template = GiTemplate::addTo($this); +``` +$template = GiTemplate::addTo($this); - $template->loadFromString('Hello, {name}world{/}'); +$template->loadFromString('Hello, {name}world{/}'); +``` -To load template from file:: +To load template from file: - $template->loadFromFile('mytemplate'); +``` +$template->loadFromFile('mytemplate'); +``` -And place the following inside ``template/mytemplate.html``:: +And place the following inside ``template/mytemplate.html``: - Hello, {name}world{/} +``` +Hello, {name}world{/} +``` GiTemplate will use :php:class:`PathFinder` to locate template in one of the directories of :ref:`resource` ``template``. -Changing template contents --------------------------- +### Changing template contents .. php:method:: set(tag, value) @@ -324,101 +326,109 @@ Changing template contents Attempts to append non-escaped value, but will do nothing if tag does not exist. -Example:: +Example: - $template = GiTemplate::addTo($this); +``` +$template = GiTemplate::addTo($this); - $template->loadFromString('Hello, {name}world{/}'); +$template->loadFromString('Hello, {name}world{/}'); - $template->set('name', 'John'); - $template->dangerouslyAppendHtml('name', ' '); +$template->set('name', 'John'); +$template->dangerouslyAppendHtml('name', ' '); - echo $template->renderToHtml(); +echo $template->renderToHtml(); +``` Using ArrayAccess with Templates ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -You may use template object as array for simplified syntax:: - - $template->set('name', 'John'); +You may use template object as array for simplified syntax: - if ($template->hasTag('has_title')) { - $template->del('has_title'); - } +``` +$template->set('name', 'John'); +if ($template->hasTag('has_title')) { + $template->del('has_title'); +} +``` -Rendering template ------------------- +### Rendering template .. php:method:: renderToHtml Converts template into one string by removing tag markers. Ultimately we want to convert template into something useful. Rendering -will return contents of the template without tags:: +will return contents of the template without tags: - $result = $template->renderToHtml(); +``` +$result = $template->renderToHtml(); - \Atk4\Ui\Text::addTo($this)->set($result); - // Will output "Hello, World" +\Atk4\Ui\Text::addTo($this)->set($result); +// Will output "Hello, World" +``` - -Template cloning ----------------- +### Template cloning When you have nested tags, you might want to extract some part of your template and render it separately. For example, you may have 2 tags SenderAddress and ReceiverAddress each containing nested tags such as "name", "city", "zip". You can't use set('name') because it will affect both names for sender and receiver. Therefore you need to use cloning. -Let's assume you have the following template in ``template/envelope.html``:: +Let's assume you have the following template in ``template/envelope.html``: -
- {Sender} - {$name}, - Address: {$street} - {$city} {$zip} - {/Sender} -
+``` +
+{Sender} + {$name}, + Address: {$street} + {$city} {$zip} +{/Sender} +
-
- {Recipient} - {$name}, - Address: {$street} - {$city} {$zip} - {/Recipient} -
+
+{Recipient} + {$name}, + Address: {$street} + {$city} {$zip} +{/Recipient} +
+``` -You can use the following code to manipulate the template above:: +You can use the following code to manipulate the template above: - $template = GiTemplate::addTo($this); - $template->loadFromFile('envelope'); // templates/envelope.html +``` +$template = GiTemplate::addTo($this); +$template->loadFromFile('envelope'); // templates/envelope.html - // Split into multiple objects for processing - $sender = $template->cloneRegion('Sender'); - $recipient = $template->cloneRegion('Recipient'); +// Split into multiple objects for processing +$sender = $template->cloneRegion('Sender'); +$recipient = $template->cloneRegion('Recipient'); - // Set data to each sub-template separately - $sender->set($senderData); - $recipient->set($recipientData); +// Set data to each sub-template separately +$sender->set($senderData); +$recipient->set($recipientData); - // render sub-templates, insert into master template - $template->dangerouslySetHtml('Sender', $sender->renderToHtml()); - $template->dangerouslySetHtml('Recipient', $recipient->renderToHtml()); +// render sub-templates, insert into master template +$template->dangerouslySetHtml('Sender', $sender->renderToHtml()); +$template->dangerouslySetHtml('Recipient', $recipient->renderToHtml()); - // get final result - $result = $template->renderToHtml(); +// get final result +$result = $template->renderToHtml(); +``` -Same thing using Agile Toolkit Views:: +Same thing using Agile Toolkit Views: - $envelope = \Atk4\Ui\View::addTo($this, [], [null], null, ['envelope']); +``` +$envelope = \Atk4\Ui\View::addTo($this, [], [null], null, ['envelope']); - $sender = \Atk4\Ui\View::addTo($envelope, [], [null], 'Sender', 'Sender'); - $recipient = \Atk4\Ui\View::addTo($envelope, [], [null], 'Recipient', 'Recipient'); +$sender = \Atk4\Ui\View::addTo($envelope, [], [null], 'Sender', 'Sender'); +$recipient = \Atk4\Ui\View::addTo($envelope, [], [null], 'Recipient', 'Recipient'); - $sender->template->set($senderData); - $recipient->template->set($recipientData); +$sender->template->set($senderData); +$recipient->template->set($recipientData); +``` We do not need to manually render anything in this scenario. Also the template of $sender and $recipient objects will be appropriately cloned @@ -429,8 +439,7 @@ have used my own View object with some more sophisticated presentation logic. The only affect on the example would be name of the class, the rest of presentation logic would be abstracted inside view's ``renderToHtml()`` method. -Other operations with tags --------------------------- +### Other operations with tags .. php:method:: del(tag) @@ -448,49 +457,53 @@ Other operations with tags Attempts to empty a tag. Does nothing if tag with name does not exist. -Repeating tags --------------- +### Repeating tags -Agile Toolkit template engine allows you to use same tag several times:: +Agile Toolkit template engine allows you to use same tag several times: - Roses are {color}red{/} - Violets are {color}blue{/} +``` +Roses are {color}red{/} +Violets are {color}blue{/} +``` If you execute ``set('color', 'green')`` then contents of both tags will be affected. Similarly if you call ``append('color', '-ish')`` then the text will be appended to both tags. -Conditional tags ----------------- +### Conditional tags Agile Toolkit template engine allows you to use so called conditional tags which will automatically remove template regions if tag value is empty. Conditional tags notation is trailing question mark symbol. -Consider this example:: +Consider this example: - My {email?}e-mail {$email}{/email?} {phone?}phone {$phone}{/?}. +``` +My {email?}e-mail {$email}{/email?} {phone?}phone {$phone}{/?}. +``` This will only show text "e-mail" and email address if email tag value is set to not empty value. Same for "phone" tag. So if you execute ``set('email', null)`` and ``set('phone', 123)`` then this -template will automatically render as:: +template will automatically render as: - My phone 123. +``` +My phone 123. +``` Note that zero value is treated as not empty value! -Views and Templates -=================== +## Views and Templates Let's look how templates work together with View objects. -Default template for a view ---------------------------- +### Default template for a view .. php:method:: defaultTemplate() - Specify default template for a view. +``` +Specify default template for a view. +``` By default view object will execute :php:meth:`defaultTemplate()` method which returns name of the template. This function must return array with @@ -502,15 +515,16 @@ multiple views load data from same template but use different region. Function can also return a string, in which case view will attempt to clone region with such a name from parent's template. This can be used by your "menu" implementation, which will clone parent's template's tag -instead to hook into some specific template:: +instead to hook into some specific template: - public function defaultTemplate() - { - return ['greeting']; // uses templates/greeting.html - } +``` +public function defaultTemplate() +{ + return ['greeting']; // uses templates/greeting.html +} +``` -Redefining template for view during adding ------------------------------------------- +### Redefining template for view during adding When you are adding new object, you can specify a different template to use. This is passed as 4th argument to ``add()`` method and has the same @@ -519,27 +533,29 @@ approach you can use existing objects with your own templates. This allows you to change the look and feel of certain object for only one or some pages. If you frequently use view with a different template, it might be better to define a new View class and re-define -``defaultTemplate()`` method instead:: +``defaultTemplate()`` method instead: - MyObject::addTo($this, ['greeting']); +``` +MyObject::addTo($this, ['greeting']); +``` -Accessing view's template -------------------------- +### Accessing view's template Template is available by the time ``init()`` is called and you can access it from inside the object or from outside through "template" -property:: +property: - $grid = \Atk4\Ui\Grid::addTo($this, [], [null], null, array('grid_with_hint')); - $grid->template->trySet('my_hint', 'Changing value of a grid hint here!'); +``` +$grid = \Atk4\Ui\Grid::addTo($this, [], [null], null, array('grid_with_hint')); +$grid->template->trySet('my_hint', 'Changing value of a grid hint here!'); +``` In this example we have instructed to use a different template for grid, which would contain a new tag "my\_hint" somewhere. If you try to change existing tags, their output can be overwritten during rendering of the view. -How views render themselves ---------------------------- +### How views render themselves Agile Toolkit perform object initialization first. When all the objects are initialized global rendering takes place. Each object's ``renderToHtml()`` @@ -554,25 +570,24 @@ the 3rd argument to ``add()`` exists — "spot". By default spot is elsewhere. Let's see how our previous example with addresses can be implemented using generic views. -:: +: - $envelope = \Atk4\Ui\View::addTo($this, [], [null], null, array('envelope')); +``` +$envelope = \Atk4\Ui\View::addTo($this, [], [null], null, array('envelope')); - // 3rd argument is output region, 4th is template location - $sender = \Atk4\Ui\View::addTo($envelope, [], [null], 'Sender', 'Sender'); - $receiver = \Atk4\Ui\View::addTo($envelope, [], [null], 'Receiver', 'Receiver'); +// 3rd argument is output region, 4th is template location +$sender = \Atk4\Ui\View::addTo($envelope, [], [null], 'Sender', 'Sender'); +$receiver = \Atk4\Ui\View::addTo($envelope, [], [null], 'Receiver', 'Receiver'); - $sender->template->trySet($senderData); - $receiver->template->trySet($receiverData); +$sender->template->trySet($senderData); +$receiver->template->trySet($receiverData); +``` .. templates and views +## Best Practices -Best Practices -============== - -Don't use Template Engine without views ---------------------------------------- +### Don't use Template Engine without views It is strongly advised not to use templates directly unless you have no other choice. Views implement consistent and flexible layer on top of @@ -581,8 +596,7 @@ The only cases when direct use of SMlite is suggested is if you are not working with HTML or the output will not be rendered in a regular way (such as RSS feed generation or TMail) -Organize templates into directories ------------------------------------ +### Organize templates into directories Typically templates directory will have sub-directories: "page", "view", "form" etc. Your custom template for one of the pages should be inside @@ -593,8 +607,7 @@ putting it into "page" directory, call it ``page_two_columns.html``. You can find similar structure inside atk4/templates/shared or in some other projects developed using Agile Toolkit. -Naming of tags --------------- +### Naming of tags Tags use two type of naming - PascalCase and underscore\_lowercase. Tags are case sensitive. The larger regions which are typically used for @@ -603,9 +616,7 @@ Examples would be: "Menu", "Content" and "Recipient". The lowercase and underscore is used for short variables which would be inserted into template directly such as "name" or "zip". -Globally Recognized Tags -======================== - +## Globally Recognized Tags Agile Toolkit View will automatically substitute several tags with the values. The tag {$attributes} is automatically replaced with a attributes incl. ``id`` diff --git a/docs/text.md b/docs/text.md index 36ed4e72b5..d63f513ccb 100644 --- a/docs/text.md +++ b/docs/text.md @@ -1,8 +1,6 @@ .. _text: -==== -Text -==== +# Text .. php:namespace:: Atk4\Ui @@ -10,54 +8,59 @@ Text Text is a component for abstracting several paragraphs of text. It's usage is simple and straightforward: -Basic Usage -=========== +## Basic Usage First argument of constructor or first element in array passed to constructor will be the text that will -appear on the label:: +appear on the label: - $text = Text::addTo($app, ['here goes some text']); +``` +$text = Text::addTo($app, ['here goes some text']); +``` -Paragraphs -========== +## Paragraphs -You can define multiple paragraphs with text like this:: +You can define multiple paragraphs with text like this: - $text = Text::addTo($app) - ->addParagraph('First Paragraph') - ->addParagraph('Second Paragraph'); +``` +$text = Text::addTo($app) + ->addParagraph('First Paragraph') + ->addParagraph('Second Paragraph'); +``` -HTML escaping -============= +## HTML escaping -By default Text will not escape HTML so this will render as a bold text:: +By default Text will not escape HTML so this will render as a bold text: - $text = Text::addTo($app, ['here goes some bold text']); +``` +$text = Text::addTo($app, ['here goes some bold text']); +``` .. warning:: If you are using Text for output HTML then you are doing it wrong. You should use a generic View and specify your HTML as a template. -When you use paragraphs, escaping is performed by default:: +When you use paragraphs, escaping is performed by default: - $text = Text::addTo($app) - ->addParagraph('No alerts') - ->addParagraph(''); +``` +$text = Text::addTo($app) + ->addParagraph('No alerts') + ->addParagraph(''); +``` -Usage -===== +## Usage Text is usable in generic components, where you want to leave possibility of text injection. For instance, -:php:class:`Message` uses text allowing you to add few paragraphs of text:: +:php:class:`Message` uses text allowing you to add few paragraphs of text: - $message = Message::addTo($app, ['Message Title']); - $message->addClass('warning'); +``` +$message = Message::addTo($app, ['Message Title']); +$message->addClass('warning'); - $message->text->addParagraph('First para'); - $message->text->addParagraph('Second para'); +$message->text->addParagraph('First para'); +$message->text->addParagraph('Second para'); +``` -Limitations -=========== +## Limitations Text may not have embedded elements, although that may change in the future. diff --git a/docs/tree-item-selector.md b/docs/tree-item-selector.md index 98113743d9..8c0cf19cf6 100644 --- a/docs/tree-item-selector.md +++ b/docs/tree-item-selector.md @@ -1,18 +1,13 @@ - .. php:namespace:: Atk4\Ui\Form\Control .. php:class:: TreeItemSelector - -============================= -TreeItemSelector Form Control -============================= +# TreeItemSelector Form Control TreeItemSelector Form Control will display a list of items in a hierarchical (tree) structure. It allow for a user to select multiple or single item within a tree like list structure. -Attributes -========== +## Attributes .. php:attr:: treeItems @@ -21,22 +16,24 @@ within the list structure and ID will be collect when user add or remove them. Items are grouped together by using nodes forming a category in the list. ID value is not mandatory for a group. The TreeItemSelector will automatically create group of items based on the treeItems array. It will create a group when an item contains a nodes key within -the treeItems array and that nodes key is not empty. Below is a sample of a group name call Electronics using two children nodes.:: - - $items = [ - 'Electronics' => [ - 'nodes' => [ - [ - 'name' => 'tv', - 'id' => '100', - ], - [ - 'name' => 'radio', - 'id' => '100', - ], +the treeItems array and that nodes key is not empty. Below is a sample of a group name call Electronics using two children nodes.: + +``` +$items = [ + 'Electronics' => [ + 'nodes' => [ + [ + 'name' => 'tv', + 'id' => '100', + ], + [ + 'name' => 'radio', + 'id' => '100', ], ], - ] + ], +] +``` .. php:attr:: allowMultiple @@ -44,59 +41,59 @@ This attribute will decide into witch mode the component will run. When allowMul the component will allow multiple selection to be made within the list of items. Otherwise, only one selection will be allowed. -Basic Usage -=========== - -Adding a TreeItemSelector form control to a Form:: - - $items = [ - [ - 'name' => 'Electronics', - 'nodes' => [ - [ - 'name' => 'Phone', - 'nodes' => [ - [ - 'name' => 'iPhone', - 'id' => 502, - ], - [ - 'name' => 'Google Pixels', - 'id' => 503, - ], +## Basic Usage + +Adding a TreeItemSelector form control to a Form: + +``` +$items = [ + [ + 'name' => 'Electronics', + 'nodes' => [ + [ + 'name' => 'Phone', + 'nodes' => [ + [ + 'name' => 'iPhone', + 'id' => 502, + ], + [ + 'name' => 'Google Pixels', + 'id' => 503, ], ], - ['name' => 'Tv', 'id' => 501, 'nodes' => []], - ['name' => 'Radio', 'id' => 601, 'nodes' => []], ], + ['name' => 'Tv', 'id' => 501, 'nodes' => []], + ['name' => 'Radio', 'id' => 601, 'nodes' => []], ], - ['name' => 'Cleaner', 'id' => 201, 'nodes' => []], - ['name' => 'Appliances', 'id' => 301, 'nodes' => []], - ]; + ], + ['name' => 'Cleaner', 'id' => 201, 'nodes' => []], + ['name' => 'Appliances', 'id' => 301, 'nodes' => []], +]; - $form = \Atk4\Ui\Form::addTo($app); - $control = $form->addControl('tree', [new TreeItemSelector(['treeItems' => $items]), 'caption' => 'Select items:'], ['type' => 'json']); - $control->set([201, 301, 503]); +$form = \Atk4\Ui\Form::addTo($app); +$control = $form->addControl('tree', [new TreeItemSelector(['treeItems' => $items]), 'caption' => 'Select items:'], ['type' => 'json']); +$control->set([201, 301, 503]); +``` Please note that when using TreeItemSelector in multiple mode, you need to specify field attribute type to 'json' because in multiple mode, it will collect item value as an array type. - -Callback Usage -============== +## Callback Usage .. php:method:: onItem($fx) It is possible to run a callback function every time an item is select on the list. The callback function will receive the selected item -set by the user.:: +set by the user.: - $control->onItem(function (array $value) { - return new \Atk4\Ui\Js\JsToast($this->getApp()->encodeJson($value)); - }); +``` +$control->onItem(function (array $value) { + return new \Atk4\Ui\Js\JsToast($this->getApp()->encodeJson($value)); +}); +``` -Note -==== +## Note This form control component is made to collect ID's of end item only, i.e. item with no children nodes, and will be working in recursive selection mode when allowMultiple is set to true. Recursive selection mean that when user click on a group, it will automatically select or unselect children diff --git a/docs/type-presentation.md b/docs/type-presentation.md index 917dcc6bf3..eb0c082a86 100644 --- a/docs/type-presentation.md +++ b/docs/type-presentation.md @@ -1,8 +1,6 @@ - .. _type-presentation: -Formatters vs Decorators -======================== +## Formatters vs Decorators This chapter describes a common technique used by various components that wish to preserve extensible nature when dealing with used-defined types. Reading this chapter will also help @@ -28,8 +26,7 @@ Decoration is performed by helper classes, such as :php:class:`Form\\Control\\Ca :php:class:`Table\\Column\\\Money`. The decorator is in control of the final output, so it can decide if it uses the value from presentation or do some decoration on its own. -Extending Data Types -==================== +## Extending Data Types If you are looking to add a new data type, such as "money + currency" combination, which would allow user to specify both the currency and the monetary value, you should start by adding @@ -60,24 +57,27 @@ of your integration. For the third party add-ons it is only possible to provide decorators. They must rely on one of the standard types, unless they also offer a dedicated model. -Manually Specifying Decorators -============================== +## Manually Specifying Decorators When working with components, they allow to specify decorators manually, even if the type -of the field does not seem compatible:: +of the field does not seem compatible: - $table->addColumn('field_name', new \Atk4\Ui\Table\Column\Password()); +``` +$table->addColumn('field_name', new \Atk4\Ui\Table\Column\Password()); - // or +// or - $form->addControl('field_name', new \Atk4\Ui\Form\Control\Password()); +$form->addControl('field_name', new \Atk4\Ui\Form\Control\Password()); +``` Selecting the decorator is done in the following order: - specified in second argument to UI `addColumn()` or `addControl()` (as shown above) - - specified using `ui` property of :php:class:`\Atk4\Data\Field`:: + - specified using `ui` property of :php:class:`\Atk4\Data\Field`: - $field->ui['form'] = new \Atk4\Ui\Form\Control\Password(); +``` +$field->ui['form'] = new \Atk4\Ui\Form\Control\Password(); +``` - fallback to :php:meth:`Form::controlFactory` @@ -87,99 +87,103 @@ Selecting the decorator is done in the following order: which do have some impact on rendering, whereas UI field `ui` property (not used here) designates the Fomantic-UI element to use. -Examples -======== +## Examples Let's explore various use cases and how to properly deal with scenarios -Display password in plain-text for Admin ----------------------------------------- +### Display password in plain-text for Admin Normally password is presented as asterisks on the Grid and Form. But what if you want to -show it without masking just for the admin? Change type in-line for the model field:: +show it without masking just for the admin? Change type in-line for the model field: - $model = new User($app->db); - $model->getField('password')->type = 'string'; +``` +$model = new User($app->db); +$model->getField('password')->type = 'string'; - $crud->setModel($model); +$crud->setModel($model); +``` .. note:: Changing element's type to string will certainly not perform any password encryption. -Hide account_number in specific Table -------------------------------------- +### Hide account_number in specific Table This is reverse scenario. Field `account_number` needs to be stored as-is but should be -hidden when presented. To hide it from Table:: +hidden when presented. To hide it from Table: - $model = new User($app->db); +``` +$model = new User($app->db); - $table->setModel($model); - $model->addDecorator('account_number', new \Atk4\Ui\Table\Column\Password()); +$table->setModel($model); +$model->addDecorator('account_number', new \Atk4\Ui\Table\Column\Password()); +``` -Create a decorator for hiding credit card number ------------------------------------------------- +### Create a decorator for hiding credit card number If you happen to store card numbers and you only want to display the last digits in tables, -yet make it available when editing, you could create your own :php:class:`Table\\Column` decorator:: +yet make it available when editing, you could create your own :php:class:`Table\\Column` decorator: - class Masker extends \Atk4\Ui\Table\Column +``` +class Masker extends \Atk4\Ui\Table\Column +{ + public function getDataCellTemplate(\Atk4\Data\Field $field = null): string { - public function getDataCellTemplate(\Atk4\Data\Field $field = null): string - { - return '**** **** **** {$mask}'; - } + return '**** **** **** {$mask}'; + } - public function getHtmlTags(\Atk4\Data\Model $row, ?\Atk4\Data\Field $field): array - { - return [ - 'mask' => substr($field->get($row), -4), - ]; - } + public function getHtmlTags(\Atk4\Data\Model $row, ?\Atk4\Data\Field $field): array + { + return [ + 'mask' => substr($field->get($row), -4), + ]; } +} +``` If you are wondering, why I'm not overriding by providing HTML tag equal to the field name, it's because this technique is unreliable due to ability to exclude HTML tags with :php:attr:`Table::$useHtmlTags`. -Display credit card number with spaces --------------------------------------- +### Display credit card number with spaces + If we always have to display card numbers with spaces, e.g. "1234 1234 1234 1234" but have the database store them without spaces, then this is a data formatting task best done by -extending :php:class:`Persistence\Ui`:: +extending :php:class:`Persistence\Ui`: - class MyPersistence extends Persistence\Ui +``` +class MyPersistence extends Persistence\Ui +{ + protected function _typecastSaveField(\Atk4\Data\Field $field, $value) { - protected function _typecastSaveField(\Atk4\Data\Field $field, $value) - { - switch ($field->type) { - case 'card': - $parts = str_split($value, 4); + switch ($field->type) { + case 'card': + $parts = str_split($value, 4); - return implode(' ', $parts); - } - - return parent::_typecastSaveField($field, $value); + return implode(' ', $parts); } - public function _typecastLoadField(\Atk4\Data\Field $field, $value) - { - switch ($field->type) { - case 'card': - return str_replace(' ', '', $value); - } + return parent::_typecastSaveField($field, $value); + } - return parent::_typecastLoadField($field, $value); + public function _typecastLoadField(\Atk4\Data\Field $field, $value) + { + switch ($field->type) { + case 'card': + return str_replace(' ', '', $value); } + + return parent::_typecastLoadField($field, $value); } +} - class MyApp extends App +class MyApp extends App +{ + public function __construct(array $defaults = []) { - public function __construct(array $defaults = []) - { - $this->uiPersistence = new MyPersistence() + $this->uiPersistence = new MyPersistence() - parent::__construct($defaults); - } + parent::__construct($defaults); } +} +``` Now your 'card' type will work system-wide. diff --git a/docs/view.md b/docs/view.md index fc0907dda2..0556916f4c 100644 --- a/docs/view.md +++ b/docs/view.md @@ -1,10 +1,6 @@ - - .. _view: -===== -Views -===== +# Views Agile UI is a component framework, which follows a software patterns known as `Render Tree` and `Two pass HTML rendering`. @@ -17,10 +13,12 @@ Agile UI is a component framework, which follows a software patterns known as of the other components descend from the `View` class. -View object is recursive. You can take one view and add another View inside of it:: +View object is recursive. You can take one view and add another View inside of it: - $v = new \Atk4\Ui\View(['ui' => 'segment', 'class.inverted' => true]); - Button::addTo($v, ['Orange', 'class.inverted orange' => true]); +``` +$v = new \Atk4\Ui\View(['ui' => 'segment', 'class.inverted' => true]); +Button::addTo($v, ['Orange', 'class.inverted orange' => true]); +``` The above code will produce the following HTML block: @@ -32,16 +30,15 @@ The above code will produce the following HTML block: All of the views combined form a ``Render Tree``. In order to get the HTML output from all the `Views` in `Render Tree` you need to execute ``render()`` for the top-most -leaf:: +leaf: - echo $v->render(); +``` +echo $v->render(); +``` Each of the views will automatically render all of the child views. - - -Initializing Render Tree -======================== +## Initializing Render Tree Views use a principle of `delayed init`, which allow you to manipulate View objects in any way you wish, before they will actuallized. @@ -70,44 +67,50 @@ in any way you wish, before they will actuallized. additional sub-views into it. In the next example I'll be creating 3 views, but it at the time their __constructor -is executed it will be impossible to determine each view's position inside render tree:: +is executed it will be impossible to determine each view's position inside render tree: - $middle = new \Atk4\Ui\View(['ui' => 'segment', 'class.red' => true]); - $top = new \Atk4\Ui\View(['ui' => 'segments']); - $bottom = new \Atk4\Ui\Button(['Hello World', 'class.orange' => true]); +``` +$middle = new \Atk4\Ui\View(['ui' => 'segment', 'class.red' => true]); +$top = new \Atk4\Ui\View(['ui' => 'segments']); +$bottom = new \Atk4\Ui\Button(['Hello World', 'class.orange' => true]); - // not arranged into render-tree yet +// not arranged into render-tree yet - $middle->add($bottom); - $top->add($middle); +$middle->add($bottom); +$top->add($middle); - // Still not sure if finished adding +// Still not sure if finished adding - $app = new \Atk4\Ui\App('My App'); - $app->initLayout($top); +$app = new \Atk4\Ui\App('My App'); +$app->initLayout($top); - // Calls init() for all elements recursively. +// Calls init() for all elements recursively. +``` Each View's `init()` method will be executed first before calling the same method for child elements. To make your execution more straightforward we recommend you to create -App class first and then continue with Layout initialization:: +App class first and then continue with Layout initialization: - $app = new \Atk4\Ui\App('My App'); - $top = $app->initLayout(new \Atk4\Ui\View(['ui' => 'segments'])); +``` +$app = new \Atk4\Ui\App('My App'); +$top = $app->initLayout(new \Atk4\Ui\View(['ui' => 'segments'])); - $middle = View::addTo($top, ['ui' => 'segment', 'class.red' => true]); +$middle = View::addTo($top, ['ui' => 'segment', 'class.red' => true]); - $bottom = Button::addTo($middle, ['Hello World', 'class.orange' => true]); +$bottom = Button::addTo($middle, ['Hello World', 'class.orange' => true]); +``` -Finally, if you prefer a more consise code, you can also use the following format:: +Finally, if you prefer a more consise code, you can also use the following format: - $app = new \Atk4\Ui\App('My App'); - $top = $app->initLayout([\Atk4\Ui\View::class, 'ui' => 'segments']); +``` +$app = new \Atk4\Ui\App('My App'); +$top = $app->initLayout([\Atk4\Ui\View::class, 'ui' => 'segments']); - $middle = View::addTo($top, ['ui' => 'segment', 'class.red' => true]); +$middle = View::addTo($top, ['ui' => 'segment', 'class.red' => true]); - $bottom = Button::addTo($middle, ['Hello World', 'class.orange' => true]); +$bottom = Button::addTo($middle, ['Hello World', 'class.orange' => true]); +``` The rest of documentation will use this concise code to keep things readable, however if you value type-hinting of your IDE, you can keep using "new" keyword. I must also @@ -116,8 +119,7 @@ to `Factory::factory()`, which will be responsible of instantiating the actual o (TODO: link to App:Factory) -Use of $app property and Dependency Injeciton -============================================= +## Use of $app property and Dependency Injeciton .. php:attr:: app @@ -125,17 +127,18 @@ Use of $app property and Dependency Injeciton View elements rely on persistence of the app class in order to perform Dependency Injection. -Consider the following example:: +Consider the following example: - $app->debug = new Logger('log'); // Monolog +``` +$app->debug = new Logger('log'); // Monolog - // next, somewhere in a render tree - $view->getApp()->debug->log('Foo Bar'); +// next, somewhere in a render tree +$view->getApp()->debug->log('Foo Bar'); +``` Agile UI will automatically pass your $app class to all the views. -Integration with Agile Data -=========================== +## Integration with Agile Data .. php:method:: setModel($model) @@ -147,39 +150,45 @@ Integration with Agile Data If you have used Agile Data, you should be familiar with a concept of creating -Models:: +Models: - $db = new \Atk4\Data\Persistence\Sql($dsn); +``` +$db = new \Atk4\Data\Persistence\Sql($dsn); - $client = new Client($db); // extends \Atk4\Data\Model +$client = new Client($db); // extends \Atk4\Data\Model +``` Once you have a model, you can associate it with a View such as Form or Grid -so that those Views would be able to interact with your persistence directly:: +so that those Views would be able to interact with your persistence directly: - $form->setModel($client); +``` +$form->setModel($client); +``` In most environments, however, your application will rely on a primary Database, which -can be set through your $app class:: +can be set through your $app class: - $app->db = new \Atk4\Data\Persistence\Sql($dsn); +``` +$app->db = new \Atk4\Data\Persistence\Sql($dsn); - // next, anywhere in a view - $client = new Client($this->getApp()->db); - $form->setModel($client); +// next, anywhere in a view +$client = new Client($this->getApp()->db); +$form->setModel($client); +``` -Or if you prefer a more consise code:: +Or if you prefer a more consise code: - $app->db = new \Atk4\Data\Persistence\Sql($dsn); +``` +$app->db = new \Atk4\Data\Persistence\Sql($dsn); - // next, anywhere in a view - $form->setModel('Client'); +// next, anywhere in a view +$form->setModel('Client'); +``` Again, this will use `Factory` feature of your application to let you determine how to properly initialize the class corresponding to string 'Client'. -UI Role and Classes -=================== - +## UI Role and Classes .. php:method:: __construct($defaults = []) @@ -195,20 +204,26 @@ A constructor of a view often maps into a ``
`` tag that has a specific role in a CSS framework. According to the principles of Agile UI, we support a wide varietty of roles. In some cases, a dedicated object will exist, for example a Button. In other cases, you can use a View and specify a UI role -explicitly:: +explicitly: - $view = View::addTo($app, ['ui' => 'segment']); +``` +$view = View::addTo($app, ['ui' => 'segment']); +``` If you happen to pass more key/values to the constructor or as second argument to add() they will be treated as default values for the properties of that -specific view:: +specific view: - $view = View::addTo($app, ['ui' => 'segment', 'name' => 'test-id']); +``` +$view = View::addTo($app, ['ui' => 'segment', 'name' => 'test-id']); +``` -For a more IDE-friendly format, however, I recommend to use the following syntax:: +For a more IDE-friendly format, however, I recommend to use the following syntax: - $view = View::addTo($app, ['ui' => 'segment']); - $view->name = 'test-id'; +``` +$view = View::addTo($app, ['ui' => 'segment']); +$view->name = 'test-id'; +``` You must be aware of a difference here - passing array to constructor will override default property before call to `init()`. Most of the components @@ -219,10 +234,12 @@ which syntax you are using. If you are don't specify key for the properties, they will be considered an -extra class for a view:: +extra class for a view: - $view = View::addTo($app, ['class.orange' => true, 'ui' => 'segment']); - $view->name = 'test-id'; +``` +$view = View::addTo($app, ['class.orange' => true, 'ui' => 'segment']); +$view->name = 'test-id'; +``` You can either specify multiple classes one-by-one or as a single string "inverted orange". @@ -247,40 +264,47 @@ You can either specify multiple classes one-by-one or as a single string In addition to the UI / Role classes during the render, element will receive extra classes from the $class property. To add extra class to -existing object:: +existing object: - $button->addClass('blue large'); +``` +$button->addClass('blue large'); +``` Classes on a view will appear in the following order: "ui blue large button" -Special-purpose properties -========================== +## Special-purpose properties A view may define a special-purpose properties, that may modify how the view is rendered. For example, Button has a property 'icon', that is implemented by creating instance of \Atk4\Ui\Icon() inside the button. -The same pattern can be used for other scenarios:: +The same pattern can be used for other scenarios: - $button = Button::addTo($app, ['icon' => 'book']); +``` +$button = Button::addTo($app, ['icon' => 'book']); +``` -This code will have same effect as:: +This code will have same effect as: - $button = Button::addTo($app); - $button->icon = 'book'; +``` +$button = Button::addTo($app); +$button->icon = 'book'; +``` -During the Render of a button, the following code will be executed:: +During the Render of a button, the following code will be executed: - Icon::addTo($button, ['book']); +``` +Icon::addTo($button, ['book']); +``` If you wish to use a different icon-set, you can change Factory's route for 'Icon' -to your own implementation OR you can pass icon as a view:: - - $button = Button::addTo($app, ['icon' => new MyAwesomeIcon('book')]); +to your own implementation OR you can pass icon as a view: +``` +$button = Button::addTo($app, ['icon' => new MyAwesomeIcon('book')]); +``` -Rendering of a Tree -=================== +## Rendering of a Tree .. php:method:: render() @@ -303,9 +327,7 @@ framework you shouldn't even use ``render()``, but instead use ``getHtml`` and ` Return array of JS chains that was assigned to current element or it's children. - -Modifying rendering logic -========================= +## Modifying rendering logic When you creating your own View, you most likely will want to change it's rendering mechanics. The most suitable location for that is inside ``renderView`` method. @@ -315,17 +337,19 @@ The most suitable location for that is inside ``renderView`` method. Perform necessary changes in the $template property according to the presentation logic of this view. -You should override this method when necessary and don't forget to execute parent::renderView():: +You should override this method when necessary and don't forget to execute parent::renderView(): - protected function renderView(): void - { - if (str_len($this->info) > 100) { - $this->addClass('tiny'); - } - - parent::renderView(); +``` +protected function renderView(): void +{ + if (str_len($this->info) > 100) { + $this->addClass('tiny'); } + parent::renderView(); +} +``` + It's important when you call parent. You wouldn't be able to affect template of a current view anymore after calling renderView. @@ -335,22 +359,28 @@ to do something before child render, override method :php:meth:`View::recursiveR .. php:attr:: template Template of a current view. This attribute contains an object of a class :php:class:`Template`. -You may secify this value explicitly:: +You may secify this value explicitly: - View::addTo($app, ['template' => new \Atk4\Ui\Template('hello')]); +``` +View::addTo($app, ['template' => new \Atk4\Ui\Template('hello')]); +``` .. php:attr:: defaultTemplate By default, if value of :php:attr:`View::$template` is not set, then it is loaded from class -specified in `defaultTemplate`:: +specified in `defaultTemplate`: - View::addTo($app, ['defaultTemplate' => './mytpl.html']); +``` +View::addTo($app, ['defaultTemplate' => './mytpl.html']); +``` You should specify defaultTemplate using relative path to your project root or, for add-ons, -relative to a current file:: +relative to a current file: - // in Add-on - View::addTo($app, ['defaultTemplate' => __DIR__ . '/../templates/mytpl.httml']); +``` +// in Add-on +View::addTo($app, ['defaultTemplate' => __DIR__ . '/../templates/mytpl.httml']); +``` Agile UI does not currently provide advanced search path for templates, by default the template is loaded from folder `vendor/atk4/ui/template`. To change this @@ -362,37 +392,43 @@ Name of the region in the owner's template where this object will output itself. By default 'Content'. -Here is a best practice for using custom template:: +Here is a best practice for using custom template: - class MyView extends View - { - public $template = 'custom.html'; +``` +class MyView extends View +{ + public $template = 'custom.html'; - public $title = 'Default Title'; + public $title = 'Default Title'; - protected function renderView(): void - { - parent::renderView(); + protected function renderView(): void + { + parent::renderView(); - $this->template->set('title', $this->title); - } + $this->template->set('title', $this->title); } +} +``` As soon as the view becomes part of a render-tree, the Template object will also be allocated. -At this point it's also possible to override default template:: +At this point it's also possible to override default template: - MyView::addTo($app, ['template' => $template->cloneRegion('MyRegion')]); +``` +MyView::addTo($app, ['template' => $template->cloneRegion('MyRegion')]); +``` Or you can set $template into object inside your constructor, in which case it will be left as-is. On other hand, if your 'template' property is null, then the process of adding View inside RenderTree will automatically clone region of a parent. -``Lister`` is a class that has no default template, and therefore you can add it like this:: +``Lister`` is a class that has no default template, and therefore you can add it like this: - $profile = View::addTo($app, ['template' => 'myview.html']); - $profile->setModel($user); - Lister::addTo($profile, [], ['Tags'])->setModel($user->ref('Tags')); +``` +$profile = View::addTo($app, ['template' => 'myview.html']); +$profile->setModel($user); +Lister::addTo($profile, [], ['Tags'])->setModel($user->ref('Tags')); +``` In this set-up a template ``myview.html`` will be populated with fields from ``$user`` model. Next, a Lister is added inside Tags region which will use the contents of a given tag as a default @@ -401,17 +437,18 @@ re-inserted back into the 'Tags' region. See also :php:class:`Template`. -Unique ID tag -============= +## Unique ID tag .. php:attr:: region ID to be used with the top-most element. -Agile UI will maintain unique ID for all the elements. The tag is set through 'name' property:: +Agile UI will maintain unique ID for all the elements. The tag is set through 'name' property: - $b = new \Atk4\Ui\Button(['name' => 'my-button3']); - echo $b->render(); +``` +$b = new \Atk4\Ui\Button(['name' => 'my-button3']); +echo $b->render(); +``` Outputs: @@ -445,47 +482,39 @@ the name of the field will be used instead of the role. This is done by setting Specify a name for the element. If container already has object with specified name, exception will be thrown. - -Reloading a View -================ +## Reloading a View .. php:method:: jsReload($getArgs) Agile UI makes it easy to reload any View on the page. Starting with v1.4 you can now use View::JsReload(), -which will respond with JavaScript Action for reloading the view:: - - $b1 = Button::addTo($app, ['Click me']); - $b2 = Button::addTo($app, ['Rand: ' . rand(1, 100)]); - - $b1->on('click', $b2->jsReload()); +which will respond with JavaScript Action for reloading the view: - // Previously: - // $b1->on('click', new \Atk4\Ui\Js\JsReload($b2)); +``` +$b1 = Button::addTo($app, ['Click me']); +$b2 = Button::addTo($app, ['Rand: ' . rand(1, 100)]); +$b1->on('click', $b2->jsReload()); +// Previously: +// $b1->on('click', new \Atk4\Ui\Js\JsReload($b2)); +``` - -Modifying Basic Elements -======================== +## Modifying Basic Elements TODO: Move to Element. Most of the basic elements will allow you to manipulate their content, HTML attributes or even -add custom styles:: - - $view->setElement('a'); - $view->setStyle('align', 'right'); - $view->setAttr('href', 'https://...'); - - - - +add custom styles: -Rest of yet-to-document/implement methods and properties -======================================================== +``` +$view->setElement('a'); +$view->setStyle('align', 'right'); +$view->setAttr('href', 'https://...'); +``` +## Rest of yet-to-document/implement methods and properties .. php:attr:: content diff --git a/docs/virtualpage.md b/docs/virtualpage.md index 1c7b14de8f..c11adb9940 100644 --- a/docs/virtualpage.md +++ b/docs/virtualpage.md @@ -1,6 +1,4 @@ - -VirtualPage Introduction ------------------------- +### VirtualPage Introduction Before learning about VirtualPage, Loader and other ways of dynamic content loading, you should fully understand :ref:`callback`. @@ -10,10 +8,12 @@ understand :ref:`callback`. Unlike any of the Callback classes, VirtualPage is a legitimate :php:class:`View`, but it's behavior is a little "different". In normal circumstances, rendering VirtualPage will result in empty string. Adding VirtualPage -anywhere inside your :ref:`render_tree` simply won't have any visible effect:: +anywhere inside your :ref:`render_tree` simply won't have any visible effect: - $vp = \Atk4\Ui\VirtualPage::addTo($layout); - \Atk4\Ui\LoremIpsum::addTo($vp); +``` +$vp = \Atk4\Ui\VirtualPage::addTo($layout); +\Atk4\Ui\LoremIpsum::addTo($vp); +``` However, VirtualPage has a special trigger argument. If found, then VirtualPage will interrupt normal rendering progress and output HTML of itself and any other Components you added to that page. @@ -26,11 +26,13 @@ To help you understand when to use VirtualPage here is the example: - Clicking the Button would dynamically load contents of VirtualPage inside a Modal window. This pattern is very easy to implement and is used by many components to transparently provide dynamic functionality. -Next is an example where :php:class:`Tabs` has support for callback for generating dynamic content for the tab:: +Next is an example where :php:class:`Tabs` has support for callback for generating dynamic content for the tab: - $tabs->addTab('Dynamic Tab Content', function (VirtualPage $vp) { - \Atk4\Ui\LoremIpsum::addTo($vp); - }); +``` +$tabs->addTab('Dynamic Tab Content', function (VirtualPage $vp) { + \Atk4\Ui\LoremIpsum::addTo($vp); +}); +``` Using VirtualPage inside your component can significantly enhance usability without introducing any complexity for developers. @@ -41,15 +43,17 @@ below). .. php:attr:: cb VirtuaPage relies on :php:class:`CallbackLater` object, which is stored in a property $cb. If the Callback is triggered -through a GET argument, then VirtualPage will change it's rendering technique. Lets examine it in more detail:: +through a GET argument, then VirtualPage will change it's rendering technique. Lets examine it in more detail: - $vp = \Atk4\Ui\VirtualPage::addTo($layout); - \Atk4\Ui\LoremIpsum::addTo($vp); +``` +$vp = \Atk4\Ui\VirtualPage::addTo($layout); +\Atk4\Ui\LoremIpsum::addTo($vp); - $label = \Atk4\Ui\Label::addTo($layout); +$label = \Atk4\Ui\Label::addTo($layout); - $label->detail = $vp->cb->getUrl(); - $label->link($vp->cb->getUrl()); +$label->detail = $vp->cb->getUrl(); +$label->link($vp->cb->getUrl()); +``` This code will only show the link containing a URL, but will not show LoremIpsum text. If you do follow the link, you'll see only the 'LoremIpsum' text. @@ -70,40 +74,46 @@ As you may know, :php:meth:`Callback::getUrl()` accepts an argument, and Virtual - getUrl('cut') gives you URL which will return ONLY the HTML of virtual page, no Layout or boilerplate. - getUrl('popup') gives you URL which will return a very minimalistic layout inside a valid HTML boilerplate, suitable for iframes or popup windows. -You can experiment with:: +You can experiment with: - $label->detail = $vp->cb->getUrl('popup'); - $label->link($vp->cb->getUrl('popup')); +``` +$label->detail = $vp->cb->getUrl('popup'); +$label->link($vp->cb->getUrl('popup')); +``` Setting Callback ^^^^^^^^^^^^^^^^ .. php:method:: set($callback) -Although VirtualPage can work without defining a callback, using one is more reliable and is always recommended:: +Although VirtualPage can work without defining a callback, using one is more reliable and is always recommended: - $vp = \Atk4\Ui\VirtualPage::addTo($layout); - $vp->set(function (\Atk4\Ui\VirtualPage $vp) { - \Atk4\Ui\LoremIpsum::addTo($vp); - }); +``` +$vp = \Atk4\Ui\VirtualPage::addTo($layout); +$vp->set(function (\Atk4\Ui\VirtualPage $vp) { + \Atk4\Ui\LoremIpsum::addTo($vp); +}); - $label = \Atk4\Ui\Label::addTo($layout); +$label = \Atk4\Ui\Label::addTo($layout); - $label->detail = $vp->cb->getUrl(); - $label->link($vp->cb->getUrl()); +$label->detail = $vp->cb->getUrl(); +$label->link($vp->cb->getUrl()); +``` This code will perform identically as the previous example, however 'LoremIpsum' will never be initialized unless you are requesting VirtualPage specifically, saving some CPU time. Capability of defining callback also makes it possible for VirtualPage to be embedded into any :ref:`component` quite reliably. -To illustrate, see how :php:class:`Tabs` component rely on VirtualPage, the following code:: +To illustrate, see how :php:class:`Tabs` component rely on VirtualPage, the following code: - $tabs = \Atk4\Ui\Tabs::addTo($layout); +``` +$tabs = \Atk4\Ui\Tabs::addTo($layout); - \Atk4\Ui\LoremIpsum::addTo($tabs->addTab('Tab1')); // regular tab - $tabs->addTab('Tab2', function (VirtualPage $p) { // dynamic tab - \Atk4\Ui\LoremIpsum::addTo($p); - }); +\Atk4\Ui\LoremIpsum::addTo($tabs->addTab('Tab1')); // regular tab +$tabs->addTab('Tab2', function (VirtualPage $p) { // dynamic tab + \Atk4\Ui\LoremIpsum::addTo($p); +}); +``` .. php:method:: getUrl($mode) @@ -112,23 +122,20 @@ To illustrate, see how :php:class:`Tabs` component rely on VirtualPage, the foll .. php:attr:: ui When using 'popup' mode, the output appears inside a `
`. If you want to change this -class, you can set $ui property to something else. Try:: - - $vp = \Atk4\Ui\VirtualPage::addTo($layout); - \Atk4\Ui\LoremIpsum::addTo($vp); - $vp->ui = 'red inverted segment'; - - $label = \Atk4\Ui\Label::addTo($layout); +class, you can set $ui property to something else. Try: - $label->detail = $vp->cb->getUrl('popup'); - $label->link($vp->cb->getUrl('popup')); +``` +$vp = \Atk4\Ui\VirtualPage::addTo($layout); +\Atk4\Ui\LoremIpsum::addTo($vp); +$vp->ui = 'red inverted segment'; +$label = \Atk4\Ui\Label::addTo($layout); +$label->detail = $vp->cb->getUrl('popup'); +$label->link($vp->cb->getUrl('popup')); +``` - - -Loader ------- +### Loader .. php:class:: Loader @@ -141,14 +148,16 @@ Comparing to VirtualPage which is a D.Y.I. solution - Loader can be used out of Loader extends VirtualPage and is quite similar to it. Like with a VirtualPage - you should use `set()` to define content that will be loaded dynamically, -while a spinner is shown to a user:: +while a spinner is shown to a user: - $loader = \Atk4\Ui\Loader::addTo($app); - $loader->set(function (\Atk4\Ui\Loader $p) { - // Simulate slow-loading component - sleep(2); - \Atk4\Ui\LoremIpsum::addTo($p); - }); +``` +$loader = \Atk4\Ui\Loader::addTo($app); +$loader->set(function (\Atk4\Ui\Loader $p) { + // Simulate slow-loading component + sleep(2); + \Atk4\Ui\LoremIpsum::addTo($p); +}); +``` A good use-case example would be a dashboard graph. Unlike VirtualPage which is not visible to a regular render, @@ -157,14 +166,16 @@ Loader needs to occupy some space. .. php:attr:: shim By default it will display a white segment with 7em height, but you can specify any other view through $shim -property:: +property: - $loader = \Atk4\Ui\Loader::addTo($app, ['shim' => [\Atk4\Ui\Message::class, 'Please wait until we load LoremIpsum...', 'class.red' => true]]); - $loader->set(function (\Atk4\Ui\Loader $p) { - // Simulate slow-loading component - sleep(2); - \Atk4\Ui\LoremIpsum::addTo($p); - }); +``` +$loader = \Atk4\Ui\Loader::addTo($app, ['shim' => [\Atk4\Ui\Message::class, 'Please wait until we load LoremIpsum...', 'class.red' => true]]); +$loader->set(function (\Atk4\Ui\Loader $p) { + // Simulate slow-loading component + sleep(2); + \Atk4\Ui\LoremIpsum::addTo($p); +}); +``` Triggering Loader @@ -192,17 +203,19 @@ behaviour does not work, you should set value for $loadEvent: - false = never load - "string" - bind to custom JS event -To indicate how custom binding works:: +To indicate how custom binding works: - $loader = \Atk4\Ui\Loader::addTo($app, ['loadEvent' => 'kaboom']); +``` +$loader = \Atk4\Ui\Loader::addTo($app, ['loadEvent' => 'kaboom']); - $loader->set(function (\Atk4\Ui\Loader $p) { - \Atk4\Ui\LoremIpsum::addTo($p); - }); +$loader->set(function (\Atk4\Ui\Loader $p) { + \Atk4\Ui\LoremIpsum::addTo($p); +}); - \Atk4\Ui\Button::addTo($app, ['Load data']) - ->on('click', $loader->js()->trigger('kaboom')); +\Atk4\Ui\Button::addTo($app, ['Load data']) + ->on('click', $loader->js()->trigger('kaboom')); +``` This approach allow you to trigger loader from inside JavaScript easily. See also: https://api.jquery.com/trigger/ @@ -215,55 +228,60 @@ If you execute :php:class:`JsReload` action on the Loader, it will return to ori Inline Editing Example ^^^^^^^^^^^^^^^^^^^^^^ -Next example will display DataTable, but will allow you to replace data with a form temporarily:: - +Next example will display DataTable, but will allow you to replace data with a form temporarily: - $box = \Atk4\Ui\View::addTo($app, ['ui' => 'segment']); +``` +$box = \Atk4\Ui\View::addTo($app, ['ui' => 'segment']); - $loader = \Atk4\Ui\Loader::addTo($box, ['loadEvent' => 'edit']); - \Atk4\Ui\Table::addTo($loader) - ->setModel($data) - ->addCondition('year', $app->stickyGet('year')); +$loader = \Atk4\Ui\Loader::addTo($box, ['loadEvent' => 'edit']); +\Atk4\Ui\Table::addTo($loader) + ->setModel($data) + ->addCondition('year', $app->stickyGet('year')); - \Atk4\Ui\Button::addTo($box, ['Edit Data Settings']) - ->on('click', $loader->js()->trigger('edit')); +\Atk4\Ui\Button::addTo($box, ['Edit Data Settings']) + ->on('click', $loader->js()->trigger('edit')); - $loader->set(function (\Atk4\Ui\Loader $p) { - $form = \Atk4\Ui\Form::addTo($p); - $form->addControl('year'); +$loader->set(function (\Atk4\Ui\Loader $p) { + $form = \Atk4\Ui\Form::addTo($p); + $form->addControl('year'); - $form->onSubmit(function (Form $form) use ($p) { - return new \Atk4\Ui\Js\JsReload($p, ['year' => $form->model->get('year')]); - }); + $form->onSubmit(function (Form $form) use ($p) { + return new \Atk4\Ui\Js\JsReload($p, ['year' => $form->model->get('year')]); }); +}); +``` Progress Bar ^^^^^^^^^^^^ .. php:attr:: progressBar = null -Loader can have a progress bar. Imagine that your Loader has to run slow process 4 times:: +Loader can have a progress bar. Imagine that your Loader has to run slow process 4 times: +``` +sleep(1); +sleep(1); +sleep(1); +sleep(1); +``` + +You can notify user about this progress through a simple code: + +``` +$loader = \Atk4\Ui\Loader::addTo($app, ['progressBar' => true]); +$loader->set(function (\Atk4\Ui\Loader $p) { + // Simulate slow-loading component sleep(1); + $p->setProgress(0.25); sleep(1); + $p->setProgress(0.5); sleep(1); + $p->setProgress(0.75); sleep(1); -You can notify user about this progress through a simple code:: - - $loader = \Atk4\Ui\Loader::addTo($app, ['progressBar' => true]); - $loader->set(function (\Atk4\Ui\Loader $p) { - // Simulate slow-loading component - sleep(1); - $p->setProgress(0.25); - sleep(1); - $p->setProgress(0.5); - sleep(1); - $p->setProgress(0.75); - sleep(1); - - \Atk4\Ui\LoremIpsum::addTo($p); - }); + \Atk4\Ui\LoremIpsum::addTo($p); +}); +``` By setting progressBar to true, Loader component will use SSE (`Server Sent Events `_) and will be sending notification about your progress. Note that currently Internet Explorer does not support SSE and it's diff --git a/docs/wizard.md b/docs/wizard.md index 6f7c2af36a..1fb05413a0 100644 --- a/docs/wizard.md +++ b/docs/wizard.md @@ -1,12 +1,8 @@ - - .. php:namespace:: Atk4\Ui .. php:class:: Wizard -====== -Wizard -====== +# Wizard Wizard is a high-level component, which makes use of callback to track step progression through the stages. It has an incredibly simple syntax for building UI and display a lovely UI for you. @@ -18,45 +14,50 @@ Demo: https://ui.agiletoolkit.org/demos/wizard.php Introduced in UI v1.4 - -Basic Usage -=========== +## Basic Usage .. php:method:: addStep($title, $callback) .. php:method:: addFinish($callback) -Start by creating Wizard inside your render tree:: +Start by creating Wizard inside your render tree: - $wizard = Wizard::addTo($app); +``` +$wizard = Wizard::addTo($app); +``` -Next add as many steps as you need specifying title and a PHP callback code for each:: +Next add as many steps as you need specifying title and a PHP callback code for each: - $wizard->addStep('Welcome', function (Wizard $wizard) { - Message::addTo($wizard, ['Welcome to wizard demonstration'])->text - ->addParagraph('Use button "Next" to advance') - ->addParagraph('You can specify your existing database connection string which will be used - to create a table for model of your choice'); - }); +``` +$wizard->addStep('Welcome', function (Wizard $wizard) { + Message::addTo($wizard, ['Welcome to wizard demonstration'])->text + ->addParagraph('Use button "Next" to advance') + ->addParagraph('You can specify your existing database connection string which will be used + to create a table for model of your choice'); +}); +``` Your callback will also receive `$wizard` as the first argument. Method addStep returns :php:class:`WizardStep`, -which is described below. You can also provide first argument to addStep as a seed or an object:: +which is described below. You can also provide first argument to addStep as a seed or an object: - $wizard->addStep([ - 'Set DSN', - 'icon' => 'configure', - 'description' => 'Database Connection String', - ], function (Wizard $p) { - // some code here - }); +``` +$wizard->addStep([ + 'Set DSN', + 'icon' => 'configure', + 'description' => 'Database Connection String', +], function (Wizard $p) { + // some code here +}); +``` -You may also specify a single Finish callback, which will be used when wizard is complete:: +You may also specify a single Finish callback, which will be used when wizard is complete: - $wizard->addFinish(function (Wizard $wizard) { - Header::addTo($wizard, ['You are DONE', 'class.huge centered' => true]); - }); +``` +$wizard->addFinish(function (Wizard $wizard) { + Header::addTo($wizard, ['You are DONE', 'class.huge centered' => true]); +}); +``` -Properties -========== +## Properties When you create wizard you may specify some of the following options: @@ -64,8 +65,7 @@ When you create wizard you may specify some of the following options: Other properties are used during the execution of the wizard. -Step Tracking -============= +## Step Tracking .. php:attr:: stepCallback @@ -89,26 +89,25 @@ Those properties will be initialized with the buttons, but some of them may be d if the button is not applicable. For example, first step should not have "Previous" button. You can change label or icon on existing button. - -Code Placement -============== +## Code Placement As you build up your wizard, you can place code inside callback or outside. It will have a different effect -on your wizard:: +on your wizard: - $wizard->buttonNext->icon = 'person'; +``` +$wizard->buttonNext->icon = 'person'; - $wizard->addStep('Step 3', function (Wizard $wizard) { - $wizard->buttonNext->icon = 'book'; - }); +$wizard->addStep('Step 3', function (Wizard $wizard) { + $wizard->buttonNext->icon = 'book'; +}); +``` Step defines the callback and will execute it instantly if the step is active. If step 3 is active, the code is executed to change icon to the book. Otherwise icon will remain 'person'. Another handy technique is disabling the button by adding "disabled" class. -Navigation -========== +## Navigation Wizard has few methods to help you to navigate between steps. @@ -122,8 +121,7 @@ If you wish to to go to specific step, you can use `$wizard->stepCallback->getUr Finally you can get URL of the current step with `$wizard->url()` (see :php:meth:`View::url`) -WizardStep -==== +## WizardStep .. php:class:: WizardStep diff --git a/js/.gitignore b/js/.gitignore index 02ce197ee2..2ccbe4656c 100644 --- a/js/.gitignore +++ b/js/.gitignore @@ -1,32 +1 @@ -# Logs -logs -*.log - -# Runtime data -pids -*.pid -*.seed - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# Bundle Profile directory -profile - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release - -# Dependency directory -# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git -node_modules - -lib +/node_modules/