Grido – DataGrid pro Nette

o5
Člen | 416
+
+2
-

Release 2.1

Někdo si už možná všiml, včera (po téměř roce) vyšla verze 2.1.0. Obsahuje 84 commitů a jedná se hlavně o fixy a pár drobných vylepšení. Nejdůležitější je, že by tato verze neměla nic rozbít a tedy doporučuji všem si změnit verzi v composer.json na 2.1, jelikož @dev verze už nyní obsahuje (pro někoho možné) BC.

Hlavní změna v 2.1:
  • Asi nejdůležitější se odehrálo v Grido\Components\Export. Tato komponenta by nyní měla fungovat správně i s velkým počtem dat a lze nyní prostřednictvím $export->setFetchLimit() ovlivnit, kolik řádků najednou se má vytáhnout z DB (výchozí limit je 100000). Jako bonus přibyly možnosti, kterými lze ovlivnit výsledný export. Pomocí $export->setHeader() lze nastavit hlavičku CSV (labely sloupců) a pomocí callbacku $export->setCustomData() lze exportovat i sloupec, který není definován. V dokumentaci to zatím není, prozatím se bude muset stačit podívat do testů
Příští verze bude 3.0:

Další verze bude 3.0 a bude vyžadovat PHP 5.5 (PHP 5.5 je venku skoro 2,5roku a PHP 5.4 je měsíc mrtvé, viz Supported Versions). Dále bude Grido pravděpodbně vyžadovat jednotlivé nette balíky >=2.3. Změny jsou plánované také v client-side. Novinky jsou v plánu také a osobně je považuji za killer features (označil jsem je WIP jelikož je již použivám v pár projektech, takže je to „jen“ otázka přehodit do master větve). Sepsané je to v Milestones.

Donate

Grido je celkem rozsáhlá komponenta (více jak 10k řádků) s aktuálně 95% pokrytím unit testy! Když se podívám na zdejší thread (asi největší na Nette fórum) a počet instalací přes Composer (26k) tak hádám, že Grido je celkem používané a dobře slouží. Taky je tu s námi skoro 3 roky! Nyní nastal čas Grido ocenit. Úvodní thread jsem aktualizoval a přidal Donors list.

Editoval o5 (3. 11. 2015 16:51)

VK
Člen | 10
+
0
-

@o5 Díky za další vývoj a určitě rád z další faktury přispěji, ale mám prosbu…proč je verze 2.1.1 omezena na Nette < 2.3.0? Je tam nějaký problém? Používám nejnovější Nette 2.3.7, s grido 2.0.8 problém nebyl a novější verze to najednou limituje. Šlo by s tím něco udělat? Případně mi popiš s čím tam je problém a zkusil bych se na to taky podívat (chápu, že nemusíš mít na to čas) a upravil tak, aby toto omezení mohlo být odstraněno. Řešením by bylo samozřejmě použít dev větev, ale to už jsem si dávno a po dost špatných zkušenostech s jinými balíčky zakázal. Díky za info.

o5
Člen | 416
+
0
-

@VK: Locknutí Nette ve verzi 2.1.1 bylo trochu unáhlené. Na githubu někdo hlásil problémy s novým na Nette a touto branche 2.1, ale až teď mi došlo, že ten člověk zřejmě nepoužil composer, protože by jinak takový problém mít neměl, jelikož díky tomuhle by se mu měl nainstalovat i balík nette/deprecated, který by to měl řešit.

Pokud se ti nechce použít @dev větev, lockni si na "o5/grido": "2.1.0".

Oli
Člen | 1215
+
0
-

@o5 je nějaká možnost jak obarvit celý řádek gridu? Místo <tr class="grid-row-2"> bych potřeboval <tr class="grid-row-2 warning"> u některých sloupců. Ta třída se tam přidává napevno, ale předpokládám, že by šlo udělat nějaký dynamický nastavení třídy. Pokud už něco takovýho není v plánu pro verzi 3?

o5
Člen | 416
+
+2
-

@Oli: nehledáš tohle?

$grid->rowCallback = function($row, \Nette\Utils\Html $tr) {
    if (...) {
        $tr->class[] = 'warning';
    }

    return $tr;
};
Oli
Člen | 1215
+
0
-

