Skip to content

Commit

Permalink
feature(views): lists can be rendered as tables
Browse files Browse the repository at this point in the history
The list view functions now accept `table` as a `list_type` value. This outputs
a bordered, one-column table with no headings.

Columns and their headings are specified via `Elgg\Views\TableColumn` objects. The
`ColumnFactory` class (accessible via `elgg()->table_columns`) includes methods for
creating columns based on core views, or properties/methods.

Newest users admin page now shows other useful info

Fixes Elgg#9629
  • Loading branch information
mrclay committed Aug 9, 2016
1 parent b2a3de2 commit 688cf8b
Show file tree
Hide file tree
Showing 25 changed files with 698 additions and 6 deletions.
2 changes: 2 additions & 0 deletions engine/classes/Elgg/Application.php
Expand Up @@ -16,6 +16,7 @@
* @since 2.0.0
*
* @property-read \Elgg\Menu\Service $menus
* @property-read \Elgg\Views\TableColumn\ColumnFactory $table_columns
*/
class Application {

Expand Down Expand Up @@ -44,6 +45,7 @@ class Application {
private static $public_services = [
//'config' => true,
'menus' => true,
'table_columns' => true,
];

/**
Expand Down
3 changes: 3 additions & 0 deletions engine/classes/Elgg/Di/ServiceProvider.php
Expand Up @@ -68,6 +68,7 @@
* @property-read \Elgg\Database\SubtypeTable $subtypeTable
* @property-read \Elgg\Cache\SystemCache $systemCache
* @property-read \Elgg\SystemMessagesService $systemMessages
* @property-read \Elgg\Views\TableColumn\ColumnFactory $table_columns
* @property-read \Elgg\Timer $timer
* @property-read \Elgg\I18n\Translator $translator
* @property-read \Elgg\UpgradeService $upgrades
Expand Down Expand Up @@ -357,6 +358,8 @@ public function __construct(\Elgg\Config $config) {
return new \Elgg\SystemMessagesService($c->session);
});

$this->setClassName('table_columns', \Elgg\Views\TableColumn\ColumnFactory::class);

$this->setClassName('timer', \Elgg\Timer::class);

$this->setClassName('translator', \Elgg\I18n\Translator::class);
Expand Down
28 changes: 28 additions & 0 deletions engine/classes/Elgg/Views/TableColumn.php
@@ -0,0 +1,28 @@
<?php
namespace Elgg\Views;

/**
* A renderer for a column of table cells and a header
*/
interface TableColumn {

/**
* Get the rendered heading cell. Cell will be auto-wrapped with a TH element if the
* returned string doesn't begin with "<th" or "<td".
*
* @return string e.g. "Title" or "<th>Title</th>"
*/
public function renderHeading();

/**
* Render a value cell. Cell will be auto-wrapped with a TD element if the returned
* string doesn't begin with "<th" or "<td".
*
* @param mixed $item Object/row from which to pull the value
* @param string $type Type of object
* @param array $item_vars Parameters from the listing function
*
* @return string e.g. "My Great Title" or "<td>My Great Title</td>"
*/
public function renderCell($item, $type, $item_vars);
}
45 changes: 45 additions & 0 deletions engine/classes/Elgg/Views/TableColumn/CallableColumn.php
@@ -0,0 +1,45 @@
<?php
namespace Elgg\Views\TableColumn;

use Elgg\Views\TableColumn;

/**
* Table column rendered by a function
*/
class CallableColumn implements TableColumn {

/**
* @var string
*/
private $heading;

/**
* @var callable
*/
private $renderer;

/**
* Constructor
*
* @param callable $renderer Rendering function
* @param string $heading Heading
*/
public function __construct(callable $renderer, $heading) {
$this->renderer = $renderer;
$this->heading = $heading;
}

/**
* {@inheritdoc}
*/
public function renderHeading() {
return $this->heading;
}

/**
* {@inheritdoc}
*/
public function renderCell($item, $type, $item_vars) {
return call_user_func($this->renderer, $item, $type, $item_vars);
}
}
145 changes: 145 additions & 0 deletions engine/classes/Elgg/Views/TableColumn/ColumnFactory.php
@@ -0,0 +1,145 @@
<?php
namespace Elgg\Views\TableColumn;

use Elgg\Values;
use Elgg\Views\TableColumn;

/**
* Factory for table column objects
*
* @internal Use elgg()->table_columns to access the instance of this.
*
* @method TableColumn admin($heading = null, $vars = [])
* @method TableColumn banned($heading = null, $vars = [])
* @method TableColumn container($heading = null, $vars = [])
* @method TableColumn excerpt($heading = null, $vars = [])
* @method TableColumn icon($heading = null, $vars = [])
* @method TableColumn item($heading = null, $vars = [])
* @method TableColumn language($heading = null, $vars = [])
* @method TableColumn link($heading = null, $vars = [])
* @method TableColumn owner($heading = null, $vars = [])
* @method TableColumn time_created($heading = null, $vars = [])
* @method TableColumn time_updated($heading = null, $vars = [])
* @method TableColumn description($heading = null)
* @method TableColumn email($heading = null)
* @method TableColumn name($heading = null)
* @method TableColumn type($heading = null)
* @method TableColumn username($heading = null)
* @method TableColumn getSubtype($heading = null)
* @method TableColumn getDisplayName($heading = null)
* @method TableColumn getMimeType($heading = null)
* @method TableColumn getSimpleType($heading = null)
*/
class ColumnFactory {

/**
* Make a column from one of the page/components/column/* views.
*
* @param string $name Column name (view will be "page/components/column/$name")
* @param string $heading Optional heading
* @param array $vars View vars (item, item_vars, and type will be merged in)
*
* @return ViewColumn
*/
public function fromView($name, $heading = null, $vars = []) {
$view = "page/components/column/$name";

if (!is_string($heading)) {
if (elgg_language_key_exists("table_columns:fromView:$name")) {
$heading = elgg_echo("table_columns:fromView:$name");
} else {
$title = str_replace('_', ' ', $name);
$heading = elgg_ucwords($title);
}
}

return new ViewColumn($view, $heading, $vars);
}

/**
* Make a column by reading a property of the item
*
* @param string $name Property name. e.g. "description", "email", "type"
* @param string $heading Heading
*
* @return CallableColumn
*/
public function fromProperty($name, $heading = null) {
if (!is_string($heading)) {
if (elgg_language_key_exists("table_columns:fromProperty:$name")) {
$heading = elgg_echo("table_columns:fromProperty:$name");
} else {
$title = str_replace('_', ' ', $name);
$heading = elgg_ucwords($title);
}
}

$renderer = function ($item) use ($name) {
return $item->{$name};
};

return new CallableColumn($renderer, $heading);
}

/**
* Make a column by calling a method on the item
*
* @param string $name Method name. e.g. "getSubtype", "getDisplayName"
* @param string $heading Heading
* @param array $args Method arguments
*
* @return CallableColumn
*/
public function fromMethod($name, $heading = null, $args = []) {
if (!is_string($heading)) {
if (elgg_language_key_exists("table_columns:fromMethod:$name")) {
$heading = elgg_echo("table_columns:fromMethod:$name");
} else {
$title = str_replace('_', ' ', $name);
$heading = elgg_ucwords($title);
}
}

$renderer = function ($item) use ($name, $args) {
return call_user_func_array([$item, $name], $args);
};

return new CallableColumn($renderer, $heading);
}

/**
* Handle undefined method calls
*
* @param string $name Method name
* @param array $arguments Arguments
* @return mixed
*/
public function __call($name, $arguments) {
// allow hook to hijack magic methods
$column = elgg_trigger_plugin_hook('table_columns:call', $name, [
'arguments' => $arguments,
]);
if ($column instanceof TableColumn) {
return $column;
}

if (elgg_language_key_exists("table_columns:fromView:$name")) {
array_unshift($arguments, $name);
return call_user_func_array([$this, 'fromView'], $arguments);
}

if (elgg_language_key_exists("table_columns:fromProperty:$name")) {
array_unshift($arguments, $name);
return call_user_func_array([$this, 'fromProperty'], $arguments);
}

if (elgg_language_key_exists("table_columns:fromMethod:$name")) {
array_unshift($arguments, $name);
return call_user_func_array([$this, 'fromMethod'], $arguments);
}

// empty column and error
_elgg_services()->logger->error(__CLASS__ . ": No method defined '$name'");
return new CallableColumn([Values::class, 'getNull'], '');
}
}
62 changes: 62 additions & 0 deletions engine/classes/Elgg/Views/TableColumn/ViewColumn.php
@@ -0,0 +1,62 @@
<?php
namespace Elgg\Views\TableColumn;

use Elgg\Views\TableColumn;

/**
* Table column rendered by a view
*/
class ViewColumn implements TableColumn {

/**
* @var string
*/
private $heading;

/**
* @var string
*/
private $view;

/**
* @var array
*/
private $vars;

/**
* Constructor
*
* @param string $view The view to render the value
* @param string $heading Heading
* @param array $vars Vars to merge into the view vars
*/
public function __construct($view, $heading = null, $vars = []) {
$this->view = $view;
$this->vars = $vars;

if (!is_string($heading)) {
$heading = elgg_echo("ViewColumn:view:$view");
}
$this->heading = $heading;
}

/**
* {@inheritdoc}
*/
public function renderHeading() {
return $this->heading;
}

/**
* {@inheritdoc}
*/
public function renderCell($item, $type, $item_vars) {
$vars = $this->vars + [
'item' => $item,
'item_vars' => $item_vars,
'type' => $type,
];

return elgg_view($this->view, $vars);
}
}
2 changes: 1 addition & 1 deletion engine/lib/entities.php
Expand Up @@ -366,7 +366,7 @@ function _elgg_get_entity_time_where_sql($table, $time_created_upper = null,
* @param array $options Any options from $getter options plus:
* item_view => STR Optional. Alternative view used to render list items
* full_view => BOOL Display full view of entities (default: false)
* list_type => STR 'list' or 'gallery'
* list_type => STR 'list', 'gallery', or 'table'
* list_type_toggle => BOOL Display gallery / list switch
* pagination => BOOL Display pagination links
* no_results => STR|Closure Message to display when there are no entities
Expand Down
14 changes: 14 additions & 0 deletions engine/lib/mb_wrapper.php
Expand Up @@ -196,6 +196,20 @@ function elgg_strtoupper() {
return call_user_func_array('strtoupper', $args);
}

/**
* Wrapper for mb_convert_case($str, MB_CASE_TITLE)
*
* @param string $str String
* @return string
* @since 2.3
*/
function elgg_ucwords($str) {
if (is_callable('mb_convert_case')) {
return mb_convert_case($str, MB_CASE_TITLE, 'UTF-8');
}
return ucwords($str);
}

/**
* Wrapper function for mb_substr_count(). Falls back to substr_count() if
* mb_substr_count() isn't available. Parameters are passed to the
Expand Down
8 changes: 5 additions & 3 deletions engine/lib/views.php
Expand Up @@ -1037,10 +1037,12 @@ function elgg_view_entity_list($entities, array $vars = array()) {
$vars["pagination"] = false;
}

if ($vars['list_type'] != 'list') {
return elgg_view('page/components/gallery', $vars);
} else {
if ($vars['list_type'] == 'table') {
return elgg_view('page/components/table', $vars);
} elseif ($vars['list_type'] == 'list') {
return elgg_view('page/components/list', $vars);
} else {
return elgg_view('page/components/gallery', $vars);
}
}

Expand Down
25 changes: 25 additions & 0 deletions languages/en.php
Expand Up @@ -131,6 +131,31 @@
'upload:error:extension' => 'Cannot save the uploaded file.',
'upload:error:unknown' => 'The file upload failed.',

/**
* Table columns
*/
'table_columns:fromView:admin' => 'Admin',
'table_columns:fromView:banned' => 'Banned',
'table_columns:fromView:container' => 'Container',
'table_columns:fromView:excerpt' => 'Description',
'table_columns:fromView:link' => 'Name/Title',
'table_columns:fromView:icon' => 'Icon',
'table_columns:fromView:item' => 'Item',
'table_columns:fromView:language' => 'Language',
'table_columns:fromView:owner' => 'Owner',
'table_columns:fromView:time_created' => 'Time Created',
'table_columns:fromView:time_updated' => 'Time Updated',

'table_columns:fromProperty:description' => 'Description',
'table_columns:fromProperty:email' => 'Email',
'table_columns:fromProperty:name' => 'Name',
'table_columns:fromProperty:type' => 'Type',
'table_columns:fromProperty:username' => 'Username',

'table_columns:fromMethod:getSubtype' => 'Subtype',
'table_columns:fromMethod:getDisplayName' => 'Name/Title',
'table_columns:fromMethod:getMimeType' => 'MIME Type',
'table_columns:fromMethod:getSimpleType' => 'Type',

/**
* User details
Expand Down
4 changes: 4 additions & 0 deletions views/default/admin/users/newest.css
@@ -0,0 +1,4 @@

.elgg-newest-users td + td {
white-space: nowrap;
}

0 comments on commit 688cf8b

Please sign in to comment.