Why does CKEditor5 use a promise to initialize it? - ckeditor

Example:
ClassicEditor
.create( document.querySelector( '#editor' ) )
.then( editor => {
console.log( editor );
} )
.catch( error => {
console.error( error );
} );
Why would editor creation need to be asynchronous?

The editor initialization can be asynchronous because some editor features or editor UI may require asynchronous initialisation.
I'm inconcrete on purpose here. As a framework developer, I don't know what kind of features the users of the framework will want to implement. However, I know some examples from the past:
an <iframe>-based editor (iframes are initialized asynchronously), i.e. an editor in which the content is edited within an <iframe>,
real-time collaboration features which need to retrieve the content from the server.
If you're implementing a plugin which need to defer initialisation, then you can simply return a promise from its init() or afterInit() methods:
class MyPlugin extends Plugin {
init() {
return new Promise( resolve => {
// Call resolve() once your plugin is ready:
resolve();
} );
}
}

Related

CKEditor5: add text when copying/cutting

I am trying to capture the event when a user uses copy/cut so that I can add some text at the end of the text placed into the pastebin.
On a traditional textarea I would listen to the "copy" event.
On CKEditor the appropriate event seems to be clipboardOutput.
But I am not clear now how to hook into CKEditor specific events and how to integrate into output pipeline.
Would appreciate some pointers.
I have the editor running with this code:
ClassicEditor
.create( document.querySelector( '#editor' ), { } )
.catch( error => {
console.error( error );
} );
I tried adding this code below but it doesn't seem to trigger
ClassicEditor.model.document.on( 'clipboardOutput', (eventInfo, data) => {
console.log(eventInfo, data);
} );
The clipboardOutput event is fired on view.Document instance.
You could use UpcastWriter to modify view.DocumentFragment provided in data.content property of the event data.
Example:
editor.editing.view.document.on( 'clipboardOutput', ( eventInfo, data ) => {
const writer = new UpcastWriter( editor.editing.view.document );
const paragraph = writer.createElement( 'p', null, [
writer.createText( 'foobar' )
] );
writer.appendChild( paragraph, data.content );
} );

How can I add content into a CKeditor5 by clicking on an outside element

I have a few buttons outside of the Ckeditor on my page, and I want to click on them and add content to the Ckeditor5.
By clicking on the buttons, I make an ajax call to retrieve the content from the server, but I'm having trouble inserting it. This is my code right now:
success: function (data) {
ClassicEditor
.create( document.querySelector( '.ckeditor' ), {
language: 'he',
} )
.then( editor => {
editor.model.change( writer => {
const insertPosition = editor.model.document.selection.getFirstPosition();
writer.insertText(data, insertPosition );
} );
} )
.catch( error => {
console.error( error );
} );
}
But this code duplicates my editor, and I don't know how to get the "editor" object without using ".create". can anyone help me with this?

React Redux thunk - Chaining dispatches

Currently i'm building an application that is heavily dependant on API calls. The api calls are done within Redux actions with Thunk middleware like so:
export const brand_fetchAll = () => {
return dispatch => {
fetch(apiURL+'brand')
.then(response => {return response.json();})
.then(content => {
dispatch({
type: 'BRAND_STORE_ALL',
content
})
})
.catch(error => console.log(error))
}}
In my component, i'm first fetching the data through separate actions. After that i'm opening up an editor:
// A component method
editModeOn(){
// Fetch data
this.props.dispatch(campaign_fetchAll());
this.props.dispatch(brand_fetchAll());
// Open editor
this.props.dispatch(page_editModeOn());
}
Right now the editor opens before the api calls have completed, so no data is being shown. It's possible to chain the dispatches within the actions, but i want to keep the modularity, so i don't have to create hundreds of custom API calls. Ideally what i want is to chain them using something like promises:
// A component method
editModeOn(){
this.props.dispatch(campaign_fetchAll().then(brand_fetchAll()).then(page_editModeOn());
}
Unfortunately i didn't yet get that to work. I hope someone can help me out. If you need more information i'm happy to hand it over. Better ideas are also very welcome :)
Thanks in advance!
Would a callback function be an option for you?
So update your code to be;
export const brand_fetchAll = (callback) => {
return dispatch => {
fetch(apiURL+'brand')
.then(response => {return response.json();})
.then(content => {
dispatch({
type: 'BRAND_STORE_ALL',
content
});
callback();
})
.catch(error => console.log(error))
}}
// A component method
editModeOn(){
// Fetch data
this.props.dispatch(campaign_fetchAll());
this.props.dispatch(brand_fetchAll(() => {
// Open editor
this.props.dispatch(page_editModeOn());
}));
}
You are chaining the callback onto the end of the api call success, however, you are not tightly coupling what it is as you are passing this in depending on the usage.

setState after an API call inside a componentDidMount

I'm trying to implement a custom select which displays a list of languages fetched from an API.
I make the api call in the componentDidMount lifecycle hook and then update the state of my component according to the data I fetched.
Everything seems to work, yet I always get this error:
Warning: setState(...): Can only update a mounted or mounting
component. This usually means you called setState() on an unmounted
component. This is a no-op. Please check the code for the
LanguageSelect component.
Here's a snippet of my code:
export default class LanguageSelect extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
languages: []
};
}
// ...
componentDidMount() {
http.get('/api/parameters/languages')
.then(
// the Array.map is just to restructure the data fetched form the API
data => this.setState({ languages : data.map(l => ({ label: l.LocalName, value: l.Code, })) }),
// Error case
error => console.error('Error: Languages data could not be fetched', error)
)
}
// Render function
}
I don't understand, The only setState call I make is inside a componentDidMount() lifecycle thus is only executed when the component is mounted ?
Thanks in advance
You could use isMounted() to check if it's mounted
if (this.isMounted()) {
this.setState({...});
}
Although it's an anti pattern and you can find some proposals on best practice solutions here: https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html

