diff --git a/index.php b/index.php
index 465eea2..f4088d0 100755
--- a/index.php
+++ b/index.php
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Harjoittelupaikat</title>
- <link rel="stylesheet" type="text/css" href="style.css">
+ <link rel="stylesheet" type="text/css" href="style2.css">
</head>
<body>
<?php include "header.php"; ?>
<h2>Tässä on ensimmäinen (ja ainoa) taulukko</h2>
<p>Tässä on taulukko</p>
<div class="table-container">
+ <input class="searchInput" data-table-id="tr" type="search" placeholder="Search" aria-label="Search" aria-target="tr">
<table>
<tr>
<th>Nimi</th>
<th>Status</th>
<th>Ruokaraha</th>
<th>Muuta</th>
+ <th>Muokkaus</th>
</tr>
<!-- Ota tiedot tiedot kannasta -->
<?php
?>
</table>
</div>
+ <script src="./search ja sort table/scripts/searchTable.js"></script>
+ <script src="./search ja sort table/scripts/sortTable.js"></script>
</section>
<!--
<aside>
<hr>
<p>© Harjoittelupaikat 2025</p>
<a href="index.html">Takaisin alkuun</a>
- </footer>
+ </footer>
+ <script>
+ var inputs = document.querySelectorAll('input'); // get the input element
+ inputs.forEach((input) => {
+ if (input.type == "text")
+ {
+ console.log(input.value);
+ input.addEventListener('input', resizeInput); // bind the "resizeInput" callback on "input" event
+ resizeInput.call(input); // immediately call the function
+ }
+ });
+
+ function resizeInput() {
+ this.style.width = this.value.length + "ch";
+ }
+ </script>
</body>
</html>
diff --git a/search ja sort table/.gitignore b/search ja sort table/.gitignore
new file mode 100644
index 0000000..46b8baa
--- /dev/null
+++ b/search ja sort table/.gitignore
+.vscode/
+index.html
diff --git a/search ja sort table/LICENSE b/search ja sort table/LICENSE
new file mode 100644
index 0000000..d04e22e
--- /dev/null
+++ b/search ja sort table/LICENSE
+MIT License
+
+Copyright (c) 2020 Rory Sullivan
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/search ja sort table/README.md b/search ja sort table/README.md
new file mode 100644
index 0000000..3a7366b
--- /dev/null
+++ b/search ja sort table/README.md
+# Search and Sort HTML Tables
+
+A simple and easy way to add sorting and search/filter functionality to your
+HTML table. See
+[here](https://rory-sullivan.github.io/Search-and-Sort-HTML-Tables/) for a
+working example.
+
+## Features
+
+* Support for multiple tables on a single page
+* Icons indicating sort direction
+* Support for hidden sort values
+* Preserves previous sort
+
+Note that this package will not work with paginated tables, the whole table has
+to be visible on the page.
+
+## Installation and Usage
+
+Download the [latest version](https://github.com/Rory-Sullivan/Search-and-Sort-HTML-Tables/releases)
+into your project (unpack it wherever you keep your static files or packages).
+Add the following scripts to the page containing the table you wish to sort or
+search (make sure to add the local path to the scripts). Put the script
+somewhere after the table, usually just before the closing body tag.
+
+```html
+<script src="./LOCAL_PATH/scripts/searchTable.js"></script>
+<script src="./LOCAL_PATH/scripts/sortTable.js"></script>
+```
+
+Use the example table provided in the example folder to build up your table or,
+if you have and existing table follow the instructions bellow.
+
+### Sort Feature
+
+To make your table sortable add a class of `.sortTable` to the `<table>` element
+and give it an `id` if it does not have one already. Make sure you have a
+`<thead>` section, all the headers in this section will become clickable and
+will sort the table based on the relevant column. By default, the sort function
+sorts alphanumerically, if you would rather sort a column numerically add
+`data-type="number"` to the appropriate header tag.
+
+#### Custom sort values
+
+Sometimes you will want to sort based on a value other than that displayed in
+the table. For example if you have a status column with categories 'low',
+'medium' and 'high', sorting this alphabetically would not make sense. In
+this case you can add a `data-sort-value` attribute to the relevant `<td>`
+tags. Data with a sort value will be sorted based on that value instead of
+the inner text. In the example above we might assign a value of 1 to 'low', 2 to
+'medium' and 3 to 'high' so that they are sorted in a more sensible order.
+
+```html
+<td data-sort-value="1">Low</td>
+<td data-sort-value="2">Medium</td>
+<td data-sort-value="3">High</td>
+```
+
+### Search/Filter Feature
+
+Add a search input with a class of `.searchInput` as below. Set the
+`data-table-id` attribute to the id of the table you wish to search.
+
+```html
+<input class="searchInput" data-table-id="TABLE_ID" type="search" placeholder="Search" aria-label="Search" aria-target="TABLE_ID">
+```
+
+### Multiple tables
+
+For multiple tables simply repeat the above steps for each table, making sure
+each table has a unique id.
+
+## Notes on the Implementation
+
+The sort function uses a simple bubble sort algorithm. While this is definitely
+not the fastest or most efficient algorithm for the job it has some advantages.
+Firstly the algorithm is small and easy to understand making the code base small
+and easy for an end user to jump in and see what is going on. Secondly it is
+very memory efficient, consuming virtually no extra memory above what it takes
+to store the table. Finally bubble sort is stable meaning it preserves the
+previous sort order of the elements.
+
+If a header is clicked multiple times in order to swap between ascending and
+descending order, the sort function is only implemented the first time.
+Subsequent sorts make a call to a reverse function which simply reverses the
+order of the table rather than sorting it again. This increases the efficiency
+of subsequent sorts.
diff --git a/search ja sort table/scripts/searchTable.js b/search ja sort table/scripts/searchTable.js
new file mode 100644
index 0000000..a7441e4
--- /dev/null
+++ b/search ja sort table/scripts/searchTable.js
+function searchTable (tableId, value) {
+ const filter = value.toLowerCase()
+ const table = document.getElementById(tableId)
+ const rows = table.getElementsByTagName('tr')
+
+ for (let i = 1; i < rows.length; i++) { // skip first row as this is the header row
+ const row = rows[i]
+ let cols = Array.from(row.getElementsByTagName('td'))
+ cols = cols.concat(Array.from(row.getElementsByTagName('th')))
+
+ let shouldHide = true
+
+ cols.forEach(col => {
+ const text = col.innerText
+ if (text.toLowerCase().indexOf(filter) > -1) {
+ shouldHide = false
+ }
+ })
+
+ if (shouldHide) {
+ row.style.display = 'none'
+ } else {
+ row.style.display = ''
+ }
+ }
+}
+
+function addSearchListeners (inputs) {
+ inputs.forEach(input => {
+ input.addEventListener('input', (event) => {
+ const target = event.target
+ const targetTableId = target.dataset.tableId
+ const value = target.value
+ searchTable(targetTableId, value)
+ })
+ })
+}
+
+const searchInputs = Array.from(document.getElementsByClassName('searchInput'))
+
+addSearchListeners(searchInputs)
diff --git a/search ja sort table/scripts/sortTable.js b/search ja sort table/scripts/sortTable.js
new file mode 100644
index 0000000..ab97823
--- /dev/null
+++ b/search ja sort table/scripts/sortTable.js
+function sortTable (tableId, col, type) {
+ const table = document.getElementById(tableId)
+ const rows = table.rows
+
+ let sorting = true
+
+ while (sorting) {
+ sorting = false
+
+ for (let i = 1; i < rows.length - 1; i++) {
+ let swap = false
+ let x = rows[i].querySelectorAll('th, td')[col]
+ let y = rows[i + 1].querySelectorAll('th, td')[col]
+
+ if (x.dataset.sortValue) {
+ x = x.dataset.sortValue
+ y = y.dataset.sortValue
+ } else {
+ x = x.innerHTML
+ y = y.innerHTML
+ }
+
+ if (type === 'number') {
+ x = Number(x)
+ y = Number(y)
+ }
+
+ if (x > y) {
+ swap = true
+ }
+
+ if (swap) {
+ rows[i].parentNode.insertBefore(rows[i + 1], rows[i])
+ sorting = true
+ }
+ }
+ }
+}
+
+function reverseTable (tableId) {
+ const table = document.getElementById(tableId)
+ const rows = table.rows
+ const lastRow = rows.length - 1
+
+ for (let i = 1; i < rows.length; i++) {
+ rows[i].parentNode.insertBefore(rows[lastRow], rows[i])
+ }
+}
+
+function getHeaders (table) {
+ const head = table.getElementsByTagName('thead')[0]
+ return Array.from(head.getElementsByTagName('th'))
+}
+
+function addSortListeners (tables) {
+ tables.forEach(table => {
+ const headers = getHeaders(table)
+ const tableId = table.id
+
+ for (let i = 0; i < headers.length; i++) {
+ const header = headers[i]
+ const columnNo = i
+ let sortType
+
+ if (header.dataset.type) {
+ sortType = header.dataset.type
+ } else {
+ sortType = ''
+ }
+
+ header.addEventListener('click', (event) => {
+ const target = event.target
+ const sorted = (target.dataset.sorted === 'true')
+
+ if (sorted) {
+ reverseTable(tableId)
+ const arrow = target.getElementsByTagName('span')[0]
+ if (arrow.innerHTML === '↑') {
+ arrow.innerHTML = '↓'
+ } else {
+ arrow.innerHTML = '↑'
+ }
+ } else {
+ headers.forEach(header2 => {
+ const arrow = header2.getElementsByTagName('span')[0]
+ if (arrow) {
+ if (header2.dataset.sorted === 'true') {
+ arrow.innerHTML = '•'
+ } else {
+ arrow.innerHTML = ''
+ }
+ } else {
+ header2.innerHTML += '<span></span>'
+ }
+ header2.dataset.sorted = false
+ })
+
+ sortTable(tableId, columnNo, sortType)
+ target.dataset.sorted = true
+ target.getElementsByTagName('span')[0].innerHTML = '↑'
+ }
+ })
+ }
+ })
+}
+
+const TABLES = Array.from(document.getElementsByClassName('sortTable'))
+
+addSortListeners(TABLES)
diff --git a/style2.css b/style2.css
new file mode 100644
index 0000000..7093bba
--- /dev/null
+++ b/style2.css
+/* Global background + text color */
+body {
+ background-color: black;
+ color: white; /* or use the animated color below */
+ margin: 0;
+ font-family: sans-serif;
+}
+
+/* Optional: animated RGB color loop for text & borders */
+@keyframes rgbLoop {
+ 0% { color: rgb(255, 0, 0); border-color: rgb(255, 0, 0); }
+ 33% { color: rgb(0, 255, 0); border-color: rgb(0, 255, 0); }
+ 66% { color: rgb(0, 0, 255); border-color: rgb(0, 0, 255); }
+ 100% { color: rgb(255, 0, 0); border-color: rgb(255, 0, 0); }
+}
+
+/* Apply to text and borders */
+body, table, th, td, nav, main, section, aside, footer {
+ animation: rgbLoop 6s linear infinite;
+}
+
+/* Table styles */
+table, th, td {
+ border: 1px solid;
+ border-collapse: collapse;
+}
+
+/* Scrollable table container */
+.table-container {
+ width: 100%;
+ overflow-x: auto;
+}
+
+/* Layout sections */
+nav {
+ text-align: right;
+}
+main {
+ width: 100vw; /* fixed typo: wv → vw */
+ display: flex;
+ flex-direction: row;
+ flex: 1;
+}
+section {
+ width: 100%;
+}
+aside {
+ text-align: right;
+ flex: 1;
+}
+footer {
+ text-align: center;
+}