Spinner in Angular.js - javascript

I want to use a spinner which I can show during some of the rest api calls for a better UX. I have come across many existing github projects which does exactly similar things.
https://github.com/cgross/angular-busy
https://github.com/urish/angular-spinner
But I'm not able to use any of the existing projects. I think before I start writing something of my own, I want to know if things which I'm looking for can be done using these projects or any other existing project.
Requirement:
During some of the rest api calls like uploading images, fetching some data, deleting images, etc, I want to show a spinner with background faded. Once I have the result, I can show the background again and remove the spinner.
I want to use this spinner with start/stop from my controller not from my html.
I don't want this spinner for all the xhr requests by default.
I think angular-busy demo does solves most of the above requirements except that it needs a promise param in html. Is there anyway by which I can control the start/stop dynamically from my controller rather than giving a promise.
Angular-spinner demo is good but it doesn't fade out background. Is there any way to fade out background ?
Can anyone give me some pointers how exactly can I solve my problem ?

I always create my own spinner with this logic:
js:
app.directive('ngSpinnerBar', ['$rootScope',
function ($rootScope) {
return {
link: function (scope, element, attrs) {
// by defult hide the spinner bar
element.addClass('hide');
// count how many time requests were sent to the server
// so when they all done the spinner will be removed
scope.counter = 0;
$rootScope.$on('$stateNetworkRequestStarted', function () {
scope.counter++;
element.removeClass('hide'); // show spinner bar
// $('body').addClass('page-on-load');
});
$rootScope.$on('$stateNetworkRequestEnded', function () {
scope.counter--;
if (scope.counter <= 0) {
scope.counter = 0;
element.addClass('hide'); // show spinner bar
// $('body').removeClass('page-on-load'); // remove page loading indicator
}
});
}
};
}
])
html:
<div ng-spinner-bar></div>
As you can see every time i send a request to the api i show the spinner (css create the spinning - link) and when result come back i send event to hide the spinner.
if you want to make things easier for you, you should create a service which send all the api requests (wrap $http). that way you can ensure every request will show the spinner.
EDIT
the first result in google gave me this - fade background in angular

