Creating div in vue for loop - vue.js

I'd like to do v-for loop for creating a div. I'm doing a minesweeper game and here is my code :
<div class="grid">
<div class="square"
v-for="(square, index) in squares"
:id="index"
:key="index"
:class="squares[index]"
#click="clicked(square, index)"
>
</div>
</div>
'Squares' in this code is an array with shuffled classes 'bomb' or 'empty'. I know that it's wrong because after I click on random square I get only this class from te 'squares' array. What should be there instead of this 'squares' array in v-for. I want to get whole with classes, attributes etc. because later I have to use 'classList' 'contains' etc.
Sorry, maybe I'm completly wrong and talking bullshit, but I started with vue 3 weeks ago.
Here is the method clicked which I want to use
clicked(square) {
if(this.isGameOver) return;
if(square.classList.contains('chechked') || square.classList.contains('flag')) return
if(square.classList.contains('bomb')) {
this.gameOver(square);
} else {
let total = square.getAttribute('data');
if(total != 0) {
square.classList.add('checked');
square.innerHTML = total;
return
}
}
square.classList.add('checked');
}

You want to access the div element but you are passing the object in the method and you are asking for classList into the object (that does not have it). You should query the element instead.
Change the #click handler in your component to:
#click="clicked"
and your method to:
clicked(event) {
let square = event.target;
console.log(square);
console.log(square.classList);
...

Related

VueJS: :class, condition with index

I'm creating a list of (thumbnail) 2 images, and #click each image should be expanded to max-width, by adding said class ('max-w-full') to the classes. The class is added by setting the array entry with the same index nr. as the image (imgclicked[0], imgclicked[1]) in the list to 1.
data:() {
imgclicked=[0,0], //by default both are set to 'small'/not 'max-w-full'
},
the template part looks like this:
<template v-for="(image,index) in images>
<a #click="zoomImgClicked(index)">
<img :src="image.filename" :class={'max-w-full' : imgclicked[index]==1,'w-12' : imgclicked[index]==0}">
</a> // using this.imgclicked[index] gives me the error 'this.imgclicked[0] is undefined'
</template>
on click the method zoomImgClicked() ist launched:
zoomImgClicked: function(i){
if(this.imgclicked[i]==0){
this.imgclicked[i]=1
}else{
this.imgclicked[i]=0
}
},
But this is not working. If I open the vue console and change the values of imgclicked[0] manually from 0 to 1 it works (images ae being resized). Also I see the method doing it's work, when logging in the console, the values are changed from 0 to 1 and vice versa. But it's not reflected on the page #click.
Why is that?
Please read Change Detection Caveats in the Vue docs.
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
You should use the $set method for this:
this.$set(this.imgclicked, i, 1)
try with array syntax:
<img
:src="image.filename"
:class="[
{
'max-w-full': imgclicked[index]==1,
'w-12': imgclicked[index]==0
},
'static class if need'
]"
>

VuesJS, generate randomkey in v-for loop

Good evening,
My problem is this: I have a loop that displays simple divs.
I have a method that specifies the dimensiosn of my div (mandatory in my case). However, when I call the method a second time by changing the sizes of the divs, it does not work because there is no re-render.
To overcome this, I generate a guid on my: key of v-for with a variable such as:
<div v-for="task in tasks" :key="task.id + guid()">...blabla...</div>
Is it possible to generate this code directly during the loop to avoid concatenation?
<div v-for="(task, maVar=guid()) in tasks" :key="maVar">...blabla...</div>
PS : code for guid() method :
guid() {
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16))
}
Thanks
You could create a computed property that returns an array of task with a guid added, or if you want to leave tasks untouched, return an object containing each task plus a guid,
computed: {
tasksWithGuid: function() {
return this.tasks.map(task => { return {task, key: task.id + guid() } })
}
}
<div v-for="taskWithGuid in tasksWithGuid" :key="taskWithGuid.key">
{{taskWithGuid.task.someProperty}}
</div>
There is a simpler, more concise technique shown below. It avoids polluting the iterated object with a redundant property. It can be used when there is no unique property in the objects you iterate over.
First in your viewmodel add the method to generate a random number (e.g. with Lodash random)
var random = require('lodash.random');
methods: {
random() {
return random(1000);
}
}
Then in your template reveal the index in v-for and randomize it in v-bind:key with your random() method from the viewmodel by concatenation.
<div v-for="(task, index) in tasks" v-bind:key="index + random()">
// Some markup
</div>
This is as clean as easy.
However note this approach would force redrawing each item in the list instead of replacing only items that differ. This will reset previously drawn state (if any) for unchanged items.
I do like this
function randomKey() {
return (new Date()).getTime() + Math.floor(Math.random() * 10000).toString()
}

Aurelia iterate over map where keys are strings

