Javascript execution timing - ensure synchronous flow, not just order of execution - javascript

I am working with JavaScript (Jquery) trying to run some operations in order. In my real code, I have a function I run on .ready() to initiate some variables. As part of this flow I read in some data from local storage to remove elements which were removed in a previous visit. The elements which I remove have a css transition class applied. When the page first loads, I do not want these animations to occur, so I require that the <body> element has the class="fully-loaded" applied in my CSS for transition to work. I am trying to add the fully-loaded class only after I know all of the additions and everything else is loaded. I have tried setting up multiple .ready() statements, using queues, etc. in order to control the order or execution.
While I think I have things running in order, the initiation function does not seem to be complete before I run the script to add the fully-loaded class. I am pasting a mini version of my code which exhibits the same problem. My question is: How can I ensure that anything run as part of my initiation function init() complete before I run the addClass call?
HTML body:
<body>
<section id="secA"> <p>Sec A</p> </section>
<section id="secB"> <p>Sec B</p> </section>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script>
//$(document).ready( Tester.init() );
//$(document).ready( $("body").addClass('fully-loaded') );
//$(document).ready( Tester.init() );
$.when(Tester.init()).done(function(){
$("body").addClass('fully-loaded');
console.log("Desired Order: 4");
});
</script>
</body>
JavaScript in another file
var Tester = (function() {
function init() {
initEvents();
}
function initEvents(){
console.log("Desired Order: 1");
setTimeout(
'$("#secA").remove(); console.log("Desired Order: 2");',
2000
);
console.log("Desired Order: 3");
}
return { init : init };
})();
CSS Code
section { width:200px; height:200px; position:absolute;}
#secA { background-color:green; }
#secB { background-color:red; }
section:nth-of-type(1) { top: 0; left: 0; }
section:nth-of-type(2) { top: 0; left: 50%; }
.fully-loaded section {
-webkit-transition: all 2.5s ease-in-out;
-moz-transition: all 2.5s ease-in-out;
transition: all 2.5s ease-in-out;
}
I think in my real code that there is something that is taking a bit of time which is async like the setTimeout. Desired Action: 4 occurs before Desired Action: 2 How do I make $("body").addClass('fully-loaded'); wait until everything started by Tester.init() is complete in a general way?

Related

how can i make text appear without clicking anything in HTML or JS?

I don't need help with getting text on my page in HTML, i know how to do that. You know how like in MS-DOS when loading stuff text appears like:
Starting MS-DOS...
MODE select code page function complete.
i've thought about calling a StartTimer and making it change the opacity of the div instantly but i'm not sure if that will work, i've been trying to find anything similar to my question online but there isn't anything. thanks if you help.
Your title indicates that you don't want to "click anything... [in] JS", so I assume that means that you're still ok with using JavaScript altogether given that you mention calling a function in your question body. Presumably you just want it to work immediately when the page loads as opposed to someone "clicking something".
If you just want to make it appear after some time use:
Your HTML markup:
<div id="myDiv" style="opacity:0">Starting MS-DOS</div>
And your JS:
setTimeout(function() {
document.querySelector("myDiv").style.opacity = 1;}
, 3000);
This will display the text to the page after 3 seconds immediately. You could also fade it into the screen with something like:
setTimeout(function() {
var interval = setInterval(function() {
document.querySelector("div#myDiv").style.opacity += 0.1;
if (document.querySelector("div#myDiv").style.opacity >= 1) {
clearInterval(interval);
}
}, 250);
}, 1000);
This approach would wait 1 second, then fade in the text over 2.5 seconds via the setInterval before clearing that interval upon hitting an opacity of 1.
How about this one, I have created this with only css
body {
background: black;
color: white;
}
#keyframes anim {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.start {
opacity: 0;
animation: anim 1.5s;
}
.end {
position: absolute;
top: 0;
opacity: 0;
animation: anim 1s 1.5s forwards;
}
<h2 class="start">Starting MS-DOS...</h2>
<h2 class="end">C:\></h2>

Jquery Fadein/FadeOut simultaneously

