jsTree: tabbing out of edit mode? - javascript

I'm using the jsTree plugin in my current Angular 2 project.
All is working well, but I'm now trying to add specific behavior:
When the user is editing a node and he presses the tab key, I want to automatically add a new empty sibling node directly underneath the node he was editing, and enable edit mode on it so that he can continue typing and effectively use the tab key as a keyboard shortcut to quickly create large numbers of nodes on the same level.
I've tried adding a keyup event listener to the high level div containing jsTree:
(keyup)="onKeyUp($event)"
The onKeyUp function:
onKeyUp(e: any) {
if (e.code === 'Tab' && this.renamingNode) {
// pressed tab while renaming subitem, insert new sibling item and start editing
}
}
Finally, the (simplified) code for the jsTree edit:
let scope = this;
$(this.currentTree).jstree().create_node(selectedItem, { 'text': '', 'type': 'value' }, 'last', function callback(e: any) {
scope.renamingNode = true;
// enable renaming of node
$(scope.currentTree).jstree().edit(e, null, function callback(addedNode: any, status: boolean, cancelled: boolean) {
scope.renamingNode = false;
// code to add addedNode to database using service
}
}
This doesn't work out as intended. When debugging, I can see that every key is captured when the user is renaming a node, but the 'tab' keypress seems to be suppressed by jsTree somehow. The onKeyUp function is not triggered, instead the default jsTree behavior proceeds, renaming the node and selecting it.
I have also looked into the different jsTree.edit callback function parameters (nodeObject, status, cancelled) but none of those seem to be useful in my case.
This is not too high on my priority last, it's more a nice-to-have, but I am genuinely curious how I could implement this.. Does anyone have any ideas?

You will have to monitor tab key on the input that pops up when you start editing a node, as below. Also check out demo - Fiddle Demo
$(document).on('keydown', '.jstree-rename-input', function(e){
if (e.keyCode == '9') {
var focusedNodeId = $('#jstree').jstree()._data.core.focused,
focusedNode = $('#jstree').jstree().get_node( '#'+focusedNodeId );
newNode = $("#jstree").jstree('create_node', '#'+focusedNodeId);
// simulate timeout to ensure new node is in the DOM and is visible
// before we start editing it
setTimeout(function(){
$("#jstree").jstree('edit', newNode);
}, 0);
}
})

Related

Not able to shift focus to Shipment Nbr field on Shipments screen

I'm using the built in Acumatica browser commands to insert a new shipment record by pressing a function key. The function Key triggers the command with px.searchFrame(window.top,"main")['px_alls'].ds.executeCommand("Insert"); For some reason, it triggers the insert command, but it doesn't shift the focus to the Shipment Nbr input field. Also, if you try to shift the focus manually using var field=px_alls["edShipmentNbr"]; field.focus(); that doesn't work either. I've been able to shift the focus to other fields, so I know the code is correct, but I can't figure out why the focus can't be shifted to the Shipment Nbr input. Any ideas on what else can be done? It's not just the Insert command either. Calling the Cancel command, which should shift the focus, doesn't work either.
What's strange is that the Insert command can be called by pressing Ctrl+Insert, and it works perfectly.
I built some code that shifts the focus to the ship date field and then tabs backwards 5 times, which emulates correctly what the insert command should do, but it only works intermittently on the client's computer.
Thanks
The Acumatica Framework provides built-in support for keyboard shortcuts via the following properties defined in PXButtonAttribute:
ShortcutShift = true/false : Determines Shift key presence
ShortcutCtrl = true/false : Determines Control key presence
ShortcutChar = ‘x’ : Determines shortcut character
Below is the sample to insert new Shipment when the user presses F2. Since the code snippet below utilizes capabilities of the framework, by pressing F2 the user executes the Insert command from the SOShipmentEntry BLC instead of simulating button click in JavaScript. This approach guarantees that all logic embedded into the Insert command, including setting focus to the Shipment Nbr input, is properly executed.
public class SOShipmentEntryExt : PXGraphExtension<SOShipmentEntry>
{
public class PXInsertShortCut<TNode> : PXInsert<TNode>
where TNode : class, IBqlTable, new()
{
public PXInsertShortCut(PXGraph graph, string name)
: base(graph, name)
{
}
public PXInsertShortCut(PXGraph graph, Delegate handler)
: base(graph, handler)
{
}
[PXUIField(DisplayName = ActionsMessages.Insert, MapEnableRights = PXCacheRights.Insert, MapViewRights = PXCacheRights.Insert)]
[PXInsertButton(ShortcutChar = (char)113)]
protected override IEnumerable Handler(PXAdapter adapter)
{
return base.Handler(adapter);
}
}
public PXInsertShortCut<SOShipment> Insert;
}
If you're executing a callback to the server in JavaScript, the callback return might set focus to another field after it finishes execution. Your focus() statement works but the callback return performs another focus() on a different control after yours.
Hooking the Ajax callback allows you to put your focus() statement after the Acumatica framework focus():
window.addEventListener('load', function () { px_callback.addHandler(ActionCallback); });
function ActionCallback(callbackContext) {
px_alls["edShipmentNbr"].focus();
};

Quill link handler not working

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)

Telerik Kendo UI grid loses custom command event handler after persisting (and restoring) its state

I have isolated the issue, see and try the full source here.
Steps to reproduce:
Press Ctrl+Enter to run the snippet
Click on 'Say Hello' custom command button, and check if the event
handler runs
Click on top left 'Save State' button
Click on 'Load State' button, and restore the previous state.
Now click again on 'Say Hello' button and demonstrate the event handle will not run, instead something weird is happening.
Notes: Please do not search for the solution around the localStorage. The issue can be reproduced by using different server side state persisting solution. (as my original app does)
Any idea where to patch? ... or workaround?
Hopefully this will help you out.
http://dojo.telerik.com/EDUCO/4
I have added the following piece of code for you:
dataBound: function (e) {
$(".k-grid-SayHello").on('click', function (a) {
console.log(e);
a.preventDefault();
alert('Hello');
});
},
When the rebind occurs I suspect that it is losing the connection to the event handler so all I have done if looked for the button based on it's class name and reattached it.
Obviously you can adapt the solution to meet your needs but this is something I do for my projects when I need to "invoke" custom actions on buttons/ dynamically create things on the fly.
Any issues let me know.
To keep function references after calling grid.setOptions()
I added the function references back to the deserialized configuration object before passing it to the setOptions method.
( http://docs.telerik.com/kendo-ui/api/javascript/ui/grid#methods-setOptions )
$(document).ready(function () {
var grid = $("#myGrid").data("kendoGrid");
var originalOptions = grid.getOptions();
var savedOptions = JSON.parse(localStorage["myGrid-options"]);
if (savedOptions) {
var detaylarFunc = originalOptions.columns[3].command[0].click;
savedOptions.columns[3].command[0].click = detaylarFunc;
grid.setOptions(savedOptions);
} else {
grid.dataSource.read();
}
});
//Custom command
function Detaylar(e) {
e.preventDefault();
var grid = $("#myGrid").data("kendoGrid");
options = grid.getOptions();
localStorage["myGrid-options"] = kendo.stringify(grid.getOptions());
}

disable CKEditor indentation plugin

I need to disable the indentation of ordered and unordered lists (<ul>, <ol>) in CKEditor (4.2 or 4.3) entirely, because i need to convert (a very limited subset of) HTML into another markup language which does not support indentation.
I've tried several approaches, but didn't have any luck:
Try 1: Remove plugin via configuration
config.removePlugins = 'indent,indentlist,indentblock';
I guess that is not working because these plugins seem to be required - you can't remove them when building a CKEditor package from the website.
When viewing CKEDITOR.plugins via the FireBug console, those plugins are still there. There doesn't even exist an own plugin folder - seems they are builtin to the core.
Try 2: Override TAB key
I created a new plugin disableTab that does entirely nothing (except return true; on execution).
The plugin is registered as a handler for keystroke of the TAB key:
config.keystrokes = [
[ 9, 'disableTab' ] // disable TAB key to avoid nesting!
];
Unfortunately, the plugin doesn't work when pressing tab on the first level of a list (<li> or <ol>). Interestingly, it works when pressing TAB in the second level of a list (ol > li > ol > li), it does not produce more nested lists below the second level. I know for sure my plugin is executed, because i inserted an alert() in my plugin for testing. At least, this is what happens in my Firefox.
But i need to disable indentation entirely, not only above level > 2.
Try 3: Block keystroke via blockedKeystrokes in editor configuration:
Doesn't work, even though it should according to the documentation:
config.blockedKeystrokes = [ 9 ];
Try 4: Remove keystroke during runtime
According to the API documentation this code should disable the keystroke, but it doesn't work for some reason:
for (instance in CKEDITOR.instances) {
var editor = CKEDITOR.instances[instance];
editor.setKeystroke(9, false);
}
Any idea how to remove the indentation of lists in CKEditor?
I don't understand why none of these approaches work. If you know why, please let me know.
Update:
Interestingly, this code greets me for almost every key event, except pressing the TAB key:
editor.on('key', function(e) { alert ("hi"); return false; });
So it seems my setup (LinuxMint 13 [Gnome 2] + Firefox 18 + CKEditor 4.2) does not fire the key event handler for the TAB key. Maybe the indent plugin uses some other event? Blur?
Update 2:
This is a Firefox (maybe linux only) issue. Several approaches work fine with Chrome or Internet Explorer.
I just checked quickly and it looks like although indentlist is required by the list plugin, if you:
0) Download CKEditor sources from http://github.com/ckeditor/ckeditor-dev
1) Remove
requires: 'indentlist',
from plugins/list/plugin.js
2) Remove
indentlist: 1,
indentblock: 1,
from dev/builder/build-config.js
3) Build release package with dev/builder/build.sh (on Windows use "Git Bash" shell)
You will find in the dev/builder/release/ckeditor folder the release version that you need.
(it's rather uncommon that a required plugin is not really required, but it's uncommon as well that one do not need indentation for lists ;-) )
try changing your code to:
editor = CKEDITOR.replace( 'element_name' );
editor.on('key', function(e) {
var key = e.data.keyCode;
if(key==9) {
return false;
}
that should work, just change 'element_name' to the textarea that you are replacing with ckeditor
The built-in indentlist plugin cancels the event bubbling when it is processed, so a standard event listener for the tab key isn't being fired. If you prioritize your event to run first, you can capture the tab key event and stop it from indenting your lists.
Ex:
editor.on('key', function (evt) {
if (editor.mode != 'wysiwyg') {
return false;
}
if (evt.data.keyCode == this.indentKey || evt.data.keyCode == 9) {
evt.cancel();
return false;
}
}, null, null, 1);
If you want to only block the indentation on numbered lists, you can add these conditionals:
editor.on('key', function (evt) {
var path = editor.elementPath();
if (editor.mode != 'wysiwyg') {
return false;
}
if (evt.data.keyCode == this.indentKey || evt.data.keyCode == 9 && path.contains('ol')) {
evt.cancel();
return false;
}
}, null, null, 1);
The event priority is outlined in the documentation here: https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_event.html#method-on

How to capture keybd events at document level, but let editable fields have their events?

EDIT: Since nobody has responded, I'll try to restate my question more succinctly. When handling keystrokes on a browser page (like left/right/space) how does one tell whether the keystroke is going to be handled by the element on the browser page that has focus or whether it's not going to be handled by that object and it would be OK to handle it globally? Ideally, you'd let the focus object see the keystroke and be able to tell whether it handled it or not. If not, you could then process it yourself. If it handled it, you would do nothing with it (assuming that the focus object has a more important use for the keystroke).
Here's a typical example in a non-browser setting. Imagine you have a Windows dialog that has a bunch of typical dialog box controls, a couple buttons and a rich edit control in it. If you're in a typical dialog box control, the Enter key on the keyboard will activate the OK button on the dialog and accept the dialog's changes. If you're in the rich edit control, the Enter key will enter a new line. The dialog box is somehow able to tell whether the current control in the dialog box wants to process the enter key or whether it should be handled globally.
In my particular browser case, I'm using YUI2 event handling to capture keystrokes at the document level so that I can allow the user to use the left/right arrows on the keyboard to move through a slideshow on the page without having to explicitly set the focus to any particular element on the page (a feature my users like). But, if the page contains any editable fields, I want those left/right arrows to be processed by that field on the page and not by my slideshow. I'm looking for an elegant way to do that.
I realize I can look at the original event target and "try" to figure out if that target is capable of handling the left/right arrows (input, textarea, contenteditable, etc...), but I was hoping for a more foolproof method. Is there any way to handle a keystroke at the document level ONLY when it isn't otherwise handled by an object on the web page. In my YUI keyboard handler now, I'm getting all keyboard events, even ones that will actually be handled by the target object.
Here's what my code looks like now and I'm looking for a more elegant way to do this that doesn't require the ObjectWantsKeys function which is somewhat brittle:
JF.Slideshow.prototype._HookupKeyNav = function () {
var k = YAHOO.util.KeyListener;
var key = k.KEY;
// see if keyboard handling is enabled or not
if (((this._config.keyboardNav == "auto") && (JF.SlideshowCnt <= 1)) || (this._config.keyboardNav == "on")) {
this._keyListener = new YAHOO.util.KeyListener(
document, {
ctrl: false,
shift: false,
alt: false,
keys: [key.LEFT, key.RIGHT, key.SPACE]
}, {
fn: this._HandleKeys,
scope: this,
correctScope: true
});
this._keyListener.enable();
}
}
JF.Slideshow.prototype._HandleKeys = function (type, args, obj) {
var keyCode = args[0];
var event = args[1];
var target = event.srcElement || event.target;
if (JFL.ObjectWantsKeys(target)) return; // if the current focus object wants keystrokes, then we shouldn't process them
var key = YAHOO.util.KeyListener.KEY;
switch (keyCode) {
case key.LEFT:
this.Back();
break;
case key.RIGHT:
this.Forward();
break;
case key.SPACE:
this.StartStopToggle();
break;
default:
break;
}
}
JFL.ObjectWantsKeys = function (o) {
// list of HTML object types that want the keystroke if they have focus
var list = {
"input": true,
"textarea": true
};
try {
// unfortunately o.contentEditable set to an empty string means it is editable so we need this more complicated comparision
if ((typeof o.contentEditable !== "undefined") && (o.contentEditable === "true" || o.contentEditable === true || o.contentEditable === "")) {
return (true); // focus object is editable
}
if (o.tagName && list[o.tagName.toLowerCase()]) {
return (list[o.tagName.toLowerCase()]); // focus object can take keys
}
} catch (e) {}
return (false);
}
I solved this by checking the type of element that had focus:
var tag = document.activeElement.tagName;
if (tag == "INPUT" || tag == "TEXTAREA")
return;
This wasn't perfect because I also didn't want the KeyListener to handle keystrokes when a YAHOO.widget.Dialog was displayed (which would give a tag name of "BODY"). I had to throw something together using a YAHOO.widget.OverlayManager and check the active dialog. If it's null I process the keystroke.
It's not elegant and I'm hoping for a better solution. I posted a message at the YUI forums:
http://yuilibrary.com/forum/viewtopic.php?p=26413
If I get an answer there it may help you as well.

Categories