for some reason the component does not initiate the monaco script initial data and available data sometimes.
if i refresh the page where the component is, it works.
I think it might be a timing issue, or that i am creating more than one instance at a time but i cant figure it out.
I found this related stackoverflow but no luck implement its solution:
Flutter web project with javascript modules that use requireJS (Monaco editor in Flutter web)
I event tried adding a delay for after the monaco loader loads and before sending the initial data, that didnt work
code1::
import 'dart:async';
import 'dart:convert';
import 'dart:html' as html;
import 'dart:ui_web';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'dart:ui' as ui;
import '../../../core/notifiers/form_page_notifier.dart';
class ScriptColumn extends ConsumerStatefulWidget {
final Map<String, dynamic> column;
final Function(String, dynamic) on_changed;
ScriptColumn({
Key? key,
required this.column,
required this.on_changed,
}) : super(key: key);
@override
_ScriptColumnState createState() => _ScriptColumnState();
}
class _ScriptColumnState extends ConsumerState<ScriptColumn> {
late html.IFrameElement _iframeElement;
bool _is_loading = true;
bool _editor_ready = false;
StreamSubscription? _messageSubscription;
bool _isDisposed = false; // Track if widget has been disposed
Map<String, dynamic> objects = {
"current": {"sys_name": "aaa"} // initial available data
};
@override
void initState() {
super.initState();
print("n_ScriptColumnState*()");
_initializeEditor();
// Listen for messages from the iframe and handle them appropriately
_messageSubscription = html.window.onMessage.listen((event) {
if (_isDisposed || !mounted) return; // Ensure widget is mounted and not disposed before processing the event
if (event.data['type'] == 'editorContent') {
final editorContent = event.data['content'] ?? "";
widget.on_changed(widget.column['sys_id'], editorContent);
} else if (event.data['type'] == 'editorReady') {
print("EDITOR IS READY");
_editor_ready = true;
_updateEditorContent();
}
});
}
void _initializeEditor() {
_iframeElement = html.IFrameElement()
..src = 'monaco_editor.html'
..style.border = 'none'
..style.height = '100%'
..style.width = '100%';
_is_loading = false;
if (mounted && !_isDisposed) {
setState(() {}); // Only call setState if the widget is still mounted and not disposed
}
}
void _updateEditorContent() {
// Check if the widget is mounted and editor is ready
if (_isDisposed || !mounted || !_editor_ready) return;
try {
final current = ref.read(form_page_provider).record;
final currentText = current[widget.column['sys_name']] ?? "nnnnnnnnnnnnnnnnnnnnnnn";
// print("currentText: ${currentText}");
final message = {
'type': 'updateContent',
'content': currentText.toString(),
'objects': jsonEncode(objects),
};
print("sending updateContent");
_iframeElement.contentWindow?.postMessage(message, '*');
} catch (e) {
// Handle any exception that occurs when trying to update the editor content
print("Error updating editor content: $e");
}
}
@override
void didUpdateWidget(ScriptColumn oldWidget) {
super.didUpdateWidget(oldWidget);
if (_isDisposed || !mounted) return; // Prevent updates if the widget is disposed
if (_editor_ready) {
_updateEditorContent();
}
}
@override
void dispose() {
_isDisposed = true; // Mark the widget as disposed
_messageSubscription?.cancel(); // Cancel the listener when the widget is disposed
super.dispose();
}
@override
Widget build(BuildContext context) {
final current = ref.watch(form_page_provider.select((data) => data.record[widget.column['sys_name']]));
if (_is_loading) {
return BasicLoader(); // Show loader while iframe is being initialized
}
// Update editor content if there are changes in the provider data
if (_editor_ready && !_isDisposed) {
_updateEditorContent();
}
// Register the iframe as a view in Flutter
platformViewRegistry.registerViewFactory(
'monaco-editor-container',
(int viewId) => _iframeElement,
);
return HtmlElementView(viewType: 'monaco-editor-container');
}
}
code2:
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.47.0/min/vs/loader.min.js"></script>
<style>
html, body, #container {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
let editor;
let receivedObjects = {};
let isEditorBlurred = false;
require.config({ paths: { 'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.47.0/min/vs' }});
require(['vs/editor/editor.main'], function() {
editor = monaco.editor.create(document.getElementById('container'), {
value: "// loading ...",
language: 'javascript',
theme: 'vs-light',
automaticLayout: true
});
editor.onDidFocusEditorText(() => {
isEditorBlurred = false;
});
editor.onDidBlurEditorText(() => {
isEditorBlurred = true;
sendEditorContent(); // Update content when editor loses focus
});
monaco.languages.registerCompletionItemProvider('javascript', {
provideCompletionItems: function(model, position) {
const textUntilPosition = model.getValueInRange({
startLineNumber: 1,
startColumn: 1,
endLineNumber: position.lineNumber,
endColumn: position.column
});
const match = textUntilPosition.match(/(w+)$/);
const wordToReplace = match ? match[1] : '';
const customSuggestions = generateSuggestions(receivedObjects, '', wordToReplace);
const builtInSuggestions = generateBuiltInSuggestions(wordToReplace);
return {
suggestions: [...customSuggestions, ...builtInSuggestions],
range: {
startLineNumber: position.lineNumber,
endLineNumber: position.lineNumber,
startColumn: position.column - wordToReplace.length,
endColumn: position.column
}
};
}
});
// Notify parent that the editor is ready
window.parent.postMessage({ type: 'editorReady' }, '*');
});
function sendEditorContent() {
if (isEditorBlurred) {
window.parent.postMessage({
type: 'editorContent',
content: editor.getValue()
}, '*');
}
}
function generateSuggestions(obj, prefix = '', wordToReplace = '') {
return Object.entries(obj).flatMap(([key, value]) => {
const fullKey = prefix ? `${prefix}.${key}` : key;
const suggestions = [{
label: fullKey,
kind: monaco.languages.CompletionItemKind.Field,
insertText: fullKey,
detail: JSON.stringify(value)
}];
if (typeof value === 'object' && value !== null) {
suggestions.push(...generateSuggestions(value, fullKey, wordToReplace));
}
return suggestions;
});
}
function generateBuiltInSuggestions(wordToReplace) {
const builtIns = [
{
label: 'JSON.stringify',
kind: monaco.languages.CompletionItemKind.Function,
insertText: 'JSON.stringify',
detail: 'Convert a JavaScript object to a JSON string'
},
{
label: 'JSON.parse',
kind: monaco.languages.CompletionItemKind.Function,
insertText: 'JSON.parse',
detail: 'Parse a JSON string into a JavaScript object'
}
];
return builtIns.filter(item => item.label.toLowerCase().startsWith(wordToReplace.toLowerCase()));
}
window.addEventListener('message', function(event) {
if (event.data && event.data.type === 'updateContent') {
console.log("update content received!")
editor.setValue(event.data.content);
if (event.data.objects) {
receivedObjects = JSON.parse(event.data.objects);
}
} else if (event.data && event.data.type === 'getEditorContent') {
sendEditorContent();
}
}, false);
</script>
</body>
</html>
output::
_ScriptColumnState*()
2
sending updateContent
EDITOR IS READY
sending updateContent
EDITOR IS READY
sending updateContent
below is the dummy code to get all the code to work::
import 'package:flutter/material.dart';
import 'package:riverpod/riverpod.dart';
import 'script_column.dart'; // Import your ScriptColumn class file
// Define a provider to simulate state management
final formPageProvider = Provider((ref) {
// Simulated record data
return {
'example_sys_name': 'Initial content of the editor',
};
});
void main() {
runApp(
ProviderScope(
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Script Column Example')),
body: Column(
children: [
Expanded(
child: Consumer(
builder: (context, ref, child) {
// Use ScriptColumn here
return ScriptColumn(
column: {'sys_id': '123', 'sys_name': 'example_sys_name'},
on_changed: (id, content) {
print('Content for $id changed: $content');
},
);
},
),
),
],
),
),
);
}
}