]> uap-core.de Git - note.git/commitdiff
replace NoteDoc enum with NoteViewModel trait
authorOlaf Wintermann <olaf.wintermann@gmail.com>
Fri, 17 Jul 2026 07:00:35 +0000 (09:00 +0200)
committerOlaf Wintermann <olaf.wintermann@gmail.com>
Fri, 17 Jul 2026 07:00:35 +0000 (09:00 +0200)
application/note/src/file.rs
application/note/src/note.rs
application/note/src/notebook.rs
ui-rs/src/ui/event.rs
ui-rs/src/ui/toolkit.rs

index 560c2c195f0569d212519dd5034acae70b00e28c..3a9df0f77f1439c7ed02afbbebf250c4edb1a0e0 100644 (file)
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  * POSSIBILITY OF SUCH DAMAGE.
  */
+
 use std::rc::Rc;
 use backend::backend::{BackendHandle, NoteId, OpenExternResult};
-use ui_rs::{action, ui_actions, UiModel};
+use ui_rs::{action, doc_cast, ui_actions, UiModel};
 use ui_rs::ui::*;
 use crate::AppStates;
+use crate::note::NoteViewModel;
 use crate::window::NoteTypeTabView;
 
 #[derive(UiModel)]
@@ -73,14 +75,15 @@ impl FileNote {
         }
     }
 
-    pub fn into_doc(self, model: &entity::note::Model) -> UiDoc<FileNote> {
-        UiDoc::new2(self, |n,d| {
+    pub fn into_doc(self, model: &entity::note::Model) -> UiDoc<dyn NoteViewModel> {
+        let doc: UiDoc<FileNote> = UiDoc::new_from_box2(Box::new(self), |n,d| {
             n.doc = d.doc_ref();
             n.nodename = model.nodename.clone();
 
             n.note_type.set(NoteTypeTabView::File as i64);
             n.note_nodename.set(model.nodename.as_str());
-        })
+        });
+        doc_cast!(doc, dyn NoteViewModel)
     }
 
     #[action]
@@ -146,3 +149,9 @@ impl FileNote {
         doc.ctx.set_state(AppStates::NoteMaximized as i32);
     }
 }
+
+impl NoteViewModel for FileNote {
+    fn save_note(&mut self) {
+
+    }
+}
\ No newline at end of file
index 6307b1eb184e01149d7643e82f0a9c0962fda44d..025fd1025ab784efb1237232c14c1447b14a2557 100644 (file)
@@ -33,13 +33,18 @@ use sea_orm::sea_query::prelude::Utc;
 use backend::backend::{BackendHandle, BroadcastMessage, NoteId, NoteTitleUpdate, SaveNoteResult};
 use backend::lockmanager::NoteLock;
 use entity::note::NoteType;
-use ui_rs::{action, ui_actions, UiModel};
+use ui_rs::{action, doc_cast, ui_actions, UiModel};
 use ui_rs::ui::*;
 use crate::AppStates;
 use crate::window::NoteTypeTabView;
 
 static TMP_ID: AtomicU64 = AtomicU64::new(1);
 
+pub trait NoteViewModel: UiModel + UiActions {
+    fn save_note(&mut self);
+}
+
+
 #[derive(UiModel)]
 pub struct Note {
     pub doc: UiDocRef<Note>,
@@ -98,8 +103,8 @@ impl Note {
         }
     }
 
-    pub fn into_doc(self, model: &entity::note::Model) -> UiDoc<Note> {
-        UiDoc::new2(self, |n,d| {
+    pub fn into_doc(self, model: &entity::note::Model) -> UiDoc<dyn NoteViewModel> {
+        let doc: UiDoc<Note> = UiDoc::new_from_box2(Box::new(self), |n,d| {
             n.doc = d.doc_ref();
             if n.id.is_new() {
                 n.init_new();
@@ -110,9 +115,11 @@ impl Note {
             }
             // make sure we receive an event, when this note is attached or detached
             d.ctx.on_attachment_status_change_action("attachment_status_changed");
-        })
+        });
+        doc_cast!(doc, dyn NoteViewModel)
     }
 
+
     /// Handles any attachment status change, which includes if this note is
     /// directly attached or detached, but also if any parent attachment status changes
     #[action]
@@ -225,78 +232,6 @@ impl Note {
         self.save_note();
     }
 
-    pub fn save_note(&mut self) {
-        if !self.modified {
-            return;
-        }
-
-        let Some(doc) = self.doc.get_doc() else {
-            return;
-        };
-
-        let content = self.text.get();
-        let title = match generate_title(content.as_str()) {
-            Some(title) => title.0.to_string(),
-            None => "New Note".to_string() // TODO: see NoteItem::new()
-        };
-
-        let (note_id, created) = match &self.id {
-            NoteId::Id(id) => (Set(*id), NotSet),
-            NoteId::TmpId(_) => (NotSet, Set(Utc::now().into()))
-        };
-
-        let note = entity::note::ActiveModel {
-            note_id: note_id.clone(),
-            collection_id: Set(self.collection_id),
-            nodename: if let Some(nodename) = &self.nodename { Set(nodename.clone())} else { NotSet },
-            kind: Set(self.kind.clone()),
-            title: Set(title),
-            lastmodified: Set(Utc::now().into()),
-            created: created,
-            version: Set(self.version)
-        };
-
-        let notecontent = entity::notecontent::ActiveModel {
-            id: if self.content_id == 0 { NotSet } else { Set(self.content_id) },
-            note_id: note_id,
-            content: Set(content),
-        };
-
-        let proxy = doc.doc_proxy();
-        self.backend.save_note(self.notebook_instance_id, self.id.clone(), note, Some(notecontent), |result|{
-            proxy.call_mainthread(move |doc, note|{
-                match result {
-                    SaveNoteResult::Ok((notemodel, content_id)) => {
-                        note.id = NoteId::Id(notemodel.note_id);
-                        note.content_id = content_id;
-                        note.version = notemodel.version;
-                        println!("Note saved: note_id: {}, content_id: {}, version: {}", notemodel.note_id, content_id, note.version);
-
-                        // make sure the "new note" button is always enabled within this note
-                        doc.ctx.unsuppress_state(AppStates::NoteEnableNew as i32);
-                    },
-                    SaveNoteResult::VersionConflict => {
-                        println!("Failed to save note: version conflict");
-                    },
-                    SaveNoteResult::Error(e) => {
-                        println!("Failed to save note: {}", e);
-                    },
-                    SaveNoteResult::ValidationError(s) => {
-                        println!("Failed to save note: validation error: {}", s);
-                    }
-                    SaveNoteResult::FileAlreadyExists(path) => {
-                        println!("Failed to save note: file already exists: {}", path.display());
-                    }
-                    SaveNoteResult::FileError(e) => {
-                        println!("Failed to save note: file error: {}", e);
-                    }
-                }
-            });
-        });
-
-        self.modified = false;
-    }
-
     #[action(u64)]
     pub fn changed_by(&mut self, _event: &ActionEvent, _from: &u64) {
         // The from id is currently unused, however it may be important when it is possible
@@ -380,6 +315,80 @@ impl Note {
     }
 }
 
+impl NoteViewModel for Note {
+    fn save_note(&mut self) {
+        if !self.modified {
+            return;
+        }
+
+        let Some(doc) = self.doc.get_doc() else {
+            return;
+        };
+
+        let content = self.text.get();
+        let title = match generate_title(content.as_str()) {
+            Some(title) => title.0.to_string(),
+            None => "New Note".to_string() // TODO: see NoteItem::new()
+        };
+
+        let (note_id, created) = match &self.id {
+            NoteId::Id(id) => (Set(*id), NotSet),
+            NoteId::TmpId(_) => (NotSet, Set(Utc::now().into()))
+        };
+
+        let note = entity::note::ActiveModel {
+            note_id: note_id.clone(),
+            collection_id: Set(self.collection_id),
+            nodename: if let Some(nodename) = &self.nodename { Set(nodename.clone())} else { NotSet },
+            kind: Set(self.kind.clone()),
+            title: Set(title),
+            lastmodified: Set(Utc::now().into()),
+            created: created,
+            version: Set(self.version)
+        };
+
+        let notecontent = entity::notecontent::ActiveModel {
+            id: if self.content_id == 0 { NotSet } else { Set(self.content_id) },
+            note_id: note_id,
+            content: Set(content),
+        };
+
+        let proxy = doc.doc_proxy();
+        self.backend.save_note(self.notebook_instance_id, self.id.clone(), note, Some(notecontent), |result|{
+            proxy.call_mainthread(move |doc, note|{
+                match result {
+                    SaveNoteResult::Ok((notemodel, content_id)) => {
+                        note.id = NoteId::Id(notemodel.note_id);
+                        note.content_id = content_id;
+                        note.version = notemodel.version;
+                        println!("Note saved: note_id: {}, content_id: {}, version: {}", notemodel.note_id, content_id, note.version);
+
+                        // make sure the "new note" button is always enabled within this note
+                        doc.ctx.unsuppress_state(AppStates::NoteEnableNew as i32);
+                    },
+                    SaveNoteResult::VersionConflict => {
+                        println!("Failed to save note: version conflict");
+                    },
+                    SaveNoteResult::Error(e) => {
+                        println!("Failed to save note: {}", e);
+                    },
+                    SaveNoteResult::ValidationError(s) => {
+                        println!("Failed to save note: validation error: {}", s);
+                    }
+                    SaveNoteResult::FileAlreadyExists(path) => {
+                        println!("Failed to save note: file already exists: {}", path.display());
+                    }
+                    SaveNoteResult::FileError(e) => {
+                        println!("Failed to save note: file error: {}", e);
+                    }
+                }
+            });
+        });
+
+        self.modified = false;
+    }
+}
+
 fn generate_title(s: &str) -> Option<(&str, usize)> {
     for line in s.lines() {
         if !line.trim().is_empty() {
index c5ee2ece590bc79ef36c0a5f915fb451ae30a1ac..d07e0f5e967bf425c1445db63f04a7efe85be413 100644 (file)
@@ -34,7 +34,7 @@ use ui_rs::{action, ui_actions, UiModel};
 use ui_rs::ui::*;
 
 use entity::note::{Model as Note, NoteType};
-use crate::note::new_note_id;
+use crate::note::{new_note_id, NoteViewModel};
 use crate::window::NavigationItem;
 
 /// Each Notebook view model gets a unique instance id, that is mostly used to identify,
@@ -80,7 +80,7 @@ impl Notebook {
             doc.ctx.on_attach_action("notebook_on_attach")
         })
     }
-
+    
     #[action]
     pub fn notebook_on_attach(&mut self, event: &mut ActionEvent) {
         let Some(obj) = &mut event.obj else {
@@ -109,10 +109,13 @@ impl Notebook {
             // doc_ref.get_doc() should never fail here
             if let Some(mut doc) = self.doc_ref.get_doc() {
                 // save the note
-                current.doc.save_note();
+                let note_proxy = current.doc.doc_proxy();
+                note_proxy.call_mainthread(move |_doc, note|{
+                    note.save_note();
+                });
 
                 // detach the note
-                current.doc.detach_from(&mut doc.ctx);
+                doc.ctx.detach(&current.doc);
                 self.selected_note = None;
             }
         }
@@ -179,19 +182,19 @@ impl Notebook {
             match note.data.kind {
                 NoteType::File => {
                     let note_data = crate::file::FileNote::new(self.instance_id, note.id.clone(), self.collection_id, self.backend.clone());
-                    NoteDoc::File(note_data.into_doc(&note.data))
+                    note_data.into_doc(&note.data)
                 },
                 _ => {
                     // default: text note (plain text or md)
                     let note_data = crate::note::Note::new(self.instance_id, note.id.clone(), self.collection_id, self.backend.clone());
-                    NoteDoc::Note(note_data.into_doc(&note.data))
+                    note_data.into_doc(&note.data)
                 }
             }
         };
         note.model = Some(doc.clone());
 
         if let Some(mut nb) = self.doc_ref.get_doc() {
-            doc.attach_to(&mut nb.ctx);
+            nb.ctx.attach(&doc);
             if is_new {
                 nb.ctx.call_action("textarea_focus");
             }
@@ -301,7 +304,7 @@ impl Notebook {
                         // The note is also currently attached
                         // Send a message to the note, that it needs an update
                         let arg = Box::new(update.from);
-                        selection.doc.ctx().call_action_with_parameter("changed_by", arg);
+                        selection.doc.ctx.call_action_with_parameter("changed_by", arg);
                     } else {
                         // Note view model is not attached, we can just discard it and the next
                         // time the note is opened, it is recreated and the content reloaded
@@ -420,7 +423,7 @@ pub struct NoteItem {
     id: NoteId,
     orig_id: NoteId,
     data: entity::note::Model,
-    model: Option<NoteDoc>,
+    model: Option<UiDoc<dyn NoteViewModel>>,
 }
 
 impl NoteItem {
@@ -453,57 +456,7 @@ impl NoteItem {
 
 pub struct NoteSelection {
     pub id: NoteId,
-    pub doc: NoteDoc,
+    pub doc: UiDoc<dyn NoteViewModel>,
     pub index: usize
 }
 
-#[derive(Clone)]
-pub enum NoteDoc {
-    Note(UiDoc<crate::note::Note>),
-    File(UiDoc<crate::file::FileNote>)
-}
-
-impl NoteDoc {
-    pub fn ctx(&self) -> &UiContext {
-        match self {
-            NoteDoc::Note(doc) => { &doc.ctx }
-            NoteDoc::File(doc) => { &doc.ctx }
-        }
-    }
-
-    pub fn attach_to(&self, ctx: &mut UiContext) {
-        match &self {
-            NoteDoc::Note(doc) => {
-                ctx.attach(doc);
-            }
-            NoteDoc::File(doc) => {
-                ctx.attach(doc);
-            }
-        }
-    }
-
-    pub fn detach_from(&self, ctx: &mut UiContext) {
-        match &self {
-            NoteDoc::Note(doc) => {
-                ctx.detach(doc);
-            }
-            NoteDoc::File(doc) => {
-                ctx.detach(doc);
-            }
-        }
-    }
-
-    pub fn save_note(&self) {
-        match self {
-            NoteDoc::Note(doc) => {
-                let note_proxy = doc.doc_proxy();
-                note_proxy.call_mainthread(move |_doc, note|{
-                    note.save_note();
-                });
-            }
-            NoteDoc::File(_) => {
-
-            }
-        }
-    }
-}
\ No newline at end of file
index 93bf3702a5d7fe8365da81dc1bad9c2added1cf7..506f496d05cda22cf2092aa3c4d92a5d779f4467 100644 (file)
@@ -278,11 +278,6 @@ pub struct ActionEventWrapper<T> {
     pub target: *mut T
 }
 
-pub struct ActionEventWrapperBoxed<T> {
-    pub callback: Box<dyn FnMut(&mut T, &mut ActionEvent)>,
-    pub target: *mut Box<T>
-}
-
 pub extern "C" fn action_event_wrapper<T>(e: *const ffi::UiEvent, data: *mut c_void) {
     unsafe {
         let wrapper = &mut *(data as *mut ActionEventWrapper<T>);
@@ -294,16 +289,6 @@ pub extern "C" fn action_event_wrapper<T>(e: *const ffi::UiEvent, data: *mut c_v
     }
 }
 
-pub extern "C" fn action_event_wrapper_boxed<T>(e: *const ffi::UiEvent, data: *mut c_void) {
-    unsafe {
-        let wrapper = &mut *(data as *mut ActionEventWrapperBoxed<T>);
-        let target = &mut *(wrapper.target);
-        let target_ref = target.as_mut();
-        action_callback(e, |event| {
-            (wrapper.callback)(target_ref, event);
-        });
-    }
-}
 
 fn action_callback<F>(e: *const ffi::UiEvent, f: F)
 where F: FnOnce(&mut ActionEvent) {
index 63e10238f9432a8b686ffe47f8e1a5c098d71218..5012b571b46568e3ceab65b5666f31967d48632f 100644 (file)
@@ -30,7 +30,7 @@
 
 use std::any::Any;
 use std::ffi::{c_char, c_int, c_void, CStr, CString};
-use crate::ui::{action_event_wrapper, action_event_wrapper_boxed, event, ffi, simple_event_wrapper, ui_object_get_context, ui_object_get_windowdata, ui_reg_destructor, ui_remove_destructor, untyped_event_wrapper, ActionEventWrapper, ActionEventWrapperBoxed, Event, EventWrapper, NoAppData, SimpleEventWrapper, SubList, UntypedEventWrapper, RUST_TYPED_OBJECT_BOX_ANY};
+use crate::ui::{action_event_wrapper, event, ffi, simple_event_wrapper, ui_object_get_context, ui_object_get_windowdata, ui_reg_destructor, ui_remove_destructor, untyped_event_wrapper, ActionEventWrapper, Event, EventWrapper, NoAppData, SimpleEventWrapper, SubList, UntypedEventWrapper, RUST_TYPED_OBJECT_BOX_ANY};
 
 use std::marker::PhantomData;
 use std::mem;
@@ -162,11 +162,14 @@ impl UiContext {
         // if this is a document context, use the document pointer as target
         // otherwise it is a UiObject context, in that case we use the window_data as target
         unsafe {
-            let doc_ptr = ui_context_document(self.ptr) as *mut T;
-            let target_ptr = if !doc_ptr.is_null() {
-                let doc_storage: *mut *mut Box<T> = doc_ptr.cast();
-                let target_ptr = *doc_storage;
-                self.add_action_for_target_boxed(target_ptr, name, f);
+            let doc_ptr = ui_context_document(self.ptr);
+            if !doc_ptr.is_null() {
+                let doc_storage: *mut *mut Box<Option<T>> = doc_ptr.cast();
+                let target_box_ptr = *doc_storage;
+                let target_opt = &mut **target_box_ptr;
+                if let Some(target_ptr) = target_opt {
+                    self.add_action_for_target(target_ptr, name, f);
+                }
             } else {
                 let obj_ptr = ui_context_obj(self.ptr);
                 assert!(!obj_ptr.is_null());
@@ -188,16 +191,6 @@ impl UiContext {
         }
     }
 
-    pub unsafe fn add_action_for_target_boxed<T, F>(&self, target_ptr: *mut Box<T>, name: &str, f: F)
-    where F: FnMut(&mut T, &mut event::ActionEvent) + 'static {
-        let wrapper = Box::new(ActionEventWrapperBoxed { callback: Box::new(f), target: target_ptr });
-        let ptr = self.reg_box(wrapper);
-        let cstr = CString::new(name).unwrap();
-        unsafe {
-            ui_add_action(self.ptr, cstr.as_ptr(), Some(action_event_wrapper_boxed::<T>), ptr as *mut c_void);
-        }
-    }
-
     pub fn add_action_untyped<F>(&self, name: &str, f: F)
     where F: FnMut(&mut event::ActionEvent) + 'static {
         unsafe {
@@ -260,7 +253,7 @@ pub extern "C" fn destroy_boxed<T>(data: *mut c_void) {
 
 pub struct UiDoc<T: ?Sized + UiModel + UiActions> {
     pub ctx: UiContext,
-    ptr: *mut *mut Box<T>,
+    ptr: *mut *mut Option<Box<T>>,
     _marker: PhantomData<T>
 }
 
@@ -303,14 +296,15 @@ impl<T: ?Sized + UiModel + UiActions> UiDoc<T> {
     pub fn new_from_box2<F>(data: Box<T>, init: F) -> UiDoc<T>
     where F: FnOnce(&mut T, &UiDoc<T>) {
         unsafe {
-            let doc = ui_document_new(mem::size_of::<*mut Box<T>>());
+            let doc = ui_document_new(10000 + mem::size_of::<*mut Box<T>>());
             let ctx = UiContext::from_ptr(ui_document_context(doc));
             ui_app_ref();
             ui_reg_destructor(ctx.ptr, std::ptr::null_mut(), doc_app_unref);
-            let data_ptr = ctx.reg_box(Box::new(data)); // returns *mut Box<T>
-            let doc_storage: *mut *mut Box<T> = doc.cast();
+            let data_ptr = ctx.reg_box(Box::new(Some(data))); // returns *mut Box<T>
+            let doc_storage: *mut *mut Option<Box<T>> = doc.cast();
             *doc_storage = data_ptr;
-            let doc_ref = &mut *data_ptr;
+            let doc_ref_option = &mut *data_ptr;
+            let doc_ref = doc_ref_option.as_mut().unwrap();
             doc_ref.init(&ctx);
             doc_ref.init_actions(&ctx);
             let doc_handle = UiDoc::<T> { ctx: ctx, ptr: doc.cast(), _marker: PhantomData };
@@ -319,34 +313,42 @@ impl<T: ?Sized + UiModel + UiActions> UiDoc<T> {
         }
     }
 
+    /// This function should not be used directly, only via the doc_cast macro.
+    ///
+    /// It is safe as long as f returns the same Box (with the same heap address). If it returns
+    /// a completely new Box, things will break (any event wrapper, that have a pointer to the
+    /// old target pointer).
     pub fn cast<U: ?Sized + UiModel + UiActions>(
         self,
         f: impl FnOnce(Box<T>) -> Box<U>,
     ) -> UiDoc<U> {
-         unsafe {
-             // replace the Box container for this document
-             // the destructor for the previous Box must be removed
-             ui_remove_destructor(self.ctx.ptr, self.ptr.cast());
-             ui_document_ref(self.ptr.cast());
-
-             let outer: Box<Box<T>> = Box::from_raw(*self.ptr);
-
-             // Prevent dropping the outer box while extracting the inner box
-             let outer = std::mem::ManuallyDrop::new(outer);
+        unsafe {
 
-             let inner: Box<T> = std::ptr::read(&**outer);
 
-             let new_inner: Box<U> = f(inner);
+            // because the doc is just transformed into a new type, the old doc must
+            // not be dropped
+            let doc = std::mem::ManuallyDrop::new(self);
 
-             let new_outer: Box<Box<U>> = Box::new(new_inner);
+            // Get the top level box, that is directly stored in the document and extract the
+            // inner value, which holds the rust document data object.
+            let mut outer: Box<Option<Box<T>>> = Box::from_raw(*doc.ptr);
+            let inner_value = outer.take();
+            // Because the old outer box is replaced, it can be dropped in this function, but
+            // usually there is already a destructor registered for it. Remove the destructor,
+            // so the outer box can be droped here.
+            ui_remove_destructor(doc.ctx.ptr, *doc.ptr.cast());
 
-             *self.ptr.cast::<*mut Box<U>>() = Box::into_raw(new_outer);
+            // Convert Box type and store it in the document
+            let new_value = inner_value.map(|v|f(v));
+            let data_ptr = doc.ctx.reg_box(Box::new(new_value));
+            let doc_storage: *mut *mut Option<Box<U>> = doc.ptr.cast();
+            *doc_storage = data_ptr;
 
-             UiDoc {
-                 ctx: self.ctx.clone(),
-                 ptr: self.ptr.cast(),
-                 _marker: PhantomData,
-             }
+            UiDoc {
+                ctx: doc.ctx.clone(),
+                ptr: doc_storage,
+                _marker: PhantomData,
+            }
         }
     }
 
@@ -358,18 +360,23 @@ impl<T: ?Sized + UiModel + UiActions> UiDoc<T> {
         UiDocProxy::from_ptr(self.ptr.cast())
     }
 
-    pub unsafe fn get_data_ptr(&self) -> *mut Box<T> {
+    pub unsafe fn get_data_ptr(& self) -> *mut Option<Box<T>> {
         unsafe {
-            let box_ptr: *mut *mut Box<T> = self.ptr.cast();
+            let box_ptr: *mut *mut Option<Box<T>> = self.ptr.cast();
             *box_ptr
         }
     }
 }
 
+/// Converts the document type to a dyn trait
+///
+/// Usage: doc_cast!(doc, dyn MyTrait)
 #[macro_export]
 macro_rules! doc_cast {
     ($doc:expr, dyn $trait:path) => {
-        $doc.cast(|b| Box::new(*b) as Box<dyn $trait>)
+        $doc.cast(|b| {
+            b as Box<dyn $trait>
+        })
     };
 }
 
@@ -382,10 +389,8 @@ impl<T: UiModel + UiActions> UiDoc<T> {
     
     pub fn new2<F>(data: T, init: F) -> UiDoc<T>
     where F: FnOnce(&mut T, &UiDoc<T>) {
-        unsafe {
-            let inner_box = Box::new(data);
-            UiDoc::new_from_box2(inner_box, init)
-        }
+        let inner_box = Box::new(data);
+        UiDoc::new_from_box2(inner_box, init)
     }
 
     /*
@@ -669,7 +674,10 @@ impl<T: ?Sized + UiModel + UiActions> UiDocProxy<T> {
             let doc: UiDoc<T> = UiDoc::from_ptr(self.ptr);
             unsafe {
                 let data = doc.get_data_ptr();
-                f(&doc, &mut *data);
+                let data_ref = &mut *data;
+                if let Some(dat) = data_ref {
+                    f(&doc, &mut *dat);
+                }
             }
             drop(self);
         });