• Skip to main content

DigitalLabs@MMU

Digital enterprise at Manchester Metropolitan University

  • Home
  • About
    • Digital Labs Team
  • What We Do
    • Teaching
    • Services
    • Products
      • Appatella
    • Portfolio
    • Tutorials
  • Latest
    • General
    • Jobs Board
    • Our Team
    • Products
    • Software Development Services
    • Digital Labs Portfolio
    • Teaching
    • Live Projects
  • Jobs At DigitalLabs
  • Contact
You are here: Home / 2018 / Archives for August 2018

Archives for August 2018

August 30, 2018 By Zebra

Taming the Urban Wild

A post-mortem of an implementation exercise

There’s only one way to ruin a good plan and that’s to put it into practice.

Over the last few weeks, I’ve gone in cold on the Urban Wild: a DigitalLabs-style project, documented and laid out on Trello. It was designed quickly in December 2017 as an exemplar to show some of the most regular elements of the toolchain here, including

  • Restlet
  • AngularJS
  • Ionic
  • GitHub

The aim was two-fold; first, to confirm the planning and documentation model was suitable for this purpose – to hand it over to someone without any prior exposure and for it to be realised – and second, to identify any flaws in the design and methodology before rolling it out on a wider basis. Take a look at the results on the Github Pages site, here.

[Read more…] about Taming the Urban Wild

Filed Under: Teaching Tagged With: Angular, GIS, Git, Implementation, Ionic, post-mortem, Project, Restlet, TheUrbanWild, Web

August 24, 2018 By Yusof_DigitalLabs

Ninth Week at Digital Labs

Yusof Week 9

Development – Final Week

Most of the time spent this week was continuing development on the view page as well as fixing minor bugs with the search page. The Single Page Web Application (SPA) is now fully deployed on Heroku which allowed us to test the application on an iPad, which works perfectly.

 

More on D3 Zoom

Last week time was spent implementing the visualization using D3. One of the main features of the visualization is the ability to pan and zoom to compare data features, this was fully implemented last week. This week I carried on developing the pan and zoom perfecting its functionality.

When using d3.zoom() for each zoom transformation d3 will return the current transformation. The transformation a contains 3 properties:

  • transform.x translation amount along the x-axis,
  • transform.y translation amount along the y-axis
  • transform.k the scale factor.

D3 will calculate each new point for every element that is within the zoom selection, this causes problems when only one element needs to be zoomed. One solution is to only re-render the elements that need to be zoomed this is only a temporary solution if the user selects to zoom all elements all the previously none rendered elements will jump to the new position.

A better solution is to have two transition vectors one for the selected elements and one for the non-selected, this means that relative positions can be maintained.

 

Setting up Heroku

Once the main aspects of the view page were complete, it felt like a good time to host the two APIs, database and Single Page Webb Application (SPA) on Heroku, allowing the client to test the search import and view processes for the first time.

Heroku is platform as a service (PaaS) which allows any developers to run application entirely in the cloud, supporting multiple languages.

Deploying to Heroku took a surprisingly long amount of time than expected, below I explain how to host an application on Heroku.

When a Heroku account has been created a new application can be set up. Heroku usually has two methods to perform actions either through the CLI (command line interface) or the dashboard (website). Before using the CLI first it must be downloaded, to CLI can be downloaded either via brew – Mac – or downloading a dedicated installer for your os.

Once the CLI has been successfully installed any type of an application can be set up. This time I will only be describing how to set up a Node application, but others follow a similar process. Within the local folder, containing your  Git repository, run the command

heroku create [choose an application name]

this will create a git remote associated with the local git repository, called Heroku. If no application name is supplied Heroku will generate one randomly.

Finally, run the git push Heroku master command, this will automatically install all the necessary dependencies and deploy your application. Once an application has been deployed running the Heroku open command will open the website within the default web browser.

It is important to make sure that your git repo is set up correctly, Heroku will by default look with the root the repo for a package.json file which should contain all the needed dependencies and the startup script. Below is an example of a package.json file.

 

{
   "name": "Singe Page Webb Application",
   "version": "1.0.0",
   "description": "The back button is broken",
   "engine": {
       "node": "10.5.0"
  },
   "dependencies": {
   "angular-typeahead": "^1.0.2",
   "express": "^4.16.3",
   "path": "^0.12.7"
},
   "scripts": {
   "start": "node src/server.js"
}
}

 

