Angular Material Nested Dialogs, part 1

I’ve been working with Angular Material for an application at work, and one of the big things I’ve been lamenting is the lack of a dialog stack for $mdDialog. In this series, I’m going to walk through the design process I’m using for coming up with such a structure, building it, and testing it.

Problem Statement

While popup dialogs are arguably a terrible interface design component, they do have their purposes, such as quick editing of medium-sized entities that are too small to navigate away from the page for, or interactive prompts that intentionally interrupt the user (think “Are you sure you want to perform this potentially harmful action?”).

The problem here is that Angular Material’s built-in dialog popup system, $mdDialog, doesn’t support multiple dialogs at the same time. It hasn’t since the linked issue was opened in 2014, and the issue remains open and in the Angular Material team’s backlog.

After looking through the documentation of the dialog system, it occurred to me that one should be able to use a combination of existing features to construct a dialog stack fairly easily; namely, the option to pass in a scope with a controller instead of a controller declaration (added from this issue), and the option to preserve a custom scope after the dialog is closed (discussed here). With these two factors combined, we can establish our own scope based on a given parent scope, create a controller on the scope the first time we open a dialog, and reuse the same scope across multiple instances of the dialog.

Initial Design

Okay, so since we’re building a stack, the first thing we need is a service which provides this functionality, and has a backing array. I’ll be doing my design in my own flavor of pseudocode, which borrows from Ruby, JS, and Python.

DialogStackService
    constructor($mdDialog):
        this.$mdDialog = $mdDialog
        this.stack = []

We now have a basic Dialog Stack service which creates a stack in its constructor. Now, what we want eventually is to model our call after the existing $mdDialog.show() call, so we can minimize the amount of changes we’ll have to make to our existing dialog invocations. As such, we’ll add a show() method:

DialogStackService
    constructor($mdDialog):
        this.$mdDialog = $mdDialog
        this.stack = []

    show(args):
        // TODO!

The goal of the show() method is to return a Promise which represents the outcome of that dialog, and not the resolution of the dialog itself. See, when $mdDialog opens a new dialog, it cancels the existing one by default, rejecting the Promise returned from that invocation of $mdDialog.show(); this isn’t what we want at all. What we want is for our dialogs to recognize when another dialog is added, and we want to keep the scope alive and on the stack so we can recreate the dialog later when it gets brought back up to the top, even though the actual dialog has been cancelled.

Okay. We know that we want to end up in a promise that will represent the dialog’s intended outcome, and not just the lifespan of the dialog window. So, let’s create our own promise. For this, I’m planning on using a Deferred object through the $q library provided by Angular, and just exposing the promise it generates. (Yeah, it’s ugly, but it works.) I’m also modifying the signature of both the constructor and show() so that we can pass in a parent scope as needed.

DialogStackService
    constructor($mdDialog, $q, $scope):
        this.$mdDialog = $mdDialog
        this.$q = $q
        this.$scope = $scope
        this.stack = []

    show(args, $parentScope):
        defer = this.$q.defer()
        scope = ($parentScope || this.$scope).$new()
        this.open(scope, defer, args)
        return defer.promise

For now, I’ve left the details of opening a dialog a bit hazy by pushing them off into a separate method. Let’s work on that now. We know we need to call $mdDialog.show() in here somewhere, and for the first invocation of a dialog, we’ll need to pass in a controller. We also need to ensure the scope stays alive if this controller ever is overwritten by another dialog; we can do this by telling the current dialog how it’s being closed when our system opens a new dialog. In addition to this, we need to make sure that we know how to tear down a dialog when another needs to take its place. Let’s take care of this right now.

