Odeslání formuláře vč. vloženého souboru a následné průběžné odesílání

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

Potřeboval bych ajaxem odeslat formulář a ten následně zpracovat.
Zpracování při neAjaxovém požadavku mi funguje, ovšem když se jedná o ajax, tak mi to nepošle i přiložený soubor.

O co mi jde?
Mám formulář s následujícími prvky
1× select
1× input[type=text]
1× input[type=file]

Při vložení souboru se automaticky formulář odešle a soubor se uloží. Pomocí invalidace snippetu mu zaměním formulář tak, aby viděl náhled fotografie a input[type=text] mu nahradím s událostí onkeyup (také odesílání formuláře k uložení). Formulář je v komponentě a Xkrát zduplikovaný přes multiplier.

Komponenta

protected function createComponentAddPhoto() {
        $form = new Form;
        $form->getElementPrototype()->class[] = 'ajax form-horizontal';
        if ($this->translator instanceof \Nette\Localization\ITranslator) {
            $form->setTranslator($this->translator);
        }
        $form->addSelect('albums', 'Albums:', $this->albumRepository->findAll()->fetchPairs('id', 'title'))
                ->setAttribute('class', 'form-control');
        $form->addText('title', 'Title:')
                ->setAttribute('class', 'form-control');
        $form->addUpload('photo', 'Photography:')
                ->setAttribute('class', 'form-control');
        $form->addSubmit('save', 'Save')
                        ->setHtmlId('send-button')
                        ->setAttribute('class', 'btn btn-primary');
        $form->onSuccess[] = $this->completeSaveSettings;
        return $form;
    }

    public function completeSaveSettings(Form $form) {
        if ($form->isSuccess()) {
            $values = $form->getValues();
            if ($values->photo->isOk()) {
                try {
                    $entity = new \Tirus\Entity\GalleryPhoto;
                    $entity->savePhoto($values->photo, $this->storage, $this->gallerySettings);
                    $entity->created = new \DateTime;
                    $entity->owner = $this->presenter->getUser()->id;
                    $entity->album = $values->albums;
                    $entity->title = $values->title;
                    $id = $this->photoRepository->persist($entity);
                    $entity = $this->photoRepository->get($id);
                } catch (\Exception $ex) {
                    \Tracy\Debugger::log($ex);
                    $form->addError('Cant save photo');
                    return;
                }
                $this->flashMessage('Obrazek byl ulozen', 'success');
            } else {
                $this->flashMessage('Musite nejdriv vybrat obrazek', 'danger');
            }
            if (!$this->presenter->isAjax()) {
                $this->redirect('this');
            }
            $this->redrawControl('addPhotoForm');
        }
    }

šablona

{snippet addPhotoForm}
    <div class="col-md-6">
        <div n:foreach="$flashes as $flash" class="row">
            <div data-alert class="alert alert-{$flash->type}">{_$flash->message}</div>
        </div>
        {control addPhoto}

    </div>
{/snippet}

pro ajax využívám nette.ajax.js
wodCZ
Člen | 49
+
0
-

nette.ajax.js neumí odesílat soubory, protože to neumí (neuměl) samotný XHR.
Řešení třeba na http://stackoverflow.com/…load/4943774#… – v komentářích se ale píše, že to využívá XHR2

Editoval Inkode (10. 9. 2014 20:03)

Tirus91
Člen | 199
+
0
-

@Inkode tak to jsem na konci :-\

wodCZ
Člen | 49
+
+1
-

Lze využít různé pluginy, které umí buď xhr2 nebo fallbacknou na flash, ale už to nebude krásně spolupracovat s Nette.

Přemýšlel jsem řešit to v JS filereaderem a posílat jako base64 string, ale nevím jestli to není úplná blbost. Pokud by to ale šlo, tak by to šlo implementovat docela jednoduše ručním voláním jako:

var frmData = {};
form.serializeArray().map(function (val, i) {
	var pos = val.name.indexOf('[]');
	if (pos !== -1) {
		var key = val.name.slice(0, pos);
		if (frmData[key] == undefined) {
			frmData[key] = [val.value];
		} else {
			frmData[key][frmData[key].length] = val.value;
		}
	} else {
		frmData[val.name] = val.value;
	}
});

