This is a paragraph.
11 |This is another paragraph.
12 |├── DOM.js ├── README.md ├── ajax.js ├── core.js ├── errors.js ├── examples ├── arrayMethods.html ├── attach.html ├── attach2.html ├── basicUsage.html ├── become.html ├── become2.html ├── become3.html ├── benchmark.html ├── cloneTo.html ├── contextSwitching.html ├── createElement.html ├── defer.html ├── defer2.html ├── do.html ├── doublecash.html ├── errors.html ├── fromHTML.html ├── fromJSON.html ├── fromStream.html ├── html.html ├── if.html ├── if2.html ├── moveOrClone.html ├── moveTo2.html ├── moveTo3.html ├── oldBadTests.html ├── parentKids.html ├── perilsFixedOrNot.html ├── perilsOfWait.html ├── playground.html ├── promisify - audio API.html ├── promisify - canvas.html ├── promisify - fileReader.html ├── promisify - geolocation.html ├── promisify - indexedDB.html ├── promisify - rAF.html ├── promisify - setInterval.html ├── promisify - xhr elaborate.html ├── promisify - xhr.html ├── runScripts.html ├── sanitize.html ├── send.html ├── send2.html ├── send3.html ├── server.js ├── takeWhile.html └── val.html ├── index.d.ts ├── index.js ├── methods.js ├── package-lock.json ├── package.json ├── promisify.js └── tsconfig.json /DOM.js: -------------------------------------------------------------------------------- 1 | export function setFormElementValue(element, value) { 2 | if (element instanceof HTMLInputElement) { 3 | const inputTypes = { 4 | checkbox: () => (element.checked = !!value), 5 | radio: () => (element.checked = element.value === value), 6 | default: () => (element.value = value), 7 | } 8 | const handler = inputTypes[element.type] || inputTypes.default 9 | return handler() 10 | } else if (element instanceof HTMLSelectElement) { 11 | if (element.multiple && Array.isArray(value)) { 12 | for (let option of element.options) { 13 | option.selected = value.includes(option.value) 14 | } 15 | } else if (typeof value === "string" || typeof value === "number") { 16 | element.value = value.toString() 17 | } 18 | } else if ( 19 | element instanceof HTMLTextAreaElement || 20 | element instanceof HTMLButtonElement 21 | ) { 22 | element.value = value 23 | } else { 24 | element.textContent = value 25 | } 26 | } 27 | 28 | export function parseArgument(prop, stringOrObj, value, attr) { 29 | if (typeof stringOrObj === "string") { 30 | attr ? prop.setAttribute(stringOrObj, value) : (prop[stringOrObj] = value) 31 | } else if (typeof stringOrObj === "object") { 32 | Object.assign(prop, stringOrObj) 33 | } 34 | } 35 | 36 | export function addStyleSheet(rules) { 37 | const importantRules = rules.trim().split(";").join(" !important;") 38 | const style = document.createElement("style") 39 | style.textContent = importantRules 40 | document.head.appendChild(style) 41 | } 42 | 43 | export function Class(type) { 44 | return (el, ...classes) => el.classList[type](...classes) 45 | } 46 | 47 | export function attach(element, ...args) { 48 | const options = 49 | args[args.length - 1] instanceof Object && 50 | ("sanitize" in args[args.length - 1] || "position" in args[args.length - 1]) 51 | ? args.pop() 52 | : {} 53 | 54 | const children = args.flat() 55 | 56 | if ((children instanceof NodeList || Array.isArray(children)) && !options) { 57 | options.all = true 58 | } 59 | 60 | modifyDOM(element, children, options) 61 | } 62 | 63 | export function moveOrClone(elements, parentSelector, options = {}) { 64 | let parents = getDOMElement(parentSelector, options) 65 | 66 | const children = Array.isArray(elements) ? elements : [elements].flat() 67 | 68 | parents.forEach((parent) => { 69 | modifyDOM(parent, children, options) 70 | }) 71 | } 72 | 73 | export function become(elements, replacements, options = { mode: "clone" }) { 74 | const handleReplacement = (element, replacement) => { 75 | if (!replacement) return 76 | const newElement = 77 | options.mode === "clone" ? replacement.cloneNode(true) : replacement 78 | element.replaceWith(newElement) 79 | } 80 | const proxyOrDOM = (candidate) => candidate.raw || candidate 81 | const makeArray = (candidate) => { 82 | if (Array.isArray(candidate)) return candidate 83 | if (candidate instanceof HTMLElement) return [candidate] 84 | if (candidate instanceof NodeList) return Array.from(candidate) 85 | return [] 86 | } 87 | 88 | const elementsArray = makeArray(proxyOrDOM(elements)) 89 | const replacementsArray = makeArray(proxyOrDOM(replacements)) 90 | 91 | elementsArray.forEach((element, index) => 92 | handleReplacement(element, replacementsArray[index]) 93 | ) 94 | } 95 | 96 | export function modifyDOM(parent, children, options) { 97 | const { 98 | position = "append", 99 | sanitize = true, 100 | mode = "move", 101 | sanitizer, 102 | all, 103 | } = options 104 | 105 | const DOMActions = { 106 | append: (parent, child) => parent.append(child), 107 | prepend: (parent, child) => parent.prepend(child), 108 | before: (parent, child) => parent.before(child), 109 | after: (parent, child) => parent.after(child), 110 | } 111 | 112 | const getCloneOrNode = 113 | mode === "clone" ? (el) => el.cloneNode(true) : (el) => el 114 | 115 | children.forEach((child) => { 116 | const domElements = getDOMElement(child, { sanitize, sanitizer, all }) 117 | domElements.forEach((domElement) => { 118 | DOMActions[position](parent, getCloneOrNode(domElement)) 119 | }) 120 | }) 121 | } 122 | 123 | export function getDOMElement(item, options) { 124 | return typeof item === "string" && item.trim().startsWith("<") // If it's an HTML string, 125 | ? createDOMFromString(item, options) // create DOM elements from it 126 | : item instanceof HTMLElement // If it's already a DOM element 127 | ? [item] // return it as an array 128 | : item instanceof NodeList // If it's a NodeList 129 | ? Array.from(item) // return it as an array 130 | : options.all // If the all flag is set 131 | ? Array.from(document.querySelectorAll(item)) // return all matching elements from the DOM as an array 132 | : [document.querySelector(item)] // Otherwise, return the first matching element from the DOM as an array 133 | } 134 | 135 | export function createDOMFromString(string, { sanitize = true, sanitizer }) { 136 | const div = document.createElement("div") 137 | sanitize ? div.setHTML(string, sanitizer) : (div.innerHTML = string) 138 | return Array.from(div.children) 139 | } 140 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jessquery 2 | 3 | `jessquery` is a lightweight wrapper around the DOM API that offers the intuitive elegance of jQuery, but streamlined for the modern web. 4 | 5 | Feel like a 🦕 for still using jQuery? Wish that it didn't bloat up your bundle size like a 🐖? Want something 🆕 and ✨? 6 | 7 | Rekindle your love for method chaining-- now in a lightweight, type-safe package! With **43** custom methods that are sneakily powerful, `jessquery` helps you seamlessly handle asynchronous tasks, customize error behaviors, and ensure your DOM operations always execute in order. And, the best part? 🏎️💨 8 | 9 | | Library | Size before gzip | Size after gzip | 10 | | --------- | ---------------- | --------------- | 11 | | jQuery | 88.3kb | 31.7kb | 12 | | jessquery | 8.82kb | 3.76kb | 13 | 14 | And, if that's too big for you, you can use our scrawny kid brother [Droxy](https://github.com/jazzypants1989/droxy/) instead. He's only 2kb after gzip! 15 | 16 |  17 | [](https://badge.fury.io/js/jessquery) 18 | 19 | - [Basic Usage](#basic-usage) 20 | - [Installation](#installation) 21 | - [Demo and Key Concepts](#demo-and-key-concepts) 22 | - [TypeScript](#typescript) 23 | - [The Rules](#the-rules) 24 | - [Code Walkthrough](#code-walkthrough) 25 | - [Advanced Usage](#advanced-usage) 26 | - [Interfaces](#interfaces) 27 | - [Contributing](#contributing) 28 | 29 | ## Basic Usage 30 | 31 | ```javascript 32 | // Most things follow the DOM API closely with slightly different names. 33 | // But, now you can chain them together 34 | // They will always execute in order! 35 | const fadeIn = [{ opacity: 0 }, { opacity: 1 }] // WAAPI keyframes 36 | const fadeOut = [{ opacity: 1 }, { opacity: 0 }] // WAAPI keyframes 37 | const animatedText = $$(".animated-text") // $$ ≈ querySelectorAll, use $ for querySelector 38 | 39 | // 40 | // 41 | animatedText 42 | .addClass("special") 43 | .wait(1000) // Will not appear for one second 44 | .toggle("hidden") 45 | .text( 46 | `
47 | In two seconds, every element matching the 'animated-text' class 48 | will fade in and out twice then disappear. 49 |
` 50 | ) 51 | .wait(2000) 52 | .transition(fadeIn, 1000) 53 | .transition(fadeOut, 1000) 54 | .transition(fadeIn, 1000) 55 | .transition(fadeOut, 1000) 56 | .purge() // All `.animated-text` elements will be removed from the DOM 57 | ``` 58 | 59 | ## Installation 60 | 61 | You can install it via NPM, PNPM, Yarn, or Bun just like anything else on NPM. 62 | 63 | ```bash 64 | npm install jessquery 65 | pnpm install jessquery 66 | yarn add jessquery 67 | bun install jessquery 68 | ``` 69 | 70 | Or, since it's so small, you can just use a CDN like the good, old days. The big problem with this is that you lose the types and the JSDoc annotations. I keep those in the `d.ts` file to keep the file size small, but I recently learned that gzip takes care of that for you. So, I'll probably change that in the future. For now, you can just use the `index.d.ts` file in your project if you want the types without installing the package. 71 | 72 | ```html 73 | 74 | 75 | ``` 76 | 77 | ## Demo and Key Concepts 78 | 79 | `jessquery` is quite different from jQuery, but it makes sense once you understand the rules. The concurrent chaining makes things a bit more complex. The key is understanding that each `$()` or `$$()` call is representative of a single queue-- not necessarily the elements that are being manipulated. It's a bit like [PrototypeJS](http://prototypejs.org/doc/latest/dom/dollar-dollar/) mixed with the async flow of something like [RxJS](https://rxjs.dev/guide/overview). 80 | 81 | The magic sauce here is that everything is a [proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy), so you can still use the full DOM API if your use case isn't covered by one of the methods. So, if you forget about the `.css` operator and use `.style` instead when using `$()`, it will just work. The NodeList that you get from `$$()` is automatically turned into an array so you can use array methods on it like `.map()` or `.filter()`. 82 | 83 | This is the benefit of using proxies, but I'm curious if this will scale well as they bring a tiny bit of overhead. This might get problematic in large applications, but I'm probably just being paranoid. I welcome anyone to do some tests! 😅 84 | 85 | Here's a [Stackblitz Playground](https://stackblitz.com/edit/jessquery?file=main.js) if you want to try it out. The demo that will load in has an extremely long chain showing the mutability that a standard `DomProxy` exhibits. To see how an error is thrown when that proxy is `fixed` in place, simply add a `true` argument to the `$()` call like this: `const container = $(".container", true)`. 86 | 87 | ## TypeScript 88 | 89 | Everything is fully type-safe, but there's no way for the `$()` and `$$()` functions to infer the type of the element you're selecting unless it's a tag name. Things like `$$('input')` will always be fully inferred even if you map over the individual elements in the collection-- in that case, each element would automatically become an `HTMLInputElement`. However, if you select a class or id, the type will always be `HTMLElement` unless you specify the type yourself like this: 90 | 91 | ```typescript 92 | const button = $1
179 | // *2
180 | // *3
181 | // *JSON.stringify(${pollAPIUntilItWorks()}, null, 2)`) 333 | .attach( 334 | `This will wait for ten seconds, but it will still show up if the API fails!` 335 | ) 336 | }) 337 | ``` 338 | 339 | ### AJAX 340 | 341 | - There's also internal `fromJSON`, `fromHTML`, and `fromStream` methods 342 | 343 | - (Above, I just wanted to show off the `promisify` method and how you don't have to await anything.) 344 | 345 | - These automatically handle fetching and parsing. 346 | 347 | - These functions expand fetch to incude additional event hooks and error handling. 348 | 349 | ```javascript 350 | const fetchOptions = { 351 | // You can use the same options as fetch, but with loads of extra features like: 352 | // Automatic Retries with exponential backoff. (default is 0) 353 | retries: 3, 354 | // The first retry will be in one second, then two seconds, then four seconds... 355 | retryDelay: 1000, 356 | // You can set up an error handler that will be called if the fetch still fails after all retries. 357 | onError: (err) => sendFetchErrorToAnalytics(err). 358 | // Or, a success handler that will reflect the DOM AFTER the element has been updated. 359 | onSuccess: () => dynamicSpans.attach("
${json.bio}
380 | ` 381 | ) 382 | .wait(5000) 383 | .fromHTML("/api/extended-bio", fetchOptions) 384 | .attach( 385 | "${summary}
`); 1111 | }, 1112 | { 1113 | error: 'Failed to load news item', 1114 | pnWait: () => $('#news-item').text('Loading news item...'), 1115 | onSuccess: () => console.log('News item loaded') 1116 | } 1117 | ) 1118 | ``` 1119 | 1120 | ##### DomProxy.fromHTML 1121 | 1122 | - **fromHTML(url: string, options?: FetchOptions): DomProxy** 1123 | 1124 | - Fetches an HTML resource from the provided URL and inserts it into the proxy's target element. 1125 | - The options object can be used define a plethora of options defined in [FetchOptions](#FetchOptions). 1126 | - These options extend the [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request) interface, so you can use all of the normal fetch options as well. (e.g., method, headers, etc.) 1127 | - The HTML is sanitized by default, which helps prevent XSS attacks. 1128 | - Example: `$('#template').fromHTML('/template.html')` 1129 | - Example: `$('#update').fromHTML('/update.html', { onWait: () => $('#fromHTML)'.text('Loading update...'), error: 'Failed to load update!' })` 1130 | - Example: `$('#content').fromHTML('/malicious-content.html', { sanitize: false })` 1131 | 1132 | ##### DomProxy.fromStream 1133 | 1134 | - **fromStream(url: string, options?: { sse?: boolean; add?: boolean; toTop?: boolean; error?: string; onError?: () => void; onWait?: () => void; waitTime?: number; runScripts?: boolean; sanitize?: boolean; onSuccess?: (data: any) => void }): DomProxy** 1135 | 1136 | - Dynamically fetches data from the provided URL and updates a single DOM element using a stream or Server-Sent Event (SSE). 1137 | - This includes all the same options as the `fromHTML` and `fromJSON` methods for normal streams, but with a few caveats for SSEs. 1138 | - the `onSuccess` callback will be invoked after each message is received, and it will receive the last event as an argument. 1139 | - SSE's have two additional options, `add` and `onTop`, which determine how the new data is added to the existing content. 1140 | - The options object can be used define a plethora of options defined in [FetchOptions](#FetchOptions). 1141 | - These options extend the [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request) interface, so you can use all of the normal fetch options as well. (e.g., method, headers, etc.) 1142 | - The HTML is sanitized by default, which helps prevent XSS attacks. 1143 | - Example: `$('#content').fromStream('/api/data', { sanitize: false })` <-- Only for trusted sources! 1144 | - Example: `$('#liveFeed').fromStream('/api/live', { sse: true, add: true, onSuccess: (data) => console.log('New data received:', data) })` 1145 | 1146 | ##### DomProxy.transition 1147 | 1148 | - **transition(keyframes: Keyframe[] | PropertyIndexedKeyframes, options: KeyframeAnimationOptions): DomProxy** 1149 | 1150 | - Animates the element using the WAAPI. 1151 | - Returns the proxy so you can continue chaining. If you need to return the animation object, use the `animate` method instead. 1152 | - Remember, this method is blocking, so watch out for any event handlers using the same variable. 1153 | - Example: `$('button').transition([{ opacity: 0 }, { opacity: 1 }], { duration: 1000 })` 1154 | - [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/Element/animate) 1155 | 1156 | ##### DomProxy.wait 1157 | 1158 | - **wait(ms: number): DomProxy** 1159 | 1160 | - Waits for a specified number of milliseconds before continuing the chain. 1161 | - Returns the proxy so you can continue chaining. If you need to return the animation object, use the `animate` method instead. 1162 | - Remember, this method is blocking, so watch out for any event handlers using the same variable. 1163 | - If you do not want to switch to a new variable, make sure that you use this in combination with `defer` which will allow the events to skip ahead in the queue. 1164 | - `wait` will **always** reliably hold up a queue when used in the middle of a chain, but each method that contains the element as an argument will operate on a different queue so `wait()` calls inside of them will not be respected in subsequent methods on the parent chain. 1165 | - These methods, such as `do`, `fromJSON`, and `if`, will respect `wait` calls held within them for their own individual queues, but they will not hold up the queue of the parent chain. 1166 | - So, if you call `wait` inside the callback for the method, you must continue the chain inside. Otherwise, the chain will continue immediately. 1167 | - Example: `$('button').wait(1000)` 1168 | - Example: 1169 | 1170 | ```javascript 1171 | testButton 1172 | .on("click", () => { 1173 | testButton.text(`Clicked ${++count} times`) 1174 | }) 1175 | .wait(2000) 1176 | .css({ color: "red" }) // BAD! The click handler will not register for 2 seconds! 1177 | 1178 | testButton 1179 | .on("click", () => { 1180 | testButton.text(`Clicked ${++count} times`) 1181 | }) 1182 | .defer((el) => el.wait(2000).css({ color: "red" })) // Good! The click handler will register immediately! 1183 | 1184 | testButton 1185 | .on("click", () => { 1186 | $(".testButton").text(`Clicked ${++count} times`) 1187 | }) 1188 | .wait(2000) 1189 | .css({ color: "red" }) // Less good, but still better! 1190 | // You made a new proxy for no reason, but at least the user can click the button! 1191 | 1192 | testButton 1193 | .do((el) => el.wait(2000).css({ color: "green" }).wait(2000)) 1194 | .css({ color: "red" }) 1195 | // This will turn red, then green. NOT GREEN, THEN RED! 1196 | ``` 1197 | 1198 | ##### DomProxy.next 1199 | 1200 | - **next(): DomProxy** 1201 | 1202 | - Switch to the next sibling of the element in the middle of a chain. 1203 | - This will throw an error if the proxy was created as "fixed" (with a second argument of true). 1204 | - Example: `$('button').css('color', 'red').next().css('color', 'blue')` 1205 | - Expectation: The next sibling of the button will turn blue. The button itself will remain red. 1206 | 1207 | ##### DomProxy.prev 1208 | 1209 | - **prev(): DomProxy** 1210 | 1211 | - Switch to the previous sibling of the element in the middle of a chain. 1212 | - This will throw an error if the proxy was created as "fixed" (with a second argument of true). 1213 | - Example: `$('button').css('color', 'red').prev().css('color', 'blue')` 1214 | - Expectation: The previous sibling of the button will turn blue. The button itself will remain red. 1215 | 1216 | ##### DomProxy.first 1217 | 1218 | - **first(): DomProxy** 1219 | 1220 | - Switch to the first child of the element in the middle of a chain. 1221 | - This will throw an error if the proxy was created as "fixed" (with a second argument of true). 1222 | - Example: `$('button').css('color', 'red').first().css('color', 'blue')` 1223 | - Expectation: The first child of the button will turn blue. The button itself will remain red. 1224 | 1225 | ##### DomProxy.last 1226 | 1227 | - **last(): DomProxy** 1228 | 1229 | - Switch to the last child of the element in the middle of a chain. 1230 | - This will throw an error if the proxy was created as "fixed" (with a second argument of true). 1231 | - Example: `$('button').css('color', 'red').last().css('color', 'blue')` 1232 | - Expectation: The last child of the button will turn blue. The button itself will remain red. 1233 | 1234 | ##### DomProxy.parent 1235 | 1236 | - **parent(): DomProxy** 1237 | 1238 | - Switch to the parent of the element in the middle of a chain. 1239 | - This will throw an error if the proxy was created as "fixed" (with a second argument of true). 1240 | - Example: `$('button').css('color', 'red').parent().css('color', 'blue')` 1241 | - Expectation: The parent of the button will turn blue. The button itself will remain red. 1242 | 1243 | ##### DomProxy.ancestor 1244 | 1245 | - **ancestor(ancestorSelector: string): DomProxy** 1246 | 1247 | - Gets the closest ancestor matching a selector. Uses the `closest` API under the hood. 1248 | - This will throw an error if the proxy was created as "fixed" (with a second argument of true). 1249 | - Example: `$('.buttons').css('color', 'red').ancestor('.container').css('color', 'blue')` 1250 | - Expectation: The container will turn blue. The buttons will remain red. 1251 | 1252 | ##### DomProxy.pick 1253 | 1254 | - **pick(subSelector: string): DomProxy** 1255 | 1256 | - Gets the first descendant matching a sub-selector. 1257 | - This will throw an error if the proxy was created as "fixed" (with a second argument of true). 1258 | - Example: `$('.container').css('color', 'red').pick('.buttons').css('color', 'blue')` 1259 | - Expectation: The descendants of the container will turn blue. The container itself will remain red. 1260 | 1261 | ##### DomProxy.pickAll 1262 | 1263 | - **pickAll(subSelector: string): DomProxyCollection** 1264 | 1265 | - Gets all descendants matching a sub-selector. 1266 | - This will throw an error if the proxy was created as "fixed" (with a second argument of true). 1267 | - Example: `$('.container').css('color', 'red').pickAll('.buttons').css('color', 'blue')` 1268 | - Expectation: The descendants of the container will turn blue. The container itself will remain red. 1269 | 1270 | ##### DomProxy.siblings 1271 | 1272 | - **siblings(): DomProxyCollection** 1273 | 1274 | - Switch to the siblings of the element in the middle of a chain. 1275 | - This will throw an error if the proxy was created as "fixed" (with a second argument of true). 1276 | - Example: `$('button').css('color', 'red').siblings().css('color', 'blue')` 1277 | - Expectation: The siblings of the button will turn blue. The button itself will remain red. 1278 | 1279 | ##### DomProxy.kids 1280 | 1281 | - **kids(): DomProxyCollection** 1282 | 1283 | - Switch to the children of the element in the middle of a chain. 1284 | - This will throw an error if the proxy was created as "fixed" (with a second argument of true). 1285 | - Example: `$('button').css('color', 'red').kids().css('color', 'blue')` 1286 | - Expectation: The children of the button will turn blue. The button itself will remain red. 1287 | 1288 | ##### DomProxy.if 1289 | 1290 | - **if: (options: { is: (el: DomProxy${summary}
`); 1744 | }, 1745 | { 1746 | error: 'Failed to load news item', 1747 | onWait: () => `Container 1
25 |Container 2
31 |This is a paragraph.
11 |This is another paragraph.
12 |${new Date().toLocaleTimeString()} \n
`) 75 | res.write(``) 76 | }, 1000) 77 | 78 | // End the stream and clear the interval after 5 seconds 79 | setTimeout(() => { 80 | clearInterval(intervalId) 81 | res.end() 82 | }, 5000) 83 | 84 | // Clear the interval if the client disconnects before 5 seconds 85 | res.on("close", () => clearInterval(intervalId)) 86 | }) 87 | 88 | server.listen(8080, () => { 89 | console.log("Server is running on port 8080") 90 | }) 91 | 92 | // ** SERVER SENT EVENTS ** 93 | 94 | // const server = http.createServer((req, res) => { 95 | // // Allow requests from any origin 96 | // res.setHeader("Access-Control-Allow-Origin", "*") 97 | 98 | // // Set other CORS headers as needed 99 | // res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE") 100 | // res.setHeader("Access-Control-Allow-Headers", "Content-Type") 101 | 102 | // // Check if the client requested SSE 103 | // if (req.headers.accept && req.headers.accept === "text/event-stream") { 104 | // // Set headers for SSE 105 | // res.writeHead(200, { 106 | // "Content-Type": "text/event-stream", 107 | // "Cache-Control": "no-cache", 108 | // "Connection": "keep-alive", 109 | // }) 110 | 111 | // // Send an event every second 112 | // const intervalId = setInterval(() => { 113 | // res.write( 114 | // `data: The server time is: ${new Date().toLocaleTimeString()}\n\n` 115 | // ) 116 | // }, 1000) 117 | 118 | // // Close the connection when the client disconnects 119 | // req.on("close", () => { 120 | // clearInterval(intervalId) 121 | // res.end() 122 | // }) 123 | // } else { 124 | // // Handle other requests (e.g., API requests, static files, etc.) 125 | // res.writeHead(404) 126 | // res.end() 127 | // } 128 | // }) 129 | 130 | // server.listen(8080, () => { 131 | // console.log("Server is running on port 8080") 132 | // }) 133 | 134 | // // **RETRIES** 135 | // let requestCounter = 0 136 | // let firstRequestTime = null 137 | 138 | // const server = http.createServer((req, res) => { 139 | // const parsedUrl = new url.URL(req.url, `http://${req.headers.host}`) 140 | 141 | // console.log(req.method, req.url) 142 | 143 | // // Set CORS headers 144 | // res.setHeader("Access-Control-Allow-Origin", "*") 145 | // res.setHeader("Access-Control-Allow-Methods", "GET") 146 | // res.setHeader("Access-Control-Allow-Headers", "Content-Type") 147 | 148 | // if (req.method === "OPTIONS") { 149 | // res.statusCode = 200 150 | // res.end() 151 | // return 152 | // } 153 | 154 | // if (req.method === "GET" && parsedUrl.pathname === "/data") { 155 | // requestCounter++ 156 | 157 | // // note the time at which the request was made 158 | // firstRequestTime = firstRequestTime || new Date() 159 | // console.log( 160 | // `Request #${requestCounter} received at ${new Date().toLocaleTimeString()}, first request at ${firstRequestTime.toLocaleTimeString()}` 161 | // ) 162 | 163 | // // Mock an error for the first 5 requests 164 | // if (requestCounter <= 5) { 165 | // res.statusCode = 500 166 | // res.end("Something went wrong!") 167 | // } else { 168 | // // note the difference in the response time 169 | // const responseTime = new Date() 170 | // res.end( 171 | // `Request #${requestCounter} took ${ 172 | // Number(responseTime) - Number(firstRequestTime) 173 | // } milliseconds` 174 | // ) 175 | // } 176 | // } else { 177 | // res.statusCode = 404 178 | // res.end("Not Found") 179 | // } 180 | // }) 181 | 182 | // server.listen(3000, () => { 183 | // console.log(`Server is running on http://localhost:${3000}`) 184 | // }) 185 | -------------------------------------------------------------------------------- /examples/takeWhile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |