Javascript Singleton design pattern and closure - javascript

I'm trying to create a Singleton object that deals with servicing an event listener:
var ChildParent = (function () {
var html_element = document.getElementById("quoteNum");
var row_number = 0;
return {
init: function(){
html_element["parenttype"+row_number].addEventListener("click", ChildParent.fire, false);
},
add: function(total) { //Adding a row
},
fire: function() {
alert("it fired!");
}
}
})();
However, when I call ChildParent.init(), the document-element will not get assigned to html_element, so i can't attach the listener. Oddly enough, the row_number variable is initialized to zero. Is there some sort of scoping conflict that I don't understand? When i use the step-into feature of opera's dragonfly, I can't create var assignments when in the init() function.

Make sure element with id "quoteNum" is available on the page when you are executing your script.
It may happen that you are executing the script without waiting for all the page elements to be rendered.
You may think about handling window.onload.
Update
the following change could help you fix the issue with html_element being initialized before the page is rendered.
var ChildParent = (function () {
function getElement(){
return html_element ? html_element : html_element = document.getElementById("quoteNum");
}
var html_element;
var row_number = 0;
return {
init: function(){
getElement()["parenttype"+row_number].addEventListener("click", ChildParent.fire, false);
},
add: function(total) { //Adding a row
},
fire: function() {
alert("it fired!");
}
}
})();

