Skip to content

Commit

Permalink
ajax powered links
Browse files Browse the repository at this point in the history
  • Loading branch information
blakewatson committed May 31, 2021
1 parent 9cb1062 commit b919b00
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 77 deletions.
10 changes: 5 additions & 5 deletions protected/composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions protected/templates/partials/timer.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@

<?php if (!$v_timer_start_new && count($v_projects)): ?>
<div class="form-group">
<p><a href="<?php echo $v_new_project_link; ?>">New Project</a></p>
<p><a href="<?php echo $v_new_project_link; ?>" data-ajax-link>New Project</a></p>
</div>
<?php endif; ?>

Expand Down Expand Up @@ -85,7 +85,7 @@

<?php if (!$v_timer_start_new && count($v_tasks)): ?>
<div class="form-group">
<p><a href="<?php echo $v_new_task_link; ?>">New Task</a></p>
<p><a href="<?php echo $v_new_task_link; ?>" data-ajax-link>New Task</a></p>
</div>
<?php endif; ?>

Expand All @@ -104,7 +104,7 @@ class="btn btn-success btn-spinner" type="submit" data-timer-submit
Start
</button>
<?php if ($v_timer_start_new): ?>
<a href="<?php echo $v_refresh_link; ?>" class="btn btn-link">Cancel</a>
<a href="<?php echo $v_refresh_link; ?>" class="btn btn-link" data-ajax-link>Cancel</a>
<?php endif; ?>
</div>
</form>
Expand Down
36 changes: 0 additions & 36 deletions public/assets/js/ajax-form.js

This file was deleted.

110 changes: 110 additions & 0 deletions public/assets/js/ajax.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import $ from 'cash-dom';
import qs from 'qs';

/**
* Attaches event listeners
*/
export function initAjax() {
initAjaxLinks();
}

/**
* Event handler for form submissions using fetch. It gets the response and does
* a body swap.
*
* @param {Event} event
*/
export async function ajaxFormSubmit(event) {
event.preventDefault();
const formEl = event.target;

// get the data from the form
let data = {};
for (const pair of new FormData(formEl)) {
data[pair[0]] = pair[1];
}

// convert to x-www-form-urlencoded
data = qs.stringify(data);
// get the url from the form's action attribute
const url = $(formEl).attr('action');

// set request headers
const myHeaders = new Headers();
myHeaders.append('Content-Type', 'application/x-www-form-urlencoded');

// set config object for fetch
const config = {
method: 'POST',
headers: myHeaders,
body: data
};

// do a body swap
await bodySwapWithUrl(url, config);
// re-bind the link events
initClickEvent()
}

/**
* Does an initial binding for ajax links. Adds an event listener to handle the
* back button.
*/
function initAjaxLinks() {
initClickEvent();

$(window).on('popstate', async (event) => {
const url = event.target.location;
await bodySwapWithUrl(url);
// re-bind ajax links
initClickEvent();
});
}

/**
* Attaches event listeners for all links with the `data-ajax-link` attribute.
*/
function initClickEvent() {
// handle link clicks
const $links = $('[data-ajax-link]').one('click', clickHandler);
}

/**
* Click handler for ajax-enabled links. Does a fetch on the link's `href` URL
* and does a body swap with the response.
*
* @param {Event} event
*/
async function clickHandler(event) {
event.preventDefault();
const url = $(event.target).attr('href');
await bodySwapWithUrl(url);

// push history
window.history.pushState({}, $('title').text(), url);

// re-bind the click handler for ajax links
initClickEvent();
}

/**
* Uses fetch to make a request and swaps the body with the response text.
*
* @param {string} url
* @param {Object} config
*/
async function bodySwapWithUrl(url, config = undefined) {
try {
// request for the url
const resp = await fetch(url, config);
// get text of response
const text = await resp.text();
// body swap
$('html').html(text);

// emit an event to signal body swap complete
$(window).trigger('postbodyswap');
} catch (err) {
console.error(err);
}
}
2 changes: 2 additions & 0 deletions public/assets/js/app.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import flatpickr from "flatpickr";
import { initTimer } from './timer';
import { initAjax } from "./ajax";

initTimer();
initAjax();

flatpickr("#rd", {
mode: "range",
Expand Down
75 changes: 42 additions & 33 deletions public/assets/js/timer.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import $ from 'cash-dom';
import { ajaxFormSubmit } from './ajax-form';
import { ajaxFormSubmit } from './ajax';

const FormTypes = {
START_FORM: 'START_FORM',
Expand All @@ -11,8 +11,44 @@ const FormTypes = {
let formType = FormTypes.START_FORM;

/**
* Hijacks the submit event. After submitting the form and performing a body
* swap, re-init the timer controller.
* Connects to the timer form and sets up an event handler to reconnect after
* a body swap. Should be called once.
*/
export function initTimer() {
initTimerForm();
// re-bind to the timer form after body swaps
$(window).on('postbodyswap', () => initTimerForm());
}

/**
* Attaches to the Timer form and makes it AJAX-enabled.
*/
function initTimerForm() {
const $timerForm = $('[data-timer-form]');

// handle form submission
handleSubmission($timerForm);

// set form type
setFormType($timerForm);

// initialize form state
const state = new FormData($timerForm[0]);

// initial validation
validate(state);

// sync form state
state.forEach((value, key) => {
$(`[name=${key}]`).on('input', (event) => {
state.set(key, event.target.value);
validate(state);
});
});
}

/**
* Hijacks the submit event. Submits the form and performs a body swap.
*
* @param {collection} $timerForm the form selected with $
*/
Expand All @@ -22,8 +58,8 @@ function handleSubmission($timerForm) {
// disable button and show loading indicator
$('[data-timer-submit').attr('disabled', 'disabled');
$('[data-timer-submit-spinner]').removeClass('d-none');
// submit the form and reconnect using an updated reference
ajaxFormSubmit(event).then(() => initTimer());
// submit the form
ajaxFormSubmit(event);
});
}

Expand All @@ -44,7 +80,7 @@ function setFormType() {
}

/**
* Responsible for disabling the Start button if the form is incomplete.
* Responsible for disabling the submit button if the form is incomplete.
*
* @param {FormData} state
*/
Expand All @@ -62,30 +98,3 @@ function validate(state) {
$('[data-timer-submit]').attr('disabled', null);
}
}

/**
* Attaches to the Timer form and makes it AJAX-enabled.
*/
export function initTimer() {
const $timerForm = $('[data-timer-form]');

// handle form submission
handleSubmission($timerForm);

// set form type
setFormType($timerForm);

// initialize form state
const state = new FormData($timerForm[0]);

// initial validation
validate(state);

// sync form state
state.forEach((value, key) => {
$(`[name=${key}]`).on('input', (event) => {
state.set(key, event.target.value);
validate(state);
});
});
}

0 comments on commit b919b00

Please sign in to comment.