How to implement a placeholder plugin that works with plain text placeholders? - ckeditor5

I'am trying to implement a plugin that translates plaintext placeholders of type __firstName__ into a model like <placeholder key="__firstName__"></placeholder> and vice versa.
I followed the tutorial on implementing an inline widget which really helped to get started.
I got the upcast part working which parses the text, splits it up and creates text- and placeholder-elements. So:
<p>Hi __firstName__,</p>
becomes:
<$root>
<paragraph>
"Hi "
<placeholder key="__firstName__">
</placeholder>
","
</paragraph>
</$root>
I'am now struggling to get the dataDowncast part working. I made these changes regarding only the dataDowncast, returning a text instead of an element with a text inside:
conversion.for('dataDowncast').elementToElement({
model: 'placeholder',
view: (modelItem, viewWriter) => {
var key = modelItem.getAttribute('key');
return viewWriter.createText(key);
}
});
I'm now facing two problems.
1.: The dataDowncast results in this:
<p>Hi _,_firstName__</p>
Everything following the placeholder seems to be shifted to the left or in other words seems to ignore the placeholder-length (-1) completely. Is the elementToElement even meant to except a text instead of an element? Or do i simply have to inform the writer about the length of the text somehow?
2.: If there are more than two placeholders one after another in the model:
<$root>
<paragraph>
<placeholder key="__firstName__">
</placeholder>
<placeholder key="__lastName__">
</placeholder>
<placeholder key="__salutation__">
</placeholder>
</paragraph>
</$root>
the dataDowncast raises this error:
Uncaught TypeError: Cannot read property 'name' of undefined
at Mapper.getModelLength (mapper.js:428)
at Mapper._findPositionIn (mapper.js:493)
at Mapper.on (mapper.js:94)
at Mapper.fire (emittermixin.js:211)
at Mapper.toViewPosition (mapper.js:277)
at DowncastDispatcher.modelViewSplitOnInsert (converters.js:214)
at DowncastDispatcher.fire (emittermixin.js:211)
at DowncastDispatcher._testAndFire (downcastdispatcher.js:473)
at DowncastDispatcher.convertInsert (downcastdispatcher.js:184)
at DataController.toView (datacontroller.js:207)
This could be a consequential error.
Dependencies uses:
"#ckeditor/ckeditor5-editor-inline": "12.0.0",
"#ckeditor/ckeditor5-widget": "11.0.0",
No plugins active.

Is the elementToElement() even meant to except a text instead of an element?
All 3 elementToElement() helpers (two-way, upcast and downcast) are expecting that on both ends (in the model and in the view) you have elements. They convert one element to another.
In general, there are 4 basic types of converters:
between model and view elements,
between a model attribute and a view element (must be AttributeElement then),
between model and view attributes,
from model markers to a view highlight or element.
It's possible to write custom converters for bigger chunks of view structures (when a bigger chunk of view is represented by a single thing in the model) but this applies to non-editable part of the view (e.g. the body of a widget). In the editable part of the content, everything in the view must map well to something in the model because every position in the view must map to some position in the model. And vice versa.
In your case, the problem is that when you converted a model element to a view text, that model element is not mapped to anything in the view. That breaks a series of things – starting from position mapping to some other pieces of code which will expect that they can map elements too.
Theoretically, it might be possible to override how positions and elements map here. CKEditor 5's Mapper offers some mechanisms for that (check out the events), but I would not go this way. It requires really deep knowledge and tests (even I'm unsure how much work it requires 😅).
So, do follow the placeholder guide where a model element is converted to a view element and vice versa.

Related

Selecting element using webdriver (duplicate identifiers)

I have to look at an application which I can't use the normal selectors (like "id", "name", etc - this is a design flaw) but I do have a custom tag which has been applied to elements on the page:
test-tag='x'
and this is fine, I can interact with this using (simple script)
var tag = '[test-tag="x"]';
var selector = $(tag);
However, I have now found that some elements (notably textboxes) have a title and a box element - both have the same custom tag applied. Now the text box is an input type. Anyone know how I can change the above to target specifially input types?
try this:
'input[test-tag="x"]'
for the input box
Take a look at this as well:
https://www.w3schools.com/cssref/css_selectors.asp

Add compound node on demand cytoscape.js

I was wondering how feasible is to add some compound elements (nodes) "on the go".
Scenario:
I have a network with nodes from n1...n10 and edges. Depending on the button the user clicks, it redraws my network including nodes inside a compound node.
Example:
When you open the graph, you have n1..n10, without compounds, but when you click on a pre-defined button, my new graph now would be:
A new compound node with n1:n5 inside (parent), and the rest n6:n10 would stay the same (outside compound).
How feasible is it ?
I've tried :
cy.batchData({
"5": {
parent: "n0" // new element I added earlier
}});
to update my element id=5 to have n0 as parent, but it haven't worked.
The main idea is to represent data (graph) with biological insight, where the "new compound area" would be a pathway or a metabolic path (or whatever I want to represent there), one by one, so the visualization won't be a mess.
Thank you.
You can't change the parent field; it's immutable. You may have to remove the elements and add the second (resultant) graph via eles.remove() and cy.add() respectively.

Pass an object to a widget in template

