Is there a way to query "up"? I'm in a Component and want to register listeners to it's parents events with control(). This requires a Query which gets me the parent of my main view.
In ExtJS4, you can use 'up()' from an Ext Element.
The params are a string of the parent element you wish to find e.g:
var parentEl = Ext.get('childID').up('div.parentClass');
If you provide some details about the structure of your components/elements I can give a specific example which should fit.
EDIT: To show going 'up' from a component
var myComponent = // however you've got it
var theParentEl = myComponent.getEl().up('div.parentClass');
Usually up('PARENTCLASS') is enough for what you're trying to do. Here is what I do all over the code so elements generates event for the form they are in:
items: [
...
{ xtype: 'checkbox', listeners: {
change: function() { this.up('window').fireEvent('checkboxchanged'); }
}}
...
]
As I understand, you want to listen to events dispatched by a component's parent from the child component's controller control function specifically.
There is not a query selector like 'parent < component' which you can put in the child controller's control function to listen to parent events.
Normally I would just add the parent view to the child's controller, then you could listen to it's events. But I assume you are not doing this because you are trying to delegate to different controllers or something.
You could fire an event in the child component whenever that parent event occurs. Inside the parent controller you could do it like this:
var child = parent.down('child');
child.fireEvent('myOwnEventName', arg1, arg2, arg3, etc);
Then you would add a handler for 'myOwnEventName' in the child controller's control function to run the logic you wanted for it.
If the parent doesn't have a controller then you should just add the parent component as a view in the child's controller.
The Sencha help says "Member expressions from candidate Components may be tested. If the expression returns a truthy value, the candidate Component will be included in the query:" in the http://docs.sencha.com/ext-js/4-0/#!/api/Ext.ComponentQuery help.
Took me a while to realize I can do the following in my controller:
this.control({
'window{down("testcomp")}[down]': { beforedestroy: this.doNotCloseIfUnsaved }
});
Using the {} operation, we can call any arbitrary code. Really bad from an efficiency standpoint, but it got the job done. I had to add the [down] because it runs component queries from right to left, so we have to be sure down() exists before we try running it (on every component). Thus, I was able to attach an event to whatever window holds my component.
Of course, other functions can be used too, like child() instead of down() if you want to ensure it is the immediate child rather than just anywhere below.
Related
I'm currently implementing a classical Minesweeper game in Vue.js which is working fine so far https://github.com/franktopel/defuse, Demo http://connexo.de/defuse/.
Now I would like to add touch support because as of now, to mark a field as "contains a mine" it is required that you right-click the field. Right-clicking is obviously not available on touch devices, so I would like to add longtap support. I'm using native events click and click.right from the parent component for each field, so the field does not handle the events, but the parent component that instantiates these fields does.
I've found this https://github.com/jerrybendy/vue-touch-events and added it to my project, yet it seems I cannot use this on the component tag (see https://github.com/franktopel/defuse/blob/master/src/components/Defuse.vue):
<m-field
v-for="field in row"
:field="field"
:key="`${field.x},${field.y}`"
#click.native="open(field)"
#click.right.native.prevent="toggleBombMarker(field)"
v-touch:longtap="toggleBombMarker(field)"
></m-field>
because that marks all fields without any user interaction and the console keeps producing
You may have an infinite update loop in a component render function.
This is how my field objects (which I'm passing to the field component) are created:
let Field = function (x, y) {
this.x = x
this.y = y
this.isOpen = false
this.hasBomb = false
this.isMarked = false
this.numNeighbourBombs = null
}
module.exports = Field
I have also tried emitting a custom event from inside my field component, yet I don't know how I can pass the triggering field to the event handler from there.
Can anyone push me in the right direction?
According to the vue-touch-events docs, the v-touch directive doesn't work in the same way as v-on; v-touch must be given a function to execute, whereas the toggleBombMarker(field) expression probably returns undefined.
If you want to pass extra parameters to the v-touch handler, your handler must return a function like this:
methods: {
toggleBombMarker(field) {
return () => {
// your handler code here
}
}
}
I am trying to make my Vue Component reusable but there is a part in it which requires to run a function on button click which I have defined in the parent component.
The component's button will always run a parent function and the parameter it passes is always the same (its only other property).
Right now I am passing 2 properties to the component: 1) an object and 2) the parent function reference, which requires the object from 1) as a parameter.
The Child-Component looks like this (stripped unnecessary code):
<button v-on:click="parentMethod(placement)">Analyze</button>
Vue.component('reporting-placement', {
props: ['placement', 'method'],
template: '#reporting-placement',
methods: {
parentMethod: function(placement) {
this.method(placement);
}
}
});
The parent is making use of the child like this:
<reporting-placement v-bind:placement="placement" v-bind:method="analyzePlacement"></reporting-placement>
methods: {
analyzePlacement: function(placement) {
this.active_placement = placement;
},
}
As you can see, the child has only one property, placement, and the callback reference. The placement must be put in as a parameter to the reference function from the parent.
But since the parent defines the parameters, the child shouldn't concern itself with what it needs to pass to the parent function. Instead I would prefer to already pass the parameter along in the parent.
So instead of
<reporting-placement v-bind:placement="placement" v-bind:method="analyzePlacement"></reporting-placement>
I would prefer
<reporting-placement v-bind:placement="placement" v-bind:method="analyzePlacement(placement)"></reporting-placement>
(including appropriate changes in the child).
But passing the parameter along does not work that way.
Is it possible (maybe in other syntax) to 'bind' the variable to the function reference so that it is automatically passed along when the callback is called?
Info: I don't get an error message if I write it down as above but the whole Vue screws up when I pass the parameter along to the component.
Hope the issue is clear :-) Thanks a lot!
By reading your proposal I've found out that you are overusing the props passing.
Your concern that child component should not have any knowledge about the way that the parent component uses the data is completely acceptable.
To achieve this you can use Vue's event broadcasting system instead of passing the method as props.
So your code will become something like this:
Vue.component('reporting-placement', {
props: ['placement', 'method'],
template: '#reporting-placement',
methods: {
parentMethod: function(placement) {
this.$emit('reporting-placement-change', placement)
}
}
});
And you can use it like this:
<reporting-placement v-bind:placement="placement" #reporting-placement-change="analyzePlacement($event)"></reporting-placement>
But if you need the data which is provided by the method from parent it's better to consider using a state management system (which can be a simple EventBus or event the more complex Vuex)
And finally, if you really like/have to pass the method as a prop, You can put it in an object, and pass that object as prop.
I have a parent component and a child component.
In my parent component I have an event on a span:
<span #click="toggle(model.id)" class="open-folder">[{{open ? '-' : '+'}}]</span>
which fires the toggle function
methods: {
toggle: function(id_epic) {
this.open = !this.open;
this.$broadcast('loadTasks', id_epic);
}
},
in this function I call the loadTasks method from the child component with id_epic parameter.
The parent can have n children linked to it, but I only want to fire the first child method not all.
When I click on the span it fires the event for all n children of the parent's tree.
EDIT: The parent and it's children are generated recursively, so the child can also be a parent on it's own.(Like a folder structure)
How can I fix this ?
Thank you.
$broadcast is used to send event to child components. In your parent component you're triggering the event loadTasks from the toggle method. This is good.
You're sending a parameter along in the event, in your case id_epic
Now in your child component, you will need something like this. You need to get the id you're sending in your child component.
events : {
'loadTask' : function(param) {
//check for the id_epic here
}
}
EDIT: The parent and it's children are generated recursively, so the child can also be a parent on it's own.(Like a folder structure)
You should also re-think that part, if you have too many nested components, things can get easily out of hand.
You should be able to stop the propagation of an event by using a second parameter in the child listener. The second parameter is passed the event object to which you can can call stopProgation() and prevent additional children from also receiving the broadcasted event.
Consider adding the following code to your child listener.
events:{
'loadTask': function(param,event){
event.stopPropagtion();
// Handle the param as needed.
}
}
Of course this system of event handling is only for Vue.js 1.0 as in Vue.js 2.0+ event broadcasting and dispatching has been depreciated in favor of a data store such as Vuex. So you may want to consider using a method which will upgrade compatible.
You can try using this.$children[0].$emit() to send the event to the first child instance. though it would probably be better to use a more explicit scheme and have children register handlers with the parent or vice versa.
Suppose we have something like this:
var parent = parentView; // A View
var child = Alloy.createController('ChildView',{});
parent.add(child);
And in the ChildView.js controller:
function closeView(){
// remove ourself from parent how?
}
Can you do this somehow? From the closeView() remove the child view from its parent?
The way i can solve it i guess is sending in the parent view in the {} options to the childview and keep a reference there {parent:parentView}. And then do a parent.remove()... in my closeView(). But is there some other way?
Use events. All Alloy Controllers implement BackBone.Events so when you create the child attach a close event to it and have the parent remove the child.
The child should only be concerned with itself. knowing about how it interacts with its parent is leaking information and not following good SOLID design. Maintenance itself would be a nightmare not to mention the mental overhead to growk that kind of design.
Parent code:
var parent = parentView; // A View
var child = Alloy.createController('ChildView',{});
child.on('close', function () {
parent.remove(child.getView());
});
parent.add(child.getView());
Child code:
function closeView(){
$.trigger('close');
}
You want to pass the reference to the parent to the child for this to work, as you mentioned
var child = Alloy.createController('ChildView',{parentView: parent});
Then in the childview:
function closeView(){
$.args.parentView.remove($.getView());
}
You can also do it the other way around by letting the parent take care of it:
in childview:
$.trigger('removeMe');
In the parent controller:
child.on('removeMe',function(){
parent.remove(child);
}
Durandal 2.0 - view composition order
I have view, that has a number of child views that I am using just to break up the HTML into separate files.
index.html(index.js)
- menu.html
- header.html
- footer.html
The menu view is populated by the index view after it's loaded.
Inside the index.html I compose the menu view like this (there is no module menu.js, just html composition here):
<!--ko compose: { view: 'menu'}--><!--/ko-->
The problem is the order of the view composition.
I have an attached event wired up in index.js that then calls a function to populate the menu div that's sat in menu.html.
But, the attached view event is called before the menu.html view has been composed.
The events looks something like this from the console:
Binding views/footer
Binding views/index
binding complete: <section data-view="views/index" style="display: none;">…</section>
[Index] Main View Attached
Binding views/header
Binding views/menu
Binding views/menudropdown
So, the main view is attached before the children.
Is there a way to change the composition order, or wait for all the child views to be composed/loaded before the main view is attached/complete?
Thanks
The short answer is "no" - you can't change the composition order.
The longer answer: perhaps I've misunderstood, but it sounds a little bit suspicious that you're populating the menu div from your view model. Could you use a custom binding to do this instead? If you can do it in a binding, then you could have a look at using a Delayed Binding Handler. From the documentation:
Sometimes your binding handler needs to work with an element only
after it is attached to the DOM and when the entire composition of the
view is complete. An example of this is any code that needs to measure
the size of an HTML element. Durandal provides a way to register a
knockout binding handler so that it does not execute until the
composition is complete. To do this, use
composition.addBindingHandler.
Alternatively, if you're happy with whatever code is running in your viewModel, you probably want to use the compositionComplete event rather than the attached event. From memory, the attached events run from parent to child (which is why the event is being called on index before the menu has been composed). In contrast, compositionComplete bubbles from child to parent:
Finally, when the entire composition process is complete, including
any parent and child compositions, the composition engine will call
the compositionComplete(view, parent) callback, bubbling from child to
parent.
You can attach a compositionComplete handler on your viewModel in the same fashion as an activate method:
// Secured Shell
define([], function () {
var viewModel = {
activate: function () {
// do stuff
},
compositionComplete: function (parent, child, settings) {
// do more stuff
debugger;
}
};
return viewModel;
});
Hope that helps.