Skip to content

Commit

Permalink
Set up javascript importmaps for Dashboard; refactor Polling
Browse files Browse the repository at this point in the history
  • Loading branch information
bensheldon committed Apr 20, 2022
1 parent bd5cd69 commit ee89c21
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 134 deletions.
29 changes: 29 additions & 0 deletions engine/app/assets/modules/charts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
function renderCharts(animate) {
const charts = document.querySelectorAll('.chart');

for (let i = 0; i < charts.length; i++) {
const chartEl = charts[i];
const chartData = JSON.parse(chartEl.dataset.json);

const ctx = chartEl.getContext('2d');
const chart = new Chart(ctx, {
type: 'line',
data: {
labels: chartData.labels,
datasets: chartData.datasets
},
options: {
animation: animate,
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true
}
}
}
});
}
}

export { renderCharts as default };
7 changes: 7 additions & 0 deletions engine/app/assets/modules/document_ready.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function documentReady(callback) {
if (document.readyState !== "loading") {
callback();
} else {
document.addEventListener("DOMContentLoaded", callback);
}
}
94 changes: 94 additions & 0 deletions engine/app/assets/modules/poller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*jshint esversion: 6, strict: false */
import renderCharts from "charts";

// NOTE: this file is a bit disorganized. Please do not use it as a template for how to organize a JS module.

const DEFAULT_POLL_INTERVAL_SECONDS = 30;
const MINIMUM_POLL_INTERVAL = 1000;

function getStorage(key) {
const value = localStorage.getItem('good_job-' + key);

if (value === 'true') {
return true;
} else if (value === 'false') {
return false;
} else {
return value;
}
}

function setStorage(key, value) {
localStorage.setItem('good_job-' + key, value);
}

function updatePageContent(newContent) {
const domParser = new DOMParser();
const parsedDOM = domParser.parseFromString(newContent, "text/html");

const newElements = parsedDOM.querySelectorAll('[data-gj-poll-replace]');

for (let i = 0; i < newElements.length; i++) {
const newEl = newElements[i];
const oldEl = document.getElementById(newEl.id);

if (oldEl) {
oldEl.replaceWith(newEl);
}
}

renderCharts(false);
}

function refreshPage() {
fetch(window.location.href)
.then(resp => resp.text())
.then(updatePageContent);
}

const Poller = {
start: () => {
Poller.updateSettings();
Poller.pollUpdates();

const checkbox = document.querySelector('input[name="toggle-poll"]');
checkbox.addEventListener('change', Poller.togglePoll)
},

togglePoll: (event) => {
console.log("toggle");
Poller.pollEnabled = event.currentTarget.checked;
setStorage('pollEnabled', Poller.pollEnabled);
},

updateSettings: () => {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);

if (urlParams.has('poll')) {
const parsedInterval = (parseInt(urlParams.get('poll')) || DEFAULT_POLL_INTERVAL_SECONDS) * 1000;
Poller.pollInterval = Math.max(parsedInterval, MINIMUM_POLL_INTERVAL);
setStorage('pollInterval', Poller.pollInterval);

Poller.pollEnabled = true;
} else {
Poller.pollInterval = getStorage('pollInterval') || (DEFAULT_POLL_INTERVAL_SECONDS * 1000);
Poller.pollEnabled = getStorage('pollEnabled') || false;
}

document.getElementById('toggle-poll').checked = Poller.pollEnabled;
},

pollUpdates: () => {
setTimeout(() => {
if (Poller.pollEnabled === true) {
refreshPage();
Poller.pollUpdates();
} else {
Poller.pollUpdates();
}
}, Poller.pollInterval);
},
};

export { Poller as default };
138 changes: 8 additions & 130 deletions engine/app/assets/scripts.js
Original file line number Diff line number Diff line change
@@ -1,133 +1,11 @@
/*jshint esversion: 6, strict: false */
const GOOD_JOB_DEFAULT_POLL_INTERVAL_SECONDS = 30;
const GOOD_JOB_MINIMUM_POLL_INTERVAL = 1000;

const GoodJob = {
// Register functions to execute when the DOM is ready
ready: (callback) => {
if (document.readyState !== "loading") {
callback();
} else {
document.addEventListener("DOMContentLoaded", callback);
}
},
import renderCharts from "charts";
import documentReady from "document_ready"
import Poller from "poller";
import start from "./modules/poller";

init: () => {
GoodJob.updateSettings();
GoodJob.addListeners();
GoodJob.pollUpdates();
GoodJob.renderCharts(true);
},

addListeners: () => {
const gjActionEls = document.querySelectorAll('[data-gj-action]');

for (let i = 0; i < gjActionEls.length; i++) {
const el = gjActionEls[i];
const [eventName, func] = el.dataset.gjAction.split('#');

el.addEventListener(eventName, GoodJob[func]);
}
},

updateSettings: () => {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);

// live poll interval and enablement
if (urlParams.has('poll')) {
const parsedInterval = (parseInt(urlParams.get('poll')) || GOOD_JOB_DEFAULT_POLL_INTERVAL_SECONDS) * 1000;
GoodJob.pollInterval = Math.max(parsedInterval, GOOD_JOB_MINIMUM_POLL_INTERVAL);
GoodJob.setStorage('pollInterval', GoodJob.pollInterval);

GoodJob.pollEnabled = true;
} else {
GoodJob.pollInterval = GoodJob.getStorage('pollInterval') || (GOOD_JOB_DEFAULT_POLL_INTERVAL_SECONDS * 1000);
GoodJob.pollEnabled = GoodJob.getStorage('pollEnabled') || false;
}

document.getElementById('toggle-poll').checked = GoodJob.pollEnabled;
},

togglePoll: (ev) => {
GoodJob.pollEnabled = ev.currentTarget.checked;
GoodJob.setStorage('pollEnabled', GoodJob.pollEnabled);
},

pollUpdates: () => {
setTimeout(() => {
if (GoodJob.pollEnabled === true) {
fetch(window.location.href)
.then(resp => resp.text())
.then(GoodJob.updateContent)
.finally(GoodJob.pollUpdates);
} else {
GoodJob.pollUpdates();
}
}, GoodJob.pollInterval);
},

updateContent: (newContent) => {
const domParser = new DOMParser();
const parsedDOM = domParser.parseFromString(newContent, "text/html");

const newElements = parsedDOM.querySelectorAll('[data-gj-poll-replace]');

for (let i = 0; i < newElements.length; i++) {
const newEl = newElements[i];
const oldEl = document.getElementById(newEl.id);

if (oldEl) {
oldEl.replaceWith(newEl);
}
}

GoodJob.renderCharts(false);
},

renderCharts: (animate) => {
const charts = document.querySelectorAll('.chart');

for (let i = 0; i < charts.length; i++) {
const chartEl = charts[i];
const chartData = JSON.parse(chartEl.dataset.json);

const ctx = chartEl.getContext('2d');
const chart = new Chart(ctx, {
type: 'line',
data: {
labels: chartData.labels,
datasets: chartData.datasets
},
options: {
animation: animate,
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true
}
}
}
});
}
},

getStorage: (key) => {
const value = localStorage.getItem('good_job-' + key);

if (value === 'true') {
return true;
} else if (value === 'false') {
return false;
} else {
return value;
}
},

setStorage: (key, value) => {
localStorage.setItem('good_job-' + key, value);
}
};

GoodJob.ready(GoodJob.init);
documentReady(function() {
renderCharts();
Poller.start();
});
1 change: 1 addition & 0 deletions engine/app/assets/vendor/es_module_shims.js

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions engine/app/controllers/good_job/assets_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,20 @@ module GoodJob
class AssetsController < ActionController::Base # rubocop:disable Rails/ApplicationController
skip_before_action :verify_authenticity_token, raise: false

JS_MODULES = {
poller: GoodJob::Engine.root.join("app", "assets", "modules", "poller.js"),
charts: GoodJob::Engine.root.join("app", "assets", "modules", "charts.js"),
document_ready: GoodJob::Engine.root.join("app", "assets", "modules", "document_ready.js"),
}.freeze

before_action do
expires_in 1.year, public: true
end

def es_module_shims_js
render file: GoodJob::Engine.root.join("app", "assets", "vendor", "es_module_shims.js")
end

def bootstrap_css
render file: GoodJob::Engine.root.join("app", "assets", "vendor", "bootstrap", "bootstrap.min.css")
end
Expand All @@ -30,5 +40,11 @@ def scripts_js
def style_css
render file: GoodJob::Engine.root.join("app", "assets", "style.css")
end

def modules_js
module_name = params[:module].to_sym
module_file = JS_MODULES.fetch(module_name) { raise ActionController::RoutingError, 'Not Found' }
render file: module_file
end
end
end
2 changes: 1 addition & 1 deletion engine/app/views/good_job/shared/_navbar.erb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
</ul>
<div class="nav-item pe-2">
<div class="form-check">
<input type="checkbox" id="toggle-poll" name="toggle-poll" data-gj-action='change#togglePoll' <%= 'checked' if params[:poll].present? %>>
<input type="checkbox" id="toggle-poll" name="toggle-poll" <%= 'checked' if params[:poll].present? %>>
<label for="toggle-poll"><%= t(".live_poll") %></label>
</div>
</div>
Expand Down
9 changes: 6 additions & 3 deletions engine/app/views/layouts/good_job/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@
<%# Assets must use *_url route helpers to avoid being overriden by config.asset_host %>
<%= stylesheet_link_tag bootstrap_url(format: :css, v: GoodJob::VERSION), skip_pipeline: true %>
<%= stylesheet_link_tag style_url(format: :css, v: GoodJob::VERSION) %>
<%= stylesheet_link_tag style_url(format: :css, v: GoodJob::VERSION, skip_pipeline: true) %>
<%= javascript_include_tag bootstrap_url(format: :js, v: GoodJob::VERSION), nonce: true %>
<%= javascript_include_tag chartjs_url(format: :js, v: GoodJob::VERSION), nonce: true %>
<%= javascript_include_tag scripts_url(format: :js, v: GoodJob::VERSION), nonce: true %>
<%= javascript_include_tag rails_ujs_url(format: :js, v: GoodJob::VERSION), nonce: true %>
<%= javascript_include_tag es_module_shims_url(format: :js, v: GoodJob::VERSION), async: true, nonce: true %>
<% importmaps = { imports: GoodJob::AssetsController::JS_MODULES.keys.each_with_object({}) { |module_name, imports| imports[module_name] = modules_path(module_name, format: :js) } } %>
<%= tag.script(importmaps.to_json.html_safe, type: "importmap", nonce: content_security_policy_nonce) %>
<%= javascript_include_tag scripts_url(format: :js, v: GoodJob::VERSION), type: "module", nonce: true, crossorigin: "anonymous" %>
</head>
<body>
<div class="d-flex flex-column min-vh-100">
Expand Down
2 changes: 2 additions & 0 deletions engine/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@
end

constraints(format: :js) do
get :es_module_shims, action: :es_module_shims_js
get :bootstrap, action: :bootstrap_js
get :rails_ujs, action: :rails_ujs_js
get :chartjs, action: :chartjs_js
get :scripts, action: :scripts_js
get "modules/:module", action: :modules_js, as: :modules
end
end
end

0 comments on commit ee89c21

Please sign in to comment.