I'm having trouble getting Aurelia to iterate over a map where the keys are strings (UUIDs).
Here is an example of the data I'm getting from an API running somewhere else:
my_data = {
"5EI22GER7NE2XLDCPXPT5I2ABE": {
"my_property": "a value"
},
"XWBFODLN6FHGXN3TWF22RBDA7A": {
"my_property": "another value"
}
}
And I'm trying to use something like this:
<template>
<div class="my_class">
<ul class="list-group">
<li repeat.for="[key, value] of my_data" class="list-group-item">
<span>${key} - ${value.my_property}</span>
</li>
</ul>
</div>
</template>
But Aurelia is telling me that Value for 'my_data' is non-repeatable.
I've found various answer by googling, but they have not been clearly explained or incomplete. Either I'm googling wrong or a good SO question and answer is needed.
As another resource to the one supplied by ry8806, I also use a Value Converter:
export class KeysValueConverter {
toView(obj) {
if (obj !== null && typeof obj === 'object') {
return Reflect.ownKeys(obj).filter(x => x !== '__observers__');
} else {
return null;
}
}
}
It can easily be used to do what you're attempting, like this:
<template>
<div class="my_class">
<ul class="list-group">
<li repeat.for="key of my_data | keys" class="list-group-item">
<span>${key} - ${my_data[key]}</span>
</li>
</ul>
</div>
</template>
The easiest method would be to convert this into an array yourself (in the ViewModel code)
Or you could use a ValueConverter inside repeat.for as described in this article Iterating Objects
The code...
// A ValueConverter for iterating an Object's properties inside of a repeat.for in Aurelia
export class ObjectKeysValueConverter {
toView(obj) {
// Create a temporary array to populate with object keys
let temp = [];
// A basic for..in loop to get object properties
// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/for...in
for (let prop in obj) {
if (obj.hasOwnProperty(prop)) {
temp.push(obj[prop]);
}
}
return temp;
}
}
/**
* Usage
* Shows how to use the custom ValueConverter to iterate an objects properties
* aka its keys.
*
* <require from="ObjectKeys"></require>
* <li repeat.for="prop of myVmObject | objectKeys">${prop}</li>
*/
OR, you could use the Aurelia Repeat Strategies provided by an Aurelia Core Team member
You'd have to import the plugin into your app.
Then you'd use it using the pipe syntax in your repeat.for....like so....
<div repeat.for="[key, value] of data | iterable">
${key} ${value.my_property}
</div>

Check for child tag inside parent tag

Is it possible to know if a parent contains child with specific class name.
Sample code
<div id="parent_tag">
<div id="div1">
<span>Title 1</span>
</div>
<div id="div2">
<b>Title 2</b>
</div>
<div id="div3">
<span>Title 3</span>
</div>
</div>
I trying to access "span tag" only with Title 3. I know I can do it by specifying id. but what if I want to it generically (i.e for all elements). So my first approach should be "I will look for span tag inside div". but I don't know how to do that? Please help.
Try XPath:
//div/span[text()="Title 3"]
Using jquery selectors to select span in div3:
$("#div3 span")
Or specifically the first span element inside div3:
$("#div3 span:first")
Here's the idea to get what you need
// Get Parent Element
WebElement parent = driver.findElement(By
.xpath("//div[#id='parent_tag']"));
// Get All children elements of the parent element
List<WebElement> children = parent.findElements(By.xpath("//*"));
// Init some based param for next usage
boolean checkClass = false;
String expectedClass = "Expected_Class";
// Fetch each child element and check whether its text contains expected string.
for (WebElement child : children) {
if (child.getAttribute("class").contains(expectedClass)) {
checkClass = true;
}
}
if (checkClass) {
System.out
.println("We have at least one child has expected class: "
+ expectedClass);
}
else System.out.println("We don't have any child has expected class: "
+ expectedClass);
if you want to check the text, we could do
if (child.getText().contains(expectedText)) {
checkResult = true;
}
}
Hope it helps.

getElementsByTagName of all siblings

UPDATE:
Let me rephrase.
I have multiple divs that all contain, among other things, an img element with the class="yes". But they're not all the same siblings.
Two examples:
<div id="div1" onclick="function(this)">
<img src="image1" />
<div>
<img src="image2" class="yes" />
</div>
</div>
<div id="div2" onclick="function(this)">
<img src="image3" class="yes" />
</div>
Now I'm trying to formulate one function for both divs that would change the source of the image with class yes.
getElementsByTagName doesn't seem to do the trick, nor does getElementsbyClassName.
Any thoughts? Thanks again!
getElementsByTagname is the wrong function.
There is no easy way to do what you're trying to do, unless you resort to third party libraries such as jQuery.
With jquery, you use this code:
$('img.yes').each(function() {
console.log(this);
});
Here is a working example of what you're trying to do: http://jsfiddle.net/ZYBp6/
You can use jQuery like this:
function some_function(x)
{
var arr = $("#" + x.id + " .yes"); // Gets all children of the class "yes"
}
Or if you only want to get img elements, you can do this instead:
function some_function(x)
{
var arr = $("#" + x.id + "img.yes");
}
In both of these examples, arr would contain DOM elements, and not the kind of "element" produced by jQuery normally.
This seems to work:
function function(x) {
$(x).find('.yes').attr('src','differentimage.png');
}