How to change an angular2 html dynamically? - javascript

Im trying to find a way to change some html lines of a component dynamically.
<li *ngFor="p in persons" [attr.id]="p.id">
<span> {{ p.name }} </span>
<a (click)="getAge(p.id)">Get Age<a/>
</li>
If the user clicks on the Get Age link i would like to replace the content inside of the corresponding li tag to something like:
<span> {{ p.OtherProperty }} </span>
<a (click)="OtherMethod(p)">Call OtherMethod<a/>
I found that ComponentFactoryResolver to create dynamic components, but i found it too overwhelming for just 2 lines of html. And i tried to change it by hand using jquery but it does not work to create the event bindings:
getAge(id) {
//some work
//remove the corresponding <li> content
$('#' + id).append('<a (click)="getAnotherValue(p.name)">GetAnotherValue<a/>');
$('#' + id).append('<span> {{ p.age}} </span>'); //this obviously doesnt work. But thats the ideia.
}
So how can i replace some html tags with angular attributes dynamically?

You could access the person's Object property dynamically like this:
object[property]; // Where property is a string
// If property equals 'name', the above sentence would equal this:
object.name; // or `object['name'];`
So, following your example, you could do this:
export class App {
persons = [
{
id: 1,
name: 'John',
age: 25
},
{
id: 2,
name: 'Mike',
age: 30
}
];
selectedProperty = 'name';
constructor() {
}
getProperty(prop) {
this.selectedProperty = prop;
}
}
And in your template you could:
<div>
<li *ngFor="let p of persons" [attr.id]="p.id">
<span> {{ p[selectedProperty] }} </span>
<br>
</li>
<button (click)="getProperty('age')">Get Age</button>
<button (click)="getProperty('name')">Get Name</button>
</div>
If I understood well, this should do the trick. You can't use ngIf because if you have 60 properties or persons then will be somewhat caothic.
Check the Plunker

Use ngIf to activate the code as shown:
<li *ngFor="p in persons" [attr.id]="p.id">
<div *ngIf=!p?.touched>
<span> {{ p.name }} </span>
<a (click)="getAge(p)">Get Age<a/>
</div>
<div *ngIf=p?.touched>
<span> {{ p.age}} </span>
<a (click)="getAnotherValue(p.name)">GetAnotherValue<a/>
</div>
</li>
isGetAgeClicked=false;
getAge(person) {
//some work
//remove the corresponding <li> content
person.touched=true
}

Related

Target element when inside v-for loop Vuejs

I am trying to build a To-Do list in Vue.js which has 3 columns: To-Do, Doing, Done.
I would like to be able to move an item between columns by clicking on arrows that are inside the list item.
Right now I have a list of objects that I separate in 3 arrays depending on a "status" attribute. I would like to change that attribute when clicking on left/right arrow then refresh the UI with new arrays.
I haven't found the way to target the element that received the click.
<ul>
<li v-for="todo in todoTodos" v-bind:key="todo._id">
<span v-if="todo.importance == 1" class="bg-success"></span>
<span v-else-if="todo.importance == 2" class="bg-warning"></span>
<span v-else-if="todo.importance == 3" class="bg-alert"></span>
<div>
<h3>{{ todo.title }}</h3>
<p>{{ todo.description }}</p>
</div>
<p class="todo__date">Début: {{ todo.datebegin }} - Fin espérée: {{ todo.dateend }}</p>
<div class="todo__actions">
<i #click="editTodo" class="icofont-edit"></i>
<i #click="moveRight" class="icofont-arrow-right"></i>
<i #click="moveLeft" class="icofont-arrow-left"></i>
</div>
</li>
</ul>
My linter prevents me from using v-for + v-if, but I guess that means I will have to re-calculate each list (todoTodos, doingTodos, doneTodos) after each modification. Is there a better way ?
I tried console.logging this e.target e.currentTarget but
this logs the entire data model
e.target and e.currentTarget logs the element which I can't use to find my way back to the todo item I want to modify
You /usually/ pass the ($event) argument to a method if you want to access an event.
<i #click="moveLeft($event)" class="icofont-arrow-left"></i>
......
methods: {
moveLeft(e){
console.log(e.target)
}
}

Protractor Map or Filter function on a table which is having repeater with inside div elements

Below is the html for a table with repeater> div> a> span.
How I can work with filter or Map function to get the compare the second column value i.e idea = ideaOne + ideaTwo + ideaThree + ideaFour along with first column value in loop as this is an anchor tag. Also what would be the proper way to deal with all table values.
Repeater gives me the complete table values but when we use filter & try to click on the first cell value, it's did perform any action neither fail the It block. console value shows all the repeater values are coming in array.
Page Object Class-
getAllTableValues: function () {
return element.all(by.repeater('idea in ctrl.ideaList.ideas')).filter(function(elem) {
return elem.getText().then(function(text) {
return text === 'K';
});
}).click();
},
Spec Class:
tablePage.getAllTableValues().then(function (val) {
console.log("####"+ val);
});
Html Code:
<ul class="total_ideas">
<!----><li ng-repeat="idea in ctrl.ideaList.ideas" class="" style="">
<div class="protractor testing">
<a class="item_idea" ui-sref="xxx.xxx.xxx.xxx.xxx({
'id': idea.id,
'value': idea.name,
'start': (ctrl.dateRange.start | date: 'M-d-y'),
'end': (ctrl.dateRange.end | date: 'M-d-y')
})" href="/xxx/xxx/;value=K">
K
</a>
</div>
<div class="protractor test testing">
<span ng-bind="ctrl.getTotalIdeas(idea)">6</span>
</div>
<div class="protractor test testing">
<span ng-bind="idea.testTerms[ctrl.period].ideaOne">2</span>
</div>
<div class="protractor test testing">
<span ng-bind="idea.testTerms[ctrl.period].ideaTwo">2</span>
</div>
<div class="protractor test testing">
<span ng-bind="idea.testTerms[ctrl.period].ideaThree">2</span>
</div>
<div class="protractor test testing">
<span ng-bind="idea.testTerms[ctrl.period].ideaFour">0</span>
</div>
</li><!----><li ng-repeat="idea in ctrl.ideaList.ideas" class="">
</li><!---->
</ul>
Please suggest here.

Run functions with checkbox interactions

I am trying to create checkboxes that will add an object to an array when checked, but will remove that object when unchecked. I am not sure if this is the correct way, but will show what I have below. Using Angular 2 by the way.
Original Code:
<div>
<ul>
<a *ngFor="let perkResult of perkList.results" (click)="onAddPerk(perkResult)">
<li>{{ perkResult.perk }}</li>
</a>
</ul>
</div>
<div *ngFor="let perk of company.perks;>
{{ perk.perk }}
<a (click)="onDeletePerk(i)"></a>
</div>
Functions:
onAddPerk(perkResult) {
// Adds a new perk to the array
this.company.perks.push({perk: (perkResult.perk)});
}
onDeletePerk(i: number) {
// Delete the perk in the selected index
this.company.perks.splice(i, 1);
}
And I want to do something like this:
<div *ngFor="let benefitResult of benefitList.results" >
<a (click)="onAddBenefit(benefitResult)">
<input type="checkbox" />
//Basically if checked then run add function, if not checked then run delete function
</a> {{ benefitResult.benefit }}
</div>
EDIT:
Got to this point, but cannot reference my other scope for the delete.
<ul *ngIf="benefitList">
<div *ngFor="let benefitResult of benefitList.results; let i = index" >
<input type="checkbox" (change)="updateBenefits($event, benefitResult, i)" >
<li>
{{ benefitResult.benefit }}
</li>
</div>
</ul>
//Original delete
<div *ngFor="let benefit of company.benefits; trackBy: customTrackBy; let i = index">
{{benefit.benefit}}
<a (click)="onDeleteBenefit(i)"></a>
</div>
//function
updateBenefits(event, benefitResult, i) {
if(event.srcElement.checked) {
this.company.benefits.push({benefit: (benefitResult.benefit)});
} else {
this.company.benefits.splice(i, 1);
}
}
You could listen to the change event on the checkboxes. Then pass the event along with the object and index to a method that will look at the status of the checkbox element.
In the component:
optionsList = {
results: [{
perk: 'free coffee',
checked: false
}, {
perk: 'car',
checked: false
}, {
perk: 'pastry',
checked: false
}, {
perk: 'free post-it notes',
checked: false
}]
};
updatePerks(event, perkResult) {
perkResult.checked = event.srcElement.checked;
}
In your HTML template:
<ul>
<li *ngFor="let listItem of optionsList.results">
<label><input type="checkbox" (change)="updatePerks($event, listItem)">
{{ listItem.perk }}</label>
</li>
</ul>
Note: I don't know your data structure, so make the necessary adjustment or post a data sample if you need more help.

Vue.js change model attached to a form upon clicking a list

I have an array of objects. These objects are loaded into a list in vue.js.
Aside from this list, I have a form that displays data from one of these objects. I want to, when clicking one of the list's elements, it will bind this specific object to the form and show its data.
How can do this in Vue.js?
My list code is:
<div id="app-7">
<ul id="food-list" v-cloak>
<food-item v-for="item in foodList" v-bind:food="item" v-bind:key="item.id" inline-template>
<li class="food">
<div class="food-header">
<img :src="'img/' + food.slug +'.png'">
<div class="food-title">
<p>{{food.name}} |
<b>{{food.slug}}</b>
</p>
<p>quantity: {{food.quantity}}</p>
</div>
<div class="food-load"> // load into form upon clicking this
</div>
</div>
</li>
</food-item>
</ul>
</div>
Since I do not have the code for the form, this is my best guess without clarification.
You can add a click handler to the item you want to be clicked. It will pass the value of the food item into the method.
<div class="food-load" #click="setFoodItem(item)">
</div>
And when that method is called, it can assign the clicked item to a data property. I'm not sure where your form is, and if it is in a different component. If it is in a child component, you would have to pass it in as a prop, or emit an event to pass it to a parent component.
data() {
return {
//create a reactive field to store the current object for the form.
foodItemForm: null
};
},
methods: {
//method for setting the current item for the form.
setFoodItem(item) {
this.foodItemForm = item;
}
}
Missing quite a bit of info in your sample code, your script is very important to see to make sense of what you would like to accomplish and where things might be going wrong.
Here's a quick list of the issue I came across with your code:
v-for refers to an individual food item as 'item', inside the loop you're trying to access properties as 'food'
You don't wrap your code in a component unless you're importing the component
When binding a value to 'v-bind:src' (or shorthand ':src') only pass the url, you should be specifying this in your script not inline.
You're better off using a button and the 'v-on:click' (or shorthand '#click') to load your selected food item into your form
You should also include your Javascript
Regardless, here's how I would handle this (took the liberty in filling in some blanks):
<template>
<div id="app">
<ul id="food-list">
<!--<food-item v-for="item in foodList" v-bind:food="item" v-bind:key="item.id" inline-template>-->
<li v-for="item in foodList" class="food">
<div class="food-header">
<img :src="item.slug" v-bind:alt="item.slug" width="250px" height="auto">
<div class="food-title">
<p>{{item.name}} | <b>{{item.slug}}</b></p>
<p>quantity: {{item.quantity}}</p>
</div>
<button class="food-load" #click="loadFoodItem(item.id)">Load Food Item</button>
</div>
</li>
<!--</food-item>-->
</ul>
<form v-if="activeFoodId != null" id="foodItemForm" action="#">
<h3>Food Form</h3>
<label for="food-id">Id:</label>
<input id="food-id" type="number" v-bind:value="foodList[activeFoodId].id"><br/>
<label for="food-slug">Slug:</label>
<input id="food-slug" type="text" v-bind:value="foodList[activeFoodId].slug"><br/>
<label for="food-name">Name:</label>
<input id="food-name" type="text" v-bind:value="foodList[activeFoodId].name"><br/>
<label for="food-quantity">Quantity:</label>
<input id="food-quantity" type="number" v-bind:value="foodList[activeFoodId].quantity">
</form>
</div>
</template>
<script>
export default {
name: 'app',
data: function () {
return {
activeFoodId: null,
foodList: [
{
id: 1,
slug: 'http://3.bp.blogspot.com/-QiJCtE3yeOA/TWHfElpIbkI/AAAAAAAAADE/Xv6osICLe6E/s320/tomato.jpeg',
name: 'tomatoes',
quantity: 4
}, {
id: 2,
slug: 'https://img.purch.com/rc/300x200/aHR0cDovL3d3dy5saXZlc2NpZW5jZS5jb20vaW1hZ2VzL2kvMDAwLzA2NS8xNDkvb3JpZ2luYWwvYmFuYW5hcy5qcGc=',
name: 'bananas',
quantity: 12
}, {
id: 3,
slug: 'https://media.gettyimages.com/photos/red-apples-picture-id186823339?b=1&k=6&m=186823339&s=612x612&w=0&h=HwKqE1MrsWrofYe7FvaevMnSB89FKbMjT-G1E_1HpEw=',
name: 'apples',
quantity: 7
}
]
}
},
methods: {
loadFoodItem: function (foodItemId) {
console.log(foodItemId)
this.activeFoodId = foodItemId
}
}
}
</script>
<style>
/# Irrelevant #/
</style>
Hope it helps!