It seems like http://bsalex.github.io/angular-loading-overlay/_site/ fits the requirements.
For example:
var app = angular.module('app-http-integration-with-reference-id-and-matchers', [
'bsLoadingOverlay',
'bsLoadingOverlayHttpInterceptor'
])
.factory('randomTextInterceptor', function(bsLoadingOverlayHttpInterceptorFactoryFactory) {
return bsLoadingOverlayHttpInterceptorFactoryFactory({
referenceId: 'random-text-spinner',
requestsMatcher: function(requestConfig) {
return requestConfig.url.indexOf('hipsterjesus') !== -1;
}
});
})
.factory('randomUserInterceptor', function(bsLoadingOverlayHttpInterceptorFactoryFactory) {
return bsLoadingOverlayHttpInterceptorFactoryFactory({
referenceId: 'random-user-spinner',
requestsMatcher: function(requestConfig) {
return requestConfig.url.indexOf('randomuser') !== -1;
}
});
})
.config(function($httpProvider) {
$httpProvider.interceptors.push('randomTextInterceptor');
$httpProvider.interceptors.push('randomUserInterceptor');
}).run(function($sce, bsLoadingOverlayService) {
bsLoadingOverlayService.setGlobalConfig({
/*
It is only an example, don't use this url in production.
Copy this template to your code base or use integration with Spin.js (see Docs & Examples)
*/
templateUrl: $sce.trustAsResourceUrl('https://raw.githubusercontent.com/bsalex/angular-loading-overlay/gh-pages/_site/loading-overlay-template.html')
});
});
app.controller('HttpIntegrationWithReferenceIdAndMatchersController', function($scope, $http, $sce, bsLoadingOverlayService) {
$scope.randomText = $sce.trustAsHtml('Fetch result here');
$scope.randomUser = undefined;
$scope.fetchRandomText = function() {
$http.get('http://hipsterjesus.com/api/')
.success(function(data) {
$scope.randomText = $sce.trustAsHtml(data.text);
})
.error(function() {
$scope.randomText = $sce.trustAsHtml('Can not get the article');
});
};
$scope.fetchRandomUser = function() {
$http.get('https://randomuser.me/api/')
.success(function(data) {
$scope.randomUser = data.results[0];
});
};
});
.random-result {
display: flex;
height: 170px;
margin-top: 1em;
}
.random-result__text,
.random-result__user {
position: relative;
overflow: auto;
border: 2px dashed #C00;
flex: 1;
margin: 0.5em;
padding: 0.5em;
text-align: center;
}
.user__photo {
width: 100px;
height: 100px;
border-radius: 50%;
margin: 0;
}
.user__name {
font-size: 1.5em;
margin: 0.5em;
padding: 0;
text-align: center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></script>
<script src="https://rawgit.com/bsalex/angular-loading-overlay/master/dist/angular-loading-overlay.js"></script>
<script src="https://rawgit.com/bsalex/angular-loading-overlay-http-interceptor/master/dist/angular-loading-overlay-http-interceptor.js"></script>
<div ng-app="app-http-integration-with-reference-id-and-matchers">
<div ng-controller="HttpIntegrationWithReferenceIdAndMatchersController">
<div class="well well-lg bs-loading-container">
<button ng-click="fetchRandomText()">Fetch random text</button>
<button ng-click="fetchRandomUser()">Fetch random user</button>
<div class="random-result">
<div class="random-result__text" bs-loading-overlay bs-loading-overlay-reference-id="random-text-spinner" bs-loading-overlay-delay="3000">
<p ng-bind-html="randomText"></p>
</div>
<div class="random-result__user user" bs-loading-overlay bs-loading-overlay-reference-id="random-user-spinner" bs-loading-overlay-delay="3000">
<div ng-if="randomUser">
<img ng-src="{{randomUser.picture.large}}" alt="" class="user__photo" />
<p class="user__name">
{{randomUser.name.first}} {{randomUser.name.last}}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
In the snippet above I've used integration with $http service. It matches requests and shows spinner with specified referenceId.
Also, the following features are available:
You can show and hide spinners from controllers injecting bsLoadingOverlayService and calling bsLoadingOverlayService.start(); and bsLoadingOverlayService.stop();;
You can wrap Promises to show and hide spinners with bsLoadingOverlayService.wrap();
You can create preconfigured handlers (bsLoadingOverlayService.createHandler({referenceId: 'handler-overlay'});), to keep options in one place and then just call preconfiguredHandler.start(); and preconfiguredHandler.stop();

Related

angular-loading-overlay doesn't show anything

I have an AngularJS frontent app, where I am using angular-loading-overlay library. The problem is that it doesn't show me my spinner.
I have a template with an assigned controller:
<div ng-controller="FooCtrl" data-ng-init="initFoo()">
<div class="bs-loading-container" bs-loading-overlay="FOO" bs-loading-overlay-template-url="/static/spinner.html">
... here some ng-repeat, data for it is loading inside initFoo();
</div>
</div>
The controller itself:
app.controller("FooCtrl", ['$scope', '$http', 'bsLoadingOverlayService',
function ($scope, $http, bsLoadingOverlayService) {
$scope.initFoo = function () {
bsLoadingOverlayService.start({referenceId: 'FOO'});
$http.get("/model").then(function (response) {
// some long-loading data
});
bsLoadingOverlayService.stop({referenceId: 'FOO'});
};
}]);
CSS fragment:
.bs-loading-container {
position: relative;
}
spinner.html itself is some div with text inside (I have simplified it for testing purposes):
<div style="position: absolute; width: 100%; height: 100%; top: 0; left: 0; background-color: rgba(255,255,255,0.7);">
<h1>Please, wait...</h1>
</div>
There is no errors in console. The method initFoo() works fine, it loads a data and the data is drawn in the page after it is loaded fine. But during loading no spinner appears.
Furthermore, if I deliberately change bs-loading-overlay-template-url to improper path, then I receive errors in console. That means, I guess, that library angular-loading-overlay itself was loaded well.
I know that it is always very specific, but may be any ideas what could I miss and what should I check?
Your problem is that you are showing the overlay, and hiding it instantly
$http.get is a asynchronous. so you should hide the overlay inside the handler methods. Try this:
$scope.initFoo = function () {
bsLoadingOverlayService.start({referenceId: 'FOO'});
$http.get("/model").then(function (response) {
// some long-loading data
bsLoadingOverlayService.stop({referenceId: 'FOO'});
});
};

Browser not rendering updated values after callback function is called

I'm using a callback function after an animation, and I'm getting a result that I don't understand.
The reader's current_state should increase by 1, then the reader should move right, and then this should repeat 3 more times.
When I run the program using my web inspector, I see that the current_state does indeed increase before moving each step. However, in my browser window I only see the reader's text change values during the first step. So when the program ends, I see a 2 in the reader's text on my screen, but the value of the reader's current_state is actually 5.
To perplex me even more, I was tinkering around and added a random button with an empty click event attached. Hitting this button will make the current_state appear as the reader's text on the screen. So if I repeatedly click this button while the program runs, it looks perfect.
I'm not looking for an entirely different way to do this, as this isn't my actual code, just an example to illustrate the issue. In my code I'm trying to stick to using angular and a recursive callback function after an animation, if it's possible to fix this issue.
Thanks in advance, this has been driving me crazy!
https://jsfiddle.net/snookieordie/ns09cvqc/7/
var app = angular.module("turingApp", []);
app.controller("turingController", ["$scope", function ($scope) {
$scope.reader = {
current_state: 1,
}
$scope.run_program = function() {
if($scope.reader.current_state < 5) {
$scope.reader.current_state++;
$(".reader").animate({"left": "+=50px"}, 1000, function() {
$scope.run_program();
});
}
}
}]);
CSS:
.reader {
position: relative;
height: 50px;
width: 50px;
left: 0px;
font-size: 35px;
background-color: coral;
}
HTML:
<body ng-app="turingApp" ; ng-controller="turingController">
<div class="reader">{{reader.current_state}}</div>
<br/><br/>
<input type="button" class="code_submit" value="Run Code" ng-click="run_program()" />
<br/><br/>
<input type="button" value="Empty Click Event" ng-click="" />
</body>
Use $scope.apply() just after animate call back function is being called, Below I have edited js code
var app = angular.module("turingApp", []);
app.controller("turingController", ["$scope", function($scope) {
$scope.reader = {
current_state: 1,
}
$scope.run_program = function() {
if ($scope.reader.current_state < 5) {
$scope.reader.current_state++;
$(".reader").animate({"left": "+=50px"}, 1000, function() {
$scope.run_program();
$scope.$apply(); // Have added this line onlye
});
}
}
}]);
Why this is needed? actually angular has watchers concept and when you are doing this complex kind of functionality watchers need to keep deep eye on variable changes, which is not present auto to achieve speed, but can be added/enhanced manually.

Show loading animation for slow script using AngularJS?

In angularjs 1.2 operations like filtering an ng-repeat with many rows (>2,000 rows) can become quite slow (>1 sec).
I know I can optimize execution times using limitTo, pagination, custom filters, etc. but I'm still interested to know if it's possible to show a loading animation while the browser is busy running long scripts.
In case of angular I think that could be invoked whenever $digest is running because that seems to be the main function that takes up most time and might be called several times.
In a related question there were no useful answers given. Any help greatly appreciated!
The problem is that as long as Javascript is executing, the UI gets no chance to update. Even if you present a spinner before filtering, it will appear "frozen" as long as Angular is busy.
A way to overcome this is to filter in chunks and, if more data are available, filter again after a small $timeout. The timeout gives the UI thread a chance to run and display changes and animations.
A fiddle demonstrating the principle is here.
It does not use Angular's filters (they are synchronous). Instead it filters the data array with the following function:
function filter() {
var i=0, filtered = [];
innerFilter();
function innerFilter() {
var counter;
for( counter=0; i < $scope.data.length && counter < 5; counter++, i++ ) {
/////////////////////////////////////////////////////////
// REAL FILTER LOGIC; BETTER SPLIT TO ANOTHER FUNCTION //
if( $scope.data[i].indexOf($scope.filter) >= 0 ) {
filtered.push($scope.data[i]);
}
/////////////////////////////////////////////////////////
}
if( i === $scope.data.length ) {
$scope.filteredData = filtered;
$scope.filtering = false;
}
else {
$timeout(innerFilter, 10);
}
}
}
It requires 2 support variables: $scope.filtering is true when the filter is active, giving us the chance to display the spinner and disable the input; $scope.filteredData receives the result of the filter.
There are 3 hidden parameters:
the chunk size (counter < 5) is small on purpose to demonstrate the effect
the timeout delay ($timeout(innerFilter, 10)) should be small, but enough to give the UI thread some time to be responsive
the actual filter logic, which should probably be a callback in real life scenarios.
This is only a proof of concept; I would suggest refactoring it (to a directive probably) for real use.
Here are the steps:
First, you should use CSS animations. No JS
driven animations and GIFs should be used within heavy processes bec. of the single thread limit. The animation will freeze. CSS animations are separated from the UI thread and they are supported on IE 10+ and all major browsers.
Write a directive and place it outside of your ng-view with
fixed positioning.
Bind it to your app controller with some special flag.
Toggle this directive's visibility before and after long/heavy processes.
(You can even bind a text message to the directive to display some
useful info to the user). -- Interacting with this or anything else directly within a loop of heavy process will take way longer time to finish. That's bad for the user!
Directive Template:
<div class="activity-box" ng-show="!!message">
<img src="img/load.png" width="40" height="40" />
<span>{{ message }}</span>
</div>
activity Directive:
A simple directive with a single attribute message. Note the ng-show directive in the template above. The message is used both to toggle the activity indicator and also to set the info text for the user.
app.directive('activity', [
function () {
return {
restrict: 'EA',
templateUrl: '/templates/activity.html',
replace: true,
scope: {
message: '#'
},
link: function (scope, element, attrs) {}
};
}
]);
SPA HTML:
<body ng-controller="appController">
<div ng-view id="content-view">...</div>
<div activity message="{{ activityMessage }}"></div>
</body>
Note that the activity directive placed outside of ng-view. It will be available on each section of your single-page-app.
APP Controller:
app.controller('appController',
function ($scope, $timeout) {
// You would better place these two methods below, inside a
// service or factory; so you can inject that service anywhere
// within the app and toggle the activity indicator on/off where needed
$scope.showActivity = function (msg) {
$timeout(function () {
$scope.activityMessage = msg;
});
};
$scope.hideActivity = function () {
$timeout(function () {
$scope.activityMessage = '';
}, 1000); // message will be visible at least 1 sec
};
// So here is how we do it:
// "Before" the process, we set the message and the activity indicator is shown
$scope.showActivity('Loading items...');
var i;
for (i = 0; i < 10000; i += 1) {
// here goes some heavy process
}
// "After" the process completes, we hide the activity indicator.
$scope.hideActivity();
});
Of course, you can use this in other places too. e.g. you can call $scope.hideActivity(); when a promise resolves. Or toggling the activity on request and response of the httpInterceptor is a good idea too.
Example CSS:
.activity-box {
display: block;
position: fixed; /* fixed position so it doesn't scroll */
z-index: 9999; /* on top of everything else */
width: 250px;
margin-left: -125px; /* horizontally centered */
left: 50%;
top: 10px; /* displayed on the top of the page, just like Gmail's yellow info-box */
height: 40px;
padding: 10px;
background-color: #f3e9b5;
border-radius: 4px;
}
/* styles for the activity text */
.activity-box span {
display: block;
position: relative;
margin-left: 60px;
margin-top: 10px;
font-family: arial;
font-size: 15px;
}
/* animating a static image */
.activity-box img {
display: block;
position: absolute;
width: 40px;
height: 40px;
/* Below is the key for the rotating animation */
-webkit-animation: spin 1s infinite linear;
-moz-animation: spin 1s infinite linear;
-o-animation: spin 1s infinite linear;
animation: spin 1s infinite linear;
}
/* keyframe animation defined for various browsers */
#-moz-keyframes spin {
0% { -moz-transform: rotate(0deg); }
100% { -moz-transform: rotate(359deg); }
}
#-webkit-keyframes spin {
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(359deg); }
}
#-o-keyframes spin {
0% { -o-transform: rotate(0deg); }
100% { -o-transform: rotate(359deg); }
}
#-ms-keyframes spin {
0% { -ms-transform: rotate(0deg); }
100% { -ms-transform: rotate(359deg); }
}
#keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(359deg); }
}
Hope this helps.
Use spin.js and the site http://fgnass.github.com/spin.js/ shows the step which is quite easy.
the loading animation is in CSS which separated from the UI thread and therefore loaded smoothly.
What you can do is detect the end of the ngRepeat as this post says.
I'll do something like, in the controller:
$scope.READY = false;
And in the directive, as the post above says, I'll do something like:
if (scope.$last) {
$scope.READY = true;
}
And you can have a css based loader/spinner with
<div class="loader" ng-show="!READY">
</div>
Ofcourse you can also have css based animations which are independent of js execution.
You could run the filter in another thread using WebWorkers, so your angularjs page won't block.
If you don't use webworkers the browser could get a javascript execution timeout and stop your angular app completely and even if you don't get any timeout your application freezes until the calculation is done.
UPDATE 23.04.14
I've seen a major performance improvement in a large scale project using scalyr and bindonce
Here is an working example :-
angular
.module("APP", [])
.controller("myCtrl", function ($scope, $timeout) {
var mc = this
mc.loading = true
mc.listRendered = []
mc.listByCategory = []
mc.categories = ["law", "science", "chemistry", "physics"]
mc.filterByCategory = function (category) {
mc.loading = true
// This timeout will start on the next event loop so
// filterByCategory function will exit just triggering
// the show of Loading... status
// When the function inside timeout is called, it will
// filter and set the model values and when finished call
// an inbuilt $digest at the end.
$timeout(function () {
mc.listByCategory = mc.listFetched.filter(function (ele) {
return ele.category === category
})
mc.listRendered = mc.listByCategory
$scope.$emit("loaded")
}, 50)
}
// This timeout is replicating the data fetch from a server
$timeout(function () {
mc.listFetched = makeList()
mc.listRendered = mc.listFetched
mc.loading = false
}, 50)
$scope.$on("loaded", function () { mc.loading = false })
})
function makeList() {
var list = [
{name: "book1", category: "law"},
{name: "book2", category: "science"},
{name: "book1", category: "chemistry"},
{name: "book1", category: "physics"}
]
var bigList = []
for (var i = 0; i <= 5000; i++) {
bigList = bigList.concat(list)
}
return bigList
}
button {
display: inline-block;
}
<html>
<head>
<title>This is an Angular Js Filter Workaround!!</title>
</head>
<body ng-app="APP">
<div ng-controller="myCtrl as mc">
<div class = "buttons">
<label>Select Category:- </label>
<button ng-repeat="category in mc.categories" ng-click="mc.filterByCategory(category)">{{category}}</button>
</div>
<h1 ng-if="mc.loading">Loading...</h1>
<ul ng-if="!mc.loading">
<li ng-repeat="ele in mc.listRendered track by $index">{{ele.name}} - {{ele.category}}</li>
</ul>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js"></script>
</body>
<html>
Promise/deferred can be used in this case, you can call notify to watch the progress of your code, documentation from angular.js: https://code.angularjs.org/1.2.16/docs/api/ng/service/$q
Here is a tutorial on heavy JS processing that uses ng-Promise: http://liamkaufman.com/blog/2013/09/09/using-angularjs-promises/, hope it is helpful.
//app Factory for holding the data model
app.factory('postFactory', function ($http, $q, $timeout){
var factory = {
posts : false,
getPosts : function(){
var deferred = $q.defer();
//avoiding the http.get request each time
//we call the getPosts function
if (factory.posts !== false){
deferred.resolve(factory.posts);
}else{
$http.get('posts.json')
.success(function(data, status){
factory.posts = data
//to show the loading !
$timeout(function(){
deferred.resolve(factory.posts)
}, 2000);
})
.error(function(data, status){
deferred.error('Cant get the posts !')
})
};
return deferred.promise;
},
getPost : function(id){
//promise
var deferred = $q.defer();
var post = {};
var posts = factory.getPosts().then(function(posts){
post = factory.posts[id];
//send the post if promise kept
deferred.resolve(post);
}, function(msg){
deferred.reject(msg);
})
return deferred.promise;
},
};
return factory;
});
You can use this code taken from this url:
http://www.directiv.es/angular-loading-bar
there you can find a workin demo also.
Here is thei code:
<!DOCTYPE html>
<html ng-app="APP">
<head>
<meta charset="UTF-8">
<title>angular-loading-bar example</title>
<link rel="stylesheet" type="text/css" href="/application/html/js/chieffancypants/angular-loading-bar/loading-bar.min.css"/>
<style>
body{
padding:25px;
}
</style>
</head>
<body ng-controller="ExampleController">
<button id="reloader" ng-click="getUsers()">Reload</button>
<ul ng-repeat="person in data">
<li>{{person.lname}}, {{person.fname}}</li>
</ul>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.2/angular.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.2/angular-animate.min.js"></script>
<script src="/application/html/js/chieffancypants/angular-loading-bar/loading-bar.min.js"></script>
<script>
angular.module('APP', ['chieffancypants.loadingBar', 'ngAnimate']).
controller("ExampleController",['$scope','$http',function($scope,$http){
$scope.getUsers = function(){
$scope.data=[];
var url = "http://www.filltext.com/?rows=10&fname={firstName}&lname={lastName}&delay=3&callback=JSON_CALLBACK"
$http.jsonp(url).success(function(data){
$scope.data=data;
})
}
$scope.getUsers()
}])
</script>
</body>
</html>
How do I use it?
Install it via npm or bower
$ npm install angular-loading-bar
$ bower install angular-loading-bar
To use, simply include it as a dependency in your app and you're done!
angular.module('myApp', ['angular-loading-bar'])

