Better coding of making a check each time function runs - javascript

I am trying to eliminate the useless check in a function, needed check variable is assigned at start-up of the app, so I don't want to check for it each time.
I have a code like this:
btnClose.addEventListener('click', function(e) {
window.close()
if (appSettings.sendAnonymousStats) {
visitor.event("Application", "App has been closed.").send()
}
})
This is my first try to optimize it, so now it doesn't need to do a "if" check each time it's called;
let btnCloseEv = appSettings.sendAnonymousStats ? btnClose.addEventListener('click', function(e) {
window.close()
visitor.event("Application", "App has been closed.").send()
}) : btnClose.addEventListener('click', function(e) {
window.close()
})
I wonder if theoretically there are better ways to achieve what I am trying to achieve?

Removing a single if statement, especially considering it only occurs once per click will not effect running time at all.
However for the purpose of discussion say it was performance critical, like if it was attached to onmousemove, then you can adjust your second approach with a small change to reduce code redundancy.
let btnCloseEv = btnClose.addEventListener('click',
appSettings.sendAnonymousStats ?
function(e) {
window.close();
visitor.event("Application", "App has been closed.").send();
} : function(e) {
window.close();
}
)
This works because functions in JS are higher order functions, which means they treated as variables and can be passed around in the same way variables can be. For instance this would work if a and b were numbers, or functions, or any other type of variable.
var c = someBoolean ? a : b;
Say each function was a lot bigger and you wanted to use this approach yet things were becoming unreadable, it would be better to name each function and attach them like so:
function moreComplexFunc(e) {
window.close();
visitor.event("Application", "App has been closed.").send();
// More complex code
}
function simpleFunc(e) {
window.close();
}
let btnCloseEv = btnClose.addEventListener(
'click',
appSettings.sendAnonymousStats ? moreComplexFunc : simpleFunc
)
Now say that you noticed there was a lot of code duplication in moreComplexFunc and simpleFunc, you could go a step further, and separate the similar code into a 3rd function like so:
function commonFunc(e) {
window.close();
}
function func1(e) {
commonFunc(e);
visitor.event("Application", "App has been closed.").send();
// other code
}
function func2(e) {
commonFunc(e);
// other code
}
let btnCloseEv = btnClose.addEventListener(
'click',
appSettings.sendAnonymousStats ? func1 : func2
)
The opportunities in a language which supports Higher Order Functions are really endless.

