RactiveJS and Stapes model not updating - javascript

I am trying to create a filtering for a Stapes object in RactiveJS, but it seem the two way binding is not responding correctly to the update.
I can't work out what is going wrong where as I thought it should just work without any issue, I have created a code example here:
https://jsbin.com/sajeje/4/edit?html,output

Here's the pattern I use for using a computed property with a filter adapted for your use case. I couldn't get the Stapes object to directly be usable as the result, but there is a getAllAsArray() that seems to work:
computed: {
filtered: function () {
var query = this.get( 'query').trim(),
users = this.get( 'users' );
return query ? users.search(query) : users.getAllAsArray();
}
},
data: function () {
return {
users: usersModel,
query: ''
}
}
Works with the sub-component as pure reactive programming without the need for an event:
<Search query="{{query}}" placeholder="Search users" />
Updated bin here: https://jsbin.com/bomumajagu/edit?html,output
Push works against Stapes model:
this.get( 'users' ).push( { name: newUser } );

Keep your users array. Your filtered result is derived from your search query and the users array. Use computed properties for this. Essentially, the search result is another array.
Here's an example using POJO, not Stapes, but same idea: http://jsfiddle.net/561fj33a/
HTML:
<script type="template/ractive" id="template-users-list">
<div>
<input type="text" value="{{ query }}">
</div>
<ul>
{{# usersList }}
<li>{{ name }}</li>
{{/}}
</ul>
</script>
JS:
var UsersList = Ractive.extend({
template: '#template-users-list',
data: {
query: '',
users: []
},
computed: {
usersList: function () {
var query = this.get('query');
var users = this.get('users');
return !query ? users : users.filter(function (user) {
return ~user.name.toLowerCase().indexOf(query);
});
}
}
});
var usersList = new UsersList({
el: 'body',
append: true
});
setTimeout(function () {
usersList.set('users', [{
name: 'James'
}, {
name: 'John'
}, {
name: 'Jenny'
}, {
name: 'George'
}, {
name: 'Harry'
}]);
}, 2000);

Related

Vue js: mapping array from API response data to checkbox list and back

I'm using Vue js to display and edit details of a person. The person being edited has a list of favourite colours that I want to display as a list of checkboxes. When I change the colours selected, and click the 'Update' button, the person object should be updated accordingly, so I can pass back to the api to update.
I've got as far as displaying the Person object's colours correctly against their respective checkboxes. But I'm struggling with passing the changes to the colour selection, back to the Person object. Below is my checkbox list and details of how I've tried to implement this. Is there a better way of doing this?
I've tried using 'b-form-checkbox-group'. Below is my code.
Please note - The list of available colours is dynamic, but I've temporarily hardcoded a list of colours ('colourData') till I get this working.
Also, in the 'UpdatePerson' method, I've commented out my attempts to get the selected colours mapped back to the Person object.
<template>
<form #submit.prevent="updatePerson">
<b-form-group label="Favourite colours:">
<b-form-checkbox-group id="favColours"
v-model="colourSelection"
:options="colourOptions"
value-field="item"
text-field="name">
</b-form-checkbox-group>
</b-form-group>
<div class="container-fluid">
<b-btn type="submit" variant="success">Save Record</b-btn>
</div>
</form>
</template>
<script>
import service from '#/api-services/colours.service'
export default {
name: 'EditPersonData',
data() {
return {
personData: {
personId: '',
firstName: '',
lastName: '',
colours:[]
},
colourData: [
{ colourId: '1', isEnabled: '1', name: 'Red' },
{ colourId: '2', isEnabled: '1', name: 'Green' },
{ colourId: '3', isEnabled: '1', name: 'Blue' },
],
selectedColours: [],
colourSelection: []
};
},
computed: {
colourOptions: function () {
return this.colourData.map(v => {
let options = {};
options.item = v.name;
options.name = v.name;
return options;
})
}
},
created() {
service.getById(this.$route.params.id).then((response) => {
this.personData = response.data;
this.colourSelection = response.data.colours.map(function (v) { return v.name; });
this.selectedColours = response.data.colours;
}).catch((error) => {
console.log(error.response.data);
});
},
methods: {
async updatePerson() {
//const cs = this.colourSelection;
//const cd = this.colourData.filter(function (elem) {
// if (cs.indexOf(elem.name) != -1) { return elem;}
//});
//this.personData.colours = [];
//this.personData.colours = cd;
service.update(this.$route.params.id, this.personData).then(() => {
this.personData = {};
}).catch((error) => {
console.log(error.response.data);
});
},
}
}
</script>
Any help wold be much appreciated.
Thanks
I got this working by making the below changes to the commented part in the 'updatePerson()' method:
methods: {
async updatePerson() {
const cs = this.colourSelection;
const cd = this.colourData.filter(function (elem) {
if (cs.some(item => item === elem.name)) { return elem; }
});
this.personData.colours = [];
this.personData.colours = cd;
service.update(this.$route.params.id, this.personData).then(() => {
this.personData = {};
}).catch((error) => {
console.log(error.response.data);
});
}
}

Not getting any results back from Fuse.js with Vue

So I'm fairly new to Vue and I'm trying to make a customer list search work with Fuse.js.
I do get the array of customers back and it's being assigned to customer_search. my keys are populated properly and the only issue is that results doesn't return anything. I'm wondering if I need to structure my customer array differently or am I missing something else altogether?
Any help would be appreciated.
Here is my code:
<template>
<div>
<div class="container">
<h1>Search</h1>
<input type="text" class="input-search" value="" v-model="query">
<p v-html="results"></p>
<p v-for="info in data" >{{info}}</p>
</div>
</div>
</template>
<script>
import Fuse from 'fuse.js'
import $ from 'jquery'
import PageService from '../../common/services/PageService'
const Search = {
data(){
return {
data: {},
fuse: {},
results: {},
query: '',
options: {
keys: [
'id',
'name',
'company',
],
minMatchCharLength: 3,
shouldSort: true,
threshold: 0.5
},
}
},
methods:{
runQuery(query){
if(query.length >= 3)
this.results = this.fuse.search(query)
},
},
computed:{
customers: function(){
return this.data
},
customer_search: function(){
return Object.values(this.data)
},
},
watch: {
query: function(){
this.runQuery(this.query)
}
},
created(){
this.fuse = new Fuse(this.customer_search, this.options)
if(this.$store.state.search != ''){
this.query = this.$store.state.search
}
PageService.getSearchObject().then((response)=>{
this.data = response.data
}).catch((err)=>{
console.log('Error')
});
},
}
export default Search
</script>
I think your runQuery method is created before your this.fuse get created so the this.fuse inside your runQuery method is not up-to-date.
Maybe try:
methods:{
runQuery(query){
if(query.length >= 3)
this.results = new Fuse(this.customer_search, this.options).search(query)
},
},

Set object in data from a method in VUE.js

I have been stuck with this issues for 2 hours now and I really can't seem to get it work.
const app = new Vue({
el: '#book-search',
data: {
searchInput: 'a',
books: {},
},
methods: {
foo: function () {
axios.get('https://www.googleapis.com/books/v1/volumes', {
params: {
q: this.searchInput
}
})
.then(function (response) {
var items = response.data.items
for (i = 0; i < items.length; i++) {
var item = items[i].volumeInfo;
Vue.set(this.books[i], 'title', item.title);
}
})
.catch(function (error) {
console.log(error);
});
}
}
});
When I initiate search and the API call I want the values to be passed to data so the final structure looks similar to the one below.
data: {
searchInput: '',
books: {
"0": {
title: "Book 1"
},
"1": {
title: "Book 2"
}
},
Currently I get Cannot read property '0' of undefined.
Problem lies here:
Vue.set(this.books[i], 'title', item.title);
You are inside the callback context and the value of this is not the Vue object as you might expect it to be. One way to solve this is to save the value of this beforehand and use it in the callback function.
Also instead of using Vue.set(), try updating the books object directly.
const app = new Vue({
el: '#book-search',
data: {
searchInput: 'a',
books: {},
},
methods: {
foo: function () {
var self = this;
//--^^^^^^^^^^^^ Save this
axios.get('https://www.googleapis.com/books/v1/volumes', {
params: {
q: self.searchInput
//-^^^^--- use self instead of this
}
})
.then(function (response) {
var items = response.data.items
var books = {};
for (i = 0; i < items.length; i++) {
var item = items[i].volumeInfo;
books[i] = { 'title' : item.title };
}
self.books = books;
})
.catch(function (error) {
console.log(error);
});
}
}
});
Or if you want to use Vue.set() then use this:
Vue.set(self.books, i, {
'title': item.title
});
Hope this helps.
yep, the problem is about context. "this" returns not what you expect it to return.
you can use
let self = this;
or you can use bind
function(){this.method}.bind(this);
the second method is better.
Also google something like "how to define context in js", "bind call apply js" - it will help you to understand what is going wrong.
// update component's data with some object's fields
// bad idea, use at your own risk
Object
.keys(patch)
.forEach(key => this.$data[key] = patch[key])

Transform data before rendering it to template in meteor

I want to return a single document with the fields joined together. That is, a result like as follows
{
_id: "someid",
name: "Odin",
profile: {
game: {
_id: "gameid",
name: "World of Warcraft"
}
}
}
I have a route controller which is fairly simple.
UserController = RouteController.extend({
waitOn: function () {
return Meteor.subscribe('users');
},
showAllUsers: function () {
this.render('userList', {
data: Meteor.users.find()
})
}
});
I've tried changing my data like so:
this.render('userList', {
data: Meteor.users.find().map(function (doc) {
doc.profile.game = Games.findOne();
return doc;
})
});
However, this does not have the intended effect of adding "game" to the user. (and yes, Games.findOne() has a result)
How can you transform the results of a cursor in meteor and iron:router?
Try defining your data as a function so it can be dynamically re-executed when needed.
UserController = RouteController.extend({
waitOn: function () {
return Meteor.subscribe('users');
},
showAllUsers: function () {
this.render('userList', {
data: function(){
return Meteor.users.find().map(function (doc) {
doc.profile.game = Games.findOne();
return doc;
});
}
});
}
});
Given your use of easy search, what might be simpler is just to define a template helper for profile
Template.userList.helpers({
profile: function(){
var game = Games.findOne({_id: this.gameId});
return { game: { _id: game._id, name: game.name }};
}
});
This assumes a single game per user. If you have more than one then you can iterate over a cursor of Games instead.

Override parse function on Backbone Collection

I have a backbone collection:
var user = new Backbone.Collection.extend({
url: '/user',
parse: function (response) {
return response.lunch;
return response.dinner;
}
});
which returns a json like this:
[
{
lunch: [{
appetizer : 'bagel',
maincourse: 'steak',
desert: 'sweets'
}]
},
{
dinner: [{
appetizer : 'chips',
main: 'rice',
desert: 'sweets'
}]
}
]
I want to combine both response.lunch and response.dinner and have a common collection: I tried:
parse: function (response) {
var collection1 = response.lunch;
var collection2 = response.dinner;
return collection1.add(collection2.toJSON(), {silent : true});
}
But it doesnot work. Also how do i do a each function to override all main with maincourse? I tried:
this.collection.each(function(model) {
var a = model.get("main");
model.set({a: "maincourse"});
}
Any help would be appreciated. Thanks!
I'm guessing that you want to merge lunch and dinner so that your collection ends up with { appetizer : 'bagel', ... } and { appetizer : 'chips', ... } inside it. If so, then simply concat the two arrays together:
parse: function(response) {
return response.lunch.concat(response.dinner);
}
If you want to rename all the main attributes to maincourse then you'd want to use get to pull out the mains, unset to remove them, and then set to put them back in with the new name:
var maincourse = model.get('main');
model.unset('main', { silent: true });
model.set('maincourse', maincourse, { silent: true });
or just edit attributes directly:
model.attributes.maincourse = model.attributes.main;
delete model.attributes.main;
or better, just rename the attribute in your parse method.

Categories