A Short Note on Making Your d3.js Viz Responsive

OK - this seems to work, and I wanted to make a note of it. It's not a new thing, but might save you some time when you need to do the same thing.

The basic thing we want to do is re-render a d3.js graph after a user has resized their browser window.

With this approach, on a window resize we reconstruct everything svg-related from scratch. This might not be feasible in your case, but otherwise it can be more involved and it is easy to miss something.

We also use David Walsh's "debounce" plugin that can be used to throttle how often you try to rerender while the user is still resizing. There might be a better way to tell when a user is "done resizing" - please let me know how: it doesn't affect the core idea here, just how often you call the "rebuild from scratch" method.

So, so here is one way that works (even when you change your iPhone from portrait to landscape - sweet yay):

  • Set up debounce when the page loads, via
    
        window.addEventListener('resize', debouncedResize);
    
  • Set up your "debouncedResize" method (this is using a delay of 250ms before calling the rerender)
    
    var debouncedResize = debounce(function() {yourRerenderAfterResizeMethod();},
                                    250);
    
  • Set up your rerender method (it might be a method on another object or whatever)
    
    function yourRerenderAfterResizeMethod() {
    
    //destroy existing svg elements
    d3.select('#idOfYourSvgElementToReconstruct').selectAll('*').remove()
    
    // ... other svg elements you will rebuilding
    
    //make sure you don't have straggler object properties hanging around that are used
    //  when rendering (this bit me once - I was trying to add svg elements to an empty thing, and
    //  all actions were silently ignored)
    
    //Now go to town and build all of your svg stuff, 
    // taking into account the new screen size.
    //Note that I have found (so far) that it seems best 
    // to set the width/height attributes of the svg elements so that
    //  when the user is resizing (and before the rerender is called), 
    //  things don't scale crazy. YMMV.
    
    }
    

From my experience so far, you can prevent potential inadvertent transient wacky scaling while the user is resizing by explicitly setting the width/height of the relevant svg elements. This might only matter when the enclosing element has a width set to 100% of the parent, but the jankiness until the rerender is called can be distracting otherwise.

Passing Data to (and from) a Modal Popup in Angular

Success! Perhaps naïve and a bit crude, but it is something that seems to work so far.

As part of playing with StrongLoop's Loopback framework, I am also beginning to learn about Angular via Loopback's examples. Today, I ran into needing to do something pretty basic in Angular, and it seems to be a bit harder to do than I expected... at least to someone very new to it.

The feature in question is the ability to pass parameters/info into a modal popup. The scenario is when you have a list of stuff, and you want to show a popup when the user clicks on one of the elements in the list, say for a confirm delete or something.

Anyone who has a list of stuff the user will interact with will need this feature, and so that's why it surprised me that it wasn't quite as straightforward as I anticipated.

Well, not straightforward to someone as new to Angular as myself.

So, below are the basics on one way that works - not a complete example, but this is kind of a "fresh note from the field", and may help someone nevertheless. It is based on Angular's sample on using the ui.bootstrap.modal functionality, and this stackoverflow post.

Here are the pieces:

  • Angular (of course)
  • Bootstrap
  • UI Bootstrap - Bootstrap components from the AngularUI Team
  • You have an html list where each row has some attributes unique for a row (id, etc.); here is a snippet in Jade syntax (N.B. The diminished clutter of writing and reading Jade for html is a pleasure, imo):
    
      tr(ng-repeat="file in files")
        td
          strong
          | {{file.originalFileName }}
        td
          button(ng-click="openConfirmDelete(file.originalFileName, file.id)",title="Delete")
          | Delete
    
    A basic list of stuff (Jade syntax)
    We want a styled popup specific to the row clicked

  • In your html file, define a script defining a template for the modal popup - here is a stripped-down one (yes, that's "raw" html inside the script tag):
    
      script#myModalContent(type='text/ng-template')
        .modal-header
          h3.modal-title Delete File {{name}}?
        .modal-footer
          button(ng-click="cancel()") Cancel
          button(ng-click="doDelete()") Yes, Delete File
    
    
    Defining the script "template" for the popup for Angular

  • In your controller js file, make sure and include 'ui.bootstrap", a la
    
       angular.module('app', ['angularFileUpload','ui.bootstrap'])
          .controller(...
    
    Referencing the UI Bootstrap stuff
    so it can be used

  • In the controller javascript (and I am assuming this is in definition of the controller that is used for the list), define the controller for the popup:
    
      var PopUpController = function($scope, $modalInstance, name,id) {
       
        $scope.name = name;
        $scope.doDelete = function() {  // this is the function called when the user 
                                        //  user clicks the "Yes Delete File" button              
          $modalInstance.close({doDelete:true, id:id}); //this gets passed back to 
                                                        // the code that created the popup
        };
        $scope.cancel = function () {
          $modalInstance.dismiss('cancel');
        };
      };
    
    Defining the controller for the modal confirmation popup

  • In the relevant controller for the list, define the function openConfirmDelete that will make use of both the script template and the PopUpController:
    
      ...).controller('FilesController', 
            function ($scope, $http, $modal) {
              $scope.openConfirmDelete = function (name,id) {
                 var modalInstance = $modal.open({
                                        templateUrl: 'myModalContent',
                                        controller: PopUpController,
                                        resolve: {
                                          name: function() {return name;}
                                          id: function () {return id;}
                                        }
                                     });
                                   });
    
                  modalInstance.result.then(function (opts) {
                      if (opts.doDelete === true) {
                            //you are now free to deal with the file/whatever,
                            //   as you will be able to get the id from opts.id
                            //  e.g, delete(opts.id);
                            // Note: as "id" is in a closure, I'm not sure it
                            //         is necessary to pass it back here, but
                            //         this shows how to pass anything back
                      }
                    }, 
                     function () {console.log('Modal dismissed at: ' + new Date());}
                  }); // modalInstance.result
              }; // openConfirmDelete
            ...
            ...
            }); //FilesController...
    
  • The final piece: creating the popup when
    the user clicks the "Delete" button on an item in the list

Whew.

Towards a Workflow for Developing a Loopback App

Several weeks ago, I came across StrongLoop's Loopback framework for node, and have been slowly starting to become familiar and comfortable with this as part of developing a custom node app with social login. The app will have be usable from both an Angular.js-based web app, and from an Android mobile app (IOS as well, if needed).

There is a considerable flurry of activity going on at StrongLoop right now, with many changes/fixes/features being added all the time. Maybe this is a common situation when building node apps. I don't know how often they are updating the npm registry, and I want to stay current with the latest in github. Further, I will likely need to fix little things myself in the core Loopback modules pending those things being fixed in the Loopback repos. I was trying to determine a workflow that would somewhat work with all these factors.

What I've settled on for now is to fork the Loopback modules I use, and then refer to my forks in package.json for my custom app. This includes core loopback, loopback-component-passport, loopback-component-push, loopback-component-storage, loopback-datasource-juggler, and strong-remoting.

I'm using WebStorm as an ide, which seems to work nicely with github, and have local projects based on my forks of the Loopback modules as well. I'm doing this rather than modify anything in the node_modules of my custom project, which would require turning off gitignore there.

Maybe there's a better/official way to do these things - will see how this works and what needs to change about it.

Popular Posts