Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce ViewElementConfigDefinition interface and utility classes #4216

Closed
jodator opened this issue Dec 1, 2017 · 18 comments · Fixed by ckeditor/ckeditor5-engine#1205
Closed
Assignees
Labels
package:engine type:feature This issue reports a feature request (an idea for a new functionality or a missing option).
Milestone

Comments

@jodator
Copy link
Contributor

jodator commented Dec 1, 2017

This gathers requirements from both heading and https://github.com/ckeditor/ckeditor5-font/issues/2#issuecomment-347838197 features.

It was proposed by @Reinmar in here.

The ViewElementConfigDefinition would server as an view configuration for some features that requires flexibility in parsing and should allow to greater customization of editor features like Heading and FontSize.

Full structure (whether values makes sense or not):

{
    // Defines name of rendered element
    name: 'h1',
    // Optional classes
    class: 'ck-heading-one',
    // Optional styles:
    style: 'font-family:serif',
    alternatives: [
        { name: 'h2', /* styles, class, etc */ }
    ]
}

This would allow to:

  • parse one or more HTML elements into model Element/Attribute,
  • enable extensibility of existing features beyond what we can imagine right now.
  • create util methods for conversions for features using those interfaces (right now Headings and Font features AFAIK).

Things to consider:

  1. I'd go with class and style rather then styles and classes - such forms are already used in other places AFAICS.
  2. How to define multiple classes/styles?
  3. Are other attributes (data-header=true) needed?

Ad. 2. Examples:

const singleClass = {
    name: 'span',
    class: 'foo'
}

const multipleClass = {
    name: 'span',
    class: [ 'foo', 'bar', 'baz' ]
}

const multipleStyles = {
    name: 'span',
    style: {
        'font-size': '12px',
        'font-weight': 'bolder'
    }
}

This might grow heavy so alternative configuration would by just strings with defined format:

const multipleClass = {
    name: 'span',
    class: 'foo,bar,baz' // or as in HTML: 'foo bar baz'
}

const multipleStyles = {
    name: 'span',
    style: 'font-size:12px;font-weight: bolder'
}

As for second flavour we already have utility methods for parsing those:
https://github.com/ckeditor/ckeditor5-engine/blob/8eda4e5308f192cee58dd3d4b425f42769f66f17/src/view/element.js#L728
https://github.com/ckeditor/ckeditor5-engine/blob/8eda4e5308f192cee58dd3d4b425f42769f66f17/src/view/element.js#L804

@jodator
Copy link
Contributor Author

jodator commented Dec 1, 2017

About preliminary implementation in font size feature:
https://github.com/ckeditor/ckeditor5-font/blob/9b10a3fd90b8b2d8dd4cb059d18991fc9efd8ce8/src/fontsize/fontsizeediting.js#L42-L92
It kinda works but as for now is unfinished so do not review it ;).

@Reinmar
Copy link
Member

Reinmar commented Dec 4, 2017

How to define multiple classes/styles?

There's already getStyle() and setStyle() in view element, so styles could be defined as key->value pairs and classes as an array.

I'd go with class and style rather then styles and classes - such forms are already used in other places AFAICS.

Makes sense. I used singular nouns because they make more sense in configuration for that specific feature. But would be dealbreakers in general.

Are other attributes (data-header=true) needed?

May be. It only depends on peoples' use cases which we cannot imagine now. But we can say that there's a pretty big chance that other attribute names will be used.

@Reinmar
Copy link
Member

Reinmar commented Dec 4, 2017

