All posts by Mark Pajak

How we did it: automating the retail order forms using Shopify.

*explicit content warning* this post makes reference to APIs.

THE PROBLEM:  Having set ourselves the challenge of improving the buying process  , our task in Team Digital was to figure out where we can do things more efficiently and smartly. Thanks to our implementation of Shopify, we have no shortage of data on sales to help with this, however the process of gathering the required information to place an order of more stock is time consuming – retail staff need to manually copy and paste machine-like product codes, look up supplier details and compile fresh order forms each time, all the while attention is taken away from what really matters, i.e. which products are currently selling, and which are not.

In a nutshell, the problem can be addressed by creating a specific view of our shop data – one that combines the cost of goods, with the inventory quantity (amount of stock left) in a way that factors in a specific period of time and which can be combined with supplier information so we know who to order each top selling product from, without having to look anything up. We were keen to get in to the world of Shopify development and thanks to the handy Shopify developer programme documentation & API help it was fairly painless to get a prototype up and running.

SETTING UP: We first had to understand the difference between public and private apps with Shopify.  A private app lets you hard code it to speak to a specific shop, whereas the public apps need to be able to authenticate on the fly to any shop. With this we felt a private app was the way to go, at least until we know it works!

Following this and armed with the various passwords and keys needed to programmatically interact with our store, the next step was to find a way to develop a query to give us the data we need, and then to automate the process  and present it in a meaningful way. By default Shopify provides its data as JSON, which is nice, if you are a computer.

TECHNICAL DETAILS: We set up a cron job on an AWS virtual machine running Node and MongoDB. Using the MEAN stack framework and some open source libraries to integrate with Google Sheets, and notably to handle asynchronous processes in a tidy way. If you’d like to explore the code – that’s all here. In addition to scheduled tasks we also built an AngularJS web client which allows staff to run reports manually and to change some settings.

Which translates as: In order to process the data automatically, we needed a database and computer setup that would allow us to talk to Shopify and Google Docs, and to run at a set time each day without human intervention.

The way that Shopify works means we couldn’t develop a single query to do the job in one go as you might in SQL (traditional database language). Also, there are limitations in how many times you can query the store. What emerged from our testing was a series of steps, and an algorithm which did multiple data extractions and recombination’s, which I’ll attempt to describe here. P.S. do shout if there is an easier way to do this ;).

STEP 1: Get a list of all products in the store. We’ll need these to know which supplier each product comes from, and the product types might help in further analysis.

STEP 2: Combine results of step one with the cost of goods. This information lives in a separate app and needs to be imported from a csv file. We’ll need this when we come to build our supplier order form.

STEP 3: Get a list of all orders within a certain period. This bit is the crucial factor in understanding what is currently selling. Whilst we do this, we’ll add in the data from the steps above so we can generate a table with all the information we need to make an order.

STEP 4: Count how many sales of each product type have taken place. This converts our list of individual transactions into a list of products with a count of sales. This uses the MongoDB aggregation pipeline and is what turns our raw data into something more meaningful. It looks a bit like this, (just so you know):

STEP 5: Add the data to a Google Sheet. What luck there is some open source code which we can use to hook our Shopify data up to Google. There are a few steps needed in order for the Google sheet to talk to our data – we basically have our server act as a Google user and share editing access with him, or her?. And while we are beginning to personify this system, we are calling it ‘Stockify’, the latest member of Team Digital, however Zak prefers the lofty moniker Dave.

The result is a table of top selling products in the last x number of days, with x being a variable we can control. The whole process takes quite a few minutes, especially if x >60, and this is due to limitations with each integration – you can only add a new line to a Google sheet once / second, and there are over 500 lines. The great thing about our app is that he/she doesn’t mind working at night or early in the morning, and on weekends or at other times when retail managers probably shouldn’t be looking at sales stats, but probably are. With Stockify/Dave scheduled for 7am each morning we know that when staff look at the data to do the ordering it will be an up to date assessment of the last 60 days’ worth of sales.

We now have the following columns in our Google Sheet, some have come directly from their corresponding Shopify table, whereas some have been calculated on the fly to give us a unique view of our data and on we can gain new insights from.

  • product_type: (from the product table)
  • variant_i:d (one product can have many variants)
  • price: (from the product table)
  • cost_of_goods: (imported from a csv)
  • order_cost: (cost_of_goods * amount sold)
  • sales_value: (price * amount sold)
  • name: (from the product table)
  • amount sold: (transaction table compared to product table / time)
  • inventory_quantity: (from the product table)
  • order_status: (if inventory_quantity < amount sold /time)
  • barcode: (from the product table)
  • sku: (from the product table)
  • vendor: (from the product table)
  • date_report_ru:n (so we know if the scheduled task failed)

TEST, ITERATE, REFINE:  For the first few iterations we failed it on some basic sense checking – not enough data was coming through. This turned out to be because we were running queries faster than the Shopify API would supply the data and transactions were missing. We fixed this with some loopy code, and now we are in the process of tweaking the period of time we wish to analyse – too short and we miss some important items, for example if a popular book hasn’t sold in the last x days, this might not be picked up in the sales report. Also – we need to factor in things like half term, Christmas and other festivals such as Chinese New Year, which Stockify/Dave can’t predict. Yet.

AUTOMATIC ORDER FORMS: To help staff compile the order form we used our latest Google-sheet-fu using  a combination of pick lists, named ranges and the query function to lookup all products tagged with a status of “Re-order”

A list of suppliers appears on the order form template:

and then this formula looks up the products for the chosen supplier and populates the order table:

“=QUERY(indirect(“last_60_days”&”!”&”11:685″),”select G where M='”&$B2&”‘ and J=’re-order'”)”

The trick is  for our app to check if the quantity sold in the last x days is less than the inventory quantity, in which case it goes on the order form.

NEXT STEPS: Oh we’re not done yet! with each step into automation we take, another possibility appears on the horizon…here’s some questions we’ll be asking our system in the coming weeks..

  • -How many products have not sold in the last x days?
  • -If the product type is books, can we order more if the inventory quantity goes below a certain threshold?
  • Even if a particular product has not sold in the last 60 days, can we flag this product type anyway so it gets added to our automatic order form?
  • While we are at it, do we need to look up supplier email addresses each time – cant we just have them appear by magic.

…furthermore we need to integrate this data with our CRM…..looks like we will be busy for a while longer.




Digital Curating Internship – an update

By David Wright (Digital Curating Intern, Bristol Culture)

Both Macauley Bridgman and I are now into week six of our internship as Digital Curating Assistants here at Bristol Culture (Bristol Museums) . At this stage we have partaken in a wide array of projects which have provided us with invaluable experiences as History and Heritage students (a discipline that combines the study if history with its digital interpretation) at the University of the West of England. We have now been on several different tours of the museum both front of house and behind the scenes. Most notably our store tour with Head of Collections Ray Barnett, which provided us with knowledge of issues facing curators nationwide such as conservation techniques, museum pests and the different methods of both utilisation and presentation of objects within the entirety of the museum’s collection.

pic from stores

In addition we were also invited to a presentation by the International Training Programme in which Bristol Museums is a partner alongside the British Museum. Presentations given by Ntombovuyo Tywakadi, Collections Assistant at Ditsong Museum (South Africa), followed by Wanghuan Shi, Project Co-ordinator at Art Exhibitions China and Ana Sverko, Research Associate at the Institute of Art History (Croatia). All three visitors discussed their roles within their respective institutions and provided us with a unique insight into curating around the world. We both found these presentations both insightful and thought provoking as we entered Q&A centred on restrictions and limitations of historical presentation in different nations.

Alongside these experiences we have also assumed multiple projects for various departments around the museum as part of our cross disciplinary approach to digital curating.

Our first project involved working with Natural Sciences Collections Officer Bonnie Griffin to photograph, catalogue and conserve Natural History specimens in the store. This was a privileged assignment which we have perhaps found the most enjoyable. The first hand curating experience and intimate access with both highly experienced staff and noteworthy artefacts we both found inspiring in relation to our respective future careers.

David Wright
David Wright – Digital Curating Intern

Following on from this we undertook a project assigned by Lisa Graves, Curator for World Cultures, to digitise the outdated card index system for India. The digital outcome of this will hopefully see use in an exhibition next year to celebrate the seventieth anniversary of Indian independence in a UK-India Year of Culture. At times we found this work to be somewhat tedious and frustrating however upon completion we have come to recognise the immense significance of digitising museum records for both the preservation of information for future generations and the increased potential such records provide for future utilisation and accessibility.

We have now fully immersed ourselves into our main Bristol Parks project which aims to explore processes by which the museum’s collections can be recorded and presented through geo-location technology. For the purposes of this project we have limited our exploration to well-known local parks, namely Clifton and Durdham Downs with the aim of creating a comprehensive catalogue of records that have been geo-referenced to precise sites within the area. With the proliferation of online mapping tools this is an important time for the museum to analyse how it records object provenance, and having mappable collections makes them suitable for inclusion in a variety of new and exciting platforms – watch this space!. Inclusive of this we have established standardised procedures for object georeferencing which can then be replicated for the use of future ventures and areas. Our previous projects for other departments have provided the foundation for us to explore and critically analyse contemporary processes and experiment with new ways to create links between objects within the museum’s collections.

id cards

As the saying goes “time flies when you are having fun”, and this is certainly true for our experience up to date. We are now in our final two weeks here at the museum and our focus is now fervently on completing our Bristol Parks project.

Google Drive for Publishing to Digital Signage

Having taken an agile development approach to our digital screen technology, it has been interesting as the various elements emerge based on our current needs. Lately there has been the need for quick ways to push posters and images to the screens for private events and one-off occasions.

Due to the complexity of the various modes, and the intricacies of events-based data and automatic scheduling it has been difficult incorporating these needs into the system. Our solution was to use Google Drive as a means to override the screens with temporary content. This means our staff can manage content for private events using tables and mobile devices, and watch the updates push through in real time.

The pathway of routes now looks like this

Untitled Diagram (1)


There are two main elements to the override process – firstly, we are using BackboneJS as the application framework because this provides a routing structure that controls the various signage modes. We added a new route at the beginning of the process to check for content added to Google Drive – if there is no content the signs follow their normal modes of operation.

Google Drive Integration

Google provide a nice way to publish web services, hidden amongst the scripts editor inside Google sheets. We created a script that loops through a Drive directory and publishes a list of contents as JSON –  you can see the result of that script here. By making the directory public, any images we load into the drive are picked up by the script. The screens then check the script for new content regularly. The good thing about this is that we can add content to specially named folders – if the folder names match either the venue or the specific machine name – all targeted screens will start showing that content.

Google drive integration

It seems that this form of web hosting will be deprecated in Google Drive at the end of August 2016. But the links we are using to get the image might still work. If not we can find a workaround – possibly by listing urls to content hosted elsewhere in the Google sheet and looking that up.

The main benefits of this solution are being able to override the normal mode of operation using Google Drive on a mobile device. This even works with video – we added some more overrides so the poster mode doesn’t loop till the next slide until after the video has finished – video brings in several issues when considering timings for digital signage. One problem with hosting via Google Drive is that files over 25MB don’t work due to Google’s antivirus checking warning which prevents the files being released.

We’ll wait to see if this new functionality gets used – and if it is reliable after August 2016. In fact – this mode might be usable on its own to manage other screens around the various venues which until now were not up datable. If successful it will vastly reduce the need to run around with memory sticks before private events – and hopefully let us spend more time generating the wonderful content that the technology is designed to publish for our visitors.

You can download the latest release and try it for yourself here.





Digital Object Labels

At Bristol Museums we use EMu to manage digital interpretation, and have several galleries with touchscreen kiosks displaying object narratives. We haven’t yet settled on a single technology, framework or data model as each new project gives us opportunities to test out new ideas, based on what our audiences want and on our previous learning. The refurbishment of our European Old Masters Gallery has given us the opportunity to extend the printed interpretation into digital.

(C) John Seaman, Bristol Culture
(C) John Seaman, Bristol Culture

The classic look of the gallery means label space is kept to a minimum, and this had reduced the amount of printed interpretation available on the physical labels. Digital gives our curators the opportunity to expand on the depth of interpretation by writing more detailed descriptions of paintings. Our challenge was to come up with a solution that provided in-gallery mobile digital interpretation that was easy to access and fast to load, and that made sense in context.

Taking a user-focused approach, we were keen to provide appropriate technology to the sorts of visitors to the gallery. Our audience research shows that mobile technology is a standard anong these visitors, as explained by Darren Roberts, our user researcher.

Our Audience segmentation shows that three of the Core Audience Segments for Rembrandt – City Sophisticates, Career Climbers, and Students – are all over 20% more likely than average visitors to use their mobile phone to access educational web content or apps.  All three groups are also over 20% more likely than average to agree with the statement ‘I couldn’t live without the internet on my mobile’. These three segments account for over a third of the general audience for the museum.

Ranked in order of segments that are both most likely to have an interest in Antiques and Fine Art and use their mobile phone to access free educational content or apps:

  1. Student Life

  2. Lavish Lifestyles

  3. City Sophisticates

  4. Career Climbers

  5. Executive Wealth

The top three are over 40% more likely than average visitors to engage in both these activities. All five are expected to be part of the core audience for the Rembrandt exhibition.


With this in mind, we set about analysing the printed labels – looking at where data could be brought in from our collections management system (EMu) automatically to minimise effort in writing content. As it turns out we already had most of this data (artist name, birth date, death date etc.) and so the main curatorial effort could be focused on text wiring for the labels, while we designed the template to bring the data together.


Thanks to some preliminary experiments, we already had a working framework to use – we are using AngularJS on the client side for rapid prototyping, templating, routing  and deployment.

Our next challenge was to optimise performance and maximise up-time. Having been inspired by the linked open data movement, we opted for having the data sit in structured JSON files that could be reused multiple times by various apps without querying the database directly. This had the double effect of reliability and speed. We did a similar thing with multimedia, running a regular content refresh cycle and packing everything up for the app to use, with images saved at sizes for thumbnail and detail views.

The finished template was as follows – we opted for a minimalist design for east of reading, and with responsive elements the pages work across multiple devices.

Mobile object label

The process of selecting source fields and mapping them to the template has inevitably thrown up areas where our database use could be improved, and where before we had data across many fields, now we have laid out better guidelines for object cataloguing that should ease this issue – for the app to work we needed set fields to extract information about the painting and artists.

We also had to deal with inconsistencies in terminology, for example the various ways dates could be written – on printed labels these variations are permitted, but we need to define the semantic patterns in order for this to work in digital. Now we have a workflow for improving the way we catalogue our objects as a result of this process.

Where some terms were abbreviated on the labels e.g “b” and”d”  for birth and death – we expanded these on the digital labels as space was not an issue and we also felt this was easier for users to read and understand – digital allows us to implement some of our user focused principles without disrupting the printed gallery interpretation.

Call to action

Through in-gallery user testing we found that whilst some features were obvious to us, visitors were not always getting to the bits we wanted them to see – we therefore added a call to action to make it clear what was available…

“Find out more about the objects in this gallery”

Something we are interested in finding out is how users navigate to their chosen painting. User stories and personas are one method we could use to get a better understanding of this. To facilitate various user journeys, we provide different routes to each digital label, either by searching by painting name, filtering on the artist’s name, or through browsing through the list view.

list view

Technical details:

The routing mechanism of AngularJS gave us a simple way to navigate through from the list view to the record view by altering the # parameter as follows:

List view:

Record view:

We also included some libraries for smooth page loading to improve the user experience. At this stage we don’t know whether the digital labels have a use outside the gallery, but in case the do we wanted the pictures to be zoomable, and there was a code library that allows this. N.B. this is not yet deep zoomable, but we are on the road to achieving that.

Data stuff

We want to be able to reuse our structured data on paintings and artists and their info and dates whenever new technology comes along, and so our data layer exists independently of the application, and it also sits outside our database on a  publicly accessible endpoint. If you want to use any of it, in JSON form you can take a look here:

We store lists of objects in separate index.json files here:

And for details info about an object you can load up records by their id here:

Structures and paths may change as we develop the system so apologies if these are not accessible at any point. We change bits in order to improve issues with loading time and reliability, but we aim to resolve this to a standard approach to our data layer with time.

We are also figuring out what structure out object (json) records need to contain in order to maximise their use outside of our collection management system. Where dates and places exist in several source fields, we can prioritise these on export to choose which dates are most suitable, and similarly for places.

We construct a standard object schema in JSON as a result of a scheduled content refresh script which queries the IMu api, prioritises which fields to include the and saves as a JSON…

json object

Next steps

We have implemented this in one gallery so far, and for one object type. We are now looking to roll this out to other galleries and look forward to similar challenges with different types of objects.

We are also extending the design of the prototype to bring in timelines and mapping functionality. These bring an interactive element to the experience and also provide new ways of visualising objects in time and space.

We included the TimelineJS3 library into our framework, and hooked it up to the same data powering the object labels. This provides a comparison of artists’ lives with each other, and with the paintings they produced.

We need to tweak the css a little, but out of the box it works well, thanks to the kind people at Knightlab.

Interactive artist timeline

take a look at our alpha for the digital timeline here


The project has made us rethink some of our cataloging standards – we are aligning our internal data capture and export to be better equipped to make use of new web tools for public engagement.

We have decoupled the tasks of writing label text, and reusing object data and applying narrative metadata. We also have a process that would allow new layers of interpretation to be written and published to the same application architecture, and we can present a simplified data entry process to staff for this label writing process.


Although we haven’t solved the problem of how to improve uptake of the application in-gallery, we’ll be ready when someone does. If its ibeacons that do it – and we think it might be, we can direct users to a single object label using a unique url to our digital label.

For now though it is just a trusty old url to point people to the page where they then navigate further, but we’d love to remove this barrier at some point.






Getting an archival tree-view to sort properly online

The digital team at Bristol Culture face new challenges every day, and with diverse collections come a diverse range of problems when it comes to publishing online. One particularly taxing issue we encountered recently was how to represent and navigate through an archives collection appropriately on the web.

Here’s what Jayne Pucknell, an archivist at the Bristol Record Office, has to say:

“To an archivist, individual items such as photographs are important but it is critical that we are able to see them within their context. When we catalogue a collection, we try to group records into series to reflect their provenance, and the original order in which they were created. These series or groups are displayed as a hierarchical ‘tree view’ which shows that arrangement.”

So far so good – we needed to display this tree-view online, and it just so happens there is a useful open source jquery plugin to help us achieve that, called jsTree.


The problem we found when we implemented this online, was that the tree view did not display the archive records in the correct order. The default sort was the order in which the records had been created, and although we were able to apply a sort to the records in our source database (EMu), we were unable to find a satisfactory sorting method that returned a numerical sort for the records based on their archival reference number. This is because the archival reference number is made up from a series of sub-numbers reflecting sub collections.

So this gave us a challenge to fix, and the opportunity to fix it was possible because of the EMu API and programming  in between the source database and collections online.  The trick was to write a php function that could reorder the archive tree before it was displayed.

Well, we did that and here’s a breakdown of what that function does:

The function takes 2 arguments – the archival number as a text string, and the level in the archive as an integer.

1.) split the reference number into an its subnumbers
2.) construct a new array from the subnumbers
3.) perform a special sort on the new array that takes into account each subnumber in turn

in theory that’s it – but looking at the code in hindsight there are a whole heap of complexities that would take longer to articulate here than just to past in the code, so lets make it open source and leave you to delve if you wish – here’s the code on Github

Another subtle complexity in this work is described further by Jayne:

“You may search and find an individual photograph and its catalogue entry will explain the specific content of that image, but to understand its wider context it is helpful to be able to consider the collection as a whole. Or you may search and find one photograph of interest but then want to explore other items which came in with that photograph. By displaying the hierarchy, you are more easily able to navigate your way through the whole collection.”

Because of the way our collections online record pages are built – a record does not immediately contain links to all its parents or children. This is problematic when building the archives tree as ideally we wish each node to link to the parent or child depicted. We therefore needed a way to get the link for each related record whilst constructing the tree. Luckily we maintain the tree structure in EMu via the parent field.

The solution was to query the parent field and get the children of that parent, then loop through each child record and add a node to the tree. This process could be repeated up the parents until a record with no parents was reached and this would then become the root node. Because the html markup was the same for each node, this process could be written as a set of functions:

1.) has_parent: take a record number and perfom a  search to see if it has a parent, if it does return the parent id.

2.) return_children: take a record number, search for its child records and return them as an array

2.) child_html: take an array of child records and construct the links for each in html

Taking advice from Jonathan Ainsworth from the University of Leeds Special Collections, who went through similar issues when building their online pages, we decided not to perform this recursively due to the chance of entering an infinite loop or incurring too much processing time. Instead I decided to call the functions for a set number of levels in the tree – this works as we did not expect more than seven levels. The thing to point out is that when you land on a particular record, the hierarchical level could be anything, but the programmed function to build the tree remains the same.

Here’s the result – using some css and the customisable features in jsTree we can indicate which is the selected record by highlighting. We also had to play around with the jsTree settings to enable the selected record to appear, by expanding each of its parent nodes in turn – to be honest it all got a bit loopy!


….here’s the link to this record on our Collections Online.

Hope this is of use to anyone going through similar issues – on the face of it the problem is a simple one, but as we are coming to learn in team digital – nothing is really ever just simple.




Anatomy of our Digital Signage Web App

At this stage in the development of our digital signage, we have a working release of the software in the live environment, and we are focussing on training, improvements to the design and data structure for the next version. This post is about the nuts and bolts of how the client-side app works, while it is still fresh.

Mode Schematic

Firstly, it is a single page web application – loaded upon calling index.html from a web browser.  Inside the index.html are just the basics you’d expect. The magic is all controlled via a master JavaScript file called require.js. This library is used to pull together all of the source code in the right order and makes sure files don’t get loaded twice etc. All of the content of the app is loaded and removed via a single content div in the body.

(... some bits removed...check the GitHub page for the whole lot)

  <head><title>BMGA Digital Signage</title>
     <link rel="stylesheet" href="css/styles.css"> 
     <script data-main="js/main" src="js/libs/require/require.js"/>    
  <body class="nocursor">
   <div id="mainContent" > </div></div>

The first JavaScript to load up is main.JS. This simple file follows theRequireJS format, which is used to alias some of the code libraries which will get used the most such as JQuery.



     templates: '../templates'


