lists: correctly handle nesting with empty values

empty values are shown as n/a when they are used in nesting, otherwise
not shown just as before
This commit is contained in:
Andreas Gohr
2023-07-05 13:40:01 +02:00
parent 71b11d8bec
commit 7b7a9290f8
5 changed files with 70 additions and 16 deletions

View File

@ -46,6 +46,21 @@ class NestedResultTest extends StructTest
[['gray', 'yellow'], 'laptop', 'dell', 'latitude'],
];
protected $multiHoleItems = [
[['green', 'yellow'], 'car', 'audi', 'a80'],
[[], 'car', 'audi', 'a4'],
[['black', 'green'], '', 'audi', 'quattro'],
[['red', 'black'], 'car', 'bmw', 'i3'],
[['blue', 'gray'], 'car', 'bmw', 'mini'],
[['red', 'black'], 'car', 'bmw', 'z1'],
[['green', 'blue'], 'laptop', 'apple', 'pro 16'],
[['red', 'blue'], 'laptop', 'apple', 'air'],
[['black', 'red'], 'laptop', 'apple', 'm1'],
[[], 'laptop', 'dell', 'xps'],
[['blue', 'yellow'], '', 'dell', 'inspiron'],
[['gray', 'yellow'], 'laptop', 'dell', 'latitude'],
];
protected $multiMultiItems = [
[['metal', 'wood'], ['green', 'yellow'], 'car', 'audi', 'a80'],
[['metal', 'wood', 'plastic'], ['yellow', 'blue'], 'car', 'audi', 'a4'],
@ -188,6 +203,17 @@ class NestedResultTest extends StructTest
);
}
public function testMultiHoles()
{
$result = $this->makeResult($this->multiHoleItems);
$nestedResult = new NestedResult($result);
$root = $nestedResult->getRoot(3);
$tree = $root->getChildren(); // nest: color, type, brand -> model
$this->assertCount(7, $tree, '6 root nodes of colors + 1 n/a expected'); // should have one n/a node
$this->assertCount(2, $tree[6]->getChildren(), 'top n/a node should have car, laptop');
$this->assertCount(3, $tree[0]->getChildren(), 'black should have car,laptop,n/a');
}
/**
* Nest by two multi value levels
*/

View File

@ -65,6 +65,7 @@ $lang['multidropdown'] = 'Hold CTRL or CMD to select multiple values.';
$lang['duplicate_label'] = "Label <code>%s</code> already exists in schema, second occurance was renamed to <code>%s</code>.";
$lang['emptypage'] = 'Struct data has not been saved for an empty page';
$lang['na'] = 'n/a';
$lang['validation_prefix'] = "Field [%s]: ";

View File

@ -51,7 +51,7 @@ class AggregationList extends Aggregation
// render own value if available
if ($self) {
$this->renderer->listcontent_open();
$this->renderListItem([$self], $node->getDepth()); // zero based depth
$this->renderListItem([$self], $node->getDepth(), true); // zero based depth
$this->renderer->listcontent_close();
}
@ -85,14 +85,14 @@ class AggregationList extends Aggregation
*
* @param Value[] $resultrow
* @param int $depth The current nesting depth (zero based)
* @param bool $showEmpty show a placeholder for empty values?
*/
protected function renderListItem($resultrow, $depth)
protected function renderListItem($resultrow, $depth, $showEmpty = false)
{
$sepbyheaders = $this->searchConfig->getConf()['sepbyheaders'];
$headers = $this->searchConfig->getConf()['headers'];
foreach ($resultrow as $index => $value) {
if ($value->isEmpty()) continue;
$column = $index + $depth; // the resultrow is shifted by the nesting depth
if ($sepbyheaders && !empty($headers[$column])) {
$header = $headers[$column];
@ -101,9 +101,9 @@ class AggregationList extends Aggregation
}
if ($this->mode === 'xhtml') {
$this->renderValueXHTML($value, $header);
$this->renderValueXHTML($value, $header, $showEmpty);
} else {
$this->renderValueGeneric($value, $header);
$this->renderValueGeneric($value, $header, $showEmpty);
}
}
}
@ -112,9 +112,10 @@ class AggregationList extends Aggregation
* Render the given Value in a XHTML renderer
* @param Value $value
* @param string $header
* @param bool $showEmpty
* @return void
*/
protected function renderValueXHTML($value, $header)
protected function renderValueXHTML($value, $header, $showEmpty = false)
{
$attributes = [
'data-struct-column' => strtolower($value->getColumn()->getFullQualifiedLabel()),
@ -127,7 +128,11 @@ class AggregationList extends Aggregation
$this->renderer->doc .= sprintf('<span class="struct_header">%s</span> ', hsc($header));
}
$this->renderer->doc .= '<div class="struct_value">';
$value->render($this->renderer, $this->mode);
if ($value->isEmpty() && $showEmpty) {
$this->renderer->doc .= '<span class="struct_na">' . $this->helper->getLang('na') . '</span>';
} else {
$value->render($this->renderer, $this->mode);
}
$this->renderer->doc .= '</div>';
$this->renderer->doc .= '</div> '; // wrapper
}
@ -138,11 +143,15 @@ class AggregationList extends Aggregation
* @param string $header
* @return void
*/
protected function renderValueGeneric($value, $header)
protected function renderValueGeneric($value, $header, $showEmpty = false)
{
$this->renderer->listcontent_open();
if ($header !== '') $this->renderer->cdata($header . ' ');
$value->render($this->renderer, $this->mode);
if ($value->isEmpty() && $showEmpty) {
$this->renderer->cdata($this->helper->getLang('na'));
} else {
$value->render($this->renderer, $this->mode);
}
$this->renderer->listcontent_close();
}
}

View File

@ -111,11 +111,18 @@ class NestedResult
$valObj = array_shift($row);
if (!$valObj) return; // no more values to nest, usually shouldn't happen
if ($valObj->getColumn()->isMulti()) {
if ($valObj->getColumn()->isMulti() && $valObj->getValue()) {
// split up multi values into separate nodes
$values = $valObj->getValue();
foreach ($values as $value) {
$newValue = new Value($valObj->getColumn(), $value);
if($values) {
foreach ($values as $value) {
$newValue = new Value($valObj->getColumn(), $value);
$node = $this->getNodeForValue($newValue, $depth);
$parent->addChild($node);
$this->nestBranch($node, $row, $nesting, $depth + 1);
}
} else {
$newValue = new Value($valObj->getColumn(), ''); // add empty node
$node = $this->getNodeForValue($newValue, $depth);
$parent->addChild($node);
$this->nestBranch($node, $row, $nesting, $depth + 1);

View File

@ -137,15 +137,26 @@ class NestedValue
*/
public function sortChildren(NestedValue $a, NestedValue $b)
{
$compA = join('-', (array)$a->getValueObject()->getCompareValue());
$compB = join('-', (array)$b->getValueObject()->getCompareValue());
// sort empty values to the end
if($compA === $compB) {
return 0;
}
if($compA === '') {
return 1;
}
if($compB === '') {
return -1;
}
// note: the way NestedResults build the NestedValues, the value object should
// always contain a single value only. But since the associated column is still
// a multi-value column, getCompareValue() will still return an array.
// So here we treat all returns as array and join them with a dash (even though
// there should never be more than one value in there)
return Sort::strcmp(
join('-', (array)$a->getValueObject()->getCompareValue()),
join('-', (array)$b->getValueObject()->getCompareValue())
);
return Sort::strcmp($compA, $compB);
}
/**