Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Shiny Server custom Handlebars.js templates

I'm not at all familiar with handlebars.js but I'd like to customize the directory index template that comes with Shiny Server. Specifically, what I'm looking to do is render a page of thumbnails of the different apps.

The file /opt/shiny-server/templates/directorIndex.html comes with the code below which reference a number of expressions including {{title}}, references to apps, dirs and files.

<!DOCTYPE html>
<html lang="en-US" xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>{{title}}</title>
  <link href='https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700,400italic,700italic' rel='stylesheet' type='text/css'>
  <link href='https://fonts.googleapis.com/css?family=Source+Code+Pro' rel='stylesheet' type='text/css'>
  <style type="text/css">
    body {
      font-family: Helvetica, Arial, sans-serif;
      background-color: #F5F5F5;
    }
    pre, tt, code, .code, #detail {
      font-family: 'Consolas', 'Courier New', 'Courier', monospace;
    }
    h1 {
      font-size: 40px;
    }
    a {
      text-decoration: none;
    }
  </style>
</head>
<body>

<h1>{{title}}</h1>

<ul>
  {{#each apps}}
    <li><a class="code" href="{{this.url}}">{{this.name}}</a> (application)</li>
  {{/each}}
  {{#each dirs}}
    <li><a class="code" href="{{this.url}}/">{{this.name}}</a></li>
  {{/each}}
  {{#each files}}
    <li><a class="code" href="{{this.url}}">{{this.name}}</a></li>
  {{/each}}
</ul>

</body>
</html>

So I have two questions.

First - how can I know what expressions are available to call?

Second - give that I just have this one html page (as far as I can tell) how do I register a helper, e.g.

Handlebars.registerHelper('splitURL', function(url) {
  var t = url.split("/");
  return t[1];
});
like image 650
Mark Avatar asked Nov 10 '22 06:11

Mark


1 Answers

I had the same desire to customize the directoryIndex.html template and enjoyed the same lack of documentation about what handlebars expressions could be used. I'm not a web developer so the code here is probably rubbish, but it works well enough. It sounds like you already solved your issue, but others may find some use in this approach. Images for each app are saved in site_dir/images.

screenshot of end result

directoryIndex.html:

<!DOCTYPE html>
<html lang="en-US" xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>{{title}}</title>
  <link href='https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700,400italic,700italic' rel='stylesheet' type='text/css'>
  <link href='https://fonts.googleapis.com/css?family=Source+Code+Pro' rel='stylesheet' type='text/css'>
  <link href="main.css" rel="stylesheet">
  <script type='text/javascript'>
    function include(arr,obj) {
      return(arr.indexOf(obj) != -1);
    }

    function updateView(data) {
      //update title and heading
      if ("title" in data) {
        var host = document.location.hostname;
        if (host in data.title) {
          document.title = data.title[host];
          document.getElementById("title").innerHTML = data.title[host];
        } else if ("default" in data.title) {
          document.title = data.title.default;
          document.getElementById("title").innerHTML = data.title.default;
        }
      }

      //hide cards (for directories like /images)
      if ("ignore" in data) {
        var element;
        for (var i in data.ignore) {
          if (element = document.getElementById("card_"+data.ignore[i])) {
            element.parentNode.removeChild(element);
          }
        }
      }

      //update each shiny app if it has JSON data
      if ("apps" in data) {
        for (var item in data.apps) {
          if (document.getElementById("card_"+item)) {
            if ("img" in data.apps[item])
              document.getElementById("img_"+item).src = "/images/" + data.apps[item].img;
            if ("name" in data.apps[item])
              document.getElementById("name_"+item).innerHTML = data.apps[item].name;
            if ("desc" in data.apps[item])
              document.getElementById("desc_"+item).innerHTML = data.apps[item].desc;
          }
        }
      }
    }


    function loadJSON(url) {
      var xmlhttp = new XMLHttpRequest();
      xmlhttp.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
          var data = JSON.parse(this.responseText);
          updateView(data)
        }
      }
      xmlhttp.open("GET", url, true);
      xmlhttp.send();
    }

    document.addEventListener("DOMContentLoaded", function() {
      loadJSON("data.json");
    });
  </script>
</head>

<body>
<div id="title_bar">
  <h1 id="title"></h1>
</div>

<div id="apps">
  {{#each dirs}}
    <div id="card_{{name}}" class="card" onclick="location.href='{{url}}';" style="cursor: pointer;">
      <img id="img_{{name}}" src="" alt="{{name}}" onerror="if (this.src != '/images/missing.png') this.src = '/images/missing.png';">
      <div class="container">
        <h4 id="name_{{name}}">{{name}}</h4>
        <p id="desc_{{name}}"></p>
      </div>
    </div>
  {{/each}}
</div>

</body>
</html>

data.json (located in the site_dir root location):

{
  "title": {
    "default": "Shiny Server",
    "dev_host": "Shiny Server (Development)",
    "accp_host": "Shiny Server (Acceptance)",
    "prod_host": "Shiny Server",
    "dev_host.fully.qualified.name": "Shiny Server (Development)",
    "accp_host.fully.qualified.name": "Shiny Server (Acceptance)",
    "prod_host.fully.qualified.name": "Shiny Server"
  },
  "ignore": [ "app_4", "app_5", "images" ],
  "apps": {
    "app_1": {
      "name": "app 1 name goes here",
      "desc": "app 1 description goes here",
      "img": "app1.png"
    },
    "app_2": {
      "name": "app 2 name",
      "desc": "app 2 desc",
      "img": "app2.png"
    },
    "app_3": {
      "name": "app 3 name",
      "desc": "",
      "img": "app3.png"
    }
  }
}

main.css (located in the site_dir root location):

body, html {
  font-family: Helvetica, Arial, sans-serif;
  background-color: #F5F5F5;
  color: #114;
  margin: 0;
  padding: 0;
}

#title_bar {
  height: 80px;
  background-color: #3475b4;
  overflow: hidden;
  border-bottom: 1px solid #3475b3;
  -moz-box-shadow:    0px 0px 10px 3px #BBC;
  -webkit-box-shadow: 0px 0px 10px 3px #BBC;
  box-shadow:         0px 0px 10px 3px #BBC;
}

#title_bar h1 {
  margin: 14px auto .5em auto;
  padding: .2em;
  color: #EEE;
  text-align: center;
}

#apps {
  margin-top: 14px;
}

.card {
  box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
  transition: 0.3s;
  border-radius: 5px;
  width: 300px;
  margin: 10px;
  display: inline-block;
  vertical-align: top;
}

.card:hover {
  box-shadow: 0 12px 24px 0 rgba(0,0,0,0.2);
}

.card img {
  display: block;
  margin: 0 auto;
  max-width: 300px;
  max-height: 250px;
}

.container {
  padding: 2px 16px;
}
like image 101
Daniel Avatar answered Dec 04 '22 11:12

Daniel