or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

forms.mdhtml-safety.mdindex.mdjavascript.md

javascript.mddocs/

0

# JavaScript Enhancement

1

2

Phoenix.HTML includes a lightweight JavaScript library that provides progressive enhancement for links and forms through data attributes. The library supports confirmation dialogs, HTTP method spoofing, and custom event handling while maintaining compatibility with older browsers.

3

4

## Capabilities

5

6

### Data Attribute Support

7

8

The JavaScript library automatically handles HTML data attributes to provide enhanced functionality without requiring JavaScript code changes.

9

10

#### Confirmation Dialogs

11

12

Automatically show confirmation dialogs for destructive actions using the `data-confirm` attribute.

13

14

```javascript { .api }

15

// Automatic handling - no JavaScript code required

16

// Add data-confirm attribute to any clickable element

17

```

18

19

**HTML Usage Examples:**

20

21

```html

22

<!-- Basic confirmation -->

23

<a href="/users/123" data-method="delete" data-confirm="Are you sure?">Delete User</a>

24

25

<!-- Form submission with confirmation -->

26

<button type="submit" data-confirm="This will permanently delete all data. Continue?">

27

Delete All Data

28

</button>

29

30

<!-- Link with confirmation -->

31

<a href="/logout" data-confirm="Are you sure you want to log out?">Logout</a>

32

```

33

34

**Behavior:**

35

- Shows browser's native `confirm()` dialog with the specified message

36

- Prevents default action if user clicks "Cancel"

37

- Allows action to proceed if user clicks "OK"

38

- Works with any clickable element (links, buttons, form controls)

39

40

#### HTTP Method Spoofing

41

42

Convert links into HTTP requests with methods other than GET using `data-method` attribute.

43

44

```javascript { .api }

45

// Required attributes for method spoofing:

46

// data-method: "patch|post|put|delete" - HTTP method to use

47

// data-to: "url" - Target URL for the request

48

// data-csrf: "token" - CSRF token for security

49

```

50

51

**HTML Usage Examples:**

52

53

```html

54

<!-- DELETE request via link -->

55

<a href="#"

56

data-method="delete"

57

data-to="/users/123"

58

data-csrf="<%= csrf_token %>">

59

Delete User

60

</a>

61

62

<!-- PUT request for state changes -->

63

<a href="#"

64

data-method="put"

65

data-to="/posts/456/publish"

66

data-csrf="<%= csrf_token %>">

67

Publish Post

68

</a>

69

70

<!-- PATCH request with confirmation -->

71

<a href="#"

72

data-method="patch"

73

data-to="/users/123/activate"

74

data-csrf="<%= csrf_token %>"

75

data-confirm="Activate this user account?">

76

Activate User

77

</a>

78

79

<!-- With target specification -->

80

<a href="#"

81

data-method="post"

82

data-to="/reports/generate"

83

data-csrf="<%= csrf_token %>"

84

target="_blank">

85

Generate Report

86

</a>

87

```

88

89

**Generated Form Structure:**

90

91

```html

92

<!-- The library creates a hidden form like this: -->

93

<form method="post" action="/users/123" style="display: none;" target="_blank">

94

<input type="hidden" name="_method" value="delete">

95

<input type="hidden" name="_csrf_token" value="token_value">

96

<input type="submit">

97

</form>

98

```

99

100

**Behavior:**

101

- Creates a hidden form with the specified method and CSRF token

102

- Submits the form programmatically using a button click (not `form.submit()`)

103

- Respects `target` attribute or opens in new window with modifier keys (Cmd/Ctrl/Shift)

104

- Uses POST as the actual HTTP method, with `_method` parameter for method override

105

- GET requests use actual GET method without CSRF token

106

107

### Custom Event Handling

108

109

The library dispatches custom events that allow for additional behavior customization and integration with other JavaScript code.

110

111

#### phoenix.link.click Event

112

113

Custom event fired before processing `data-method` and `data-confirm` attributes, allowing for custom behavior modification.

114

115

```javascript { .api }

116

// Event: phoenix.link.click

117

// Type: CustomEvent with bubbles: true, cancelable: true

118

// Target: The clicked element

119

// Timing: Fired before data-method and data-confirm processing

120

121

window.addEventListener('phoenix.link.click', function(e) {

122

// e.target - the clicked element

123

// e.preventDefault() - prevent default Phoenix.HTML handling

124

// e.stopPropagation() - prevent data-confirm processing

125

// return false - disable data-method processing

126

});

127

```

128

129

**Custom Event Examples:**

130

131

