How can this re-usable AngularJS service update on mousemove? - javascript

I developed a re-usable AngularJS service that allows the user to start, stop, and re-set a countdown timer from a view by clicking on buttons in the view. The re-usable service can be accessed through any controller that includes it. The working code for the minimal example countdown app can be viewed by clicking the link to this plnkr.
But I want the timer to be re-set to its max default value every time a user moves the mouse anywhere in the browser window. This means adding $window.addEventListener(...) somewhere in the service because the service has to be re-usable across any controller, while also responding to any movement of the mouse anywhere over the browser window, even the areas not contained within an HTML element linked to a controller. Thus, I cannot simply resort to adding ng-mousemove="somemethod()" in the html body tag the way this other example does. I would also prefer to avoid the $rootScope.broadcast approach taken by this other posting because I would like to keep the code isolated in the service as much as possible.
What specific changes need to be made to the code below so that the timer will be re-set to its default value any time the user moves the mouse anywhere in the browser window?
Though all of the code is in the plnkr for easy editing, I am also including it here.
index.html is:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Timer</title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></script>
<script src="myTimer.js" type="text/javascript"></script>
<script src="exampleController.js" type="text/javascript"></script>
<script src="app.js" type="text/javascript"></script>
</head>
<body ng-app="intervalExample">
<div>
<div ng-controller="ExampleController">
Test variable: {{ mytimer.testvariable }}<br>
Time Remaining : <font color='red'>{{mytimer.timeleft}}</font>
<br>
<button type="button" data-ng-click="mytimer.startCountdown()">Start Countdown</button>
<button type="button" data-ng-click="mytimer.stopCountdown()">Stop Countdown</button>
<button type="button" data-ng-click="mytimer.resetCountdown()">Reset Timer</button>
</div>
</div>
</body>
</html>
app.js is:
angular.('intervalExample',['ExampleController']);
exampleController.js is:
angular
.module('ExampleController', ['mytimer'])
.controller('ExampleController', ['$scope', 'mytimer', function($scope, mytimer) {
$scope.mytimer = mytimer;
}]);
myTimer.js is:
angular
.module('mytimer', [])
.service('mytimer', ['$rootScope', '$interval', function($rootScope, $interval) {
var $this = this;
this.testvariable = "some value. ";
this.timeleft = 15;
var stop;
this.startCountdown = function() {
// Don't start a new countdown if we are already counting down
if ( angular.isDefined(stop) ) return;
stop = $interval(function() {
if ($this.timeleft > 0 ) {
$this.timeleft = $this.timeleft - 1;
} else {
$this.stopCountdown();
}
}, 1000);
};
this.stopCountdown = function() {
if (angular.isDefined(stop)) {
$interval.cancel(stop);
stop = undefined;
}
};
this.resetCountdown = function() {
this.timeleft = 15;
};
// this.$on('$destroy', function() {
// Make sure that the interval is destroyed too
// $this.stopCountdown();
// });
function subsFunc() {
$window.addEventListener('mousemove', function(e) {
$this.timeleft = 15;
})
}
}]);

Issues to consider:
You are never calling subsFunc() and when you do will see that
$window is not injected in service
You will need to debounce the mousemove callback since the event
triggers about every pixel. Resetting your timer every pixel makes no
sense and would cause significant needless digests.
Use of directive for buttons would negate needing to inject in numerous controllers
Same for timer display ... can be directive and depending on UI combined with buttons

Related

Update background image without refresh angularjs

Hey everyone I am currently building a mobile app for my company and I have a angularjs function that is currently adding a css class based on whether it's day or night. It does what it's supposed to do perfectly by putting a day or night image based on whether it's day or night.
The issue I am running into is that when testing the app right before the clock let's say goes from 1:59 to 2 it doesn't change the background image until you refresh the page. I want it to automatically change the background image without having to refresh and can't seem to find a way to do it. I will link the code here!
Any help is appreciated as it has me completely stumped...
Here is what I have in my html.
<div class="dashboard-welcome" ng-class="{'hide-homescreen-image-landscape'
: isLandscape, 'homescreenImageDay' : isDay, 'homescreenImageNight' :
isNight }">
Here is where the function is being called
angular.module('starter').controller('homeCtrl', ['$scope', '$interval',
'jsBridge', 'authService', 'tierService', '$window', fnc]);
function fnc($scope, $interval, jsBridge, authService, tierService, $window)
{
$scope.$on('$ionicView.afterEnter', function (event, viewData) {
setOrientation();
$scope.isDay = !isDayTime(1000);
$scope.isNight = isDayTime(1000);
});
Here is where the function is instantiated. Basically checking what time it is.
var isDayTime = function () {
var h = new Date().getHours();
if (h >= 14) {
return true;
} else {
return false;
}
}
I can't supply all the code since this application is thousands of lines long but this is the working function as of now. Just need it to switch background images without refreshing using angularjs...
Assuming that inside your div you are using an background-image: url("...")
I suggest you set up an object to hold a single $scope.isDay value instead of doing the calculation twice.
Also use the $interval service to check every nth millisecond to update your $scope.isDay value.
The code below works fine in dynamically changing a background image on a page using its CSS class.
HTML:
<div ng-class="data.isDay ? 'back1' : 'back2'"></div>
JS:
var exampleApp = angular.module('exampleApp', []);
exampleApp.controller('exampleController', function($scope, $interval) {
$scope.data = {
isDay: true,
classVal: {
one: 'text-danger',
two: 'text-success'
}
};
$interval(function() {
$scope.toggleImage();
}, 600);
$scope.toggleImage = function() {
$scope.data.isDay = ($scope.data.isDay ? false : true)
};
$scope.toggleImage();
});
Here is also a plnkr demo

Variables not updating outside function

So, I have two controllers (they have been reduced for simplicity) with one function each. The functions change a variable inside each of the controllers. The thing is, it looks like only the variable in the first controller is updated.
app.js
(function() {
var app = angular.module('ScBO', []);
var token = {};
app.controller("LoginController", ['$http', function($http){
this.userData = {};
var lc = this;
this.in = false;
this.loginGo = function(){
$http.post(<link>, <userData>)
.then(function successCallback(response){
token = response.data.access_token;
lc.in=true;
}, function errorCallback(response){
lc.in=false;
});
};
}]);
app.controller("DashboardController", ['$http', function($http){
var dsb = this;
this.totalLocals = 0;
this.refresh = function(){
$http.get('<link>?access_token='+token)
.then(function successCallback(response){
dsb.totalLocals = response.data.number_of_locals.number_of_locals;
}, function errorCallback(response){
});
};
}]);
})();
index.html
<body ng-controller="LoginController as loginCtrl">
<div id="login-div" ng-hide="loginCtrl.in">
<form ...>
...
</form>
</div>
<div id="dashboard" ng-show="loginCtrl.in" ng-controller="DashboardController as dsb">
<div class="numbers">
<p>Number of Locals</p>
{{dsb.totalLocals}}
</div>
</div>
</body>
So in the html, the first div appears in the beginning and not the second one. Then it hides and the second div shows up. This means that they are following the update of the loginCtrl.in variable.
However, when I call the refresh function, it doesn't update the value in the last div.
I've tried with $scope and have been searching in here but I haven't been able to find a solution yet. Specially since the two controllers are equal but only one of them seems to be updating the variables normally.
So, I've figured out the problem.
In the code above the error doesn't show as I only posted part of the code for simplicity, but I thought it was enough.
The thing was: I had the button that triggers the refresh function in a different div (like #leonardoborges ' plunkr). Like that two controllers are instantiated with their own values, something that I didn't know. The timeout function updates all instances of the controller, so that was confusing me.
So now I just put everything within one instance of the controller.

Managing Multiple Counters in Angularjs

Within my application I need to use of multiple counters on the same page. Each of these counters will count from 0 to 100%, back to 0, and again to 100%.
I am using interval to accomplish this using the below simplified block of code
$interval(function() {
if (data[counter] < 100) {
data[counter] = data[counter] + interval;
} else {
data[counter] = 0;
}
}, 1000);
The requirements I am attempting to solve for are:
The amount of counters on the page may vary depending on results from a DB
Based on events, any particular counter may be started or stopped
The counters must be independently defined to facilitate unique counting intervals
I feel the best approach would be to create an independent block of code that could be executed when I expect counting to begin and a block where I can execute a stop command.
My first attempt at this was to create a service within Angular. It worked great for the first counter and solved for the last 2 requirements, however because of Angular treating services as singletons it did not allow for multiple independent counters on a page.
My questions is looking for direction on the best way to approach this. I've seen recommendations of creating services as APIs, but I also see the potential of using directives. Does anyone have recommendations?
Here is an answer based on directives. I split up the actual counter from the GUI. So, you could use any GUI you like.
The counter and GUI together look like this:
<counter count="counter1Count" api="counter1Api"></counter>
<counter-gui count="{{counter1Count}}" api="counter1Api"></counter-gui>
Notice how using the same variable links them together. Check out the code snippet for the full example:
var app = angular.module('myApp',[]);
app.controller('MyCtrl', ['$scope', function($scope) {
$scope.counter1Api={};
}]);
app.directive('counterGui',function(){
return {
restrict: 'E',
template : '<h1>{{count}}</h1>' +
'<button ng-click="api.start()" class="btn btn-default" type="button">Start</button>' +
'<button ng-click="api.stop()" class="btn btn-default" type="button">Stop</button>' +
'<button ng-click="api.reset()" class="btn btn-default" type="button">Reset</button>',
scope: {
count : "#",
api : "="
}
};
});
app.directive('counter',function(){
return {
restrict: 'E',
controller: ['$scope','$interval', function myCounterController($scope,$interval) {
var intervalPromise= null;
reset();
function reset() {
$scope.count= 0;
console.log("reset",$scope.count);
}
function start() {
// Make sure the timer isn't already running.
if (!intervalPromise){
intervalPromise= $interval(function() {
if ($scope.count < 100) {
$scope.count++;
} else {
$scope.count = 0;
}
}, 1000);
}
}
function stop() {
if (intervalPromise) {
$interval.cancel(intervalPromise);
intervalPromise = null;
}
}
$scope.api={
reset : reset,
start : start,
stop : stop
};
}],
scope: {
count : "=",
api : "="
}
};
});
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>AngularJS Counter Example</title>
<!-- AngularJS -->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.js"></script>
</head>
<body ng-app="myApp">
<div ng-controller="MyCtrl">
<h1>Counter 1</h1>
<counter count="counter1Count" api="counter1Api"></counter>
<counter-gui count="{{counter1Count}}" api="counter1Api"></counter-gui>
<h1>Counter 2</h1>
<counter count="counter2Count" api="counter2Api"></counter>
<counter-gui count="{{counter2Count}}" api="counter2Api"></counter-gui>
<h1>Counter 3</h1>
<p>Two GUIs displaying the same counter.</p>
<counter count="counter3Count" api="counter3Api"></counter>
<counter-gui count="{{counter3Count}}" api="counter3Api"></counter-gui>
<counter-gui count="{{counter3Count}}" api="counter3Api"></counter-gui>
</div>
</body>
</html>

Angular 2.0 and Mathjax not working well

I pieced together a semi working edition of mathjax with angular 2.0, but it is breaking in a manner I can not quite grasp. I have added a plunkr below to clearly demonstrate the situation.
In my code (not the plunkr) this is my relevant html:
<textarea
#editorArea
ngDefaultControl
spellcheck="false"
class="editor"
[(ngModel)]='assignment.content.text'
(keyup)="updateResult()"
[ngStyle]="{'height' : formatDimension(editorDimensions.height), 'padding' : formatDimension(editorDimensions.padding)}
"></textarea>
<div class="result" #result>{{ editorArea.value }}</div>
and this is the relevant update function triggered from the HTML:
#ViewChild('result') result : ElementRef;
updateResult() {
MathJax.Hub.Queue(["Typeset", MathJax.Hub, this.result.nativeElement]);
}
Finally this is my mathJax configuration:
<script type="text/x-mathjax-config">
MathJax.Hub.Config({tex2jax: {inlineMath: [['$','$'], ['\\(','\\)']]}});
</script>
http://plnkr.co/edit/lEJZZaxKUYxFGdLtWW7Z?p=preview
It seems like the main question here is how to repeatedly render the contents of the <textarea> into a separate area of the page using MathJax. This is covered in a simple case in the Modifying Math on the Page documentation.
Basically you have two options:
Option 1
Keep ahold of the rendered math element and then use the Text function to re-render with a new math string (Note: this requires that the whole textarea is one big math string, not normal text with math strings interspersed) Plunker:
HTML :
<div id="result">$ $ <!-- empty mathstring as placeholder -->
hello-mathjax.ts (portion):
ngOnInit() {
var self = this;
// callback function saves the rendered element, so it can
// be re-rendered on update with the "Text" function.
MathJax.Hub.Queue(
["Typeset",MathJax.Hub,"result"],
function () {self.resultElement = MathJax.Hub.getAllJax("result")[0];
self.updateResult();
}
);
}
updateResult () {
// re-render the same element with the value from the textarea
MathJax.Hub.Queue(["Text",this.resultElement,this.inputValue]);
}
updateResult () {
MathJax.Hub.Queue(["Text",this.resultElement,this.inputValue]);
}
Option 2
Wipe out the renderd div every time and completely re-render the contents of the textarea. (This is the way to go if the textarea will contain a mix of mathstrings and regular text) Plunker:
HTML:
<div id="result"></div> <!-- No placeholder math string needed -->
hello-mathjax.ts (portion):
ngOnInit() {
var self = this;
MathJax.Hub.Queue(
["Typeset",MathJax.Hub,"result"],
function () {
self.updateResult();
}
);
}
updateResult () {
var resultDiv = document.getElementById("result");
// Replace the rendered content entirely
// with the bare text from the textarea.
resultDiv.innerHTML = this.inputValue;
// Rerender the entire resultDiv
MathJax.Hub.Queue(["Typeset",MathJax.Hub,"result"]);
}
This plunker demonstrates rendering a <textarea> that contains a mix of non-math and math statements (e.g. test test $\frac 12$)
This Plunker demonstrates rendering a <textarea> that should be interpreted entirely as math statements (e.g. \frac {11}2)

Why does MathJax appear to toggle on and off as I type LaTeX?

I have "borrowed" the code below from other sources. As far as I can tell it is basically the same as
this MathJax demo page. The problem I'm having is that I don't see the results I have typed for odd numbered key presses. For example, when I type the first character I don't see anything in the MathPreview div. But, I see the first two characters after typing the second character. This pattern repeats so that it is as if MathJax toggles on for even key presses, but it turns off for odd numbered key presses. Any ideas why this is happening? This doesn't occur on the demo page I linked to above.
<!DOCTYPE html>
<html>
<head>
<title>Mathematics</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="bower_components/jquery/dist/jquery.min.js"></script>
<link rel="stylesheet" href="assets/css/styles.css">
<script src="bower_components/MathJax/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script>
var Preview = {
delay: 150, // delay after keystroke before updating
preview: null, // filled in by Init below
buffer: null, // filled in by Init below
timeout: null, // store setTimeout id
mjRunning: false, // true when MathJax is processing
oldText: null, // used to check if an update is needed
// Get the preview and buffer DIV's
Init: function () {
this.preview = document.getElementById("MathPreview");
this.buffer = document.getElementById("MathBuffer");
},
// Switch the buffer and preview, and display the right one.
// (We use visibility:hidden rather than display:none since
// the results of running MathJax are more accurate that way.)
SwapBuffers: function () {
var buffer = this.preview, preview = this.buffer;
this.buffer = buffer; this.preview = preview;
buffer.style.visibility = "hidden"; buffer.style.position = "absolute";
preview.style.position = ""; preview.style.visibility = "";
},
// This gets called when a key is pressed in the textarea.
// We check if there is already a pending update and clear it if so.
// Then set up an update to occur after a small delay (so if more keys
// are pressed, the update won't occur until after there has been
// a pause in the typing).
// The callback function is set up below, after the Preview object is set up.
Update: function () {
if (this.timeout) {clearTimeout(this.timeout)}
this.timeout = setTimeout(this.callback,this.delay);
},
// Creates the preview and runs MathJax on it.
// If MathJax is already trying to render the code, return
// If the text hasn't changed, return
// Otherwise, indicate that MathJax is running, and start the
// typesetting. After it is done, call PreviewDone.
CreatePreview: function () {
Preview.timeout = null;
if (this.mjRunning) return;
var text = document.getElementById("MathInput").value;
if (text === this.oldtext) return;
this.buffer.innerHTML = this.oldtext = text;
this.mjRunning = true;
MathJax.Hub.Queue(
["Typeset",MathJax.Hub,this.buffer],
["PreviewDone",this]
);
},
// Indicate that MathJax is no longer running,
// and swap the buffers to show the results.
PreviewDone: function () {
this.mjRunning = false;
this.SwapBuffers();
}
};
// Cache a callback to the CreatePreview action
Preview.callback = MathJax.Callback(["CreatePreview",Preview]);
Preview.callback.autoReset = true; // make sure it can run more than once
</script>
<script type="text/x-mathjax-config;executed=true">
MathJax.Hub.Config({
showProcessingMessages: false,
tex2jax: { inlineMath: [['$','$'],['\\(','\\)']] }
});
</script>
</head>
<body>
<div class="content">
<div id="MathJax_Message" style="display: none;"></div>
<div class="left_half_page">
<div class="content">
<div class="fill_with_padding">
<textarea class="content no_border" id="MathInput" onkeyup="Preview.Update()"></textarea>
</div>
</div>
</div>
<div class="right_half_page">
<div class="content">
<div class="fill_with_padding">
<div id="MathPreview" class="content">
<div id="MathBuffer">
<div>
<script>Preview.Init();</script>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
The problem is that your MathBuffer and MathPreview are nested. They should be siblings. The code uses a double-buffering technique that shows one buffer while the other is being typeset, and then switches the two. One is displayed while the other is hidden. If one is inside the other, you will only see the result every other keystroke.
Also, note that the contents of the buffers are replaced by the input, and so when you replace the MathPreview buffer, you remove the MathBuffer and the script it contains. Note that in the MathJax page that you link to, the two div's (the MathPreview and MathBuffer) are not nested, and the initialization script occurs after both of them (not nested within them).
If you fix the nesting problems, I think it will work for you.

Categories