Nový Nette DataGrid pro Bootstrap s možností vypsat strom

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
svobodai
Člen | 136
+
0
-

mesour napsal(a):

Zdravím, tak jsem konečně nahrál novou verzi, je toho dost nového. Jsou nějaké deprecated a co je dost důležité, tak byly z gridu odstraněny dvě metody injectHttpRequest a setCacheDir.

Asi největší změna je nový defaultní filtr: http://grid.mesour.com/filter/, není potřeba nastavovat žádný formulář ;-)

Tady je vidět, jak se dá nastavit vlastní šablona pro vykreslení filtrovacího formuláře: http://grid.mesour.com/filter/custom/#…

Dál jsem udělal metody sortable, editable, setFilterForm a setCheckboxSelection deprecated.

Mimo jiné vznikly dva eventy pro nastavení callbacků, protože s nimi byl problém: onSort a onEditCell.

Přidal jsem stránku, kde je možné zjistit, jak přepsat defaultní nastavení: http://grid.mesour.com/setting/

Byl odstraněný jquery.ui.datetimepicker a místo toho je tam bootstrap datetimepicker.

A také každý column má v hlavičce na <th> classu podle ID sloupce, takže třeba class=„grid-column-email“. Email je ID sloupce ;-)

Co není v přehledu změn, tak z gridu už není god object. Funkcionalita byla přesunuta do subcomponent (extensions), tím se odlehčilo i základní templatě grid.latte

Všechny změny jsou zde: http://grid.mesour.com/versions/#1.4

Nikde to v dokumentaci nevidím a tak se chci zeptat, jestli byl zapracován ten defaultní order a přepínání možnosti řazení podle jednotlivých sloupců, nebo postupné přidávání sloupců do order?

mesour
Nette Blogger | 236
+
0
-

svobodai napsal(a):

mesour napsal(a):

Zdravím, tak jsem konečně nahrál novou verzi, je toho dost nového. Jsou nějaké deprecated a co je dost důležité, tak byly z gridu odstraněny dvě metody injectHttpRequest a setCacheDir.

Asi největší změna je nový defaultní filtr: http://grid.mesour.com/filter/, není potřeba nastavovat žádný formulář ;-)

Tady je vidět, jak se dá nastavit vlastní šablona pro vykreslení filtrovacího formuláře: http://grid.mesour.com/filter/custom/#…

Dál jsem udělal metody sortable, editable, setFilterForm a setCheckboxSelection deprecated.

Mimo jiné vznikly dva eventy pro nastavení callbacků, protože s nimi byl problém: onSort a onEditCell.

Přidal jsem stránku, kde je možné zjistit, jak přepsat defaultní nastavení: http://grid.mesour.com/setting/

Byl odstraněný jquery.ui.datetimepicker a místo toho je tam bootstrap datetimepicker.

A také každý column má v hlavičce na <th> classu podle ID sloupce, takže třeba class=„grid-column-email“. Email je ID sloupce ;-)

Co není v přehledu změn, tak z gridu už není god object. Funkcionalita byla přesunuta do subcomponent (extensions), tím se odlehčilo i základní templatě grid.latte

Všechny změny jsou zde: http://grid.mesour.com/versions/#1.4

Nikde to v dokumentaci nevidím a tak se chci zeptat, jestli byl zapracován ten defaultní order a přepínání možnosti řazení podle jednotlivých sloupců, nebo postupné přidávání sloupců do order?

Defaultní order jsem zkoušel zapracovat, ale akorát by to rozbilo současné řešení, takže to vidím až na nějakou další verzi. Zatím bych doporučil nastavit řazení „natvrdo“ a ve sloupcích řazení vypnout pro vaší MsSQL databázi.

Přepínání řazení jsem neřešil, to mi vypadlo, omlouvám se.

Postupné přidávání sloupců do order funguje, pokud řadíte podle dvou a více sloupců, jdou do SQL všechny.

mesour
Nette Blogger | 236
+
0
-

svobodai napsal(a):

Nikde to v dokumentaci nevidím a tak se chci zeptat, jestli byl zapracován ten defaultní order a přepínání možnosti řazení podle jednotlivých sloupců, nebo postupné přidávání sloupců do order?

Nakonec to potřebuji do jednoho projektu, takže se nějak pokusím implementovat defaultní order a to přepínání taky. Updatuji pak verzi 1.4, nebudu dělat novou.

svobodai
Člen | 136
+
0
-

OK díky.
Mám trochu problém s tím custom filter formem.
Kromě toho, že v šabloně nemohu použít snippety uvnitř formu (u následujícího prvku to hlásí, že nezná proměnnou $_form).
To jsem zatím vyřešil tím, že mám snippet okolo toho formu, ale problém je, že když mám ajaxový volání upravy filtrace tak se mi po invalidaci toho snippetu nezmění data resp. podle konzole to vlastně žádný snippet nenajde.
Používám, script, který už jsem si odladil na jiném formuláři.
Když si v tom handleru nechám vypsat $this, kde by měl být ten formulář tak ho tam nevidím což je divné.
Napadá někoho co s tím?

tom
Člen | 171
+
0
-

mesour napsal(a):

Zdravím, tak jsem konečně nahrál novou verzi, je toho dost nového. Jsou nějaké deprecated a co je dost důležité, tak byly z gridu odstraněny dvě metody injectHttpRequest a setCacheDir.

Asi největší změna je nový defaultní filtr: http://grid.mesour.com/filter/, není potřeba nastavovat žádný formulář ;-)

Tady je vidět, jak se dá nastavit vlastní šablona pro vykreslení filtrovacího formuláře: http://grid.mesour.com/filter/custom/#…

Dál jsem udělal metody sortable, editable, setFilterForm a setCheckboxSelection deprecated.

Mimo jiné vznikly dva eventy pro nastavení callbacků, protože s nimi byl problém: onSort a onEditCell.

Přidal jsem stránku, kde je možné zjistit, jak přepsat defaultní nastavení: http://grid.mesour.com/setting/

Byl odstraněný jquery.ui.datetimepicker a místo toho je tam bootstrap datetimepicker.

A také každý column má v hlavičce na <th> classu podle ID sloupce, takže třeba class=„grid-column-email“. Email je ID sloupce ;-)

Co není v přehledu změn, tak z gridu už není god object. Funkcionalita byla přesunuta do subcomponent (extensions), tím se odlehčilo i základní templatě grid.latte

