jQuery-FileUpload, snadný upload souborů
- JZechy
- Člen | 161
Někteří jej horečně stahují, někteří mi o něm píšou emaily… No a někteří ho možná ještě nikdy neviděli. A představím ho tedy i zde :).
O co se jedná?
jQuery-FileUpload je komponenta napsaná jako rozšíření původního UploadControlu, která k nahrávání souborů využívá blueimp/jQuery-FileUpload a uživatelské rozhraní založené na bootstrapu.
Co to umí?
- Možnost vlastního zpracování nahrávaných souborů.
- Omezení
nahrávání souborů – např. pouze obrázky, …
- Komponenta už nabízí základní filtry pro obrázky, dokumenty, archivy či audio soubory.
- Možnost vlastního renderování
- K dispozici připraveny Bootstrap3 a 4 rendery a HTML5 only render.
- Umí si poradit i s chybami
Instalace a použití
Instalace ani použití nejsou nijak extra komplikované:
- composer require jzechy/jquery-fileupload
- Zaregistrovat jako rozšíření.
- Překopírovat skripty a styly do WWW složky
- V šabloně zavolat vložení stylů a skriptů.
- A můžete začít používat.
- Na vás už jenom je, jak si napíšete UploadModel, tedy jak budete nakládat s nahrávanými soubory.
Odkazy
- GitHub: JZechy/jQuery-FileUpload
- Demo
Uvítám ke komponentě jakékoli připomínky či nápady, kudy se dál při její tvorbě vydat, a co byste rádi uvítali :).
Editoval JZechy (13. 10. 2017 13:22)
- Jan Mikeš
- Člen | 771
Šla by udělat instalace skrze extension v configu?
Taktéž jsem se ještě nedíval do zdrojů, tak nevím co přesně běží na pozadí
{\Zet\FileUpload\FileUploadControl::getHead($basePath)}
{\Zet\FileUpload\FileUploadControl::getScripts($basePath)}
Ale šel by tento krok vynechat a načítat si css + js jinak? Používám webloader, takže není problém loadovat zdroje z composeru.
Editoval Lexi (7. 10. 2016 19:12)
- Landsman
- Člen | 152
@JZechy Tak nějak jsem nepochopil to tlačítko „odeslat“ v demo ukázce. Samotný upload probíhá již při vybrání souborů, ne? V součastnosti řeším komponentu, která by mi nahrávala obrázky, ke kterým bych mohl ještě po uploadu řešit dodatečná data, jako: pořadí, title, … Dá se to rozšiřovat?
- JZechy
- Člen | 161
@Landsman
Nahrajou se, ale po odeslání s nima můžeš dělat pak další věci.
Protože to, co ti vrátí funkce save() můžeš pak využívat z
$form->getValues().
Nevím teď přesně co myslíš tím rozšiřováním, ale se soubory můžeš víceméně dělat co chceš po odeslání nebo při nahrávání.
Editoval JZechy (8. 10. 2016 17:24)
- JZechy
- Člen | 161
@pitr82 Ahoj, z toho by ses moc nedověděl… Tohle je jenom alternativní vzhled bootstrapu z bootswatch.com, konkrétně paper. S rozhraním fileuploaderu jsem v demu nic nedělal.
- pitr82
- Člen | 121
@JZechy Ještě jeden dotaz.
Teď jsem řešil proč mi nejdou odesílat velké soubory na server. Měl jsem
nastaven limit na 2000M.
Ale nekontroluje se nikde direktiva: post_max_size, ta byla nastavena na
menší, než soubory.
Padalo to tady: UploadController.php:118
Samozřejmě nedostal soubor.
- JZechy
- Člen | 161
@pitr82 Promin, tohle mi tu vyšumělo z hlavy… Upřímně se přiznám, že kontrolovat tuto direktivu mě vlastně vůbec nenapadlo (dokonce jinde jsem s tím taky nesetkal).
Myslím ale, že to není špatný nápad hlídat případné nastavení v php.ini, aby se nenastavil větší limit, než povoluje konfigurace serveru.
- naSkladeNemame
- Člen | 5
Zdravim chlapi,
Mozno sa budem pytat blbo za co sa vopred ospravedlnujem ale uz si nejak neviem rady.
Pouzil som tento Zechyho plugin .. je fajn no problem mi nastava ked chcem uz riesit samotny upload.
Pre upload pouzivam resp. chcem pouzivat ondrs/upload-manager (https://github.com/…load-manager)
Vytvorim si komponentu v presenteri:
function createComponentFileUpload() {
$form = new Form;
$form->addFileUpload("uploader");
$form->addSubmit('save')
->setAttribute("class", "btn btn-primary");
$form->onSuccess[] = [$this, "processUpForm"];
return $form;
}
v sablone si ju zavolam:
{control fileUpload}
v metode processUpForm si zavolam spominany upload-manager ktory by mal osluzit samotny upload suborov podla nastaveni:
/** @var ondrs\UploadManager\Upload @inject */
public $upload;
$this->upload->filesToDir("/");
tato metoda „filesToDir“ si sama zistuje nahrane subory tymto sposobom cez httpRequest:
/** @var Nette\Http\IRequest */
private $httpRequest;
$this->httpRequest->getFiles()
a tu je ten problemovy bod kedy vlastne tento httpRequest vracia NULL .. a neviem preco .. ako keby samotny fileUpload neulozi nahrane subory?
kazdopadne skusal som samorejme aj priamo v processe po odoslani FORMu pouzit $values->uploader co vrati model v metode save() → tam sa subor sice nachadza ale ked si ho posuniem do upload-managera tak sa mi objavi chybova hlaska:
file_get_contents(/tmp/phplpJs2X): failed to open stream: No such file or directory
to zase vyzera ako keby fileUpload neukladal nahrate subory ani do tempu alebo co .. ?
viete mi prosim vas niekto poradit co robim zle?
dik vopred
Editoval naSkladeNemame (19. 1. 2017 11:14)
- JZechy
- Člen | 161
@naSkladeNemame FileUpload s těmi soubory nic extra nedělá, také si prostě vytáhne z requestu nahrané soubory. Upřímně si mě teď trochu dostal, protože netuším, proč tohle nefunguje*.
Koukal jsem ale na ten upload-manager a nejjistější bude, když si ten upload-manager vložíš do svého UploadModelu a v save zavoláš jeho metodu singleFileToDir($pathToDir, $file).
* Leda že bys to možná volal až po odeslání formuláře, tam už totiž možná reference na ten soubor nebude, protože formulářem už se $_FILES neodesílá.
Editoval JZechy (19. 1. 2017 11:32)
- naSkladeNemame
- Člen | 5
JZechy napsal(a):
@naSkladeNemame FileUpload s těmi soubory nic extra nedělá, také si prostě vytáhne z requestu nahrané soubory. Upřímně si mě teď trochu dostal, protože netuším, proč tohle nefunguje*.
Koukal jsem ale na ten upload-manager a nejjistější bude, když si ten upload-manager vložíš do svého UploadModelu a v save zavoláš jeho metodu singleFileToDir($pathToDir, $file).
to som tiez samozrejme skusal:
/** @var Upload @inject */
public $upload;
public function save(FileUpload $file) {
$this->upload->singleFileToDir("images/uploads", $file);
return $file;
}
skonci opat chybou:
Call to a member function singleFileToDir() on null
nechapem tomu
* Leda že bys to možná volal až po odeslání formuláře, tam už totiž možná reference na ten soubor nebude, protože formulářem už se $_FILES neodesílá.
no hej $this->upload->filesToDir() som volal az v processe po odoslani FORMu .. kde by som to podla teba mal volat teda?
Editoval naSkladeNemame (19. 1. 2017 12:10)
- JZechy
- Člen | 161
@naSkladeNemame Od toho máš ten UploadModel, protože upload probíhá asynchroně, když na to příjde, ono ani není nutné ten formulář vůbec odesílat.
Ve chvíli, kdy tam vybereš soubor, se začne odesílat, to se přes vnitřní controller uploaderu dostane až do toho upload modelu, kde se předává aktuálně nahrávaný soubor.
Tudíž, když odesíláš formulář, máš už tam pouze hodnoty uploaderu, tedy to, co si vracíš ze save. A zřejmě, jak ses přesvědčil, už temp fily dávno neexistují v tuhle chvíli.
- naSkladeNemame
- Člen | 5
Este by som mal jeden dotaz v tejto zalezitosti.
Tym ze v v mojom modeli v metode save() zavolam upload suborov cez file-manager (co zbehne v poriadku) a chcem mu nejakym sposobom povedat nazov do akej zlozky ich ma uploadnut (povedzme z GET parametru) ako je to v tomto pripadne najcistejsie urobit? → resp. ako to tuto info predat do tohoto modelu?
dik vopred za radu
- JZechy
- Člen | 161
@kralik
/**
* Uložení nahraného souboru.
* @param \Nette\Http\FileUpload $file
* @return mixed Vlastní navrátová hodnota.
*/
public function save(\Nette\Http\FileUpload $file) {
$uploadedId = $this->fileService->saveUploadedFile($file, $this->user->getId());
return $uploadedId;
}
Ve fileService:
/**
* @param \Nette\Http\FileUpload $file
* @param int $uploaderId
* @return int
*/
public function saveUploadedFile(\Nette\Http\FileUpload $file, $uploaderId) {
$newFileName = $this->generateRandomFileName();
$path = $this->getFileFullPath($newFileName);
$path = $path . "/" . $newFileName;
$file->move($this->fileDirectory . "/" . $path);
$filename = explode(".", $file->getName());
$extension = $filename[ $lastIndex = count($filename) - 1 ];
unset($filename[ $lastIndex ]);
$filename = implode(".", $filename);
$entity = new \Model\Content\File();
$entity->uploader = $this->orm->user->getById($uploaderId);
$entity->name = $filename;
$entity->extension = $extension;
$entity->filepath = $path;
$entity->status = $entity::STATUS_UNFINISHED;
$this->orm->file->persistAndFlush($entity);
return $entity->id;
}
Ale takhle ti tady můžu postovat polovinu svého projektu, jak to tam používám a moudrý z toho být nemusíš… Lepší bude, když povíš, co ti nejde nebo s čím máš problém :)
- kralik
- Člen | 230
jj přesně z toho moc moudrý nebudu.
Na tomto vláknu je info, že vlastně odesílání formuláře ani není
třeba. Prakticky se soubor uploaduje ihned po vložení.
Jde mi o to.
Otevřu si nějaký produkt, kde jeho ID je 3 …
admin/uploadfoto?Id=3
A při ukládání souboru bych potřeboval to ID nějak dostat do toho
„svého vlastního modelu“ spolu se souborem a poté uložit soubor na
server a mata data do DB.
Zatím ale moc nevím jak na to?
Díky
Editoval kralik (26. 1. 2017 14:13)
- JZechy
- Člen | 161
@kralik Myslím, že aktuálně to jinak nepůjde o moc líp, než ten formulář zkrátka odeslat a předat si ID do něj.
Nicméně můžeš zkusit si do Modelu vložit Nette Request a zjistit, zda v GETu není parametr ID. Pokud je tam v rámci action/render presenteru, tak by ho controller neměl z odkazu pro odeslání odstranit.
Jinak to asi vypadá, že to bude chtít vyrobit možnost přidávat k odeslání vlastní parametry :).
- kralik
- Člen | 230
Ahoj,
nějak se v tom pořád plácám.
Nevím jak vytvořit správný MyModel, jak ho zakomponovat a načíst a jak si
do modelu přidat Nette Request?
A tím zjistit Id, které je stále v GET.
Mohl bys toto více rozepsat v dokumentaci?
Skvělé by bylo nějaké, konkrétní řešení, které by bylo použito
v demu a ukládalo by pomocí vlastního modelu data do složky na serveru.
A k tomu popsáno v dokumentaci.
To by bylo úplně super.
Díky
- CZechBoY
- Člen | 3608
edit: tak to asi nejde použít
Bez znalosti pluginu…
class AbcPresenter extends Presenter
{
/** @var int @persistent */
public $id;
private $model;
private $formFactory;
public function actionDefault($id)
{
$this->id = $id;
}
protected function createComponentForm()
{
$form = $this->formFactory->create();
$form->onSuccess[] = function($form, $values) {
$this->model->save($this->id, $values);
};
return $form;
}
}
Editoval CZechBoY (30. 1. 2017 10:56)
- JZechy
- Člen | 161
@CZechBoY Tohle neprojde, model se volá asynchroně během uploadu, v onSuccess je už jenom výsledek z modelu.
@kralik
Model stačí zaregistrovat jako každou jinou Nette službu, kdy si do
konstruktoru vložíš \Nette\Http\Request. A v configu pak jen fileuploadu
řekneš, jak se ta třída jmenuje.
S uložením souboru to taky není žádná věda, prostě jenom zavoláš metodu move().
Tohle jsou ale základní věci, co máš v Nette znát.
Nic speciálního na tom pluginu není, je to jako bys psal úplně normální upload ve formuláři, akorát že to za tebe jenom řeší uploadování ajaxem :).
- kralik
- Člen | 230
Rád si doplním znalosti. Myslel jsme, že bys mi mohl pomoci, když jsem na
svoji neznalost narazil právě tady.
A rád bych se přiučitl zcela na tomto konkrétním případě, než aby
hledal v dokumentaci Nette.
config.neon
<?php
...
extensions:
fileUpload: Zet\FileUpload\FileUploadExtension
uploadModel: App\Model\UploadModel
?>
vlastní model
<?php
namespace App\Model;
use Nette,
Nette\Diagnostics\Debugger;
/**
* Provádí ukládání fotografií na server
*/
class UploadModel extends Nette\Object
{
/** @var \Nette\Http\Request */
protected $httpReq;
public function __construct(\Nette\Http\Request $httpReq) {
$this->httpReq = $httpReq;
}
// tady někde ???
$this->fileupload->move(__DIR__.'/galerie/test/');
}
?>
Ale to dělám špatně, protože dostávám chybu.
Proto jsem tě chtěl požádat o zcela konkrétní podobu uloadModelu.
Děkuji
- JZechy
- Člen | 161
@kralik
V configu to děláš špatně, má to být takhle:
extensions:
fileUpload: Zet\FileUpload\FileUploadExtension
fileUpload:
uploadModel: App\Model\UploadModel
UploadModel si načal dobře, akorát musí vypadat takhle:
class UploadModel extends Nette\Object implements \Zet\FileUpload\Model\IUploadModel
{
/** @var \Nette\Http\Request */
protected $httpReq;
public function __construct(\Nette\Http\Request $httpReq) {
$this->httpReq = $httpReq;
}
public function save(\Nette\Http\FileUpload $file) {
$file->move(__DIR__.'/galerie/test/');
}
}
- kralik
- Člen | 230
Model jsem Upravil.
Moc díky.
Bohužel dostávám chybu:
<?php
Notice
Undefined index: uploader
Source file
File: ...\src\Model\UploadController.php:118
108: }
109:
110: /**
111: * Zpracování uploadu souboru.
112: */
113: public function handleUpload() {
114: $files = $this->request->getFiles();
115: $token = $this->request->getPost("token");
116:
117: /** @var \Nette\Http\FileUpload $file */
118: $file = $files[ $this->uploadControl->getHtmlName() ];
119: $model = $this->uploadControl->getUploadModel();
120: $cache = $this->uploadControl->getCache();
121: $filter = $this->getFilter();
?>
Můj formulář:
<?php
protected function createComponentFormFileUpload($name) {
$form = new UI\Form;
$this[$name] = $form;
$form->addFileUpload("uploader")
->setUIMode(\Zet\FileUpload\FileUploadControl::UI_MINIMAL);
$form->addSubmit('save')
->setAttribute("class", "btn btn-primary");
$form->onSuccess[] = [$this, "submitFormFotoUpload"];
return $form;
}
?>
I přesto, jak píšeš,že odeslat formulář vlastně nebude třeba.
- Ot@s
- Backer | 476
kralik napsal(a):
Bohužel dostávám chybu:
<?php Notice Undefined index: uploader Source file File: ...\src\Model\UploadController.php:118 108: } 109: 110: /** 111: * Zpracování uploadu souboru. 112: */ 113: public function handleUpload() { 114: $files = $this->request->getFiles(); 115: $token = $this->request->getPost("token"); 116: 117: /** @var \Nette\Http\FileUpload $file */ 118: $file = $files[ $this->uploadControl->getHtmlName() ]; 119: $model = $this->uploadControl->getUploadModel(); 120: $cache = $this->uploadControl->getCache(); 121: $filter = $this->getFilter(); ?>
Proveď si dump $files
a
$this->uploadControl->getHtmlName()
. Z toho poznáš,
v čem je problém. Tipuji, že bude problém v absenci prefixu
$this->getUniqueId()
, resp. řádek 118 by mohl vypadat
nějak takto:
$file = $files[ $this->getUniqueId() . self::$NAME_SEPARATOR . $this->uploadControl->getHtmlName() ];
Editoval Ot@s (30. 1. 2017 13:49)
- kralik
- Člen | 230
v šabloně(uloadfoto.latte) mám toto:
<?php
...
{control formFileUpload}
...
?>
zpracování formuláře:
<?php
public function submitFormFotoUpload(UI\Form $form) {
$uid = $this->uid;
$val = $form->getHttpData();
//$httpRequest = $container->getByType('Nette\Http\Request');
//$soubor = $this->presenter->getFiles();
\Tracy\Debugger::barDump($val,'UploadVal');
}
?>
ten dump $files mi píše: Service of type
App\Model\UploadModel not found
Asi jsem nesprávně nebo vůbec nenačetl službu.
Editoval kralik (30. 1. 2017 14:00)
- JZechy
- Člen | 161
@kralik Jo, to je chyba od tebe, zaregistroval sis ten UploadModel do configu? (A tím nemyslím konfigurační konstantu ve fileUpload).
@Ot@s
Zobrazený kod je zdroják uploaderu, předpokládám, že ten funguje jak má,
když @kralik má jako jediný tento exotický problém… Navíc je to
neupravená metoda od původního Nette UploadControl.
Zprvu jsem měl dojem, že si právě v renderu nějak ručně přejmenuje input, tudíž HTML name neodpovídá, ale to zjevně není ono.
Editoval JZechy (30. 1. 2017 14:05)
- kralik
- Člen | 230
Když si totiž dám do config.neon na konec služeb:
<?php
services:
- App\Model\UserManager
- App\RouterFactory
router: @App\RouterFactory::createRouter
- App\Model\Main
- App\Model\UploadModel
?>
Tak dostanu chybu:
<?php
Fatal Error
Class App\Model\UploadModel contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (Zet\FileUpload\Model\IUploadModel::rename, Zet\FileUpload\Model\IUploadModel::remove)
?>
Tak právě nevím. Asi službu špatně načítám.
- JZechy
- Člen | 161
@kralik Základy OOP… Když nějaká třída implementuje interface, musí mít implementaci všech metod daného interface. Používáš nějaké IDE? Ten by tě upozornil na to, že ta třída neimplementuje všechny metody.
Interface uploaderu předepisuje tři metody: save, rename, delete.
Editoval JZechy (30. 1. 2017 14:27)
- kralik
- Člen | 230
Tak to jsme nevěděl.
Opravil jsme ve svém modelu.
uploadModel
<?php
class UploadModel extends Nette\Object implements \Zet\FileUpload\Model\IUploadModel
{
/** @var \Nette\Http\Request */
protected $httpReq;
public function __construct(\Nette\Http\Request $httpReq) {
$this->httpReq = $httpReq;
}
public function save(\Nette\Http\FileUpload $file) {
$file->move(__DIR__.'/foto/doplnky/');
}
public function rename($upload, $newName) {
}
public function remove($uploaded) {
}
?>
Teď mám chybu stále Undefine index: uploader
Jinak v logu mám:
<?php
Nette\InvalidStateException
Cannot send header after HTTP headers have been sent (output started at \vendor\tracy\tracy\src\Tracy\Dumper.php:75).
?>
A když dám dump: dump($this->uploadControl->getHtmlName());
Tak mám chybu tuto:
<?php
User Notice
Possible problem: you are sending a HTTP header while already having some data in output buffer. Try Tracy\OutputDebugger or start session earlier.
?>
- kralik
- Člen | 230
Nevím.
podívám-li se na kód HTML tak mám toto
<?php
<input id="frm-formFileUpload-upload" class="fileupload-input jcf-real-element" type="file" multiple="" name="upload" style="position: absolute; opacity: 0;">
?>
Error
<?php
Notice
Undefined index: upload
?>
Debug jsem nechal jen do log souboru.
Zatím namám tušení kde dělám chybu.
ve $files mám toto, což si myslím, je správně
<?php
array (1)
upload => Nette\Http\FileUpload #3609
| name private => "foto2.jpg" (9)
| type private => NULL
| size private => 7909
| tmpName private => "D:\wamp5625\tmp\php76A8.tmp" (27)
| error private => 0
?>
Editoval kralik (31. 1. 2017 11:41)
- kralik
- Člen | 230
Nejsem si vědom, že bych dělal něco extra, něco dalšího.
- Zkoušel jsem fotku přidat přes klasické „Procházet“
- Zkoušel jsem fotku jen přetáhnou na formulář.
V obou případech se fotka jakoby nahraje, progres bar běží do plna a
objeví se pod formulářem název fotografie.
Bohužel fotografie se na server nezapíše na uvedenou cestu v mém modelu a
dojde právě k chybě Undefined index: upload
Tak vůbec nevím.
Ve formuláři jsem zrušil onSuccess, ale chabá stále stejná.
- JZechy
- Člen | 161
První kulatý release s č. 10 a verzí 1.2.1 uvolněn :).
Přibyla možnost přidat si k uploadu vlastní parametry a automaticky se kontroluje, zda nastavení maximální velikost neporušuje nastavení direktiv v php.ini.
Do plánu se také dostal nový milestone 2.0.0-beta1, ve kterém dojde k trochu většímu přepracování uploaderu, hlavním cílem pro začátek nové verze bude převedení šablon do renderer objektu, pomocí kterého bude možné maximálně upravit vzhled uploaderu bez nutnosti znalosti ID/tříd v šablonách pro zaměření JavaScriptem.
Editoval JZechy (8. 2. 2017 19:11)