Search code examples
javascriptcordovaphonegap-pluginscordova-pluginsvisual-studio-cordova

Access Cordova navigator.camera plugin outside of index.html is undefined


Can't seem to find an explanation for this. I am currently using Visual Studio 2015 RC. Installed the camera plugin and am using the Cordova example as a base. The problem is however, that I can't access the plugin if this code is outside of the index.html page.

I have tried referencing the Cordova.js on my second page but it doesn't help. How do I access the camera plugin outside of index.html? This is the code I am using from the Cordova examples:

Index Page:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>CordovaApp</title>

    <!-- CordovaApp references -->
    <link href="css/index.css" rel="stylesheet" />
    <script src="scripts/jquery/jquery-2.1.4.min.js"></script>
    <script src="scripts/jquery.mobile-1.4.5/jquery.mobile-1.4.5.min.js"></script>
    <script>
        $(document).on('pagecreate', function () {
            $("#submitLogin").on("click", function () {
                MoveToPage("/test.html");
            });
            function MoveToPage(page) {
                var dirPath = dirname(location.href);
                fullPath = dirPath + page;
                window.location.href = fullPath;

                function dirname(path) {
                    return path.replace(/\\/g, '/').replace(/\/[^\/]*$/, '');
                }
            }
        });

        //***For the sake of having it on two pages
        var pictureSource;
        var destinationType;

        document.addEventListener("deviceready", onDeviceReady, false);

        function onDeviceReady() {
            pictureSource = navigator.camera.PictureSourceType;
            destinationType = navigator.camera.DestinationType;
        }

        function onPhotoDataSuccess(imageData) {
            var smallImage = document.getElementById('smallImage');
            smallImage.style.display = 'block';
            smallImage.src = "data:image/jpeg;base64," + imageData;
        }
        function onPhotoURISuccess(imageURI) {
            var largeImage = document.getElementById('largeImage');
            largeImage.style.display = 'block';
            largeImage.src = imageURI;
        }
        function capturePhoto() {
            navigator.camera.getPicture(onPhotoDataSuccess, onFail, {
                quality: 50,
                destinationType: destinationType.DATA_URL
            });
        }
        function capturePhotoEdit() {
            navigator.camera.getPicture(onPhotoDataSuccess, onFail, {
                quality: 20, allowEdit: true,
                destinationType: destinationType.DATA_URL
            });
        }
        function getPhoto(source) {
            navigator.camera.getPicture(onPhotoURISuccess, onFail, {
                quality: 50,
                destinationType: destinationType.FILE_URI,
                sourceType: source
            });
        }
        function onFail(message) {
            alert('Failed because: ' + message);
        }
    </script>
</head>
<body>
    <button style="color:white; background-color:dodgerblue;" id="submitLogin">Log In</button>

    <!-- Cordova reference, this is added to your app when it's built. -->
    <script src="cordova.js"></script>
    <script src="scripts/platformOverrides.js"></script>

    <script src="scripts/index.js"></script>
</body>
</html>

Second Page:

<!DOCTYPE html>
<html>
  <head>
    <title>Capture Photo</title>

    <script type="text/javascript" charset="utf-8" src="cordova.js"></script>
    <script type="text/javascript" charset="utf-8">

    var pictureSource;   // picture source
    var destinationType; // sets the format of returned value

    // Wait for device API libraries to load
    //
    document.addEventListener("deviceready",onDeviceReady,false);

    // device APIs are available
    //
    function onDeviceReady() {
        pictureSource=navigator.camera.PictureSourceType;
        destinationType=navigator.camera.DestinationType;
    }

    // Called when a photo is successfully retrieved
    //
    function onPhotoDataSuccess(imageData) {
      // Uncomment to view the base64-encoded image data
      // console.log(imageData);

      // Get image handle
      //
      var smallImage = document.getElementById('smallImage');

      // Unhide image elements
      //
      smallImage.style.display = 'block';

      // Show the captured photo
      // The in-line CSS rules are used to resize the image
      //
      smallImage.src = "data:image/jpeg;base64," + imageData;
    }

    // Called when a photo is successfully retrieved
    //
    function onPhotoURISuccess(imageURI) {
      // Uncomment to view the image file URI
      // console.log(imageURI);

      // Get image handle
      //
      var largeImage = document.getElementById('largeImage');

      // Unhide image elements
      //
      largeImage.style.display = 'block';

      // Show the captured photo
      // The in-line CSS rules are used to resize the image
      //
      largeImage.src = imageURI;
    }

    // A button will call this function
    //
    function capturePhoto() {
      // Take picture using device camera and retrieve image as base64-encoded string
      navigator.camera.getPicture(onPhotoDataSuccess, onFail, { quality: 50,
        destinationType: destinationType.DATA_URL });
    }

    // A button will call this function
    //
    function capturePhotoEdit() {
      // Take picture using device camera, allow edit, and retrieve image as base64-encoded string
      navigator.camera.getPicture(onPhotoDataSuccess, onFail, { quality: 20, allowEdit: true,
        destinationType: destinationType.DATA_URL });
    }

    // A button will call this function
    //
    function getPhoto(source) {
      // Retrieve image file location from specified source
      navigator.camera.getPicture(onPhotoURISuccess, onFail, { quality: 50,
        destinationType: destinationType.FILE_URI,
        sourceType: source });
    }

    // Called if something bad happens.
    //
    function onFail(message) {
      alert('Failed because: ' + message);
    }

    </script>
  </head>
  <body>
    <button onclick="capturePhoto();">Capture Photo</button> <br>
    <button onclick="capturePhotoEdit();">Capture Editable Photo</button> <br>
    <button onclick="getPhoto(pictureSource.PHOTOLIBRARY);">From Photo Library</button><br>
    <button onclick="getPhoto(pictureSource.SAVEDPHOTOALBUM);">From Photo Album</button><br>
    <img style="display:none;width:60px;height:60px;" id="smallImage" src="" />
    <img style="display:none;" id="largeImage" src="" />
  </body>
</html>

EDIT: I found that the button I created on my index page uses:

window.location = path;

When I changed this button to an anchor tag, the javascript from the index page stays loaded and I am able to access the camera on another page. This is still a problem for me however as I want to be able to use the onclick event on the index page to validate my user before he/she is able to access the next page and camera. This would also mean that I would have to load all my javascript on the first page which I don't want to do. Is there anyway around this?

EDIT: Possible extra information that might be useful. I installed the plugin using Visual Studio 2015 RC. Created a blank Cordova app and installed the plugin by double clicking config.xml > Plugins > Camera > Add. My config.xml looks like so:

<?xml version="1.0" encoding="utf-8"?>
<widget xmlns:cdv="http://cordova.apache.org/ns/1.0" xmlns:vs="http://schemas.microsoft.com/appx/2014/htmlapps" id="io.cordova.myappc768b8113d304787b0649513e48ca2c4" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" defaultlocale="en-US">
  <name>CordovaApp2</name>
  <description>A blank project that uses Apache Cordova to help you build an app that targets multiple mobile platforms: Android, iOS, Windows, and Windows Phone.</description>
  <author href="http://cordova.io" email="[email protected]">Apache Cordova Team </author>
  <content src="index.html" />
  <access origin="*" />
  <vs:features />
  <preference name="SplashScreen" value="screen" />
  <preference name="windows-target-version" value="8.1" />
  <preference name="windows-phone-target-version" value="8.1" />
  <platform name="android">
    <icon src="res/icons/android/icon-36-ldpi.png" density="ldpi" />
    <icon src="res/icons/android/icon-48-mdpi.png" density="mdpi" />
    <icon src="res/icons/android/icon-72-hdpi.png" density="hdpi" />
    <icon src="res/icons/android/icon-96-xhdpi.png" density="xhdpi" />
  </platform>
  <platform name="ios">
    <!-- iOS 8.0+ -->
    <!-- iPhone 6 Plus  -->
    <icon src="res/icons/ios/icon-60-3x.png" width="180" height="180" />
    <!-- iOS 7.0+ -->
    <!-- iPhone / iPod Touch  -->
    <icon src="res/icons/ios/icon-60.png" width="60" height="60" />
    <icon src="res/icons/ios/icon-60-2x.png" width="120" height="120" />
    <!-- iPad -->
    <icon src="res/icons/ios/icon-76.png" width="76" height="76" />
    <icon src="res/icons/ios/icon-76-2x.png" width="152" height="152" />
    <!-- iOS 6.1 -->
    <!-- Spotlight Icon -->
    <icon src="res/icons/ios/icon-40.png" width="40" height="40" />
    <icon src="res/icons/ios/icon-40-2x.png" width="80" height="80" />
    <!-- iPhone / iPod Touch -->
    <icon src="res/icons/ios/icon-57.png" width="57" height="57" />
    <icon src="res/icons/ios/icon-57-2x.png" width="114" height="114" />
    <!-- iPad -->
    <icon src="res/icons/ios/icon-72.png" width="72" height="72" />
    <icon src="res/icons/ios/icon-72-2x.png" width="144" height="144" />
    <!-- iPhone Spotlight and Settings Icon -->
    <icon src="res/icons/ios/icon-small.png" width="29" height="29" />
    <icon src="res/icons/ios/icon-small-2x.png" width="58" height="58" />
    <!-- iPad Spotlight and Settings Icon -->
    <icon src="res/icons/ios/icon-50.png" width="50" height="50" />
    <icon src="res/icons/ios/icon-50-2x.png" width="100" height="100" />
  </platform>
  <platform name="windows">
    <icon src="res/icons/windows/Square150x150Logo.scale-100.png" width="150" height="150" />
    <icon src="res/icons/windows/Square150x150Logo.scale-240.png" width="360" height="360" />
    <icon src="res/icons/windows/Square30x30Logo.scale-100.png" width="30" height="30" />
    <icon src="res/icons/windows/Square310x310Logo.scale-100.png" width="" height="" />
    <icon src="res/icons/windows/Square44x44Logo.scale-240.png" width="106" height="106" />
    <icon src="res/icons/windows/Square70x70Logo.scale-100.png" width="70" height="70" />
    <icon src="res/icons/windows/Square71x71Logo.scale-240.png" width="170" height="170" />
    <icon src="res/icons/windows/StoreLogo.scale-100.png" width="50" height="50" />
    <icon src="res/icons/windows/StoreLogo.scale-240.png" width="120" height="120" />
    <icon src="res/icons/windows/Wide310x150Logo.scale-100.png" width="310" height="150" />
    <icon src="res/icons/windows/Wide310x150Logo.scale-240.png" width="744" height="360" />
  </platform>
  <platform name="wp8">
    <icon src="res/icons/wp8/ApplicationIcon.png" width="62" height="62" />
    <icon src="res/icons/wp8/Background.png" width="173" height="173" />
  </platform>
  <platform name="android">
    <splash src="res/screens/android/screen-hdpi-landscape.png" density="land-hdpi" />
    <splash src="res/screens/android/screen-ldpi-landscape.png" density="land-ldpi" />
    <splash src="res/screens/android/screen-mdpi-landscape.png" density="land-mdpi" />
    <splash src="res/screens/android/screen-xhdpi-landscape.png" density="land-xhdpi" />
    <splash src="res/screens/android/screen-hdpi-portrait.png" density="port-hdpi" />
    <splash src="res/screens/android/screen-ldpi-portrait.png" density="port-ldpi" />
    <splash src="res/screens/android/screen-mdpi-portrait.png" density="port-mdpi" />
    <splash src="res/screens/android/screen-xhdpi-portrait.png" density="port-xhdpi" />
  </platform>
  <platform name="ios">
    <splash src="res/screens/ios/screen-iphone-portrait.png" width="320" height="480" />
    <splash src="res/screens/ios/screen-iphone-portrait-2x.png" width="640" height="960" />
    <splash src="res/screens/ios/screen-ipad-portrait.png" width="768" height="1024" />
    <splash src="res/screens/ios/screen-ipad-portrait-2x.png" width="1536" height="2048" />
    <splash src="res/screens/ios/screen-ipad-landscape.png" width="1024" height="768" />
    <splash src="res/screens/ios/screen-ipad-landscape-2x.png" width="2048" height="1536" />
    <splash src="res/screens/ios/screen-iphone-568h-2x.png" width="640" height="1136" />
    <splash src="res/screens/ios/screen-iphone-portrait-667h.png" width="750" height="1334" />
    <splash src="res/screens/ios/screen-iphone-portrait-736h.png" width="1242" height="2208" />
    <splash src="res/screens/ios/screen-iphone-landscape-736h.png" width="2208" height="1242" />
  </platform>
  <platform name="windows">
    <splash src="res/screens/windows/SplashScreen.scale-100.png" width="620" height="300" />
    <splash src="res/screens/windows/SplashScreen.scale-240.png" width="1152" height="1920" />
    <splash src="res/screens/windows/SplashScreenPhone.scale-240.png" width="1152" height="1920" />
  </platform>
  <platform name="wp8">
    <splash src="res/screens/wp8/SplashScreenImage.jpg" width="480" height="800" />
  </platform>
  <vs:plugin name="org.apache.cordova.camera" version="0.3.6" />
  <feature name="Camera">
    <param name="android-package" value="org.apache.cordova.CameraLauncher" />
  </feature>
