Skip to content
KnockoutJS app for Joseph, Oregon's downtown bronze sculpture art walk
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Type Name Latest commit message Commit time
Failed to load latest commit information.

Joseph Art Walk

This app uses Knockout.js, the Joseph Art Walk API, and the Google Maps API to show the locations and some background information on the bronze statues and art galleries along Main Street in Joseph, Oregon.

» Click here for a live demo


  1. Download this zip file or clone the git repository.
  2. Open index.html in your browser.



Why KnockoutJS?

KnockoutJS works on the MVVM design pattern where the UI is separated into three parts:

  1. The model consists of the list of gallery and statue locations with artist info, verbose location, latitude and longitude, title, and other supporting information.

  2. The view consists of the UI in the webpage seen by the end user, namely the map object and its properties (size, popup info windows, markers), a list of locations in text form, and a search box and dropdown menu for the user to filter that list.

  3. The view-model binds the model and view so the user can make updates to the model through the view's search box an dropdown menu; any updates to the model are immediately reflected in the view for the user to see.

Feature Requirements


The call to the Joseph Art Walk API returns a list of locations, which are stored in the global locations object and used to instantiate the ViewModel.

Example Property Example Value
artist "Ramon Parmenter"
artistURL ""
location "in front of The Dog Spot"
lat 45.35149
lng -117.229985
corner "1st St and Main St"
medium "bronze"
title "Garden Walk"
imgSrc ""
imgAttribution ""
imgLicense ""
imgLicenseLink ""
arttype "statue"


The page is a responsive, mobile-first design.

This is a single page application.

The color palette was generate from Material-UI:

Material UI Color Palette

The font is Roboto from Google Fonts.

The user interface items in the view are:

  • Header with centered title
  • Section for app introduction
  • Google map to display markers
  • Search box
  • Dropdown menu
    • All
    • Galleries
    • Statues
    • None
  • Unordered list of locations
  • Footer


The map must be no more than the height and width of the screen. The user must be able to continue scrolling on the page and not get hung up in the map. Whenever it is updated, the map must update its bounds.

Clicking on a map marker must show an infoWindow.

The list of all locations must be displayed on a vertical list.

Tapping on a marker or on a list item must activate that specific marker animation and show the info window of that marker in the map window.

Tapping again on the same marker or list item must activate the animation again and hide the info window of that marker in the map window.

The search box must, with every typed character, filter the visible map markers and the list of items in the locations list.

The dropdown menu must also filter the visible map markers and the list of items in the locations list.

There must be an additional graphic to reset all inputs.


commit #eed94b0: feat: Add basic html and css [view]

Commit #eed94b0

commit #752ea83: feat: Create and bind markers list [mv]

Commit #752ea83

The observable arrays in Knockout are only observable at the level of the array themselves, so the View-Model will know to re-render the array if an element is added or removed, but not if an element is changed. The elements themselves are not observable. The trick is a somewhat dirty hack from this StackOverflow question.

commit #22c3721: feat: Manage observable markers array, update list

Commit #22c3721

The displayed list of statues and galleries now refreshes smoothly with every keystroke in the search box.

commit #bd8f273: feat: Add and bind the search box

Commit #bd8f273

Once the two asynchronous API calls are working, the blank map is visible and the rest of the functions are prototyped to return strings to console.log() and the program architecture is finalized.

Both calls are asynchronous, but the Google Maps API callback initMap binds the ViewModel, which itself calls the Joseph Art Walk API. If the Maps API call fails, though, the rest of the app never loads, which is a problem.

commit #fa02e10: refactor: Prototype remaining functions

Commit #fa02e10

Initializing the list of markers, which now lives in AppViewModel, happens now in its own separate function, initMarkers, that creates each marker member with everything needed by both the filtered list and the map.

mapMarker = new google.maps.Marker({
 animation: marker.animation,
 arttype: marker.arttype,
 desc: marker.description,
 imgSrc: marker.imgSrc,
 imgAlt: marker.imgAlt,
 position: marker.position,
 title: marker.title,
 url: marker.url,
 visible: marker.visible()

It's an ongoing struggle to get KnockoutJS to observe the 'visible' tag in the list of markers, so for the moment the hacky refresh technique to refresh the entire array is in place. The updateMarkers function is called by each keystroke in the search box or by a selection in the dropdown menu, and all it does is set the visible property on each marker accordingly.

commit #880223a: feat: Filter map and list

Commit #880223a

The filtered list looks much better after applying CSS display: grid properties and adding a placeholder image until we can grab the iamges from the Joseph Art Walk API. The filtered list doesn't look good on mobile yet, but that will be easy to fix by applying mobile queries to the filtered list grid.

commit #a9de359: feat: Add CSS grid to filtered list

Some internal refactoring of self/this and removing logging statements makes the code cleaner. The log statements were used to examine the marker object as it was manipulated by different user inputs.

commit #73728a0: refactor: Fix this/self, remove console.log

Because users might be entering text to the search box on a laptop or on their phones, it's best to make the search results case-insensitive. This is done by comparing the lower case marker title to the lower case search string.

if (marker.title.toLowerCase().includes(op.toLowerCase())) {
	marker.visible = true;
} else {
	marker.visible = false;

commit #4fbffb4: feat: Make search case-insensitive

Commit #4fbffb4

You can’t perform that action at this time.