Skip to content

Feature/Add Form support for containsMany field using MultiLine #784

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 47 commits into from
Sep 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
64c4287
init
ibelar Jun 26, 2019
b61d508
Apply fixes from StyleCI
romaninsh Jun 26, 2019
80c3178
something
DarkSide666 Jul 9, 2019
9025168
Apply fixes from StyleCI
romaninsh Jul 9, 2019
c65ee76
Merge branch 'develop' into test/multiline-containsmany
ibelar Jul 22, 2019
e21526d
feature/Add Form support for containsMany field using MultiLine
ibelar Jul 23, 2019
14242b4
Apply fixes from StyleCI
romaninsh Jul 23, 2019
1cd3228
update test file
ibelar Jul 23, 2019
426c2d7
delete test file
ibelar Jul 23, 2019
7f14fd7
remove db file
ibelar Jul 23, 2019
5f956ee
Merge remote-tracking branch 'origin/develop' into feature/multine-co…
romaninsh Jul 23, 2019
4df3629
re-added .sql file
romaninsh Jul 23, 2019
7560c09
update template
ibelar Jul 23, 2019
b15334d
add support for row limit
ibelar Jul 23, 2019
7c6b978
Apply fixes from StyleCI
romaninsh Jul 23, 2019
46bd96d
add support for dropdown
ibelar Jul 24, 2019
23ff75b
Apply fixes from StyleCI
romaninsh Jul 24, 2019
ccd35bd
fix
ibelar Jul 25, 2019
6031abb
add caption
ibelar Jul 25, 2019
3295cb3
remove console.log
ibelar Jul 25, 2019
e23ef32
allow to specify options for supported field type
ibelar Jul 25, 2019
107b877
update option property name
ibelar Jul 25, 2019
1e7991e
Update Mapping of fields into desired JS Format
PhilippGrashoff Aug 14, 2019
64e8ab6
Apply fixes from StyleCI
romaninsh Aug 14, 2019
d9d8705
fix js using new options
ibelar Aug 15, 2019
3b68650
Apply fixes from StyleCI
romaninsh Aug 15, 2019
9b89ebe
fix defining option
ibelar Aug 15, 2019
0c3d126
Merge branch 'develop' into feature/multine-containsmany
ibelar Aug 15, 2019
fd3a242
Merge branch 'develop' into feature/multine-containsmany
ibelar Aug 15, 2019
9980e55
enhanced
ibelar Aug 15, 2019
a19abce
code review
ibelar Aug 16, 2019
871e979
First scratch for multiline Documentation
PhilippGrashoff Aug 16, 2019
a5ecdbf
add semantic-ui-vue in atkjs bundle
ibelar Aug 16, 2019
9ccca5f
update webpack configuration
ibelar Aug 16, 2019
367ff44
Better draft, formatting and Screenshots follow
PhilippGrashoff Aug 16, 2019
6526efe
Add some formatting
PhilippGrashoff Aug 16, 2019
df6620c
Update Formatting
PhilippGrashoff Aug 16, 2019
c0c832a
Add Multiline Screenshots
PhilippGrashoff Aug 16, 2019
e5c91c6
Fix Link to Screenshot
PhilippGrashoff Aug 16, 2019
bb7462e
Added Expressions, onLineChange()
PhilippGrashoff Aug 22, 2019
f45d538
Chapter "Changing appearance"
PhilippGrashoff Aug 22, 2019
a9a8a01
Improve formatting
PhilippGrashoff Aug 22, 2019
b4a501c
update doc
ibelar Aug 23, 2019
1577740
add demo
DarkSide666 Sep 5, 2019
6d4da94
Apply fixes from StyleCI
romaninsh Sep 5, 2019
126abef
minor fix
ibelar Sep 9, 2019
1e38da6
remove useSuiVue
ibelar Sep 9, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed demos/atk4.mwb
Binary file not shown.
27 changes: 27 additions & 0 deletions demos/atk4.sql
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,33 @@
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;



--
-- Table structure for table `client`
--
DROP TABLE IF EXISTS `client`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `client` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`addresses` blob,
`accounts` blob,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `client`
--

