Yii: wgrywanie plików z użyciem AJAX

Dla jednej z moich klientek tworzyłam niedawno prostą stronę internetową dzięki której będzie ona mogła prezentować oferowane przez nią stroje i dodatki. Nieodzownym elementem tej strony był więc panel administracyjny umożliwiający wygodne i intuicyjne dodawanie zdjęć. Założyłam również, że osoba obsługująca serwis nie musi dbać o to by przed wgraniem zdjęcia zmniejszać je do odpowiednich rozmiarów i tworzyć miniatury.

Szukając odpowiedniego dodatku do frameworka Yii zwróciłam szczególną uwagę na rozszerzeniu eajaxupload. To rozszerzenie (instalacja i użycie opisane są dość czytelnie w dokumentacji) pozwala wgrać plik w tle a nawet dopisać potrzebną informację do bazy danych, ale nie spełnia wszystkich moich oczekiwań. Na szczęście i na to jest rada.

Podgląd

Po pierwsze chciałam, żeby dodawanie zdjęć było łatwe i przyjemne a więc potrzebny jest podgląd wgranego zdjęcia w postaci na przykład miniatury. Typowe wywołanie widgetu w widoku:

$this->widget('ext.EAjaxUpload.EAjaxUpload',
    array(
        'id'=>'uploadFile',
        'config'=>array(
        'action'=>Yii::app()->createUrl('mycontroler/upload'),
        'allowedExtensions'=>array("jpg","jpeg","gif","png"),
        'sizeLimit'=>1*1024*1024,// maximum file size in bytes
        'minSizeLimit'=>2*1024,// minimum file size in bytes
    )
));

należy więc rozszerzyć o funkcję przypiętą do zdarzenia `onComplete`, oraz o definicje powiadomień i funkcję wyświetlająca powiadomienia:

$this->widget('ext.EAjaxUpload.EAjaxUpload',
array(
    'id'=>'uploadFile',
    'config'=>array(
        'action'=>Yii::app()->createUrl('mycontroler/upload'),
        'allowedExtensions'=>array("jpg","jpeg","gif","png"),
        'sizeLimit'=>1*1024*1024,
        'minSizeLimit'=>2*1024,
        'onComplete'=>"js:function(id, fileName, responseJSON){
            var filename = responseJSON['filename'];
            var success = responseJSON['success'];
            var foto_id = responseJSON['foto_id'];
            if(success == true)
            {
                jQuery('.reg_div_new .deleteImage').removeClass('displaynone');
                jQuery('.reg_div_new #add_filename').val(filename);
                jQuery('.reg_div_new #foto_id').val(foto_id);
                jQuery('.qq-upload-button input').attr('disabled','disabled'); 
                jQuery('#image').html('<img id=\'thumb\' class=\'margin_t10\' src=" . Yii::app()->baseUrl . '/' .Yii::app()->params['upload_fold']. Yii::app()->params['thumb_pref']. "'+filename+'><div id= \'deleteImage\' class=\'active\'><a href=\''+filename+'\' id=\'remove_img\'><img src=" . Yii::app()->baseUrl . "/images/cancel.png alt=\'Remove image\'>usuń</a></div>')
            }else{
                jQuery('.reg_div_new .deleteImage').addClass('displaynone');
                jQuery('#uploadFile_new .qq-upload-button input').removeAttr('disabled','disabled'); 
            }
        }",
        'messages'=>array(
            'typeError'=>"{file} has invalid extension. Only {extensions} are allowed.",
            'sizeError'=>"{file} is too large, maximum file size is {sizeLimit}.",
            'minSizeError'=>"{file} is too small, minimum file size is {minSizeLimit}.",
            'emptyError'=>"{file} is empty, please select files again without it.",
            'onLeave'=>"The files are being uploaded, if you leave now the upload will be cancelled."
        ),
        'showMessage'=>"js:function(message){ alert(message); }"
    )
));

Poniżej widgetu w formularzu należy jeszcze dodać sekcję o id=”image” do której odwołuje się powyższy skrypt, oraz sekcję z ukrytymi polami formularza, które dotyczą dodanego zdjęcia i będą dołączone do formularza.

<div id="image"></div>
<div class="reg_div_new"> 
    <?php echo $form->hiddenField($model, 'filename',array('id'=>'add_filename'));
    echo $form->hiddenField($model, 'foto_id',array('id'=>'foto_id'));?>
</div>

Jak to działa? Widget wywołuje poprzez AJAX metodę kontrolera zdefiniowana w parametrze `action`. W naszym przypadku:

'action'=>Yii::app()->createUrl('mycontroler/upload')

Trzeba więc w kontrolerze mycontrolerController zdefiniować metodę actionUpload() i zadbać o to, żeby w funkcji accessRules() zdefiniować odpowiednie reguły dostępu do tej funkcji. Dane zwrócone przez tę metodę, w formacje json przechwytywane są przez skrypt do zmiennej responseJSON i wykorzystane w funkcji zdefiniowanej w sekcji ‘onComplete’ min do wyświetlenia miniatury wczytanego obrazka (wiersz 19). Poza tym pojawia się link który umożliwia ewentualne skasowanie wczytanego obrazka, co wymaga dopisana osobnej funkcji, ale o tym za chwile. Dopisane są też odpowiednie wartości do ukrytych pól formularza (wiersze 17-18).

Chciałam jeszcze zwrócić uwage na dwa parametry, które zdefiniowałam w pliku konfiguracyjnym a które wykorzystałam w powyższym skrypcie:

  • params[‘upload_fold’] – folder w którym umieszczane są wgrywane pliki
  • params[‘thumb_pref’] – przedrostek odróżniający nazwę wgranego zdjęcia od nazwy jego miniatury

Skalowanie i miniatura

Wspomniałam, że moim celem było min zdjęcie z użytkownika obowiązku wcześniejszego przygotowania miniatury zdjęcia która wykorzystywana była zarówno w panelu administracyjnym jak i później w samej prezentacji. Co zatem dzieje się w metodzie actionUpload()? Poniżej zawartość:

public function actionUpload(){
    Yii::import("ext.EAjaxUpload.qqFileUploader");

    $folder=Yii::app()->params['upload_fold'];
    $allowedExtensions = array("jpg","jpeg","gif","png");
    $sizeLimit = 10 * 1024 * 1024;

    $uploader = new qqFileUploader($allowedExtensions, $sizeLimit);
    $result = $uploader->handleUpload($folder);
    $result['filename'] = urlencode($result['filename']);

    $fileSize=filesize($folder.$result['filename']);
    $fileName=$result['filename'];
    
    $img = Yii::app()->simpleImage->load($folder.$result['filename']);
    $fileeNameNew = Yii::app()->params['thumb_pref'].$fileName;
    $img->resizeToWidth(190);
    $img->save($folder.$fileeNameNew);
    
    $model = new Foto(); //dodanie do bazy
    $model->plik = $fileName;
    if($model->save())
        $result['foto_id'] = $model->id;

    $return = htmlspecialchars(json_encode($result), ENT_NOQUOTES);
    echo $return;      
}

W pierwszej kolejności importowana jest klasa odpowiedzialna za wgranie pliku na serwer. W wierszach 4-6 zdefiniowane są niezbędne parametry. Następnie tworzony jest obiekt klasy qqFileUploader i wywołana metoda handleUpload tego obiektu. Zwraca ona tablicę w której zapisane są informacje na temat pobranego pliku. To wystarczy do samego wgrania pliku.

Jednak jeśli chcemy go jeszcze przeskalować i stworzyć miniaturę należy skorzystać z rozszerzenia simpleImage. W wierszach 15-19 skrypt skaluje wczytany plik do odpowiedniego rozmiaru i zapisuje pod nową nazwą tj z prefiksem.

Ostatnia sekcja to zapisanie do bazy informacji o wczytanym pliku. Oczywiście to jak będzie wyglądał ten fragment zależy wyłącznie od struktury bazy danych. Na koniec, zgodnie z tym co już sygnalizowałam funkcja zwraca dane w formacje json.

Kasowanie wczytanego pliku

Uprzedziłam, że do kompletu potrzebny nam będzie jeszcze skrypt obsługujący button kasowania załadowanego w formularzu pliku. W katalogu ze skryptami zdefiniowałam plik remove_image.js o następującej zawartości:

$(document).ready(function() {
    $(document).delegate('#remove_img','click',function(ev){
	ev.preventDefault();
	var name = $(this).attr('href');
	
	request  = $.post( "/mycontroler/delImage", {file_name:name},function() {});

	 request.done(function(data ) {
	    if(data == 1){
		$('#image').empty();
	    }
	    else{
		$('#image').append("error: "+data);
	    }
	  });

	request.fail(function(jqXHR, textStatus, errorThrown) {
	   alert(
		"The following error occurred: "+
		textStatus+ "::"+ errorThrown
	    );
	});
    });
});

Wystarczy więc w widoku w którym zdefiniowany jest omawiany formularz dodać ten skrypt

Yii::app()->clientScript->registerScriptFile($strBaseUrl.'/js/remove_image.js',0);

Następnie w kontrolerze mycontroler należy zdefiniować metodę actionDelImage() do której ten skrypt się odwołuje. Metoda ta, jak się można domyślać powinna usuwać plik z serwera oraz z bazy jeśli został tam dodany.

Opisane skrypty zostały wykorzystane na stronie http://businessservice-fashion.pl/

2 komentarze do wpisu „Yii: wgrywanie plików z użyciem AJAX”

Leave a Reply

%d bloggers like this: