regattanetwork's avatar

Uploaded image doesn't display immediately

I have a form that allows a user to upload a profile picture (calling it 'avatar'). The file saves correctly to the directory and the database. The problem is, the image only shows in the browser after the page is refreshed. Until refreshing the page, a broken image icon is displayed.

How can I fix this so the avatar image shows on the webpage immediately after the user clicks the button, 'Save My Avatar'?

Here's my code:

ROUTES - routes.php:

 Route::get('profile',[
  'as' => 'profile',
  'uses' => 'ProfileController@edit'
 ])->before('auth');

 Route::put('profile',[
  'as' => 'profile',
  'uses' => 'ProfileController@update'
 ])->before('auth');

MODEL - User.php:


public function uploads() { return $this->hasMany('Upload'); } public function avatar() { return $this->hasOne('Upload')->where('type', 'avatar'); }

VIEW - profile.blade.php:

    <form action="{{ 'profile' }}" method="put" id="MyForm">

      <div class="fileupload fileupload-new" data-provides="fileupload">

      <!-- DISPLAY AVATAR IF USER HAS SAVED ONE: -->
        <div class="fileupload-new thumbnail" style="width: 200px; height: 200px;">
          <img src="<?php 
            if ($user->avatar) {
              echo Croppa::url('user' . DS . $user->uploadDirectory . DS . $user->avatar->file_name,200, 200);
            } else { 
              echo URL::to('/mcsimple/assets/images/no-avatar.jpg'); 
            }
          ?>" />
        </div>

        <div class="fileupload-preview fileupload-exists thumbnail" style="max-width: 200px; max-height: 200px; line-height: 20px;"></div>

        <div>
        <!-- BUTTON TO SAVE AVATAR: -->
          <div class="fileupload-exists" style="padding-bottom: 5px">
            <input class="btn-primary btn" type="submit" value="Save My Avatar">
          </div>
        <!-- BUTTONS TO SELECT OR CHANGE AVATAR:       -->
          <span class="btn btn-file">
            <span class="fileupload-new">Select image</span>
            <span class="fileupload-exists">Change</span>
            <input type="file" id="avatar" name="avatar" >
          </span>
  <!--           <a href="#" class="btn fileupload-exists" data-dismiss="fileupload">Remove</a> -->
        </div>

      </div> 

    </form>


CONTROLLER - ProfilerController.php:

<?php

define('DS', DIRECTORY_SEPARATOR);

class ProfileController extends BaseController {

// ******************************************************************
// VIEW THE PAGE TO EDIT A USER PROFILE:
// ******************************************************************
  /**
   *
   * @param  int  $id
   * @return Response
   */
  public function edit() {
    $user = Auth::user();

    //If user hasn't saved a home address, lets add a temporary placeholder for the view
    if ($user->homeAddress == NULL){
      unset($user->homeAddress);
      $home = new Address;
      $home->type = 'home';
      $home->user_id = $user->id;
      $home->save();
    }

    return View::make('user.profile')->with(array('user' => $user));
  }

// ******************************************************************
// UPDATE A USER PROFILE:
// ******************************************************************
  /**
   *
   * @param  int  $id
   * @return Response
   */

  public function update()
  {
    $user = Auth::user();

    // VALIDATION RULES:    
    $rules = array(
      'first_name' => 'required',
      'last_name' => 'required',
      'username' => 'required|email'
      );

    $validation = Validator::make(Input::all(), $rules);

    // CHECK RULES PASS BEFORE UPDATING USER PROFILE:
    if ($validation->fails()) {
      return Redirect::route('profile')->withErrors($validation)->withInput();

    // UPDATE USER PROFILE:
    } else {
      $user->first_name = Input::get('first_name');
      $user->last_name = Input::get('last_name');
      $user->username = Input::get('username');
      $user->phone = Input::get('phone');
      $user->cell_phone = Input::get('cell_phone');
      $user->allow_sms = Input::get('allow_sms');
      $user->birth_date = Input::get('birth_date');

      // UPDATE USER ADDRESS - IF NONE EXISTS, CREATE A NULL ADDRESS:
      if ($user->homeAddress() == NULL){
        // unset($user->homeAddress);       
        $home = new Address;
        $home->type = 'home';
        $home->user_id = $user->id;
        $home->save();
      } else { 
        $home = $user->homeAddress;
      }

      foreach (Input::get('home_address') as $key => $value) {
        $home->$key = $value;
      }

      $home->save();

  // ##################################################
    // AVATAR:  
  // ##################################################
      if (Input::hasFile('avatar')) {

      // REMOVE OLD AVATAR:
        if ($user->avatar != NULL) {

        // DELETE FROM THE DIRECTORY:
          File::delete(public_path() . DS . 'user' . DS . $user->uploadDirectory . DS . $user->avatar->file_name);

        // DELETE FROM THE DATABASE:
          $user->avatar->delete();
        }

        $file = Input::file('avatar');
        $file_name = time() . '-' . $file->getClientOriginalName();

      // SAVE AVATAR TO THE DATABASE:
        $avatar = new Upload;
        $avatar->type = 'avatar';
        $avatar->file_name = $file_name;
        $avatar->user_id = $user->id;
        $avatar->save();

        // Input::file('avatar')->move('/home/regattanetwork/www.regattanetwork.com/htdocs/myrn/public/' .$user->upload_directory, $avatar->file_name);

      // SAVE FILE TO PUBLIC PATH:

        $file = $file->move(public_path() . DS . 'user' . DS . $user->uploadDirectory, $file_name);

        $user->uploads()->save($avatar);

        //for some reason laravel caches this     
        unset($user->avatar);
      }

      // SAVE CHANGES TO DATABASE AND DISPLAY MESSAGE:

      $user->save();

      return View::make('user.profile')->with(array('user' => $user, 'alert_message' => 'Your Information Has Been Saved!', 'alert_class' => 'alert-success'));
    }
  }
}

Thanks! Rachel

0 likes
8 replies
nolros's avatar

The best way to do this is in Javascript vs blade. That said, I don't see you returning and image after you updated the data. You've uploaded the file and moved it to perm path, but I don't see a get on the image and response unless I missed it?

You would need to do a File::get and then send it back with your view make

// I'm not including formatting, etc. 
File::get( public_path() . DS . 'user' . DS . $user->uploadDirectory, $file_name);

View::make('user.profile')
     ->with('avatar_tb', $image); 
// I remove the rest of your make array for brevity

pmall's avatar

@nolros No, in his blade template he use $user->avatar so no need to pass the avatar to the view.

@regattanetwork first, you can clean up your balde template :

<!-- DISPLAY AVATAR IF USER HAS SAVED ONE: -->
<div class="fileupload-new thumbnail" style="width: 200px; height: 200px;">
  @if($user->avatar)
    <img src="{{ Croppa::url('user' . DS . $user->uploadDirectory . DS . $user->avatar->file_name,200, 200); }}">
  @else
    <img src="{{ URL::to('/mcsimple/assets/images/no-avatar.jpg'); }}">
  @endif
</div>

And by the way what is Croppa::url ??

Then you save the avatar two times. Using should be enough :

$avatar = new Upload;
$avatar->type = 'avatar';
$avatar->file_name = $file_name;
$user->uploads()->save(avatar);

It assign the user_id to the avatar and save it to the database. You were actually saving the avatar two times. Also no need to save the user model. No need to use unset($user->avatar) too.

Finally you are doing it wrong at the end of the upload action. In a typical pĥp/laravel workflow, the create/upload/delete method should not render a view but redirect to another page. It prevents the user from submitting the form two times if he reload the page. And it prevents you from repeating show logic in the show, store, update, etc... actions

You sould return a redirect :