frmData['soubor'] = 'nejaky vystup z http://www.html5rocks.com/en/tutorials/file/dndfiles/'
$.nette.ajax({
	url: 'xxx',
	method:'post',
	data: frmData
});
  • na straně php zjistit, zda je to ajax a pokud ano, tak dekódovat, …

ALE možná existuje elegantnější řešení, počkej si, jestli odpoví někdo zkušenější

Tirus91
Člen | 199
+
0
-

@Inkode
super, toto je pro mne jednodušší :) .. Já jsem si zatím udělal dropdown a něco s tím pokusím udělat a třeba mi to následně bude stačit. Problém je ten, že budu moci odesílat jen formulář kde je daný soubor…

a i když je to base64, tak mi to nějak ani nevadí (jo jen problém že to asi nezvládne validaci u addUpload)

Asi to udělám tak, že když tam dragAndDropnu soubory, tak je budu postupně jeden po druhém odesílat a rovnou je odtamtud odebírat a invalidovat snippet se seznamem fotek. To by možná bylo celkem fajn.

Editoval Tirus91 (10. 9. 2014 23:25)

Tirus91
Člen | 199
+
0
-

Jak mohu v signálu komponenty přijmout data, která odesílám pomocí ajaxu?

David Matějka
Moderator | 6445
+
0
-

@Tirus91 v $this->request v presenteru ma Nette\Application\Request

Tirus91
Člen | 199
+
0
-

@matej21
a odesílat to mám jak? Když to pošlu takto

var data = {
                                    file: reader.result,
                                    name: file.name
                                };
                                $.nette.ajax({
                                    type: "POST",
                                    dataType: 'json',
                                    url: {link upload!},
                                    data: JSON.stringify(data, null, 2)
                                });

tak mi tam příjde request->post a v tom je jakési pole které nemohu ani indexovat normálně

David Matějka
Moderator | 6445
+
0
-

zkus to uploadovat nejak takhle: http://stackoverflow.com/…-with-jquery

Tirus91
Člen | 199
+
0
-

@matej21
To mi asi nepůjde, jelikož nahrávání provádím pomocí

{form addPhoto}

            {input files}
            <div class="drop_zone_container">
                <div id="drop_zone" style="">{_'Drop files here'}</div>
                <output id="list"></output>
            </div>
            {block scripts}
            <script>
                function handleFileSelect(evt) {
                    evt.stopPropagation();
                    evt.preventDefault();
                    console.log(evt);
                    if (evt.target.id == 'inputFiles') {
                        var files = evt.target.files; // FileList object.
                    } else {
                        var files = evt.dataTransfer.files; // FileList object.
                    }

                    console.log(files);
                    // files is a FileList of File objects. List some properties.
                    var output = [];
                    var maxSize = {$maxUpload};

                    for (var i = 0, f; f = files[i]; i++) {
                        if (!f.type.match('image.*')) {
                            continue;
                        }
                        var bigger = false;
                        if (f.size > maxSize) {
                            bigger = true;
                        }
                        output.push('<li class="bg-info" id="file_' + i + '"><strong>', escape(f.name), '</strong> (', f.type || 'n/a', ') - ',
                                f.size, ' bytes',
                                '</li>');
                        if (bigger) {
                            continue;
                        }
                        (function(index, file) {
                            reader = new FileReader();
                            reader.onload = function(e) {
                                console.log(e);
                                document.getElementById('file_' + index).className = 'bg-success';
                                var postedData = {
                                    file: reader.result,
                                    name: file.name
                                };
                                $.nette.ajax({
                                    type: "POST",
                                    dataType: 'json',
                                    enctype: 'multipart/form-data',
                                    url: {link upload!},
                                    data: postedData
                                });
                                //$('#file_' + index).fadeOut('slow');
                            };
                            reader.readAsText(f)
                        })(i, f);
                    }
                    document.getElementById('list').innerHTML = '<ul class="files">' + output.join('') + '</ul>';
                }
                function handleDragOver(evt) {
                    evt.stopPropagation();
                    evt.preventDefault();
                    evt.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
                }

                // Setup the dnd listeners.
                document.getElementById('inputFiles').addEventListener('change', handleFileSelect, false);
                var dropZone = document.getElementById('drop_zone');
                dropZone.addEventListener('dragover', handleDragOver, false);
                dropZone.addEventListener('drop', handleFileSelect, false);
            </script>
            {/block}
        {/form}