General: asynchonious validation in angular2

Since couple evening I've played with form validation in augular2.
All basic cases were easy to implement and they works fine but I stick with asynchronous validation. I have created a very tiny example http://plnkr.co/edit/Xo8xwJjhlHkXrunzS8ZE and it didn't work.
According to test "should fire an event after the status has been updated to pending" from model_spec.ts Registration via creation of control group suppose to work in a way
builder.group({login: ["",Validators.required,validationFuctionWhichReturnsPromise]
I spent a full evening to discovered that this code has been released in alfa-46 (and I used alfa-45) and after update depencies the async validation started to work. The feature is very fresh and is not fully documented but
(for those who haven't tried it yet) Basically async validator is a function which have a Control argument and return a promise which validation result. There are two ways to register a validator. 1) the one which I used in my example and 2) as a directive which Provide validators via NG_ASYNC_VALIDATORS (See UniqLoginValidator and NgFormControl to see how it work). You can compose more than one validator (not tested yet but functions to do this are in code, see https://github.com/angular/angular/commit/cf449dd).
But when I finally reach to up and running validators a new problem arrived. Async validator is perfect to used it in server side validation. But the validation is invoked after each change of model.fe after each keyup. So if we will send request to a server after each key up, it won't be too efficient way ;) I checked how it is done in angular 1 and they is a possibility to debounce validation events.
My questions are:
How to implement throttle or debounce with async validators? I saw some ideas but none of them were fine (mostly because they need to change angular code itself). Is there any valid way to do this without waiting for new angular release ?
I was thinking about to warping a validator function with debounce (from underscorejs) but it will not work because angular expects to get a valid promise every time.
My second though was that if all event use RxJs under the hood then maybe I can apply debounce on stream of event which is responsible for validation. In model.ts the promise returned from validator is change to observable and a new subscribed is added. We don't have any access to obs(Observable) to apply debounce there.
Is there any way or id to change,easy extend a control over the form validation ?
I spotted a close related problem in How to trigger Form Validators in angular2
PS there is other issue related to async validators and it is still open https://github.com/angular/angular/issues/1068
Here is a helper class that you can use to debounce all your async validators:
import {Component} from 'angular2/core';
import {Observable} from 'rxjs/Observable';
import {Observer} from 'rxjs/Observer';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import {Control} from 'angular2/common';
export class AsyncValidator {
_validate;
constructor(validator: (control: Control) => any, debounceTime = 1000) {
let source: any = new Observable((observer: Observer<Control>) => {
this._validate = (control) => observer.next(control);
});
source.debounceTime(debounceTime)
.distinctUntilChanged(null, (x) => x.control.value)
.map(x => { return { promise: validator(x.control), resolver: x.promiseResolver }; })
.subscribe(
(x) => x.promise.then(resultValue => x.resolver(resultValue),
(e) => { console.log('async validator error: %s', e); }));
}
private _getValidator() {
return (control) => {
let promiseResolver;
let p = new Promise((resolve) => {
promiseResolver = resolve;
});
this._validate({ control: control, promiseResolver: promiseResolver });
return p;
};
}
static debounce(validator: (control: Control) => any, debounceTime = 400) {
var asyncValidator = new this(validator, debounceTime);
return asyncValidator._getValidator();
}
}
Then all you have to do where use async validators is just wrap your validator with this call and write your validator the same as you would normally:
AsyncValidator.debounce(control => this.asyncValidator(control));
Here is an example usage:
export class AppComponent {
form: ControlGroup;
constructor(private _formBuilder: FormBuilder) {
var validator = AsyncValidator.debounce(control => this.asyncValidator(control));
this.form = _formBuilder.group({
name: ['', Validators.required, validator],
});
}
asyncValidator(control): any {
let p = new Promise(resolve => {
// get from server information need to validate control
if (control.value === 'valid value') {
resolve(null);
} else {
resolve({
asyncValidator: {
valid: false
}
});
}
});
return p;
}
}
There is an awesome issue on angular site that deals with the problem of both debouncing and switchMapping the validation:
https://github.com/angular/angular/issues/6895
This is mine working solution (but all the credit goes to guys from thread)
class AsyncValidator{
private validatorInput: Subject<string>;
private validatorChain: Observable<any>;
constructor(service: ManageUsersService) {
this.validatorInput = new Subject();
this.validatorChain = this.validatorInput
.debounceTime(400)
.distinctUntilChanged()
.switchMap(value => service.findUsersByName(value)
.map(() => ({error: 'Error'})) //example of failed validation
.catch(() => Observable.of(null))) //example of successful validation
.do(v => console.log('mapped', v))
.share()
.take(1);
}
validate = (control: AbstractControl) => {
// An immediate timeout is set because the next has to occur after the
// validator chain is subscribed to.
setTimeout(() => this.validatorInput.next(control.value), 0);
return this.validatorChain;
}
You use it like this:
this.createUserForm = fb.group({
login: [ null,
Validators.required,
new AsyncValidator(userService).validate
],
});
}

Resources