Setting class values in Angular template - angularjs

I'm a bit of an Angular novice an I'm struggling with something I thought would be very simple.
I have a template that is used in a read only version of a form in my app. This template displays a status field that would be styled (text/background colour) based on its value, e.g
'New issue' - orange
'In progress' - green
'Overdue' - red
etc...
My css is something like;
.status-bar {padding: 3px; border: solid 1px #333}
.new-issue {background-color: orange; color: #000}
.in-progress {background-color: green; color: #fff}
.overdue {background-color: red; color: #fff}
The issue status field is available through the controller and i use code something like this to display it.
<div class="status-bar">{{issue.status}}</div>
All works fine.
I then naively tried to simply insert the class name as an expression, e.g
<div class="status-bar {{issue.status}}">{{issue.status}}</div>
Thinking that would give me a this kind of output..
<div class="status-bar overdue">overdue</div>
But it doesn't work. I've looked at the ng-class directive, and other options but can't work this out.
Ideally I need a solution that would allow me to append/insert a class name based on a value (not a true/false like ng-class). So I'd like my oputput HTML to be like the following...
<div class="status-bar in-progress">In Progress</div>
OR
<div class="status-bar new-issue">New Issue</div>
OR
<div class="status-bar overdue">overdue</div>
etc...
The range of status values may change so my class names must be calculated as per the above pattern
e.g,
<div class="status-bar another-status">Another Status</div>
So I need a way to hyphenate and lowercase my issue.status value and then add it as a class name. I presume a directive is the way forward, which would be ideal so I can use it in other views.
This is so easy to do after the fact in jQuery etc..., but I can't see the 'Angular' way to do it?!
Many thanks upfront for any help provided...

ng-class with a custom filter is what you are looking for...
HTML
<h1 ng-class="class1">{{class1 | titleCase}}</h1>
JS
app.filter('titleCase', function () {
return function (input) {
var words = input.split('-');
for (var i = 0; i < words.length; i++) {
words[i] = words[i].charAt(0).toUpperCase() + words[i].slice(1);
}
return words.join(' ');
}
});
and here is working PLUNKER...

ng-class should work :
<div class="status-bar" ng-class="issue.status">{{issue.status}}</div>
Working fiddle : http://jsfiddle.net/w2SFr/2/

What You are looking for is probably http://docs.angularjs.org/api/ng/directive/ngClass. You may also want to look at AngularJS ngClass conditional

Related

Customizing Day Cell content in FullCalendar

I am using fullcalendar with react. I am trying to customize the dayGrid view. According to the Content Injection docs for react I can use custom content for the rendering of both the date and the header cells. The dayCellContent "hook" states that:
Generated content is inserted inside the inner-most wrapper of the day cell. It does not replace the cell.
I've provided an implementation for the dayCellContent and noticed that my content gets injected into the following structure:
<td class="fc-daygrid-day fc-day fc-day-wed fc-day-past rot_time-off_day-cell" data-date="2021-04-07">
<div class="fc-daygrid-day-frame fc-scrollgrid-sync-inner">
<div class="fc-daygrid-day-top">
<a class="fc-daygrid-day-number">
...custom content goes here
</a>
</div>
<div class="fc-daygrid-day-events"></div>
<div class="fc-daygrid-day-bg"></div>
</div>
</td>
Now, the problem is that this structure lets you insert content ONLY in the upper right corner of the date cell due to the positioning of the element. Furthermore, it is in an anchor element.
Example:
function renderDayCell(dayCellContent: DayCellContentArg) {
return (
<div>
{dayCellContent.dayNumberText}
</div>
);
}
Is there a clean way to customize the whole content of the cell somehow? I've seen a couple of sites using fullcalendar that have their content inserted directly into the td. Not sure if this is version dependent or they're using the alternative JS approach based on domNodes or html. I am using version 5.6.0 of fullcalendar.
I had the same requirement although not using React. I solved it using a manual manipulation of the DOM elements as suggested above. I have used jQuery for the select and manipulation. It is posted here in case anyone would like to see an example of how this can be achieved using DOM manipulation.
I implemented dayCellContent to make the day-cell DOM element easily identifiable by wrapping it in a span, with a unique id attribute based on the day of year number:
dayCellContent: function(info, create) {
const element = create('span', { id: "fc-day-span-"+info.date.getDayOfYear() }, info.dayNumberText);
return element;
},
This dayCellContent implementation makes no visible difference to the calendar but makes it easier to identify the elements to be modified in the DOM.
I then implemented dayCellDidMount to do the DOM manipulation by finding the appropriate cells and selecting their parent’s parent:
dayCellDidMount: function(info) {
let element = "<div style='position: absolute; left: 4px; top: 4px;'><a href='https://www.w3schools.com/'>TEST-"+info.dayNumberText+"</a></div>";
$('#fc-day-span-'+info.date.getDayOfYear()).parent().parent().prepend(element);
},
In this case I have just put a link to w3c in the top left of the cell with test text which also includes the day number. It results in cells that look like this:
Clearly the CSS could be improved and should be moved out to the CSS definitions but it illustrates the point.
Warning: This approach makes assumptions about the DOM structure that FullCalendar generates. The generated HTML may change in future versions of the product which could invalidate it. If you go this way then be careful when doing a FullCalendar update.
Note that the getDayOfYear function is from the ext-all.js library. Any way of uniquely identifying the day will work.
ngAfterViewInit(){
// Your CSS as text
var styles =.fc td, .fc th { vertical-align: top; padding: 0; height: 100px; } a{ color:#3d1cba; }
let styleSheet = document.createElement("style");
styleSheet.innerText = styles;
document.head.appendChild(styleSheet);
let arrTD = document.querySelectorAll('td.fc-timeline-slot');
let arrTR= document.querySelectorAll('td.fc-timeline-lane.fc-resource');
let arrInject= document.querySelectorAll('td.fc-timeline-lane.fc-resource>div.fc-timeline-lane-frame');
console.log(arrTR);
let k=-1;
arrTR.forEach(eachTR => {
let i=1;
let str = '';
k++;
let data_resource_id= eachTR.getAttribute('data-resource-id');
console.log(data_resource_id);
arrTD.forEach(eachTD => {
let k=100*(i-1);
i=i+1;
let data_date= eachTD.getAttribute('data-date');
console.log(data_date);
let data_resource_id= eachTR.getAttribute('data-resource-id');
console.log(data_resource_id);
str = str + '<span data-date="'+data_date+'" data-resource-id="'+data_resource_id+'" class="plus_icon" style="position:relative;top: 0px; left: '+k+'px !important;width:500px;height:500px;z-index:3;-moz-border-radius:100px;border:1px solid #ddd;-moz-box-shadow: 0px 0px 8px #fff;">+</span>';
});
arrInject[k].innerHTML=str;
});
let elementList = this.elRef.nativeElement.querySelectorAll('span.plus_icon');
for(let i=0;i<elementList.length;i++){
elementList[i].addEventListener('click', this.plusClick.bind(this));
}
}

addEventListener does not fire on <li> in for loop

I've been searching the i-net now for 2 days and still can't find a solution for my problem so I want to ask you.
Background: I have a unordered list with list items and each li has an ::after element. I want to add a class to the li on click on the ::after Element.
Here is the important code:
/* jslint browser: true */
/*global window */
window.onload = function() {
//Possible Solution - 1st Try
var lit = document.getElementById("li1");
function showMe(){
this.classList.add("in-view");
console.log(this.className);
}
document.getElementById("li1").addEventListener("click", showMe.bind(this,lit),false);
// Possible Solution - 2nd Try
var liitems = document.getElementsByClassName("liitems");
function pullTextOnClick() {
liitems[i].classList.add("in-view");
}
for (var i=0; i<liitems.length; i++) {
liitems[i].addEventListener("click",pullTextOnClick, false);
}
};
.timeline ul li {
list-style-type:none;
position:relative;
width:6px;
margin:0 auto;
padding-top:50px;
background:#fff;
}
.timeline ul li::after{
content:'';
position:absolute;
left:50%;
bottom:0;
transform: translateX(-50%);
width:50px;
height:50px;
border-radius:50%;
background: inherit;
cursor:pointer;
}
<div class="container">
<section class="timeline" id="timelineexpand">
<ul>
<li class="liitems" id="li1" onclick="showMe()">
<div>
<time>1992</time>
Sth happend
</div>
</li>
<li class="liitems" id="li2">
<div>
<time>1997</time>
Sth else
</div>
</li>
How I understand my problem and what I found out on the Internet:
The addEventListener opens the 2nd argument (eventHandler) with an object reference. Which is, when I log it with the console, often the "window" or "undefined". So I have to somehow tell the function (showMe or pullTextOnClick) what "this" is or which li-element to use.
I would prefer it, if the eventListener would be in the for loop and constantly checking if a li-Node was clicked. If this happens, the index of the li-element should be forwarded to the function, which then adds the class "in-view".
The problem: The function showMe/pullTextOnClick does not fire. If i add a console.log inside these functions, they don't log anything. Why is that?
So I hope that somebody can explain me why my code does not work or how to improve it. Thank you!
Summary of my Questions:
why is "/global window/ doing anything, and what? It is in comments, but if I don't add it, it says "window.onload" is not defined.
How to bind the eventTarget (li-element) to the eventHandler-fct (showMe, pullTextOnClick)?
how does the "bind"-fct work? I read many explanations, but still don't understand why my code does not work.
I tried to add a class with "addClass", but it said that the fct was not defined. It only works with classList.add., but why?
Problem solved:
I tried to click the li-element. BUT when i tried to do this, the parent-container was selected. Reason was, because the li-element had a z-index of -1. I did this to hide it behind an other picture. Lesson learned.

Add css class when user logged ng-class

How can I use ng-class to add a Css class where I put display: none; when the user is log in?. I'm using mean.js so I first tried using
ng-show="authentication.user"
But that only makes the element to hide like visivility : hidden;
Besides I need to add other classes in elements that i keep rather if the user is logged or not where I change margins and other stuff.
I tried using in angular a condition:
$scope.logged = false;
if ($scope.authentication.user) {
$scope.logged = true;
}
and then adding the ng-class condition like this:
ng-class="logged ? 'myClass1' : 'myClass2'"
But it failed, what am I doing wrong?
ng-if="authentication.user"
will remove the element altogether unlike ng-show which hides it.
With AngularJS, the right syntax is :
ng-class="{ className : Boolean}"
A example with multiple classes from AngularJS documentation :
ng-class="{strike: deleted, bold: important, 'has-error': error}"
Or with a function :
ng-class="{ invalid : !form.validPassword()}"
Hope this will help.

Error: ngRepeat:dupes Duplicate Key in Repeater (need track by $index)

https://plnkr.co/edit/bpFi5WuojpNO2rh5vF3T?p=preview
See the README in the Plunker for the following explaination:
I would like the "INJECT NEW" button to create a blank input under
the one that was clicked, not at the end.
The reason they are getting added at the end is because of :
<div ng-repeat="problem in problems track by $index">
The track by $index is breaking the injection.
If I take out the track by $index then I get the error:
https://docs.angularjs.org/error/ngRepeat/dupes?p0=problem%20in%20problems&p1=object:171&p2={%22key%22:null,%22component%22:null,%22$$hashKey%22:%22object:171%22}
How can I have the inject functionality but not get the error?
Can change the method like below and can get it working :
$scope.addMotFault = function(idx) {
if ($scope.problems.length > 1) {
// Now more than one item, we need to
// inject the additional one under the clicked item
// this index + 1
problemPrototype.key = idx + 1;
$scope.problems.splice(idx + 1, 0, angular.copy(problemPrototype));
} else {
// Only one item, so just push new problem
// no need to "inject"
problemPrototype.key = 0;
$scope.problems.push(angular.copy(problemPrototype));
}
};
html:
<div ng-repeat="problem in problems" style="border: 1px #ccc solid; margin:5px; padding: 5px">
It would work i believe.
As per your question I am sending you the required answer.
Please find the attached link and go through the code. you will find the solution.
https://plnkr.co/edit/nhqAnD1hSKuIs9HTsT30?p=preview
you can use ng-if in place $first and can check on the basis on length.
<button ng-click="removeMotFault($index)" ng-if="problems.length > 1">REMOVE</button>

Running header with nested generated content

This is a question about Paged Media.
I want to mix a string-set with running elements. It works in PrinceXML but not in PDFReactor. My question, is this possible in PDFReactor?
HTML
<p class="head">
<span class="first">Repeatable title</span>
<span class="divider">|</span>
<span class="last"></span>
</p>
CSS
p.head {
position: running(heading);
font-size: 8pt;
margin: 0;
padding: 0;
}
#page {
size: A4;
#top-left {
content: element(heading);
}
}
So far everything is peachy. But when I try to define a string-set from a H1 and try to write this into span.last this isn't working.
h1 {
string-set: doctitle content();
}
p.head span.last {
content: string(doctitle);
}
This is possible with PDFreactor as well. Just the syntax is a bit different. PDFreactor does not support the content() function for the string-set property with Named Strings. Instead it uses the self value which works like content() or content(text) (see http://www.pdfreactor.com/product/doc_html/index.html#NamedStrings )
There is a second issue. You are setting the content property on the span element itself. Usually in CSS, creating generated content with the content property is actually only allowed for page margin boxes and pseudo elements such as ::before and ::after. This is also how browsers support it. Not sure why this works in Prince.
So basically you just have to make 2 minor adjustments to your style sheet to make this work in PDFreactor:
h1 {
string-set: doctitle self;
}
p.head span.last::before {
content: string(doctitle);
}

Resources