How can i create multiple editors whit class name - ckeditor5

I try to create multiple ckeditors classic.
The problem is that i dont know how many i will have. So i init them by className.
This init the editors and work, but dont send anything trough ajax. So when i try to update the textarea only i can send the last editor because window.editor is overwrite.
I try then to store in an array but i cant pass th for neditor value because its a then so it executes when the for loop is done, so neditor is always 2.
How can i made it work? Idont understand very well promises
Thanks!
import ClassicEditor from './ckeditor5-build-classic';
var allEditors = document.querySelectorAll('.usarCkeditor');//i have 2 editors in that case
for (var neditores = 0; neditores < allEditors.length; neditores++) {
ClassicEditor.create(allEditors[neditores])
.then(editor => {
window.editor = editor;//window.editor is alway the last editor
})
.catch(err => {
console.error(err.stack);
});
}
This is other version :
for (var neditores = 0; neditores < allEditors.length; neditores++) {
window["editor" + neditores] = 'paco';
ClassicEditor.create(document.querySelector('#' + allEditors[neditores].attributes.id.value))
.then(editor => {
console.log(editor);
window["editor" + neditores] = editor;
console.log(neditores);//always 2 so it overwrites the array
})
.catch(err => {
console.error(err.stack);
});
}
//window.editor -> [0] = 'paco';[1] = 'paco';[2]= ckeditor

Please notice that querySelectorAll returns an HTMLNodeList not an Array.
If you want to initialize editor over multiple elements with classes you can do it for example in this way. All references to editors instances will be stored in window.editors object.
window.editors = {};
document.querySelectorAll( '.editor' ).forEach( ( node, index ) => {
ClassicEditor
.create( node, {} )
.then( newEditor => {
window.editors[ index ] = newEditor
} );
} );
This is a working sample: https://codepen.io/msamsel/pen/pXONjB?editors=1010

Related

Manipulate innerText of a CKEditor ViewElement

I am creating a little custom plugin for the CKEditor5 for the #neoscms.
Neos is using the #ckeditor5 but with a custom view.
The plugin is more or less a placeholder plugin. The user can configure a data-source with a key value store for items (identifier and labels). The dropdown in the CKEditor is filled with the items and when the user selects an item from the dropdown, it creates a placeholder element that should end in a span element with some data-attributes.
The main idea was to have an empty element and just data-attributes to identify the element and being able to assign live data. But it turns out that the live data thing is tricky. When I manipulate the span with an extra JS snippet on the Website, the CKEditor cannot handle this.
Is it possible to manipulate a view element in the DOM and still have a working Editor?
The Plugin works fine if I just add inner Text in the downCasting and don't replace something. But the live data would be nice.
Neos Backend with a element
Maybe that code gives an idea of the package.
It is not ready yet as this is more or less the main feature ;)
import {Plugin, toWidget, viewToModelPositionOutsideModelElement, Widget,} from "ckeditor5-exports";
import PlaceholderCommand from "./placeHolderCommand";
export default class PlaceholderEditing extends Plugin {
static get requires() {
return [Widget];
}
init() {
this._defineSchema();
this._defineConverters();
this.editor.commands.add(
"placeholder",
new PlaceholderCommand(this.editor)
);
this.editor.editing.mapper.on(
"viewToModelPosition",
viewToModelPositionOutsideModelElement(this.editor.model, (viewElement) =>
viewElement.hasClass("internezzo-placeholder")
)
);
this.editor.config.define("placeholderProps", {
types: ["name", "node", "nodePath"],
});
this.editor.config.define("placeholderBrackets", {
open: "[",
close: "]",
});
}
_defineSchema() {
const schema = this.editor.model.schema;
schema.register("placeholder", {
allowWhere: "$text",
isInline: true,
isObject: true,
allowAttributes: [
"name",
"node",
"nodePath",
"data-placeholder-identifier",
"data-node-identifier",
"data-node-path",
],
});
}
_defineConverters() {
const conversion = this.editor.conversion;
const config = this.editor.config;
conversion.for("upcast").elementToElement({
view: {
name: "span",
classes: ["foobar-placeholder"],
},
model: (viewElement, writer) => {
const name = viewElement.getAttribute('data-placeholder-identifier');
const node = viewElement.getAttribute('data-node-identifier');
const nodePath = viewElement.getAttribute('data-node-path');
const modelWriter = writer.writer || writer;
return modelWriter.createElement("placeholder", {name, node, nodePath, editable: false});
},
});
conversion.for("editingDowncast").elementToElement({
model: "placeholder",
view: (modelItem, writer) => {
const viewWriter = writer.writer || writer;
const widgetElement = createPlaceholderView(modelItem, viewWriter);
return toWidget(widgetElement, viewWriter);
},
});
conversion.for("dataDowncast").elementToElement({
model: "placeholder",
view: (modelItem, writer) => {
const viewWriter = writer.writer || writer;
return createPlaceholderView(modelItem, viewWriter);
},
});
// Helper method for downcast converters.
function createPlaceholderView(modelItem, viewWriter) {
const name = modelItem.getAttribute("name");
const node = modelItem.getAttribute("node");
const nodePath = modelItem.getAttribute("nodePath");
const placeholderView = viewWriter.createContainerElement("span", {
class: "foobar-placeholder",
"data-placeholder-identifier": name,
"data-node-identifier": node,
"data-node-path": nodePath,
});
// Would be nice to remove that and have just empty spans that get dynamic data
let innerText = config.get("placeholderBrackets.open") + name;
innerText += config.get("placeholderBrackets.close");
viewWriter.insert(
viewWriter.createPositionAt(placeholderView, 0),
viewWriter.createText(innerText)
);
return placeholderView;
}
}
}
So, the extra JS snippet that is used by the website is searching for spans with the class foobar-placeholder and writes a value with live data into the span. That works in the frontend, of course, but the backend of the CMS that uses CKEditor has issues with the changing data.
I could not find a solution with docs of CKEditor, and maybe I misuse the API somehow, but I now found a working solution for me.
My website snippet is now communicating with the Plugin via Broadcast messages. And then I search for placeholder elements and check if I need to change an attribute.
const broadcastChannel = new BroadcastChannel('placeholder:changeData');
broadcastChannel.postMessage({identifier: name, value});
And in the plugin
// Receive new values for placeholder via broadcast
const broadcastChannel = new BroadcastChannel('placeholder:changeData');
broadcastChannel.onmessage = (message) => {
const identifier = get('data.identifier', message);
const newValue = get('data.value', message);
this.editor.model.change( writer => {
if (identifier) {
this._replaceAttribute(writer, identifier, newValue);
}
});
};
Only downside now is that I need to reload the page, but already read that this is maybe cause by my element down casting and I change attributes.

