From 058cf7604e48b293125c5066533ec896f43c1b58 Mon Sep 17 00:00:00 2001
From: Tor-Einar Skog <tor-einar.skog@nibio.no>
Date: Wed, 30 Oct 2024 10:31:44 +0100
Subject: [PATCH] feat: Turned cydiapomonella.js into a module

---
 cydiapomonella/static/js/cydiapomonella.js    | 420 +++++++++---------
 .../templates/cydiapomonella/index.html       |  17 +-
 2 files changed, 223 insertions(+), 214 deletions(-)

diff --git a/cydiapomonella/static/js/cydiapomonella.js b/cydiapomonella/static/js/cydiapomonella.js
index 8d34962f..86440fcb 100644
--- a/cydiapomonella/static/js/cydiapomonella.js
+++ b/cydiapomonella/static/js/cydiapomonella.js
@@ -1,247 +1,251 @@
-const weatherStationDataURL = "https://lmt.nibio.no/services/rest/vips/getdata/forecastfallback";
-const gridDataURL = "https://weather.vips.nibio.no/rest/grid/openmeteo/"; // TODO: Revider APIet
-const TIMEZONE = "Europe/Oslo";
-const DATE_FORMAT = "YYYY-MM-DD";
-const HEATSUM_BASE_TEMP = 10.0;
-
-const appleWeatherStations = [
-    { "id": 11, "name": "Apelsvoll" },
-    { "id": 12, "name": "Balestrand" },
-    { "id": 13, "name": "Bø" },
-    { "id": 86, "name": "Darbu" },
-    { "id": 19, "name": "Gjerpen" },
-    { "id": 21, "name": "Gvarv" },
-    { "id": 22, "name": "Hjelmeland" },
-    { "id": 25, "name": "Hønefoss" },
-    { "id": 27, "name": "Kise" },
-    { "id": 29, "name": "Landvik" },
-    { "id": 30, "name": "Lier" },
-    { "id": 65, "name": "Ljøsne" },
-    { "id": 32, "name": "Lyngdal" },
-    { "id": 35, "name": "Njøs" },
-    { "id": 41, "name": "Rygge" },
-    { "id": 42, "name": "Sande" },
-    { "id": 131, "name": "Sandefjord" },
-    { "id": 64, "name": "Slinde" },
-    { "id": 47, "name": "Svelvik" },
-    { "id": 91, "name": "Søve" },
-    { "id": 61, "name": "Åsbakken" }
-];
-
-function renderWeatherstations() {
-    let wsSelect = document.getElementById("weatherStationId");
-    appleWeatherStations.forEach((ws) => {
-        wsSelect.options[wsSelect.options.length] = new Option(ws["name"], ws["id"]);
-    });
-}
+class CydiaPomonella 
+{
 
-async function runModel() {
-    let wsSelect = document.getElementById("weatherStationId");
-    let selectedWeatherStationId = wsSelect.options[wsSelect.selectedIndex].value;
-    if(selectedWeatherStationId == "-1")
-    {
-        return;
+    weatherStationDataURL = "https://lmt.nibio.no/services/rest/vips/getdata/forecastfallback";
+    gridDataURL = "https://weather.vips.nibio.no/rest/grid/openmeteo/"; // TODO: Revider APIet
+    TIMEZONE = "Europe/Oslo";
+    DATE_FORMAT = "YYYY-MM-DD";
+    HEATSUM_BASE_TEMP = 10.0;
+
+    appleWeatherStations = [
+        { "id": 11, "name": "Apelsvoll" },
+        { "id": 12, "name": "Balestrand" },
+        { "id": 13, "name": "Bø" },
+        { "id": 86, "name": "Darbu" },
+        { "id": 19, "name": "Gjerpen" },
+        { "id": 21, "name": "Gvarv" },
+        { "id": 22, "name": "Hjelmeland" },
+        { "id": 25, "name": "Hønefoss" },
+        { "id": 27, "name": "Kise" },
+        { "id": 29, "name": "Landvik" },
+        { "id": 30, "name": "Lier" },
+        { "id": 65, "name": "Ljøsne" },
+        { "id": 32, "name": "Lyngdal" },
+        { "id": 35, "name": "Njøs" },
+        { "id": 41, "name": "Rygge" },
+        { "id": 42, "name": "Sande" },
+        { "id": 131, "name": "Sandefjord" },
+        { "id": 64, "name": "Slinde" },
+        { "id": 47, "name": "Svelvik" },
+        { "id": 91, "name": "Søve" },
+        { "id": 61, "name": "Åsbakken" }
+    ];
+
+    renderWeatherstations = function () {
+        let wsSelect = document.getElementById("weatherStationId");
+        this.appleWeatherStations.forEach((ws) => {
+            wsSelect.options[wsSelect.options.length] = new Option(ws["name"], ws["id"]);
+        });
     }
-    let startDateHourly = getStartDateHourly();
-    let endDateHourly = getEndDateHourly();
-    try
-    {
-        // Get the hourly data for the past X days
-        const hourlyDataResponse = await fetch(
-            weatherStationDataURL + "?weatherStationId=" + selectedWeatherStationId 
-            + "&elementMeasurementTypes[]=RR&elementMeasurementTypes[]=TM"
-            + "&timeZone="  + TIMEZONE 
-            + "&startDate=" + startDateHourly.format(DATE_FORMAT) + "&startTime=0" 
-            + "&endDate=" + endDateHourly.format(DATE_FORMAT) + "&endTime=12"  
-            + "&logIntervalId=1"
-        );
-        if(! hourlyDataResponse.ok){
-            throw new Error("Response status: " + hourlyDataResponse.status);
+
+    async runModel() {
+        let wsSelect = document.getElementById("weatherStationId");
+        let selectedWeatherStationId = wsSelect.options[wsSelect.selectedIndex].value;
+        if(selectedWeatherStationId == "-1")
+        {
+            return;
         }
+        let startDateHourly = this.getStartDateHourly();
+        let endDateHourly = this.getEndDateHourly();
+        try
+        {
+            // Get the hourly data for the past X days
+            const hourlyDataResponse = await fetch(
+                this.weatherStationDataURL + "?weatherStationId=" + selectedWeatherStationId 
+                + "&elementMeasurementTypes[]=RR&elementMeasurementTypes[]=TM"
+                + "&timeZone="  + this.TIMEZONE 
+                + "&startDate=" + startDateHourly.format(this.DATE_FORMAT) + "&startTime=0" 
+                + "&endDate=" + endDateHourly.format(this.DATE_FORMAT) + "&endTime=12"  
+                + "&logIntervalId=1"
+            );
+            if(! hourlyDataResponse.ok){
+                throw new Error("Response status: " + hourlyDataResponse.status);
+            }
 
-        const hourlyData = await hourlyDataResponse.json();
+            const hourlyData = await hourlyDataResponse.json();
 
-        // Get the daily RR data for the past X days
-        const dailyData = getDailyRRData(hourlyData);
+            // Get the daily RR data for the past X days
+            const dailyData = this.getDailyRRData(hourlyData);
 
-        renderTable(hourlyData, dailyData);
+            this.renderTable(hourlyData, dailyData);
 
-        runHeatSumCalculations();
+            this.runHeatSumCalculations();
+            
+        }
+        catch(error)
+        {
+            console.error(error.message);
+        }
+    }
+
+    async runHeatSumCalculations()
+    {
+        document.getElementById("heatSumContainer").style.display="block";
+        let wsSelect = document.getElementById("weatherStationId");
+        let selectedWeatherStationId = wsSelect.options[wsSelect.selectedIndex].value;
+        if(selectedWeatherStationId == "-1")
+        {
+            return;
+        }
+        // Get daily temperature data for the heat sum
+        const heatSumStartDate = this.setToTZMidnight(moment(document.getElementById("heatSumStartDate").value));
+        const heatSumEndDate = this.setToTZMidnight(moment().tz(this.TIMEZONE).subtract(1,"days"));
         
+        const heatSumDataResponse = await fetch(
+            this.weatherStationDataURL + "?weatherStationId=" + selectedWeatherStationId 
+            + "&elementMeasurementTypes[]=TM"
+            + "&timeZone="  + this.TIMEZONE 
+            + "&startDate=" + heatSumStartDate.format(this.DATE_FORMAT) + "&startTime=0" 
+            + "&endDate=" + heatSumEndDate.format(this.DATE_FORMAT) + "&endTime=23"  
+            + "&logIntervalId=2"
+        );
+
+        if(! heatSumDataResponse.ok){
+            throw new Error("Response status: " + heatSumDataResponse.status);
+        }
 
-        //calculateHeatSum()
+        const heatSumData = await heatSumDataResponse.json();
+
+        this.renderHeatSumResult(selectedWeatherStationId, this.calculateHeatSum(heatSumData), heatSumData.length, heatSumStartDate, heatSumEndDate);
     }
-    catch(error)
+
+    calculateHeatSum(heatSumData)
     {
-        console.error(error.message);
+        let heatSum = 0.0;
+        heatSumData.forEach((obs) =>{
+            heatSum += Math.max(0.0, (obs.value - this.HEATSUM_BASE_TEMP));
+        });
+        return heatSum;
     }
-}
 
-async function runHeatSumCalculations()
-{
-    document.getElementById("heatSumContainer").style.display="block";
-    let wsSelect = document.getElementById("weatherStationId");
-    let selectedWeatherStationId = wsSelect.options[wsSelect.selectedIndex].value;
-    if(selectedWeatherStationId == "-1")
+    renderHeatSumResult(selectedWeatherStationId, heatSum, numberOfDays, heatSumStartDate, heatSumEndDate)
     {
-        return;
-    }
-    // Get daily temperature data for the heat sum
-    const heatSumStartDate = setToTZMidnight(moment(document.getElementById("heatSumStartDate").value));
-    const heatSumEndDate = setToTZMidnight(moment().tz(TIMEZONE).subtract(1,"days"));
-    
-    const heatSumDataResponse = await fetch(
-        weatherStationDataURL + "?weatherStationId=" + selectedWeatherStationId 
-        + "&elementMeasurementTypes[]=TM"
-        + "&timeZone="  + TIMEZONE 
-        + "&startDate=" + heatSumStartDate.format(DATE_FORMAT) + "&startTime=0" 
-        + "&endDate=" + heatSumEndDate.format(DATE_FORMAT) + "&endTime=23"  
-        + "&logIntervalId=2"
-    );
-
-    if(! heatSumDataResponse.ok){
-        throw new Error("Response status: " + heatSumDataResponse.status);
+        document.getElementById("heatSumBaseTemp").innerHTML=this.HEATSUM_BASE_TEMP;
+        this.appleWeatherStations.forEach((ws)=>{
+            if(ws.id == parseInt(selectedWeatherStationId))
+            {
+                document.getElementById("selectedWeatherStationName").innerHTML = ws.name;
+            }
+        });
+        document.getElementById("heatSumStartDateDisplay").innerHTML = heatSumStartDate.format(this.DATE_FORMAT);
+        document.getElementById("heatSumEndDateDisplay").innerHTML = heatSumEndDate.format(this.DATE_FORMAT);
+        document.getElementById("numberOfDays").innerHTML = numberOfDays;
+        document.getElementById("heatSum").innerHTML = heatSum.toFixed(1);
     }
 
-    const heatSumData = await heatSumDataResponse.json();
-
-    renderHeatSumResult(selectedWeatherStationId, calculateHeatSum(heatSumData), heatSumData.length, heatSumStartDate, heatSumEndDate);
-}
-
-function calculateHeatSum(heatSumData)
-{
-    let heatSum = 0.0;
-    heatSumData.forEach((obs) =>{
-        heatSum += Math.max(0.0, (obs.value - HEATSUM_BASE_TEMP));
-    });
-    return heatSum;
-}
-
-function renderHeatSumResult(selectedWeatherStationId, heatSum, numberOfDays, heatSumStartDate, heatSumEndDate)
-{
-    document.getElementById("heatSumBaseTemp").innerHTML=HEATSUM_BASE_TEMP;
-    appleWeatherStations.forEach((ws)=>{
-        if(ws.id == parseInt(selectedWeatherStationId))
+    getDailyRRData(hourlyData)
+    {
+        let RRBuckets = {};
+        for(let i=0; i< hourlyData.length;i++)
         {
-            document.getElementById("selectedWeatherStationName").innerHTML = ws.name;
+            let obs = hourlyData[i];
+            if(obs.elementMeasurementTypeId == "RR")
+            {
+                let date = this.setToTZMidnight(moment(obs.timeMeasured));
+                if(RRBuckets[date.unix()] == null)
+                {
+                    RRBuckets[date.unix()] = [];
+                }
+                RRBuckets[date.unix()].push(obs.value);
+            }
         }
-    });
-    document.getElementById("heatSumStartDateDisplay").innerHTML = heatSumStartDate.format(DATE_FORMAT);
-    document.getElementById("heatSumEndDateDisplay").innerHTML = heatSumEndDate.format(DATE_FORMAT);
-    document.getElementById("numberOfDays").innerHTML = numberOfDays;
-    document.getElementById("heatSum").innerHTML = heatSum.toFixed(1);
-}
 
-function getDailyRRData(hourlyData)
-{
-    let RRBuckets = {};
-    for(let i=0; i< hourlyData.length;i++)
+        let dailyRRData = [];
+        Object.keys(RRBuckets).forEach((key) => {
+            let dateBucket = RRBuckets[key];
+            let RRSum = dateBucket.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
+            let RR = {
+                timeMeasured: moment.unix(key).toISOString(),
+                elementMeasurementTypeId: "RR",
+                logIntervalId: 1,
+                value: RRSum
+            }
+            dailyRRData.push(RR);
+        });
+        
+        return dailyRRData;
+    }
+
+    renderTable(hourlyData, dailyData)
     {
-        let obs = hourlyData[i];
-        if(obs.elementMeasurementTypeId == "RR")
+        document.getElementById("weatherData").style.display="block";
+        let tableBody = document.getElementById("tableBody");
+        tableBody.innerHTML = "";
+        let currentDate = this.getStartDateHourly();
+        let today = this.setToTZMidnight(moment());
+        //console.info("today=" + today.format());
+        while(currentDate < today)
         {
-            let date = setToTZMidnight(moment(obs.timeMeasured));
-            if(RRBuckets[date.unix()] == null)
+            //console.info("currentDate=" + currentDate.format());
+            let nightTemps = this.getNightTempsForDate(moment(currentDate).tz(this.TIMEZONE), hourlyData);
+            //console.info(nightTemps);
+            let rowHTML = "<tr><td>" + currentDate.format(this.DATE_FORMAT) + "</td>";
+            
+            for(let i=0; i<nightTemps.length;i++)
             {
-                RRBuckets[date.unix()] = [];
+                rowHTML += "<td>" + nightTemps[i].toFixed(1) + "</td>";
             }
-            RRBuckets[date.unix()].push(obs.value);
+            dailyData.forEach((obs) =>{
+                if(moment(obs.timeMeasured).unix() == currentDate.unix())
+                {
+                    rowHTML += "<td>" + obs.value.toFixed(1) + "</td>";
+                }
+            });
+            rowHTML += "</tr>"
+            tableBody.innerHTML += rowHTML;
+            currentDate.add(1,"days");
         }
     }
 
-    let dailyRRData = [];
-    Object.keys(RRBuckets).forEach((key) => {
-        let dateBucket = RRBuckets[key];
-        let RRSum = dateBucket.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
-        let RR = {
-            timeMeasured: moment.unix(key).toISOString(),
-            elementMeasurementTypeId: "RR",
-            logIntervalId: 1,
-            value: RRSum
-        }
-        dailyRRData.push(RR);
-    });
-    
-    return dailyRRData;
-}
-
-function renderTable(hourlyData, dailyData)
-{
-    document.getElementById("weatherData").style.display="block";
-    let tableBody = document.getElementById("tableBody");
-    tableBody.innerHTML = "";
-    let currentDate = getStartDateHourly();
-    let today = setToTZMidnight(moment());
-    //console.info("today=" + today.format());
-    while(currentDate < today)
+    getStartDateHourly = function()
     {
-        //console.info("currentDate=" + currentDate.format());
-        let nightTemps = getNightTempsForDate(moment(currentDate).tz(TIMEZONE), hourlyData);
-        //console.info(nightTemps);
-        let rowHTML = "<tr><td>" + currentDate.format(DATE_FORMAT) + "</td>";
-        
-        for(let i=0; i<nightTemps.length;i++)
-        {
-            rowHTML += "<td>" + nightTemps[i].toFixed(1) + "</td>";
-        }
-        dailyData.forEach((obs) =>{
-            if(moment(obs.timeMeasured).unix() == currentDate.unix())
+        let daysBack = undefined;
+        document.getElementsByName("radioDays").forEach((radio)=>{
+            if(radio.checked)
             {
-                rowHTML += "<td>" + obs.value.toFixed(1) + "</td>";
+                daysBack = radio.value;
             }
         });
-        rowHTML += "</tr>"
-        tableBody.innerHTML += rowHTML;
-        currentDate.add(1,"days");
+        return this.setToTZMidnight(moment()).subtract(daysBack,"days");
     }
-}
 
-function getStartDateHourly()
-{
-    let daysBack = undefined;
-    document.getElementsByName("radioDays").forEach((radio)=>{
-        if(radio.checked)
+    getEndDateHourly()
+    {
+        return moment().tz(this.TIMEZONE).hour(12);
+    }
+
+    /**
+     * 
+     * @param date 
+     * @param {Array} weatherData 
+     * @return array[temp@19,temp@20,temp@21,temp@22, temp@23]
+     */
+    getNightTempsForDate(date, weatherData)
+    {
+        // Create an array of the desired timestamps
+        let timestamps = [];
+        let nightTemps = [];
+        for(let i=19;i<=23;i++)
         {
-            daysBack = radio.value;
+            timestamps.push(date.hour(i).minute(0).second(0).millisecond(0).unix());
         }
-    });
-    return setToTZMidnight(moment()).subtract(daysBack,"days");
-}
-
-function getEndDateHourly()
-{
-    return moment().tz(TIMEZONE).hour(12);
-}
+        weatherData.forEach((wd)=>{
+            let timeMeasured = moment(wd.timeMeasured).unix();
+            //console.info(timeMeasured);
+            if(wd.elementMeasurementTypeId == "TM" && timestamps.indexOf(timeMeasured) >=0)
+            {
+                nightTemps[timestamps.indexOf(timeMeasured)] = wd.value;
+                //console.info("Found " + wd.timeMeasured + "at index " + tsArray.indexOf(timeMeasured));
+            }
+        });
+        return nightTemps;
+        //console.info(tsArray);
+    }
 
-/**
- * 
- * @param date 
- * @param {Array} weatherData 
- * @return array[temp@19,temp@20,temp@21,temp@22, temp@23]
- */
-function getNightTempsForDate(date, weatherData)
-{
-    // Create an array of the desired timestamps
-    let timestamps = [];
-    let nightTemps = [];
-    for(let i=19;i<=23;i++)
+    setToTZMidnight(theDate)
     {
-        timestamps.push(date.hour(i).minute(0).second(0).millisecond(0).unix());
+        return theDate.tz(this.TIMEZONE).hour(0).minute(0).second(0).millisecond(0);
     }
-    weatherData.forEach((wd)=>{
-        let timeMeasured = moment(wd.timeMeasured).unix();
-        //console.info(timeMeasured);
-        if(wd.elementMeasurementTypeId == "TM" && timestamps.indexOf(timeMeasured) >=0)
-        {
-            nightTemps[timestamps.indexOf(timeMeasured)] = wd.value;
-            //console.info("Found " + wd.timeMeasured + "at index " + tsArray.indexOf(timeMeasured));
-        }
-    });
-    return nightTemps;
-    //console.info(tsArray);
 }
 
-function setToTZMidnight(theDate)
-{
-    return theDate.tz(TIMEZONE).hour(0).minute(0).second(0).millisecond(0);
-}
\ No newline at end of file
+export default CydiaPomonella;
\ No newline at end of file
diff --git a/cydiapomonella/templates/cydiapomonella/index.html b/cydiapomonella/templates/cydiapomonella/index.html
index d0f9921e..ea7c11af 100644
--- a/cydiapomonella/templates/cydiapomonella/index.html
+++ b/cydiapomonella/templates/cydiapomonella/index.html
@@ -22,17 +22,21 @@ Created: 2024-10-28
 {% block title%}{% trans "Cydia pomonella" %}{%endblock%}
 
 {% block extendJS %}
-<script src="{% static "js/moment.min.js" %}"></script>
-<script src="{% static "js/moment-timezone.js" %}"></script>
-<script src="{% static "js/moment-timezone-with-data.min.js" %}"></script>
-<script src="{% static "js/cydiapomonella.js" %}"></script>
+<script type="text/javascript" src="{% static "js/moment.min.js" %}"></script>
+<script type="text/javascript" src="{% static "js/moment-timezone.js" %}"></script>
+<script type="text/javascript" src="{% static "js/moment-timezone-with-data.min.js" %}"></script>
 {% endblock %}
 
 {% block customJS %}
-<script type="text/javascript">
+<script type="module">
+    import CydiaPomonella from "../static/js/cydiapomonella.js";
+    const cydiaPomonella = new CydiaPomonella();
     window.onload = (event) => {
-        renderWeatherstations();
+        cydiaPomonella.renderWeatherstations();
     }
+    // We need to do it this way to keep the "this" reference of the class
+    window.runModel = function () {cydiaPomonella.runModel()};
+    window.runHeatSumCalculations = function () {cydiaPomonella.runHeatSumCalculations();}
 </script>
 {% endblock %}
 
@@ -53,6 +57,7 @@ Created: 2024-10-28
 </div>
 <div class="row">
     <div class="col-sm-4">
+        
         <select name="weatherStationName" id="weatherStationId" class="form-control" onchange="runModel();">
             <option value="-1">-- {% trans "Select weather station" %} --</option>
         </select>
-- 
GitLab