htmx integration
This commit is contained in:
185
db.sql
Normal file
185
db.sql
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
-- --------------------------------------------------------
|
||||||
|
-- Host: 10.10.5.31
|
||||||
|
-- Server-Version: 8.0.42-0ubuntu0.24.04.1 - (Ubuntu)
|
||||||
|
-- Server-Betriebssystem: Linux
|
||||||
|
-- HeidiSQL Version: 12.10.0.7000
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET NAMES utf8 */;
|
||||||
|
/*!50503 SET NAMES utf8mb4 */;
|
||||||
|
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
||||||
|
/*!40103 SET TIME_ZONE='+00:00' */;
|
||||||
|
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||||
|
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||||
|
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
||||||
|
|
||||||
|
|
||||||
|
-- Exportiere Datenbank-Struktur für hikos
|
||||||
|
CREATE DATABASE IF NOT EXISTS `hikos` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;
|
||||||
|
USE `hikos`;
|
||||||
|
|
||||||
|
-- Exportiere Struktur von Tabelle hikos.aduser
|
||||||
|
CREATE TABLE IF NOT EXISTS `aduser` (
|
||||||
|
`aduser_id` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`aduser_samaccountname` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`aduser_sid` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`aduser_ruleset_id` int NOT NULL DEFAULT '-1',
|
||||||
|
PRIMARY KEY (`aduser_id`),
|
||||||
|
UNIQUE KEY `aduser_samaccountname` (`aduser_samaccountname`),
|
||||||
|
UNIQUE KEY `aduser_sid` (`aduser_sid`),
|
||||||
|
KEY `FK_aduser_ruleset` (`aduser_ruleset_id`),
|
||||||
|
CONSTRAINT `FK_aduser_ruleset` FOREIGN KEY (`aduser_ruleset_id`) REFERENCES `ruleset` (`ruleset_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
-- Exportiere Daten aus Tabelle hikos.aduser: ~0 rows (ungefähr)
|
||||||
|
|
||||||
|
-- Exportiere Struktur von Tabelle hikos.contact
|
||||||
|
CREATE TABLE IF NOT EXISTS `contact` (
|
||||||
|
`contact_id` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`contact_owner_id` int NOT NULL DEFAULT '-1',
|
||||||
|
`contact_aduser_id` int DEFAULT NULL,
|
||||||
|
`contact_displayname` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`contact_phone` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`contact_mobile` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`contact_homeoffice` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`contact_email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`contact_room` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`contact_department_id` int DEFAULT NULL,
|
||||||
|
`contact_location_id` int DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`contact_id`) USING BTREE,
|
||||||
|
UNIQUE KEY `contact_aduser_id` (`contact_aduser_id`),
|
||||||
|
KEY `FK_contact_department` (`contact_department_id`),
|
||||||
|
KEY `FK_contact_location` (`contact_location_id`),
|
||||||
|
CONSTRAINT `FK_contact_aduser_2` FOREIGN KEY (`contact_aduser_id`) REFERENCES `aduser` (`aduser_id`),
|
||||||
|
CONSTRAINT `FK_contact_department` FOREIGN KEY (`contact_department_id`) REFERENCES `department` (`department_id`),
|
||||||
|
CONSTRAINT `FK_contact_location` FOREIGN KEY (`contact_location_id`) REFERENCES `location` (`location_id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
-- Exportiere Daten aus Tabelle hikos.contact: ~1 rows (ungefähr)
|
||||||
|
REPLACE INTO `contact` (`contact_id`, `contact_owner_id`, `contact_aduser_id`, `contact_displayname`, `contact_phone`, `contact_mobile`, `contact_homeoffice`, `contact_email`, `contact_room`, `contact_department_id`, `contact_location_id`) VALUES
|
||||||
|
(1, -1, NULL, 'Bergner, Jan', '1142', '123', '123', '123', '123', NULL, NULL);
|
||||||
|
|
||||||
|
-- Exportiere Struktur von Tabelle hikos.contactkeyword
|
||||||
|
CREATE TABLE IF NOT EXISTS `contactkeyword` (
|
||||||
|
`contactkeyword_contact` int NOT NULL,
|
||||||
|
`contactkeyword_keyword` int NOT NULL,
|
||||||
|
PRIMARY KEY (`contactkeyword_contact`,`contactkeyword_keyword`),
|
||||||
|
KEY `FK_contactkeyword_keyword` (`contactkeyword_keyword`),
|
||||||
|
CONSTRAINT `FK_contactkeyword_contact` FOREIGN KEY (`contactkeyword_contact`) REFERENCES `contact` (`contact_id`),
|
||||||
|
CONSTRAINT `FK_contactkeyword_keyword` FOREIGN KEY (`contactkeyword_keyword`) REFERENCES `keyword` (`keyword_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
-- Exportiere Daten aus Tabelle hikos.contactkeyword: ~0 rows (ungefähr)
|
||||||
|
|
||||||
|
-- Exportiere Struktur von Tabelle hikos.department
|
||||||
|
CREATE TABLE IF NOT EXISTS `department` (
|
||||||
|
`department_id` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`department_name` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
PRIMARY KEY (`department_id`),
|
||||||
|
UNIQUE KEY `department_name` (`department_name`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
-- Exportiere Daten aus Tabelle hikos.department: ~0 rows (ungefähr)
|
||||||
|
|
||||||
|
-- Exportiere Struktur von Tabelle hikos.flag
|
||||||
|
CREATE TABLE IF NOT EXISTS `flag` (
|
||||||
|
`flag_id` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`flag_contact_id` int NOT NULL,
|
||||||
|
`flag_key` varchar(250) COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`flag_value` longtext COLLATE utf8mb4_general_ci,
|
||||||
|
PRIMARY KEY (`flag_id`),
|
||||||
|
UNIQUE KEY `flag_contact_id_flag_key` (`flag_contact_id`,`flag_key`),
|
||||||
|
CONSTRAINT `FK__contact` FOREIGN KEY (`flag_contact_id`) REFERENCES `contact` (`contact_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
-- Exportiere Daten aus Tabelle hikos.flag: ~0 rows (ungefähr)
|
||||||
|
|
||||||
|
-- Exportiere Struktur von Tabelle hikos.keyword
|
||||||
|
CREATE TABLE IF NOT EXISTS `keyword` (
|
||||||
|
`keyword_id` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`keyword_owner` int NOT NULL DEFAULT '-1',
|
||||||
|
`keyword_name` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
PRIMARY KEY (`keyword_id`),
|
||||||
|
UNIQUE KEY `keyword_owner_keyword_name` (`keyword_owner`,`keyword_name`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
-- Exportiere Daten aus Tabelle hikos.keyword: ~2 rows (ungefähr)
|
||||||
|
REPLACE INTO `keyword` (`keyword_id`, `keyword_owner`, `keyword_name`) VALUES
|
||||||
|
(1, -1, 'Test'),
|
||||||
|
(2, 1, 'Demo1');
|
||||||
|
|
||||||
|
-- Exportiere Struktur von Tabelle hikos.location
|
||||||
|
CREATE TABLE IF NOT EXISTS `location` (
|
||||||
|
`location_id` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`location_name` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`location_address` varchar(250) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`location_zip` varchar(250) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`location_city` varchar(250) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`location_id`),
|
||||||
|
UNIQUE KEY `location_name` (`location_name`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
-- Exportiere Daten aus Tabelle hikos.location: ~0 rows (ungefähr)
|
||||||
|
|
||||||
|
-- Exportiere Struktur von Tabelle hikos.ruleset
|
||||||
|
CREATE TABLE IF NOT EXISTS `ruleset` (
|
||||||
|
`ruleset_id` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`ruleset_name` varchar(250) COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`ruleset_default_contact_read` int NOT NULL DEFAULT '1',
|
||||||
|
`ruleset_default_contact_write` int NOT NULL DEFAULT '0',
|
||||||
|
`ruleset_default_contact_delete` int NOT NULL DEFAULT '0',
|
||||||
|
`ruleset_default_keyword_read` int NOT NULL DEFAULT '1',
|
||||||
|
`ruleset_default_keyword_write` int NOT NULL DEFAULT '0',
|
||||||
|
`ruleset_default_keyword_delete` int NOT NULL DEFAULT '0',
|
||||||
|
`ruleset_default_keyword_attach` int NOT NULL DEFAULT '0',
|
||||||
|
`ruleset_default_keyword_detach` int NOT NULL DEFAULT '0',
|
||||||
|
`ruleset_default_aduser_read` int NOT NULL DEFAULT '0',
|
||||||
|
`ruleset_default_aduser_write` int NOT NULL DEFAULT '0',
|
||||||
|
`ruleset_default_aduser_delete` int NOT NULL DEFAULT '0',
|
||||||
|
`ruleset_default_location_read` int NOT NULL DEFAULT '1',
|
||||||
|
`ruleset_default_location_write` int NOT NULL DEFAULT '0',
|
||||||
|
`ruleset_default_location_delete` int NOT NULL DEFAULT '0',
|
||||||
|
`ruleset_default_department_read` int NOT NULL DEFAULT '1',
|
||||||
|
`ruleset_default_department_write` int NOT NULL DEFAULT '0',
|
||||||
|
`ruleset_default_department_delete` int NOT NULL DEFAULT '0',
|
||||||
|
`ruleset_self_contact_read` int NOT NULL DEFAULT '1',
|
||||||
|
`ruleset_self_contact_write` int NOT NULL DEFAULT '1',
|
||||||
|
`ruleset_self_keyword_attach` int NOT NULL DEFAULT '1',
|
||||||
|
`ruleset_self_keyword_detach` int NOT NULL DEFAULT '1',
|
||||||
|
`ruleset_private_contact_read` int NOT NULL DEFAULT '1',
|
||||||
|
`ruleset_private_contact_write` int NOT NULL DEFAULT '1',
|
||||||
|
`ruleset_private_keyword_add` int NOT NULL DEFAULT '1',
|
||||||
|
`ruleset_private_keyword_delete` int NOT NULL DEFAULT '1',
|
||||||
|
`ruleset_private_keyword_attach` int NOT NULL DEFAULT '1',
|
||||||
|
`ruleset_private_keyword_detach` int NOT NULL DEFAULT '1',
|
||||||
|
PRIMARY KEY (`ruleset_id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
-- Exportiere Daten aus Tabelle hikos.ruleset: ~2 rows (ungefähr)
|
||||||
|
REPLACE INTO `ruleset` (`ruleset_id`, `ruleset_name`, `ruleset_default_contact_read`, `ruleset_default_contact_write`, `ruleset_default_contact_delete`, `ruleset_default_keyword_read`, `ruleset_default_keyword_write`, `ruleset_default_keyword_delete`, `ruleset_default_keyword_attach`, `ruleset_default_keyword_detach`, `ruleset_default_aduser_read`, `ruleset_default_aduser_write`, `ruleset_default_aduser_delete`, `ruleset_default_location_read`, `ruleset_default_location_write`, `ruleset_default_location_delete`, `ruleset_default_department_read`, `ruleset_default_department_write`, `ruleset_default_department_delete`, `ruleset_self_contact_read`, `ruleset_self_contact_write`, `ruleset_self_keyword_attach`, `ruleset_self_keyword_detach`, `ruleset_private_contact_read`, `ruleset_private_contact_write`, `ruleset_private_keyword_add`, `ruleset_private_keyword_delete`, `ruleset_private_keyword_attach`, `ruleset_private_keyword_detach`) VALUES
|
||||||
|
(-1, 'Default_Ruleset', 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
|
||||||
|
(0, 'Default_Admin_Ruleset', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
|
||||||
|
|
||||||
|
-- Exportiere Struktur von Tabelle hikos.sessions
|
||||||
|
CREATE TABLE IF NOT EXISTS `sessions` (
|
||||||
|
`id` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`username` varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`token` varchar(512) COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`expires_at` timestamp NOT NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
-- Exportiere Daten aus Tabelle hikos.sessions: ~4 rows (ungefähr)
|
||||||
|
REPLACE INTO `sessions` (`id`, `username`, `token`, `created_at`, `expires_at`) VALUES
|
||||||
|
(5, 'admin', 'qITgAy47f5gmlBG72y_vJWh5IC7_aXQGKsL6M7IFTWU', '2025-05-20 18:14:30', '2025-05-21 18:14:30'),
|
||||||
|
(6, 'admin', 'Vjde9ngqPdUchF_I2_FzekK6Kb3-wBtZy_7mFPtRMV8', '2025-05-20 19:18:58', '2025-05-21 19:18:58'),
|
||||||
|
(7, 'admin', 'E2znECPx1Ppcwsby4ky7sQMf55Wax0KttnFmV83rqoc', '2025-05-21 04:21:43', '2025-05-22 04:21:43'),
|
||||||
|
(8, 'admin', 'Y7MVV-Nd4i1BPEerPLbv0jM0gJ4dYnnqZ0OtxnVWUXw', '2025-05-21 04:22:08', '2025-05-22 04:22:08');
|
||||||
|
|
||||||
|
/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */;
|
||||||
|
/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
|
||||||
|
/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */;
|
||||||
|
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */;
|
86
main.go
86
main.go
@@ -128,6 +128,7 @@ type Keyword struct {
|
|||||||
type DataPort struct {
|
type DataPort struct {
|
||||||
Contacts []Contact
|
Contacts []Contact
|
||||||
Keywords []Keyword
|
Keywords []Keyword
|
||||||
|
/*Links []ContactKeywordLink*/
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ################################################################## */
|
/* ################################################################## */
|
||||||
@@ -144,33 +145,39 @@ func (s *Server) privateHello(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetDataReturnDataPort(QueryContact, QueryKeyword string) DataPort {
|
func GetDataReturnDataPort(QueryContact, QueryKeyword string) DataPort {
|
||||||
rows, err := DB.Query(QueryContact)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("a", err)
|
|
||||||
}
|
|
||||||
var contList []Contact
|
var contList []Contact
|
||||||
for rows.Next() {
|
var keywordList []Keyword
|
||||||
var c Contact
|
|
||||||
err = rows.Scan(&c.Id, &c.OwnerId, &c.AdUserId, &c.DisplayName, &c.Phone, &c.Mobile, &c.Homeoffice, &c.Email, &c.Room, &c.DepartmentId, &c.LocationId)
|
if QueryContact != "" {
|
||||||
|
rowsContact, err := DB.Query(QueryContact)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("b", err)
|
fmt.Println("a", err)
|
||||||
|
}
|
||||||
|
for rowsContact.Next() {
|
||||||
|
var c Contact
|
||||||
|
err = rowsContact.Scan(&c.Id, &c.OwnerId, &c.AdUserId, &c.DisplayName, &c.Phone, &c.Mobile, &c.Homeoffice, &c.Email, &c.Room, &c.DepartmentId, &c.LocationId)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("b", err)
|
||||||
|
}
|
||||||
|
contList = append(contList, c)
|
||||||
}
|
}
|
||||||
contList = append(contList, c)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rows1, err := DB.Query(QueryKeyword)
|
if QueryKeyword != "" {
|
||||||
if err != nil {
|
rowsKeyword, err := DB.Query(QueryKeyword)
|
||||||
fmt.Println("c", err)
|
|
||||||
}
|
|
||||||
var keywordList []Keyword
|
|
||||||
for rows1.Next() {
|
|
||||||
var c0 Keyword
|
|
||||||
err = rows1.Scan(&c0.Id, &c0.Owner, &c0.Name)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("d", err)
|
fmt.Println("c", err)
|
||||||
|
}
|
||||||
|
for rowsKeyword.Next() {
|
||||||
|
var c0 Keyword
|
||||||
|
err = rowsKeyword.Scan(&c0.Id, &c0.Owner, &c0.Name)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("d", err)
|
||||||
|
}
|
||||||
|
keywordList = append(keywordList, c0)
|
||||||
}
|
}
|
||||||
keywordList = append(keywordList, c0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return DataPort{Contacts: contList, Keywords: keywordList}
|
return DataPort{Contacts: contList, Keywords: keywordList}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,44 +290,39 @@ func main() {
|
|||||||
tplFull.ExecuteTemplate(w, "layout", nil)
|
tplFull.ExecuteTemplate(w, "layout", nil)
|
||||||
})*/
|
})*/
|
||||||
|
|
||||||
mux.HandleFunc("/htmx/kontakt", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/htmx/contact", func(w http.ResponseWriter, r *http.Request) {
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
http.Error(w, "bad request", http.StatusBadRequest)
|
http.Error(w, "bad request", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sparam := strings.TrimSpace(r.Form.Get("search"))
|
sparam := strings.TrimSpace(r.Form.Get("search"))
|
||||||
|
fmt.Println("All_OK", sparam)
|
||||||
sqlq := "SELECT * FROM contact c WHERE c.contact_displayname LIKE '%" + sparam + "%' OR c.contact_phone LIKE '%" + sparam + "%' OR c.contact_mobile LIKE '%" + sparam + "%' OR c.contact_homeoffice LIKE '%" + sparam + "%';"
|
sqlq := "SELECT * FROM contact c WHERE c.contact_displayname LIKE '%" + sparam + "%' OR c.contact_phone LIKE '%" + sparam + "%' OR c.contact_mobile LIKE '%" + sparam + "%' OR c.contact_homeoffice LIKE '%" + sparam + "%';"
|
||||||
|
D := GetDataReturnDataPort(sqlq, "")
|
||||||
rows, err := db.Query(sqlq)
|
tplKontakt.ExecuteTemplate(w, "kontakt", D)
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
var contList []Contact
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
var c Contact
|
|
||||||
err = rows.Scan(&c.Id)
|
|
||||||
contList = append(contList, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
tplKontakt.ExecuteTemplate(w, "kontakt", contList)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.HandleFunc("/htmx/kontaktbyschlagwort", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/htmx/kontaktbyschlagwort", func(w http.ResponseWriter, r *http.Request) {
|
||||||
tplKontakt.ExecuteTemplate(w, "kontakt", nil)
|
tplKontakt.ExecuteTemplate(w, "kontakt", nil)
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.HandleFunc("/htmx/amt", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/htmx/department", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
http.Error(w, "bad request", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sparam := strings.TrimSpace(r.Form.Get("search"))
|
||||||
|
fmt.Println("Department_OK", sparam)
|
||||||
|
sqlq := "SELECT c.* FROM contact c JOIN department d ON c.contact_department_id = d.department_id WHERE d.department_name LIKE '%" + sparam + "%';"
|
||||||
|
D := GetDataReturnDataPort(sqlq, "")
|
||||||
|
tplKontakt.ExecuteTemplate(w, "kontakt", D)
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.HandleFunc("/htmx/room", func(w http.ResponseWriter, r *http.Request) {
|
||||||
tplKontakt.ExecuteTemplate(w, "kontakt", nil)
|
tplKontakt.ExecuteTemplate(w, "kontakt", nil)
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.HandleFunc("/htmx/raum", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/htmx/location", func(w http.ResponseWriter, r *http.Request) {
|
||||||
tplKontakt.ExecuteTemplate(w, "kontakt", nil)
|
|
||||||
})
|
|
||||||
|
|
||||||
mux.HandleFunc("/htmx/gebaeude", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
tplKontakt.ExecuteTemplate(w, "kontakt", nil)
|
tplKontakt.ExecuteTemplate(w, "kontakt", nil)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
9
static/packages/htmx/ext/README.md
Normal file
9
static/packages/htmx/ext/README.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Why Are These Files Here?
|
||||||
|
|
||||||
|
These are legacy extensions for htmx 1.x and are **NOT** actively maintained or guaranteed to work with htmx 2.x.
|
||||||
|
They are here because we unfortunately linked to unversioned unpkg URLs in the installation guides for them
|
||||||
|
in 1.x, so we need to keep them here to preserve those URLs and not break existing users functionality.
|
||||||
|
|
||||||
|
If you are looking for extensions for htmx 2.x, please see the [htmx 2.0 extensions site](https://htmx.org/extensions),
|
||||||
|
which has links to the new extensions repos (They have all been moved to their own NPM projects and URLs, like
|
||||||
|
they should have been from the start!)
|
5
static/packages/htmx/ext/_WARNING_DEPRECATED_FILES_.txt
Normal file
5
static/packages/htmx/ext/_WARNING_DEPRECATED_FILES_.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
!! THESE FILES ARE DEPRECATED AND UNSUPPORTED !!
|
||||||
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
|
||||||
|
SEE README.md FOR MORE DETAILS
|
11
static/packages/htmx/ext/ajax-header.js
Normal file
11
static/packages/htmx/ext/ajax-header.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
if (htmx.version && !htmx.version.startsWith("1.")) {
|
||||||
|
console.warn("WARNING: You are using an htmx 1 extension with htmx " + htmx.version +
|
||||||
|
". It is recommended that you move to the version of this extension found on https://htmx.org/extensions")
|
||||||
|
}
|
||||||
|
htmx.defineExtension('ajax-header', {
|
||||||
|
onEvent: function (name, evt) {
|
||||||
|
if (name === "htmx:configRequest") {
|
||||||
|
evt.detail.headers['X-Requested-With'] = 'XMLHttpRequest';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
20
static/packages/htmx/ext/alpine-morph.js
Normal file
20
static/packages/htmx/ext/alpine-morph.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
if (htmx.version && !htmx.version.startsWith("1.")) {
|
||||||
|
console.warn("WARNING: You are using an htmx 1 extension with htmx " + htmx.version +
|
||||||
|
". It is recommended that you move to the version of this extension found on https://htmx.org/extensions")
|
||||||
|
}
|
||||||
|
htmx.defineExtension('alpine-morph', {
|
||||||
|
isInlineSwap: function (swapStyle) {
|
||||||
|
return swapStyle === 'morph';
|
||||||
|
},
|
||||||
|
handleSwap: function (swapStyle, target, fragment) {
|
||||||
|
if (swapStyle === 'morph') {
|
||||||
|
if (fragment.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
||||||
|
Alpine.morph(target, fragment.firstElementChild);
|
||||||
|
return [target];
|
||||||
|
} else {
|
||||||
|
Alpine.morph(target, fragment.outerHTML);
|
||||||
|
return [target];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
97
static/packages/htmx/ext/class-tools.js
Normal file
97
static/packages/htmx/ext/class-tools.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
(function () {
|
||||||
|
|
||||||
|
if (htmx.version && !htmx.version.startsWith("1.")) {
|
||||||
|
console.warn("WARNING: You are using an htmx 1 extension with htmx " + htmx.version +
|
||||||
|
". It is recommended that you move to the version of this extension found on https://htmx.org/extensions")
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitOnWhitespace(trigger) {
|
||||||
|
return trigger.split(/\s+/);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseClassOperation(trimmedValue) {
|
||||||
|
var split = splitOnWhitespace(trimmedValue);
|
||||||
|
if (split.length > 1) {
|
||||||
|
var operation = split[0];
|
||||||
|
var classDef = split[1].trim();
|
||||||
|
var cssClass;
|
||||||
|
var delay;
|
||||||
|
if (classDef.indexOf(":") > 0) {
|
||||||
|
var splitCssClass = classDef.split(':');
|
||||||
|
cssClass = splitCssClass[0];
|
||||||
|
delay = htmx.parseInterval(splitCssClass[1]);
|
||||||
|
} else {
|
||||||
|
cssClass = classDef;
|
||||||
|
delay = 100;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
operation: operation,
|
||||||
|
cssClass: cssClass,
|
||||||
|
delay: delay
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function performOperation(elt, classOperation, classList, currentRunTime) {
|
||||||
|
setTimeout(function () {
|
||||||
|
elt.classList[classOperation.operation].call(elt.classList, classOperation.cssClass);
|
||||||
|
}, currentRunTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleOperation(elt, classOperation, classList, currentRunTime) {
|
||||||
|
setTimeout(function () {
|
||||||
|
setInterval(function () {
|
||||||
|
elt.classList[classOperation.operation].call(elt.classList, classOperation.cssClass);
|
||||||
|
}, classOperation.delay);
|
||||||
|
}, currentRunTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
function processClassList(elt, classList) {
|
||||||
|
var runs = classList.split("&");
|
||||||
|
for (var i = 0; i < runs.length; i++) {
|
||||||
|
var run = runs[i];
|
||||||
|
var currentRunTime = 0;
|
||||||
|
var classOperations = run.split(",");
|
||||||
|
for (var j = 0; j < classOperations.length; j++) {
|
||||||
|
var value = classOperations[j];
|
||||||
|
var trimmedValue = value.trim();
|
||||||
|
var classOperation = parseClassOperation(trimmedValue);
|
||||||
|
if (classOperation) {
|
||||||
|
if (classOperation.operation === "toggle") {
|
||||||
|
toggleOperation(elt, classOperation, classList, currentRunTime);
|
||||||
|
currentRunTime = currentRunTime + classOperation.delay;
|
||||||
|
} else {
|
||||||
|
currentRunTime = currentRunTime + classOperation.delay;
|
||||||
|
performOperation(elt, classOperation, classList, currentRunTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function maybeProcessClasses(elt) {
|
||||||
|
if (elt.getAttribute) {
|
||||||
|
var classList = elt.getAttribute("classes") || elt.getAttribute("data-classes");
|
||||||
|
if (classList) {
|
||||||
|
processClassList(elt, classList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
htmx.defineExtension('class-tools', {
|
||||||
|
onEvent: function (name, evt) {
|
||||||
|
if (name === "htmx:afterProcessNode") {
|
||||||
|
var elt = evt.detail.elt;
|
||||||
|
maybeProcessClasses(elt);
|
||||||
|
if (elt.querySelectorAll) {
|
||||||
|
var children = elt.querySelectorAll("[classes], [data-classes]");
|
||||||
|
for (var i = 0; i < children.length; i++) {
|
||||||
|
maybeProcessClasses(children[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
100
static/packages/htmx/ext/client-side-templates.js
Normal file
100
static/packages/htmx/ext/client-side-templates.js
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
if (htmx.version && !htmx.version.startsWith("1.")) {
|
||||||
|
console.warn("WARNING: You are using an htmx 1 extension with htmx " + htmx.version +
|
||||||
|
". It is recommended that you move to the version of this extension found on https://htmx.org/extensions")
|
||||||
|
}
|
||||||
|
htmx.defineExtension('client-side-templates', {
|
||||||
|
transformResponse : function(text, xhr, elt) {
|
||||||
|
|
||||||
|
var mustacheTemplate = htmx.closest(elt, "[mustache-template]");
|
||||||
|
if (mustacheTemplate) {
|
||||||
|
var data = JSON.parse(text);
|
||||||
|
var templateId = mustacheTemplate.getAttribute('mustache-template');
|
||||||
|
var template = htmx.find("#" + templateId);
|
||||||
|
if (template) {
|
||||||
|
return Mustache.render(template.innerHTML, data);
|
||||||
|
} else {
|
||||||
|
throw "Unknown mustache template: " + templateId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var mustacheArrayTemplate = htmx.closest(elt, "[mustache-array-template]");
|
||||||
|
if (mustacheArrayTemplate) {
|
||||||
|
var data = JSON.parse(text);
|
||||||
|
var templateId = mustacheArrayTemplate.getAttribute('mustache-array-template');
|
||||||
|
var template = htmx.find("#" + templateId);
|
||||||
|
if (template) {
|
||||||
|
return Mustache.render(template.innerHTML, {"data": data });
|
||||||
|
} else {
|
||||||
|
throw "Unknown mustache template: " + templateId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var handlebarsTemplate = htmx.closest(elt, "[handlebars-template]");
|
||||||
|
if (handlebarsTemplate) {
|
||||||
|
var data = JSON.parse(text);
|
||||||
|
var templateId = handlebarsTemplate.getAttribute('handlebars-template');
|
||||||
|
var templateElement = htmx.find('#' + templateId).innerHTML;
|
||||||
|
var renderTemplate = Handlebars.compile(templateElement);
|
||||||
|
if (renderTemplate) {
|
||||||
|
return renderTemplate(data);
|
||||||
|
} else {
|
||||||
|
throw "Unknown handlebars template: " + templateId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var handlebarsArrayTemplate = htmx.closest(elt, "[handlebars-array-template]");
|
||||||
|
if (handlebarsArrayTemplate) {
|
||||||
|
var data = JSON.parse(text);
|
||||||
|
var templateId = handlebarsArrayTemplate.getAttribute('handlebars-array-template');
|
||||||
|
var templateElement = htmx.find('#' + templateId).innerHTML;
|
||||||
|
var renderTemplate = Handlebars.compile(templateElement);
|
||||||
|
if (renderTemplate) {
|
||||||
|
return renderTemplate(data);
|
||||||
|
} else {
|
||||||
|
throw "Unknown handlebars template: " + templateId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var nunjucksTemplate = htmx.closest(elt, "[nunjucks-template]");
|
||||||
|
if (nunjucksTemplate) {
|
||||||
|
var data = JSON.parse(text);
|
||||||
|
var templateName = nunjucksTemplate.getAttribute('nunjucks-template');
|
||||||
|
var template = htmx.find('#' + templateName);
|
||||||
|
if (template) {
|
||||||
|
return nunjucks.renderString(template.innerHTML, data);
|
||||||
|
} else {
|
||||||
|
return nunjucks.render(templateName, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var xsltTemplate = htmx.closest(elt, "[xslt-template]");
|
||||||
|
if (xsltTemplate) {
|
||||||
|
var templateId = xsltTemplate.getAttribute('xslt-template');
|
||||||
|
var template = htmx.find("#" + templateId);
|
||||||
|
if (template) {
|
||||||
|
var content = template.innerHTML ? new DOMParser().parseFromString(template.innerHTML, 'application/xml')
|
||||||
|
: template.contentDocument;
|
||||||
|
var processor = new XSLTProcessor();
|
||||||
|
processor.importStylesheet(content);
|
||||||
|
var data = new DOMParser().parseFromString(text, "application/xml");
|
||||||
|
var frag = processor.transformToFragment(data, document);
|
||||||
|
return new XMLSerializer().serializeToString(frag);
|
||||||
|
} else {
|
||||||
|
throw "Unknown XSLT template: " + templateId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var nunjucksArrayTemplate = htmx.closest(elt, "[nunjucks-array-template]");
|
||||||
|
if (nunjucksArrayTemplate) {
|
||||||
|
var data = JSON.parse(text);
|
||||||
|
var templateName = nunjucksArrayTemplate.getAttribute('nunjucks-array-template');
|
||||||
|
var template = htmx.find('#' + templateName);
|
||||||
|
if (template) {
|
||||||
|
return nunjucks.renderString(template.innerHTML, {"data": data});
|
||||||
|
} else {
|
||||||
|
return nunjucks.render(templateName, {"data": data});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
});
|
15
static/packages/htmx/ext/debug.js
Normal file
15
static/packages/htmx/ext/debug.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
if (htmx.version && !htmx.version.startsWith("1.")) {
|
||||||
|
console.warn("WARNING: You are using an htmx 1 extension with htmx " + htmx.version +
|
||||||
|
". It is recommended that you move to the version of this extension found on https://htmx.org/extensions")
|
||||||
|
}
|
||||||
|
htmx.defineExtension('debug', {
|
||||||
|
onEvent: function (name, evt) {
|
||||||
|
if (console.debug) {
|
||||||
|
console.debug(name, evt);
|
||||||
|
} else if (console) {
|
||||||
|
console.log("DEBUG:", name, evt);
|
||||||
|
} else {
|
||||||
|
throw "NO CONSOLE SUPPORTED"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
20
static/packages/htmx/ext/disable-element.js
Normal file
20
static/packages/htmx/ext/disable-element.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
if (htmx.version && !htmx.version.startsWith("1.")) {
|
||||||
|
console.warn("WARNING: You are using an htmx 1 extension with htmx " + htmx.version +
|
||||||
|
". It is recommended that you move to the version of this extension found on https://htmx.org/extensions")
|
||||||
|
}
|
||||||
|
// Disable Submit Button
|
||||||
|
htmx.defineExtension('disable-element', {
|
||||||
|
onEvent: function (name, evt) {
|
||||||
|
let elt = evt.detail.elt;
|
||||||
|
let target = elt.getAttribute("hx-disable-element");
|
||||||
|
let targetElements = (target == "self") ? [ elt ] : document.querySelectorAll(target);
|
||||||
|
|
||||||
|
for (var i = 0; i < targetElements.length; i++) {
|
||||||
|
if (name === "htmx:beforeRequest" && targetElements[i]) {
|
||||||
|
targetElements[i].disabled = true;
|
||||||
|
} else if (name == "htmx:afterRequest" && targetElements[i]) {
|
||||||
|
targetElements[i].disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
41
static/packages/htmx/ext/event-header.js
Normal file
41
static/packages/htmx/ext/event-header.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
(function(){
|
||||||
|
if (htmx.version && !htmx.version.startsWith("1.")) {
|
||||||
|
console.warn("WARNING: You are using an htmx 1 extension with htmx " + htmx.version +
|
||||||
|
". It is recommended that you move to the version of this extension found on https://htmx.org/extensions")
|
||||||
|
}
|
||||||
|
function stringifyEvent(event) {
|
||||||
|
var obj = {};
|
||||||
|
for (var key in event) {
|
||||||
|
obj[key] = event[key];
|
||||||
|
}
|
||||||
|
return JSON.stringify(obj, function(key, value){
|
||||||
|
if(value instanceof Node){
|
||||||
|
var nodeRep = value.tagName;
|
||||||
|
if (nodeRep) {
|
||||||
|
nodeRep = nodeRep.toLowerCase();
|
||||||
|
if(value.id){
|
||||||
|
nodeRep += "#" + value.id;
|
||||||
|
}
|
||||||
|
if(value.classList && value.classList.length){
|
||||||
|
nodeRep += "." + value.classList.toString().replace(" ", ".")
|
||||||
|
}
|
||||||
|
return nodeRep;
|
||||||
|
} else {
|
||||||
|
return "Node"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (value instanceof Window) return 'Window';
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
htmx.defineExtension('event-header', {
|
||||||
|
onEvent: function (name, evt) {
|
||||||
|
if (name === "htmx:configRequest") {
|
||||||
|
if (evt.detail.triggeringEvent) {
|
||||||
|
evt.detail.headers['Triggering-Event'] = stringifyEvent(evt.detail.triggeringEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
146
static/packages/htmx/ext/head-support.js
Normal file
146
static/packages/htmx/ext/head-support.js
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
//==========================================================
|
||||||
|
// head-support.js
|
||||||
|
//
|
||||||
|
// An extension to htmx 1.0 to add head tag merging.
|
||||||
|
//==========================================================
|
||||||
|
(function(){
|
||||||
|
|
||||||
|
if (htmx.version && !htmx.version.startsWith("1.")) {
|
||||||
|
console.warn("WARNING: You are using an htmx 1 extension with htmx " + htmx.version +
|
||||||
|
". It is recommended that you move to the version of this extension found on https://htmx.org/extensions")
|
||||||
|
}
|
||||||
|
|
||||||
|
var api = null;
|
||||||
|
|
||||||
|
function log() {
|
||||||
|
//console.log(arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeHead(newContent, defaultMergeStrategy) {
|
||||||
|
|
||||||
|
if (newContent && newContent.indexOf('<head') > -1) {
|
||||||
|
const htmlDoc = document.createElement("html");
|
||||||
|
// remove svgs to avoid conflicts
|
||||||
|
var contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, '');
|
||||||
|
// extract head tag
|
||||||
|
var headTag = contentWithSvgsRemoved.match(/(<head(\s[^>]*>|>)([\s\S]*?)<\/head>)/im);
|
||||||
|
|
||||||
|
// if the head tag exists...
|
||||||
|
if (headTag) {
|
||||||
|
|
||||||
|
var added = []
|
||||||
|
var removed = []
|
||||||
|
var preserved = []
|
||||||
|
var nodesToAppend = []
|
||||||
|
|
||||||
|
htmlDoc.innerHTML = headTag;
|
||||||
|
var newHeadTag = htmlDoc.querySelector("head");
|
||||||
|
var currentHead = document.head;
|
||||||
|
|
||||||
|
if (newHeadTag == null) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// put all new head elements into a Map, by their outerHTML
|
||||||
|
var srcToNewHeadNodes = new Map();
|
||||||
|
for (const newHeadChild of newHeadTag.children) {
|
||||||
|
srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// determine merge strategy
|
||||||
|
var mergeStrategy = api.getAttributeValue(newHeadTag, "hx-head") || defaultMergeStrategy;
|
||||||
|
|
||||||
|
// get the current head
|
||||||
|
for (const currentHeadElt of currentHead.children) {
|
||||||
|
|
||||||
|
// If the current head element is in the map
|
||||||
|
var inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML);
|
||||||
|
var isReAppended = currentHeadElt.getAttribute("hx-head") === "re-eval";
|
||||||
|
var isPreserved = api.getAttributeValue(currentHeadElt, "hx-preserve") === "true";
|
||||||
|
if (inNewContent || isPreserved) {
|
||||||
|
if (isReAppended) {
|
||||||
|
// remove the current version and let the new version replace it and re-execute
|
||||||
|
removed.push(currentHeadElt);
|
||||||
|
} else {
|
||||||
|
// this element already exists and should not be re-appended, so remove it from
|
||||||
|
// the new content map, preserving it in the DOM
|
||||||
|
srcToNewHeadNodes.delete(currentHeadElt.outerHTML);
|
||||||
|
preserved.push(currentHeadElt);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (mergeStrategy === "append") {
|
||||||
|
// we are appending and this existing element is not new content
|
||||||
|
// so if and only if it is marked for re-append do we do anything
|
||||||
|
if (isReAppended) {
|
||||||
|
removed.push(currentHeadElt);
|
||||||
|
nodesToAppend.push(currentHeadElt);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if this is a merge, we remove this content since it is not in the new head
|
||||||
|
if (api.triggerEvent(document.body, "htmx:removingHeadElement", {headElement: currentHeadElt}) !== false) {
|
||||||
|
removed.push(currentHeadElt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push the tremaining new head elements in the Map into the
|
||||||
|
// nodes to append to the head tag
|
||||||
|
nodesToAppend.push(...srcToNewHeadNodes.values());
|
||||||
|
log("to append: ", nodesToAppend);
|
||||||
|
|
||||||
|
for (const newNode of nodesToAppend) {
|
||||||
|
log("adding: ", newNode);
|
||||||
|
var newElt = document.createRange().createContextualFragment(newNode.outerHTML);
|
||||||
|
log(newElt);
|
||||||
|
if (api.triggerEvent(document.body, "htmx:addingHeadElement", {headElement: newElt}) !== false) {
|
||||||
|
currentHead.appendChild(newElt);
|
||||||
|
added.push(newElt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove all removed elements, after we have appended the new elements to avoid
|
||||||
|
// additional network requests for things like style sheets
|
||||||
|
for (const removedElement of removed) {
|
||||||
|
if (api.triggerEvent(document.body, "htmx:removingHeadElement", {headElement: removedElement}) !== false) {
|
||||||
|
currentHead.removeChild(removedElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
api.triggerEvent(document.body, "htmx:afterHeadMerge", {added: added, kept: preserved, removed: removed});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
htmx.defineExtension("head-support", {
|
||||||
|
init: function(apiRef) {
|
||||||
|
// store a reference to the internal API.
|
||||||
|
api = apiRef;
|
||||||
|
|
||||||
|
htmx.on('htmx:afterSwap', function(evt){
|
||||||
|
var serverResponse = evt.detail.xhr.response;
|
||||||
|
if (api.triggerEvent(document.body, "htmx:beforeHeadMerge", evt.detail)) {
|
||||||
|
mergeHead(serverResponse, evt.detail.boosted ? "merge" : "append");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
htmx.on('htmx:historyRestore', function(evt){
|
||||||
|
if (api.triggerEvent(document.body, "htmx:beforeHeadMerge", evt.detail)) {
|
||||||
|
if (evt.detail.cacheMiss) {
|
||||||
|
mergeHead(evt.detail.serverResponse, "merge");
|
||||||
|
} else {
|
||||||
|
mergeHead(evt.detail.item.head, "merge");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
htmx.on('htmx:historyItemCreated', function(evt){
|
||||||
|
var historyItem = evt.detail.item;
|
||||||
|
historyItem.head = document.head.outerHTML;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
})()
|
28
static/packages/htmx/ext/include-vals.js
Normal file
28
static/packages/htmx/ext/include-vals.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
(function(){
|
||||||
|
if (htmx.version && !htmx.version.startsWith("1.")) {
|
||||||
|
console.warn("WARNING: You are using an htmx 1 extension with htmx " + htmx.version +
|
||||||
|
". It is recommended that you move to the version of this extension found on https://htmx.org/extensions")
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeObjects(obj1, obj2) {
|
||||||
|
for (var key in obj2) {
|
||||||
|
if (obj2.hasOwnProperty(key)) {
|
||||||
|
obj1[key] = obj2[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj1;
|
||||||
|
}
|
||||||
|
|
||||||
|
htmx.defineExtension('include-vals', {
|
||||||
|
onEvent: function (name, evt) {
|
||||||
|
if (name === "htmx:configRequest") {
|
||||||
|
var includeValsElt = htmx.closest(evt.detail.elt, "[include-vals],[data-include-vals]");
|
||||||
|
if (includeValsElt) {
|
||||||
|
var includeVals = includeValsElt.getAttribute("include-vals") || includeValsElt.getAttribute("data-include-vals");
|
||||||
|
var valuesToInclude = eval("({" + includeVals + "})");
|
||||||
|
mergeObjects(evt.detail.parameters, valuesToInclude);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
16
static/packages/htmx/ext/json-enc.js
Normal file
16
static/packages/htmx/ext/json-enc.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
if (htmx.version && !htmx.version.startsWith("1.")) {
|
||||||
|
console.warn("WARNING: You are using an htmx 1 extension with htmx " + htmx.version +
|
||||||
|
". It is recommended that you move to the version of this extension found on https://htmx.org/extensions")
|
||||||
|
}
|
||||||
|
htmx.defineExtension('json-enc', {
|
||||||
|
onEvent: function (name, evt) {
|
||||||
|
if (name === "htmx:configRequest") {
|
||||||
|
evt.detail.headers['Content-Type'] = "application/json";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
encodeParameters : function(xhr, parameters, elt) {
|
||||||
|
xhr.overrideMimeType('text/json');
|
||||||
|
return (JSON.stringify(parameters));
|
||||||
|
}
|
||||||
|
});
|
189
static/packages/htmx/ext/loading-states.js
Normal file
189
static/packages/htmx/ext/loading-states.js
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
;(function () {
|
||||||
|
|
||||||
|
if (htmx.version && !htmx.version.startsWith("1.")) {
|
||||||
|
console.warn("WARNING: You are using an htmx 1 extension with htmx " + htmx.version +
|
||||||
|
". It is recommended that you move to the version of this extension found on https://htmx.org/extensions")
|
||||||
|
}
|
||||||
|
|
||||||
|
let loadingStatesUndoQueue = []
|
||||||
|
|
||||||
|
function loadingStateContainer(target) {
|
||||||
|
return htmx.closest(target, '[data-loading-states]') || document.body
|
||||||
|
}
|
||||||
|
|
||||||
|
function mayProcessUndoCallback(target, callback) {
|
||||||
|
if (document.body.contains(target)) {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mayProcessLoadingStateByPath(elt, requestPath) {
|
||||||
|
const pathElt = htmx.closest(elt, '[data-loading-path]')
|
||||||
|
if (!pathElt) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathElt.getAttribute('data-loading-path') === requestPath
|
||||||
|
}
|
||||||
|
|
||||||
|
function queueLoadingState(sourceElt, targetElt, doCallback, undoCallback) {
|
||||||
|
const delayElt = htmx.closest(sourceElt, '[data-loading-delay]')
|
||||||
|
if (delayElt) {
|
||||||
|
const delayInMilliseconds =
|
||||||
|
delayElt.getAttribute('data-loading-delay') || 200
|
||||||
|
const timeout = setTimeout(function () {
|
||||||
|
doCallback()
|
||||||
|
|
||||||
|
loadingStatesUndoQueue.push(function () {
|
||||||
|
mayProcessUndoCallback(targetElt, undoCallback)
|
||||||
|
})
|
||||||
|
}, delayInMilliseconds)
|
||||||
|
|
||||||
|
loadingStatesUndoQueue.push(function () {
|
||||||
|
mayProcessUndoCallback(targetElt, function () { clearTimeout(timeout) })
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
doCallback()
|
||||||
|
loadingStatesUndoQueue.push(function () {
|
||||||
|
mayProcessUndoCallback(targetElt, undoCallback)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLoadingStateElts(loadingScope, type, path) {
|
||||||
|
return Array.from(htmx.findAll(loadingScope, "[" + type + "]")).filter(
|
||||||
|
function (elt) { return mayProcessLoadingStateByPath(elt, path) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLoadingTarget(elt) {
|
||||||
|
if (elt.getAttribute('data-loading-target')) {
|
||||||
|
return Array.from(
|
||||||
|
htmx.findAll(elt.getAttribute('data-loading-target'))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return [elt]
|
||||||
|
}
|
||||||
|
|
||||||
|
htmx.defineExtension('loading-states', {
|
||||||
|
onEvent: function (name, evt) {
|
||||||
|
if (name === 'htmx:beforeRequest') {
|
||||||
|
const container = loadingStateContainer(evt.target)
|
||||||
|
|
||||||
|
const loadingStateTypes = [
|
||||||
|
'data-loading',
|
||||||
|
'data-loading-class',
|
||||||
|
'data-loading-class-remove',
|
||||||
|
'data-loading-disable',
|
||||||
|
'data-loading-aria-busy',
|
||||||
|
]
|
||||||
|
|
||||||
|
let loadingStateEltsByType = {}
|
||||||
|
|
||||||
|
loadingStateTypes.forEach(function (type) {
|
||||||
|
loadingStateEltsByType[type] = getLoadingStateElts(
|
||||||
|
container,
|
||||||
|
type,
|
||||||
|
evt.detail.pathInfo.requestPath
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
loadingStateEltsByType['data-loading'].forEach(function (sourceElt) {
|
||||||
|
getLoadingTarget(sourceElt).forEach(function (targetElt) {
|
||||||
|
queueLoadingState(
|
||||||
|
sourceElt,
|
||||||
|
targetElt,
|
||||||
|
function () {
|
||||||
|
targetElt.style.display =
|
||||||
|
sourceElt.getAttribute('data-loading') ||
|
||||||
|
'inline-block' },
|
||||||
|
function () { targetElt.style.display = 'none' }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
loadingStateEltsByType['data-loading-class'].forEach(
|
||||||
|
function (sourceElt) {
|
||||||
|
const classNames = sourceElt
|
||||||
|
.getAttribute('data-loading-class')
|
||||||
|
.split(' ')
|
||||||
|
|
||||||
|
getLoadingTarget(sourceElt).forEach(function (targetElt) {
|
||||||
|
queueLoadingState(
|
||||||
|
sourceElt,
|
||||||
|
targetElt,
|
||||||
|
function () {
|
||||||
|
classNames.forEach(function (className) {
|
||||||
|
targetElt.classList.add(className)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
classNames.forEach(function (className) {
|
||||||
|
targetElt.classList.remove(className)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
loadingStateEltsByType['data-loading-class-remove'].forEach(
|
||||||
|
function (sourceElt) {
|
||||||
|
const classNames = sourceElt
|
||||||
|
.getAttribute('data-loading-class-remove')
|
||||||
|
.split(' ')
|
||||||
|
|
||||||
|
getLoadingTarget(sourceElt).forEach(function (targetElt) {
|
||||||
|
queueLoadingState(
|
||||||
|
sourceElt,
|
||||||
|
targetElt,
|
||||||
|
function () {
|
||||||
|
classNames.forEach(function (className) {
|
||||||
|
targetElt.classList.remove(className)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
classNames.forEach(function (className) {
|
||||||
|
targetElt.classList.add(className)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
loadingStateEltsByType['data-loading-disable'].forEach(
|
||||||
|
function (sourceElt) {
|
||||||
|
getLoadingTarget(sourceElt).forEach(function (targetElt) {
|
||||||
|
queueLoadingState(
|
||||||
|
sourceElt,
|
||||||
|
targetElt,
|
||||||
|
function() { targetElt.disabled = true },
|
||||||
|
function() { targetElt.disabled = false }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
loadingStateEltsByType['data-loading-aria-busy'].forEach(
|
||||||
|
function (sourceElt) {
|
||||||
|
getLoadingTarget(sourceElt).forEach(function (targetElt) {
|
||||||
|
queueLoadingState(
|
||||||
|
sourceElt,
|
||||||
|
targetElt,
|
||||||
|
function () { targetElt.setAttribute("aria-busy", "true") },
|
||||||
|
function () { targetElt.removeAttribute("aria-busy") }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name === 'htmx:beforeOnLoad') {
|
||||||
|
while (loadingStatesUndoQueue.length > 0) {
|
||||||
|
loadingStatesUndoQueue.shift()()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})()
|
15
static/packages/htmx/ext/method-override.js
Normal file
15
static/packages/htmx/ext/method-override.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
if (htmx.version && !htmx.version.startsWith("1.")) {
|
||||||
|
console.warn("WARNING: You are using an htmx 1 extension with htmx " + htmx.version +
|
||||||
|
". It is recommended that you move to the version of this extension found on https://htmx.org/extensions")
|
||||||
|
}
|
||||||
|
htmx.defineExtension('method-override', {
|
||||||
|
onEvent: function (name, evt) {
|
||||||
|
if (name === "htmx:configRequest") {
|
||||||
|
var method = evt.detail.verb;
|
||||||
|
if (method !== "get" || method !== "post") {
|
||||||
|
evt.detail.headers['X-HTTP-Method-Override'] = method.toUpperCase();
|
||||||
|
evt.detail.verb = "post";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
21
static/packages/htmx/ext/morphdom-swap.js
Normal file
21
static/packages/htmx/ext/morphdom-swap.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
if (htmx.version && !htmx.version.startsWith("1.")) {
|
||||||
|
console.warn("WARNING: You are using an htmx 1 extension with htmx " + htmx.version +
|
||||||
|
". It is recommended that you move to the version of this extension found on https://htmx.org/extensions")
|
||||||
|
}
|
||||||
|
htmx.defineExtension('morphdom-swap', {
|
||||||
|
isInlineSwap: function(swapStyle) {
|
||||||
|
return swapStyle === 'morphdom';
|
||||||
|
},
|
||||||
|
handleSwap: function (swapStyle, target, fragment) {
|
||||||
|
if (swapStyle === 'morphdom') {
|
||||||
|
if (fragment.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
||||||
|
// IE11 doesn't support DocumentFragment.firstElementChild
|
||||||
|
morphdom(target, fragment.firstElementChild || fragment.firstChild);
|
||||||
|
return [target];
|
||||||
|
} else {
|
||||||
|
morphdom(target, fragment.outerHTML);
|
||||||
|
return [target];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
50
static/packages/htmx/ext/multi-swap.js
Normal file
50
static/packages/htmx/ext/multi-swap.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
(function () {
|
||||||
|
|
||||||
|
if (htmx.version && !htmx.version.startsWith("1.")) {
|
||||||
|
console.warn("WARNING: You are using an htmx 1 extension with htmx " + htmx.version +
|
||||||
|
". It is recommended that you move to the version of this extension found on https://htmx.org/extensions")
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {import("../htmx").HtmxInternalApi} */
|
||||||
|
var api;
|
||||||
|
|
||||||
|
htmx.defineExtension('multi-swap', {
|
||||||
|
init: function (apiRef) {
|
||||||
|
api = apiRef;
|
||||||
|
},
|
||||||
|
isInlineSwap: function (swapStyle) {
|
||||||
|
return swapStyle.indexOf('multi:') === 0;
|
||||||
|
},
|
||||||
|
handleSwap: function (swapStyle, target, fragment, settleInfo) {
|
||||||
|
if (swapStyle.indexOf('multi:') === 0) {
|
||||||
|
var selectorToSwapStyle = {};
|
||||||
|
var elements = swapStyle.replace(/^multi\s*:\s*/, '').split(/\s*,\s*/);
|
||||||
|
|
||||||
|
elements.map(function (element) {
|
||||||
|
var split = element.split(/\s*:\s*/);
|
||||||
|
var elementSelector = split[0];
|
||||||
|
var elementSwapStyle = typeof (split[1]) !== "undefined" ? split[1] : "innerHTML";
|
||||||
|
|
||||||
|
if (elementSelector.charAt(0) !== '#') {
|
||||||
|
console.error("HTMX multi-swap: unsupported selector '" + elementSelector + "'. Only ID selectors starting with '#' are supported.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectorToSwapStyle[elementSelector] = elementSwapStyle;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (var selector in selectorToSwapStyle) {
|
||||||
|
var swapStyle = selectorToSwapStyle[selector];
|
||||||
|
var elementToSwap = fragment.querySelector(selector);
|
||||||
|
if (elementToSwap) {
|
||||||
|
api.oobSwap(swapStyle, elementToSwap, settleInfo);
|
||||||
|
} else {
|
||||||
|
console.warn("HTMX multi-swap: selector '" + selector + "' not found in source content.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
63
static/packages/htmx/ext/path-deps.js
Normal file
63
static/packages/htmx/ext/path-deps.js
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
(function(undefined){
|
||||||
|
'use strict';
|
||||||
|
if (htmx.version && !htmx.version.startsWith("1.")) {
|
||||||
|
console.warn("WARNING: You are using an htmx 1 extension with htmx " + htmx.version +
|
||||||
|
". It is recommended that you move to the version of this extension found on https://htmx.org/extensions")
|
||||||
|
}
|
||||||
|
// Save a reference to the global object (window in the browser)
|
||||||
|
var _root = this;
|
||||||
|
|
||||||
|
function dependsOn(pathSpec, url) {
|
||||||
|
if (pathSpec === "ignore") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var dependencyPath = pathSpec.split("/");
|
||||||
|
var urlPath = url.split("/");
|
||||||
|
for (var i = 0; i < urlPath.length; i++) {
|
||||||
|
var dependencyElement = dependencyPath.shift();
|
||||||
|
var pathElement = urlPath[i];
|
||||||
|
if (dependencyElement !== pathElement && dependencyElement !== "*") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (dependencyPath.length === 0 || (dependencyPath.length === 1 && dependencyPath[0] === "")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshPath(path) {
|
||||||
|
var eltsWithDeps = htmx.findAll("[path-deps]");
|
||||||
|
for (var i = 0; i < eltsWithDeps.length; i++) {
|
||||||
|
var elt = eltsWithDeps[i];
|
||||||
|
if (dependsOn(elt.getAttribute('path-deps'), path)) {
|
||||||
|
htmx.trigger(elt, "path-deps");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
htmx.defineExtension('path-deps', {
|
||||||
|
onEvent: function (name, evt) {
|
||||||
|
if (name === "htmx:beforeOnLoad") {
|
||||||
|
var config = evt.detail.requestConfig;
|
||||||
|
// mutating call
|
||||||
|
if (config.verb !== "get" && evt.target.getAttribute('path-deps') !== 'ignore') {
|
||||||
|
refreshPath(config.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ********************
|
||||||
|
* Expose functionality
|
||||||
|
* ********************
|
||||||
|
*/
|
||||||
|
|
||||||
|
_root.PathDeps = {
|
||||||
|
refresh: function(path) {
|
||||||
|
refreshPath(path);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}).call(this);
|
15
static/packages/htmx/ext/path-params.js
Normal file
15
static/packages/htmx/ext/path-params.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
if (htmx.version && !htmx.version.startsWith("1.")) {
|
||||||
|
console.warn("WARNING: You are using an htmx 1 extension with htmx " + htmx.version +
|
||||||
|
". It is recommended that you move to the version of this extension found on https://htmx.org/extensions")
|
||||||
|
}
|
||||||
|
htmx.defineExtension('path-params', {
|
||||||
|
onEvent: function(name, evt) {
|
||||||
|
if (name === "htmx:configRequest") {
|
||||||
|
evt.detail.path = evt.detail.path.replace(/{([^}]+)}/g, function (_, param) {
|
||||||
|
var val = evt.detail.parameters[param];
|
||||||
|
delete evt.detail.parameters[param];
|
||||||
|
return val === undefined ? "{" + param + "}" : encodeURIComponent(val);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
151
static/packages/htmx/ext/preload.js
Normal file
151
static/packages/htmx/ext/preload.js
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
if (htmx.version && !htmx.version.startsWith("1.")) {
|
||||||
|
console.warn("WARNING: You are using an htmx 1 extension with htmx " + htmx.version +
|
||||||
|
". It is recommended that you move to the version of this extension found on https://htmx.org/extensions")
|
||||||
|
}
|
||||||
|
// This adds the "preload" extension to htmx. By default, this will
|
||||||
|
// preload the targets of any tags with `href` or `hx-get` attributes
|
||||||
|
// if they also have a `preload` attribute as well. See documentation
|
||||||
|
// for more details
|
||||||
|
htmx.defineExtension("preload", {
|
||||||
|
|
||||||
|
onEvent: function(name, event) {
|
||||||
|
|
||||||
|
// Only take actions on "htmx:afterProcessNode"
|
||||||
|
if (name !== "htmx:afterProcessNode") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SOME HELPER FUNCTIONS WE'LL NEED ALONG THE WAY
|
||||||
|
|
||||||
|
// attr gets the closest non-empty value from the attribute.
|
||||||
|
var attr = function(node, property) {
|
||||||
|
if (node == undefined) {return undefined;}
|
||||||
|
return node.getAttribute(property) || node.getAttribute("data-" + property) || attr(node.parentElement, property)
|
||||||
|
}
|
||||||
|
|
||||||
|
// load handles the actual HTTP fetch, and uses htmx.ajax in cases where we're
|
||||||
|
// preloading an htmx resource (this sends the same HTTP headers as a regular htmx request)
|
||||||
|
var load = function(node) {
|
||||||
|
|
||||||
|
// Called after a successful AJAX request, to mark the
|
||||||
|
// content as loaded (and prevent additional AJAX calls.)
|
||||||
|
var done = function(html) {
|
||||||
|
if (!node.preloadAlways) {
|
||||||
|
node.preloadState = "DONE"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr(node, "preload-images") == "true") {
|
||||||
|
document.createElement("div").innerHTML = html // create and populate a node to load linked resources, too.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return function() {
|
||||||
|
|
||||||
|
// If this value has already been loaded, then do not try again.
|
||||||
|
if (node.preloadState !== "READY") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special handling for HX-GET - use built-in htmx.ajax function
|
||||||
|
// so that headers match other htmx requests, then set
|
||||||
|
// node.preloadState = TRUE so that requests are not duplicated
|
||||||
|
// in the future
|
||||||
|
var hxGet = node.getAttribute("hx-get") || node.getAttribute("data-hx-get")
|
||||||
|
if (hxGet) {
|
||||||
|
htmx.ajax("GET", hxGet, {
|
||||||
|
source: node,
|
||||||
|
handler:function(elt, info) {
|
||||||
|
done(info.xhr.responseText);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, perform a standard xhr request, then set
|
||||||
|
// node.preloadState = TRUE so that requests are not duplicated
|
||||||
|
// in the future.
|
||||||
|
if (node.getAttribute("href")) {
|
||||||
|
var r = new XMLHttpRequest();
|
||||||
|
r.open("GET", node.getAttribute("href"));
|
||||||
|
r.onload = function() {done(r.responseText);};
|
||||||
|
r.send();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function processes a specific node and sets up event handlers.
|
||||||
|
// We'll search for nodes and use it below.
|
||||||
|
var init = function(node) {
|
||||||
|
|
||||||
|
// If this node DOES NOT include a "GET" transaction, then there's nothing to do here.
|
||||||
|
if (node.getAttribute("href") + node.getAttribute("hx-get") + node.getAttribute("data-hx-get") == "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guarantee that we only initialize each node once.
|
||||||
|
if (node.preloadState !== undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get event name from config.
|
||||||
|
var on = attr(node, "preload") || "mousedown"
|
||||||
|
const always = on.indexOf("always") !== -1
|
||||||
|
if (always) {
|
||||||
|
on = on.replace('always', '').trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FALL THROUGH to here means we need to add an EventListener
|
||||||
|
|
||||||
|
// Apply the listener to the node
|
||||||
|
node.addEventListener(on, function(evt) {
|
||||||
|
if (node.preloadState === "PAUSE") { // Only add one event listener
|
||||||
|
node.preloadState = "READY"; // Required for the `load` function to trigger
|
||||||
|
|
||||||
|
// Special handling for "mouseover" events. Wait 100ms before triggering load.
|
||||||
|
if (on === "mouseover") {
|
||||||
|
window.setTimeout(load(node), 100);
|
||||||
|
} else {
|
||||||
|
load(node)() // all other events trigger immediately.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Special handling for certain built-in event handlers
|
||||||
|
switch (on) {
|
||||||
|
|
||||||
|
case "mouseover":
|
||||||
|
// Mirror `touchstart` events (fires immediately)
|
||||||
|
node.addEventListener("touchstart", load(node));
|
||||||
|
|
||||||
|
// WHhen the mouse leaves, immediately disable the preload
|
||||||
|
node.addEventListener("mouseout", function(evt) {
|
||||||
|
if ((evt.target === node) && (node.preloadState === "READY")) {
|
||||||
|
node.preloadState = "PAUSE";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "mousedown":
|
||||||
|
// Mirror `touchstart` events (fires immediately)
|
||||||
|
node.addEventListener("touchstart", load(node));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark the node as ready to run.
|
||||||
|
node.preloadState = "PAUSE";
|
||||||
|
node.preloadAlways = always;
|
||||||
|
htmx.trigger(node, "preload:init") // This event can be used to load content immediately.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for all child nodes that have a "preload" attribute
|
||||||
|
event.target.querySelectorAll("[preload]").forEach(function(node) {
|
||||||
|
|
||||||
|
// Initialize the node with the "preload" attribute
|
||||||
|
init(node)
|
||||||
|
|
||||||
|
// Initialize all child elements that are anchors or have `hx-get` (use with care)
|
||||||
|
node.querySelectorAll("a,[hx-get],[data-hx-get]").forEach(init)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
14
static/packages/htmx/ext/rails-method.js
Normal file
14
static/packages/htmx/ext/rails-method.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
if (htmx.version && !htmx.version.startsWith("1.")) {
|
||||||
|
console.warn("WARNING: You are using an htmx 1 extension with htmx " + htmx.version +
|
||||||
|
". It is recommended that you move to the version of this extension found on https://htmx.org/extensions")
|
||||||
|
}
|
||||||
|
htmx.defineExtension('rails-method', {
|
||||||
|
onEvent: function (name, evt) {
|
||||||
|
if (name === "configRequest.htmx") {
|
||||||
|
var methodOverride = evt.detail.headers['X-HTTP-Method-Override'];
|
||||||
|
if (methodOverride) {
|
||||||
|
evt.detail.parameters['_method'] = methodOverride;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
31
static/packages/htmx/ext/remove-me.js
Normal file
31
static/packages/htmx/ext/remove-me.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
(function(){
|
||||||
|
if (htmx.version && !htmx.version.startsWith("1.")) {
|
||||||
|
console.warn("WARNING: You are using an htmx 1 extension with htmx " + htmx.version +
|
||||||
|
". It is recommended that you move to the version of this extension found on https://htmx.org/extensions")
|
||||||
|
}
|
||||||
|
function maybeRemoveMe(elt) {
|
||||||
|
var timing = elt.getAttribute("remove-me") || elt.getAttribute("data-remove-me");
|
||||||
|
if (timing) {
|
||||||
|
setTimeout(function () {
|
||||||
|
elt.parentElement.removeChild(elt);
|
||||||
|
}, htmx.parseInterval(timing));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
htmx.defineExtension('remove-me', {
|
||||||
|
onEvent: function (name, evt) {
|
||||||
|
if (name === "htmx:afterProcessNode") {
|
||||||
|
var elt = evt.detail.elt;
|
||||||
|
if (elt.getAttribute) {
|
||||||
|
maybeRemoveMe(elt);
|
||||||
|
if (elt.querySelectorAll) {
|
||||||
|
var children = elt.querySelectorAll("[remove-me], [data-remove-me]");
|
||||||
|
for (var i = 0; i < children.length; i++) {
|
||||||
|
maybeRemoveMe(children[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
135
static/packages/htmx/ext/response-targets.js
Normal file
135
static/packages/htmx/ext/response-targets.js
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
(function(){
|
||||||
|
|
||||||
|
if (htmx.version && !htmx.version.startsWith("1.")) {
|
||||||
|
console.warn("WARNING: You are using an htmx 1 extension with htmx " + htmx.version +
|
||||||
|
". It is recommended that you move to the version of this extension found on https://htmx.org/extensions")
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {import("../htmx").HtmxInternalApi} */
|
||||||
|
var api;
|
||||||
|
|
||||||
|
var attrPrefix = 'hx-target-';
|
||||||
|
|
||||||
|
// IE11 doesn't support string.startsWith
|
||||||
|
function startsWith(str, prefix) {
|
||||||
|
return str.substring(0, prefix.length) === prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {HTMLElement} elt
|
||||||
|
* @param {number} respCode
|
||||||
|
* @returns {HTMLElement | null}
|
||||||
|
*/
|
||||||
|
function getRespCodeTarget(elt, respCodeNumber) {
|
||||||
|
if (!elt || !respCodeNumber) return null;
|
||||||
|
|
||||||
|
var respCode = respCodeNumber.toString();
|
||||||
|
|
||||||
|
// '*' is the original syntax, as the obvious character for a wildcard.
|
||||||
|
// The 'x' alternative was added for maximum compatibility with HTML
|
||||||
|
// templating engines, due to ambiguity around which characters are
|
||||||
|
// supported in HTML attributes.
|
||||||
|
//
|
||||||
|
// Start with the most specific possible attribute and generalize from
|
||||||
|
// there.
|
||||||
|
var attrPossibilities = [
|
||||||
|
respCode,
|
||||||
|
|
||||||
|
respCode.substr(0, 2) + '*',
|
||||||
|
respCode.substr(0, 2) + 'x',
|
||||||
|
|
||||||
|
respCode.substr(0, 1) + '*',
|
||||||
|
respCode.substr(0, 1) + 'x',
|
||||||
|
respCode.substr(0, 1) + '**',
|
||||||
|
respCode.substr(0, 1) + 'xx',
|
||||||
|
|
||||||
|
'*',
|
||||||
|
'x',
|
||||||
|
'***',
|
||||||
|
'xxx',
|
||||||
|
];
|
||||||
|
if (startsWith(respCode, '4') || startsWith(respCode, '5')) {
|
||||||
|
attrPossibilities.push('error');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < attrPossibilities.length; i++) {
|
||||||
|
var attr = attrPrefix + attrPossibilities[i];
|
||||||
|
var attrValue = api.getClosestAttributeValue(elt, attr);
|
||||||
|
if (attrValue) {
|
||||||
|
if (attrValue === "this") {
|
||||||
|
return api.findThisElement(elt, attr);
|
||||||
|
} else {
|
||||||
|
return api.querySelectorExt(elt, attrValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {Event} evt */
|
||||||
|
function handleErrorFlag(evt) {
|
||||||
|
if (evt.detail.isError) {
|
||||||
|
if (htmx.config.responseTargetUnsetsError) {
|
||||||
|
evt.detail.isError = false;
|
||||||
|
}
|
||||||
|
} else if (htmx.config.responseTargetSetsError) {
|
||||||
|
evt.detail.isError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
htmx.defineExtension('response-targets', {
|
||||||
|
|
||||||
|
/** @param {import("../htmx").HtmxInternalApi} apiRef */
|
||||||
|
init: function (apiRef) {
|
||||||
|
api = apiRef;
|
||||||
|
|
||||||
|
if (htmx.config.responseTargetUnsetsError === undefined) {
|
||||||
|
htmx.config.responseTargetUnsetsError = true;
|
||||||
|
}
|
||||||
|
if (htmx.config.responseTargetSetsError === undefined) {
|
||||||
|
htmx.config.responseTargetSetsError = false;
|
||||||
|
}
|
||||||
|
if (htmx.config.responseTargetPrefersExisting === undefined) {
|
||||||
|
htmx.config.responseTargetPrefersExisting = false;
|
||||||
|
}
|
||||||
|
if (htmx.config.responseTargetPrefersRetargetHeader === undefined) {
|
||||||
|
htmx.config.responseTargetPrefersRetargetHeader = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
* @param {Event} evt
|
||||||
|
*/
|
||||||
|
onEvent: function (name, evt) {
|
||||||
|
if (name === "htmx:beforeSwap" &&
|
||||||
|
evt.detail.xhr &&
|
||||||
|
evt.detail.xhr.status !== 200) {
|
||||||
|
if (evt.detail.target) {
|
||||||
|
if (htmx.config.responseTargetPrefersExisting) {
|
||||||
|
evt.detail.shouldSwap = true;
|
||||||
|
handleErrorFlag(evt);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (htmx.config.responseTargetPrefersRetargetHeader &&
|
||||||
|
evt.detail.xhr.getAllResponseHeaders().match(/HX-Retarget:/i)) {
|
||||||
|
evt.detail.shouldSwap = true;
|
||||||
|
handleErrorFlag(evt);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!evt.detail.requestConfig) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
var target = getRespCodeTarget(evt.detail.requestConfig.elt, evt.detail.xhr.status);
|
||||||
|
if (target) {
|
||||||
|
handleErrorFlag(evt);
|
||||||
|
evt.detail.shouldSwap = true;
|
||||||
|
evt.detail.target = target;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
19
static/packages/htmx/ext/restored.js
Normal file
19
static/packages/htmx/ext/restored.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
if (htmx.version && !htmx.version.startsWith("1.")) {
|
||||||
|
console.warn("WARNING: You are using an htmx 1 extension with htmx " + htmx.version +
|
||||||
|
". It is recommended that you move to the version of this extension found on https://htmx.org/extensions")
|
||||||
|
}
|
||||||
|
htmx.defineExtension('restored', {
|
||||||
|
onEvent : function(name, evt) {
|
||||||
|
if (name === 'htmx:restored'){
|
||||||
|
var restoredElts = evt.detail.document.querySelectorAll(
|
||||||
|
"[hx-trigger='restored'],[data-hx-trigger='restored']"
|
||||||
|
);
|
||||||
|
// need a better way to do this, would prefer to just trigger from evt.detail.elt
|
||||||
|
var foundElt = Array.from(restoredElts).find(
|
||||||
|
(x) => (x.outerHTML === evt.detail.elt.outerHTML)
|
||||||
|
);
|
||||||
|
var restoredEvent = evt.detail.triggerEvent(foundElt, 'restored');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
})
|
374
static/packages/htmx/ext/sse.js
Normal file
374
static/packages/htmx/ext/sse.js
Normal file
@@ -0,0 +1,374 @@
|
|||||||
|
/*
|
||||||
|
Server Sent Events Extension
|
||||||
|
============================
|
||||||
|
This extension adds support for Server Sent Events to htmx. See /www/extensions/sse.md for usage instructions.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
if (htmx.version && !htmx.version.startsWith("1.")) {
|
||||||
|
console.warn("WARNING: You are using an htmx 1 extension with htmx " + htmx.version +
|
||||||
|
". It is recommended that you move to the version of this extension found on https://htmx.org/extensions")
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {import("../htmx").HtmxInternalApi} */
|
||||||
|
var api;
|
||||||
|
|
||||||
|
htmx.defineExtension("sse", {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init saves the provided reference to the internal HTMX API.
|
||||||
|
*
|
||||||
|
* @param {import("../htmx").HtmxInternalApi} api
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
init: function(apiRef) {
|
||||||
|
// store a reference to the internal API.
|
||||||
|
api = apiRef;
|
||||||
|
|
||||||
|
// set a function in the public API for creating new EventSource objects
|
||||||
|
if (htmx.createEventSource == undefined) {
|
||||||
|
htmx.createEventSource = createEventSource;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* onEvent handles all events passed to this extension.
|
||||||
|
*
|
||||||
|
* @param {string} name
|
||||||
|
* @param {Event} evt
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
onEvent: function(name, evt) {
|
||||||
|
|
||||||
|
var parent = evt.target || evt.detail.elt;
|
||||||
|
switch (name) {
|
||||||
|
|
||||||
|
case "htmx:beforeCleanupElement":
|
||||||
|
var internalData = api.getInternalData(parent)
|
||||||
|
// Try to remove remove an EventSource when elements are removed
|
||||||
|
if (internalData.sseEventSource) {
|
||||||
|
internalData.sseEventSource.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Try to create EventSources when elements are processed
|
||||||
|
case "htmx:afterProcessNode":
|
||||||
|
ensureEventSourceOnElement(parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
// HELPER FUNCTIONS
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* createEventSource is the default method for creating new EventSource objects.
|
||||||
|
* it is hoisted into htmx.config.createEventSource to be overridden by the user, if needed.
|
||||||
|
*
|
||||||
|
* @param {string} url
|
||||||
|
* @returns EventSource
|
||||||
|
*/
|
||||||
|
function createEventSource(url) {
|
||||||
|
return new EventSource(url, { withCredentials: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitOnWhitespace(trigger) {
|
||||||
|
return trigger.trim().split(/\s+/);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLegacySSEURL(elt) {
|
||||||
|
var legacySSEValue = api.getAttributeValue(elt, "hx-sse");
|
||||||
|
if (legacySSEValue) {
|
||||||
|
var values = splitOnWhitespace(legacySSEValue);
|
||||||
|
for (var i = 0; i < values.length; i++) {
|
||||||
|
var value = values[i].split(/:(.+)/);
|
||||||
|
if (value[0] === "connect") {
|
||||||
|
return value[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLegacySSESwaps(elt) {
|
||||||
|
var legacySSEValue = api.getAttributeValue(elt, "hx-sse");
|
||||||
|
var returnArr = [];
|
||||||
|
if (legacySSEValue != null) {
|
||||||
|
var values = splitOnWhitespace(legacySSEValue);
|
||||||
|
for (var i = 0; i < values.length; i++) {
|
||||||
|
var value = values[i].split(/:(.+)/);
|
||||||
|
if (value[0] === "swap") {
|
||||||
|
returnArr.push(value[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return returnArr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* registerSSE looks for attributes that can contain sse events, right
|
||||||
|
* now hx-trigger and sse-swap and adds listeners based on these attributes too
|
||||||
|
* the closest event source
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} elt
|
||||||
|
*/
|
||||||
|
function registerSSE(elt) {
|
||||||
|
// Add message handlers for every `sse-swap` attribute
|
||||||
|
queryAttributeOnThisOrChildren(elt, "sse-swap").forEach(function (child) {
|
||||||
|
// Find closest existing event source
|
||||||
|
var sourceElement = api.getClosestMatch(child, hasEventSource);
|
||||||
|
if (sourceElement == null) {
|
||||||
|
// api.triggerErrorEvent(elt, "htmx:noSSESourceError")
|
||||||
|
return null; // no eventsource in parentage, orphaned element
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set internalData and source
|
||||||
|
var internalData = api.getInternalData(sourceElement);
|
||||||
|
var source = internalData.sseEventSource;
|
||||||
|
|
||||||
|
var sseSwapAttr = api.getAttributeValue(child, "sse-swap");
|
||||||
|
if (sseSwapAttr) {
|
||||||
|
var sseEventNames = sseSwapAttr.split(",");
|
||||||
|
} else {
|
||||||
|
var sseEventNames = getLegacySSESwaps(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < sseEventNames.length; i++) {
|
||||||
|
var sseEventName = sseEventNames[i].trim();
|
||||||
|
var listener = function(event) {
|
||||||
|
|
||||||
|
// If the source is missing then close SSE
|
||||||
|
if (maybeCloseSSESource(sourceElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the body no longer contains the element, remove the listener
|
||||||
|
if (!api.bodyContains(child)) {
|
||||||
|
source.removeEventListener(sseEventName, listener);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// swap the response into the DOM and trigger a notification
|
||||||
|
if(!api.triggerEvent(elt, "htmx:sseBeforeMessage", event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
swap(child, event.data);
|
||||||
|
api.triggerEvent(elt, "htmx:sseMessage", event);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register the new listener
|
||||||
|
api.getInternalData(child).sseEventListener = listener;
|
||||||
|
source.addEventListener(sseEventName, listener);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add message handlers for every `hx-trigger="sse:*"` attribute
|
||||||
|
queryAttributeOnThisOrChildren(elt, "hx-trigger").forEach(function(child) {
|
||||||
|
// Find closest existing event source
|
||||||
|
var sourceElement = api.getClosestMatch(child, hasEventSource);
|
||||||
|
if (sourceElement == null) {
|
||||||
|
// api.triggerErrorEvent(elt, "htmx:noSSESourceError")
|
||||||
|
return null; // no eventsource in parentage, orphaned element
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set internalData and source
|
||||||
|
var internalData = api.getInternalData(sourceElement);
|
||||||
|
var source = internalData.sseEventSource;
|
||||||
|
|
||||||
|
var sseEventName = api.getAttributeValue(child, "hx-trigger");
|
||||||
|
if (sseEventName == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only process hx-triggers for events with the "sse:" prefix
|
||||||
|
if (sseEventName.slice(0, 4) != "sse:") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove the sse: prefix from here on out
|
||||||
|
sseEventName = sseEventName.substr(4);
|
||||||
|
|
||||||
|
var listener = function() {
|
||||||
|
if (maybeCloseSSESource(sourceElement)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!api.bodyContains(child)) {
|
||||||
|
source.removeEventListener(sseEventName, listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ensureEventSourceOnElement creates a new EventSource connection on the provided element.
|
||||||
|
* If a usable EventSource already exists, then it is returned. If not, then a new EventSource
|
||||||
|
* is created and stored in the element's internalData.
|
||||||
|
* @param {HTMLElement} elt
|
||||||
|
* @param {number} retryCount
|
||||||
|
* @returns {EventSource | null}
|
||||||
|
*/
|
||||||
|
function ensureEventSourceOnElement(elt, retryCount) {
|
||||||
|
|
||||||
|
if (elt == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle extension source creation attribute
|
||||||
|
queryAttributeOnThisOrChildren(elt, "sse-connect").forEach(function(child) {
|
||||||
|
var sseURL = api.getAttributeValue(child, "sse-connect");
|
||||||
|
if (sseURL == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureEventSource(child, sseURL, retryCount);
|
||||||
|
});
|
||||||
|
|
||||||
|
// handle legacy sse, remove for HTMX2
|
||||||
|
queryAttributeOnThisOrChildren(elt, "hx-sse").forEach(function(child) {
|
||||||
|
var sseURL = getLegacySSEURL(child);
|
||||||
|
if (sseURL == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureEventSource(child, sseURL, retryCount);
|
||||||
|
});
|
||||||
|
|
||||||
|
registerSSE(elt);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureEventSource(elt, url, retryCount) {
|
||||||
|
var source = htmx.createEventSource(url);
|
||||||
|
|
||||||
|
source.onerror = function(err) {
|
||||||
|
|
||||||
|
// Log an error event
|
||||||
|
api.triggerErrorEvent(elt, "htmx:sseError", { error: err, source: source });
|
||||||
|
|
||||||
|
// If parent no longer exists in the document, then clean up this EventSource
|
||||||
|
if (maybeCloseSSESource(elt)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, try to reconnect the EventSource
|
||||||
|
if (source.readyState === EventSource.CLOSED) {
|
||||||
|
retryCount = retryCount || 0;
|
||||||
|
var timeout = Math.random() * (2 ^ retryCount) * 500;
|
||||||
|
window.setTimeout(function() {
|
||||||
|
ensureEventSourceOnElement(elt, Math.min(7, retryCount + 1));
|
||||||
|
}, timeout);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
source.onopen = function(evt) {
|
||||||
|
api.triggerEvent(elt, "htmx:sseOpen", { source: source });
|
||||||
|
}
|
||||||
|
|
||||||
|
api.getInternalData(elt).sseEventSource = source;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* maybeCloseSSESource confirms that the parent element still exists.
|
||||||
|
* If not, then any associated SSE source is closed and the function returns true.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} elt
|
||||||
|
* @returns boolean
|
||||||
|
*/
|
||||||
|
function maybeCloseSSESource(elt) {
|
||||||
|
if (!api.bodyContains(elt)) {
|
||||||
|
var source = api.getInternalData(elt).sseEventSource;
|
||||||
|
if (source != undefined) {
|
||||||
|
source.close();
|
||||||
|
// source = null
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* queryAttributeOnThisOrChildren returns all nodes that contain the requested attributeName, INCLUDING THE PROVIDED ROOT ELEMENT.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} elt
|
||||||
|
* @param {string} attributeName
|
||||||
|
*/
|
||||||
|
function queryAttributeOnThisOrChildren(elt, attributeName) {
|
||||||
|
|
||||||
|
var result = [];
|
||||||
|
|
||||||
|
// If the parent element also contains the requested attribute, then add it to the results too.
|
||||||
|
if (api.hasAttribute(elt, attributeName)) {
|
||||||
|
result.push(elt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search all child nodes that match the requested attribute
|
||||||
|
elt.querySelectorAll("[" + attributeName + "], [data-" + attributeName + "]").forEach(function(node) {
|
||||||
|
result.push(node);
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {HTMLElement} elt
|
||||||
|
* @param {string} content
|
||||||
|
*/
|
||||||
|
function swap(elt, content) {
|
||||||
|
|
||||||
|
api.withExtensions(elt, function(extension) {
|
||||||
|
content = extension.transformResponse(content, null, elt);
|
||||||
|
});
|
||||||
|
|
||||||
|
var swapSpec = api.getSwapSpecification(elt);
|
||||||
|
var target = api.getTarget(elt);
|
||||||
|
var settleInfo = api.makeSettleInfo(elt);
|
||||||
|
|
||||||
|
api.selectAndSwap(swapSpec.swapStyle, target, elt, content, settleInfo);
|
||||||
|
|
||||||
|
settleInfo.elts.forEach(function(elt) {
|
||||||
|
if (elt.classList) {
|
||||||
|
elt.classList.add(htmx.config.settlingClass);
|
||||||
|
}
|
||||||
|
api.triggerEvent(elt, 'htmx:beforeSettle');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle settle tasks (with delay if requested)
|
||||||
|
if (swapSpec.settleDelay > 0) {
|
||||||
|
setTimeout(doSettle(settleInfo), swapSpec.settleDelay);
|
||||||
|
} else {
|
||||||
|
doSettle(settleInfo)();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* doSettle mirrors much of the functionality in htmx that
|
||||||
|
* settles elements after their content has been swapped.
|
||||||
|
* TODO: this should be published by htmx, and not duplicated here
|
||||||
|
* @param {import("../htmx").HtmxSettleInfo} settleInfo
|
||||||
|
* @returns () => void
|
||||||
|
*/
|
||||||
|
function doSettle(settleInfo) {
|
||||||
|
|
||||||
|
return function() {
|
||||||
|
settleInfo.tasks.forEach(function(task) {
|
||||||
|
task.call();
|
||||||
|
});
|
||||||
|
|
||||||
|
settleInfo.elts.forEach(function(elt) {
|
||||||
|
if (elt.classList) {
|
||||||
|
elt.classList.remove(htmx.config.settlingClass);
|
||||||
|
}
|
||||||
|
api.triggerEvent(elt, 'htmx:afterSettle');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasEventSource(node) {
|
||||||
|
return api.getInternalData(node).sseEventSource != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
481
static/packages/htmx/ext/ws.js
Normal file
481
static/packages/htmx/ext/ws.js
Normal file
@@ -0,0 +1,481 @@
|
|||||||
|
/*
|
||||||
|
WebSockets Extension
|
||||||
|
============================
|
||||||
|
This extension adds support for WebSockets to htmx. See /www/extensions/ws.md for usage instructions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
|
||||||
|
if (htmx.version && !htmx.version.startsWith("1.")) {
|
||||||
|
console.warn("WARNING: You are using an htmx 1 extension with htmx " + htmx.version +
|
||||||
|
". It is recommended that you move to the version of this extension found on https://htmx.org/extensions")
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {import("../htmx").HtmxInternalApi} */
|
||||||
|
var api;
|
||||||
|
|
||||||
|
htmx.defineExtension("ws", {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* init is called once, when this extension is first registered.
|
||||||
|
* @param {import("../htmx").HtmxInternalApi} apiRef
|
||||||
|
*/
|
||||||
|
init: function (apiRef) {
|
||||||
|
|
||||||
|
// Store reference to internal API
|
||||||
|
api = apiRef;
|
||||||
|
|
||||||
|
// Default function for creating new EventSource objects
|
||||||
|
if (!htmx.createWebSocket) {
|
||||||
|
htmx.createWebSocket = createWebSocket;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default setting for reconnect delay
|
||||||
|
if (!htmx.config.wsReconnectDelay) {
|
||||||
|
htmx.config.wsReconnectDelay = "full-jitter";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* onEvent handles all events passed to this extension.
|
||||||
|
*
|
||||||
|
* @param {string} name
|
||||||
|
* @param {Event} evt
|
||||||
|
*/
|
||||||
|
onEvent: function (name, evt) {
|
||||||
|
var parent = evt.target || evt.detail.elt;
|
||||||
|
|
||||||
|
switch (name) {
|
||||||
|
|
||||||
|
// Try to close the socket when elements are removed
|
||||||
|
case "htmx:beforeCleanupElement":
|
||||||
|
|
||||||
|
var internalData = api.getInternalData(parent)
|
||||||
|
|
||||||
|
if (internalData.webSocket) {
|
||||||
|
internalData.webSocket.close();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Try to create websockets when elements are processed
|
||||||
|
case "htmx:beforeProcessNode":
|
||||||
|
forEach(queryAttributeOnThisOrChildren(parent, "ws-connect"), function (child) {
|
||||||
|
ensureWebSocket(child)
|
||||||
|
});
|
||||||
|
forEach(queryAttributeOnThisOrChildren(parent, "ws-send"), function (child) {
|
||||||
|
ensureWebSocketSend(child)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function splitOnWhitespace(trigger) {
|
||||||
|
return trigger.trim().split(/\s+/);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLegacyWebsocketURL(elt) {
|
||||||
|
var legacySSEValue = api.getAttributeValue(elt, "hx-ws");
|
||||||
|
if (legacySSEValue) {
|
||||||
|
var values = splitOnWhitespace(legacySSEValue);
|
||||||
|
for (var i = 0; i < values.length; i++) {
|
||||||
|
var value = values[i].split(/:(.+)/);
|
||||||
|
if (value[0] === "connect") {
|
||||||
|
return value[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ensureWebSocket creates a new WebSocket on the designated element, using
|
||||||
|
* the element's "ws-connect" attribute.
|
||||||
|
* @param {HTMLElement} socketElt
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function ensureWebSocket(socketElt) {
|
||||||
|
|
||||||
|
// If the element containing the WebSocket connection no longer exists, then
|
||||||
|
// do not connect/reconnect the WebSocket.
|
||||||
|
if (!api.bodyContains(socketElt)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the source straight from the element's value
|
||||||
|
var wssSource = api.getAttributeValue(socketElt, "ws-connect")
|
||||||
|
|
||||||
|
if (wssSource == null || wssSource === "") {
|
||||||
|
var legacySource = getLegacyWebsocketURL(socketElt);
|
||||||
|
if (legacySource == null) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
wssSource = legacySource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guarantee that the wssSource value is a fully qualified URL
|
||||||
|
if (wssSource.indexOf("/") === 0) {
|
||||||
|
var base_part = location.hostname + (location.port ? ':' + location.port : '');
|
||||||
|
if (location.protocol === 'https:') {
|
||||||
|
wssSource = "wss://" + base_part + wssSource;
|
||||||
|
} else if (location.protocol === 'http:') {
|
||||||
|
wssSource = "ws://" + base_part + wssSource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var socketWrapper = createWebsocketWrapper(socketElt, function () {
|
||||||
|
return htmx.createWebSocket(wssSource)
|
||||||
|
});
|
||||||
|
|
||||||
|
socketWrapper.addEventListener('message', function (event) {
|
||||||
|
if (maybeCloseWebSocketSource(socketElt)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = event.data;
|
||||||
|
if (!api.triggerEvent(socketElt, "htmx:wsBeforeMessage", {
|
||||||
|
message: response,
|
||||||
|
socketWrapper: socketWrapper.publicInterface
|
||||||
|
})) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
api.withExtensions(socketElt, function (extension) {
|
||||||
|
response = extension.transformResponse(response, null, socketElt);
|
||||||
|
});
|
||||||
|
|
||||||
|
var settleInfo = api.makeSettleInfo(socketElt);
|
||||||
|
var fragment = api.makeFragment(response);
|
||||||
|
|
||||||
|
if (fragment.children.length) {
|
||||||
|
var children = Array.from(fragment.children);
|
||||||
|
for (var i = 0; i < children.length; i++) {
|
||||||
|
api.oobSwap(api.getAttributeValue(children[i], "hx-swap-oob") || "true", children[i], settleInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
api.settleImmediately(settleInfo.tasks);
|
||||||
|
api.triggerEvent(socketElt, "htmx:wsAfterMessage", { message: response, socketWrapper: socketWrapper.publicInterface })
|
||||||
|
});
|
||||||
|
|
||||||
|
// Put the WebSocket into the HTML Element's custom data.
|
||||||
|
api.getInternalData(socketElt).webSocket = socketWrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} WebSocketWrapper
|
||||||
|
* @property {WebSocket} socket
|
||||||
|
* @property {Array<{message: string, sendElt: Element}>} messageQueue
|
||||||
|
* @property {number} retryCount
|
||||||
|
* @property {(message: string, sendElt: Element) => void} sendImmediately sendImmediately sends message regardless of websocket connection state
|
||||||
|
* @property {(message: string, sendElt: Element) => void} send
|
||||||
|
* @property {(event: string, handler: Function) => void} addEventListener
|
||||||
|
* @property {() => void} handleQueuedMessages
|
||||||
|
* @property {() => void} init
|
||||||
|
* @property {() => void} close
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param socketElt
|
||||||
|
* @param socketFunc
|
||||||
|
* @returns {WebSocketWrapper}
|
||||||
|
*/
|
||||||
|
function createWebsocketWrapper(socketElt, socketFunc) {
|
||||||
|
var wrapper = {
|
||||||
|
socket: null,
|
||||||
|
messageQueue: [],
|
||||||
|
retryCount: 0,
|
||||||
|
|
||||||
|
/** @type {Object<string, Function[]>} */
|
||||||
|
events: {},
|
||||||
|
|
||||||
|
addEventListener: function (event, handler) {
|
||||||
|
if (this.socket) {
|
||||||
|
this.socket.addEventListener(event, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.events[event]) {
|
||||||
|
this.events[event] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.events[event].push(handler);
|
||||||
|
},
|
||||||
|
|
||||||
|
sendImmediately: function (message, sendElt) {
|
||||||
|
if (!this.socket) {
|
||||||
|
api.triggerErrorEvent()
|
||||||
|
}
|
||||||
|
if (!sendElt || api.triggerEvent(sendElt, 'htmx:wsBeforeSend', {
|
||||||
|
message: message,
|
||||||
|
socketWrapper: this.publicInterface
|
||||||
|
})) {
|
||||||
|
this.socket.send(message);
|
||||||
|
sendElt && api.triggerEvent(sendElt, 'htmx:wsAfterSend', {
|
||||||
|
message: message,
|
||||||
|
socketWrapper: this.publicInterface
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
send: function (message, sendElt) {
|
||||||
|
if (this.socket.readyState !== this.socket.OPEN) {
|
||||||
|
this.messageQueue.push({ message: message, sendElt: sendElt });
|
||||||
|
} else {
|
||||||
|
this.sendImmediately(message, sendElt);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleQueuedMessages: function () {
|
||||||
|
while (this.messageQueue.length > 0) {
|
||||||
|
var queuedItem = this.messageQueue[0]
|
||||||
|
if (this.socket.readyState === this.socket.OPEN) {
|
||||||
|
this.sendImmediately(queuedItem.message, queuedItem.sendElt);
|
||||||
|
this.messageQueue.shift();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
init: function () {
|
||||||
|
if (this.socket && this.socket.readyState === this.socket.OPEN) {
|
||||||
|
// Close discarded socket
|
||||||
|
this.socket.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new WebSocket and event handlers
|
||||||
|
/** @type {WebSocket} */
|
||||||
|
var socket = socketFunc();
|
||||||
|
|
||||||
|
// The event.type detail is added for interface conformance with the
|
||||||
|
// other two lifecycle events (open and close) so a single handler method
|
||||||
|
// can handle them polymorphically, if required.
|
||||||
|
api.triggerEvent(socketElt, "htmx:wsConnecting", { event: { type: 'connecting' } });
|
||||||
|
|
||||||
|
this.socket = socket;
|
||||||
|
|
||||||
|
socket.onopen = function (e) {
|
||||||
|
wrapper.retryCount = 0;
|
||||||
|
api.triggerEvent(socketElt, "htmx:wsOpen", { event: e, socketWrapper: wrapper.publicInterface });
|
||||||
|
wrapper.handleQueuedMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.onclose = function (e) {
|
||||||
|
// If socket should not be connected, stop further attempts to establish connection
|
||||||
|
// If Abnormal Closure/Service Restart/Try Again Later, then set a timer to reconnect after a pause.
|
||||||
|
if (!maybeCloseWebSocketSource(socketElt) && [1006, 1012, 1013].indexOf(e.code) >= 0) {
|
||||||
|
var delay = getWebSocketReconnectDelay(wrapper.retryCount);
|
||||||
|
setTimeout(function () {
|
||||||
|
wrapper.retryCount += 1;
|
||||||
|
wrapper.init();
|
||||||
|
}, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify client code that connection has been closed. Client code can inspect `event` field
|
||||||
|
// to determine whether closure has been valid or abnormal
|
||||||
|
api.triggerEvent(socketElt, "htmx:wsClose", { event: e, socketWrapper: wrapper.publicInterface })
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onerror = function (e) {
|
||||||
|
api.triggerErrorEvent(socketElt, "htmx:wsError", { error: e, socketWrapper: wrapper });
|
||||||
|
maybeCloseWebSocketSource(socketElt);
|
||||||
|
};
|
||||||
|
|
||||||
|
var events = this.events;
|
||||||
|
Object.keys(events).forEach(function (k) {
|
||||||
|
events[k].forEach(function (e) {
|
||||||
|
socket.addEventListener(k, e);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
close: function () {
|
||||||
|
this.socket.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper.init();
|
||||||
|
|
||||||
|
wrapper.publicInterface = {
|
||||||
|
send: wrapper.send.bind(wrapper),
|
||||||
|
sendImmediately: wrapper.sendImmediately.bind(wrapper),
|
||||||
|
queue: wrapper.messageQueue
|
||||||
|
};
|
||||||
|
|
||||||
|
return wrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ensureWebSocketSend attaches trigger handles to elements with
|
||||||
|
* "ws-send" attribute
|
||||||
|
* @param {HTMLElement} elt
|
||||||
|
*/
|
||||||
|
function ensureWebSocketSend(elt) {
|
||||||
|
var legacyAttribute = api.getAttributeValue(elt, "hx-ws");
|
||||||
|
if (legacyAttribute && legacyAttribute !== 'send') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var webSocketParent = api.getClosestMatch(elt, hasWebSocket)
|
||||||
|
processWebSocketSend(webSocketParent, elt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hasWebSocket function checks if a node has webSocket instance attached
|
||||||
|
* @param {HTMLElement} node
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function hasWebSocket(node) {
|
||||||
|
return api.getInternalData(node).webSocket != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* processWebSocketSend adds event listeners to the <form> element so that
|
||||||
|
* messages can be sent to the WebSocket server when the form is submitted.
|
||||||
|
* @param {HTMLElement} socketElt
|
||||||
|
* @param {HTMLElement} sendElt
|
||||||
|
*/
|
||||||
|
function processWebSocketSend(socketElt, sendElt) {
|
||||||
|
var nodeData = api.getInternalData(sendElt);
|
||||||
|
var triggerSpecs = api.getTriggerSpecs(sendElt);
|
||||||
|
triggerSpecs.forEach(function (ts) {
|
||||||
|
api.addTriggerHandler(sendElt, ts, nodeData, function (elt, evt) {
|
||||||
|
if (maybeCloseWebSocketSource(socketElt)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {WebSocketWrapper} */
|
||||||
|
var socketWrapper = api.getInternalData(socketElt).webSocket;
|
||||||
|
var headers = api.getHeaders(sendElt, api.getTarget(sendElt));
|
||||||
|
var results = api.getInputValues(sendElt, 'post');
|
||||||
|
var errors = results.errors;
|
||||||
|
var rawParameters = results.values;
|
||||||
|
var expressionVars = api.getExpressionVars(sendElt);
|
||||||
|
var allParameters = api.mergeObjects(rawParameters, expressionVars);
|
||||||
|
var filteredParameters = api.filterValues(allParameters, sendElt);
|
||||||
|
|
||||||
|
var sendConfig = {
|
||||||
|
parameters: filteredParameters,
|
||||||
|
unfilteredParameters: allParameters,
|
||||||
|
headers: headers,
|
||||||
|
errors: errors,
|
||||||
|
|
||||||
|
triggeringEvent: evt,
|
||||||
|
messageBody: undefined,
|
||||||
|
socketWrapper: socketWrapper.publicInterface
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!api.triggerEvent(elt, 'htmx:wsConfigSend', sendConfig)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors && errors.length > 0) {
|
||||||
|
api.triggerEvent(elt, 'htmx:validation:halted', errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var body = sendConfig.messageBody;
|
||||||
|
if (body === undefined) {
|
||||||
|
var toSend = Object.assign({}, sendConfig.parameters);
|
||||||
|
if (sendConfig.headers)
|
||||||
|
toSend['HEADERS'] = headers;
|
||||||
|
body = JSON.stringify(toSend);
|
||||||
|
}
|
||||||
|
|
||||||
|
socketWrapper.send(body, elt);
|
||||||
|
|
||||||
|
if (evt && api.shouldCancel(evt, elt)) {
|
||||||
|
evt.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getWebSocketReconnectDelay is the default easing function for WebSocket reconnects.
|
||||||
|
* @param {number} retryCount // The number of retries that have already taken place
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
function getWebSocketReconnectDelay(retryCount) {
|
||||||
|
|
||||||
|
/** @type {"full-jitter" | ((retryCount:number) => number)} */
|
||||||
|
var delay = htmx.config.wsReconnectDelay;
|
||||||
|
if (typeof delay === 'function') {
|
||||||
|
return delay(retryCount);
|
||||||
|
}
|
||||||
|
if (delay === 'full-jitter') {
|
||||||
|
var exp = Math.min(retryCount, 6);
|
||||||
|
var maxDelay = 1000 * Math.pow(2, exp);
|
||||||
|
return maxDelay * Math.random();
|
||||||
|
}
|
||||||
|
|
||||||
|
logError('htmx.config.wsReconnectDelay must either be a function or the string "full-jitter"');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* maybeCloseWebSocketSource checks to the if the element that created the WebSocket
|
||||||
|
* still exists in the DOM. If NOT, then the WebSocket is closed and this function
|
||||||
|
* returns TRUE. If the element DOES EXIST, then no action is taken, and this function
|
||||||
|
* returns FALSE.
|
||||||
|
*
|
||||||
|
* @param {*} elt
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function maybeCloseWebSocketSource(elt) {
|
||||||
|
if (!api.bodyContains(elt)) {
|
||||||
|
api.getInternalData(elt).webSocket.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* createWebSocket is the default method for creating new WebSocket objects.
|
||||||
|
* it is hoisted into htmx.createWebSocket to be overridden by the user, if needed.
|
||||||
|
*
|
||||||
|
* @param {string} url
|
||||||
|
* @returns WebSocket
|
||||||
|
*/
|
||||||
|
function createWebSocket(url) {
|
||||||
|
var sock = new WebSocket(url, []);
|
||||||
|
sock.binaryType = htmx.config.wsBinaryType;
|
||||||
|
return sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* queryAttributeOnThisOrChildren returns all nodes that contain the requested attributeName, INCLUDING THE PROVIDED ROOT ELEMENT.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} elt
|
||||||
|
* @param {string} attributeName
|
||||||
|
*/
|
||||||
|
function queryAttributeOnThisOrChildren(elt, attributeName) {
|
||||||
|
|
||||||
|
var result = []
|
||||||
|
|
||||||
|
// If the parent element also contains the requested attribute, then add it to the results too.
|
||||||
|
if (api.hasAttribute(elt, attributeName) || api.hasAttribute(elt, "hx-ws")) {
|
||||||
|
result.push(elt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search all child nodes that match the requested attribute
|
||||||
|
elt.querySelectorAll("[" + attributeName + "], [data-" + attributeName + "], [data-hx-ws], [hx-ws]").forEach(function (node) {
|
||||||
|
result.push(node)
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @param {T[]} arr
|
||||||
|
* @param {(T) => void} func
|
||||||
|
*/
|
||||||
|
function forEach(arr, func) {
|
||||||
|
if (arr) {
|
||||||
|
for (var i = 0; i < arr.length; i++) {
|
||||||
|
func(arr[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
5261
static/packages/htmx/htmx.js
Normal file
5261
static/packages/htmx/htmx.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -8,15 +8,16 @@
|
|||||||
<title>HiKoS</title>
|
<title>HiKoS</title>
|
||||||
<link rel="icon" type="image/vnd.icon" href="/static/img/favicon.ico">
|
<link rel="icon" type="image/vnd.icon" href="/static/img/favicon.ico">
|
||||||
<link rel="stylesheet" href="/static/css/main.css">
|
<link rel="stylesheet" href="/static/css/main.css">
|
||||||
|
<script src="/static/packages/htmx/htmx.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div id="bereich-a">
|
<div id="bereich-a">
|
||||||
<div class="top">
|
<div class="top">
|
||||||
<input type="text" placeholder="Suchfeld" />
|
<input type="text" name="search" placeholder="Suchfeld" autocomplete="false" hx-post="/htmx/contact" hx-trigger="keyup changed delay:500ms" hx-target="#z-1" hx-swap="outerHTML" />
|
||||||
<input type="text" placeholder="Amt" />
|
<input type="text" name="search" placeholder="Amt" autocomplete="false" hx-post="/htmx/department" hx-trigger="keyup changed delay:500ms" hx-target="#z-1" hx-swap="outerHTML" />
|
||||||
<input type="text" placeholder="Raum" />
|
<input type="text" name="search" placeholder="Raum" autocomplete="false" hx-post="/htmx/room" hx-trigger="keyup changed delay:500ms" hx-target="#z-1" hx-swap="outerHTML" />
|
||||||
<input type="text" placeholder="Gebäude" />
|
<input type="text" name="search" placeholder="Gebäude" autocomplete="false" hx-post="/htmx/location" hx-trigger="keyup changed delay:500ms" hx-target="#z-1" hx-swap="outerHTML" />
|
||||||
</div>
|
</div>
|
||||||
{{ template "kontakt" . }}
|
{{ template "kontakt" . }}
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user