tneotia / html-editor-enhanced

A Flutter package that provides a WYSIWYG editor backed by flutter_inappwebview and the Summernote library.
https://pub.dev/packages/html_editor_enhanced
MIT License
275 stars 337 forks source link

[BUG] Getting Error when using multiple html editors in single screen on WEB. #218

Open shobhit-fliplearn opened 2 years ago

shobhit-fliplearn commented 2 years ago

Describe the bug Getting error 'UncaughtTypeError : document.getElementByClassName[0] is undefined' when using multiple html editors in single screen on WEB

To Reproduce Steps to reproduce the behavior:

  1. Run the below code snippet.
//import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:html_editor_enhanced/html_editor.dart';
import 'package:file_picker/file_picker.dart';

void main() => runApp(HtmlEditorExampleApp());

class HtmlEditorExampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(),
      darkTheme: ThemeData.dark(),
      home: HtmlEditorExample(title: 'Flutter HTML Editor Example'),
    );
  }
}

class HtmlEditorExample extends StatefulWidget {
  HtmlEditorExample({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _HtmlEditorExampleState createState() => _HtmlEditorExampleState();
}

class _HtmlEditorExampleState extends State<HtmlEditorExample> {
  String result = '';
  final HtmlEditorController controller = HtmlEditorController();

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        if (!kIsWeb) {
          controller.clearFocus();
        }
      },
      child: Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
          elevation: 0,
          actions: [
            IconButton(
                icon: Icon(Icons.refresh),
                onPressed: () {
                  if (kIsWeb) {
                    controller.reloadWeb();
                  } else {
                    controller.editorController!.reload();
                  }
                })
          ],
        ),
        /*
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            controller.toggleCodeView();
          },
          child: Text(r'<\>',
              style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
        ),
        */
        body: SingleChildScrollView(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              initHtmlEditor(keyname: "key1"),
              initHtmlEditor(txt: 'My Editor', keyname: 'key2'),
              initHtmlEditor(txt: 'My HTML Bloc', keyname: 'key3'),
              //initHtmlEditor(txt: 'My Bloc', keyname: 'key4'),
              //initHtmlEditor(txt: 'My Last Option', keyname: 'key5'),
            ],
          ),
        ),
      ),
    );
  }

  HtmlEditor initHtmlEditor(
      {String txt = "Your text here 1...", String keyname = "a1"}) {
    //sleep(Duration(seconds: 1));
    return HtmlEditor(
      key: Key(keyname),
      controller: controller,
      htmlEditorOptions: HtmlEditorOptions(
        hint: txt,
        shouldEnsureVisible: true,
        //initialText: "<p>text content initial, if any</p>",
      ),
      htmlToolbarOptions: HtmlToolbarOptions(
        toolbarPosition: ToolbarPosition.aboveEditor, //by default
        toolbarType: ToolbarType.nativeScrollable, //by default
        onButtonPressed:
            (ButtonType type, bool? status, Function()? updateStatus) {
          //print(
          // "button '${describeEnum(type)}' pressed, the current selected status is $status");
          return true;
        },
        onDropdownChanged: (DropdownType type, dynamic changed,
            Function(dynamic)? updateSelectedItem) {
          //print("dropdown '${describeEnum(type)}' changed to $changed");
          return true;
        },
        mediaLinkInsertInterceptor: (String url, InsertFileType type) {
          //print(url);
          return true;
        },
        mediaUploadInterceptor: (PlatformFile file, InsertFileType type) async {
          //print(file.name); //filename
          //print(file.size); //size in bytes
          //print(file.extension); //file extension (eg jpeg or mp4)
          return true;
        },
      ),
      otherOptions: const OtherOptions(height: 250),
      callbacks: Callbacks(onBeforeCommand: (String? currentHtml) {
        //print('html before change is $currentHtml');
      }, onChangeContent: (String? changed) {
        //print('content changed to $changed');
      }, onChangeCodeview: (String? changed) {
        //print('code changed to $changed');
      }, onChangeSelection: (EditorSettings settings) {
        //print('parent element is ${settings.parentElement}');
        //print('font name is ${settings.fontName}');
      }, onDialogShown: () {
        //print('dialog shown');
      }, onEnter: () {
        //print('enter/return pressed');
      }, onFocus: () {
        //print('editor focused');
      }, onBlur: () {
        //print('editor unfocused');
      }, onBlurCodeview: () {
        //print('codeview either focused or unfocused');
      }, onInit: () {
        print('init');
        /*
        for (int i = 0; i < 2000; i++) {
          // do nothing
        }
        */
      },
          //this is commented because it overrides the default Summernote handlers
          /*onImageLinkInsert: (String? url) {
                  print(url ?? "unknown url");
                },
                onImageUpload: (FileUpload file) async {
                  print(file.name);
                  print(file.size);
                  print(file.type);
                  print(file.base64);
                },*/
          onImageUploadError:
              (FileUpload? file, String? base64Str, UploadError error) {
        print(describeEnum(error));
        print(base64Str ?? '');
        if (file != null) {
          print(file.name);
          print(file.size);
          print(file.type);
        }
      }, onKeyDown: (int? keyCode) {
        //print('$keyCode key downed');
        //print('current character count: ${controller.characterCount}');
      }, onKeyUp: (int? keyCode) {
        //print('$keyCode key released');
      }, onMouseDown: () {
        //print('mouse downed');
      }, onMouseUp: () {
        //print('mouse released');
      }, onNavigationRequestMobile: (String url) {
        //print(url);
        return NavigationActionPolicy.ALLOW;
      }, onPaste: () {
        //print('pasted into editor');
      }, onScroll: () {
        //print('editor scrolled');
      }),
      plugins: [
        SummernoteAtMention(
            getSuggestionsMobile: (String value) {
              var mentions = <String>['test1', 'test2', 'test3'];
              return mentions
                  .where((element) => element.contains(value))
                  .toList();
            },
            mentionsWeb: ['test1', 'test2', 'test3'],
            onSelect: (String value) {
              print(value);
            }),
      ],
    );
  }
}

Expected behavior All the html editors must render correctly.

Screenshots image_2022_02_10T06_37_20_250Z

Device: On Web(Chrome Browser).

Additional context It will be of great help if you can find the solution for the same.

tneotia commented 2 years ago

You should not use the same controller for all editors. This may be causing the issue with the rendering, can you please try to use a separate controller for each editor?

shobhit-fliplearn commented 2 years ago

Updated Code :


//import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:html_editor_enhanced/html_editor.dart';
import 'package:file_picker/file_picker.dart';

void main() => runApp(HtmlEditorExampleApp());

class HtmlEditorExampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(),
      darkTheme: ThemeData.dark(),
      home: HtmlEditorExample(title: 'Flutter HTML Editor Example'),
    );
  }
}

class HtmlEditorExample extends StatefulWidget {
  HtmlEditorExample({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _HtmlEditorExampleState createState() => _HtmlEditorExampleState();
}

class _HtmlEditorExampleState extends State<HtmlEditorExample> {
  String result = '';
  final HtmlEditorController controller1 = HtmlEditorController();
  final HtmlEditorController controller2 = HtmlEditorController();
  final HtmlEditorController controller3 = HtmlEditorController();
  final HtmlEditorController controller4 = HtmlEditorController();
  final HtmlEditorController controller5 = HtmlEditorController();

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        if (!kIsWeb) {
          controller1.clearFocus();
        }
      },
      child: Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
          elevation: 0,
          actions: [
            IconButton(
                icon: Icon(Icons.refresh),
                onPressed: () {
                  if (kIsWeb) {
                    controller1.reloadWeb();
                  } else {
                    controller1.editorController!.reload();
                  }
                })
          ],
        ),
        /*
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            controller.toggleCodeView();
          },
          child: Text(r'<\>',
              style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
        ),
        */
        body: SingleChildScrollView(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              initHtmlEditor(controller1, keyname: "key1"),
              initHtmlEditor(controller2, txt: 'My Editor', keyname: 'key2'),
              initHtmlEditor(controller3, txt: 'My HTML Bloc', keyname: 'key3'),
              //initHtmlEditor(controller4, txt: 'My Bloc', keyname: 'key4'),
              //initHtmlEditor(controller5, txt: 'My Last Option', keyname: 'key5'),
            ],
          ),
        ),
      ),
    );
  }

  HtmlEditor initHtmlEditor(HtmlEditorController tcontroller,
      {String txt = "Your text here 1...", String keyname = "a1"}) {
    //sleep(Duration(seconds: 1));
    return HtmlEditor(
      key: Key(keyname),
      controller: tcontroller,
      htmlEditorOptions: HtmlEditorOptions(
          hint: txt,
          shouldEnsureVisible: true,
          //initialText: "<p>text content initial, if any</p>",
          inputType: HtmlInputType.text),
      otherOptions: const OtherOptions(height: 250),
      callbacks: Callbacks(onBeforeCommand: (String? currentHtml) {
        //print('html before change is $currentHtml');
      }, onChangeContent: (String? changed) {
        //print('content changed to $changed');
      }, onChangeCodeview: (String? changed) {
        //print('code changed to $changed');
      }, onChangeSelection: (EditorSettings settings) {
        //print('parent element is ${settings.parentElement}');
        //print('font name is ${settings.fontName}');
      }, onDialogShown: () {
        //print('dialog shown');
      }, onEnter: () {
        //print('enter/return pressed');
      }, onFocus: () {
        //print('editor focused');
      }, onBlur: () {
        //print('editor unfocused');
      }, onBlurCodeview: () {
        //print('codeview either focused or unfocused');
      }, onInit: () {
        //print('init');
        /*
        for (int i = 0; i < 2000; i++) {
          // do nothing
        }
        */
      },
          //this is commented because it overrides the default Summernote handlers
          /*onImageLinkInsert: (String? url) {
                  print(url ?? "unknown url");
                },
                onImageUpload: (FileUpload file) async {
                  print(file.name);
                  print(file.size);
                  print(file.type);
                  print(file.base64);
                },*/
          onImageUploadError:
              (FileUpload? file, String? base64Str, UploadError error) {
        print(describeEnum(error));
        print(base64Str ?? '');
        if (file != null) {
          print(file.name);
          print(file.size);
          print(file.type);
        }
      }, onKeyDown: (int? keyCode) {
        //print('$keyCode key downed');
        //print('current character count: ${controller.characterCount}');
      }, onKeyUp: (int? keyCode) {
        //print('$keyCode key released');
      }, onMouseDown: () {
        //print('mouse downed');
      }, onMouseUp: () {
        //print('mouse released');
      }, onNavigationRequestMobile: (String url) {
        //print(url);
        return NavigationActionPolicy.ALLOW;
      }, onPaste: () {
        //print('pasted into editor');
      }, onScroll: () {
        //print('editor scrolled');
      }),
      plugins: [
        SummernoteAtMention(
            getSuggestionsMobile: (String value) {
              var mentions = <String>['test1', 'test2', 'test3'];
              return mentions
                  .where((element) => element.contains(value))
                  .toList();
            },
            mentionsWeb: ['test1', 'test2', 'test3'],
            onSelect: (String value) {
              print(value);
            }),
      ],
    );
  }
}

Same error : image_2022_02_15T04_00_16_785Z

Please help.

kemalyilmaz77 commented 2 years ago

I have this problem too. is there a solution?