"app"], function(App) {

Next up is main.js. This loads up the code libraries required to start the app, and brings in our first global function – used to close each ‘view’. For a single page app it is really important to destroy any lingering event handlers and other bits which can take up memory and cause the app to go a bit crazy – something that Backbone apps have difficulties with, and otherwise known as Zombie Views. Killing Zombies is important.


], function($, _, Backbone, Router){
var initialize = function(){

  Backbone.View.prototype.close = function () { //KILL ZOMBIE VIEWS!!!!


 return { 
     initialize: initialize

It gets a bit more fun next as we call the backbone ‘router’ – and from now on I’ll only add snippets from the files, to see the lot head to GitHub. The router is what drives navigation through each of the modes that the screens can display. Each route takes its parameters from the url and so this means we can control the modes by appending the text ‘sponsors’, ‘posters’ or ‘events’ to the index.html in the browser.

In addition to the mode we can pass in parameters – which poster to display, which page of sponsors, which venue etc. This was a solution to the problem of how to remember which posters have not yet been shown. If you only wish the poster mode to last 40 seconds, but you’ve got lots of posters – you need to remember which posters come next in the sequence. Additionally as you loop through modes, you need to pass along each parameter until you are back on poster mode. This is why every route has all the parameters for venue and poster.

This slightly convoluted situation has arisen as we are using a page refresh to flip between modes and so without relying on local storage our variables are only around as long as the page lasts


 var AppRouter = Backbone.Router.extend({
 routes: { 
 'sponsors(/venue:venue)(/stick:stick)(/logo:page)(/poster:page)(/machine:machine)': 'sponsors', 
 'posters(/venue:venue)(/stick:stick)(/logo:page)(/poster:page)(/machine:machine)': 'posters', 


The code for a single route looks a bit like this and works as follows.  We start off with an option to stick or move – this allows us to have a screen stay on a particular mode. Then we look at our settings.JSON file which contains the machine specific settings for all of the signs across each venue. The machine name is the only setting help locally on the system and this is used to let each machine find their node of settings (loop times, etc.).

 app_router.on('route:posters', function(venue,stick,logoOffset,posterOffset,machine){
 var stick = stick || "move"
 var logoOffset=logoOffset||0
 var posterOffset=posterOffset||0;
 var venue = settings.location;
 var venue = venue || "ALL"
 var posterView = new PosterView({venue:self.venue,stick: stick,logoOffset:logoOffset,posterOffset:posterOffset,machine:machine,settings:settings,type: settings.eventTypes});


With all settings loaded, and filtered by machine name and the mode specified – we are ready to load up the view. This contains all of the application logic for a particular mode, brings in the html templates for displaying the content, and performs the data fetches and other database functions needed to display current events/posters…more on that in a bit

Amongst the code here are some functions used to check which orientation the image supplied is, and then cross reference that with the screen dimensions, and then check if that particular machine is ‘allowed’ to display mismatched content. Some are and some aren’t, it kinda depends. When we push a landscape poster to a portrait screen, we have lots of dead space. A4 looks OK on both but anything squished looks silly. So in the dead space we can display a strapline, which is nice, until there is only a tiny bit of dead space. Oh yep, there is some code to make the font smaller for a bit if there is just enough for a caption..etc.   ….turns out poster mode wasn’t that easy after all!

], function($, _, Backbone, posterFullScreenTemplate ,posterFullScreenTemplateLandscape,PostersCollection,Globals){

 var PosterView = Backbone.View.extend({
 el: $("#eventsList"),
  addPostersFromLocaLFile: function(){ 
 var self = this;
 self.PostersCollection = new PostersCollection({parse:true}) 
 self.PostersCollection.fetch({ success : function(data){
 $( document ).ready(function() {
 setTimeout(function() { 
 }, settings.posterMode_time * 1000);
 }, settings.posterLoop_time * 1000);
 }, dataType: "json" });
 renderPosters: function (response) { 

 if( self.posterOffset>= response.models.length){self.posterOffset=0}
 var width = (response.models[self.posterOffset].get('width'))
 var height = (response.models[self.posterOffset].get('height'))
 ImageProportion = width/height 
 //enforced orientation lock
 while(LANDSCAPE==false ){ 
 if( self.posterOffset>= response.models.length){self.posterOffset=0}
 var width = (response.models[self.posterOffset].get('width'))
 var height = (response.models[self.posterOffset].get('height'))
 ImageProportion = width/height 
 self.$el.html(self.PostertemplateLandscape({poster: response.models[self.posterOffset],displayCaption:displayCaption,miniFont:miniFont},offset=self.posterOffset,TemplateVarialbes=Globals.Globals)); 


return PosterView;

Referenced by the view is the file which acts as a database would do, called the collection, and there is a collection for each data type. The poster collection looks like this, and its main function is to point at a data source, in this case a local file, and then to allow us to perform operations on that data. We want to be able to filter on venue, and also on event type -(each machine can be set to filter on different event types)  and so below you see the functions which do this… and they cater for various misspellings of our venues just in case 🙂


], function(_, Backbone, SponsorModel){

 var PosterCollection = Backbone.Collection.extend({
 sort_key: 'startTime', // default sort key

 url : function() {
 var EventsAPI = 'data/posters.JSON'; 
 return EventsAPI
 byEventType: function(typex) { 
 filteredx = this.filter(function(box) {
 var venuetoTest = box.get("type")
 if( box.get("type")){
 venuetoTest = (box.get("type").toUpperCase())}
 return typex.indexOf(venuetoTest) !== -1;
 return new PosterCollection(filteredx);

 venueFilter: function(venue) { 

 if(venue.toUpperCase()=="M SHED"){venue = "M SHED"}
 if(venue.toUpperCase()=="BMAG"){venue = "BRISTOL MUSEUM AND ART GALLERY"}
 if(venue.toUpperCase()=="MSHED"){venue = "M SHED"}
 filteredx = this.filter(function(box) {
 var venuetoTest = box.get("venue")
 if( box.get("venue")){
 venuetoTest = (box.get("venue").toUpperCase())}
 return venuetoTest==venue ||box.get("venue")==null
 return new PosterCollection(filteredx);
 parse : function(data) { 
 return data 


 return PosterCollection;


Referenced by the collection is the model – this is where we define the data that each poster record will need. One thing to watch here is that the field names match exactly those in the data source. When backbone loads in data from a JSON file or API, it looks for these field names in the source data and loads up the records accordingly (models in backbone speak) . So once the source data is read, we populate our poster collection with models, each model contains the data for a single poster etc.


], function(_, Backbone) {

 PosterModel = Backbone.Model.extend({

 defaults: {
 category: 'exhibition',
 irn: '123456' ,
 startDate: '01/01/2015' ,
 endDate: '01/01/2015' ,
 venue: 'MSHED' ,
 caption: 'caption' ,
 strapline: 'strapline' ,
 copyright: '© Bristol Museums Galleries and Archives' 

 initialize: function(){
 //alert("Welcome to this world");
 adopt: function( newChildsName ){
 // this.set({ child: newChildsName });

 return PosterModel;


With the collection loaded with data, and all the necessary venue and event filters applied, it is time to present the content – this is where the templates come in. A template is an html file, with a difference. The poster template contains the markup and styling needed to fill the screen, and uses the underscore library to insert and images into the design.

/*posterFullScreenTemplate_1080x1920.html */


    color: #BDBDBD;
    position: relative;
    margin-top: 40px;
  /*padding-left: 20px;*/

    font-weight: bold;
    font-size: 51.5px;
    line-height: 65px;

   font-size:35 !important;
   line-height:1 !important;



<div id="sponsorCylcer"> 
 var imageError= TemplateVarialbes.ImageRedirectURL+ poster.get('irn') + TemplateVarialbes.ImageSizePrefix
 var imageError= TemplateVarialbes.ImageRedirectURL+poster.get('irn') + TemplateVarialbes.ImageSizePrefix 
 <div id="poster_1" class="">
 <img onError="this.onerror=null;this.src='<% print(imageError) %>';" src="images/<%= poster.get('irn') %>.jpg" />
 <div id="imageCaption"> <%= poster.get('caption') %><br> <%= poster.get('copyright') %></div>

 <% if (poster.get('type').indexOf("poster") !== -1 && displayCaption==true){ %>
 <div id="datesAndInfo">
 <h1>from <%= poster.get('startDate') %> till <%= poster.get('endDate') %></h1>

 <%} else{ 
 if ( displayCaption==true){ 

 <div id="caption">
 <div class="captionText <% if( miniFont!=false){print(miniFont)} %>" > <%= poster.get('strapline').replace(/(?:\r\n|\r|\n)/g, '<br />') %> </div>
 <%} } %>

 <% if (poster.get('type').indexOf("poster") !== -1 && displayCaption==true){ %>
<div id="datesAndInfo">
<h1>from <%= poster.get('startDate') %> till <%= poster.get('endDate') %></h1>

<%} else{ 
if ( displayCaption==true){ 

<div id="caption">
<div class="captionText <% if( miniFont!=false){print(miniFont)} %>" > <%= poster.get('strapline').replace(/(?:\r\n|\r|\n)/g, '<br />') %> </div>
<%} } %>

Once the template is loaded, the poster displays, and that’s pretty much job done for that particular mode, except that we want posters to be displayed on a loop, and so the view reloads the template every x seconds depending on what has been set for that machine using the digital signage administration panel. A master timer controls how long the poster loop has been running for and moves to the next mode after that time. Additionally a counter keeps a note of the number of posters displayed and passes that number across to the next mode so when poster mode comes back round, the next poster in the sequence is loaded.


folder structureUsing the require backbone framework for the application has kept things tidy throughout the project and has meant that extending new modes and adding database fields is as hassle free as possible. It is easy to navigate to the exact file to make the changes – which is pretty important once the app gets beyond a certain size. Another good thing is that bugs in one mode don’t break the app, and if there is no content for a mode the app flips to the next without complaining – this is important in the live environment where there are no keyboards in easy reach to ‘OK’ any error messages.



Furthermore the app is robust – we have it running on Ubuntu, Windows 7 [in Chinese], and a Raspberry PI, and it hasn’t crashed so far. Actually if it does its job right, the application architecture  won’t get noticed at all (which is why I am writing this blog)  – and the content will shine through…. one reason I have avoided any scrolling text or animations so far – posters look great just as they are, filling the screen.

Now that our content editors are getting to grips with the system, we are starting to gather consensus about which modes should be prominent, in which places – after all if you have different modes, not every visitor will see the same content – so it there any point in different modes?  Let the testing commence!



Thanks to Thomas Davis for the helpful info at and Andrew Henderson for help Killing Zombies.






We recently launched a system for service-wide digital signage across multiple devices, operating systems, screen sizes and screen orientations. I developed the solution with flexibility as a priority to allow us to adapt as new situations and requirements arise. In practise, going live was the best form of testing and we continue to tweak the signs based on their position, content and user needs.

If there was a take home message from this process, it is not to underestimate the amount of variables in even the simplest form of display. That’s to say, if the system is to be flexible, then these variables need to be made available to the administrators to tinker with, without the need for them to change the source code. This calls for an administration system specifically designed for the purpose of managing the variables for the digital displays, which I have called the DIGITAL SIGNAGE ADMINISTRATION PANEL

Here’s an overview of the process by which content pushes through to the signs:


Digital Sign Administration


The interface is a basic HTML table displaying a list of each digital sign and the sign specific settings. Each sign is given a name which is used by the client machines to choose settings applicable to them on power up. The location is used to change the overall look and branding of the signs at different buildings. Then follows a series of time settings which control how long each mode is displayed for. The signs flip between sponsor, poster and events list modes. In order to control the sorts of content to display on each sign, for example to restrict one sign to just display exhibition details we use a comma separated list of event types which match those used in the content management system (EMu). To keep a handle on which settings relate to which machine, a comments field allows us to make notes on this – even with just 3 identical machines deployed, it is a useful reminder to know which is which in case we wish them to behave differently in future.

Panel interface In addition to the settings displayed are some hidden columns which contain further settings, such as the urls of the various APIs used to harvest data, which could one day change. These hidden settings are made available to be edited at the click of a button.


To prevent accidental changes being made to the table, users must click the padlock icon and enter a password. Then all data in the table becomes editable, and changes are fed back in real time to be stored on the server. To allow users to see the effects of their changes on the content of each machine, the machine names become links which navigate to a web page which emulates that particular digital sign.


As part of the scheduled content update, the sign settings are extracted from MYSQL and saved as a JSON text file. A similar additional file is required to store the arrow settings. As each digital screen knows its name and it can access the settings by matching its machine name with the relevant node in the settings.JSON file.



One of the biggest challenges in the solution was the requirement to build in a system of wayfinding arrows for each event. Not only does each arrow need to be configured for each room location, but each digital sign is in a different location and so the problem is compounded. This called for an entity relation between the event spaces and the digital signs. As we are using MYSQL to store the sign settings, I added a new table in the database specifically to handle the arrows, and because each sign had multiple events, and each event can have multiple arrow directions depending on the sign location, we needed an additional interface to allow us to configure these settings.

To do this I extended the framework used to build the administration panel to include another panel for the arrows:

Arrow settings

A nifty JavaScript plugin ( allowed me to incorporate the wayfinding icons into a dropdown list to make it easy for administrators to change the settings:

icons in dropdown


The administration panel is built using the Backbone JavaScript framework, and with RequireJS to manage the dependencies. This allows for easy extendibility, for example to incorporate the arrow way finding system.

Folder structure

Backbone’s model syncing methods also make it more straightforward to add new settings as new requirements arise and to match these with the database and perform updates:

backbone sync


A PHP script on the server listens out for updates from the admin panel and saves these into MYSQL. The same script returns the new settings in JSON and it is this that is used to refresh the admin panel once changes have been made, and also to make the settings available to the scripts involved in updating the content.

The next steps for this are to include icons for upstairs and downstairs, as I have observed museum visitors reading the up symbol to mean directly ahead when in actual fact it was meant to direct people to the upper level.

NB: as ever, the devil is in the detail and far more logic for this application has been baked into the source code than could be practicably explained here , and so we hope to release the digital signage administration panel on GitHub once this development phase is over.





4.2 Testing the Hidden Museum app by Mark Pajak

I’m Mark Pajak, a documentation officer for the Bristol Culture service. I have just tested the hidden museum app before starting work today. This is my first experience with the app so its all new and I have no preconceptions to cloud my first impressions of it.


A simple and colourful ‘oversized’ design was very easy to navigate with big buttons.


I didn’t read any instructions except for those written inside each button, so following the steps the app wanted me to take was straightforward. In some cases real life got in the way of my game play, such as an impromptu meeting, but I can’t fault the app for not knowing the museum was closed before 10 so the upper gallery was locked – or can I??


Yes, as a VERY regular museum visitor I am fairly locked into a routine so anything out of the ordinary is novel, and there are still many galleries I rarely visit – so a random object hunt was fun, and cut through the usual formalities of gallery interpretation & object arrangement to surprise me. not just with an object, but with new information about something I would normally not stop to look at.


There was a lag on the scrolling when picking an avatar, other than that I didn’t detect anything the app looked like it wasn’t supposed to be doing.

Other stuff

It took a while to realise the app could tell which direction I was pointing in, though with hindsight my iPhone can do that so that’s just what they do,  which led me to consider how and why it might use that information,  and it gave a certain ‘big brother’ feeling, but doesn’t everything these days? Also I have a slight aversion to taking photos with an ipad, but that’s just me :).


I could imagine someone wanting to chose a different object just because they arent fussed about climbing many stairs, but I guess that’s where kids come in – the challenge of winning the game is probably enough to get feet moving.


Simple, quick, attractive and fun – which is impressive and means there are some clever things going on ‘behind the scenes’, or at least that’s my preconception.

Supporting evidence for milestone 4.2 – informal user testing



Developing a Prototype Digital Signage Application



We are soon to upgrade digital signage across various museum sites, and my role has been to develop the various software mechanisms to gather and display the data for our prototypes. This is a brief post about how our prototype currently works. As a bit of background our legacy signage is based on flash which, although pretty and robust under certain circumstances, has several limitations making it no longer a valid option.

Use Cases

The software would be used by both museum staff wishing to publish events, and users who need to access information about the timings and locations of events. We also have other uses such as those wishing to display messages from sponsors or front of house staff.

Client Side

We chose to implement the signs in html/JavaScript as we already had a working model for doing this which could be adapted, and this would give us the most flexibility and control for future developments. I decided to use the Backbone JavaScript framework to organise the application because of the way it would allow different templates to be used for our different designs, and also because of the way the sign data could be defined and extracted from various sources before being published. This would allow us to be flexible about which systems we use to manage the data – some of these are still in specification, and so we have the option to change data sources quite easily in future. I also used the RequireJS plugin to manage the various other plugins and dependencies we may encounter during development. With this framework and application structure in place before work began it made building the application fairly straightforward and the modular design means we can troubleshoot effectively and adapt the designs easily in future.

Server Side

Because we already use the Events Module of the KE EMu Collections Management Software to manage the exhibition object and multimedia workflow, most of the data we wanted to publish to the signs already exists as event records – so we just needed a way to publish this straight from EMu. I developed a PHP API which returns a JSON list of events (title, description, dates, etc.) which can be accessed over Wi-Fi (hopefully!). To make the system more robust we also wanted the data and images to be held locally on the digital signs, so we also needed another way to send and store the data. I adapted the API to also save the events list to a file which could be stored locally on the signs to achieve this. Similarly for the multimedia this also needed to be saved locally in case of the Wi-Fi going down. To make life easier for staff we have commissioned a new tab in EMu specifically for digital signage – this brings together just the fields used to manage and display sign data, but it also means we can harness records that already exist in the system, in keeping with the ‘Create Once, Publish Everywhere’ ethos.

Additionally I also wanted to open up other options for source data to go to the signs, for staff that would not normally have access to our collections database, so I developed an API in Google application script to allow us to manage and publish data using a Google Docs spreadsheet, if needed.

Update Scripts

We needed a mechanism to transfer the application and its content over to the signs to be held locally. Our digital team were experimenting with Ubuntu for the sign OS so I built the data loader engine using Linux shell scripts. These scripts would download a zipped version of the software on power up, and unzip the files. This would also allow us to carry out upgrades to fix bugs and improve the design during testing. I decided to use a switch, contained in a settings file which could be used to control whether the whole sign application got updated, or just the images and text to be displayed. This way I can update signs individually for testing new releases. These settings would also control which mode the sign was in – so we can specify landscape vs portrait, or which museum building the sign was in so the branding could be adjusted. This settings file would have to live outside of the main application in order for us to use one app for all signs, and this process would need to be documented in the installation instructions.

So, the update scripts had logic for upgrading, or updating the sign data as well as some failsafe code in case of only a partial download or no internet connection. The various update scripts were controlled by a master script which would be set to run each time the sign was powered on, and this would also start Chrome in full screen kiosk mode with the various parameters for local file access and other bits.


I used Chrome Dev tools to build the front end, working from a design supplied by our in house team. As the signs are pretty large and tall the Chrome screen emulator helped to get the proportions right. We decided not to go with a responsive design because tests had already showed problems with css media queries when connecting to digital screens, also there was not any use cases for small screens, and again our framework makes different designs easy to implement in the same app. The main issue so far with the designs is not knowing how many events records there will be on any one day, and so we don’t yet know if we will have to scroll / rotate the records, or if we will have trouble filling all the slots.  For testing though I added some code to beef up the records in case there were not enough to fill each entry. The html was fairly simple – just a table and an image, but this was getting created from the source data using Underscore, a prerequisite of Backbone. The designs also specified images to fade in and out on rotation to represent the events, but not all events would have images, so I used a separate template and Backbone collection for images – this means the system won’t crash if not all events have images, (unlike our legacy flash software).

Further Information

Here’s a link to the latest release of the software on GitHub

Next steps

To work with team digital to refine and test the installation process, and see what our users think.




Running Google Chrome in Kiosk Mode – Tips, Tricks and Workarounds

We are using Google Chrome to publish collections based information and multimedia to the galleries in M Shed using a web application. Here are a few pointers which have helped us get the system up and running

How to run chrome in kiosk mode?

Kiosk screenshot

Google Chrome comes with a built in kiosk mode which makes it load up as a full screen browser and without the usual menu bars and features that would normally let you navigate away from or close down the app. There are various ways you can do this, all of which involve tagging on the –kiosk argument to the command that starts Chrome. N.B. don’t try this out this unless you can CRT-ALT-DEL out of it! The script below can be saved as a .bat file in windows, run from the command line, or the extra arguments can be inserted into the proprties of the Chrome icon used to load up the application.

The following batch script loads up chrome in kiosk mode at a specific page:

start “Chrome” chrome file:///C:/Kiosk2014/CaseLayout.htm?KIOSK=LB-DS-ICT02 –kiosk

So far so good, but there are lots of reasons why this alone is not sufficient for the gallery environment. Here are some problems you may run in to, and the workarounds we have found….

1.) Chrome comes with an array of shortcut key combinations that let you access its hidden features, such as the chrome task manager (shift + ESC) and downloads (Crt + J). This means that if your gallery pcs have keyboards then users may be able to hack their way out of chrome and into the PC or off into the web (why a gallery keyboard would have shift and escape keys is beyond me, but ours do). To prevent this problem needs a minimal amount of JavaScript to catch the key press events and convert them to nothing before they are passed to the browser for interpretation. Here’s what is working for us right now:

$(document).keydown(function(e){ //when any key is pressed

if(e.keyCode == 27||e.keyCode ==18) { // if the key is CTRL or ALT

e.preventDefault(); } }); // do nothing

2.) How to run a website saved on the local file system? By default Chrome wont access scripts in files held locally (although Firefox will). Our kiosk applications are all held locally on each machine as a safety precaution in case of network downtime. To overcome the default behaviour in Chrome add the following argument to the startup command above


So the command we now have is:

start “Chrome” chrome file:///C:/Kiosk2014/CaseLayout.htm?KIOSK=LB-DS-ICT02 –kiosk –allow-file-access-from-files

3.) When the machine is rebooted or you force quit chrome, it restarts with the message “Chrome didn’t’ shut down correctly” in a yellow bar at the top of the screen which must be manually closed. This is unsightly for users and likely to be a common occurrence in the gallery environment. To overcome this we pass another parameter to the start command which causes Chrome to start in incognito mode and prevents the message. So our command is now this:

start “Chrome” chrome file:///C:/Kiosk2014/CaseLayout.htm?KIOSK=LB-DS-ICT02 –kiosk –allow-file-access-from-files –incognito

4.) When the web application crashes for whatever reason, there may be no way for a user to reload the page, or for the page to reload itself automatically. There are many different sorts of crashes that can occur, such as the ‘aw snap’ error where Chrome gives an unsmily face and a link to reload the page or navigate away off into the net. Since fixing some bugs and optimising our code we have not seen this error for a while, but we do have a method to return to the web app if something goes wrong. One method is to use windows task scheduler to close and reopen the web app after a set period of time, and handily we already have most of the code for this in the above command. We set the task to be triggered when the computer is idle for 5 minutes, and make the task run the following code – which kills any running chrome process and restarts the app at the right page:

@echo off

taskkill /F /IM chrome.exe /T

start “Chrome” chrome file:///C:/Kiosk2014/CaseLayout.htm?KIOSK=LB-DS-ICT02 –kiosk –allow-file-access-from-files –incognito

Incidentally this is the exact same script we have in our start up folder so that whenever the computer is rebooted, the application starts.

5.) Despite the above, we were still suffering from a ‘grey screen of death’ every once in a while, which loaded the background page in the right colour, but failed to load anything else. This was probably due to the complexity of the application and its various plugins, but it was very undesirable and almost impossible to replicate in our development environment. What was clear was that when the grey screen happened, none of the JavaScript files for the app had been loaded, rendering it useless and stuck. The workaround we have used for this was to bind an onClick event to the document body which forces a page reload, and to remove this event using JavaScript. This means that if the script files fail to load, the page will reload when someone touches it, and chances are everything will be ok, and if everything is ok – the reload click event is removed and the application functions normally.

So, at the top of the document we have this:

<body onClick=”location.reload()”>

and right before the closing body tag we have this:

<script type=”text/javascript”> $(function() { setTimeout(function () { if($(“#VisitorStoriesHelpText”).length>0){ $(‘body’).attr(“onClick”,””) } }, 1 * 1 * 1000);}); </script>

…..actually we haven’t seen the grey screen of death for a while, but a least it is no longer a show stopper.

So in conclusion – Google Chrome can be run in full screen mode as a gallery kiosk application, but it is not plain sailing, and in the gallery environment expect to see strange things happening. We are not out of the woods yet, and the legacy hardware keeps us on our toes, but at least in terms of the web application we can overcome many of the issues that this solution has presented us with.



We have seen on a number of machines some strange artefacts, pixellation and tearing occurring – when coloured spekles appear, or portions of black screen, in some cases the whole machine becomed unresponsive.  This only happens in the live environment and may be something to do with chrome kiosk mode vs windows 7. To solve this I have added another flag to the chrome command – –disable-gpu. With this added the pixellation goes away, it returns when the flag is removed. Time will tell if this solution holds water, or if it puts too much load on the cpu. We still have room for optimisation (using chrome dev tools) so we should be able to reduce the load if problems persist.