Canjs changed event - javascript

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.

Related

External manipulation of Vue filelds

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.

Bind knockout view model to Bootstrap Tree View

I am trying to get Knockout and a Bootstrap-TreeView to work together.
(The component: https://github.com/jonmiles/bootstrap-treeview)
At the moment, I'm passing the JSON from an API call to the constructor of the View Model. This will change later but for simplicity, I'm doing this.
What I need then is to bind click events to each node. So if I click the root node, nothing happens, click a folder, and I can get a list of all it's direct child text values (Just alert them for now), and if I click a file node, I alert the 'data' value from that node.
Here's a fiddle to see what I have done so far.
https://jsfiddle.net/Cralis/h15n2tp7/
My View Model simply initialises with the json data. And then a computed in the view model does the setup of the Tree View.
// Create the View Model.
var ViewModel = function(jsonData) {
var self = this;
self.MyData = ko.observable(jsonData);
ko.computed(function() {
$('#tree').treeview({
data: self.MyData()
})
.on('nodeSelected', function(event, data) {
if (data.nodeLevel == 2) { // Are we clicking a File?
alert("Clicked a File. Data: " + data.data)
}
else
if(data.nodeLevel == 1) { // We're clicking a folder.
alert("Clicked a folder. Would like to somehow alert a list of all child node text values.")
}
});
})
}
// Create the View Model and initialise with initial data
var vm = new ViewModel(getTree());
// Bind.
ko.applyBindings(vm, document.getElementById("bindSection"));
This works, but I don't think I'm using Knockout much. That's because my click events are in my javascript, and my Knockout view model doesn't really have any control.
How can I allow Knockout to 'see' the click events. So, onclick of a node, a knockout computed (I think?) fires and I can then control the UI based on bind events.
Outside of this, I have a DIV which shows a list of files. What I was was that when a folder level node gets selected, I can populate that div with all the 'text' values from the children of that selected folder node.
Any pointers in how I can achieve this would be amazing. I'm just not sure how I can get data-bind="click... to the nodes, which can then run the code that's currently in the 'onclick' in my fiddle.
I've updated your fiddle with a custom binding: https://jsfiddle.net/h15n2tp7/2/
As I already posted here in this question: add-data-bind-property-to-a...
I think this is the best way do it. The problem here is the synchronization between 1) fetching JSON 2) applying bindings 3) creating DOM elements. Creating custom binding lets you do that easily without much of messy code. In your case, when a getTree function is done via $.get, you need to create a view model in .done function, and apply bindings after that. So the provided fiddle will change a bit, but the idea is the same. Note, that you don't need any observables (if the tree data does not change while the app is running). If it does change though, make sure that you implement update function in a custom binding (knockout custom binding reference).

vue JS not propagating changes from parent to component

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/

Displaying checkbox selected rows with vuejs

I'm starting out with vuejs and a vue grid at https://jsfiddle.net/kc11/7fqgavvq/2/.
I want to display the checked row objects in the:
<pre> {{ selected| json}} </pre>
at the bottom of the table. I've come across Check all checkboxes vuejs with an associated fiddle at https://jsfiddle.net/okv0rgrk/206/ that shows what I mean if you look at the outputted Selected Ids.
To get this working I'll need to add a method to the table component similar to
methods: {
selectAll: function() {
this.selected = [];
for (user in this.users) {
this.selected.push(this.users[user].id);
}
}
in https://jsfiddle.net/okv0rgrk/206/
Can someone explain this function as I am having trouble in particular with what 'this' means in this context.
this refers to your component. So anything inside of your component can be called using this. You can access data with this.users or this.selected, run methods with this.selectAll() or access anything else in your component.
In that fiddle, there is a users attribute on the data, so this.users refers to that array. The function selectAll() empties the this.selected array, then goes through and re-adds every user to the array, this.selected.
Edit -- computed properties
Add this to your component:
computed:{
userCount: function(){
return this.users.length;
}
}
then, anywhere in this component, this.userCount will return the number of users. This variable will update anytime the number of users changes. That is why its a "computed" property - you don't have to update, it just automatically recalculates when it needs to.

Knockout component updating observable not being subscribed to by parent view model?

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.

Categories

Resources