I would like to allow the user to show that they have practiced one or more of their specific tasks on different dates.
the task is not to be marked as “done” in a boolean sense but rather I need to track each time a specific user works on a specific task.
I am trying to keep the row count down so I am thinking the most efficient way to store the data would be to create a row for userA/task 1, then use split tags to record each time userA works on task1. - Row 2 would be userA task2, with timestamps for each time they worked on task2.
Row 3 would be userB, Task1 timestamp, row 4 userB/task2, etc
The issue is I can see the row count exploding as users and tasks increase. No doubt there is some Glide-y work around that is beyond my noob skills at this point. Help appreciated.
Carl
Hey fellow Gliders!
Part two of my Trebuchet series is live!
Last week, I posted a tutorial on a new way to create inline arrays using the Trebuchet method
Part 2 is a deep dive on how you can Trebuchet more than a single value by creating arrays of JSON .
Check out how to build an inventory ordering app using the JSON Trebuchet Method!
:popcorn:Enjoy!
[Glide: Build an Inventory Ordering App Using the JSON Trebuchet Method]
I think it should be done in a JSON trebuchet sense.
Have two user-specific fields for “start time” and “stop time”. Then a JSON object with those two fields.
Have a multiple files column to store the JSON array.
Have a “Make array” column to combine the multiple files column with the JSON object.
Create two buttons with two actions, “start” and “stop” for your front end.
Start button would be visible when the “start time” field is empty. It would set the current timestamp to the “start time” field.
End button would be visible when the “start time” field is not empty. It would set the current timestamp to the “end time” field, set the “Make array” column value to the “Multiple files” field, then clear both the start and end time.
I will record a video later for this.
Summary:
The video demonstrates the process of recreating a stopwatch component and explaining the setup involving custom components, JSON arrays, and time calculations.
Chapters:
00:00 Introduction and Setup
02:00 Data Processing and...
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<title>AlpineJS Stopwatch</title>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/alpinejs/3.10.5/cdn.min.js"
defer
></script>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f0f0f0;
}
.stopwatch {
background-color: #ffffff;
border-radius: 10px;
padding: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
text-align: center;
}
.time {
font-size: 48px;
font-weight: bold;
margin-bottom: 20px;
color: #333;
}
button {
font-size: 16px;
padding: 10px 20px;
margin: 0 5px;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
}
.start { background-color: #4CAF50; color: white; }
.pause { background-color: #FFC107; color: black; }
.resume { background-color: #2196F3; color: white; }
.stop { background-color: #F44336; color: white; }
.reset { background-color: #9E9E9E; color: white; }
button:hover { opacity: 0.8; }
</style>
</head>
<body>
<div class="stopwatch" x-data="stopwatch()">
<div
class="time"
x-text="formatTime(elapsedTime)"
></div>
<button
@click="start"
x-show="!isRunning && !isPaused"
class="start"
>
Start
</button>
<button
@click="pause"
x-show="isRunning"
class="pause"
>
Pause
</button>
<button
@click="resume"
x-show="isPaused"
class="resume"
>
Resume
</button>
<button
@click="stop"
x-show="isRunning || isPaused"
class="stop"
>
Stop
</button>
<button
@click="reset"
x-show="isPaused || (!isRunning && !isPaused)"
class="reset"
>
Reset
</button>
</div>
<script>
function stopwatch() {
return {
isRunning: false,
isPaused: false,
startTime: 0,
elapsedTime: 0,
timerInterval: null,
start() {
if (!this.isRunning && !this.isPaused) {
this.isRunning = true;
this.startTime =
Date.now() - this.elapsedTime;
this.timerInterval = setInterval(() => {
this.elapsedTime =
Date.now() - this.startTime;
}, 10);
startRecord();
}
},
pause() {
if (this.isRunning) {
this.isRunning = false;
this.isPaused = true;
clearInterval(this.timerInterval);
}
},
resume() {
if (this.isPaused) {
this.isRunning = true;
this.isPaused = false;
this.startTime =
Date.now() - this.elapsedTime;
this.timerInterval = setInterval(() => {
this.elapsedTime =
Date.now() - this.startTime;
}, 10);
}
},
stop() {
this.isRunning = false;
this.isPaused = false;
clearInterval(this.timerInterval);
S.duration = this.elapsedTime / 1000;
this.elapsedTime = 0;
setTimeout(() => {
logRecord();
}, 1000);
},
reset() {
this.elapsedTime = 0;
reset();
},
formatTime(ms) {
let centiseconds = Math.floor((ms % 1000) / 10);
let seconds = Math.floor((ms / 1000) % 60);
let minutes = Math.floor(
(ms / (1000 * 60)) % 60
);
let hours = Math.floor(
(ms / (1000 * 60 * 60)) % 24
);
return `${hours
.toString()
.padStart(2, "0")}:${minutes
.toString()
.padStart(2, "0")}:${seconds
.toString()
.padStart(2, "0")}.${centiseconds
.toString()
.padStart(2, "0")}`;
},
};
}
</script>
</body>
</html>
JavaScript to generate the table:
function jsonToHtmlTable(jsonString) {
// Parse JSON
const data = JSON.parse(jsonString);
// Function to format date
function formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleString('en-US', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
fractionalSecondDigits: 3
});
}
// Function to format duration
function formatDuration(durationSeconds) {
const totalSeconds = parseFloat(durationSeconds);
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = Math.floor(totalSeconds % 60);
const milliseconds = Math.round((totalSeconds % 1) * 1000);
let formattedDuration = '';
if (hours > 0) {
formattedDuration += `${hours}h `;
}
if (minutes > 0 || hours > 0) {
formattedDuration += `${minutes}m `;
}
formattedDuration += `${seconds}.${milliseconds.toString().padStart(3, '0')}s`;
return formattedDuration.trim();
}
// Generate table HTML
let tableHtml = '<table style="border-collapse: collapse; width: 100%; font-family: Arial, sans-serif;"><thead style="background-color: #f2f2f2;"><tr><th style="border: 1px solid #ddd; padding: 12px; text-align: left;">Start Time</th><th style="border: 1px solid #ddd; padding: 12px; text-align: left;">End Time</th><th style="border: 1px solid #ddd; padding: 12px; text-align: left;">Duration</th></tr></thead><tbody>';
data.forEach((item, index) => {
const rowStyle = index % 2 === 0 ? 'background-color: #f9f9f9;' : 'background-color: #ffffff;';
tableHtml += `<tr style="${rowStyle}">`;
tableHtml += `<td style="border: 1px solid #ddd; padding: 12px;">${formatDate(item['Start Time'])}</td>`;
tableHtml += `<td style="border: 1px solid #ddd; padding: 12px;">${formatDate(item['End Time'])}</td>`;
tableHtml += `<td style="border: 1px solid #ddd; padding: 12px;">${formatDuration(item['Duration'])}</td>`;
tableHtml += '</tr>';
});
tableHtml += '</tbody></table>';
return tableHtml;
}
return jsonToHtmlTable(p1);
1 Like
Thanks for the detailed explanaion, video and code. There’s a lot to absorb but its great to now have a pausable stopwatch function.
Initially I am going to implement a simple “I completed task 1 today” for earch user and will implement the duration in the next version.
MUCH appreciated.
1 Like
tuzin
July 14, 2024, 3:08pm
5
I still don’t get how the actions are connected to the buttons. Could you please explain it? Thank you!
There are 3 defined actions with Glide: startRecord, logRecord and reset. I name them in the actions section. You can check the code part and see where I reference those.
Basically each button I have in the HTML would have an on-click action defined by the @click part. Then with each action, I do some stuff (change the status of some boolean variables, set some values, and call actions where needed).
tuzin
July 15, 2024, 7:32am
7
I apologize for my blindness! So the names of the actions simply become function names we can use however we like, right?
Yes that’s correct. You just have to call it again in the body of the HTML.
1 Like
Thanks for sharing Thinh, the new custom component is so powerful. I was sure that you would love it and test it asap