Angularjs ng-repeat rendering more items than are in the array - javascript

I currently have the following code:
<div class="carousel" ng-if="novedadesEmpleados.cumpleanosDeHoy.length">
<div ng-repeat="empleado in novedadesEmpleados.todaysBirthdays" carousel-renderer class="event-card">
<birthday-card empleado="empleado" card-notifier></birthday-card>
</div>
</div>
Which used to work fine until I changed from using Bootstrap Carousel to Tiny Slider.
The array currently has 2 elements in it, however the HTML is rendering 6, 4 oh which are just showing the default angularjs text (name of properties in curly brackets) and the other 2 are displaying the correct information.
What could be causing this? The two directives are used to call the Tiny Slider initializer after they're done rendering:
angular.module('PortalIntranet').directive('cardNotifier', function ($timeout) {
return function(scope, element, attrs){
if(scope.$last)
scope.$emit('FinishedRendering');
}
});
angular.module('PortalIntranet').directive('carouselRenderer', function ($timeout) {
return function (scope, element, attrs) {
scope.$on('FinishedRendering', function (event) {
var slider = tns({
container: '.card-slider',
autoplay: true,
slideBy: 'page',
autoplayTimeout: 3500,
speed: 750,
autoplayButton: false,
nav: false,
autoplayButtonOutput: false,
controls: false
});
console.log(slider);
});
}
});

Angular is not likely displaying more items then are in the array. First, make sure your data is clean.
I believe the root ;) of your problem is in the directives and the rootScope. Most likely you need have the check when the condition is met on the rootScope or scope of the "carouselRenderer" not on the scope of the "cardNotifier". i.e.
if (scope.$parent.$last)
You want the last of the repeated items in the parent scope.
Also, as a side note, make sure your css classes are in the right place. Why is the "event-card" on the carousel-renderer and not on the birthday-card?

Related

angularjs - get ng-repeat element using element.html() inside a jsPanel directive

I am trying to create a directive which will take the html content and place it n a jsPanel element.
So far I am able to compile the scope and able get elements with the scope. Then i tried to get html tag with ng-repeat, where i got stuck.
Following is the code used to create the directive
mainApp.directive('jspanelcontent', function($compile) {
console.log("loading");
var directive = {};
directive.restrict = 'AE';
directive.compile = function(element, attributes) {
var linkFunction = function($scope, element, attributes) {
console.log(element.html());
var contenthtml = element.html();
contenthtml = angular.element($compile(contenthtml)($scope));
element.html("");
$scope.jspanleinstance = $.jsPanel({
position: {
my: "left-bottom",
at: "left-bottom",
offsetY: 15
},
theme: "rebeccapurple",
contentSize: {
width: 600,
height: 350
},
headerTitle: attributes.panelcaption,
content: contenthtml,
});
// });
}
return linkFunction;
}
return directive;
});
And using like following in html
<div jspanelcontent panelcaption="list">
<div ng-controller="ListController">
{{test}}
<div ng-repeat="user in users">
<span>{{user.id}}</span>
<span>{{user.name}}</span>
<span>{{user.email}}</span>
<br />
</div>
</div>
The console log returns as
Output i am getting (jsPanel)
As you see the "test" variable is properly bind and the ng-repeat element is display as commented, I am not aware why it's returning like that. If anyone can figure out the issue, then i might get it working.
I know i can access the users data as $scope.users inside the directive. The problem here is that user able to use the directive commonly, so i have to assume that i don't the variables in the $scope.
I stuck in this place, and couldn't find any solutions to try. Any suggestions or solutions will be more helpful. :)
NOTE: No syntax errors, outside the directive the data is displaying (Tested)

Highlight on cell change (perhaps disable lazy loading)

So in a normal table, I have this directive:
app.directive('highlightOnChange', function () {
return {
link: function (scope, element, attrs) {
attrs.$observe('highlightOnChange', function (val) {
element.addClass('flight-item-highlighted');
setTimeout(function () {
element.removeClass('flight-item-highlighted');
}, 300);
});
}
};
});
this works fine when used like this in an table:
<td highlight-on-change="{{value}}" ng-repeat="(key,value) in items"></td>
The idea is to highlight the specific cell once it is updated. This is needed because it is a realtime application, meaning changes will come from the server and needs to be highlighted when changed to let the user know.
But now I am using Angular UI-Grid which lazy loads the data (which is nice for performance) but not so nice for this feature.
I was able to implement it by defining a cell template:
<div class="ui-grid-cell-contents" highlight-on-change="{{grid.getCellValue(row, col)}}">{{grid.getCellValue(row, col)}}</div>
put it in the options:
$scope.tableColumns = [
{ field: 'name', displayName: 'Firstname', width:100, visible: true, cellClass: self.cellClass, cellTemplate: '/Templates/gridcelltemplate.html' },
{ field: 'gender' }, { field: 'company' }];
$scope.gridOptions = {
data: self.data,
columnDefs: self.tableColumns,
enableColumnResizing: true,
enableColumnMenus: false,
rowTemplate: '/Templates/rowtemplate.html',
headerTemplate : '/Templates/headertemplate.html',
};
But the result is that elements are always highlighted on scroll, obviously because they are created at that point (It does work nice for a single update though).
So one solution that I think of would be to change the css class from the controller for a specific column when the data has changed. But because the index will change on scroll for a particular row it would not make any sense. (Because row indexes are always from 0 to x, where x is the last shown row).
Another solution I can do is to keep the same directive, give a function that would call my controller. Controller would somehow need to check if the value is new or not and if so return true. Which would then result in an highlight.
inside directive:
....
scope: {
changed: '&',
},
link: function (scope, element, attrs) {
attrs.$observe('highlightOnChange', function (val) {
//now observed value of column, it has changed, call controller to check if it is a new value
var result = scope.changed({ val: val });
....
html template:
<div class="ui-grid-cell-contents" highlight-on-change="{{grid.getCellValue(row, col)}}" changed="grid.appScope.columnChanged(val)">{{grid.getCellValue(row, col)}}</div>
To be honest, I rather have lazy loading disabled, that would solve a lot of problems actually.
Edit:
Well, I found by setting gridOptions.virtualizationThreshold: self.data.length the lazy loading is disabled. But of course this comes with some major performance issues (having about 20 columns, 1000 rows). So perhaps an more elegant way would be nice :)

AngularJs Directive update

I want to update my directive content only at some desired places, but not at others. I have simulated my problem here:
http://jsfiddle.net/Lvc0u55v/2945/
The problem is, I have an 'editor' directive which is applied in two places:
<span class="editor1" editor ></span>
<span class="editor2" editor ></span>
I want to update the content of span class="editor1" on button click.
How do I do it?
Why not go with a relatively Angularesque approach by isolating the scope of the directive and to a maximum extent, avoid jQuery in your logic.
So you could have your directive defined as such:
.directive('editor', function() {
return {
scope: {
upd : '=',
editordata : '=data'
},
template: '<div>{{editordata}}</div>',
controller: function($scope, $rootScope, $element) {
$rootScope.$on('update', function(evt, data) {
if(data.upd === $scope.upd){
$scope.editordata = data.txt;
}
})
},
link: function(scope, el, attr) {}
}
})
Here, you are passing the required information which the editor directive depends upon through its scope, namely upd (which I suppose is how you want to uniquely identify the items by) and the text data.
Meanwhile, you can define a list of the editor items in the common parent controller MyCtrl and iterate over them in the DOM with ng-repeat.
// MyCtrl controller
$scope.list = [
{upd: 'editor1', data: 'original data for editor1'},
{upd: 'editor1', data: 'original data for editor2'}
]
<!-- HTML -->
<div ng-repeat="item in list" upd="item.upd" data="item.data"></div>
Demo
You can check if the current directory has the "editor1" class and if so complete your logic.
You can look at this example :
element[0].querySelector('.editor1') !== undefined'

Unable to get sparklines working with dynamic data in AngularJS version of SmartAdmin template

I am working on a project using the AngularJS version of the SmartAdmin Bootstrap template available here.
As part of the project, I need to add sparklines to several of the pages. I have it working just fine with static data such as:
<div id="revenue-chart" class="sparkline no-padding bg-dark-orange"
data-sparkline-type="line"
data-fill-color="#ffc65d"
data-sparkline-line-color="#ffc65d"
data-sparkline-spotradius="0"
data-sparkline-width="100%"
data-sparkline-height="180px"> 90,60,50,75,30,42,19,100,99,76,89,65 </div>
The SmartAdmin template provides a way to implement the jQuery sparklines without direct manipulation of the javascript. That is the reason for the data-* directives.
However, when I switch the code to the following, it no longer displays a graph.
<div id="revenue-chart" class="sparkline no-padding bg-dark-orange"
data-sparkline-type="line"
data-fill-color="#ffc65d"
data-sparkline-line-color="#ffc65d"
data-sparkline-spotradius="0"
data-sparkline-width="100%"
data-sparkline-height="180px"> {{revenue.points}} </div>
I added a <div> {{revenue.points}} </div> to the page, and it prints the values correctly, so I know the values are being passed to the page.
My guess is that the graph is being rendered before the Angular code is processed thereby showing no data for the graph to display. Is there some code I can add to trigger the graph to be redrawn after the Angular substitution occurs? Or is it possible to delay drawing the graph until after the substitution occurs?
I am new to both Angular and Bootstrap, so I am certain I am missing something, I just am not sure where else to look on this.
The template uses Bootstrap 3.3 and Angular 1.4.2.
I ended up creating a directive. The completed directive is:
"use strict";
angular.module('app.tvidash').directive('inlineRevenueChart', function(){
return {
restrict: 'A',
require: 'ngModel',
scope: {
data: '='
},
link: function(scope, element, attrs, ngModel){
var opts = {};
opts.type = attrs.type || 'line';
scope.$watch(attrs.ngModel, function() {
render();
});
scope.$watch(attrs.opts, function(){
render();
});
var render = function() {
var model;
if(attrs.opts) angular.extend(opts, angular.fromJson(attrs.opts));
console.log(opts);
// Trim trailing comma if we are a string
angular.isString(ngModel.$viewValue) ? model = ngModel.$viewValue.replace(/(^,)|(,$)/g, "") : model = ngModel.$viewValue;
var data;
// Make sure we have an array of numbers
angular.isArray(model) ? data = model : data = model.split(',');
$(element).sparkline(data, opts);
};
}
}
});
And the accompanying div tag:
<div id="revenue-chart" class="db-chart sparkline no-padding bg-dark-orange"
inline-revenue-chart
ng-model="revenue.points"
opts="{{ {type: 'line', width: '100%', height: '180px', lineColor: '#ffc65d', fillColor: '#ffc65d', spotRadius: 0.1, drawNormalOnTop: false} }}"></div>
I am sure there is a better way to do the options, but this is working as I need it for now. Thanks to those that took a look.

AngularJS ng-switch without extra DOM

I have a custom directive and an object myObj on the current $scope (inside an ng-repeat).
If the object has a type of html, I want to use one template:
<span ng-bind-html="myObj.html"></span>`
Otherwise I want to use a different template:
<span>{{myObj.value}}</span>`
My problem
This is invalid because a custom directive template must contain exactly one root node:
<span ng-if="myObj.type==='html'" ng-bind-html="myObj.html"></span>
<span ng-if="myObj.type!=='html'">{{myObj.value}}</span>
This is invalid because it destroys my page with extra DOM: (wrapping all my spans (there could be thousands) in unnecessary ng-switch nodes...)
<ng-switch on="myObj.type">
<span ng-switch-when="html" ng-bind-html="myObj.html"></span>
<span ng-switch-default>{{myObj.value}}</span>
</ng-switch>
My Question
Is it possible to have a directive pick it's template based on the result of a switch, without creating extra unnecessary DOM? For example, you can specify replace: true when creating a directive - is it possible to similarly have an ng-switch where the result replaces the switch tag itself?
Edit
My Directive:
return {
replace: true,
controller: 'ChunkController',
scope: {
chunk: '=deChunk'
},
templateUrl: de.partial.chunk,
link: function (scope, el, attr, ctrl) {
el.on('keydown', handleKeypress.bind(ctrl));
el.on('click', ctrl.showValue);
}
};
And its usage:
<div class="content" contenteditable="{{node.type!=='static'}}">
<div data-ng-repeat="chunk in node.chunks" data-de-chunk="chunk"></div>
</div>
With the intent that the child <div> will be replaced with the sequence of <span>s from above.
I wouldn't even bother if you are storing the html in a service just check to see if a value for myObj.html exists in the object and if it does compile and bind the html in the linker function instead of using ng-bind-html
something like this maybe:
myapp.directive('something',function($compile){
return {
link: function(scope,elem,attrs) {
var obj = scope.$eval(attrs.something);
if(obj.html) {
var html = angular.element($compile(obj.html)(scope));
elem.append(html);
} else {
//go get the data and set obj.html
}
}
}
});

Categories