Is the above code executed after the element with id quoteNum exists in the DOM?
If not html_element will be null.
So make sure that all the above code is in a script tag that occurs somewhere in the document below the element (or is in a window.onload jQuery's document ready type event).

Related

Jquery .change() event fires only once

So I'm fairly novice with jquery and js, so I apologise if this is a stupid error but after researching I can't figure it out.
So I have a list of data loaded initially in a template, one part of which is a dropdown box that lets you filter the data. My issue is that the filtering only works once? As in, the .change function inside $(document).ready() only fires the once.
There are two ways to reload the data, either click the logo and reload it all, or use the search bar. Doing either of these at any time also means the .change function never fires again. Not until you refresh the page.
var list_template, article_template, modal_template;
var current_article = list.heroes[0];
function showTemplate(template, data)
{
var html = template(data);
$("#content").html(html);
}
$(document).ready(function()
{
var source = $("#list-template").html();
list_template = Handlebars.compile(source);
source = $("#article-template").html();
article_template = Handlebars.compile(source);
source = $("#modal-template").html();
modal_template = Handlebars.compile(source);
showTemplate(list_template,list);
$(".articleButton").click(function()
{
var index = $(this).data("id");
current_article = list.heroes[index];
showTemplate(article_template,current_article);
$('.poseThumb').click(displayModal);
});
$("#classFilter").change(function()
{
console.log("WOW!");
var classToFilter = this.value;
var filteredData =
{
heroes: list.heroes.filter(function(d)
{
if (d.heroClass.search(classToFilter) > -1)
{
return true;
}
return false;
})
};
console.log(filteredData);
showTemplate(list_template,filteredData);
$(".articleButton").click(function()
{
var index = $(this).data("id");
current_article = filteredData.heroes[index];
showTemplate(article_template,current_article);
$('.poseThumb').click(displayModal);
});
});
$("#searchbox").keypress(function (e)
{
if(e.which == 13)
{
var rawSearchText = $('#searchbox').val();
var search_text = rawSearchText.toLowerCase();
var filteredData =
{
heroes: list.heroes.filter(function(d)
{
if (d.name.search(search_text) > -1)
{
return true;
}
return false;
})
};
console.log(filteredData);
showTemplate(list_template,filteredData);
$(".articleButton").click(function()
{
var index = $(this).data("id");
current_article = filteredData.heroes[index];
showTemplate(article_template,current_article);
$('.poseThumb').click(displayModal);
});
}
});
$("#logo").click(function()
{
showTemplate(list_template,list);
$(".articleButton").click(function()
{
var index = $(this).data("id");
current_article = list.heroes[index];
showTemplate(article_template,current_article);
$('.poseThumb').click(displayModal);
});
});
//$("#logo").click();
});
function displayModal(event)
{
var imageNumber = $(this).data("id");
console.log(imageNumber);
var html = modal_template(current_article.article[0].vicPose[imageNumber]);
$('#modal-container').html(html);
$("#imageModal").modal('show');
}
I should note two things: first, that the search bar works perfectly, and the anonymous function inside both of them is nearly identical, and like I said, the filtering works perfectly if you try it after the initial load. The second is that the same problem occurs replacing .change(anonymous function) with .on("change",anonymous function)
Any help or advice would be greatly appreciated. Thanks.
I agree with Fernando Urban's answer, but it doesn't actually explain what's going on.
You've created a handler attached to an HTML element (id="classFilter") which causes part of the HTML to be rewritten. I suspect that the handler overwrites the HTML which contains the element with the handler on it. So after this the user is clicking on a new HTML element, which looks like the old one but doesn't have a handler.
There are two ways round this. You could add code inside the handler which adds the handler to the new element which has just been created. In this case, that would mean making the handler a named function which refers to itself. Or (the easier way) you could do what Fernando did. If you do this, the event handler is attached to the body, but it only responds to clicks on the #classFilter element inside the body. In other words, when the user clicks anywhere on the body, jQuery checks whether the click happened on a body #classFilter element. This way, it doesn't matter whether the #classFilter existed when the handler was set. See "Direct and delegated events" in jQuery docs for .on method.
Try to use some reference like 'body' in the event listeners inside your DOM like:
$('body').on('click','.articleButton', function() {
//Do your stuff...
})
$('body').on('click','#classFilter', function() {
//Do your stuff...
})
$('body').on('keypress','#searchbox', function() {
//Do your stuff...
})
$('body').on('click','#logo', function() {
//Do your stuff...
})
This will work that you can fire it more than once.

JS prevent a function from running as onclick event

I would like to disable a certain function from running as an onclick event.
Here, I would like to disable myfunc1, not myfunc2. Actually I want to disable myfunc1 from the whole page, but anyway this is the only thing that I need.
I have no control over the page and I am using userscript or other script injection tools to achieve this.
What I've tried:
Redefining the function after the page has loaded: I've tried adding an event listener to an event DOMContentLoaded with function(){ myfunc1 = function(){}; }
This seems to be working, but in a fast computer with fast internet connection, sometimes it runs before the myfunc1 is defined (in an external js file that is synchronously loaded). Is there any way that I can guarantee that the function will be executed after myfunc1 is defined?
Is there any way that I can 'hijack' the onclick event to remove myfunc1 by its name?
You should use event listeners, and then you would be able to remove one with removeEventListener. If you can't alter the HTML source you will need something dirty like
function myfunc1() {
console.log('myfunc1');
}
function myfunc2() {
console.log('myfunc2');
}
var a = document.querySelector('a[onclick="myfunc1();myfunc2();"]');
a.setAttribute('onclick', 'myfunc2();');
Click me
Or maybe you prefer hijacking the function instead of the event handler:
function myfunc1() {
console.log('myfunc1');
}
function myfunc2() {
console.log('myfunc2');
}
var a = document.querySelector('a[onclick="myfunc1();myfunc2();"]');
var myfunc1_;
a.parentNode.addEventListener('click', function(e) { // Hijack
if(a.contains(e.target)) {
myfunc1_ = window.myfunc1;
window.myfunc1 = function(){};
}
}, true);
a.addEventListener('click', function(e) { // Restore
window.myfunc1 = myfunc1_;
myfunc1_ = undefined;
});
Click me
Another way this could be done is using Jquery and setting the onlick propery on the anchor tag to null. Then you could attach a click function with just myfunc2() attached.
$(document).ready(function () {
$("a").prop("onclick", null);
$("a").click(function(){
myfunc2();
});
});
function myfunc1() {
console.log('1');
}
function myfunc2() {
console.log('2');
}
<a class="test" href="#" onclick="myfunc1();myfunc2();">Example</a>
You can see the codepen here - http://codepen.io/anon/pen/BLBYpO
Perhaps you are into jQuery.
$(document).ready(function(){
var $btn = $('button[onclick*="funcOne()"]');
$btn.each(function(){
var newBtnClickAttr;
var $this = $(this);
var btnClickAttr = $this.attr("onclick");
newBtnClickAttr = btnClickAttr.replace(/funcOne\(\)\;/g, "");
$this.attr("onclick", newBtnClickAttr);
});
});
Where in the variable $btn gets all the button element with an onclick attribute that contains funcOne().
In your case, this would be the function you would like to remove on the attribute e.g., myfunc1();.
Now that you have selected all of the elements with that onclick function.
Loop them and get there current attribute value and remove the function name by replacing it with an empty string.
Now that you have the value which does not contain the function name that you have replace, you can now update the onclick attribute value with the value of newBtnClickAttr.
Check this Sample Fiddle

jQuery global/local event execution order

I have a panel widget with a button. Clicking the button should execute some global actions related to all such widgets and after that execute some local actions related to this widget instance only. Global actions are binded in a separate javascript file by CSS class like this:
var App = function ()
{
var handleWidgetButton = function ()
{
$('.widgetBtn').on('click', function (e)
{
// do smth global
});
return {
init: function ()
{
handleWidgetButton();
}
};
}
}();
jQuery(document).ready(function()
{
App.init();
});
And in the html file local script is like this:
$("#widgetBtn1234").click(function (e)
{
// do smth local
});
Currently local script is executed first and global only after while I want it to be the opposite. I tried to wrap local one also with document.ready and have it run after global but that doesn't seem to change the execution order. Is there any decent way to arrange global and local jQuery bindings to the same element?
The problem you're having comes from using jQuery's .ready() function to initialize App, while you seem to have no such wrapper in your local code. Try the following instead:
var App = function ()
{
var handleWidgetButton = function ()
{
$('.widgetBtn').on('click', function (e)
{
// do smth global
});
return {
init: function ()
{
handleWidgetButton();
}
};
}
}();
$(function()
{
App.init();
});
Then in your local JS:
$(function() {
$("#widgetBtn1234").click(function (e)
{
// do smth local
});
});
Note that $(function(){}) can be used as shorthand for $(document).ready(function(){});. Also, make sure your JS file is located before your local JS, as javascript runs sequentially.
Alternatively, you can use setTimeout() to ensure everything's loaded properly:
(function executeOnReady() {
setTimeout(function() {
// Set App.isInitialized = true in your App.init() function
if (App.isInitialized) runLocalJs();
// App.init() hasn't been called yet, so re-run this function
else executeOnReady();
}, 500);
})();
function runLocalJs() {
$("#widgetBtn1234").click(function (e)
{
// do smth local
});
};
How about this instead:
var widget = $("#widgetBtn1234").get(0);//get the vanilla dom element
var globalHandler = widget.onclick; //save old click handler
// clobber the old handler with a new handler, that calls the old handler when it's done
widget.onclick = function(e){
//do smth global by calling stored handler
globalHandler(e);
//afterward do smth local
};
There might be a more jqueryish way to write this, but I hope the concept works for you.
-------VVVV----keeping old answer for posterity----VVVV--------
Why not something like this?
var App = function ()
{
var handleWidgetButton = function ()
{
$('.widgetBtn').on('click', function (e)
{
// do smth global
if(this.id === 'widgetBtn1234'){
//do specific things for this one
}
});
return {
init: function ()
{
handleWidgetButton();
}
};
}
}();
Please excuse any syntax errors I might have made as I haven't actually tested this code.
Check out my simple JQ extension I created on jsbin.
http://jsbin.com/telofesevo/edit?js,console,output
It allows to call consequentially all defined personal click handlers after a global one, handle missed handlers case if necessary and easily reset all personal handlers.

Do I need to remove and replace event handlers with JQuery to swap them?

I want the events click and touchstart to trigger a function.
Of course this is simple with JQuery. $('#id').on('click touchstart', function{...});
But then once that event is triggered, I want that same handler to do something else when the events are triggered,
and then later, I want to go back to the original handling function.
It seems like there must be a cleaner way to do this than using $('#id').off('click touchstart'); and then re-applying the handler.
How should I be doing this?
You can create a counter variable in some construct in your javascript code that allows you to decide how you want to handle your event.
$(function() {
var trackClicks = (function() {
var clicks = true;
var getClicks = function() {
return clicks;
};
var eventClick = function() {
clicks = !clicks;
};
return {
getClicks: getClicks,
eventClicks: eventClicks
}
})();
$('#id').on('click touchstart', function {
if (trackClicks.getClicks()) {
handler1();
} else {
handler2();
}
trackClicks.eventClick();
});
function handler1() { //firsthandler};
function handler2() { //secondhandler};
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
The way I would do this is by creating a couple of functions for the handler function to call based on certain flags. Sudo code would be something like this:
function beginning_action() {
...
}
function middle() {
...
}
var beginning_state = true;
$('#id').on('click touchstart', function{
if(beginning_state) {
beginning_action();
} else {
middle();
}
});
Then all you need to do is change the variable beginning_state to change which function is called. Of course you would give them better names that describe what they do and not when they do it.
Additionally, if you want the handler to call more than two functions you can change the beginning_state variable from a boolean to an int and check it's value to determine which function to call.
Good luck!

Function becomes undefined after first trigger

I have a block of code like so:
function doSomething() {
someVar.on("event_name", function() {
$('#elementId').click(function(e) {
doSomething();
});
});
}
// and on document ready
$(function () {
$('#anotherElemId').click(function () {
doSomething();
});
});
The problem that I'm encountering is that when I call doSomething() from anotherElemId click event(that is binded on document ready) it works as expected, but calling it recursively from elementId click doesn't work.
Any ideas? Thinking is something trivial that I'm missing.
Is someVar an actual jQuery reference to a dom element? (e.g. $('#someitem'))
The second problem is you cant put a .click event inside a function that you would like to instantiate later on. If you are trying to only allow #elementId to have a click event AFTER some previous event, try testing if a tester variable is true:
var activated = false;
$(function () {
$('#anotherElemId').click(function () {
activated = true;
});
$('#secondElemId').on("event_name", function() {
if (activated) {
// code that happens only after #anotherElemId was clicked.
}
});
});

Categories

Resources