</widget>

Solution

  • This does not reproduce on a simple project with Cordova 4.3.0 and VS 2015 RC when using an anchor or window.location.href to navigate to the second page. If you truly are navigating to another page, you'll need to ensure you add your cordova.js script reference on this second page. See below for the pages that illustrate this working.

    More than likely what is happening is one of the following things:

    1. "Single page app" JavaScript framework often add event listeners to elements like anchors so that when you click on them they load the page behind the scenes and fade it in using a transition effect. However, what it is doing is actually keeping you on the same page from a "window" perspective. The DOM is not unloaded and thus you don't need to reload all your JS code. Good frameworks will also update the navigation history so things like the back button work. I believe jQuery mobile is one of those frameworks. SPA frameworks like this generally provide some sort of API to do this behind the scenes transition from code. However, an explicit call window.location will go around your JavaScript framework and cause it to dump the entire contents of the DOM and you'll have to reload all of your JavaScript including cordova.js. (No different than if you go from http://www.microsoft.com to http://www.bing.com. It's a critical security feature.) To work around this, you shouldn't navigate using window.location and instead use the SPA framework's navigation APIs to do the page transition.
    2. If you are referencing cordova.js on the second page along with all of your JavaScript code, you could be hiting a JavaScript exception earlier in your code that is causing the load of cordova.js not to occur since execution stopped due to the exception.
    3. There is an issue we just identified with checking certain files into source control. We recently identified that checking in the android.json, windows.json, wp8.json, or remote_ios.json files in the plugins folder can cause a strange behavior where the build succeeds, but plugins do not function. The only indication that there is an issue is that you will see an exception in the output window. See Tools for Apache Cordova - Installed Plugins are skipped in build

    To see this working correctly. Add these files to a blank project and then add the camera plugin via the config designer to see things working from a second page. The camera will open up and you can take a picture. Clicking on the anchor or button will move to a second page and then do the same thing.

    www/index.html:

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <title>Second Page Test</title>   
        <link href="css/index.css" rel="stylesheet" />
    </head>
    <body>
        <a href="page2.html">Click to go to page 2 via anchor!</a>
        <button id="gotopage2">Click to go to page 2 via window.location.href</button>
        <!-- Cordova reference, this is added to your app when it's built. -->
        <script src="cordova.js"></script>
        <script src="scripts/platformOverrides.js"></script>
        <script src="scripts/index.js"></script>
        <img id="myImage" src="#"/>
    </body>
    </html>
    

    www/page2.html:

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <title>Second Page Test</title>
        <link href="css/index.css" rel="stylesheet" />
    </head>
    <body>
        <!-- Cordova reference, this is added to your app when it's built. -->
        <script src="cordova.js"></script>
        <script src="scripts/platformOverrides.js"></script>
        <script src="scripts/index.js"></script>
        <img id="myImage" src="#" />
    </body>
    </html>
    

    www/scripts/index.js:

    (function () {
        "use strict";
    
        document.addEventListener( 'deviceready', onDeviceReady.bind( this ), false );
    
        function onDeviceReady() {
            // Handle the Cordova pause and resume events
            document.addEventListener( 'pause', onPause.bind( this ), false );
            document.addEventListener( 'resume', onResume.bind( this ), false );
    
            var page2button = document.getElementById("gotopage2")
            if (page2button) {
                page2button.addEventListener("click", function () {
                    window.location.href = "page2.html";
                });
            }
            navigator.camera.getPicture(onSuccess, onFail, {
                quality: 50,
                destinationType: Camera.DestinationType.DATA_URL
            });
    
            function onSuccess(imageData) {
                var image = document.getElementById('myImage');
                image.src = "data:image/jpeg;base64," + imageData;
            }
    
            function onFail(message) {
                alert('Failed because: ' + message);
            }
        };
    
        function onPause() {
            // TODO: This application has been suspended. Save application state here.
        };
    
        function onResume() {
            // TODO: This application has been reactivated. Restore application state here.
        };
    } )();