Archives

Archives / 2014
  • Cordova – file-transfer, unzip and present adventures

    For a hybrid project I have a very simple requirement: download zip file, unzip it, serve content on a page from artifacts (html text, images).

    I started with Intel XDK, an environment I did some other work with. A great integrated development environment, but some of its big problems: lagging behind in version of Cordova, fixed set of selected plugins, no possibility to include other plugins. Because I wanted to unzip, I tried it with a Javascript only solution zip.js. The problem I had was that it worked if I downloaded the zip file over http using importHttpContent(), but I could’t get it working with loading from the local file system. Another problem I had was with where files ended up on different devices: in / on wp8, on /storage/sdcard0 on Android, and on iOS in even another location.

    I decided to go with plain Cordova, with the command-line commands. That was a good decision. It was now possible to use the latest version (3.4 at time of writing), and use the new file and file-transfer plugins that now support a huge improvement to hybrid development: the cdvfile protocol. Files can now be addressed with respect to one multi-platform root: cdvfile://, rather than through device-specific paths. There is also a great plugin available to unzip that understands the new cdvfile:// notation as well. I ended up with a simple example to showcase the download, unzip and present case. To get it working execute the following commands with a Cordova 3.4 installation:

    1. cordova create Cordova-TransferUnzipPresent com.svdoever.tranferunzippresent TransferUnzipPresent
    2. cd Cordova-TransferUnzipPresent
    3. cordova platform add android
    4. cordova plugin add org.apache.cordova.file
    5. cordova plugin add org.apache.cordova.file-transfer
    6. cordova plugin add org.chromium.zip
    7. Replace code in www\index.html with the code below
    8. cordova emulate android

    Works with iOS platform as well. The zip plugin does not work on wp8 (yet).

    The code:

    <!-- Code by Serge van den Oever, http://weblogs.asp.net/soever -->
    <!-- Using Cordova 3.4 with the following plugins: -->
    <!-- 'org.apache.cordova.file' (version 1.0.1), 'org.apache.cordova.file-transfer' (0.4.2), 'org.chromium.zip' (2.0.0)  -->
    <!DOCTYPE html>
    <html>
    <head>
        <title>Cordova Download Unzip Display Sample</title>
        <meta http-equiv="Content-type" content="text/html; charset=utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=0">
        <script src="cordova.js"></script>
    </head>
    
    <body>
    <button id="btnLoad">Load</button>
    <button id="btnUnzip">Unzip</button>
    <hr/>
    <div id="statusPlace"></div>
    <hr/>
    <img id="imgPlace" src="http://lorempixel.com/320/200">
    <br/>
    <div id="txtPlace">TEXT COMES HERE</div>
    
    <script type="application/javascript">
        document.addEventListener("deviceready", onDeviceReady, false);
    
        function registerHandlers() {
            document.getElementById("btnLoad").onclick = function() {
                var that = this,
                        App = new DownloadApp(),
                        fileName = "ft-p.zip",
                        uri = encodeURI("https://dl.dropboxusercontent.com/u/7197720/ftpack.zip"),
                        folderName = "content";
                console.log("load button clicked");
                document.getElementById("statusPlace").innerHTML += "<br/>Loading: " + uri;
                App.load(uri, folderName, fileName,
                        /*progress*/function(percentage) { document.getElementById("statusPlace").innerHTML += "<br/>" + percentage + "%"; },
                        /*success*/function(entry) { document.getElementById("statusPlace").innerHTML += "<br/>Zip saved to: " + entry.toURL(); },
                        /*fail*/function() { document.getElementById("statusPlace").innerHTML += "<br/>Failed load zip: " + that.uri; }
                );
            };
            document.getElementById("btnUnzip").onclick = function() {
                var that = this,
                        App = new DownloadApp(),
                        fileName = "ft-p.zip",
                        folderName = "content";
                console.log("zip button clicked");
                App.unzip(folderName, fileName,
                        /*success*/function() { alert("Unzipped and assigned"); },
                        /*fail*/function(error) { alert("Unzip failed: " + error.code); }
                );
            };
        }
    
        function onDeviceReady() {
            // navigator.splashscreen.hide();
            document.getElementById("statusPlace").innerHTML += "<br/>deviceready event received";
            registerHandlers();
        }
    
        var DownloadApp = function() {
        }
    
        DownloadApp.prototype = {
            load: function(uri, folderName, fileName, progress, success, fail) {
                var that = this;
                that.progress = progress;
                that.success = success;
                that.fail = fail;
                filePath = "";
    
                that.getFilesystem(
                        function(fileSystem) {
                            console.log("GotFS");
                            that.getFolder(fileSystem, folderName, function(folder) {
                                filePath = folder.toURL() + "/" + fileName;
                                that.transferFile(uri, filePath, progress, success, fail);
                            }, function(error) {
                                console.log("Failed to get folder: " + error.code);
                                typeof that.fail === 'function' && that.fail(error);
                            });
                        },
                        function(error) {
                            console.log("Failed to get filesystem: " + error.code);
                            typeof that.fail === 'function' && that.fail(error);
                        }
                );
            },
    
            getFilesystem:function (success, fail) {
                window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
                window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, success, fail);
            },
    
            getFolder: function (fileSystem, folderName, success, fail) {
                fileSystem.root.getDirectory(folderName, {create: true, exclusive: false}, success, fail)
            },
    
            transferFile: function (uri, filePath, progress, success, fail) {
                var that = this;
                that.progress = progress;
                that.success = success;
                that.fail = fail;
    
                var transfer = new FileTransfer();
                transfer.onprogress = function(progressEvent) {
                    if (progressEvent.lengthComputable) {
                        var perc = Math.floor(progressEvent.loaded / progressEvent.total * 100);
                        typeof that.progress === 'function' && that.progress(perc); // progression on scale 0..100 (percentage) as number
                    } else {
                    }
                };
    
                transfer.download(
                        uri,
                        filePath,
                        function(entry) {
                            console.log("File saved to: " + entry.toURL());
                            typeof that.success === 'function' && that.success(entry);
                        },
                        function(error) {
                            console.log("An error has occurred: Code = " + error.code);
                            console.log("download error source " + error.source);
                            console.log("download error target " + error.target);
                            console.log("download error code " + error.code);
                            typeof that.fail === 'function' && that.fail(error);
                        }
                );
            },
    
            unzip: function(folderName, fileName, success, fail) {
                var that = this;
                that.success = success;
                that.fail = fail;
    
                zip.unzip("cdvfile://localhost/persistent/" + folderName + "/" + fileName,
                          "cdvfile://localhost/persistent/" + folderName,
                        function(code) {
                            console.log("result: " + code);
                            that.getFilesystem(
                                    function(fileSystem) {
                                        console.log("gotFS");
                                        that.getFolder(fileSystem, folderName + "/ftpack", function (folder) {
                                            document.getElementById("imgPlace").src = folder.nativeURL + "/img.jpg";
                                            folder.getFile("text.html", {create: false}, function (fileEntry) {
                                                fileEntry.file(function(file) {
                                                    var reader = new FileReader();
                                                    reader.onloadend = function (evt) {
                                                        console.log("Read as text");
                                                        console.log(evt.target.result);
                                                        document.getElementById("txtPlace").innerHTML = evt.target.result;
                                                        typeof that.success === ' function && that.success();'
                                                    };
                                                    reader.readAsText(file);
                                                }, function(error) {
                                                    console.log("Failed to get file");
                                                    typeof that.fail === 'function' && that.fail(error);
                                                });
                                            }, function (error) {
                                                console.log("failed to get file: " + error.code);
                                                typeof that.fail === 'function' && that.fail(error);
                                            });
                                        }, function (error) {
                                            console.log("failed to get folder: " + error.code);
                                            typeof that.fail === 'function' && that.fail(error);
                                        });
                                    }, function(error) {
                                        console.log("failed to get filesystem: " + error.code);
                                        typeof that.fail === 'function' && that.fail(error);
                                    });
                        }
                );
            }
        }
    </script>
    </body>
    </html>
    
  • AngularJS–don’t use self-closing div tags

    I had a piece of code that gave strange results in AngularJS. The issue was that I used a self closing <div /> tag instead of <div>…</div>. Self closing div tags are not supported in HTML5. The complete code can be found at http://stackoverflow.com/questions/21552560/angularjs-bug-in-ng-include-when-not-using-jquery and the plunker http://plnkr.co/edit/O3NSb2VEwAEDrkmtoKZ6?p=preview.

    My wrong code was written as:

    <script id="paragraphTmpl.html" type="text/ng-template">
        <h4>{{paragraph.Title}}</h4>
        <!-- comment line below to have the paragraphs render correctly --> 
        <div ng-bind-html="trustAsHtml(paragraph.Content)"/>
        <ng-include ng-repeat="paragraph in paragraph.Paragraphs" 
                    src="'paragraphTmpl.html'">
    </script>

    <div> <h3>{{chaptercontent.Title}}</h3>

    &lt;div ng-bind-html=&quot;trustAsHtml(chaptercontent.Content)&quot;/&gt;
    &lt;ng-include ng-repeat=&quot;paragraph in chaptercontent.Paragraphs&quot; 
                src=&quot;'paragraphTmpl.html'&quot;/&gt;
    

    </div>

    It works fine when jQuery is included, but not when you use the AngularJS JQLite implementation.
    When <div ng-bind-html="trustAsHtml(chaptercontent.Content)"></div> is used, the code works correctly.
  • Webstorm – great IDE for web development

    I’m really interested in front-end web development, and although Visual Studio 2013 is the main IDE in our company I’m really impressed by Webstorm from JetBrains, the guys behind Resharper. It is a full featured web development IDE for under $50. Although I’m impressed by the better support of Visual Studio 2013 for web development, Webstorm feels like “exactly made” for web development.The reason I favor Webstorm right now is that Visual Studio is pushing you too much into the Microsoft way, with server side code, web.config files etc. I want to build single page apps, front-end (JavaScript and Typescript) code only, talking to services, that can be hosted on any web server, or even drop box, and can also be embedded into a Phonegap mobile application if needed.

    And the great thing: it works on OSX and Windows, I have my code on dropbox and can hop from mac to windows without changing a thing.

    Check out http://www.jetbrains.com/webstorm/

    Note: I’m in no way affiliated with, or sponsered by, JetBrains, just a happy and impressed user.