Bookboard – A Model for the Future of the Web

One of the missions of PhoneGap has always been to bring about its own demise. The PhoneGap team wants the web to have all the functionality that we currently enjoy on devices so that there isn’t a need for PhoneGap to exist. It’s one of the reasons I love the project; I think that’s the perfect goal. I hope we get away from app stores and back to a world where the web rules all. There are obviously many things that have to happen before that becomes a reality but every once in a while I see a glimpse of what the web could be if that vision comes to pass. The latest iteration of that is Bookboard. (Sign up here)

Book Board Selection Screen

Bookboard is a web app that was written for the iPad. It makes heavy use of iOS specific features that allow websites to add themselves to the home screen for a full-screen experience and provide specific icons so that to the end user, it kind of feels like an app that just didn’t come from the app store. Even though it’s iOS-specific right now, the UI design is such that it would work across platforms. So many apps, even PhoneGap apps, follow the specific list->detail form that brings a lot of baggage with it. You have to work at making sure your list/detail view works the way it’s supposed to on iOS or Android (or other platforms). But Bookboard has a very design-heavy UI that lends itself to any platform. The books intuitively ask to be swiped from side to side as you are looking through them (with a nice parallax effect in the background) and when you go into the book itself you use the same swipe motions you would expect. It’s list-detail but brought to life in an interactive way.

Reading a Bookboard Book

Bookboard also makes use of hardware acceleration of CSS for many of its transitions. Everything on my iPad 2 is incredibly fluid and it’s difficult to tell that it’s *not* native. One of the things many app developers have to deal with is making their content feel native without it actually being native. The Bookboard approach seems ideal to me. They’re using a UI that hasn’t been replicated by every other app so they get a bit of leeway in terms of performance, but they don’t need it because they’re offloading to the device hardware via CSS. It works out very well.

Bookboard Menu Screen

Beyond the tech side Bookboard is simply a beautiful, well-designed app. It’s meant for children to read (and they can unlock achievements for reading more books) so it has to be intuitive, but it also looks great. Attention to detail like the parallax scrolling add a friendly touch and my daughter loved the app. She knows how to use an iPad and scrollable books, friendly gestures, and words that are magnified on touch all came naturally to her. If you’re a parent with a kid who enjoys reading (or you want them to enjoy it more) Bookboard is well worth a try.

This is how I want the mobile web to look and behave. Bookboard leverages the mobile-app centric parts of iOS to create an app-like experience while still retaining the unique, design-centric approach that has made the web so great. It’s this kind of custom UI and design influence that starts to make developers stop and think about whether they go native or web. A basic list-centric app isn’t that tough to do in native so more often than not, it probably makes sense just to go native. But the kind of custom UI and design that Bookboard uses lets you involve the designers much more deeply in the process. Since most designers (should) feel comfortable with CSS, they can jump in and contribute directly to the end result. If you can get performance like this using web technologies, I think it becomes a tougher sell to try and do native when web gets you more platforms, more reach, more designer input, and more deployment flexibility.

Hopefully in the next couple of years apps will have been replaced by experiences like Bookboard. It would make my home screen a much more interesting and beautiful place while giving developers ultimate flexibility. That’s a big win for the web. I encourage you to go sign up and see what I mean.

Using requestAnimationFrame to Optimize Dragging Events

requestAnimationFrame is an API that was originally created by Mozilla but has found its way into Chrome and I think it has huge, huge implications for user interface. A lot of the examples I’ve seen have talked about how you can use it to optimize animation, which makes sense. The basic premise of requestAnimationFrame is that instead of trying to move things out of sync with the browser, you get a hook directly into the browser refresh/redraw and on that refresh you can tell the browser to do something specific with requestAnimationFrame. So it’s sort of like a queuing system whereby you tell the browser what you want to happen, and when it goes through its next round of repaints, it executes what’s in that queue. This makes perfect sense for animation since the animation can’t move any faster than the repaint cycles of the browser. But it also turns out it makes great sense to use it for dragging events as well.

The Problem:

For me, the problem manifested when I was working on making the Brackets sidebar resizable. Like with a lot of resize events I was doing the resize on a mousemove event, so I’d track the mousemove and then resize the sidebar accordingly as the mouse moved. In theory it seemed like a decent way to do things until I opened it up in the Chrome Developer tools:

Holy repaint batman!

