ckeditor / ckeditor5

Powerful rich text editor framework with a modular architecture, modern integrations, and features like collaborative editing.
https://ckeditor.com/ckeditor-5
Other
9.45k stars 3.7k forks source link

[Docs] Example integration with React, Angular 1/2+, Vue, Ember #599

Closed Reinmar closed 6 years ago

Reinmar commented 7 years ago

There's been a lot of discussion around Angular in https://github.com/ckeditor/ckeditor5/issues/347 but we need some summary in form of a documentation.

Similarly, we need some documentation about integration with React, Vue and Ember.

If anyone is willing to give it a try we'd be grateful for step-by-step introductions to how to initialize and integrate CKEditor 5 with those platforms. The simplest way you can share such tutorials with us is by writing them in comments in this thread and we'll convert them to guides that can be published on https://docs.ckeditor.com/ckeditor5/latest/. Thanks in advance!


UPDATE (27.07.2018)

💥 We released official integrations for React and Angular 💥

figuerres commented 7 years ago

Sounds good!

stnor commented 6 years ago

Angular1 example using two-way binding with ngModel:

import CkEditor from "@ckeditor/ckeditor5-build-classic";
export default class CkEditorDirective {

   constructor() {
     this.restrict = 'A';
     this.require = 'ngModel';
   }

  static create() {
    return new CkEditorDirective();
  }

  link(scope, elem, attr, ngModel) {
    CkEditor.create(elem[0]).then((editor) => {
      editor.document.on('changesDone', () => {
        scope.$apply(() => {
          ngModel.$setViewValue(editor.getData());
        });
      });

      ngModel.$render = () => {
        editor.setData(ngModel.$modelValue);
      };

      scope.$on('$destroy', () => {
        editor.destroy();
      });
    })
  }
}

Registration:

export default angular.module('admin.newsletter', [])
    .directive('ckEditor', CkEditorDirective.create)

Usage:

<textarea ck-editor ng-model="$ctrl.body"></textarea>
Reinmar commented 6 years ago

Thanks!

One thing I spotted here is that this code doesn't handle rejecting the promise returned by CKEditor.create().

And actually a second thing – the change event is fired on every atomic change and retrieving data after each of those atomic operations are applied may be unsafe. I think that it would be safer to listen to changesDone (fired when a whole set of changes is being rendered).

We're aware that this bit with the change event may be confusing and we'll rethink it during the planned engine refactoring (in this ticket, to be precise).

stnor commented 6 years ago

Ok, thanks. I updated the example to use changesDone. When it comes to the rejection-handling, I'm not sure what to do should it fail. One could always add

.catch((error) => {
      console.error(error);
    });
gurnzbot commented 6 years ago

I have ckeditor v4.7.3 running in my react app. I have file browser/uploads working as well. Truly thanks to https://github.com/codeslayer1/react-ckeditor to get the ball rolling and all easy updates of new ckeditor builds/versions.

If I can be of any help please let me know how.

pburrows commented 6 years ago

Here's a VERY primitive Angular2+ example. Note that it only works about 25% of the time for me because of this error. Enough reloading usually gets it to work.

import { Directive, ElementRef, EventEmitter, Output } from '@angular/core';

declare var ClassicEditor: any;

@Directive({
    selector: '[ckeditor]'
})
export class CkEditorDirective {

    @Output() change: EventEmitter<string> = new EventEmitter();

    constructor(el: ElementRef) {
        console.log(el.nativeElement);
        ClassicEditor.create(el.nativeElement)
            .then(editor => {
                editor.document.on('changesDone', () => {
                    const data: string = editor.getData();
                    this.change.emit(data);
                });
                console.log(editor);
            }, (err) => {
                if (err.message.indexOf('TypeError: e.on is not a function') >= 0) {
                    console.log('grr!');
                }
                console.log(err.message);
                console.log(err);
            });
    }
}

and then the html would look like this:

<textarea class="form-control" ckeditor (change)="textChanged($event)" [(ngModel)]="requestText"></textarea>
chihab commented 6 years ago

Nice initiative, I think it would be better to have a separate issue for each framework.

As for Angular, the main issue I've found is loading svg items, since internally angular cli rule on svg assets is to use file-loader instead of raw-loader used in CKeditor webpack config.

The only solution I've came up with is to eject webapack config using 'ng eject' which gives you the whole webpack config under the hood (running webpack would then be exactly same as ng build) but now you do have hands on webpack config.

Modifications I've made to make it suitable for my use case:

      {
        // Or /ckeditor5-[^/]+\/theme\/icons\/[^/]+\.svg$/ if you want to limit this loader
        // to CKEditor 5's icons only.
        test: /ckeditor5-[^/]+\/theme\/icons\/[^/]+\.svg$/,
        use: ['raw-loader']
      },
      {
        "test": /(ionicons|font-awesome).*\/[^/]+\.svg$/,
        "loader": "file-loader",
        "options": {
          "name": "[name].[hash:20].[ext]",
          "limit": 10000
        }
      },
      {
        "test": /\.(eot|cur)$/,
        "loader": "file-loader",
        "options": {
          "name": "[name].[hash:20].[ext]",
          "limit": 10000
        }
      }

Note that I had to replace "test": /\.(eot|svg|cur)$/ with "test": /\.(eot|cur)$/ and make specific regexp for my svg resources folders.

I was trying not to override/modify webpack config and keep with angular cli simple usage but as far as I understood, the only way to have that would be that CKeditor uses file loader for svg resources rather than raw-loader (don't really know what I am talking about thought).

pat123456 commented 6 years ago

Hi,

This is how I integrated v.5 in vue.js component (with two-way biding): (more setup could be added, for ex like in this v.4 integration : https://github.com/dangvanthanh/vue-ckeditor2/blob/master/src/VCkeditor.vue

But I feel it's still a little bit "tinker". Ckeditor 5 is mvc based, could not there be the possibility to directly connect/bind the "model" of the editor with vuejs data/model (or other js framework) ?

<template>
    <textarea id="editor" :value="value"
        v-on:input="updateValue($event.target.value)" >
    </textarea>
</template>

<script>
import ClassicEditor from '@ckeditor/ckeditor5-build-classic';

export default {
    name: "ckeditor",
    data: function () {
        return {
            instance: null
        }
    },
    props: ['value'],
    watch: {
        value(){
            let html = this.instance.getData();

            if(html != this.value){
                this.instance.setData(this.value)
            }
        }
    },
    mounted(){

        ClassicEditor
            .create( this.$el, {
                toolbar: [ 'bold', 'italic', 'link', 'bulletedList']
            } )
            .then( editor => {
                this.instance = editor;

                editor.document.on( 'changesDone', ( evt, data ) => {
                    this.updateValue(editor.getData())
                } );
            } )
            .catch( error => {
                console.error( error );
            } );
        },
        methods: {
            updateValue: function (value) {
                this.$emit('input', value)
            }
        }
}
</script>

and use it like that : <ckeditor v-model="editorcontent"></ckeditor>

pat123456 commented 6 years ago

@chihab why do you need to take care about webpack.config and svg integration when using ckeditor with angular ? normaly we only use builds versions of ckeditor in framework projects, not sources ? I'm pretty new with ckeditor and don't know angular, so may my question make no sense...

chihab commented 6 years ago

@pat123456 I am also pretty new to ckeditor and your observation totally makes sense. My project has to use a custom build and at the begging I was making a poc to test its integration into an existing angular project A better approach would certainly be to maintain a separate project for ckeditor custom build specific to our needs and that would be agnostic to the application framework and then only have to use it inside the project as a directive or a component. I was also thinking about making it a separate web component using ionic's stencil project.

pat123456 commented 6 years ago

Ok understand. Yes seems more logic to have a separate framework/build project. Anyway, for the documentation it's certainly the method to describe in priority.

figuerres commented 6 years ago

@pburrows hey i am trying to get ckeditor5 working in my angular 5 app. used your sample code but something is missing. it compiles with ng build but it looks like the editor code is not getting added to my bundles so at runtime i get an error that the editor is not defined.

any ideas ? does anyone have a working sample to check with ?

do i need to add something to modules ? do i need to force a script to be added outside of angular/build ?

pburrows commented 6 years ago

The code I posted is a directive. So, make sure that whatever module it is in CkEditorDirective is in both the "exports" and "declarations" section. And then make sure you are importing that module into whatever other module you want to use.

You do not need to add scripts anywhere else (such as to the .angular-cli.json file).

Also, I have the directive in a separate "shared" module. If you choose to put it all in the same module, you still need to add it to declarations for that module.

figuerres commented 6 years ago

one module -- the app. my @NgModule has it in the declare like my other directive code, the other one works btw. so the basic setup is right.

in trying to make it work i did find that adding it to my angular-cli file DOES make it work. as far as i can see before i did that i got the runtime undfined error. i will go back and re-try some things and see if any other things changed.

one other item: i am building with angular 5 not 4 -- angular 5 compile / build is a bit different than 4 and may not pull in code if it can not see it used. have you tried your code with V5 of angular ??

pburrows commented 6 years ago

Good point. It is probably a v4 vs v5 difference. I haven't updated to v5 yet.

figuerres commented 6 years ago

@pburrows also one other thing to check, which package did you npm install ?? that may also be a factor in what happens. i used the classic editor build, but there are other packages that can be used ...

figuerres commented 6 years ago

so far i have not found a way to pull in the script unless i use a pre built editor package and use the angular-cli json file. that does pull in the script but does not allow angular compiler to do anything with it other than add it to the project.

lbjones commented 6 years ago

With the lastest release, this code no longer works:

editor.document.on('changesDone', () => {
   this.$emit('input', this.instance.getData());
});

After searching through the documentation, I haven't been able to find the proper way to detect changes to the editor. Any ideas?

Reinmar commented 6 years ago

Since beta.1 there's no changesDone event anymore. You can now use the Document#change event now. Refer to its documentation for a description how it works and what it means.

Reinmar commented 6 years ago

We began the work on Angular (https://github.com/ckeditor/ckeditor5/issues/1020) and React (https://github.com/ckeditor/ckeditor5/issues/1015) components for CKEditor 5.

We've got some doubts in https://github.com/ckeditor/ckeditor5/issues/1020 on how to propose this component in the most flexible way. We'll be grateful for all the feedback.

ma2ciek commented 6 years ago

We've just released ckeditor5-angular and ckeditor5-react on the NPM 🎉

Both of them provide comprehensive readme files, so you can integrate them easily into your applications.

wwalc commented 6 years ago

Just a note that this ticket should not be closed until we add proper content in the official documentation (a dedicated section/article(s) for integrations). A significant group of users when looking for Angular/React instruction will simply use the search input in the official documentation.

The first result that currently is returned when looking for React contains pretty outdated information: https://docs.ckeditor.com/ckeditor5/latest/builds/guides/faq.html#how-to-use-ckeditor-5-with-frameworks-angular-react-etc

Reinmar commented 6 years ago

I updated existing docs (including the FAQ). All of them link to https://github.com/ckeditor/ckeditor5/issues/1002 where I'd like to gather all feedback regarding next frameworks we can work on.

abhinavdypp commented 3 years ago

Hi , I have some problem with svg tag on ckeditor5 . In my case ckeditor filter out svg tag .So i have written schema for this but now SVG tag is now working fine but its child element filter out . please help me on this .

            editor5._instance.model.schema.register( 'circle', {
                    allowIn: [ '$text', 'paragraph', '$block' ],
                    allowAttributes: [ '*' ]//'width', 'height', 'cx', 'cy', 'r', 'stroke', 'fill'
                } );
                editor5._instance.model.schema.register( 'svg', {
                    allowIn: [ '$text', 'paragraph', '$block','image' ],
                    allowAttributes: [ '*' ]//'width', 'height', 'cx', 'cy', 'r', 'stroke', 'fill'
                } );

                editor5._instance.conversion.for( 'upcast' ).elementToElement( {
                    view: 'svg',
                    model: ( viewElement, modelWriter ) => {
                        return modelWriter.createElement( 'svg', viewElement.getAttributes() );
                    }
                } );

                editor5._instance.conversion.for( 'downcast' ).elementToElement( {
                    model: 'svg',
                    view: ( modelElement, viewWriter ) => {
                        return viewWriter.createContainerElement( 'svg', modelElement.getAttributes() );
                    }
                } );
                editor5._instance.model.schema.extend( 'circle', {
                    allowIn: ['$text', 'paragraph', '$block' ,'blockQuote'],
                    isBlock: false
                } );