javascriptasp.net-coredevexpressspell-checking

DevExpress SpellChecker - ASP .NET Core


I am having this next issue as regards the spellchecker I am trying to set up. The target platform for running my application is .Net Framework 4.8

First of all, I followed the Spellcheck chapter from the ASP.NET CORE controls documentation. The rest of the DevExpress Rich Editor works perfectly, does what it has to do.

To give more detail, in order to set up the spellchecker, you have to Create a nspell Bundle for the RichEdit which is is a built that is done through npm.

1: Run the following commands within the root directory:

console
npm i nspell@2.1.5 --save-dev
npm i webpack@5.91.0 --save-dev
npm i webpack-cli@5.1.4 --save-dev
npm i dictionary-en@3.2.0 --save-dev

If you need the corresponding dictionaries, you have to go to this certain page and download them. https://github.com/wooorm/dictionaries

  1. Add the import directive for every additional dictionary to the node_modules/devexpress-richedit/bin/nspell-index.js file. Did that.

  2. Register additional dictionaries with a corresponding “lang” attribute prior to the default English dictionary to use them first while spell checking.

import enAff from 'dictionary-en/index.aff';
import enDic from 'dictionary-en/index.dic';
    
import esAff from 'dictionary-es/index.aff';
import esDic from 'dictionary-es/index.dic';
    
export const nspell = nspellImport;
export const dictionaries = [
  { lang: 'es', aff: esAff, dic: esDic },
  { lang: 'en', aff: enAff, dic: enDic },
];
  1. Run the following command that builds an nspell bundle according to the node_modules/devexpress-richedit/bin/nspell.webpack.config.js configuration file:
npx webpack --mode production --config=node_modules/devexpress-richedit/bin/nspell.webpack.config.js

I ran it and the command creates the node_modules/devexpress-richedit/dist/custom/nspell.js file. A script in the file appends the nspell object to the JavaScript window object.