DialogStackService
    constructor($mdDialog, $q, $scope):
        // Previous stuff
        this.OVERWRITTEN = "OVERWRITTEN";

    show(args, $parentScope):
        defer = this.$q.defer()
        scope = ($parentScope || this.$scope).$new()
        this.$mdDialog.cancel(this.OVERWRITTEN)
        this.open(scope, defer, args)
        return defer.promise

    open(scope, defer, args):
        this.$mdDialog.show({
            // Preserve what can be preserved
            templateUrl: args.templateUrl,
            clickOutsideToClose: args.clickOutsideToClose,
            escapeToClose: args.escapeToClose,
            // Write the stuff we need for creating the controller
            controller: args.controller,
            controllerAs: args.controllerAs,
            locals: args.locals,
            bindToController: args.bindToController,
            // Overwrite the scope
            scope: scope,
            preserveScope: true,
        }).then( (value) => {
            defer = this.close()
            defer.resolve(value)
        }).catch( (reason) => {
            if (reason !== this.OVERWRITTEN):
                defer = this.close()
                defer.reject(reason)
        });
        this.stack.push({scope, defer, args})

    close():
        {scope, defer, args} = this.stack.pop()
        scope.$destroy()
        if !this.stack.empty():
            {prevScope, prevDefer, prevArgs} = this.stack.peek()
            this.reopen(prevScope, prevDefer, prevArgs)
        return defer

Now, we have to implement a reopen() method… or do we? It does the same thing that open does, just… slightly differently. For one, we don’t want to pass a controller argument to $mdDialog.show(), since we already have a scope with a controller on it, and we don’t want to push the data onto the stack, since it’s already there. So, let’s modify our code a bit:

DialogStackService
    constructor($mdDialog, $q, $scope):
        // ...

    show(args, $parentScope):
        defer = this.$q.defer()
        scope = ($parentScope || this.$scope).$new()
        this.$mdDialog.cancel(this.OVERWRITTEN)
        this.open(scope, defer, args, true)
        return defer.promise

    open(scope, defer, args, createNew):
        this.$mdDialog.show({
            // Preserve what can be preserved
            templateUrl: args.templateUrl,
            clickOutsideToClose: args.clickOutsideToClose,
            escapeToClose: args.escapeToClose,
            // Write the stuff we need for creating the controller (if needed!)
            controller: (createNew ? args.controller : undefined),
            controllerAs: (createNew ? args.controllerAs : undefined),
            locals: (createNew ? args.locals : undefined),
            bindToController: (createNew ? args.bindToController : undefined),
            // Overwrite their scope
            scope: scope,
            preserveScope: true,
        }).then( (value) => {
            defer = this.close()
            defer.resolve(value)
        }).catch( (reason) => {
            if (reason !== this.OVERWRITTEN):
                defer = this.close()
                defer.reject(reason)
        });
        if createNew:
            this.stack.push({scope, defer, args})

    close():
        {scope, defer, args} = this.stack.pop()
        scope.$destroy()
        if !this.stack.empty():
            {prevScope, prevDefer, prevArgs} = this.stack.peek()
            this.open(prevScope, prevDefer, prevArgs, false)
        return defer

Now that we’ve done this, we know we can handle the core of the functionality we need for a dialog stack. This does have some caveats:

  • Dialog controllers won’t be reinitialized when a dialog is resumed; this could break dialogs which do their own polling for data or which need to be refreshed upon reload.
  • We can’t pass in our own scope to set up the dialog on the first go; in all of the dialog controllers I’ve seen, they don’t pass in an existing scope, so I’m assuming that this is okay. The code can be modified to preserve this or expose the passed-in scope as needed, but for now, I’ve left it out.
  • Dialogs which store state outside of their scope or their controller might not work properly; since I’m rebinding the whole thing, regenerating the template, and so forth upon dialog resumption, any presentation details which aren’t capable of being rebuilt from the scope and controller state alone will likely break.
  • Locals do not get passed back in when resuming a dialog; it’s the responsibility of the dialog controller to ensure that these are handled properly when the controller is first initialized.
  • Doing an Angular route transition or Angular UI-Router state transition may not clear the existing dialog stack; this should be checked for manually on route change or state change. A method for clearing the stack may be added and invoked from that point, which would look something like this:
clearStack():
    while !this.stack.empty():
        this.$mdDialog.cancel()

Moving Forward

Now that we have some pseudocode, and we’ve identified some key aspects of the behavior of our stack, we can start working on implementation and testing. We’ll be looking at this in the next blog post in the series. Stay tuned!

Breakout, Part 2

Ladies and gentlemen, we have a game.

…if you can call it that.

badgame.gif

For my first real stretch of development, my focus was on setting up a workflow and making sure everything was installed and set up, and get a barebones version of Breakout up and running.

As you can see, it’s pretty… um… well, yeah. But it’s a game!

In Breakout, Part 1, I mentioned that I’d be following the Noobtuts tutorial for making a 2D Arkanoid clone; that’s exactly what I did. Instead of using the supplied sprites, I decided to make my own. This was probably a terrible idea.

The goal, however, was to get my basic workflow up and running, and familiarize myself with the basics of Unity 2D. The tools I’m using for this are:

  • Unity 5.3.0f4
  • Paint
  • Paint.NET (for adding transparency to the ball)
  • Github Desktop
  • Visual Studio Community

I’m stopping here for right now, but the next tasks I want to tackle are adding score, lives and deaths. Beyond that, I need to add sounds and particle effects, balance the gameplay a bit, and add the required modifications for the assessment: an options menu, the “Doomguy” face, and my own customization: the Thru-Brick powerup.

For those who wish to follow along, you can track and download the latest version of the game at https://github.com/nickiannone/Breakout/

Stay tuned!

Breakout, Part 1

Recently, a friend who I’ve been in fleeting contact with over the years messaged me out of the blue, asking if I’d like to take part in a little side project. While I probably shouldn’t give away too many details, it’s a game, and it’s going to involve Unity. Part of the on-boarding process for the project team includes an assessment of development skill with Unity: Recreate the classic game Breakout.

breakout2600

The Atari 2600 port of Breakout.

I have three weeks to build a working copy of Breakout, emphasizing use of the Unity Canvas and Sprite classes. I also have to produce three modifications to the original formula: A menu/options screen, a “Doomguy”-esque facial expression indicating the mood of the events on screen, and another modification of my own design. This wouldn’t be that difficult, but the date of completion is rather tight: January 3rd.

Continue reading

Spring Forward, Fall Back

Well, it’s been a while since my last post; I’ve been in the process of transitioning from one job to another, and I’m about to set off on a new life as a software developer for a consulting company on Monday. While I won’t divulge the name of the company here, I can say that I’m really excited to be able to work for them for a number of reasons, not the least of which being that they actually care a lot about software development. This is just the latest step in a month full of personal transformation and growth; two months ago, if you’d have told me this is how my life would be unfolding right now, I’d have laughed in your face. However, things have gone remarkably well for me, and I’m extremely thankful for everything I’ve been given in the past month.

That being said, I’m in the process of updating myself and becoming more familiar with Spring MVC, since the framework is essentially the lingua franca of the company I’m going to be working for. Readers can follow along on Github.

Now, at my last job, we worked exclusively with Struts 1 and Struts 2 (and a teeny bit of Grails) as the MVC web framework of choice; I have many things to say about Struts, and few of them are good. Having the opportunity to learn a newer Web framework is so much fun, after the drudgery of Struts 1. However, I’d like to state for the record that the Spring MVC tutorial provided by the developers of Spring is rather outdated; I made the unfortunate assumption that it wouldn’t be as bad as a third-party tutorial, but I was wrong. Come on, Spring guys, would it kill you to write a new tutorial? At the very least, Stack Overflow has provided quite a few answers to the problems I’ve encountered thus far, so maybe the age isn’t all bad. Maybe tweak your existing one to use Tomcat 7 or 8, JUnit 4, and either Maven or Gradle, and you’d eliminate the vast majority of the issues I’ve encountered with the current version.

I’ve also been dabbling in Ruby development a fair bit; it’s such a fun language and ecosystem to play around in, I don’t know why I hadn’t started playing with it sooner. A group here in Milwaukee called RubyMKE has been hosting meetups for the language, and I’ve attended one so far, as well as poring over _why’s Poignant Guide to Ruby. I have to say, that book (graphic novel? cartoon? creative hemorrhage?) is one of the best programming guides I’ve ever read. For those who’ve never heard of why the lucky stiff, he’s a fascinating character in the Ruby community who, unfortunately, disappeared from the Internet in 2009; his content and teachings have taken on a life of their own, as recompiled by his followers. My own involvement in Ruby so far has been mostly working my way through Ruby Koans, Ruby Monk, and Rails for Zombies, and messing with Gosu in an attempt to make a sort of haunted house attraction simulator game, which I’m calling “Spookhouse“.

Stepping away from programming for a bit, this last point is kind of near and dear to me; I’ve worked in the same haunted house, Hauntfest, for the past five years, with my girlfriend and many of my close friends joining me in the spooky revelry this past month.

This year was especially meaningful, since one of the original actors, Ted Hembrook, passed away last July. We had to celebrate his birthday in October without him, sharing his traditional batches of ghost pepper brownies before opening for the night. Hauntfest, and Ted, are both big inspirations for me and for my game project, and without my involvement in the haunted house, I probably wouldn’t be the person I am today.

One of the things that I’ve found to be the most difficult about making a game so far (as early on as I am) is the disconnect between the programming work and the graphics/sound work. Yeah, I can code an animation system, or hack together a 2d raytracer, but I’ll never be able to draw well or consistently enough to have production quality art on my own. As it stands, I’m still trying to work out the core gameplay details, but in short, imagine tower defense meets The Sims’ design mode, with a patina of classic Midwestern haunted house and a sly sense of schadenfraude. Then there’s the core question of game development: Is it fun? Right now, I have no way of answering that. I’m just setting out to try and make a game that I’d like to play; hopefully something fun and complete comes out of that.

I’ve also had a rather interesting experience with the job interview process, which for many reasons I won’t go into details about, but I’ll say this: I’ve never had so many reasons to feel bad about turning down job offers. As I detailed on my LinkedIn account, I’ve had time on my hands. And boy, have I made use of it.

The Disney Experience

Over the past week, I haven’t been blogging much, on account of a week-long vacation in California. During my vacation, I visited Disneyland with some relatives, friends, and my girlfriend, and I was struck by how wonderfully efficient and experiential the Disney parks are.

Now, I’ve been to all four Disney World parks, both water parks, Downtown Disney Marketplace, and the Disney Boardwalk, but never to Disneyland until this trip. When I was young, my parents would load me and my siblings into the minivan with a bungie-corded portable TV, some Game Boys, and a bunch of VHS tapes, and trek cross-country from Wisconsin to Florida, where we would timeshare at the Westgate resorts in Kissimmee every Easter. Every year, we’d go to a Disney park, from the time I was 3 years old until we stopped going regularly in my teens. As a kid, I reveled in the magic of the parks; as an adult and a software engineer, I’m starting to figure out exactly why: Disney’s shining examples of Efficiency and Experience.

Continue reading

Reddit: update

To follow up on my previous post, Ellen Pao has stepped down as CEO of Reddit.

Replacing her will be Steve Huffman, original cofounder of the site.

When I said something had to change, this wasn’t what I had in mind; however, I’m so glad that one of the people who originally created the vision for Reddit is taking it back over. Only time will tell how this decision will work out, but things are looking good.

Computing Ethics

As a software engineering student at MSOE, I was informed that I’d have to take two soft courses with the word “Ethics” in the title, and that they were both required for graduation: HU432: Engineering Ethics, and CS409: Ethical and Professional Issues in Computing. Sadly, the latter is no longer taught at MSOE.

While Engineering Ethics was more broad and applied to both managers and engineers in all fields, Computing Ethics (as it was called by the students) covered specific topics of ethics as applied to the world of software and computing (along the lines of Google’s “Don’t Be Evil”).

Continue reading