```javascript

132

// Add custom prompt behavior

133

window.addEventListener('phoenix.link.click', function(e) {

134

var message = e.target.getAttribute("data-prompt");

135

var answer = e.target.getAttribute("data-prompt-answer");

136

137

if (message && answer && (answer !== window.prompt(message))) {

138

e.preventDefault(); // Cancel the action

139

}

140

});

141

142

// Add analytics tracking

143

window.addEventListener('phoenix.link.click', function(e) {

144

var action = e.target.getAttribute("data-method");

145

var url = e.target.getAttribute("data-to");

146

147

if (action && url) {

148

analytics.track('method_link_click', {

149

method: action,

150

url: url,

151

element: e.target.tagName.toLowerCase()

152

});

153

}

154

});

155

156

// Custom loading states

157

window.addEventListener('phoenix.link.click', function(e) {

158

var target = e.target;

159

160

if (target.getAttribute('data-method')) {

161

target.classList.add('loading');

162

target.setAttribute('disabled', 'disabled');

163

164

// Note: Phoenix.HTML will proceed with form submission

165

// You may need additional handling for cleanup

166

}

167

});

168

169

// Disable data-method for specific conditions

170

window.addEventListener('phoenix.link.click', function(e) {

171

var target = e.target;

172

173

if (target.classList.contains('disabled') || target.hasAttribute('disabled')) {

174

return false; // Disables data-method processing

175

}

176

});

177

```

178

179

### Browser Compatibility

180

181

The library includes compatibility features for older browsers, particularly Internet Explorer 9 and earlier.

182

183

#### CustomEvent Polyfill

184

185

Provides CustomEvent support for browsers that don't have native implementation.

186

187

```javascript { .api }

188

// Internal polyfill - automatically used when needed

189

// Creates CustomEvent constructor for IE<=9

190

function CustomEvent(event, params) {

191

params = params || {bubbles: false, cancelable: false, detail: undefined};

192

var evt = document.createEvent('CustomEvent');

193

evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);

194

return evt;

195

}

196

```

197

198

**Supported Browsers:**

199

- Modern browsers: Native CustomEvent

200

- Internet Explorer 9+: Polyfilled CustomEvent

201

- Internet Explorer 8 and below: Not supported

202

203

### Integration Examples

204

205

#### Complete Phoenix Template Integration

206

207

```elixir

208

# In your Phoenix template (.heex file)

209

<div class="user-actions">

210

<%= link "Edit", to: Routes.user_path(@conn, :edit, @user) %>

211

212

<%= link "Delete",

213

to: "#",

214

"data-method": "delete",

215

"data-to": Routes.user_path(@conn, :delete, @user),

216

"data-csrf": Plug.CSRFProtection.get_csrf_token(),

217

"data-confirm": "Are you sure you want to delete #{@user.name}?",

218

class: "btn btn-danger" %>

219

220

<%= link "Archive",

221

to: "#",

222

"data-method": "patch",

223

"data-to": Routes.user_path(@conn, :update, @user, action: :archive),

224

"data-csrf": Plug.CSRFProtection.get_csrf_token(),

225

"data-confirm": "Archive this user?",

226

class: "btn btn-warning" %>

227

</div>

228

229

<!-- Include the Phoenix HTML JavaScript -->

230

<script src="<%= Routes.static_path(@conn, "/js/phoenix_html.js") %>"></script>

231

```

232

233

#### Advanced Custom Handling

234

235

```javascript

236

// Comprehensive custom event handler

237

document.addEventListener('DOMContentLoaded', function() {

238

// Track all Phoenix.HTML link interactions

239

window.addEventListener('phoenix.link.click', function(e) {

240

var target = e.target;

241

var method = target.getAttribute('data-method');

242

var url = target.getAttribute('data-to');

243

var confirm = target.getAttribute('data-confirm');

244

245

// Custom analytics

246

if (method && url) {

247

analytics.track('phoenix_link_action', {

248

method: method,

249

path: new URL(url, window.location.origin).pathname,

250

confirmed: !confirm || window.confirm ? true : false

251

});

252

}

253

254

// Custom loading states

255

if (method && !target.classList.contains('no-loading')) {

256

target.classList.add('loading');

257

target.innerHTML = '<span class="spinner"></span> ' + target.innerHTML;

258

}

259

260

// Custom confirmation for sensitive operations

261

if (target.classList.contains('super-dangerous')) {

262

var userInput = window.prompt(

263

'Type "DELETE" to confirm this irreversible action:'

264

);

265

266

if (userInput !== 'DELETE') {

267

e.preventDefault();

268

return false;

269

}

270

}

271

});

272

273

// Handle custom data attributes

274

document.addEventListener('click', function(e) {

275

var target = e.target;

276

277

// Custom data-disable-for attribute

278

var disableFor = target.getAttribute('data-disable-for');

279

if (disableFor) {

280

target.disabled = true;

281

setTimeout(function() {

282

target.disabled = false;

283

}, parseInt(disableFor, 10));

284

}

285

});

286

});

287

```

288

289

#### Phoenix LiveView Integration

290

291

```javascript

292

// Integration with Phoenix LiveView

293

document.addEventListener('DOMContentLoaded', function() {

294

// Re-initialize Phoenix.HTML behavior after LiveView updates

295

document.addEventListener('phx:update', function() {

296

// Phoenix.HTML automatically handles new elements

297

// No additional initialization needed

298

});

299

300

// Custom handling for LiveView-specific patterns

301

window.addEventListener('phoenix.link.click', function(e) {

302

var target = e.target;

303

304

// Don't interfere with LiveView elements

305

if (target.hasAttribute('phx-click') || target.closest('[phx-click]')) {

306

// Let LiveView handle the event

307

return;

308

}

309

310

// Standard Phoenix.HTML processing continues...

311

});

312

});

313

```

314

315

## Installation and Setup

316

317

### Manual Installation

318

319

```html

320

<!-- Include in your layout template -->

321

<script src="/js/phoenix_html.js"></script>

322

323

<!-- Or from CDN (replace VERSION) -->

324

<script src="https://unpkg.com/phoenix_html@VERSION/priv/static/phoenix_html.js"></script>

325

```

326

327

### Build Tool Integration

328

329

```javascript

330

// With webpack, vite, or similar

331

import "phoenix_html"

332

333

// Or CommonJS

334

require("phoenix_html")

335

336

// Or ES6 import (if configured)

337

import 'phoenix_html/priv/static/phoenix_html.js';

338

```

339

340

### Content Security Policy (CSP)

341

342

The library uses `eval()` equivalent operations for form creation. Update CSP headers if needed:

343

344

```

345

Content-Security-Policy: script-src 'self' 'unsafe-eval';

346

```

347

348

Or more specifically:

349

```

350

Content-Security-Policy: script-src 'self' 'wasm-unsafe-eval';

351

```

352

353

## Security Considerations

354

355

### CSRF Protection

356

357

Always include CSRF tokens with `data-method` requests:

358

359

```elixir

360

# In Phoenix templates

361

"data-csrf": Plug.CSRFProtection.get_csrf_token()

362

363

# In Phoenix controllers

364

csrf_token = Plug.CSRFProtection.get_csrf_token()

365

```

366

367

### XSS Prevention

368

369

The library doesn't modify user content but relies on Phoenix.HTML's server-side escaping:

370

371

```elixir

372

# Safe - content is escaped by Phoenix.HTML

373

<%= link "Delete #{@user.name}",

374

to: "#",

375

"data-confirm": "Delete #{@user.name}?",

376

"data-method": "delete" %>

377

378

# Dangerous - if @user.name contains HTML

379

# Use html_escape/1 or ensure data is pre-escaped

380

```

381

382

### URL Validation

383

384

Always validate `data-to` URLs on the server side:

385

386

```elixir

387

# Good - relative URLs

388

"data-to": Routes.user_path(@conn, :delete, @user)

389

390

# Dangerous - user-controlled URLs

391

"data-to": @user_provided_url # Could be malicious

392

```

393

394

## Troubleshooting

395

396

### Common Issues

397

398

```javascript

399

// Issue: Events not firing

400

// Solution: Ensure library is loaded before DOM ready

401

402

// Issue: CSRF token errors

403

// Solution: Verify token is current and matches server expectations

404

405

// Issue: Method not working

406

// Solution: Check that all required attributes are present

407

// - data-method

408

// - data-to

409

// - data-csrf (for non-GET requests)

410

411

// Issue: Confirmation not showing

412

// Solution: Check for JavaScript errors and event propagation stopping

413

414

// Issue: Form submission not working

415

// Solution: Verify server accepts _method parameter for method override

416

```

417

418

### Debug Mode

419

420

```javascript

421

// Add debug logging

422

window.addEventListener('phoenix.link.click', function(e) {

423

console.log('Phoenix.HTML link clicked:', {

424

target: e.target,

425

method: e.target.getAttribute('data-method'),

426

to: e.target.getAttribute('data-to'),

427

confirm: e.target.getAttribute('data-confirm'),

428

csrf: e.target.getAttribute('data-csrf')

429

});

430

});

431

```

432

433

## Best Practices

434

435

1. **CSRF Tokens**: Always include valid CSRF tokens for non-GET requests

436

2. **URL Safety**: Use server-generated URLs, never user input directly

437

3. **Confirmation Messages**: Use clear, specific confirmation text

438

4. **Loading States**: Add visual feedback for method requests

439

5. **Graceful Degradation**: Ensure functionality works without JavaScript when possible

440

6. **Event Cleanup**: Remove event listeners when appropriate for SPA-style applications

441

7. **Testing**: Test with JavaScript disabled to ensure core functionality remains accessible