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 = $('
Content
'); +``` + +### Google Closure + +```javascript +// Google Closure +const newDiv = goog.dom.createDom('div', {'class': 'new'}, 'Content'); + +// Shorthand +const newDiv = goog.dom.$dom('div', {'class': 'new'}, 'Content'); + +// Complex nesting +const complex = goog.dom.createDom('div', {'class': 'wrapper'}, + goog.dom.createDom('h1', null, 'Title'), + goog.dom.createDom('p', null, 'Paragraph') +); +``` + +### ClojureScript + +```clojure +;; ClojureScript with goog.dom +(def new-div (gdom/createDom "div" #js {:class "new"} "Content")) + +;; With hiccup-style libraries (hipo, hiccups) +(require '[hipo.core :as hipo]) +(def new-div (hipo/create [:div.new "Content"])) + +;; Complex with hiccup +(def complex (hipo/create + [:div.wrapper + [:h1 "Title"] + [:p "Paragraph"]])) +``` + +--- + +## Utility Functions + +### jQuery: $.extend() - Object Merging + +```javascript +// jQuery - Shallow merge +const defaults = { color: 'blue', size: 'medium' }; +const options = { color: 'red' }; +const settings = $.extend({}, defaults, options); +// Result: { color: 'red', size: 'medium' } + +// jQuery - Deep merge +const obj1 = { a: { b: 1, c: 2 } }; +const obj2 = { a: { b: 3 } }; +const result = $.extend(true, {}, obj1, obj2); +// Result: { a: { b: 3, c: 2 } } +``` + +### Google Closure + +```javascript +// Google Closure - goog.object +goog.require('goog.object'); + +// Shallow merge (modifies target) +const settings = {}; +goog.object.extend(settings, defaults, options); + +// Clone then merge +const settings = goog.object.clone(defaults); +goog.object.extend(settings, options); + +// Note: No built-in deep merge - use goog.object.unsafeClone for deep copy +``` + +### ClojureScript + +```clojure +;; ClojureScript - native merge (immutable, shallow) +(def defaults {:color "blue" :size "medium"}) +(def options {:color "red"}) +(def settings (merge defaults options)) +;; => {:color "red" :size "medium"} + +;; Deep merge +(def obj1 {:a {:b 1 :c 2}}) +(def obj2 {:a {:b 3}}) +(def result (merge-with merge obj1 obj2)) +;; => {:a {:b 3 :c 2}} + +;; Or use a deep-merge library +(require '[clojure.walk :refer [postwalk]]) +``` + +--- + +### jQuery: $.each() - Iteration + +```javascript +// jQuery - Array iteration +$.each(['a', 'b', 'c'], function(index, value) { + console.log(`${index}: ${value}`); +}); + +// jQuery - Object iteration +$.each({ name: 'John', age: 30 }, function(key, value) { + console.log(`${key}: ${value}`); +}); + +// jQuery - Return false to break +$.each([1, 2, 3, 4, 5], function(i, num) { + if (num > 3) return false; + console.log(num); +}); +// Output: 1, 2, 3 +``` + +### Google Closure + +```javascript +// Google Closure - goog.array and goog.object +goog.require('goog.array'); +goog.require('goog.object'); + +// Array iteration +goog.array.forEach(['a', 'b', 'c'], function(value, index) { + console.log(index + ': ' + value); +}); + +// Object iteration +goog.object.forEach({ name: 'John', age: 30 }, function(value, key) { + console.log(key + ': ' + value); +}); + +// Break with goog.array.some (return true to stop) +goog.array.some([1, 2, 3, 4, 5], function(num) { + if (num > 3) return true; + console.log(num); + return false; +}); +``` + +### ClojureScript + +```clojure +;; ClojureScript - Array iteration +(doseq [[index value] (map-indexed vector ["a" "b" "c"])] + (println (str index ": " value))) + +;; Simpler +(run! println ["a" "b" "c"]) + +;; Object/map iteration +(doseq [[k v] {:name "John" :age 30}] + (println (str (name k) ": " v))) + +;; Early termination with reduced +(reduce (fn [_ num] + (if (> num 3) + (reduced nil) + (println num))) + nil [1 2 3 4 5]) +;; Output: 1, 2, 3 +``` + +--- + +### jQuery: $.map() - Transform Array + +```javascript +// jQuery +const numbers = [1, 2, 3, 4]; +const doubled = $.map(numbers, n => n * 2); +// Result: [2, 4, 6, 8] + +// Return multiple values +const expanded = $.map([1, 2], n => [n, n * 2]); +// Result: [1, 2, 2, 4] + +// Return null to remove +const filtered = $.map([1, 2, 3, 4], n => n > 2 ? n : null); +// Result: [3, 4] +``` + +### Google Closure + +```javascript +// Google Closure +goog.require('goog.array'); + +// Map +const doubled = goog.array.map([1, 2, 3, 4], function(n) { + return n * 2; +}); + +// Flat map (expand) +const expanded = goog.array.flatten( + goog.array.map([1, 2], function(n) { + return [n, n * 2]; + }) +); + +// Filter then map (no null removal in map) +const filtered = goog.array.filter([1, 2, 3, 4], function(n) { + return n > 2; +}); +``` + +### ClojureScript + +```clojure +;; ClojureScript - map +(def numbers [1 2 3 4]) +(def doubled (map #(* % 2) numbers)) +;; => (2 4 6 8) + +;; mapcat for expansion (flat map) +(def expanded (mapcat (fn [n] [n (* n 2)]) [1 2])) +;; => (1 2 2 4) + +;; filter + map or keep +(def filtered (filter #(> % 2) [1 2 3 4])) +;; => (3 4) + +;; Or use keep (removes nils) +(def filtered (keep #(when (> % 2) %) [1 2 3 4])) +``` + +--- + +### jQuery: $.grep() - Filter Array + +```javascript +// jQuery +const numbers = [1, 2, 3, 4, 5, 6]; + +// Keep elements where callback returns true +const evens = $.grep(numbers, n => n % 2 === 0); +// Result: [2, 4, 6] + +// With invert=true, keep elements where callback returns false +const odds = $.grep(numbers, n => n % 2 === 0, true); +// Result: [1, 3, 5] +``` + +### Google Closure + +```javascript +// Google Closure +goog.require('goog.array'); + +const numbers = [1, 2, 3, 4, 5, 6]; + +// Filter +const evens = goog.array.filter(numbers, function(n) { + return n % 2 === 0; +}); + +// Inverted (negate the condition) +const odds = goog.array.filter(numbers, function(n) { + return n % 2 !== 0; +}); +``` + +### ClojureScript + +```clojure +;; ClojureScript +(def numbers [1 2 3 4 5 6]) + +;; Filter +(def evens (filter even? numbers)) +;; => (2 4 6) + +;; Inverted with remove or complement +(def odds (remove even? numbers)) +;; => (1 3 5) + +;; Or +(def odds (filter odd? numbers)) +(def odds (filter (complement even?) numbers)) +``` + +--- + +### jQuery: $.merge() - Merge Arrays + +```javascript +// jQuery +const arr1 = [1, 2]; +const arr2 = [3, 4]; +$.merge(arr1, arr2); +// arr1 is now [1, 2, 3, 4] +``` + +### Google Closure + +```javascript +// Google Closure +goog.require('goog.array'); + +const arr1 = [1, 2]; +const arr2 = [3, 4]; + +// Extend modifies first array +goog.array.extend(arr1, arr2); +// arr1 is now [1, 2, 3, 4] + +// Or create new array +const merged = goog.array.concat(arr1, arr2); +``` + +### ClojureScript + +```clojure +;; ClojureScript - concat (immutable, creates new sequence) +(def arr1 [1 2]) +(def arr2 [3 4]) +(def merged (concat arr1 arr2)) +;; => (1 2 3 4) + +;; As vector +(def merged (into arr1 arr2)) +;; => [1 2 3 4] +``` + +--- + +### jQuery: $.inArray() - Find Index + +```javascript +// jQuery +const arr = ['a', 'b', 'c', 'd']; + +$.inArray('b', arr); // Returns: 1 +$.inArray('x', arr); // Returns: -1 +$.inArray('b', arr, 2); // Returns: -1 (starts search at index 2) +``` + +### Google Closure + +```javascript +// Google Closure +goog.require('goog.array'); + +const arr = ['a', 'b', 'c', 'd']; + +goog.array.indexOf(arr, 'b'); // Returns: 1 +goog.array.indexOf(arr, 'x'); // Returns: -1 +goog.array.indexOf(arr, 'b', 2); // Returns: -1 +``` + +### ClojureScript + +```clojure +;; ClojureScript +(def arr ["a" "b" "c" "d"]) + +;; Using .indexOf on vector +(.indexOf arr "b") ;; => 1 +(.indexOf arr "x") ;; => -1 + +;; Or idiomatic ClojureScript +(defn index-of [coll item] + (first (keep-indexed #(when (= %2 item) %1) coll))) + +(index-of arr "b") ;; => 1 +(index-of arr "x") ;; => nil +``` + +--- + +### jQuery: $.isPlainObject() / $.isEmptyObject() + +```javascript +// jQuery +$.isPlainObject({}); // true +$.isPlainObject({ a: 1 }); // true +$.isPlainObject([]); // false +$.isPlainObject(new Date()); // false + +$.isEmptyObject({}); // true +$.isEmptyObject({ a: 1 }); // false +``` + +### Google Closure + +```javascript +// Google Closure +goog.require('goog.object'); + +goog.isObject({}); // true (but also true for arrays!) + +// Check plain object more carefully +function isPlainObject(obj) { + return goog.isObject(obj) && + !goog.isArray(obj) && + Object.getPrototypeOf(obj) === Object.prototype; +} + +// Empty object check +goog.object.isEmpty({}); // true +goog.object.isEmpty({ a: 1 }); // false +``` + +### ClojureScript + +```clojure +;; ClojureScript +(map? {}) ;; true +(map? {:a 1}) ;; true +(map? []) ;; false + +;; Empty check +(empty? {}) ;; true +(empty? {:a 1}) ;; false + +;; For JS objects specifically +(object? #js {}) ;; true +(goog.object/isEmpty #js {}) ;; true +``` + +--- + +### Removed jQuery Functions (Use Native) + +```javascript +// jQuery 3.x (deprecated) -> Native JavaScript + +// $.isArray() -> Array.isArray() +if (Array.isArray(myVar)) { /* ... */ } + +// $.isFunction() -> typeof +if (typeof callback === 'function') { /* ... */ } + +// $.trim() -> String.prototype.trim() +var trimmed = userInput.trim(); + +// $.parseJSON() -> JSON.parse() +var data = JSON.parse(jsonString); + +// $.now() -> Date.now() +var timestamp = Date.now(); +``` + +### Google Closure Equivalents + +```javascript +// Google Closure +goog.isArray(myVar); +goog.isFunction(callback); +goog.string.trim(userInput); +// JSON.parse is native, or use goog.json.parse for older browsers +var timestamp = goog.now(); +``` + +### ClojureScript Equivalents + +```clojure +;; ClojureScript +(array? my-var) +(fn? callback) +(clojure.string/trim user-input) +(js/JSON.parse json-string) +(.now js/Date) +``` + +--- + +## DOM Manipulation + +### jQuery: DOM Ready + +```javascript +// jQuery - Shorthand (recommended) +$(function() { + console.log('DOM ready'); +}); + +// jQuery - Explicit ready +$(document).ready(function() { + console.log('DOM ready'); +}); + +// jQuery - Arrow function +$(() => { + console.log('DOM ready'); +}); +``` + +### Google Closure + +```javascript +// Google Closure +goog.require('goog.dom'); +goog.require('goog.events'); + +// Using DOMContentLoaded +goog.events.listen(document, 'DOMContentLoaded', function() { + console.log('DOM ready'); +}); + +// Or check if already ready +if (document.readyState === 'complete' || document.readyState === 'interactive') { + console.log('DOM already ready'); +} else { + goog.events.listen(document, 'DOMContentLoaded', function() { + console.log('DOM ready'); + }); +} +``` + +### ClojureScript + +```clojure +;; ClojureScript +(require '[goog.events :as gevents]) + +;; Using goog.events +(gevents/listen js/document "DOMContentLoaded" + (fn [_] (println "DOM ready"))) + +;; Or with native JS +(.addEventListener js/document "DOMContentLoaded" + (fn [_] (println "DOM ready"))) + +;; Check if already loaded +(if (or (= "complete" (.-readyState js/document)) + (= "interactive" (.-readyState js/document))) + (println "Already ready") + (gevents/listen js/document "DOMContentLoaded" + (fn [_] (println "DOM ready")))) +``` + +--- + +### jQuery: Append/Remove Elements + +```javascript +// jQuery - Append +$('#container').append('

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 +//
+$('#user').data('userId'); // 456 +$('#user').data('role'); // 'admin' + +// Remove data +$('#element').removeData('temp'); +``` + +### Google Closure + +```javascript +// Google Closure - Using dataset API (HTML5) +const element = goog.dom.getElement('element'); + +// Set data (via dataset) +element.dataset.userId = '123'; +element.dataset.config = JSON.stringify({ theme: 'dark', lang: 'en' }); + +// Get data +const userId = element.dataset.userId; +const config = JSON.parse(element.dataset.config); + +// Or use a WeakMap for complex data +const dataStore = new WeakMap(); + +function setData(element, key, value) { + let data = dataStore.get(element) || {}; + data[key] = value; + dataStore.set(element, data); +} + +function getData(element, key) { + const data = dataStore.get(element) || {}; + return key ? data[key] : data; +} +``` + +### ClojureScript + +```clojure +;; ClojureScript - Using dataset +(def element (gdom/getElement "element")) + +;; Set via dataset +(set! (.. element -dataset -userId) "123") + +;; Get +(.. element -dataset -userId) + +;; For complex data, use atoms or state management +(def data-store (atom {})) + +(defn set-data! [element key value] + (swap! data-store assoc-in [element key] value)) + +(defn get-data [element key] + (get-in @data-store [element key])) + +;; Or use a library like reagent for reactive state +``` + +--- + +## AJAX + +### jQuery: $.ajax() and Shortcuts + +```javascript +// jQuery - Full AJAX +$.ajax({ + url: '/api/data', + method: 'GET', + dataType: 'json' +}) +.done(function(data) { + console.log('Success:', data); +}) +.fail(function(jqXHR, textStatus, error) { + console.error('Error:', error); +}) +.always(function() { + console.log('Complete'); +}); + +// jQuery - Shorthand GET +$.get('/api/data', function(data) { + console.log(data); +}); + +// jQuery - Shorthand POST +$.post('/api/data', { name: 'John' }, function(response) { + console.log(response); +}); + +// jQuery - Get JSON +$.getJSON('/api/data.json', function(data) { + console.log(data); +}); + +// jQuery - Get Script +$.getScript('https://example.com/script.js') + .done(function() { + console.log('Script loaded'); + }); +``` + +### Google Closure + +```javascript +// Google Closure +goog.require('goog.net.XhrIo'); +goog.require('goog.events'); + +// GET request +goog.net.XhrIo.send('/api/data', function(e) { + const xhr = e.target; + if (xhr.isSuccess()) { + const data = xhr.getResponseJson(); + console.log('Success:', data); + } else { + console.error('Error:', xhr.getStatus()); + } + console.log('Complete'); +}); + +// POST request +goog.net.XhrIo.send('/api/data', function(e) { + const data = e.target.getResponseJson(); + console.log(data); +}, 'POST', 'name=John', {'Content-Type': 'application/x-www-form-urlencoded'}); + +// With instance for more control +const xhr = new goog.net.XhrIo(); +goog.events.listen(xhr, goog.net.EventType.COMPLETE, function(e) { + if (xhr.isSuccess()) { + console.log(xhr.getResponseJson()); + } +}); +goog.events.listen(xhr, goog.net.EventType.ERROR, function(e) { + console.error('Request failed'); +}); +xhr.send('/api/data', 'GET'); + +// JSON with headers +const headers = {'Content-Type': 'application/json'}; +goog.net.XhrIo.send('/api/data', callback, 'POST', + JSON.stringify({name: 'John'}), headers); +``` + +### ClojureScript + +```clojure +;; ClojureScript with goog.net.XhrIo +(require '[goog.net.XhrIo :as xhr]) + +;; GET request +(xhr/send "/api/data" + (fn [e] + (let [target (.-target e)] + (if (.isSuccess target) + (println "Success:" (.getResponseJson target)) + (println "Error:" (.getStatus target)))))) + +;; POST request +(xhr/send "/api/data" + (fn [e] (println (.getResponseJson (.-target e)))) + "POST" + (js/JSON.stringify #js {:name "John"}) + #js {"Content-Type" "application/json"}) + +;; With cljs-ajax library (recommended) +(require '[ajax.core :refer [GET POST]]) + +(GET "/api/data" + {:handler (fn [response] (println "Success:" response)) + :error-handler (fn [error] (println "Error:" error))}) + +(POST "/api/data" + {:params {:name "John"} + :format :json + :response-format :json + :handler (fn [response] (println response))}) + +;; With cljs-http and core.async +(require '[cljs-http.client :as http] + '[cljs.core.async :refer [ (p/all [(fetch-user) (fetch-posts)]) + (p/then (fn [[user posts]] + (println "User:" user) + (println "Posts:" posts))) + (p/catch (fn [error] + (println "Failed:" error)))) +``` + +--- + +## Deferred/Promises + +### jQuery: $.Deferred() + +```javascript +// jQuery +function asyncOperation() { + const deferred = $.Deferred(); + + setTimeout(function() { + const success = Math.random() > 0.5; + + if (success) { + deferred.resolve('Operation succeeded'); + } else { + deferred.reject('Operation failed'); + } + }, 1000); + + return deferred.promise(); +} + +asyncOperation() + .done(function(result) { + console.log(result); + }) + .fail(function(error) { + console.error(error); + }) + .always(function() { + console.log('Complete'); + }); +``` + +### Google Closure + +```javascript +// Google Closure - goog.Promise (or native Promise) +goog.require('goog.Promise'); + +function asyncOperation() { + return new goog.Promise(function(resolve, reject) { + setTimeout(function() { + const success = Math.random() > 0.5; + + if (success) { + resolve('Operation succeeded'); + } else { + reject('Operation failed'); + } + }, 1000); + }); +} + +asyncOperation() + .then(function(result) { + console.log(result); + }) + .thenCatch(function(error) { + console.error(error); + }) + .thenAlways(function() { + console.log('Complete'); + }); + +// Or with native Promise +function asyncOperation() { + return new Promise(function(resolve, reject) { + // ... same as above + }); +} +``` + +### ClojureScript + +```clojure +;; ClojureScript with core.async channels +(require '[cljs.core.async :refer [! chan go timeout]]) + +(defn async-operation [] + (let [c (chan)] + (go + ( (rand) 0.5) + (>! c {:status :success :result "Operation succeeded"}) + (>! c {:status :error :error "Operation failed"}))) + c)) + +(go + (let [{:keys [status result error]} ( (rand) 0.5) + (resolve "Operation succeeded") + (reject "Operation failed"))) + 1000)))) + +(-> (async-operation) + (p/then #(println %)) + (p/catch #(println "Error:" %)) + (p/finally #(println "Complete"))) +``` + +--- + +## Callbacks + +### jQuery: $.Callbacks() + +```javascript +// jQuery - Basic usage +const callbacks = $.Callbacks(); + +function fn1(value) { + console.log('fn1:', value); +} + +function fn2(value) { + console.log('fn2:', value); +} + +callbacks.add(fn1); +callbacks.add(fn2); +callbacks.fire('hello'); +// Output: +// fn1: hello +// fn2: hello + +// With flags +const onceCallbacks = $.Callbacks('once'); +onceCallbacks.add(fn1); +onceCallbacks.fire('first'); // fn1: first +onceCallbacks.fire('second'); // (nothing - already fired) + +// Memory flag +const memoryCallbacks = $.Callbacks('memory'); +memoryCallbacks.fire('done'); +memoryCallbacks.add(fn1); // Immediately called with 'done' +``` + +### Google Closure + +```javascript +// Google Closure - Custom implementation +goog.provide('app.Callbacks'); + +app.Callbacks = function(flags) { + this.list_ = []; + this.fired_ = false; + this.lastArgs_ = null; + this.once_ = flags && flags.indexOf('once') !== -1; + this.memory_ = flags && flags.indexOf('memory') !== -1; +}; + +app.Callbacks.prototype.add = function(fn) { + this.list_.push(fn); + if (this.memory_ && this.fired_) { + fn.apply(null, this.lastArgs_); + } +}; + +app.Callbacks.prototype.fire = function() { + if (this.once_ && this.fired_) return; + + this.fired_ = true; + this.lastArgs_ = arguments; + + for (var i = 0; i < this.list_.length; i++) { + this.list_[i].apply(null, arguments); + } +}; + +// Usage +var callbacks = new app.Callbacks(); +callbacks.add(fn1); +callbacks.fire('hello'); +``` + +### ClojureScript + +```clojure +;; ClojureScript - Using atoms +(defn create-callbacks [& {:keys [once memory]}] + (let [callbacks (atom []) + fired (atom false) + last-args (atom nil)] + {:add (fn [f] + (swap! callbacks conj f) + (when (and memory @fired) + (apply f @last-args))) + :fire (fn [& args] + (when-not (and once @fired) + (reset! fired true) + (reset! last-args args) + (doseq [f @callbacks] + (apply f args))))})) + +;; Usage +(def cbs (create-callbacks)) +((:add cbs) (fn [v] (println "fn1:" v))) +((:add cbs) (fn [v] (println "fn2:" v))) +((:fire cbs) "hello") + +;; With memory +(def mem-cbs (create-callbacks :memory true)) +((:fire mem-cbs) "done") +((:add mem-cbs) (fn [v] (println "Late callback:" v))) +;; Immediately prints: Late callback: done +``` + +--- + +## Animation + +### jQuery: fadeIn/fadeOut + +```javascript +// jQuery +$('.dynamic-content').fadeIn(); +$('.dynamic-content').fadeOut(); +$('.dynamic-content').fadeToggle(); + +// With duration +$('#element').fadeIn(400); +$('#element').fadeOut('slow'); + +// With callback +$('#element').fadeIn(400, function() { + console.log('Fade complete'); +}); +``` + +### Google Closure + +```javascript +// Google Closure +goog.require('goog.fx.dom'); +goog.require('goog.events'); + +const element = goog.dom.getElement('element'); + +// Fade in +const fadeIn = new goog.fx.dom.FadeIn(element, 400); +fadeIn.play(); + +// Fade out +const fadeOut = new goog.fx.dom.FadeOut(element, 400); +fadeOut.play(); + +// With callback +goog.events.listen(fadeIn, goog.fx.Transition.EventType.END, function() { + console.log('Fade complete'); +}); + +// Fade to specific opacity +const fade = new goog.fx.dom.Fade(element, 0, 0.5, 400); +fade.play(); +``` + +### ClojureScript + +```clojure +;; ClojureScript with goog.fx.dom +(require '[goog.fx.dom :as fx]) +(import '[goog.fx Transition]) + +(def element (gdom/getElement "element")) + +;; Fade in +(let [anim (fx/FadeIn. element 400)] + (.play anim)) + +;; Fade out +(let [anim (fx/FadeOut. element 400)] + (.play anim)) + +;; With callback +(let [anim (fx/FadeIn. element 400)] + (gevents/listen anim (.-END Transition.EventType) + (fn [_] (println "Fade complete"))) + (.play anim)) +``` + +--- + +### jQuery: slide animations + +```javascript +// jQuery +$('#element').slideDown(); +$('#element').slideUp(); +$('#element').slideToggle(); + +// With duration and callback +$('#element').slideDown(400, function() { + console.log('Slide complete'); +}); +``` + +### Google Closure + +```javascript +// Google Closure +goog.require('goog.fx.dom'); + +const element = goog.dom.getElement('element'); +const size = goog.style.getSize(element); + +// Slide down (from 0 height) +const slideDown = new goog.fx.dom.Resize( + element, + [size.width, 0], + [size.width, size.height], + 400 +); +slideDown.play(); + +// Slide up (to 0 height) +const slideUp = new goog.fx.dom.Resize( + element, + [size.width, size.height], + [size.width, 0], + 400 +); +slideUp.play(); +``` + +### ClojureScript + +```clojure +;; ClojureScript +(require '[goog.fx.dom :as fx] + '[goog.style :as gstyle]) + +(def element (gdom/getElement "element")) +(def size (gstyle/getSize element)) + +;; Slide down +(let [anim (fx/Resize. element + #js [(.-width size) 0] + #js [(.-width size) (.-height size)] + 400)] + (.play anim)) +``` + +--- + +### jQuery: Custom Animations + +```javascript +// jQuery - animate() +$('#element').animate({ + opacity: 0.5, + left: '+=50px', + height: 'toggle' +}, 1000, 'swing', function() { + console.log('Animation complete'); +}); +``` + +### Google Closure + +```javascript +// Google Closure - Combining animations +goog.require('goog.fx'); +goog.require('goog.fx.dom'); +goog.require('goog.fx.AnimationParallelQueue'); + +const element = goog.dom.getElement('element'); + +// Create parallel animations +const queue = new goog.fx.AnimationParallelQueue(); + +// Fade to 0.5 +queue.add(new goog.fx.dom.Fade(element, 1, 0.5, 1000)); + +// Move right +const pos = goog.style.getPosition(element); +queue.add(new goog.fx.dom.Slide( + element, + [pos.x, pos.y], + [pos.x + 50, pos.y], + 1000 +)); + +goog.events.listen(queue, goog.fx.Transition.EventType.END, function() { + console.log('Animation complete'); +}); + +queue.play(); +``` + +### ClojureScript + +```clojure +;; ClojureScript +(require '[goog.fx :as gfx] + '[goog.fx.dom :as fx]) +(import '[goog.fx AnimationParallelQueue Transition]) + +(def element (gdom/getElement "element")) +(def pos (gstyle/getPosition element)) + +(let [queue (AnimationParallelQueue.)] + (.add queue (fx/Fade. element 1 0.5 1000)) + (.add queue (fx/Slide. element + #js [(.-x pos) (.-y pos)] + #js [(+ 50 (.-x pos)) (.-y pos)] + 1000)) + (gevents/listen queue (.-END Transition.EventType) + (fn [_] (println "Animation complete"))) + (.play queue)) +``` + +--- + +## Security Features + +### jQuery: Prototype Pollution Prevention + +```javascript +// jQuery 4.0 blocks these attempts +$.extend(true, {}, JSON.parse('{"__proto__": {"polluted": true}}')); +$.extend(true, {}, { constructor: { prototype: { polluted: true } } }); + +// Object.prototype remains unpolluted +console.log({}.polluted); // undefined +``` + +### Google Closure + +```javascript +// Google Closure - goog.object doesn't have deep merge by default +// Use Object.freeze for protection +const safeDefaults = Object.freeze({ + setting1: 'value1' +}); + +// Manual safe merge +function safeMerge(target, source) { + for (const key in source) { + if (source.hasOwnProperty(key) && + key !== '__proto__' && + key !== 'constructor') { + target[key] = source[key]; + } + } + return target; +} +``` + +### ClojureScript + +```clojure +;; ClojureScript - Immutable data structures prevent this by design +;; Clojure maps don't have prototype chain issues + +(def defaults {:setting1 "value1"}) +(def user-input (js->clj (js/JSON.parse "{\"__proto__\": {\"polluted\": true}}"))) + +;; merge just treats __proto__ as a regular key +(def merged (merge defaults user-input)) +;; => {:setting1 "value1", "__proto__" {"polluted" true}} + +;; The JS Object.prototype is unaffected +``` + +--- + +### jQuery: XSS Mitigation + +```javascript +// jQuery - Escape selectors +const userInput = 'div">'; +const escaped = $.escapeSelector(userInput); +$(escaped); // Safe +``` + +### Google Closure + +```javascript +// Google Closure - String escaping +goog.require('goog.string'); +goog.require('goog.html.SafeHtml'); + +// HTML escaping +const escaped = goog.string.htmlEscape(userInput); + +// Safe HTML creation +const safeHtml = goog.html.SafeHtml.htmlEscape(userInput); +``` + +### ClojureScript + +```clojure +;; ClojureScript +(require '[goog.string :as gstring]) + +;; HTML escaping +(def escaped (gstring/htmlEscape user-input)) + +;; With hiccup libraries, escaping is automatic +;; (hipo/create [:div user-input]) - auto-escaped +``` + +--- + +### jQuery: Trusted Types Support + +```javascript +// jQuery 4.0 accepts TrustedHTML objects +const policy = trustedTypes.createPolicy('myPolicy', { + createHTML: (string) => DOMPurify.sanitize(string) +}); + +const trustedContent = policy.createHTML('

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*