JavaScript Integration: Building Dynamic Interfaces
Setting Up JavaScript for API Access
Enqueuing Scripts with API Data
function myplugin_enqueue_scripts() {
wp_enqueue_script(
'myplugin-app',
plugins_url('js/app.js', __FILE__),
['wp-api-fetch'], // Use WordPress's built-in fetch wrapper
'1.0.0',
true
);
wp_localize_script('myplugin-app', 'myPluginApi', [
'root' => esc_url_raw(rest_url()),
'nonce' => wp_create_nonce('wp_rest'),
'userId' => get_current_user_id()
]);
}
add_action('wp_enqueue_scripts', 'myplugin_enqueue_scripts');Using wp.apiFetch
WordPress includes wp-api-fetch for authenticated requests:
// If wp-api-fetch is available
wp.apiFetch({ path: '/wp/v2/posts' }).then(posts => {
console.log(posts);
});
// With parameters
wp.apiFetch({
path: '/wp/v2/posts',
method: 'POST',
data: {
title: 'New Post',
content: 'Content here',
status: 'publish'
}
}).then(post => {
console.log('Created:', post);
});Building a Task Manager UI
HTML Structure
<div id="task-app">
<div class="task-header">
<h2>My Tasks</h2>
<button id="add-task-btn" class="button button-primary">Add Task</button>
</div>
<div class="task-filters">
<button data-filter="all" class="active">All</button>
<button data-filter="pending">Pending</button>
<button data-filter="completed">Completed</button>
</div>
<div id="task-list" class="task-list">
<!-- Tasks loaded dynamically -->
</div>
<div id="task-modal" class="modal" style="display:none;">
<div class="modal-content">
<h3>Add Task</h3>
<input type="text" id="task-title" placeholder="Task title">
<button id="save-task">Save</button>
<button id="cancel-task">Cancel</button>
</div>
</div>
</div>JavaScript Application
(function() {
'use strict';
const API = {
baseUrl: myPluginApi.root + 'taskmanager/v1',
async request(endpoint, options = {}) {
const response = await fetch(this.baseUrl + endpoint, {
...options,
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': myPluginApi.nonce,
...options.headers
}
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || 'API request failed');
}
return response.json();
},
getTasks(filter = '') {
const params = filter ? `?status=${filter}` : '';
return this.request(`/tasks${params}`);
},
createTask(title) {
return this.request('/tasks', {
method: 'POST',
body: JSON.stringify({ title })
});
},
updateTask(id, data) {
return this.request(`/tasks/${id}`, {
method: 'PUT',
body: JSON.stringify(data)
});
},
deleteTask(id) {
return this.request(`/tasks/${id}`, {
method: 'DELETE'
});
}
};
const TaskApp = {
currentFilter: 'all',
init() {
this.bindEvents();
this.loadTasks();
},
bindEvents() {
// Add task button
document.getElementById('add-task-btn').addEventListener('click', () => {
this.showModal();
});
// Filter buttons
document.querySelectorAll('.task-filters button').forEach(btn => {
btn.addEventListener('click', (e) => {
this.setFilter(e.target.dataset.filter);
});
});
// Save task
document.getElementById('save-task').addEventListener('click', () => {
this.saveTask();
});
// Cancel
document.getElementById('cancel-task').addEventListener('click', () => {
this.hideModal();
});
// Task list delegation
document.getElementById('task-list').addEventListener('click', (e) => {
const taskEl = e.target.closest('.task-item');
if (!taskEl) return;
const taskId = taskEl.dataset.id;
if (e.target.matches('.complete-btn')) {
this.toggleComplete(taskId);
} else if (e.target.matches('.delete-btn')) {
this.deleteTask(taskId);
}
});
},
async loadTasks() {
const taskList = document.getElementById('task-list');
taskList.innerHTML = '<div class="loading">Loading...</div>';
try {
const filter = this.currentFilter === 'all' ? '' : this.currentFilter;
const tasks = await API.getTasks(filter);
this.renderTasks(tasks);
} catch (error) {
taskList.innerHTML = `<div class="error">${error.message}</div>`;
}
},
renderTasks(tasks) {
const taskList = document.getElementById('task-list');
if (tasks.length === 0) {
taskList.innerHTML = '<div class="empty">No tasks found</div>';
return;
}
taskList.innerHTML = tasks.map(task => `
<div class="task-item ${task.status}" data-id="${task.id}">
<span class="task-title">${this.escapeHtml(task.title)}</span>
<div class="task-actions">
<button class="complete-btn">
${task.status === 'completed' ? 'โฉ๏ธ Undo' : 'โ
Complete'}
</button>
<button class="delete-btn">๐๏ธ Delete</button>
</div>
</div>
`).join('');
},
async saveTask() {
const input = document.getElementById('task-title');
const title = input.value.trim();
if (!title) {
alert('Please enter a task title');
return;
}
try {
await API.createTask(title);
input.value = '';
this.hideModal();
this.loadTasks();
} catch (error) {
alert('Failed to create task: ' + error.message);
}
},
async toggleComplete(id) {
const taskEl = document.querySelector(`[data-id="${id}"]`);
const isCompleted = taskEl.classList.contains('completed');
try {
await API.updateTask(id, {
status: isCompleted ? 'pending' : 'completed'
});
this.loadTasks();
} catch (error) {
alert('Failed to update task: ' + error.message);
}
},
async deleteTask(id) {
if (!confirm('Delete this task?')) return;
try {
await API.deleteTask(id);
this.loadTasks();
} catch (error) {
alert('Failed to delete task: ' + error.message);
}
},
setFilter(filter) {
this.currentFilter = filter;
document.querySelectorAll('.task-filters button').forEach(btn => {
btn.classList.toggle('active', btn.dataset.filter === filter);
});
this.loadTasks();
},
showModal() {
document.getElementById('task-modal').style.display = 'flex';
document.getElementById('task-title').focus();
},
hideModal() {
document.getElementById('task-modal').style.display = 'none';
},
escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
};
document.addEventListener('DOMContentLoaded', () => TaskApp.init());
})();CSS Styling
.task-app {
max-width: 600px;
margin: 2rem auto;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
.task-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.task-filters {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
.task-filters button {
padding: 0.5rem 1rem;
border: 1px solid #ddd;
background: #fff;
cursor: pointer;
border-radius: 4px;
}
.task-filters button.active {
background: #0073aa;
color: #fff;
border-color: #0073aa;
}
.task-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
border: 1px solid #ddd;
margin-bottom: 0.5rem;
border-radius: 4px;
background: #fff;
}
.task-item.completed .task-title {
text-decoration: line-through;
opacity: 0.6;
}
.modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
background: #fff;
padding: 2rem;
border-radius: 8px;
min-width: 300px;
}
Tip: For complex applications, consider using React, Vue, or Alpine.js for better state management.
Next Steps
In the next lesson, we'll optimize API performance with caching, rate limiting, and best practices.
๐ฏ Lesson Complete! You can now build interactive JavaScript interfaces powered by the REST API.