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.
Related
Here is my setup, and I know its not ideal (I inherited this project from a couple of developers that are no longer working on this web app). I'm trying to marry Vue.js and JQuery so that I can simply re-use JQuery based components (instead of rewriting them) on my Vue.js page (the two previous developers having design differences).
What I need is this: I am filling a table with components post page Mount. Everything about this table populates correctly except I have buttons in some of the cells in which I would like to use Vue.js to operate those buttons' functionality. I can add a v-on:click= attribute just fine but since these attributes are added to the buttons post Mount their Vue attributes are not being listened to. On the client side (Edge/Chrome in this case) I can see the vue attributes in the developers console when inspecting the element. I looked up examples such as this article:
How to make Vue js directive working in an appended html element regarding how to correctly re-render components after they are appended post Mount. Since these objects were created and appended to the web page after mounting they are not Reactive.
The article suggests creating a component that can then be mounted manually, using a template to create the component. I've tried this and found that using Vue.extend to create a new subclass/component now disallows me to access methods in my original app. Let me place some code and then I'll recap:
What I see on the client side console:
<button type="button" class="results_table_col_1_button" v-on:click="test()">200-715-122-306-ATP</button>
How the data table is being updated via JQUERY
display_search_results: function (data) {
data = JSON.parse(data);
let num = data.row_order.length;
if (num > 0) {
displayDataTable(data);
$("#tableContainer").show();
$("#test_count").text(num + " test" + (num === 1 ? '' : 's') + " found");
$('#dataDisplayTable tr').not(':first').each(function (i, el) {
let save_id = $(el).find('td.results_table_col_1').text()
$(el).find('button.results_table_col_1_button').attr("v-on:click", 'test()')
What I tried via the article
//How the new component is being created:
var testComp = new ATP_Button().$mount()
document.getElementsByClassName("results_table_col_1")[i].appendChild(testComp.$el)
//with the constructor:
var ATP_Button = Vue.extend({
template: '<button type="button" v-on:click="test()">Is this working</button>',
})
This creates the button but test is then undefined as it is not initialized or referenced to in the subclass object ATP_Button. How do I pass my 'main' Vue app's test() function to this component so that when the button $emits its event the parent's test() function is called?
I also tried adding props to the constructor so that I could maybe pass test() into the component upon creation like this:
var ATP_Button = Vue.extend({
props: ['test'],
template: '<button type="button" v-on:click="test_child()">Is this working</button>',
methods: {
test_child: function () {
this.test
alert(this.test)
}
}
})
And created the component as such:
var testComp = new ATP_Button(this.test_func).$mount()
Now however this.test inside the component is undefined as it would seem that passing the function through is not working properly (probably due to developer error aka me).
RECAP
A JQUERY table object is being populated via a backend process with HTML elements to be displayed on the front end view of the webpage
One element that is added is a button that needs to be able to call a Vue.js method from the main app
The elements are added to the table after the page has been mounted thus they are not reactive
Trying to manually mount the component creates a subclass object that does not have access to the main app's methods
Trying to pass the main app's method test() to the component during construction failed and left the test variable undefined
Eventually I need the buttons to call a method from the main app with dynamic variables inside as arguments
I appreciate anyone who is able to offer any assistance.
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/
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.
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.
Problem: Meteor JS app with 2 distinct templates that need to share some data.
They are dependent on one another, since I aim to extract text (Step 1) from one, and then create dynamic buttons (Step 2) in another template. The content of the buttons is dependent on the table.
buttons.html
<template name="buttons">
{{#each dynamicButtons }}
<button id="{{ name }}">{{ name }}</button>
{{/each}}
</template>
My goal is for the name property to come from the content of reactiveTable.html (see above, or their Github page, package meteor add aslagle:reactive-table.
These need to be dynamically linked since table re-renders constantly w/ different group of products, which are linked up through Template.reactiveTable and a specific data context (Pub/Sub pattern).
IF the table is (re)rendered, then parse it's content and extract text. Once the table is parsed, dynamically inject newly created buttons into the UI. Note UI.insert takes two arguments, the Object to insert, and then location (DOM node to render it in).
Template.reactiveTable.rendered = function () {
UI.insert( UI.render( Template.buttons ) , $('.reactive-table-filter').get(0) )
};
(Insert new buttons every time a reactiveTable is rendered.)
This code works, but is flawed since I cannot grab the newly rendered content from reactiveTable. As shown in this related question, using ReactiveDict package:
Template.buttons.helpers({
dynamicButtons: function() {
var words = UI._templateInstance().state.get('words');
return _.map(words, function(word) {
return {name: word};
});
}
});
Template.buttons.rendered = function() {
// won't work w/ $('.reactiveTable) since table not rendered yet, BUT
// using $('h1') grabs content and successfully rendered dynamicButtons!
var words = $('h1').map(function() {
return $(this).text();
});
this.state.set('words', _.uniq(words));
};
Template.buttons.created = function() {
this.state = new ReactiveDict;
};
How can I change my selector to extract content from Template.reactiveTable every time is re-renders to create buttons dynamically? Thanks.
You’re using a lot of undocumented functions in there, and UI.insert and UI.render which are bad practice. The just-released Meteor 0.9.1 eliminates them, in fact.
Create your dynamic buttons the Meteoric way: by making them dependent on a reactive resource. For example, a Session variable. (You could also use a client-side-only collection if you want.)
Template.reactiveTable.rendered = function () {
// Get words from wherever that data comes from
Session.set('buttons', words);
};
Template.buttons.helpers({
dynamicButtons: function() {
if (Session.equals('buttons', null))
return [];
else
return _.map(Session.get('buttons'), function(word) {
return {name: word};
});
}
});
Every time reactiveTable is rendered or rerendered, the buttons Session variable will update. And because your dynamic buttons are depending on it, and since Session variables are a reactive resource, the buttons will rerender automatically to reflect the changes.