CKEditor5: prevent attributes being copied to new paragraph on enter - ckeditor5

I am working on a project that allows users to inject content into the editable. I have added a contentid attribute to the paragraph so I can keep track of what has been added and where it is.
<p contentid="1">some default content</p>
When enter is pressed from within that paragraph, the next paragraph that is entered also has the contentid="1" attribute. I want the new paragraph to not have the contentid attribute.
I'm trying to determine how/where best to achieve this. I have been hacking around with listening to the enter key and schema.addChildCheck but not making much progress.

This seems to do the trick. Maybe a little hacky.. but realized that the selection is always in the newly created element. So I didn't need to find a reference to it in a callback, I could just grab the new paragraph from the current selection.
editor.commands.get( 'enter' ).on( 'afterExecute', () => {
const block = first( editor.model.document.selection.getSelectedBlocks() );
editor.model.change( writer => {
writer.removeAttribute( 'contentid', block );
});
});

Related

CKEditor 5 copy selected content from one editor to another

I have two editors on the screen, one read-only. What I want to do is allow the user to select content from the read-only editor and paste it into the current position of the other by clicking a button. (the logic may manipulate the text which is one reason I don't want to use the system's clipboard.)
So far I have the function that is able to paste the text like as follows. (I am using the Angular wrapper which explains the presence of the CKEditorComponent reference.
doPaste(pasteEvent: PasteEvent, editorComponent: CKEditorComponent) {
const editor = editorComponent.editorInstance;
editor.model.change(writer => {
writer.insertText(pasteEvent.text, editor.model.document.selection.getFirstPosition() );
});
}
What I can't find from the documentation is how to extract the selected text. What I have so far is:
clickPasteSelectedPlain(editorComponent: CKEditorComponent) {
const editor = editorComponent.editorInstance;
const selection = editor.model.document.selection;
console.log('clickPasteAll selection', selection);
console.log('clickPasteAll selectedcontent', editor.model.document.getSelectedContent);
}
The selection appears to change depending on what is selected in the editor's view. The getSelectedContent function is undefined. How do I get the content?
With a bit of poking around I figured out how to do this. I'll document it here on the chance that it will help someone down the road avoid the process of discovery that I went through.
On the source document I have a ckeditor element like this:
<div *ngIf="document">
<ckeditor #ckEditor
[editor]="Editor" [config]="ckconfig" [disabled]="true"
[(ngModel)]="document.text"></ckeditor>
<button mat-flat-button (click)="clickPasteSelectedPlain(ckEditor)">Paste Selected Text Plain</button>
</div>
In the component the function called on the click event is like this:
#Output() paste = new EventEmitter<PasteEvent>();
...
clickPasteSelectedPlain(editorComponent: CKEditorComponent) {
const editor = editorComponent.editorInstance;
this.paste.emit({
content: editor.model.getSelectedContent(editor.model.document.selection),
obj: this.document,
quote: false
});
}
The PasteEvent is defined as an exported interface which I will omit here to save space. The content key will refer to a DocumentFragment.
Note that I am passing the CKEditorComponent as a parameter. You could also access it via an Angular #ViewChild declaration but note that my ckeditor is inside an *ngIf structure. I think that works well in Angular 6 but in the past I have had difficulty with #ViewChild references when the target was conditionally in the DOM. This method always works but use whatever method you want.
The event fired by the emit is processed with a method that looks like this:
doPaste(pasteEvent: PasteEvent, editorComponent: CKEditorComponent) {
const editor = editorComponent.editorInstance;
editor.model.insertContent(pasteEvent.content);
}
Because the content is a DocumentFragment the paste operation will include all formatting and text attributes contained in the selected source. But that's all there is to it.

Get SelectedItem in Dropdownlist without FormMethod.Post

I have a dropdownlist that is bound to a list in my model. Model.list_IDs
The current page is an "editing" page where a user would inherently change a property of the model by using the dropdownlist.
#Html.DropDownListFor(model => Model.ID, Model.list_IDs, new { style = "width:250px" })
The items within the dropdownlist are not intuitive, so I would like to provide a button, that when clicked, retrieves additional information via a stored procedure (called from the controller via a method called GetDetails)
The button is created through an action link, and I plan to display a partial view (like a focused pop up window) that shows the additional information once the button is clicked.
#Html.ActionLink(" ", "GetDetails", new { id = Model.ID.ToString() }, new { #class = "lnkMagnify16", title = "View Details" })
Obviously Model.ID.ToString() is incorrect, because it will only send the model's current ID, rather than the ID currently selected in the dropdownlist.
How can I change Model.ID.ToString() to represent the dropdownlist's current selected item?
I know there is a way to do this using FormMethod.Post (Get selected item in DropDownList ASP.NET MVC) but I do not want to reload the page with a submit button. I'd also like to avoid "third party" approaches like JavaScript if possible.
JavaScript is not a "third party" approach, and it's also the only way to accomplish what you want here. Whether by a standard form post or via AJAX (JavaScript), you must make a new request to the server to get the new information you want.
Now, since all you're specifically after is a way to dynamically update the the Model.ID value in the link, you can do that without AJAX or a form post, but you still must use JavaScript, since all dynamic behavior client-side originates from JavaScript. Basically, you'd need to bind to the change event of the dropdown and alter the href property of your link.
document.getElementById('ID').addEventListener('change', function () {
var selectedId = this.options[this.selectedIndex].value;
document.getElementById('AwesomeLink').href = // alter `href` with `selectedId`
});
However, that link is still going to change the whole view if the user clicks it. If you truly want the user to stay on the page, then you'll need to fetch the response of that link using AJAX and then either add it to the page somehow, whether that be directly or via a modal popup (which will required yet even further JavaScript to achieve). Also, it's not clear how the Model.ID value ends up in the URL, i.e. whether it's part of the path (/some/url/{id}/) or as a query string param (/some/url/?id={id}). If it's the former, I'd recommend switching to the latter, as it will make it much easier to build your URL client-side.
var xhr = new XMLHttpRequest();
xhr.addEventListener("load", function (response) {
// add response.responseText to the DOM
});
xhr.open("GET", "/some/url/?id=" + selectedId);
xhr.send();

I want JAWS to tell the user what kind of node they are in while navigating the dijit.Tree

We have a dijit.Tree that indicates a node type by using an icon. The icon is a unique indicator that tells the person this node is a "book" or a "DVD" or a "magazine" for example.
dijit renders the icon as a background image in CSS which we know screen readers do not see.
I tried overriding the getTooltip method to provide a tooltip saying "book" or "DVD". It successfully adds the "title" attribute to the "dijitTreeRow". If I mouse over the node, I see the text. This is not ever focused on when the user moves down to get from one node to the next.
When navigating the tree, the up and down arrows traverse the nodes. The span with the visible text is focused on and that string is read. You can see the dotted line focus as well as hear this with JAWS in the most basic of examples: https://dojotoolkit.org/reference-guide/1.10/dijit/Tree.html
What I have not been able to figure out is how to create an indicator that the screen reader will pick up on that will read "Book" alongside "The Great Gatsby".
Does anyone have any tips on how they made this dijit widget accessible for the screen reader when the images are an indicator that should be heard by the blind user?
The tree supports HTML labels, via setting the labelType property on the model you give it.
Assuming you don't want to change the store data (or override the getLabel method), you can reimplement dijit/Tree.getLabel and produce the HTML label, and wrap it with a span with an aria-label.
(code lifted from the dijit.Tree reference).
var myModel = new ObjectStoreModel({
store: myStore,
labelType: "html", // Hack to tell the tree node to render as HTML
query: {id: 'world'}
});
var tree = new Tree({
model: myModel,
getLabel: function(item) {
var label = this.model.getLabel(item);
// dojo.string
return dstring.substitute("<span aria-label='dvd ${0}'>${0}</span>", [label]);
}
});
If your data might contain HTML-ish characters that you don't want to render, escape the characters in getLabel too.

Dynamic menu button items in TinyMCE

I have a custom menubutton in my tinyMCE editor that uses specific HTML elements elsewhere on the page as the menu items. I use a jQuery selector to get the list of elements and then add one each as a menu item:
c.onRenderMenu.add(function(c,m) {
m.add({ title: 'Pick One:', 'class': 'mceMenuItemTitle' }).setDisabled(1);
$('span[data-menuitem]').each(function() {
var val = $(this).html();
m.add({
title: $(this).attr("data-menuitem"),
onclick: function () { tinyMCE.activeEditor.execCommand('mceInsertContent', false, val) }
});
});
});
My problem is that this only happens once when the button is first clicked and the menu is first rendered. The HTML elements on the current page will change occasionally based on user clicks and some AJAX, so I need this selector code to run each time the menu is rendered to make sure the menu is fully up-to-date. Is that possible?
Failing that, is it possible to dynamically update the control from the end of my AJAX call elsewhere in the page? I'm not sure how to access the menu item and to update it. Something using tinyMCE.activeEditor.controlManager...?
Thanks!
I found a solution to this problem, though I'm not sure it's the best path.
It doesn't look like I can make tinyMCE re-render the menu, so instead I've added some code at the end of my AJAX call: after it has updated the DOM then it manually updates the tinymce drop menu.
The menu object is accessible using:
tinyMCE.activeEditor.controlManager.get('editor_mybutton_menu')
where mybutton is the name of my custom control. My quick-and-dirty solution is to call removeAll() on this menu object (to remove all the current menu items) and then to re-execute my selector code to find the matching elements in the (new) DOM and to add the menu items back based on the new state.
It seems to work just fine, though tweaks & ideas are always welcome!

Clearing input textbox using FuncUnit

I am writing FuncUnit for my application. I am browsing the application in Google Chrome. I have a textbox which is initially hidden. I need to make it visible and then clear the text already present in that textbox. I have the following code which makes the box visible but fails to clear the text in it.
S('#search').visible().clearText();
Can anyone tell what is wrong here?
Try to clear the textbox by typing - Ctrl+A and Delete.
var input = S('input.my-input');
input.type('[ctrl]a[ctrl-up][delete]', function() {
// Continue in test case after the text has been removed
});
Your statement is not accurate. visible() does not turn things visible. It is a wait function which waits for the source element to become visible before proceeding to the next action.
koalix's key sequence works. With the type() command you might need to first click into the text input before clearing it.
Try:
S('#search').visible().click().type('[ctrl]a[ctrl-up][delete]');
You could also try empty quotes <" ">
var input = S('input.my-input');
input.type('', function() {
// remove existing text
});
I don't know if you're still waiting for an answer.
I think you're not using visible() in the correct way.
In FuncUnit (see docs here), among other things, you can distinguish between "actions" and "waits". visible() is a wait, and should be used to wait for an element to become visible, like this:
S('#el').visible( function() {
// do something when element with id="el" becomes visible
});