Just have such a view:
<div class="items">
<div class="datemark" data-date="1">Today</div>
<div class="item" data-date="1">Item 1</div>
<div class="item" data-date="1">Item 2</div>
<div class="item" data-date="1">Item 3</div>
<div class="datemark" data-date="2">Tommorow</div>
<div class="item" data-date="2">Item 1</div>
<div class="item" data-date="2">Item 2</div>
<div class="item" data-date="2">Item 3</div>
</div>
with such data:
data = [
1: {
human: 'Today',
date: 1,
items: [
item1: {
name: 'test',
date: 1 // possible here
},
item2: {...},
]
},
2: {
human: 'Tommorow',
date: 2,
items: [
item1: {...},
item2: {...},
]
}
]
or it can be:
data = [
item1: {
name: 'test',
date: 1 // possible here
},
item2: {
name: 'other',
date: 2 // possible here
},
...
]
How to make it works and can be sorted desc/asc by datemarks and 'inside' datemarks range if it have different hours and minutes also?
Tried v-for but only simple lists, how with datemarks?
Thanks!
You don't have to do it like this, use a computed property instead, and you can see I used slice, it's because I want to copy the array instead of sorting the elements in place:
const app = new Vue({
el: '#app',
data: {
order: 'ASC',
list: [
{
name: 'item1',
date: 1 // possible here
},
{
name: 'item2',
date: 2 // possible here
},
{
name: 'item3',
date: 3 // possible here
},
{
name: 'test',
date: 4 // possible here
},
{
name: 'other',
date: 5 // possible here
}
]
},
computed: {
sortedList() {
return this.list.slice(0)
.sort((a, b) => {
if (this.order === 'ASC') {
return b.date - a.date;
}
return a.date - b.date;
})
}
},
methods: {
toggleSort(type){
this.order = type;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
<div id="app">
<div>
<button v-on:click="toggleSort('ASC')">ASC</button>
<button v-on:click="toggleSort('DSC')">DSC</button>
</div>
<div>
<div class="datemark" v-for="item in sortedList">
<div class="item">{{item.name}}</div>
</div>
</div>
</div>
And for your reference, if you're actually doing comparison on real dates, use this instead"
array.sort(function(a,b){
// Turn your strings into dates, and then subtract them
// to get a value that is either negative, positive, or zero.
return new Date(b.date) - new Date(a.date);
});
Related
I'm trying to implement a load more button to my code. I would be able to this in javascript but I can't find a similar way in vue.
This is my vue code. I've tried asking the element with the company id but it's not reactive so I can't just change the style.
<main>
<ul>
<li v-for="company in companiesdb" :key="company.id" v-bind:id="company.id" ref="{{company.id}}" style="display: none">
{{company.name}}<br>
{{company.email}}
</li>
</ul>
</main>
this is my failed atempt of doing it in javascript but as I've mentioned before ref is not reactive so I can't do it this way
limitView: function (){
const step = 3;
do{
this.numberCompaniesVis ++;
let li = this.$refs[this.numberCompaniesVis];
li.style = "display: block";
}while (this.numberCompaniesVis % 3 != step)
}
I think the way you are approaching this problem is a little complex. Instead, you can create a computed variable that will change the number of lists shown.
Here's the code
<template>
<div id="app">
<ul>
<li v-for="(company, index) in companiesLoaded" :key="index">
{{ company }}
</li>
</ul>
<button #click="loadMore">Load</button>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
companiesdb: [3, 4, 1, 4, 1, 2, 4, 4, 1],
length: 5,
};
},
methods: {
loadMore() {
if (this.length > this.companiesdb.length) return;
this.length = this.length + 3;
},
},
computed: {
companiesLoaded() {
return this.companiesdb.slice(0, this.length);
},
},
};
</script>
So instead of loading the list from companiesdb, create a computed function which will return the new array based on companiesdb variable. Then there's the loadMore function which will be executed every time user clicks the button. This function will increase the initial length, so more lists will be shown.
Here's the live example
Just use computed property to create subset of main array...
const vm = new Vue({
el: '#app',
data() {
return {
companies: [
{ id: 1, name: "Company A" },
{ id: 2, name: "Company B" },
{ id: 3, name: "Company C" },
{ id: 4, name: "Company D" },
{ id: 5, name: "Company E" },
{ id: 6, name: "Company F" },
{ id: 7, name: "Company G" },
{ id: 8, name: "Company H" },
{ id: 9, name: "Company I" },
{ id: 10, name: "Company J" },
],
companiesVisible: 3,
step: 3,
}
},
computed: {
visibleCompanies() {
return this.companies.slice(0, this.companiesVisible)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<ul>
<li v-for="company in visibleCompanies" :key="company.id" :id="company.id">
{{company.name}}
</li>
</ul>
<button #click="companiesVisible += step" v-if="companiesVisible < companies.length">Load more...</button>
</div>
I have to build a quiz/survey app in vue.js, I'm pretty new to vue and still trying to learn it. I have a quiz/survey that asks different questions depending on what the user answers in the initial question.
so if the user picks yes it will display question 2 if the user picks no it will display question 3 etc.
I'm not sure what the best way of going around it but so far I have this.
Is there anyway i can use the value of my answer as the questionIndex after a person clicks next?
JS file:
"use strict";
let quiz = {
title: "Asbestos Quiz",
questions: [
{
text: 'Do you need help with an Asbestos Survey?',
answers: [
{
text: "Yes",
value: '2'`enter code here`
},
{
text: "No",
value: '3'
},
]
},
{
text: 'Was your property constructed pre 2000',
answers: [
{
text: "Yes",
value: '4'
},
{
text: "No",
value: '5'
},
]
},
{
text: 'Do you need an Asbestos Management plan?',
answers: [
{
text: "Yes",
value: '6'
},
{
text: "No",
value: '7'
},
]
}
]
};
var app = new Vue({
el: "#app",
data: {
quiz: quiz,
questionIndex: 0,
responses: [],
errors: [],
error: ''
},
methods: {
prev: function() {
this.questionIndex--;
},
next: function() {
if (this.responses[this.questionIndex] === undefined) {
this.errors[this.questionIndex] = 1;
this.error = 'Please select your answer';
}
else {
this.errors[this.questionIndex] = 0;
this.questionIndex++;
}
},
score: function() {
},
playAgain: function() {
this.questionIndex = 0;
}
}
});
HTML:
<html lang="en">
<head>
<title>Vue quiz/survey</title>
<meta name="viewport" content="width=device-width"/>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<!-- <link rel="stylesheet" href="index.css"> -->
</head>
<body>
<div id="app">
<div class="container">
<div class="jumbotron mt-3">
<h1 class="mb-5">{{ quiz.title }}</h1>
<hr>
<p v-if="errors[questionIndex]" class="alert alert-danger">
{{ error }}
</p>
<div v-for="(question, index) in quiz.questions">
<div v-show="index === questionIndex">
<h4 class="mt-5 mb-3">{{ question.text }}</h4>
<div v-for="answer in question.answers" class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio"
:value="answer.value"
:name="index"
v-model="responses[index]">
{{answer.text}}
</label>
</div>
<div class="mt-5">
<button
class="btn btn-primary"
v-if="questionIndex > 0"
#click="prev">
prev
</button>
<button class="btn btn-secondary" #click="next">
next
</button>
</div>
</div>
</div>
<div v-show="questionIndex === quiz.questions.length">
<h3>Your Results</h3>
<p>
You are: {{ score() }}
</p>
<button class="btn btn-success" #click="playAgain">
Play Again!
</button>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="index.js"></script>
</body>
</html>
I thought that this sounded like a potentially interesting exercise, so I spent some time creating an implementation in a Vue CLI sandbox app that I built and use for trying out various ideas.
I learned a few things, and hopefully you will get something out of it. I left the 'Previous' functionality as a TODO if you decide you like it and want implement that yourself.
QuizQuestions.vue
<template>
<div class="quiz-questions">
<div class="jumbotron mt-3">
<h1 class="mb-5">{{ quiz.title }}</h1>
<hr>
<question v-if="!showResults" :question="currentQuestion" #answer-selected="processAnswer" />
<div class="mt-5">
<button class="btn btn-primary" v-if="currentQuestionId > 1 && !showResults" #click="getPreviousQuestion">
prev
</button>
<button v-if="!showResults" class="btn btn-secondary" #click="getNextQuestion">
{{ nextButtonLabel }}
</button>
</div>
<div v-if="showResults">
<h3>Your Results</h3>
<table class="table table-bordered">
<thead>
<tr>
<th>QUESTION</th>
<th>ANSWER</th>
</tr>
</thead>
<tbody>
<tr v-for="(response, index) in responses" :key="index">
<td>{{ getQuestionText(response.questionId) }}</td>
<td>{{ getAnswerText(response.answerId) }}</td>
</tr>
</tbody>
</table>
<button class="btn btn-success" #click="playAgain">
Play Again!
</button>
</div>
</div>
</div>
</template>
<script>
import quiz from './quiz.js';
import Question from '#/components/stackoverflow/Question'
export default {
components: {
Question
},
data() {
return {
quiz: quiz,
currentQuestionId: 1,
currentAnswerId: 1,
previousQuestionId: 0,
responses: [],
showResults: false,
errors: [],
error: ''
}
},
computed: {
currentQuestion() {
return this.quiz.questions.find( question => {
return question.id === this.currentQuestionId;
})
},
nextQuestionId() {
let retVal = 0;
if (this.currentAnswerId > 0) {
let tempAnswer = this.currentQuestion.answers.find( answer => {
return answer.id === this.currentAnswerId;
});
retVal = tempAnswer.nextQuestionId;
}
return retVal;
},
lastQuestion() {
return this.currentQuestion.answers[0].nextQuestionId === 0;
},
nextButtonLabel() {
return this.lastQuestion ? 'Finish' : 'Next';
}
},
methods: {
getPreviousQuestion() {
this.currentQuestionId = this.previousQuestionId;
},
getNextQuestion() {
// TODO: Look for existing response for this question in case the 'Previous' button was pressed
// If found, update answer
// Store current question id and answer id in responses
let response = { questionId: this.currentQuestionId, answerId: this.currentAnswerId };
this.responses.push(response);
if (this.lastQuestion) {
this.showResults = true;
return;
}
this.previousQuestionId = this.currentQuestionId;
this.currentQuestionId = this.nextQuestionId;
//console.log(this.responses);
},
getQuestionText(id) {
let result = this.quiz.questions.find( question => {
return question.id === id;
});
return result.text;
},
getAnswerText(id) {
// NOTE: Since answers are currently limited to '1 = Yes' and '2 = No',
// this method does not need to involve any look up
return id === 1 ? 'Yes' : 'No';
},
processAnswer(selectedAnswerId) {
this.currentAnswerId = selectedAnswerId;
},
score() {
return 'TODO'
},
playAgain() {
this.currentQuestionId = 1;
this.showResults = false;
this.responses = [];
}
}
}
</script>
Question.vue
<template>
<div class="question">
<h4 class="mt-5 mb-3">{{ question.text }}</h4>
<div class="form-check" v-for="(answer, idx) in question.answers" :key="idx">
<input class="form-check-input" type="radio"
:value="answer.id" v-model="answerId" #change="answerSelected">
<label class="form-check-label">
{{answer.text}}
</label>
</div>
</div>
</template>
<script>
export default {
props: {
question: {
type: Object,
required: true
}
},
data() {
return {
answerId: 1
}
},
watch:{
question() {
// Reset on new question
this.answerId = 1;
}
},
methods: {
answerSelected() {
this.$emit('answer-selected', this.answerId);
}
}
}
</script>
I also modified your test data by adding various ID properties to help with tracking, as well as created a few more placeholder questions.
quiz.js
const quiz = {
title: "Asbestos Quiz",
questions: [
{
id: 1,
text: 'Do you need help with an Asbestos Survey?',
answers: [
{
id: 1,
text: "Yes",
nextQuestionId: 2
},
{
id: 2,
text: "No",
nextQuestionId: 3
},
]
},
{
id: 2,
text: 'Was your property constructed pre 2000',
answers: [
{
id: 1,
text: "Yes",
nextQuestionId: 4
},
{
id: 2,
text: "No",
nextQuestionId: 5
},
]
},
{
id: 3,
text: 'Do you need an Asbestos Management plan?',
answers: [
{
id: 1,
text: "Yes",
nextQuestionId: 6
},
{
id: 2,
text: "No",
nextQuestionId: 7
},
]
},
{
id: 4,
text: 'Question 4',
answers: [
{
id: 1,
text: "Yes",
nextQuestionId: 0
},
{
id: 2,
text: "No",
nextQuestionId: 0
},
]
},
{
id: 5,
text: 'Question 5',
answers: [
{
id: 1,
text: "Yes",
nextQuestionId: 0
},
{
id: 2,
text: "No",
nextQuestionId: 0
},
]
},
{
id: 6,
text: 'Question 6',
answers: [
{
id: 1,
text: "Yes",
nextQuestionId: 0
},
{
id: 2,
text: "No",
nextQuestionId: 0
},
]
},
{
id: 7,
text: 'Question 7',
answers: [
{
id: 1,
text: "Yes",
nextQuestionId: 0
},
{
id: 2,
text: "No",
nextQuestionId: 0
},
]
}
]
};
export default quiz;
I am trying to implement nested comments in vue.js and nuxt.js.
Each comment can have one or more children comments.
Each child comment, can again, have one or more children comments.
Unlimited levels of nested comments is possible.
As you can see in the diagram I have attached, I would like each comment to "know" (for the sake of simplicity, to display) the following information:
The depth of the comment (I have this working already). Example, all of the "top-level" comments are at depth=0, all their children are at depth=1, and so on.
The number of direct children
the number of children (including nested children, unlimited levels deep)
I came across this question on StackOverflow but it doesn't quite do the trick. Or maybe I am doing something wrong.
In case you want to take a look at my (very messy) code, here it is. However, I'm willing to start over, so appreciate any pointers on how to pass the data up / down the chain of nested comments (vue components). Some sample code would be great.
components/PostComment.vue:
<template>
<div>
<div class="tw-flex tw-flex-wrap tw-justify-end">
<div :class="indent" class="tw-w-full tw-flex">
<div class="tw-font-bold tw-p-4 tw-border-gray-400 tw-border tw-rounded tw-text-right">
<div class="kb-card-section">
<div class="kb-card-section-content tw-flex tw-flex-wrap tw-items-center tw-text-left">
<div class="tw-flex tw-w-full">
<div class="tw-hidden md:tw-block md:tw-w-2/12 tw-text-right tw-my-auto">
<div class="tw-flex">
<p class="tw-w-full tw-text-xs tw-text-gray-600 tw-text-right">children: {{ numNestedChildComments }}, depth: {{depth}}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="tw-w-full" v-if="commentData.nested_comments" v-for="nestedComment in commentData.nested_comments">
<post-comment
:commentData="nestedComment"
:depth="depth + 1"
:numChildCommentsOfParent=numNestedChildComments
/>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'post-comment', // necessary for recursive components / nested comments to work
props: {
depth: {
type: Number,
required: true
},
postAuthorData: {
type: Object,
required: true
},
commentAuthorData: {
type: Object,
required: true
},
commentData: {
type: Object,
required: true
},
numChildCommentsOfParent: {
type: Number,
required: true
},
},
data() {
return {
numNestedChildComments: this.numChildCommentsOfParent,
}
},
mounted() {
this.incrementNumParentComments();
},
methods: {
incrementNumParentComments() {
this.numNestedChildComments++;
this.$emit('incrementNumParentComments');
},
},
computed: {
indent() {
switch (this.depth) {
case 0:
return "tw-ml-0 tw-mt-1";
case 1:
return "tw-ml-4 tw-mt-1";
case 2:
return "tw-ml-8 tw-mt-1";
case 3:
default:
return "tw-ml-12 tw-mt-1";
}
},
},
}
</script>
Figured it out with some help from Rodrigo Pedra from the Laracasts community.
Here as a parent component calling the tree roots:
<template>
<div>
<MyTree v-for="item in records" :key="item.id" :item="item" />
</div>
</template>
<script>
import MyTree from './MyTree';
const FIXTURE = [
{
id: 1,
children: [
{
id: 2,
children: [{id: 3}, {id: 4}, {id: 5}],
},
{
id: 6,
children: [
{id: 7},
{id: 8, children: [{id: 9}, {id: 10}]},
],
},
],
},
{
id: 11,
children: [
{id: 12, children: [{id: 13}, {id: 14}, {id: 15}]},
{id: 16, children: [{id: 17}]},
{id: 18},
],
},
];
export default {
components: {MyTree},
data() {
return {
records: FIXTURE,
};
},
};
</script>
And here is the tree component:
<template>
<div>
<div style="border: 1px solid black; padding: 5px;" :style="offset">
id: {{ item.id }}
// depth: {{ depth }}
// direct: {{ direct }}
// children: {{ childrenCount }}
</div>
<template v-if="item.children">
<MyTree
v-for="record in item.children"
:key="record.id"
:item="record"
:depth="depth + 1"
#born="handleBorn()" />
</template>
</div>
</template>
<script>
const COLORS = [
'white',
'lightgray',
'lightblue',
'lightcyan',
'lightskyblue',
'lightpink',
];
export default {
// MUST give a name in recursive components
// https://vuejs.org/v2/guide/components-edge-cases.html#Recursive-Components
name: 'MyTree',
props: {
item: {type: Object, required: true},
depth: {type: Number, default: 0},
},
data() {
return {
childrenCount: 0,
};
},
computed: {
direct() {
if (Array.isArray(this.item.children)) {
return this.item.children.length;
}
return 0;
},
offset() {
return {
'margin-left': (this.depth * 20) + 'px',
'background-color': COLORS[this.depth % COLORS.length],
};
},
},
mounted() {
this.$emit('born');
},
methods: {
handleBorn() {
this.childrenCount++;
this.$emit('born');
},
},
};
</script>
Screenshot:
I have an value like that my problem is i don't know how much nested that dummy array object so how to use ng-repeat that print all dummy array object
demo: [{
id: 1,
dummy: [
{
id: 1,
dummy: [
{
id: 1,
dummy: [
{
id: 1
}
]
}
]
}
]
}]
try like this. as this answers: How to put ng-repeat inside ng-repeat for n number of times
and How can I make recursive templates in AngularJS when using nested objects?
var app = angular.module("app", []);
app.controller('mainCtrl', function($scope){
$scope.demo = [{
id: 1,
dummy: [
{
id: 1,
dummy: [
{
id: 1,
dummy: [
{
id: 1,
dummy: [
{
id: 1,
dummy:[]
}
]
}
]
}
]
}
]
}]
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.2/css/font-awesome.min.css" rel="stylesheet"/>
<div ng-app="app" ng-controller="mainCtrl">
<script type="text/ng-template" id="dummy.html">
<ul>
<li ng-repeat="d in demo" >
<div >{{d.id}}</div>
<div ng-show="d.dummy.length" ng-include=" 'dummy.html' " onload="demo = d.dummy"></div>
</li>
</ul>
</script>
<div ng-include=" 'dummy.html'" ></div>
</div>
I am having a hard time doing an Angular filter to solve a problem as below.
The filter logic is as below:
1) If all listItem of that item has qtyLeft != 0, do not display that item
2) If any of the listItem of that item has qtyLeft == 0, display the item title as well as coressponding listItem that have qtyLeft == 0
Here's a basic example of my data structure, an array of items:
$scope.jsonList = [
{
_id: '0001',
title: 'titleA',
list: {
listName: 'listNameA',
listItem: [
{
name: 'name1',
qtyLeft: 0
},
{
name: 'name2',
qtyLeft: 0
},
]
}
},
{
_id: '0002',
title: 'titleB',
list: {
listName: 'listNameB',
listItem: [
{
name: 'name3',
qtyLeft: 2
},
{
name: 'name4',
qtyLeft: 0
},
]
}
},
{
_id: '0003',
title: 'titleC',
list: {
listName: 'listNameC',
listItem: [
{
name: 'name5',
qtyLeft: 2
},
{
name: 'name6',
qtyLeft: 2
},
]
}
},
]
Here is the final expected outcome:
<div ng-repeat="item in jsonList | filter: filterLogic">
<div> </div>
</div>
// final outcome
<div>
<div>Title: titleA, ListItem: Name1, Name2</div>
<div>Title: titleB, ListItem: Name4</div>
</div>
Created working Plunkr here. https://plnkr.co/edit/SRMgyRIU7nuaybhX3oUC?p=preview
Do not forget to include underscore.js lib in your project if you are going to use this directive.
<div ng-repeat="jsonItem in jsonList | showZeroElement track by $index">
<div>Title:{{ jsonItem.title}}, ListItem:<span ng-repeat="item in
jsonItem.list.listItem track by $index" ng-if="item.qtyLeft==0">
{{item.name}}</span>
</div>
</div>
And
app.filter('showZeroElement', function() {
return function(input) {
var items = []
angular.forEach(input, function(value, index) {
angular.forEach(value.list.listItem, function(val, i) {
var found = _.findWhere(items, {
'title': value.title
})
if (val.qtyLeft === 0 && found === undefined) {
items.push(value)
}
})
})
return items
}
})