That screenshot may be a bit confusing, but basically, every mouse move there are a TON of “Recalculate Style/Paint” events happening. That means every single mousemove the browser is trying to repaint and so each mousemove event takes about 50 milliseconds to process. That may not sound like much, but think about how often mousemove fires. What’s worse is that because of how the browser redraws, even though it’s trying to redraw everything on mouse move, it can’t actually do that because the mousemove events are happening faster than the browser can redraw. Here’s the code that’s doing all that repainting:

    /**
     * @private
     * Sets sidebar width and resizes editor. Does not change internal sidebar open/closed state.
     * @param {number} width Optional width in pixels. If null or undefined, the default width is used.
     * @param {!boolean} updateMenu Updates "View" menu label to indicate current sidebar state.
     * @param {!boolean} displayTriangle Display selection marker triangle in the active view.
     */
    function _setWidth(width, updateMenu, displayTriangle) {
        // if we specify a width with the handler call, use that. Otherwise use
        // the greater of the current width or 200 (200 is the minimum width we'd snap back to)
 
        var prefs                   = PreferencesManager.getPreferenceStorage(PREFERENCES_CLIENT_ID, defaultPrefs),
            sidebarWidth            = Math.max(prefs.getValue("sidebarWidth"), 10);
 
        width = width || Math.max($sidebar.width(), sidebarWidth);
 
        if (typeof displayTriangle === "boolean") {
            var display = (displayTriangle) ? "block" : "none";
            $sidebar.find(".triangle-visible").css("display", display);
        }
 
        if (isSidebarClosed) {
            $sidebarResizer.css("left", 0);
        } else {
            $sidebar.width(width);
            $sidebarResizer.css("left", width - 1);
 
            // the following three lines help resize things when the sidebar shows
            $sidebar.find(".sidebar-selection").width(width);
            $projectFilesContainer.triggerHandler("scroll");
            $openFilesContainer.triggerHandler("scroll");
 
            if (width > 10) {
                prefs.setValue("sidebarWidth", width);
            }
        }
 
        if (updateMenu) {
            var text = (isSidebarClosed) ? "Show Sidebar" : "Hide Sidebar";
            $sidebarMenuText.first().text(text);
        }
 
        EditorManager.resizeEditor();
    }
 
    /**
     * @private
     * Install sidebar resize handling.
     */
    function _initSidebarResizer() {
        var $mainView               = $(".main-view"),
            $body                   = $(document.body),
            prefs                   = PreferencesManager.getPreferenceStorage(PREFERENCES_CLIENT_ID, defaultPrefs),
            sidebarWidth            = prefs.getValue("sidebarWidth"),
            startingSidebarPosition = sidebarWidth;
 
        $sidebarResizer.css("left", sidebarWidth - 1);
 
        if (prefs.getValue("sidebarClosed")) {
            toggleSidebar(sidebarWidth);
        } else {
            _setWidth(sidebarWidth, true, true);
        }
 
        $sidebarResizer.on("dblclick", function () {
            if ($sidebar.width() === 1) {
                // mousedown is fired first. Sidebar is already toggeled open to 1px.
                _setWidth(null, true, true);
            } else {
                toggleSidebar();
            }
        });
        $sidebarResizer.on("mousedown.sidebar", function (e) {
            var startX = e.clientX;
            $body.toggleClass("resizing");
            // check to see if we're currently in hidden mode
            if (isSidebarClosed) {
                toggleSidebar(1);
            }
 
            $mainView.on("mousemove.sidebar", function (e) {
                var doResize = true,
                    newWidth = Math.max(e.clientX, 0);
 
                // if we've gone below 10 pixels on a mouse move, and the
                // sidebar is shrinking, hide the sidebar automatically an
                // unbind the mouse event.
                if ((startX > 10) && (newWidth < 10)) {
                    toggleSidebar(startingSidebarPosition);
                    $mainView.off("mousemove.sidebar");
                    $body.toggleClass("resizing");
                    doResize = false;
                } else if (startX < 10) {
                    // reset startX if we're going from a snapped closed position to open
                    startX = startingSidebarPosition;
                }
 
                if (doResize) {
                    // if we've moving past 10 pixels, make the triangle visible again
                    // and register that the sidebar is no longer snapped closed.
                    var forceTriangle = null;
 
                    if (newWidth > 10) {
                        forceTriangle = true;
                    }
 
                    _setWidth(newWidth, false, forceTriangle);
                }
 
                if (newWidth === 0) {
                    $mainView.off("mousemove.sidebar");
                    $("body").toggleClass("resizing");
                }
 
                e.preventDefault();
            });
 
            $mainView.one("mouseup.sidebar", function (e) {
                $mainView.off("mousemove.sidebar");
                $body.toggleClass("resizing");
                startingSidebarPosition = $sidebar.width();
            });
 
            e.preventDefault();
        });
    }

The main issue is that there is a lot of jQuery selecting (and then setting) that’s going on. Look at the mousemove event handler and see how much is going on there. That’s kind of bad. It not only does some math, it also calls _setWidth(), which goes through and modifies significant parts of the DOM. It turns out that it’s pretty expensive to pull from and modify the DOM, and when it’s happening a lot every time the mouse moves, you’re going to get a significant bottleneck. If only there was a way to only do all of that getting and setting when the browser could handle it. That’s the beauty of requestAnimationFrame

Optimizing with requestAnimationFrame