Create a new scope outside ng-repeat

The idea is how a list of texts can be modified by pressing the button next to the text. We can also apply it to the title text which is outside the list.
HTML:
<div ng-controller="TextController">
<div class="title">
<span>{{ text }}</span>
<button ng-click="edit()">Edit</button>
</div>
<ul>
<li ng-repeat="text in list">
<span>{{ text }}</span>
<button ng-click="edit()">Edit</button>
</li>
</ul>
</div>
JavaScript:
angular.module("app").
controller("TextController", function($scope) {
$scope.text = "hello";
$scope.list = [....]; // list of texts;
$scope.edit = function() {
this.text += " world";
};
});
I'm not sure if I wrote it the right way. However, everything works fine except the edit button in the title which is when I'm trying to edit the title only, it accidentally edits all text which is in its children scope.
What I'm trying to do is to give the title a new scope so that the button doesn't affect other texts because it isn't a parent of any scope.
Yeah What you are trying to do is right:
The {{text}} variable is bound to the same controller scope. so the edit button just updates that value which makes it to change everywhere
You should try to update $scope.list value:
When you updated the scope, the html will render.
$scope.edit = function() {
$scope.list[this.$index] += " world";
};
Please check the demo:
http://jsfiddle.net/Lvc0u55v/7050/
Why don't you use another variable name for ng-repeat(i have made second text => txt). It is better to have separate functions for updating list and text, but if you want, you can try
<div ng-controller="TextController">
<div class="title">
<span>{{ text }}</span>
<button ng-click="edit()">Edit</button>
</div>
<ul>
<li ng-repeat="txt in list">
<span>{{ txt }}</span>
<button ng-click="edit($index)">Edit</button>
</li>
</ul>
</div>
The view is passing $index of the array element:
$scope.edit = function(listIndex) {
if(listIndex) $scope.list[listIndex] += " world"
else $scope.text += " world";
};
You could create an "include" combined with an ng-template so that you get a new scope.
http://jsfiddle.net/pfeq0mwe/3/
<div ng-app="myApp" ng-controller="myCtrl">
<div ng-include src="'myTemplate.htm'">
</div>
<ul>
<li ng-repeat="text in list">
<span>{{ text }}</span>
<button ng-click="edit()">Edit</button>
</li>
</ul>
<script type = "text/ng-template" id = "myTemplate.htm">
<div class="title">
<span>{{ text }}</span>
<button ng-click="edit()">Edit</button>
</div>
</script>
</div>
<div >
</div>

Categories

Resources