imba / imba

🐤 The friendly full-stack language
https://imba.io
MIT License
6.3k stars 174 forks source link

Hook into node injection/alter #90

Closed evaisse closed 4 years ago

evaisse commented 7 years ago

Is there a way to achieve some kind of hooking into the dom tag creation/removal flow (for animation) by giving an API like the snabbdom one ?

thinking something like

tag myanimated < div

    def domshow animationDone

    def domhide animationDone

    def dommove animationDone
somebee commented 7 years ago

Are you talking about triggering methods when that dom-node is added / removed / moved in the dom-tree, or when animations are finished (hiding, showing, etc)?

Update: Sorry, saw your link to snabbdom now. It is certainly possible - but would be hacky to use without instead supporting it directly. I'll get back to you after looking at how easy it would be to do.

evaisse commented 7 years ago

I did manage to get something working by adding directly in imba.js that stuff (make a .imba Pull-Request later maybe). I always regret that UI frameworks does not include animations flow as first-class citizen.

imba.js


        /*
            Remove specified child from current node.
            @return {self}
            */

        Imba.Tag.prototype.removeChild = function (child){
            var self = this;
            var done = function () {
                var par = self.dom();
                var el = child instanceof Imba.Tag ? (child.dom()) : (child);
                delete child._onRemoval;
                if (el && el.parentNode == par) { par.removeChild(el) };
                return self;
            };

            if (child instanceof Imba.Tag && child.onhide) {
                if (!child._onRemoval) {
                    child._onRemoval = true;
                    child.onhide(function () {
                        done();
                    });
                }
            } else {
                child._onRemoval = true;
                return done();
            }

        };

        Imba.Tag.prototype.appendChild = function (node){
            if ((typeof node=='string'||node instanceof String)) { node = Imba.document().createTextNode(node) };
            if (node) { this.dom().appendChild(node._dom || node) };
            if (node instanceof Imba.Tag && node.onshow) node.onshow();
            return this;
        };

myapp.imba

tag mygentlyfadingtag

    def onshow
        TweenMax.fromTo dom, .2, {opacity: 0}, {opacity: 1}

    def onhide done
        TweenMax.to dom, .2, 
            opacity: 0
            onComplete: done

By the way, I did struggle a bit with the doc at first time, but now I'm confident with the imba syntax, I should say it's an awesome piece of work you did there. Give an awesome boost of speed to my react app. Congratulations.

somebee commented 7 years ago

Are you using 1.0.0-beta? If so, I guess you could use tag#mount and tag#unmount. These are triggered when the nodes are actually appearing in the document (attached to the root).

evaisse commented 7 years ago

In 1-0-0 mount/unmount methods are attached to TagManager not a tag

    Imba.TagManagerClass.prototype.unmount = function (node){
        return this;
    };

So this do not work.

tag mygentlyfadingtag

    def mount node
        TweenMax.fromTo dom, .5, {opacity:0}, {opacity:1}
        super

    def unmount node

        TweenMax.to node, .5, 
            opacity: 0
            onComplete: do super

Can you give me an hint to achieve the same behavior as above ?

Still, I can manage to do my animation stuff by wrapping this methods

Imba.Tag.prototype.originalAppendChild = Imba.Tag.prototype.appendChild;
Imba.Tag.prototype.originalRemoveChild = Imba.Tag.prototype.removeChild;
Imba.Tag.prototype.originalInsertBefore = Imba.Tag.prototype.insertBefore;

Imba.Tag.prototype.insertBefore = function (node) {
    this.originalInsertBefore(node);
    if (node instanceof Imba.Tag && node.onshow) node.onshow(node);
};

Imba.Tag.prototype.appendChild = function (node) {
    this.originalAppendChild(node);
    if (node instanceof Imba.Tag && node.onshow) node.onshow(node);
};

Imba.Tag.prototype.removeChild = function (child){
    var self = this;
    if (child instanceof Imba.Tag && child.onhide) {
        child.onhide(function () {
            self.originalRemoveChild(child);
        });
    } else {
        return self.originalRemoveChild(child);
    }

};
somebee commented 7 years ago

Oh, it should work, but I see now that it doesnt behave too naturally. It only works whenever all your rendering happens within the imba scheduler. You either need to use Imba.mount to mount your root tag, or make sure to call Imba.commit after it is attached to the document.

Here's a quick example: scrimba - quick demo of tag#mount. There are a few quirks at the end there, but that is due to a bug in how scrimba keeps previous code around on each run. Working on a fix for that :)


By the way, that screencast above - is not a regular screencast. You can pause it at any time and change the code yourself to rerun the example etc. All written in Imba. Pretty cool, right :)

evaisse commented 7 years ago

Alright ! thanks a lot for the demo, awesome tools here. I think I'll stick to the current implementation for my animations till the behavior had been stabilized then I'll switch to the regular mount/unmount api.

by the way, here's my demo app (just type some filters in the input to hide/show some entries) :

Again, thanks for all the great work here.