How to create custom camera plugin?

Yes sure, in the “ng-click” you call methods that are defined in your controller.
And those controller methods then call the service methods which then actually take (or choose) the photo.

So, view --> controller --> service.

Does that help or not?

You see no index.html, what are you running then, how did you create the app?

Hi leob,

So for your sample js, should I only use addImage function in my ng-click?

The addImage function from my sample JS calls the Cordova camera plugin to take or select a photo. So if that’s what you want to do, yes then you call that from your ng-click.

HI Leob,

I followed the instructions on the page.

git clone https://github.com/leob/ionic-quickstarter
mv ionic-quickstarter myapp
cd myapp

Hm that’s weird.

Yes sometimes the process is smooth and sometimes you’d run into errors, the software dependencies can be complex.

What I’ll do is I’ll download and install the starter myself and see if it’s still running okay, it’s a long time ago since I did that myself. Some (global) dependencies may have changed and broken stuff, e.g. the Ionic CLI (installed globally) is at a way higher version now.

Stuff does break in ways you don’t expect.

Also I never tested this out on Windows (which I see you are using). I believe this Ionic stuff (with all of its nodejs/npm dependencies) can be problematic on Windows. Did you hear about “Ionic Box” ? I’ve heard it’s good for setting up a dev environment on Windows.

And did you try to run “ionic serve” with the “-c” command so that it echoes its logging to the console/terminal? Then I think it will diplay some errors that will give you a hint what’s going wrong.

Regarding “index.html” and the location of the templates and so on:

The “html” templates are located in subdirectories of “src/js/app”, they’re not in “www/templates” as the basic starter apps are doing, because I don’t like that (only good for simple apps).

The file/directory structure that I’m using is based on best practices recommended by people like John Papa and is better suited to more complex apps. It’s all explained in a section of the README, please take a few minutes to read that section because then it will all become clear.

Now regarding the “index.html”. First you have to know that for “ionic serve” you need to look under “src”, not under “www”. This is because it’s configured that way in the “ionic.project” file (or it should be, please double-check your ionic.project file).

Then you need to know that initially there is NO index.html file, only an “index-template.html” file. The reason is that there is a gulp process (called “gulp-inject”) which will take this index-template.html file, inject all of the script-dependencies into it, and produce an “index.html” out of that.

If this “gulp-inject” process fails (for instance because the NPM module failed to install), then the index.html file will not be created, which would explain your error.

If you use the “-c” argument with “ionic serve” and look at the terminal then I expect that the cause of the error will become evident.

hi leob,

Whats is the name of your controller to use in ng-controller?

var batchModule = angular.module('starter', ['ionic', 'ngCordova'])

var ImageController = function ($scope, $q, ImageService, FileManager) {
    function addImage() {
        var type = 1;   // 1 = camera, 2 = photo library
        var fileName = 'image -' + Date.now() + '.jpg';
        // Set image options (quality, height/width)
        var imageOpts = getImageOpts(type);
        var targetDir = cordova.file.dataDirectory + '/LottoPictures';  // target directory on the native file system
        var fileUrl = null;       // file URL on the native file system
        //
        // Now execute all the steps of the "pipeline" via Promise chaining:
        //
        ImageService.getPicture(type, imageOpts.pictureQuality, imageOpts.targetSize).then(function (imageUrl) {
            $log.debug("ImageService#getPicture imageUrl = '" + imageUrl + "'");
            return FileManager.downloadFile(imageUrl, targetDir, 'uncropped-' + fileName);
        }).then(function (result) {
            $log.debug("FileManager#downloadFile uncropped result = " + JSON.stringify(result));
            fileUrl = result.nativeURL;
            // image file downloaded to the native file system, clean up the temp files
            ImageService.cleanup();
        }).then(function (ignore) {
            $log.debug("Done");
        }).catch(function (error) {
            $log.debug("Error in addImage: " + JSON.stringify(error));
        });
    }
};

