CKEditor5: How to create model element value from data attribute? - ckeditor

I have legacy html data that I'm trying to edit with CKEditor5. The data format is:
<my-data-element url="https://something.com/more/stuff"></my-data-element>
The desired model format is
<my-model-element>
https://something.com/more/stuff
</my-model-element>
where the url attribute in the data is now the text of the model element. my-model-element is an editable widget so the user can easily modify the existing URL, copy/paste/etc. When the model is convert to data, the text in my_model-element should be converted to the url value for my-data-element. Reading the value of the url attribute is relatively easy, but I can't figure out how to set the text of the my-model-element. While this looks similar to a link, it's not a link. I considered borrowing from the link editing code, but that's a lot of code and this should be a root level object.
For data down casting, extracting the value of the element to set as the url is easy. The code below leaves the text of my-model-element in my-data-element but I can deal with that for now. It also results in my-data-element having the attribute undefined="undefined", for some reason, but I can also live with that.
schema.register( 'my-model-element', {
isObject: true,
allowWhere: '$block',
allowAttributes: ['url'],
allowContentOf: '$block'
} );
conversion.for( 'dataDowncast' ).elementToElement( {
model: 'myElement',
view: ( modelItem, {writer: viewWriter } ) => {
const data = modelItem.getChild(0).data;
const elem = viewWriter.createContainerElement (
'my-data-element', { url: data }
);
return elem;
}
} );
conversion.for( 'dataDowncast' ).attributeToAttribute( {
model: 'url',
// view has to be a function or the url doesn't get updated
view: () => 'url',
});
For up casting I can get the url from my-data-element, but have not been successful setting the text of my-model-element. Instead, the text value of my-model-element remains empty.
conversion.for( 'upcast' ).elementToElement( {
model: ( viewElement, {writer: modelWriter }) => {
// Pulling the URL works
const url = viewElement.getAttribute('url');
// But creating the child of the new element doesn't
const text = modelWriter.createText(`${url} DOESNT WORK`);
const elem = modelWriter.createElement('my-model-element', {}, text);
return elem;
},
view: {
name: 'my-data-element',
}
} );
I've read the majority of the CKEditor5 documentation on up and down casting, and the tutorials on block, inline, and data driven widgets.

Related

Storybook problem while migrating argument of type object from addon-knobs to addon-controls

I'm having some trouble migrating one thing from the old addon-knobs to the new controls. Let me explain, maybe it's not such difficult task but I'm blocked at the moment.
I'm using StencilJS to generate Web Components and I have a custom select component that accepts a options prop, this is an array of objects (the options of the select)
So, the story for this component in the previous version of Storybook looks something like this:
export const SelectWithArray = () => {
const selectElement = document.createElement('my-select');
selectElement.name = name;
selectElement.options = object('Options', options);
selectElement.disabled = boolean('Disabled', false);
selectElement.label = text('Label', 'Label');
return selectElement;
};
This works fine, the select component receives the options property correctly as an array of objects.
Now, migrating this to the new Storybook version without addon-knobs, the story is looking like this:
const TemplateWithArray: Story<ISelect> = (args) => {
return `
<my-select
label="${args.label}"
disabled="${args.disabled}"
options="${args.options}"
>
</my-select>
`;
};
export const SelectWithArray: Story<ISelect> = TemplateWithArray.bind({});
SelectWithArray.argTypes = {
options: {
name: 'Options',
control: { type: 'object' },
}
}
SelectWithArray.args = {
options: [
{ text: 'Option 1', value: 1 },
]
}
And with this new method, the component is not able to receive the property as expected.
I believe the problem is that now, the arguments is being set directly on the HTML (which would only be accepting strings) and before it was being set on the JS part, so you could set attributes other than strings.
Is there a way to achieve this? without having to send the arguments as a string.
Thanks a lot!!
One way I've discovered so far is to bind the object after the canvas has loaded via the .play function;
codeFullArgs.play = async () => {
const component = document.getElementsByTagName('your-components-tag')[0];
component.jobData = FullArgs.args.jobData;
}

Apollo TypePolicies: Custom Merge function in cached field breaks pagination

