In an anchor tag I would like call one of two different events based on if the user clicked once or twice. However if I implement ng-click and ng-dblclick, both are activated.
Is there any way to route to the appropriate listener based on click count?
You can use a combination of ng-click and $timeout to count the number of times the function has been executed. the code could look like something like this;
<a ng-click="clicked()" />
$scope.clickCount = 0;
var timeoutHandler = null;
$scope.clicked = function()
{
if (timeoutHandler != null)
$timeout.cancel( timeoutHandler );
$scope.clickCount++;
timeoutHandler = $timeout(function()
{
//now you know the number of clicks.
//set the click count to zero for future clicks
$scope.clickCount = 0;
}, 500)
}
Related
I am using ng-table in my application, I was looking to reset current page to 1 when user changes sort order. I gone through ng-table documentation, but no use.
You should be able to do this via the page() function of NgTableParams:
$scope.tableParams.page(1);
For that, you need to subscribe to the ngTableEventsChannel.afterReloadData(); that is fired after sorting changes. There's an example that logs the events and shows how to subscribe.
This works for me:
var vm = this;
vm.tableParams = new NgTableParams();
vm.recentPage = 1;
ngTableEventsChannel.onAfterDataSorted(function() {
vm.tableParams.page(1);
vm.tableParams.reload();
}, $scope, function(tableParams) {
var reset = tableParams._params.page !== 1 && vm.recentPage === tableParams._params.page;
vm.recentPage = tableParams._params.page;
return reset;
});
When I'm getting the list of results in my cordova app, i want to roll it to the specified element with this function:
$scope.roll = function () {
//var rankScroll = $ionicScrollDelegate.$getByHandle('rank');
var meElement = document.getElementById('scroll');
if (!meElement) {
$ionicScrollDelegate.scrollTo(0, 0);
return;
}
var top = meElement.getBoundingClientRect().top - 50;
$ionicScrollDelegate.scrollTo(0, top);
console.log(top);
console.log($ionicScrollDelegate.getScrollView());
}
It works nicely, but I can't scroll to any other place in the list. I want to unlock scrolling in this solution or find better one. It should scroll on page loaded, not on click.
All the best
That function lock your scroll because of the $scope's binding.
If you only want that function to be invoked when the view is loaded, you should call it once at the the time the view is loaded.
You can do that by this way in your controller:
var roll = function () {
//var rankScroll = $ionicScrollDelegate.$getByHandle('rank');
var meElement = document.getElementById('scroll');
if (!meElement) {
$ionicScrollDelegate.scrollTo(0, 0);
return;
}
var top = meElement.getBoundingClientRect().top - 50;
$ionicScrollDelegate.scrollTo(0, top);
console.log(top);
console.log($ionicScrollDelegate.getScrollView());
}
$scope.$on("$ionicView.loaded", function (scopes, states) {
roll();
});
You can check more $ionicView events in this ion-view Document
I am hoping that someone has seen this issue before and that you may have a solution.
I have a function that is called when my controller loads that should scroll an HTML element (happens to be a div element) to the top of a containing div that is scrollable. The HTML for the page is in a view that is loaded when the user navigates to the route associated with it, and the div to be scrolled to is created in an ng-repeat.
The scroll function works perfectly when called from a click function in a user interaction with the page after the controller and the view are loaded. The function is:
// scrolls the specified html element into view
function scrollToElementByID(id) {
// locate the html element to scroll to
var
elem = document.getElementById(question.divID);
// found the element associated with the question - set focus on it
if (elem) {
// get the position of the top of the element
var
topPos = elem.offsetTop;
// get the div in which the element is located
var
elemParent = elem.parentNode;
// scroll the containing div to the element position
elemParent.scrollTop = topPos;
}
}
However, the function does not work when first called from an initialization function that is called when the controller first loads. The getElementById function call returns null, which seems like it is indicating that the html element has not been created at the time of the call.
I have tried using the $viewContentLoaded event, but that fires long before the scroll call and it still fails, and setting a $timeout and an $interval did not work either. Anybody have a suggestion as to how I can scroll my scrollable div to a specified element when the user navigates to the route?
Thanks!
Further info based on Fedaykin's comment:
I modified your controller code to try to demonstrate what I need. Your sample directive works well when the user interacts with the page after it is loaded, as in your plunkr, but I need the scroll to occur automatically like this (see the comments marked CHANGE):
app.controller( 'myCtrl', [ '$scope', '$routeParams', function ( $scope, $routeParams ){
$scope.value = 'test';
$scope.itemsOne = [];
$scope.itemsTwo = [];
for(var i=0; i<10; i++){
$scope.itemsOne.push(makeSentence());
$scope.itemsTwo.push(makeSentence());
}
// CHANGE: get the row and column from query string params
var
row = $routeParams.r,
col = $routeParams.c;
// CHANGE: controller initialization function - called when controller loads
function init() {
// scroll to requested row in requested column
$scope.scrollToMe(row, col);
}
// CHANGE: call controller initialization
init();
function makeSentence() {
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < Math.random() * 200; i++ ){
for( var i=0; i < Math.random() * possible.length; i++ )
text += possible.charAt(Math.floor(Math.random() * possible.length));
text += " ";
}
//console.log(text);
return text;
}
$scope.scrollMeTo = function(column, row){
// scroll me to an area in a column
if(column == 1)
$scope.itemColumn1 = row;
if(column == 2)
$scope.itemColumn2 = row;
};
}] );
Thanks for the help!
I have code that creates a grid like this:
<div ng-repeat="row in home.grid.data track by row.examId">
<div>{{ row.examId }}</div>
<div>xxxx</div>
</div>
I have more columns after these.
Is there a way I can speed up the way my page reacts? It seems that when
I have a lot of data in the grid then the pages reacts slowly. Would it
make a difference if I used ng-model in an input type field for the row.examId. Note that
some of the fields that follow can be edited but most are just display only.
I believe bindonce does exactly what you need.
By reducing the number of watchers it allows the page to become more responsive. Check their demos.
This is what I have done. There are two ways. Irrespective of both solutions, use bindOnce. Keep a look out on the number of watchers on the page. Look at the end of this solution - how to keep track of watchers on a page.
I have added a solution 3 and this is working awesome, styling is a bit difficult
Solution 1:
Use a pagination control with bind once.
Solution 2
This is what worked for me and it is very elegant. You repeat with bindonce and then implement infinite scrolling. I have followed this blog post and it works like a charm. The idea is you limit the number of rows and change the limit as you scroll.
ng-repeat="item in items | orderBy:prop | filter:query | limitTo:limit"
Essentially, your html would look like this. I have modified the OP's code to use bindonce.
<div id="estates-listing" extend-height>
<div class="content" infinite-scroll="addMoreItems()" infinite-scroll-distance="2">
<div class="content-wrapper">
<div class="house" bindonce="estate" ng-animate="'animate'" ng-class="{inactive: (selectedEstate != null || selectedEstate != undefined) && estate.id!=selectedEstate.id , active:(selectedEstate != null || selectedEstate != undefined) && estate.id==selectedEstate.id}" ng-repeat="estate in estates | orderBy: orderProp : orderReverse | limitTo: config.itemsDisplayedInList track by estate.id" ng-mouseover="highlightMarker(estate)" ng-mouseleave="leaveMarker(estate)" ng-click="showDetailview(estate.id)" >
<div id="l-el{{estate.id}}">
</div>
</div>
</div>
</div>
</div>
Here is the infinite scroll directive from the post. Add this to your app, please don't use the standard infinite scroll using bower install.
app.directive('infiniteScroll', [
'$rootScope', '$window', '$timeout', function($rootScope, $window, $timeout) {
return {
link: function(scope, elem, attrs) {
var checkWhenEnabled, handler, scrollDistance, scrollEnabled;
$window = angular.element($window);
elem.css('overflow-y', 'scroll');
elem.css('overflow-x', 'hidden');
elem.css('height', 'inherit');
scrollDistance = 0;
if (attrs.infiniteScrollDistance != null) {
scope.$watch(attrs.infiniteScrollDistance, function(value) {
return scrollDistance = parseInt(value, 10);
});
}
scrollEnabled = true;
checkWhenEnabled = false;
if (attrs.infiniteScrollDisabled != null) {
scope.$watch(attrs.infiniteScrollDisabled, function(value) {
scrollEnabled = !value;
if (scrollEnabled && checkWhenEnabled) {
checkWhenEnabled = false;
return handler();
}
});
}
$rootScope.$on('refreshStart', function(event, parameters){
elem.animate({ scrollTop: "0" });
});
handler = function() {
var container, elementBottom, remaining, shouldScroll, containerBottom;
container = $(elem.children()[0]);
elementBottom = elem.offset().top + elem.height();
containerBottom = container.offset().top + container.height();
remaining = containerBottom - elementBottom ;
shouldScroll = remaining <= elem.height() * scrollDistance;
if (shouldScroll && scrollEnabled) {
if ($rootScope.$$phase) {
return scope.$eval(attrs.infiniteScroll);
} else {
return scope.$apply(attrs.infiniteScroll);
}
} else if (shouldScroll) {
return checkWhenEnabled = true;
}
};
elem.on('scroll', handler);
scope.$on('$destroy', function() {
return $window.off('scroll', handler);
});
return $timeout((function() {
if (attrs.infiniteScrollImmediateCheck) {
if (scope.$eval(attrs.infiniteScrollImmediateCheck)) {
return handler();
}
} else {
return handler();
}
}), 0);
}
};
}
]);
Solution 3:
Be adventurous and use UI-Grid, UI Grid is the new ng-grid. It is not production ready, but we are playing around in production in a table where we have over 1000 records- out of the box it is awesome. The tutorials are extensive but not much SO support. It has virtualization in built and since it is an extension of ng-grid, it has a lot of backward compatibility. Here is a example with 10,000 rows
Number of watchers on the page:
Here is a function to track the number of watchers on the page. The thumb rule is never exceed 2500 watchers, but we restrict ourselves to < 1000.
$scope.TotalWatchers = function () {
var root = $(document.getElementsByTagName('body'));
var watchers = 0;
var f = function (element) {
if (element.data().hasOwnProperty('$scope')) {
watchers += (element.data().$scope.$$watchers || []).length;
}
angular.forEach(element.children(), function (childElement) {
f(angular.element(childElement));
});
};
f(root);
return watchers;
};
The biggest thing I've found to help with performance of large tables is to limit event binding to the parent object and make use of bubbling to capture the events of the children.
In the event of the parent you can get which target was hit. I use the following code.
obj.onclick = function (e) {
e = window.event || e;
var t = e.target || e.srcElement;
}
in this event e is your regular event object and t is the object that was the initial target before the event bubbled. You need to use t as 'this' references the object that the event is bound to not the object that triggered the event.
Where I was using the code which was a really large table it reduced the rendering time of the table by almost 80% by moving the events to a parent node that was static. This also helps if you need to update the contents as you don't have to re-bind any events.
Hope this helps.
I am asking myself if I am doing it right. The problem I have is that I want to preserve caret position after AngularJS update textarea value.
HTML looks like this:
<div ng-controlle="editorController">
<button ng-click="addSomeTextAtTheEnd()">Add some text at the end</button>
<textarea id="editor" ng-model="editor"></textarea>
</div>
My controller looks like this:
app.controller("editorController", function($scope, $timeout, $window) {
$scope.editor = "";
$scope.addSomeTextAtTheEnd = function() {
$timeout(function() {
$scope.editor = $scope.editor + " Period!";
}, 5000);
}
$scope.$watch("editor", function editorListener() {
var editor = $window.document.getElementById("editor");
var start = editor.selectionStart;
var end = editor.selectionEnd;
$scope.$evalAsync(function() {
editor.selectionStart = start;
editor.selectionEnd = end;
});
});
});
Let say I start typing some text in textarea. Then I hit the button which will soon add " Period!" at the end of $scope.editor value. During the 5 seconds timeout I make focus on textarea again and write some more text. After 5 seconds my textarea value is updated.
I am watching for $scope.editor value. The editorListener will be executed on every $digest cycle. In this cycle also happens two-way data binding. I need to correct caret position right after data binding. Is $scope.$evalAsync(...) the right place where should I do this or not?
Here is a directive I use to manipulate the caret position; however, like I stated in a comment, there is an issue with IE.
Below is something that may help you plan this out. One thing I noticed in your question is that you mention a condition where the user may re-focus the input box to type additional text, which I would believe resets the timeout; would this condition be true?
Instead of using a button to add text, would you rather just add it without? Like running the addSomeTextAtTheEnd function whenever a user un-focuses from the input box?
If you have to use the button, and a user re-focuses on the input box and types more into, you should cancel your button timeout.
Like:
myVar = setTimeout(function(){ alert("Hello"); }, 3000);
// Then clear the timeout in your $watch if there is any change to the input.
clearTimeout(myVar);
If you do it this way, perhaps you will not even need to know the cursor position as the addSomeTextAtTheEnd function timeout will just reset on any input change before the 5 second timeout. If the 5 second timeout occurs then the addSomeTextAtTheEnd will run and "append text to the end" like it's supposed to do. Please give more information and I will update this as needed.
app.directive('filterElement', ['$filter', function($filter){
return {
restrict:'A', // Declares an Attributes Directive.
require: '?ngModel', // ? checks for parent scope if one exists.
link: function( scope, elem, attrs, ngModel ){
if( !ngModel ){ return }
var conditional = attrs.rsFilterElement.conditional ? attrs.rsFilterElement.conditional : null;
scope.$watch(attrs.ngModel, function(value){
if( value == undefined || !attrs.rsFilterElement ){ return }
// Initialize the following values
// These values are used to set the cursor of the input.
var initialCursorPosition = elem[0].selectionStart
var initialValueLength = elem[0].value.length
var difference = false
// Sets ngModelView and ngViewValue
ngModel.$setViewValue($filter( attrs.rsFilterElement )( value, conditional ));
attrs.$$element[0].value = $filter( attrs.rsFilterElement )( value, conditional );
if(elem[0].value.length > initialValueLength){ difference = true }
if(elem[0].value.length < initialValueLength){ initialCursorPosition = initialCursorPosition - 1 }
elem[0].selectionStart = difference ? elem[0].selectionStart : initialCursorPosition
elem[0].selectionEnd = difference ? elem[0].selectionEnd : initialCursorPosition
});
} // end link
} // end return
}]);