I attempt to create a simple Gantt chart (Table):
I use computed property newTasks() to imitate the getting asynchronously a list of tasks from a Vuex Store but my solution doesn’t work.
Could you help me please.
Not working solution with computed property: https://jsfiddle.net/AlexPilugin/98b0pw24/
new Vue({
el: "#app",
data: {
longMode: false,
columns: 7,
tasks: null
},
watch: {
newTasks(val) {
this.tasks = val;
this.$forceUpdate();
}
},
created() {
this.$nextTick(() => {
this.tasks = this.newTasks;
this.$forceUpdate();
})
},
computed: {
newTasks() {
let tasks = null;
console.log("newTasks-------------");
const self = this;
setTimeout(() => {
if (self.longMode) {
tasks = [{
start: 0,
end: 3
},
{
start: 2,
end: 9
},
{
start: 7,
end: 13
}
];
} else {
tasks = [{
start: 0,
end: 4
},
{
start: 2,
end: 6
}
];
}
console.log("return new tasks:");
console.log(tasks);
return tasks;
}, 2000);
}
},
methods: {
toggle() {
this.longMode = !this.longMode;
this.columns = this.longMode ? 14 : 7;
this.tasks = this.newTasks;
console.log("clicked! longMode: " + this.longMode);
},
getLineStyle(index) {
let rowIndex = 2;
console.log("getLineStyle(" + rowIndex + ")");
console.log(this.$refs);
if (this.tasks && this.tasks.length > 0) {
console.log(this.tasks);
let task = this.tasks[rowIndex];
console.log(task);
let start = task.start;
let end = task.end;
let startRef = 'cell' + start;
let endRef = 'cell' + end;
let startLeft = this.$refs[startRef][0].offsetLeft;
let endLeft = this.$refs[endRef][0].offsetLeft;
let taskWidth = (endLeft - startLeft) + 'px';
let styleObject = {
left: startLeft,
width: taskWidth
};
return styleObject;
}
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
table {
border-collapse: collapse;
table-layout: auto;
/*fixed;*/
border: 1px solid #012656;
}
.cell {
padding: 5px;
border: 1px solid #012656;
cursor: pointer;
}
td.cell:nth-child(7n-1),
td.cell:nth-child(7n) {
background: #eee;
}
.line {
position: absolute;
top: 4px;
height: 6px;
border-radius: 3px;
background: red;
overflow: hidden;
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<table class="gantt-table">
<tr #click="toggle">
<td v-for="(cell, colIndex) in columns" :ref="'cell'+colIndex" :key="'col-'+colIndex" class="cell">
{{ colIndex+1 }}
</td>
</tr>
<tr v-show="tasks && tasks.length > 0" v-for="(row, rowIndex) in tasks" :key="'tr-'+rowIndex">
<div style="position: relative;">
<div class="line" :style="getLineStyle(rowIndex)"></div>
</div>
<td v-for="(cell, colIndex) in columns" :key="'row-'+rowIndex+'col-'+colIndex" class="cell">
<div>  </div>
</td>
</tr>
</table>
</div>
Another approach (with method): https://jsfiddle.net/AlexPilugin/qxbd4gcw/
https://jsfiddle.net/AlexPilugin/98b0pw24/3/ - Script is not drawing extra rows based on number of tasks (tasks.length)
and rowIndex in getLineStyle(rowIndex) in undefined! Why?!!
Edit
With Promise
Why watch doesn't work?.
Why both solutions don't work:
<tr v-for="(row, rowIndex) in newTasks" ...>
<tr v-for="(row, rowIndex) in tasks" ..>
Vue.config.productionTip = false;
Vue.config.devtools=false;
new Vue({
el: "#app",
data: {
longMode: false,
columns: 7,
tasks: []
},
created() {
this.tasks = this.newTasks;
},
watch: {
newTasks(tasks) {
console.log("watch: newTasks()-------------");
console.log(tasks);
this.tasks = tasks;
this.$forceUpdate();
}
},
computed: {
newTasks() {
let tasks = null;
if(this.longMode) {
tasks = [
{start: 0, end: 3},
{start: 2, end: 9},
{start: 7, end: 13}
];
} else {
tasks = [
{start: 0, end: 4},
{start: 2, end: 6}
];
}
this.delay(2000).then(() => {
console.log("return new tasks:");
console.log(tasks);
return tasks;
})
}
},
methods: {
toggle() {
this.longMode = !this.longMode;
this.columns = this.longMode ? 14 : 7;
this.tasks = this.newTasks;
console.log("clicked! longMode: " + this.longMode);
},
delay(t) {
return new Promise(function(resolve) {
setTimeout(resolve, t)
});
},
getLineStyle(index) {
let rowIndex = 2;
console.log("getLineStyle("+rowIndex+")");
console.log(this.$refs);
if(this.tasks && this.tasks.length>0) {
console.log(this.tasks);
let task = this.tasks[rowIndex];
console.log(task);
let start = task.start;
let end = task.end;
let startRef = 'cell'+start;
let endRef = 'cell'+end;
let startLeft = this.$refs[startRef][0].offsetLeft;
let endLeft = this.$refs[endRef][0].offsetLeft;
let taskWidth = (endLeft - startLeft) + 'px';
let styleObject = {left: startLeft, width: taskWidth};
return styleObject;
}
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
table {
border-collapse: collapse;
table-layout: auto;
/*fixed;*/
border: 1px solid #012656;
}
.cell {
padding: 5px;
border: 1px solid #012656;
cursor: pointer;
}
td.cell:nth-child(7n-1),
td.cell:nth-child(7n) {
background: #eee;
}
.line {
position: absolute;
top: 4px;
height: 6px;
border-radius: 3px;
background: red;
overflow: hidden;
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<table class="gantt-table">
<tr #click="toggle">
<td
v-for="(cell, colIndex) in columns"
:ref="'cell'+colIndex"
:key="'col-'+colIndex"
class="cell"
>
{{ colIndex+1 }}
</td>
</tr>
<tr
v-show="newTasks && newTasks.length > 0"
v-for="(row, rowIndex) in newTasks"
:key="'tr-'+rowIndex"
>
<!--
<div style="position: relative;">
<div class="line" :style="getLineStyle(rowIndex)"></div>
</div>
-->
<td
v-for="(cell, colIndex) in columns"
:key="'row-'+rowIndex+'col-'+colIndex"
class="cell"
>
<div>  </div>
</td>
</tr>
</table>
</div>
Related
I am currently trying to add a feature where when I click the name of a user in the table it will display all of the posts made by that User however I am completely stumped. The url for the posts is https://jsonplaceholder.typicode.com/posts
I currently have the table set up and will attach images of the table and code below.
Here is the problem I am trying to solve for further context
Using jQuery or vanilla JS you will display each USER in a table. When the user selects a USER in the table, it will display all of the 'POSTS' that were created by that USER.
Make use of event delegation together with Element.closest and the data-* attribute respectively its HTMLElement.dataset counterpart.
document
.querySelector('tbody')
.addEventListener('click', ({ target }) => {
const currentRow = target.closest('tr');
const userId = currentRow.dataset.id;
console.clear();
console.log('userId: ', userId);
});
body {
margin: 0 0 0 2px;
}
table {
border-collapse: collapse;
}
thead th {
color: #fff;
font-weight: bolder;
background-color: #009577;
}
th, td {
padding: 4px 10px 6px 10px;
color: #0a0a0a;
}
tbody tr:nth-child(even) {
background-color: #eee;
}
tbody tr:hover {
outline: 2px dotted orange;
}
tbody tr {
cursor: pointer;
}
tbody tr:nth-child(even) { background-color: #eee; }
tbody tr:hover { outline: 2px dotted orange; }
<table>
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Username</th>
<th>Email</th>
</tr>
</thead>
<tbody>
<tr data-id='1'>
<td>1</td>
<td>Leanne Graham</td>
<td>Bret</td>
<td>Sincere#april.biz</td>
</tr>
<tr data-id='2'>
<td>2</td>
<td>Ervin Howell</td>
<td>Antonette</td>
<td>Shanna#melissa.tv</td>
</tr>
<tr data-id='3'>
<td>3</td>
<td>Clementine Bauch</td>
<td>Samantha</td>
<td>Nathan#yesenia.net</td>
</tr>
<tr data-id='4'>
<td>4</td>
<td>Patricia Lebsack</td>
<td>Karianne</td>
<td>Julianne.OConner#kory.org</td>
</tr>
<tr data-id='5'>
<td>5</td>
<td>Chelsey Dietrich</td>
<td>Kamren</td>
<td>Lucio_Hettinger#annie.ca</td>
</tr>
</tbody>
</table>
A vanilla based implementation of a component-like approach which is partially configurable via data-* attributes then possibly might look like the next provided example code ...
function emptyElementNode(node) {
[...node.childNodes].forEach(child => child.remove());
}
function clearTableContent(root) {
[
...root.querySelectorAll('thead'),
...root.querySelectorAll('tbody'),
].forEach(child => child.remove());
}
function createTableHead(headerContentList) {
const elmThead = document.createElement('thead');
const elmTr = headerContentList
.reduce((root, content) => {
const elmTh = document.createElement('th');
elmTh.textContent = content;
root.appendChild(elmTh);
return root;
}, document.createElement('tr'));
elmThead.appendChild(elmTr);
return elmThead;
}
function createTableBody(rowContentKeyList, userList) {
return userList
.reduce((elmTbody, userItem) => {
const elmTr = rowContentKeyList
.reduce((root, key) => {
const elmTd = document.createElement('td');
elmTd.textContent = userItem[key];
root.appendChild(elmTd);
return root;
}, document.createElement('tr'));
elmTr.dataset.id = userItem.id;
elmTbody.appendChild(elmTr);
return elmTbody;
}, document.createElement('tbody'));
}
function createAndRenderPostItem(root, { title, body }) {
const elmDt = document.createElement('dt');
const elmDd = document.createElement('dd');
elmDt.textContent = title;
elmDd.textContent = body;
root.appendChild(elmDt);
root.appendChild(elmDd);
return root;
}
function updateSelectedStates(selectedRow) {
[...selectedRow.parentNode.children]
.forEach(rowNode =>
rowNode.classList.remove('selected')
);
selectedRow.classList.add('selected');
}
function handleUserPostsRequestFromBoundData({ target }) {
const { postsRoot, requestUrl, placeholder } = this;
const currentRow = target.closest('tr');
const userId = currentRow?.dataset?.id;
if (userId) {
createListOfUserPosts({
postsRoot,
url: requestUrl.replace(placeholder, userId)
});
updateSelectedStates(currentRow);
}
}
async function createListOfUserPosts({ postsRoot, url }) {
emptyElementNode(postsRoot);
if (postsRoot && url) {
const response = await fetch(url);
const postList = await response.json();
postList.reduce(createAndRenderPostItem, postsRoot);
}
}
async function createListOfUsers({ usersRoot, postsRoot }) {
const usersRequestUrl = usersRoot.dataset.request;
const userPostsRequestUrl = postsRoot.dataset.request;
const userPostsPlaceholder = postsRoot.dataset.placeholder;
const response = await fetch(usersRequestUrl);
const userList = await response.json();
if (userList.length >= 1) {
const displayConfig = JSON.parse(
usersRoot.dataset.display ?? '{}'
);
const headerContentList = Object.values(displayConfig);
const rowContentKeyList = Object.keys(displayConfig);
emptyElementNode(postsRoot);
clearTableContent(usersRoot);
usersRoot.appendChild(
createTableHead(headerContentList)
);
usersRoot.appendChild(
createTableBody(rowContentKeyList, userList)
);
usersRoot.addEventListener(
'click',
handleUserPostsRequestFromBoundData
.bind({
postsRoot,
requestUrl: userPostsRequestUrl,
placeholder: userPostsPlaceholder,
})
);
}
}
function initializeUserPostsComponent(root) {
const usersRoot = root.querySelector('[data-users]');
const postsRoot = root.querySelector('[data-posts]');
createListOfUsers({ usersRoot, postsRoot });
}
document
.querySelectorAll('[data-user-posts]')
.forEach(initializeUserPostsComponent);
body { margin: 0 0 0 2px; font-size: .8em; }
table { border-collapse: collapse; }
table caption { text-align: left; padding: 3px; }
thead th { color: #fff; font-weight: bolder; background-color: #009577; }
th, td { padding: 3px 5px; color: #0a0a0a; }
tbody tr:nth-child(even) { background-color: #eee; }
tbody tr:hover { outline: 2px dotted orange; }
tbody tr.selected { background-color: rgb(255 204 0 / 18%); }
tbody tr { cursor: pointer; }
[data-user-posts]::after { clear: both; display: inline-block; content: ''; }
.user-overview { float: left; width: 63%; }
.user-posts { float: right; width: 36%; }
.user-posts h3 { margin: 0; padding: 4px 8px; font-size: inherit; font-weight: normal; }
<article data-user-posts>
<table
data-users
data-request="https://jsonplaceholder.typicode.com/users"
data-display='{"id":"Id","name":"Name","username":"Username","email":"Email"}'
class="user-overview"
>
<caption>Select a user to view posts</caption>
</table>
<div class="user-posts">
<h3>Posts:</h3>
<dl
data-posts
data-request="https://jsonplaceholder.typicode.com/users/:userId/posts"
data-placeholder=":userId"
>
</dl>
</div>
<article>
To add a click event handler to a table row with JavaScript, we can loop through each row and set the onclick property of each row to an event handler.
For instance, we add a table by writing:
<table>
<tbody>
<tr>
<td>foo</td>
<td>bar</td>
</tr>
<tr>
<td>baz</td>
<td>qux</td>
</tr>
</tbody>
</table>
Then we write:
const createClickHandler = (row) => {
return () => {
const [cell] = row.getElementsByTagName("td");
const id = cell.innerHTML;
console.log(id);
};
};
const table = document.querySelector("table");
for (const currentRow of table.rows) {
currentRow.onclick = createClickHandler(currentRow);
}
to add event handlers to each row.
Reference : https://thewebdev.info/2021/09/12/how-to-add-an-click-event-handler-to-a-table-row-with-javascript/#:~:text=Row%20with%20JavaScript-,To%20add%20an%20click%20event%20handler%20to%20a%20table%20row,row%20to%20an%20event%20handler.&text=to%20add%20event%20handlers%20toe,table%20row%20as%20a%20parameter.
new Vue({
el: "#app",
data: {
getQuestionAnswers: [
{
name: 'foo',
checked: false,
status: 'ok'
},
{
name: 'bar',
checked: false,
status: 'notok'
},
{
name: 'baz',
checked: false,
status: 'medium'
},
{
name: 'oo',
checked: false,
status: 'medium'
}
]
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
width:100%
}
.red {
color: red;
}
.bcom {
width: 100%;
display: flex;
}
.container1 {
width: 50px;
}
.container2 {
width: calc(100% - 105px);
padding: 8px 0;
height: 30px;
box-sizing: border-box;
}
.h-line {
height: 1px;
margin-bottom: 18px;
width: 100%;
background-color: black;
}
.container3{
margin-left: 5px;
width: 50px;
}
.point:hover {
width: 200px;
}
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="app">
<div class="bcom"
v-for="(group, index) in getQuestionAnswers"
:key="index + group.name"
:group="group"
>
<div>
<input type="checkbox" v-model="group.checked"/>
{{ group.name }}
</div>
<div class="container2">
<div class="h-line" v-if="group.checked"></div>
</div>
<div>
<input type="checkbox"/>
{{ group.status }}
</div>
</div>
</div>
Onclick of checkbox, how to add multiple lines from one point in Vuejs?
As seen in the image, On click of the checkbox, Based on the status, I need to match from one point to three multiple status. like "ok, notok, medium"
i have taken v-model in the checkbox,to check and perfome two way data binding But not sure....what to do further. Do I need to take computed property and write condition to check and draw three multiple lines???
there are som positioning issues here, but this sample should be enough for you to get it working:
template
<div id="demo" :ref="'plane'">
<canvas :ref="'canvas'"></canvas>
<div
class="bcom"
v-for="(group, index) in getQuestionAnswers"
:key="index + group.name"
:group="group"
>
<div>
<input
type="checkbox"
v-on:click="() => onToggleCheckbox(group)"
v-model="group.checked"
:ref="'checkbox_' + group.name"
/>
<span>{{ group.name }}</span>
</div>
<div>
<span>{{ group.status }}</span>
<input type="checkbox" :ref="'status_' + group.name" />
</div>
</div>
</div>
script:
export default {
name: 'App',
data: () => ({
ctx: undefined,
draw(begin, end, stroke = 'black', width = 1) {
if (!this.ctx) {
const canvas = this.$refs['canvas'];
if (!canvas?.getContext) return;
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
this.ctx = canvas.getContext('2d');
}
if (stroke) {
this.ctx.strokeStyle = stroke;
}
if (width) {
this.ctx.lineWidth = width;
}
this.ctx.beginPath();
this.ctx.moveTo(...begin);
this.ctx.lineTo(...end);
this.ctx.stroke();
},
onToggleCheckbox(group) {
const planeEl = this.$refs['plane'];
const planeRect = planeEl.getBoundingClientRect();
const fromEl = this.$refs['checkbox_' + group.name];
const fromRect = fromEl.getBoundingClientRect();
const from = {
x: fromRect.right - planeRect.left,
y: fromRect.top + fromRect.height / 2 - planeRect.top,
};
const toEl = this.$refs['status_' + group.name];
const toRect = toEl.getBoundingClientRect();
const to = {
x: toRect.left - planeRect.left,
y: toRect.top + toRect.height / 2 - planeRect.top,
};
console.log(planeRect, from, to);
this.draw(
Object.values(from),
Object.values(to),
group.checked ? 'white' : 'black',
group.checked ? 3 : 2
);
},
getQuestionAnswers: [
{
name: 'foo',
checked: false,
status: 'ok',
},
{
name: 'bar',
checked: false,
status: 'notok',
},
{
name: 'baz',
checked: false,
status: 'medium',
},
{
name: 'oo',
checked: false,
status: 'medium',
},
],
}),
};
style
body {
background: #20262e;
padding: 20px;
font-family: Helvetica;
}
#demo {
position: relative;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
canvas {
position: absolute;
background: red;
width: 100%;
height: 100%;
left: 0;
top: 0;
background: #fff;
z-index: -1;
}
.bcom {
width: 100%;
display: flex;
justify-content: space-between;
z-index: 2;
}
this only draws one line but you could easily add the others. I figured you might change your data schema to something like:
getQuestions() {
{
name: string,
checked: boolean,
statuses: [string...],
},
getStatuses() {
{
name: string
}
but not knowing about your requirements here, I decided to post the above before making further changes. (here is the sort of refactor I was referring to: https://stackblitz.com/edit/vue-yuvsxa )
addressing first comment:
in app.vue only there is one data called[((questions))], inside question we are looping and setting the status.
this is easy to address with a bit of preprocessing:
questionsAndStatusesMixed: // such as [{...question, ...statuses}],
questions: [],
statuses: [],
mounted() {
const statusesSet = new Set()
this.questionsAndStatusesMixed.forEach(item => {
const question = {
name: item.name,
checked: item.checked,
answer: item.status // is this the answer or .. these never made sense to me,
statuses: this.statuses // assuming each question should admit all statuses/that is, draw a line to each
}
const status = {
name: item.name
}
this.questions.push(question)
statusesSet.add(status)
})
Array.from(statusesSet).forEach(item => this.statuses.push(item))
}
$(function () {
var people = [];
var ctCells = [], questionCells = [], userCells = [];
var $tBody = $("#userdata tbody");
$.getJSON('https://api.myjson.com/bins/18g7fm', function (data) {
$.each(data.ct_info, function (i, f) {
ctCells.push(`<td id=${f.id}>${f.id}</td><td>${f.name}</td>`);
var users = []
var question = []
f.Qid_info.forEach((x) => {
x.user_info.forEach((y) => {
//avoid duplicates
var foundUser = users.find((user) => {
return user.id === y.id
})
if (!foundUser) {
users.push(y)
}
})
})
f.Qid_info.forEach((x) => {
var foundQuestion = question.find((questions) => {
return questions.id === x.id
})
if (!foundQuestion) {
question.push(x)
}
})
$.each(question, function (i, question) {
ctCells.push(`<td colspan="2"> </td>`)
questionCells.push(`<td id=${question.id}>${question.id}</td><td>${question.isActive}</td><td>${question["is complex"]}</td><td>${question["is breakdown"]}</td>`);
})
$.each(users, function (i, user) {
var a = user.data.map(function (key) {
return key
})
// ctCells.push(`<td colspan="2"> </td>`)
// questionCells.push(`<td colspan="${lent+1}"> </td>`)
userCells.push(`<td><div style="display:flex; flex-direction:row">
${
users.forEach(val => {
`<div style="display:flex; flex-direction:column">
<div>${val.id}${val.name}</div>
<div>${val.updatedAt}</div>
<div style="display:flex; flex-direction:column">
${user.data.forEach(IN => {
`
<div style="display:flex; flex-direction:row">
<div><p>${console.log(IN.git_ids)}</p></div>
</div>
`
})}
</div>
</div>`
})
}
</div></td>`)
// userCells.push(`<td id=${user.id}>UserId--- ${user.id} UserName---- ${user.name}${a.map(value => {
// return `
// <div id="${user.id}" >
// <td><input type="checkbox" style="display: inline;"> </td>
// <td> <span id="text">${Object.keys(value)[0]}</span></td>
// <td> <textarea type="text" class="gitplc" placeholder="GitId">${ value.git_ids}</textarea> </td> j
// </div>
// `
// })
// }</td><td>${user.updatedAt}</td>`);
})
});
console.log(userCells)
$.each(ctCells, function (i) {
console.log(userCells)
$tBody.append(`<tr>${ctCells[i]}${questionCells[i]}${userCells[i]}</tr>`)
})
});
});
#scrlSec {
overflow-x: scroll;
overflow-y: hidden;
white-space: nowrap;
}
/* .checkSec { width: 60%; } */
.checkSec .tbl {
padding-top: 20px;
}
.checkSec td {
padding-left: 10px;
}
.checkSec .btnGreen {
color: white;
background-color: #4CAF50;
border: none;
padding: 15px 32px;
width: 100%;
text-decoration: none;
}
.checkSec .gitplc {
width: 80%;
}
.checkSec #text {
font-size: 14px;
}
.checkSec .tbl .colOne {
width: 50%;
float: left;
}
.checkSec .tbl .colTwo {
width: 50%;
float: right;
}
#user {
overflow: auto;
}
.flex-container {
display: flex;
}
th,
td {
font-weight: normal;
padding: 5px;
text-align: center;
width: 120px;
vertical-align: top;
}
th {
background: #00B0F0;
}
tr+tr th,
tbody th {
background: #DAEEF3;
}
tr+tr,
tbody {
text-align: left
}
table,
th,
td {
border: solid 1px;
border-collapse: collapse;
table-layout: fixed;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table id='userdata'>
<thead>
<tr>
<th colspan="2" id="ct">CT INFO</th>
<th colspan="4" id="que">Question</th>
<th id="user">User Info</th>
</tr>
<tr>
<th>CT ID</th>
<th>CT</th>
<th>Id</th>
<th>isActive</th>
<th>is Complex</th>
<th>is Breakdown</th>
<th>USER</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
I am printing json data in table format in html. But in users column i am getting undefined but when i print those variable in console that is printing correct value.
This is my json api . When i print value in foreach loop of user i am getting undefined but on console.log i have proper value printing in console.
https://api.myjson.com/bins/18g7fm
This is happening because you are using forEach() inside a template literal. forEach() returns undefined by default. Use map() function instead.
userCells.push(`<td><div style="display:flex; flex-direction:row">
${
users.map(val => {
return `<div style="display:flex; flex-direction:column">
<div>${val.id}${val.name}</div>
<div>${val.updatedAt}</div>
<div style="display:flex; flex-direction:column">
${user.data.map(IN => {
return `
<div style="display:flex; flex-direction:row">
<div><p>${console.log(IN.git_ids)}</p></div>
</div>
`
})}
</div>
</div>`
})
}
</div></td>`)
I am new to Vue.js and I am stuck. I have pulled in API data from Github jobs via Vanilla JavaScript and now I want to create a user filter. I am starting out with a filter for job "type" meaning Full time, part time, etc. Also, this code in being put into a plugin in WordPress called Shortcoder. I have watched tutorials and looked at three other posts on Stackoverflow that are similar but cannot figure out why I am getting this error: "unexpected SyntaxError:Unexpected token: on the line that states selectedType="Full Time". Maybe I have just stared at it for too long and need a fresh set eyes. I would really appreciate someone else reviewing it. Thanks in advance. Here is my code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- VUE.JS CDN -->
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.22/dist/vue.js"></script>
<title>Github Jobs API</title>
<style>
* {
box-sizing: border-box
}
html {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-family: 'Dosis', sans-serif;
line-height: 1.6;
color: #696969;
background: white;
}
#root {
max-width: 1200px;
margin: 0 auto;
}
h1 {
text-align: center;
padding: 1.5rem 2.5rem;
background-color: #b0bac6;
margin: 0 0 2rem 0;
font-size: 1.5rem;
color: #696969;
}
p {
padding: 0 2.5rem 2.5rem;
margin: 0;
}
.container1{
display: flex;
flex-wrap: wrap;
}
.card {
margin: 1rem;
background: #bobac6;
box-shadow: 2px 4px 25px rgba(0, 0, 0, .1);
border-radius: 12px;
overflow: hidden;
transition: all .2s linear;
}
.card:hover {
box-shadow: 2px 8px 45px rgba(0, 0, 0, .15);
transform: translate3D(0, -2px, 0);
}
#media screen and (min-width: 600px) {
.card {
flex: 1 1 calc(50% - 2rem);
}
}
#media screen and (min-width: 900px) {
.card {
flex: 1 1 calc(33% - 2rem);
}
}
.card:nth-child(2n) h1 {
background-color: #b0bac6;
color: #696969;
}
.card:nth-child(4n) h1 {
background-color: #b0bac6;
color: #696969;
}
.card:nth-child(5n) h1 {
background-color #b0bac6;
color: #696969;
}
.container2 {
padding: 20px;
width: 90%;
max-width: 400px;
margin: 0 auto;
}
label {
display: block;
line-height: 1.5em;
}
ul {
margin-left: 0;
padding-left: 0;
list-style: none;
}
li {
padding: 8px 16px;
border-bottom: 1px solid #eee;
}
</style>
</head>
<body>
<!-- DIV for Vue.js filter -->
<div class="container2" id="jobs">
<div class="filter">
<label><input type="radio" v-model="selectedType" value="Full Time"/>Full Time</label>
<label><input type="radio" v-model="selectedType" value="Part Time"/>Part Time</label>
</div>
<ul class="job-list">
<li v-for="job in filteredJobs">{{ job.title }}</li>
</ul>
</div>
<!-- DIV FOR API DATA CARD DISPLAY -->
<div id="root"></div>
<script>
// USED STRICT MODE TO SOLVE: “UNCAUGHT SYNTAX ERROR: UNEXPECTED TOKEN
U IN JSON # POSITION 0”
'use strict';
// INITIATING APIDATA AS GLOBAL VARIABLE
var apiData= " ";
const app = document.getElementById('root');
const container1 = document.createElement('div');
container1.setAttribute('class', 'container1');
app.appendChild(container1);
var request = new XMLHttpRequest();
//GET REQUEST WITH USE OF HEROKU AS A PROXY TO SOLVE CORS ERROR
request.open('GET','https://cors-anywhere.herokuapp.com/https://jobs.github.com/positions.json?&markdown=true&page=1',
true);
request.onload = function () {
//CONVERT JSON DATA TO JAVASCRIPT OBJECTS USING JSON.PARSE
var apiData = JSON.parse(this.response);
if (request.status >= 200 && request.status < 400) {
apiData.forEach(job => {
const card = document.createElement('div');
card.setAttribute('class', 'card');
const h1 = document.createElement('h1');
h1.textContent = job.title;
const p = document.createElement('p');
job.description = job.description.substring(0, 300);
p.textContent = `${job.description}...`;
container1.appendChild(card);
card.appendChild(h1);
card.appendChild(p);
});
// ERROR HANDLING
} else {
const errorMessage = document.createElement('marquee');
errorMessage.textContent = `It's not working!`;
app.appendChild(errorMessage);
}
}
request.send();
// APPLY FILTER TO API DATA USING VUE.JS
var vm = new Vue({
el: "#jobs",
data() {
return {
apiData:[],
search: ' ',
filterJobs: [ ]},
selectedType:"Full Time"
},
computed: {
filteredJobs: function() {
vm = this;
var category = vm.selectedType;
if(type === "part time") {
return vm.jobs;
} else {
return vm.jobs.filter(function(job) {
return job.type === type;
});
}
}
}
});
</script>
</body>
</html>
The syntax errors are in data():
data() {
return {
apiData: [],
search: ' ',
filterJobs: []}, // <-- FIXME: unexpected '}'
selectedType: "Full Time"
// <-- FIXME: missing '}' for the return statement
},
computed: {
...
Here's the code corrected:
data() {
return {
apiData: [],
search: ' ',
filterJobs: [], // <-- fixed
selectedType: "Full Time"
} // <-- fixed
},
computed: {
...
var vm = new Vue({
el: "#jobs",
data() {
return {
apiData: [],
search: " ",
jobs: [],
selectedType: "full time"
};
},
computed: {
filteredJobs: function() {
vm = this;
var type = vm.selectedType;
if (type === "part time") {
return vm.jobs;
} else {
return vm.jobs.filter(function(job) {
return job.type === type;
});
}
}
},
mounted() {
this.jobs = [
{id: 1, type: 'part time'},
{id: 2, type: 'full time'},
{id: 3, type: 'part time'},
{id: 4, type: 'full time'},
{id: 5, type: 'part time'},
]
}
});
<script src="https://unpkg.com/vue#2.6.1/dist/vue.min.js"></script>
<div id="jobs">
<ul>
<li v-for="job in filteredJobs" :key="job.id">
type: {{job.type}}
</li>
</ul>
</div>
I have a Vue Calendar that I'm working on. When a day is clicked, I would like to open a full-width box under the week of the selected day that displays details of the day (Think Google Images layout) I know how to pass data in Vue, but how do I add this details (component, view?) under the row of the current week? (See my CodePen
<div class="calendar">
<div class="header z-depth-2">
<a #click="lastMonth" class="waves-effect waves-light btn"><i class="material-icons left">chevron_left</i>Last Month</a>
<p>{{month}} {{year}}</p>
<a #click="nextMonth" class="waves-effect waves-light btn"><i class="material-icons right">chevron_right</i>Next Month</a>
</div>
<ul class="dates month">
<li class="dow" v-for="dow in days">{{dow}}</li>
<li v-for="blank in firstDayOfMonth" class="day"></li>
<li v-for="date in daysInMonth" #click="openday(date)"
class="day" :class="{'today': date == initialDate && month == initialMonth && year == initialYear}">
<span>{{date}}</span>
</li>
</ul>
</div>
JS
new Vue({
el: '.calendar',
data: {
today: moment(),
dateContext: moment(),
days: ['S', 'M', 'T', 'W', 'T', 'F', 'S']
},
methods:{
nextMonth: function () {
var t = this;
t.dateContext = moment(t.dateContext).add(1, 'month');
},
lastMonth: function () {
var t = this;
t.dateContext = moment(t.dateContext).subtract(1, 'month');
}
},
computed: {
year: function () {
var t = this;
return t.dateContext.format('YYYY');
},
month: function () {
var t = this;
return t.dateContext.format('MMMM');
},
daysInMonth: function () {
var t = this;
return t.dateContext.daysInMonth();
},
currentDate: function () {
var t = this;
return t.dateContext.get('date');
},
firstDayOfMonth: function () {
var t = this;
var firstDay = moment(t.dateContext).subtract((t.currentDate - 1), 'days');
return firstDay.weekday();
},
//Previous Code Above
initialDate: function () {
var t = this;
return t.today.get('date');
},
initialMonth: function () {
var t = this;
return t.today.format('MMMM');
},
initialYear: function () {
var t = this;
return t.today.format('YYYY');
}
}
})
Because you have no concept of a row (you just auto-wrap), you can't really insert a box that takes up a row. What I've done here is insert a div that is positioned absolutely, so that I can make it full width, but it overlays the rows below, rather than shifting them out of the way.
If you rework it to have weeks, you could insert a detail box between weeks.
The basic working, though, is that you set a data item for the currently selected date when you click on a date, and you clear that item to dismiss the detail box (a click in the detail box does it, here). The HTML is controlled by a v-if.
new Vue({
el: '.calendar',
data: {
today: moment(),
dateContext: moment(),
days: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
selectedDate: null
},
methods: {
nextMonth: function() {
var t = this;
t.dateContext = moment(t.dateContext).add(1, 'month');
},
lastMonth: function() {
var t = this;
t.dateContext = moment(t.dateContext).subtract(1, 'month');
},
openday(date) {
this.selectedDate = date;
},
dismiss() {
this.selectedDate = null;
}
},
computed: {
year: function() {
var t = this;
return t.dateContext.format('YYYY');
},
month: function() {
var t = this;
return t.dateContext.format('MMMM');
},
daysInMonth: function() {
var t = this;
return t.dateContext.daysInMonth();
},
currentDate: function() {
var t = this;
return t.dateContext.get('date');
},
firstDayOfMonth: function() {
var t = this;
var firstDay = moment(t.dateContext).subtract((t.currentDate - 1), 'days');
return firstDay.weekday();
},
//Previous Code Above
initialDate: function() {
var t = this;
return t.today.get('date');
},
initialMonth: function() {
var t = this;
return t.today.format('MMMM');
},
initialYear: function() {
var t = this;
return t.today.format('YYYY');
}
}
})
*,
*:before,
*:after {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
body {
font-size: 1.5em;
font-weight: 100;
color: rgba(255, 255, 255, 1);
background: #222222;
}
.calendar {
transform: translate3d(0, 0, 0);
width: 100vw;
}
.header {
display: flex;
padding: 0 1em;
justify-content: space-between;
align-items: center;
width: 100%;
background-color: #333;
}
.fade-enter {
/*overflow: hidden;*/
opacity: 0;
}
.fade-enter-active {
animation: fadeIn 1s ease-out;
opacity: 1;
}
.month.in.next {
animation: moveFromTopFadeMonth .4s ease-out;
opacity: 1;
}
.month.out.next {
animation: moveToTopFadeMonth .4s ease-in;
opacity: 1;
}
.month.in.prev {
animation: moveFromBottomFadeMonth .4s ease-out;
opacity: 1;
}
.month.out.prev {
animation: moveToBottomFadeMonth .4s ease-in;
opacity: 1;
}
.dates {
display: flex;
flex-wrap: wrap;
}
.day {
width: 14%;
padding: 1em;
text-align: center;
cursor: pointer;
}
.dow {
width: 14%;
text-align: center;
padding: 1em;
color: teal;
font-weight: bold;
}
.detail-panel {
width: 100vw;
background-color: darkred;
color: white;
height: 10rem;
position: absolute;
left: 0;
}
.today {
color: teal;
font-weight: bold;
}
.day-name {
font-size: 1em;
text-transform: uppercase;
margin-bottom: 5px;
color: rgba(255, 255, 255, .5);
letter-spacing: .7px;
}
.day-number {
font-size: 24px;
letter-spacing: 1.5px;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.5.1/moment.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.min.js"></script>
<link href="//cdnjs.cloudflare.com/ajax/libs/materialize/0.98.2/css/materialize.min.css" rel="stylesheet" />
<div class="calendar">
<div class="header z-depth-2">
<a #click="lastMonth" class="waves-effect waves-light btn"><i class="material-icons left">chevron_left</i>Last Month</a>
<p>{{month}} {{year}}</p>
<a #click="nextMonth" class="waves-effect waves-light btn"><i class="material-icons right">chevron_right</i>Next Month</a>
</div>
<ul class="dates month">
<li class="dow" v-for="dow in days">{{dow}}</li>
<li v-for="blank in firstDayOfMonth" class="day"></li>
<li v-for="date in daysInMonth" class="day" :class="{'today': date == initialDate && month == initialMonth && year == initialYear}">
<span #click="openday(date)">{{date}}</span>
<div class="detail-panel" v-if="selectedDate === date" #click="dismiss">Hi there I see you selected {{date}}</div>
</li>
</ul>
</div>