Skip to content
This repository has been archived by the owner on Jul 29, 2019. It is now read-only.

Alter higher level of event's DOM #1126

Closed
daniel-pfeiffer opened this issue Jul 27, 2015 · 21 comments
Closed

Alter higher level of event's DOM #1126

daniel-pfeiffer opened this issue Jul 27, 2015 · 21 comments
Assignees

Comments

@daniel-pfeiffer
Copy link

daniel-pfeiffer commented Jul 27, 2015

I need to illustrate some of my events with an SVG (actually a mini timeline for subparts of the event). This SVG should stretch the whole width of the event box. Since that can grow and shrink with zooming I don't know its width, so I declare 100%.

If I put it in content, that will be the much bigger width of my text (with some pretty css gradient to make it look part of the event):
in-content

To get it where I need it, I should put it two levels higher, directly into .vis-item, which I simulate here by dragging it in the debugger:
out-of-content

My problem is that there is no way of reaching that part of the DOM. I tried adding a <script> to content, to rearrange it, but it doesn't seem to get run (and it would be an awkward hack). I'd like to have an official way to get it in.

I'd be willing to do it, but I don't know your preference. Mine is write once, so I'd be happy with some hook function, a quick shot solution to just add e.g. bottomContent in RangeItem.prototype.redraw. This could be as simple as if (this.options.massageDOM) this.options.massageDOM(this.dom, itemData); fully under the caller's responsibility.

OTOH I see a solution, probably overkill but maximally coherent with what is already there: an alternate bottomTemplate, and update handling as in Item.prototype._updateContents. If I do all that, we'd have quite a bit of additional code, maybe replicated for several types. In a dynamic scenario, the tag for that should only be added if there actually is something to put in, which many users won't have.

What solution would you prefer me to do? Or is there something like this coming, that I missed?

@AlexDM0
Copy link
Contributor

AlexDM0 commented Jul 28, 2015

Hi,

Isnt it easier to just put the margins at 0?
http://visjs.org/docs/timeline/#Configuration_Options

Regards

@daniel-pfeiffer
Copy link
Author

Hi Alex,

I'm currently not touching the margin, but it shows up as - on all 3 div levels, which defaults to 0. I spent a lot of time playing with css on all levels, all to no avail. The thing is that due to your overflow magic vis-item-content is much wider than its parent vis-item-overflow. So my (1st example above) svg would somehow need to ignore its parent and inherit from its grand-parent.

If you can give me a combination of content html and matching css for text overflowing but svg exactly the dynamic width of the vis-item, I'd be very happy!

thanks -- Daniel

@josdejong
Copy link
Contributor

how about something like:

    .vis-item .vis-item-overflow {
      overflow: visible;
    }
    .vis-item .vis-item-content {
      width: 100%;
      padding: 0;
    }

For some more inspiration, see example rangeOverflowItem.html and the configuration option align (see docs).

@daniel-pfeiffer
Copy link
Author

Hi Jos,
wow, how does that work? Alas there are two problems:

  • the small one: if an event is partly out on the left edge, then the box of ...-overflow shows that nicely. But while ...-content has the same width, the box starts at the timeline's left edge. This is nice for the text, whose start can be read, but my svg gets shifted right too, and then juts out.
  • the huge one (which I'd seen -- I thought -- when using div instead of span inside): This totally messes up your layout, which (given enough events) looks like a broken printer.

thanks -- Daniel

@josdejong
Copy link
Contributor

About your first point: try the configuration option align:'left'.

I'm not sure what you mean with your second point.

@daniel-pfeiffer
Copy link
Author

Align would work, at the disadvantage of losing readability. Can I mimic what viz does, but only for the svg?
Re 2nd point: If I set ...-content width to 100% I get my events on top of one another -- hard to discern, but apparently only the overflow parts overlap:
in-content

@josdejong
Copy link
Contributor

Ha ha, that's quite a mess indeed. For stacking items, the Timeline determines the size of an item from it's outer and inner contents. If you set the contents width to match that of the outer border, the Timeline cannot determine the size of the overflowing contents... So here are two things at odds with each other.

From your earlier screenshots I understand that you want both the text contents to overflow and not overlap with other items (like example rangeOverflowItem.html), and you want an element having the same with as the outer border for drawing this SVG.

A few options:

  • Looking at your last screenshot, there is too much iformation anyway to show. If neatly stacked, the items would be stacked sky high. So maybe you can set the overflow:hidden for the inner contents, and use a title or some tooltip solution to display more information.
  • You could create a new custom Item type, extending RangeItem, which has your desired behavior.

@daniel-pfeiffer
Copy link
Author

You are absolutely right about stacking sky high ☹ Our major strategy for that one is filtered views, displaying only a subset. But I'm also looking forward to your implementing the open issue of making the body of the timeline (or better yet each group individually?) vertically scrollable.
For the custom type, I don't see where that would hook in. At first sight everything in vis.js is private and inaccessible from the outside. And the 4 existing event types are hard-wired in several places.

@josdejong
Copy link
Contributor

If you've too much information to display you indeed have to filter it, or hide most of it and display with popups/titles when hovering items.

The body of the Timeline automatically becomes vertically scrollable when you set a fixed height or maxHeight and the contents do not fit (only a vertical scrollbar is missing).

I thought we had an example somewhere showing how to create a custom item type but I can't find it. You can extend vis.timeline.components.items.RangeItem for this and for example call your new type MyRangeItem. Then you have to register your new type in the object vis.timeline.components.ItemSet.types, for example vis.timeline.components.ItemSet.types.myrange = MyRangeItem. Then you can use the new type "myrange".

@daniel-pfeiffer
Copy link
Author

Hi Jos, I wanted to at least say thank you! Even if I haven't gotten round to trying this yet, it sounds promising. However, with more realistic data, the sky high stacking has gotten worse and is my priority now.

@daniel-pfeiffer
Copy link
Author

I've gotten round to (partially) implementing my initial request. Essentially:

var node = $('#'+id)[0].parentNode.parentNode.parentNode; // hack depends on vis.js html structure!
item.className += ' highEnoughClass'; // will have these precalculated for the possible svg heights
myDataSet.update(item);
node.innerHTML += `<svg ...>...</svg>`;
timelineWidget.redraw();

For some reason this breaks the floating content, where the beginning sticks to the left edge as the item starts scrolling out and back in.
@josdejong What I haven't figured out is when to do this. There doesn't seem to be a callback for "timeline's DOM manipulation is finished". For now my workaround would be 1sec timers as long as not all getVisibleItems() have been processed by me, initially and on every rangechanged.

@josdejong
Copy link
Contributor

There is indeed no such hook as "beforeRendering" and "afterRendering". Maybe you can override the Core.prototype._redraw method with your own, which first invokes the original _redraw method and then executes your own logic.

@daniel-pfeiffer
Copy link
Author

Hi Jos, this seems to be down to the problem I had before: This time I explored webpack more deeply, but I still don't see how I can access Core, which gets wrapped into privacy.
I also now better understand what you suggested on Jul 30 and am exploring that, but came up against a surprise. When I extend node.innerHTML with <svg width="100%"...> as in my previous post it works, because node is alread rendered. But when I add it in my SvgItem.prototype.redraw, it's still just a dead bit of dom, and 100% seems to default to 0 in FF and auto in Chrome, both of which don't display it. If there is a later modification to the node, the svg suddenly becomes visible.

@josdejong
Copy link
Contributor

hm, that's true, Core wasn't exposed. I've added this in the develop branch. For now, you could access Core like this: var Core = vis.Timeline.prototype.constructor. Besides that, you could create a custom bundle and add/expose all classes that you're interested in yourself, see Custom builds.

As for the custom item you're creating: I'm not sure what's going on from your description, a RangeItem has a width determined from it's start and end. The width of an item is only calculated once, maybe at the first render the width is still 0 or something?

@daniel-pfeiffer
Copy link
Author

Wow, I finally got it. I was generating my svg-node as a string and essentially doing dom.frame.innerHTML += mysvg. This caused the whole vis-item-overflow (why do you confusingly call that member frame, not overflow?) to be rerendered and the vis-item-content you had cached in dom.content was no longer the real one.
I'm still not sure which way to do this. Wrapping vis.Timeline.prototype.constructor.prototype._redraw seems like a hack and I need to do it twice, once for you to render, then I modify the dom, then I let you rerender with the new size :-(
I need to extend RangeItem.prototype.redraw in just small place (but even though I added my option all over vis.js, it didn't pick it up, you know better how to add an option). Just please add two lines after:

  if (this.dirty) {
    if (this.options.domCallback)
      this.options.domCallback(dom,this.data);

Cloning the whole function into a subclass seems dirty, leaving me vulnerable to missing any future changes. Actually extending Item.prototype._updateContents would probably also do all I need. But I can't find a chain similar to the one you suggested for Core, for reaching that.

@josdejong
Copy link
Contributor

I'm still not sure which way to do this. Wrapping vis.Timeline.prototype.constructor.prototype._redraw seems like a hack

Yes, this is probably going to be hacky.

@daniel-pfeiffer
Copy link
Author

For now I'm patching each of your versions as it comes out, putting my actual code instead of the 2 callback lines I suggest above. Working like a marvel, but not a long term maintainable solution.
It would be so great, if you could include such a callback option, with whatever name suits you! I would need those arguments in any order though. If you consider dom to be private, I could of course more expensively search for it in the browser's dom.

@mojoaxel
Copy link
Member

mojoaxel commented Nov 1, 2016

@daniel-pfeiffer Is that still an issue for you? Should we add the functions or close this issue?

@vk7282
Copy link

vk7282 commented Nov 15, 2016

I think @daniel-pfeiffer is right and i also need this functionality to be implemented for my usage in the required project. So this feature can be a good advantage for more developers as well in the future.

@yotamberk
Copy link
Contributor

@daniel-pfeiffer, @vk7282 take a look at this: #2437
😄

@yotamberk yotamberk added this to the Minor Release v4.18 milestone Dec 16, 2016
@yotamberk
Copy link
Contributor

Added with #2437

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants