I'm using a child component to build a complex object consisting of recursive child arrays and objects. I'd like a way to get access to this object in the parent component using live binding.
Values within the object change based on user input (select boxes, inputs etc.), so I need a way to emit the #Output() EventEmitter and pass the entire object whenever one if it's properties change.
Any ideas on how to accomplish this or is there another route I can take?
See some concept code below, in which the input has two-way data binding to a deep value in the complex object, and I need to emit this change to a parent by detecting the change somehow.
The actual form is more complex, so I'd like to avoid using specific events like watching for a change in each of the inputs to manually trigger the event emitter if possible.
Parent template:
<child-component (emitter)="emitterHandler($event)"></child-component>
Parent component:
emitterHandler(obj){ console.log(obj); }
Child component:
#Output() emitter: EventEmitter<any> = new EventEmitter();
complexObj = {data: 'test', lines: [{data: 'asd', time: 123},{data: 'xcv', time: 233}]};
Child template:
<input [(ngModel)]="complexObj.lines[0].data"></input>
You can use valueChanges of the NgForm :
Component
#ViewChild('myForm') myForm: NgForm;
// this will be called when any input inside the form changes
this.myForm.valueChanges.subscribe(val => {
// emit the event
});
Template
<form #myForm='ngForm' ...>
// your select , input or nany form elements
</form>
Above code is for template driven form,
Here is the article for data-driven form on this :
https://alligator.io/angular/reactive-forms-valuechanges/
Nice way to go is :
this.myForm.statusChanges.subscribe(res => {
if (res === 'VALID') {
// emit(this.myForm.values)
}
});
This will emit the values when the data is valid, and in above code, no matter values is right or wrong it will fire the event.
Related
I am looking to use custom Javascript to interact with form fields tied with Vue framework. The form appears in a WordPress theme search page (https://wilcity.com/search-without-map/)
Autoselect the region value (this I can perform using the JS below)
markerCityName = "Atlanta";
for (i = 0; i < document.getElementsByClassName("wilcity-select-2 select2-hidden-accessible")[0].options.length; i++) {
if (document.getElementsByClassName("wilcity-select-2 select2-hidden-accessible")[0].options[i].innerText == markerCityName) {
document.getElementsByClassName("wilcity-select-2 select2-hidden-accessible")[0].selectedIndex =
document.getElementsByClassName("wilcity-select-2 select2-hidden-accessible")[0].options[i].index;
triggerEvent(document.getElementsByClassName("select2-selection select2-selection--single")[0], 'focus');
triggerEvent(document.getElementsByClassName("select2-selection select2-selection--single")[0], 'keydown');
triggerEvent(document.getElementsByClassName("wilcity-select-2 select2-hidden-accessible")[0], 'change');
}
}
Selected value participates in the search without having to manually select it from the form interface.
(i) After the javascript runs and selects "Atlanta" in the region drop down.
(ii) Select any other field in the form for search to be executed.
(iii) you will notice this search did not take into account the pre-select region value "Atlanta"
I am unable to do (2). The autoselected value is not sent in post when form value changes, and the autoselected value is not picked up.
Modifying the DOM directly won't work, as you've discovered, because Vue doesn't know about those changes and is still working based on its internal state. You need to modify Vue's underlying data model instead.
Every Vue component's root DOM element will have a __vue__ property attached, which you can use to access and modify the component's internal state from outside:
// Set up a Vue component with some data in it:
Vue.component('child', {
data() {
return {
foo: 'Data from inside Vue'
}
},
template: '<div id="component">{{foo}}</div>'
})
new Vue({
el: '#app',
});
// now outside Vue:
document.getElementById('component').__vue__.$data.foo = "Updated value from outside vue"
<script src="https://unpkg.com/vue#latest/dist/vue.min.js"></script>
<div id="app">
<child></child>
</div>
Use of the __vue__ property isn't officially supported, as far as I know, but Vue's author says it's safe to use:
the official devtool relies on it too, so it's unlikely to change or break.
I am pretty new to Vue Framework. I am trying to propagate the changes from parent to child whenever the attributes are added or removed or, at a later stage, updated outside the component. In the below snippet I am trying to write a component which shows a greeting message based on the name attribute of the node which is passed as property from the parent node.
Everything works fine as expected if the node contains the attribute "name" (in below snippet commented) when initialized. But if the name attribute is added a later stage of execution (here for demonstration purpose i have added a set timeout and applied). The component throws error and the changes are not reflected . I am not sure how I can propagate changes for dynamic attributes in the component which are generated based on other events outside the component.
Basically I wanted to update the component which displays different type of widgets based on server response in dynamic way based on the property passed to it .Whenever the property gets updated I would like the component update itself. Why the two way binding is not working properly in Vuejs?
Vue.component('greeting', {
template: '#treeContainer',
props: {'message':Object},
watch:{
'message': {
handler: function(val) {
console.log('###### changed');
},
deep: true
}
}
});
var data = {
note: 'My Tree',
// name:"Hello World",
children: [
{ name: 'hello' },
{ name: 'wat' }
]
}
function delayedUpdate() {
data.name='Changed World';
console.log(JSON.stringify(data));
}
var vm = new Vue({
el: '#app',
data:{
msg:data
},
method:{ }
});
setTimeout(function(){ delayedUpdate() ;}, 1000)
<script src="https://vuejs.org/js/vue.js"></script>
<div id="app">
<greeting :message="msg"></greeting>
</div>
<script type="text/x-template" id="treeContainer">
<h1>{{message.name}}</h1>
</script>
Edit 1: #Craig's answer helps me to propagate changes based on the attribute name and by calling set on each of the attribute. But what if the data was complex and the greeting was based on many attributes of the node. Here in the example I have gone through a simple use case, but in real world the widget is based on many attributes dynamically sent from the server and each widget attributes differs based on the type of widget. like "Welcome, {{message.name}} . Temperature at {{ message.location }} is {{ message.temp}} . " and so on. Since the attributes of the node differs , is there any way we can update complete tree without traversing through the entire tree in our javascript code and call set on each attribute .Is there anything in VUE framework which can take care of this ?
Vue cannot detect property addition or deletion unless you use the set method (see: https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats), so you need to do:
Vue.set(data, 'name', 'changed world')
Here's the JSFiddle: https://jsfiddle.net/f7ae2364/
EDIT
In your case, I think you are going to have to abandon watching the prop and instead go for an event bus if you want to avoid traversing your data. So, first you set up a global bus for your component to listen on:
var bus = new Vue({});
Then when you receive new data you $emit the event onto the bus with the updated data:
bus.$emit('data-updated', data);
And listen for that event inside your component (which can be placed inside the created hook), update the message and force vue to re-render the component (I'm using ES6 here):
created(){
bus.$on('data-updated', (message) => {
this.message = message;
this.$forceUpdate();
})
}
Here's the JSFiddle: https://jsfiddle.net/9trhcjp4/
Let's say I have two custom elements: a and b.
a contains another element that can be clicked and I want to trigger a custom event when this happens. That custom event should then be caught by element b.
I can use this.trigger in element a, but this.on in element b doesn't catch the event (which seems logical, since it wasn't triggered on b).
So: Can I retrieve element a in element b to do something like "elementA.on()" in element b?
Basically, I want functionality like RiotControl, but without storing the data, since I just want to trigger a modal in my use case and not store anything.
I could probably fall back to jQuery and trigger and listen for events on document, but is this really a good way?
You can create a shared observable e.g:
var eventBus = riot.observable();
When you mount your elements you can pass it as an option:
riot.mount('elementa', { eventBus: eventBus })
riot.mount('elementb', { eventBus: eventBus })
Then they can listen and trigger on the same observable:
this.opts.eventBus.trigger('openModal', ...maybeSomeOptions);
this.opts.eventBus.on('openModal', function(...maybeSomeOptions) {});
Disclaimer: riotjs is new to me
The way I tackled this was to use a mixin to automatically include the same Observable into multiple tags.
// mixins.js
let ObservableMixin = {
observable: riot.observable()
};
Then in any .tag file where I wanted the shared observable to live, I would simply call.
// Any .tag file
this.mixin(ObservableMixin);
...
this.observable.trigger('eventName', { optional: options });
For more information about mixins check out: Riot Mixins
I've written a component called Upload which allows users to upload files and then report back with a JSON object with these files. In this particular instance, the Upload component has a parameter which comes from a parent view model:
<upload params="dropzoneId: 'uploadFilesDropzone', postLocation: '/create/upload', uploadedFiles: uploadedFiles"></upload>
The one of importance is called uploadedFiles. The parameter binding here means I can reference params.uploadedFiles on my component and .push() new objects onto it as they get uploaded. The data being passed, also called uploadedFiles, is an observableArray on my parent view model:
var UploadViewModel = function () {
// Files ready to be submitted to the queue.
self.uploadedFiles = ko.observableArray([]);
};
I can indeed confirm that on my component, params.uploadedFiles is an observableArray, as it has a push method. After altering this value on the component, I can console.log() it to see that it has actually changed:
params.uploadedFiles.push(object);
console.log(params.uploadedFiles().length); // was 0, now returns 1
The problem is that this change does not seem to be reflected on my parent viewmodel. self.uploadedFiles() does not change and still reports a length of 0.
No matter if I add a self.uploadedFiles.subscribe(function(newValue) {}); subscription in my parent viewmodel.
No matter if I also add a params.uploadedFiles.valueHasMutated() method onto my component after the change.
How can I get the changes from my array on my component to be reflected in the array on my parent view model?
Why do you create a new observable array when the source already is one? You can't expect a new object to have the same reference as another one: simply pass it to your component viewModel as this.uploads = params.uploads. In the below trimmed-down version of your example, you'll see upon clicking the Add button that both arrays (well the same array referenced in different contexts) stay in sync.
ko.components.register('upload', {
viewModel: function(params) {
this.uploads = params.uploads;
this.addUpload = function() { this.uploads.push('item'); }.bind(this);
},
template: [
'<div><button type="button" data-bind="click: addUpload">Add upload</button>',
'<span data-bind="text: uploads().length + \' - \' + $root.uploads().length"></span></div>'].join('')
});
var app = {
uploads: ko.observableArray([])
};
ko.applyBindings(app);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="component: {name: 'upload', params: {uploads: uploads}}"></div>
It is only in case your source array is not observable that things get a little more complicated and you need to have a manual subscription to update the source, eg. you would insert the following in the viewModel:
this.uploads.subscribe(function(newValue) { params.uploads = newValue; });
Additionally the output in the text binding would not be updated for the source because it is not observable. If for some reason that I cannot conceive of you would want to have 2 different observableArrays (1 source & 1 component), you should still be able to do with the line above, but replace the function code with params.uploads(newValue)
The problem may be related to this bug (to be confirmed): https://github.com/knockout/knockout/issues/1863
Edit 1: So this was not a bug. You have to unwrap the raw param to access the original observable. In your case, it would be:
params.$raw.uploadedFiles() //this would give you access to the original observableArray and from there, you can "push", "remove", etc.
The problem is that when you pass a param to a component, it gets wrapped in a computed observable and when you unwrap it, you don't have the original observableArray.
Reference: http://knockoutjs.com/documentation/component-custom-elements.html#advanced-accessing-raw-parameters
While Binding Property that involves Parent --> Child Relation
Use Binding in this way
If You want to bind data to Child Property
data-bind='BindingName : ParentViewmodel.ChildViewModel.ObservableProperty'
Here it seems you want to subscibe to a function when any data is pushed in Array for that you can write subscribe on Length of Observable array which can help you capture event that you want.
This should solve your problem.
Is there a way to detect list change in canjs and make the view redraw? I am changing the list but this is not shown on screen.
At the moment i have view model
TodosListViewModel = can.Map.extend({
todoCreated: function(context, element) {
// new todo is created
var Todo = this.Todo;
new Todo({
name: can.trim(element.val())
}).save();
element.val("");
},
tagFiltered: function(context, element) {
// filter todos according to tag
this.todos = this.todos.filter(function(todo) {
return todo.tag === element.val();
});
}
});
And component
can.Component.extend({
// todos-list component
// lists todos
tag: "todos-list",
template: can.view("javascript_view/todos-list"),
scope: function() {
// make the scope for this component
return new TodosListViewModel({
todos: new TodoList({}),
Todo: Todo
});
},
events: {
"{scope.Todo} created": function(Todo, event, newTodo) {
// todo created
this.scope.attr("todos").push(newTodo);
},
"{scope.todos} changed": function(a,b,c,d,e,f,g,h) {
console.log("todo change",d,e);
}
}
});
The markup
<input type="text" name="tagFilter" placeholder="Tag lookup" can-enter="tagFiltered" />
The rest of code http://git.io/vrPCTQ
In the case you're showing in the fiddle, you haven't define "page" in the scope to take a raw string value from the component's tag (using "#" as the value for scope.page). Check out the one-line difference in router's scope here:
http://jsfiddle.net/tkd9Lvtm/3/
EDIT: That didn't address the original question, so here's what else you can do to get this started. I made a new fiddle version for you.
http://jsfiddle.net/tkd9Lvtm/4/
The best way with CanJS 2.1 to accomplish what you want is to use can-value attributes on your form fields to two-way bind your elements to attribute values on your view model. You can see that the input field for the tag search is now using can-value instead of can-change -- this makes it independent of the filter function, which is only used to draw the items farther down.
CanJS will automatically rerun the filter when the attribute changes, because calling this.attr("filterTerm") inside the view model's filter function sets up binding the first time it's run. The live bound view layer is making computes out of these functions "under the hood" and these computes (a) listen to changes on attributes that are read inside the function; and (b) updates the DOM with each change to listened-to attributes. Using the view model to store the value in the filter field then allows that function to fire again on each change.