Uploading Images

Basics

As suggested in the introduction, Image Core is designed to be used primarily within the clientside end of things in terms of usage, and that is especially true for the uploading of an image. The Image Core addon, when it was installed has added a route to the router called upload/image that handles the uploading and saving to the database, and then returns the id for you to save to the table you are linking from. In essence, you will make a direct server request to upload the image and then make sure you handle the response correctly.

Note: It is important to realize that calling upload saves to the database immediately so to save space in the database it is suggested that you keep track of ids that have been uploaded to remove them if a user cancels request or uploads another one (because if not, they will remain in the image table, completely unlinked with anything else).

Client Side Handling

There are a few aspects of this clientside setup, but it is not too complicated. Choosing between "basic" and "data cleanup" versions are fairly simple, and which one you use simply answers this question: to the end user, do they think that the changes to the images are made immediately, or do they think that the changes to images are made when a "save" button is clicked? And this entirely depends upon the design and functionality of your app.

Handlebars File

For most of our apps, we use the dropzone for the uploading of our images. While any image upload component (such as even fw-file-upload) will work, because of the frequency for multiple images, dropzone is recommended. Make sure to install the ember-cli-dropzonejs and the dropzone packages before trying to use this component. See Dropzone Documentation for configuration details.

A basic example for what is expected for drop-zone is as follows (for more details on uploadUrl and imageSuccess, see below):

<DropZone
    @url={{uploadUrl}}
    @thumbnailWidth=120
    @dictDefaultMessage="Drop files (or click) to upload a new image"
    @success={{action 'imageSuccess'}}
/>

Since this drop-zone will display within it only those images which have been uploaded this current page load (including any images that were deleted), it is highly suggested that either above or underneath the drop-zone you display all the images that are currently saved. For details on displaying images see displaying images.

Javascript File

The javascript file only needs to contain the uploadUrl variable and the imageSuccess function (for the upload). But for retaining information to make sure that the only images saved in the database are the ones connected to a model, it requires a little more complexity.

Basic

At its most basic level, all that is required of this is an example of what is required:

config: inject(),
uploadUrl: computed('config.apiRoot', function () {
    return this.get('config').formUrl('upload', 'image');
}),

actions: {
    //single image
    imageSuccess(image, response) {
        this.set('model.image', response);
    },

    //multiple images
    imageSuccess(image, response) {
        this.get('model.images').pushObject(response);
    }
}

uploadUrl computes the url that has been injected into the router by the Image Core addon that handles all the uploading for you (namely upload/image). Then dropzone properly calls the upload function for you, so all you have to worry about is what to do in the imageSuccess (which is called upon a successful completion of the upload). At the core, all imageSuccess needs to do is set the proper value to the id. Of course, this more basic way can be used if you are not saving the image to a model, but you simply have a setting that contains an array of images.

Including Cleanup

If you are saving an image or images to a specific model, you should consider this method. When considering cleanup, there are a few things that are needed to be considered. The only image that should be on the database are the image(s) that are set to the value when the save button is clicked (or the original image when the cancel button is clicked). That being said, it is easiest to think about this with two different arrays imagesToDeleteUponSave and imagesToDeleteUponCancel. For the actual deleting, check out deleting images, but part of the setup is in this imageSuccess. Another aspect to consider is if you are planning to save only one single image or not. If it is only one image, any image uploaded should replace subsequent images, so the imageSuccess should incorporate this as well.

The general concept here is that any image added (whether for single or multiple images) should be deleted from the database when cancel is called, and additionally, for single images, any image except the current should be deleted upon save (all previous images). and Here are the imageSuccess functions including cleanup:

imagesToDeleteUponSave: [],
imagesToDeleteUponCancel: [],

actions: {
    //single image (replace)
    imageSuccess(image, response) {
        // when canceled, delete this new image
        this.get('imagesToDeleteUponCancel').pushObject(response);
        // when saved, delete previous image (if there is one)
        if (this.get('model.image')) {
            this.get('imagesToDeleteUponSave').pushObject(this.get('model.image'));
        }
        this.set('model.image', response);
    },

    //multiple images
    imageSuccess(image, response) {
        // when canceled, delete this new image
        this.get('imagesToDeleteUponCancel').pushObject(response);

        this.get('model.images').pushObject(response);
    },
}

If you add these few lines of code it will ensure that nothing will slip between the cracks and many images will be saved on your database, which are not linked with anything.

Server Side Handling

Going directly through the serverside to upload an image should be done rarely if at all. In none of our current apps which use Image Core, do we use this direct "override" method. Because this app is designed to use the php built in $_FILES array, it would seem to be redundant to attempt to upload am image from serverside, since you would have to call a server function directly anyway (might as well use the one already built for you). The route used in the client side uses the ImageCore/Standard/ImageController::image() function (it has no parameters), so if one insists, it may be possible to directly call this request from the serverside (using the class, then directly calling the function). But overall, this is not recommended, and unless there is a specific reason you cannot, using the more commonly used client side paths would be much smoother.