await for specific cell value

I’ve created a spinner that is showing while waiting for user input. Once user enters the data, the spinner cell value becomes Blob() object.

The problem is that while the cell value is a spinner, which is a markdown string, all other cells that are dependent on that fail in red. How to make them actually await until the value of a spinner cell is not markdown?

Try using Observable Inputs with the required option:

viewof zipURL = Inputs.text({placeholder: "Paste .zip file URL..", submit: "Go", required: true})
1 Like

If I do this, then the spinner is not moving too. And I like the text control by @jashkenas.

In your notebook, you have the following line:

zipStream = zipURL
  ? fetch(zipURL).then((r) => r.blob())
  : md`Waiting for the input${zipURLSpinner}`

This effectively does exactly what you need. I suppose you could do something similar in your definition of zipFile and the output of aq.table by testing that zipURL is well formed.

The most direct translation of what you are trying to do is to lift your logic up into a view

viewof zipStream = {
  if (zipURL.length > 0) {
    return Inputs.input(await fetch(zipURL).then(r => r.blob()));
  } else {
    return Object.defineProperty(md`waiting for URL${zipURLSpinner}`, "value", {
      value: invalidation
    });
  }
}

Inputs.input is shorthand for creating a view that has not DOM and just a value. You could also do it with

Object.defineProperty(html`<span`, 'value', {value: await fetch(zipURL).then(r => r.blob())})

or

const ui = html`span`
ui.value = await fetch(zipURL).then(r => r.blob())

A view is always a top level event emitter (usually a DOM node) with a “value” property. So we use the top for presentation stuff like your spinners, and the ‘value’ for the data (your blob).

If I were implementing this, it looks like you are trying to create a sequential UI of “do A, then do B”, which I use coroutines for: Composing views across time: viewroutine / Tom Larkworthy / Observable but its very tricky to get right and I am not really sure I have that abstraction right yet. So take that more as academically interesting at the moment.

2 Likes

Patching explicit checks for specific variable in every dependent element is what I am trying to avoid. Because that would make such element non-exportable.

@tomlarkworthy I think the easiest way would be to just use two cells - 1 to have a spinner, which will hide itself once .zip value appears, and another with the vale. But am trying to decompose the problem to avoid extra white space.

Here is are some things that I think I got right.

  1. The final goal for the .zip control is to be able to provide the .zip from either URL, FileAttachment, Uploaded .zip or address hash.
  2. The cell should always return one type of the value, or else all dependent cells should contain a type check.
  3. For the cell to be “awaitable” it should return a JavaScript Promise.
  4. md`` and html`` elements return DOM objects that are not “awaitable”.
  5. It is impossible to make DOM objects “awaitable” by themselves, because turning them into Promise objects will make them unrenderable.
  6. It is possible to attach .value property to DOM objects that may or may not be a Promise.

Then there are some things I am not 100% sure are correct.

  1. To help build DOM elements that make sure the attached .value is a Promise, Observable provides Input.* helpers and viewof keyword.
  2. If Observable cell refers to other cell with DOM node, it automatically tries to see if there is a .value and use that.
  3. Awaitable .values are plain JavaScript Promises. You can create and assign them themselves.
  4. Once the Promise in another cell is resolved, the link between cells becomes lost. You need to make sure the Promise returns value, but is never completely resolved (if that’s not the case - then I don’t get how it works at all).

I will try now try to make my zipStream return only one type of object - DOM with .value and see how it goes.

Not quite. viewof produces two cells internally. If you write viewof foo, you end up with viewof foo (which has the DOM element as its value), and foo (which holds the value of the DOM element’s value property). You may also find this explanation helpful.

Now, whenever a cell produces a promise as its value, Observable’s Runtime will wait for that promise to resolve before handing it off to other cells. You can play around with that behavior by using the stdlib’s Promises helpers, e.g.:

// Produces the string "done!" after 3 seconds
foo = Promises.delay(3000, "done!")

Cells cannot be awaitable, because the Runtime abstracts that away for you. It only runs/reruns dependent cells once the promise has resolved.

There’s no link between cells. Observable wraps your cell’s code in a function that receives all referenced values as arguments. For example, if a cell contains:

`${foo} ${bar}`

then Observable will compile that cell to

"use strict";(
function(foo,bar){return(
`${foo} ${bar}`
)}
)

Now whenever the foo or bar cell produces a new value, this function will be invoked with their updated values, and the returned value becomes the cell’s new value.

In JavaScript, everything that’s not a scalar value (string, number, bool …) is passed around by reference. If multiple cells consume a value that contains a DOM element, then they all have a reference to same element. If the value updates, it might produce a new reference, but it doesn’t have to:

hey = {
  const hey = html`<p>Hello!`;
  while(true) {
    yield hey;
    await Promises.delay(3000);
  }
}

Let’s verify that with the following code (note that this contains the cell’s previous value, which is undefined on the first run). You should see the above cell’s “Hello!” start out red, then turn green after 3 seconds:

{
  hey.style.color = this === hey ? 'green' : 'red';
  return hey;
}

To really see that we’re only working with references, you could even create an intermediate cell:

ho = hey

and then reference that:

{
  ho.style.color = this === ho ? 'green' : 'red';
  return ho;
}

A word of warning: This pattern produces side effects, because it modifies another cell’s value. You should try to avoid side effects in Observable cells.

1 Like

Still trying to grasp that bit by bit. Looks like viewof is a top level construct, and only for named cells - viewof experiments / Anatoli Babenia / Observable - which means I can not just create a few viewof “objects” in my cell and return them conditionally.

So there are two paths for me right now.

  1. Use top level viewof, but then I don’t know where to insert my custom logic which should monitor three parent cells for changes.
  2. Write the logic without viewof and return DOM with .value - and this probably won’t work, because without viewof nobody reads .value or listens for events.

I think it hasn’t been mentioned explicitely yet, but you can reference a view’s DOM element directly, to avoid a dependency on its value. I meant to write a lengthy response here, but a notebook is actually the superior format for that.

I’ve started one, part 1 is done, don’t know if I’ll finish the rest today, but you might as well take a peek already:

1 Like

Part 2 is done and covers cell dependencies, viewof and generators.input. I didn’t touch on promises in this context because I don’t think they’ll become relevant as a direct tool.

Hopefully I’ll find time tomorrow to finish part 3, but part 2 should already give you all the background required to come up with a solution.

1 Like

I’ve filled the remaining gaps and published the notebook as

3 Likes

I need to draw some boxes to understand who wraps who with or without viewof, but I think I’ve got the idea to repeat that.