Search code examples
cyclejs

Can there be more than one DOM object in the Cycle.js drivers?


All the Cycle.js examples I've found so far, use a single DOM object, named "DOM", in the drivers argument to run(main, drivers). Is it possible to have more than one object, e.g., one named "DOM1" and another "DOM2"? The purpose of this would be to control two separate dynamic DOM areas within a single HTML page, in order to keep a third DOM area statically defined in index.html, and sandwiched between DOM1 and DOM2.

As a side question, the examples I've seen typically target an HTML div with an id of #app or #main-container, and then the sink is defined with a @cycle/dom div function, thus creating AFAICT an unnecessary div within a div. I haven't found a clear explanation or reference of how the virtual nodes are supposed to be defined. Say that DOM2 above targets an HTML form element and that is supposed to contain two input elements. Does it have to start with a div as in all the examples, or can the inputs be defined directly in the .map call, and if so, how?


Solution

  • There is nothing preventing you from having a DOM1 and DOM2 sinks in your app. bloodyKnuckles' example illustrate that perfectly https://esnextb.in/?gist=b54baa4131974b7f12d190fb63be8aeb

    That being said, I am not sure I really see the point of doing this. If it's a matter of performance, I don't think you will gain much in splitting the rendering of your app into two DOMDrivers. The virtual DOM lib (in cycle's case snabbdom) is tailored to recognize pieces of DOM that haven't change from those which have and only updates the latter.

    If it's a matter of responsibilities (those 2 pieces of DOM have very different purposes), then I would rather create two different cycle apps that both render in different parts of the DOM. (and then call run twice in your main file)

    function app1(sources) {
      return {
        DOM: xs.of(div("hello from app1"))
      }
    }
    
    
    function app2(sources) {
      return {
        DOM: xs.of(div("hello from app2"))
      }
    }
    
    run(app1, {
      DOM: makeDOMDriver("#app1")
    })
    
    
    run(app2, {
      DOM: makeDOMDriver("#app2")
    })
    

    This way you have a clear separation of the concerns of both apps.

    Now to answer your question about why a piece of virtual DOM needs to be wrapped in a div. It is because a piece of virtual DOM have to have a single root element. Said otherwise: a piece of virtual DOM have to be standalone (just like an HTML document only has a single <html> element which is the root). It is, actually, a nice constraint to have because it forces you to have standalone components. In the example you give (with an <input> field), there is absolutely no problem in returning a vDOM like this:

    DOM: xs.of(input(/*...*/))
    

    But if your component has an input and a label, then you will need to wrap it in another vNode

    DOM: xs.of(div([label(/*...*/), input(/*...*/)])