I'm using a request header with a key and a value to read from the AngularJS controller. If that header, in this case, is AppHeaders is valid and has a custom value, I need to trigger a click from that controller. The code that I have, if something like this:
$scope.$applyAsync(function() {
if (appHeaders != null && appHeaders['header-name'] != null) {
if (appHeaders['header-name'] == "custom-value") {
$('.class-name').click();
}
}
});
What's wrong? I make a deep debug into this and the conditional works fine. I guess that the problem is because the element on the DOM doesn't exist when the click is fired.
Thanks for your help guys! The final solution results on apply the $broadcast event on the functions declared and use a counter to validate the last cycle of the calls to trigger the click on the element.
// Here we declare an empty array to store each request
var requests = [];
// Our first function
$scope.firstFunction = function() {
// Execute broadcast event with the name of the function
$scope.$broadcast('requestEnded',requests.push('firstFunction'));
};
// Our last function
$scope.secondFunction = function() {
// Execute broadcast event with the name of the function
$scope.$broadcast('requestEnded', requests.push('secondFunction'));
};
// This listener is executed each time that requestEnded is fired
$scope.$on('requestEnded', function(event, countRequests) {
// Here we validate that the count of requests is the desire
if (countRequests == 2) {
// Trigger Click
$('.class-selector').click();
}
});
I write a post with the entire research explaining this:
https://jbrizio.github.io/2017/10/20/Trigger-click-when-determinate-requests-finish-using-AngularJS.html
Related
I have built a SPA with Riot.js that visualizes data that is received by a proprietary messaging system. In order to make the tags react to changes, I created a global observable (riot.ob) in the riot context that triggers a message when a new values are received from the server:
riot.ob.trigger('valueUpdate', stationId, datapointId, value);
There are many different tags that subscribe to this event and trigger an update, if the message is meant for this tag:
riot.ob.on('valueUpdate', function (stationId, datapointId, value) {
if (stationId == self.stationId && datapointId == self.datapoint.id) {
self.value = value;
self.update();
}
});
That works quite well. The problem arises when I navigate within my application, which means I need to unmount tags in a specific area and mount something else. When I unmount a tag like the above, I need to unsubscribe the valueUpdate event, so I use this:
this.on('unmount', function () {
riot.ob.off('valueUpdate');
})
But now all other tags that are still mounted somewhere else are automatically unsubscribed as well and don't listen to this valueUpdate event any more. How can I unsubscribe a single tag's event handler function only? Do I have to create a named function and pass it to riot.ob.off('valueUpdate'); somehow?
According to source code of method off you should pass a callback function that you want to unsubscribe as the second param. Otherwise, all listeners will be deleted.
Next code should work as you expect:
var callback = function (stationId, datapointId, value) {
if (stationId == self.stationId && datapointId == self.datapoint.id) {
self.value = value;
self.update();
}
}
riot.ob.on('valueUpdate', callback);
this.on('unmount', function () {
riot.ob.off('valueUpdate', callback );
})
Currently I have a text input attached to a model with a $scope.watch statement observing the model. This is all used to achieve a type of auto complete / typeahead feature.
<!-- HTML -->
<input type="text" ng-model="search.mySearchText">
// JS
var deregister = $scope.$watch('search.mySearchText', doSearch);
function doSearch() {
mySearchService.executeSearch(search.mySearchText)
.then(function(res) {
// do something with the data
});
}
This works fine. However, occasionally in my .then function I want to make changes to search.mySearchText. Obviously this would cause the watcher to be fired again, but I don't want this.
What I'm hoping to do is find a way to suppress the $watch from firing that next time. Maybe by somehow telling Angular that that particular watched model property is no longer dirty?
I tried removing the $watch by de/re- registering the watch at appropriate times, but that didn't work either.
function doSearch() {
mySearchService.executeSearch(search.mySearchText)
.then(function(res) {
deregister(); // shut off the watch
search.mySearchText = 'some new string'; // manipulate the model property that I don't want to cause a search
deregister = $scope.$watch('search.mySearchText', doSearch);
});
}
However, this didn't prevent the event firing like I expected, which is why I'm now looking for a way to suppress the event.
You could have a variable that determines whether doSearch exits early, like so:
var searchActive = true;
function doSearch() {
if (!searchActive) return;
mySearchService.executeSearch(search.mySearchText)
.then(function(res) {
searchActive = false;
// do manipulation of search.mySearchText
searchActive = true;
});
}
I am triggering a custom event on an element using jQuery and I want the event handler to be able to pass data (in the form of an object) back to the method that called it. The trigger and the handler are in different scopes and files and cannot be merged or shared in a conventional manner. (The trigger method is part of a utility library I am writing and the event handler is part of my front-end view template).
I know this code doesn't work, but I am writing it to kind of illustrate what I am hoping to try. The pattern is based of of work I have done in .NET.
var data = { foo: 'bar' }
element.trigger('some-event', data)
if(data.foo != 'bar')
alert('someone changed foo!')
And the handler...
element.bind('some-event', function(event, data)
{
if(some_condition)
data.foo = 'biz'
});
The specific implementation is not terrible important to me as long as I don't have rearrange my code to stick both the trigger and the bind in the same scope.
How do I get return value back from my event handler?
EDIT
To provide a little more context, the triggering method is responsible for obtaining and processing data, and finally rendering it out as markup to the page. Before it does that, it raises the custom event with the same processed data object so that other components of the app can have the chance to do something with the processed data.
In certain cases, it would be beneficial for the event handlers to modify that set of data, or event signal to the triggering method that the data has already been handled and does not need additional processing or rendering.
Something like below. In this example, the handler might change the way the data is displayed based upon some property value. It may also choose to render the data out a different way (e.g. input) rather than the default rendering in the triggering function (e.g. label).
This implementation is an example, but the end goals of returning an object from the handler, or modifying the data in the handler such that the triggering method can access it are common to my actual project.
var data = load_data();
element.trigger('loading_data', data);
if(data.abort_render!==true)
{
element.append('<label>Foo</label>')
element.append('<span>' + data.foo + '</span>')
}
And the handler...
element.bind('loading-data', function(event, data)
{
if(data.is_password == true)
{
data.foo = '*******' //changed property persisted to triggering method and processed normally
}
if(data.type == 'abc')
{
element.append('<input value="' + data.foo + '"/>');
data.abort_render = true; //signals to the triggering method that it should not render the data to the page
}
}
There is a way. Below is the jQuery method (modified from Example 2 in this answer: https://stackoverflow.com/a/20707308/870729 )
// Refactor below for efficiency - replace 'body' with closest known container element
jQuery('body').on(element, function(event, data) {
if (data.foo != 'bar') {
alert('someone moved my foo!');
}
});
For a more robust solution, try this:
// Bind the event to the element
jQuery('body').on(element, 'some-event', function(event, data) {
// Call the callback function, pass in the data
eventCallback(data);
});
// Call this function to set up the data and trigger the event
function triggerMyEvent() {
var data = { foo: 'bar' };
element.trigger('some-event', data);
}
// The callback function set in the event binding above
function eventCallback(data) {
if(data.foo != 'bar')
alert('someone changed foo!');
}
The challenge may be in the flow of the logic / code. Below is the code (from your question) - I've added some comments to try and help explain the flow:
// Command 1: This will run first
var data = load_data();
// Command 2: This will run second
element.trigger('loading_data', data);
// Command 3: This will run independently of the trigger callback
// and the data will not be modified based on the trigger.
// You will want to restructure your code to run this within the
// trigger callback
if(data.abort_render!==true)
{
element.append('<label>Foo</label>')
element.append('<span>' + data.foo + '</span>')
}
// Event handler (will run independently of Command 3)
jQuery('body').on(element, 'loading_data', function(event, data) {
// THIS will run independently of the "Command 3" code above
// and may or may not be done before Command 3 is run.
// That's why this needs to trigger the code that handles
// the data checks and various outputs based on the data
handleLoadData(data);
}
// A specific function designed to be run AFTER event handler,
// specifically to handle the various data settings that may be set.
function handleLoadData(data) {
if(data.is_password == true) {
data.foo = '*******' //changed property persisted to triggering method and processed normally
}
if(data.type == 'abc') {
element.append('<input value="' + data.foo + '"/>');
data.abort_render = true; //signals to the triggering method that it should not render the data to the page
}
// This code will need to be moved here, because data may
// not be updated when Command 3 is run above. Alternatively,
// You could put it in another function, and call that function
// from here.
if(data.abort_render!==true) {
element.append('<label>Foo</label>')
element.append('<span>' + data.foo + '</span>')
}
}
Will NOT run in the order you expect. See the comments above to help
I have a problem with the javascript code I am trying to work with. I am trying to call a function to set an event handler and within that event handler also remove it when the event is called. In this particular instance I am trying to ADD an event handler whenever I want input, then a callback is passed to the function and the code is run when the input is ready. Also at this stage I try to remove it so the callback doesn't get triggered more than once, but there seems to be a problem with this. Here is the code:
this.validateInput = function(NaN, callback) {
// Wait for the user to click submit or press
// enter, then if the input is a valid number
// return true. If it is not valid return false
$(document).keydown(function(e) {
// If the enter key is pressed, if the box is focused and if
if (e.which == 13 && $("#inputBox").is(":focus")) {
// Print the user's input regardless of whether it is a
// number or not.
var newOutput = $("#inputBox").val()
$("#output").append(newOutput + "<br>");
// If the user wants the input to be a number then
// the program checks if the input is not numerical.
if (NaN && !isNaN($("#inputBox").val())) {
// Get input from screen
var newInput = $("#inputBox").val();
// Remove this handler
this.removeKeyhandler();
// Call the code passed to the function
callback(newInput);
// Return from the function.
return;
// This checks if the user wants non-number input
// and runs the following code IF the input is not numerical
} else if (!NaN && isNaN($("#inputBox").val())) {
// Get input from screen
var newInput = $("#inputBox").val();
// Remove this handler
this.removeKeyhandler();
// Call the code passed to the function
callback(newInput);
// Return from the function
return;
}
}
});
}
For reference, #inputBox is an input box, #output is the <div> I am trying to output to, and removeKeyHandler() simply contains the code $(document).off("keydown", document);. If you want to see the full file/project, it is here.
The only thing that seems not to be working is the event handler not removing, it keeps going as many times as you add input. If you download the project and open up index.html you should see what I mean.
I see your problem.. your 'this' you refer to in your code is not in the right scope..
simply do this:
function display() {
var ref = this;
}
Now replace these:
this.removeKeyhandler();
with this:
ref.removeKeyhandler();
Also in your removing function change it to this:
$(document).off("keydown");
good luck!
Firstly, is it possible? Been struggling with this one for hours; I think the reason my events aren't firing is because one event is unbinding/overwriting the other. I want to bind two change events to the same element. How can I do that?
As per request, here's the function I'm struggling with:
(function($) {
$.fn.cascade = function(name, trigger, url) {
var cache = {};
var queue = {};
this.each(function() {
var $input = $(this);
var $trigger = $input.closest('tr').prev('tr').find(trigger);
//$input.hide();
var addOptions = function($select, options) {
$select.append('<option value="">- Select -</option>');
for(var i in options) {
$select.append('<option value="{0}">{1}</option>'.format(options[i][0], options[i][1]));
}
$select.val($input.val()).trigger('change');
}
var $select = $('<select>')
// copy classes
.attr('class', $input.attr('class'))
// update hidden input
.bind('change', function() {
$input.val($(this).val());
})
// save data for chaining
.data('name', name)
.data('trigger', $trigger);
$input.after($select);
$trigger.bind('change', function() {
var value = $(this).val();
$select.empty();
if(value == '' || value == null) {
$select.trigger('change');
return;
}
// TODO: cache should be a jagged multi-dimensional array for nested triggers
if(value in cache) {
addOptions($select, cache[value]);
} else if(value in queue) {
$select.addClass('loading');
queue[value].push($select);
} else {
var getDict = {}
getDict[name] = value;
// TODO: use recursion to chain up more than one level of triggers
if($(this).data('trigger')) {
getDict[$(this).data('name')] = $(this).data('trigger').val();
}
$select.addClass('loading');
queue[value] = [$select];
$.getJSON(url, getDict, function(options) {
cache[value] = options;
while(queue[value].length > 0) {
var $select = queue[value].pop();
$select.removeClass('loading');
addOptions($select, options);
}
});
}
}).trigger('change');
});
return this;
}
})(jQuery);
The relevant chunk of HTML is even longer... but essentially it's a select box with a bunch of years, and then an <input> that gets (visibly) replaced with a <select> showing the vehicle makes for that year, and then another <input> that gets replaced with the models for that make/year.
Actually, it seems to be running pretty well now except for on page load. The initial values are getting wiped.
Solved the issue by pulling out that $select.bind() bit and making it live:
$('select.province').live('change', function() {
$(this).siblings('input.province').val($(this).val());
});
$('select.make').live('change', function() {
$(this).siblings('input.make').val($(this).val());
});
$('select.model').live('change', function() {
$(this).siblings('input.model').val($(this).val());
});
Sucks that it's hard-coded in there for my individual cases though. Ideally, I'd like to encapsulate all the logic in that function. So that I can just have
$('input.province').cascade('country', 'select.country', '/get-provinces.json');
$('input.make').cascade('year', 'select.year', '/get-makes.json');
$('input.model').cascade('make', 'select.make', '/get-models.json');
Yes that is possible.
$(…).change(function () { /* fn1 */ })
.change(function () { /* fn2 */ });
jQuery event binding is additive, calling .change a second time does not remove the original event handler.
Ryan is correct in jQuery being additive, although if you find there are problems because you are chaining the same event, beautiful jQuery allows another approach, and that is calling the second function within the first after completion of the first as shown below.
$('input:checkbox').change(function() {
// Do thing #1.; <-- don't forget your semi-colon here
(function() {
// Do thing #2.
});
});
I use this technique frequently with form validation, one function for checking and replacing disallowed characters input, and the second for running a regex on the results of the parent function.
Update to Post:
OK... You all are quick to beat on me with your negative scores, without understanding the difference in how we each view Mark's request. I will proceed to explain by example why my approach is the better one, as it allows for the greatest flexibility and control. I have thrown up a quick example at the link below. A picture's worth a 1000 words.
Nested Functions on One Event Trigger
This example shows how you can tie in three functions to just one change event, and also how the second and third functions can be controlled independently, even though they are still triggered by the parent change event. This also shows how programmatically the second and third functions can BOTH be tied into the same parent function trigger, yet respond either with or independently (see this by UNCHECKING the checkbox) of the parent function it is nested within.
$('#thecheckbox').change(function() {
$("#doOne").fadeIn();
if ($('#thecheckbox').attr('checked')) { doFunc2() }
else { doFunc3() };
function doFunc2() { $("#doTwo").fadeIn(); return true; }
function doFunc3() { $("#doTwo").fadeOut(); return true; }
$("#doThree").fadeIn();
});
I've included the third 'Do thing #3 in the example, to show how yet another event can follow the two nested functions as described earlier.
Forgive the earlier bad pseudocode originally posted first, as I always use ID's with my jQuery because of their ability to give everything an individual status to address with jQuery. I never use the 'input:checkbox' method in my own coding, as this relies on the 'type' attribute of an input statement, and therefore would require extra processing to isolate any desired checkbox if there is more than one checkbox in the document. Hopefully, the example will succeed at articulating what my comments here have not.
I am actually not sure exactly if you can bind two different change events. But, why not use logic to complete both events? For example...
$('input:checkbox').change(function() {
// Do thing #1.
// Do thing #2.
});
That way, you get the same benefit. Now, if there are two different things you need to do, you may need to use logic so that only one or the other thing happens, but I think you would have to do that anyway, even if you can bind two change events to the same element.