-
Notifications
You must be signed in to change notification settings - Fork 33
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
Implementation of the core feature #1
Conversation
…itial data, instead of setting it after instanceReady.
… resolves given event.
…yed writeValue by ControlValueAccessor.
src/ckeditor/ckeditor.component.ts
Outdated
/** | ||
* Keeps track of the editor's data. | ||
* | ||
* It's also decorated as an input which is useful when not using the ngModel. | ||
* | ||
* Store actual data in private _data property and use setter to update data, | ||
* to allow `(data)` binding. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is redundant. It's a common practice used in object-oriented languages (e.g. Java, C#).
src/ckeditor/ckeditor.component.ts
Outdated
@@ -147,7 +171,9 @@ export class CKEditorComponent implements AfterViewInit, OnDestroy, ControlValue | |||
return this.initialDisabled; | |||
} | |||
|
|||
constructor( private elementRef: ElementRef<HTMLElement>, private ngZone: NgZone ) { } | |||
constructor( private elementRef: ElementRef<HTMLElement>, private ngZone: NgZone ) { | |||
this._data = ''; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be consistent let initialize this value at declaration.
src/ckeditor/ckeditor.component.ts
Outdated
if ( this.onChange && this.instance && this.data !== data ) { | ||
this.forceChange = true; | ||
} | ||
this.updateData( data ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After your changes updateData
function is redundant and its content should be placed directly here.
src/ckeditor/ckeditor.component.ts
Outdated
|
||
return; | ||
} | ||
|
||
let element = document.createElement( this.tagName ); | ||
const element = document.createElement( this.tagName ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's subjective but I like to treat const
as a real business constant value like enums etc. Actually, you are changing the value of the element line below, so it's not constant at all. The let
keyword wasn't placed here accidentally, however, I understand that this approach varies between developers. WDYT about small RFC on slack?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And hey! You have to type fewer letters with let
🏆
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's generally suggested to use const
everywhere unless you need to reassign value. Even my IDE suggests to change each let
to const
when it hasn't been reassigned. Popular linter configurations also requires to use const
whenever possible.
It's subjective but I like to treat const as a real business constant value like enums etc.
In such case it's more common to use uppercased names to distinguish them from other declarations. In CKEditor codebase you can find plenty of them, eg. CKEDITOR.TRISTATE_DISABLED
.
src/ckeditor/ckeditor.component.ts
Outdated
} | ||
|
||
// Todo: should we remove it, as there is getter for data? | ||
private getData() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, this can be safely removed now.
src/ckeditor/ckeditor.component.ts
Outdated
@@ -165,11 +191,13 @@ export class CKEditorComponent implements AfterViewInit, OnDestroy, ControlValue | |||
writeValue( value: string | null ): void { | |||
// This method is called with the `null` value when the form resets. | |||
// A component's responsibility is to restore it to the initial state. | |||
// Known issue, that writeValue is fired twice, first with empty data, | |||
// then with actual initial data: /~https://github.com/angular/angular/issues/14988 | |||
if ( value === null ) { | |||
value = ''; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After your changes, you are replacing the negative value with an empty string so this condition is redundant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Editor initialization
I wonder what's wrong with initializing data on instanceReady
event? This allows you to drop nasty timeout for editor creation and additional forceChange
property. To improve the whole process you could just add condition to make sure that data really changed when using ng forms:
if ( this.data !== this.instance.getData() ) {
this.instance.setData( this.data );
}
so editor's data change won't be duplicated.
I think that dataChange
output gives us opportunity to treat change
output as 1:1 CKEditor API - and now we can have 3 different listeners for data:
dataChange
for 2-way binding, only if data actually changedonChange
used by CVA - also called when data changedchange
1:1 CKEditor API
I already prepared some POC to test whole logic, you can check it at t/1b
.
Inline editor
There is also an issue with inline editor - place selection at the end of the editor and start typing. Selection will be moved at the begining of the editor on every typed character.
Destroying editor
I think that we have to stress the whole timeout inside ngDestroy
a lot. It doesn't seems to work properly:
- Run samples.
- Open console.
- Move between samples e.g. from simple usage to integration playground.
Actual
Pushed few commits. All tests passe in IE11, tests passed in IE9, but there is some typeerror because of missing |
a45cce9
to
f1ad71d
Compare
Summary of how integration works, after changes requested by reviewers:
<ckeditor></ckeditor>
in template to add editor.ckeditor.js
location can be pointed aseditorUrl
property.ckeditor.js
can be loaded in<script>
.type
property. Allowed types aredivarea
andinline
.Angular doesn't expose hook
beforeDestroy
which would allow us for proper cleanup. Workaround for that missing hook is that editor is initialized as child of body, outside of its component, and once is it created it is appended to right place. That's because unloading component by angular before editor is initialized results in console errors and possibly memory leaks. Framed editor can't be moved around, movingiframe
around document breaks it, so we can't supportclassic
editor. See Enable classic editor #6ngModel
. However CVA interface implementation doesn't includedisabled
, because CKEditor doesn't support it either. It is similar, yet different to supportedreadOnly
.data
orngModel
.ready
change
dataChange
focus
blur
config
can be set as attribute in template. It can be used to access any other event.tagName
property can be used to change element on which editor is created, although I'm not sure if we really need it.ngIf
,hidden
.start
: "ng serve", // Build and serve sample, for manual testing.build
: "ng build", // Build samples with component.build-package
: "node ./scripts/build-package.js", // Build package - only our componenttest
: "ng test", // Run automatic test other than e2ee2e
: "ng e2e" // Run e2e testsOnly limitation comes from point 3. every other CKEditor 4 feature seems to work correctly.
Original PR message and edits:
Changes within this PR:
I’ve reformatted code to be compliant with /~https://github.com/cksource/att-ckeditor-dev code style.
Two-way data binding is implemented for property
ngModel
and fordata
(dataChange
) properties.dataChange
event is duplicate ofchange
, but to allow two-way we need event like:${eventName}change
.This was tricky as
ControlValueAccessor
fireswriteValue
asynchronously afterngAfterViewInit
while regular attribute binding is done way earlier even beforengOnInit
.This produces inconsistency on when data is set on component.
Editor should be initialized with initial data, and we shouldn’t set it on
instanceReady
.I changed it so editor is now created with initial data, however I had to move editor creation method into
setTimeout
.I’ve updated tests, so methods
beforeEach
andafterEach
will prevent further testing until editor is ready/destroyed.I've added third sample called 'playground'. I've created it for manual testing and debugging. There are two editors, classic and inline, both are bound together, so they share same data. One is bound by
ngModel
and second bydata
.Additionally I’ve done some research on topics from our talk, however I didn’t apply changes regarding it:
Dropping setTimeout from
ngOnDestroy
.Currently this breaks some tests, but I think this is an issue with tests, not our component.It is removed.
Is calling
this.ngZone.run
necessary?Actually calling this method is result of performing all editors logic inside
ngZone.runOutsideAngular
. And we should do this for performance purpose. Angular tracks changes, events and performs lots of other tasks. We don’t need to do all this for our editor contents. However we need to go back into ngZone on event callbacks so it is executed in Angular scope.Can we pass data inside angular
<ckeditorComponent>
tags?Answer is: yes.
However I think that would lead to more complications and I think we should drop this idea.
Consider example:
<ckeditorComponent>some content</ckeditorComponent>
We can achieve content, by using
<ng-content>
tag in templates. However I didn’t find way to access it inside component constructor. We could put<ng-content>
in<textarea>
which we use for editor creation, but as updating data replaces<textarea>
content with new string every component/element reference would be broken. Such approach would suggest users that they can put angular components inside our editor.Disabled or readonly according to specs:
; if there is no containing element when the disabled attribute is set, the control is enabled.disabled
This Boolean attribute indicates that the user cannot interact with the control. If this attribute is not specified, the control inherits its setting from the containing element, for examplereadonly
This Boolean attribute indicates that the user cannot modify the value of the control. Unlike the disabled attribute, the readonly attribute does not prevent the user from clicking or selecting in the control. The value of a read-only control is still submitted with the form.Fixed failing tests.
Most of tests now are using
done()
inbeforeEach
andafterEach
, so test cases won't run until component is fully reseted.To do:
Reduce preset.Included in PR.Consider other ways of importing editor.
End-to-end testing.included in PR.Closes #2
Closes #3
Closes #4