public function update(){

  // ...

  if(Input::hasFile('avatar')){

    // REMOVE OLD AVATAR:
    if($user->avatar != NULL){

      // DELETE FROM THE DIRECTORY:
      File::delete(public_path() . DS . 'user' . DS . $user->uploadDirectory . DS . $user->avatar->file_name);

      // DELETE FROM THE DATABASE:
      $user->avatar->delete();
    }

    $file = Input::file('avatar');
    $file_name = time() . '-' . $file->getClientOriginalName();

    // SAVE FILE TO PUBLIC PATH:
    $file = $file->move(public_path() . DS . 'user' . DS . $user->uploadDirectory, $file_name);

    // SAVE AVATAR TO THE DATABASE:
    $avatar = new Upload;
    $avatar->type = 'avatar';
    $avatar->file_name = $file_name;
    $user->uploads()->save($avatar);

  }

  return Redirect::route('route.to.user.profile');

}

If you want to add notifications, Jeffrey made a great package named laracasts/flash to pass flash message :) (Laravel 4 only)

regattanetwork's avatar

Thanks for both your help. @pmall, I tried your suggested edits in the controller, but it didn't solve the problem.

I'm going to keep trying and will post updates if I figure it out.

Thanks! Rachel

jekinney's avatar

With out nit picking you much, you have to much going on:). Any case it seems like your redirect and page loading is happening faster then your file process can be completed. I'd have to look at your code better, but it was an issue I had awile back too.

regattanetwork's avatar

@jekinney, thanks - I'll look into page loading.

And I'm open to constructive feedback. What do you mean by 'too much going on'?

Thanks, Rachel

jekinney's avatar

@regattanetwork

Your controller is huge. I don't know if you have a paid account, but look into the repository videos if you can.

For me, and that is just me, I hate clicking into five different places, and try to remember where I have methods and functions so even for the most basic (even a profile website that is maybe 1 - 4 pages) I still use repositories.

So for example my controllers look like:

<?php

use Core\Repositories\RegisterRepository;

class RegisterController extends \BaseController {

    /**
     * @var RegisterRepository
     */
    protected $registerRepo;

    /**
     * @param RegisterRepository $registerRepo
     */
    function __construct(RegisterRepository $registerRepo)
    {
        $this->registerRepo = $registerRepo;
        $this->beforeFilter('guest');
    }

    /**
     * Show the form for creating a new resource.
     * GET /register
     *
     * @return Response
     */
    public function create()
    {
        return View::make('register.create');
    }

    /**
 * Store a newly created user in storage.
 *
 * POST /register
 *
 * @return Response
 */
    public function store()
    {
        $user = $this->registerRepo->createUser(Input::only('username', 'email', 'password', 'password_confirmation'));

        return Redirect::route('register.success', compact('user'));
    }

    public function success($user)
    {
        return View::make('register.success', compact('user'));
    }

    /**
     * Activate new User after registration .
     *
     * POST /register/activate/{token}
     *
     * @return Response
     */
    public function activateUser($token)
    {
        $this->registerRepo->emailValidation($token);

        return Redirect::route('login');
    }
}

The Register Repo is where I do all the work, then for me if I have an issue or need to update something I can go straight to the repository and edit the tests, mostly done unless I need to add more data from a form.

<?php namespace Core\Repositories;

use Core\Entities\User;
use Core\Mailers\UserMailer as SendMail;

class RegisterRepository extends BaseRepository {

    protected $model;

   /**
   * @var RegisterForm
    */
   protected $registerForm;


    function __construct(User $model, RegisterForm $registerForm)
    {
        $this->model = $model;
        $this->registerForm = $registerForm;
    }

    public function createUser($input)
    {

    $this->registerForm->validate($input);

        $token = str_random(60);

        $input = array_add($input, 'token', $token);

        $user = $this->model->create($input);
    }

    public function emailValidation($token)
    {
        if(! isset($token))
        {
            throw new \ValidateEmailException('You have tried to go to the wrong URL');
        }

        $user = $this->model->where('token', $token)->first();

        $user->active = 1;
        $user->token  = '';
        $user->save();
    }
}

Granted this is not adding profiles etc, just something I had open at the moment as I am working on it still (Event for email!!!)

pmall's avatar

@regattanetwork what is the error now ?

The main problem with you approach was you didn't redirected the user a the end of the update action. You should not render view at the end of the update action.

Please or to participate in this conversation.