externalizing ng-class for table cells - javascript

I'm trying to see if angularJs is useful for me to create a team-management application.
The issue I have:
I have a complex ng-class definition, being
ng-class="{'guard': ( guard.checked && day.func.indexOf('guard') != -1) }"
and it will prove to be bigger yet.
I was wondering if there is a way to have basically this:
# pseudocode, needs to be translated to js/angularJs
function getClasses(){
classes = ''
if ('guard' in user.day.func and guardCheckBox == checked){
classes = classes.append(' guard')
}
if ('f2' in user.day.func and f2CheckBox == checked){
classes = classes.append(' f2')
}
....
if ('fx' in user.day.func and fxCheckBox == checked){
classes = classes.append(' fx')
}
return(stripLeadingSpace(classes)
}
any tips on what to search, or any bits of code would be appreciated
a js-fiddle with what I have as of yet can be found here:
http://jsfiddle.net/mTJDh/1/
code from the fiddle for dead links
HTML:
Guard
<!--
this snippet applies the class 'guard' to every cell when the checkbox 'Guard' is checked
-->
<div ng-controller="MyCtrl">
<table ng-repeat="user in users">
<tr>
<td>{{user.name}}</td>
<td ng-repeat="day in user.days" ng-class="{'guard': ( guard.checked && day.func.indexOf('guard') != -1) }">
{{day.number}}
</td>
</tr>
</table>
</div>
JS
var myApp = angular.module('myApp', []);
function MyCtrl($scope) {
$scope.users = [
{name: 'PEDC',
days : [{number:'1', func:'guard'},
{number:'2', func:'guard'},
{number:'3', func:'guard'},
{number:'4', func:['guard','spoc']}
]
},
{name: 'JOVH',
days : [{number:'1', func:'guard'},
{number:'2', func:'guard'},
{number:'3', func:'spoc'},
{number:'4', func:'guard'}
]
}
];
}
CSS
.pending-delete {
background-color: pink
}
.guard {
border:solid black 1px
}
.spoc {
background-color: pink
}
EDIT:
This is the actual solution I use now:
http://jsfiddle.net/mTJDh/2/
basically:
added functions isGuard, isSpoc and isHoliday to my controller, with the day as an argument
these return true or false based on the json array.
idea gotten from here and https://docs.angularjs.org/api/ng/input/input%5Bcheckbox%5D

ngClass also accepts methods defined on scope which return a boolean value. So you can do something like this:
<td ng-repeat="day in user.days" ng-class="{ 'guard' : getClass(day) }">
{{day.number}}
</td>
JS
$scope.getClass = function(day){
return $scope.guard.checked && day.func.indexOf('guard') != -1
}

I updated your fiddle:
http://jsfiddle.net/mTJDh/4/
use the ngClass as in the accepted answer:
<td ng-repeat="day in user.days" ng-class="getClasses(day)" day="day">
{{day.number}}
</td>
but this time rewrite the method getClasses to return an array.
the array contains at the end every class you wants for a specific day.
$scope.getClasses = function(day){
var classes = [];
if($scope.spoc && $scope.isSpoc(day)) classes.push("spoc");
if($scope.guard && $scope.isGuard(day)) classes.push("guard");
if($scope.holiday && $scope.isHoliday(day)) classes.push("holiday");
return classes;
}
and if you want a more generic one:
http://jsfiddle.net/mTJDh/5/
define:
var availableClasses = [
"guard",
"spoc",
"holiday"]
and use a loop:
$scope.getClasses = function (day) {
var classes = [];
angular.forEach(availableClasses, function (value) {
if ($scope[value] && day.func.indexOf(value) != -1) classes.push(value);
});
return classes;
}

I would use a directive, it was a bit hard to tell from your example which scope variables your CSS rules rely on (and what exactly the rules are), but hopefully it's enough to get started.
.directive('guardClass', [function() {
return {
restrict: 'A',
scope: {
guard: '=',
user: '='
},
link: function(scope, element, attrs, controller) {
scope.$watch(function() {
//return enough info about scope.guard and scope.user
//to know when one has changed
return ...
}, function() {
var classes = [];
if (...) {
classes.push('guard');
}
if (...) {
classes.push('f2');
}
....
if (...) {
classes.push('fx');
}
element.attr('class', classes.join(' '));
});
}
};
}])
And then in HTML
<td guard-class guard="guard" user="user" />
You feed the directive the two (or more) objects it needs to calculate the CSS classes. The directive sets up a $watch to trigger whenever whatever properties on those objects change. It then finds all CSS classes that needs to be there and puts them on the element using angular element.
This saves you from cluttering up your controller with this logic, and it saves you from having extensive amounts of logic inside your templates.

Related

AngularJS get first item in a repeat that has a certain value

<div ng-repeat="widget in widgets"
ng-class="">
<div>{{widget.row}}</div>
</div>
I'm trying to apply a class inside the repeat based on a particular value in the repeat, for example if widget.row = 0 and it is the first widget with that value displayed then give it a class and all the other widgets that have row as 0 do not get the class. This will need to be the case if it equals 1 or 2 and so on so I can't just use $first as there will be multiple row values and multiple widgets for example it may output something like:
0 0 0 0 1 1 2 2 2 2
So the easiest way for me to achieve this was using the Adjacent sibling selector rather than do it with angular as each item is not really aware of the others:
<div ng-repeat="widget in widgets"
class="widget-row-{{widget.row}}">
<div>{{widget}}</div>
</div>
and then use CSS for:
.widget-row-0:first-child {}
.widget-row-0 + .widget-row-1 {}
.widget-row-1 + .widget-row-2 {}
.widget-row-2 + .widget-row-3 {}
Best practise is to prepare your data in a init function in your controller. It's nice and KISS! It's the best way to prepare your data in control function instead of misapply the E2E binding of AngularJS. It solve your problem so no class is written when there is no need for (as you asked for). Its proceeded once instead of calling a function again, again and again by E2E binding like ng-class="shouldIAddAClass()".
View
<div ng-repeat="widget in widgets"
ng-class="{ 'first' : widget.first }">
<div>{{widget.row}}</div>
</div>
Controller
$scope.widgets = [{
row: 0
}, {
row: 2
},{
row: 0
},{
row: 1
},{
row: 1
},{
row: 2
},{
row: 0
}];
//self calling init function
(function init () {
var widgetRowFound = {};
angular.forEach($scope.widgets, function (widget, key) {
if (angular.isDefined(widgetRowFound[widget.row])) {
$scope.widgets[key].first = false;
} else {
$scope.widgets[key].first = true;
widgetRowFound[widget.row] = true;
}
});
})();
Not the cleanest one but will work
<div ng-repeat="widget in widgets">
<div ng-class="{'myClass': applyClass(0, widget.row)}"></div>
</div>
----------
$scope.widgetsRows = {};
function applyClass(number, row){
if(!$scope.widgetsRows[row]){
$scope.widgetsRows[row] = true
}
return row == number && $scope.widgetsRows[row];
}
You can add the class you want to use to the widget objects in the controller first:
var tempRow = "";
for(var i = 0;i < $scope.widgets.length;i++) {
if($scope.widgets[i].row != tempRow) {
$scope.widgets[i].class = "myClass";
tempRow = $scope.widgets[i].row;
}
}
Then you can use that class:
<div id="widgets" ng-repeat="widget in widgets"
class="{{widget.class}}">
<div>{{widget.row}}</div>
</div>
Hope this helps
You can create a method that will be called from ng-class to achieve your goal. The method should return the class to be used.
$scope.firstHitFound = false;
$scope.isFirstZeroValue = function(value){
if($scope.firstHitFound == false && value == 0){
$scope.firstHitFound = true;
return class1;
}else{
return class2;
}
}
The HTML / Angular shoudl look as:
<div ng-class="isFirstZeroValue(widget.row)">
If you want to style it, add the class to all the widget that match your criteria, and use css to perform it only on the first of them.
Html:
<div id="widgets" ng-repeat="widget in widgets"
ng-class="{'widget-first': widget.row == 0}">
<div>{{widget.row}}</div>
</div>
Css:
#widgets.widget-first:first-of-type {
background: #ff0000;
}
You can use ng-class in addition of your ng-repeat:
Example
<div ng-repeat="widget in widgets" ng-class="{'test': widget.value === 0}">
<div>{{widget.row}}</div>
</div>
You need to call a method that will check if the row result is not same with previous value. If it not same , it will return true value and will be assigned ng-class, and if not return false. Filter this out using ng-if.
Html
<div ng-repeat="widget in widgets"
ng-class="">
<div ng-if="calculate(widget.row)">
<div ng-class="test">{{widget.row}}</div>
</div>
<div ng-if="!calculate(widget.row)">
<div>{{widget.row}}</div>
</div>
</div>
Controller
var arr = [];
$scope.calculate = function (row) {
arr.push(row);
var breakLoop = false;
angular.forEach(arr, function (oldVal, newVal) {
breakLoop = false;
if (oldVal != newVal) {
breakLoop = true;
}
)};
return breakLoop;
}

