Program description: I'm making a simple timetable program with Vue.js. The page has a "calendar" (a table of 7 columns, where each column correspond to a specific date) and a button on the bottom of each calendar column. On clicking a button a modal should pop up with a specific date in its body-content. The date in the body-content of the modal should be the same as in date in the column title, where the button is placed. Dates can also be changed on click of a forward or backward button. When clicking forward all dates shift one day forward. In case of backward all dates shift one day backward.
Approach to the program: I have a parent component App.vue and a child component for the modal modal.vue. In the parent component as one of the attributes I have array dateArr which contains 7 different dates in the following format: ['9/11/21', '9/12/21', ..., '15/11/21']. On App.vue mount getDatesArray method is being called which fills dateArr attribute with dates. On clicking backward and forward currentOffset is being changed and getDatesArray method is being triggered. The first row of the table consists of dates, therefore I create it with v-for and iterate over each date in dateArr. The body of the table consists of 3 rows in each column. The last row in each column contains a unique button and a modal. In the last row I want to bind each component to a specific date, so I yet again iterate over each date in dateArr. The modal component has a slot in it, where I put unique date from dateArr. On click of the button the visibility of the modal changes with change method.
Problem: Everything works well, however, whatever button I click, the content (date) of the modal component stays the same: 15/11/21, which is the last element of the array. The purpose of the program is to have unique content (date) inside each modal component with the core attribute (dateArr) being dynamic. I've tried implementing different solutions to this problem, but none were of use. I will really appreciate any help!
App.vue:
<template>
<table class = 'dataFrame'>
<thead>
<tr>
<th v-for="item in dayArr" :key="item.id">
{{item}}
</th>
</tr>
</thead>
<tbody>
<tr v-for="index in [1,2,3]" :key="index">
<td v-for="item_index in dayArr.length" :key="item_index">
Dummy content
</td>
</tr>
<tr>
<td v-for="date in dayArr" :key="date.id">
<button #click="change">+</button>
<modal-component #changevisibility="change" v-if="modalToggled">
<p>{{ date }}</p>
</modal-component>
</td>
</tr>
</tbody>
</table>
<div class="buttonSection">
<button #click="currentOffset --" class="back" v-show="currentOffset >= -7">forward</button>
<button #click="currentOffset ++" class="forward" v-show="currentOffset <= 7">backward</button>
</div>
</template>
<script>
import basecard from './components/card.vue'
import navbarComponent from './components/menu.vue'
import modalComponent from './components/modal.vue'
export default {
data() {
return {
currentOffset: 0,
modalToggled : false,
dayArr: []
}
},
methods: {
change() {
this.modalToggled = !this.modalToggled
},
getDatesArray() {
let date = new Date();
date.setDate(date.getDate() + this.currentOffset);
let dayArr = [];
for (let i = 0; i < 7; i++) {
let customDateArray = new Intl.DateTimeFormat('en-GB', { day: 'numeric', month: 'short', year: 'numeric', hour: 'numeric', minute: 'numeric' }).formatToParts(date)
let dateParts = {}
customDateArray.map(({type, value}) => {
dateParts[type] = value
})
dayArr.push(`${dateParts.day}/${date.getMonth()+1}/${dateParts.year.slice(-2)}`)
date.setDate(date.getDate() + 1);
}
this.dayArr = dayArr
}
},
mounted () {
this.getDatesArray()
},
watch: {
currentOffset () {
this.getDatesArray()
}
},
name: 'App',
components: {
modalComponent
}
}
</script>
<style>
#app {
display: flexbox;
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50
}
.dataFrame {
border: 2px solid;
width: 100%;
border-color: #2c3e50;
}
.buttonSection {
margin-top: 1rem;
width: 100%;
max-height: 100%;
}
.back {
float: left;
}
.forward {
float: right;
}
</style>
modal.vue:
<template>
<div class="modal">
<div class="modal-content">
<slot></slot>
<button #click="changeVisibilityFromComponent">
<span class="close">Close ×</span>
</button>
</div>
</div>
</template>
<script>
export default {
name: 'modalComponent',
methods: {
changeVisibilityFromComponent () {
this.$emit('changevisibility')
},
}
}
</script>
<style scoped>
.modal {
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgb(0,0,0);
background-color: rgba(0,0,0,0.4);
}
.modal-content {
background-color: #fefefe;
margin: 15% auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
}
.close:hover,
.close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
</style>
The first thing I thought about is to pass an index to change function, thus calculating some value independent of the v-for loop:
<td v-for="(date, index) in dayArr" :key="date.id">
<!-- pass index to change method -->
<button #click="change(index)" :id="index">+</button>
<modal-component #changevisibility="change(index)" v-if="modalToggled">
<!-- to slot pass the new variable -->
<!-- remember to add it to data() with some init value like null -->
<p>{{ currentDate }}</p>
</modal-component>
</td>
...
methods: {
change(index) {
// set currentDate value each time change method is being run
this.currentDate = this.dayArr[index];
this.modalToggled = !this.modalToggled
},
...
}
The reason for the issue you’ve experienced, is that v-for renders a list of items and sets the variables in a same way the code below does:
let x = 0;
for (i = 0; i < 10; i++) {
x = i;
}
console.log(x); // this will always console out x = 9
In contrary, event handling (like #click) makes a directive that listens to DOM events and runs a method each time the event is triggered. That’s at least how I understand it…
I try to append a couple of buttons with click event when the component is mounted:
data() {
return {
show_word: true,
game: {
//type: "memory, working memory",
type: 1,
name: "Symbols",
start:false,
total_combo:0,
total_correct:0,
total_incorrect:0,
total_correct_in_a_row:0,
max_correct_in_a_row:0,
correct_percent:0,
avg_time_for_ans:0,
button: '<button #click="checkNumbers">click</button>',
score_plus:0,
score_multi:0,
d_timer:600,
finish:false,
positive: null,
negative: null,
numbers_array:[],
abuse: {
clicks:5,
seconds:1,
max:3,
flag: false
}
},
}
},
methods:{
playGame(){
let tilesize = 50, tilecount = 6;
$( '#numbersContainer' ).empty()
let gRows = Math.floor($("#numbersContainer").innerWidth()/tilesize);
let gCols = Math.floor($('#numbersContainer').innerHeight()/tilesize);
let vals = _.shuffle(_.range(tilecount));
let xpos = _.shuffle(_.range(gRows));
let ypos = _.shuffle(_.range(gCols));
console.log(vals);
console.log(xpos);
console.log(ypos);
let $this = this;
_.each(vals, function(d,i){
var $newdiv = $('<button #click="checkNumbers('+(d+1)+')"/>').addClass("tile btn btn-info");
$this.game.numbers_array.push(d+1)
$newdiv.css({
'position':'absolute',
'left':(xpos[i] * tilesize)+'px',
'top':(ypos[i] * tilesize)+'px'
}).appendTo( '#numbersContainer' ).html(d+1);
});
},
checkNumbers($val){
console.log($val)
},
}
This is the html that i getting:
<div id="numbersContainer" class="col-ms-3">
<button #click="checkNumbers(6)" class="tile btn btn-info" style="position: absolute; left: 250px; top: 250px;">6</button>
<button #click="checkNumbers(5)" class="tile btn btn-info" style="position: absolute; left: 150px; top: 150px;">5</button>
<button #click="checkNumbers(1)" class="tile btn btn-info" style="position: absolute; left: 100px; top: 0px;">1</button>
<button #click="checkNumbers(3)" class="tile btn btn-info" style="position: absolute; left: 50px; top: 100px;">3</button>
<button #click="checkNumbers(2)" class="tile btn btn-info" style="position: absolute; left: 200px; top: 200px;">2</button>
<button #click="checkNumbers(4)" class="tile btn btn-info" style="position: absolute; left: 0px; top: 50px;">4</button>
</div>
The problems is that the "click" event is not working. Seems like the "checkNumbers" function is not register, although it registered in my methods.
What is the right way to append html element with register events?
Thank !
Don't manipulate the DOM directly, that's why you're using Vue in the first place. Use a combination of v-for and data() to manipulate state and have Vue populate the DOM.
Adding the #click="" within the direct DOM manipulation is going to cause the element to not be managed by Vue, in other words it won't have the Vue context.
const Game = {
props: ["tilecount"],
data() {
return {
tiles: []
};
},
mounted() {
this.tiles = _.shuffle(_.range(this.tilecount));
},
methods: {
checkNumbers(val) {
console.log(val);
}
}
};
const app = new Vue({
el: "#app",
components: {
game: Game
},
data() {
return {
tilecount: 50
};
}
});
.game .number-container {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
}
.game .number-container button {
flex-basis: 20%;
height: 50px
}
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
<div id="app">
<game :tilecount="tilecount" inline-template>
<div class="game">
<div ref="numberContainer" class="number-container">
<button v-for="tile in tiles" #click="checkNumbers(tile)"> {{tile}}</button>
</div>
</div>
</game>
</div>
I'm trying to render >3 graphs, using Dygraphs in JS.
Using some example codes, I was able to create a dummy for my work, just like this.
The demo works as it should, but here is my scenario:
I am trying to render 3 or more graphs with values from different ranges. I want to zoom in a time peroid on a graph and I want all the other graphs to zoom with it.
Right now, said graph will be zoomed and the others are going to be messed up:
$(document).ready(function() {
var someData = [
"2009/01/01,10,11,12\n" +
"2009/01/02,12,10,11\n" +
"2009/01/03,9,10,13\n" +
"2009/01/04,5,20,15\n" +
"2009/01/05,8,3,12\n",
"2009/01/01,510,511,512\n" +
"2009/01/02,518,510,511\n" +
"2009/01/03,519,510,513\n" +
"2009/01/04,525,520,515\n" +
"2009/01/05,508,513,512\n",
"2009/01/01,0.10,0.11,0.01\n" +
"2009/01/02,0.12,1,0.11\n" +
"2009/01/03,0.09,0.10,0.13\n" +
"2009/01/04,0.05,0.20,0.15\n" +
"2009/01/05,0.08,0.03,0.12\n",
"2009/01/01,110,111,112\n" +
"2009/01/02,112,110,111\n" +
"2009/01/03,109,110,113\n" +
"2009/01/04,105,120,115\n" +
"2009/01/05,108,103,112\n"
];
var graphs = ["x", "foo", "bar", "baz"];
var titles = ['', '', '', ''];
var gs = [];
var blockRedraw = false;
for (var i = 1; i <= 4; i++) {
gs.push(
new Dygraph(
document.getElementById("div" + i),
someData[i - 1], {
labels: graphs,
title: titles[i - 1],
legend: 'always'
}
)
);
}
var sync = Dygraph.synchronize(gs);
function update() {
var zoom = document.getElementById('chk-zoom').checked;
var selection = document.getElementById('chk-selection').checked;
sync.detach();
sync = Dygraph.synchronize(gs, {
zoom: zoom,
selection: selection
});
}
$('#chk-zoom, #chk-selection').change(update);
});
.chart {
width: 500px;
height: 300px;
}
.chart-container {
overflow: hidden;
}
#div1 {
float: left;
}
#div2 {
float: left;
}
#div3 {
float: left;
clear: left;
}
#div4 {
float: left;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="http://dygraphs.com/dygraph.js"></script>
<script src="http://dygraphs.com/src/extras/synchronizer.js"></script>
<p>Zooming and panning on any of the charts will zoom and pan all the others. Selecting points on one will select points on the others.</p>
<p>To use this, source <code>extras/synchronizer.js</code> on your page. See the comments in that file for usage.</p>
<div class="chart-container">
<div id="div1" class="chart"></div>
<div id="div2" class="chart"></div>
<div id="div3" class="chart"></div>
<div id="div4" class="chart"></div>
</div>
<p>
Synchronize what?
<input id="chk-zoom" checked="" type="checkbox">
<label for="chk-zoom">Zoom</label>
<input id="chk-selection" checked="" type="checkbox">
<label for="chk-selection">Selection</label>
</p>
For me it looks like that, the other graphs want to show the same value range for the selected time peroid. If this is the case, then do I just need to redraw the other graphs somehow?
Below, I have modified your code (only 2 lines) and now I think it is working as you need.
Inside the synchronize options, I have added the option "range" set to false. With this option, the y-axis is not synchronized, which is what you need.
The other thing I´ve done, is to force a call to update() after the graphs synchronization. In the way you had the code, the update was not called until a checkbox was modified, so at the first, the graphs synchronization was not working.
I hope this could help you and sorry for not answering before ;)
$(document).ready(function() {
var someData = [
"2009/01/01,10,11,12\n" +
"2009/01/02,12,10,11\n" +
"2009/01/03,9,10,13\n" +
"2009/01/04,5,20,15\n" +
"2009/01/05,8,3,12\n",
"2009/01/01,510,511,512\n" +
"2009/01/02,518,510,511\n" +
"2009/01/03,519,510,513\n" +
"2009/01/04,525,520,515\n" +
"2009/01/05,508,513,512\n",
"2009/01/01,0.10,0.11,0.01\n" +
"2009/01/02,0.12,1,0.11\n" +
"2009/01/03,0.09,0.10,0.13\n" +
"2009/01/04,0.05,0.20,0.15\n" +
"2009/01/05,0.08,0.03,0.12\n",
"2009/01/01,110,111,112\n" +
"2009/01/02,112,110,111\n" +
"2009/01/03,109,110,113\n" +
"2009/01/04,105,120,115\n" +
"2009/01/05,108,103,112\n"
];
var graphs = ["x", "foo", "bar", "baz"];
var titles = ['', '', '', ''];
var gs = [];
var blockRedraw = false;
for (var i = 1; i <= 4; i++) {
gs.push(
new Dygraph(
document.getElementById("div" + i),
someData[i - 1], {
labels: graphs,
title: titles[i - 1],
legend: 'always'
}
)
);
}
var sync = Dygraph.synchronize(gs);
update();
function update() {
var zoom = document.getElementById('chk-zoom').checked;
var selection = document.getElementById('chk-selection').checked;
sync.detach();
sync = Dygraph.synchronize(gs, {
zoom: zoom,
range: false,
selection: selection
});
}
$('#chk-zoom, #chk-selection').change(update);
});
.chart {
width: 500px;
height: 300px;
}
.chart-container {
overflow: hidden;
}
#div1 {
float: left;
}
#div2 {
float: left;
}
#div3 {
float: left;
clear: left;
}
#div4 {
float: left;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="http://dygraphs.com/dygraph.js"></script>
<script src="http://dygraphs.com/src/extras/synchronizer.js"></script>
<p>Zooming and panning on any of the charts will zoom and pan all the others. Selecting points on one will select points on the others.</p>
<p>To use this, source <code>extras/synchronizer.js</code> on your page. See the comments in that file for usage.</p>
<div class="chart-container">
<div id="div1" class="chart"></div>
<div id="div2" class="chart"></div>
<div id="div3" class="chart"></div>
<div id="div4" class="chart"></div>
</div>
<p>
Synchronize what?
<input id="chk-zoom" checked="" type="checkbox">
<label for="chk-zoom">Zoom</label>
<input id="chk-selection" checked="" type="checkbox">
<label for="chk-selection">Selection</label>
</p>
I receive some datas with ajax, and I would like to display it as google line charts. I don't know how many chart I have to display, so I have made it dynamic using a javascript forEach to call my function drawchart();
Here I receive the datas :
$j.ajax({
type: "GET",
url: "../"+folder_destination+"slimrest/getValues",
data: "",
success: function(msg){
msg.forEach(function(entry) {
drawchart(entry['id'], entry['data']);
});
}
});
Here is my function which is supposed to add a div with a unique ID then draw the chart inside :
function drawchart(id_rec){
$j("#charts_cartes").append("<div id=\"gchart_"+id_rec+"\"></div>");
var data = new google.visualization.DataTable();
data.addColumn('number', 'max');
data.addColumn('number', 'min');
data.addColumn('number', 'Mesures');
//Will be dynamic as soon as I'll be able to print more than one chart
data.addRows([
[1, 37.8, 80.8],
[2, 30.9, 69.5]
]);
var options = {
width: 500,
height: 200
};
var chart = new google.charts.Line(document.getElementById('gchart_'+id_rec));
chart.draw(data, options);
}
I see my divs in the html code, but the second chart is never displayed.
It seems that the <svg></svg> balise is empty for one of the charts :
<div id="charts_cartes">
<div id="gchart_39">
<div style="position: relative; width: 500px; height: 200px;">
<div
style="position: absolute; left: 0px; top: 0px; width: 100%; height: 100%;">
<svg width="500" height="200">
<defs><!-- [chart code well displayed but too long to write here] --></defs>
</svg>
</div>
</div>
</div>
<div id="gchart_40">
<div style="position: relative; width: 500px; height: 200px;">
<div
style="position: absolute; left: 0px; top: 0px; width: 100%; height: 100%;">
<svg width="500" height="200">
<defs></defs></svg>
</div>
</div>
</div>
</div>
Problem fixed adding a setTimeout to the draw function of the googlechart :
setTimeout(function(){ chart.draw(data, options); }, 1000);
There must be some conflicts if drawing to much charts almost simultaneously ...
The solution above works, but setTimeout with just 1 millisecond works, so that if you're dynamically generating the charts with some display logic, it won't be loading for 1000 milliseconds.
So this also worked for me in an AngularJS app.
setTimeout(function(){ chart.draw(data, options); }, 1);
I am developping a metro style application for Windows 8 using HTML5/CSS/JS
I'm trying to display items with different size in a grouped List View. (like all metro style apps do...) I found on the internet that I need to put a GridLayout to my list View and implement the groupInfo. It succeeds in modifying the first item (the picture inside the first item is bigger than the other items), but then all the items have the size of the first item. I would like something like this instead :
Here is what I have done :
updateLayout: function (element, viewState, lastViewState) {
//I get my listView winControl defined in HTML
var listView = element.querySelector(".blocksList").winControl;
var globalList = new WinJS.Binding.List(globalArray);
var groupDataSource = globalList.createGrouped(this.groupKeySelector,
this.groupDataSelector, this.groupCompare);
// I set the options of my listView (the source for the items and the groups + the grid Layout )
ui.setOptions(listView, {
itemDataSource: groupDataSource.dataSource,
groupDataSource: groupDataSource.groups.dataSource,
layout: new ui.GridLayout({
groupHeaderPosition: "top",
groupInfo: function () {
return {
multiSize: true,
slotWidth: Math.round(480),
slotHeight: Math.round(680)
};
}
}),
itemTemplate: this.itemRenderer,
});
},
// je définis le template pour mes items
itemRenderer: function (itemPromise) {
return itemPromise.then(function (currentItem, recycled) {
var template = document.querySelector(".itemTemplate").winControl.renderItem(itemPromise, recycled);
template.renderComplete = template.renderComplete.then(function (elem) {
//if it is the first item I put it widder
if (currentItem.data.index == 0) {
// elem.querySelector(".item-container").style.width = (480) + "px";
elem.style.width = (2*480) + "px";
}
});
return template.element;
})
},
the html part is :
<section aria-label="Main content" role="main">
<div class="blocksList" aria-label="List of blocks" data-win-control="WinJS.UI.ListView"
data-win-options="{itemTemplate:select('.itemTemplate'), groupHeaderTemplate:select('.headerTemplate')
, selectionMode:'none', swipeBehavior:'none', tapBehavior:'invoke'}">
</div>
</section>
<!--Templates-->
<div class="headerTemplate" data-win-control="WinJS.Binding.Template">
<div class="header-title" data-win-bind="innerText: categorieName"/>
</div>
<div class="itemTemplate" data-win-control="WinJS.Binding.Template">
<div class="item-container" >
<div class="item-image-container">
<img class="item-image" data-win-bind="src: urlImage" src="#" />
</div>
<div class="item-overlay">
<h4 class="item-title" data-win-bind="textContent: title"></h4>
</div>
</div>
</div>
et le css
.newHomePage p {
margin-left: 120px;
}
.newHomePage .blocksList {
-ms-grid-row: 2;
}
.newHomePage .blocksList .win-horizontal.win-viewport .win-surface {
margin-left: 120px;
margin-bottom: 60px;
}
.newHomePage .blocksList .item-container {
height: 340px;
width: 240px;
color: white;
background-color: red;
-ms-grid-columns: 1fr;
-ms-grid-rows: 1fr;
display: -ms-grid;
}
.newHomePage .blocksList .win-item {
/*-ms-grid-columns: 1fr;
-ms-grid-rows: 1fr 30px;
display: -ms-grid;*/
height: 130px;
width: 240px;
background: white;
outline: rgba(0, 0, 0, 0.8) solid 2px;
}
Thank you for your help.
In Release Preview (and also RTM), the property names in the groupInfo function changed:
multiSize -> enableCellSpanning
slotWidth -> cellWidth
slotHeight -> cellHeight
Try that and see if you get better results. If you don't announce cell spanning in this way, then the GridLayout takes the first item's size as that for all the items.
Chapter 5 of my ebook from Microsoft Press (RTM preview coming soon) will cover all the details of this.
.Kraig