I want to be able to overwrite one of the fields in ListItem when a ListItem of the same Id comes in from my middleware. The issue is that if I create a custom merge function for any field it makes my pagination stop working. The new data is not properly merged in the list of results.
When I console.log in the resultPagination merge-function it seems to merge properly. But, in my interface where I am calling resultPagination the state is stuck on loading. So, in other words, I can fetch the first page of results, but when I call fetchMore I get stuck on loading.
If I remove the custom merge function in ListItem the pagination starts working properly again. If I don't define a merge for any specific fields in ListItem, but just define merge as true the pagination works properly, and the ListItem is updated, but the values in the ListItem are completely overwritten ( I just want to change the value of one field ).
How do I define a custom Merge function for ListItem while still allowing Pagination to work in the result Pagination?
Below I give an example of how my structure is looking right now:
export const typePolicies: TypePolicies = {
ListItem: {
fields: {
tags: {
merge: (existing: Tag[] = [], incoming: Tag[]) => {
return !!incoming ? incoming : existing;
}
}
}
},
Query: {
fields: {
resultPagination: {
keyArgs: ["query", "sortByDate", "specificFields"],
merge: (existing, incoming, args) => {
if (!incoming) return existing;
if (args.args.offset === 0) return incoming;
const existingListItemLists: ListItem[][] =
existing?.articleSummaries ?? [];
const incomingListItemLists: ListItem[][] =
incoming?.articleSummaries ?? [];
return {
...incoming,
results: [...existingListItemLists, ...incomingListItemLists]
};
}
}
}
}
};

How to avoid attribute-operation-attribute-exists when calling writer.setAttribute() in CKEditor5?

I am developing a simple CKEditor5 plug-in. Part of the plug-in is a "Command" that executes like this:
execute(options) {
const contentItemUtils = this.editor.plugins.get('ContentItemUtils');
const contentItemElement = contentItemUtils.getClosestSelectedContentItemElement(this.editor.model.document.selection);
this.editor.model.change(writer => {
writer.setAttribute('width', options.width, contentItemElement);
});
}
The problem happens when I call writer.setAttribute. I always get an error like this:
CKEditorError: attribute-operation-attribute-exists {"node":{"attributes":{"contentId":"CORE08954D2EBB7042799E0A059DC90703DD","contentName":"Paris","contentType":"Destination","contentTypeDisplay":"Destination","contentViewing":"draft","categoryLayout":"overview","detailPageId":"","alignment":""},"name":"contentItem"},"key":"width"}
Read more: https://ckeditor.com/docs/ckeditor5/latest/framework/guides/support/error-codes.html#error-attribute-operation-attribute-exists
What I am trying to do -- set a model attribute to a new value -- seems fairly simple.
Is there a restriction about updating model attributes that already have values?
I ended up first removing the attribute and then adding it :
editor.model.change( writer => {
const element = selection.getSelectedElement();
writer.removeAttribute( 'format', element)
editor.model.change( writer => {
writer.setAttribute( 'format', 'date', element)
});
} );

Slickgrid: Custom editor to store different data than that displayed

I've written a custom editor for Slickgrid which shows drop down box with items from sql db. These items are in the formal [id] [text]
I wish to store the [id] into the slickgrid data, but show the [text] to the user. There doesn't appear to be a callback for "display" rather than "store", so not sure how to do this? Hopefully I don't have to write a custom renderer as well?
eg.
this.init = function () {
$select = $("<SELECT tabIndex='0' class='editor-result'><OPTION value='1'>Passed</OPTION><OPTION value='0'>Failed</OPTION></SELECT>");
$select.appendTo(args.container);
$select.focus();
};
this.serializeValue = function () {
return $select.val();
};
this.applyValue = function (item, state) {
item[args.column.field] = state;
};
It appears that yes, I do have to add a custom renderer (or rather, a formatter), but it's quite simple, so not so bad:
function ResultFormatter(row, cell, value, columnDef, dataContext) {
return value ? "Passed" : "Failed";
}

How to access object data posted by ajax in codeigniter

I am trying to access an object with form data sent to my controller. However, when I try to access objects I get values of null or 0. I used two methods, the first by serializing and the second by storing names and values in one object. (the code below sends/posts serialized)
Here is my JS...
$("#createUser").click(function() {
//store input values
var inputs = $('#newUserForm :input');
var input = $('#newUserForm :input').serializeArray();
console.log(input);
//if I want just the values in one object
var values = {};
$(inputs).each(function() {
values[this.name] = $(this).val();
});
console.log(values);
if(LiveValidation.massValidate( validObj )){
$.post('./adminPanel/createUser', function(input){
alert('Load was performed.');
//test confirmation box
$("#msgbox").html("Grrrrreat");
//drop down confirmation
$("#msgbox").slideDown();
});
} else {
//test fail box
$("#failbox").html("Fail");
$("#failbox").slideDown();
}
});
In the controller side I try to access data the following way...
$this->input->post("firstName")
where firstName is the name of the field.
Below is an image of the objects passed.
Top being serialized array and the bottom a single object with all the names and values of form...
If you're using jQuery, you can use jQuery's built in serialize/query string functions to get the data from a form: http://api.jquery.com/serialize/
In your case:
var data = $('#newUserForm').serialize(); // is a string like "firstName=jon"

Resources