Vue - Update rendered elements from a v-for with :style that uses their values [duplicate] - arrays

I'm looping through elements and I'm positioning div using top and left CSS properties:
<div
v-for="coord in coords"
:style="{ top: coord.y + 'px', left: coord.x + 'px' }"
></div>
Sometimes instead of top property I need to use bottom (this depends on one of my Vuex store values). How can I dynamically define if I should use top or bottom CSS property?
I tried to used computed prop isTopOrBottom which would return 'top' or 'bottom: :style="{ isTopOrBottom: coord.y + 'px', left: coord.x + 'px' }". But this is not working in Vue.

You can use the ternary operator (in case computed properties are not working)
For example:
<span
class="description"
:class="darkMode ? 'dark-theme' : 'light-theme'"
>
Hope this help.

It should be like JavaScript string concatenation
<div
v-for="coord in coords"
:style="'top: '+coord.y + 'px;left: '+coord.x + 'px'"
></div>
Vue.config.productionTip = false;
Vue.config.devtools=false;
var app = new Vue({
el: '#app',
data:{
coords:[{y:10,x:10},{y:20,x:20},{y:30,x:30}]
}
});
.border-line{
border: 1px solid;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
<div id="app">
<div class="border-line"
v-for="coord in coords"
:style="'margin-top: '+coord.y + 'px;margin-left: '+coord.x + 'px'"
>Test</div>
</div>

You could do something like this:
:class="{ is-top: isTop, is-bottom: isBottom }"
And in your script:
computed() {
isTop() {
// return top condition
},
isBottom() {
// return bottom condition
}
}
Handle css:
.is-top {
...
}
.is-bottom {
...
}

You can also use a Vuejs custom directive for this! https://v2.vuejs.org/v2/guide/custom-directive.html
Check this out:
In your template:
<p v-position="expressionThatMakesItTop" v-if="isSignedIn">Welcome back {{user.email}}</p>
If you want to register a directive locally, components also accept a directives option, check that out on the documentation I linked.
I am going to show you how to do it globally so in your main.js file, before constructing the Vue instance of course:
I left the console.log that displays the objects that you can use in your directive so you can explore them on your console and tailor this to your needs.
Vue.directive("position", {
bind: function(el, binding, vnode) {
console.log(el, binding, vnode);
el.style.left = `${vnode.context.coord.x}px`;
if (binding.value) {
el.style.top = `${vnode.context.coord.y}px`;
return;
}
el.style.bottom = `${vnode.context.coord.y}px`;
}
});

Related

ng-style not refreshing dynamically

I have an md-card with this code:
<md-card ng-style="{ width: pageWidth > 900 ? '40vw' : '80vw' }">...</md-card>
pageWidth is a $scope variable bound to $(window).width(). Here is the code for that:
$scope.pageWidth = $(window).width();
$(window).resize(() => {
$scope.pageWidth = $(window).width();
console.log('page width: ' + $scope.pageWidth);
})
$(document).ready(() => {
$(window).resize(() => {
$scope.pageWidth = $(window).width();
console.log('page width: ' + $scope.pageWidth);
})
})
The style is applied correctly when the page loads, but not when I manually resize the page. As you can see in the second code block, I added a console.log statement to the handlers, so I know that $scope.pageWidth is updating with every pixel of width I change. However, the width of the md-card never changes from one to the other. What's going on here?
And before you mark this as a duplicate, people have asked this before, but not in a way where their answers apply to my situation.
Sorry, I'm not posting an answer for this other then that you have a typo in first line should be:
<md-card ng-style="{ width: pageWidth > 900 ? '40vw' : '80vw' }">...</md-card>
But from what I can see what you are doing can be done much more efficiently using normal CSS - no need to put javascript logic for that. Also I would advise using AngularJS $window (you will need to inject it) instead of global window object and I'm against using Jquery in Angular applications and Jquery DOM manipulations unless it's really really (and I will say again really) necessary.
Check this link about media queries:
https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries
You will see that you can easily check max-width and max-height, also min-width/height and tons of different things that might solve your problems with pure CSS and no need for Javascript/Jquery mixed with AngularJS.
Your CSS would be something like:
#media screen and (max-width: 900px) {
md-card {
width: 80vw;
}
}
#media screen and (min-width: 901px) {
md-card {
width: 40vw;
}
}
Of course this would be globally on all md-card elements if you need it more specific add classes on each element and change media queries.

change text color from directive when innerHTML matches

I have a page where I am rendering many columns with the help of the ng-repeat.
HTML
<div ng-repeat="col in selectedColumn" class="cellHldr ng-status-color">{{lead[col.name]}}</div>
Now I have made a directive ngStatusColor
.directive('ngStatusColor', function () {
return {
restrict: "C",
compile: function (tElement, tAttributes) {
return {
post: function postLink( scope, element, attributes ) {
console.log("element",element[0].innerHTML );
if(element[0].innerHTML=='Open'){
console.log("hellooo");
}
}
}
}
}
});
I need to color the text of that column which has the {{lead[col.name]}}like 'open','closed'. Rest should be left as it is
Since you are going the innerHtml way , you can just add a font tag to the inner html to change the color :
if(element[0].innerHTML=='Open'){
element[0].innerHTML="<font color='red'>"+element[0].innerHTML+"</font>";
}
The angular way to do this would be using ng-class.On your div which put the following :
ng-class="{ 'red': lead[col.name]=='Open', blue: lead[col.name]=='close' }"
And create two css classes red and blue:
.red {
color: red;
}
.blue {
color: blue;
}
What this will do this is when it will assign red class to your element if the value of lead[col.name] becomes 'Open' , Blue if 'Close'.
If you want to get more info about ng-class , I would recommend you to go through this link :ng-class uses
Use ng-class for the same.
CSS
.colorChange {
color: green;
}
HTML
<div ng-repeat="col in selectedColumn" class="cellHldr ng-status-color" ng-class="lead[col.name] === 'open' ? 'colorChange': 'normalStyle'">{{lead[col.name]}}</div>
Will this approach work ?
if(element[0].innerHTML=='Open'){
element[0].style.color = "red";
}

How to get height of a div in Ionic

I'm working to develop a mobile app using Ionic.
A bit of background first as to what I am trying to achieve.
I've got a challenging bit of design I am coding in. I am placing an image of fixed height, in the bottom right hand corner of a div which has a flexible height. The text within the div then needs to wrap around the image.
Like this:
What the end result should be like
The HTML and CSS side of things
I've got the CSS and HTML sussed (at least I think!). The HTML is:
//this line is in the head
<style ng-bind-html="myStyles"></style>
//the rest is in the body of the HTML
<div class="score_block">
<div class="description">
<div class="image_container">
<img src="img/emotional_man.png">
</div>
<p>{{area.levelling.description}}</p>
<div class="habits_button">
<button ng-click="$state.go('app.planner')" class="button button-stable button-icon button-block">Plan habits</button>
</div>
</div>
</div>
And the CSS (written using SASS) is like this:
.score_block {
text-align: center;
position: relative;
.description {
text-align: left;
margin: 10px 0;
}
.image_container {
clear: both;
float: right;
width: 100px;
height: 100px;
img {
height: 100px;
width: 100px;
}
}
}
.score_block:before {
content: "";
float: right;
height: 200px;
width: 0;
}
If I change the height of the 'score_block:before' class I can reposition the image just I need.
The Javascript so far
So with the Javascript side of things I'm hoping that if I can figure out the height of the .description div, I can subtract the height of the image from it and tell the CSS how to position the image. I need some AngularJS to do this - I think that's what I need as JQuery doesn't work in Ionic as far as I know.
So far I have JS code that does this:
.controller('emotionalCtrl', function ($scope, $state, AreasService, _) {
//these commented out lines are to show things I have tried but don't work
//var blockH = $(".description").height();
//var descriptionHeight = angular.element('description');
//var number = descriptionHeight('offsetHeight');
var number = 0;
$scope.myStyles = "#habit_area_homepage .score_block:before { height:" + number + "px; }";
})
I'm looking to do a calculation on the variable number and pass that back in. I can manually change the value of number of it works fine so I know everything else is good. I've read some stuff about doing directives etc but all the examples I've seen confuse me. Maybe I need to put a directive in here or something to help me get the height of the .description element but I just can't figure out to do this. I've spent nearly two days getting this far!
I'm pretty new to AngularJS and Ionic so any help would be really appreciated. Thanks!
There are multiple ways to accomplish dynamic styles.
According to your provided code. I recommend you add styles to head.
Run below codes in your controller or "run":
angular.module("app",[]).run(function(){
var stylesTpl="<style>#habit_area_homepage .score_block:before { height:" + number + "px; } </style>";
angular.element(document).find("head").append(stylesTpl);
})
Check this post for built-in directives of angular to achieve dynamic styles:
How do I conditionally apply CSS styles in AngularJS?
If you want to get the height of a specific div, you have two ways:
Assign an id to the div, and use
var element = document.getElementById("id");
console.log(element.offsetHeight);
Use querySelectors, this returns the first and only one element:
var element = document.querySelector(".description");
console.log(element.offsetHeight);
Using directive is also a good way, check:
Get HTML Element Height without JQuery in AngularJS

Removing nvd3 angular charts

I have a directive which - based on the bound data and meta data - displays either a line chart or a bar chart. I use nvd3 to display those charts and displaying the first time works fine. But when the metadata changes and the charttype should change, I see both.
This is my link function:
link: (scope: IReportChartScope, element: JQuery, attrs) => {
currentElement = element;
scope.$watch('diagramInfo', (newVal: IDiagramInformation, oldVal: IDiagramInformation, scp: IReportChartScope) => {
if (innerElement) {
d3.select('#' + scope.diagramId + ' svg').remove();
innerElement.remove();
}
if (!newVal) {
currentElement.html(loadingDataTemplate);
} else {
var request = <IPeriodDiagramDataRequest>diagramInfo.Request;
if (request.Period.Id == DiagramAggregationPeriod.All) {
currentElement.html(barChartTemplate);
} else {
currentElement.html(lineChartTemplate);
}
innerElement = $compile(currentElement.contents())(scope);
}
});
}
You can see that in the beginning I try to remove the innerElement of my directive, but it somehow doesn't work. These are my templates:
var loadingDataTemplate = '<h1>Loading...</h1>';
var lineChartTemplate = '<nvd3-line-chart id="{{diagramId}}" data="diagramInfo.Series" showxaxis="true" showyaxis="true" tooltips="true" interactive="true" ' +
'showlegend="true" width="{{width}}" height="{{height}}" nodata="Es wurden keine Daten geladen." margin="{top: 30, right: 30, bottom:30, left: 90}">' +
'<svg ng-style="{width: width + \'px\', height: height + \'px\'}"></svg></nvd3-line-chart>';
var barChartTemplate = '<nvd3-multi-bar-chart id="{{diagramId}}" data="diagramInfo.Series" showxaxis="true" showyaxis="true" tooltips="true" interactive="true" ' +
'showlegend="true" width="{{width}}" height="{{height}}" nodata="Es wurden keine Daten geladen." margin="{top: 30, right: 30, bottom:30, left: 90}">' +
'<svg ng-style="{width: width + \'px\', height: height + \'px\'}"></svg></nvd3-multi-bar-chart>';
I don't want to use ng-if or ng-hide because the directive will have a bit more functionality. Any ideas how I can get rid of it? it's also interesting to prevent memory leaks from happening. I've found How to avoid memory leaks using angularjs-nvd3-directives and used it for my inspiration, but probably not enough.
Ok, I guess AngularJS wasn't built to be used this way, instead I defined a rather huge template file which consists of one <div ng-include="chartTemplate"></div> and a many <script type="text/ng-template" id="xyzChart.html"/>...</script>.
In my directive I now only define the template I want to use in chartTemplate and everything works like a charm. Although I'm not sure if this doesn't cause memory leaks, but let's take a problem at a time.

How can I animate the movement of remaining ng-repeat items when one is removed?

I have a dynamic list of items using ng-repeat. When something happens an item may disappear. I have handled smoothly animating the removal of these items using ng-animate, but after they are gone, the remaining items simply snap to their new position. How can I animate this movement smoothly?
I've tried applying an "all" transition to the repeated class and using ng-move with no success.
You can achieve this by animating the max-height property. Check out this sample:
http://jsfiddle.net/k4sR3/8/
You will need to pick a sufficiently high value for max-height (in my sample, I used 90px). When an item is initially being added, you want it to start off with 0 height (I'm also animating left to have the item slide in from the left, as well as opacity, but you can remove these if they don't jibe with what you're doing):
.repeated-item.ng-enter {
-webkit-transition:0.5s linear all;
-moz-transition:0.5s linear all;
-o-transition:0.5s linear all;
transition:0.5s linear all;
max-height: 0;
opacity: 0;
left: -50px;
}
Then, you set the final values for these properties in the ng-enter-active rule:
.repeated-item.ng-enter.ng-enter-active {
max-height: 90px;
opacity: 1;
left: 0;
}
Item removal is a bit trickier, as you will need to use keyframe-based animations. Again, you want to animate max-height, but this time you want to start off at 90px and decrease it down to 0. As the animation runs, the item will shrink, and all the following items will slide up smoothly.
First, define the animation that you will be using:
#keyframes my_animation {
from {
max-height: 90px;
opacity: 1;
left: 0;
}
to {
max-height: 0;
opacity: 0;
left: -50px;
}
}
(For brevity, I'm omitting the vendor-specific definitions here, #-webkit-keyframes, #-moz-keyframes, etc - check out the jsfiddle above for the full sample.)
Then, declare that you will be using this animation for ng-leave as follows:
.repeated-item.ng-leave {
-webkit-animation:0.5s my_animation;
-moz-animation:0.5s my_animation;
-o-animation:0.5s my_animation;
animation:0.5s my_animation;
}
Basics
In case anyone is struggling with figuring out how to get AngularJS animations to work at all, here's an abbreviated guide.
First, to enable animation support, you will need to include an additional file, angular-animate.js, after you load up angular.js. E.g.:
<script type="text/javascript" src="angular-1.2/angular.js"></script>
<script type="text/javascript" src="angular-1.2/angular-animate.js"></script>
Next, you will need to load ngAnimate by adding it to the list of your module's dependencies (in the 2nd parameter):
var myApp = angular.module('myApp', ['ngAnimate']);
Then, assign a class to your ng-repeat item. You will be using this class name to assign the animations. In my sample, I used repeated-item as the name:
<li ng-repeat="item in items" class="repeated-item">
Then, you define your animations in the CSS using the repeated-item class, as well as the special classes ng-enter, ng-leave, and ng-move that Angular adds to the item when it is being added, removed, or moved around.
The official documentation for AngularJS animations is here:
http://docs.angularjs.org/guide/animations
TLDR: Jank is bad, do animations with transform. Check out this fiddle for css and demo.
Explanation
Note that animating height, max-height, top, ... is really bad performance wise because they cause reflows and thus jank (more information on html5rocks|high-performance-animations).
There is however a method getting this type of animation using only transforms by utilizing the sibling selector.
When elements are added there is one reflow because of the new item, all items below are transformed up so they stay at the same position and then the transformation is removed for a smooth slide-in.
In reverse when elements are removed they are transformed to the new position for a smooth slide-out and when the element is finally removed there is again one reflow and the transform is removed instantly so they stay at their position (this is also why it is important to only have transition set on ng-animate).
Alternatively to the example you could also do a transform: scaleY(0) on the deleted item and only transform: translateY() the siblings.
Caveat
Note that this snippet has trouble when multiple elements are removed in quick succession (before the previous animation has completed).
This can be fixed by having an animation time faster than the time a user takes to delete another item or by doing some more work on the animation (out of scope of this answer).
Finally some code
Note: apparently SO breaks the demo with multiple deletes - check out the fiddle to see it in work.
angular.module('app', ['ngAnimate'])
.controller('testCtrl', ['$scope', function($scope) {
var self = this;
self.items = [];
var i = 65;
for(; i < 72; i++)
{
self.items.push({ value: String.fromCharCode(i) });
}
self.addItem = function()
{
self.items.push({ value: String.fromCharCode(i) });
i++;
}
self.removeItemAt = function(index)
{
self.items.splice(index, 1);
}
}])
li
{
height: 48px;
width: 300px;
border: 1px solid lightgrey;
background-color: white;
position: relative;
list-style: none;
}
li.ng-enter,
li.ng-enter ~ li {
transform: translateY(-100%);
}
li.ng-enter.ng-enter-active,
li.ng-enter.ng-enter-active ~ li {
transform: translateY(0);
}
li.ng-animate {
z-index: -1;
}
li.ng-animate,
li.ng-animate ~ li {
transition: transform 0.6s;
}
li.ng-leave,
li.ng-leave ~ li {
transform: translateY(0);
}
li.ng-leave.ng-leave-active,
li.ng-leave.ng-leave-active ~ li {
transform: translateY(-100%);
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.23/angular-animate.js"></script>
<div ng-app="app" ng-controller="testCtrl as ctrl">
<ul>
<li ng-repeat="item in ctrl.items" ng-bind="item.value">
</li>
</ul>
<button ng-click="ctrl.addItem()">
Add
</button>
<button ng-click="ctrl.removeItemAt(5)">
Remove at 5
</button>
</div>

Resources