Vlastní formulářový prvek – padá Chrome
- BrunoPuzjak
- Člen | 7
Zdravím všechny.
Vytvořil jsem si vlastní prvek pro formulář, který registruji pomocí
továrničky:
<?php
class AutocompleteFactory {
/** @var \Nette\Application\LinkGenerator @inject */
public $linkGenerator;
public function __construct(
Nette\Application\LinkGenerator $linkGenerator
)
{
$this->linkGenerator = $linkGenerator;
}
public function create($dataUrl, $service, $label, $params = [], $minInput = 3, $initSelect = 'name')
{
return new Autocomplete(
$this->linkGenerator
, $dataUrl
, $service
, $label
, $params
, $minInput
, $initSelect
);
}
/**
* Extension kontejneru s formulářovými komponentami
* @param string $methodName
*/
public function register($methodName, $dataUrl, $service, $minInput = 3, $initSelect = ['id' => 'id', 'name' => 'name'])
{
Nette\Forms\Container::extensionMethod($methodName, function ($container, $name, $label = NULL, $params = []) use ($service, $initSelect, $dataUrl, $minInput) {
return $container[$name] = $this->create($dataUrl, $service, $label, $params, $minInput, $initSelect);
});
}
}
?>
Samotný formulářový prvek funguje v pořádku – mám stránku pro vytvoření produktu a tento prvek se používá pro svázání s dalšími produkty.
Problém přichází při využití v edit formuláři.
Pokud má produkt přiřazen dalších 10 produktů a formulář by měl tedy
zobrazit 10 těchto autocomplete, tak mi Chrome zahlásí
ERR_RESPONSE_HEADERS_TOO_BIG.
Mozzila nebo dokonce i Edge si s tím bez problému poradí a stránku
zobrazí.
Další věc co mi vrtá hlavou je, že pokud v presenteru dám dump httpResponse, tak se stránka zobrazí i v Chrome!
<?php
public function afterRender()
{
parent::afterRender();
\Tracy\Debugger::dump($this->getHttpResponse());
}
?>
Má někdo nápad, co mi uniká? Předem díky!
Editoval BrunoPuzjak (2. 2. 2018 13:58)
- BrunoPuzjak
- Člen | 7
nightfish napsal(a):
Podívej se do Chrome Developer Tools – záložka Network, rozklikni si požadavek na svou stránku a podívej se do Response Headers, jestli tam není něco neobvyklého.
Možná toto: Transfer-Encoding:chunked ?
Teď ještě koukám, že na localu to mám na každé stránce, ale na ostrém
serveru to není.
Čím by to mohlo být?
General:
Request URL: moje_url
Request Method:GET
Status Code:200 OK
Remote Address:80
Referrer Policy:no-referrer-when-downgrade
Response Headers
Cache-Control:no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Connection:Keep-Alive
Content-Type:text/html; charset=utf-8
Date:Fri, 02 Feb 2018 10:41:04 GMT
Expires:Thu, 19 Nov 1981 08:52:00 GMT
Keep-Alive:timeout=5, max=99
Pragma:no-cache
Server:Apache/2.4.16 (Win32) OpenSSL/1.0.1p PHP/5.6.11
Set-Cookie:PHPSESSID=ik65rhq35hoas76gvmvuj51s43; expires=Mon, 05-Mar-2018
10:41:05 GMT; Max-Age=2678400; path=/; httponly
Transfer-Encoding:chunked
Vary:X-Requested-With
X-Frame-Options:SAMEORIGIN
X-Powered-By:Nette Framework
Editoval BrunoPuzjak (2. 2. 2018 11:57)
- nightfish
- Člen | 518
Podle Stack Overflow je maximální velikost hlaviček pro Chrome 256 kB, což výše uvedené ani zdaleka nedosahuje – takže se musím zeptat, tyhle Response Headers ti to vypsalo u stejného požadavku, kdy Chrome zahlásil chybu ERR_RESPONSE_HEADERS_TOO_BIG?
- BrunoPuzjak
- Člen | 7
nightfish napsal(a):
Podle Stack Overflow je maximální velikost hlaviček pro Chrome 256 kB, což výše uvedené ani zdaleka nedosahuje – takže se musím zeptat, tyhle Response Headers ti to vypsalo u stejného požadavku, kdy Chrome zahlásil chybu ERR_RESPONSE_HEADERS_TOO_BIG?
Promiň, předchozí headers byly z požadavku s dumpem v presenteru – ta chvíle kdy se stránka zobrazí i v Chrome.
Toto jsou headers požadavku bez dump – tedy to když to hodí ERR_RESPONSE_HEADERS_TOO_BIG
General
Request URL: moje_url
Referrer Policy:no-referrer-when-downgrade
Request Headers
Provisional headers are shown
Upgrade-Insecure-Requests:1
User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
like Gecko) Chrome/63.0.3239.132 Safari/537.36
X-DevTools-Emulate-Network-Conditions-Client-Id:(XXXXXXXXXXXXXXXXXXXX)
Ještě jedna věc, kterou jsem nenapsal – pokud je ve formuláři těchto autocomplete méně (řekněme třeba 5), tak stránka najede i v chrome bez dumpu. To že to funguje s tím dump mě zaráží asi nejvíce.
Při googlení jsem našel, že tato chyba byla v Chrome a hlášený bug by snad měl být opraven, ale…
Původně jsem ve formuláři měl pouze selecty a celé to také běželo bez problému. Teď je místo selectu se 100 options pouze select s jedním option (jako promt) a vyhledávání potom jede přes select2 ajax. Takže ve výsledku by se do prohlížeče mělo poslat v prvním požadavku ještě méně kódu než v původním řešením…
EDIT:
Ty poslední headers mi to hodilo pouze jednou při reload. Teď se v network
záložce zobrazí pouze stažení 3 obrázků (z cache). Jsou to ale jen ty
pro zobrazení chyby v chrome (dinosaurus, kaktusy a ikona nefunkční stránky
:D).
Nic dalšího nikde není.
Editoval BrunoPuzjak (2. 2. 2018 14:38)
- BrunoPuzjak
- Člen | 7
Tak asi opraveno :)
Odebral jsem z továrničky předávání $service a $initSelect.
Asi to zabíralo hodně paměti – každé instanci toho autocomplete se
předala třída s připojením do db… :D
Na druhou stranu link generátor tomu také předávám a to se zvládá.
No budu muset nějak dořešit defaultní hodnotu, ale formulář už jede.
- BrunoPuzjak
- Člen | 7
CZechBoY napsal(a):
backend by nemel mit moc vliv na http hlavicky…
Taky mi to tam moc nepasuje, ale po tom, co jsem zkusil ten dump, který to rozjel už jsem si nebyl jistý ničím…
- BrunoPuzjak
- Člen | 7
Ještě bych to tu trochu rozvedl…
Jak jsem psal, používám tento vlastní prvek pro ajaxové vyhledávání
produktů v systému (pomocí select2). Mám teď ale problém s defaultní
hodnotou.
Autocomplete pro svázání produktů je možné přidávat replicatorem, takže ve formuláři to vypadá takto:
<?php
public function createComponentEditForm()
{
$form = new \Nette\Application\UI\Form();
...
$products = $form->addDynamic('products', function (Container $container) {
$container->addProductAutocomplete('product_id', 'Propojený produkt');
$container->addSubmit('remove', 'Odebrat vazbu')
->setValidationScope(FALSE)
->onClick[] = [$this, 'dynamicRemove'];
});
$products->addSubmit('add', 'Přidat vazbu')
->setValidationScope(FALSE)
->onClick[] = [$this, 'dynamicAdd'];
return $form;
}
public function dynamicAdd(SubmitButton $button)
{
$button->parent->createOne();
$this->redrawControl($button->parent->name);
}
?>
Buttony pro přidání a odebrání prvku jsou v latte zajaxovány:
<div n:snippet="products">
<div class="row js-autocomplete-init">
{foreach $form['products']->getContainers() as $id => $product}
<div class="col-xs-4">
{input products-$id-product_id class => 'autocomplete form-control select-style'}
</div>
<div class="col-xs-2" style="line-height: 40px;">
{input produts-$id-remove class => 'ajax btn-sm btn-danger danger-style'}
</div>
{/foreach}
</div>
{input products-add class => 'ajax btn btn-success'}
</div>
Celé to tedy funguje tak, že při kliknutí na tlačítko ‚Přidat vazbu‘ se ajaxově vytvoří další container s autocomplete a invaliduje se snippet ‚products‘. Ten se potom ve stránce přepíše a je vidět další autocomplete + tlačítko ‚Odebrat vazbu‘.
Původně jsem to používal tak, že ten můj vlastní formulářový prvek Autocomplete dostal při registraci $service a v případě, že by již měl mít defaultní hodnotu tak dohledal přes tento $service a předané id odpovídající název produktu a při vytváření selectu vytvořil ještě jeden selected option, aby byla hodnota rovnou vidět.
Jelikož při editaci to v chrome hází tu výše uvedenou chybu, tak jsem musel $service vynechat a řešit defaultní hodnotu jinak (tedy pokud to ještě nějaká hodná duše nevyřeší :))…
Řekl jsem si tedy, že předám do selectu ještě další url (pro nalezení jednoho záznamu podle id) a při vytvoření v js nejdříve ajaxem vytáhnu tuto hodnotu a teprve potom zavolám na prvek select2. Funguje to celkem v pohodě, ale uživatelsky nepřívětivě :D Řekněme že jsme v té editaci produktu a rozhodnu se přidat další svázaný produkt (už tam jsou třeba 3). Kliknu tedy na ‚Přidat vazbu‘ a ajaxově se mi do stránky dostane další select. Bohužel v tu chvíli se musejí všechny ostatní selecty ve snippetu překreslit také, takže vidím nejdříve 4 prázdné selecty a postupně se k nim načítá defaultní (vybraná hodnota)… ale fuj.
Tak další možnost? Posílat hodnotu selectu například ve tvaru „123:Název“. Potom bych mohl bez jakéhokoliv připojení do db nebo na api rozdělit hodnotu na id a název produktu, takže i vytvořit defaultní selected option. Ale to zase doprovází spoustu zbytečného kódu ve vytvoření, zpracování a připravení default hodnot přímo v kódu formuláře. :(
Ještě jedno hodně zoufalé řešení, které mě napadlo je v té třídě Autocomplete použít místo $service dotaz na api pomocí curl. V takovém případě by se to zpracovalo už na serveru a uživatel by neviděl postupné načítání… Ale narvat curl do té komponenty mi přijde jako další fuj :D
Nějaké návrhy/nápady nebo cokoliv?
Díky moc!
Editoval BrunoPuzjak (3. 2. 2018 16:52)