requestAnimationFrame is a pretty straightforward API to use, but it took me a little bit to figure it out. Basically you call window.webkitRequestAnimationFrame() (that’s the webkit-specific prefix) and pass in a function that will be called every time the browser gets to the point where it can redraw the page. What’s a little tricky is that you have to tell the browser to keep listening for it because normally it just gets called once. So what I found easiest was to just call window.webkitRequestAnimationFrame() at the end of the function you’re passing into requestAnimationFrame. Here’s my rewritten example of the _initSidebarResizer():

    function _initSidebarResizer() {
        var $mainView               = $(".main-view"),
            $body                   = $(document.body),
            prefs                   = PreferencesManager.getPreferenceStorage(PREFERENCES_CLIENT_ID, defaultPrefs),
            sidebarWidth            = prefs.getValue("sidebarWidth"),
            startingSidebarPosition = sidebarWidth,
            animationRequest        = null,
            isMouseDown             = false;
 
        $sidebarResizer.css("left", sidebarWidth - 1);
 
        if (prefs.getValue("sidebarClosed")) {
            toggleSidebar(sidebarWidth);
        } else {
            _setWidth(sidebarWidth, true, true);
        }
 
        $sidebarResizer.on("dblclick", function () {
            if ($sidebar.width() === 1) {
                // mousedown is fired first. Sidebar is already toggeled open to 1px.
                _setWidth(null, true, true);
            } else {
                toggleSidebar();
            }
        });
        $sidebarResizer.on("mousedown.sidebar", function (e) {
            var startX = e.clientX,
                newWidth = Math.max(startX, 0),
                doResize = true;
 
            isMouseDown = true;
 
            // take away the shadows (for performance reasons during sidebarmovement)
            $sidebar.find(".scroller-shadow").css("display", "none");
 
            $body.toggleClass("resizing");
            // check to see if we're currently in hidden mode
            if (isSidebarClosed) {
                toggleSidebar(1);
            }
 
            animationRequest = window.webkitRequestAnimationFrame(function doRedraw() {
                // only run this if the mouse is down so we don't constantly loop even
                // after we're done resizing.
                if (isMouseDown) {
                    // if we've gone below 10 pixels on a mouse move, and the
                    // sidebar is shrinking, hide the sidebar automatically an
                    // unbind the mouse event.
                    if ((startX > 10) && (newWidth < 10)) {
                        toggleSidebar(startingSidebarPosition);
                        $mainView.off("mousemove.sidebar");
                        $body.toggleClass("resizing");
                        doResize = false;
                    } else if (startX < 10) {
                        // reset startX if we're going from a snapped closed position to open
                        startX = startingSidebarPosition;
                    }
 
                    if (doResize) {
                        // if we've moving past 10 pixels, make the triangle visible again
                        // and register that the sidebar is no longer snapped closed.
                        var forceTriangle = null;
 
                        if (newWidth > 10) {
                            forceTriangle = true;
                        }
                        // for right now, displayTriangle is always going to be false for _setWidth
                        // because we want to hide it when we move, and _setWidth only gets called
                        // on mousemove now.
                        _setWidth(newWidth, false, false);
                    }
 
                    if (newWidth === 0) {
                        $mainView.off("mousemove.sidebar");
                        $("body").toggleClass("resizing");
                    }
                    animationRequest = window.webkitRequestAnimationFrame(doRedraw);
                }
            });
 
            $mainView.on("mousemove.sidebar", function (e) {
                newWidth = Math.max(e.clientX, 0);
 
                e.preventDefault();
            });
 
            $mainView.one("mouseup.sidebar", function (e) {
                isMouseDown = false;
 
                // replace shadows and triangle
                $sidebar.find(".triangle-visible").css("display", "block");
                $sidebar.find(".scroller-shadow").css("display", "block");
 
                EditorManager.resizeEditor();
                $projectFilesContainer.triggerHandler("scroll");
                $openFilesContainer.triggerHandler("scroll");
                $mainView.off("mousemove.sidebar");
                $body.toggleClass("resizing");
                startingSidebarPosition = $sidebar.width();
            });
 
            e.preventDefault();
        });
    }

There are a few changes there but the biggest is that the mousemove event only sets the newWidth property based on the mouse event. Nothing else. That keeps mousemove, which happens a lot, nice and small. Everything else has been moved up to mousedown but wrapped in a function named doResize(), which is passed into window.webkitRequestAnimationFrame(). So instead of doing everything on mousemove I’m telling the browser that I only want to do the heavy lifting when the browser is going to do a repaint anyway. And because the newWidth is being updated by mousemove I’ve got the correct width updated and the browser will repaint the sidebar in the correct position.

One thing that confused me was how to stop requestAnimationFrame. There is a cancelRequestAnimationFrame but no one seems to use it because requestAnimationFrame() will run only one extra time when it’s called unless you tell it otherwise. Unfortunately, that’s exactly what this code does because we recursively call requestAnimationFrame() as soon as the function finishes so that if the page is still being dragged, we can get new values. But if we don’t find a way to stop it, the browser will constantly try to repaint even if there’s not a drag event going on. That’s why I use the isMouseDown boolean. If the mouse isn’t down, it’s a way to tell requestAnimationFrame() that I don’t want it to fire any more so it’s going to run once more, then stop.

The result is pretty significant:

Ahhh, much better

The mousemove event now takes 0ms, and since it happens often, that’s a fantastic improvement right off the bat. And now all of the layout/paint events are happening based on an Animation Frame Event instead of on mousemove. You can also see spots where mousemove fires a couple of times before the Animation Frame Event fires and in the old code that would have caused an unnecessary layout/repaint series because even though the browser was doing all of the leg-work on it, it wouldn’t even be able to render it until the next animation frame came up. This way we’re only having the browser do the work when it can.