chtěl jsem to dát do base64 a odeslat jako string

Editoval Tirus91 (11. 9. 2014 20:01)

Tirus91
Člen | 199
+
0
-

@matej21
Tak jsem to uz rozjel :) .. díky za rady. Už to posílám jako data URL. Jen mám jeden problém. Když tam vložím více souborů, tak se mi všechny zruší a odešle se jen ten poslední. Čím to může být?

Aborted


Accept	*/*
Accept-Encoding	gzip, deflate
Accept-Language	cs,en-us;q=0.7,en;q=0.3
Content-Length	2638306
Content-Type	application/x-www-form-urlencoded; charset=UTF-8
Cookie	tirus_cms=0vsbm93801epnpaik3ksetq400; nette-browser=15h1yf5mqo
Host	cms.local
Referer	http://cms.local/Administration/Galleries/photo/
User-Agent	Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0
X-Requested-With	XMLHttpRequest
David Matějka
Moderator | 6445
+
+2
-

to asi dela unique extension v nette.ajax.js
muzes ho vypnout, myslim, ze pomoci

$.nette.ext("unique", null);
Tirus91
Člen | 199
+
0
-

@matej21
díky, tím jsem narazil na problém se session..
Zkusil jsem dle https://forum.nette.org/…fy-chyba-303 doplnit session Id, ale asi špatně

Také když mi to někdy spadne na této exception, tak ajax to vyhodnotí, že bylo vše OK a provede mi to kod v success

$.nette.ext("unique", null);
$.nette.ajax({
    type: 'POST',
    url: {link upload!},
    data: {
        file: e.target.result,
        fileType: fi.type,
        fileName: fi.name,
        fileSize: fi.size,
        {=session_name()}: {=session_id()}
    },
    success: function(data, status, xhr) {
    }
});

Editoval Tirus91 (11. 9. 2014 21:15)

wodCZ
Člen | 49
+
0
-

Problém se session?
Pochopil bych to u toho flashe, ale tohle je normální HTTP požadavek, takže by to mělo fungovat stejně – ve verzi bez JS také nemáš hidden se session name a id, ne?

Tirus91
Člen | 199
+
0
-

@Inkode
Nevím co přesněji myslíš.
Zde je celé vykreslení formuláře

{form addPhoto}

            {input files}
            <div class="drop_zone_container">
                <div id="drop_zone" style="">{_'Drop files here'}</div>
                <output id="list"></output>
            </div>
            {block scripts}
            <script>
                function handleFileSelect(evt) {
                    evt.stopPropagation();
                    evt.preventDefault();
                    console.log(evt);
                    if (evt.target.id == 'inputFiles') {
                        var files = evt.target.files; // FileList object.
                    } else {
                        var files = evt.dataTransfer.files; // FileList object.
                    }

                    console.log(files);
                    // files is a FileList of File objects. List some properties.
                    var output = [];
                    var maxSize = {$maxUpload};

                    for (var i = 0, f; f = files[i]; i++) {
                        if (!f.type.match('image.*')) {
                            continue;
                        }
                        var bigger = false;
                        if (f.size > maxSize) {
                            bigger = true;
                        }
                        var itemLi = '<li class="bg-';
                        if(bigger){
                            itemLi += 'danger';
                        }else{
                            itemLi += 'info';
                        }
                        itemLi += '" id="file_' + i + '"><strong>'+ escape(f.name)+ '</strong> ('+  f.type + ') - '+f.size+ ' bytes'+'</li>';
                        output.push(itemLi);
                        if (bigger) {
                            continue;
                        }
                        (function(index, fi) {
                            reader = new FileReader();
                            reader.onload = function(e) {
                                console.log(e);

                                $.nette.ext("unique", null);
                                $.nette.ajax({
                                    type: 'POST',
                                    url: {link upload!},
                                    data: {
                                        file: e.target.result,
                                        fileType: fi.type,
                                        fileName: fi.name,
                                        fileSize: fi.size,
                                        {!=session_name()}: {=session_id()}
                                    },
                                    success: function(data, status, xhr) {
                                        document.getElementById('file_' + index).className = 'bg-success';
                                        $('#file_' + index).fadeOut('slow');
                                    },
                                    error: function(){
                                        document.getElementById('file_' + index).className = 'bg-danger';
                                    }
                                });

                            };
                            reader.readAsDataURL(f)
                        })(i, f);
                    }
                    document.getElementById('list').innerHTML = '<ul class="files">' + output.join('') + '</ul>';
                }
                function handleDragOver(evt) {
                    evt.stopPropagation();
                    evt.preventDefault();
                    evt.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
                }

                // Setup the dnd listeners.
                document.getElementById('inputFiles').addEventListener('change', handleFileSelect, false);
                var dropZone = document.getElementById('drop_zone');
                dropZone.addEventListener('dragover', handleDragOver, false);
                dropZone.addEventListener('drop', handleFileSelect, false);
            </script>
            {/block}
        {/form}

Odesílá se následující

fileName	P1010213 - kopie.JPG
fileSize	1904966
fileType	image/jpeg
tirus_cms	jur2gl3691psr14o6u1hu2eva1
file		DATA URL obrázku

hlavička požadavku

Accept	*/*
Accept-Encoding	gzip, deflate
Accept-Language	cs,en-us;q=0.7,en;q=0.3
Content-Length	2744112
Content-Type	application/x-www-form-urlencoded; charset=UTF-8
Cookie	tirus_cms=jur2gl3691psr14o6u1hu2eva1; nette-browser=15h1yf5mqo
Host	cms.local
Referer	http://cms.local/Administration/Galleries/photo/
User-Agent	Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0
X-Requested-With	XMLHttpRequest

