-
-
Notifications
You must be signed in to change notification settings - Fork 104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature Request: Callback handler on view when rendered #192
Comments
@chrismbeckett I believe this has been discussed a few times in the gitter channel The conclusion has usually been; whilst it's possible to add a hook that does something along these lines, there isn't really such a thing as "rendered" since the binding system is async, so calling it "rendered" is a bit of a misnomer. Usually the issue with using Something like:
So I believe the best that can be done is the above - I'm not sure if that causes any issues though but maybe it could be run after |
Hey, I appreciate the feedback, the info on the taskQueue is much appreciated. |
I am trying to somehow notify phantomjs that my aurelia app is ready. I am using
The statement |
@schatekar you could setup a listener for the |
+1 on this I have no idea how to run code (i.e. initialize/retrigger a jquery plugin) after a binding has been rendered. Yeah, I can use attached() but that only works the first time. |
I also used the aurelua-composed here https://github.com/doktordirk/aurelia-notification/blob/refactor-use-aurelia-pal/src/notification.js |
@EisenbergEffect That.S exactly the issue i had the others day with the changed behaviour/order of view attaching. I d suggest to have a Blog/Doc Entry about it |
@emzero What do you mean by "attached only works the first time"? |
@EisenbergEffect Maybe I'm not doing things the Aurelia way but consider the following case: I have a And as OP says, the need to do something with dynamically added elements to the DOM is very common in the world of jQuery plugins. Is there a way to set a callback to be executed everytime a binding is updated and rendered? |
Wait...what? You are adding a component and its attached isn't being called? That would be a serious bug. Can you provide some concrete code examples here of what you are doing? I may no be quite understanding the details... |
@EisenbergEffect No, it's not a component. It just HTML with Aurelia string interpolation binding. myViewModel.html <div repeat.for="item of myArray">
<div class="text">${item.text}</div>
</div> myViewModel.js export class MyViewModel() {
activated() {
this.myArray = GetFromSomeExternalAPI();
}
attached() {
// do something with those divs of class="text" (i.e. run some jQuery plugin)
}
// Here I also have set a setTimeOut function that will poll for new data from that External API and concat the response into this.myArray (which triggers the render of the repeat.for)
} So the first time the Am I making sense? |
@EisenbergEffect - should this issue be re-opened and tagged as a feature request or documentation request? The original issue was how to make Aurelia play nicely with jQuery. Either it does, and people don't understand how to do it (doc request), or Aurelia doesn't play that nicely with jQuery but people wish it would (feature request). |
To solve this, you want to create a custom attribute to encapsulate your jQuery widget. Here's the general pattern for that: @inject(Element)
export class MyPluginCustomAttribute {
constructor(element) {
this.element = element;
}
attached() {
this.plugin = jQuery(this.element).myPlugin();
}
detached() {
this.plugin.destroy();
}
} Then you apply it on the items you want to apply the plugin to: <div repeat.for="item of myArray">
<div class="text" my-plugin>${item.text}</div>
</div> |
So, let's assume that this plugin requires a complex hierarchy of DOM elements to function correctly (tabs, or a complex list, etc), and let's say the template source in a VM that ultimately renders the DOM hierarchy that the jQuery UI plugin requires is a combination of components. When the attached() method for the custom attribute you demonstrated above run on that one particular component, does it ensure that the DOM has been fully rendered for all sub-components used by that component in its own template? Does that also extend to transcluded content injected from the parent, and the components it might contain, as well as conditional rendering, bindings, etc? I think the question remains unanswered. The intention of a rendered() method on the VM is to know that all of the components on the VM have been rendered out so you can use jQuery plugins against the DOM safely. From the response by @charlespockert above, there seems to be some confusion that the attached() event in a custom attribute doesn't ensure that result? |
Yes, when There is no way to have a |
Awesome - thank you for that clarification, and the sample. I have been using the attached() method, but for simple use cases, and was never sure it was going to be reliable under load. Your response has certainly addressed my concerns. |
@EisenbergEffect I didn't mean a Maybe re-triggering the |
@emzero You can build such a handler yourself very easily using the technique above. Simply create a custom attribute that dispatches a custom bubbling dom event in its attached callback. Then just put that custom attribute on any element you care to be notified about. Finally, add an event listener inside any element you need to know about that event. |
@EisenbergEffect Your example works well if I needed to execute something for every element that has the custom attribute. I just need to run something once after everything have been "re-attached". Is it really impossible to attach a handler whenever a view is updated because of its view-model data changed (i.e. one or more items have been added to an array that's being used in a In your example I only know when each "item" has been attached, not when all of them. I just need to know when that update is done and it's all attached to the DOM (don't care if it's done "painting'). Sorry to be annoying with it but I can't figure out the Aurelia wayt to implement a simple ajax polling system that gets data => render the update to the DOM => once it's all attached, run some code |
Attached fires whenever the current component that implements the attached callback is added to the DOM...not whenever any component inside it's content is added to the DOM. Why do you need to depend on the DOM here at all? If you have an array of data, can't you just execute your code whenever your data changes? That would be the correct way to handle this. |
@EisenbergEffect I have an array of data that gets updated in a timer. New data (posts) are pushed to this array which is used in a If I do what you suggest, I end up appending one element at a time and calling Is there a way to programatically get the output HTML that a Custom Element would render? In a view-model import CustomElement;
export class MyViewModel() {
someMethod() {
let html = CustomElement
.withProperty('bindableProperty', this.something)
.withProperty('anotherBindableProperty', this.somethingElse)
.getHtml();
}
} This would assign <template>
<require from="./CustomElement"><require>
<custom-element bindableProperty.bind="something" anotherBIndableProperty="somethingElse"></custom-element>
</template> This view would have its view-model with |
@emzero you can watch the DOM for mutation events using a mutation observer. Even better you could just use the This would give you a collection of DOM elements that had been added and you could pass these as a collection in one go to the masonry plugin |
@charlespockert That sounds just what I need. Unfortunately I can't find anything on this |
@emzero search for There are some examples of use: http://plnkr.co/edit/FauNC4JgvG1X6ugbOzYo?p=preview (I forked someone elses Plunk but look at cs-tabs.html and cs-tabs.js) The alternative to all this is to inspect the DOM yourself using a mutation observer (but you may have to deal with browsers that don't support that) or to take advantage of your "data fetch" method... this should work assuming you know a bit about how Aurelia batches/renders the DOM All updates to the DOM are batched by a task queue. This means several data updates that result in bindings changing in quick succession only end up as a single big DOM batch update, improving performance. You can actually put your code on the back of this queue manually to ensure that any outstanding bindings have taken effect (and likely your DOM elements are present). An example is here: http://blog.durandal.io/2015/06/05/building-aurelias-focus-attribute/ Just search for TaskQueue This will probably work for you, quite a few people in the gitter channel have had jQuery plugins that needed to wait for a DOM update before they could take effect and this seems to solve the issue most of the time |
Yes to what @charlespockert has said :) |
It is possible to delegate/trigger an event in a custom attribute? import {inject, bindable,customAttribute} from 'aurelia-framework';
@inject(Element)
@customAttribute('render')
export class RenderCustomAttribute {
constructor(element) {
this.element = element;
}
bind() {
console.log('bind',this.value);
}
} <li repeat.for="item of items" render.delegate="elementAppeared($event,item)">
</li> elementAppeared(event,item) {
console.log('rendered', event.target);
} |
@mreiche this feels more like a question rather than part of this issue. I guess what you're looking for is the EventAggregator. If you have another issue please either open a new issue or visit our official gitter channel if you need help with the EventAggregator |
It's on topic, because it referes to aurelia/binding#132 @EisenbergEffect wrote:
This is my solution I'm not so happy with: import {inject,customAttribute} from 'aurelia-framework';
import {DOM} from 'aurelia-pal';
@inject(Element)
@customAttribute('element')
export class ElementCustomAttribute {
constructor(element) {
this.element = element;
}
attached() {
let ev = DOM.createCustomEvent('attached');
this.element.dispatchEvent(ev);
}
detached() {
let ev = DOM.createCustomEvent('detached');
this.element.dispatchEvent(ev);
}
} <require from="element-attribute.js"/>
<li repeat.for="item of items" element attached.trigger="elementAppeared($event,item)">
</li> |
@mreiche I'm adding this a bit late, but I have a simpler codebase for the same purpose : import {customAttribute} from 'aurelia-framework';
@customAttribute('attached')
export class AttachedCustomAttribute {
attached() {
this.value();
}
} <require from="attached.js"/>
<li repeat.for="item of items" attached.call="elementAppeared(item)"/> You can also add some bit of code on |
A lot of jQuery-style components and frameworks (including Material Design Lite) are designed to bind to DOM elements after they have been rendered. MeteorJS for example allows a "rendered" event handler to be added to a view that is called when the view is rendered into the DOM and provides an ideal place to enable jQuery components, etc. The event also provides the root DOM element for the particular view.
Right now the only way to do this seems to be to create a custom element or attribute and use the attached() callback. It would be nice to have a callback like this on the view.
The text was updated successfully, but these errors were encountered: