monio icon indicating copy to clipboard operation
monio copied to clipboard

Feature: `doRender(..)`

Open getify opened this issue 3 years ago • 3 comments

This is a fancy, reactive string builder. It's used to define "components" as do-routines that yield string(s) which are all concated together to produce a chunk of HTML, which can then be shoved into the DOM. Since it's based on IOx, it can be set as dependent on other IOs/IOxs as inputs and reactively re-rendered on each update.

Example for how to use the utility:

// this could be an IOx which can have its value updated
// over time if desired 
var id = IO.of(42);

doRender(whatever,[ id ])

// this step is just console.log()ing, but it could instead
// be pushing the HTML into a DOM element
.map(v => console.log("html",v))

.run({});

// here's a "component" -- you `yield` out string(s), or any
// monad that produces a string, and it concats them all.
//
// the nice thing is, other than the string concating, this
// is a full do-routine that can be async, do any monad/IO
// operations, etc, whatever you need to define your
// component's markup
function *whatever(context,id) {
	yield `<div id="${id}">`;
	for (let i = 0; i < 3; i++) {
		yield `<span>${i}</span>`;
	}
	yield `</div>`;
}

And here's the candidate first implementation of doRender(..):

function doRender(gen,deps = [],...args) {
	return IOx.do(runner,deps);

	// ************************************
	
	async function *runner(context,...depsVals) {
		var htmlIO = IO.of("");
		
		var it = gen(context,...depsVals,...args);
		var res;
		while (true) {
			res = it.next(res);
			if (isPromise(res)) {
				res = await res;
			}
			else {
				let done = res.done;
				
				if (isMonad(res.value)) {
					if (IO.is(res.value)) {
						res = res.value.chain(concatIfString);
					}
					else if (Nothing.is(res.value)) {
						return IO.of("");
					}
					else if (Either.is(res.value) || Maybe.is(res.value)) {
						res = res.value.fold(IO.of,concatIfString);
					}
					else {
						res = res.value.fold(concatIfString);
					}
				}
				else {
					res = concatIfString(res.value);
				}

				res = yield res;
				if (done) {
					return htmlIO.run(context);
				}
			}
		}
		
		// ************************************
		
		function concatIfString(v) {
			if (typeof v == "string") {
				htmlIO = htmlIO.concat(IO.of(v));
				return IO.of();
			}
			else {
				return IO.of(v);
			}
		}
	}
}

getify avatar Apr 09 '22 16:04 getify

OK, so at @DrBoolean's sage observation, this can be generalized to support any monoid (not just strings) without too much complexity:

var id = IO.of(42);

doBuilder(whatever,v => (typeof v == "string"),[ id ])
.map(v => console.log("html",v))
.run({});

function *whatever(context,id) {
	yield `<div id="${id}">`;
	for (let i = 0; i < 3; i++) {
		yield `<span>${i}</span>`;
	}
	yield `</div>`;
}

And the implementation of doBuilder(..):

function doBuilder(
	doRoutine,
	isMonoid = v => (v && typeof v.concat == "function"),
	deps = [],
	...args
) {
	return IOx.do(runner,deps);

	// ************************************
	
	async function *runner(context,...depsVals) {
		var concatIO = IO.of("");
		
		var it = doRoutine(context,...depsVals,...args);
		var res;
		while (true) {
			res = it.next(res);
			if (isPromise(res)) {
				res = await res;
			}
			else {
				let done = res.done;
				
				if (isMonad(res.value)) {
					if (IO.is(res.value)) {
						res = res.value.chain(concatIfMonoid);
					}
					else if (Nothing.is(res.value)) {
						return IO.of("");
					}
					else if (Either.is(res.value) || Maybe.is(res.value)) {
						res = res.value.fold(IO.of,concatIfMonoid);
					}
					else {
						res = res.value.fold(concatIfMonoid);
					}
				}
				else {
					res = concatIfMonoid(res.value);
				}

				res = yield res;
				if (done) {
					return concatIO.run(context);
				}
			}
		}
		
		// ************************************
		
		function concatIfMonoid(v) {
			if (isMonoid(v)) {
				concatIO = concatIO.concat(IO.of(v));
				return IO.of();
			}
			else {
				return IO.of(v);
			}
		}
	}
}

getify avatar Apr 10 '22 01:04 getify

Next revision/abstraction:

var id = IO.of(42);

strBuilderX(whatever,[ id ])
.map(v => console.log("html",v))
.run({});

function *whatever(context,id) {
	yield `<div id="${id}">`;
	for (let i = 0; i < 3; i++) {
		yield `<span>${i}</span>`;
	}
	yield `</div>`;
}

And the updated implementation:

function strBuilder(doRoutine,...args) {
	var runner = buildRunner(doRoutine,v => (v && typeof v == "string"),...args);
	return IO.do(runner);
}

function strBuilderX(doRoutine,deps = [],...args) {
	var runner = buildRunner(doRoutine,v => (v && typeof v == "string"),...args);
	return IOx.do(runner,deps);
}

function buildRunner(doRoutine,isMonoid = v => (v && typeof v.concat == "function"),...args) {
	return async function *runner(context,...depsVals){
		var concatIO = IO.of("");
		
		var it = doRoutine(context,...depsVals,...args);
		var res;
		while (true) {
			res = it.next(res);
			if (isPromise(res)) {
				res = await res;
			}
			else {
				let done = res.done;
				
				if (isMonad(res.value)) {
					if (IO.is(res.value)) {
						res = res.value.chain(concatIfMonoid);
					}
					else if (Nothing.is(res.value)) {
						return IO.of("");
					}
					else if (Either.is(res.value) || Maybe.is(res.value)) {
						res = res.value.fold(IO.of,concatIfMonoid);
					}
					else {
						res = res.value.fold(concatIfMonoid);
					}
				}
				else {
					res = concatIfMonoid(res.value);
				}

				res = yield res;
				if (done) {
					return concatIO.run(context);
				}
			}
		}
		
		// ************************************
		
		function concatIfMonoid(v) {
			if (isMonoid(v)) {
				concatIO = concatIO.concat(IO.of(v));
				return IO.of();
			}
			else {
				return IO.of(v);
			}
		}
	};
}

getify avatar Apr 10 '22 07:04 getify

else if (Nothing.is(res.value)) {
   return IO.of("");
}

This line needs to be generalized from using "" as the empty monoid value to having such a value parameterized to buildRunner(..).

getify avatar Jul 28 '22 15:07 getify