Mercurial > hg > orthanc-stone
view Applications/StoneWebViewer/WebApplication/index.html @ 1687:cc5a2e076746
Added error check to emscripten_get_element_css_size
author | Benjamin Golinvaux <bgo@osimis.io> |
---|---|
date | Wed, 25 Nov 2020 17:02:43 +0100 |
parents | 84fe7089ccaa |
children | 59fc37c10cfa |
line wrap: on
line source
<!doctype html> <html class="wv-html"> <head> <title>Stone Web Viewer</title> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" /> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" /> <meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <link rel="icon" href="data:;base64,iVBORw0KGgo="> <link rel="stylesheet" href="css/all.css"> <!-- Font Awesome --> <link rel="stylesheet" href="css/bootstrap.css"> <link rel="stylesheet" href="open-sans.css"> <link rel="stylesheet" href="app.css"> <!-- https://stackoverflow.com/a/16863182/881731 --> <style> .tooltip { position: fixed; } </style> <!-- Fix if Bootstrap CSS is not used --> <!--style> *, *::before, *::after { box-sizing: border-box; } </style--> </head> <body class="wv-body"> <div id="wv"> <div class="wvLoadingScreen" v-show="!ready"> <span class="wvLoadingSpinner"> <div class="bounce1"></div> <div class="bounce2"></div> <div class="bounce3"></div> </span> </div> <div class="fluid-height fluid-width" v-show="ready"> <div class="wvWarning wvPrintExclude" v-show="modalWarning"> <div class="wvWarning-content clearfix"> <span class="wvWarning-text"> <h2 class="mb10"><i class="fa fa-exclamation-triangle wvWarning-icon mr5"></i>Warning!</h2> <p class="mn mb10" style="color:#000"> You browser is not supported. You might expect inconsistent behaviours and must not use the viewer to produce a diagnostic. </p> </span> </div> <div class="text-right mb10 mr10"> <button class="btn btn-primary" @click="modalWarning=false">OK</button> </div> </div> <div class="wvWarning wvPrintExclude" v-show="modalNotDiagnostic"> <div class="wvWarning-content clearfix"> <span class="wvWarning-text"> <h2 class="mb10"><i class="fa fa-exclamation-triangle wvWarning-icon mr5"></i>Warning!</h2> <p class="mn mb10" style="color:#000"> The Stone Web Viewer is intended for <b>patients reviewing their images<b>, for research purpose and for quality assurance. </p> <div class="form-check"> <input class="form-check-input" type="checkbox" v-model="settingNotDiagnostic" id="checkboxNotDiagnostic1"> <label class="form-check-label" for="checkboxNotDiagnostic1" style="color:#000;display:inline"> Show this warning at startup </label> </div> </span> </div> <div class="text-right mb10 mr10"> <button class="btn btn-primary" @click="modalNotDiagnostic=false">OK</button> </div> </div> <div class="wvWarning wvPrintExclude" v-show="modalPreferences"> <div class="wvWarning-content clearfix"> <span class="wvWarning-text"> <h2 class="mb10">Preferences</h2> <div class="form-check"> <input class="form-check-input" type="checkbox" v-model="settingNotDiagnostic" id="checkboxNotDiagnostic2"> <label class="form-check-label" for="checkboxNotDiagnostic2" style="color:#000;display:inline"> Warn about diagnostic usage at startup </label> </div> <div class="form-check"> <input class="form-check-input" type="checkbox" v-model="settingSoftwareRendering" id="checkboxSoftwareRendering"> <label class="form-check-label" for="checkboxSoftwareRendering" style="color:#000;display:inline"> Use software rendering (will reload the viewer) </label> </div> </span> </div> <div class="text-right mb10 mr10"> <button class="btn btn-primary" @click="ApplyPreferences()">OK</button> </div> </div> <div class="wvLayoutLeft wvLayoutLeft--closed" v-show="!leftVisible"> <div class="wvLayoutLeft__actions--outside" style="z-index:10"> <button class="wvLayoutLeft__action button__base wh__25 lh__25 text-center" @click="leftVisible = true"> <i class="fa fa-angle-double-right"></i> </button> </div> </div> <div class="wvLayoutLeft wvPrintExclude" v-show="leftVisible" v-bind:class="{ 'wvLayoutLeft--small': leftMode == 'small' }" > <div class="wvLayoutLeft__actions" style="z-index:10"> <button class="wvLayoutLeft__action button__base wh__25 lh__25 text-center" @click="leftVisible = false"> <i class="fa fa-angle-double-left"></i> </button> </div> <div class="wvLayoutLeft__content"> <div class="wvLayoutLeft__contentTop"> <div class="float__left dropdown" style="max-width: calc(100% - 4.5rem); height:4.5rem !important" v-show="leftMode != 'small'"> <button type="button" class="wvButton--border" data-toggle="dropdown"> {{ getSelectedStudies }} <span class="caret"></span> </button> <ul class="dropdown-menu checkbox-menu allow-focus"> <li v-for="study in studies" v-bind:class="{ active: study.selected }" @click="study.selected = !study.selected"> <a> {{ study.tags['0008,1030'] }} <span v-if="study.selected"> <i class="fa fa-check"></i></span> </a> </li> </ul> </div> <div class="float__right wvButton" v-if="leftMode == 'grid'" @click="leftMode = 'full'"> <i class="fa fa-th-list"></i> </div> <div class="float__right wvButton" v-if="leftMode == 'full'" @click="leftMode = 'small'"> <i class="fa fa-ellipsis-v"></i> </div> <div class="float__right wvButton" v-if="leftMode == 'small'" @click="leftMode = 'grid'"> <i class="fa fa-th"></i> </div> <!--p class="clear disclaimer mbn">For patients, teachers and researchers.</p--> </div> <div class="wvLayoutLeft__contentMiddle"> <div v-for="study in studies"> <div v-if="study.selected"> <div v-bind:class="'wvStudyIsland--' + study.color"> <div v-bind:class="'wvStudyIsland__header--' + study.color"> <!-- Actions --> <div class="wvStudyIsland__actions" v-bind:class="{ 'wvStudyIsland__actions--oneCol': leftMode == 'small' }"> <a class="wvButton"> <!-- download --> <i class="fa fa-download"></i> </a> </div> <!-- Title --> {{ study.tags['0008,1030'] }} <br/> <small>{{ study.tags['0008,0020'] }}</small> </div> <div class="wvStudyIsland__main"> <ul class="wvSerieslist"> <li class="wvSerieslist__seriesItem" v-bind:class="{ highlighted : GetActiveSeries().includes(series[seriesIndex].tags['0020,000e']), 'wvSerieslist__seriesItem--list' : leftMode != 'grid', 'wvSerieslist__seriesItem--grid' : leftMode == 'grid' }" v-on:dragstart="SeriesDragStart($event, seriesIndex)" v-on:click="ClickSeries(seriesIndex)" v-for="seriesIndex in study.series"> <div class="wvSerieslist__picture" style="z-index:0" draggable="true" v-if="series[seriesIndex].type != stone.ThumbnailType.UNKNOWN" > <div v-if="series[seriesIndex].type == stone.ThumbnailType.LOADING"> <img src="img/loading.gif" style="vertical-align:baseline" width="65px" height="65px" /> </div> <i v-if="series[seriesIndex].type == stone.ThumbnailType.PDF" class="wvSerieslist__placeholderIcon fa fa-file-pdf"></i> <i v-if="series[seriesIndex].type == stone.ThumbnailType.VIDEO" class="wvSerieslist__placeholderIcon fa fa-video-video"></i> <div v-if="[stone.ThumbnailType.IMAGE, stone.ThumbnailType.NO_PREVIEW].includes(series[seriesIndex].type)" class="wvSerieslist__placeholderIcon" v-bind:title="leftMode == 'full' ? null : '[' + series[seriesIndex].tags['0008,0060'] + '] ' + series[seriesIndex].tags['0008,103e']"> <i v-if="series[seriesIndex].type == stone.ThumbnailType.NO_PREVIEW" class="fa fa-eye-slash"></i> <img v-if="series[seriesIndex].type == stone.ThumbnailType.IMAGE" v-bind:src="series[seriesIndex].thumbnail" style="vertical-align:baseline" width="65px" height="65px" v-bind:title="leftMode == 'full' ? null : '[' + series[seriesIndex].tags['0008,0060'] + '] ' + series[seriesIndex].tags['0008,103e']" /> <div v-bind:class="'wvSerieslist__badge--' + study.color" v-if="series[seriesIndex].numberOfFrames != 0">{{ series[seriesIndex].numberOfFrames }}</div> </div> </div> <div v-if="leftMode == 'full'" class="wvSerieslist__information" draggable="true" v-on:dragstart="SeriesDragStart($event, seriesIndex)" v-on:click="ClickSeries(seriesIndex)"> <p class="wvSerieslist__label"> [{{ series[seriesIndex].tags['0008,0060'] }}] {{ series[seriesIndex].tags['0008,103e'] }} </p> </div> </li> </ul> </div> </div> </div> </div> </div> <div class="wvLayoutLeft__contentBottom"> </div> </div> </div> <div class="wvLayout__main" v-bind:class="{ 'wvLayout__main--smallleftpadding': leftVisible && leftMode == 'small', 'wvLayout__main--leftpadding': leftVisible && leftMode != 'small' }" > <div class="wvToolbar wvToolbar--top wvPrintExclude" style="left: 0px; text-align:left"> <a href="https://www.orthanc-server.com/" target="_blank"> <img src="img/orthanc.png" style="max-height: 100%; padding: 8px" /> </a> </div> <div class="wvToolbar wvToolbar--top wvPrintExclude"> <div class="ng-scope inline-object"> <div class="tbGroup"> <div class="tbGroup__toggl"> <button class="wvButton" v-bind:class="{ 'wvButton--underline' : !viewportLayoutButtonsVisible }" data-toggle="tooltip" data-title="Change layout" @click="viewportLayoutButtonsVisible = !viewportLayoutButtonsVisible;HideAllTooltips()"> <i class="fa fa-th"></i> </button> </div> <div class="tbGroup__buttons--bottom" v-show="viewportLayoutButtonsVisible"> <div class="inline-object"> <button class="wvButton" @click="SetViewportLayout('1x1')"> <img src="img/grid1x1.png" style="width:1em;height:1em" /> </button> </div> <div class="inline-object"> <button class="wvButton" @click="SetViewportLayout('2x1')"> <img src="img/grid2x1.png" style="width:1em;height:1em" /> </button> </div> <div class="inline-object"> <button class="wvButton" @click="SetViewportLayout('1x2')"> <img src="img/grid1x2.png" style="width:1em;height:1em" /> </button> </div> <div class="inline-object"> <button class="wvButton" @click="SetViewportLayout('2x2')"> <img src="img/grid2x2.png" style="width:1em;height:1em" /> </button> </div> </div> </div> </div> <div class="ng-scope inline-object"> <div class="tbGroup"> <div class="tbGroup__toggl"> <button class="wvButton" v-bind:class="{ 'wvButton--underline' : !mouseActionsVisible }" data-toggle="tooltip" data-title="Mouse actions" @click="mouseActionsVisible = !mouseActionsVisible;HideAllTooltips()"> <i class="fa fa-mouse-pointer"></i> </button> </div> <div class="tbGroup__buttons--bottom" v-show="mouseActionsVisible"> <div class="inline-object"> <button class="wvButton" data-toggle="tooltip" data-title="Combined tool" @click="SetMouseButtonActions(stone.WebViewerAction.GRAYSCALE_WINDOWING, stone.WebViewerAction.PAN, stone.WebViewerAction.ZOOM)"> <i class="far fa-hand-point-up"></i> </button> </div> <div class="inline-object"> <button class="wvButton" data-toggle="tooltip" data-title="Zoom" @click="SetMouseButtonActions(stone.WebViewerAction.ZOOM, stone.WebViewerAction.ZOOM, stone.WebViewerAction.ZOOM)"> <i class="fas fa-search"></i> </button> </div> <div class="inline-object"> <button class="wvButton" data-toggle="tooltip" data-title="Pan" @click="SetMouseButtonActions(stone.WebViewerAction.PAN, stone.WebViewerAction.PAN, stone.WebViewerAction.PAN)"> <i class="fas fa-arrows-alt"></i> </button> </div> <div class="inline-object"> <button class="wvButton" data-toggle="tooltip" data-title="3D cross-hair" @click="SetMouseButtonActions(stone.WebViewerAction.CROSSHAIR, stone.WebViewerAction.PAN, stone.WebViewerAction.ZOOM)"> <i class="fas fa-crosshairs"></i> </button> </div> </div> </div> </div> <!--div class="ng-scope inline-object"> <button class="wvButton--underline text-center active"> <i class="fa fa-hand-pointer-o"></i> </button> </div> <div class="ng-scope inline-object"> <button class="wvButton--underline text-center"> <i class="fa fa-search"></i> </button> </div> <div class="ng-scope inline-object"> <button class="wvButton--underline text-center"> <i class="fa fa-arrows"></i> </button> </div--> <div class="ng-scope inline-object"> <button class="wvButton--underline text-center" data-toggle="tooltip" data-title="Invert contrast" v-on:click="InvertContrast()"> <i class="fa fa-adjust"></i> </button> </div> <div class="ng-scope inline-object"> <button class="wvButton--underline text-center" data-toggle="tooltip" data-title="Change windowing" id="windowing-popover" v-bind:class="{ 'active' : showWindowing }" v-on:click="ToggleWindowing()"> <i class="fa fa-sun"></i> </button> </div> <div id="windowing-content" v-show="showWindowing" class="popover wvToolbar__windowingPresetConfigPopover" style="position: absolute; display: block" > <div class="arrow"></div> <h3 class="popover-title">Change windowing</h3> <div class="popover-content"> <!--p class="wvToolbar__windowingPresetConfigNotice"> Click on the button to toggle the windowing tool or apply a preset to the selected viewport. </p--> <ul class="wvToolbar__windowingPresetList"> <li v-for="preset in windowingPresets" class="wvToolbar__windowingPresetListItem"> <a href="#" v-on:click="SetWindowing(preset.center, preset.width)"> {{ preset.name }} <small>({{ preset.info }})</small> </a> </li> <li class="wvToolbar__windowingPresetListItem"> <a href="#" v-on:click="SetWindowing(-400, 1600)"> CT Lung <small>(C -400, W 1600)</small> </a> </li> <li class="wvToolbar__windowingPresetListItem"> <a href="#" v-on:click="SetWindowing(300, 1500)"> CT Abdomen <small>(C 300, W 1500)</small> </a> </li> <li class="wvToolbar__windowingPresetListItem"> <a href="#" v-on:click="SetWindowing(40, 80)"> CT Bone <small>(C 40, W 80)</small> </a> </li> <li class="wvToolbar__windowingPresetListItem"> <a href="#" v-on:click="SetWindowing(40, 400)"> CT Brain <small>(C 40, W 400)</small> </a> </li> <li class="wvToolbar__windowingPresetListItem"> <a href="#" v-on:click="SetWindowing(-400, 1600)"> CT Chest <small>(C -400, W 1600)</small> </a> </li> <li class="wvToolbar__windowingPresetListItem"> <a href="#" v-on:click="SetWindowing(300, 600)"> CT Angio <small>(C 300, W 600)</small> </a> </li> </ul> </div> </div> <div class="ng-scope inline-object"> <button class="wvButton--underline text-center" data-toggle="tooltip" data-title="Flip horizontally" v-on:click="FlipX()"> <i class="fas fa-exchange-alt"></i> </button> </div> <div class="ng-scope inline-object"> <button class="wvButton--underline text-center" data-toggle="tooltip" data-title="Flip vertically" v-on:click="FlipY()"> <i class="fas fa-exchange-alt fa-rotate-90"></i> </button> </div> <div class="ng-scope inline-object"> <button class="wvButton--underline text-center" data-toggle="tooltip" data-title="Show image information" v-bind:class="{ 'active' : showInfo }" v-on:click="showInfo = !showInfo"> <i class="fa fa-info-circle"></i> </button> </div> <div class="ng-scope inline-object"> <button class="wvButton--underline text-center" data-toggle="tooltip" data-title="Reference lines" v-bind:class="{ 'active' : showReferenceLines }" v-on:click="showReferenceLines = !showReferenceLines"> <i class="fa fa-bars"></i> </button> </div> <div class="ng-scope inline-object"> <button class="wvButton--underline text-center" data-toggle="tooltip" data-title="User preferences" v-on:click="modalPreferences = true"> <i class="fa fa-user"></i> </button> </div> </div> <div class="wvLayout__splitpane--toolbarAtTop wvPrintFullPage"> <div id="viewport" class="wvSplitpane"> <viewport v-on:updated-series="SetViewportSeries(1, $event)" v-on:selected-viewport="activeViewport=1" v-show="viewport1Visible" canvas-id="canvas1" v-bind:series="viewport1Series" v-bind:left="viewport1Left" v-bind:top="viewport1Top" v-bind:width="viewport1Width" v-bind:height="viewport1Height" v-bind:show-info="showInfo" v-bind:active="activeViewport==1"></viewport> <viewport v-on:updated-series="SetViewportSeries(2, $event)" v-on:selected-viewport="activeViewport=2" v-show="viewport2Visible" canvas-id="canvas2" v-bind:series="viewport2Series" v-bind:left="viewport2Left" v-bind:top="viewport2Top" v-bind:width="viewport2Width" v-bind:height="viewport2Height" v-bind:show-info="showInfo" v-bind:active="activeViewport==2"></viewport> <viewport v-on:updated-series="SetViewportSeries(3, $event)" v-on:selected-viewport="activeViewport=3" v-show="viewport3Visible" canvas-id="canvas3" v-bind:series="viewport3Series" v-bind:left="viewport3Left" v-bind:top="viewport3Top" v-bind:width="viewport3Width" v-bind:height="viewport3Height" v-bind:show-info="showInfo" v-bind:active="activeViewport==3"></viewport> <viewport v-on:updated-series="SetViewportSeries(4, $event)" v-on:selected-viewport="activeViewport=4" v-show="viewport4Visible" canvas-id="canvas4" v-bind:series="viewport4Series" v-bind:left="viewport4Left" v-bind:top="viewport4Top" v-bind:width="viewport4Width" v-bind:height="viewport4Height" v-bind:show-info="showInfo" v-bind:active="activeViewport==4"></viewport> </div> </div> </div> </div> </div> <script type="text/x-template" id="viewport-template"> <div v-bind:id="canvasId + '-container'" v-bind:style="{ padding:'2px', position:'absolute', left: left, top: top, width: width, height: height }"> <div v-bind:class="{ 'wvSplitpane__cellBorder--selected' : active, 'wvSplitpane__cellBorder' : series.color == '', 'wvSplitpane__cellBorder--blue' : series.color == 'blue', 'wvSplitpane__cellBorder--red' : series.color == 'red', 'wvSplitpane__cellBorder--green' : series.color == 'green', 'wvSplitpane__cellBorder--yellow' : series.color == 'yellow', 'wvSplitpane__cellBorder--violet' : series.color == 'violet' }" v-on:dragover="SeriesDragAccept($event)" v-on:drop="SeriesDragDrop($event)" style="width:100%;height:100%"> <div class="wvSplitpane__cell" v-on:click="MakeActive()"> <div v-show="status == 'ready'" style="position:absolute; left:0; top:0; width:100%; height:100%;"> <!--div style="width: 100%; height: 100%; background-color: red"></div--> <canvas v-bind:id="canvasId" class="viewport-canvas" style="position:absolute; left:0; top:0; width:100%; height:100%" oncontextmenu="return false"></canvas> <div v-show="showInfo"> <div class="wv-overlay"> <div v-if="'tags' in series" class="wv-overlay-topleft"> {{ series.tags['0010,0010'] }}<br/> {{ series.tags['0010,0020'] }} </div> <div v-if="'tags' in series" class="wv-overlay-topright"> {{ series.tags['0008,1030'] }}<br/> {{ series.tags['0008,0020'] }}<br/> {{ series.tags['0020,0011'] }} | {{ series.tags['0008,103e'] }} </div> <div class="wv-overlay-timeline-wrapper wvPrintExclude"> <div style="text-align:left; padding:5px" v-show="numberOfFrames != 0"> <div style="width: 12em; padding: 1em;" v-show="cineControls"> <label> Frame rate <span class="wv-play-button-config-framerate-wrapper"> <input type="range" min="1" max="60" v-model="cineFramesPerSecond" class="wv-play-button-config-framerate"> </span> {{ cineFramesPerSecond }} fps </label> </div> <button class="btn btn-primary btn-sm" @click="DecrementFrame()"> <i class="fa fa-chevron-circle-left"></i> </button> {{ currentFrame }} / {{ numberOfFrames }} <button class="btn btn-primary btn-sm" @click="IncrementFrame()"> <i class="fa fa-chevron-circle-right"></i> </button> <div class="btn-group btn-group-sm" role="group"> <button type="button" class="btn btn-primary" @click="CinePlay()"> <i class="fas fa-play fa-flip-horizontal"></i> </button> <button type="button" class="btn btn-primary" @click="CinePause()"> <i class="fas fa-pause"></i> </button> <button type="button" class="btn btn-primary" @click="CineBackward()"> <i class="fas fa-play"></i> </button> </div> </div> </div> <div class="wv-overlay-bottomright wvPrintExclude" style="bottom: 0px"> <div v-show="quality == stone.DisplayedFrameQuality.NONE" style="display:block;background-color:red;width:1em;height:1em" /> <div v-show="quality == stone.DisplayedFrameQuality.LOW" style="display:block;background-color:orange;width:1em;height:1em" /> <div v-show="quality == stone.DisplayedFrameQuality.HIGH" style="display:block;background-color:green;width:1em;height:1em" /> </div> </div> </div> </div> <div v-if="status == 'waiting'" class="wvPaneOverlay"> [ drop a series here ] </div> <!-- Don't use "v-if" here, otherwise the tooltips of the PDF viewer are not initialized --> <div v-show="status == 'pdf'" > <pdf-viewer v-bind:prefix="canvasId + '-pdf'" ref="pdfViewer"></pdf-viewer> </div> <div v-if="status == 'video'" class="wvPaneOverlay"> [ videos are not supported yet ] <!--video class="wvVideo" autoplay="" loop="" controls="" preload="auto" type="video/mp4" src="http://viewer-pro.osimis.io/instances/e465dd27-83c96343-96848735-7035a133-1facf1a0/frames/0/raw"> </video--> </div> <div v-if="status == 'loading'" class="wvPaneOverlay"> <span class="wvLoadingSpinner"> <div class="bounce1"></div> <div class="bounce2"></div> <div class="bounce3"></div> </span> </div> </div> </div> </div> </script> <script type="text/x-template" id="pdf-viewer"> <div style="position:absolute; left:0; top:0; width:100%; height:100%;"> <!-- "line-height: 0px" to fit height: https://stackoverflow.com/a/12616341/881731 --> <div v-bind:id="prefix + '-container'" style="position: absolute; left: 0; top: 0; width:100%;height:100%;overflow:auto;line-height: 0px;"> <canvas v-bind:id="prefix + '-canvas'" style="position: absolute; top:0px; left:0px;" oncontextmenu="return false"></canvas> </div> <div class="wv-overlay"> <div class="wv-overlay-bottomleft wvPrintExclude"> <button class="btn btn-primary" @click="FitWidth()" data-toggle="tooltip" data-title="Fit page width"> <i class="fas fa-text-width"></i> </button> <button class="btn btn-primary" @click="FitHeight()" data-toggle="tooltip" data-title="Fit page height"> <i class="fas fa-text-height"></i> </button> <button class="btn btn-primary" @click="ZoomIn()" data-toggle="tooltip" data-title="Zoom in"> <i class="fas fa-search-plus"></i> </button> <button class="btn btn-primary" @click="ZoomOut()" data-toggle="tooltip" data-title="Zoom out"> <i class="fas fa-search-minus"></i> </button> <button class="btn btn-primary" @click="PreviousPage()" data-toggle="tooltip" data-title="Show previous page"> <i class="fa fa-chevron-circle-left"></i> </button> {{currentPage}} / {{countPages}} <button class="btn btn-primary" @click="NextPage()" data-toggle="tooltip" data-title="Show next page"> <i class="fa fa-chevron-circle-right"></i> </button> </div> </div> </div> </script> <script src="js/jquery-3.4.1.min.js"></script> <script src="js/bootstrap.min.js"></script> <script src="js/vue.min.js"></script> <script src="js/axios.min.js"></script> <script src="js/pdf.js"></script> <script src="stone.js"></script> <script src="pdf-viewer.js"></script> <!-- Must be before inclusion of "app.js" --> <script src="app.js"></script> <script src="print.js"></script> </body> </html>