oqtane.framework: Editor error thrown: Quill is not defined
An error is thrown when opening the Rich Text Editor for the first time or if reloading the page using Empty Cache and Hard Reload. Maybe the quill-interop.js script is not loaded before the CreateEditor interop call is executed.

Exception thrown: 'Microsoft.JSInterop.JSException' in System.Private.CoreLib.dll
Microsoft.AspNetCore.Components.Server.Circuits.RemoteRenderer: Warning: Unhandled exception rendering component: QuillBlotFormatter is not defined
ReferenceError: QuillBlotFormatter is not defined
at Object.createQuill (http://localhost:44357/js/quill-interop.js:6:49)
at http://localhost:44357/_framework/blazor.server.js:8:31421
at new Promise (<anonymous>)
at e.beginInvokeJSFromDotNet (http://localhost:44357/_framework/blazor.server.js:8:31390)
at http://localhost:44357/_framework/blazor.server.js:1:19202
at Array.forEach (<anonymous>)
at e.invokeClientMethod (http://localhost:44357/_framework/blazor.server.js:1:19173)
at e.processIncomingData (http://localhost:44357/_framework/blazor.server.js:1:17165)
at e.connection.onreceive (http://localhost:44357/_framework/blazor.server.js:1:10276)
at WebSocket.i.onmessage (http://localhost:44357/_framework/blazor.server.js:1:38091)
Microsoft.JSInterop.JSException: QuillBlotFormatter is not defined
ReferenceError: QuillBlotFormatter is not defined
at Object.createQuill (http://localhost:44357/js/quill-interop.js:6:49)
at http://localhost:44357/_framework/blazor.server.js:8:31421
at new Promise (<anonymous>)
at e.beginInvokeJSFromDotNet (http://localhost:44357/_framework/blazor.server.js:8:31390)
at http://localhost:44357/_framework/blazor.server.js:1:19202
at Array.forEach (<anonymous>)
at e.invokeClientMethod (http://localhost:44357/_framework/blazor.server.js:1:19173)
at e.processIncomingData (http://localhost:44357/_framework/blazor.server.js:1:17165)
at e.connection.onreceive (http://localhost:44357/_framework/blazor.server.js:1:10276)
at WebSocket.i.onmessage (http://localhost:44357/_framework/blazor.server.js:1:38091)
at Microsoft.JSInterop.JSRuntime.InvokeWithDefaultCancellation[T](String identifier, Object[] args)
at Oqtane.Modules.Controls.RichTextEditor.OnAfterRenderAsync(Boolean firstRender) in D:\temp\Oqtane.Client\Modules\Controls\RichTextEditor.razor:line 120
at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle)
Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost: Error: Unhandled exception in circuit 'Ie-x6S-hNCalIyPqBsJknPK9X9iz3PXHCuHPLqb_08o'.
Microsoft.JSInterop.JSException: QuillBlotFormatter is not defined
ReferenceError: QuillBlotFormatter is not defined
at Object.createQuill (http://localhost:44357/js/quill-interop.js:6:49)
at http://localhost:44357/_framework/blazor.server.js:8:31421
at new Promise (<anonymous>)
at e.beginInvokeJSFromDotNet (http://localhost:44357/_framework/blazor.server.js:8:31390)
at http://localhost:44357/_framework/blazor.server.js:1:19202
at Array.forEach (<anonymous>)
at e.invokeClientMethod (http://localhost:44357/_framework/blazor.server.js:1:19173)
at e.processIncomingData (http://localhost:44357/_framework/blazor.server.js:1:17165)
at e.connection.onreceive (http://localhost:44357/_framework/blazor.server.js:1:10276)
at WebSocket.i.onmessage (http://localhost:44357/_framework/blazor.server.js:1:38091)
at Microsoft.JSInterop.JSRuntime.InvokeWithDefaultCancellation[T](String identifier, Object[] args)
at Oqtane.Modules.Controls.RichTextEditor.OnAfterRenderAsync(Boolean firstRender) in D:\temp\Oqtane.Client\Modules\Controls\RichTextEditor.razor:line 120
at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle)
Microsoft.AspNetCore.Routing.EndpointMiddleware: Information: Executed endpoint '/_blazor'
Microsoft.AspNetCore.Hosting.Diagnostics: Information: Request finished in 854.0505ms 101
[2020-05-20T19:11:04.558Z] Error: There was an unhandled exception on the current circuit, so this circuit will be terminated. For more details turn on detailed exceptions by setting 'DetailedErrors: true' in 'appSettings.Development.json' or set 'CircuitOptions.DetailedErrors'.
[2020-05-20T19:11:04.560Z] Information: Connection disconnected.
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Comments: 107 (102 by maintainers)
Commits related to this issue
- refactoring of #518 to simplify registration of scripts in modules and themes — committed to sbwalker/oqtane.framework by sbwalker 4 years ago
- Merge pull request #610 from sbwalker/master refactoring of #518 to simplify registration of scripts in modules and themes — committed to oqtane/oqtane.framework by sbwalker 4 years ago
@jimspillane it seems that we have been researching the same topics and coming to the same conclusions š A few additional thoughtsā¦
The issues with JavaScript dependencies and resource loading are widespread in modern web applications and there are many solutions which have been created to deal with this problem. There are simpler module loader solutions such as RequireJS, SystemJS, LoadJS which can be easily integrated into an application. And there are more elaborate module bundling solutions such as WebPack, etc⦠which have more dependencies. The question is, which is the appropriate solution for Oqtane? I would conclude that since Blazor is based on the fundamental concept of reducing your dependency on JavaScript, then the the right solution is the simpler module loaders, as the focus should be on using less JavaScript and more native Blazor functionality. Of the simpler module loaders identified, LoadJS is the smallest and most focused on solving the specific dependency issues we have encountered.
We can absolutely add an additional property to the Resource object to support dependencies - this is not a breaking change and will be easy for developers to understand.
I realize that simply removing a <script> tag from the DOM does not actually result in the memory being deallocated in the browser - once it is loaded, it stays loaded ( unlike stylesheets ). However it is important to recognize the different personas in an Oqtane application. A typical user will never have the ability to edit content - so there is no need for them to ever load scripts such as Quill. And if the script is never loaded then it will not consume any resources. This is the reason why it is still important for Oqtane to be intelligent about which scripts it loads for a user/page.
In regards to loading multiple versions of the same JavaScript libaries, I have to go back to the higher level philosophy once again. As a modular application framework I believe that there should be certain foundational aspects which all module/theme developers should target. These include the Oqtane APIs but also include specific UI frameworks such as Bootstrap and jQuery. We can guarantee that the framework as a whole functions properly with all of these foundational aspects in place. If a developer decides to replace a foundational dependency with a newer/custom version then we cannot guarantee that Oqtane will functional properly. In addition, I think there should be a general focus on avoiding JavaScript if possible and relying on native Blazor functionality. If we encourage these 2 general philosophies it dramatically reduces the potential for incompatibilities and conflicts - which results in a substantial benefit for all Oqtane users. Obviously it would still be technically possible for a developer to ignore these philosophies but it would then become their own problem to deal with.
@sbwalker I created the PR with the modifications that I have made. Please add your additions and we can finally close this one š
That would work for modules that fit that use case. If there are complex dependencies, the developer has the option to use the lower-level functions that are exposed by the Oqtane interop and LoadJS. It is the best of both worlds.
I was focused on getting the Quill scripts working, so I didnāt put much thought into the Themes. I think we have a winner if your added logic works well with the themes too.
@jimspillane I tried out your solution and it works perfectly. I also did some additional experimentation based on some ideas I had, and I would like to get your feedbackā¦
Background: Modules and Module Controls ( ie. RichTextEditor, etc⦠) inherit from ModuleBase. ModuleBase contains a Resources property.
ModuleBase.cs - I added a default implementation for OnAfterRenderAsync(). This uses the assumption that a module will only declare a single interop script - and the interop script is responsible for pulling in any other dependencies using LoadJS. ( Note that in my code I changed the LoadScript() method to use your LoadJS logic ).
RichTextEditor.razor: I declared the interop script resource and I added a call to the base implementation of OnAfterRenderAsync ( only because RichTextEditor needs to do other things in OnAfterRenderAsync - normally this would not be necessary ).
With this change, the solution still works and the scripts are loaded properly. The benefit is that a developer declares their scripts the same way they do currently, and the framework takes care of the complexity of registering them.
Note that Themes and Theme Controls ( ie. Login button ) inherit from ThemeBase⦠and ThemeBase contains a Resources property - so the exact same logic will work for themes as well.
If you look in the SiteRouter where it builds the collection of page resources, it removes duplicates.
Actually I think you nailed the problem on the head @jimspillane . In Blazor components, all UI interactions are async by design, as you do not want any UI thread blocking to occur. Blazor components do have some basic lifecycle events (ie. OnInitialized, OnParamaterSet, OnAfterRender) however it is important to note that these are not āPageā lifecycle events like in a traditional MVC/WebForms app - they are āComponentā lifecycle events. Each component is basically its own micro-application - and there is no hierarchical dependency between them. So if you have a parent component and a child component it is possible that the child component could actually render before the parent (or vice versa) as they are treated as independent entities that run asynchronously. For this reason the responsibility is on the developer to deal with component dependencies. For example you will often see logic in Blazor component markup which checks if a local variable is null as the local variable is usually being populated by an async call so the UI needs to await it to be completed.
Some insights from my previous experience loading dynamic JS parts, as we have been doing this kind of stuff for many years on 2sxc:
Basics - no Real Dynamic Loading in JS
window.tinymceobject) and then returns a promise so that the object needing it can pick it upā¦window)Using
requireorawait importfor Lazy Loadingrequireorimportwhich is how modern JS applications dynamically load parts on-the-fly. Itās also the backbone of lazy-loading in Angular - check out the away import example⦠and for a certain amount of unloading, you can also kill things from the cache - see this (not verified)
Blazor & Oqtane are probably not using require properly
My guess ATM is that the current edition of Oqtane tries to use simple pre-built JS bundles from a standard
dist/something.min.jswhich is either not specifically meant for async loading, or it provides that, but weāre not actually using that feature.For example Quill seems to be a UMD package but Iām going to guess weāre not using
requireor anything to verify itās loaded, weāre probably just hoping that our code runs after it has been added to the page.Leveraging SPA technology
So this is a moment where we probably should simply leverage existing SPA methodology and use
requireorawait importfor this stuff. I believe that it would make most of the issues go away just like that.Maybe this is already happening and my rambling isnāt helping anybody š. Thoughts?
That is a good question. Technically there is no code level dependency between Themes and Containers so the UI allows users to choose Containers that do not belong to the current Theme. However there is likely a stylesheet dependency between Themes and Containers - so perhaps it makes sense to restrict the selection based on Theme ( similar to the behavior for Layouts ).
Thank you for verifying. I think I will reach out to Steve Sanderson regarding the recommended way to solve this probiem.