Signmak Immersive 3DVista development Custom HTML inside a tour

Running your own HTML inside a 3DVista tour

3DVista will host your own HTML, and that is where the useful work is. The part nobody writes down is how your code gets hold of the tour, and what happens when a second tool wants it too.

The embedded window is a guest, not a resident

Your HTML runs inside the tour's page, but it is your document, with your own scripts and your own styles. It does not automatically know anything about the tour around it, and the tour does not know anything about it either.

So the only question that matters is how the two are introduced, and everything else in this guide follows from the answer.

One thing to know before you start: 3DVista publishes no JavaScript API and says so plainly in its own knowledge base. Everything below is a surface that works, not a surface anybody promised, which is exactly why so little has been written about it. What that means, and how to write against it safely, is its own page.

The tour is handed to you. Do not go looking for it

The instinct is to reach for a global and hope. That is the version that works on your machine and breaks in somebody's published tour, because what a global is called is not yours to depend on.

The reliable shape is the opposite: the tour is passed in, from the action that runs your code, and your job is to take whatever arrives and decide whether it is really the tour.

Check it by shape, not by name

A name can change between versions and can be taken by something else. What does not change is what the object can do. So the test is behavioural:

function hasTour(x){
  return !!(x && typeof x.getComponentByName === 'function'
              && typeof x.setComponentVisibility === 'function');
}

If it can find a component by name and set a component's visibility, it is the tour, whatever it is called today. This is the single most useful line in the whole file and it is four lines long.

Cache it, because you will not always be given it

You get handed the tour when an action fires. Your code will need it later, at a moment nobody is calling you. So the first time a real one arrives, keep it:

function resolveTour(x){
  if (hasTour(x)) { W._dvLastStoredTour = x; return x; }
  if (hasTour(W._dvLastStoredTour)) return W._dvLastStoredTour;
  return null;
}

Two rules fall out of those four lines. Never cache something you have not shape checked, or you will store a broken reference and every later call fails somewhere far away from the cause. And always re-check the cache, because the thing you stored an hour ago may no longer be the live tour.

Reading state back is not the mirror of writing it

Setting is simple and there is one way to do it. Reading is not, and this is where most add-ons quietly go wrong: they set something, assume it took, and drift out of sync with the tour over a long session.

In practice you try the methods that might be there, in order, and you keep your own record as the last resort:

function getVisible(component, name){
  try { if (typeof component.isVisible  === 'function') return !!component.isVisible();  } catch(e){}
  try { if (typeof component.getVisible === 'function') return !!component.getVisible(); } catch(e){}
  return (name in myState) ? !!myState[name] : null;   // null means: genuinely unknown
}

The try/catch around each attempt is not defensive noise. A method can exist and still throw on a component that is not currently mounted, and one throw with no catch takes the rest of your tool down with it.

Returning null for unknown rather than false is the other half. False means hidden. Null means you do not know, and a toggle that treats those two the same will flip the wrong way the first time it is asked before the tour is ready.

Two tools in one tour will collide

This is the failure nobody warns you about, and it arrives on the day a client asks for a second feature. Both tools were written the same sensible way, both put their state on the window, and the second one loaded wins. The first one does not error. It just stops behaving, which is far worse to debug.

The fix is that every tool owns exactly one global, named after itself, and everything it knows lives under it:

window._dvAudioPlayers = window._dvAudioPlayers || {};
window._dvAudioPlayers[myInstanceId] = { state: {}, timers: {} };

A registry keyed by instance, not a single object, because the same tool can legitimately appear twice in one tour. I ship several separate 3DVista tools and they are written so that any combination of them can sit in the same tour without any of them noticing the others. That constraint is worth designing for on the first one, because retrofitting it across four finished tools is a bad afternoon.

The skeleton

Put it together and the vendor specific part lives in exactly one place, which is the point. If 3DVista ever changes how the tour is handed over, you edit two functions and nothing else in your tool knows it happened.

(function(W, D){
  var NS = '_dvMyTool';
  W[NS] = W[NS] || {};

  function hasTour(x){ /* shape check, above */ }
  function resolveTour(x){ /* cache, above */ }

  W[NS].run = function(tourFromAction, args){
    var tour = resolveTour(tourFromAction);
    if (!tour) return;                      // fail quietly, never throw into the tour
    var c = tour.getComponentByName(args.target);
    if (!c) return;
    tour.setComponentVisibility(c, true);
  };
})(window, document);

Fail quietly. Your tool throwing an exception into the tour's own event loop is how you turn a feature that did not work into a tour that does not work.

What this is worth

None of this is exotic. It is the difference between a tour that does something clever once and a tour that keeps doing it after the client has added two more things to it.

If you would rather it was just built, that is the 3DVista development work, and you can send an existing tour rather than starting again.

What you get

  • How the tour object reaches your code, and why it is given rather than found
  • Why you identify it by shape instead of by name
  • Why it has to be cached the first time you see it
  • Reading state back, which is not the mirror of writing it
  • The collision that breaks the second tool you add to a tour
  • A skeleton to paste, with the vendor call in exactly one place