Geniální, přesně to jsem hledal. Díky :-)

VK
Člen | 10
+
0
-

@o5 Super, díky za info, s 2.1.0 si určitě vystačím :)

reflex
Člen | 28
+
0
-

Ahoj,

je mozne udelat fulltext filter nad ruznyma sloupcema v DB?

Dekuju

Mirka
Člen | 5
+
0
-

Ahoj,
prošla jsem snad vše, co se o Grido psalo. Našla jsem několik řešení, ale na aktuální verzi nefungují.
Čas v DB mám unix a nedaří se mi správně nastavit FilterDate.

$grid->addColumnDate('created_at', 'Created', \Grido\Components\Columns\Date::FORMAT_DATETIME)
		 ->setFilterDate()
     ->setWhere(function($value, $selection) {
         $date = $this->paymentManager->unixDB($value);
         $selection->where('created_at LIKE ', $date);
		});

Tohle řešení jsem našla v tomto vláknu, ale neděje se nic, ani žádná chyba, filtr je prázdný, value se nevyplní…
Můžete mě někdo prosím nakopnout?
Děkuji :)

Oli
Člen | 1215
+
0
-

Ahoj @Mirka,
měla by jsi takhle nastavit setCondition a takhle potom napíšeš ten filtr.

CZechBoY
Člen | 3608
+
0
-

Neřešil někdo case insensitive filtrování?
V MySQL klasicky nastavím _ci sloupec, ale v oraclu mi to nějak nejede, nebo nevim.

Jde nastavit vyhledávání tak, aby pro text se sloupec i hodnota převedla na uppercase (např)?

SELECT * FROM tabulka WHERE upper(sloupec1) LIKE upper('%hodnota%');

Editoval CZechBoY (24. 11. 2015 14:33)

o5
Člen | 416
+
0
-

@CZechBoY: je potřeba změnit výchozí generování condition pro text filter.

Tam kde používám PostgeSQL to řeším takovým drobným hackem, který spočívá v použití vlastního text filtru. Překryju si __getCondition() v Grido\Components\Filter\Text.

<?php

namespace App\Controls\Grido\Filters;

/**
 * Text input filter.
 *
 * @property int $suggestionLimit
 * @property-write callback $suggestionCallback
 */
class Text extends \Grido\Components\Filters\Text
{
    const DEFAULT_CONDITION = 'LIKE ?';

    /** @var string */
    protected $condition = self::DEFAULT_CONDITION;

    public function __getCondition($value)
    {
        if ($value === '' || $value === NULL) {
            return FALSE; //skip
        }

        $condition = $this->getCondition();

        if (is_string($condition) && $condition === self::DEFAULT_CONDITION) {
            $columns = $this->getColumn();

            foreach ($columns as &$column) {
                $column = Condition::isOperator($column)
                    ? $column
                    : "LOWER(CAST($column AS TEXT))";
            }

            $condition = Condition::setup($columns, 'LIKE LOWER(?)', $this->formatValue($value), FALSE);

        } else {
            $condition = parent::__getCondition($value);
        }

        return $condition;
    }
}
  • Je samozřejmě vhodné si rovněž překrýt metodu $grid->addFilterText() (třeba ve společném předku všech gridů).
CZechBoY
Člen | 3608
+
0
-

@o5 díky, určitě překryju addFilterText(), jen jsem přesně nevěděl co tam přidat :-)

Mirka
Člen | 5
+
0
-

Oli napsal(a):

Ahoj @Mirka,
měla by jsi takhle nastavit setCondition a takhle potom napíšeš ten filtr.

Díky moc @Oli
Ještě jedna otázečka, je možné nějak předávat i název sloupce? Abych mohla použít metodu na všechny sloupečky, co mám s datumem? Je jich požehnaně…

vymak
Člen | 92
+
0
-