LOCK TABLES `client` WRITE;
/*!40000 ALTER TABLE `client` DISABLE KEYS */;
INSERT INTO `client` VALUES (1,'John',null,null),(2,'Jane',null,null);
/*!40000 ALTER TABLE `client` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `country`
--
Expand Down
37 changes: 37 additions & 0 deletions demos/multiline-containsmany.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

require 'init.php';
require 'database.php';

class Client extends atk4\data\Model
{
public $table = 'client';
public $caption = 'Client';

public function init()
{
parent::init();

$data = [];

$this->addField('name');
$this->containsMany('Accounts', [Account::class]);
}
}

class Account extends atk4\data\Model
{
public $caption = ' ';

public function init()
{
parent::init();

$this->addField('email', ['required' => true, 'ui' => ['multiline' => ['input', ['icon' => 'envelope', 'type' => 'email']]]]);
$this->addField('password', ['required' => true, 'ui' => ['multiline' => ['input', ['icon' => 'key', 'type' => 'password']]]]);
$this->addField('site', ['required' => true]);
$this->addField('type', ['default' => 'user', 'values' => ['user' => 'Regular User', 'admin' => 'System Admin'], 'ui' => ['multiline' => ['width' => 'four']]]);
}
}

$app->add(['CRUD'])->setModel(new Client($db));
8 changes: 4 additions & 4 deletions demos/multiline.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,19 @@ public function init()
parent::init();

$this->addField('item', ['required' => true, 'default' => 'item']);
$this->addField('qty', ['type' => 'number', 'caption' => 'Qty / Box', 'required' => true, 'ui' => ['multiline' => ['width' => 2]]]);
$this->addField('box', ['type' => 'number', 'caption' => '# of Boxes', 'required' => true, 'ui' => ['multiline' => ['width' => 2]]]);
$this->addField('qty', ['type' => 'integer', 'caption' => 'Qty / Box', 'required' => true, 'ui' => ['multiline' => ['width' => 2]]]);
$this->addField('box', ['type' => 'integer', 'caption' => '# of Boxes', 'required' => true, 'ui' => ['multiline' => ['width' => 2]]]);
$this->addExpression('total', ['expr' => function ($row) {
return $row['qty'] * $row['box'];
}, 'type' => 'number']);
}, 'type' => 'integer']);
}
}

$app->add(['Header', 'MultiLine form field', 'icon' => 'database', 'subHeader' => 'Collect/Edit multiple rows of table record.']);

$data = [];

$inventory = new InventoryItem(new \atk4\data\Persistence_Array($data));
$inventory = new InventoryItem(new \atk4\data\Persistence\Array_($data));

// Populate some data.
$total = 0;
Expand Down
Binary file added docs/images/multiline_email_address.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/multiline_user_addresses.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
211 changes: 211 additions & 0 deletions docs/multiline.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@

.. php:namespace:: atk4\ui\FormField

.. php:class:: MultiLine


=====
Multiline Form Field
=====


The Multiline Form Field is not a single field, but is used to edit several Model records.
A good example is a user who can have many addresses. In this example, the Model `User` containsMany `Addresses`.
This means that the addresses are not stored into a separate database table but into the field `addresses` of user table::

/**
* User model
*/
class User extends \atk4\data\Model
{
public $table = 'user';

public function init()
{
parent:: init();

$this->addField('firstname', ['type' => 'string']);
$this->addField('lastname', ['type' => 'string']);

$this->containsMany('addresses', [Address::class, 'system' => false]);
//$this->hasMany('Email', [Email::class]);
}
}

/**
* Address Model
*/
class Address extends \atk4\data\Model
{
public $table = 'addresses';

public function init()
{
parent::init();

$this->addField('street_and_number', ['type' => 'string']);
$this->addField('zip', ['type' => 'string']);
$this->addField('city', ['type' => 'string']);
$this->addField('country', ['type' => 'string']);
}
}

//Create some sample record of user Model
$user_data = [];
$user = new User(new \atk4\data\Persistence\Array_($user_data));
$user->set('firstname', 'Hans');
$user->set('lastname', 'Test');
$user->save();


//Add a Form to the UI and set User as Model
$user_form = $app->add('Form');
$user_form->setModel($user);

This leads to a Multiline component automatically rendered for adding, editing and deleting Addresses of the user:

.. image:: images/multiline_user_addresses.png

You can also check LINK_TO_DEMO/multiline.php for this example





Manually setting up Multiline
=============================

Multiline FormField is used by default if a Model `containsMany()` or `containsOne()` other Model, but you can set up the multiline component manually. For example, if you wish to edit
a `hasMany()` relation of a Model along with the Model itself. (In contrary to containsMany(), the records of the related Model are stored in a separate table). Lets say a User can have many email addresses,
but you want to store them in a separate table. Uncomment the line `//$this->hasMany('Email', [Email::class]);` in User Model to use it::

/**
* Email Model
*/
class Email extends \atk4\data\Model
{
public $table = 'email';

public function init()
{
parent::init();

$this->addField('email_address', ['type' => 'string']);

$this->hasOne('user_id', [User::class]);
}
}

Now when we use a Form for User records, it won't automatically add a Multiline to edit the email addresses.
If you want to edit them along with the user, Multiline is set up in a few lines::

//Create some sample record of user Model
$user_data = [];
$user = new User(new \atk4\data\Persistence\Array_($user_data));
$user->id = 1;
$user->set('firstname', 'Hans');
$user->set('lastname', 'Test');
$user->save();

//Add a form to UI to edit User record
$user_form = $app->add('Form');
$user_form->setModel($user);
$ml = $user_form->addField('email_addresses', ['MultiLine']);
$ml->setModel($user->ref('Email'));

//set up saving of Email on Form submit
$user_form->onSubmit(function($form) use ($ml) {
$form->model->save();
$ml->saveRows();
//show saved data for testing purposes
return new jsToast(var_export($ml->model->export(), true));
});


Now, there is another MultiLine FormField to add, edit or delete the users email addresses:

.. image:: images/multiline_email_address.png


Multiline and Expressions
=========================
If a Model has Expressions, they automatically get updated when a field value is changed. A loading icon on the ``+`` sign indicates that the expression values are updated.
Lets use the example of demos/multiline.php::

class InventoryItem extends \atk4\data\Model
{
public function init()
{
parent::init();
$this->addField('item', ['required' => true, 'default' => 'item']);
$this->addField('qty', ['type' => 'number', 'caption' => 'Qty / Box', 'required' => true, 'ui' => ['multiline' => ['width' => 2]]]);
$this->addField('box', ['type' => 'number', 'caption' => '# of Boxes', 'required' => true, 'ui' => ['multiline' => ['width' => 2]]]);
$this->addExpression('total', ['expr' => function ($row) {
return $row['qty'] * $row['box'];
}, 'type' => 'number']);
}
}

The 'total' expression will get updated on each field change automatically when InventoryItem is set as model to Multiline.


Manually adding actions on a field value change
===============================================
If you want to define a callback which gets executed if a field value is changed, you can do so using the ``onLineChange()`` method. The first parameter is the callback, the second one an array including the field names which trigger the callback when 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 Toast with some message::

$multiline->onLineChange(function ($rows, $form) {
$total = 0;
foreach ($rows as $row => $cols) {
$qty = array_column($cols, 'qty')[0];
$box = array_column($cols, 'box')[0];
$total = $total + ($qty * $box);
}
return new jsToast('The new Total is '.number_format($total, 2));
}, ['field1', 'field2']);


Changing appearance of Multiline
================================

Header
------
- The header uses the field's caption by default.
- You can edit it by setting the ``header`` property.
- If you want to hide the header, set the ``$header`` property to an empty string ``''``.

Changing how fields are displayed
---------------------------------
If you want to change how single inputs are displayed in the multiline, you can use field's ui property::

$model->addFields([
['name', 'type' => 'string', 'ui' => ['multiline' => ['input', ['icon' => 'user', 'type' => 'text']]]],
['value', 'type' => 'string', 'ui' => ['multiline' => ['input', ['type' => 'number']]]],
['description', 'type' => 'string', 'ui' => ['multiline' => ['textarea']]],
]);

This above will display a name, value and description field within a multiline form field. The value field input will use the html attribute type set to number and the
description field will be display as a textarea input.

The `$ui['multiline']` property can be set using an array. The first element of the array is the field type to render as html in multiline form field and should contains a string value. The supported field type are input, textarea, dropdown or checkbox.
The second element of the array represent the options associated with the field type and should contains an array.
Since Multiline form field used some of Semantic-ui Vue component to render the field type in html, the options accepted
are based on Semantic-ui vue supported property. For example, input field type, or component in Semantic-ui Vue can have it's html type attribute set using the type option, like the value field set above.

You may see each option you can use by looking at Semantic-ui vue component property:
- `input <https://semantic-ui-vue.github.io/#/elements/input>`_
- `dropdown <https://semantic-ui-vue.github.io/#/modules/dropdown>`_
- `checkbox <https://semantic-ui-vue.github.io/#/modules/checkbox>`_

Note: There is no option available for textarea.

Footer
------
You can add a footer to Multiline FormField by adding a sublayout to it. In this example, we add a footer containing a read-only input which could get the value from ``onLineChange`` callback (see above)::

$ml = $form->addField('ml', ['MultiLine', 'options' => ['color' => 'blue']]);
$ml->setModel($inventory);
// Add sublayout with total field.
$sub_layout = $f->layout->addSublayout('Columns');
$sub_layout->addColumn(12);
$c = $sub_layout->addColumn(4);
$f_total = $c->addField('total', ['readonly' => true])->set($total);
14 changes: 14 additions & 0 deletions js/Release.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
## Release note

### version 1.9.0 (2019-08-16)

- Add textarea support in Multiline Vue Component
- Add input options support in Multiline Vue Component

- Include semantic-ui-vue in bundle.
- atkjs-ui.min.js bundle file now include semantic-ui-vue package so there is no need to explicitly load it.

### version 1.8.0 (2019-07-23)

- Multiline Vue component now support containsMany / containsOne.
- Multiline Vue component now support limiting number of row data.
- Multiline Vue component now support Dropdown as a field type.

### version 1.7.0

- New atkConfirm plugin. Will display a user confirmaton dialog using fomantic ui modal.
Expand Down
Loading