This is more of a real-world scenario of why requestAnimationFrame can be helpful. Now that I’m digging into it more I’m looking at doing a couple of posts on the basics and starting from a smaller example with requestAnimationFrame but this example made me a believer in how cool it is so I wanted to share.

Huge thanks to Paul Irish for info on using requestAnimationFrame and chatting about it. Talking it through helped a ton to wrap my head around it.

Hoping for a Web App Future

If I think back to 2006/2007 I was very happy with how things were shaking out. The web was on the upswing and we were moving away from native applications. All of the great things about the web–its ubiquity, its freedom, its openness–were being harnessed to create native-like experiences that, I hoped at the time, would see us all do away with native apps. At the time, there wasn’t much (I thought) that web apps couldn’t do that we needed native apps for. How wrong I was. With the introduction of the iPhone, and subsequent smartphone releases, we’ve seen a huge shift back to native applications. Part of that is performance, right now native apps just feel better than mobile web apps, but it also came about because of just how many things native mobile apps could do. Geolocation, accelerometers, contact info–the smartphone showed how many things “apps” needed access to and for the most part those features have been exclusive to native applications. So just as the web was starting to really take off, we’ve slid back into native application territory.

It bummed me out, and still does. I thought AIR was an okay solution to the problem, but by the time AIR came around it was pretty clear that “the web” had come to mean HTML/JS, and I’m fine with that. So as PhoneGap started getting traction, and then Adobe took a major interest in the project, I was excited about the prospect of working on it as an Adobe evangelist, and more importantly, working with the teams behind it to see what else they had up their sleeves as the web moves forward.

But another side benefit is that it’s put me on what I’d consider the “right” side of the web argument. Two things got me thinking more about this. One was a very good post by a VC named Mark Suster, who while not telling his companies to focus exclusively on the web, has told them to make it a big part of what they do. So many startups nowadays are thinking completely mobile-first while ignoring the web, I’d say at their peril. When I think of my own usage, I’m still using a lot of native apps (for reasons I can’t quite figure out) but the ones I enjoy most have a web component that is well done and part of the overall experience. Yelp comes to mind. Another is Untappd. I can do almost anything I need to on the website version of Untappd so it’s not as though I’m getting a watered down experience. It more easily lets me move between contexts and devices while still using the service. I contrast that to something like Foursquare or Path. Both are mobile-centric, and with Foursquare I can’t check in because it wants to be sure that I am where I say I am (using GPS) but it still makes the web side of it less useful for me. Path is unusable when you’re not on a mobile device as far as I can tell. When I log in all I get is “download the app”. Which I hate. Mobile is clearly important but the web can’t be ignored.

Path Login

If this is what I see when I log into your site, you're too mobile-first.

The second thing that got me thinking more about this was something Brian Leroux pointed to on the Cordova mailing list, a post by Tim Berners-Lee about Installable Web Apps. This is a model I would love to see take hold. As Tim notes, there are a few things that users need to have when they’re installing web apps, and some trust/permissions issues that need to be figured out. Right now, I think PhoneGap is closest to this model, but a huge, huge, part of me wishes PhoneGap didn’t need to exist. If we could somehow skip the native shim and just take for granted that every platform supported, and at its core used, installable web apps. Maybe something like the WebOS model. But we’re not there yet. So for now, I’m glad I get to work with PhoneGap and build apps with web technologies. Eventually though I think PhoneGap can be used as inspiration for installable web apps. This is kind of how the standards world moves, as more and more people adopt something, people find ways to bring that something back into the standards. I think some of PhoneGap’s APIs and methodologies would make a great start at the idea of installable web apps. And I think the guys behind PhoneGap will be at the forefront of making those things happen, which means Adobe is going to be a really cool place to be over the next few years. It feels like there is a lot of potential to change the world and while I miss spending time with Flash, I feel like the HTML/JS/CSS work I’ve been doing and that Adobe is investing in, will make a similar impact on the web down the road.

Dealing with Binary Data from a Canvas Object using JavaScript TypedArrays

I’m not sure how helpful this will be for anyone, but during the process of doing a binary WebSocket demo I found myself learning a lot about JavaScript typed arrays and how that translates into binary data. The demo I wrote took Canvas image data and sent it over a binary WebSocket connection. The WebSocket server took that data and sent it out to all of the connected clients, who would then render the Canvas data as a PNG. It’s kind of a niche use case but I wanted to specifically create a binary WebSocket demo. It also was a more efficient way to send image data than doing something like base64 encoding it. First off, the Mozilla documentation was REALLY helpful. Major props to them.

Translating Canvas Data Into a Binary Format

