Dynamically Change CSS Class in Ember - css

Super, super new to Ember, so apologies if this is straight forward. I want to know the best way to dynamically change properties in a CSS class rendered within a component.
I made a component, like so:
//route_template.hbs
{{map-view point=model}}
And I pass through point, which has two coordinate properties: model.xCoordinate, and model.yCoordinate.
Here's my component template. You can see I am currently using this hacky inline styling to style the point location on the page:
//component_template.hbs
{{#each point as |mapPoint|}}
<i class="point-icon" style={{html-safe (concat 'left:' mapPoint.xCoordinate 'px; top:' mapPoint.yCoordinate 'px;')}}></i>
{{/each}}
I know: gross. Is there a better way to do this? Should I make a handlebars helper? An action? If someone could just point me in the general direction, I can take it from there. Thanks so much!

have a look at my answer here.
probably ember-css-properties is what you're looking for. Another option is a helper or a computed property.
A helper could offer this API:
<i class="point-icon" style={{build-css
left=(concat mapPoint.xCoordinate 'px')
top=(concat mapPoint.yCoordinate 'px')
}}></i>
or even assume px by default:
<i class="point-icon" style={{build-css
left=concat mapPoint.xCoordinate
top=concat mapPoint.yCoordinate
}}></i>
if you want to use a computed property you would do this in your template:
<i class="point-icon" style={{myComputedStyle}}></i>
and this in your style
myComputedStyle: computed('mapPoint.{xCoordinate,yCoordinate}', {
get() {
return htmlSafe(`
left: ${xCoordinate}px;
top: ${yCoordinate}px;
`);
}
}),
be careful: with all of theese (except ember-css-properties) you need to understand the implications:
If the user can manipulate mapPoint.xCoordinate or mapPoint.yCoordinate to an unexpected value you may open a security hole!

You don't need to use the concat helper in <> type elements. You can do this instead
<i class="point-icon" style="left: {{mapPoint.xCoordinate}}px; top: {{mapPoint.yCoordinate}}px;"></i>
For {{}} blocks though you'll need to use concat
{{my-component style=(concat 'left: ' mapPoint.xCoordinate 'px; top: ' mapPoint.yCoordinate 'px;')}}
Also, if I'm correct, you only need to do html safe if you have markups in your string.
See https://guides.emberjs.com/v3.1.0/templates/writing-helpers/#toc_escaping-html-content

Related

How to assign dynamic variables to CSS content in Vue.js?

Apologies if this shows how much of a novice I am, but I'd like to know more about dynamic variables and CSS in Vue. I'd like to create a system where each time a button is pressed, the letters of the button label become further apart.
Inside a component is it possible to use a counter script such as:
<script>
export default {
name: 'Counter',
data() {
return {
count: 3,
}
},
methods: {
intrement() {
this.count += 1;
}
}
}
</script>
And then use the count integer value to change CSS text spacing for example?
So that in the template, I could use:
<template>
<header>
<div>
<button class="header_button" style="letter-spacing: `v-bind(count) + ch`;">MYBUTTON</button>
</div>
</header>
</template>
I appreciate this is a strange and specific example, but if anyone could give me some feedback as to why this doesn't work, as well as suggestions on how I could achieve this I'd be super appreciative.
In that case, you can directly use the following
<button :style="`letter-spacing: ${count}ch;`">
Here is a playground.
PS: :style is a shorthand for v-bind:style as explained here.
v-bind for CSS (mixing script + style) is also a thing.
Here, you're only using script + template combo, so an interpolation is enough.

Is there a better way to do this in handlebars?

Im using handlebars to spit out some static pages using a partial like so:
{{> component/card size="small" title="Card Title" }}
Now depending on the "size" of the card required, i need to set some different tailwind classes. Im currently doing it like so, but there must be a better way? Adding the class to the container and writing css is not an option.
{{setVariable "additionalHeadingClass" "text-5 md:text-6 mb-4"}}
{{#ifEquals size "small"}}
{{setVariable "additionalHeadingClass" "text-4 mb-1"}}
{{/ifEquals}}
{{#ifEquals size "large"}}
{{setVariable "additionalHeadingClass" "text-4 sm:text-5 md:text-8 mb-4"}}
{{/ifEquals}}
<h3 class="text-primary font-bold {{#root.additionalHeadingClass}}">{{title}}</h3>
and heres the helper im using:
Handlebars.registerHelper("setVariable", function (varName, varValue, options) {
if (!options.data.root) {
options.data.root = {};
}
options.data.root[varName] = varValue;
});
My opinion is that there is too much code in your template for what it actually does. Despite the intimidating number of lines, we really just want to map a size to a string of class names. I would also advise against the setVariable because I find it harder to think about when we creating a side-effect by manipulating a variable on our context object. I would much prefer a more functional-style helper, where we just give it the size and it returns the class names string.
I would create such a helper using a simple switch:
Handlebars.registerHelper('additionalClasses', function(size) {
switch (size) {
case 'large':
return 'text-4 sm:text-5 md:text-8 mb-4';
case 'small':
return 'text-4 mb-1';
default:
return 'text-5 md:text-6 mb-4';
}
});
And then we may reduce our template to the much simpler:
<h3 class="text-primary font-bold {{additionalClasses size}}">{{title}}</h3>
I have created a fiddle for reference.

Vue JS CSS style binding

I am trying to bind CSS styling to vuejs tags. I dont seem to be able to get it working.
Below is the code which i am trying. Can anyone help me out with this? I am not able to get the Styling to work. I am trying this as well as binding based on conditional. Both doesnt seem to work. Can anyone help me with this? I have tried all the ways i could find on stackoverflow, none of them seem to work for me. Can any one help me if i am doing something wrong?
<b-table
class="PatientTable"
borderless
hover
v-on:row-clicked="redirectToPatientView"
:items="users"
:fields="fields"
:current-page="currentPage"
:per-page="perPage"
id="tableData"
>
<template v-for="key1 in fields" v-slot:[`cell(${key1})`]="{ value }" id="tableData" >
<b class="patientData" id="tableData" v-bind:key="key1" v-bind:style="'{font-size:200px;}'">{{ value }}</b>
</template>
When you bind a style, pass in an object instead of a string of an object.
<!-- Instead of: -->
<b :style="'{font-size:200px;}'">{{ value }}</b>
<!-- Do: -->
<b :style="{ 'font-size' : '200px' }">{{ value }}</b>
Notice that, in the second line, the object is placed directly into the double-quotes, without its own set of single-quotes. The contents of those double-quotes are straight up JavaScript, so you don't have to escape the object in them. What you're essentially trying to do is along these lines:
<b :style="styleBinding">{{ value }}</b>
<script>
export default {
data: function() {
return {
styleBinding: {
'font-size': '200px',
'margin-top': '5em',
'other-css-property': 'value'
}
}
}
}
</script>
It's just that, since you're only using a single property, it's a little cleaner to do in-line in the template.
use :class binding instead?
<component :class="{'your-classname' : condition}">
Since inline styling is not really advisable. https://v2.vuejs.org/v2/guide/class-and-style.html

Change CSS class's property on click

I've read around a little bit and have a good start to what I ultimately want. This was helpful, along with another article which I forgot the link to. However, everything I've read ADDS a CSS class or property to an element. I want to CHANGE a property of an existing CSS class, but I don't know how to target it.
I think I want to use ng-class in one of these use cases taken from the Angular documentation:
If the expression evaluates to a string, the string should be one or more space-delimited class names.
If the expression evaluates to an object, then for each key-value pair of the object with a truthy value the corresponding key is used as a class name.
My existing code uses ng-class along with some controller logic.
HTML
<div ng-controller="ngToggle">
<div ng-class="{'inset-gray-border' : style}">
<div class="subcontainer" ng-click="toggleStyle()">{{item.name}}</div>
</div>
</div>
This currently adds the inset-gray-border class to the nested div, but I just want to change the border property in the subcontainer class.
Controller
angular.module('app').controller('ngToggle', ['$scope', function($scope) {
$scope.style = false;
$scope.toggleStyle = function() {
$scope.style = $scope.style === false ? true: false;
};
}]);
I considered using a directive, but I believe that would be overkill. I think this can be achieved in a controller.
EDIT: After further research I think jQLite can do the trick, but that would probably require a directive.
CHANGE a property of an existing CSS class
Add a css rule that does that using the new class you added using ng-class. The specificity will over ride the original rule
.subcontainer{
color : blue
}
.inset-gray-border .subcontainer{
color:red
}
Instead of a big toggleStyle function, you can write that stuff in UI side only.
Here is fiddle. As you want to change border property of .subcontainer, Overwrite that property by adding .insert-gray-border
<div ng-controller="ngToggle">
<div >
<div ng-class="{'subcontainer':true,'inset-gray-border' : style}" ng-click="style=!style">{{item.name}}</div>
</div>
</div>
The benifit of this is , it uses local scope instead of controller scope.
The best bet would be to have two CSS classes defined, one for the base (untoggled) case, another with all the properties that you want for when the property is toggled on.
In this case you may want something like:
.container .subcontainer {}
.container .subcontainer-bordered { border: solid 1px #123456}
Then your HTML code be updated to reflect this structure
<div ng-controller="ngToggle">
<div class="container">
<div class="subcontainer" ng-class="{'subcontainer-bordered': style}" ng-click="style = !style">{{item.name}}</div>
</div>
</div>

How do I conditionally apply CSS styles in AngularJS?

Q1. Suppose I want to alter the look of each "item" that a user marks for deletion before the main "delete" button is pressed. (This immediate visual feedback should eliminate the need for the proverbial "are you sure?" dialog box.) The user will check checkboxes to indicate which items should be deleted. If a checkbox is unchecked, that item should revert back to its normal look.
What's the best way to apply or remove the CSS styling?
Q2. Suppose I want to allow each user to personalize how my site is presented. E.g., select from a fixed set of font sizes, allow user-definable foreground and background colors, etc.
What's the best way to apply the CSS styling the user selects/inputs?
Angular provides a number of built-in directives for manipulating CSS styling conditionally/dynamically:
ng-class - use when the set of CSS styles is static/known ahead of time
ng-style - use when you can't define a CSS class because the style values may change dynamically. Think programmable control of the style values.
ng-show and ng-hide - use if you only need to show or hide something (modifies CSS)
ng-if - new in version 1.1.5, use instead of the more verbose ng-switch if you only need to check for a single condition (modifies DOM)
ng-switch - use instead of using several mutually exclusive ng-shows (modifies DOM)
ng-disabled and ng-readonly - use to restrict form element behavior
ng-animate - new in version 1.1.4, use to add CSS3 transitions/animations
The normal "Angular way" involves tying a model/scope property to a UI element that will accept user input/manipulation (i.e., use ng-model), and then associating that model property to one of the built-in directives mentioned above.
When the user changes the UI, Angular will automatically update the associated elements on the page.
Q1 sounds like a good case for ng-class -- the CSS styling can be captured in a class.
ng-class accepts an "expression" that must evaluate to one of the following:
a string of space-delimited class names
an array of class names
a map/object of class names to boolean values
Assuming your items are displayed using ng-repeat over some array model, and that when the checkbox for an item is checked you want to apply the pending-delete class:
<div ng-repeat="item in items" ng-class="{'pending-delete': item.checked}">
... HTML to display the item ...
<input type="checkbox" ng-model="item.checked">
</div>
Above, we used ng-class expression type #3 - a map/object of class names to boolean values.
Q2 sounds like a good case for ng-style -- the CSS styling is dynamic, so we can't define a class for this.
ng-style accepts an "expression" that must evaluate to:
an map/object of CSS style names to CSS values
For a contrived example, suppose the user can type in a color name into a texbox for the background color (a jQuery color picker would be much nicer):
<div class="main-body" ng-style="{color: myColor}">
...
<input type="text" ng-model="myColor" placeholder="enter a color name">
Fiddle for both of the above.
The fiddle also contains an example of ng-show and ng-hide. If a checkbox is checked, in addition to the background-color turning pink, some text is shown. If 'red' is entered in the textbox, a div becomes hidden.
I have found problems when applying classes inside table elements when I had one class already applied to the whole table (for example, a color applied to the odd rows <myClass tbody tr:nth-child(even) td>). It seems that when you inspect the element with Developer Tools, the element.style has no style assigned. So instead of using ng-class, I have tried using ng-style, and in this case, the new CSS attribute does appear inside element.style. This code works great for me:
<tr ng-repeat="element in collection">
[...amazing code...]
<td ng-style="myvar === 0 && {'background-color': 'red'} ||
myvar === 1 && {'background-color': 'green'} ||
myvar === 2 && {'background-color': 'yellow'}">{{ myvar }}</td>
[...more amazing code...]
</tr>
Myvar is what I am evaluating, and in each case I apply a style to each <td> depending on myvar value, that overwrites the current style applied by the CSS class for the whole table.
UPDATE
If you want to apply a class to the table for example, when visiting a page or in other cases, you can use this structure:
<li ng-class="{ active: isActive('/route_a') || isActive('/route_b')}">
Basically, what we need to activate a ng-class is the class to apply and a true or false statement. True applies the class and false doesn't. So here we have two checks of the route of the page and an OR between them, so if we are in /route_a OR we are in route_b, the active class will be applied.
This works just having a logic function on the right that returns true or false.
So in the first example, ng-style is conditioned by three statements. If all of them are false, no style is applied, but following our logic, at least one is going to be applied, so, the logic expression will check which variable comparison is true and because a non empty array is always true, that will left an array as return and with only one true, considering we are using OR for the whole response, the style remaining will be applied.
By the way, I forgot to give you the function isActive():
$rootScope.isActive = function(viewLocation) {
return viewLocation === $location.path();
};
NEW UPDATE
Here you have something I find really useful. When you need to apply a class depending on the value of a variable, for example, an icon depending on the contents of the div, you can use the following code (very useful in ng-repeat):
<i class="fa" ng-class="{ 'fa-github' : type === 0,
'fa-linkedin' : type === 1,
'fa-skype' : type === 2,
'fa-google' : type === 3 }"></i>
Icons from Font Awesome
This works well when ng-class can't be used (for example when styling SVG):
ng-attr-class="{{someBoolean && 'class-when-true' || 'class-when-false' }}"
(I think you need to be on latest unstable Angular to use ng-attr-, I'm currently on 1.1.4)
I have published an article on working with AngularJS+SVG. It talks about this issue and numerous others. http://www.codeproject.com/Articles/709340/Implementing-a-Flowchart-with-SVG-and-AngularJS
span class="circle circle-{{selectcss(document.Extension)}}">
and code
$scope.selectcss = function (data) {
if (data == '.pdf')
return 'circle circle-pdf';
else
return 'circle circle-small';
};
css
.circle-pdf {
width: 24px;
height: 24px;
font-size: 16px;
font-weight: 700;
padding-top: 3px;
-webkit-border-radius: 12px;
-moz-border-radius: 12px;
border-radius: 12px;
background-image: url(images/pdf_icon32.png);
}
This solution did the trick for me
<a ng-style="{true: {paddingLeft: '25px'}, false: {}}[deleteTriggered]">...</a>
You can use ternary expression. There are two ways to do this:
<div ng-style="myVariable > 100 ? {'color': 'red'} : {'color': 'blue'}"></div>
or...
<div ng-style="{'color': (myVariable > 100) ? 'red' : 'blue' }"></div>
Another option when you need a simple css style of one or two properties:
View:
<tr ng-repeat="element in collection">
[...amazing code...]
<td ng-style="{'background-color': getTrColor(element.myvar)}">
{{ element.myvar }}
</td>
[...more amazing code...]
</tr>
Controller:
$scope.getTrColor = function (colorIndex) {
switch(colorIndex){
case 0: return 'red';
case 1: return 'green';
default: return 'yellow';
}
};
See the following example
<!DOCTYPE html>
<html ng-app>
<head>
<title>Demo Changing CSS Classes Conditionally with Angular</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
<script src="res/js/controllers.js"></script>
<style>
.checkboxList {
border:1px solid #000;
background-color:#fff;
color:#000;
width:300px;
height: 100px;
overflow-y: scroll;
}
.uncheckedClass {
background-color:#eeeeee;
color:black;
}
.checkedClass {
background-color:#3ab44a;
color:white;
}
</style>
</head>
<body ng-controller="TeamListCtrl">
<b>Teams</b>
<div id="teamCheckboxList" class="checkboxList">
<div class="uncheckedClass" ng-repeat="team in teams" ng-class="{'checkedClass': team.isChecked, 'uncheckedClass': !team.isChecked}">
<label>
<input type="checkbox" ng-model="team.isChecked" />
<span>{{team.name}}</span>
</label>
</div>
</div>
</body>
</html>
As of AngularJS v1.2.0rc, ng-class and even ng-attr-class fail with SVG elements (They did work earlier, even with normal binding inside the class attribute)
Specifically, none of these work now:
ng-class="current==this_element?'active':' ' "
ng-attr-class="{{current==this_element?'active':' '}}"
class="class1 class2 .... {{current==this_element?'active':''}}"
As a workaround, I've to use
ng-attr-otherAttr="{{current==this_element?'active':''}}"
and then style using
[otherAttr='active'] {
... styles ...
}
One more (in the future) way to conditionally apply style is by conditionally creating scoped style
<style scoped type="text/css" ng-if="...">
</style>
But nowadays only FireFox supports scoped styles.
There is one more option that I recently discovered that some people may find useful because it allows you to change a CSS rule within a style element - thus avoiding the need for repeated use of an angular directive such as ng-style, ng-class, ng-show, ng-hide, ng-animate, and others.
This option makes use of a service with service variables which are set by a controller and watched by an attribute-directive I call "custom-style". This strategy could be used in many different ways, and I attempted to provide some general guidance with this fiddle.
var app = angular.module('myApp', ['ui.bootstrap']);
app.service('MainService', function(){
var vm = this;
});
app.controller('MainCtrl', function(MainService){
var vm = this;
vm.ms = MainService;
});
app.directive('customStyle', function(MainService){
return {
restrict : 'A',
link : function(scope, element, attr){
var style = angular.element('<style></style>');
element.append(style);
scope.$watch(function(){ return MainService.theme; },
function(){
var css = '';
angular.forEach(MainService.theme, function(selector, key){
angular.forEach(MainService.theme[key], function(val, k){
css += key + ' { '+k+' : '+val+'} ';
});
});
style.html(css);
}, true);
}
};
});
well i would suggest you to check condition in your controller with a function returning true or false .
<div class="week-wrap" ng-class="{today: getTodayForHighLight(todayDate, day.date)}">{{day.date}}</div>
and in your controller check the condition
$scope.getTodayForHighLight = function(today, date){
return (today == date);
}
One thing to watch is - if the CSS style has dashes - you must remove them. So if you want to set background-color, the correct way is:
ng-style="{backgroundColor:myColor}"
Here's how i conditionally applied gray text style on a disabled button
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
styleUrls: [ './app.component.css' ],
template: `
<button
(click)='buttonClick1()'
[disabled] = "btnDisabled"
[ngStyle]="{'color': (btnDisabled)? 'gray': 'black'}">
{{btnText}}
</button>`
})
export class AppComponent {
name = 'Angular';
btnText = 'Click me';
btnDisabled = false;
buttonClick1() {
this.btnDisabled = true;
this.btnText = 'you clicked me';
setTimeout(() => {
this.btnText = 'click me again';
this.btnDisabled = false
}, 5000);
}
}
Here's a working example:
https://stackblitz.com/edit/example-conditional-disable-button?file=src%2Fapp%2Fapp.component.html

Resources