How are you?
This is from a previous post and a solution was posted.
JS :
$(document).ready(function() {
var allBoxes = $("div.boxes").children("div");
transitionBox(null, allBoxes.first());
});
function transitionBox(from, to) {
function next() {
var nextTo;
if (to.is(":last-child")) {
nextTo = to.closest(".boxes").children("div").first();
} else {
nextTo = to.next();
}
to.fadeIn(500, function() {
setTimeout(function() {
transitionBox(to, nextTo);
}, 5000);
});
}
if (from) {
from.fadeOut(500, next);
} else {
next();
}
}
JSFIDDLE HERE
However I was trying to extend this a bit, where when box 1 fades out, you can see box 2 fading in slightly at the same time - simultaneously, and as box2 fades out ...box 3 is fading in at the same time with the opacity going from 0 to 1
I'm fine and you? :') .
I have a solution that maybe can help.
Have you tried making 1 class named display and setting display: block; and then put it on the function as toggleClass(). Finally you make a new class named as .transition(I do this with all my project to make them easier) and put it on the div or add it with some code like: $("div").addClass("transition");.
the code for .transition should be like this:
.transition {
-webkit-transition: all 0.5s ease-in-out;
-moz-transition: all 0.5s ease-in-out;
-o-transition: all 0.5s ease-in-out;
transition: all 0.5s ease-in-out;
}
You can also try insted of CSS and jQuery using only CSS.
an example could be using CSS Animations. Define the class of every box and then make a animation and add a delay on every animation so it will show every certain time, make them infinite so the will loop.
Hope you understand :)
Editing line 14 of your jsFiddle to add a delay created a smoother effect so you don't see two at once. Which I surmise is the answer to the question.
Line 14 edits: to.delay(100).fadeIn(500, function () {

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'])

Starting a CSS animation every time a JavaScript function is called?

I'm trying to get a CSS3 animation to animate every time a function is called.
My CSS code for the animation is in a class called 'enable' and it simply starts the animation.
My FUNCTIONS code is as follows:
document.getElementById("red").className = "enable";
setTimeout(function() {
document.getElementById("red").className = "";
}, 1000);
That works properly when using the setTimeout function but when i change the entire FUNCTIONS code to say like this:
document.getElementById("red").className = "";
document.getElementById("red").className = "enable";
It only works once.
Why can't I remove the class and then add it immediately after.
Why doesn't my second code do the same thing as the first one?
Thanks in advanced!
It may be helpful if included your html/css.
You have to use the setTimeout, otherwise the browser is not quick enough to pick up the change. Since javascript runs in a single thread, there is some breathing time needed for the browser to react to the change in class.
Here is an example: http://jsfiddle.net/brendonparker/75TfR
<!-- CSS -->
<style>
#mydiv {
-webkit-transition: all ease 1s;
transition: all ease 1s;
}
#mydiv.enabled {
background: red;
}
</style>
<!-- HTML -->
<div id="mydiv" >
<p>Hello World</p>
<p>Test</p>
</div>
<!-- JS -->
<script>
function animate(){
var d = document.getElementById('mydiv');
d.className = 'enabled';
setTimeout(function(){
d.className = '';
}, 1000);
}
animate();
</script>

Using transitionend correctly in order to run script once final CSS3 transition has completed?

I'm trying to learn how transitionend is used with my CSS3 transitions so I have a set of images that are sized into a grid as well as the opacity changed from 0 - 1, ideally what I want to do is wait until all those images have finished and the final transitionend event has fired off before carrying on with my next code. At the moment I'm simply trying to log out a message when transitionend fires but I'm getting nothing which means I'm probably using this wrong. Can anyone advise how I could do this?
JS Fiddle: http://jsfiddle.net/mWE9W/2/
CSS
.image img {
position: absolute;
top: 0;
left: 0;
opacity: 0.01;
-webkit-transition: all 1s ease-in;
-webkit-transform: scale(0);
height: 150px;
width: 150px;
display:block;
}
.inner.active .image img {
-webkit-transform: scale(1);
top: 0;
left: 0;
opacity: 1;
}
JS
$('.image img').on('webkitTransitionEnd', function(e) {
console.log('this ran')
$('h2').fadeIn();
}, false);
1) You don't need last argument false in .on method call. Your callback never called because of that.
2) Once you'll remove that unneeded argument you'll notice that callback is actually called 16 times. This happens because you have 4 images with 4 transition proporties. Animating each property causes callback to be called. So you need to make some sort of check that image transition is complete, and only after all transitions are done call your .fadeIn() method. The code will look like following:
var imageCount = $('.image img').length, animatedCount = 0, animCompleteImages = $();
$('img').imagesLoaded(function() {
$('.inner').addClass('active').on('webkitTransitionEnd', 'img', function(e) {
if(!animCompleteImages.filter(this).length) {
animCompleteImages = animCompleteImages.add(this);
animatedCount++;
if(animatedCount === imageCount) {
$('h2').fadeIn();
}
}
});
});​
Working JS fiddle available here.

Categories

Resources