Canvas has a getImageData() method that gives you an ImageData object. Within that ImageData object is a data property, which has the actual array data of the image. Normally I would have been able to stop right there because that would (in theory) have the information I needed. But what I had to get at was the ArrayBuffer. The way the spec has been implemented you can’t do anything with the ArrayBuffer. Instead you have to use an ArrayBufferView, which takes the form of TypedArrays in JavaScript. Luckily, to get the actual buffer you can just call the buffer property on any of the typed arrays and do what you want with it. But Canvas (at least in Chrome) is slightly different. The object you get from the ArrayBuffer of ImageData.data is something called CanvasPixelArray. Currently CanvasPixelArray doesn’t behave like a regular typed array, it looks like it will become a Uint8ClampedArray but the way it works in Chrome right now the CanvasPixelArray doesn’t provide access to a buffer property so you can’t send/access the ArrayBuffer data. Luckily getting that data into a Uint8Array, which you can get the buffer data from is pretty easy:

     imagedata = context.getImageData(0, 0, imagewidth,imageheight);
 
     var canvaspixelarray = imagedata.data;
 
 
     var canvaspixellen = canvaspixelarray.length;
     var bytearray = new Uint8Array(canvaspixellen);
 
     for (var i=0;i<canvaspixellen;++i) {
          bytearray[i] = canvaspixelarray[i];
     }

With that new Uint8Array all that we have to do is grab the buffer property and we can send it across the wire (beyond this post, but I’m planning on writing up the binary WebSocket info).

Reassembling the pieces

Now what I wanted to do was take that binary data from my Canvas and render it as a PNG file on the screen. The first step is to use a Canvas object to render out a PNG. But before that we have to get the data into a Canvas. In theory, you should be able to just do what happened above in reverse. But it’s not quite that simple. Once you get back the binary data from somewhere (WebSocket say), you’ve got an ArrayBuffer that you have to deal with. There is a putImageData() that takes an ImageData object, and we can create an ImageData object a few different ways, but you can’t set the data property of it. It’s read only. So we can’t take our data from the ArrayBuffer and just put it into our Canvas. We have to manually loop through the data property and line-by-line change the data.

          var bytearray = new Uint8Array(event.data);
 
 
          var tempcanvas = document.createElement('canvas');
               tempcanvas.height = imageheight;
               tempcanvas.width = imagewidth;
          var tempcontext = tempcanvas.getContext('2d');
 
          var imgdata = tempcontext.getImageData(0,0,imagewidth,imageheight);
 
          var imgdatalen = imgdata.data.length;
 
          for(var i=8;i<imgdatalen;i++)
          {
               imgdata.data[i] = bytearray[i];
          }
 
          tempcontext.putImageData(imgdata,0,0);

If the above isn’t clear, basically I’m just creating a new Uint8Array with the data from the server, then creating the temporary Canvas so I can get image data from it, and when I have that, I’m looping through the data property and inserting my own data from the Uint8Array.

Rendering it as a PNG

So now we have a Canvas (not being displayed) that has all of the data from our server, so it’s an exact graphical copy of the info we received. Turning that into a PNG is actually pretty easy because HTML is awesome. Canvas has a toDataURL() method that will take whatever is in the Canvas and create a string that can be put into the src property of an image. Then putting that image somewhere on the DOM will display the data as an image.

          var img = document.createElement('img');
               img.height = imageheight;
               img.width = imagewidth;
               img.src = tempcanvas.toDataURL();

Fin

Now that I’ve gotten my head around binary data a lot more, I’m kind of excited about JavaScript typed arrays. Looking through the list it looks like the typed arrays will help a lot with byte manipulation because of the different types. It also looks like they’re pretty fast (at least the fastest option at the time of that post).

HTML/JS/CSS and Tooling

I enjoyed this post by Grant Skinner that walks through his view of the evolution of technology and where/how/when tooling starts to come in. Adobe makes tools for web professionals. That’s what we’ve always done and that’s what we’ll do for a long time. You could even paint a broader brush that we make tools for creative people to share their creations. Watching our own evolution over the past year or so with regards to HTML tooling has been very interesting. We got some flack for not moving in earlier, but as Grant rightly points out, tools are a major investment and only once you have stability can you make that investment. It was never a matter of momentum around HTML or a focus on Flash, it was just the fact that things weren’t quite ready for tools.

In fact, I’d argue they still aren’t. But we’ve taken that as something that comes with the web. It’s always evolving, always moving, and while things will start to coalesce more and more, in the end, you have to get in and be ready to move. That’s kind of the approach we’ve taken with Adobe Edge. We just released Preview 4 of Edge which incorporates a lot of features that people have been asking for. Some of it I’m not even sure if it was on the original roadmap. But the Edge team made a conscious decision to be very agile, to build Edge in such a way that it could incorporate customer feedback quickly, and then getting product management on board to do lots of versions very quickly. I think it’s worked out very well and despite being on the earlier side of Grant’s curves, I think Edge will be a very helpful tool for a lot of people because of it.

Developer tools are a bit of a different story in terms of both ecosystem and readiness in my mind. There isn’t one, big, HTML IDE that people seem to like (akin to Flash Builder, Eclipse or Visual Studio). Instead people seem to be using a lot of different things and experimenting. What actually seems to be most popular right now are the basic text editors like TextMate or Sublime (my favorite). These seem to be focused on helping smart people work faster. Lots of shortcuts, lots of snippets, but not a lot in terms of helping along the learning process. And I think that’s just where we are now as far as HTML/JS/CSS tooling. But I’m excited to see that evolution as well and see what happens when frameworks get a bit more standardized and more general web developers start jumping into JS more and more. Will those people need a more robust HTML/CSS/JS editor that’s still developer centric? And I think the answer is yes, but I think it’s also tough to really see what that would look like until the JS/HTML/CSS stack is a bit more solid. But I’m excited to watch it and find out. And from what I’ve seen of Adobe’s HTML tooling side, we’re taking a good approach and I’m excited to see what people think as the PMs share more and more of it.

