Concatenate/Join Template Elements with a "+" - javascript

Is there a way to concatenate template elements? I have this template, which is a loop, but I can't figure out how I can concatenate/join multiple items. If I place the + inside the <kbd> tag, I get an extra + at the end and it is within the kbd tag (which I would like outside of the tag).
<template>
<kbd v-for="(v, i) in item" :key="i">{{ v }}</kbd>
</template>
The data I am using looks something like this (I am actually using a store):
data: () {
return {
item: [ 'a', 'b', 'c' ]
}
}
The result I am looking for is:
<kbd>a</kbd> + <kbd>b</kbd> + <kbd>c</kbd>

Easy enough. Conditional rendering based on index. Something similar to this should work:
<template v-for="(v, i) in item">
{{i > 0 ? ' + ' : ''}}<kbd :key="i">{{ v }}</kbd>
</template>

Related

How to convert string to expression ( value ) in aurelia repeat for?

Array used in repeat for loop
let loopArr = ["item.name + ' /'+ item.DisplayName? item.DisplayName: item.otherDisplayName",
"item.description + ' /'+ item.anotherDescription"]
Template
<div repeat.for = item of data">
<div repeat.for = "row of loopArr">
<span textcontent.bind="renderRow(row, item)></span>
</div>
</div>
Component method
renderRow(row, item){
return eval(row)
}
Actually I wanted to display like below in template
<div repeat.for = item of data">
<div repeat.for = "row of loopArr">
<span>${item.name + ' /'+ item.DisplayName? item.DisplayName: item.otherDisplayName} </span>
<span>${item.description + ' /'+ item.anotherDescription} </span>
</div>
</div>
Since I wanted to loop through dynamic loopArr, instead of using eval to convert from string to value, is there any better way to compute the value from string? Also, eval doesnt work for multiline statements, is there any other approach/way to handle the above problem?
How to convert string to value and display in aurelia template?
Any help would be appreciated!
I'm not sure why you're adding the logic in string format and using eval. You could directly add it to the template and display it:
<div repeat.for="item of data">
<span>${item.name + '/' + (item.DisplayName ? item.DisplayName: item.otherDisplayName)}</span>
<span>${item.description + ' / '+ item.anotherDescription} </span>
</div>
Let's assume you have a list of custom string formats and you are importing them from another file. You could create an array of functions instead of array of strings. This is much better way of deferring the string creation than running eval
displayTemplates = [
item => item.name + '/' + (item.DisplayName ? item.DisplayName: item.otherDisplayName),
item => item.description + '/'+ item.anotherDescription
]
and then in the template:
<div repeat.for="item of data">
<template repeat.for="func of displayTemplates">
<span>${ func(item) }</span> <!-- call each func on item object -->
</template>
</div>
Also, there is a logical error in your string format. + operator has higher precedence compared to the ternary operator.
So,
item.name + '/' + item.DisplayName ? item.DisplayName : item.otherDisplayName
is actually evaluated as
(item.name + '/' + item.DisplayName) ? item.DisplayName : item.otherDisplayName
So, this expression will always evaluate to item.DisplayName because item.name + '/' + item.DisplayName will never be falsy.
You need to add () around the ternary operation:
item.name + '/' + (item.DisplayName ? item.DisplayName: item.otherDisplayName)
// OR
item.name + '/' + (item.DisplayName ?? item.otherDisplayName)

How to replace a string with a component (vue)

