Vue.js list not updating when data changes - vue.js

i'm trying re-organised a list of data. I have given each li a unique key, but still, no luck!
I have had this working before exactly like below, think i'm cracking up!
let app = new Vue({
el: '#app',
data: {
list: [
{ value: 'item 1', id: '43234r' },
{ value: 'item 2', id: '32rsdf' },
{ value: 'item 3', id: 'fdsfsdf' },
{ value: 'item 4', id: 'sdfg543' }
]
},
methods: {
randomise: function() {
let input = this.list;
for (let i = input.length-1; i >=0; i--) {
let randomIndex = Math.floor(Math.random()*(i+1));
let itemAtIndex = input[randomIndex];
input[randomIndex] = input[i];
input[i] = itemAtIndex;
}
this.list = input;
}
}
});
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<ul>
<li v-for="item in list" :key="item.id">{{ item.value }}</li>
</ul>
Randomize
</div>
Edit:
Thanks for the answers, to be honest the example I provided may not have been the best for my actual issue I was trying to solve. I think I may have found the cause of my issue.
I'm basically using a similar logic as above, except i'm moving an array of objects around based on drag and drop, this works fine with normal HTML.
However, i'm using my drag and drop component somewhere else, which contains ANOTHER component and this is where things seem to fall apart...
Would having a component within another component stop Vue from re-rendering when an item is moved within it's data?
Below is my DraggableBase component, which I extend from:
<script>
export default {
data: function() {
return {
dragStartClass: 'drag-start',
dragEnterClass: 'drag-enter',
activeIndex: null
}
},
methods: {
setClass: function(dragStatus) {
switch (dragStatus) {
case 0:
return null;
case 1:
return this.dragStartClass;
case 2:
return this.dragEnterClass;
case 3:
return this.dragStartClass + ' ' + this.dragEnterClass;
}
},
onDragStart: function(event, index) {
event.stopPropagation();
this.activeIndex = index;
this.data.data[index].dragCurrent = true;
this.data.data[index].dragStatus = 3;
},
onDragLeave: function(event, index) {
this.data.data[index].counter--;
if (this.data.data[index].counter !== 0) return;
if (this.data.data[index].dragStatus === 3) {
this.data.data[index].dragStatus = 1;
return;
}
this.data.data[index].dragStatus = 0;
},
onDragEnter: function(event, index) {
this.data.data[index].counter++;
if (this.data.data[index].dragCurrent) {
this.data.data[index].dragStatus = 3;
return;
}
this.data.data[index].dragStatus = 2;
},
onDragOver: function(event, index) {
if (event.preventDefault) {
event.preventDefault();
}
event.dataTransfer.dropEffect = 'move';
return false;
},
onDragEnd: function(event, index) {
this.data.data[index].dragStatus = 0;
this.data.data[index].dragCurrent = false;
},
onDrop: function(event, index) {
if (event.stopPropagation) {
event.stopPropagation();
}
if (this.activeIndex !== index) {
this.data.data = this.array_move(this.data.data, this.activeIndex, index);
}
for (let index in this.data.data) {
if (!this.data.data.hasOwnProperty(index)) continue;
this.data.data[index].dragStatus = 0;
this.data.data[index].counter = 0;
this.data.data[index].dragCurrent = false;
}
return false;
},
array_move: function(arr, old_index, new_index) {
if (new_index >= arr.length) {
let k = new_index - arr.length + 1;
while (k--) {
arr.push(undefined);
}
}
arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
return arr; // for testing
}
}
}
</script>
Edit 2
Figured it out! Using the loop index worked fine before, however this doesn't appear to be the case this time!
I changed the v-bind:key to use the database ID and this solved the issue!

There are some Caveats with arrays
Due to limitations in JavaScript, Vue cannot detect the following changes to an array:
When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue
When you modify the length of the array, e.g. vm.items.length = newLength
To overcome caveat 1, both of the following will accomplish the same as vm.items[indexOfItem] = newValue, but will also trigger state updates in the reactivity system:
Vue.set(vm.items, indexOfItem, newValue)
Or in your case
randomise: function() {
let input = this.list;
for (let i = input.length-1; i >=0; i--) {
let randomIndex = Math.floor(Math.random()*(i+1));
let itemAtIndex = input[randomIndex];
Vue.set(input, randomIndex, input[i]);
Vue.set(input, i, itemAtIndex);
}
this.list = input;
}