I have a Dojo UI widget that has a widget embedded within it. I need to pass an object to this embedded widget for it to set itself up correctly, but I'm not sure how to do it.
I have been templating in the embedded widget in the template for the wrapper widget, for example:
...<div class="thing"
data-dojo-type="mycompany.widgets.ComplexEmbeddedWidget"
data-dojo-props="stuff: '${stuff}'"></div>...
but this doesn't seem to work, I guess the data is passed as a string maybe?
I'm pulling out this data by setting it to a property in the embedded widget and then referencing it in my postMixInProperties function.
Doubtless this is the wrong approach, what should I be doing to set up an embedded widget such as this?
I think if you are going to use this approach, you want to convert the javascript object json before it is passed to the templated embedded widget.
You can easily do this by requiring 'dojo/json' and doing
this.stuff=jsonModule.stringify(this.stuffAsObject);
As you have already discovered, if you are setting more complex properties, programmatic instantiation is probably the way to go.
If your mycompany.widgets.ComplexEmbeddedWidget is desperate to have the object 'stuff' set allready once it is initialized (in constructor), then im not sure this approach will do, however a simple fix could be removing the ' quotes around ${stuff}?
What happens is basically that you derive the widget with dijit/_TemplatedMixin. This in turn, during buildRendering, calls _stringRepl on 'this' (the widget). I am not completely certain of the flow, since youre working with WidgetsInTemplate..
lets as example, set a widgets attribute to an array via markup:
<div
data-dojo-type="dijit.form.Select"
data-dojo-props="options:[ 'val1', 'val2']">
</div>
As you see, no quotes around the value - or it will render as a string. Lets then change your ComplexEmbedded template to
dojo.declare("exampleName", [_WidgetsInTemplateMixin, _TemplatedMixin], {
templateString: '<div class="outerWidgetDomNode">
...
<div class="thing"
data-dojo-type="mycompany.widgets.ComplexEmbeddedWidget"
data-dojo-props="stuff: ${stuff}"></div>
...
'
});
To instantiate the ComplexEmbeddedWidget.stuff with an object, this needs to be a string. _Templated uses dojo.string.substitute, which probably would fail if given deep nested object.
Markup example:
<div data-dojo-type="exampleName" data-dojo-props="stuff: '{ json:\'Representation\', as:\'String\'}'"></div>
Or via programmatic
var myObj = { obj:'Representation', as:'Object' };
var anExampleName = new exampleName({
stuff: dojo.toJson(myObj) // stringify here
}, 'exampleNode');
Lets know how goes, ive been wanting to look into the presendence of flow with this embedding widgets into template stuff for a while :)
You can programmatically insert widgets. This seems to be be the way to go if the inserted widget requires JavaScript objects to be passed to it.

Semantic zoom grouped collection

IN my metro application i want to create a semantic view for the page.
For that i am manually creating a grouped collection object using foreach loop. I am not using LINQ to group the object collection because of some reason .
SO now when i try to populate semantic zoom it displays nothing(no semantic zoom).
How can i bind my own collection to grouped collection source
XAML
<CollectionViewSource x:Name="GroupedSource" IsSourceGrouped="true" />
Code behind file
GroupedSource.Source =context.Collection; // my own grouped collection..
When using LINQ it working fine.But i cant use lINQ because of some reasons
Is there anything else i need to do to get
Try using this code to set the source:
(semanticZoom.ZoomedOutView as ListViewBase).ItemsSource = GroupedSource.View.CollectionGroups;
Need a bit more detail, but. The IsSourceGrouped="true" is only half of the story unless you bind to a hierrachal datasource. You need to specify the property that contains the child collection - ItemsPath="myItems" as an xaml property of the CollectionViewSource. If that is not the issue it may be a case of precidence of execution. put a break point and check context.Collection is populated before it is used, if context.Collection is an ObservableCollection you should be able to populate at anytime (ie. async fill).
<CollectionViewSource x:Name="GroupedSource" IsSourceGrouped="true" ItemsPath="Items" />

Assert css locator is equal to it's expected value

I'm doing some drag and drop js testing with selenium-client. It works (taking screengrabs before and after clearly show the elements to switch places), but I'm having trouble programatically asserting the change happened.
Am I mental, or can I not do something like:
selenium.assert_equal("css locator", "expected id of element")
which, in this case, would look something like:
selenium.assert_equal("css=li:nth-child(1)", "li#list_item_2")
Any tips on how to implement this would be great.
Thanks,
Adam
Edit: if I had selenium.get_element that would take a selector and return what it was, I could then perform the assertion in the next step.
i.e.
element = selenium.get_element("css=li:nth-child(1)")
assert_equal(element, "li#list_item_2")
(I think).
Your example won't work because you're comparing two strings that aren't equal. One way to assert that your element has moved would be to use isElementPreset as demonstrated below:
//before drag and drop
assertTrue(selenium.isElementPresent("css=#source li:nth-child(1)"));
assertFalse(selenium.isElementPresent("css=#target li:nth-child(1)"));
//drag and drop code here
...
//after drag and drop
assertTrue(selenium.isElementPresent("css=#target li:nth-child(1)"));
assertFalse(selenium.isElementPresent("css=#source li:nth-child(1)"));
This example uses the Java client API, but it should be easy to work out how to do it in your preferred client API. It will also depend heavily on your application, as the above will check that the element with id of 'target' has one child li element before the drag and drop, and none afterwards. If you have a snippet of your HTML source I might be able to prove a more robust example.