After mastering Michael Hartls Rails Tutorial I am now trying to write my first own ROR 5 Apllication. Right now, I am trying to implement a JavaScript on a page inside my app. The Javascript is displaying the time inside the html and displaying a factor (according to the time)inside a form, which is calculating insulin for a given(eaten) amount of carbohydrates, when it is loaded.
This is the Javascript File bolus_rechner.js:
//Standard Data for a 3 Year old child
let dFakTni = 0.5;
let faKtmor = 1.3;
let faKtnoon = 0.8;
let fakTeven = 0.5;
let d = new Date();
let h = d.getHours();
let m = d.getMinutes();
let uhrZeit = function zeit() {
if (h < 10) { h = '0' + h; }
if (m < 10) { m = '0' + m; }
return (h + ":" + m + " Uhr");
}
function time(){
let uHr = document.getElementById("Uhrzeit");
uHr.innerHTML = "Es ist " + uhrZeit() + ".";
}
// Displaying the Timebased carbohydrate Faktor (BE-Faktor) and the actual time inside the form and htmluhrzeit document.addEventListener("turbolinks:load",
function timeFakt() {
time();
let beFaktor = document.getElementById("faktor");
if (h > 20 || h < 6) {
beFaktor.value = dFakTni;
}
else if (h >= 6 && h < 11) {
beFaktor.value = faKtmor;
}
else if (h >= 11 && h < 18) {
beFaktor.value = faKtnoon;
}
else if (h >= 18 && h <= 20) {
beFaktor.value = fakTeven;
}
};
document.addEventListener("turbolinks:load", timeFakt);
// Possibiltiy to change the carbohydrate factor (BE Faktor)
function faktorAendern() {
let beFaktor = document.getElementById("faktor");
if (h > 20 || h < 6) {
dFakTni = beFaktor.value;
}
else if (h >= 6 && h < 11) {
faKtmor = beFaktor.value;
}
else if (h >= 11 && h < 18) {
faKtnoon = beFaktor.value;
}
else if (h >= 18 && h <= 20) {
fakTeven = beFaktor.value;
};
};
// Calculation of the needed insulin for the amount of carbohydrates eaten
function insulinBerechnen() {
let eat = document.getElementById("be").value;
let uLin = document.getElementById("insulin");
let prod1 = (eat * dFakTni).toFixed(2);
let prod2 = (eat * faKtmor).toFixed(2);
let prod3 = (eat * faKtnoon).toFixed(2);
let prod4 = (eat * fakTeven).toFixed(2);
if (h > 20 || h < 6) {
uLin.innerHTML = prod1.toString();
}
else if (h >= 6 && h < 11) {
uLin.innerHTML = prod2.toString();
}
else if (h >= 11 && h < 18) {
uLin.innerHTML = prod3.toString();
}
else if (h >= 18 && h <= 20) {
uLin.innerHTML = prod4.toString();
};
};
This is the HTML: application.html.erb:
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<%= render 'layouts/rails_default' %>
<%= render 'layouts/shim' %>
</head>
<body>
<%= render 'layouts/header' %>
<div class="container">
<%= yield %>
<%= render 'layouts/footer' %>
<%= debug(params) if Rails.env.development? %>
</div>
</body>
</html>
bolus.html.erb:
<% provide(:title, "Bolus Rechner") %>
<body>
<h1 class="text-center">My Diabetes Diary</h1>
<h2 class="text-center">Bolus Rechner</h1>
<h4 class="text-center text-muted" id="Uhrzeit"></h4>
<div class="container">
<form>
<div class="form-group row top buffer">
<label for="be" class="offset-sm-4 col-sm-2 col-form-label text-left">gegessene BE</label>
<div class="col-sm-1">
<input type="number" class="form-control" id="be" name="be">
<!--<small id="be-info" class="form-text text-muted">
(Hier können Sie die gegessenen Kohlenhydrate in Broteinheiten eingeben.)
</small>-->
</div>
</div>
<div class="form-group row">
<label for="faktor" class="offset-sm-4 col-sm-2 col-form-label">BE-Faktor</label>
<div class="col-sm-1">
<input type="number" class="form-control" id="faktor" name="faktor" value="">
<!--<small id="faktor" class="form-text text-muted">
(Hier können Sie den aktuellen BE-Faktor ändern, wenn nötig.)
</small>-->
</div>
<div class="col-sm-1">
<button class="btn btn-sm btn-danger" type="button" onclick="faktorAendern()">ändern</button>
</div>
</div>
<div class="row">
<div class= "offset-sm-5 col-sm2 ">
<button type="button" class="btn btn-primary btn-md" onclick="insulinBerechnen()">Insulin berechnen</button>
</div>
</div>
<div class="row top-buffer">
<div class="offset-sm-4 col-sm-4 lead bg-warning">
Sie müssen <strong id="insulin"></strong> U (Insulineinheiten) spritzen
</div>
</div>
</div>
</form>
</body>
<head><%= javascript_include_tag "Bolus_Rechner"%></head>
This is the header _header.html.erb:
<header>
<nav class="navbar navbar-toggleable-sm navbar-inverse bg-inverse fixed-top">
<a class="navbar-brand" href="#">My Diabetes Diary</a>
<ul class="navbar-nav navbar-right">
<li class="nav-item">
<%= link_to "Home", root_path, :class => "nav-link" %>
</li>
<li class="nav-item">
<%= link_to "Help", static_pages_help_path, :class => "nav-link"%>
</li>
<li class="nav-item">
<%= link_to "About", static_pages_about_path, :class => "nav-link" %>
</li>
<li class="nav-item">
<%= link_to "Bolus Rechner", static_pages_bolus_path, :class => "nav-link" %>
</li>
</ul>
</nav>
</header>
This is the rails_default partial _rails_default.html.erb:
<%= csrf_meta_tags %>
<%= stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload'%>
<%= render 'layouts/shim' %>
My problem(s):
1)
When using turbolinks:load as the event trigger, when I refresh the page everything works fine. When I press the "Bolus"-link inside the header it works fine for the first time. Every subsequent time I press the Bolus Link a Zero(0) is added to the innerhtml of "Uhrzeit" due too my zeit() function. I understand that it is because turbolinks cashes the first rendered bolus.html and just adds an additional zero to the already calculated time everytime I press the link. Does anyone know how I can prevent turbolinks from doing this.
2) A Zero(0) is added to the Time Display everytime I load a different page/action from my app. How do I prevent rails/turbolinks from doing that and make sure that the bolus_rechner.js is only loaded when I press the link to the bolus path.
Thank You for Your help in advance
I found the solution. The JavaScript(bolus_rechner.js) was the Problem, especialy assigning the function to show the time in a global variable. When using turbolinks the JS is cached in the global namespace of the website/application and everytime another site of the app loads the script is executed because of the event listener with turbolinks:load. The function zeit() and its return value is assigned to uhrZeit. uhrZeit() is executed by time() and time by timefakt().
let uhrZeit = function zeit() {
if (h < 10) { h = '0' + h; }
if (m < 10) { m = '0' + m; }
return (h + ":" + m + " Uhr");
}
That means, when the script is executed the first time, and the actual time is 8h 7m, the time is displayed as 08:07. The next time a site of the app is called, 8 and respectively 7 is compared with ten again and it is still smaller than ten. Then another zero is added to the returned string and so on. Also there are numerous errors because on sites other than bolus.html.erb the html elements withe the respective ids do not exist which causes numerous reference and type errors.
The Solution is to eliminate the global variables and to create the time with helper functions and use them inside the other functions
The new script looks like this:
function currentMinutes(){
let date = new Date();
return date.getMinutes();
}
function currentHours(){
let date = new Date();
return date.getHours();
}
function time() {
let hours = currentHours()
let minutes = currentMinutes()
if (hours < 10) { hours = '0' + hours; }
if (minutes < 10) { minutes = '0' + minutes; }
return (hours + ":" + minutes + "Uhr");
}
function showTime(){
let uHr = document.getElementById("Uhrzeit");
//checking if the id exists on the page
if (uHr === null){return}
uHr.innerHTML = "Es ist " + time() + ".";
}
// Displaying the Timebased carbohydrate Faktor (BE-Faktor) and the actual time inside the form and html
function timeFakt() {
showTime();
let hours = currentHours()
let beFactorNight = 0.5;
let beFactorMorning = 1.3;
let beFactorNoon = 0.8;
let beFactorEvening = 0.5;
let beFactor = document.getElementById("faktor");
//checking if the id exists on the page
if (beFactor === null){return}
if (hours > 20 || hours < 6) {
beFactor.value = beFactorNight;
}
else if (hours >= 6 && hours < 11) {
beFactor.value = beFactorMorning;
}
else if (hours >= 11 && hours < 18) {
beFactor.value = beFactorNoon;
}
else if (hours >= 18 && hours <= 20) {
beFactor.value = beFactorEvening;
}
};
document.addEventListener("turbolinks:load", timeFakt);
// Calculation of the needed insulin for the amount of carbohydrates eaten
function insulinBerechnen() {
//Form validation and checking if the ids exist on the page
let eat = document.getElementById("be").value;
if( eat <= 0){
alert("If the carbs you ate are zero or below you don't need any insulin");
return};
let beFaktor = document.getElementById("faktor");
if (beFaktor === null){return};
if( beFaktor.value <= 0){
alert("Enter a Valid factor bigger than 0");
return};
let uLin = document.getElementById("insulin");
//calculating the Insulin according to the time of day
let hours = currentHours()
if (hours > 20 || hours < 6) {
if (beFaktor.value === ""){beFactorNight = 0.5}
else {beFactorNight = beFaktor.value};
uLin.innerHTML = (eat * beFactorNight).toFixed(2).toString();
}
else if (hours >= 6 && hours < 11) {
if (beFaktor.value === ""){beFactorMorning = 1.3}
else {beFactorMorning = beFaktor.value};
uLin.innerHTML = (eat * beFactorMorning).toFixed(2).toString();
}
else if (hours >= 11 && hours < 18) {
if (beFaktor.value === ""){beFactorNoon = 0.8}
else {beFactorNoon = beFaktor.value};
uLin.innerHTML = (eat * beFactorNoon).toFixed(2).toString();
}
else if (hours >= 18 && hours <= 20) {
if (beFaktor.value === ""){beFactorEvening = 0.8}
else {beFactorEvening = beFaktor.value};
uLin.innerHTML = (eat * beFactorEvening).toFixed(2).toString();
};
};
I also added a kind of form validation to check for values of 0 and below.
That was some hard work but I learned an important lesson from it.
Maybe this post can help other newbie programmers to solve their Javascript issues with turbolinks and to understand better how JavaScript works in the context of RoR5 App.
Cheers Sebastian