Všechny změny jsou zde: http://grid.mesour.com/versions/#1.4

Perfektní, díky

Ještě se zeptám, dá se nějak přepsat i šablona na zobrazení stromu nebo gridu? Můžu si samozřejmě upravit ty co tam jsou ale po další aktualizaci bych o to přišel. Dík

mesour
Nette Blogger | 236
+
+2
-

tom napsal(a):

Perfektní, díky

Ještě se zeptám, dá se nějak přepsat i šablona na zobrazení stromu nebo gridu? Můžu si samozřejmě upravit ty co tam jsou ale po další aktualizaci bych o to přišel. Dík

Přibudou dvě metody setCustomRenderer a setCustomTemplate, parametr template bude cesta k souboru a parametr k rendereru bude Render\IRendererFactory, grid tree bude potřeba podědit od DataGrid\Render\Tree\RendererFactory, protože podle toho pozná, že jde o tree a správně nastaví hierarchii.

svobodai
Člen | 136
+
0
-

Po stisku talčítka filtrovat se mi objeví tato chyba.

Method App\FrontModule\Presenters\HomepagePresenter::createComponentGrid() did not return or create the desired component.

funkce vypadá takto

protected function createComponentGrid($name) {
	if ($this->getUser()->isLoggedIn()) {

		$selection = $this->getSelectData();
		$selection->order('Pieces.IdPiece');
		$source = new NetteDbDataSource($selection);
		$grid = new Grid($source, $this, $name);

		$grid->enableFilter($this['filterForm'], __DIR__ . '/../templates/components/filterForm.latte');

		$filter_values = $grid->getFilterValues();

		//using custom filtering
		if (empty($filter_values) === FALSE) {
			if ($filter_values['interpret']) {
				$source->where('Pieces.Interprets.IdInterpret', $filter_values['interpret']);
			}
			if ($filter_values['piece']) {
				$source->where('Pieces.IdPiece', (int) $filter_values['piece']);
			}
			if ($filter_values['years']) {
				$source->where('YEAR(PlayDateTime)', (int) $filter_values['years']);
			}
		}

		$grid->enablePager();
		$grid->setPageLimit(20);

		$grid->enableExport(__DIR__ . '/../../../temp/cache'); // here enable export

		$grid->column(new Column\Text(array(
			 Column\Text::ID => 'PieceName',
			 Column\Text::TEXT => 'Název titulu',
			 Column\Text::EDITABLE => FALSE
		)));

		$grid->column(new Column\Date(array(
			 Column\Date::ID => 'PlayDate',
			 Column\Date::TEXT => 'Datum vysílání',
			 Column\Date::FORMAT => 'd.m.Y',
			 Column\Date::EDITABLE => FALSE
		)));

		$grid->column(new Column\Date(array(
			 Column\Date::ID => 'PlayTime',
			 Column\Date::TEXT => 'Čas vysílání',
			 Column\Date::FORMAT => 'H:i:s',
			 Column\Date::EDITABLE => FALSE
		)));


		$grid->column(new Column\Text(array(
			 Column\Text::ID => 'PlayFootage',
			 Column\Text::TEXT => 'Stopáž',
			 Column\Text::EDITABLE => FALSE
		)));

		return $grid;
	}
}

Co dělám špatně v příkladu je to stejné.
Stejně se chová při změně order

Editoval svobodai (14. 10. 2014 14:00)

mpis
Člen | 65
+
0
-

mesour napsal(a):

Přibudou dvě metody setCustomRenderer a setCustomTemplate,…

Customizace vzhledu bude vynikající.
Ale jak to bude řešeno, když pro vzhled gridu jsou čtyři šablony (Grid.latte, Pager.latte,
PagerAdvanced.latte, Export.latte) a budu chtít např. změnit spodní řádek s paginatorem?

Momentálně se trápím s tím, jak dostat paginator a tlačítko pro export do jednoho řádku. Pořád to nemůžu trefit. Zatracené styly.

jazby
Člen | 44
+
0
-

svobodai napsal(a):

Po stisku talčítka filtrovat se mi objeví tato chyba.

Method App\FrontModule\Presenters\HomepagePresenter::createComponentGrid() did not return or create the desired component.

funkce vypadá takto

protected function createComponentGrid($name) {
	if ($this->getUser()->isLoggedIn()) {

		$selection = $this->getSelectData();
		$selection->order('Pieces.IdPiece');
		$source = new NetteDbDataSource($selection);
		$grid = new Grid($source, $this, $name);

		...........

		return $grid;
	}
}

Co dělám špatně v příkladu je to stejné.

To skoro vypadá jako by za to mohla ta podmínka if ($this->getUser()->isLoggedIn()) { }…
kod mi přijde v pořádku

svobodai
Člen | 136
+
0
-

jazby napsal(a):

svobodai napsal(a):

Po stisku talčítka filtrovat se mi objeví tato chyba.

Method App\FrontModule\Presenters\HomepagePresenter::createComponentGrid() did not return or create the desired component.

funkce vypadá takto

protected function createComponentGrid($name) {
	if ($this->getUser()->isLoggedIn()) {

		$selection = $this->getSelectData();
		$selection->order('Pieces.IdPiece');
		$source = new NetteDbDataSource($selection);
		$grid = new Grid($source, $this, $name);

		...........

		return $grid;
	}
}

Co dělám špatně v příkladu je to stejné.

To skoro vypadá jako by za to mohla ta podmínka if ($this->getUser()->isLoggedIn()) { }…
kod mi přijde v pořádku

Jo tím to bylo, resp. nebylo předtím nastavení storage. Teď už to funguje, resp. handle se zpracuje, ale neinvaliduje se grid (nedojde k překreslení gridu).

mesour
Nette Blogger | 236
+
0
-

Zdravím, dodělal jsem to nastavení řazení, zde je dokumentace: http://grid.mesour.com/basic/ordering/. Co je důležité vědět ze změn, tak teď je defaultně zakázané sortovat podle více sloupců.

Je to přidané do verze 1.4, protože jsem tam ještě něco opravoval, tak jsem nedělal 1.4.1.

Našel jsem ve verzi 1.4 jednu chybku, je to opravené. Kdo už má nainstalovanou verzi 1.4, tak doporučuji smazat adresář vendor/mesour a spustit composer update.

jazby
Člen | 44
+
0
-