Scrolling log file (tail -f) animation using javascript

I'd like to create an animation on a website to mimic a scrolling log file or tail -f. I'd feed it a list of fake log messages and they would be written to the bottom of the div and scroll up and off the top as new messages are displayed and then loop around. It needs to look authentic, white on black using a fixed width font etc.
Does anyone know of any javascript or jQuery libraries which could help me with this? I'm a beginner with javascript, so any advice on how to approach this would be much appreciated.
I've made a simple example for you
http://jsfiddle.net/manuel/zejCD/1/
// some demo data
for(var i=0; i<100; i++) {
$("<div />").text("log line " + i).appendTo("#tail")
}
// scroll to bottom on init
tailScroll();
// add button click
$("button").click(function(e) {
e.preventDefault();
$("<div />").text("new line").appendTo("#tail");
tailScroll();
});
// tail effect
function tailScroll() {
var height = $("#tail").get(0).scrollHeight;
$("#tail").animate({
scrollTop: height
}, 500);
}
#tail {
border: 1px solid blue;
height: 500px;
width: 500px;
overflow: hidden;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div id="tail">
<div>some line of text</div>
</div>
<button>Add Line</button>
Here is a great solution
This uses an ajax request, and the HTTP Range: header to request only the last ~30KB of a log file. It then polls for data appended to that file, and only ever retrieves new data (no refreshing the whole file, or even the last 30KB). Handles file truncation too.
https://github.com/ukhas/js-logtail#readme
I've updated Manuel van Rijn's script to include a timer and a toggle switch, along with some minor changes to the log lines. hope this helps.
http://jsfiddle.net/5rLw3LoL/
html:
<div id="tail">
<div>some line of text</div>
</div>
<button>Add Line</button>
js:
var tailcounter = 100;
var tailswitch = false;
// scroll to bottom on init
tailScroll();
// add line to log
function tailappend() {
$("<div />").text("log line " + tailcounter).appendTo("#tail");
tailcounter++;
tailScroll();
}
// auto update every second
var t = setInterval(tailappend, 1000);
// toggle updates button click
$("button").click(function (e) {
e.preventDefault();
switch (tailswitch) {
case false:
clearInterval(t); // turns off auto update
tailswitch = true;
alert("auto update off");
break;
case true:
t = setInterval(tailappend, 1000); // restarts auto update
tailswitch = false;
alert("auto update on");
break;
}
});
// tail effect
function tailScroll() {
var height = $("#tail").get(0).scrollHeight;
$("#tail").animate({
scrollTop: height
}, 500);
}
css: (important for formatting)
#tail {
border: 1px solid blue;
height: 400px;
width: 500px;
overflow: hidden;
}
This can be achieved with CSS by simply flipping the outer and inner container using transform: rotateX(180deg); https://jsfiddle.net/tnrn6h59/2/
Only issue here is that the scroll is also reversed, not an issue for mobile.

Wait cursor over entire html page

Is it possible to set the cursor to 'wait' on the entire html page in a simple way? The idea is to show the user that something is going on while an ajax call is being completed. The code below shows a simplified version of what I tried and also demonstrate the problems I run into:
if an element (#id1) has a cursor style set it will ignore the one set on body (obviously)
some elements have a default cursor style (a) and will not show the wait cursor on hover
the body element has a certain height depending on the content and if the page is short, the cursor will not show below the footer
The test:
<html>
<head>
<style type="text/css">
#id1 {
background-color: #06f;
cursor: pointer;
}
#id2 {
background-color: #f60;
}
</style>
</head>
<body>
<div id="id1">cursor: pointer</div>
<div id="id2">no cursor</div>
Do something
</body>
</html>
Later edit...
It worked in firefox and IE with:
div#mask { display: none; cursor: wait; z-index: 9999;
position: absolute; top: 0; left: 0; height: 100%;
width: 100%; background-color: #fff; opacity: 0; filter: alpha(opacity = 0);}
<a href="#" onclick="document.getElementById('mask').style.display = 'block'; return false">
Do something</a>
The problem with (or feature of) this solution is that it will prevent clicks because of the overlapping div (thanks Kibbee)
Later later edit...
A simpler solution from Dorward:
.wait, .wait * { cursor: wait !important; }
and then
Do something
This solution only shows the wait cursor but allows clicks.
If you use this slightly modified version of the CSS you posted from Dorward,
html.wait, html.wait * { cursor: wait !important; }
you can then add some really simple jQuery to work for all ajax calls:
$(document).ready(function () {
$(document).ajaxStart(function () { $("html").addClass("wait"); });
$(document).ajaxStop(function () { $("html").removeClass("wait"); });
});
or, for older jQuery versions (before 1.9):
$(document).ready(function () {
$("html").ajaxStart(function () { $(this).addClass("wait"); });
$("html").ajaxStop(function () { $(this).removeClass("wait"); });
});
I understand you may not have control over this, but you might instead go for a "masking" div that covers the entire body with a z-index higher than 1. The center part of the div could contain a loading message if you like.
Then, you can set the cursor to wait on the div and don't have to worry about links as they are "under" your masking div. Here's some example CSS for the "masking div":
body { height: 100%; }
div#mask { cursor: wait; z-index: 999; height: 100%; width: 100%; }
This seems to work in firefox
<style>
*{ cursor: inherit;}
body{ cursor: wait;}
</style>
The * part ensures that the cursor doesn't change when you hover over a link. Although links will still be clickable.
I have been struggling with this problem for hours today.
Basically everything was working just fine in FireFox but (of course) not in IE.
In IE the wait cursor was showing AFTER the time consuming function was executed.
I finally found the trick on this site:
http://www.codingforums.com/archive/index.php/t-37185.html
Code:
//...
document.body.style.cursor = 'wait';
setTimeout(this.SomeLongFunction, 1);
//setTimeout syntax when calling a function with parameters
//setTimeout(function() {MyClass.SomeLongFunction(someParam);}, 1);
//no () after function name this is a function ref not a function call
setTimeout(this.SetDefaultCursor, 1);
...
function SetDefaultCursor() {document.body.style.cursor = 'default';}
function SomeLongFunction(someParam) {...}
My code runs in a JavaScript class hence the this and MyClass (MyClass is a singleton).
I had the same problems when trying to display a div as described on this page. In IE it was showing after the function had been executed. So I guess this trick would solve that problem too.
Thanks a zillion time to glenngv the author of the post. You really made my day!!!
Easiest way I know is using JQuery like this:
$('*').css('cursor','wait');
css: .waiting * { cursor: 'wait' }
jQuery: $('body').toggleClass('waiting');
Why don't you just use one of those fancy loading graphics (eg: http://ajaxload.info/)? The waiting cursor is for the browser itself - so whenever it appears it has something to do with the browser and not with the page.
To set the cursor from JavaScript for the whole window, use:
document.documentElement.style.cursor = 'wait';
From CSS:
html { cursor: wait; }
Add further logic as needed.
Try the css:
html.waiting {
cursor: wait;
}
It seems that if the property body is used as apposed to html it doesn't show the wait cursor over the whole page. Furthermore if you use a css class you can easily control when it actually shows it.
Here is a more elaborate solution that does not require external CSS:
function changeCursor(elem, cursor, decendents) {
if (!elem) elem=$('body');
// remove all classes starting with changeCursor-
elem.removeClass (function (index, css) {
return (css.match (/(^|\s)changeCursor-\S+/g) || []).join(' ');
});
if (!cursor) return;
if (typeof decendents==='undefined' || decendents===null) decendents=true;
let cname;
if (decendents) {
cname='changeCursor-Dec-'+cursor;
if ($('style:contains("'+cname+'")').length < 1) $('<style>').text('.'+cname+' , .'+cname+' * { cursor: '+cursor+' !important; }').appendTo('head');
} else {
cname='changeCursor-'+cursor;
if ($('style:contains("'+cname+'")').length < 1) $('<style>').text('.'+cname+' { cursor: '+cursor+' !important; }').appendTo('head');
}
elem.addClass(cname);
}
with this you can do:
changeCursor(, 'wait'); // wait cursor on all decendents of body
changeCursor($('#id'), 'wait', false); // wait cursor on elem with id only
changeCursor(); // remove changed cursor from body
I used a adaptation of Eric Wendelin's solution. It will show a transparent, animated overlay wait-div over the whole body, the click will be blocked by the wait-div while visible:
css:
div#waitMask {
z-index: 999;
position: absolute;
top: 0;
right: 0;
height: 100%;
width: 100%;
cursor: wait;
background-color: #000;
opacity: 0;
transition-duration: 0.5s;
-webkit-transition-duration: 0.5s;
}
js:
// to show it
$("#waitMask").show();
$("#waitMask").css("opacity"); // must read it first
$("#waitMask").css("opacity", "0.8");
...
// to hide it
$("#waitMask").css("opacity", "0");
setTimeout(function() {
$("#waitMask").hide();
}, 500) // wait for animation to end
html:
<body>
<div id="waitMask" style="display:none;"> </div>
... rest of html ...
My Two pence:
Step 1:
Declare an array. This will be used to store the original cursors that were assigned:
var vArrOriginalCursors = new Array(2);
Step 2:
Implement the function cursorModifyEntirePage
function CursorModifyEntirePage(CursorType){
var elements = document.body.getElementsByTagName('*');
alert("These are the elements found:" + elements.length);
let lclCntr = 0;
vArrOriginalCursors.length = elements.length;
for(lclCntr = 0; lclCntr < elements.length; lclCntr++){
vArrOriginalCursors[lclCntr] = elements[lclCntr].style.cursor;
elements[lclCntr].style.cursor = CursorType;
}
}
What it does:
Gets all the elements on the page. Stores the original cursors assigned to them in the array declared in step 1. Modifies the cursors to the desired cursor as passed by parameter CursorType
Step 3:
Restore the cursors on the page
function CursorRestoreEntirePage(){
let lclCntr = 0;
var elements = document.body.getElementsByTagName('*');
for(lclCntr = 0; lclCntr < elements.length; lclCntr++){
elements[lclCntr].style.cursor = vArrOriginalCursors[lclCntr];
}
}
I have run this in an application and it works fine.
Only caveat is that I have not tested it when you are dynamically adding the elements.
BlockUI is the answer for everything. Give it a try.
http://www.malsup.com/jquery/block/
This pure JavaScript seems to work pretty well ... tested on FireFox, Chrome, and Edge browsers.
I'm not sure about the performance of this if you had an overabundance of elements on your page and a slow computer ... try it and see.
Set cursor for all elements to wait:
Object.values(document.querySelectorAll('*')).forEach(element => element.style.cursor = "wait");
Set cursor for all elements back to default:
Object.values(document.querySelectorAll('*')).forEach(element => element.style.cursor = "default");
An alternative (and perhaps a bit more readable) version would be to create a setCursor function as follows:
function setCursor(cursor)
{
var x = document.querySelectorAll("*");
for (var i = 0; i < x.length; i++)
{
x[i].style.cursor = cursor;
}
}
and then call
setCursor("wait");
and
setCursor("default");
to set the wait cursor and default cursor respectively.
Lots of good answers already, but none of them mentions the <dialog> element.
Using this element we can create a solution similar to the masking <div>.
Here we use showModal() to "hide" elements, and we use ::backdrop to set the cursor style to wait on the entire page:
function showWaitDialog() {
document.getElementById('id_dialog').showModal();
}
#id_dialog, #id_dialog::backdrop {
cursor: wait;
}
<button onclick="showWaitDialog()">click me</button>
<dialog id="id_dialog">busy...</dialog>
The dialog is hidden by default, and can be shown using either the show() method, or the showModal() method, which prevents clicking outside the dialog.
The dialog can be forced to close using the close() method, if necessary.
However, if your button links to another page, for example, then the dialog will disappear automatically as soon as the new page is loaded.
Note that the dialog can also be closed at any time by hitting the Esc key.
CSS can be used to style the dialog however you like.
The example uses the html onclick attribute, just for simplicity. Obviously, addEventListener() could also be used.
Late to the party but simply give the Html tag an id by targeting
document.documentElement
and in the CSS place at the top
html#wait * {
cursor: wait !important;
}
and simply remove it when you want to stop this cursor.

Categories

Resources