From 1cf2696289c02e492f9b3e66b8d97b06afad5bc6 Mon Sep 17 00:00:00 2001 From: "Dave St.Germain" Date: Mon, 7 Apr 2014 19:06:54 -0400 Subject: [PATCH] Added analytics event logging to PDF book viewer --- lms/djangoapps/staticbook/views.py | 27 +++++++- lms/static/js/pdf-analytics.js | 102 +++++++++++++++++++++++++++++ lms/templates/pdf_viewer.html | 12 ++-- lms/templates/static_pdfbook.html | 20 +----- 4 files changed, 136 insertions(+), 25 deletions(-) create mode 100644 lms/static/js/pdf-analytics.js diff --git a/lms/djangoapps/staticbook/views.py b/lms/djangoapps/staticbook/views.py index 9ebee3c355..593ab248d5 100644 --- a/lms/djangoapps/staticbook/views.py +++ b/lms/djangoapps/staticbook/views.py @@ -80,25 +80,46 @@ def pdf_index(request, course_id, book_index, chapter=None, page=None): raise Http404("Invalid book index value: {0}".format(book_index)) textbook = course.pdf_textbooks[book_index] - if request.GET.get('viewer','') == 'true': - return render_to_response('pdf_viewer.html') + viewer_params = '&file=' + current_url = '' if 'url' in textbook: textbook['url'] = remap_static_url(textbook['url'], course) + viewer_params += textbook['url'] + current_url = textbook['url'] + # then remap all the chapter URLs as well, if they are provided. + current_chapter = None if 'chapters' in textbook: for entry in textbook['chapters']: entry['url'] = remap_static_url(entry['url'], course) + if chapter is not None: + current_chapter = textbook['chapters'][int(chapter) - 1] + else: + current_chapter = textbook['chapters'][0] + viewer_params += current_chapter['url'] + current_url = current_chapter['url'] + + if page is not None: + viewer_params += '&page={}'.format(page) + + if request.GET.get('viewer','') == 'true': + template = 'pdf_viewer.html' + else: + template = 'static_pdfbook.html' return render_to_response( - 'static_pdfbook.html', + template, { 'book_index': book_index, 'course': course, 'textbook': textbook, 'chapter': chapter, 'page': page, + 'viewer_params': viewer_params, + 'current_chapter': current_chapter, 'staff_access': staff_access, + 'current_url': current_url, }, ) diff --git a/lms/static/js/pdf-analytics.js b/lms/static/js/pdf-analytics.js new file mode 100644 index 0000000000..1a020eee72 --- /dev/null +++ b/lms/static/js/pdf-analytics.js @@ -0,0 +1,102 @@ +function sendLog(name, data, event_type) { + var message = data || {}; + message.chapter = PDF_URL || ''; + message.name = "textbook.pdf." + name; + Logger.log(event_type ? event_type : message.name, message); +}; + +// this event is loaded after the others to accurately represent the order of events: +// click next -> pagechange +$(function() { + var first_page = true; + var scroll = {timeStamp: 0, direction: null}; + + $(window).bind("pagechange", function(event) { + // log every page render + var page = event.originalEvent.pageNumber; + var old_page = PDFView.previousPageNumber; + // pagechange is called many times per viewing. + if (PDFView.previousPageNumber !== page || first_page) { + first_page = false; + if ((event.timeStamp - scroll.timeStamp) < 50) { + sendLog("page.scrolled", {"page": page, "direction": scroll.direction}); + } + sendLog("page.loaded", {"type": "gotopage", "old": old_page, "new": page}, "book"); + scroll.timeStamp = 0; + } + }); + + $('#viewerContainer').bind('DOMMouseScroll mousewheel', function(event) { + scroll.timeStamp = event.timeStamp; + scroll.direction = PDFView.pageViewScroll.down ? "down" : "up"; + }); +}); + +$('#viewThumbnail,#sidebarToggle').on('click', function() { + sendLog("thumbnails.toggled", {"page": PDFView.page}); + }); + +$('#thumbnailView a').live('click', function(){ + sendLog("thumbnail.navigated", {"page": $('#thumbnailView a').index(this) + 1, "thumbnail_title": $(this).attr('title')}); +}); + +$('#viewOutline').on('click', function() { + sendLog("outline.toggled", {"page": PDFView.page}); + }); + +$('#previous').on('click', function() { + sendLog("page.navigatednext", {"type": "prevpage", "new": PDFView.page - 1}, "book"); + }); + +$('#next').on('click', function() { + sendLog("page.navigatednext", {"type": "nextpage", "new": PDFView.page + 1}, "book"); + }); + +$('#zoomIn,#zoomOut').on('click', function() { + sendLog("zoom.buttons.changed", {"direction": $(this).attr("id") == "zoomIn" ? "in" : "out", "page": PDFView.page}); + }); + +$('#pageNumber').on('change', function() { + sendLog("page.navigated", {"page": $(this).val()}); + }); + +var old_amount = 1; +$(window).bind('scalechange', function(evt) { + var amount = evt.originalEvent.scale; + if (amount !== old_amount) { + sendLog("display.scaled", {"amount": amount, "page": PDFView.page}); + old_amount = amount; + } +}); + +$('#scaleSelect').on('change', function() { + sendLog("zoom.menu.changed", {"amount": $("#scaleSelect").val(), "page": PDFView.page}); +}); + +var search_event = null; +$(window).bind("find findhighlightallchange findagain findcasesensitivitychange", function(event) { + if (search_event && event.type == 'find') { + clearTimeout(search_event); + } + search_event = setTimeout(function(){ + var message = event.originalEvent.detail; + message.status = $('#findMsg').text(); + message.page = PDFView.page; + var event_name = "search"; + switch (event.type) { + case "find": + event_name += ".executed"; + break; + case "findhighlightallchange": + event_name += ".highlight.toggled"; + break; + case "findagain": + event_name += ".navigatednext"; + break; + case "findcasesensitivitychange": + event_name += "casesensitivity.toggled"; + break; + } + sendLog(event_name, message); + }, 500); +}); diff --git a/lms/templates/pdf_viewer.html b/lms/templates/pdf_viewer.html index 30209b1b2b..ce25fe5987 100644 --- a/lms/templates/pdf_viewer.html +++ b/lms/templates/pdf_viewer.html @@ -23,15 +23,13 @@ http://sourceforge.net/adobe/cmap/wiki/License/ - PDF.js viewer + ${current_chapter['title'] if current_chapter else '' |h} - - @@ -41,8 +39,11 @@ http://sourceforge.net/adobe/cmap/wiki/License/ PDFJS.imageResourcesPath = '${static.url('/static/css/vendor/pdfjs/images/')}'; PDFJS.workerSrc = '${static.url('/static/js/vendor/pdfjs/pdf.worker.js')}'; PDFJS.cMapUrl = '${static.url('/static/css/vendor/pdfjs/cmaps/')}'; +PDF_URL = '${current_url | h}'; - +<%static:js group='main_vendor'/> +<%static:js group='application'/> +<%static:js group='courseware'/> @@ -401,7 +402,8 @@ PDFJS.cMapUrl = '${static.url('/static/css/vendor/pdfjs/cmaps/')}'; - + + diff --git a/lms/templates/static_pdfbook.html b/lms/templates/static_pdfbook.html index 8734758222..da70745b3f 100644 --- a/lms/templates/static_pdfbook.html +++ b/lms/templates/static_pdfbook.html @@ -22,6 +22,7 @@ $(function(){ 'title': $(this).text() }); $('#viewer-frame').focus(); + Logger.log("textbook.pdf.chapter.navigated", {"name": "textbook.pdf.chapter.navigated", "chapter": url, "chapter_title": $(this).text()}); }); }); @@ -39,26 +40,11 @@ $(function(){ %endif -<% -params = '&file=' -label = "" -if 'url' in textbook: - params += textbook['url'] -elif chapter is not None: - chap = textbook['chapters'][int(chapter) - 1] -else: - chap = textbook['chapters'][0] -params += chap['url'] -label = chap['title'] - -if page is not None: - params += '&page={}'.format(page) -%>