Flex Mobile European Tour 2011

Next week I’m going to be hitting the road with my colleague Mihai Corlan to spread the news about what Adobe has been up to the past few months. The primary reason for the trip is to show off the work the product teams have done with Flex on devices. Mihai and I are going to be doing some hands-on sessions showing just how easy it is to build great looking applications for iOS and Android. Bring your laptop, a copy of Flash Builder, and a device and we’ll walk you through all the steps you need to go through to start building and deploying mobile applications.

The other part of these events is providing some firsthand demos of some of the things we showed off at MAX. I think MAX was a major turning point for Adobe and Mihai and I will be showing off the touch tooling, talking about the creative cloud, and showing all of the things Adobe is up to in the world of HTML5. Plus we’ll give you some sneak peaks of the next generation of the Flash Platform. So there’s a ton of info and you’ll have the chance to ask questions firsthand. 2012 is going to be a great ride for the Adobe community so we want to make sure you have all the info you need to be successful.

Here are the cities we’re hitting:

Update: For those of you in the UK, there is an event on Monday, the 7th. I couldn’t make it out in time for that, but Mihai will be there covering everything.

November 9th

November 10th

November 11th

November 14th

November 15th

November 17th

November 19th

Can’t wait to see you and talk about application development and Adobe’s 2012.

Using Lawnchair for Data Storage in PhoneGap

I’ve been looking around for good ways to store information for a PhoneGap application I’m working on. I think that the PhoneGap SQL APIs may still be the best bet but I also wanted to check out some of the more unique storage options. One of the options I came across was Lawnchair, by Brian Leroux. One of the beautiful things about Lawnchair is that it’s incredibly simple. It does persistent JSON storage, has adapters for different storage options (localstorage, webSQL, etc) and is very lightweight. One of the not-fun things about Lawnchair is that it’s incredibly simple. While I found it easy to use (mostly) there were some areas that didn’t work. I think part of the issue is that I don’t have a lot of experience with NoSQL solutions like CouchDB (where Lawnchair draws a lot of its inspiration) so I think a lot of the issue lies with me.

That said, I found it to be a powerful, if basic way to store data and I think it’s worth knowing about. This isn’t going to be helpful for people who know JavaScript pretty well or who have used Lawnchair a lot, but I figured jotting downs some of my thoughts and the basics of it would be helpful for my learning process. So here it is.

Quick Info Up Front

One of the first things I ran into was that Lawnchair will behave slightly differently depending on the adapter you end up using. For instance, I started out using the default ‘dom’ adapter, which just ties into the localStorage API and uses key/value pairs. So when you call the save() method, you pass in a key with values. But upon switching to the webkit-sqlite adapter, which relies on databases, I found that the old key/value pair code didn’t seem to work because of the way that the webkit-sqlite adapter assigns ids. The key is still stored, but it doesn’t seem to work as a selector. I think this might have something to do with the fact that I’m using ints as my key and not strings, but haven’t done enough research.

This post will use the webkit-sqlite adapter so keep that in mind as you’re looking at the code.

Getting Started

Getting started with Lawnchair is a beautiful thing. Simply drop the Lawnchair.js file and whatever adapter you want to use, then just start using the Lawnchair class. I found it’s helpful to assign it to a variable so that you can access it easily from other functions. It also requires a callback function. Normally, when you set the callback, you can use the this scope to perform operations on the database, but in my example I’m just tracing out that we connected.

     var beers = Lawnchair({name:'beers'},function(e){
          console.log('storage open');
     });

So beers is now the reference to my data storage and I can call the Lawnchair APIs using that variable.

Saving Data

While at first I was a little bit confused about how to treat Lawnchair as a way to store more complex data types, storing objects is incredibly easy. Just wrap whatever you want in a JavaScript object and then call the save method.

          var obj = {beername:"Wet Hop",brewername:"Deschuttes",brewerlocation:"Bend, OR"
                         ,beerstyle:"IPA",quantity:1,purchasedate:"12/11/2011",price:"9.00"
                         ,cellardate:"9/11/2011",cellartemp:40,brewdate:"8/10/2011"};
          beers.save({key:"1",value:obj});

In this case, I assigned a key of “1″, which makes sure it’s a string. If you just use integers, it ends up translating to the database as 1.0. This actually becomes kind of a pain later on as you’ll see, so it’s important to keep it in mind. If you use a string as your key, it will just make the ID that string, so it’s a little bit simpler to use. Below is what the data storage looks like when you don’t use a string.

By using strings I found it makes the retrieve operations a lot more simple. In fact, I couldn’t figure out how to use the get operation using the webkit-sqlite adapter and a number for the key. Getting 1 didn’t work and getting 1.0 didn’t work, so I just ended up using a string. I think that’s more a limitation of my JS knowledge than anything, but it was something that I struggled with.

One cool feature of modifying data in Lawnchair is that when you save() something, if you pass in a key that already exists, it will just overwrite that existing key. No having to worry about update versus insert, Lawnchair just figures it out and saves the data correctly.

Getting Data

There are a couple of good ways to get data; the first is the get() method. Pretty straightforward: You pass in a key, and it will return the value associated with that key.

             beers.get("1",function(obj){
                  console.log(obj);
             });

The object that comes back includes a key property and a value property, which is where the data is stored. So in my example object above, I can reference obj.value.beername to get the name of the beer.

Another helpful retrieve method is the all() method, which just dumps every object from the storage area in an array. Every function in Lawnchair can use a callback function and in this one it’s especially helpful. In my app I use the all() method to loop through the beers and display them in a list.

     beers.all(function(arrBeers){
          for(var i = 0; i<arrBeers.length;i++)
          {
               console.log(arrBeers.length);
               var listdiv = document.createElement('li');
                 listdiv.setAttribute('id','listdiv');
                 listdiv.innerHTML = arrBeers[i].value.beername;              
               $('#beer_list').append(listdiv);    
          }
          $('#beer_list').listview("refresh");
     });

Modifying Data

As I mentioned above, the way Lawnchair works is that when you save an object with the same key, it just overwrites it. So the key to modifying data is just grabbing it using the get() method, adjusting the data on the object, and calling save() with the same key.

          beers.get("1",function(thisobj){
               console.log(thisobj);
               var obj = {};
                    obj = thisobj.value;
                    obj.beername = "Not Wet Hop";
               beers.save({key:thisobj.key,value:obj});
          });

I found it kind of helpful to use the existing key on my retrieved object to save since that’s a good way to make sure you’re saving the object you want and not overwriting another one or creating a new one. Key management becomes kind of important with Lawnchair but it’s not tough to do.

Beyond

One of the things I found helpful after digging in was checking out the Lawnchair plugins. The couple that I downloaded were the query plugin and the callback plugin. The query plugin just lets you use some basic query syntax to pull out information. The callback plugin lets you specify callbacks for before and after events so you can make your code do specific things based on when you’re accessing the data.

It’s also really important to take a look at the adapters because that’s critical to making sure the application will work across devices. In general the adapters should be hot-swappable in that you should be able to just move adapters in and out without having to change the code. I had some trouble getting the latest DOM adapter to work, so I can’t totally verify that, but as I understand it that’s the way it’s supposed to work.

Conclusion

After messing with it, I think Lawnchair is one of the most straightforward ways to store data for a basic PhoneGap application. It’s not going to hold up to all of the use cases that a complex application needs, but for something where you’re storing some simple data, Lawnchair is perfect. It leverages existing web storage options and using adapters you can make sure it’s cross platform.

Full Code

I posted the full code snippet here. Keep in mind I’m a JavaScript newbie so if you see things I’m doing wrong I’d be grateful to hear about them.

MAX Reflections

I’m sitting down with some tea while my little girl is taking a nap feeling the big exhale from MAX. The energy of the past few days has been largely fantastic and I always find MAX to be rejuvenating both from a professional standpoint and a personal standpoint. Getting to connect with the community and my colleagues at Adobe has been great. In the contrast to the buzzing of MAX, the current deep quiet of my house leaves me reflecting a bit on the week.

This will go down as a very transformational MAX. The announcement of the Creative Cloud and the fact that it will include all of Creative Suite Master Collection as well as the touch tools and services (including TypeKit) is one of the biggest things I’ve seen from Adobe in a long time. And it feels like we’re jumping in with both feet and getting back to the core of what Adobe does: empowering designers to create with great tools. I thought the news about the single edition of the Digital Publishing suite was a perfect example of that. It makes the blossoming world of digital publishing accessible to more people.

The PhoneGap announcement was, for me, the most significant announcement of the week. By acquiring Nitobi (fantastic guys) and contributing the PhoneGap project to the Apache Foundation, Adobe took a huge, huge step into the world of HTML5. It was a perfect way to start a day 2 keynote that focused on the things Adobe is doing to be a part of the HTML5 ecosystem.

Based on the Twitter stream there seemed to be a feeling that the lack of traditional Flash indicated that Adobe is giving up on it. I think that misses the big picture. With the Nitobi acquisition and the embracing of PhoneGap, Adobe is making a significant and meaningful bet on the web and cross-platform mobile applications. This can’t be overstated. For Flash developers we have AIR, which will let you build cross-platform mobile applications. For HTML developers we have PhoneGap, which will let you create cross-platform mobile applications. Both are web technologies that don’t require developers to be locked into a specific operating system or type of device. You see the same thing with our digital publishing suite; it doesn’t matter if you want to deploy on iOS, Android, or PlayBook, you can. And that’s possible largely because of the web formats that go into creating the DPS apps.

This isn’t about Flash versus HTML, this is about supporting creative and interactive content across the broadest platform in the world: the web. Whether it’s mobile apps or browser content; animations, interactive web applications, or 3D gaming experiences, Adobe genuinely believes that the web is the best way for our customers to deliver their creations. By making PhoneGap a cornerstone of our story, I think we’ve proven our commitment to that mission.

