Comprehensive comparison document covering all jQuery 4.0.0 examples with equivalent code in Google Closure Library and ClojureScript. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2500 lines
53 KiB
Markdown
2500 lines
53 KiB
Markdown
# 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
|
|
<!-- CDN -->
|
|
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
|
|
```
|
|
|
|
```javascript
|
|
// ES Module
|
|
import $ from 'jquery';
|
|
|
|
// CommonJS
|
|
const $ = require('jquery');
|
|
```
|
|
|
|
### Google Closure Library
|
|
|
|
```html
|
|
<!-- Include base.js -->
|
|
<script src="closure-library/closure/goog/base.js"></script>
|
|
<script>
|
|
goog.require('goog.dom');
|
|
goog.require('goog.events');
|
|
</script>
|
|
```
|
|
|
|
```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 = $('<div class="new">Content</div>');
|
|
```
|
|
|
|
### 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('<p>New paragraph</p>');
|
|
|
|
// jQuery - Prepend
|
|
$('#container').prepend('<p>First paragraph</p>');
|
|
|
|
// 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('<strong>Bold text</strong>');
|
|
```
|
|
|
|
### 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 = '<strong>Bold text</strong>';
|
|
|
|
// 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) "<strong>Bold text</strong>")
|
|
|
|
;; With dommy
|
|
(dommy/text (sel1 :#element))
|
|
(dommy/set-text! (sel1 :#element) "New text")
|
|
(dommy/html (sel1 :#element))
|
|
(dommy/set-html! (sel1 :#element) "<strong>Bold</strong>")
|
|
```
|
|
|
|
---
|
|
|
|
### 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
|
|
// <div id="user" data-user-id="456" data-role="admin">
|
|
$('#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 [<!]])
|
|
(require-macros '[cljs.core.async.macros :refer [go]])
|
|
|
|
(go
|
|
(let [response (<! (http/get "/api/data"))]
|
|
(println (:body response))))
|
|
```
|
|
|
|
---
|
|
|
|
### jQuery: $.when() - Parallel Requests
|
|
|
|
```javascript
|
|
// jQuery
|
|
function fetchUser() {
|
|
return $.ajax('/api/user');
|
|
}
|
|
|
|
function fetchPosts() {
|
|
return $.ajax('/api/posts');
|
|
}
|
|
|
|
$.when(fetchUser(), fetchPosts())
|
|
.done(function(userData, postsData) {
|
|
console.log('User:', userData[0]);
|
|
console.log('Posts:', postsData[0]);
|
|
})
|
|
.fail(function() {
|
|
console.error('One or more requests failed');
|
|
});
|
|
```
|
|
|
|
### Google Closure
|
|
|
|
```javascript
|
|
// Google Closure - Using goog.Promise.all (or native Promise.all)
|
|
goog.require('goog.Promise');
|
|
goog.require('goog.net.XhrIo');
|
|
|
|
function fetchUser() {
|
|
return new goog.Promise(function(resolve, reject) {
|
|
goog.net.XhrIo.send('/api/user', function(e) {
|
|
if (e.target.isSuccess()) {
|
|
resolve(e.target.getResponseJson());
|
|
} else {
|
|
reject(e.target.getStatus());
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function fetchPosts() {
|
|
return new goog.Promise(function(resolve, reject) {
|
|
goog.net.XhrIo.send('/api/posts', function(e) {
|
|
if (e.target.isSuccess()) {
|
|
resolve(e.target.getResponseJson());
|
|
} else {
|
|
reject(e.target.getStatus());
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
goog.Promise.all([fetchUser(), fetchPosts()])
|
|
.then(function(results) {
|
|
console.log('User:', results[0]);
|
|
console.log('Posts:', results[1]);
|
|
})
|
|
.thenCatch(function(error) {
|
|
console.error('Failed:', error);
|
|
});
|
|
```
|
|
|
|
### ClojureScript
|
|
|
|
```clojure
|
|
;; ClojureScript with core.async
|
|
(require '[cljs-http.client :as http]
|
|
'[cljs.core.async :refer [<! go]])
|
|
|
|
(go
|
|
(let [user-chan (http/get "/api/user")
|
|
posts-chan (http/get "/api/posts")
|
|
user (<! user-chan)
|
|
posts (<! posts-chan)]
|
|
(println "User:" (:body user))
|
|
(println "Posts:" (:body posts))))
|
|
|
|
;; Or with promesa library
|
|
(require '[promesa.core :as p])
|
|
|
|
(-> (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
|
|
(<! (timeout 1000))
|
|
(if (> (rand) 0.5)
|
|
(>! c {:status :success :result "Operation succeeded"})
|
|
(>! c {:status :error :error "Operation failed"})))
|
|
c))
|
|
|
|
(go
|
|
(let [{:keys [status result error]} (<! (async-operation))]
|
|
(case status
|
|
:success (println result)
|
|
:error (println "Error:" error))
|
|
(println "Complete")))
|
|
|
|
;; With promesa library (Promise-based)
|
|
(require '[promesa.core :as p])
|
|
|
|
(defn async-operation []
|
|
(p/create
|
|
(fn [resolve reject]
|
|
(js/setTimeout
|
|
(fn []
|
|
(if (> (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"><script>alert("xss")</script>';
|
|
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('<p>Safe content</p>');
|
|
$('#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*
|