If your package.json is not within the root of the repo this article explains how to configure heroku to look within a subdirectory. Heroku has mutiple articles for more more information on the initial setup of an application – take a look.

 

Environment Variables

Environment variables are used to describe aspects of a Node application. Unlike global environment variables, env variables are used for a specific node process usually defining port numbers or specific URLs. To add a new property to the env object, which is a property of the process object, each property can be appended before running the application.

 

PORT=8001 node app.js

 

Appending each env property before running the application can easily become cumbersome, a better solution is to create a dedicated env file. An env file contains a JSON object with each property an env variable.

Twilio has a great article on using environment variables in the node, describing the basics to alternative methods.

The two methods to set up environment variables on Heroku is either through the CLI or the Heroku dashboard. The first thing to mention is Heroku calls environment variables config vars, using the CLI the Heroku config command will list all the config vars. To add a new config var, use the Heroku config: set followed by a key-value pair, to remove a config var use Heroku config: unset followed by config key.

To modify the config vars from the dashboard navigate to the app’s setting tabs, under the config var selection config vars can be added, edited or deleted.

Accessing Heroku config vars from the Node application is exactly the same as accessing environment variables, for example, to get the app’s PORT config var with process.env.PORT.

When running the Heroku application locally to access config vars Heroku will automatically read an env file ‘each name/value pair is inserted into the environment, to mimic the action of config vars.’. The example below demonstrates how to copy a config var to the env file.

 

heroku config:get PORT -s >> .env

 

Reflection

This week is the last week of development – equally a sad but fulfilling one. Throughout the 9 weeks, 4 of them have been spent investigating and designing and the other 6 have been development.

I have learned a huge amount about how to take a concept and turn it into a fully working system, this was my first time designing a whole system using Trello to document the process and Pencil to wireframe each part. It was definitely hard to jump straight into the deep – but extremely enjoyable developing my understanding of how a small company operates as well as how easily a system can fail, and what to put in place to stop it.

 I learned how to effectively communicate with clients – again my first time – it has been extremely gratifying showing them each phase of the process. For 9 weeks I have met with the client, for 10 minutes, nearly every day discussing the process and any changes needed. Especially within the fourth and fifth week of development a few extra features were requested. When a new feature is added I document a ‘change request’ and predict how long it will take to develop. Only then I can decide if there is enough time.

Programming every day has greatly improved my core Javascript and also my understanding of AngularJS. Throughout the development time I had to quickly learn new technologies; Swagger, AuthO and D3. Swagger was used to implement the two APIs, AuthO to implement the authentication process and D3 to visualise the time series data.

Conclusion

Overall the 6 weeks of development has been extremely rewarding especially seeing the visualisations fully working. Some of the elements of the SPA still need development which will push back debugging and testing by a couple of days but I am confident all the major bugs have been ironed out. After the two weeks of debugging the last week is left for handover and documentation.

Filed Under: Our Team, Yusof's Diary Tagged With: audience:Student, Heroku

August 20, 2018 By Yusof_DigitalLabs

Eighth Week At Digital Labs

Yusof Week 8

View Page

This week completely focused on the view page, using D3 to build the time series graph and annotations. Since this was my first time using D3 the start of the week was much slower compared to previous weeks. The graph visualization panning and zooming, as well as annotations display and the ability to edit them, are fully complete, luckily no unknown unknowns have popped up.

D3

D3 stands for Data-Driven Documents, allowing developers to present data using HTML SVG and CSS. Unlike other data visualization libraries D3 works by giving you a set list of components to build any type of visualization wanted, many other visualization libraries lockdown to very specific visualizations with specific properties. This can be extremely problematic, the developer relies heavily on a set feature list but with D3 the core components provided can be used to build any specific feature such as panning on one axis.

D3 works by manipulating the Document Object Model (DOM) according to the given data, using a combination of select and modify methods. The select method is D3’s most basic but most useful, allowing the selection of the DOM either by tag, class name or id name. Once an element has been selected a child element can be appended or an attribute can be set or get. Using only the select method a developer can iterate through an array appending an h1 tag to the body as well as styling each tag independently. The example below demonstrates how to select all div tags and colour each div background red.