I have strings that contains ### and I am replacing with array values. Now I want to use them with a component, I created the component and it works but I don't know how to use it in the strings. I don't want to wrap them manually because I don't know how the strings will be, it can have several ###. If it has 2 ###, options will have 2 subArrays.
What is the better way to do it?
Code: https://jsfiddle.net/tsobh4nu/
Vue.component('opt', {
template: `<label>
<span class="bold" v-for="(str,idx) in options">
{{str + " / "}}
</span>
</label>`,
props:{options: Array}
})
new Vue({
el: '#app',
data: {
str: "I have ### and you have a ###.",
options: [
['AAA', 'BBB', 'CCC'],
['XXX', 'YYY', 'ZZZ']
]
},
computed:{
replacedStr(){
let newStr = this.str;
this.options.forEach(option=>{
newStr = newStr.replace('###',this.concatenateOptions(option));
})
return newStr;
}
},
methods: {
concatenateOptions(strArr) {
let separator = "";
let strOptions = "";
strArr.forEach(word => {
strOptions += separator + word;
separator = " / ";
});
return strOptions;
}
}
})
.bold {
font-weight: bold
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app">
<p>I want something like this, but using components: </p>
{{replacedStr}}
<br>
<hr>
My Components:<br>
<opt :options="options[0]"></opt>
<br>
<opt :options="options[1]"></opt>
</div>
Many thanks.
This is more general, but I hope it will help someone. Add a dynamic component in your template: <component v-bind:is="processedHtml"></component>.
Then add a computed method:
computed: {
processedHtml () {
let html = this.html.replace('[Placeholder]', '<my-component></my-component>');
return {
template: '<div>' + html + '</div>'
}
}
}
Where <my-component> is your custom component and the this.html is your HTML content that contains the placeholder [Placeholder].
It is important to return an element that has one root node. That's why the return is wrapped with <div>.
Read more advanced tutorial about this issue here in my blog. For example, to pass props to <my-component>
I just experienced the same issue. I had an element that needed to display the current count of an item. The current count came from the store and was constantly changing. I used v-text. I know this is pretty situation specific, but hopefully it helps someone down the line.
<P id="results_count" v-text="current_count"></P>
and in the data portion of the component I had a property named current_count that was updated via methods.

Two way bindings trigger 10 digest() iteration reached

I have some problems with angular binding and I'm not very experienced with it. I will post all questions here as they are related.
Here is some angularjs code snipped that triggers 10 digest() cycle reached. I found some similar posts and the cause is that a change is performed recursively in digest(), but I cannot find the cause in my example.
Here is code:
<work-timekeepings-day timekeepings="dailyTimekeepingCtrl.timekeepingList | timekeepingDay : dailyTimekeepingCtrl.selectedDay" day="dailyTimekeepingCtrl.selectedDay"></work-timekeepings-day>
Component:
var workTimekeepingsDay = TimekeepingsApp.component('workTimekeepingsDay', {
templateUrl : 'angular/components/work-timekeepings-day.html',
controllerAs: '$workTkDayCtrl',
bindings : {
timekeepings : '=',
day: '='
}
});
HTML template:
<div class="row lightgreen-row padding-5 border-rounded" ng-repeat-start="workTk in $workTkDayCtrl.timekeepings">
<div class="col-md-4"> <b> {{ workTk.user.firstName + ' ' + workTk.user.lastName }} </b> </div> </div> ...
Filter function:
var timekeepingDayFilter = TimekeepingsApp.filter('timekeepingDay', function() {
return function(timekeepings, filterDay) {
var result=[];
angular.forEach(timekeepings, function(timekeeping) {
var timekeepingDay = moment(timekeeping.workDay);
if (timekeepingDay.isSame(filterDay, 'day')) {
result.push(timekeeping);
}
});
return result;
}
});
If I apply filter function inside HTML template it doesn't trigger the error, but the two-way binding with 'day' variable seems to not work properly. If I update 'dailyTimekeepingCtrl.selectedDay' in another component, bound in the same way, the change is not reflected in workTimekeepingsDay component.
Here is the filter applied in component template:
<work-timekeepings-day timekeepings="dailyTimekeepingCtrl.timekeepingList" day="dailyTimekeepingCtrl.selectedDay"></work-timekeepings-day>
<div class="row lightgreen-row padding-5 border-rounded" ng-repeat-start="workTk in $workTkDayCtrl.timekeepings | timekeepingDay : day">
<div class="col-md-4"> <b> {{ workTk.user.firstName + ' ' + workTk.user.lastName }} </b> </div> </div> ..
Q1: Why is the 'digest() aborted' error occur if I am not updating the base array? How can I pass directly the filtered array to component in timekeepings variable?
Q2: Why is day variable not updated in component if dailyTimekeepingCtrl.selectedDay is updated?
I solved this by using lodash memoize function to use result from cash. Although I'd have preferred to not use an external library, but to change the algorithm, I am still happy with this.

ng-class not working. Where did I do wrong?

I know there are many problems here listed like this. but I can't pinpoint where I did wrong..
<li ng-repeat="item in type.sub | orderBy:y" ng-click="openpic($parent.$index, $index)" ng-class="{ 'active': $parent.$index + ':' + $index == current }">
we have $scope.current variable, and a nested menu with numbering id like 1:2, 1:3, 1:4, 2:1, 2:2, 2:3 and so on.
Goal is very simple. I need to make this <li> active, if the $parent.$index:$index is equal to $scope.current. ID is set whenever openpic($parent.$index, $index) triggered. We have li.active class in css.
So, can someone please show me, what's missing in that code?
TRY 1:
Tried:
<li ng-repeat="item in type.sub | orderBy:y" ng-click="openpic($parent.$index, $index)" ng-class="{ 'active': $parent.$index + ':' + $index == $parent.current }">
Still not working.
TRY 2
I have something like this:
ng-class="{{ $parent.$index + ':' + $index == $parent.current && 'active' || '' }}"
and it shows as ng-class="active" but class attribute did not updated.
TRY 3
ng-class="{{ $parent.$index + ':' + $index == $parent.current && 'highlight' || '' }}"
it shows ng-class='highlight", but class still shows class="ng-binding ng-scope"
TRY 4
ng-class="isActive($parent.$index,$index)"
It solves the problem, but it seems a little bit overkill for a simple switch function. Any more ideas?
TRY 5
As per major-mann code suggestion that works at TRY 4, I made these adjustment, and surprisingly, it works.
ng-class="$parent.$index + ':' + $index == $parent.current && 'active' || ''"
That one works. Removed all braces completely ????
First of all make {{$parent.$index:$index}} to make sure to get the right data (I just never met such a method to data access) ;
If it's ok, then try use:
ng-class="$parent.$index:$index === current ? 'active' : ''"
Or (best way) use function for this:
ng-class="someFn($parent.$index)"
$scope.someFn = function(index){
//index, index:index, index[n] or something else;
return index === $scope.current ? 'active' : '';
};
Update:
If You want get access to parent ng-repeat, better use
<... ng-repeat="(index1, data1) in firstData">
<... ng-repeat="(index2, data2) in secondData">
<... ng-something="index1 operator index2 or function(index1, index2)">
I hope, it will help;
As pointed to in the comments, rather a non-answer, but usually it is best practice to try to keep code (even simple ternaries) inside the JavaScript blocks rather than inside the HTML. This generally makes debugging any issues you may be having infinitely easier to deal with.
So, for example, you could change your HTML to something like:
<li ng-repeat="..." ng-click="..." ng-class="isActive($parent.$index, $index)">
And in your controller, you could add something like:
$scope.menu= function isActive(pidx, idx) {
return {
active: pidx + ':' + idx === $scope.current
};
};
To achieve your desired result.
If you take this a step further, you will probably realize that it is even better to do something like
$scope.cls.menu = function(pidx, idx) {
return {
active: pidx + ':' + idx === $scope.current,
any: 'other',
dynamic: 'classes',
you: 'need'
};
}
And call the function appropriately in the view.

How do i get a value from Kendo UI template

I have this template:
var template = kendo.template("<div class='relatedItemRow'>"
+ "<span id='relatedItemName'>Related Item #: Number #</span>"
+ "<div class='relatedItemRowInfo'><span >#: Name #</span>"
+ "<a data-relatedItemID='#: Value #' class='removeRelatedItem'>"
+ "<img src= '#: Img #'/</a></div><br class='clear'/></div>");
var data = {
Name: "" + checkbox.getAttribute('flatName'),
Number: $('#relatedItemsList').children().length + 1,
Img: '/Content/images/x_remove.png',
Value: checkbox.value
};
var result = template(data);
$("#relatedItemsList").append(result);
I am able to access the data-relatedItemID by using:
$('#relatedItemsList').children().eq(i).children().last().attr('data-relatedItemID')
But how do I get to the Number field in data? I want to change that one dynamically.
I have tried:
$('#relatedItemsList').children().eq(i).children().attr('Number') == 5
but it does not work. Any idea how to do that?
I know that there is an answer for this question even accepted but I'd like to suggest a different approach that I think it is much simpler and more Kendo UI oriented and is using Kendo UI ObservableObject. This allows you to update an HTML that is bound to the ObservableObject without having to crawl the HTML.
This approach is as follow:
Wrap your data definition with a Kendo Observable Array initialization.
Redefine your template and start using using data-binding.
Append this new template to your HTML.
Bind data to the HTML.
Now you can get current value using data.get("Number") or set a new value using data.set("Number", 5) and the HTML get magically updated.
The code is:
Template definition
<script type="text/kendo-template" id="template">
<div class='relatedItemRow'>
<span id='relatedItemName'>Related Item <span data-bind="text : Number"></span></span>
<div class='relatedItemRowInfo'>
<span data-bind="html : Name"></span>
<a class='removeRelatedItem' data-bind="attr: { data-relatedItemID : Value }">
<img data-bind="attr : { src : Img }"/>
</a>
</div>
<br class='clear'/>
</div>
</script>
data definition as:
var data = new kendo.data.ObservableObject({
Name: "" + checkbox.getAttribute('flatName'),
Number: $('#relatedItemsList').children().length + 1,
Img: '/Content/images/x_remove.png',
Value: checkbox.value
});
and the initialization of the HTML is:
$("#relatedItemsList").append($("#template").html());
Getting current value of Number is:
var old = data.get("Number");
Setting is:
data.set("Number", 5);
Running example in JSFiddle here : http://jsfiddle.net/OnaBai/76GWW/
The variable "result" and thus the data you're trying to change aren't a Kendo templates, they're just html created by the template, and the number is just text in the html. To modify the number without rebuilding the whole string, you need to change the template so you can select the number by itself with jQuery by putting it in it's own element, and adding something to identify it.
var template = kendo.template("<div class='relatedItemRow'><span id='relatedItemName'>Related Item <span class='relatedNumberValue'>#: Number #</span></span><div class='relatedItemRowInfo'><span >#: Name #</span><a data-relatedItemID='#: Value #' class='removeRelatedItem'><img src= '#: Img #'/</a></div><br class='clear'/></div>");
//other code the same
And once you can select it, then you can change it like this.
$('#relatedItemsList').children().eq(i).find('.relatedNumberValue').text(5);
And retrieve it like this.
var foo = $('#relatedItemsList').children().eq(i).find('.relatedNumberValue').text();

Categories

Resources