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/
Spoko, tylko dlaczego Yii1 dla nowej apki jak jest juz Yii2.
Bo lubię Yii1