<html>
   <body>
       <div>
           <h1>
            Hi
           </h1>
       </div>
       <div>
           <h2>
              Hi there
           </h2>
           <h2>
              How are you
           </h2>
       </div>
   </body>
</html>
d3.select('body')
.selectAll('div')
  .style('background-color','red')

 

First the select statement searches the DOM and selects the first occurrence of the body tag, second, the selectAll statement selects all div elements within the body and finally, the style adds a style attribute to each div setting the background color to red.

One thing to note about D3 is a selection method typically returns the current selection or a new selection allowing methods to be easily chained. By convention, if a selection method returns the current selection use four spaces to indent if a new selection is returned use two spaces of indentation.

D3 is a powerful tool but with great power comes great responsibility. D3 takes considerably long time to build a visualization but when used correctly responsive, intuitive and beautiful visualizations can be built. The best example of D3 is The New York Times 2013 Obama’s Budget Proposal visualization, inspecting the element you can view the core elements used.

D3 Graph

To build the time series graph D3 was used, this whole week had me working closely with D3 slowly building each component. The first element that needed to be built was the graph itself, akin to past weeks I used a combination of tutorials, D3 documentation and my past knowledge of javascript. Building a line graph within D3 is relatively simple, the graph consists of two main components axis and the line itself.

To build an axis within D3 you first need to specify the scale. A D3 scale maps data to a visual representation (x and y coordinates in pixels), D3 provides multiple methods both for continuous and discrete data. The example below specifies two linear scales and sets their ranges.

 

//set scales and ranges
var x=d3.scaleLinear().range([0,width]);
var y=d3.scaleLinear().range([0,width]);

 

After the scale ranges have been set, the domains need to be specified. The domain is the complete set of values when specifying the domain you provide an array of two number specifying the minimum and maximum data points.

The example below uses the d3.min and d3.max methods returning the minimum data point and maximum data point from an array respectively. d3.extent may be used instead, which returns the minimum and maximum values.

 

x.domain([
   d3.min(data.Xdata),
   d3.max(data.Xdata)
]);
​
//or
​
x.domain(d3.extent(data.Xdata));

 

The final step is to render the actual trend line using the d3.line method which generates a line from an array of data. Using both the x and y scale specified earlier the line method can generate an svg path on screen.

 

var line=d3.line()
.x(function (d){
   return  x(d.Xdata);
})
.y(function (d){
   return  y(d.Ydata);
});

 

Voila, a line graph is built, well almost. You might notice that the trend line is rendered on screen but no axis is this is because we haven’t told d3 to render any axis. To render an axis simply use the d3.axis[position] method specifying the position and scale.

 

var xAxis=d3.axisBottom(x);
var yAxis=d3.axisLeft(y);

The example below shows the fully how to implement a line graph using D3.

//select svg and specify width and height
var graph=d3.select('svg')
  .attr('width',800)
  .attr('height',500)
​
//graph data
var data=[data]
​
//specify the ranges of the x and y scales
var x=d3.scaleLinear().range([0,800]);
var y=d3.scaleLinear().range([0,800]);
​
//specify to d3 to render x and y scales
var xAxis=d3.axisBottom(x);
var yAxis=d3.axisLeft(y);
​
//specify to d3 how to render the line
var line=d3.line()
.x(function (d){
   return   x(d.Xdata);
})
.y(function (d){
   return  y(d.Ydata);
});
​
//specify the domains of the x and y scales
x.domian(d3.extent(data.Xdata));
y.domain(d3.extent(data.Ydata));
​
//create a group tag and append the x axis
graph.append('g')
.attr('class','axis axis--x')
.attr('transform','translate(0,'+500+')')
.call(xAxis);
​
//create a group tag and append th y axis
graph.append("g")
    .attr("class", "axis axis--y")
    .call(yAxis);
​
//create a group then a path tag and calculate the path definition to be drawn.
graph.append('g').append('path')
.attr('class','line')
.attr('d',function (d){returnline(data)})
​
​

Zooming and Panning

Initially, I predicted that zooming and panning would take a considerable amount of development time instead I completed both the zooming and panning just under half the predicted time mostly due to the in-built d3 zooming capability. D3.zoom method provides a transition vector when the user either zooms or pans, the vector contains three parts a scale, x and y. The transition vector can be used to rescale both the x and y scales appropriately.

 

function zoomed() {
   var t=d3.event.transform;
   var xt=t.rescaleX(x);
   var yt=t.rescaleY(y);
}

 

Using both xt and yt variables any component can be re-rendered.

The problem with using D3.zoom is it doesn’t distinguish between zooming and panning, for our graph multiple trends line can be viewed, panning should offset only one trend line whilst zoom should zoom all trend lines. To distinguish between zooming and panning, I compared previous transition vectors to the current one, if the scale component has changed I know the user has zoomed if not I know the user had panned.

 

//last transition vector
var lastTransitionVector;
​
function  zoomed(){
   //current transition vector 
   var t=d3.event.transform;
   
   //k is the scale component, if not equal to the 
   //last transition vector user has zoomed
   var isZooming=endZoomvector.k!=t.k
   
   if(isZooming){
       console.log('user is zooming');
  }else{
       console.log('user is not zooming');
  }
   
   //update the lastTransitionVector
   lastTransitionVector=t;
}

 

Annotations D3

Annotations can be built directly using D3 but this can be a long and laborious process, instead I used the d3-annotation library. D3-annotation is a library, built by Susie Lu, used to build responsive annotations.

Using the library drastically sped up the development time clawing back a few hours lost from previous weeks. Building basic annotation is very quick process, each annotation needs an x and y pixel or data co-ordinate. If a data co-ordinate is provided the accessors method can be used to it into an x and y pixel co-ordinate, d3-annotation will take care of rendering process. The example below demonstrates how to create a simple annotation.

 

const  type=d3.annotationLabel
​
//annotations
const annotations=[{
   note: {
       label: 'Description'
       bgPadding: 20,
       title: 'Annotation'
  },
   data: {
       xPoint: '100',
       yPoint: '20'
  },
   className: 'show-bg'
}]
​
//set up range and domain (not shown here)
​
​
const makeAnnotations=d3.annotation()
.notePadding(15)
.type(type)
//convert data points to x and y pixel co-ordinates 
.accessors({
     x: d=>x(data.xPoint),
     y: d=>y(data.yPoint)
})
.annotations(annotations)
​
d3.select('svg')
.append('g')
.attr('class','annotation-group')
.call(makeAnnotations)

 

Once a annotation can be rendered on screen, I had to make sure each annotation maintains its correct position when the user pans and zooms the graph. Akin to the time series graph, I used the rescale method to rescale the x scale to calculate each annotation position. The example below demonstrates how to re-render each annotation, the function expects an array of annotations and a transition vector. Its important to remember the d3-annotation library clears all annotations then renders them back therefor if one annotation moves but a second doesn’t both need to be re-rendered.

 

function   annotationRescale(annotations,t){
   const  type=d3.annotationLabel
   
   var xt=t.rescale(x);
   var yt=t.rescale(y);
   
   const  makeAnnotations=d3.annotation()
      .notePadding(15)
      .type(type)
      .accessors({
         x: d=>xt(data.xPoint),
         y: d=>yt(data.yPoint)
      })
      .annotations(annotations)
}

 

One of the main features of annotations is the ability to drag each one to a new position. Luckily the d3-annotation provides a drag event calling a function when an annotation is dragged. The function simply translates the annotations x -coordinate to the cursors x-coordinate.

Building the mechanics of the annotations was a relatively smooth process compared to the annotations UI design. In the first few weeks of design I completely fleshed out the UI for the whole system, both the search and import pages followed the UI wireframes, on the other hand, the annotations UI did not. I quickly realized the original design was extremely clunky and would not work well on a touch device, not wasting too much time, Laurie and I designed a better more intuitive system that would scale nicely on all devices.

Conclusion

Overall this week has been very satisfying,taking raw data from a database and displaying it on screen. Due to last weeks, extra features and bugs, development still isn’t finished, all that remains is the tags and columns panel. By next week all features should be complete leaving the rest of the time for debugging and testing.

Filed Under: Our Team, Yusof's Diary Tagged With: audience:Student, D3

August 13, 2018 By Yusof_DigitalLabs

Seventh Week at Digital Labs

Yusof Week 7

Mistakes Made

This week has been a slow week, through-out development time we constantly focused on reducing any kind of feature creep. The mistake I made this week was focusing too much on the small features leading to specific features taking much longer than expected. Luckily the server, search page and import are 100% complete allowing me to focus on the view page next week.

Ui-router Negates ngRoute

Single Page Web Applications (SPA) contain no page reloading, instead, they download the whole application. To route users to different views (web pages), the Angular ngRoute module can be used. NgRoute functions by linking controllers to views by watching the URL using $location.url(). Using $routeProvider specific routes can be defined, the example below routes ‘/import’ URL to the importController and import.html template.

 

angular.module('importApp',['ngRoute'])
​
  .config(function($routeProvider)){
           $routeProvider
          .when('/import/:importId',{
           templateUrl: 'import.html',
           controller: 'importController',
           resolve: {
           //get data
          }
  })
};

 

You will notice ‘:importId’ appends the import URL the colons tells us that ‘importId is a path parameter, within the importController the URL parameters can be accessed using $routeParams. Additionally, the example uses a resolve property which can be used to request data before instantiating the controller. If the resolve property object contains a promise the controller will only be instantiated once resolving all promises if a rejection happens the $routeChangeError event fires. The $routeChangeError can be hooked upon to redirect a user to a different view.

NgRoute is very powerful tool that can easily support small-scale applications but lacks required features for more complex ones. An alternative to ngRoute is ui-router, ui-router is a 3rd-party module that contains all ngRoute features as well as much needed extra functionality. Ui-router provides multiple named views, one template can contain multiple views, nested views which can inherit from one another. Furthermore, ui-router provides a superior way to access state parameters. The example below shows 2 views within the same template.

 

<!--index.html-->
<body>
   <divui-view='importDetails'></div>
   <divui-view='importSelection'></div>
</body>

 

angular.module('importApp',['ui-router'])
​
  .config(function($stateProvider)){
           $stateProvider
            .state('import',{
             views: {
             'importDetails': {
               templateUrl: 'importDetails.html',
               controller: 'importDetailsController'
            },
             'importSelection': {
              templateUrl: 'importSelection.html',
              controller: 'importSelectionController'
            }
          }
  })
};

 

Each ‘div’ with the ui-view attribute within the html describes each view, the view names must match with the keys within the views object.

Furthermore, ui-router provides lazy loading allowing the application to download resources on-demand rather than startup time. Using lazy loading can drastically reduce the startup time of an application only by downloading the necessary data when needed. Ui-router will download the needed data for a state before entering by waiting for the function promise to resolve. Below the example shows how to configure the import state to lazy load, which loads import.js before going to the ‘import’ state.

 

angular.module('importApp',['ui-router'])
​
  .config(function($stateProvider)){
            varimportState={
            name: 'import',
            url: '/import/:importId',
            templateUrl: 'import.html',
            controller: 'importController',
            lazyLoad: function(){
             returnSystem.import('import')
            }
          }
           
           $stateProvider.state(importState);
  })
};

 

State Page Stored in URL

When first meeting with the client one requirement the client requested was the ability to save searches and share views, each feature would take a large amount of time and may provide limited functionality. Instead of having two different features, save searches and share views, storing the pages state within the URL provides a cleaner solution which would take less development time.

Storing the pages state within the URL, is common practice with Single Page Web Applications (SPA), allowing the user to intuitively share the pages URL knowing the URL with direct back to the exact state of the page.

Storing the page state in the URL is a cyclic process, each variable that needs to be watched within the page is stored within the URL as a query parameter, when one of the watched variables changes the corresponding URL parameter changes, triggering the pages UI to update.

A common mistake when storing the page state in the URL is to update the URL and UI independently. Updating the URL and UI independently is considered bad practice as it breaks the cycle thus the URL and UI could de-sync causing unwanted side effects.

 

To maintain the page state within the URL I used ui-router, using $stateParams and $sate services. The first part I developed was to extract the parameters from the URL using $stateParams. The $stateParams is a service to provide controllers or services with URL parameters which are declared when configuring the application. Within the $sateParams object each key corresponds with each URL parameter with the value the value of the parameter. Within the application, the URL parameters are extracted if a parameter has changed or when the state is initially loaded. The example below demonstrates how to extract the importId URL parameter using the $stateParams service.

 

app.controller('importController',['$stateParams', function($stateParams){
   varurlParams=function(){
       return$stateParams.importId;
  }
}])

 

Once the application could extract URL parameters the second step was to update the parameters dynamically. When researching how to update URL parameters the UI-router official documentation said to use the $state service. The $state service exposes multiple methods to control the current state, for example, $state.get gets the registered StateDeclaration object (used to define a state). To update the URL parameters the .go method can be used passing the absolute state name, state object or relative state path as well as a map of parameters which will populate $stateParams. To update the current state a ‘.’ can be passed as the state name to the .go method.

 

app.controller('importController',['$state', function($state){
   functionstateReload(params){
       $state.go('.',params);
  }
}])

 

The issue with using this method is the application state reloads each time the .go method is called, which doesn’t provide a seamless user experience. The application page reloads because the .go method calls the .transitionTo method which transitions to a new state. To fix the reloading issue the dynamic flag can be used. When declaring parameters for a specific state within the application configuration the dynamic flag can be set to true. Setting the dynamic flag to true will not cause the state to enter/exit when the parameters value changes, when set to false the state will enter/exit when a parameter changes

 

angular.module('importApp',['ui-router'])
​
  .config(function($stateProvider)){
            varimportState={
            name: 'import',
            url: '/import/:importId',
            params: {
              importId: {
              dynamic: true
              }
            },
            templateUrl: 'import.html',
            controller: 'importController',
          }
           
           $stateProvider.state(importState);
  })
};

 

A comment mistake when integrating the page state within the URL is having too many watchers. The $watch method registers a listener callback which is fire whenever the passed expression changes. It might be tempting to attach a listener for each variable when a variable change the .go method is called updating the URL parameters but this considered bad practice. Having too many watchers can impact the application performance drastically, once the number of watchers exceeds around 2,000 an application performance will start to have noticeable performance issues. Instead a function should be implemented and is specifically called when URL parameters need to be updated.

Conclusion

Overall this issues last week have impacted this weeks development before this week started I still had to finish the incomplete features from last week. I was hoping to fully complete the Single Page Web Application (SPA) this week but finishing the Import page took longer than expected. Next week the whole system should be complete leaving me with 1 week of extra development time.

Filed Under: Our Team, Yusof's Diary Tagged With: Angular, audience:Student, feature creep, ngRoute

August 3, 2018 By Yusof_DigitalLabs

Sixth Week at DigitalLabs

Yusof Week 6

Simple Start

The start of the week focused on finishing the import page and testing the search page. This is the third week of development meaning by next week development should be complete, if no more unexpected errors show up.

Errors Everywhere

Development this week has been the slowest, a lot of my time was spent debugging  the code. Unexpected errors will always happen within software development, this week two days were spent fixing bugs. Below details the two major bugs I was experiencing and how I fixed them.

Furthermore, the predicted development time has been pushed back by two days which takes two days off the extra features development time. Luckily development should still easily be completed within the 6 weeks of development time

Third Party Troubles

When building the Single Page Web Application (SPA), I have been using multiple third-party libraries when needed, for example, Angular Materials. Using third-party libraries can save a lot of time allowing me to focus on the application login but it means that my code is tied down to the third-party libraries.

To implement the autocomplete as well as the UI chips, represents a small set of information, I used the Angular jsTag library. The jsTag library relies on angular-typeahead which is a wrapper for twitter-typeahead, I followed the official tutorial to implement all three libraries. Once jsTag was implemented I noticed it didn’t work 100%, when selecting an item using the arrow and return keys the item would not select correctly, only the first character of the selected item would be viewed.

To diagnose the problem I first re-read the official tutorial and documentation double checking my implementation. After confirming I implemented the library correctly, I checked the Issues list on the GitHub repository. The issues contained multiple records of other developers experiencing similar problems but with no solution. Instead of switching to a different a library I attempted to solve the problem myself initially looking at a jsTag demo. When testing the demo application I couldn’t reproduce the same issue but the source code for the application wasn’t available.

Luckily the developer used Github Pages, a service which serves a website from a GitHub repository, to host the demo. I compared the demos source code with mine which again confirmed I implemented the library correctly.

My final step was to replicate the demo exactly from the ground up, testing every time a new part was added. Whilst importing each library I noticed that I was using a different version of the angular-typeahead library. Throughout the documentation and tutorial, there was no mention of version compatibility, the jsTags relies on an earlier version of angular-typeahead. Using the correct angular-typeahead version fixed all the issues I was experiencing.

The demo source code can be found here and the compatible angular-typeahead v0.2.1 can be found here. Additional the issue fix can be found here

Back Button Breaks

The Single Page Web Application (SPA) built with AngularJS, SPAs function similar to desktop applications rather than traditional websites. The advantage SPA have over traditional websites is they have relatively quicker response times, the disadvantage is they can easily break expected features. Whilst building the import page the browser back button provided with unexpected results. The import page contains a simple folder browser when pressing the back button the user would expect the folder browser path to go up, instead pressing the back button went to the previous page.

The unexpected result happens because navigating folders using the folder browser dynamically updates the DOM instead of directing the user to a different URL thus nothing added to the back button’s history.

To fix the back button problem I first researched the problem collating articles on stack overflow and on UI-router. The stack overflow provided many examples explaining how to detect a back button event and how to handle the event but they all relied on the ‘$stateChange’ events which are official depreciated since ui-router 1.0. To migrate ‘$stateChange’ to ui-router 1.0 the Transition Hook API is used instead, after reading the APIs documentation and tutorials I converted the stack overflow example to use the API.

To detect when a transition start runs the onStart hook can be used. This hook can be used to perform tasks before the transition is complete, this hook is also invoked when the user presses the back button. The example below logs to the console ‘Transition onStart Invoked’ when the user switches pages.

$transitions.onStart({},function(transition){
    console.log('Transition onStart Invoked');
})

The problem with using the onStart hook is it doesn’t tell us what invoked the transition change for example by clicking on a link or pressing the back button.I first had to understand what else happens when a transition changes to detect if the used pressed the back button. I discovered that the ‘locationChangeSucess’ event fires followed by the ‘before’ event, which invokes the onStart hook, when the user presses the back button, otherwise the before event fires initially, followed by the ‘locationChangeSucess’. 

Knowing the locationChangeSucces event fires first when the the users pressed the back button, I could hook on to this event. The example below sets a new scope property ‘current location’ to the user’s location when the hook is invoked.

 

 

$scope.$on('$locationChangeSucess',function(){
    $scope.currentLocation  = $location.url();
})

I checked if the currentLocation property was equal to the URL transitioning to, by using the ‘.to‘ method. If both properties are equal, the user has pressed the back button, if not the user has transitioned using a different method. Attaching the hooks to the root scope rather than a local scope will detect the events globally.

The example below logs to the console ‘back button pressed’ and cancels the transition, using the ‘.abort‘ method, when pressing the back button

$rootScope.$on('$locationChangeSuccess', function() {
    $rootScope.currentLocation = $location.url();
});
$transitions.onStart({}, function(transition) {
    //extracts the URL and params from transitioning 
    var newLocation = parseUrl(transition.to());
    
    if(newLocation === $rootScope.currentLocation){
        console.log('back button pressed')
        //stops the transition from happening
        transition.abort();
    }
​
});

The example above uses Angular services to detect a back button click, the same results can be achieved just using pure Javascript. The history object which ”exposes useful methods and properties that let you … manipulate the contents of the history stack”. Using the same hook as before ‘locationChangeStart’ we can use the history object to manipulate the transition. The example below illustrates  how to redirect the user back to the original page 

$scope.$on('$locationChangeStart', function () {
    //as if the user clicked the Forward button
    history.forward(); 
});

The disadvantage with using the history object is that support is unknown for Internet Explorer, Opera, and Safari. Doing a quick test (03/08/2018), Chrome, Safari, Opera and Firefox all support the history object.

Conclusion

This week mostly consisted of debugging but was still no less enjoyable. Seeing the SPA moving slowly closer to completion is immensely gratifying as well as getting feedback from the clients. Next week the SPA should be close to completion if everything sticks to the plan.

Filed Under: Our Team, Yusof's Diary Tagged With: angular-typeahead, AngularJS, audience:Student, jsTag Library

  • Home
  • Contact DigitalLabs@MMU
  • Digital Labs Team
  • Jobs Board
  • Privacy Policy
  • Twitter

Copyright 2018 - 2020