diff --git a/jquery-closure-clojurescript-comparison.md b/jquery-closure-clojurescript-comparison.md new file mode 100644 index 0000000..3c5e397 --- /dev/null +++ b/jquery-closure-clojurescript-comparison.md @@ -0,0 +1,2499 @@ +# jQuery vs Google Closure Library vs ClojureScript Comparison + +A comprehensive comparison of jQuery 4.0.0 with Google Closure Library and ClojureScript (with Google Closure), providing equivalent code for every example in the jQuery documentation. + +--- + +## Table of Contents + +1. [Overview](#overview) +2. [Installation](#installation) +3. [Core Selection](#core-selection) +4. [Utility Functions](#utility-functions) +5. [DOM Manipulation](#dom-manipulation) +6. [Event Handling](#event-handling) +7. [Traversal](#traversal) +8. [Data Storage](#data-storage) +9. [AJAX](#ajax) +10. [Deferred/Promises](#deferredpromises) +11. [Callbacks](#callbacks) +12. [Animation](#animation) +13. [Security Features](#security-features) + +--- + +## Overview + +| Aspect | jQuery 4.0.0 | Google Closure Library | ClojureScript + Closure | +|--------|-------------|----------------------|------------------------| +| **Status** | Active (Jan 2026) | Sunset (Aug 2024) | Active | +| **Size** | ~30kb gzipped | Modular (tree-shaken) | Compiled & optimized | +| **Philosophy** | Imperative, chaining | Object-oriented, modular | Functional, immutable | +| **Browser compat** | Last 3 versions | Cross-browser normalized | Via Closure Compiler | +| **Build system** | Optional | Closure Compiler | ClojureScript compiler | + +### Key Differences + +**jQuery**: Designed for simplicity and DOM manipulation with a fluent chaining API. Excellent for quick prototyping and small projects. + +**Google Closure Library**: A comprehensive, modular library built for large-scale applications. Designed to work with the Closure Compiler for aggressive optimizations. Note: The library was sunset in August 2024. + +**ClojureScript**: A Clojure dialect that compiles to JavaScript. Automatically includes Google Closure Library and uses the Closure Compiler. Offers functional programming paradigms with immutable data structures. + +--- + +## Installation + +### jQuery 4.0.0 + +```html + + +``` + +```javascript +// ES Module +import $ from 'jquery'; + +// CommonJS +const $ = require('jquery'); +``` + +### Google Closure Library + +```html + + + +``` + +```javascript +// Modern ES modules (if using Closure Compiler) +goog.module('myapp.main'); +const dom = goog.require('goog.dom'); +const events = goog.require('goog.events'); +``` + +### ClojureScript + +```clojure +;; deps.edn +{:deps {org.clojure/clojurescript {:mvn/version "1.11.132"}}} + +;; In your namespace +(ns myapp.core + (:require [goog.dom :as gdom] + [goog.events :as gevents]) + (:import [goog.events EventType])) +``` + +--- + +## Core Selection + +### jQuery: Select by ID + +```javascript +// jQuery +const $header = $('#header'); +``` + +### Google Closure + +```javascript +// Google Closure +const header = goog.dom.getElement('header'); +// or shorthand +const header = goog.dom.$('header'); +``` + +### ClojureScript + +```clojure +;; ClojureScript with goog.dom +(require '[goog.dom :as gdom]) +(def header (gdom/getElement "header")) + +;; Or using native JS interop +(def header (.getElementById js/document "header")) +``` + +--- + +### jQuery: Select by Class + +```javascript +// jQuery +const $buttons = $('.btn'); +``` + +### Google Closure + +```javascript +// Google Closure - single element +const button = goog.dom.getElementByClass('btn'); + +// Multiple elements +const buttons = goog.dom.getElementsByClass('btn'); +``` + +### ClojureScript + +```clojure +;; ClojureScript +(def button (gdom/getElementByClass "btn")) +(def buttons (gdom/getElementsByClass "btn")) + +;; With dommy library +(require '[dommy.core :as dommy :refer-macros [sel sel1]]) +(def button (sel1 :.btn)) +(def buttons (sel :.btn)) +``` + +--- + +### jQuery: Select by Tag + +```javascript +// jQuery +const $paragraphs = $('p'); +``` + +### Google Closure + +```javascript +// Google Closure +const paragraphs = goog.dom.getElementsByTagName('p'); + +// With class filter +const items = goog.dom.getElementsByTagNameAndClass('div', 'item'); +``` + +### ClojureScript + +```clojure +;; ClojureScript +(def paragraphs (gdom/getElementsByTagName "p")) + +;; With dommy +(def paragraphs (sel :p)) +``` + +--- + +### jQuery: Select with Context + +```javascript +// jQuery +const $items = $('.item', '#container'); +``` + +### Google Closure + +```javascript +// Google Closure +const container = goog.dom.getElement('container'); +const items = goog.dom.getElementsByClass('item', container); + +// Using query selector +const items = goog.dom.query('.item', container); +``` + +### ClojureScript + +```clojure +;; ClojureScript +(def container (gdom/getElement "container")) +(def items (gdom/getElementsByClass "item" container)) + +;; With dommy +(def items (sel container :.item)) +``` + +--- + +### jQuery: Create Elements + +```javascript +// jQuery +const $newDiv = $('
New paragraph
'); + +// jQuery - Prepend +$('#container').prepend('First paragraph
'); + +// jQuery - Remove +$('#element').remove(); + +// jQuery - Empty (remove children) +$('#container').empty(); +``` + +### Google Closure + +```javascript +// Google Closure +goog.require('goog.dom'); + +const container = goog.dom.getElement('container'); + +// Append +const newP = goog.dom.createDom('p', null, 'New paragraph'); +goog.dom.appendChild(container, newP); + +// Prepend +const firstP = goog.dom.createDom('p', null, 'First paragraph'); +goog.dom.insertChildAt(container, firstP, 0); + +// Remove +const element = goog.dom.getElement('element'); +goog.dom.removeNode(element); + +// Empty (remove children) +goog.dom.removeChildren(container); +``` + +### ClojureScript + +```clojure +;; ClojureScript with goog.dom +(require '[goog.dom :as gdom]) + +(def container (gdom/getElement "container")) + +;; Append +(let [new-p (gdom/createDom "p" nil "New paragraph")] + (gdom/appendChild container new-p)) + +;; Prepend +(let [first-p (gdom/createDom "p" nil "First paragraph")] + (gdom/insertChildAt container first-p 0)) + +;; Remove +(gdom/removeNode (gdom/getElement "element")) + +;; Empty +(gdom/removeChildren container) + +;; With dommy library +(require '[dommy.core :as dommy :refer-macros [sel1]]) +(dommy/append! (sel1 :#container) some-element) +(dommy/remove! (sel1 :#element)) +``` + +--- + +### jQuery: Class Manipulation + +```javascript +// jQuery +$('#element').addClass('active'); +$('#element').removeClass('inactive'); +$('#element').toggleClass('visible'); +$('#element').hasClass('active'); // returns boolean +``` + +### Google Closure + +```javascript +// Google Closure +goog.require('goog.dom.classlist'); + +const element = goog.dom.getElement('element'); + +goog.dom.classlist.add(element, 'active'); +goog.dom.classlist.remove(element, 'inactive'); +goog.dom.classlist.toggle(element, 'visible'); +goog.dom.classlist.contains(element, 'active'); // returns boolean + +// Multiple classes +goog.dom.classlist.addAll(element, ['class1', 'class2']); +goog.dom.classlist.removeAll(element, ['class1', 'class2']); + +// Swap classes +goog.dom.classlist.swap(element, 'old-class', 'new-class'); +``` + +### ClojureScript + +```clojure +;; ClojureScript with goog.dom.classlist +(require '[goog.dom.classlist :as classlist]) + +(def element (gdom/getElement "element")) + +(classlist/add element "active") +(classlist/remove element "inactive") +(classlist/toggle element "visible") +(classlist/contains element "active") ;; returns boolean + +;; With dommy +(require '[dommy.core :as dommy :refer-macros [sel1]]) +(dommy/add-class! (sel1 :#element) :active) +(dommy/remove-class! (sel1 :#element) :inactive) +(dommy/toggle-class! (sel1 :#element) :visible) +(dommy/has-class? (sel1 :#element) :active) +``` + +--- + +### jQuery: CSS/Style Manipulation + +```javascript +// jQuery - Get style +const color = $('#element').css('color'); + +// jQuery - Set single style +$('#element').css('color', 'red'); + +// jQuery - Set multiple styles +$('#element').css({ + color: 'red', + fontSize: '16px', + backgroundColor: '#fff' +}); +``` + +### Google Closure + +```javascript +// Google Closure +goog.require('goog.style'); + +const element = goog.dom.getElement('element'); + +// Get style +const color = goog.style.getStyle(element, 'color'); + +// Set single style +goog.style.setStyle(element, 'color', 'red'); + +// Set multiple styles +goog.style.setStyle(element, { + 'color': 'red', + 'font-size': '16px', + 'background-color': '#fff' +}); + +// Other style utilities +goog.style.setOpacity(element, 0.5); +goog.style.setSize(element, 100, 200); // width, height +goog.style.setPosition(element, 10, 20); // x, y +goog.style.showElement(element, true); // show/hide +``` + +### ClojureScript + +```clojure +;; ClojureScript with goog.style +(require '[goog.style :as gstyle]) + +(def element (gdom/getElement "element")) + +;; Get style +(def color (gstyle/getStyle element "color")) + +;; Set single style +(gstyle/setStyle element "color" "red") + +;; Set multiple styles +(gstyle/setStyle element #js {:color "red" + :font-size "16px" + :background-color "#fff"}) + +;; With dommy +(dommy/set-style! (sel1 :#element) :color "red") +(dommy/set-style! (sel1 :#element) :font-size "16px") +``` + +--- + +### jQuery: Text and HTML Content + +```javascript +// jQuery - Get/Set text +const text = $('#element').text(); +$('#element').text('New text content'); + +// jQuery - Get/Set HTML +const html = $('#element').html(); +$('#element').html('Bold text'); +``` + +### Google Closure + +```javascript +// Google Closure +goog.require('goog.dom'); + +const element = goog.dom.getElement('element'); + +// Get/Set text +const text = goog.dom.getTextContent(element); +goog.dom.setTextContent(element, 'New text content'); + +// Get/Set HTML (be careful with XSS) +const html = element.innerHTML; +element.innerHTML = 'Bold text'; + +// Safe HTML setting (with Trusted Types support) +goog.require('goog.dom.safe'); +goog.dom.safe.setInnerHtml(element, trustedHtml); +``` + +### ClojureScript + +```clojure +;; ClojureScript with goog.dom +(def element (gdom/getElement "element")) + +;; Get/Set text +(def text (gdom/getTextContent element)) +(gdom/setTextContent element "New text content") + +;; Get/Set HTML +(def html (.-innerHTML element)) +(set! (.-innerHTML element) "Bold text") + +;; With dommy +(dommy/text (sel1 :#element)) +(dommy/set-text! (sel1 :#element) "New text") +(dommy/html (sel1 :#element)) +(dommy/set-html! (sel1 :#element) "Bold") +``` + +--- + +### jQuery: Attributes + +```javascript +// jQuery - Get attribute +const href = $('a').attr('href'); + +// jQuery - Set attribute +$('a').attr('href', 'https://example.com'); + +// jQuery - Set multiple attributes +$('#img').attr({ + src: 'image.jpg', + alt: 'Description' +}); + +// jQuery - Remove attribute +$('#input').removeAttr('disabled'); +``` + +### Google Closure + +```javascript +// Google Closure +const element = goog.dom.getElement('link'); + +// Get attribute +const href = element.getAttribute('href'); + +// Set attribute +element.setAttribute('href', 'https://example.com'); + +// Set multiple with goog.dom +goog.dom.setProperties(element, { + 'href': 'https://example.com', + 'target': '_blank' +}); + +// Remove attribute +element.removeAttribute('disabled'); +``` + +### ClojureScript + +```clojure +;; ClojureScript +(def element (gdom/getElement "link")) + +;; Get attribute +(def href (.getAttribute element "href")) + +;; Set attribute +(.setAttribute element "href" "https://example.com") + +;; Set multiple +(gdom/setProperties element #js {:href "https://example.com" + :target "_blank"}) + +;; Remove +(.removeAttribute element "disabled") + +;; With dommy +(dommy/attr (sel1 :a) :href) +(dommy/set-attr! (sel1 :a) :href "https://example.com") +(dommy/remove-attr! (sel1 :#input) :disabled) +``` + +--- + +## Event Handling + +### jQuery: Basic Event Binding + +```javascript +// jQuery - on() method +$('#button').on('click', function(e) { + console.log('Button clicked'); +}); + +// jQuery - Shorthand +$('#button').click(function() { + console.log('Button clicked'); +}); + +// jQuery - Multiple events +$('#input').on('focus blur', function(e) { + console.log('Focus state changed'); +}); +``` + +### Google Closure + +```javascript +// Google Closure +goog.require('goog.events'); +goog.require('goog.events.EventType'); + +const button = goog.dom.getElement('button'); + +// Basic listener +goog.events.listen(button, goog.events.EventType.CLICK, function(e) { + console.log('Button clicked'); +}); + +// Multiple events +const input = goog.dom.getElement('input'); +goog.events.listen(input, [goog.events.EventType.FOCUS, goog.events.EventType.BLUR], + function(e) { + console.log('Focus state changed'); + } +); +``` + +### ClojureScript + +```clojure +;; ClojureScript with goog.events +(require '[goog.events :as gevents]) +(import '[goog.events EventType]) + +(def button (gdom/getElement "button")) + +;; Basic listener +(gevents/listen button EventType.CLICK + (fn [e] (println "Button clicked"))) + +;; Multiple events +(doseq [event-type [EventType.FOCUS EventType.BLUR]] + (gevents/listen input event-type + (fn [e] (println "Focus state changed")))) + +;; With dommy +(dommy/listen! (sel1 :#button) :click + (fn [e] (println "Button clicked"))) +``` + +--- + +### jQuery: Event Delegation + +```javascript +// jQuery - Event delegation +$('#container').on('click', '.item', function(e) { + console.log('Item clicked:', $(this).text()); +}); +``` + +### Google Closure + +```javascript +// Google Closure - Manual delegation +goog.require('goog.events'); +goog.require('goog.dom'); + +const container = goog.dom.getElement('container'); + +goog.events.listen(container, goog.events.EventType.CLICK, function(e) { + // Check if target matches selector + const target = e.target; + if (goog.dom.classlist.contains(target, 'item')) { + console.log('Item clicked:', goog.dom.getTextContent(target)); + } + + // Or find closest matching ancestor + const item = goog.dom.getAncestorByClass(target, 'item'); + if (item) { + console.log('Item clicked:', goog.dom.getTextContent(item)); + } +}); +``` + +### ClojureScript + +```clojure +;; ClojureScript - Manual delegation +(gevents/listen (gdom/getElement "container") EventType.CLICK + (fn [e] + (let [target (.-target e)] + (when (classlist/contains target "item") + (println "Item clicked:" (gdom/getTextContent target)))))) + +;; With dommy - built-in delegation +(dommy/listen! [(sel1 :#container) :.item] :click + (fn [e] + (println "Item clicked:" (dommy/text (.-selectedTarget e))))) +``` + +--- + +### jQuery: One-time Events + +```javascript +// jQuery - one() for single execution +$('#button').one('click', function() { + console.log('This only fires once'); +}); +``` + +### Google Closure + +```javascript +// Google Closure +goog.events.listenOnce(button, goog.events.EventType.CLICK, function(e) { + console.log('This only fires once'); +}); +``` + +### ClojureScript + +```clojure +;; ClojureScript +(gevents/listenOnce button EventType.CLICK + (fn [e] (println "This only fires once"))) + +;; With dommy +(dommy/listen-once! (sel1 :#button) :click + (fn [e] (println "This only fires once"))) +``` + +--- + +### jQuery: Removing Event Handlers + +```javascript +// jQuery - Named handler for removal +function clickHandler(e) { + console.log('Clicked'); +} + +$('#button').on('click', clickHandler); +$('#button').off('click', clickHandler); + +// Remove all click handlers +$('#button').off('click'); + +// Remove all handlers +$('#button').off(); +``` + +### Google Closure + +```javascript +// Google Closure +function clickHandler(e) { + console.log('Clicked'); +} + +// Listen returns a key for removal +const key = goog.events.listen(button, goog.events.EventType.CLICK, clickHandler); + +// Remove specific handler +goog.events.unlistenByKey(key); + +// Or remove by parameters +goog.events.unlisten(button, goog.events.EventType.CLICK, clickHandler); + +// Remove all handlers from element +goog.events.removeAll(button); + +// Remove all handlers of specific type +goog.events.removeAll(button, goog.events.EventType.CLICK); +``` + +### ClojureScript + +```clojure +;; ClojureScript +(defn click-handler [e] + (println "Clicked")) + +;; Store the key +(def key (gevents/listen button EventType.CLICK click-handler)) + +;; Remove by key +(gevents/unlistenByKey key) + +;; Remove by parameters +(gevents/unlisten button EventType.CLICK click-handler) + +;; Remove all +(gevents/removeAll button) + +;; With dommy +(dommy/listen! (sel1 :#button) :click click-handler) +(dommy/unlisten! (sel1 :#button) :click click-handler) +``` + +--- + +### jQuery: Focus Event Order (W3C in 4.0) + +```javascript +// jQuery 4.0 order (W3C compliant): +// blur → focusout → focus → focusin + +const $input = $('#myInput'); + +$input.on('blur', () => console.log('1. blur')); +$input.on('focusout', () => console.log('2. focusout')); +$input.on('focus', () => console.log('3. focus')); +$input.on('focusin', () => console.log('4. focusin')); +``` + +### Google Closure + +```javascript +// Google Closure - Uses native event order +const input = goog.dom.getElement('myInput'); + +goog.events.listen(input, goog.events.EventType.BLUR, function() { + console.log('blur'); +}); + +goog.events.listen(input, goog.events.EventType.FOCUS, function() { + console.log('focus'); +}); + +// focusin/focusout via FocusHandler for cross-browser support +goog.require('goog.events.FocusHandler'); +const focusHandler = new goog.events.FocusHandler(input); +goog.events.listen(focusHandler, goog.events.FocusHandler.EventType.FOCUSIN, function() { + console.log('focusin'); +}); +``` + +### ClojureScript + +```clojure +;; ClojureScript +(gevents/listen input EventType.BLUR #(println "blur")) +(gevents/listen input EventType.FOCUS #(println "focus")) + +;; For focusin/focusout +(import '[goog.events FocusHandler]) +(def focus-handler (FocusHandler. input)) +(gevents/listen focus-handler (.-FOCUSIN FocusHandler.EventType) #(println "focusin")) +``` + +--- + +## Traversal + +### jQuery: .find() + +```javascript +// jQuery - Find all paragraphs inside #content +$('#content').find('p').addClass('styled'); + +// Chained traversal +$('#nav') + .find('li') + .find('a') + .addClass('nav-link'); +``` + +### Google Closure + +```javascript +// Google Closure +const content = goog.dom.getElement('content'); +const paragraphs = goog.dom.getElementsByTagName('p', content); +goog.array.forEach(paragraphs, function(p) { + goog.dom.classlist.add(p, 'styled'); +}); + +// Or with query +const links = goog.dom.query('#nav li a'); +goog.array.forEach(links, function(a) { + goog.dom.classlist.add(a, 'nav-link'); +}); +``` + +### ClojureScript + +```clojure +;; ClojureScript +(let [content (gdom/getElement "content") + paragraphs (gdom/getElementsByTagName "p" content)] + (doseq [p (array-seq paragraphs)] + (classlist/add p "styled"))) + +;; With dommy +(doseq [p (sel (sel1 :#content) :p)] + (dommy/add-class! p :styled)) +``` + +--- + +### jQuery: .filter() + +```javascript +// jQuery - Filter by selector +$('li').filter('.active').css('font-weight', 'bold'); + +// Filter by function +$('input').filter(function() { + return $(this).val().length > 0; +}).addClass('has-value'); +``` + +### Google Closure + +```javascript +// Google Closure +goog.require('goog.array'); + +// Filter by class +const items = goog.dom.getElementsByTagName('li'); +const activeItems = goog.array.filter(items, function(li) { + return goog.dom.classlist.contains(li, 'active'); +}); +goog.array.forEach(activeItems, function(li) { + goog.style.setStyle(li, 'font-weight', 'bold'); +}); + +// Filter by function +const inputs = goog.dom.getElementsByTagName('input'); +const filledInputs = goog.array.filter(inputs, function(input) { + return input.value.length > 0; +}); +``` + +### ClojureScript + +```clojure +;; ClojureScript +(->> (array-seq (gdom/getElementsByTagName "li")) + (filter #(classlist/contains % "active")) + (run! #(gstyle/setStyle % "font-weight" "bold"))) + +;; Filter inputs with values +(->> (array-seq (gdom/getElementsByTagName "input")) + (filter #(> (count (.-value %)) 0)) + (run! #(classlist/add % "has-value"))) + +;; With dommy +(->> (sel :li) + (filter #(dommy/has-class? % :active)) + (run! #(dommy/set-style! % :font-weight "bold"))) +``` + +--- + +### jQuery: .closest() + +```javascript +// jQuery - Find parent form +$('input').on('blur', function() { + const $form = $(this).closest('form'); + validateForm($form); +}); +``` + +### Google Closure + +```javascript +// Google Closure +goog.require('goog.dom'); + +goog.events.listen(input, 'blur', function(e) { + const form = goog.dom.getAncestorByTagNameAndClass(e.target, 'form'); + validateForm(form); +}); + +// By class +const container = goog.dom.getAncestorByClass(element, 'container'); +``` + +### ClojureScript + +```clojure +;; ClojureScript +(gevents/listen input "blur" + (fn [e] + (let [form (gdom/getAncestorByTagNameAndClass (.-target e) "form")] + (validate-form form)))) + +;; By class +(def container (gdom/getAncestorByClass element "container")) +``` + +--- + +### jQuery: .has() + +```javascript +// jQuery - Only select divs that contain a span +$('div').has('span').addClass('has-span'); +``` + +### Google Closure + +```javascript +// Google Closure +const divs = goog.dom.getElementsByTagName('div'); +goog.array.forEach(divs, function(div) { + if (goog.dom.getElementsByTagName('span', div).length > 0) { + goog.dom.classlist.add(div, 'has-span'); + } +}); +``` + +### ClojureScript + +```clojure +;; ClojureScript +(->> (array-seq (gdom/getElementsByTagName "div")) + (filter #(pos? (.-length (gdom/getElementsByTagName "span" %)))) + (run! #(classlist/add % "has-span"))) +``` + +--- + +### jQuery: .index() + +```javascript +// jQuery +$('li.active').index(); // Position among siblings + +const $items = $('li'); +$items.index($('.active')); // Position in collection +``` + +### Google Closure + +```javascript +// Google Closure +goog.require('goog.array'); + +// Among siblings +const active = goog.dom.getElementByClass('active'); +const siblings = goog.dom.getChildren(active.parentNode); +const index = goog.array.indexOf(siblings, active); + +// In collection +const items = goog.dom.getElementsByTagName('li'); +const activeItem = goog.dom.getElementByClass('active'); +const indexInCollection = goog.array.indexOf(items, activeItem); +``` + +### ClojureScript + +```clojure +;; ClojureScript +(let [active (gdom/getElementByClass "active") + siblings (gdom/getChildren (.-parentNode active))] + (.indexOf (array-seq siblings) active)) +``` + +--- + +### jQuery: .add() + +```javascript +// jQuery - Combine selections +$('p') + .add('span') + .add('div.highlight') + .addClass('styled'); +``` + +### Google Closure + +```javascript +// Google Closure - Combine into array +const combined = goog.array.concat( + goog.array.toArray(goog.dom.getElementsByTagName('p')), + goog.array.toArray(goog.dom.getElementsByTagName('span')), + goog.array.toArray(goog.dom.getElementsByClass('highlight')) +); + +goog.array.forEach(combined, function(el) { + goog.dom.classlist.add(el, 'styled'); +}); +``` + +### ClojureScript + +```clojure +;; ClojureScript +(let [combined (concat + (array-seq (gdom/getElementsByTagName "p")) + (array-seq (gdom/getElementsByTagName "span")) + (array-seq (gdom/getElementsByClass "highlight")))] + (run! #(classlist/add % "styled") combined)) +``` + +--- + +## Data Storage + +### jQuery: .data() + +```javascript +// jQuery - Set data +$('#element').data('userId', 123); +$('#element').data('config', { theme: 'dark', lang: 'en' }); + +// Get data +const userId = $('#element').data('userId'); // 123 +const config = $('#element').data('config'); + +// Get all data +const allData = $('#element').data(); + +// HTML5 data attributes are auto-loaded +//Safe content
'); +$('#container').html(trustedContent); +``` + +### Google Closure + +```javascript +// Google Closure - Built-in safe DOM methods +goog.require('goog.dom.safe'); +goog.require('goog.html.SafeHtml'); + +// Create safe HTML +const safeHtml = goog.html.SafeHtml.create('p', {}, 'Safe content'); + +// Set inner HTML safely +goog.dom.safe.setInnerHtml(container, safeHtml); +``` + +### ClojureScript + +```clojure +;; ClojureScript +(require '[goog.dom.safe :as safe] + '[goog.html.SafeHtml :as SafeHtml]) + +(def safe-html (SafeHtml/create "p" nil "Safe content")) +(safe/setInnerHtml container safe-html) +``` + +--- + +## Summary: Choosing Between Libraries + +| Use Case | Recommendation | +|----------|---------------| +| Quick prototyping | jQuery - simplest API | +| Large-scale app | ClojureScript - functional, optimized | +| Legacy codebase | jQuery + migrate plugin | +| Functional programming | ClojureScript | +| Maximum optimization | ClojureScript (Closure Compiler) | +| React/modern framework | Consider native APIs or framework tools | + +### Migration Path + +If migrating from jQuery: +1. **To Google Closure**: Replace jQuery calls with `goog.dom`, `goog.events`, `goog.style` +2. **To ClojureScript**: Use `goog.dom` namespace or wrapper libraries like `dommy` + +### Key Takeaways + +- **jQuery** provides the most concise syntax for DOM manipulation +- **Google Closure** offers modular, optimizable code (but is sunset) +- **ClojureScript** combines functional programming with Closure Library benefits +- Modern browsers support many jQuery features natively (`querySelector`, `fetch`, `classList`) + +--- + +## Resources + +### jQuery +- [jquery.com](https://jquery.com) +- [api.jquery.com](https://api.jquery.com) + +### Google Closure Library +- [google.github.io/closure-library](https://google.github.io/closure-library) +- [Closure Cheatsheet](https://anton.shevchuk.name/wp-demo/closure-tutorials/cheatsheet.html) + +### ClojureScript +- [clojurescript.org](https://clojurescript.org) +- [ClojureScript Google Closure Reference](https://clojurescript.org/reference/google-closure-library) +- [Dommy Library](https://github.com/plumatic/dommy) +- [cljs-ajax](https://github.com/JulianBirch/cljs-ajax) + +--- + +*Comparison document for jQuery 4.0.0 (January 2026) vs Google Closure Library vs ClojureScript*