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 {
|
||||
Contacts []Contact
|
||||
Keywords []Keyword
|
||||
/*Links []ContactKeywordLink*/
|
||||
}
|
||||
|
||||
/* ################################################################## */
|
||||
@@ -144,33 +145,39 @@ func (s *Server) privateHello(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func GetDataReturnDataPort(QueryContact, QueryKeyword string) DataPort {
|
||||
rows, err := DB.Query(QueryContact)
|
||||
if err != nil {
|
||||
fmt.Println("a", err)
|
||||
}
|
||||
var contList []Contact
|
||||
for rows.Next() {
|
||||
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)
|
||||
var keywordList []Keyword
|
||||
|
||||
if QueryContact != "" {
|
||||
rowsContact, err := DB.Query(QueryContact)
|
||||
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 err != nil {
|
||||
fmt.Println("c", err)
|
||||
}
|
||||
var keywordList []Keyword
|
||||
for rows1.Next() {
|
||||
var c0 Keyword
|
||||
err = rows1.Scan(&c0.Id, &c0.Owner, &c0.Name)
|
||||
if QueryKeyword != "" {
|
||||
rowsKeyword, err := DB.Query(QueryKeyword)
|
||||
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}
|
||||
}
|
||||
|
||||
@@ -283,44 +290,39 @@ func main() {
|
||||
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 {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
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 + "%';"
|
||||
|
||||
rows, err := db.Query(sqlq)
|
||||
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)
|
||||
D := GetDataReturnDataPort(sqlq, "")
|
||||
tplKontakt.ExecuteTemplate(w, "kontakt", D)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/htmx/kontaktbyschlagwort", func(w http.ResponseWriter, r *http.Request) {
|
||||
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)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/htmx/raum", func(w http.ResponseWriter, r *http.Request) {
|
||||
tplKontakt.ExecuteTemplate(w, "kontakt", nil)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/htmx/gebaeude", func(w http.ResponseWriter, r *http.Request) {
|
||||
mux.HandleFunc("/htmx/location", func(w http.ResponseWriter, r *http.Request) {
|
||||
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>
|
||||
<link rel="icon" type="image/vnd.icon" href="/static/img/favicon.ico">
|
||||
<link rel="stylesheet" href="/static/css/main.css">
|
||||
<script src="/static/packages/htmx/htmx.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div id="bereich-a">
|
||||
<div class="top">
|
||||
<input type="text" placeholder="Suchfeld" />
|
||||
<input type="text" placeholder="Amt" />
|
||||
<input type="text" placeholder="Raum" />
|
||||
<input type="text" placeholder="Gebäude" />
|
||||
<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" 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" 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" 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>
|
||||
{{ template "kontakt" . }}
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user