I'm also thinking that perhaps a better name would be ViewElementDefinition. Also, we may consider whether the Element() constructor shouldn't accept it. Since conversion builders simply accept element to be used (it's then cloned quite like in a prototypal inheritance) that would already handle most of the job in M->V conversion. V->M conversion is a bigger problem (alternatives, matching and consuming), especially that we're still planning to review converters and builders.

@jodator
Copy link
Contributor Author

jodator commented Dec 4, 2017

I'd go with this as part of https://github.com/ckeditor/ckeditor5-font/issues/2 since I've already half of it done there. I will also introduce this in heading feature.

@jodator jodator self-assigned this Dec 4, 2017
@Reinmar
Copy link
Member

Reinmar commented Dec 4, 2017

I'd go with this as part of ckeditor/ckeditor5-font#2 since I've already half of it done there. I will also introduce this in heading feature.

WDYM? Separate changes need to land in ckeditor5-engine, so it's a pretty much unrelated ticket.

@jodator
Copy link
Contributor Author

jodator commented Dec 4, 2017

OK so maybe it wasn't clear (as I thought it was). I'd like to go with this ticket now to implement this in header and then finish ckeditor5-font/2. As for now I don't see much work for this ticket alone besides docs. It might shed some light on utility methods when I'll make changes in those 2 features (font and heading). To have some idea's how this interface going to be used in other words.

As for PR's it rather be two unrelated PRs: one in engine and following in font feature.

@Reinmar
Copy link
Member

Reinmar commented Dec 4, 2017

I'm also thinking that perhaps a better name would be ViewElementDefinition. Also, we may consider whether the Element() constructor shouldn't accept it.

PS. The feature using ViewElementDefinition as its configuration needs to decide whether to use AttributeElement() or ContainerElement() which means that potential tool for creating these elements need to allow this (or simply that both constructors accept ViewElementDefinition). Right now I don't see other types of elements being used this way, but it may happen in the future.

@pjasiun
Copy link

pjasiun commented Dec 4, 2017

It makes sens to have common syntax to create view/DOM elements in the simpler way. The one proposed above, with name, style (object), class (string or array) and attribute (object) is fine for me (agree that we need to keep if consisten and allways use singular or plurar).

However, I think that we should not go too far. alternatives is not a part of the view element definition, and should not be part of this ticket. If we will try to create too powerfull declarative API we will have to support more and more login as a declarative syntax.

@Reinmar
Copy link
Member

Reinmar commented Dec 4, 2017

alternatives will need to be added anyway. It wasn't meant to be a part of this ticket, but something (i.e. some converter builders) need to be able to handle these definitions.

If we will try to create too powerfull declarative API we will have to support more and more login as a declarative syntax.

We won't be able to use these options without support for alternatives. We can either drop configurability completely or implement them.

@jodator
Copy link
Contributor Author

jodator commented Dec 4, 2017

Since alternatives may not look nice in docs and might be misleading let's to with a view definition as:

// an array of matcher patterns
view: {
   to: { name, class, style, attribute }
   from:  [
       { name, class, style, attribute },
       { name, class, style, attribute },
        //...
   ]
}
// or a callback for most complex cases:
view: {
   to: { name, class, style, attribute }
   from: () => {}
}
// or when defined as one object it will assume view.to=== view.from:
view: {
    name,
    class,
    style,
    attribute
}

as converting M->V will always create the same result but when parsing content it might be required to convert different elements to same model.

@jodator
Copy link
Contributor Author

jodator commented Dec 5, 2017

Probably support for priorities makes sense also:

const headingConfig = {
	options: [
		{ model: 'paragraph', title: 'paragraph' },
		{
			model: 'heading1',
			view: {
				from: [
					{ name: 'h1' },
					{ name: 'p', attribute: { 'data-heading': 'h1' }, priority: 'high' }
				],
				to: {
					name: 'h1'
				}
			},
			title: 'User H1'
		}
	]
}

would allow to convert such HTML:

<h1>foobar</h1>
<p data-heading="h1">foobar</p>
<p>Other text</p>

@pjasiun
Copy link

pjasiun commented Dec 5, 2017

As much as I don't like declarative APIs (in general), I have no better idea in this case. However I think that to will be a allways one of from values, so instead of view.to, and view.from we might have view (used to render element but also used to parse it) and posibility to add more types of DOM elements (or attributes) which will be parsed as this elements (accepts, parse, alternative, overrides, have no good idea now to call it). This parameter should be in the view matcher format, accept regexps and callbacks.

@jodator
Copy link
Contributor Author

jodator commented Dec 11, 2017

@pjasiun: after some quick thinking until better name appears I'll go with converts as separate attribute to view as this would convert specified view elements to this model.

const headingConfig = {
	options: [
		// 1: straight forward: any 'h1' element:
		{
			model: 'heading1',
			view: 'h1'
		},
		// 2: a bit narrower: any 'h2' element with 'heading-foo' class
		{
			model: 'headingFoo',
			view: {
				name: 'h2',
				class: 'heading-foo' // also as an array: [ 'foo', 'bar' ]
				// would also accept: 'attribute', `style` objects
			}
		},
		// 3: Full flavor with other elements parsed as 'headingBar':
		{
			model: 'headingBar',
			view: 'h3',
			converts: [
				{ name: 'p', attribute: { 'data-heading': 'h3' } },
				{ name: 'p', class: [ 'heading', 'heading-lvl-2' ] },
				{ name: 'h4', style: { 'font-size': '23em' } }
			]
		}
	]
}

as an alternative to option 3 we might define view in full-flavor as:

{
	output: { name: 'h1' },
	converts: [
		{ name: 'h2' },
		{ name: 'h3', attribute: { 'data-heading': 'lvl1' } }
	]
}

ps.: converts or convert?

@Reinmar
Copy link
Member

Reinmar commented Dec 11, 2017

I'd go with accepts or even acceptsAlso. We do convert in both directions and this converts was supposed to only add more options for the V->M converter.

@jodator
Copy link
Contributor Author

jodator commented Dec 11, 2017

@pjasiun & @Reinmar what about option 3 (full flavour?)

{
    view: {},
    acceptsAlso: {}
}

or:

{
    view: {
        output: {},
        acceptsAlso: {}
    },
}

?

@Reinmar
Copy link
Member

Reinmar commented Dec 11, 2017

No opinion. Both seem good to me at the moment.

@pjasiun
Copy link

pjasiun commented Dec 11, 2017

I'm for the first one. The second looks like there is input missing (output does not sounds like something what can be also an input).

@jodator
Copy link
Contributor Author

jodator commented Dec 14, 2017

@Reinmar & @pjasiun I've created some helper methods for conversion to use those configuration options. You can see that in linked PR.

Reinmar referenced this issue in ckeditor/ckeditor5-engine Dec 21, 2017
Feature: Introduced `ViewElementDefinition` and `definition-based-converters` module with a set of utils allowing to turn element definitions to converters. Closes #1198.
@mlewand mlewand transferred this issue from ckeditor/ckeditor5-engine Oct 9, 2019
@mlewand mlewand added this to the iteration 14 milestone Oct 9, 2019
@mlewand mlewand added status:confirmed type:feature This issue reports a feature request (an idea for a new functionality or a missing option). package:engine labels Oct 9, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
package:engine type:feature This issue reports a feature request (an idea for a new functionality or a missing option).
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants