diff --git a/demos/grid.php b/demos/grid.php
new file mode 100644
index 0000000000..af68046d7b
--- /dev/null
+++ b/demos/grid.php
@@ -0,0 +1,20 @@
+add(['Grid']);
+$g->setModel(new Country($db));
+$g->addQuickSearch();
+
+$g->menu->addItem(['Add Country', 'icon'=>'add square'], new \atk4\ui\jsExpression('alert(123)'));
+$g->menu->addItem(['Re-Import', 'icon'=>'power']);
+$g->menu->addItem(['Delete All', 'icon'=>'trash', 'red active']);
+
+$g->addAction('Say HI', new \atk4\ui\jsExpression('alert("hi")'));
+$g->addAction(['icon'=>'pencil'], new \atk4\ui\jsExpression('alert($(this).closest("tr").data("id"))'));
+
+$sel = $g->addSelection();
+$g->menu->addItem('show selection')->on('click', new \atk4\ui\jsExpression(
+ 'alert("Selected: "+[].join(", "))', [$sel->jsChecked()]
+));
diff --git a/demos/init.php b/demos/init.php
index c0bb8e3a5f..385f4d4982 100644
--- a/demos/init.php
+++ b/demos/init.php
@@ -22,7 +22,7 @@
$form = $layout->leftMenu->addGroup(['Grid and Table', 'icon'=>'table']);
$form->addItem('Data table with formatted columns', ['table']);
$form->addItem('Table interractions', ['multitable']);
- $form->addItem('Grid - Table+Bar+Search+Paginator', ['table']);
+ $form->addItem('Grid - Table+Bar+Search+Paginator', ['grid']);
$form->addItem('Interactivity - Modals and Expanders', ['expander']);
$basic = $layout->leftMenu->addGroup(['Basics', 'icon'=>'cubes']);
diff --git a/docs/misc.rst b/docs/misc.rst
index 5e3b27fedd..db7b9e74c1 100644
--- a/docs/misc.rst
+++ b/docs/misc.rst
@@ -127,7 +127,6 @@ Label has the following propetries:
All the above can be string, array (passed to Icon, Image or View class) or an object.
-
.. php:class:: HelloWorld
HelloWorld
diff --git a/src/App.php b/src/App.php
index 23f0fd23ec..381c8665fa 100644
--- a/src/App.php
+++ b/src/App.php
@@ -36,6 +36,11 @@ class App
public $ui_persistence = null;
+ /**
+ * Constructor.
+ *
+ * @param array $defaults
+ */
public function __construct($defaults = [])
{
// Process defaults
@@ -88,6 +93,11 @@ public function __construct($defaults = [])
}
}
+ /**
+ * Catch exception.
+ *
+ * @param mixed $exception
+ */
public function caughtException($exception)
{
$l = new \atk4\ui\App();
@@ -107,6 +117,11 @@ public function caughtException($exception)
$this->run_called = true;
}
+ /**
+ * Outputs debug info.
+ *
+ * @param string $str
+ */
public function outputDebug($str)
{
echo 'DEBUG:'.$str.'
';
@@ -116,6 +131,8 @@ public function outputDebug($str)
* Will perform a preemptive output and terminate. Do not use this
* directly, instead call it form Callback, jsCallback or similar
* other classes.
+ *
+ * @param string $output
*/
public function terminate($output = null)
{
@@ -124,6 +141,14 @@ public function terminate($output = null)
exit;
}
+ /**
+ * Initializes layout.
+ *
+ * @param string|Layout\Generic $layout
+ * @param array $options
+ *
+ * @return $this
+ */
public function initLayout($layout, $options = [])
{
if (is_string($layout)) {
@@ -154,6 +179,14 @@ public function addStyle($style)
$this->html->template->appendHTML('HEAD', $this->getTag('style', $style));
}
+ /**
+ * Normalizes class name.
+ *
+ * @param string $name
+ * @param string $prefix
+ *
+ * @return string
+ */
public function normalizeClassNameApp($name, $prefix = null)
{
if (strpos('/', $name) === false && strpos('\\', $name) === false) {
@@ -163,6 +196,11 @@ public function normalizeClassNameApp($name, $prefix = null)
return $name;
}
+ /**
+ * Create object and associate it with this app.
+ *
+ * @return object
+ */
public function add()
{
if ($this->layout) {
@@ -180,6 +218,9 @@ public function add()
}
}
+ /**
+ * Runs app and echo rendered template.
+ */
public function run()
{
$this->run_called = true;
@@ -201,6 +242,13 @@ public function init()
$this->_init();
}
+ /**
+ * Load template.
+ *
+ * @param string $name
+ *
+ * @return Template
+ */
public function loadTemplate($name)
{
$template = new Template();
@@ -297,20 +345,49 @@ public function url($args = [])
* 10. pass array as text to net tags (array must contain 1 to 3 elements corresponding to arguments):
* getTag('a', ['href'=>'foo.html'], ['b','click here']);
* --> click here
+ *
+ * @param string|array $tag
+ * @param string $attr
+ * @param string|array $value
+ *
+ * @return string
*/
public function getTag($tag = null, $attr = null, $value = null)
{
if ($tag === null) {
$tag = 'div';
} elseif (is_array($tag)) {
- $value = $attr;
- $attr = $tag;
+ $tmp = $tag;
+
$tag = 'div';
+ $value = '';
+
+ if (isset($tmp[0])) {
+ $tag = $tmp[0];
+ unset($tmp[0]);
+ }
+
+ if (isset($tmp[1])) {
+ $value = $tmp[1];
+ unset($tmp[1]);
+ }
+
+ $attr = $tmp;
+ }
+ if ($tag[0] === '<') {
+ return $tag;
}
if (is_string($attr)) {
$value = $attr;
$attr = null;
}
+
+ if (is_string($value)) {
+ $value = $this->encodeHTML($value);
+ } elseif (is_array($value)) {
+ $value = $this->getTag($value);
+ }
+
if (!$attr) {
return "<$tag>".($value ? ($value)."$tag>" : '');
}
diff --git a/src/Button.php b/src/Button.php
index cbc921a540..f5949292e1 100644
--- a/src/Button.php
+++ b/src/Button.php
@@ -70,24 +70,4 @@ public function renderView()
parent::renderView();
}
-
- /**
- * Makes button into a "" element with a link.
- *
- * @param string $url
- *
- * @return $this
- */
- public function link($url)
- {
- $this->element = 'a';
- if (is_string($url)) {
- $this->setAttr('href', $url);
-
- return $this;
- }
- $this->setAttr('href', $this->app->url($url));
-
- return $this;
- }
}
diff --git a/src/Grid.php b/src/Grid.php
new file mode 100644
index 0000000000..9a221e2638
--- /dev/null
+++ b/src/Grid.php
@@ -0,0 +1,82 @@
+menu) {
+ $this->menu = $this->add(['Menu', 'activate_on_click'=>false], 'Menu');
+ }
+
+ if (!$this->table) {
+ $this->table = $this->add(['Table', 'very compact'], 'Table');
+ }
+ }
+
+ public function addButton($text)
+ {
+ return $this->menu->addItem()->add(new Button($text));
+ }
+
+ public function addQuickSearch($fields = [])
+ {
+ if (!$fields) {
+ $fields = [$this->model->title_field];
+ }
+
+ $x = $this->menu->addMenuRight();
+ $this->quickSearch = $x->addItem()->setElement('div')->add(new \atk4\ui\FormField\Input(['placeholder'=>'Search', 'icon'=>'search']))->addClass('transparent');
+ }
+
+ public function addAction($label, $action)
+ {
+ if (!$this->actions) {
+ $this->actions = $this->table->addColumn('TableColumn/Actions');
+ }
+
+ $this->actions->addAction($label, $action);
+ }
+
+ public function setModel(\atk4\data\Model $model, $columns = null)
+ {
+ return $this->model = $this->table->setModel($model, $columns)->setLimit($this->ipp);
+ }
+
+ public function addSelection()
+ {
+ $this->selection = $this->table->addColumn('TableColumn/Checkbox');
+
+ // Move element to the beginning
+ $k = array_search($this->selection, $this->table->columns);
+ $this->table->columns = [$k => $this->table->columns[$k]] + $this->table->columns;
+
+ return $this->selection;
+ }
+}
diff --git a/src/Menu.php b/src/Menu.php
index 44a5a9cf5b..957cb63750 100644
--- a/src/Menu.php
+++ b/src/Menu.php
@@ -17,11 +17,16 @@ class Menu extends View
public function addItem($name = null, $action = null)
{
- $item = $this->add(new Item(['element'=>'a']));
- if (!is_null($name)) {
- $item->set($name);
+ if (is_object($name)) {
+ $item = $name;
+ } elseif ($name) {
+ $item = new Item($name);
+ } else {
+ $item = new Item();
}
+ $item = $this->add($item)->setElement('a');
+
if (is_array($action)) {
$action = $this->app->url($action);
}
@@ -30,6 +35,10 @@ public function addItem($name = null, $action = null)
$item->setAttr('href', $action);
}
+ if ($action instanceof jsExpressionable) {
+ $item->js('click', $action);
+ }
+
return $item;
}
@@ -77,6 +86,14 @@ public function addGroup($title)
return $group;
}
+ public function addMenuRight()
+ {
+ $menu = $this->add(new self(), ['RightMenu', 'ui'=>false]);
+ $menu->removeClass('item')->addClass('right menu');
+
+ return $menu;
+ }
+
public function add($object, $region = null)
{
$item = parent::add($object, $region);
diff --git a/src/Table.php b/src/Table.php
index 210bd0f378..7113596d9d 100644
--- a/src/Table.php
+++ b/src/Table.php
@@ -104,19 +104,21 @@ class Table extends Lister
*
* @param string $name Data model field name
* @param Column\Generic $columnDef
- * @param array $fieldDef Array of defaults for new Model field
*
* @return Column\Generic
*/
- public function addColumn($name, $columnDef = null, $fieldDef = [])
+ public function addColumn($name, $columnDef = null)
{
if (!$this->model) {
$this->model = new \atk4\ui\misc\ProxyModel();
}
- $field = $this->model->hasElement($name);
- if (!$field) {
- $field = $this->model->addField($name, $fieldDef);
+ if ($name !== null) {
+ $field = $this->model->hasElement($name);
+ if (!$field) {
+ $columnDef = $name;
+ $name = null;
+ }
}
if ($columnDef === null) {
@@ -132,7 +134,16 @@ public function addColumn($name, $columnDef = null, $fieldDef = [])
}
$columnDef->table = $this;
- $this->columns[$name] = $columnDef;
+ if (is_null($name)) {
+ $this->columns[] = $columnDef;
+ } elseif (isset($this->columns[$name])) {
+ if (!is_array($this->columns[$name])) {
+ $this->columns[$name] = [$this->columns[$name]];
+ }
+ $this->columns[$name][] = $columnDef;
+ } else {
+ $this->columns[$name] = $columnDef;
+ }
return $columnDef;
}
@@ -336,9 +347,19 @@ public function renderHeaderCells()
{
$output = [];
foreach ($this->columns as $name => $column) {
- $field = $this->model->getElement($name);
- $output[] = $column->getHeaderCell($field);
+ // If multiple formatters are defined, use the first for the header cell
+ if (is_array($column)) {
+ $column = $column[0];
+ }
+
+ if (!is_int($name)) {
+ $field = $this->model->getElement($name);
+
+ $output[] = $column->getHeaderCell($field);
+ } else {
+ $output[] = $column->getHeaderCell();
+ }
}
return implode('', $output);
@@ -384,9 +405,19 @@ public function getRowTemplate()
{
$output = [];
foreach ($this->columns as $name => $column) {
- $field = $this->model->getElement($name);
- $output[] = $column->getCellTemplate($field);
+ // If multiple formatters are defined, use the first for the header cell
+ if (is_array($column)) {
+ $column = $column[0];
+ }
+
+ if (!is_int($name)) {
+ $field = $this->model->getElement($name);
+
+ $output[] = $column->getCellTemplate($field);
+ } else {
+ $output[] = $column->getCellTemplate();
+ }
}
return implode('', $output);
diff --git a/src/TableColumn/Actions.php b/src/TableColumn/Actions.php
new file mode 100644
index 0000000000..ba160fa0ab
--- /dev/null
+++ b/src/TableColumn/Actions.php
@@ -0,0 +1,40 @@
+actions) + 1);
+
+ if (!is_object($button)) {
+ $button = new \atk4\ui\Button($button);
+ }
+ $button->app = $this->table->app;
+
+ $this->actions[$name] = $button;
+ $button->addClass('b_'.$name);
+ $button->addClass('compact');
+ $this->table->on('click', '.b_'.$name, $callback);
+ }
+
+ public function getCellTemplate(\atk4\data\Field $f = null)
+ {
+ $output = '';
+
+ // render our actions
+ foreach ($this->actions as $action) {
+ $output .= $action->getHTML();
+ }
+
+ return $this->getTag('td', 'body', [$output]);
+ }
+
+ // rest will be implemented for crud
+}
diff --git a/src/TableColumn/Checkbox.php b/src/TableColumn/Checkbox.php
new file mode 100644
index 0000000000..160a7fc2cb
--- /dev/null
+++ b/src/TableColumn/Checkbox.php
@@ -0,0 +1,45 @@
+table->jsRender().").find('.checked.".$this->class."').closest('tr').map(function(){ ".
+ "return $(this).data('id');}).get()");
+ }
+
+ public function init()
+ {
+ parent::init();
+ if (!$this->class) {
+ $this->class = 'cb_'.$this->short_name;
+ }
+ }
+
+ public function getHeaderCell(\atk4\data\Field $f = null)
+ {
+ if (isset($f)) {
+ throw new Exception(['Checkbox must be placed in an empty column. Don\'t specify any field.', 'field'=>$f]);
+ }
+ $this->table->js(true)->find('.'.$this->class)->checkbox();
+
+ return parent::getHeaderCell($f);
+ }
+
+ public function getCellTemplate(\atk4\data\Field $f = null)
+ {
+ return $this->getTag('td', 'body', ['div', 'class'=>'ui checkbox '.$this->class, ['input', 'type'=>'checkbox']]);
+ }
+}
diff --git a/src/TableColumn/Generic.php b/src/TableColumn/Generic.php
index fa06c0e83e..c70069fb77 100644
--- a/src/TableColumn/Generic.php
+++ b/src/TableColumn/Generic.php
@@ -8,6 +8,8 @@
class Generic
{
use \atk4\core\AppScopeTrait;
+ use \atk4\core\InitializerTrait;
+ use \atk4\core\TrackableTrait;
/**
* Link back to the table, where column is used.
@@ -101,8 +103,12 @@ public function getTag($tag, $position, $value)
*
* @return string
*/
- public function getHeaderCell(\atk4\data\Field $f)
+ public function getHeaderCell(\atk4\data\Field $f = null)
{
+ if ($f === null) {
+ return $this->getTag('th', 'head', '');
+ }
+
return $this->getTag('th', 'head', $f->getCaption());
}
@@ -135,8 +141,12 @@ public function getTotalsCell(\atk4\data\Field $f, $value)
*
* @return string
*/
- public function getCellTemplate(\atk4\data\Field $f)
+ public function getCellTemplate(\atk4\data\Field $f = null)
{
+ if ($f === null) {
+ return $this->getTag('td', 'body', '{$c_'.$this->short_name.'}');
+ }
+
return $this->getTag('td', 'body', '{$'.$f->short_name.'}');
}
diff --git a/src/TableColumn/Link.php b/src/TableColumn/Link.php
index 149fa6e24c..0cbda4a29b 100644
--- a/src/TableColumn/Link.php
+++ b/src/TableColumn/Link.php
@@ -31,15 +31,19 @@ public function __construct($page = [])
/**
* kill me now for this code :!!
*/
- public function getCellTemplate(\atk4\data\Field $f)
+ public function getCellTemplate(\atk4\data\Field $f = null)
{
+ if (is_null($f)) {
+ $f = $this;
+ }
+
foreach ($this->page as &$val) {
$val = str_replace('{$', '___o', $val);
$val = str_replace('}', 'c___', $val);
}
$href = $this->app->url($this->page);
- $output = $this->getTag('td', 'body', $this->app->getTag('a', ['href'=>$href], '{$'.$f->short_name.'}'));
+ $output = $this->getTag('td', 'body', ['a', 'href'=>$href, '{$'.$f->short_name.'}']);
$output = str_replace('___o', '{$', $output);
$output = str_replace('c___', '_urlencode}', $output);
diff --git a/src/TableColumn/Money.php b/src/TableColumn/Money.php
index 7276c253b2..7ca3fdbbb6 100644
--- a/src/TableColumn/Money.php
+++ b/src/TableColumn/Money.php
@@ -11,8 +11,12 @@ class Money extends Generic
public $attr = ['all'=>['class'=>['right aligned single line']]];
- public function getCellTemplate(\atk4\data\Field $f)
+ public function getCellTemplate(\atk4\data\Field $f = null)
{
+ if (!isset($f)) {
+ throw new Exception(['Money column requires a field']);
+ }
+
return $this->app->getTag(
'td',
['class'=> '{$_'.$f->short_name.'_money}'],
diff --git a/src/TableColumn/Status.php b/src/TableColumn/Status.php
index d68451f966..96a6705c62 100644
--- a/src/TableColumn/Status.php
+++ b/src/TableColumn/Status.php
@@ -26,13 +26,17 @@ public function __construct($states)
$this->states = $states;
}
- public function getCellTemplate(\atk4\data\Field $f)
+ public function getCellTemplate(\atk4\data\Field $f = null)
{
+ if ($f === null) {
+ throw new Exception(['Status can be used only with model field']);
+ }
+
return $this->app->getTag(
'td',
['class'=> '{$_'.$f->short_name.'_status}'],
- $this->app->getTag('i', ['class'=>'icon {$_'.$f->short_name.'_icon}'], '').
- ' {$'.$f->short_name.'}'
+ [$this->app->getTag('i', ['class'=>'icon {$_'.$f->short_name.'_icon}'], '').
+ ' {$'.$f->short_name.'}', ]
);
}
diff --git a/src/TableColumn/Template.php b/src/TableColumn/Template.php
index 43d0a55d24..0ecdbec0d6 100644
--- a/src/TableColumn/Template.php
+++ b/src/TableColumn/Template.php
@@ -24,7 +24,7 @@ public function __construct($template)
$this->template = $template;
}
- public function getCellTemplate(\atk4\data\Field $f)
+ public function getCellTemplate(\atk4\data\Field $f = null)
{
return $this->getTag('td', 'body', $this->template);
}
diff --git a/src/View.php b/src/View.php
index af3fcd142d..38ccbd25eb 100644
--- a/src/View.php
+++ b/src/View.php
@@ -266,6 +266,40 @@ protected function setProperty($key, $val)
]);
}
+ /**
+ * Sets View element.
+ *
+ * @param string $element
+ *
+ * @return $this
+ */
+ public function setElement($element)
+ {
+ $this->element = $element;
+
+ return $this;
+ }
+
+ /**
+ * Makes view into a "" element with a link.
+ *
+ * @param string $url
+ *
+ * @return $this
+ */
+ public function link($url)
+ {
+ $this->element = 'a';
+ if (is_string($url)) {
+ $this->setAttr('href', $url);
+
+ return $this;
+ }
+ $this->setAttr('href', $this->app->url($url));
+
+ return $this;
+ }
+
// }}}
// {{{ Default init() method and add() logic
diff --git a/template/semantic-ui/grid.html b/template/semantic-ui/grid.html
new file mode 100644
index 0000000000..9bd5ed4bfd
--- /dev/null
+++ b/template/semantic-ui/grid.html
@@ -0,0 +1,3 @@
+<{_element}div{/} id="{$_id}" class="{_ui}ui{/} {$class} {_class}{/}" style="{$style}" {$attributes}>
+{$Menu}
+{$Table}{_element}div{/}>
\ No newline at end of file
diff --git a/template/semantic-ui/grid.pug b/template/semantic-ui/grid.pug
new file mode 100644
index 0000000000..76432f46d5
--- /dev/null
+++ b/template/semantic-ui/grid.pug
@@ -0,0 +1,7 @@
+<{_element}div{/} id="{$_id}" class="{_ui}ui{/} {$class} {_class}{/}" style="{$style}" {$attributes}>
+
+| {$Menu}
+
+| {$Table}
+
+{_element}div{/}>
diff --git a/template/semantic-ui/html.html b/template/semantic-ui/html.html
index 8d24a2d8d9..653b81140f 100644
--- a/template/semantic-ui/html.html
+++ b/template/semantic-ui/html.html
@@ -6,7 +6,7 @@
-
+