svobodai napsal(a):
Jo tím to bylo, resp. nebylo předtím nastavení storage. Teď už to funguje, resp. handle se zpracuje, ale neinvaliduje se grid (nedojde k překreslení gridu).

A nepřekreslí se při jaké akci? Je možno vidět jak to je napsané?

svobodai
Člen | 136
+
0
-

jazby napsal(a):

svobodai napsal(a):
Jo tím to bylo, resp. nebylo předtím nastavení storage. Teď už to funguje, resp. handle se zpracuje, ale neinvaliduje se grid (nedojde k překreslení gridu).

A nepřekreslí se při jaké akci? Je možno vidět jak to je napsané?

Ano když dám refresh obrazovky tak se překreslí.

svobodai
Člen | 136
+
0
-

mesour napsal(a):

Zdravím, dodělal jsem to nastavení řazení, zde je dokumentace: http://grid.mesour.com/basic/ordering/. Co je důležité vědět ze změn, tak teď je defaultně zakázané sortovat podle více sloupců.

Je to přidané do verze 1.4, protože jsem tam ještě něco opravoval, tak jsem nedělal 1.4.1.

Našel jsem ve verzi 1.4 jednu chybku, je to opravené. Kdo už má nainstalovanou verzi 1.4, tak doporučuji smazat adresář vendor/mesour a spustit composer update.

Mám trochu problém, s funkcí applyOrder.
Tam př načítání těch keys mi na tom načtení z DB se již zrejmně uplatňuje stránkování, protože mi MSSQL vyhodí chybu s tím související. Tzn. že chybí ORDER BY
Jinak když vypustím tuto kontrolu tak funguje super.
Zatím ujsem si do té funkce fetch dal toto.

			$tmp = explode(":", $table->getConnection()->getDsn());
			if ($tmp[0] == "sqlsrv") {
				$table->order(1);
			}

Pak to funguje, ale to je defacto jen pro mne, protože mám upravenej i driver, aby to ten offset bralo.

Při použití řazení se mi obrazovka překreslí, při použití filtrace bohužel ne.

Editoval svobodai (14. 10. 2014 16:23)

mesour
Nette Blogger | 236
+
0
-

svobodai napsal(a):

jazby napsal(a):

svobodai napsal(a):
Jo tím to bylo, resp. nebylo předtím nastavení storage. Teď už to funguje, resp. handle se zpracuje, ale neinvaliduje se grid (nedojde k překreslení gridu).

A nepřekreslí se při jaké akci? Je možno vidět jak to je napsané?

Ano když dám refresh obrazovky tak se překreslí.

Jde spíš o to, při jaké akci se nepřekreslí, zda u pageru při přechodu na jinou stránku, při řažení nebo při filtrování a tak dál.

Jestli se jedná o ty snipety ve vlastní templatě pro filtr, bylo by dobré vidět, jak vypadá ta templata, případně inicializace formuláře, který tam vykresluješ a nějaký popis co od toho očekáváš, abych to mohl případně vyzkoušet.

svobodai
Člen | 136
+
0
-

mesour napsal(a):

svobodai napsal(a):

jazby napsal(a):

svobodai napsal(a):
Jo tím to bylo, resp. nebylo předtím nastavení storage. Teď už to funguje, resp. handle se zpracuje, ale neinvaliduje se grid (nedojde k překreslení gridu).

A nepřekreslí se při jaké akci? Je možno vidět jak to je napsané?

Ano když dám refresh obrazovky tak se překreslí.

Jde spíš o to, při jaké akci se nepřekreslí, zda u pageru při přechodu na jinou stránku, při řažení nebo při filtrování a tak dál.

Jestli se jedná o ty snipety ve vlastní templatě pro filtr, bylo by dobré vidět, jak vypadá ta templata, případně inicializace formuláře, který tam vykresluješ a nějaký popis co od toho očekáváš, abych to mohl případně vyzkoušet.

Template