A jako hlavička odpovědi je

Cache-Control	no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Connection	close
Content-Length	1082
Content-Type	text/html
Date	Thu, 11 Sep 2014 20:07:34 GMT
Expires	Thu, 19 Nov 1981 08:52:00 GMT
Pragma	no-cache
Server	Apache/2.4.9 (Win64) OpenSSL/1.0.1h PHP/5.5.14
Set-Cookie	tirus_cms=jur2gl3691psr14o6u1hu2eva1; expires=Fri, 11-Sep-2015 20:07:41 GMT; Max-Age=31536000; path=/; httponly nette-browser=15h1yf5mqo; path=/; httponly
X-Powered-By	PHP/5.5.14
X-Tracy-Error-Log	C:\wwwroot\htdocs\tirus_local\log\exception-2014-09-11-22-07-41-bf1a412b9e54228f3a271b2c8871d8ba.html

Editoval Tirus91 (11. 9. 2014 22:12)

wodCZ
Člen | 49
+
0
-

Promiň, špatně jsem se vyjádřil – jakou chybu ti to hlásí (jaký je obsah souboru z hlavičky X-Tracy-Error-Log C:\wwwroot\.....\....html) ?

Tirus91
Člen | 199
+
0
-

@Inkode
http://tirus.eu/exception.html
dal jsem celý soubor sem

co tak koukám, tak to nastává když provádím relogin (kvůli aktualizaci identity – je to tam kvůli tomu abych zajistil aktuální data i v případě že admin někomu změní roli nebo něco jiného)

Tak po zakomentování to funguje. Jak to mohu nahradit ten relogin? Potřebuji nahradit seznam rolí i identitu :(

Dále, jak mohu udělat postupné nahrávání těch souborů? (aby se to nenahrávalo najednou)

To samé, zda by šlo udělat abych ten soubor rovnou odesílal. Začalo mi to i padat na nedostatku paměti :(

Editoval Tirus91 (12. 9. 2014 0:59)