Show and Hide a <div> inside an ng-repeat with 'dirPagination' (AngularJS) - angularjs

In this very site I've seen this question many times, but none of them works for me because of two things: I need the div to toggle when clicking the "options" button, but also to hide when clicking outside of the div; and I need it to work with dirPagination.
I saw this answer, it works fine but with one problem I just can't solve: it shows ALL the hidden divs at the same time, instead of only showing the one I clicked.
Here's my code:
<body ng-controller="MainCtrl">
<!-- pagination -->
<dir-pagination-controls
max-size="7"
direction-links="true"
boundary-links="true">
</dir-pagination-controls>
<ul>
<li dir-paginate="report in reports | itemsPerPage:4">
Options
<h3>{{report.Title}}</h3>
<div class="options" ng-show="dp">
<p>These are some options</p>
</div>
</li>
</ul>
</body>
And the JS to show the options:
//options div
$scope.showOptions = function(event){
if($scope.dp === false || $scope.dp === undefined) {
$scope.dp = true;
event.stopPropagation();
} else {
$scope.dp = false;
}
};
window.onclick = function(){
if($scope.dp){
$scope.dp = false;
$scope.$apply();
}
};
I've made a Plunker in case you wanna se it in action: my Plunker link
Can somebody help me with this issue? :(

Add a new boolean property on your reports array , for example show
var reports = [
{
"ID":1,
"Title":"Report 1",
"Show":false
},
{
"ID":2,
"Title":"Report 2",
"Show":false
}
]
Apply the property in to ng-show and also pass the current report scope object in to showOptions method to write the logic for hide and show.
<li dir-paginate="report in reports | itemsPerPage:4">
Options
<h3>{{report.Title}}</h3>
<div class="options" ng-show="report.Show">
<p>These are some options</p>
</div>
</li>
$scope.showOptions = function(event,report){
report.Show=!report.Show;
/*you can iterate through each reports and change show to false if the clicked report id not equal to report id , Angular JS will automatically update the scope in to the view*/
reports.forEach(function(item, index) {
if (item.ID !== report.ID) {
item.Show = false;
}
});
if($scope.dp === false || $scope.dp === undefined) {
$scope.dp = true;
event.stopPropagation();
} else {
$scope.dp = false;
}
};
https://next.plnkr.co/edit/hnhWMrgR3GMtVcES

You need to use a separate variable for each div you want to show. You could add the dp attribute to the report. There is no need to loop over the reports to hide them. You can just keep track of the currently visible report and hide it when another one is toggled.
Here is the relevant HTML:
<li dir-paginate="report in reports | itemsPerPage:4">
Options
<h3>{{report.Title}}</h3>
<div class="options" ng-show="report.dp">
<p>These are some options</p>
</div>
</li>
and JavaScript
var visibleReport;
$scope.showOptions = function(event, report){
if (report == visibleReport) {
report.dp = !report.dp;
}
else {
if (visibleReport) visibleReport.dp = false;
report.dp = true;
}
visibleReport = report
event.stopPropagation();
};
window.onclick = function(){
if (visibleReport) visibleReport.dp = false;
visibleReport = null;
$scope.$apply();
};
Here is a working plunker https://next.plnkr.co/edit/sWLxBGlF8D22nvYp?preview

Related

Issue with implementing horizontal group menu in Angularjs

I am new to angularjs and was implementing a menu framework which would be both vertical & horizontal depending on a button toggle.The menu being implemented is with custom directives which looks like below.
<my-menu>
<my-menu-item></my-menu-item>
<my-menu-group label="Fruits">
<my-menu-item label="Apple"></my-menu-item>
<my-menu-item label="Orange"></my-menu-item>
</my-menu-group>
<my-menu-group label="Vegetables">
<my-menu-item label="Tomato"></my-menu-item>
<my-menu-item label="Beans"></my-menu-item>
</my-menu-group>
<my-menu>
The template for the <my-menu-group> is:
<li class="my-selectable-item" ng-click="clickedMenuGroupItem()" ng-class="{'my-item-horizontal': !isVertical()}">
<div class="acc-noselect">
<i class="fa {{icon}} my-menu-icon"></i>
{{label}}
<i ng-if="isVertical()"
class="fa fa-angle-right my-group-indicator-left" ng-class="{'fa-rotate-90':isOpen}"></i>
<i ng-if="!isVertical()" style="margin-left:5px;"
class="fa fa-angle-down" ng-class="{'fa-rotate-180':isOpen}"></i>
</div>
The link function has the code snippet as :
link: function(scope,el,attr,ctrl) {
scope.isOpen = false;
scope.closeMenu = function () {
scope.isOpen = false;
};
scope.clickedMenuGroupItem = function () {
scope.isOpen = !scope.isOpen;
if (el.parents('.my-subitem-section').length === 0)
scope.setSubmenuPosition();
ctrl.setOpenMenuScope(scope);
};
scope.isVertical = function() {
return ctrl.isVertical() || el.parents('.my-subitem-section').length > 0;
};
scope.setSubmenuPosition = function(){
var pos = el.offset();
$('.my-subitem-section').css({'left':pos.left + 20, 'top' : 40});
};
}
Vertical menu works just fine.However when the menu is horizontal if I click on 'Fruits' I get the respective list values and then when i click on vegetables both the list gets rendered.What I'm expecting is a toggle functionality.The bottom line here is that I m not able to control the scope of previously clicked element in the link function.
I've tried my best to be as descriptive as possible here.Please help ?

Slow reaction to button click in NgRepeat list of 1000 items

I have a list of 1000+ items which I display using NgRepeat in Angular 1.3. The list populates with buttons. I have noticed significant delay on the click event int he list once it grows in size. When the list is only 5-10 items the clicks are instant. When the list is 1000 there is about 2-5 second delay before the button clicks are actually processed.
Now I cannot tell if this is a browser issue, but I suspect it has to do with too many listeners being used somewhere, causing the browser to check for them.
Here is sample of code in case there is a culprit hiding in there:
<div id="side" class="animated" style="min-height: 250px;"
data-ng-class="{'fadeInRight':documentDone}" data-ng-style="settings.listCss">
<div class="col-md-12 text-center" data-ng-style="settings.listCss"><h4>{{label}}</h4> {{inSide}} </div>
<div data-ng-repeat="doc in ::documents track by $index" id="{{ ::doc.id }}"
class="document ng-hide" data-ng-show="doc.show"
data-ng-init="docSettings = (settingslist[doc.companyid] || settings)" data-ng-style="::docSettings.listCss">
<div class="col-md-12" data-ng-style="docSettings.listCss">
<h4>
<span>{{ ::$index + 1 }}</span>
<span class="title-in-clusters">
{{ ::doc.title }}
<button type="button"
class="btn btn-primary btn-xs"
data-ng-click="viewJob(doc, docSettings)"
data-ng-style="docSettings.buttonCss">
<strong>VIEW</strong>
</button>
<a href="{{ ::doc.joburl }}" class="apply" target="_blank">
<button type="button" class="btn btn-primary btn-xs" data-ng-click="apply(doc.jobid, doc.companyid)"
data-ng-style="docSettings.buttonCss">
<strong>APPLY</strong>
</button>
</a>
</span>
</h4>
</div>
<div class="col-md-12" data-ng-style="docSettings.listCss">
<span class=""><strong>ID: </strong>{{ ::doc.jobid }}</span>
<img data-ng-if="docSettings.heading.logourl && docSettings.heading.logourl != ''"
data-ng-src="{{docSettings.heading.logourl}}" class="side-logo inline-block" id="">
</div>
<div class="col-md-12" data-ng-style="docSettings.listCss">
<strong>Location: </strong><span class="">{{ ::doc.location }}</span>
</div>
<div class="col-md-12" data-ng-style="docSettings.listCss">
<strong>Updated Date: </strong><span class="">{{ ::doc.updateddate }}</span>
</div>
<div class="col-md-12" data-ng-style="docSettings.listCss">
<hr data-ng-style="docSettings.listCss">
</div>
</div>
</div>
There is nothing offensive about the other functions that are called when the button is pressed.
var modalInstance;
$scope.viewJob = function(modalDoc, docSettings) {
$scope.modalDoc = modalDoc;
$scope.docSettings = docSettings;
//the trusAsHtml takes string creates an object, so this will in essence convert string to object
//make sure you check if it is a string since it could be called multiple times by user (close and reopen same modal)
if (modalDoc.overview && typeof modalDoc.overview === 'string') {
$scope.modalDoc.overview = $sce.trustAsHtml(modalDoc.overview);
}
if (modalDoc.qualifications && typeof modalDoc.qualifications === 'string') {
$scope.modalDoc.qualifications = $sce.trustAsHtml(modalDoc.qualifications);
}
if (modalDoc.responsibilities && typeof modalDoc.responsibilities === 'string') {
$scope.modalDoc.responsibilities = $sce.trustAsHtml(modalDoc.responsibilities);
}
modalInstance = $modal.open({
templateUrl: 'app/modal/job_preview.html',
//templateUrl: 'myModalContent.html',
scope: $scope
});
};
I want to optimize this code so it can sever a list of up to 1500, but I cannot for the life of me find the culprit.
I will also take any solutions to reduce the load instead. Like for now I am thinking I may limit the number of DOM elements to 10 to so, and have angular rotate what is being viewed as user scrolls if it will result in better UX.
UPDATE:
Many things have been tried, from use of bind-once to more convoluted solutions that retard some of the watchers Which are enat but require a lot of Math to estimate which items are visible etc.
I finally decided on one solution that was easiest to do: I made a list of only items I wish shown and on mouse scroll up or down I edit the list.
First part of the solution is use of two directives:
.directive('ngMouseWheelUp', function() {
return function($scope, $element, $attrs) {
$element.bind("DOMMouseScroll mousewheel onmousewheel",
function(event) {
// cross-browser wheel delta
var event = window.event || event; // old IE support
var delta = Math.max(-1, Math.min(1, (event.wheelDelta || -event.detail)));
if(delta > 0) {
$scope.$apply(function(){
$scope.$eval($attrs.ngMouseWheelUp);
});
// for IE
event.returnValue = false;
// for Chrome and Firefox
if(event.preventDefault) {
event.preventDefault();
}
}
});
};
})
.directive('ngMouseWheelDown', function() {
return function($scope, $element, $attrs) {
$element.bind("DOMMouseScroll mousewheel onmousewheel", function(event) {
// cross-browser wheel delta
var event = window.event || event; // old IE support
var delta = Math.max(-1, Math.min(1, (event.wheelDelta || -event.detail)));
//console.log(event);
if(delta < 0) {
$scope.$apply(function(){
$scope.$eval($attrs.ngMouseWheelDown);
});
// for IE
event.returnValue = false;
// for Chrome and Firefox
if(event.preventDefault) {
event.preventDefault();
}
}
});
};
})
These two enable me to disable scrolling in the list on the right side. Then I would create two additional arrays from the documents in routeScope. First list would be generated whenever the documents were updated (which was an event listener for event emitted by the UI of the right hand side graph), this filter would only return array members that had the show property set to true:
var showFilter = function(object) {
return object.show;
}
This would be my array of visible items. From this array I created another Array of shown items. I defined a constant for max size of 7, so at most there are 7 items shown. And of course I set overflow of the parent container to none to disable scrollbar. (I may add a scroll graphic so the user knows he can scroll this field later)
Then I added the following directives to the side div:
data-ng-mouse-wheel-up="listUp()" data-ng-mouse-wheel-down="listDown()"
And inside the controller I defined listUp and listDown to work off an index and the max size constant to figure out which elements from the visible list I should add to the front or the back of the shown list.
/**
* Simulate scrolling up of list by removing bottom element and adding to top
*/
$scope.listUp = function() {
$rootScope.shownDocuments.unshift(getPrev());
$rootScope.shownDocuments.pop();
}
/**
* Simulate scrolling down of list by removing top element and adding to bottom
*/
$scope.listDown = function() {
$rootScope.shownDocuments.push(getNext());
$rootScope.shownDocuments.shift();
}
/**
* return next item in visibleDocuments array
*/
var getNext = function() {
$rootScope.topIndex++;
if ($rootScope.topIndex > $rootScope.visibleDocuments.length) {
$rootScope.topIndex -= $rootScope.visibleDocuments.length;
}
return ($rootScope.visibleDocuments[($rootScope.topIndex+max_shown_size)%$rootScope.visibleDocuments.length]);
}
/**
* Return previous item in visibleDocuments array
*/
var getPrev = function() {
$rootScope.topIndex--;
if ($rootScope.topIndex < 0) {
$rootScope.topIndex += $rootScope.visibleDocuments.length;
}
return ($rootScope.visibleDocuments[$scope.topIndex]);
}
Use of rootScope vs scope is mostly because modals would cause some undesirable behaviors if they were dismissed improperly.
Finally a reset function for the view:
/**
* Resets the list of documents in the visibleList (IE which are visible to client)
*/
var updateVisibleDocuments = function() {
$rootScope.topIndex = 0;
$rootScope.visibleDocuments = $rootScope.documents.filter(showFilter);
//clear view
$rootScope.shownDocuments = [];
$rootScope.topIndex = 0;
for (var i = 0; i < max_shown_size; i++) {
$rootScope.shownDocuments.push(getNext());
}
$rootScope.topIndex = 0;
}
This solution works really well because I only render 7 items even if my list has 100k items. This limits number of watchers tremendously.
You may want to try paginating to reduce the amount of things angular and the browser need to deal with on screen at any one time.

ng-click anywhere on document?

I have this login menu and its show on click but whenever i click its still open. Now i need to implement when user click anywhere on page that $scope.loginOpened = false...(to close that div with login menu)how i can do that? Any suggestion ?
This is an example of login menu:
$scope.toggleLoggedIn = function () {
$scope.loggedInOpened = !$scope.loggedInOpened;
$scope.languagesOpened = false;
$scope.loginOpened = false;
};
EDIT:
<div class="fade-show-hide" ng-show="loggedInOpened" ng-cloak>
#Html.Partial("~/Views/Shared/_LoggedInPartial.cshtml")
</div>
you can use blur()
example
<input type="text" ng-enter="doBlur($event)">
script
$scope.doBlur = function($event){
var target = $event.target;
if (!$scope.loginOpened){
// do more here, like blur or other things
$(target).blur(function() {
//do what you want here
});
}
}
If you want to hide the div you can follow the below process.
<div class="fade-show-hide" ng-hide="loggedInOpened" ng-cloak>
#Html.Partial("~/Views/Shared/_LoggedInPartial.cshtml")
</div>
<button type="button" id="btn" ng-click="toggleLoggedIn()">Hide div</button>
$scope.toggleLoggedIn = function () {
$scope.loggedInOpened = !$scope.loggedInOpened;
$scope.languagesOpened = false;
$scope.loginOpened = true;
};

AngularJS ng-repeat filter, function over object property

I am looking for a way to check for each movie if the movie has the category which is selected. Movies is an array which contains objects, those objects have some properties, like you can see in the code below. The categories property is a array of categories where the movie is in. Now there is a variable selectedCategories where the current selected category is stored in.
I don't want to use custom filters, because I think it has te be possible with this one, I just can't quite get it. In the javascript function there can't be changed too much either.
If the return of hasSelectedCategory is true, then it has to execute the block, if false not.
Thanks in advance.
//in the javascript file
scope.hasSelectedCategory = function(categories){
var hasCategory = false;
if (categories.indexOf(scope.selectedCategory) !== -1){
hasCategory = true;
}
return hasCategory;
};
//in the html file
<div class="movieListItem {{listItemView}}" ng-repeat="movie in movies | filter:{hasSelectedCategory(categories): true}">
<h4>{{movie.title}}</h4>
<a href="http://www.youtube.com/embed/{{movie.youtubeId}}?rel=0&autoplay=1">
<img ng-src="{{findPosterSource(movie)}}" class="poster"> </a>
<p ng-hide="listItemView === 'grid'">
{{movie.description}}
</p>
<div class="categories" >
<span ng-repeat="category in movie.categories"> <a ng-href="#">{{category}}</a> </span>
</div>
</div>
You have to use the filter like this:
ng-repeat="movie in movies | filter:hasSelectedCategory"
The hasSelectedCategory function will be invoked for each movie in the movies list. In order to filter by selected categories you can use a function like this:
$scope.hasSelectedCategory = function(movie) {
var hasCategory = false;
angular.forEach($scope.selectedCategories, function(selectedCategory) {
if (!hasCategory && movie.categories.indexOf(selectedCategory) !== -1) {
hasCategory = true;
}
});
return hasCategory;
};
Demo (plunker)

Knockout.js Checkbox checked and click event

We're trying to implement a checkbox and list with the following functionality:
Clicking the checkbox will either clear the array if there are items in there, or add a new item if not.
Remove an item from the array when clicking the Remove button, once the last item is removed the checkbox automatically unchecks itself.
The problem I am having is that if you click to remove each array item, then click the checkbox to add a blank entry, I'm expecting the checkbox to be checked again (as per the checked observable), however it is not?
I have the following code:
http://jsfiddle.net/UBsW5/3/
<div>
<input type="checkbox" data-bind="checked: PreviousSurnames().length > 0, click: $root.PreviousSurnames_Click" />Previous Surname(s)?
</div>
<div data-bind="foreach: PreviousSurnames">
<div>
<input type="text" data-bind="value: $data">
<span data-bind="click: $root.removePreviousSurname">Remove</span>
</div>
</div>
var myViewModelExample = function () {
var self = this;
self.PreviousSurnames = ko.observableArray(['SURNAME1', 'SURNAME2', 'SURNAME3']);
self.removePreviousSurname = function (surname) {
self.PreviousSurnames.remove(surname);
};
self.PreviousSurnames_Click = function () {
if (self.PreviousSurnames().length === 0) {
self.PreviousSurnames.push('');
}
else {
self.PreviousSurnames.removeAll();
}
alet(2)
}
}
ko.applyBindings(new myViewModelExample());
If you are using together the click and the checked then you need to return true from your click handler to allow the browser default click action which is in this case checking the checkbox:
self.PreviousSurnames_Click = function () {
if (self.PreviousSurnames().length === 0) {
self.PreviousSurnames.push('');
}
else {
self.PreviousSurnames.removeAll();
}
return true;
}
Demo JSFiddle.
You need to use a computed to monitor the length of the observable array. This way when the length reaches zero you can react to it automatically.
self.surnames = ko.computed(function() {
var checked = true;
if (self.PreviousSurnames().length === 0) {
self.PreviousSurnames.push('');
checked = false;
}
return checked;
});
Now you will have the blank text box when all of the names are cleared. If you update your binding on the checkbox it will function properly as well.
<input type="checkbox" data-bind="checked: surnames, click: PreviousSurnames_Click" />Previous Surname(s)?
FIDDLE

Resources