Last active
October 2, 2017 13:44
-
-
Save ahwayakchih/b8131fb788a0f05a7fd2e5b4ef68065d to your computer and use it in GitHub Desktop.
Drupal - add "add", "edit" and "remove" buttons next to the autocomplete inputs of entityreference fields.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- EXAMPLE of type information that will be used by "Add" buttons, if available: | |
<script type="application/json" id="entityreference-types"> | |
{ | |
"types": { | |
"blockquote": "Blockquote", | |
"custom-page": "Page", | |
"product": "Product", | |
"simple-picture": "Simple Picture" | |
}, | |
"fields": { | |
"field-404-background-image": [ | |
"simple-picture" | |
], | |
"field-background-image": [ | |
"simple-picture" | |
], | |
"field-button-link": [ | |
"custom-page", | |
"product" | |
], | |
"field-featured-strip": [ | |
"blockquote", | |
"custom-page", | |
"simple-picture" | |
], | |
"field-gallery": [ | |
"simple-picture" | |
] | |
} | |
} | |
</script> | |
--> | |
<script> | |
(function () { | |
// fieldTypes data must be added to HTML elsewhere. | |
// It should be in a `script` tag, with type `application/json` and `id` set to "entityreference-types". | |
// See example above. | |
const fieldTypes = (function(id) { | |
const data = document.querySelector('script[id="entityreference-types"][type="application/json"]'); | |
if (!data) { | |
return {}; | |
} | |
// Strip any comments that Drupal might have added | |
const source = data.textContent.replace(/^[^{]*/, '').replace(/[^}]*$/, ''); | |
try { | |
const info = JSON.parse(source); | |
return Object.keys(info.fields).reduce((result, field) => { | |
result[field.replace(/_/g, '-')] = info.fields[field].reduce((acc, type) => { | |
const label = info.types[type]; | |
if (label) { | |
acc[type.replace(/_/g, '-')] = label; | |
} | |
return acc; | |
}, {}); | |
return result; | |
}, {}); | |
} | |
catch (e) { | |
console.warn('Could not parse entityreference-types data', e); | |
return {}; | |
} | |
})('entityreference-types'); | |
const fieldObserver = new MutationObserver(function(mutations) { | |
mutations.forEach(function(mutation) { | |
if (mutation.addedNodes) { | |
initializeAutocompleteButtons(); | |
} | |
}); | |
}); | |
const fieldObserverConfig = { attributes: true, childList: true, characterData: true }; | |
const locale = (function() { | |
// Get region if available | |
// Then node/ID/edit | |
// Then language code, if available. Since Drupal may add some other stuff there, | |
// like 'add' or 'add/en/ar-SA', we have to be strict about what we accept as a language code, | |
// and select only from the last `/whatever` and only `2-letter(optional: -[2-letter])`. | |
const match = document.location.pathname.match(/(?:([^/]+)\/)?node\/\d+\/edit(?:[\w\W]*\/([^/]{2}(?:-[^/]{2})?)(?:\/|$))?/); | |
return { | |
region: (match && match[1]) || '', | |
lang: (match && match[2]) || '', | |
}; | |
})(); | |
function throttle (f, delay) { | |
var timeout = null; | |
return function () { | |
if (timeout) { | |
return; | |
} | |
timeout = setTimeout(() => { | |
timeout = null; | |
f(); | |
}, delay); | |
} | |
} | |
function onWindowEveryLoad (targetWindow, callback) { | |
let interval; | |
const onload = function () { | |
this.onpageshow = null; | |
this.onbeforeunload = onunload; | |
callback(this); | |
} | |
const onunload = function () { | |
if (!this.closed) { | |
watcher(this); | |
} | |
} | |
const watcher = function (w) { | |
const href = w.location.href; | |
if (interval) { | |
clearInterval(interval); | |
} | |
interval = setInterval(() => { | |
if (!w.closed && w.location.href === href) { | |
return; | |
} | |
if (w.closed) { | |
w = null; | |
} | |
else { | |
w.onpageshow = onload; | |
if (w.document && w.document.readyState !== 'loading') { | |
// Dispatch early to make sure our `onload` will be called, | |
// just in case we're too late with setting up `onpageshow`. | |
w.dispatchEvent(new Event('pageshow')); | |
} | |
} | |
clearInterval(interval); | |
interval = null; | |
}, 60); | |
} | |
targetWindow.onpageshow = onload; | |
} | |
function getSanitizedURL (url) { | |
return url && url.replace(/\/+/g, '/'); | |
} | |
function getNIDFromAutocompleteValue (value) { | |
const match = value && value.match(/\((\d+)\)\s*("|$)/); | |
return match && match[1]; | |
} | |
function getTypesReferencedByField (field) { | |
const fieldName = field.id.replace(/^edit-/, ''); | |
const types = fieldTypes[fieldName]; | |
if (!types) { | |
return []; | |
} | |
return Object.keys(types).map(type => ({ | |
name: type, | |
label: types[type], | |
})); | |
} | |
function updateAddButtonHrefWithValue (field, button, value) { | |
if (value || !field.classList.contains('can-add-new')) { | |
button.classList.add('disabled'); | |
} | |
else { | |
button.classList.remove('disabled'); | |
} | |
} | |
function createAddButton (field, input) { | |
const allowedTypes = getTypesReferencedByField(field); | |
if (!allowedTypes || allowedTypes.length < 1) { | |
return null; | |
} | |
const button = document.createElement('span'); | |
button.classList.add('form-edit-entityreference', 'form-submit', 'add-submit'); | |
button.appendChild(document.createTextNode('Add')); | |
if (allowedTypes.length > 0) { | |
const ul = document.createElement('ul'); | |
ul.classList.add('select-content-type'); | |
allowedTypes.forEach(type => { | |
const li = document.createElement('li'); | |
li.classList.add('option'); | |
const a = document.createElement('a'); | |
a.appendChild(document.createTextNode(type.label)); | |
a.classList.add('value'); | |
a.setAttribute('href', getSanitizedURL(`/${locale.region}/node/add/${type.name}/${locale.lang}`)); | |
a.setAttribute('target', '_blank'); | |
a.setAttribute('data-type', type.name); | |
li.appendChild(a); | |
ul.appendChild(li); | |
}); | |
field.classList.add('can-add-new'); | |
button.appendChild(ul); | |
} | |
button.onclick = function (event) { | |
event.preventDefault(); | |
event.stopPropagation(); | |
const a = event.target; | |
const href = a.getAttribute('href'); | |
const target = a.getAttribute('target') || '_blank'; | |
const type = a.getAttribute('data-type') || (href && href.replace(/^[\w\W]*\/node\/add\/([^/]+)[\w\W]*$/, '$1')); | |
if (!href) { | |
return; | |
} | |
onWindowEveryLoad(window.open(href, target), function (w) { | |
if (!w.document.body.classList.contains(`node-type-${type}`)) { | |
if (!w.document.body.classList.contains(`page-node-add-${type}`)) { | |
w.close(); | |
} | |
return; | |
} | |
const title = w.document.querySelector('h1.page-title'); | |
const links = w.document.querySelectorAll('#navigation .tabs.primary a'); | |
const nid = Array.from(links).reduce((acc, link) => { | |
const href = link.getAttribute('href'); | |
const m = href && href.match(/\/node\/(\d+)\/edit(\/|$)/); | |
return (m && m[1]) || acc; | |
}, ''); | |
if (title && nid) { | |
input.value = `${title.textContent} (${nid})`; | |
const e = new Event('change'); | |
input.dispatchEvent(e); | |
w.close(); | |
const addMore = field.querySelector('input.field-add-more-submit'); | |
if (addMore) { | |
addMore.dispatchEvent(new Event('mousedown')); | |
} | |
} | |
}); | |
}; | |
const updater = throttle(() => updateAddButtonHrefWithValue(field, button, input.value), 50); | |
input.addEventListener('change', event => updater()); | |
input.addEventListener('keyup', event => updater()); | |
updateAddButtonHrefWithValue(field, button, input.value); | |
return button; | |
} | |
function updateEditButtonHrefWithValue (field, button, value) { | |
const nid = getNIDFromAutocompleteValue(value); | |
if (nid) { | |
button.setAttribute('href', getSanitizedURL(`/${locale.region}/node/${nid}/edit/${locale.lang}`)); | |
button.classList.remove('disabled'); | |
} | |
else { | |
button.setAttribute('href', ''); | |
button.classList.add('disabled'); | |
} | |
} | |
function createEditButton (field, input) { | |
const button = document.createElement('a'); | |
button.classList.add('form-edit-entityreference', 'form-submit', 'edit-submit'); | |
button.setAttribute('href', ''); | |
button.setAttribute('target', '_blank'); | |
button.appendChild(document.createTextNode('Edit')); | |
button.onclick = function (event) { | |
const href = this.getAttribute('href'); | |
if (!href) { | |
event.preventDefault(); | |
event.stopPropagation(); | |
return; | |
} | |
}; | |
const updater = throttle(() => updateEditButtonHrefWithValue(field, button, input.value), 50); | |
input.addEventListener('change', event => updater()); | |
input.addEventListener('keyup', event => updater()); | |
updateEditButtonHrefWithValue(field, button, input.value); | |
return button; | |
} | |
function updateRemoveButtonHrefWithValue (field, button, value) { | |
const nid = getNIDFromAutocompleteValue(value); | |
if (nid) { | |
button.setAttribute('href', getSanitizedURL(`/${locale.region}/node/${nid}/translate/delete/${locale.lang}`)); | |
button.classList.remove('disabled'); | |
} | |
else { | |
button.setAttribute('href', ''); | |
button.classList.add('disabled'); | |
} | |
} | |
function createRemoveButton (field, input) { | |
const id = input.id; | |
const button = document.createElement('a'); | |
button.classList.add('form-edit-entityreference', 'form-submit', 'edit-delete'); | |
button.setAttribute('href', ''); | |
button.setAttribute('target', '_blank'); | |
button.appendChild(document.createTextNode('Remove')); | |
button.onclick = function (event) { | |
const href = this.getAttribute('href'); | |
event.preventDefault(); | |
event.stopPropagation(); | |
input.value = ''; | |
input.dispatchEvent(new Event('change')); | |
const tr = input.closest && input.closest('.field-multiple-table tr'); | |
if (tr) { | |
tr.style.display = 'none'; | |
} | |
}; | |
const updater = throttle(() => updateRemoveButtonHrefWithValue(field, button, input.value), 50); | |
input.addEventListener('change', event => updater()); | |
input.addEventListener('keyup', event => updater()); | |
updateRemoveButtonHrefWithValue(field, button, input.value); | |
return button; | |
} | |
function initializeFieldInputButtons (field, input) { | |
if (input.classList.contains('autocompleteButtonsReady')) { | |
return; | |
} | |
input.classList.add('autocompleteButtonsReady'); | |
const nextSibling = input.nextSibling; | |
const addButton = createAddButton(field, input); | |
if (addButton) { | |
input.parentNode.insertBefore(addButton, nextSibling); | |
} | |
input.parentNode.insertBefore(createEditButton(field, input), nextSibling); | |
input.parentNode.insertBefore(createRemoveButton(field, input), nextSibling); | |
} | |
function initializeFieldButtons (field) { | |
Array.from(field.querySelectorAll('input.form-autocomplete')) | |
.forEach(input => initializeFieldInputButtons(field, input)); | |
} | |
function initializeFieldDescriptionLinks (field) { | |
const description = field.querySelector('.form-item > .description'); | |
if (!description) { | |
return; | |
} | |
const links = description.querySelectorAll('a[href^="/node/add/"][target=_blank]'); | |
if (!links) { | |
return; | |
} | |
Array.from(links).forEach(link => { | |
link.setAttribute('href', getSanitizedURL(`/${locale.region}/${link.getAttribute('href')}`)); | |
}); | |
} | |
function initializeAutocompleteButtons () { | |
const references = document.querySelectorAll('form.node-form .field-type-entityreference'); | |
Array.from(references).forEach(field => { | |
fieldObserver.observe(field, fieldObserverConfig); | |
initializeFieldButtons(field); | |
initializeFieldDescriptionLinks(field); | |
}); | |
} | |
function initialize () { | |
initializeAutocompleteButtons(); | |
} | |
initialize(); | |
})(); | |
</script> | |
<style> | |
.field-type-entityreference .form-edit-entityreference.form-submit, | |
.field-type-entityreference .form-edit-entityreference.form-submit .select-content-type { | |
cursor: pointer; | |
border-radius: 0; | |
border: none; | |
padding: 5px 10px; | |
margin-left: 1em; | |
color: #fff; | |
text-align: center; | |
font-weight: normal; | |
font-size: 1.077em; | |
font-family: "Lucida Grande",Verdana,sans-serif; | |
background: #e3e3e3; | |
transition: all 0.3s; | |
text-decoration: none; | |
vertical-align: middle; | |
} | |
.field-type-entityreference .form-edit-entityreference.form-submit.add-submit { | |
display: none; | |
cursor: context-menu; | |
position: relative; | |
background: #0074bd; | |
} | |
.field-type-entityreference .form-edit-entityreference.form-submit.add-submit:hover { | |
z-index: 2; | |
} | |
.field-type-entityreference.can-add-new .form-edit-entityreference.form-submit.add-submit { | |
display: inline; | |
} | |
.field-type-entityreference.can-add-new .form-edit-entityreference.form-submit.add-submit.disabled { | |
display: none; | |
} | |
.field-type-entityreference .form-edit-entityreference.form-submit.add-submit:after { | |
display: inline-block; | |
content: '»'; | |
width: 1em; | |
margin-left: 0.3em; | |
vertical-align: text-bottom; | |
transform: rotate(0); | |
transition: all 0.3s; | |
} | |
.field-type-entityreference .form-edit-entityreference.form-submit.add-submit:hover:after { | |
transform: rotate(90deg); | |
} | |
.field-type-entityreference .form-edit-entityreference.form-submit.edit-submit { | |
background: #4d8f46; | |
} | |
.field-type-entityreference .form-edit-entityreference.form-submit.edit-delete { | |
background: #b73939; | |
} | |
.field-type-entityreference .form-edit-entityreference.form-submit.disabled { | |
cursor: default; | |
background: #e3e3e3; | |
opacity: 0.5; | |
} | |
.field-type-entityreference .form-edit-entityreference.form-submit.add-submit .select-content-type { | |
display: none; | |
position: absolute; | |
right: 0; | |
margin: 0; | |
padding: 0; | |
background: #0074BD; | |
text-align: left; | |
list-style: none; | |
} | |
.field-type-entityreference .form-edit-entityreference.form-submit.add-submit .select-content-type .value { | |
display: inline-block; | |
cursor: pointer; | |
width: 100%; | |
height: 100%; | |
padding: 5px 10px; | |
color: #fff; | |
white-space: nowrap; | |
} | |
.field-type-entityreference .form-edit-entityreference.form-submit.add-submit .select-content-type .option:nth-child(odd) .value { | |
background: rgba(255, 255, 255, 0.1); | |
} | |
.field-type-entityreference .form-edit-entityreference.form-submit.add-submit:hover .select-content-type { | |
display: block; | |
} | |
.field-type-entityreference .form-edit-entityreference.form-submit.add-submit.disabled:hover .select-content-type { | |
display: none; | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment