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;
}
I'm using vue-2.4 and element-ui 1.4.1.
Situation
I have a basic input which is linked with v-model to a computed property. When blur I check if the value input is greater or lower than min and max and I do what I have to do ... Nothing fancy here.
Problem
The value displayed in the input does not always equal enteredValue
Steps to reproduce
1) Input 60 --> Value displayed is the max so 50 and enteredValue is 50 (which is ok)
2) Click outside
3) Input 80 --> Value displayed is 80 and enteredValue is 50
Questions
How can I fix that so the value displayed is always the same as the enteredValue ?
Here is the minimal code to reproduce what I'm facing JSFIDDLE
<div id="app">
The variable enteredValue is {{enteredValue}}
<el-input v-model="measurementValueDisplay" #blur="formatInput($event)"></el-input>
</div>
var Main = {
data() {
return {
enteredValue: '',
max: 50,
min: 10
}
},
computed: {
measurementValueDisplay: {
get: function () {
return this.enteredValue + ' inchs'
},
set: function (newValue) {
}
},
},
methods: {
formatInput($event) {
let inputValue = $event.currentTarget.value;
if (inputValue > this.max) { this.enteredValue = this.max}
else if (inputValue < this.min) { this.enteredValue = this.min}
else this.enteredValue = inputValue
}
}
}
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')
Reading this vuejs, will understand what happens
"computed properties are cached based on their dependencies. A computed property will only re-evaluate when some of its dependencies have changed."
Changed some comportament of the code. Made run:
computed() method not works properly for update value in window. But if looks at console the value yes updated.
So, i remove computed (getter and setter), and put into data, without setter and getter( i dont like this in javascript).
var Main = {
data() {
return {
measurementValueDisplay:'fff',
enteredValue: '',
max: 50,
min: 10
}
},
computed: {
/*measurementValueDisplay: {
get: function () {
console.log('Computed was triggered so I assume enteredValue changed',this.enteredValue);
return this.enteredValue + ' inchs'
},
set: function (newValue) {
console.log('setter de qye', this.enteredValue);
}
},*/
},
methods: {
formatInput($event) {
this.enteredValue = 0;
let inputValue = $event.currentTarget.value;
console.log(inputValue);
if (inputValue > this.max) { this.enteredValue = this.max}
else if (inputValue < this.min) { this.enteredValue = this.min}
else this.enteredValue = inputValue
this.measurementValueDisplay = this.enteredValue + ' inchs'
console.log(this.enteredValue, 'oioioioio0');
}
}
}
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')
Your problem is that the values used in the computed property was not updated with the validation capping at 50 (Was 50, is now updated to 50, no need to recalculate), therefore v-model did not update the input.
I've edited your jsfiddle to use two computed properties:
One with an accessor to validate the entered value, one which returns the value with " inch" appended.
Here is the interesting part:
computed: {
measurementValueDisplay: {
get: function () {
return this.enteredValue
},
set: function (newValue) {
this.enteredValue = 0;
let inputValue = parseInt(newValue);
if(Number.isNaN(inputValue)){this.enteredValue = this.min}
else if (inputValue > this.max) { this.enteredValue = this.max}
else if (inputValue < this.min) { this.enteredValue = this.min}
else this.enteredValue = inputValue
}
},
valueWithInch(){
return this.enteredValue + " inch";
}
},
In case anybody still needs a hack for this one, you can use a value that will always change ( for example a timestamp )
var Main = {
data() {
return {
enteredValue: '',
max: 50,
min: 10,
now: 1 //line added
}
},
computed: {
measurementValueDisplay: {
get: function () {
return (this.now - this.now + 1 ) * this.enteredValue + ' inchs'; //line changed
},
set: function (newValue) {
this.now = Date.now(); //line added
}
},
},
methods: {
formatInput($event) {
let inputValue = $event.currentTarget.value;
if (inputValue > this.max) { this.enteredValue = this.max}
else if (inputValue < this.min) { this.enteredValue = this.min}
else this.enteredValue = inputValue
}
}
}
I'm new to Dojo and I'm making my first widget to be used in EPiServer CMS.
The widget is supposed to dynamically add contacts when clicking on a button. This means that the only control my templateString has is a button. When clicking on it I call a function that creates all of the other controls that will contain the contact infomation, such as name, email etc. This is working fine, I use domConstruct.create without a problem.
Thing is, when I want to create the existing contacts based on the value that comes from the server I can't manage to use domConstruct.create, it just returns "undefined". I'm making the call in the postCreate event which happens after the DOM is ready. (I've tested this as well). Does anyone have a clue what could be wrong? Like I said creating the controls when clicking on the Add button works like a charm.
The function that throws the error is:
postCreate: function () {
// call base implementation
this.inherited(arguments);
//this._loadContacts(this.value);
var node = domConstruct.create("fieldset", { id: "test" }, "cccp"); //this simple create instruction returns undefined here.
//Bind button
this.connect(this.btnAdd, "onclick", dojo.partial(this._createContact, new Object()));
},
Exactly on the line that does the domConstruct.create. Below is the entire code for the widget:
define([
"dojo/_base/array",
"dojo/_base/connect",
"dojo/_base/declare",
"dojo/_base/lang",
"dojo/dom-construct",
"dojo/on",
"dijit/_CssStateMixin",
"dijit/_Widget",
"dijit/_TemplatedMixin",
"dijit/_WidgetsInTemplateMixin",
"epi/epi",
"epi/shell/widget/_ValueRequiredMixin"
],
function (
array,
connect,
declare,
lang,
domConstruct,
on,
_CssStateMixin,
_Widget,
_TemplatedMixin,
_WidgetsInTemplateMixin,
epi,
_ValueRequiredMixin
) {
var amountContacts = 0;
var contactContainerPrefixName = "contactInfo";
return declare("meridian.editors.StringList", [_Widget, _TemplatedMixin, _WidgetsInTemplateMixin, _CssStateMixin, _ValueRequiredMixin], {
templateString: "<div class=\"dijitInline\">\
<form id=\"cccp\" action=\"\">\
</form>\
<button id=\"btnAdd\" data-dojo-attach-point=\"btnAdd\" type=\"button\" class=\"\">Add Contact</button><br/> \
<span>${helptext}</span>\
</div>",
baseClass: "epiStringList",
helptext: "Place items on separate lines",
intermediateChanges: false,
value: null,
multiple: true,
onChange: function (value) { },
_onChange: function (value) {
this.onChange(value);
},
postCreate: function () {
// call base implementation
this.inherited(arguments);
//this._loadContacts(this.value);
var node = domConstruct.create("fieldset", { id: "test" }, "cccp"); //this simple create instruction returns undefined here.
//Bind button
this.connect(this.btnAdd, "onclick", dojo.partial(this._createContact, new Object()));
},
isValid: function () {
// summary:
// Check if widget's value is valid.
// tags:
// protected, override
return !this.required || lang.isArray(this.value) && this.value.length > 0 && this.value.join() != "";
},
_loadContacts:function(data)
{
for (var i = 0; i < data.length; i++) {
this._createContact(data[i]);
}
},
_createContact:function(data)
{
//increase the number of contacts
amountContacts++;
var contactInfoContainer = contactContainerPrefixName+amountContacts;
//Create container for contact data
var contactInfo = domConstruct.create("fieldset", { id: contactInfoContainer }, "cccp");
//Create container for PROPERTIES
var contactInfoProperties = domConstruct.create("div", {class: "properties"}, contactInfoContainer);
//Name
this._createTextbox("name", data.fullName, "Name", contactInfoProperties, false, false);
/*//Email
this._createTextbox("email", "Email", contactInfoProperties, false);
//Phone 1
this._createTextbox("phoneOne", "Phone 1", contactInfoProperties, true);
//Phone 2
this._createTextbox("phoneTwo", "Phone 2", contactInfoProperties, true);
//Fax
this._createTextbox("fax", "Fax", contactInfoProperties, false);
//Organization
this._createTextbox("organization", "Organization", contactInfoProperties, false);
//Department
this._createTextbox("department", "Department/Unit", contactInfoProperties, false);
//Role
this._createTextbox("role", "Role/Job title", contactInfoProperties, false);*/
//Website
this._createTextbox("website", data.website, "Website", contactInfoProperties, false, false);
//Address
//this._createTextbox("address", "Address", contactInfoProperties, false);
//TLP
this._createTLP_DDL("tlp", "TLP", contactInfoProperties);
//Create container for CATEGORIES
var contactInfoCategories = domConstruct.create("div", { class: "categories" }, contactInfoContainer);
//Categories heading
var p=domConstruct.create("p", { class: "heading" }, contactInfoCategories);
p.innerHTML = "Categories";
//Alert, Warning & Incident Response
this._createCategory("alert", contactInfoCategories, "Alert, Warning & Incident Response");
//Threat
this._createCategory("threat", contactInfoCategories, "Threat");
//Vulnerability
this._createCategory("vulnerability", contactInfoCategories, "Vulnerability");
//Industry
this._createCategory("industry", contactInfoCategories, "Industry");
//Policy
this._createCategory("policy", contactInfoCategories, "Policy");
//R & D
this._createCategory("rd", contactInfoCategories, "R & D");
//Sharing
this._createCategory("sharing", contactInfoCategories, "Sharing");
//Crime
this._createCategory("crime", contactInfoCategories, "Crime");
//SCADA/Process Control
this._createCategory("scada", contactInfoCategories, "SCADA/Process Control");
//Assurance
this._createCategory("assurance", contactInfoCategories, "Assurance");
//Standards
this._createCategory("standards", contactInfoCategories, "Standards");
//Resilience
this._createCategory("resilience", contactInfoCategories, "Resilience");
//Exercises
this._createCategory("exercises", contactInfoCategories, "Exercises");
//Defence
this._createCategory("defence", contactInfoCategories, "Defence");
//Media handling
this._createCategory("media", contactInfoCategories, "Media handling");
//General PoC
this._createCategory("poc", contactInfoCategories, "General PoC");
//Add remove button
var btnRemove = domConstruct.create("input", { type: "button", id: "btnRemove" + amountContacts, value: "Remove"/*,onclick:"_deleteContact(this)"*/ }, contactInfo);
this.connect(btnRemove, "onclick", dojo.partial(this._deleteContact, btnRemove.id));
},
_createTextbox:function(id, textBoxValue, labelText, parent, checkbox, checkboxChecked)
{
var currentId = id+amountContacts;
var textboxContainer = domConstruct.create("div", {class:"form-item"}, parent);
this._createLabel(currentId, textboxContainer, labelText);
//Create textbox and attach update handler
var textbox = domConstruct.create("input", { type: "text", id: currentId, name: currentId }, textboxContainer);
if (textBoxValue)
textbox.value = textBoxValue;
this.connect(textbox, "onchange", this._updateValue);
if (checkbox) {
this._createCheckbox("inline", textboxContainer, currentId + "_24", "24/7?");
}
},
_createTLP_DDL:function(id, labelText, parent)
{
var currentId = id+amountContacts;
var ddlContainer = domConstruct.create("div", { class: "form-item" }, parent);
this._createLabel(currentId, ddlContainer, labelText);
var ddl = domConstruct.create("select", { name: currentId, id: currentId }, ddlContainer);
//Add options
var option1 = domConstruct.create("option", { val: "-1" }, ddl);
option1.innerHTML = "None";
var option1 = domConstruct.create("option", { val: "1" }, ddl);
option1.innerHTML = "Yes";
var option1 = domConstruct.create("option", { val: "0" }, ddl);
option1.innerHTML = "No";
},
_createCategory:function(id, parent, checkboxText)
{
var currentId = id + amountContacts;
var categoryContainer = domConstruct.create("div", { class: "form-item inline" }, parent);
this._createCheckbox("", categoryContainer, currentId, checkboxText);
},
_createLabel:function(forId, container, labelText)
{
var label = domConstruct.create("label", { for: forId }, container);
label.innerHTML = labelText;
},
_createCheckbox:function(labelClass, container, checkboxId, checkboxText){
var labelCheckbox = domConstruct.create("label", { class: labelClass }, container);
//Create checkbox and attach update handler
var checkbox = domConstruct.create("input", { type: "checkbox", id: checkboxId, name: checkboxId }, labelCheckbox);
this.connect(checkbox, "onchange", this._updateValue);
var span = domConstruct.create("span", {}, labelCheckbox);
span.innerHTML = checkboxText;
},
_deleteContact:function(targetBtnId)
{
var userInput = window.confirm("Are you sure you want to delete this contact?");
if (userInput) {
dojo.destroy(contactContainerPrefixName+targetBtnId[targetBtnId.length-1]);
}
},
_updateValue: function () {
//TODO: Delete test data
var contacts = [];
//The amount of contacts can have changed after deletions, so the global variables might not hold the real value
var actualAmountContacts = 0;
for (var i = 1; i <= amountContacts; i++) {
if (this._isValidContact(i)) {
var currentContact = new Object();
currentContact.fullName = this._getValueById("name" + i);
currentContact.website = this._getValueById("website" + i);
contacts[actualAmountContacts] = currentContact;
actualAmountContacts++;
}
}
this._set("value", contacts);
this.onChange(contacts);
},
_getValueById: function (id) {
var node = dojo.byId(id);
var textValue = (node != null) ? node.value : "";
return textValue;
},
_isValidContact:function(itemNumber){
//If there's no name or telephone then it's not a valid contact
return (this._getValueById("name" + itemNumber) != "" || this._getValueById("phoneOne" + itemNumber) != "" || this._getValueById("phoneTwo" + itemNumber) != "");
},
});
});
UPDATE:
After a lot of test I've realized it's not the domConstruct.create statement which causes the error, the problem is that the form element that has "id:cccp" and to which I want to attach the created elements is null at this point. It is defined in the templateString as you can see in the source code but it's still null in the postCreate method. So the question is now, what event gets triggered when the HTML in the templateString is loaded?
In the end I solved the problem, so I write the workaround here and I hope it helps somebody!
I changed the templateString and added the attribute data-dojo-attach-point=\"form\" to the form that was not being found. Then in the postCreate method I accessed the form using this (this.form ), which worked without a problem. The final implementation for the function turned out as follows:
postCreate: function () {
// call base implementation
this.inherited(arguments);
//this._loadContacts(this.value);
var node = domConstruct.create("fieldset", { id: "test" }, this.form); //this simple create instruction returns undefined here.
//Bind button
this.connect(this.btnAdd, "onclick", dojo.partial(this._createContact, new Object()));
},
I still don't understand why dojo.byId("cccp") would return null, but I'm happy I found a workaround that solved the problem. Cheers!
For some reason in IE8 when I run this function after an onclick event it causes a page refresh. I don't wan the page refresh to happen.
var edealsButton = dojo.byId("edeals_button");
var edealEmailInput = dojo.byId("edeals_email");
var edealsSignup = dojo.byId("edeals_signup");
var edealsThankYou = dojo.byId("edeals_thankyou");
var currentValue = dojo.attr(edealEmailInput, 'value');
if (currentValue != '' && currentValue != 'Enter your email') {
var anim = dojox.fx.flip({
node: edealsSignup,
dir: "right",
duration: 300
})
dojo.connect(anim, "onEnd", this, function() {
edealsSignup.style.display = "none";
edealsThankYou.style.display = "block";
})
dojo.connect(anim, "onBegin", this, function() {
var criteria = { "emailAddress": '"' + currentValue + '"' };
alert("currentValue " + currentValue);
var d = essentials.CallWebserviceMethod('AlmondForms.asmx/SignupEdeal', criteria);
d.addCallback(function(response) {
var obj = dojo.fromJson(response);
alert(obj.d);
if (obj != null && obj.d != null) {
//alert(obj.d);
if (obj.d == false)
{
var edealSuccess = dojo.byId("edeals_succes_thankyou");
var edealError = dojo.byId("edeals_error_thankyou");
alert("edealError: " + edealError);
dojo.style(edealSuccess, "display", "none");
dojo.style(edealError, "display", "inline-block");
}
else
{
var edealSuccess = dojo.byId("edeals_succes_thankyou");
var edealError = dojo.byId("edeals_error_thankyou");
dojo.style(edealSuccess, "display","inline-block");
dojo.style(edealError, "display", "none");
}
}
else {
var edealSuccess = dojo.byId("edeals_succes_thankyou");
var edealError = dojo.byId("edeals_error_thankyou");
dojo.style(edealSuccess, "display", "none");
dojo.style(edealError, "display", "inline-block");
}
});
})
anim.play();
edealEmailInput.innerHTML == 'Enter your email';
}
else
{
dojo.attr(edealEmailInput, 'value', 'Enter your email');
dojo.style(edealEmailInput, 'color', '#CC2525');
}
It looks like your "d.addCallback" code might not be disposed of properly. You might want to try placing "dojo.stopEvent()" possibly before the "anim.play();" line and see if this would stop the postback.
From the api.dojotoolkit.org site, the dojo.stopEvent() "prevents propagation and clobbers the default action of the passed event". From the docs.dojocampus.org site, they say that "dojo.stopEvent(event) will prevent both default behavior any any propagation (bubbling) of an event."
Good luck, and hope this helps you out some.