Ahoj,
měl bych na Vás jeden dotaz. Je nějak možné po provedení inline editace, aby došlo k revalidaci celého řádku, na kterém byla úprava provedena?

	$that = $this;
	$grid->addColumnText('name', _('Stav'))
		->setEditable()
		->setEditableCallback(function ($id, $newValue) use ($that) {
				$this->dictaphoneRepository->update($id, array('state_id' => $newValue));
				$that->redrawControl(); // toto nějak nefunguje
				return TRUE;
		})
		->setEditableControl($this->getSelect())
		->setEditableValueCallback(function ($row) {
			return $row->state_id;
		})
		->setReplacement($this->dictaphoneRepository->getStateArr())
		->setSortable()
		->setFilterText()
		->setSuggestion();

Těším se na Vaše odpovědi :).

cujan
Člen | 410
+
0
-

Caute, ide grido pod najnovsou verziou nette?

Oli
Člen | 1215
+
0
-

@cujan ano. Akorát pod ním nejde myslím 2.2.1 verze. Použij 2.2.0 nebo dev.

VK
Člen | 10
+
0
-

Narazil jsem na problém při použití vnořeného objektu (používám Doctrine data source). V Gridu na toto existuje setter setColumn(). Jenže v případě, že editor (což je referenční vazba v db) je nulový, tak použitý Symfony „property accessor“ vyhazuje exception, že udávaná cesta neexistuje (což je pravda, protože editor vrací null a ne objekt).

Otázka tedy, zda mi něco uniká a dá se to vyřešit v Gridu ještě nějakým nastavením a pokud ne, tak bych byl pro řádek #598 nahradit tímto:

if (!$this->getPropertyAccessor()->isReadable($object, $name)) {
	return null;
} else {
	return $this->getPropertyAccessor()->getValue($object, $name);
}

Pokud souhlas, tak můžu poslat PR.

cujan
Člen | 410
+
0
-

@Oli cize composer by som mal upravit asi takto?

<?php
{
    "require": {
        "php" : ">=5.3.2",
        "nette/nette": "2.0.*",
        "o5/grido": "2.2.1"
    }
}
?>

Editoval cujan (27. 11. 2015 21:59)

Oli
Člen | 1215
+
0
-

@cujan sekl jsem se ve verzích (psal jsm to z mobilu a neověřoval jsem to). Je to 2.1.0. 2.1.1. mám pocit, že právě nejde kvůli nahlášení issue a rychlému hotfixu, kterej ale nebyl problémem. @dev verze každopádně jde určitě.

A nette si nastav na 2.3.* Ono to je potom i víc vidět jakou používáš verzi.

cujan
Člen | 410
+
0
-

Uz sa to tu riesilo, len to nijako nemozem najst…ako nastavim cislovanie riadkov, pri vypise zaznamov?

helvete
Člen | 16
+
0
-

libik napsal(a):

Grido mi zobrazovalo 11 zaznamu (strankovani po 10). Na 2. strance jsem smazal ten 11. a koncim v ladence s notice „Page is out of range.“

<?php 439: trigger_error(„Page is out of range.“, E_USER_NOTICE);?>

Nastaveni grida mam:

<?php
$grid = new \Grido\Grid($this, $name);
        $grid->setRememberState(true);
        $grid->setModel($this->items->findAll());
        $grid->setPerPageList(array(10, 20, 50));
        $grid->setDefaultPerPage(10);
        $grid->translator->lang = 'cs';
	...
?>

V cem muze byt problem?

Ahoj podobnou vec jsem resil dnes. Snadno vyresis pretizenim metody \Grido\Grid::getData(). V casti kde je

<?php
if ($applyPaging && $data && !in_array($this->page, range(1, $this->getPaginator()->pageCount))) {
	$this->__triggerUserNotice("Page is out of range.");
	$this->page = 1;
}
?>

staci upravit na:

<?php
if ($applyPaging && $data && !in_array(
	$this->page, range(1, $this->getPaginator()->pageCount))
) {
	$this->page = $this->getPaginator()->pageCount;
}
?>

Nevyhazuje se potom notice a user je presmerovan na posledni stranku, ktera je k dispozici.

CZechBoY
Člen | 3608
+
0
-

Podařilo se někomu vložit grido do komponenty?

Chci si udělat tabulku, která se bude vkládat na více místech a nechce se mi to dělat v presenteru.

Oli
Člen | 1215
+
0
-

