Animate dynamic height with AngularJS - css

So, I have been trying to animate kind of like a Angular Accordion, but with no success. I figured it out with fixed heights, but not with dynamic. height: auto; does not work. :(
Maybe some of you have had a similar problem?
My code:
html:
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular-route.js"></script>
<link rel="stylesheet" href="style.css">
<script src="script.js"></script>
</head>
<body>
<section ng-app="myApp">
<div ng-controller="myCtrl as vm">
<ul ng-init="vm.tab=1">
<li ng-repeat="item in vm.data">
<a href ng-click="vm.tab = item.thingy">{{item.name}}</a>
<div ng-show="vm.tab === item.thingy">
<img ng-src="{{item.img}}" width="50px"><br>
<div class="longDiv">{{item.description}}</div>
</div>
</li>
</ul>
</div>
</section>
</body>
</html>
js:
var app = angular.module('myApp', []);
app.controller('myCtrl', ['$scope',
function($scope) {
var vm = this;
vm.data = [{
name: "First",
title: "oneTitle",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur.",
year: "2013",
img: "http://static.hdw.eweb4.com/media/wp_400/1/5/42735.jpg",
thingy: 1
}, {
name: "third",
title: "twoTitle",
description: "Quisque pulvinar libero sed eros ornare",
year: "2014",
img: "http://static.hdw.eweb4.com/media/wp_400/1/1/8519.jpg",
thingy: 2
}, {
name: "Second",
title: "threeTitle",
description: "Cras accumsan ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur massa vitae tortor vehicula .",
year: "2015",
img: "http://static.hdw.eweb4.com/media/wp_400/1/5/43326.jpg",
thingy: 3
}, {
name: "fourth",
title: "FourTitle",
description: "Suspendisse ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur vitae mattis magna.",
year: "2011",
img: "http://static.hdw.eweb4.com/media/wp_400/1/5/42413.jpg",
thingy: 4
}];
}
]);
Thanks in advance!
Plnkr here

I've solved this problem by creating a "collapse" directive:
ngModule.directive('collapse', [function () {
return {
restrict: 'A',
link: function ($scope, ngElement, attributes) {
var element = ngElement[0];
$scope.$watch(attributes.collapse, function (collapse) {
var newHeight = collapse ? 0 : getElementAutoHeight();
element.style.height = newHeight +"px";
ngElement.toggleClass('collapsed', collapse);
});
function getElementAutoHeight() {
var currentHeight = getElementCurrentHeight();
element.style.height = 'auto';
var autoHeight = getElementCurrentHeight();
element.style.height = currentHeight +"px";
getElementCurrentHeight(); // Force the browser to recalc height after moving it back to normal
return autoHeight;
}
function getElementCurrentHeight() {
return element.offsetHeight
}
}
};
}]);
To use the directive, you just throw it on your element and set it to the scope variable that says whether or not to collapse it:
<div collapse="isCollapsed">
This will be collapsed
</div>
Now you just need to apply a CSS transition on the height and it will smoothly collapse & expand.
Since this directive also adds a class, you can apply other transitions like opacity.
Here's a Codepen with an example: http://codepen.io/KaidenR/pen/GoRJLx

Modified #kaiden answer, if using angular directive can use
require('./').directive('collapse', collapseDirective);
/**
* #ngInject
*/
function collapseDirective() {
return {
restrict: 'A',
link: link
};
function link($scope, ngElement, attributes) {
var element = ngElement[0];
// set the height as a data attr so we can use it again later
element.dataset.heightOld = angular.element(element).prop('offsetHeight');
$scope.$watch(attributes.collapse, function (collapse) {
var newHeight = !collapse ? 0 : element.dataset.heightOld;
element.style.height = newHeight + 'px';
ngElement.toggleClass('collapsed', collapse);
});
}
}
As the link function then just add CSS transitions for height. This sets a data attribute to store the height then uses it when toggling. In my application I used some markup like
<ion-item class="item-accordion" collapse="vm.isActive($index)">
Note this is inside an ng-repeat and calls a function on view model. This is also an ionic project hence the ion item tag.

You can try setting max-height to 0 and then remove the style/class as suggested here:
http://css3.bradshawenterprises.com/animating_height/

So, after long hours of trying to figure out what is the easiest and shortest way to make jQuery accordion with angular content, I figured one easy, but messy way.
Basically I made a new variable in my $scope- accordion, and put in there all accordion html:
vm.data = [{
name : "First",
accordion: ["<h3>Title1</h3><div>Content 1</div><h3>Title2</h3><div>Content 1</div>"]};
For me this works, because now I can use this variable in ng-repeat:
<div ng-repeat="item in vm.data>
<div ng-repeat="acc in item.accordion" ng-bind-html="acc | normalHtml"></div>
And as long there is only one string in this variable, I get one accordion div.
After that I called accordion function inside a setTimout function and everything works fine.
BUT this is messy and kind of "with a hammer where I could've used a feather" situation.
So, for now this is fine for me, but I am still looking for a prettier solution.
Thank you all for helping me out. If some of you have any idea how to make this better, I welcome every answer :)

Ok I think I found the holy grail. At least for my uses :)
I was having trouble using nested collapsed elements with the solution from #Kaiden and #Gareth Fuller, as any outer collapse element would keep the original height that it got before any inner collapse elements were collapsed. So I got inspired from #Icycool's suggestion and changed to animate on max-height, and keep height set to 'auto', which fixes that.
You can run a working demo below.
var testModule = angular.module('testModule', []);
testModule.controller('testController', function($scope) {
$scope.isCollapsed = false;
$scope.toggle = function() {
$scope.isCollapsed = !$scope.isCollapsed;
};
});
testModule.directive('collapse', [function() {
return {
restrict: 'A',
link: function link($scope, $element, $attrs) {
var element = $element[0];
$element.addClass('collapse');
const originalHeight = $element.prop('offsetHeight');
$scope.$watch($attrs.collapse, function(collapse) {
if (collapse) {
element.style.maxHeight = '0px';
} else {
element.style.maxHeight = originalHeight + 'px';
}
$element.toggleClass('sh-collapsed', collapse);
});
}
};
}]);
.collapse {
height: 'auto';
overflow: hidden;
transition: max-height 0.3s;
background: lightgreen;
}
<script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.4/angular.min.js"></script>
<div ng-app="testModule" ng-controller="testController">
<button type="button" ng-click="toggle()">Toggle</button> Collapsed: {{isCollapsed}}
<ul collapse="isCollapsed">
<li>Line One</li>
<li>Line Two</li>
<li>Line Three</li>
<li>Line Four</li>
<li>Line Five</li>
</ul>
</div>

Related

Next JS Error product.map Is Not a Function

Hello everyone i have a question according to my case, i have
index.js code bellow :
export default function Home({ productList }) {
return (
<div className={styles.container}>
<Head>
<title>OlResto UI Design With Next JS</title>
<meta name="description" content="This is stunning website build using next js" />
<link rel="icon" href="/favicon.ico" />
</Head>
<Featured />
<ProductList productList={ productList } />
</div>
);
};
export const getServerSideProps = async () => {
const res = await axios.get("http://localhost:3000/api/products");
return {
props: {
productList: res.data
}
}
};
the second one i have file call ProductList.jsx
code below:
const ProductList = ({ productList }) => {
return (
<div className={styles.container}>
<h1 className={styles.title}>THE BEST PIZZA IN TOWN</h1>
<p className={styles.desc}>Lorem ipsum dolor sit amet consectetur adipisicing elit. Sint, nemo at, labore porro reiciendis rem ad tempora blanditiis adipisci a eum animi obcaecati, dignissimos non perspiciatis natus corrupti consectetur libero.</p>
<div className={styles.wrapper}>
{productList.map(product => (
<ProductCard key={product._id} product={product} />
))}
</div>
</div>
);
};
export default ProductList;
Those files produce an error like this :
TypeError: productList.map is not a function
<div className={styles.wrapper}>
{productList.map(product => (
^
<ProductCard key={product._id} product={product} />
))}
</div>
Sorry if my question messed up, hope you guys can help my problem, thank you in advance

Specifying alternate background image using ngStyle

I am trying to specify an alternate background image for a DIV like so:
<div [ngStyle]="{'background-image':'url(1.jpg), url(2.jpg)'}"></div>
Neither of the images are displaying (it works if I don't specify an alternate image).
Is it possible to specify multiple background images using ngStyle?
Working demo
Template file
<div [ngStyle]='styles'>
<h1>Lorem Ipsum Dolor</h1>
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.</p>
</div>
Ts file
export class AppComponent {
name = 'Angular';
img1 = 'https://www.w3schools.com/css/img_flwr.gif';
img2 = 'https://www.w3schools.com/css/paper.gif'
isSelected: boolean = true;
styles = {};
setCurrentStyles() {
this.styles = {
backgroundImage: this.isSelected ?
`url(${this.img1})`:`url(${this.img2})`
};
}
toogleImage() {
this.isSelected = !this.isSelected;
this.setCurrentStyles();
}
}
Try like this
template.html
<div class="image" [ngStyle]="{background: !isActive ? 'url(https://www.fonewalls.com/wp-content/uploads/Aqua-Solid-Color-Background-Wallpaper-for-Mobile-Phone.png)' : 'url(https://www.fonewalls.com/wp-content/uploads/Midnight-Blue-Solid-Color-Background-Wallpaper-for-Mobile-Phone.png)'}"></div>
cmponent.ts
isActive: boolean = true;
You can also keep your HTML clean with moving all the logic into the component.ts.
In the end you would have something like this:
<div class="image" [ngStyle]="{
'background-image': 'url(' + backgroundImageString + ')'
}"></div>
Then in your component:
private defineBackImage(someArg) {
if (stuff) {
this.backgroundImageString = url1;
} else {
this.backgroundImageString = url2;
}
}
You can trigger this function on init of according to specific events, also you can extend this logic to display much more than 2 backgrounds

Polymer 3 - Google Maps

I would like to know how to include Google Map in Polymer 3. I have just upgraded to Polymer 3 from Polymer 2. This is my sample that is not working base of the starter-kit
import { PolymerElement, html } from '#polymer/polymer/polymer-element.js';
import '#em-polymer/google-map/google-map.js';
import '#em-polymer/google-map/google-map-marker.js';
import './shared-styles.js';
class MyView1 extends PolymerElement {
static get template() {
return html`
<style include="shared-styles">
:host {
display: block;
padding: 10px;
}
google-map {
height: 600px;
width: 600px;
}
</style>
<google-map latitude="37.779" longitude="-122.3892" min-zoom="9" max-zoom="11" language="en" api-key="XYZ">
</google-map>
<div class="card">
<div class="circle">1</div>
<h1>View One</h1>
<p>Ut labores minimum atomorum pro. Laudem tibique ut has.</p>
<p>Lorem ipsum dolor sit amet, per in nusquam nominavi periculis, sit elit oportere ea.Lorem ipsum dolor sit amet, per in nusquam nominavi periculis, sit elit oportere ea.Cu mei vide viris gloriatur, at populo eripuit sit.</p>
</div>
`;
}
}
window.customElements.define('my-view1', MyView1);
I get the following 2 errors:
element-mixin.js:322 template getter must return HTMLTemplateElement
and
Uncaught TypeError: Cannot read property 'api' of undefined
at HTMLElement._initGMap (google-map.js:480)
at HTMLElement.attached (google-map.js:457)
at HTMLElement.attached (class.js:262)
at HTMLElement.connectedCallback (legacy-element-mixin.js:117)
at HTMLElement._attachDom (element-mixin.js:653)
at HTMLElement._readyClients (element-mixin.js:620)
at HTMLElement._flushClients (property-effects.js:1749)
at HTMLElement.ready (property-effects.js:1853)
at HTMLElement.ready (element-mixin.js:604)
at HTMLElement._enableProperties (properties-changed.js:363)
#em-polymer/google-map/google-map.js was
import { Polymer } from '../../#polymer/polymer/lib/legacy/polymer-fn.js';
import { IronResizableBehavior } from '../../#polymer/iron-resizable-behavior/iron-resizable-behavior.js';
import '../google-apis/google-maps-api.js';
import './google-map-marker.js';
Polymer({
_template: `
<style>
and fix is
import { Polymer } from '../../#polymer/polymer/lib/legacy/polymer-fn.js';
import { IronResizableBehavior } from '../../#polymer/iron-resizable-behavior/iron-resizable-behavior.js';
import '../google-apis/google-maps-api.js';
import './google-map-marker.js';
import { html } from '#polymer/polymer/lib/utils/html-tag.js';
Polymer({
_template: html`
<style>

CSS selector for label without child elements

First of all the solution in that question: CSS selector to bold only labels without child elements does not solve my issue.
I have labels with text only and others with text and child input, select and textarea form's elements.
i.e:
<label class="control-label">State
<select name="status" id="status" class="form-control">
<option value="">Choose..</option>
...
</select>
</label>
and other like:
<label for="defect" class="control-label">Error</label>
I need to set white-space: nowrap to labels that has no child HTML elements only and as the above question answers stated, I tried the following:
label{
white-space: nowrap; /* Label without HTML*/
}
label + label {
white-space: normal; /* Label with HTML */
}
However, it does not work.
One solution would be adding a class to the element and using CSS to format it accordingly.
label.empty {
white-space: nowrap;
}
Link to the documentation: https://developer.mozilla.org/en-US/docs/Web/CSS/Class_selectors
The other comment points to using :empty but in your case the <label> contains some text and doesn't apply as empty
AFAIK there is no solution using CSS selectors. The solution proposed by #FabioPontes to control via an additional class name would be the most straight-forward.
Following is a javascript solution that verifies for an element's child nodes and applies a white-space:nowrap if (1) there is only one child node and (2) this node is of type text. Please view node types.
var elements = document.getElementsByClassName("control-label");
for (i = 0; i < elements.length; i++) {
if (elements[i].childNodes.length == 1 && elements[i].childNodes[0].nodeType == 3) {
elements[i].style.whiteSpace = "nowrap";
}
}
<div>
<label class="control-label">State - we need a really long text to check if the white-space nowrap is actually working so lets place this text here.
<select name="status" id="status" class="form-control">
<option value="">Choose..</option>
</select>
</label>
</div>
<div style="margin-top:20px;">
<label for="defect" class="control-label">Error - we need a really long text to check if the white-space nowrap is actually working so lets place this text here.</label>
</div>
The option to add a class has already been suggested by Fabio Pontes and is a good one, but if you don't want to add classes, here are a couple options.
The first thing you could do is modify your markup to wrap each label in a div and then leverage the :only-child pseudo selector. In order for this to work, you'll have to include the select element as a sibling of the label, rather than as a child of it.
.control-label-wrapper label:only-child {
white-space: nowrap;
}
<div class="control-label-wrapper">
<label class="control-label">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ut tellus massa. Phasellus dictum mollis lobortis.</label>
<select name="status" id="status" class="form-control">
<option value="">Choose..</option>
</select>
</div>
<div class="control-label-wrapper">
<label for="defect" class="control-label">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ut tellus massa. Phasellus dictum mollis lobortis.</label>
</div>
Another option which may not require modifying your makup at all is to use an attribute selector. Perhaps you're already using an attribute for all these childless labels. The example HTML in your question suggests you may be.
label[for="defect"] {
white-space: nowrap;
}
<label class="control-label">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ut tellus massa. Phasellus dictum mollis lobortis.
<select name="status" id="status" class="form-control">
<option value="">Choose..</option>
</select>
</label>
<label for="defect" class="control-label">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ut tellus massa. Phasellus dictum mollis lobortis.</label>

Does deep nesting flexbox layout cause performance issue?

I have been working on a ReactJS project where I create most of the components using flexbox layout. Since with react, we can have deeply nested components, so my layout is having nested flexbox layout.
Now my question is, does this have any issue with performance? On a single page, there are many components and each component have 3 to 4 level nested flexbox layout. Will that cause a performance issue?
Have done a little test. Rendered 100 components, each with 10 nested layout. With and without flexbox. Here are the code snippets:
Component/index.js
#CSSModules(styles, { allowMultiple: true })
export default class TheComponent extends Component {
render() {
const { deepNest, flex } = this.props
return (
<div>{ this.renderComp(deepNest, flex) }</div>
)
}
renderComp(deepNest, flex) {
const flexProperties = [
{ justifyContent: "center", alignItems: "center" },
{ justifyContent: "flex-start", alignItems: "flex-end" },
{ flexDirection: "row" }
]
const content = [
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus interdum quis ligula vel elementum. Integer non rhoncus purus, eget dignissim ante.",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus interdum quis ligula vel elementum. Integer non rhoncus purus, eget dignissim ante. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus interdum quis ligula vel elementum. Integer non rhoncus purus, eget dignissim ante."
]
if (deepNest > 0 && flex) {
return (
<div styleName="containerFlex" style={flexProperties[deepNest % 3]}>
<div styleName="contentFlex" style={flexProperties[deepNest % 3]}>
{ content[deepNest % 3] }
</div>
<div styleName="nestedFlex" style={flexProperties[deepNest % 3]}>
{ this.renderComp(deepNest - 1, flex) }
</div>
</div>
)
}
if (deepNest > 0 && !flex) {
return (
<div styleName="container">
<div styleName="content">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus interdum quis ligula vel elementum. Integer non rhoncus purus, eget dignissim ante.
</div>
<div styleName="nested">
{ this.renderComp(deepNest - 1, flex) }
</div>
</div>
)
}
}
}
WithFlex/index.js
import TheComponent from "../Component"
#CSSModules(styles, { allowMultiple: true })
export default class WithFlex extends Component {
constructor(props) {
super(props)
this.state = { render: false }
}
render() {
const {render} = this.state
// number of components to render
const arr = _.range(100)
return (
<div>
<div
style={{ display: "block", padding: 30, lineHeight: "60px" }}
onClick={() => this.setState({render: !render})}>
Start Render
</div>
{ render && arr.map((i) => <TheComponent key={i} deepNest={10} flex={true}/> ) }
</div>
)
}
}
WithoutFlex/index.js
import TheComponent from "../Component"
#CSSModules(styles, { allowMultiple: true })
export default class WithoutFlex extends Component {
constructor(props) {
super(props)
this.state = { render: false }
}
render() {
const {render} = this.state
// number of components to renders
const arr = _.range(100)
return (
<div>
<div
style={{ display: "block", padding: 30, lineHeight: "60px" }}
onClick={() => this.setState({render: !render})}>
Start Render
</div>
{ render && arr.map((i) => <TheComponent key={i} deepNest={10} flex={false}/> ) }
</div>
)
}
}
Results from Chrome dev-tool timeline.
WithFlex
WithoutFlex
Summary
The difference is not that much. Also in flexbox, I put random properties to choose from. So I think it's alright with the performance. Hope it will help other devs.
Old flexbox (display: box) is 2.3x slower than new flexbox (display: flex).
Regular block layout (non-float), will usually be as fast or faster than new flexbox since it’s always single-pass. But new flexbox should be faster than using tables or writing custom JS-base layout code.
For more info
Article1
Article2

Resources