I have concisely followed these 4 steps and then had to do this next series of actions:

  1. Create the spell-checker-worker.js file with the following content: ( it's inside the documentation I attached)

  2. Place the nspell.js and spell-checker-worker.js files into the directory that contains the control scripts (wwwroot for .NET Core, Scripts for MVC and Web Forms). In my case, I put it inside a folder located in Scripts/devexpress-richedit

  3. For an application on a client framework, add the following code to the page that contains the RichEdit control. In my case I added the code inside my loadOptionsToInitializeContainer function which is called in my main razor View Page.

  4. In order to confirm that what I did is working, I attached the function addWordToDictionary on the Ribbon tab by the name Add word to dictionary. The ribbon item is inserted correctly on the bar every time I click, it enters inside the function but it does absolutely nothing. To give a bit more in-depth explanation of the problem is that I track checkWordSpelling and addWordToDictionary functions in the handler which DevExpress possesses for the client-side. It's this one. It is a handler that is added and at this point is tracking the command name of both functions that are included on initialization.

    richElementContainer.events.customCommandExecuted.addHandler(function (s, e) {
       switch (e.commandName) {
           case 'checkWordSpelling':
               var text = s.document.getText();
           break;
           case 'addWordToDictionary':
               console.log(s);
               console.log(e);
           break;
       }
    });
    

CODE: JAVASCRIPT

function loadOptionsToInitializeContainer() {
    const options = DevExpress.RichEdit.createOptions();

    options.confirmOnLosingChanges.enabled = true;
    options.confirmOnLosingChanges.message = 'Are you sure you want to perform the action? All unsaved document data will be lost.';
    options.width = '1100px';
    options.height = '1100px';
    options.bookmarks.visibility = true;
    options.bookmarks.color = '#ff0000';
    options.fields.updateFieldsBeforePrint = true;
    options.fields.updateFieldsOnPaste = true;
    options.rangePermissions.showBrackets = true;
    options.rangePermissions.bracketsColor = 'red';
    options.rangePermissions.highlightRanges = false;
    options.rangePermissions.highlightColor = 'lightgreen';
    options.handled = false;

    //// spellchecker worker initialization
    var spellCheckerWorker = null;
    var spellCheckerCallbacks = Object.create(null);
    var spellCheckerWorkerCommandId = 0;

    options.spellCheck.checkWordSpelling = function (word, callback) {
        if (!spellCheckerWorker) {
            var myDictionary = JSON.parse(localStorage.getItem('myDictionary')) || [];
            spellCheckerWorker = new Worker('./spell-checker-worker.js');
            myDictionary.forEach(function (word) {
                spellCheckerWorker.postMessage({
                    command: 'addWord',
                    word: word,
                });
            });
            console.log(myDictionary);

            spellCheckerWorker.onmessage = function (e) {
                var savedCallback = spellCheckerCallbacks[e.data.id];
                delete spellCheckerCallbacks[e.data.id];
                savedCallback(e.data.isCorrect, e.data.suggestions);
            };
        }

        var currId = spellCheckerWorkerCommandId++;
        spellCheckerCallbacks[currId] = callback;
        spellCheckerWorker.postMessage({
            command: 'checkWord',
            word: word,
            id: currId,
        });
    };
    options.spellCheck.addWordToDictionary = function (word) {
        var myDictionary = JSON.parse(localStorage.getItem('myDictionary')) || [];
        myDictionary.push(word);
        localStorage.setItem('myDictionary', JSON.stringify(myDictionary));

        spellCheckerWorker.postMessage({
            command: 'addWord',
            word: word,
        });
    };

    var contextMenu = options.contextMenu;
    var reviewTab = new DevExpress.RichEdit.RibbonTab();

    var ribbonButton = new DevExpress.RichEdit.RibbonButtonItem("addWordToDictionary", "Add word to dictionary", { icon: "check", showText: true, beginGroup: true });
    reviewTab.insertItem(ribbonButton, 16);
    reviewTab.id = 16;
    reviewTab.localizationId = "Spellchecking tab";
    reviewTab.title = "Spellchecker";
    options.ribbon.insertTab(reviewTab, 16);
    var mailMergeTab = options.ribbon.getTab(DevExpress.RichEdit.RibbonTabType.MailMerge);
    options.ribbon.removeTab(mailMergeTab);
    var tab = options.ribbon.getTab(DevExpress.RichEdit.RibbonTabType.Insert);
    var mailMergeTab = options.ribbon.getTab(DevExpress.RichEdit.RibbonTabType.MailMerge);
    var tabHeadersFooters = options.ribbon.getTab(DevExpress.RichEdit.RibbonTabType.HeadersFooters);
    var fileTab = options.ribbon.getTab(DevExpress.RichEdit.RibbonTabType.File);
    var ribbonItemFooter = tab.getItem(DevExpress.RichEdit.InsertTabItemId.InsertFooter);
    var ribbonItemHeader = tab.getItem(DevExpress.RichEdit.InsertTabItemId.InsertHeader);
    var ribbonItemPageNumber = tab.getItem(DevExpress.RichEdit.InsertTabItemId.InsertPageNumberField);
    var ribbonItemHeadersFooters = tabHeadersFooters.getItem(DevExpress.RichEdit.HeaderAndFooterTabItemId.ClosePageHeaderFooter);

    // gets Home Tab
    var fileItemSave = fileTab.getItem(DevExpress.RichEdit.FileTabItemId.ExportDocument);

    // gets Save Option from Home Tab
    // Removes Save item from Home Tab
    fileTab.removeItem(fileItemSave);
    tab.removeItem(ribbonItemFooter);
    tab.removeItem(ribbonItemHeader);
    tabHeadersFooters.removeItem(ribbonItemHeadersFooters);

    var richElement = document.getElementById("rich-container");
    return [richElement, options];

}

RAZOR

In my razor View, I call the javascript function which I wrote for initializing the options for the container/rich-editor

  // this is that function which passes all options to a constant
  const containerPlusOptions = loadOptionsToInitializeContainer();

  // this is the container/rich-editor that is created from those options
      richElementContainer = window.richElementContainer = DevExpress.RichEdit.create(containerPlusOptions[0], containerPlusOptions[1]);
  window.completed = createRichEdit(window.modelRaw, window.inform);

What I really need to find is how to be able to set up the spellchecker correctly, I am at a certainly good path here but I have been stuck for some days now and don't know what to try experimenting with. Thanks


Solution

  • So, after a lot of investigation of this issue what I found is that I was referencing wrongly the spell-checker-worker.js on my main function that sets all the options for the DevExpress RichEdit component editor.

    The solution for the spell-checker to function properly was to rewrite the function that loads all the initial options for the editor like this manner:

    function loadOptionsToInitializeContainer() {
        const options = DevExpress.RichEdit.createOptions();
    
        options.confirmOnLosingChanges.enabled = true;
        options.confirmOnLosingChanges.message = 'Are you sure you want to perform the action? All unsaved document data will be lost.';
        options.width = '1100px';
        options.height = '1100px';
        options.bookmarks.visibility = true;
        options.bookmarks.color = '#ff0000';
        options.fields.updateFieldsBeforePrint = true;
        options.fields.updateFieldsOnPaste = true;
        options.rangePermissions.showBrackets = true;
        options.rangePermissions.bracketsColor = 'red';
        options.rangePermissions.highlightRanges = false;
        options.rangePermissions.highlightColor = 'lightgreen';
        options.handled = false;
         // here is the function which enables the spell checking utility on the editor
        enableSpellChecker(options, options.enableSpellChecker);
    
        var contextMenu = options.contextMenu;
        var reviewTab = new DevExpress.RichEdit.RibbonTab();
    
        var ribbonButton = new DevExpress.RichEdit.RibbonButtonItem("addWordToDictionary", "Add word to dictionary", { icon: "check", showText: true, beginGroup: true });
        reviewTab.insertItem(ribbonButton, 16);
        reviewTab.id = 16;
        reviewTab.localizationId = "Spellchecking tab";
        reviewTab.title = "Spellchecker";
        options.ribbon.insertTab(reviewTab, 16);
        var mailMergeTab = options.ribbon.getTab(DevExpress.RichEdit.RibbonTabType.MailMerge);
        options.ribbon.removeTab(mailMergeTab);
        var tab = options.ribbon.getTab(DevExpress.RichEdit.RibbonTabType.Insert);
        var mailMergeTab = options.ribbon.getTab(DevExpress.RichEdit.RibbonTabType.MailMerge);
        var tabHeadersFooters = options.ribbon.getTab(DevExpress.RichEdit.RibbonTabType.HeadersFooters);
        var fileTab = options.ribbon.getTab(DevExpress.RichEdit.RibbonTabType.File);
        var ribbonItemFooter = tab.getItem(DevExpress.RichEdit.InsertTabItemId.InsertFooter);
        var ribbonItemHeader = tab.getItem(DevExpress.RichEdit.InsertTabItemId.InsertHeader);
        var ribbonItemPageNumber = tab.getItem(DevExpress.RichEdit.InsertTabItemId.InsertPageNumberField);
        var ribbonItemHeadersFooters = tabHeadersFooters.getItem(DevExpress.RichEdit.HeaderAndFooterTabItemId.ClosePageHeaderFooter);
    
        // gets Home Tab
        var fileItemSave = fileTab.getItem(DevExpress.RichEdit.FileTabItemId.ExportDocument);
    
        // gets Save Option from Home Tab
        // Removes Save item from Home Tab
        fileTab.removeItem(fileItemSave);
        tab.removeItem(ribbonItemFooter);
        tab.removeItem(ribbonItemHeader);
        tabHeadersFooters.removeItem(ribbonItemHeadersFooters);
    
        var richElement = document.getElementById("rich-container");
        return [richElement, options];
    
    }
    
    
    function enableSpellChecker(options, enableSpellChecker) {
        let boolValue = enableSpellChecker.toLowerCase() == 'true' ? true : false;
        options.spellCheck.enabled = boolValue;
        options.spellCheck.suggestionCount = 5;
    
        options.spellCheck.checkWordSpelling = function (word, callback) {
            if (!spellCheckerWorker) {
                var myDictionary = JSON.parse(localStorage.getItem('myDictionary')) || [];
    
                 // this is where the error was, I was clearly pointing out a wrong directory for the worker to properly function.
                spellCheckerWorker = new Worker('/Scripts/devexpress-richedit/spell-checker-worker.js');
                spellCheckerWorker.onmessage = function (e) {
                    var savedCallback = spellCheckerCallbacks[e.data.id];
                    delete spellCheckerCallbacks[e.data.id];
                    if (e.data.suggestions != undefined && e.data.suggestions.length > 0) {
                        savedCallback(e.data.isCorrect, e.data.suggestions);
                    } else {
                        savedCallback(e.data.isCorrect, myDictionary);
                    }
    
                };
            }
    
            var currId = spellCheckerWorkerCommandId++;
            spellCheckerCallbacks[currId] = callback;
    
            spellCheckerWorker.postMessage({
                command: 'checkWord',
                word: word,
                id: currId,
            });
        };
    
    
        options.spellCheck.addWordToDictionary = function (word) {
            var myDictionary = JSON.parse(localStorage.getItem('myDictionary')) || [];
            myDictionary.push(word);
            localStorage.setItem('myDictionary', JSON.stringify(myDictionary));
    
            spellCheckerWorker.postMessage({
                command: 'addWord',
                word: word,
            });
        };
    
    }
    

    When I set up a demo page for checking out what the issue was is that the spell-checker-worker.js on my Network Tab in Google Chrome was giving me a 200 result. It was properly functioning as it is supposed to.

    After this, I was convinced that in my other main page, where the spellchecker is malfunctioning, there had to be something that is not correct there. And effectively, the problem was that the worker javascript file was referenced with a wrong relative path.

    Instead of this which is the correct way to go:

    spellCheckerWorker = new Worker('/Scripts/devexpress-richedit/spell-checker-worker.js');

    I had it like this manner:

    spellCheckerWorker = new Worker('./spell-checker-worker.js');

    If to anyone has occurred a similar issue to this, please check this answer so it will save you any headaches as regards the corresponding matter.