batchModule.factory('ImageService', function ($cordovaCamera, $q, $log) {

    function optionsForType(type, quality, targetSize) {
        var source;
        switch (type) {
            case 0:
                source = Camera.PictureSourceType.CAMERA;
                break;
            case 1:
                source = Camera.PictureSourceType.PHOTOLIBRARY;
                break;
        }
        return {
            quality: quality, // e.g. 75,
            destinationType: Camera.DestinationType.FILE_URI,
            sourceType: Camera.PictureSourceType.CAMERA,
            allowEdit: true, // PASS TRUE HERE TO ALLOW CROPPING AND SO ON
            encodingType: Camera.EncodingType.JPEG,
            popoverOptions: CameraPopoverOptions,
            saveToPhotoAlbum: true, //false,
            targetWidth: 500, // e.g. 500,
            targetHeight: 500, // e.g. 500,
            correctOrientation: true // SEE: simonmacdonald.blogspot.ca/2012/07/change-to-camera-code-in-phonegap-190.html
        };
    }

    var getPicture = function (type, quality, targetSize) {
        return $q(function (resolve, reject) {
            var options = optionsForType(type, quality, targetSize);

            $cordovaCamera.getPicture(options).then(function (imageUrl) {

                $log.debug('ImageService#getPicture, $cordovaCamera.getPicture imageUrl = ' + imageUrl);

                resolve(imageUrl);

            }, function (error) {
                $log.debug('ImageService#getPicture, $cordovaCamera.getPicture error = ' + JSON.stringify(error));

                reject(error);
            });

        });
    };

    var cleanup = function () {
        // Cleanup temp files from the camera's picture taking process. Only needed for Camera.DestinationType.FILE_URI.
        // Returns a promise the result of which is probably ignored.
        return $cordovaCamera.cleanup();
    };

    return {
        getPicture: getPicture,
        cleanup: cleanup
    };
});

//
// https://github.com/apache/cordova-plugin-file
// http://www.raymondcamden.com/2014/08/18/PhoneGapCordova-Example-Getting-File-Metadata-and-an-update-to-the-FAQ
// http://www.html5rocks.com/en/tutorials/file/filesystem/
// http://community.phonegap.com/nitobi/topics/dataurl_to_png
//

batchModule.factory('FileManager', function ($q, $log, $cordovaFile, $cordovaFileTransfer, $cordovaFileOpener2) {

    var downloadFile = function(sourceURI, targetDir, targetFile) {
        var deferred = $q.defer();

        $log.debug("FileManager#downloadFile source (original): '" + sourceURI + "'");
        sourceURI = decodeURI(sourceURI);

        var targetPath = targetDir + targetFile;
        var trustHosts = true;
        var options = {};

        $cordovaFileTransfer.download(sourceURI, targetPath, options, trustHosts).then(
          function(result) {
              deferred.resolve(result);
          }, function(error) {
              $log.debug("error: " + JSON.stringify(error));
              deferred.reject(error);
          }, function (progress) {
              //$timeout(function () {
              //  $scope.downloadProgress = (progress.loaded / progress.total) * 100;
              //})
          });

        return deferred.promise;
    };

    var removeFile = function (baseDir, filePath) {
        $log.debug("FileManager#removeFile baseDir = '" + baseDir + "', filePath = '" + filePath + "'");

        return $cordovaFile.removeFile(baseDir, filePath);
    };

    return {
        downloadFile: downloadFile,
        removeFile: removeFile
    };
});

Regarding the ENOENT, do you have the problem when viewing the app in Ionic View after doing “ionic upload” ? That’s a known issue which is discussed here:

https://github.com/leob/ionic-quickstarter/issues/24

The solution/workaround is also discussed there and documented here:

https://github.com/leob/ionic-quickstarter#a-note-about-ionic-upload-and-the-ionic-view-app

HI Leob, There’s no Index.html file in the www folder when i build. That is the issue. Am on a windows machine.

Yes I understood that. If you’re doing “ionic upload” and using Ionic View then the reason is explained in my previous post. Otherwise (if you’re doing “ionic run” or “ionic build”) then you can have a look at the hints from my earlier post:

How to create custom camera plugin?

If you have console logging enabled then I’m quite sure there will be some error message that explains what is going wrong.

P.S. by the way if you’re struggling to get the CLI and all that stuff working on Windows, maybe this helps:

http://mosalem.blogspot.nl/2015/10/using-taco-to-create-ionic-projects-for_19.html

This is a command line tool called “taco” which Microsoft just released, works with Ionic too, according to the article.

Hey I’ve finished my code containing the image stuff (Cordova camera, cropping, displaying) and uploaded it to my Github repo:

There’s a controller file “userProfile.ctrl.js” (and a template userProfile.html) which demonstrates how to take a picture, crop it and display it (images are stored in local storage, for simplicity).

Now there are a few things you need to know to get it working:

It works only on a device, because it needs Cordova, and camera hardware obviously So, you need to follow the steps in the README (see https://github.com/leob/ionic-quickstarter#gulp-build) to build and install on a device:

gulp build
ionic run

Now this is easy enough but there is one ‘gotcha’. When you do gulp build it will use the configuration settings from config-prod.json (https://github.com/leob/ionic-quickstarter/blob/master/src/js/config/config-prod.json).

But those settings, by default, will cause your app to run with login/auth based on Parse.com. So this will only work if you’ve got Parse.com set up.

If you don’t want this, so if you just want to run with the “mock” (fake) auth service, and still be able to use the image functionality (on a device), then simply change the value of the “devMode” from ‘false’ to “true” in config-prod.json.

So, in src/js/config/config-prod.json you get this:

"devMode": true

instead of “devMode”: false.

Now you can build and run the app on a device and it will just go to the start page or to the login page. If it goes to the login page you just put in a fake email address like a@b.com and for the password put in “password”. You then end up on the home page with 3 tabs.

Now either click on the “gear” icon in the top right corner, or (alternatively) select “User Profile” from the side menu.

You then see the 'User Profile" page where you can enter your (fake) user profile data, e,g. name and so on.

One other thing you can do there (which is what it’s all about now) is upload a profile pic. Click the image placeholder and you should get an ‘action menu’ where you can take or choose a pic, and then you can crop it and see how it gets displayed.

Hi leob,

Thanks for a job well done . I will try to run it later tonight. Pls note that my machine is Windows . And I use ionic platform add android and ionic build android to run.

I hope I won’t fall into the previous issue where there’s no index file in the www folder.

Can’t wait to try it out . Thanks once again .

1 Like

Cool, hope it works for you. As explained you can set it up so that you don’t need Parse.com or Firebase or whatever so then the setup should be minimal for you to be able to try out the image functionality.

P.S. by the way, I advise you to run with “ionic serve -c -l” (with the “labs” option, and showing errors in the console).

I’m pretty sure it will work then, I believe the “-l” option fixes the “missing index.html” problem.

If not, look at the errors in the console (terminal).

UPDATE: if you add the “-c” argument to “ionic serve” then you will probably see this error:

“libsass bindings not found. Try reinstalling node-sass?”

This is a known issue which many people (also Ionic users) have come across, if you search for “libsass bindings not found” then you will see many discussion threads about it.

The best solution seems to be this answer:

Error running gulp sass

As you can see, the solution is that it works only with certain versions of nodejs. The problem is especially with “new” nodejs versions, e.g. nodejs v.4. I am using nodejs v.0.12 and I don’t have the problem.

Another discussion and possible solution is this post:

'libsass' bindings not found

So that is the issue. I’ve added a warning and some info to my README because I’ve seen many people struggling with this issue.

P.S. if you already tried downloading the new version, then just delete the thing and do a fresh install please. I think I got to the bottom of the ENOENT and “libsass” issues and fixed it so that it works both with the old and new nodejs versions.

Hi Leob,

everything worked fine this time. I needed to install gulp and do a gulp build. after that i could see my Index.html file in the www folder. Moving forward , you architecture is strange and very unlike the normal ionic project.

I am used to a controller.js, app.js and template files. how do i continue work? am lost as to where my controller is, where to add template files and where to my own contollers or code. Do i need to read your github page again?..

kinda lost on the organistaion and how to add my own js files and controllers.

Hey that’s good to hear.

I’m very glad to hear that it works, there’s another guy who’s trying to get it working and it’s an endless story, I’m about to give up. So tell me, which nodejs are yo running, is it nodejs v.4 ?

The architecture is different from the simple starters, but it’s not that strange. The well-known AngularJS expert John Papa is one of the people who recommend this structure, see this description in his famous Style Guide:

https://github.com/johnpapa/angular-styleguide#style-y152

It’s called “directory per feature” (or module) and is better suited to larger apps (the structure in the default starter apps doesn’t scale well to larger apps, lumping everything in 3 Javascript files isn’t really a good idea).

I’ve also described the structure in the README file:

Just read that section (and maybe also the John Papa link that I gave you) and I am sure it will become all clear.

Hi Leob,

my node js version is v0.12.7. I just checked the version inside the app folder anyway using the command node --version.

As per the guy, is he running on a windows machine? what’s the issue? can you refer him to me if he’s trying to run on a windows?

I will read the documents and see what happens. Hope to provide you positive feedback soon.

Thanks for the good work. :grin: