I’m writing about my Neighbourhood Map project for one main reason. It has been a really hard project, and the best way to learn about it is to write about it.
Before I dive into the detail, this blog post is primarily aimed at present and future me. As I mentioned above, it is to help with my learning but also for me to look back on and see how I worked through this project.
First I had to get my head around how the architecture of the app, using the MVVM paradigm. The rubric from Udacity for the project stated:
Code is properly separated based upon Knockout best practices (follow an MVVM pattern, avoid updating the DOM manually with jQuery or JS, use observables rather than forcing refreshes manually, etc). Knockout should not be used to handle the Google Map API.
My emphasis on the final sentence. This is the part that threw me, although I interpreted it too literally. Knockout should be used to manage the View (UI) and connect to the Model via the ViewModel, but Google Maps API should be used to handle the actual aspects of the map.
My 1:1 session with the Udacity coach helped to put things in perspective, and at this stage, whilst planning is important, over analysis and designing too complex an architecture (for my skill level) is not good. Better to plan, build, get things working, then refactor later as appropriate.
I followed the MVVM design based on the lessons and the mini project (cat clicker) to get up and running.
Then I was into the challenge full on.
Problems and solutions
This is not an exhaustive list but pulls out some of the key problems I faced and how I solved them.
Managing Foursquare JSON responses
Resolving the issue where Foursquare does not always have the info for each venue e.g. one venue has a description and a price range, another venue does not. This caused the infowindows to throw errors and not open.
- A forum post recommended using ‘hasOwnProperty` - MDN: hasOwnProperty
- For example:
Allowing a user to filter an array
Ideas and inspiration on how to solve this came from a few sources.
First of all, a blog post by Ryan Niemeyer on Utility functions in KnockoutJS. This in particular was the idea behind options to filter an array, combined with
indexOf (see this from MDN re Array.prototype.indexOf()).
Secondly, as the first options were not providing a completed solution, in the end, via one of the forum mentors, this link to a KO Maps example provided the inspiration I needed.
This gave me ideas on how to solve not just this challenge but also creating a favourites list. To fully understand it, I picked it apart, studied it, slept on it, and then developed a plan how I could make it work for a user filtered list and also for a favourites list.
In essence, the approach is to create an array, then an observable array based on the original array. The user filter input - from the data-bind
textInput() - is used to filter the observable array. In short, this is how to create a list which can be filtered by user input but which shows as complete (i.e. has the contents of the original array) if no user input is detected.
The next challenge is to link the filtered list to the rendering of the location markers. The Google Maps API includes
setVisible(bool) for the location markers. So the logic which determines what is in the observable array then also needs to switch the location marker to
I stepped through trying to get my plan to work and encountered a few errors along the way:
Issue 1: Cannot locate the markers
Uncaught TypeError: Cannot read property 'setVisible' of undefined
So tried to add
this.marker = null; to my Constructor as the marker was not part of my Model i.e. not in the constructor to add a property when instantiating an object.
Issue 2: null ain’t it
Uncaught TypeError: Cannot read property 'setVisible' of null
Nope, that’s not quite it although thinking it through, I still believe I need this object property.
Issue 3: Need to refactor how I instantiate my objects
locationItem.marker is available to my
This solved it. It works! 😀 👍
I split out the functions like this:
Create an array, instantiating
Add to this array of objects with further information, using separate functions, crucially without the keyword
new. In other words, add to the existing objects, do not create “new” objects.
This allows the mirrored observable array to render the list in the html file (via
foreach: arrayName) and it shows a complete list.
As soon as the user starts to search, the filter is applied via the
databind=“textinput: userFilter”. In addition, a KO
keyup event is added to the data-bind so the function managing the observable is run after each keyup.
Attempt 1: Using KO Observables
First, I need to plan how to set the object properties and observables.
this.favourite to my constructor, with a default value
data-binds: In a similar way to the toggle functionality for my responsive menu, I plan to toggle the css styling for the favourite icon.
this.toggleFav = ko.observable(false);
When a user clicks on the favourite icon, this will call the function
manageFav() which will switch
toggleFav observable from false to true, or true to false.
It will also activate a CSS style to change the icon e.g. from black to yellow.
Then an observable array, originally based on the
attractions array, will repopulate. Only
truthy fav observables will be pushed into this new array.
This observable array with
truthy properties will be used with a
data-bind foreachto iterate a list in the
contentString used to render the contents of the infowindow.
contentString which informs the rendering of the marker’s infowindow and it doesn’t.
Attempt 2: Using Google Maps API for ‘rightclick’ on the marker
Let’s explore the Google Maps API docs for some insight.
Second best option is to go for a
rightclick event using the Google Maps API. I don’t like the UX of the right click, although it’s a lot less clunky than a double click which I also tried but it really didn’t work at all.
I ran this to test it:
Then built up the code for a favourites list.
I added favourite to the Constructor with a
Then an if statement to toggle truthy to falsy, and vice versa.
Then I built an observable array to store the favourites, which only pushed to this array if
locationItem.favourite == true.
In the HTML
data-bind I added a
After some backwards and forwards resolving console errors, I got it working but with one bug.
The array rendered as a list and it duplicated the entries e.g.
I like venue 1; renders venue 1.
Then I like venue 2; renders venue 1, venue 1, venue 2.
After some experimenting, I realised I needed to clear the array before repopulating with venues where
locationItem = true.
That’s it. Project complete! It feels great to have built something which tested my skills so thoroughly.
My key takeaways from this project are:
- Plan but do not over plan. Better to have a good plan, then build some code which can be refactored later.
- Use the power of
console.log(). It’s an invaluable way of determining what is actually going on, if your code is doing what you think it is, and it really helped with my understanding of how my code behaves.
- Try to keep things simple. Sometimes easier said than done but strive for it nevertheless.
- Read the docs!
- Try to solve the problem yourself but after a certain point, ask for help/guidance i.e. someone experienced to point you in the right direction.
Thanks for reading. I hope this proves useful (to future me) to see how I tackled a difficult project.