How to use ng-class compare to self invoked value

Hi I have been using this tag to change my css style, if the condition totalAsset and sortedAsset are same
<div class="table-row" ng-repeat="x in myData"
ng-model="sort(x.totalAsset)"
ng-class="{'lightblue': x.totalAsset == sortedAsset}">
totalAsset is my data in like this
$scope.myData = [
{
totalAsset: "23557"
},
{
totalAsset: "4512190",
},
{
totalAsset: "2190",
},
{
totalAsset: "1256790",
}
]
i have create a function that self sort the totalAsset
$scope.sort = function(totalAsset) {
$scope.unsortedAsset = totalAsset;
$scope.sortedAsset = $scope.unsortedAsset.split("").sort().join("");
console.log(sortedAsset);
}
in the logic only the first and last row will become blue the other two rows remain same.
But my problem here is only the last one become blue, the first one doesn't.
I am not sure about, if you need the {{ }}( they are not needed in ng2 atleast ).
<div class="table-row" ng-repeat="x in myData"
ng-model="sort(x.totalAsset)"
ng-class="x.totalAsset == sortedAsset ? 'lightblue' : ''">
Second approach is to do the if in controllers function
$scope.areValuesEqual() {
if($scope.x.totalAssets == $scope.sortedAssets) {
return 'lightblue';
}
return;
}
But this looks rather ugly, but i am just throwing this out there.

i10n angularjs assign translate variable

Okay so i have the following small translation file:
{
"components" : {
"1" : "Video",
"2" : "Lyd",
"3" : "Dokument",
"4" : "Tekst"
}
}
And then i have the following li item:
<li ng-repeat="type in componentTypes" ng-hide="module.module_type_id == 2 || module.module_type_id == 10">{{type.name}}</li>
What you need to notice is :
{{type.name}}
Or more precisely:
translate="components.{{1}}"
With this it does not translate the <a></a> tag.
However if i do
translate="components.1"
it translates correctly however this method doesnt work for me
so my question is how can dynamicly change the value of a the translate attribute?
The reason components.{{1}} is not working is because the double curlies in Angular is just meant to evaluate an expression. 1 is just a number, so you'll get components.1 everytime.
If I understand what you need correctly, you need to have a corresponding component based on type. So if type.id === 1 then your type is Video.
In order to achieve that in Angular dynamically, you should just have:
translate="{{components[type.id]}}"
Fiddle
As far as I understand you want to dynamize the translation json if added a new type to your componentTypes array.
There is a solution for that need, you can implement a new custom translation loader factory and use it with specified way here https://github.com/angular-translate/angular-translate/wiki/Asynchronous-loading. After that you have to add this new item to the translation json, your array and then refresh the translation.
View:
<div ng-app="myApp">
Links to jsfiddle.net must be accompanied by code. Please indent all code by 4 <div ng-controller="MyCtrl">
<input type="text" ng-model="type.name" /> Add Component
<ul>
<li ng-repeat="type in componentTypes" ng-hide="module.module_type_id == 2 || module.module_type_id == 10">{{ 'components.' + type.name | translate }}</li>
</ul>
</div>
</div>
Implementation of your application:
var myApp = angular.module('myApp', ['pascalprecht.translate']);
var components_en = {
"components": {
"1": "Video",
"2": "Lyd",
"3": "Dokument",
"4": "Tekst"
}
};
myApp.config(function ($translateProvider) {
$translateProvider.useLoader('customLoader', {});
$translateProvider.translations('en', components_en);
$translateProvider.preferredLanguage('en');
});
myApp.controller('MyCtrl', function ($scope, $translate) {
$scope.module = {
module_type_id: 1
};
$scope.addComponent = function (type) {
// Add the componentTypes array you took from database
$scope.componentTypes.push({
name: $scope.componentTypes.length + 1
});
// Add the translation object
components_en["components"][$scope.componentTypes.length] = type.name;
console.log(components_en);
$translate.refresh();
};
$scope.componentTypes = [{
name: 1
}, {
name: 2
}, {
name: 3
}, {
name: 4
}];
});
myApp.factory('customLoader', function ($http, $q) {
return function (options) {
var deferred = $q.defer();
deferred.resolve(components_en);
return deferred.promise;
}
})
I prepared a demonstration for that like usage, please below jsfiddle:
http://jsfiddle.net/nerezo/1z071wzg/6/
Note: This like translation modifications will not be persistent and new translations will be lost.
Please give this a try:
translate="{{'components.' + type.id}}" //if there is id in type
or
translate="{{'components.' + ($index + 1)}}"

How to write VISIBILITY if-else condition in ANGULARJS?

How to Write Condition on data which is fetching from Json to Angularjs?
Example : if user FIRM NAME exists Show else if user FULL NAME exists Show else Show REALNAME
I have a working Example of fetching data
at line number 25 <h3 class="moduletitle">Name : {{ module.realname }}</h3>
Please See that in PLUNKER
I hope i will get the working code update along with PLUNKER
I can suggest you have a function that returns the entity in which you want to display. Then using ng-show / ng-hide to display/hide the things you want.
Example:
function pseudoDecide(){
var displaythis = "";
if(/*boolean exp*/){ displaythis = "firm" }
else if(/*boolean exp*/) { displaythis = "full" }
else(/*boolean exp*/) { displaythis = "real" }
return displaythis;
}
Then <div ng-show="{{psedoDecide() === 'firm'}}>" etc etc, something like that.
With AngularJS 1.1.5+, you can use the ternary operator inside an expression. In your case, I believe you want something like:
<h3 class="moduletitle">Name : {{ module.firmname ? module.firmname : (module.fullname ? module.fullname : module.realname)) }}</h3>
If you don't want a nested ternary in your template, you could also go this route:
Somewhere in your controller:
$scope.pickName = function (module) {
var val;
if (module.firm_name) {
val = module.firm_name;
} else if (module.full_name) {
val = module.full_name;
} else {
val = module.realname;
}
return val;
};
And in your template:
<h3 class="moduletitle">Name : <span ng-bind="pickName(module)"></span></h3>

Filter users by one keyword in a nested observableArray

I am trying to filter my users observableArray which has a nested keywords observableArray
based on a keywords observableArray on my viewModel.
When I try to use ko.utils.arrayForEach I get a stack overflow exception. See the code below, also posted in this jsfiddle
function User(id, name, keywords){
return {
id: ko.observable(id),
name: ko.observable(name),
keywords: ko.observableArray(keywords),
isVisible: ko.dependentObservable(function(){
var visible = false;
if (viewModel.selectedKeyword() || viewModel.keywordIsDirty()) {
ko.utils.arrayForEach(keywords, function(keyword) {
if (keyword === viewModel.selectedKeyword()){
visible = true;
}
});
if (!visible) {
viewModel.users.remove(this);
}
}
return visible;
})
}
};
function Keyword(count, word){
return{
count: ko.observable(count),
word: ko.observable(word)
}
};
var viewModel = {
users: ko.observableArray([]),
keywords: ko.observableArray([]),
selectedKeyword: ko.observable(),
keywordIsDirty: ko.observable(false)
}
viewModel.selectedKeyword.subscribe(function () {
if (!viewModel.keywordIsDirty()) {
viewModel.keywordIsDirty(true);
}
});
ko.applyBindings(viewModel);
for (var i = 0; i < 500; i++) {
viewModel.users.push(
new User(i, "Man " + i, ["Beer", "Women", "Food"])
)
}
viewModel.keywords.push(new Keyword(1, "Beer"));
viewModel.keywords.push(new Keyword(2, "Women"));
viewModel.keywords.push(new Keyword(3, "Food"));
viewModel.keywords.push(new Keyword(4, "Cooking"));
And the View code:
<ul data-bind="template: { name: 'keyword-template', foreach: keywords }"></ul><br />
<ul data-bind="template: { name: 'user-template', foreach: users }"></ul>
<script id="keyword-template" type="text/html">
<li>
<label><input type="radio" value="${word}" name="keywordgroup" data-bind="checked: viewModel.selectedKeyword" /> ${ word }<label>
</li>
</script>
<script id="user-template" type="text/html">
<li>
<span data-bind="visible: isVisible">${ $data.name }</span>
</li>
</script>
Your isVisible dependentObservable has created a dependency on itself and is recursively trying to evaluate itself based on this line:
if (!visible) {
viewModel.users.remove(this);
}
So, this creates a dependency on viewModel.users, because remove has to access the observableArray's underlying array to remove the user. At the point that the array is modified, subscribers are notified and one of the subscribers will be itself.
It is generally best to not change the state of any observables in a dependentObservable. you can manually subscribe to changes to a dependentObservable and makes your changes there (provided the dependentObservable does not depend on what you are changing).
However, in this case, I would probably instead create a dependentObservable at the viewModel level called something like filteredUsers. Then, return a version of the users array that is filtered.
It might look like this:
viewModel.filteredUsers = ko.dependentObservable(function() {
var selected = viewModel.selectedKeyword();
//if nothing is selected, then return an empty array
return !selected ? [] : ko.utils.arrayFilter(this.users(), function(user) {
//otherwise, filter on keywords. Stop on first match.
return ko.utils.arrayFirst(user.keywords(), function(keyword) {
return keyword === selected;
}) != null; //doesn't have to be a boolean, but just trying to be clear in sample
});
}, viewModel);
You also should not need the dirty flag, as dependentObservables will be re-triggered when any observables that they access have changed. So, since it accesses selectedKeyword, it will get re-evaluated whenever selectedKeyword changes.
http://jsfiddle.net/rniemeyer/mD8SK/
I hope that I properly understood your scenario.

Categories