hyperapp-render icon indicating copy to clipboard operation
hyperapp-render copied to clipboard

renderToNode (or renderToElement)

Open apla opened this issue 4 years ago • 5 comments

Hi,

I'm submitting a ...

  • [ ] bug report
  • [x] feature request
  • [ ] other (Please do not submit support requests here (below))

Within hyperapp sometimes I need to make some preparations, to ensure everything is going fine with DOM/events/data. For example:

  1. generate <video> element, append some sources, and check which one browser wants to load;
  2. create <img> element and check if image is accessible and what size they have;
  3. insert svg fragment into already loaded though <object> tag svg dom.

It is really useful to have function, which renders h('video', ...) into new Element, which can be inserted into existing DOM or just loaded (like images or videos), because as of now my preparations looks like

const el = document.createElement('video');
el.addEventListener('loadeddata', this.onVideoLoad.bind(this), false);
el.addEventListener('abort', this.onVideoAbort.bind(this), false);
el.addEventListener('waiting', this.onVideoWaiting.bind(this), false);
el.addEventListener('loadedmetadata', this.onVideoMeta.bind (this), false);
el.setAttribute('preload', 'metadata');
el.setAttribute('muted', 'true');
el.setAttribute('playsinline', 'true');
this.config.sources.forEach(source => {
	var srcEl = document.createElement('source');
	srcEl.src = patchLocalVideo(source.url);
	srcEl.type = source.type;
	el.appendChild(srcEl);
});

I can implement it myself and make a pull request if you're interested in this.

apla avatar Sep 08 '19 21:09 apla

Sorry, but I do not understand how this is related to hyperapp-render package? It just renders your VDOM to string and that's it, for adding event listeners, changing nodes and attributes you should use hyperapp package. Also it is not clear why do you want to patch some urls in runtime, why can't you provide correct urls from the beginning.

How I see your code should look like:

// app.js
import { h } from 'hyperapp'

export const state = {
  sources: [/*...*/]
}

export const actions = {
  onVideoLoad() {/*...*/},
  onVideoAbort() {/*...*/},
  onVideoWaiting() {/*...*/},
  onVideoMeta() {/*...*/},
}

export const view = (state, actions) => (
  <video
    onloadeddata={actions.onVideoLoad}
    onabort={actions.onVideoAbort}
    onwaiting={actions.onVideoWaiting}
    onloadedmetadata={actions.onVideoMeta}
    preload="metadata"
    muted="true"
    playsinline="true"
  >
    {state.sources.map((source) => (
      <source key={source.url} src={source.url} type={source.type} />
    ))}
  </video>
)
// client.js
import { app } from 'hyperapp'
import { state, actions, view } from './app'
app(state, actions, view, document.getElementById('app'))
// server.js
import http from 'http'
import { renderToString } from 'hyperapp-render'
import { state, actions, view } from './app'
http.createServer((req, res) => {
  res.setHeader('Content-Type', 'text/html')
  res.end(renderToString(
    <html>
      <head><title>Video</title></head>
      <body>
        <div id="app">{view(state, actions)}</div>
      </body>
    </html>
  ))
}).listen(8080)

P.S.: You can use hyperapp-starter to test it.

frenzzy avatar Sep 09 '19 10:09 frenzzy

hyperapp will render those elements visible. I need to preload elements outside of document to ensure incoming data is right (for example, video have exact resolution of 5520x1320 pixels) or to cache it (for example, set image src and wait it loaded) and only after this step push data into hyperapp view.

I can hide those elements or put outside browser view box, but it is huge slowdown, ui freeze and sometimes browser crash when you have many high resolution images and videos on page. When browser already loaded data from videos and images, everything goes smooth.

Why hyperapp-render? This package prepares HTML/SVG text to be hydrated before app renders view, and renderElement will prepare DOM node before app renders view with the same jsx/h syntax.

So, my example will look like this:


// client.js
import { app } from 'hyperapp'
import { state, actions, view, preloadVideo } from './app'
app(state, actions, view, document.getElementById('app'))

const appData = {
  videos: [{
    sources: [{
      url: "1.mp4",
      type: "video/mp4"
    }, {
      url: "1.webm",
      type: "video/webm"
    }]
  }]
};

// This should work as part of subscription, but I still learning how to do that
Promise.all (appData.videos.map (v => preloadVideo(v))).then (videoEvents => {
  // browser will load one of videos by type:
  // safari for mac and ios + ie will load h.264
  // chrome + ff will load webm
  // but currentSrc will definitely contain playable video
  changeState (state => (videos: videoEvents.map (event => event.target.currentSrc)))
})


// app.js
import { h } from 'hyperapp'

export const state = {
  videos: []
}

export const actions = {
  onVideoLoad() {/*...*/},
  onVideoAbort() {/*...*/},
  onVideoWaiting() {/*...*/},
  onVideoMeta() {/*...*/},
}

export const view = (state, actions) => (
  <video
    onloadeddata={actions.onVideoLoad}
    onabort={actions.onVideoAbort}
    onwaiting={actions.onVideoWaiting}
    onloadedmetadata={actions.onVideoMeta}
    preload="metadata"
    muted="true"
    playsinline="true"
  >
    {state.sources.map((source) => (
      <source key={source.url} src={source.url} type={source.type} />
    ))}
  </video>
)

export function preloadVideo (videoParams) {
return new Promise ((resolve, reject) => {
  const videoEl = renderElement(<video
    onloadeddata={resolve}
    preload="metadata"
    muted="true"
    playsinline="true"
  >
    {state.sources.map((source) => (
      <source key={source.url} src={source.url} type={source.type} />
    ))}
  </video>);
  videoEl.load()
});
}

apla avatar Sep 09 '19 11:09 apla

I still do not see why/how you use hyperapp-render. If you want to display some view after videos or images are loaded, you don't need hyperapp-render, just use hyperapp:

import { app, h } from 'hyperapp'

const state = {
  videoIsReadyToRender: false
}

const actions = {
  load() {
    // (pre)load video here
    return { videoIsReadyToRender: true }
  }
}

const view = (state, actions) => (
  <div onload={actions.load}>
    {state.videoIsReadyToRender ? <YourVideo /> : <b>Loading video...</b>}
  </div>
)

app(state, actions, view, document.body)

frenzzy avatar Sep 09 '19 13:09 frenzzy

DINAMO-loading

apla avatar Sep 09 '19 13:09 apla

😁Let's go to Slack and discuss in chat mode quickly (my nickname is @frenzzy)

frenzzy avatar Sep 09 '19 14:09 frenzzy