Here is an working example: Randomize items fiddle
Basically I changed the logic of your randomize function to this:
randomize() {
let new_list = []
const old_list = [...this.list] //we don't need to copy, but just to be sure for any future update
while (new_list.length < 4) {
const new_item = old_list[this.get_random_number()]
const exists = new_list.findIndex(item => item.id === new_item.id)
if (!~exists) { //if the new item does not exists in the new randomize list add it
new_list.push(new_item)
}
}
this.list = new_list //update the old list with the new one
},
get_random_number() { //returns a random number from 0 to 3
return Math.floor(Math.random() * 4)
}

randomise: function() { let input = this.list;
for (let i = input.length-1; i >=0; i--) {
let randomIndex = Math.floor(Math.random()*(i+1));
let itemAtIndex = this.list[randomIndex];
Vue.set(this.list,randomIndex,this.list[i])
this.list[randomIndex] = this.list[i];
this.list[i] = itemAtIndex;
} this.list = input;
}
Array change detection is a bit tricky in Vue. Most of the in place
array methods are working as expected (i.e. doing a splice in your
$data.names array would work), but assigining values directly (i.e.
$data.names[0] = 'Joe') would not update the reactively rendered
components. Depending on how you process the server side results you
might need to think about these options described in the in vue
documentation: Array Change Detection.
Some ideas to explore:
using the v-bind:key="some_id" to have better using the push to add
new elements using Vue.set(example1.items, indexOfItem, newValue)
(also mentioned by Artokun)
Source
Note that it works but im busy so i cant optimize it, but its a little bit too complicted, i Edit it further tomorrow.

Since Vue.js has some caveats detecting array modification as other answers to this question highlight, you can just make a shallow copy of array before randomazing it:
randomise: function() {
// make shallow copy
let input = this.list.map(function(item) {
return item;
});
for (let i = input.length-1; i >=0; i--) {
let randomIndex = Math.floor(Math.random()*(i+1));
let itemAtIndex = input[randomIndex];
input[randomIndex] = input[i];
input[i] = itemAtIndex;
}
this.list = input;
}

Related

Update v-html without misbehaving focus on typing VUE JS

I need help,
Requirement
when the user types in an input box I want to highlight the link with blue color if any
My Research
when I dig into it, I realize that without using a contenteditable div it's not possible to do, also there is no v-model associated with contenteditable div I am manually updating the state.
so far I have this, courtesy- contenteditable div append a html element and v-model it in Vuejs
<div id="app"><div class="flex">
<div class="message" #input="updateHtml" v-html="html" contenteditable="true"></div>
<br>
<div class="message">{{ html }}</div>
</div>
</div>
<script>
let app = new Vue({
el: '#app',
data: {
html: 'some text',
},
methods: {
updateHtml: function(e) {
this.html = e.target.innerHTML;
},
renderHtml: function(){
this.html += '<img src="https://cdn-images-1.medium.com/max/853/1*FH12a2fX61aHOn39pff9vA.jpeg" alt="" width=200px>';
}
}
});</script>
Issue
every time user types something, the focus is misbehaving which is strange to me, I want v-html to update along with user types #keyup,#keydown also have the same behavior.it works ok on #blur #focusout events, but that's not what I want
Appreciate Help.Thanks
I figured it out myself. Posting the answer so that may help other developers. v-HTML doesn't do all the trick. You’ll need to store the cursor position so it can be restored properly each time the content updates as well as parse the content so that it renders as expected. Here is the example
HTML
<p>
An example of live syntax highlighting in a content-editable element. The hard part is storing and restoring selection after changing the DOM to account for highlighting.
<p>
<div contentEditable='true' id='editor'>
Edit text here. Try some words like bold and red
</div>
<p>
Just a demo trivial syntax highlighter, should work with any syntax highlighting you want to implement.
</p>
JS
const editor = document.getElementById('editor');
const selectionOutput = document.getElementById('selection');
function getTextSegments(element) {
const textSegments = [];
Array.from(element.childNodes).forEach((node) => {
switch(node.nodeType) {
case Node.TEXT_NODE:
textSegments.push({text: node.nodeValue, node});
break;
case Node.ELEMENT_NODE:
textSegments.splice(textSegments.length, 0, ...(getTextSegments(node)));
break;
default:
throw new Error(`Unexpected node type: ${node.nodeType}`);
}
});
return textSegments;
}
editor.addEventListener('input', updateEditor);
function updateEditor() {
const sel = window.getSelection();
const textSegments = getTextSegments(editor);
const textContent = textSegments.map(({text}) => text).join('');
let anchorIndex = null;
let focusIndex = null;
let currentIndex = 0;
textSegments.forEach(({text, node}) => {
if (node === sel.anchorNode) {
anchorIndex = currentIndex + sel.anchorOffset;
}
if (node === sel.focusNode) {
focusIndex = currentIndex + sel.focusOffset;
}
currentIndex += text.length;
});
editor.innerHTML = renderText(textContent);
restoreSelection(anchorIndex, focusIndex);
}
function restoreSelection(absoluteAnchorIndex, absoluteFocusIndex) {
const sel = window.getSelection();
const textSegments = getTextSegments(editor);
let anchorNode = editor;
let anchorIndex = 0;
let focusNode = editor;
let focusIndex = 0;
let currentIndex = 0;
textSegments.forEach(({text, node}) => {
const startIndexOfNode = currentIndex;
const endIndexOfNode = startIndexOfNode + text.length;
if (startIndexOfNode <= absoluteAnchorIndex && absoluteAnchorIndex <= endIndexOfNode) {
anchorNode = node;
anchorIndex = absoluteAnchorIndex - startIndexOfNode;
}
if (startIndexOfNode <= absoluteFocusIndex && absoluteFocusIndex <= endIndexOfNode) {
focusNode = node;
focusIndex = absoluteFocusIndex - startIndexOfNode;
}
currentIndex += text.length;
});
sel.setBaseAndExtent(anchorNode,anchorIndex,focusNode,focusIndex);
}
function renderText(text) {
const words = text.split(/(\s+)/);
const output = words.map((word) => {
if (word === 'bold') {
return `<strong>${word}</strong>`;
}
else if (word === 'red') {
return `<span style='color:red'>${word}</span>`;
}
else {
return word;
}
})
return output.join('');
}
updateEditor();
Hope this helps...

Highcharts with decrease order in each interval

Is it possible to create a graph sorted in each time interval using Highcharts?
For expample, in this picture for January data will be in order: New York, Tokyo, London, Berlin. The same for each months - data should be shown decrease order
Highcharts doesn't have a built-in function to do that, but for example you can use the render event and organize columns, by changing their positions in the way you need.
events: {
render: function() {
var series = this.series,
longestSeries = series[0],
sortedPoints = [],
selectedPoints = [];
// find a series with the highest amount of points
series.forEach(function(s) {
if (s.points.length > longestSeries.points.length) {
longestSeries = s;
}
});
longestSeries.points.forEach(function(point) {
series.forEach(function(s) {
selectedPoints.push(s.points[point.index]);
});
sortedPoints = selectedPoints.slice().sort(function(a, b) {
return b.y - a.y;
});
selectedPoints.forEach(function(selectedPoint) {
if (
selectedPoints.indexOf(selectedPoint) !==
sortedPoints.indexOf(selectedPoint) &&
selectedPoint.graphic
) {
// change column position
selectedPoint.graphic.attr({
x: sortedPoints[selectedPoints.indexOf(selectedPoint)].shapeArgs.x
});
}
});
sortedPoints.length = 0;
selectedPoints.length = 0;
});
}
}
Live demo: https://jsfiddle.net/BlackLabel/tnrch8v1/
API Reference:
https://api.highcharts.com/highcharts/chart.events.render
https://api.highcharts.com/class-reference/Highcharts.SVGElement#attr
#ppotaczek Thank You for help a lot! I solve my issue, but i had to make some changes to your code:
events: {
render: function() {
if (this.series.length === 0) return
var series = this.series,
longestSeries = series[0],
sortedPoints = [],
selectedPoints = [];
// find a series with the highest amount of points
series.forEach(function(s) {
if (s.points.length > longestSeries.points.length) {
longestSeries = s;
}
});
longestSeries.points.forEach(function(point) {
series.forEach(function(s) {
selectedPoints.push(s.points[point.index]);
});
sortedPoints = selectedPoints.slice().sort(function(a, b) {
return b.y - a.y;
});
selectedPoints.forEach(function(selectedPoint) {
if (
selectedPoints.indexOf(selectedPoint) !==
sortedPoints.indexOf(selectedPoint) &&
selectedPoint.graphic
) {
// change column position
selectedPoint.graphic.attr({
x: selectedPoints[sortedPoints.indexOf(selectedPoint)].shapeArgs.x
});
}
});
sortedPoints.length = 0;
selectedPoints.length = 0;
});
}
},
}
So i changed:
// change column position
selectedPoint.graphic.attr({
x: sortedPoints[selectedPoints.indexOf(selectedPoint)].shapeArgs.x
});
to:
// change column position
selectedPoint.graphic.attr({
x: selectedPoints[sortedPoints.indexOf(selectedPoint)].shapeArgs.x
});

React Native: how can I achieve the dynamic keys with multiple objects

Here is my code I tried,
var array=[];
var list = this.state.list;
var getList = function(i){
var add = +i + 1;
return {
["value"+add]:{
Description:list[i].Description,
Length:list[i].Length,
Height:list[i].Height,
Weight:list[i].Weight,
VolumeWeight:list[i].VolumeWeight,
ActualWeight:list[i].ActualWeight,
}
}
}.bind(this)
for(var i in list){
array.push(getList(i));
}
var dataArray = array.map(function(e){
return JSON.stringify(e);
});
dataString = dataArray.join(",");
data1 = {
ConsigneeBranchName:this.state.searchText,
ConsigneeBranchCode:this.state.code,
ConsigneeBranchFullAddress:this.state.DAddress,
SenderBranchCode:this.state.code1,
SenderBranchName:this.state.searchTexts,
SenderBranchFullAddress:this.state.Address,
CreatedByEmployeeCode:id,
CreatedByEmployeeFullName:userName,
jsonString:{
JsonValues:{
id:"MyID",
values:dataString
}
}
}
But I want the result is exactly this
var result = {
"ConsigneeBranchName":"",
"ConsigneeBranchCode":"",
"ConsigneeBranchFullAddress":"",
"SenderBranchCode":"",
"SenderBranchName":"",
"SenderBranchFullAddress":"",
"CreatedByEmployeeCode":"",
"CreatedByEmployeeFullName":"",
"jsonString":"{
"JsonValues": {
"id": "MyID",
"values": {
"value1":{
"Description”:"testSmarter1",
"Length”:"60",
"Height”:"50",
"Weight”:"70",
"VolumeWeight”:"75",
"ActualWeight”:”78"
},
"value2:{
"Description":"Documents",
"Length":"120",
"Height":"68",
"Weight":"75",
"VolumeWeight":"122.4",
"ActualWeight":"123"
},
}
}
}
};
Please any one help me
I want the object with dynamic keys within a single object {key1:{des:1,value:as},key2:{des:2,value:aw},key3:{des:3,value:au}}
can you please help me I have tried so many times
see this below image I want this part, inside the single object, I can join multiple objects with dynamic keys
lodash already has a function called keyBy, you can use it to get this functionality. If adding lodash doesn't make sense in your project.
I have implemented a vanilla JS version.
function keyBy(array, mapperFn) {
const resultObj = {};
array.map(item => resultObj[mapperFn(item)] = item);
return resultObj;
}
function arrayToObject (array, keyName = 'id') {
return keyBy(array, function(element) {return element[keyName]});
}
API:
arrayToObject(targetArray, stringNameOfThePorpertyYouWantToUseAsKey);
USAGE:
const listOfUsers = [{name: 'Jenitha', reputation: 6}, {name: 'Chandan', reputation: 3}];
const mapOfUsersByName = arrayToObject(listOfUsers, 'name');

using vue.set in vuex is not updating the state

I'm trying to add an object into a nested array, and it is not working, but I've used this for other states and it works fine.
Has it got something to do with it begin a nested array?
this is the code I'm using
Vue.set(state.sections[getCurrentSection(state).index].rows[getCurrentRow(state).index].columns[getCurrentColumn(state).index].elements, 0, element)
this is the element object
var element = {
id: id,
style: {
backgroundColor: {
value: 'rgba(255,255,255,1)',
},
},
}
what am I doing wrong?
UPDATE
Another option to clone:
function hasProp(arg1, arg2) {
return Object.prototype.hasOwnProperty.call(arg1, arg2);
}
function extend(arg1, arg2) {
const keys = Object.keys(arg2);
const len = keys.length;
let i = 0;
while (i < len) {
arg1[keys[i]] = arg2[keys[i]];
i += 1;
}
if (hasProp(arg2, 'toString')) {
arg1.toString = arg2.toString;
}
if (hasProp(arg2, 'valueOf')) {
arg1.valueOf = arg2.valueOf;
}
return arg1;
}
const obj1 = {
a: 1,
b: 2,
c: { a: 1, b: 2}
};
const cloned = extend({}, obj1);
cloned.a = 9999;
console.log(obj1, cloned);
Make a deep copy of your state.sections;
Create a new object and make your modifications on it;
Replace state.sections with you new object - state.sections = Object.assign({}, newObject).
Your state and your view updated.

Custom data source with WinJS?

I am currently implementing a custom data source in a Windows8 application. However, I got some trouble with it: no data is displayed.
First, here is the code:
var dataArray = [
{ title: "Basic banana", text: "Low-fat frozen yogurt", picture: "images/60banana.png" },
// Other data taken from Windows8 ListView quick start
{ title: "Succulent strawberry", text: "Sorbet", picture: "images/60strawberry.png" }
];
var searchAdDataAdapter = WinJS.Class.define(
function () {}, // Constructor
{
itemsFromIndex: function (requestIndex, countBefore, countAfter) {
var that = this;
if (requestIndex >= that._maxCount) {
return WinJS.Promise.wrapError(new WinJS.ErrorFromName(UI.FetchError.doesNotExist));
}
var fetchSize, fetchIndex;
// See which side of the requestIndex is the overlap.
if (countBefore > countAfter) {
// Limit the overlap
countAfter = Math.min(countAfter, 10);
// Bound the request size based on the minimum and maximum sizes.
var fetchBefore = Math.max(
Math.min(countBefore, that._maxPageSize - (countAfter + 1)),
that._minPageSize - (countAfter + 1)
);
fetchSize = fetchBefore + countAfter + 1;
fetchIndex = requestIndex - fetchBefore;
} else {
countBefore = Math.min(countBefore, 10);
var fetchAfter = Math.max(Math.min(countAfter, that._maxPageSize - (countBefore + 1)), that._minPageSize - (countBefore + 1));
fetchSize = countBefore + fetchAfter + 1;
fetchIndex = requestIndex - countBefore;
}
// Create an array of IItem objects:
// results =[{ key: key1, data : { field1: value, field2: value, ... }}, { key: key2, data : {...}}, ...];
for (var i = 0, itemsLength = dataArray.length ; i < itemsLength ; i++) {
var dataItem = dataArray[i];
results.push({
key: (fetchIndex + i).toString(),
data: dataArray[i]
});
}
// Get the count.
count = dataArray.length;
return {
items: results, // The array of items.
offset: requestIndex - fetchIndex, // The index of the requested item in the items array.
totalCount: count
};
},
getCount: function () {
return dataArray.length;
}
}
);
var searchAdDataSource = WinJS.Class.derive(WinJS.UI.VirtualizedDataSource, function () {
this._baseDataSourceConstructor(new searchAdDataAdapter());
});
// Create a namespace to make the data publicly
// accessible.
var publicMembers = {
itemList: new searchAdDataSource()
};
WinJS.Namespace.define("DataExample", publicMembers);
I know the code is a little bit long, but the major part of it is taken from official Microsoft custom data source quick start.
I tried to debug it, but it seems the code contained in itemFromIndex is never used (my breakpoint is never reached).
The HTML code is:
<div id="basicListView" data-win-control="WinJS.UI.ListView"
data-win-options="{itemDataSource : DataExample.itemList.dataSource}">
</div>
I do not use any template for the moment, to simplify the code as more as I can. Data are normally displayed in text this way (but nothing appears).
Have one of this great community any idea?
Furthermore, I do not understand the countBefore and countAfter parameters, even with the documentation. Can somebody explain it to me with other words?
Thanks a lot! :)
Try modifying your HTML code to the following:
<div id="basicListView" data-win-control="WinJS.UI.ListView"
data-win-options="{itemDataSource : DataExample.itemList}">
</div>
No need to call the .datasource member, as you are talking to the datasource directly.