@CZechBoY co ti na tom nejde? Já to používám jedině tak. Je to uplně stejný, jako by jsi to měl v presenteru.

class UserComponent extends Control
{
	private $grido;

	protected function createComponentGrid($name)
	{
		$this->grido = new \Grido\Grid($name);
		// ...
		return $this->grido;
	}
}
CZechBoY
Člen | 3608
+
0
-

@Oli hm, tak to je divný. Dává mi to do url parametry bez prefixu jména komponenty. Prostě jen filter[xxx]=aaa.

V render metodě dáváš normálně

$this['grido']->render();

nebo jak?

Já tu komponentu dělám trošičku jinak a nevim jestli v tom nebude problém.

protected function createComponentGrido ()
{
	$grido = $this->gridoFactory->create($this->selection);
		.
		.
		.
	return $grido;
}

GridoFactory je potom továrnička vytvářející Grido

class Grido extends \Grido\Grid
{
	public function __construct (Selection $selection = null, IContainer $parent = null, $name = null)
	{
		parent::__construct($parent, $name);

		if ($selection) {
			$this->setModel($selection);
		}
	}
}

Editoval CZechBoY (15. 12. 2015 19:29)

Oli
Člen | 1215
+
0
-

Historicky si na to vytvářím šablonu (dřív jsem tam měl další věci, teď už jen tu komponentu), ale nemělo by to na to mít vliv. Filtr mě to generuje dobře: ?grid-grid-filter%5Bid%5D=6496 Vytvářím to teda ale takhle $this->grid = new \Grido\Grid($this, $name);. Ten příspěvek předtím jsem psal z hlavy, tak nevím jestli to na to nemůže mít vliv (jeslti to vytvářiš s jedním parametrem $name).

CZechBoY
Člen | 3608
+
0
-

@Oli tak jsem doplnil parent i name

protected function createComponentGrido($name)
{
	$grido = $this->gridoFactory->create(null, $this, $name);
}

ale furt mám filter parametry bez prefixu jména komponenty :(

Mart78
Člen | 31
+
0
-

Mohl by mi někdo poradit jak název sloupce s akcemi? Sloupce to berou z parametru, ale odkud sloupec s akcemi? Hledal jsem v dokumentaci i v API.

chap
Člen | 81
+
+1
-

Mart78 napsal(a):

Mohl by mi někdo poradit jak název sloupce s akcemi? Sloupce to berou z parametru, ale odkud sloupec s akcemi? Hledal jsem v dokumentaci i v API.

Asi chceš přejmenovat sloupec „akce“ – to lze pomocí vlastního translatoru:
$grid->setTranslator($tvujTranslator);
Překlady tu:
https://github.com/…tions/cs.php

Martk
Člen | 661
+
0
-

Lze nastavit exportování jinak než do csv? Hledal jsem v doc. a kódu, ale nenašel jsem nic.

Oli
Člen | 1215
+
0
-

Nejde. Musíš si napsat vlastní export…

drick
Člen | 61
+
0
-

Zaujimalo by ma, ci je nejak mozne spojit do modelu viacero tabuliek Nette Database.

Teda, chcel by som nieco taketo:

$grid->model = $this->database->table(‚users‘);
$grid->model = $this->database->table(‚settings_for_users‘);

Zial, ked to takto zapisem, tak $grid->model nerozozna prvu tabulku. Je mozne tam nejak hodit 2 tabulky naraz?

CZechBoY
Člen | 3608
+
0
-

@drick jj, musis dat neco jako

$selection = $db->table($table1)->select($table1 . '.*')->select($table2 . '.*');
$grido->setModel($selection);

Pripadne si ty sloupce vyjmenuj.

drick
Člen | 61
+
0
-

Perfektne! dakujem.

Maxell92
Člen | 38
+
0
-

Ahoj, měl bych dotazy k inline editaci:

  1. Je možné uložit hodnotu hned po výběru data z DatePickeru? Takhle se musí vybrat hodnota, znovu kliknout do inputu a dát enter.
  2. Je možné po uložení udělat refresh řádku? Některé další sloupce mají podmínky, které může editace ovlivnit a měla by se zobrazit jiná hodnota.
  3. Občas se mi dataPicker chová divně – Při každém focusu na input se nastaví na dnešní datum. Což je v kombinaci s bodem 1 dost nepoužitelné :) Dělala mi to i editace v online příkladu, ale teď jsem to zkoušel znovu a tam to funguje. Nějaký tip, čím by to mohlo být?
Maxell92
Člen | 38
+
0
-

Odpovím si sám, alespoň na část :)
1 + 3 – zavedl jsem si vlastní datePicker:

<script>
var initDatePicker = function(element, sendAfterSelectDate) {
    $(element).datetimepicker({
        onSelectDate: function() {
            if (element && sendAfterSelectDate) {
                $(element).trigger(jQuery.Event("keypress.grido", {keyCode: 13}))
            }
        }
    });
};

window.Grido.Grid.prototype.onInit.push(function(Grido)
{
    Grido.$element.on('focus', 'input.date-picker', function() {
        initDatePicker($(this), true);
    });
});
</script>

Obdobně jsem udělal uložení po kliku mimo input:

<script>
window.Grido.Grid.prototype.onInit.push(function(Grido)
{
    Grido.$element.on('focusout', 'input:not(.date-picker)', function() {
        $(this).trigger(jQuery.Event("keypress.grido", {keyCode: 13}));
    });
});
</script>
Oli
Člen | 1215
+
0
-

@Maxell92 odpověď na tvou 2. otázku je myslím $grid->reload(). A co se týče 1 a 2, není to co tu popisuješ náhodou řešení tohohle issue?

ch4rli3
Člen | 6
+
0
-

Dobrý den,
procházel jsem tuto diskuzi a nemůžu najít řešení na následující problém – nebo mám já něco špatně:

mám 2 tabulky v databázi:
jedna je package_kind se strukturou: id, material

druhá je products se strukturou: id, productName, package_kind_id

V gridu se snažím vypsat produkty tak, aby se mi zobrazil přímo material z tabulky package_kind

Zkoušel jsem to takto:

$grid->addColumnText('package_kind_id', 'Druh obalu')
                               ->setColumn(function($item){
                                   return $item->package_kind->material;
                               });

nicméně mi to nefunguje a pořád mi to hází „Cannot read an undeclared column ‚package_kind‘“

Napadá někoho prosím co dělám špatně? přeci jen jsem v tomto začínající. Díky :-)

Oli
Člen | 1215
+
+1
-

Jinde v Nette ti to funguje? Pokud jo, tak je to divný. Nevím jestli tabulka nemusí být v množným čísle package_kinds?

Každopádně je lepší použít setCustomRender, protože pokud takhle použiješ setColumn, tak ti pak nemusí fungovat řazení, filtrování, editace…

ch4rli3
Člen | 6
+
0
-

Zjistil jsem že ani jinde mi to nefunguje (což sám nevím popravdě proč), ale množné číslo být dle výkladu nette nemusí. Nicméně obešel jsem to způsobem viz níže a funguje to, sice trochu kostrbatě, ale vzhledem k tomu, že to je malá interní aplikace, ve které se tato část moc nebude moc používat, je to jen pro orientaci, tak to stačí.

$grid->addColumnText('package_kind_id', 'Druh obalu')
                    ->setCustomRender(function($item)
                    {
                        $packageKinds = $this->products->getTypeOrKind('package_kind');
                        return $packageKinds[$item->package_kind_id];
                    });

funkce getTypeOrKind vrací pole ‚id‘, ‚material‘ a z toho si pak vypíšu vlastní hodnotu co potřebuju a funguje to :-)

Editoval ch4rli3 (15. 3. 2016 13:25)

zac24
Člen | 41
+
0
-

z dema je patrné, že lze pro hromadnou operaci provést výběr jen na aktivní stránce a při změně stránkování grido výběr zapomene. Nebylo by vhodné vybavid grido smysluplnějším chováním, kdy si výběr na jednotlivých stránkách nějakým klientským mechanismem zapamatuje a umožní tak provést hromadnou operaci nad libovolným výběrem napříč všemi stránkami tabulky ? Zabily by se dvě mouchy jednou ranou, rázem by bylo umožněno provést výběr s pomocí třeba dvou i více po sobě aplikovaných filtrů, vyfiltrovat si pohodlně články jednoho autora, pak druhého, z každého jich pár vybrat a protože grido nezapomene výběr před aplikací nového filtru jako doposud moci pak provést nad oběma hromadnou operaci?

