Reactivity vue not rendering data with a method - arrays

<template>
<div
v-for="(a, i) in arr"
:key="i"
:checked="a"
#click="toggleItem(i)"
class="checkbox"
>
<div class="out">{{ arr }}</div>
</template>
<script>
export default {
data() {
return {
arr: [true, false, true, false, true, true, true],
};
},
methods: {
toggleItem(index) {
this.arr.splice(index, 1, !index);
}
};
</script>
I wonder why the array arr is not rerendered in the template, probably because of the vue reactivity issues ?
I would like to receive rerendered {{arr}} in the template with opposite array items, changed from true to false and false to true.

Index is a number, so given this line of code:
this.arr.splice(index, 1, !index);
If say index = 3, then !index = false (so says JavaScript when you negate any number), meaning the line of code can be simplified to:
this.arr.splice(index, 1, false);
Instead you need to specify the index of the array:
this.arr.splice(index, 1, !this.arr[index])
Here's a sandbox example.

Related

How to use Vuelidate for an Object of an Array?

I have the following vue array;
server: [
{ id: 1, name: 'Name A', ipaddress: '192.168.1.1', status: true, applied: true, modal: false },
{ id: 1, name: 'Name A', ipaddress: '192.168.1.1', status: true, applied: true, modal: false },
]
I use this array to show these information on a data table. Users can add new rows to this table, that is, they can push the array. In addition, they can delete the rows they want from the table with the splice method. Finally, each row has an edit button. Since these buttons are connected to the elements in the array with the v-model, users can make changes on the row they want in the modal window that opens.
Adding and editing operations are carried out with two different modalities that open when the button is pressed.
In line with all this information, there is a question I want to ask. How can I write validation with Vuelidation to an array where new rpws can be added continuously? Here is my vuelidation functions;
validations: {
server: {
required,
$each: {
name: {
required
},
ipaddress: {
required
}
}
}
}
As an example, I just defined the required attribute for two elements. And here is how I use them in my add and edit modals;
<div>
<div">
<label>Name</label>
</div>
<div>
<label>:</label>
</div>
<div>
<input v-if="server[i].modal" v-model="server[i].name" type="text"/>
</div>
<small class="error-msg" v-if="!$v.server[i].name.required && $v.server[i].name.$dirty">Name is required.</small>
</div>
Here is how I add a new row to the table;
addNewRow(){
this.server.push({
name: "",
ipaddress: "",
status: true,
applied: false,
modal: false
});
},
And now I have this error;
[Vue warn]: Error in render: "TypeError: Cannot read property 'name' of undefined"
I think I'm missing an important part here so how can I make this correct? Thanks in advance.
Your add modal might be delay with the template so no server[i] item would be defined. I would suggest using a different variable for add (newItem = {name: '', ...})

VUEjs templates multiple selectboxes

So, I'm assigned to work with vue at work, but VUE and I aren't friends yet. Currently I'm facing an issue that I don't know how to resolve - I'll explain it the best I can with the limited VUE knowledge I possess.
Simplistically I have a vue component, which looks like this:
Vue.component('input-checkboxes', {
template: '#input_checkboxes',
props: ['id', 'label', 'list', 'required', 'readonly']
});
Then I have a template that looks like this:
<template id="input_checkboxes">
<div>
<div>{{ label }}</div>
<div>
<label v-for="list_item in list">
<input type="checkbox" v-model="value" :name="id" :required="required" :readonly="readonly" value="{{ list_item.name }}"> {{ list_item.name }}
</label>
</div>
</div>
</template>
Then I have a rather large vue instance that I'll paste the relevant parts of here.
This variable is being created:
var some_form = {
form : {
Endless: '',
Amounts: '',
Of: '',
Names: '',
In: '',
The: '',
Form: '',
THIS-ONE: ''
}
};
var vm = new Vue({
el: '#form_product',
data: $.extend({
someStuff : 'some values',
someLists : {}
}, some_form),
ready: function() {
this.getLists(); // Fetches alot of lists
},
methods: {
this.$http.get(
framework.url('api','getLookupLists')
).then(function (response) {
this.lists = response.body;
this.pageLoading = false;
}.bind(this));
}
In the end I have my html page that amongst loads of other fields, that works very well, has this:
<input-checkboxes
id="THIS-ONE"
label="A Fitting Label"
:value.sync="form.SomeID"
:list="lists.AnAppropriateList">
</input-checkboxes>
So, that's the gist of the setup. I have numerous other components like input-text, that works just fine (someone else made it before I did), I even created other components by copying his way, and just changing some elements.
I cannot get checkboxes to work, I think my problem is that there are numerous inputs, and that I don't know how to bind the results of those inputs to my VUE instance.
I really hope this makes sense, because I would really like some pointers on how to get on... Maybe if someone duplicated this setup really simplistic and showed how the array of values from the checkboxes could be bound to the vue instance?
There are a couple of mistakes you are (or might be) making.
First of all, the value prop you pass down has to be an array (seems
like it's a string from your example)
value is not correctly set, you need to set it by doing :value="someValue"; you can't have curlies in an attribute.
Lastly, value should probably be the id of the item and not the name. You have a chance of a collision if you use the name.
Bonus: you don't need to use :name at all (unless you are submitting the form server side...? But I can't see why you would do that.)
Here's a simple working example to sum this up:
HTML
<label v-for="list_item in list">
<input type="checkbox" v-model="value" :required="required" :readonly="readonly" :value="list_item.id"> {{ list_item.name }}
</label>
JS
var app = new Vue({
el: 'main',
data: function () {
return {
value: [],
label: 'Label name',
readonly: false,
required: true,
list: [
{
name: 'Item 1',
id: 'item1'
},
{
name: 'Item 2',
id: 'item2'
}
]
}
}
})
I've also made a bin for you to try it out.

Having trouble with react-grid-layout example

Been trying to get into react and was looking at react-grid-layout when I came across a bit of a roadblock. I've pasted in the example from here essentially as is, but for some reason, when I drag an element it's not sticking. The error I'm getting in the console is:
Uncaught TypeError: this.props.onLayoutChange is not a function
I'm sure it's a simple thing that I'm missing, but this is my first React project and I would appreciate some guidance.
My code is included below:
'use strict';
var React = require('react');
var _ = require('lodash');
var ResponsiveReactGridLayout = require('react-grid-layout').Responsive;
/**
* This layout demonstrates how to use a grid with a dynamic number of elements.
*/
var AddRemoveLayout = React.createClass({
getDefaultProps() {
return {
className: "layout",
cols: {lg: 12, md: 10, sm: 6, xs: 4, xxs: 2},
rowHeight: 100
};
},
getInitialState() {
return {
items: [0, 1, 2, 3, 4].map(function(i, key, list) {
return {i: i, x: i * 2, y: 0, w: 2, h: 2, add: i === list.length - 1};
}),
newCounter: 0
};
},
createElement(el) {
var removeStyle = {
position: 'absolute',
right: '2px',
top: 0,
cursor: 'pointer'
};
var i = el.add ? '+' : el.i;
return (
<div key={i} _grid={el}>
{el.add ?
<span className="add text" onClick={this.onAddItem} title="You can add an item by clicking here, too.">Add +</span>
: <span className="text">{i}</span>}
<span className="remove" style={removeStyle} onClick={this.onRemoveItem.bind(this, i)}>x</span>
</div>
);
},
onAddItem() {
console.log('adding', 'n' + this.state.newCounter);
this.setState({
// Add a new item. It must have a unique key!
items: this.state.items.concat({
i: 'n' + this.state.newCounter,
x: this.state.items.length * 2 % (this.state.cols || 12),
y: Infinity, // puts it at the bottom
w: 2,
h: 2
}),
// Increment the counter to ensure key is always unique.
newCounter: this.state.newCounter + 1
});
},
// We're using the cols coming back from this to calculate where to add new items.
onBreakpointChange(breakpoint, cols) {
this.setState({
breakpoint: breakpoint,
cols: cols
});
},
onLayoutChange(layout) {
this.props.onLayoutChange(layout);
this.setState({layout: layout});
},
onRemoveItem(i) {
console.log('removing', i);
this.setState({items: _.reject(this.state.items, {i: i})});
},
render() {
return (
<div>
<button onClick={this.onAddItem}>Add Item</button>
<ResponsiveReactGridLayout onLayoutChange={this.onLayoutChange} onBreakpointChange={this.onBreakpointChange}
{...this.props}>
{_.map(this.state.items, this.createElement)}
</ResponsiveReactGridLayout>
</div>
);
}
});
module.exports = AddRemoveLayout;
React.render(<AddRemoveLayout/>, document.getElementById('app'))
The error you are receiving is an error about a missing prop. In a react component you basically have 2 places to keep your data, in its parent and in your component itself. Your parent often has props while declaring it because those are properties you pass to the child (like an attribute in an HTML tag). Then we have the state which is data inside a component itself.
The error you are receiving is saying that we didn't get a required prop from our parent (You can also see that inside the onLayoutChange(layout) function a call is being made to the this.props.onLayoutChange(layout) method).
So basically we are missing a few props. In the example from GitHub there is a root file called test-hook.jsx (https://github.com/STRML/react-grid-layout/blob/master/test/test-hook.jsx). This root node has as a child ( the code you are trying to render directly ) in which it is passing the required function as a property.
You can either use the test-hook.jsx or you can write your own root node which has a state with the layout and the required function which updates that state (see the github example on how to do that).
So after some searching, I figured out that the example was specifying the onLayoutChange function as a placeholder. If I wanted a custom funcion, I needed to define that.
Simply removing this function altogether and using the default fixed the issue.
Remove this:
onLayoutChange(layout) {
this.props.onLayoutChange(layout);
this.setState({layout: layout});
},
#Dirk-Jan explained it well. But the proper solution IMHO is to remove the prop call:
onLayoutChange(layout) {
// this.props.onLayoutChange(layout);
this.setState({layout: layout});
},
So the meaningful part is still there. In the examples the test-hook.jsx parent has to get hold of the layout so it can display it outside of the layout container for demonstration purposes. In a real-world application we don't need that.

Is it possible to assign a model to ionSlide or get selected slide (not index) from ionSlideBox?

I'm working in a way to present a bi-dimensional list inside an Ionic Application (along with AngularJs). I currently have an API which outputs a json object much like the following:
[
[
{ id: 1, loaded: false, thumbnail: "https://s3.amazonaws.com/uifaces/faces/twitter/sauro/128.jpg", description: "1" },
{ id: 2, loaded: false, thumbnail: "https://s3.amazonaws.com/uifaces/faces/twitter/jsa/128.jpg", description: "2" },
{ id: 3, loaded: false, thumbnail: "https://s3.amazonaws.com/uifaces/faces/twitter/k/128.jpg", description: "3" }
],
[
{ id: 4, loaded: false, thumbnail: "https://s3.amazonaws.com/uifaces/faces/twitter/jadlimcaco/128.jpg", description: "4" },
{ id: 5, loaded: false, thumbnail: "https://s3.amazonaws.com/uifaces/faces/twitter/rem/128.jpg", description: "5" },
{ id: 6, loaded: false, thumbnail: "https://s3.amazonaws.com/uifaces/faces/twitter/csswizardry/128.jpg", description: "6" }
]
]
Every item on the parent array is a new array of slides (ie, items to be displayed horizontally, one at a time).
Using Ionic's list template and a couple of it's components (ionSlideBox and ionSlide), I was able to come up with the following markup:
<ion-slide-box show-pager="false" class="list" ng-repeat="row in list">
<ion-slide ng-repeat="column in row" class="item item-thumbnail-left">
<img ng-src="{{column.thumbnail}}">
<h2>{{column.description}}</h2>
<p>{{column.loaded}}</p>
</ion-slide>
</ion-slide-box>
My problem is: I need to execute code whenever a slide is activated for the first time and set the "loaded" property to true.
However, ionSlideBox's option on-slide-changed only gives me the ability to take the index of the activated slide. In my case, this doesn't really help me because even if I know the slide's index, I still need to figure out from which array it belongs.
The dimensions here are both dynamic and can be any number at any time.
You can see a sample running here:
http://plnkr.co/edit/ZdhRl2BHVkJRCtK7sQgf?p=preview
If on-slide-changed would support more parameters, I could pass the outer array index along with the selected slide index (inner array index) but I can't figure out a way to make it happen.
I appreciate any help. Thanks.
Value of on-slide-changed is an expression, which gets called when the slide changes. Because of this, you can use any function call with any parameters currently in scope. Slide box injects one more variable: index with index of the slide the slide box is sliding to.
Thus you only need the outer index, which is also already in scope, in this case from the ng-repeat directive, named $index.
Let's use it in your app:
<ion-slide-box show-pager="false" class="list" ng-repeat="row in list"
on-slide-changed="onSlideChanged($index, index)">
with handler in scope:
$scope.onSlideChanged = function(index1, index2) {
var item = $scope.lista[index1][index2];
if (!item.loaded) {
item.loaded = true;
console.log("ITEM LOADED:", item);
}
};
or even move array accessing to view:
<ion-slide-box show-pager="false" class="list" ng-repeat="row in list"
on-slide-changed="onSlideChanged(list[$index][index])">
with handler in scope:
$scope.onSlideChanged = function(item) {
if (!item.loaded) {
item.loaded = true;
console.log("ITEM LOADED:", item);
}
};
New plunker

Dynamic filter within ng-repeat in AngularJS

For my AngularJS app I have an ng-repeat in an ng-repeat like so:
Html:
<div ng-app="myApp">
<div ng-controller="myController">
<h2>working filter</h2>
<div ng-repeat="category in categories">
<h3>{{category}}</h3>
<div ng-repeat="item in items | filter:{price.red: 0} ">{{item.name}}</div>
</div>
<h2>broken filter</h2>
<div ng-repeat="category in categories">
<h3>{{category}}</h3>
<!-- price[category] should be red, blue, yellow, depending on the category in the ng-repeat -->
<!-- price[category] should be bigger then 0 (or not zero, negative values will not occur) -->
<div ng-repeat="item in items | filter:{price[category]: 0} ">{{item.name}}</div>
</div>
</div>
</div>
Javascript:
var myApp = angular.module('myApp', []);
myApp.controller('myController', function ($scope) {
$scope.categories = ['red', 'blue', 'yellow'];
$scope.items = [
{name: 'one', price:{red: 0, blue: 1, yellow: 3} },
{name: 'two', price:{red: 2, blue: 0, yellow: 0}},
]
});
Please see my jsFiddle http://jsfiddle.net/hm8qD/
So I ran into two problems:
The flter does not accept [] brackets so I cannot make the filter dynamic, based on the category
The filter needs to return items that have a price[category] greater then zero.
For the greater then zero I could just make a custom filter. However as far as I know filters don't accept parameters so I have no way of getting the category (red, blue or yellow) to it.
Please note this is an simplified version of my actual code and this context might not make the best of sense. I hope I was clear in explaining what I need the filter to do, since I'm new to AngularJS.
Apparently it is actually possible to pass arguments to your filter, but you have to make a custom filter instead of using "filter: expression". What I did was create a custom filter which takes the items and category as arguments and returns the array with filtered items.
myApp.filter('myFilter', function () {
return function (items, category) {
var newItems = [];
for (var i = 0; i < items.length; i++) {
if (items[i].price[category] > 0) {
newItems.push(items[i]);
}
};
return newItems;
}
});
See the updated Fiddle here: http://jsfiddle.net/hm8qD/3/
I found a different and quite neat solution for sending parameters to the filter (written in es6):
$scope.myFilter = (category) => {
return (item) => {
if(category === 'fish' && item.type === 'salmon'){
return true
}
return false
}
}
the markup would be:
<div ng-repeat="item in items |
filter:myFilter(someCategory)>
{{item.name}}
</div>
So basically you can just scope the "category" into the filter as myFilter returns a function on the format expected by the filter.

Resources