{snippet filterSnippet}
	{form $form}
		{label userinterpret /} {input userinterpret}
		<br>
		{label interpret /} {input interpret}
		<br>
		{label years /} {input years}
		<br>
		{label datefrom /} {input datefrom}
		{label dateto /}  {input dateto}
		<br>
		{label piece /} {input piece}
		{label broadcast /} {input broadcast}
		<br>
		{label station /} {input station}
		{label payer /} {input payer}
		<br>
		{input filter}
		{input reset}
	{/form}
	{include #js}
{/snippet}
{define  #js}
.......
{/define}

Pager a Řazení grid překreslí.

Při změně ve filtračním formuláři se příslušný handler provádí, jen nevím jak se odkázat na ten snippet okolo toho formu abych ho mohl překreslit. Handler je přímo v prezenteru.

Když ve filtrovacím formuláři nastavím vyhledávací parametry a stisknu Filter tak podle konsole se vše udělá jak má, ale nedojde k překreslení gridu. Stejně tak když ten filtr ruším tlačítkem Reset.
Pokud dám refresh ručně vše se načte správně podle uplatněného filtru.

Editoval svobodai (14. 10. 2014 16:42)

mesour
Nette Blogger | 236
+
0
-

svobodai napsal(a):

Pager a Řazení grid překreslí.

Při změně ve filtračním formuláři se příslušný handler provádí, jen nevím jak se odkázat na ten snippet okolo toho formu abych ho mohl překreslit. Handler je přímo v prezenteru.

Když ve filtrovacím formuláři nastavím vyhledávací parametry a stisknu Filter tak podle konsole se vše udělá jak má, ale nedojde k překreslení gridu. Stejně tak když ten filtr ruším tlačítkem Reset.
Pokud dám refresh ručně vše se načte správně podle uplatněného filtru.

Zkoušel jsi v handleru zavolat $this->redrawContol(); bez parametru nebo $this[‚tvunNazevComponentyGridu‘]->redrawControl() taky bez parametru?

svobodai
Člen | 136
+
0
-

mesour napsal(a):

svobodai napsal(a):

Pager a Řazení grid překreslí.

Při změně ve filtračním formuláři se příslušný handler provádí, jen nevím jak se odkázat na ten snippet okolo toho formu abych ho mohl překreslit. Handler je přímo v prezenteru.

Když ve filtrovacím formuláři nastavím vyhledávací parametry a stisknu Filter tak podle konsole se vše udělá jak má, ale nedojde k překreslení gridu. Stejně tak když ten filtr ruším tlačítkem Reset.
Pokud dám refresh ručně vše se načte správně podle uplatněného filtru.

Zkoušel jsi v handleru zavolat $this->redrawContol(); bez parametru nebo $this[‚tvunNazevComponentyGridu‘]->redrawControl() taky bez parametru?

Ta metoda $this->redrawContol(); pomohla i když to překreslí ten základní snippett.
V tomto případě problém nastane pokud ten filtr uplatním. Při této akci nastane chyba Attempt to modify property of non-object v ApplyOrder, na řádku 55

52:                $data = array_keys($this->parent->getDataSource()->fetch());
53:                foreach ($this->settings['ordering'] as $key => $value) {
54:                    if (!in_array($key, $data)) {
55:                        unset($this->settings->ordering[$key]);
56:                    }
57:                }

zrejmě by tam mělo být unset($this->settings[‚ordering‘][$key]);
Ještě vznikne problém, že to načtení keys vrátí prázdné pole. a pak to ten ordering vymaže a vznikne pro MSSQL problém při načtení dat pro grid.

tom
Člen | 171
+
0
-

Funguje vám pager? Když si ho přidám, tak se vykreslí, ale po kliku nedělá nic, až po refreshi stránky. Může to také souviset s PHP 5.3?

Díky

tom
Člen | 171
+
0
-

tom napsal(a):

Funguje vám pager? Když si ho přidám, tak se vykreslí, ale po kliku nedělá nic, až po refreshi stránky. Může to také souviset s PHP 5.3?

Díky

Řazení a stránkování přestalo fungovat až s tou dnešní verzí.

mpis
Člen | 65
+
0
-

tom napsal(a):

Funguje vám pager?

Mně pager funguje správně, ale u pageradvanced mi nefunguje přechod na zadanou stránku.
Zatím jsem nepřišel na to, čím by to mohlo být.
Přitom nainstalované demo mi funguje správně ve stejném prostředí (Apache24, php5.5.16, nette2.2.3).

tom
Člen | 171
+
0
-

svobodai napsal(a):

mesour napsal(a):

svobodai napsal(a):

Pager a Řazení grid překreslí.

Při změně ve filtračním formuláři se příslušný handler provádí, jen nevím jak se odkázat na ten snippet okolo toho formu abych ho mohl překreslit. Handler je přímo v prezenteru.

Když ve filtrovacím formuláři nastavím vyhledávací parametry a stisknu Filter tak podle konsole se vše udělá jak má, ale nedojde k překreslení gridu. Stejně tak když ten filtr ruším tlačítkem Reset.
Pokud dám refresh ručně vše se načte správně podle uplatněného filtru.

Zkoušel jsi v handleru zavolat $this->redrawContol(); bez parametru nebo $this[‚tvunNazevComponentyGridu‘]->redrawControl() taky bez parametru?

Ta metoda $this->redrawContol(); pomohla i když to překreslí ten základní snippett.
V tomto případě problém nastane pokud ten filtr uplatním. Při této akci nastane chyba Attempt to modify property of non-object v ApplyOrder, na řádku 55

52:                $data = array_keys($this->parent->getDataSource()->fetch());
53:                foreach ($this->settings['ordering'] as $key => $value) {
54:                    if (!in_array($key, $data)) {
55:                        unset($this->settings->ordering[$key]);
56:                    }
57:                }

zrejmě by tam mělo být unset($this->settings[‚ordering‘][$key]);
Ještě vznikne problém, že to načtení keys vrátí prázdné pole. a pak to ten ordering vymaže a vznikne pro MSSQL problém při načtení dat pro grid.

Ano v logu mám stejnou chybu ale u mě teda náhrada řádku 55 v souboru Ordering.php za kód

<?php
unset($this->settings['ordering'][$key]);
?>

nepomohla

svobodai
Člen | 136
+
0
-

No ten řádek akorát vyhazuje sloupce pro order z pole, pokud ten sloupec není v dotazu, Takže je pak otázka co máš v tom sloupci data. Já když dám aplikovat filtr tak už v tom mám prázdné pole.

mesour
Nette Blogger | 236
+
0
-

svobodai napsal(a):

No ten řádek akorát vyhazuje sloupce pro order z pole, pokud ten sloupec není v dotazu, Takže je pak otázka co máš v tom sloupci data. Já když dám aplikovat filtr tak už v tom mám prázdné pole.

Rychlý fix je odstranit elseif a dát tam jen if. Udělal jsem fix a do půl hodiny ho dám online.

Řeší to jak problém s prázdným order i když je nastavený default, tak i exception „Attempt to modify property of non-object“.

Ve fixu budou na gridu dvě nové metody: getRealColumnNames a hasEmptyData. Tím se ještě navíc odstranilo jedno volání fetch do DB, které bylo navíc.

Pro rychlou opravu obsah metody applyOrder():

if (isset($this->settings['ordering']) && !empty($this->settings['ordering'])) {
	$data = array_keys($this->parent->getDataSource()->fetch());
	foreach ($this->settings['ordering'] as $key => $how_to_order) {
		if (!in_array($key, $this->parent->getRealColumnNames())) {
			unset($this->settings['ordering'][$key]);
		} else {
			$this->parent->getDataSource()->orderBy($key, $how_to_order);
		}
	}
	// ...
}
if(empty($this->settings['ordering']) && !empty($this->default_order)) {
	$this->parent->getDataSource()->orderBy($this->default_order[0], $this->default_order[1]);
}

Editoval mesour (14. 10. 2014 21:44)

svobodai
Člen | 136
+
0
-

Ještě dotaz k filtraci. Možná mi něco nedochází, ale přijde mi, že se ten filtr do dat vůbec neprojeví

$grid = new Grid($source, $this, $name);

    $grid->enableFilter($this['userFilter']); // here enable filter and set your form component

    $filter_values = $grid->getFilterValues(); // get values from filter

    if(empty($filter_values) === FALSE) {
        if(isset($filter_values['name_surname']) && !empty($filter_values['name_surname'])) {
            $source->where('CONCAT(name, " ", surname) LIKE ?', '%' . $filter_values['name_surname'] . '%');

// Jak se tato úprava source dostane do GRIDU
        }
    }

Může mi to někdo objasnit.

mesour
Nette Blogger | 236
+
0
-

mpis napsal(a):

tom napsal(a):

Funguje vám pager?

Mně pager funguje správně, ale u pageradvanced mi nefunguje přechod na zadanou stránku.
Zatím jsem nepřišel na to, čím by to mohlo být.
Přitom nainstalované demo mi funguje správně ve stejném prostředí (Apache24, php5.5.16, nette2.2.3).

Mě funguje pager i přechod na zadanou stránku normálně (php5.5.11, Apache2.4.9, nette2.2.3).

Načítáte jQuery už v hlavičce? Je možné, že není namapovaný event na odeslání formuláře pro přechod na zadanou stránku.

Mimo jiné mám v plánu udělat možnost vrátit si javascripty a styly pro grid, aby byly možné načíst třeba až v patičce. Defaultně by se načítaly automaticky, tak jako je tomu do teď v templatě gridu.

@tom, je také možné, že nemáte v JS tenhle kód:

//this jQuery code for AJAX
$(document).on('click', '.ajax:not(form)', function(e) {
	e.preventDefault();
	$.get($(this).attr('href'));
});

Teď si uvědomuji, že bych mohl použít jinou classu a mohlo by to být součástí gridu.

Editoval mesour (14. 10. 2014 22:11)

mesour
Nette Blogger | 236
+
0
-

svobodai napsal(a):

Ještě dotaz k filtraci. Možná mi něco nedochází, ale přijde mi, že se ten filtr do dat vůbec neprojeví

$grid = new Grid($source, $this, $name);

    $grid->enableFilter($this['userFilter']); // here enable filter and set your form component

    $filter_values = $grid->getFilterValues(); // get values from filter

    if(empty($filter_values) === FALSE) {
        if(isset($filter_values['name_surname']) && !empty($filter_values['name_surname'])) {
            $source->where('CONCAT(name, " ", surname) LIKE ?', '%' . $filter_values['name_surname'] . '%');

// Jak se tato úprava source dostane do GRIDU
        }
    }

Může mi to někdo objasnit.

Na tomto řádku:

$grid = new Grid($source, $this, $name);

jde $source do gridu pod referencí, takže co se na něm provede i dál se projeví uvnitř.

mesour
Nette Blogger | 236
+
0
-

mesour napsal(a):

svobodai napsal(a):

No ten řádek akorát vyhazuje sloupce pro order z pole, pokud ten sloupec není v dotazu, Takže je pak otázka co máš v tom sloupci data. Já když dám aplikovat filtr tak už v tom mám prázdné pole.

Rychlý fix je odstranit elseif a dát tam jen if. Udělal jsem fix a do půl hodiny ho dám online.

Řeší to jak problém s prázdným order i když je nastavený default, tak i exception „Attempt to modify property of non-object“.

Ve fixu budou na gridu dvě nové metody: getRealColumnNames a hasEmptyData. Tím se ještě navíc odstranilo jedno volání fetch do DB, které bylo navíc.

Fix je hotový na pushnutý, takže stačí smazat a updatovat verzi 1.4. Od verze 1.5 už to budu nějak lépe verzovat, takže se nebude stávat, že by se přepisovala verze.

tom
Člen | 171
+
0
-

mesour napsal(a):
Načítáte jQuery už v hlavičce? Je možné, že není namapovaný event na odeslání formuláře pro přechod na zadanou stránku.

Mimo jiné mám v plánu udělat možnost vrátit si javascripty a styly pro grid, aby byly možné načíst třeba až v patičce. Defaultně by se načítaly automaticky, tak jako je tomu do teď v templatě gridu.

@tom, je také možné, že nemáte v JS tenhle kód:

//this jQuery code for AJAX
$(document).on('click', '.ajax:not(form)', function(e) {
	e.preventDefault();
	$.get($(this).attr('href'));
});

Teď si uvědomuji, že bych mohl použít jinou classu a mohlo by to být součástí gridu.

Jojo předělal jsem to načítání do hlavičky a tento kód tam mám … ale stále to nejde :(

svobodai
Člen | 136
+
0
-

mesour napsal(a):

svobodai napsal(a):

Ještě dotaz k filtraci. Možná mi něco nedochází, ale přijde mi, že se ten filtr do dat vůbec neprojeví

$grid = new Grid($source, $this, $name);

    $grid->enableFilter($this['userFilter']); // here enable filter and set your form component

    $filter_values = $grid->getFilterValues(); // get values from filter

    if(empty($filter_values) === FALSE) {
        if(isset($filter_values['name_surname']) && !empty($filter_values['name_surname'])) {
            $source->where('CONCAT(name, " ", surname) LIKE ?', '%' . $filter_values['name_surname'] . '%');

// Jak se tato úprava source dostane do GRIDU
        }
    }

Může mi to někdo objasnit.

Na tomto řádku:

$grid = new Grid($source, $this, $name);

jde $source do gridu pod referencí, takže co se na něm provede i dál se projeví uvnitř.

Aha, mělo mě napadnout se kouknout do deklarace konstruktoru třídy a hned bych to věděl.

mesour
Nette Blogger | 236
+
+1
-

tom napsal(a):

mesour napsal(a):
Načítáte jQuery už v hlavičce? Je možné, že není namapovaný event na odeslání formuláře pro přechod na zadanou stránku.

Mimo jiné mám v plánu udělat možnost vrátit si javascripty a styly pro grid, aby byly možné načíst třeba až v patičce. Defaultně by se načítaly automaticky, tak jako je tomu do teď v templatě gridu.

@tom, je také možné, že nemáte v JS tenhle kód:

//this jQuery code for AJAX
$(document).on('click', '.ajax:not(form)', function(e) {
	e.preventDefault();
	$.get($(this).attr('href'));
});

Teď si uvědomuji, že bych mohl použít jinou classu a mohlo by to být součástí gridu.

Jojo předělal jsem to načítání do hlavičky a tento kód tam mám … ale stále to nejde :(

To je zvláštní, jak jsem psal, mě to funguje. Zítra se na to podívám pod verzí php5.3, ale nemyslím, že to bude ten problém.

Jediné co se změnilo na pageru od verze 1.3 do 1.4 tak to, že se parametr page, který určije, na jaké stránce jste je, že se posílá přes presistent parametr.

Editoval mesour (15. 10. 2014 8:59)

svobodai
Člen | 136
+
0
-

mesour napsal(a):

tom napsal(a):

mesour napsal(a):
Načítáte jQuery už v hlavičce? Je možné, že není namapovaný event na odeslání formuláře pro přechod na zadanou stránku.

Mimo jiné mám v plánu udělat možnost vrátit si javascripty a styly pro grid, aby byly možné načíst třeba až v patičce. Defaultně by se načítaly automaticky, tak jako je tomu do teď v templatě gridu.

@tom, je také možné, že nemáte v JS tenhle kód:

//this jQuery code for AJAX
$(document).on('click', '.ajax:not(form)', function(e) {
	e.preventDefault();
	$.get($(this).attr('href'));
});

Teď si uvědomuji, že bych mohl použít jinou classu a mohlo by to být součástí gridu.

Jojo předělal jsem to načítání do hlavičky a tento kód tam mám … ale stále to nejde :(

To je zvláštní, jak jsem psal, mě to funguje. Zítra se na to podívám pod verzí php5.3, ale nemyslím, že to bude ten problém.

Jediné co se změnilo na pageru od verze 1.3 do 1.4 tak to, že se parametr pager, který určije, na jaké stránce jste je, že se posílá přes presistent parametr.

Mně to pod verzí 5.4.4 funguje, tedy ORDER a Paginator

Editoval svobodai (15. 10. 2014 8:43)

mesour
Nette Blogger | 236
+
0
-

Právě jsem na jedno projektu řešil statusy, jsou tam řekněme rezervace a ty mají určité statusy. Například canceled a booked. Pokud je status canceled, tak chci button, který mi rezervaci znovu zabookuje a pokud je booked, tak button, který rezervaci stornuje. Dalo by se to vyřešit jedním buttonem a logiku udělat v handleru, ale bylo také potřeba aby button vypadal při každém statusu jinak. Takže jsem vytvořil column StatusColumn a obal na button StatusButton.

namespace App\Libs;

use \Nette\Utils\Html,
    \DataGrid\Column,
    \DataGrid\Grid_Exception;


class StatusColumn extends Column\BaseOrdering {

	/**
	 * Possible option key
	 */
	const BUTTONS = 'buttons';

	static public $no_active_class = 'no-active-button';

	private $active_count = 0;

	public function addButton(StatusButton $button) {
		$this->option[self::BUTTONS][] = $button;
	}

	protected function setDefaults() {
		return array(
		    self::BUTTONS => array()
		);
	}

	public function getHeaderAttributes() {
		$this->fixOption();
		if (array_key_exists(self::TEXT, $this->option) === FALSE) {
			throw new Grid_Exception('Option \DataGrid\TextColumn::TEXT is required.');
		}
		return array('class' => 'act buttons-count-1');
	}

	public function getHeaderContent() {
		return parent::getHeaderContent();
	}

	public function getBodyAttributes($data) {
		if (!isset($data[$this->option[self::ID]])) {
			throw new Grid_Exception('Column "' . $this->option[self::ID] . '" does not exist in data.');
		}
		$class = 'right-buttons ' . self::$no_active_class;
		foreach($this->option[self::BUTTONS] as $button) {
			if (!$button instanceof StatusButton) {
				throw new Grid_Exception('Option \DataGrid\StatusColumn::BUTTONS must be array contains instances of Components\StatusButton.');
			}
			if($button->isActive($this->option[self::ID], $data)) {
				$class .= ' is-' . $button->getStatus();
				$this->active_count++;
			}
		}
		return array('class' => $class);
	}

	public function getBodyContent($data) {
		$buttons = '';
		$active_count = 0;
		foreach($this->option[self::BUTTONS] as $button) {
			if($button->isActive($this->option[self::ID], $data)) {
				$button->setPresenter($this->getGrid()->presenter);
				$buttons .= $button->create($data) . ' ';
				$active_count++;
			}
		}
		$container = Html::el('div', array('class' => 'thumbnailx buttons-count-' . $active_count));
		$container->setHtml($buttons);
		return $container;
	}

}
<?php

namespace App\Libs;

use \Nette\Application\UI\Presenter,
    DataGrid\Setting,
    DataGrid\Grid_Exception,
    DataGrid\Components\Button;

/**
 * @author mesour <matous.nemec@mesour.com>
 * @package Mesour DataGrid
 */
class StatusButton extends Setting {

	/**
	 * Possible option key
	 */
	const BUTTON = 'button',
	    STATUS = 'name',
	    CALLBACK = 'function',
	    CALLBACK_ARGS = 'func_args';

	/**
	 * Row data for button
	 *
	 * @var Array
	 */
	private $data = array();

	/**
	 *
	 * @var \Nette\Application\UI\Presenter
	 */
	protected $presenter;

	/**
	 * @param Presenter $presenter
	 * @param array $option
	 * @param null $data
	 */
	public function __construct(array $option = array(), Presenter $presenter = NULL, $data = NULL) {
		parent::__construct($option);
		if (empty($data) === FALSE) {
			$this->data = $data;
		}
		$this->presenter = $presenter;
	}

	public function setPresenter(Presenter $presenter) {
		$this->presenter = $presenter;
	}

	public function setButton(Button $button) {
		$this->option[self::BUTTON] = $button;
		return $this;
	}

	public function setStatus($status) {
		$this->option[self::STATUS] = $status;
		return $this;
	}

	public function setCallback($callback) {
		$this->option[self::CALLBACK] = $callback;
		return $this;
	}

	public function setCallbackArguments(array $arguments) {
		$this->option[self::CALLBACK_ARGS] = $arguments;
		return $this;
	}

	public function getStatus() {
		return $this->option[self::STATUS];
	}

	public function isActive($column_name, $data) {
		$this->data = $data;
		if (array_key_exists(self::CALLBACK, $this->option) === FALSE) {
			return $this->data[$column_name] === $this->option[self::STATUS] ? TRUE : FALSE;
		} else {
			if (is_callable($this->option[self::CALLBACK])) {
				$args = array($data);
				if (isset($this->option[self::CALLBACK_ARGS]) && is_array($this->option[self::CALLBACK_ARGS])) {
					$args = array_merge($args, $this->option[self::CALLBACK_ARGS]);
				}
				return call_user_func_array($this->option[self::CALLBACK], $args);
			} else {
				throw new Grid_Exception('Callback in Component\StatusButton setting is not callable.');
			}
		}
	}

	protected function setDefaults() {
		return array(
		    self::BUTTON => NULL
		);
	}

	/**
	 * Create button
	 *
	 * @param Array $data
	 * @return String
	 * @throws Grid_Exception
	 */
	public function create($data = NULL) {
		if (empty($data) === FALSE) {
			$this->data = $data;
		}
		if (is_null($this->presenter)) {
			throw new Grid_Exception('Presenter is not set for Components\StatusButton.');
		}
		if (!isset($this->option[self::STATUS])) {
			throw new Grid_Exception('Option \DataGrid\StatusButton::STATUS is required.');
		}
		if (!$this->option[self::BUTTON] instanceof Button) {
			throw new Grid_Exception('Button must be instanceof Components\Button.');
		}
		$this->option[self::BUTTON]->setPresenter($this->presenter);
		return $this->option[self::BUTTON]->create($data);
	}

	/**
	 * See method create
	 *
	 * @return String
	 */
	public function __toString() {
		return $this->create();
	}

}

Použití je následující:

use DataGrid\Components\Button,
    DataGrid\Components\Link,
    App\Libs\StatusColumn,
    App\Libs\StatusButton;

$table_id = 'reservation_id';

$status_column = new StatusColumn(array(
    StatusColumn::ID => 'status',
    StatusColumn::TEXT => '',
));
$storno = new StatusButton(array(
    StatusButton::STATUS => 'booked',
    StatusButton::BUTTON => new Button(NULL, array(
		Button::BUTTON_CLASSES => 'ajax',
		Button::ICON_CLASSES => 'fa fa-times-circle-o',
		Button::TITLE => 'Cancel',
		Button::ATTRIBUTES => array(
	    	'href' => new Link(array(
				Link::HREF => 'cancel!',
				Link::PARAMS => array(
		    		'id' => '{' . $table_id . '}',
		    		'room_id' => '{room_id}'
				)
	    	)),
		),
    )),
));
$status_column->addButton($storno);

$book = new StatusButton(array(
    StatusButton::STATUS => 'storno',
    StatusButton::BUTTON => new Button(NULL, array(
		Button::BUTTON_CLASSES => 'ajax',
		Button::ICON_CLASSES => 'fa fa-book',
		Button::TITLE => 'Book it',
		Button::ATTRIBUTES => array(
	    	'href' => new Link(array(
				Link::HREF => 'book!',
				Link::PARAMS => array(
		    		'id' => '{' . $table_id . '}',
		    		'room_id' => '{room_id}'
				)
	    	)),
		),
    ))
));
$status_column->addButton($book);

$grid->column($status_column);

StatusButton::STATUS udává při jakém statusu se button zobrazí, takže může být zobrazeno i více buttonů při jednom statusu. StatusColumn::ID ⇒ ‚status‘, udává na jakém slopci se bude hledat. Ještě má další dvě nastavení StatusColumn::CALLBACK a StatusColumn::CALLBACK_ARGS, callback musí být is_callable a vrací true pokud se má button zobrazit jinak false. Mimo jiné na sloupci StatusColumn je možné nastavit ordering.

Tenhle column přidám do verze 1.4.1 a od verze 1.5 nebo dalších by mohl nahradit ActionColumn, tohle je mnohem více univerzální a navíc by zmizelo defaultní nastavení akcí pro ActionColumn.

Dávám to sem proto, kdyby to někdo potřeboval už teď. A také pro inspiraci, jakým způsobem je možné dělat vlastní sloupce s vlastní logikou. Když se pak o nějaký takový column budete chtít podělit, tak se rád podívám a když bude přínosný, tak ho zapracuji do nějaké nové verze.

Editoval mesour (16. 10. 2014 14:01)

svobodai
Člen | 136
+
0
-

Tak stále nedochází k přenačtení datagridu při uplatnění filtrace.
Kdzž jsem koukal do konzole, v případě těch úspěšných překreslení se překresluje snippet snippet–gridSnippet, kdežto v případě kdy se neprovede refresh gridu se má překreslit snippet snippet-grid-Front:Homepagegrid ten je uvnitř toho prvního snippetu. Tak nevím co je správně.

Editoval svobodai (15. 10. 2014 13:22)

tom
Člen | 171
+
0
-

Existuje u těch filtrů nějaká možnost mít tam jen obyčejný dropdown, který by se plnil nějakým číselníkem z databáze? Ty možnosti filtrování co tam teď jsou jsou pěkné, ale pro své uživatele bych raději něco jednodušší :-) Řeší se to nějak pomocí custom filtru?

Díky

svobodai
Člen | 136
+
0
-

tom napsal(a):

Existuje u těch filtrů nějaká možnost mít tam jen obyčejný dropdown, který by se plnil nějakým číselníkem z databáze? Ty možnosti filtrování co tam teď jsou jsou pěkné, ale pro své uživatele bych raději něco jednodušší :-) Řeší se to nějak pomocí custom filtru?

Díky

Do custom filtru si můžeš vytvořit vlastní formulář ve kterém si uděláš co potřebuješ.

$grid->enableFilter($this['userFilter'], případně šablona);

protected function createComponentUserFilter() {
	$form = new Form;
	...
	return $form;
}
tom
Člen | 171
+
0
-

svobodai napsal(a):

tom napsal(a):

Existuje u těch filtrů nějaká možnost mít tam jen obyčejný dropdown, který by se plnil nějakým číselníkem z databáze? Ty možnosti filtrování co tam teď jsou jsou pěkné, ale pro své uživatele bych raději něco jednodušší :-) Řeší se to nějak pomocí custom filtru?

Díky

Do custom filtru si můžeš vytvořit vlastní formulář ve kterém si uděláš co potřebuješ.

$grid->enableFilter($this['userFilter'], případně šablona);

protected function createComponentUserFilter() {
	$form = new Form;
	...
	return $form;
}

Aha přehlídl jsem to v dokumentaci, už to tam vidím. Dík

mesour
Nette Blogger | 236
+
0
-

svobodai napsal(a):

Tak stále nedochází k přenačtení datagridu při uplatnění filtrace.
Kdzž jsem koukal do konzole, v případě těch úspěšných překreslení se překresluje snippet snippet–gridSnippet, kdežto v případě kdy se neprovede refresh gridu se má překreslit snippet snippet-grid-Front:Homepagegrid ten je uvnitř toho prvního snippetu. Tak nevím co je správně.

Po každém filtrování se zavolá redraw na gridu, to znamená celá templata grid.latte a všechny vnořené se překreslí, tedy i vaše templata pro form. Je to divné, ještě testuji a možná udělám update na verzi 1.4.1, kde bude nový event.

tom
Člen | 171
+
0
-

mesour napsal(a):

svobodai napsal(a):

Tak stále nedochází k přenačtení datagridu při uplatnění filtrace.
Kdzž jsem koukal do konzole, v případě těch úspěšných překreslení se překresluje snippet snippet–gridSnippet, kdežto v případě kdy se neprovede refresh gridu se má překreslit snippet snippet-grid-Front:Homepagegrid ten je uvnitř toho prvního snippetu. Tak nevím co je správně.

Po každém filtrování se zavolá redraw na gridu, to znamená celá templata grid.latte a všechny vnořené se překreslí, tedy i vaše templata pro form. Je to divné, ještě testuji a možná udělám update na verzi 1.4.1, kde bude nový event.

Mě podobným způsobem blbne stránkování a řazení redraw se sice zavolá, snippety vrátí ale nestane se nic … Mám Nette 2.2.3

mesour
Nette Blogger | 236
+
0
-

tom napsal(a):

mesour napsal(a):

svobodai napsal(a):

Tak stále nedochází k přenačtení datagridu při uplatnění filtrace.
Kdzž jsem koukal do konzole, v případě těch úspěšných překreslení se překresluje snippet snippet–gridSnippet, kdežto v případě kdy se neprovede refresh gridu se má překreslit snippet snippet-grid-Front:Homepagegrid ten je uvnitř toho prvního snippetu. Tak nevím co je správně.

Po každém filtrování se zavolá redraw na gridu, to znamená celá templata grid.latte a všechny vnořené se překreslí, tedy i vaše templata pro form. Je to divné, ještě testuji a možná udělám update na verzi 1.4.1, kde bude nový event.

Mě podobným způsobem blbne stránkování a řazení redraw se sice zavolá, snippety vrátí ale nestane se nic … Mám Nette 2.2.3

To skoro vypadá, jako bys tam neměl jquery.nette.js: https://componette.org/search/?…

mpis
Člen | 65
+
0
-

jquery.nette.js za to může.
Po přidání mi začal v paginátoru správně fungovat přechod na zadanou stránku.
Určitě by bylo dobré v dokumentaci uvést seznam všech .css a .js souborů, které jsou nutné ke správné funkci gridu. A u .js souborů i jejich správné pořadí.

Editoval mpis (16. 10. 2014 11:24)

mesour
Nette Blogger | 236
+
+1
-

mpis napsal(a):

jquery.nette.js za to může.
Po přidání mi začal v paginátoru správně fungovat přechod na zadanou stránku.
Určitě by bylo dobré v dokumentaci uvést seznam všech .css a .js souborů, které jsou nutné ke správné funkci gridu. A u .js souborů i jejich správné pořadí.

Jistě, pořadí .js souborů tam uvedu, že musí být jQuery načtené před vložením gridu do stránky.

Od další verze plánuji nějakou statickou metodu, která vrátí obsah všech JS potřebných pro grid, jak to bude přesně fungovat teď ještě nevím. Pak už si to bude moci každý uložit do souboru a načíst kdekoliv. Defaultně se budou JS načítat tak jako do teď v templatě gridu.

jquery.nette.js jsem do required nepsal, protože jsem předpokládal, že když někdo chce aby fungovaly snippety automaticky, že tam tento JS má. Raději to tam ale také napíši ;-)

svobodai
Člen | 136
+
0
-

Já jsem zatím používal nette.ajax.js link . část kódu mi s tím funguje. Kdyby byl ten seznam js a jejich pořadí tak bych to uvítal, protože kdzž přidám ten jquery.nette.js tak mi pak nefunguje ani js kód, který mi předtím fungoval.

Editoval svobodai (16. 10. 2014 21:24)

mesour
Nette Blogger | 236
+
0
-

svobodai napsal(a):

Já jsem zatím používal nette.ajax.js link . část kódu mi s tím funguje. Kdyby byl ten seznam js a jejich pořadí tak bych to uvítal, protože kdzž přidám ten jquery.nette.js tak mi pak nefunguje ani js kód, který mi předtím fungoval.

No jasně, je jich víc. Ten co jsem poslal je takový základní. Jde o to, aby měl namapované eventy na ajaxComplete a aby po nich v responseTextu našel JSON a případně překreslil snippety, když nějaké obsahuje. To by ale teoreticky měli umět všechny.

mesour
Nette Blogger | 236
+
0
-

Zveřejnil jsem 1.4.1. Není toho moc nového, pouze jsem přidal nějaký redrawing, takže by už neměl být problém s překreslováním po AJAXu a také jsem přidal event onFilter, který se aktivuje po filtrování, takže i kdyby náhodou někomu nefungoval redraw, který tam je, tak v tomto callbacku je možné udělat vlastní.

Zde jsou změny: http://grid.mesour.com/versions/#…

Editoval mesour (17. 10. 2014 16:22)

sepo
Člen | 69
+
+2
-

mesour napsal(a):

Zveřejnil jsem 1.4.1. Není toho moc nového, pouze jsem přidal nějaký redrawing, takže by už neměl být

správna linka :
http://grid.mesour.com/versions/#…

mesour
Nette Blogger | 236
+
0
-

sepo napsal(a):

mesour napsal(a):

Zveřejnil jsem 1.4.1. Není toho moc nového, pouze jsem přidal nějaký redrawing, takže by už neměl být

správna linka :
http://grid.mesour.com/versions/#…

Nechal jsem tam .local… Už jsem to opravil ;-)

tom
Člen | 171
+
0
-

Používám teď Grid se sortováním. V případě, že nenastavím enableRowSelection ani setCheckboxSelection tak se Grid při přetahování položky myší promazává – postupně mizí textové sloupce. Header a Actions zůstane.

Pokud enableRowSelection nebo setCheckboxSelection nastavím, tak to funguje.

tom
Člen | 171
+
0
-

Export do CSV – dá se nějak nastavit název souboru a kódování češtiny?

Díky

mesour
Nette Blogger | 236
+
0
-

tom napsal(a):

Používám teď Grid se sortováním. V případě, že nenastavím enableRowSelection ani setCheckboxSelection tak se Grid při přetahování položky myší promazává – postupně mizí textové sloupce. Header a Actions zůstane.

Pokud enableRowSelection nebo setCheckboxSelection nastavím, tak to funguje.

Zkus updatovat na 1.4.2. Dal jsem tam novou verzi nestesSortable.js a přestalo to takhle blbnout.