I’m glad I was at MAX to see all of this in person.

Edit: This is a great piece by Daryl Taft of eWeek that talks about Flash and HTML. And it’s great to see that the “and not or” message is getting picked up. But what I like about this particular message is that when you follow it upstream a bit more, it just means we love the web. And if that’s the case (and I feel like it is) then the technology becomes secondary to the goals of helping people create cross-os and cross-device content with web technologies.

Slides and Code from 360Flex Denver

360Flex Denver360|Flex Denver was an absolutely fantastic event. I was at the first 360 and have watched the event grow and mature over the years. It’s always been the best Flex conference out there, but there was something about those first few 360′s that had so much energy and got the community excited. This event felt like that. I’d rank it as one of the best 360′s I’ve ever been to. The attendees were enthusiastic and great to talk to, the keynotes were awesome and inspiring, and John and Nicole were at the top of their game as far as hosting went. It might have been all of the mobile stuff, it might have been the announcement on day 2 of Project Spoon, but this 360Flex was just all around great.

So of course, I made a bit of a misstep with my talk. At what was one of the greatest Flex conferences of the past 5 years, I go and give an HTML5/jQuery talk. But hopefully it was still fun. And to be honest, as I dove into HTML5 and jQuery for this talk, I learned that we have it very, very good as Flex developers. There are a lot of great things about HTML5 and especially jQuery, but if you’re building complex RIAs and applications, Flex has solved a ton of problems that the HTML5/JS world hasn’t yet. I definitely think they will, and the community around JS and jQuery is great, but as a Flex developer, you’ve got the best of both worlds and you’re going to be able to jump over to jQuery/HTML5 projects and bring some very valuable knowledge about building complex apps.

Thanks to everyone who attended the talk. My slides can be found here (and embedded below) and you can grab the demos from my GitHub repository here.

WebSocket Charting Demo with HTML5 and JavaScript

One of the PHP demos that goes over pretty well is my Flex socket demo. It used to be that Flex/Flash was really the only way to take advantage of WebSockets but with browser vendors implementing bleeding edge support for HTML5, developers can now use the WebSocket API in JavaScript. I wanted to combine that with the Canvas API and try to recreate a basic example of my Flex socket demo in HTML5/JavaScript. It mostly works (demo embedded at the bottom).

Code

  • socket.html – HTML/JS file for connecting to the socket server
  • socket.php – PHP file that creates the socket server

Who Can Run This

It’s been kind of cool to see the browser vendors really move forward to implement support for various bits of HTML5 and CSS3 without a defined spec. That can be problematic because the spec is still changing, causing code to break in new versions of browsers, but in general it means that web developers can live on the cutting edge for most browsers. Currently, the WebSocket API (and this example) should work in the latest version of Chrome and the Firefox 4 beta.

Differences in WebSockets with Flash and HTML5

Overall the WebSocket API is pretty easy to use. I took my existing PHP socket server code that works in Flash and tried to use it for the HTML5 version but all I was getting was the “close” event. The problem was that I wasn’t thinking about the handshake. Flash uses a policy file to determine whether or not it can connect, but the WebSocket API uses a handshake. I was able to grab some example code, change my PHP server to add the handshake then everything basically worked. The only other small change I had to make was to add a character (chr 0 and chr 255) to either end of the message I was sending through the socket_write method. I’m still not entirely sure why this is, but my onmessage event wouldn’t fire until I added those.

Charting with Canvas

I’m very excited about the Canvas API in HTML5. If you’re a Flash developer who has been working with the Flash drawing API, you’re going to be able to do some very cool stuff. The APIs are fairly similar with some syntax changes that you have to be aware of. The biggest pain I found was that once something is on the canvas, it’s on the canvas. You can’t reference specific drawn elements like you can in ActionScript. That makes doing things like the hover effect on specific charting points basically impossible.

Partly because of that, and because I’m still getting used to JavaScript I went with a very scaled down version of the chart. All this chart does is draw some grid lines and then plot the points as it gets them from the socket server. When you get to the end, the points are drawn off the page. I also couldn’t really figure out how to add to a path in a function call so I just went with the points instead of making it an actual line graph. I’m fairly sure that most of these are due to my JavaScript incompetence and not a limitation of Canvas.

Conclusion

It was both a lot of fun and very painful to go back to JavaScript. It’s an incredibly powerful language but if you come from Flash, you’ll find yourself banging your head against the wall because of subtle differences. The one thing that keeps tripping me up is figuring out how the DOM works compared to Flash. Another sticking point is the tooling. We have very good tooling on the Flash side compared to JavaScript. Chrome’s developer tools and Firebug both help a lot, but there’s no tool out there that provides code completion for the Canvas drawing API. When you don’t know the API that well, it means a lot of Googling, and a lot of the examples are pretty basic. I think a tool with basic code completion for Canvas would make it a lot easier to start creating complex content in.

It’s definitely rough to go from Flash to JavaScript but hopefully I’ll be playing around with the new stuff in HTML5 more, especially as Dreamweaver gets more support baked in.