Just in case the function was a little more complex, another approach would be to simply put it in an additional event handler:
let btnCloseEv = btnClose.addEventListener('click',
function(e) {
window.close();
/*
... more code ...
*/
}
)
if (appSettings.sendAnonymousStats) {
btnClose.addEventListener('click',
function(e) {
visitor.event("Application", "App has been closed.").send();
}
}

Related

Reusing code between different event handlers

I have one code block which I want to invoke in different scenarios when a click is triggered, depending on whether the event is direct or is delegated.
But on changing the code to on, it only works partially.
I have one code:
$(document).on('click','.selected-option',function(event){
//lot of code
I want to use:
$('.selected-option').click(function(event){ //lots of code }
I want to use this together like:
if (some condition)
{
$(document).on('click','.selected-option',function(event){
}
else
{
$('.selected-option').click(function(event){
}
and want to use the same code.
You don't have to use anonymous functions to handle events. Just write a regular function:
function handleClick(event) {
// lots of code
}
Then bind the function to as many events as you want:
if (some condition) {
$(document).on('click','.selected-option', handleClick);
else {
$('.selected-option').click(handleClick);
}
define a function and do the job;
var funCalled = function(){
//your detailed actions
}
and call it in different conditions!
if (some condition) {
$(document).on('click','.selected-option',function(event){
funCalled()
})
} else {
$('.selected-option').click(function(event){
funCalled()
});
}
var testfunction = function(currentObj){
// your code here
}
if (some condition)
{
$(document).on('click','.selected-option',function(event){
testfunction($(this));
});
}
else {
$('.selected-option').click(function(event){
testfunction($(this));
});
}

Dynamically change the handler of a click binding in knockout.js

I got the following binding working like a charm :
<button class="flatButton buttonPin" data-bind="click:EnterPinMode">Add pin</button>
In my viewmodel I define the Handler like this :
self.EnterPinMode = function(data,event)
{
//Doing several things here
//....
}
Now, let's say I want to change the behavior of that button after the first click on it...how could I do it ? I already managed quite easily to change the button text :
self.EnterPinMode = function(data,event)
{
//Doing several things here
//....
var curButton = $(event.target);
curButton.text("Cancel");
}
But what about changing the button behaviour ? If I had set this handler through jQuery, that wouldn't be an issue, but is there a way to "replace" the click binding on that control so that now it will call ExitPinMode handler, for example.
I've got some doubts on this being possible given the fact that knockout works only with declarative binding (at least without plugin...), but I thought it was worth asking.
Please note that I will actually need some kind of 3 ways toggle, I just simplify it here to a "normal" toggle for the sake of the example.
I think using a hasBeenClicked flag that's private to the view model is fine, and probably the best solution for this.
If you really want to swap out the handler, that should be easy enough, though, with something like this:
function enterPinMode() {
//Doing several things here
//....
var curButton = $(event.target);
curButton.text("Cancel");
//set click handler to a step 2 function
self.pinAction = exitPinMode;
}
function exitPinMode() {
//....
}
self.pinAction = enterPinMode;
Maybe one of the simplest solution is to add a boolean like hasBeenClicked set to false at the begining and then set it to true.
Example:
self.hasBeenClicked = false;
self.EnterPinMode = function(data,event)
{
if (!self.hasBeenClicked )
{
var curButton = $(event.target);
curButton.text("Cancel");
self.hasBeenClicked = true;
}
else
{
//behaviour an a second click
}
}
Hope it helps !
You can try this
var vm = function () {
var self = this;
var nextState = 'firstState';
var states = {
firstState: function () {
nextState = 'secondState';
//Do stuff
},
secondState: function () {
nextState = 'thirdState';
//Do stuff
},
thirdState: function () {
nextState = 'firstState';
//Do stuff
}
}
self.EnterPinMode = function () {
states[nextState].call();
}
}
What you should try to remember first about MVVM is that you are designing an object to represent your view. If your view will have different states, There is nothing wrong with having your viewmodel know about these states and knowing what to do in what state. Stick with MVVM. You wont be disappointed.

Making my mouseevent coded in jquery a lot more elegant

I wrote this in order to fix the problem IE has with select drop down lists being truncated if their options were longer than the default value of the select. Now it works fine but I want to improve the code in order to learn how to write things in a much more useable fashion.
$(document).ready(function() {
if ($.browser.msie) {
$('select').focus(function() { $(this).addClass('expand').removeClass('clicked'); })
$('select').blur(function() { $(this).removeClass('expand clicked'); })
$('select').mousedown(function () { $(this).addClass('expand').removeClass('clicked'); } )
$('select').hover(function () { }, function () {if (!$(this).hasClass('clicked')) { $(this).removeClass('expand'); $(this.blur()) }})
$('select').click (function() { $(this).toggleClass('clicked'); })
$('select').change(function(){ $(this).removeClass('expand clicked'); $('select.widerIE').blur() })
}
});
I tried making functions which were called by each event but that seemed to fail eg:
$('select').click(test (a))
function test (a) {
$(a).addClass('expand').removeClass('clicked')
}
It's not clear to me what you're trying to achive. One thing is sure - you can't define a event handler like that (see note below):
$('select').click(test (a))
Note: Technically, you could define your event handler like in code above. For that to work, function test would have to return a function that would be actual handler for the event.

Detecting when jasmine tests complete

I am running jasmine tests like this;
jasmine.getEnv().addReporter(new jasmine.TrivialReporter());
jasmine.getEnv().execute();
I would like to detect, using JavaScript, when the tests complete. How can I?
As #Xv. suggests, adding a reporter will work. You can do something as simple as:
jasmine.getEnv().addReporter({
jasmineDone: function () {
// the specs have finished!
}
});
See http://jasmine.github.io/2.2/custom_reporter.html.
Some alternative ways:
A) Use the ConsoleRunner, that accepts an onComplete option. Older versions (1.2rc1) receive the complete callback as a standalone parameter.
Since you also supply the function that writes (options.print) you keep control about having the test reports written to the console.
You can have several reporters active at the same time jasmineEnv.addReporter().
B) Haven't tried, but you could create your own reporter, with empty implementations of every public method but jasmineDone()
C) Check an old post in the Jasmine google group, where the author saves and overrides jasmine.getEnv().currentRunner().finishCallback:
var oldCallback = jasmineEnv.currentRunner().finishCallback;
jasmineEnv.currentRunner().finishCallback = function () {
oldCallback.apply(this, arguments);
$("body").append( "<div id='_test_complete_signal_'></div" );
};
jasmineEnv.execute();
I found two different ways to solve this issue. One is to hack jasmine to throw a custom event when it completes. Because I wanted to screen scrape after the test loaded, I inserted the event trigger into jasmine-html.js at the end of "reportRunnerResults"
$( 'body' ).trigger( "jasmine:complete" );
Then it's a matter of listening for the event:
$( 'body' ).bind("jasmine:complete", function(e) { ... }
In my case, I was running jasmine in an iFrame and wanted to pass the results to a parent window, so I trigger an event in the parent from my first bind:
$(window.parent).find('body').trigger("jasmine:complete");
It is also possible to do this without jquery. My strategy was to poll for text to be added to the "finished-at" span. In this example I poll every .5 seconds for 8 seconds.
var counter = 0;
function checkdone() {
if ( $('#test-frame' ).contents().find('span.finished-at').text().length > 0) {
...
clearInterval(timer);
} else {
counter += 500;
if (counter > 8000) {
...
clearInterval(timer);
}
}
}
var timer = setInterval( "checkdone()", 500 );
I'm running Jasmine 1.3.1 with the HtmlReporter. I ended up hooking in like this:
var orig_done = jasmineEnv.currentRunner_.finishCallback;
jasmineEnv.currentRunner_.finishCallback = function() {
orig_done.call(this);
// custom code here
};

Get text from field on keyup, but with delay for further typing

I have a form which is submitted remotely when the various elements change. On a search field in particular I'm using a keyup to detect when the text in the field changes. The problem with this is that when someone types "chicken" then the form is submitted seven times, with only the last one counting.
What would be better is something like this
keyup detected - start waiting (for one second)
another keyup detected - restart waiting time
waiting finishes - get value and submit form
before I go off and code my own version of this (I'm really a backend guy with only a little js, I use jQuery for everything), is there already an existing solution to this? It seems like it would be a common requirement. A jQuery plugin maybe? If not, what's the simplest and best way to code this?
UPDATE - current code added for Dan (below)
Dan - this may be relevant. One of the jQuery plugins I'm using on the page (tablesorter) requires this file - "tablesorter/jquery-latest.js", which, if included, leads to the same error with your code as before:
jQuery("input#search").data("timeout", null) is undefined
http‍://192.168.0.234/javascripts/main.js?1264084467
Line 11
Maybe there's some sort of conflict between different jQuery definitions? (or something)
$(document).ready(function() {
//initiate the shadowbox player
// Shadowbox.init({
// players: ['html', 'iframe']
// });
});
jQuery(function(){
jQuery('input#search')
.data('timeout', null)
.keyup(function(){
jQuery(this).data('timeout', setTimeout(function(){
var mytext = jQuery('input#search').val();
submitQuizForm();
jQuery('input#search').next().html(mytext);
}, 2000)
)
.keydown(function(){
clearTimeout(jQuery(this).data('timeout'));
});
});
});
function submitQuizForm(){
form = jQuery("#searchQuizzes");
jQuery.ajax({
async:true,
data:jQuery.param(form.serializeArray()),
dataType:'script',
type:'get',
url:'/millionaire/millionaire_quizzes',
success: function(msg){
// $("#chooseQuizMainTable").trigger("update");
}
});
return true;
}
Sorry i haven't tested this and it's a bit off the top of my head, but something along these lines should hopefully do the trick. Change the 2000 to however many milliseconds you need between server posts
<input type="text" id="mytextbox" style="border: 1px solid" />
<span></span>
<script language="javascript" type="text/javascript">
jQuery(function(){
jQuery('#mytextbox')
.data('timeout', null)
.keyup(function(){
clearTimeout(jQuery(this).data('timeout'));
jQuery(this).data('timeout', setTimeout(submitQuizForm, 2000));
});
});
</script>
Here's your fancy jquery extension:
(function($){
$.widget("ui.onDelayedKeyup", {
_init : function() {
var self = this;
$(this.element).keyup(function() {
if(typeof(window['inputTimeout']) != "undefined"){
window.clearTimeout(inputTimeout);
}
var handler = self.options.handler;
window['inputTimeout'] = window.setTimeout(function() {
handler.call(self.element) }, self.options.delay);
});
},
options: {
handler: $.noop(),
delay: 500
}
});
})(jQuery);
Use it like so:
$("input.filterField").onDelayedKeyup({
handler: function() {
if ($.trim($(this).val()).length > 0) {
//reload my data store using the filter string.
}
}
});
Does a half-second delay by default.
As an update, i ended up with this which seems to work well:
function afterDelayedKeyup(selector, action, delay){
jQuery(selector).keyup(function(){
if(typeof(window['inputTimeout']) != "undefined"){
clearTimeout(inputTimeout);
}
inputTimeout = setTimeout(action, delay);
});
}
I then call this from the page in question's document.ready block with
afterDelayedKeyup('input#search',"submitQuizForm()",500)
What would be nice would be to make a new jquery event which uses this logic, eg .delayedKeyup to go alongside .keyup, so i could just say something like this for an individual page's document.ready block.
jQuery('input#search').delayedKeyup(function(){
submitQuizForm();
});
But, i don't know how to customise jquery in this way. That's a nice homework task though.
Nice job, Max, that was very helpful to me! I've made a slight improvement to your function by making it more general:
function afterDelayedEvent(eventtype, selector, action, delay) {
$(selector).bind(eventtype, function() {
if (typeof(window['inputTimeout']) != "undefined") {
clearTimeout(inputTimeout);
}
inputTimeout = setTimeout(action, delay);
});
}
This way you can use it for any type of event, although keyup is probably the most useful here.
I know this is old, but it was one of the first results when I was searching for how to do something like this so I though I would share my solution. I used a combination of the provided answers to get what I needed out of it.
I wanted a custom event that worked just like the existing jQuery events, and it needed to work with keypress + delete, backspace and enter.
Here's my jQuery plugin:
$.fn.typePause = function (dataObject, eventFunc)
{
if(typeof dataObject === 'function')
{
eventFunc = dataObject;
dataObject = {};
}
if(typeof dataObject.milliseconds === 'undefined')
dataObject.milliseconds = 500;
$(this).data('timeout', null)
.keypress(dataObject, function(e)
{
clearTimeout($(this).data('timeout'));
$(this).data('timeout', setTimeout($.proxy(eventFunc, this, e), dataObject.milliseconds));
})
.keyup(dataObject, function(e)
{
var code = (e.keyCode ? e.keyCode : e.which);
if(code == 8 || code == 46 || code == 13)
$(this).triggerHandler('keypress',dataObject);
});
}
I used $.proxy() to preserve the context in the event, though there could be a better way to do this, performance-wise.
To use this plugin, just do:
$('#myElement').typePause(function(e){ /* do stuff */ });
or
$('#myElement').typePause({milliseconds: 500, [other data to pass to event]},function(e){ /* do stuff */ });

Categories

Resources