ZahorskyJan
Člen | 59
+
0
-

vymak napsal(a):

Ahoj,
měl bych na Vás jeden dotaz. Je nějak možné po provedení inline editace, aby došlo k revalidaci celého řádku, na kterém byla úprava provedena?

	$that = $this;
	$grid->addColumnText('name', _('Stav'))
		->setEditable()
		->setEditableCallback(function ($id, $newValue) use ($that) {
				$this->dictaphoneRepository->update($id, array('state_id' => $newValue));
				$that->redrawControl(); // toto nějak nefunguje
				return TRUE;
		})
		->setEditableControl($this->getSelect())
		->setEditableValueCallback(function ($row) {
			return $row->state_id;
		})
		->setReplacement($this->dictaphoneRepository->getStateArr())
		->setSortable()
		->setFilterText()
		->setSuggestion();

Těším se na Vaše odpovědi :).

Existuje nějaké řešení?

cujan
Člen | 410
+
0
-

Dá sa nastavit nejakym sposobom cislo riadka?

Oli
Člen | 1215
+
0
-

Však jsem psal, tohle nefunguje? Nikdy jsem to nezkoušel, ale fungovat by to mohlo…

->setEditableCallback(function ($id, $newValue) use ($grid) {
    $this->dictaphoneRepository->update($id, array('state_id' => $newValue));
	$grid->reload();
    return TRUE;
})
ZahorskyJan
Člen | 59
+
0
-

Oli napsal(a):

Však jsem psal, tohle nefunguje? Nikdy jsem to nezkoušel, ale fungovat by to mohlo…

->setEditableCallback(function ($id, $newValue) use ($grid) {
    $this->dictaphoneRepository->update($id, array('state_id' => $newValue));
	$grid->reload();
    return TRUE;
})

Bohužel nefunguje. Myslím si, že je to kvůli tomu, že handleEditable() volá presenter->sendResponse se svým payload a tím to celé ukončí. Napadlo mě upravit si client-side JS aby se po úspěšném uložení zavolal grid jako kdyby uživatel kliknul na tlačítko gridu „hledat“, což by mohlo mít ten efekt. Ale nepřijde mi to úplně správně. Nějaký nápad?

CZechBoY
Člen | 3608
+
0
-

Lze nějak podmínit jestli se u řádku ukáže odkaz na akci?

Příklad: vypisuju galerie a některý galerie nelze upravovat/mazat.

Oli
Člen | 1215
+
+1
-

setCustomRender a do nej

public function gridHrefRenderEdit($item, \Nette\Utils\Html $el)
	{
		if(!$this->user->isAllowed('user', 'edit') && !$this->user->isAllowed($this->user->identity, $item, 'edit'))
		{
			$el->class = [' hidden'];
		}
		return $el;
	}

Plus samozrejme osetrit pri zpracovani… :)

Editoval Oli (9. 4. 2016 14:59)

Šaman
Člen | 2659
+
0
-

Ahoj, dá se nějak nastavit, aby aktivní stránka v paginátoru měla nějakou unikátní třídu? Teď má jen disabled, stejně jako může mít i tlačítko ‚předchozí‘ a ‚další‘. Chci si aktuání stránku zvýraznit, ale ne ta listovací tlačítka. Díky.

--
A propo, tlačitko zpět, je-li zakázané, sice není odkaz a, je to span, ale přesto má atribut href.

phoffman
Člen | 7
+
0
-

Jak by šel udělat filtr pro více sloupců? Něco jako jeden fulltext filtr, který prohledává více(klidně všechny) zobrazované sloupce.

Editoval phoffman (18. 4. 2016 22:08)

Figa
Člen | 21
+
+1
-

Ahoj, kdy je v plánu vydat další verzi? Rád bych vytvořil šablonu pro AdminLTE a viděl jsem, že v dev verzi to lze. Předem děkuji za odpověď a děkuji za skvělou práci.