I have a single shared jQuery function that checks a RadioButton selection: if 1 is selected, it hides a span, otherwise it shows it.
This shared function is called both on startup and on Change, because on startup, it needs to do the same thing. The startup works, but the onChange reference does NOT work:
JS_OBJ = {
toggleTier : function() {
if ($('input[name="tier"]:checked').val() == 'Y_YES')
{
$('#tierSpan').hide();
}
else
{
$('#tierSpan').show();
}
},
// this is called from document.onReady - it comes here, OK
onReady : function() {
// on startup, toggle Tier - works OK
this.toggleTier();
// Also link the radio button Change to this shared function
$('input[name="tier"]:radio').change(function () {
alert('About to enter toggle...');
// NEVER COMES HERE - Object doesn't support this property or method
this.toggleTier();
});
}
};
the this is changing value as it is passing thru the different zones. when it is first instantiated, it has a good value, but the radiobutton:change has a different this
I was able to change it get it to work:
$('input[name="tier"]:radio').change(function () {
alert('About to enter toggle...');
self; //closure
toggleTier();
});
see this: What underlies this JavaScript idiom: var self = this?
Inside the change event, this does not refer to the current JS_OBJ, it refers to the current event target in stead. You want to explicitly save your reference to this, so you can use it inside the event.
Example:
onReady : function() {
var me = this;
me.toggleTier();
// Also link the radio button Change to this shared function
$('input[name="tier"]:radio').change(function () {
me.toggleTier();
});
}
Related
I am using $.observable(array).insert() to append items to a list. This is updating my view as it should: new list items are rendered to the DOM. However, I would like to issue a click event on the new DOM node (I'm relying on the event to add a class to expand the item and attach another listener to the body so the area can be closed).
I have tried both
$.observable(_model.leadTimes).insert(leadTime);
$leadTimes.find('.lead-time-data').last().find('.start-editing').click();
...and
function watchLeadTimes() {
var changeHandler = function (ev, eventArgs) {
if (eventArgs.change === 'insert') {
$leadTimes.find('.lead-time-data').last().find('.start-editing').click();
}
};
$.observe(_model.leadTimes, changeHandler);
}
And neither of them worked, however, if I wrap the jQuery method in a setTimout, like setTimeout(function () { $leadTimes.find('.lead-time-data').last().find('.start-editing').click(); }, 400);, it does work, leading me to believe this is an issue of timing with the DOM render somehow not finishing before my jQuery click() method is invoked.
Since the odds are decent that you will see this, Borris, thank you for the library and all that you do! I think jsViews is an excellent middle ground between the monolithic frameworks out there and plain old jQuery noodling!
Edit 02/09/17
It turns out my issue was overlapping click events--I was inadvertently handling a click to deselect my element immediately after it was selected. However I took the opportunity to rewrite things to use a more declarative approach following Borris' linked example.
Now in my template I am using a computed observable, isSelected to toggle the .editing class:
{^{for leadTimes}}
<tr class="lead-time-data" data-link="class{merge:~isSelected() toggle='editing'}">
<span>{^{:daysLead}}</span>
</tr>
{{/for}}
And this JS:
function addNewLeadTimeClickHandler() {
var onNewLeadTimeClick = function () {
e.stopPropagation(); // this is what I was missing
var leadTime = {
daysLead: 1,
description: ''
};
$.observable(_model.activityMapping.leadTimes).insert(leadTime);
selectLeadtime(_model.activityMapping.leadTimes.length -1);
}
$leadTimes.on('click', '.add', onNewLeadTimeClick);
}
function selectLeadtime(index) {
var addStopEditingClickHandler = function () {
var onClickHandler = function (event) {
if ($(event.target).closest('tr').hasClass('editing')) {
setHandler();
return;
}
selectLeadtime(-1)
};
function setHandler() {
var clickEvent = 'click.ActivityChangeRequestDetailController-outside-edit-row';
$('html:not(.edit)').off(clickEvent).one(clickEvent, onClickHandler);
};
setHandler();
}
if (_model.selectedLeadtimeIndex !== index) {
$.observable(_model).setProperty('selectedLeadtimeIndex', index)
addStopEditingClickHandler();
}
}
function isSelected() {
var view = this;
return this.index === _model.selectedLeadtimeIndex;
}
// isSelected.depends = ["_model^selectedLeadtimeIndex"];
// for some reason I could not get the above .depends syntax to work
// ...or "_model.selectedLeadtimeIndex" or "_model.selectedLeadtimeIndex"
// but this worked ...
isSelected.depends = function() {return [_model, "selectedLeadtimeIndex"]};
The observable insert() method is synchronous. If your list items are rendered simply using {^{for}}, then that is also synchronous, so you should not need to use setTimeout, or a callback. (There are such callbacks available, but you should not need them for this scenario.)
See for example http://www.jsviews.com/#samples/editable/tags (code here):
$.observable(movies).insert({...});
// Set selection on the added item
app.select($.view(".movies tr:last").index);
The selection is getting added, synchronously, on the newly inserted item.
Do you have other asynchronous code somewhere in your rendering?
BTW generally you don't need to add new click handlers to added elements, if you use the delegate pattern. For example, in the same sample, a click handler to remove a movie is added initially to the container "#movieList" with a delegate selector ".removeMovie" (See code). That will work even for movies added later.
The same scenario works using {{on}} See http://www.jsviews.com/#link-events: "The selector argument can target elements that are added later"
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
Is there anyway to call a function when for example pressing a close button on a modal window that will take different action depending on the function that opened the modal window?
So say for example we had a landing page with items to click on that showed a image of that item in a modal window and a certain function was called when the image was opened from this context and we had a search side nav-bar that displayed items and when these were clicked the function that opened the modal windows was different from the first. Now when closing the modal window, and depending on the function that was called to open the modal, I would like to write a condition that would allow me to either go back to landing page or return to side nav-bar.
I don't have any code to show, but I was wondering if such a thing is possible; writing a condition based on the function that was previously called? What would be the command for that condition?
So
function 1 () {
doSomething;
}
function 2 () {
doAnotherThing;
}
$("closeButton").on('click', function () {
if (function 1 was called) {
// do something else
} else if (function2 was called) {
// do another thing
}
}
Could something like that be possible?
var fnClicked = null
function fn1() {
fnClicked = fn1;
doSomething();
}
function fn2() {
fnClicked = fn2;
doAnotherThing();
}
$('closeButton').on('click', function(){
if (fnClicked === fn1) {
//do something else
} else if (fnClicked === fn2) {
//do another thing
}
});
Alternatively you could hav fn1 and fn2 unbind the closebutton click event and rebind it to the appropriate followup.
In an MVC framework, you can bind a property to the related view. If not, you can always keep bind state to the window object.
If you also don't want to do that, you can keep the state in the DOM (the close button) as an attribute. For example, a data-attribute.
$("closeButton").on('click', function (e) {
var state = $(e.currentTarget).data("state");
}
You can use data attributes on the modal element to store info that indicates what area the modal was opened from. Then when closing the modal, look in that attribute and decide what to do based on the value stored there when the modal was opened.
Variables can store references to functions in Javascript. So I would have function1 set some internal variable that would be checked when you close the modal:
var calledBy;
function1 () {
calledBy = function1;
//open modal
}
function2 () {
calledBy = function2;
//open modal
}
$("closeButton").on("click", function () {
if(calledBy === function1) {
//...
} else if(calledBy === function2) {
//...
}
});
But as hyperstack pointed out, it's better organization to have one function for opening the modal and pass in an argument. I would have an object for the modal:
var modal = {
//...
calledBy: null,
open: functio (calledBy) {
this.calledBy = calledBy;
}
};
You can use the 'this' special keyword to refer to the object on which a method is being invoked.
EG.
<div class="cval">
test
</div>
<script>
$(".cval").click(function (e) {
e.preventDefault();
alert($(this).attr('class'));
if($(this).attr('class') == 'cval')
//dosomething
else
//dosomething
});
</script>
Interrogating any of the elements attribute(s) for value and then using a conditional to control flow.
I have a problem with event object passed to the function in drop event. In my code, div#dropArea has it's drop event handled by firstDrop function which does some animations and then calls the proper function dropFromDesktop which handles the e.dataTransfer.files object. I need this approach in two separate functions because the latter is also used further by some other divs in the HTML document (no need to duplicate the code). First one is used only once, to hide some 'welcome' texts.
Generally, this mechanism lets you drag files from desktop and drop them into an area on my website.
Here's, how it looks (in a shortcut):
function firstDrop(ev) {
var $this = $(this);
//when I call the function here, it passes the event with files inside it
//dropFromDesktop.call($this, ev);
$this.children('.welcomeText').animate({
opacity: '0',
height: '0'
}, 700, function() {
$('#raw .menu').first().slideDown('fast', function() {
//when I call the function here, it passes the event, but 'files' object is empty
dropFromDesktop.call($this, ev);
});
});
}
function dropFromDesktop(ev) {
var files = ev.originalEvent.dataTransfer.files;
(...) //handling the files
}
$('#dropArea').one('drop', firstDrop);
$('some_other_div').on('drop', dropFromDesktop);
The problem is somewhere in jQuery.animation's callback - when I call my function inside it, the event object is passed correctly, but files object from dataTransfer is empty!
Whole script is put inside $(document).ready(function() { ... }); so the order of function declarations doesn't matter, I guess.
I suspect your problem is related with the lifetime of the Event object. Unfortunately, I have no clue about the cause of it. But, there is a way to workaround it that I can think of and it is keeping a reference to Event.dataTransfer.files instead.
var handleFileList = function(fn) {
return function(evt) {
evt.preventDefault();
return fn.call(this, evt.originalEvent.dataTransfer.files);
};
};
var firstDrop = function(fileList) { ... }
var dropFromDesktop = function(fileList) { ... }
$('#dropArea').one('drop', handleFileList(firstDrop));
$('some_other_div').on('drop', handleFileList(dropFromDesktop));
I have an AJAX call as such :
$('a.delete_task').live('click', function() {
$this = $(this);
function deleteFunction(){
var obj = $this.parents('.task');
$(obj).addClass('highlighted');
$.post($this.attr('href'), { _method: 'delete' }, function(data) {
if ( $single_item_collection == true ) {
} else {
};
});
};
SSK.confirm_delete($this, deleteFunction, "task");
return false;
});
And then I take my deleteFunction() and throw it into the delete_confirmation :
$(function(){
window.SSK = new(Class.extend({
confirm_delete: function(obj, action, label){
$(".confirm-deletion").live("click", function(){
action.call(obj);
$(this).parents("#delete-message").fadeOut();
return false;
});
},
The problem is that when I click it the first time it works. When I click it the second time, it passes through the first $(this), and the second $(this). Likewise, when I click another item for a third time, it tries and pass all three and so on.
Somehow it is caching $(this). As crazy as that is. And passing it everytime the method is passed again.
Confirm delete as a function creates a popup and passes the method of the link you originally clicked to it as the variable obj.
Then if you click confirm it does this :
$(".confirm-deletion").live("click", function(){
action.call(obj);
$(this).parents("#delete-message").fadeOut();
return false;
});
You are adding a click event to the element 'confirm-deletion' every single time the user clicks 'delete_task'. That's why the click event is firing multiple times, it's literally been added multiple times.
No, it's surely not crazy caching. Read https://developer.mozilla.org/en/JavaScript/Reference/Operators/this and rewrite confirm_delete and/or deleteFunction.
I'm quite sure that you need to put $(this) in a caching value in the click-handler closure, and then use that in deleteFunction.