How can I access to each element inside of a grid element with Cypress?

I have a Grid component which includes 24 divs and inside of each div I need to take the value.
This value actually arrives in <p>, so which is the best way to do this?
Below is the app image. I would appreciate an example.
You could probably do something like storing the data in an object or array outside of the Cypress chain. Without a code example, here's my best guess.
cy.get('grid').within(() => { // cy.within() searches only within yielded element
const data = {}; // create data object
cy.get('div').each(($div, index) => { // cy.each() allows us to iterate through yielded elements
data[index] = $div.text() // or possibly some other JQuery command to get the value
// additionally, could go without the index at all and make `data` an array.
}).then(() => {
// whatever needs to be done with `data`, wrapped in `then` to make sure data is populated correctly.
});
});
You can add use each for this to loop through the elements and then do further operations:
cy.get('.chakra-stack')
.find('p')
.each(($ele) => {
cy.log($ele.text().trim()) //Logs all the texts one by one
})
Just add the p selector to your cy.get() command
cy.get('div.chakra-stack p') // access <p> within <div>
.each($el => {
cy.wrap($el).invoke('text')
.then(text => {
...
})
})
To get the value before the text
cy.get('div.chakra-stack p') // access <p> within <div>
.each($el => {
cy.wrap($el)
.prev() // element with value
.invoke('text')
.then(value => {
...
})
})
Accessing values by text label like this
const values = {}
cy.get('div.chakra-stack p')
.each($el => {
const frase = $el.text()
cy.wrap($el).prev().invoke('text')
.then(value => values[frase] = +value)
})
.then(() => {
// values = {'shield': 1, 'session': 2, ...}
})

Find all clickable elements using webdriverio

I am new to webdriver io and I want to get all clickable elements using webdriver io and iterate through them. I came across 'browser.findElements' API, but could not get it to work. Can anyone provide me a sample code ?
var assert = require('assert');
var homePage = require("../../pages/home_page");
describe('Keyboard friendly home page', () => {
it('User should be able to navigate using tab',() => {
browser.url(homePage.url);
elements = browser.findElements("div");
clickableElements = [];
elements.forEach(element => {
if (element.isDiplayed() && element.isClickable()) {
clickableElements.push(element);
}
});
clickableElements.array.forEach(element => {
console.log(elemtent.getText() + "is clickable");
});
});
});
There might be two problems with your example:
incorrect use of findElements, see documentation; you can use $$ command where you pass only a selector, no need to pass a location strategy as well
the last forEach loop should look like this:
clickableElements.forEach(element => {
console.log(elemtent.getText() + "is clickable");
});

Calling to a method from ForEach doesn't working properly - vue.js

I'm trying to call a method from a foreach in vue, but the method is performed only one time,
No matter how many variables are in the list.
attached here the two functions I used:
CleanChips() {
this.chips.forEach((item) => {
this.RemoveRequirement(item)
})
},
RemoveRequirement(item) {
var index = this.chips.indexOf(item);
if (index > -1) {
this.chips.splice(index, 1);
this.chips = [...this.chips];
}
},
The RemoveRequirement function is performed only one time.
Any idea what's wrong here?
You modify the array itself in RemoveRequirement while running through it in CleanChips. You should create a copy of the original array to iterate in order to safely delete elements in the original array.
CleanChips() {
const chipsCopy = [...this.chips]
chipsCopy.forEach((item) => {
this.RemoveRequirement(item)
})
},
RemoveRequirement(item) {
var index = this.chips.indexOf(item);
if (index > -1) {
this.chips.splice(index, 1);
// this.chips = [...this.chips]; // you don't need this line because `splice` is reactivity-compatible.
}
},

Lodash reduce array of objects from array of objects

I'm trying to get a list of all guideScenes in all sections via
let combinedScenes = _.reduce(sections,
(prev, section) => {return {...prev, ...section.guideScenes}},
{}
)
It's only returning the first section list, how do I get all of them in there?
const sections = [{guideScenes: ['hello']}, {guideScenes: ['world']}, {guideScenes: ['hi', 'yo']}]
let combinedScenesObj = _.reduce(sections, (prev, section, index) => {
return {...prev, [index]: [...section.guideScenes]}
}, {})
console.log('object:', combinedScenesObj)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.core.js"></script>
You were close you needed a key to be added to each array because you were trying to add to an object
//dummy data
const sections = [{guideScenes: ['hello']}, {guideScenes: ['world']}, {guideScenes: ['hi', 'yo']}]
if you're wanting to resolve to an object you should give the item a key, for instance, I've used the index in this example
let combinedScenesObj = _.reduce(sections, (prev, section, index) => {
return {...prev, [index]:[...section.guideScenes]};
}, {})
// { 0: ["hello"], 1: ["world"], 2: ["hi", "yo"] }