Create tasklist with DataviewJS in CustomJS?

Hello,
I am trying to organize my workshop/video course material with Obsidian and DataviewJS and it worked pretty good.
My material consists of

  • courses
    • chapters
      • lessons

A chapter note has outgoing links to lessons and contains a dataviewJS overwiew of all tasks in the linked lessons.

This is how I create the list of tasks:

var lessonfolder = "_RMKA/lessons"; 
const olessons = 
	// all outgoing notes
	dv.current().file.outlinks
	// only those below the lesson folder
	.filter(f => f.path.startsWith(lessonfolder))
	// make page object from link
	.map(f => dv.page(f));

for (let i = 0; i < olessons.length; i++) {
	let lesson = olessons[i]
	let tasks_total = lesson.file.tasks.length
	let tasks_open =  lesson.file.tasks.where(t => !t.completed).length
	let lesson_number = i + 1
	
	dv.header(3, "📖 " + lesson_number + ". " + lesson.file.name)
	dv.paragraph(" (" + (tasks_total - tasks_open) + " von " + tasks_total + " erledigt)")
	dv.taskList(lesson.file.tasks, false);
}


Well… that’s almost good. The big drawback of this approach is that the Obsidian templates I am using contain the complete JS code which makes it very hard to maintain it. Whenever I want to change the code, I have to change all notes.

Then I discovered the customJS plugin. WOW :bomb: !

I think that definitely can solve my problem.
But I am struggling…

It seems that I cannot create HTML elements from inside of the JS class.

That’s the class chapterLessons. It’s almost the same code, with little differences:

  • the class ChapterLessons holds the method getLessonTasks
  • instead of dv.current(), the function uses chapter as argument
  • because I am in a 2rd party plugin, I create the dataview API object with let dv = DataviewAPI
class ChapterLessons {
    getLessonTasks(chapter) {
		let dv = DataviewAPI;
        
		//Create a table with all notes linked from "chapter" which are in the folder "lessons".
		var lessonfolder = "_RMKA/lessons"; 
		// all outgoing notes
		const olessons = 
			// all outgoing notes		
			chapter.file.outlinks
		   	// only those below the lession folder
			.filter(f => f.path.startsWith(lessonfolder))
			// create a page object from the link object
			.map(f => dv.page(f));
		for (let i = 0; i < olessons.length; i++) {
			let lesson = olessons[i]
			let tasks_total = lesson.file.tasks.length
			let tasks_open =  lesson.file.tasks.where(t => !t.completed).length
			let lesson_number = i + 1
			dv.header(3, "📖 " + lesson_number + ". " + lesson.file.name)
			dv.paragraph(" (" + (tasks_total - tasks_open) + " von " + tasks_total + " erledigt)")
			dv.taskList(lesson.file.tasks, false);
		}	
    }
}

Then I replaced the JS code in the note with that line:

const {ChapterLessons} = customJS;
ChapterLessons.getLessonTasks(dv.current());

That should do the trick.
But it shows this error in the note:

Evaluation Error: TypeError: dv.header is not a function
    at ChapterLessons.getLessonTasks (scripts/js/ChapterLessons.js:49:7)
    at eval (eval at <anonymous> (plugin:dataview), <anonymous>:8:16)
    at DataviewInlineApi.eval (plugin:dataview:18619:16)
    at evalInContext (plugin:dataview:18620:7)
    at asyncEvalInContext (plugin:dataview:18630:32)
    at DataviewJSRenderer.render (plugin:dataview:18651:19)
    at DataviewRefreshableRenderer.maybeRefresh (plugin:dataview:18235:22)
    at t.e.tryTrigger (app://obsidian.md/app.js:1:695499)
    at t.e.trigger (app://obsidian.md/app.js:1:695432)
    at t.trigger (app://obsidian.md/app.js:1:2028169)

I tried to tackle the error down by commenting lines; when I omit header and paragraph, the error in the note disappears, but the Obsidian developer console throws an error:

plugin:dataview:19208 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'createEl')
    at DataviewApi.taskList (plugin:dataview:19208:39)
    at ChapterLessons.getLessonTasks (scripts/js/ChapterLessons.js:51:7)
    at eval (eval at <anonymous> (plugin:dataview:1:1), <anonymous>:8:16)
    at DataviewInlineApi.eval (plugin:dataview:18619:16)
    at evalInContext (plugin:dataview:18620:7)
    at asyncEvalInContext (plugin:dataview:18630:32)
    at DataviewJSRenderer.render (plugin:dataview:18651:19)
    at DataviewRefreshableRenderer.maybeRefresh (plugin:dataview:18235:22)
    at t.e.tryTrigger (app.js:1:695499)
    at t.e.trigger (app.js:1:695432)

Now tasklist seems to have a problem.
I am completely lost now and a bit frustrated… :frowning:
Can somebody point me into the right direction? What am I doing wrong?

Thanks a lot!
Simon

HA!; Once again it has been proven that a problem can be solved just by putting it into words :slight_smile:

It was actually very simple: the dv (dataview) object created in the class function probably has no context to the page (at least that’s how I explain it to myself…)
Instead of passing dv.current(), I pass the whole dv into the function and create dv.current inside the object.

class ChapterLessons {
    getLessonTasks(dv) {               // <=====
		let chapter = dv.current()   // <=====
        
		//Create a table with all notes linked from "chapter" which are in the folder "lessons".
		var lessonfolder = "_RMKA/lessons"; 
		// all outgoing notes
		const olessons = 
			// all outgoing notes		
			chapter.file.outlinks
		   	// only those below the lession folder
			.filter(f => f.path.startsWith(lessonfolder))
			// create a page object from the link object
			.map(f => dv.page(f));
		for (let i = 0; i < olessons.length; i++) {
			let lesson = olessons[i]
			let tasks_total = lesson.file.tasks.length
			let tasks_open =  lesson.file.tasks.where(t => !t.completed).length
			let lesson_number = i + 1
			dv.header(3, "📖 " + lesson_number + ". " + lesson.file.name)
			dv.paragraph(" (" + (tasks_total - tasks_open) + " von " + tasks_total + " erledigt)")
			dv.taskList(lesson.file.tasks, false);
		}	
    }
}

Inside the notes:

//Create ChapterLessons object from scripts/js/ChapterLessons.js (plugin: customJS)
const {ChapterLessons} = customJS;
ChapterLessons.getLessonTasks(dv);      // <=====

Thanks for giving me a place to develop my thoughts :laughing:

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.