I'm trying to write a custom handler for the link input value. In case the user inputs a link that does not have a custom protocol, I wish to prepend a http: before the input value. That's because if link value lacks http:, link is not interpreted and about:blank is shown intead. (https://github.com/quilljs/quill/issues/1268#issuecomment-272959998)
Here's what I've written (similar to the official example here):
toolbar.addHandler("link", function sanitizeLinkInput(linkValueInput) {
console.log(linkValueInput); // debugging
if (linkValueInput === "")
this.quill.format(false);
// do nothing, since it implies user has just clicked the icon
// for link, hasn't entered url yet
else if (linkValueInput == true);
// do nothing, since this implies user's already using a custom protocol
else if (/^\w+:/.test(linkValueInput));
else if (!/^https?:/.test(linkValueInput)) {
linkValueInput = "http:" + linkValueInput;
this.quill.format("link", linkValueInput);
}
});
Every time the user clicks the link icon, nothing happens and true is logged to the console. I actually wished this handler to be executed when person clicks "save" on the tooltip that's shown after pressing the link icon.
Any idea how to do this? Hints or suggestions are also appreciated.
Thanks!
congregating all the information
The snow theme itself uses the toolbar's addHandler to show a tooltip and so it is impossible to use the addHandler method to achieve what we wish to.
So, instead we can do the following:
var Link = Quill.import('formats/link');
var builtInFunc = Link.sanitize;
Link.sanitize = function customSanitizeLinkInput(linkValueInput) {
var val = linkValueInput;
// do nothing, since this implies user's already using a custom protocol
if (/^\w+:/.test(val));
else if (!/^https?:/.test(val))
val = "http:" + val;
return builtInFunc.call(this, val); // retain the built-in logic
};
this method doesn't hook onto handlers but instead modifies the built-in sanitisation logic itself. We have also retained the original behavior of the sanitisation so that doesn't modify the editor's original behavior.
Alternatively, we could actually hook onto the save button of the tooltip, using this code. But it is too long a method compared to the one above.
As far as I can tell, the handling of creating and updating links is a bit distributed in Quill's sources. The default Snow theme handles editing links to some extent: it tracks the user selection and last selected link internally. Because of this I do not think that it is possible to achieve what you want currently in Quill using only a custom handler.
You may want to open an issue to report this, the authors might be willing to add such a handler.
In the meantime I came up with a way to update the link by simply listening for events causing the edit tooltip to close. There are some complications, because a link can be edited and the theme then relies on its internal tracking to update it. However, all in all I think that this solution is not too bad. You might want to add some error checking here and there, but overall it seems to work nicely and do what you want it do to. I have created a Fiddle demonstrating this. For completeness, I have included it here as a code snippet too.
var quill = new Quill('#editor', {
modules: {
toolbar: true
},
theme: 'snow'
}),
editor = document.getElementById('editor'),
lastLinkRange = null;
/**
* Add protocol to link if it is missing. Considers the current selection in Quill.
*/
function updateLink() {
var selection = quill.getSelection(),
selectionChanged = false;
if (selection === null) {
var tooltip = quill.theme.tooltip;
if (tooltip.hasOwnProperty('linkRange')) {
// user started to edit a link
lastLinkRange = tooltip.linkRange;
return;
} else {
// user finished editing a link
var format = quill.getFormat(lastLinkRange),
link = format.link;
quill.setSelection(lastLinkRange.index, lastLinkRange.length, 'silent');
selectionChanged = true;
}
} else {
var format = quill.getFormat();
if (!format.hasOwnProperty('link')) {
return; // not a link after all
}
var link = format.link;
}
// add protocol if not there yet
if (!/^https?:/.test(link)) {
link = 'http:' + link;
quill.format('link', link);
// reset selection if we changed it
if (selectionChanged) {
if (selection === null) {
quill.setSelection(selection, 0, 'silent');
} else {
quill.setSelection(selection.index, selection.length, 'silent');
}
}
}
}
// listen for clicking 'save' button
editor.addEventListener('click', function(event) {
// only respond to clicks on link save action
if (event.target === editor.querySelector('.ql-tooltip[data-mode="link"] .ql-action')) {
updateLink();
}
});
// listen for 'enter' button to save URL
editor.addEventListener('keydown', function(event) {
// only respond to clicks on link save action
var key = (event.which || event.keyCode);
if (key === 13 && event.target === editor.querySelector('.ql-tooltip[data-mode="link"] input')) {
updateLink();
}
});
<link href="https://cdn.quilljs.com/1.1.10/quill.snow.css" rel="stylesheet" />
<script src="https://cdn.quilljs.com/1.1.10/quill.min.js"></script>
<div id="editor"></div>
Let me know if you have any questions.
The toolbar handler just calls your given function when the button in the toolbar is clicked. The value passed in depends on the status of that format in the user's selection. So if the user has highlighted just plain text, and clicked the link button, you will get false. If the user highlighted the link, you will get the value of the link, which is by default the url. This is explained with an example here: http://quilljs.com/docs/modules/toolbar/#handlers.
The snow theme uses toolbar's addHandler itself to show a tooltip and it looks like you are trying to hook into this, which is not possible at the moment.
It looks like what you are really trying to do is control the sanitization logic of a link. Sanitization exists at a lower level in Quill since there are many ways to insert a link, for example from the tooltip UI, from paste, from the different APIs, and more. So to cover them all the logic is in the link format itself. An example of custom formats of links specifically is covered in http://quilljs.com/guides/cloning-medium-with-parchment/#links. You can also just modify Quill's own sanitize method but this is not recommended as it is not documented nor covered by semver.
let Link = Quill.import('formats/link');
Link.sanitize = function(value) {
return 'customsanitizedvalue';
}
after spending half an hour
found this solution
htmlEditorModuleConfig = {
toolbar: [
['link']
],
bounds: document.body
}
Add 'bounds: document.body' in configuration
I have to do same exact thing,(validate url before sending to server) so I end up with some thing like this.
const editor = new DOMParser().parseFromString(value,
'text/html');
const body = qlEditor.getElementsByTagName('body');
const data = document.createElement('div');
data.innerHTML = body[0].innerHTML;
Array.from(data.querySelectorAll('a')).forEach((ele) => {
let href = ele.getAttribute('href');
if (!href.includes('http') && !href.includes('https')) {
href = `https://${href}`;
ele.setAttribute('href', href);
}
});
body[0].innerHTML = data.innerHTML;
Maybe this is an old question but this is the way I make it work.
First, it whitelist other custom protocols to be accepted as valid ones.
Then, we run the sanitize method that is already included on the Quill core, and based on the custom protocols list it will return the URL or about:blank.
Then, if this is a about:blank is because it did not pass the sanitization method. If we get the URL then we verify whether or not it has a protocol from the list and if not, then we append http:// and in that way we do not get relative URL or blank unless it is not being whitelisted:
https://your-site.com/www.apple.com
about:blank
Hope it helps anyone else having this same issue.
const Link = Quill.import('formats/link')
// Override the existing property on the Quill global object and add custom protocols
Link.PROTOCOL_WHITELIST = ['http', 'https', 'mailto', 'tel', 'radar', 'rdar', 'smb', 'sms']
class CustomLinkSanitizer extends Link {
static sanitize(url) {
// Run default sanitize method from Quill
const sanitizedUrl = super.sanitize(url)
// Not whitelisted URL based on protocol so, let's return `blank`
if (!sanitizedUrl || sanitizedUrl === 'about:blank') return sanitizedUrl
// Verify if the URL already have a whitelisted protocol
const hasWhitelistedProtocol = this.PROTOCOL_WHITELIST.some(function(protocol) {
return sanitizedUrl.startsWith(protocol)
})
if (hasWhitelistedProtocol) return sanitizedUrl
// if not, then append only 'http' to not to be a relative URL
return `http://${sanitizedUrl}`
}
}
Quill.register(CustomLinkSanitizer, true)
I have declared a class based on dgrid/OnDemandGrid. The class can display a selected record for editing using dojox/form/Manager, which I have placed in a dijit/Dialog. When editing the first record everything works fine, but subsequent submits seem to accumulate and fire the submit event repeatedly, although the put method only seems to get called once per submit.
Please see the Firebug output here http://speedyshare.com/hQBuP/submitRecord.png (just click the file name at the top)
The edit and submit methods look like the code below. Any suggestions to what is wrong with my code are welcome.
Thanks in advance.
editRecord: function() {
this.editMode = "edit";
var rec = this.store.get(currentRowId);
var form = registry.byId(this.editFormId);
var dialog = registry.byId(this.dialogId);
form.reset();
form.setFormValues(rec);
form.on("submit", lang.hitch(this, this.submitRecord));
var cancelButton = registry.byId(this.cancelButtonId);
dialog.show().then(function(){cancelButton.focus();});
},
submitRecord: function(event) {
// Testing counter
if(!this.counter)
this.counter = 1;
else
this.counter++;
console.log("Submit event: " + this.counter);
// Get form, dialog and retrieve record
var form = registry.byId(this.editFormId);
var dialog = registry.byId(this.dialogId);
// Check validity
if(!form.validate()) {
return false;
}
var rec = form.gatherFormValues();
// Put record in store
this.store.put(rec).then( /*..... pop up status or error toaster (code omitted)..*/ );
// Dismiss dialog
form.reset();
dialog.hide();
// Stop submit event
event.stopPropagation();
event.preventDefault();
return false;
}
You are attaching a submit event listener every time editRecord is called, which is presumably every time you show your dialog. You really only want to be attaching that listener once. Since you're never removing it and adding it every time, you're effectively causing the same function to fire n+1 times on the next submit after each time editRecord is called. Hook up the submit event handler exactly once after creating the form instead.
If I had to guess, the reason store.put is only being called once is because you reset the form afterwards, so subsequent repetitive calls to submitRecord will fail the validate call and bail out before the put call.
I’ve made a one page site. When user clicks on the menu buttons, content is loaded with ajax.
It works fine.
In order to improve SEO and to allow user to copy / past URL of different content, i use
function show_content() {
// change URL in browser bar)
window.history.pushState("", "Content", "/content.php");
// ajax
$content.load("ajax/content.php?id="+id);
}
It works fine. URL changes and the browser doesn’t reload the page
However, when user clicks on back button in browser, the url changes and the content have to be loaded.
I've done this and it works :
window.onpopstate = function(event) {
if (document.location.pathname == '/4-content.php') {
show_content_1();
}
else if (document.location.pathname == '/1-content.php') {
show_content_2();
}
else if (document.location.pathname == '/6-content.php') {
show_content_();
}
};
Do you know if there is a way to improve this code ?
What I did was passing an object literal to pushState() on page load. This way you can always go back to your first created pushState. In my case I had to push twice before I could go back. Pushing a state on page load helped me out.
HTML5 allows you to use data-attributes so for your triggers you can use those to bind HTML data.
I use a try catch because I didn't had time to find a polyfill for older browsers. You might want to check Modernizr if this is needed in your case.
PAGELOAD
try {
window.history.pushState({
url: '',
id: this.content.data("id"), // html data-id
label: this.content.data("label") // html data-label
}, "just content or your label variable", window.location.href);
} catch (e) {
console.log(e);
}
EVENT HANDLERS
An object filled with default information
var obj = {
url: settings.assetsPath, // this came from php
lang: settings.language, // this came from php
historyData: {}
};
Bind the history.pushState() trigger. In my case a delegate since I have dynamic elements on the page.
// click a trigger -> push state
this.root.on("click", ".cssSelector", function (ev) {
var path = [],
urlChunk = document.location.pathname; // to follow your example
// some data-attributes you need? like id or label
// override obj.historyData
obj.historyData.id = $(ev.currentTarget).data("id");
// create a relative path for security reasons
path.push("..", obj.lang, label, urlChunk);
path = path.join("/");
// attempt to push a state
try {
window.history.pushState(obj.historyData, label, path);
this.back.fadeIn();
this.showContent(obj.historyData.id);
} catch (e) {
console.log(e);
}
});
Bind the history.back() event to a custom button, link or something.
I used .preventDefault() since my button is a link.
// click back arrow -> history
this.back.on("click", function (ev) {
ev.preventDefault();
window.history.back();
});
When history pops back -> check for a pushed state unless it was the first attempt
$(window).on("popstate", function (ev) {
var originalState = ev.originalEvent.state || obj.historyData;
if (!originalState) {
// no history, hide the back button or something
this.back.fadeOut();
return;
} else {
// do something
this.showContent(obj.historyData.id);
}
});
Using object literals as a parameter is handy to pass your id's. Then you can use one function showContent(id).
Wherever I've used this it's nothing more than a jQuery object/function, stored inside an IIFE.
Please note I put these scripts together from my implementation combined with some ideas from your initial request. So hopefully this gives you some new ideas ;)
I need to set up a custom script for tracking a users click through on a form submission field. This is what I've got so far. As the user navigates down through the form fields the counter variable (base) totals up how far along the path the user has reached. I want to send the results off when the user leaves the page by sending out the base variable. I'm thinking of using the .unload function in jQuery. However for some reason unload isn't responding the way I think it should. Any ideas?
var base = 0; //declares a variable of 0. This should refresh when a new user lands on the form page.
function checkPath(fieldNo, path) { //this function should check the current base value of the base variable vs the current fieldNo
if (fieldNo >= path) { //checks to see if base if lower than fieldNo
base = fieldNo; //if true, base is set to current fieldNo
return base;
} else {
return base; //if false, simply returns base.
}
};
$('#order_customer_fields_forename').focus(function () { //when the form box is selected should run checkPath then alert result.
checkPath(1, base);
});
$('#order_customer_fields_surname').focus(function () {
checkPath(2, base);
});
$('#order_customer_fields_postcode').focus(function () {
checkPath(3, base);
});
$('#order_customer_fields_address1').focus(function () {
checkPath(4, base);
});
$('#order_customer_fields_address2').focus(function () {
checkPath(5, base);
});
$(window).unload(function () {
alert(base);
});
The unload event fires too late for the effect you need. You should try using the onbeforeunload event using either vanilla Javascript:
window.onbeforeunload = function (e) {
// Your code here
};
Or jQuery:
$(window).bind('beforeunload', function (e) {
// Your code here
});
Either way, you should be aware that this is not an ideal solution for what you are trying to achieve. This event is implemented unevenly across browsers. Chrome seems to be the most restrictive, and IE the most permissive, in its implementation.
A different direction you may want to take is sending the data to the server by XHR whenever the user completes a field.
I have a contact form that sends a value to a hidden input on successful completion of the sendmail function. I want to detect this value change and then use it to apply a class to a div/paragraph.
I asked a similar question recently and I'm aware that this requires the script to continually check the doc after DOM is loaded but even after adding .change() it just doesn't seem to want to add the class.
Here's the jQuery:
$(document).ready(function() {
$("#acf_success_sent").change(function(){
if ($("#acf_success_sent").val() == "1"){
$("#acf_verified").addClass('gone');
}
});
});
any help would be great. here's a link to a test version of form in case you're interested, everything works except the verified symbol doesn't disappear after a successful send http://seeshell.me/forms/contact.php
There'll be no "change" event fired when code updates the value of your <input> element, so the handler you've registered won't run. What you could do however is fire "change" from a watchdog:
var watchdog = setInterval(function() {
if ($('#acf_success_sent').val() !== originalValue)
$('#acf_success_sent').trigger('change');
}, 100);
How you set up "originalValue" depends on your application. You could, for example, keep a separate ".data()" value, and watch for whenever your saved value differs from the current "value" attribute. Or you could keep the value in a closure variable:
var watchdog = (function() {
var $acfSuccessSent = $('#acf_success_sent'), cachedValue = $acfSuccessSent.val();
return function() {
if (cachedValue !== $acfSuccessSent.val())
$acfSuccessSent.trigger('change');
};
})();