Vuejs bind class to row then later hide/show the row? - javascript

I'm trying to create a table where the user can hide rows, and keep them hidden as they delete other rows.
In my html I use vuejs to bind a class when rendering the table:
<tr v-for="item in mylist" :class="{'noFruits': (item.fruits.length == 0)}">
There is a user checkbox to hide rows with that class:
<label><input type="checkbox" v-model="showBlankFruits" #change="setBlankDisplay">Show Blank Fruits</label>
In my Vue instance, the checkbox executes a method to hide/show rows with that class via jquery to attach the css display property:
methods: {
setBlankDisplay: function() {
if (this.showBlankFruits) {
$('.noFruits').css('display', '');
} else {
$('.noFruits').css('display', 'none');
}
},
In my jsfiddle, when a user deletes a row, the hidden row reappears. I see that attaching styles with jquery in this instance is not good... does anyone have a suggestion for a better method?

Mixing Vue and jQuery is not recommended, as you can do pretty much everything just using Vue and you don't get any conflicting operations that don't know what the other library/framework is doing.
The following will show the row if either the fruits array length is truthy, in other words not 0, or if showBlankFruits is true:
<tr v-for="item in mylist" v-show="item.fruits.length || showBlankFruits">
The following will toggle showBlankFruits when clicking the checkbox:
<label><input type="checkbox" v-model="showBlankFruits">Show Blank Fruits</label>
Full code example:
JSFiddle

You can also write something like this.
I've used computed and removed the jQuery part completely.
You must declare data as a function instead of an data object (https://v2.vuejs.org/v2/guide/components.html#data-Must-Be-a-Function)
You do not need to call the mounted method to set the initial state. It's already set with your data object.
In your code, you have to call mounted, because jQuery can only hide the results, when the DOM is loaded.
new Vue({
el: '#app',
data() {
return {
showBlankFruits: true,
mylist: [
{'user': 'Alice', 'fruits': [{'name': 'apple'}]},
{'user': 'Bob', 'fruits': [{'name': 'orange'}]},
{'user': 'Charlie', 'fruits': []},
{'user': 'Denise', 'fruits': [{'name': 'apple'}, {'name': 'orange'}]},
]
}
},
computed: {
list() {
return this.mylist.filter(item => (item.fruits.length > 0 && !this.showBlankFruits) || (item.fruits.length === 0 && this.showBlankFruits))
},
},
methods: {
delItem(item) {
let index = this.mylist.indexOf(item);
if (index > -1) {
this.mylist.splice(index, 1);
}
}
}
})
<script src="https://unpkg.com/vue"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<div id="app">
<label><input type="checkbox" v-model="showBlankFruits">Show Blank Fruits</label>
<br> <br>
<table>
<tr> <th class="col">User</th> <th class="col">Fruits</th> <th class="col"></th> </tr>
<tr v-for="item in list">
<td>{{ item.user }}</td>
<td> <span v-for="f in item.fruits"> {{ f.name }} </span> </td>
<td> <button #click="delItem(item)">Delete</button> </td>
</tr>
</table>
</div>

This is a great example of the power for declarative rendering rather than using DOM manipulation in components. The problem you are running into is when the Vue engine re-renders your list it doesn't know about the manual manipulation of elemets you did in the setBlankDisplay method. The way to get around that is to use component logic in the definition of the view itself, sort of like you did to set the noFruits class in the first place.
So, I propose you get rid of setBlankDisplay and replace it with the method:
itemDisplay(item) {
if (item.fruits.length === 0 && !this.showBlankFruits) {
return 'none';
}
return '';
},
Then you can reference it in the definition of your tr elements linked to a css display property, like so:
<tr v-for="item in mylist" :class="{'noFruits': (item.fruits.length == 0)}" :style="{display: itemDisplay(item)}">
I've updated the jsfiddle with this modification, showing that the state of hidden fruits remains when other items are deleted.
Take this as a general example of the dangers of using jquery to change the state of the view. Every effort should be taken to define the entire view in terms of component logic.

Related

Get data from component to another page view

There is one component "TableFields.vue", and two view files "Home.vue" and "Statistics.vue".
In component, I have changes: [] variable under data() object.
data () {
return {
startStopA: true,
startStopB: true,
initialValueA: 3,
initialValueB: 3,
randomNumbersArray: [],
randomSignA: '+',
randomSignB: '+',
signsArray: ['+', '-'],
intervalA: null,
intervalB: null,
changes: []
}
},
That changes array dynamically gets object from calculationsA() function.
calculationsA () {
this.randomSignA = this.signsArray[
Math.floor(Math.random() * this.signsArray.length)
]
this.randomSignA === '+'
? (this.initialValueA += this.randomNumbersArray[0])
: (this.initialValueA -= this.randomNumbersArray[0])
const d = new Date()
// console.log(d.toLocaleTimeString())
// console.log(this.randomNumbersArray[0])
// this.changes.push(this.randomNumbersArray[0])
// this.changes.push(d.toLocaleTimeString())
// console.log(this.changes)
const newChange = {}
newChange.field = 'A'
newChange.value = this.randomNumbersArray[0]
newChange.time = d.toLocaleTimeString()
this.changes.push(newChange)
},
How do I pass changes: [] from TableField.vue component to Statistics.vuepage, in order to code dynamic table with changes array objects data. I am not sure, do I need to create new component or this can be done without it. Basically, this is the working code from the TableField.vue component that is implemented for testing purposes and can be seen from Home.vue which is root url.
<div class="statistics">
<table>
<tr>
<th>Field</th>
<th>Value</th>
<th>Time</th>
</tr>
<tr v-for="item in changes" :key="item.value">
<td>{{ item.field }}</td>
<td>{{ item.value }}</td>
<td>{{ item.time }}</td>
</tr>
</table>
</div>
</div>
I need that code to work at Statistics.vue page.
Here is the link to gitlab repo for better convenience.
You can import TableField.vue in Statistics.vue and pass changes: [] as props.
User "async" from discord vue chat has solved this for me.
Solution was a bit complicated and required a changes to few files.
Refer to gitlab repo if you are curious to find out more about it, because I can't document it here due to its complexity.
According to me, the better solution should be using Vuex (https://vuex.vuejs.org/) that permits you to store data in sharing point called store.
Alternatively you can use events as documented here: https://v2.vuejs.org/v2/guide/components-custom-events.html#Binding-Native-Events-to-Components

passing a dynamic v-model as function param

I am very new to vuejs, and I am working with making a dynamic table where the left column will have a hidden input that will ultimately query a database and display the result in a pop up (for a quick cross referencing ability). I have made it to the point where it builds the table properly with a v-for, but I can't quite figure out how to bind the dynamic v-models to the js function that will run the process. If I am going about this the completely wrong way, please let me know. Cheers and thanks!
...
<tr v-for="tableRow in rtnUnsubs">
<td class="unsubCell">
<input name= "[tableRow.share_id]" v-model="[tableRow.share_id]" value="[tableRow.share_id]">{{ tableRow.share_id }}
<button v-on:click="getSub">view</button>
</td>
<td class="unsubCell">{{ tableRow.unsubscriber_type }}</td>
<td class="unsubCell">{{ tableRow.unsubscriber_id }}</td>
</tr>
...
<script>
...
getSub(/*v-model from input*/) {
window.alert(/*do some stuff with v-model*/)
return;
}
Ordinarily, the method bound to a click will get the event, but you can pass whatever you want it to get (include $event if you want to add that to other arguments).
When you say you want to "bind v-models", I take it to mean you want to pass the current data.
new Vue({
el: '#app',
data: {
rows: [{
share_id: 'IT ME'
},
{
share_id: 'THE OTHER ONE'
}
]
},
methods: {
getSub(data) {
console.log("Working with", data);
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="app">
<div v-for="tableRow in rows">
<input name="tableRow.share_id" v-model="tableRow.share_id" value="tableRow.share_id">{{ tableRow.share_id }}
<button v-on:click="getSub(tableRow.share_id)">view</button>
</div>
</div>

(X-Editable) with (Select2 Tags) not working, Can't populate the current tags and keep track of and added/removed tags

I'm trying to get X-Editable plugin with Select2 Tags option to work where I can populate some tags from an array object which I'm retrieving from server.
HTML
<div class="container">
<table>
<tr>
<td>Name:</td>
<td><div class="editable" data-asset="name"></div></td>
</tr>
<tr><td>Description</td><td><div class="editable" data-type="textarea" data-asset="description"></div></td></tr>
<tr>
<td>Tags:</td>
<td>
<div class="editable" data-asset="tags"></div>
<button id="asset-button-tags-edit">Edit Tags</button>
</td>
</tr>
</table>
</div>
JavaScript:
data = {
///...
// current tags <- user can add and remove tags
assetTags: [{
'778': 'Racer',
'456': 'BMW',
'112': 'M3'
}],
// available tags <- user could only add tags from this list
availableTags: [{
'345': 'Winner',
'789': 'Boy Racer',
'101': 'Boy Racer',
'009': 'Orange',
'778': 'Racer',
'456': 'BMW',
'112': 'M3'
}]
///...
}
$assetTags.editable({
type: 'select2',
pk: 1,
autotext : 'always',
source : getSource(),
value : data.assetTags,
emptytext: 'None',
display: function(value, sourceData) {
var html = data.assetTags,
checked = $.fn.editableutils.itemsByValue(data.assetTags, data.assetTags, 'id');
if(checked.length) {
$.each(checked, function(i, v) { html.push($.fn.editableutils.escape(v.text)); });
$(this).html(html.join(', '));
}
else {
$(this).empty();
}
},
select2: {
multiple : true,
initSelection : data.assetTags
}
});
How it should work:
User could only add tags to data.assetTags which are available at
data.availableTags
User could remove all the tags from data.assetTags
So whatever changes is done, it should be in sync with data.assetTags and that's what I will be sending back to the server.
Here is what I've currently achieved, not sure where I'm going wrong! hopefully somebody could help me with this :)
DEMO: http://jsfiddle.net/Farzad/20e6e1os/10/
Many thanks
Maybe you should follow this structure in multiple tags using x-editable: https://jsfiddle.net/emo_noel10/dLeumnpg/25/ and http://jsfiddle.net/dplanella/N6bQE/36/.
Try to change $assetsTag with the div class i.e: $('.editable').editable({});. And try to change the div tag with span tag.
I'm just new to the x-editable library, just sharing my learnings. Let me know if it is working. And btw, please answer here: (How to save data and to add new tag and its data from select 2 multiple select tag) if you have an idea on how to add new tags in this link https://jsfiddle.net/emo_noel10/dLeumnpg/25/. Thanks.

Parsing JSON result to object by using checkbox

I have loaded a JSON list into a table and I would like to parse 1 JSON result or multiple results into an object, so I can send it to the server.
My table looks like this so far:
HTML
<tr ng-repeat="t in student">
<td ng-model="herkanserNaam">{{ t.Name }}</td>
<td>{{ t.City }}</td>
<td>
<div class="checkbox" ng-click="laatzien(herkanserNaam, herkanserCheck)" ng-model="herkanserCheck">
<label>
<input type="checkbox">
</label>
</div>
</td>
</tr>
Controller
$scope.laatzien = function(name, active) {
var herkanser = [{
"name" : name,
"active" : false
}];
console.log(herkanser);
}
How would I be able to check one or multiple results and save the data(t.Name) into an object by using a checkbox? So far the function laatzien() is returning the empty values defined in herkanser.
The reason your laatzien method is failing is due to how you are using your directives. Let's work with the example you provided to get your laatzien method to fire.
HTML
<tr ng-repeat="student in students">
<td>{{ student.Name }}</td>
<td>{{ student.City }}</td>
<td>van</td>
<td>Huis</td>
<td>j.huis#student.han.nl</td>
<td>
<div class="checkbox">
<label>
<input type="checkbox" ng-model="student.isActive" ng-change="laatzien(student)">
</label>
</div>
</td>
</tr>
Javascript
$scope.laatzien = function (student) {
var herkanser = [{
"name": student.name,
"active": student.isActive
}];
console.log(herkanser);
}
I have made some opinionated changes in your example for readability purposes, others were needed to get the directives to fire as expected. Below are the changes to your snippets.
Renamed the student array to students. This will require a change in your controller from $scope.student to $scope.students.
Renamed the t object to student.
Removed the ng-click directive from your div.
Added an ng-change directive on your checkbox. Now when you click the checkbox your laatzien method should fire.
Added an isActive property to your student. Inside of your laatzien method, you may now check the state of the checkbox. If the checkbox is checked, student.isActive = true. If the checkbox is not checked, student.isActive = false.
From your code, you seem to want to build the "list of checked students" and send that to the server. In other words, what you want, is to allow the user to check on multiple students and at the end collect everything that was checked and send it over to the server.
If that's the case then your strategy to put an ng-click over the checkbox is wrong.
What you need is to bind your checkbox to your $scope model. Such as this:
<input type="checkbox" ng-model="t.isChecked" ng-true-value="true" ng-false-value="false'">
When the user checks the checkbox for a student. Your model will automatically be updated.
To collect the data to send over the server you need to put an ng-click on a submit button. In the event handler, simply loop through every student in your $scope "students" model and only save the ones that have isChecked property to true to be sent over to the server.
Hope this helps!
You could make a function to push thet item into an obj like so...
$scope.students = [
{
"name":"John",
"city":"Boston"
},
{
"name":"Amy",
"city":"Dallas"
}
]
$scope.activeObj = [];
$scope.laatzien = function(obj) {
if($.inArray(obj, $scope.activeObj) == -1) {
$scope.activeObj.push(obj);
} else {
var index = $scope.activeObj.indexOf(obj);
$scope.activeObj.splice(index, 1);
}
}
http://jsfiddle.net/5fcnazb2/

Populate knockout view model from javascript

I'm in the process of replacing one hell of a lot of javascript/jquery code with knockoutjs and I'm trying to figure out the best way forward. I have no time to replace everything at the same time so I will have to integrate the knockout logic with the existing javascript...
Is there a way to populate a knockout view model from javascript which is not called from a data-bind attribute? Any help would be nice since I've not been able to find this anywhere else (at least not anything that worked).
I know what I'm mentioning here isn't the "correct" way of doing things, but I'm trying to migrate parts of the javascript code... Doing it all in one go isn't an option at the moment.
(using knockout 3.2)
Edit:
Typically the existing javascript does something like:
$('#productlist').append(productItemHtmlCode);
And I would rather have it do something like:
ViewModel.productList.push(productItemObject);
If I understand correctly, currently you have something like this:
<div id='myDiv'>
current status is: <span id='statusSpan'>Active</span>
</div>
with some corresponding javascript that might be something like:
function toggleStatus() {
var s= document.getElementById('statusSpan');
s.innerHTML = s.innerHTML == 'Active' ? 'Inactive' : 'Active';
}
And you want to change it so that the javascript is updating the viewmodel rather than manipulating the DOM?
var app = (function() {
var vm = {
statusText: ko.observable('Active'),
toggleStatus: toggleStatus
}
return vm
function toggleStatus() {
vm.statusText = vm.statusText == 'Active' ? 'Inactive' : 'Active';
}
}) ();
ko.applyBindings(app,document.getElementById('myDiv'));
And then the html would be
<div id='myDiv'>
current status is: <span id='statusSpan' data-bind="text: statusText"></span>
</div>
If that's what you're talking about, that's what Knockout is designed for. The javascript updates the viewmodel, knockout manipulates the DOM.
The example you give is easy to represent in Knockout.
the HTML:
<div>
<table data-bind="foreach: products">
<tr>
<td data-bind="text: id"></td>
<td data-bind="text: name"></td>
<td data-bind="text: category"></td>
</tr>
</table>
</div>
and in the viewmodel:
vm = {
products: ko.observableArray(), // empty array to start
addProduct: addProduct
}
return vm;
function addProduct(id, name, category) {
products.push({id: id, name: name, category:category});
}
etc.

Categories

Resources