vuejs / Discussion

Vue.js discussion
167 stars 17 forks source link

wait-for directive and events not making sense #273

Open jakeryansmith opened 9 years ago

jakeryansmith commented 9 years ago

Ok so I've been at this for the last 2 hours and I can't figure it out. I'm trying to postpone the rendering of a component using the 'wait-for' directive. I've tired every possible thing I can think of. Could someone maybe give an example of how this works? Thanks!

adam12 commented 9 years ago

Possibly something like this?

http://jsbin.com/noduyaviju/edit?html,js,output

jakeryansmith commented 9 years ago

Your code worked, which has helped clarify what the real issue might be. It looks like the

this.$emit('data-loaded');

only works if it's inside a function? If I do this:

compiled: function () {
        this.$emit('data-loaded');
    },

It does not work; however, placing it inside a function like you did:

compiled: function () {
        // Some long task, maybe XHR?
        //setTimeout(function() {
        this.$emit('data-loaded');
        //}.bind(this), 2000);
    },

works just fine. So going back to the original problem I had: I'm trying to use the vue-resource plugin to pull in data from an API and I want to emit the event when it's finished:

this.$http.get(url)
   .success(function (data, status, request) {
      this.product = data;
      this.$emit('data-loaded');
})

But this does not work for some reason.

I hope that makes sense. Any ideas? Thanks!

jakeryansmith commented 9 years ago

I think I have found the issue. It has to do with a third party plugin tinyMCE. The component I'm loading has a textarea field I want to use as a tinyMCE WYSIWYG editor. This works fine until I try to delay the loading of the component.

swift1 commented 9 years ago

I dug a little deeper into this issue and found out that the reason it doesn't work is that the DOM isnt ready to replace the template at the time the component is compiled. It is indeed possible to call the $emit without being inside a function though. Consider the following example for more clarification:

new Vue({
  el: "#app",
     events: {
        'hook:created': function () {
            console.log('created');
        },
        'hook:beforeCompile': function () {
            console.log('beforeCompile');
        },
        'hook:compiled': function () {
            console.log('compiled');
        },
        'hook:ready': function () {
            console.log('ready');
            this.$children[0].$emit('data-loaded');
        },
        'hook:attached': function () {
            console.log('attached');
        },
        'hook:detached': function () {
            console.log('detached');
        },
        'hook:beforeDestroy': function () {
            console.log('beforeDestroy');
        },
        'hook:destroyed': function () {
            console.log('destroyed');
        }  
   },
  components: {
    'data-table': {
      template: 'Loaded',     
      events: {
        'hook:created': function () {
            //this.$emit('data-loaded');
            console.log('hook:created');
        },
        'hook:beforeCompile': function () {
            //this.$emit('data-loaded');
            console.log('hook:beforeCompile');
        },
        'hook:compiled': function () {
           //this.$emit('data-loaded');
           //Vue.nextTick(function () {
            console.log('hook:compiled');
           //});
        },
        'hook:ready': function () {
            this.$emit('data-loaded');
            console.log('hook:ready');
        },
        'hook:attached': function () {
            this.$emit('data-loaded');
            console.log('hook:attached');
        },
        'hook:detached': function () {
            this.$emit('data-loaded');
            console.log('hook:detached');
        },
        'hook:beforeDestroy': function () {
            this.$emit('data-loaded');
            console.log('hook:beforeDestroy');
        },
        'hook:destroyed': function () {
            this.$emit('data-loaded');
            console.log('hook:destroyed');
        },
      },
    }
  }
});

It's interesting to note that when you have defined a "wait-for" parameter, the component will never proceed beyond the "compiled" function, so the "compiled" step is the last chance you have to emit anything from the component. However, as you can see from the example, I have cheated by initiating the call on the component's event emitter from the parent instead. Basically, if you emit on compiled, the event does get emitted, but the DOM isnt yet ready to replace the template. That's also why it worked with setTimeout, because after x milliseconds, the DOM is ready. But in a practical case, when fetching stuff asynchronously, this will most likely never be a real problem, since it only takes a few milliseconds for the DOM to get ready.

swift1 commented 9 years ago

I figured out a better way to do it if you wanna make sure the DOM is ready (a little gem from the documentation). Instead of using wait-for, you can pass a callback to the parent when the child component is ready. Example:

<div id="app">
    Loading...
    <data-table on-load="{{onChildLoaded}}"></data-table>
</div>
new Vue({
  el: "#app",
    methods: {
        onChildLoaded: function (msg) {
            console.log(msg);
        }
    },
    components: {
        'data-table': {
            template: 'Loaded',    
            props: ['onLoad'],
            ready: function () {
                this.onLoad('the child has now finished loading!')
            }
        }
    }
});
jamesxv7 commented 9 years ago

Just in case, in version 1.0 the wait-for directive could be deprecated:

"wait-for can potentially be deprecated by making the data function accept asynchronously resolved data. (e.g. returning a Promise)"

jakeryansmith commented 9 years ago

Thanks for all the help and suggestions! Since all I really wanted to do was hide the component until all the data was ready, I found the easiest way is to wrap all the components html in a div with a CSS class that hides it, then in the compiled stage I make my ajax call and when it's finished I simply remove the class to display the component. It seems to work just fine.

jamesxv7 commented 9 years ago

So this thread could be considered closed? @jakeryansmith

jakeryansmith commented 9 years ago

Sure @jamesxv7