All,
I have an Angular2 application. In this application I have a button that opens a dialog. In this dialog is a single div which I need to act on. Is there any way to execute code in a callback after the dialog and all it's children have been created?
The closest I've got is
<modal #openDialog (focusin)="openExplorer('someData')">
...
</modal>
If I go with that approach then my code get's rexecuted if a user clicks on the form fields inside of this div.
// HTML
<p (click)="openTheDialog(openDialog, 'thisDivRightHere')">Click here to open me, fool</p>
<modal #openDialog (focusin)="openExplorer('someData')">
<div id="thisDivRightHere"></div>
</modal>
// Component
openTheDialog(dlg, someDiv) void: {
this.fooBarService.preformTheAction(someDiv);
}
// Service
preformTheAction = function(someDiv) {
$('#' + someDiv).html(new Date());
}
In the above example, without (focusin) then I can't get a handle on "thisDivRightHere"
But if I use (focusin) I can but the date changes every time somethings interacted with.
I solved it by using setTimeout to wait for the element and a carefully placed callback
// HTML
<p (click)="openTheDialog(openDialog)">Click here to open me, fool</p>
<modal #openDialog (onOpen)="openIt(openDialog, 'thisDivRightHere')" >
<div id="thisDivRightHere"></div>
</modal>
tester(someDiv, dialog, callback): void {
var checkExist = setInterval(function() {
if ($('#'+someDiv).length) {
console.log("Exists!");
clearInterval(checkExist);
callback(dialog);
}
}, 100); // check every 100ms
}
// Component
openTheDialog(dlg) {
dlg.open();
}
openIt(dlg, someDiv) void: {
this.tester(someDiv, this.myService, function(dlg) {
dialog.preformTheAction(someDiv);
});
}
// Service
preformTheAction = function(someDiv) {
$('#' + someDiv).html(new Date());
}
Related
I'm having some problems with users clicking buttons multiple times and I want to suppress/ignore clicks while the first Ajax request does its thing. For example if a user wants add items to their shopping cart, they click the add button. If they click the add button multiple times, it throws a PK violation because its trying to insert duplicate items into a cart.
So there are some possible solutions mentioned here: Prevent a double click on a button with knockout.js
and here: How to prevent a double-click using jQuery?
However, I'm wondering if the approach below is another possible solution. Currently I use a transparent "Saving" div that covers the entire screen to try to prevent click throughs, but still some people manage to get a double click in. I'm assuming because they can click faster than the div can render. To combat this, I'm trying to put a lock on the Ajax call using a global variable.
The Button
<span style="SomeStyles">Add</span>
Knockout executes this script on button click
vmProductsIndex.AddItemToCart = function (item) {
if (!app.ajaxService.inCriticalSection()) {
app.ajaxService.criticalSection(true);
app.ajaxService.ajaxPostJson("#Url.Action("AddItemToCart", "Products")",
ko.mapping.toJSON(item),
function (result) {
ko.mapping.fromJS(result, vmProductsIndex.CartSummary);
item.InCart(true);
item.QuantityOriginal(item.Quantity());
},
function (result) {
$("#error-modal").modal();
},
vmProductsIndex.ModalErrors);
app.ajaxService.criticalSection(false);
}
}
That calls this script
(function (app) {
"use strict";
var criticalSectionInd = false;
app.ajaxService = (function () {
var ajaxPostJson = function (method, jsonIn, callback, errorCallback, errorArray) {
//Add the item to the cart
}
};
var inCriticalSection = function () {
if (criticalSectionInd)
return true;
else
return false;
};
var criticalSection = function (flag) {
criticalSectionInd = flag;
};
// returns the app.ajaxService object with these functions defined
return {
ajaxPostJson: ajaxPostJson,
ajaxGetJson: ajaxGetJson,
setAntiForgeryTokenData: setAntiForgeryTokenData,
inCriticalSection: inCriticalSection,
criticalSection: criticalSection
};
})();
}(app));
The problem is still I can spam click the button and get the primary key violation. I don't know if this approach is just flawed and Knockout isn't quick enough to update the button's visible binding before the first Ajax call finishes or if every time they click the button a new instance of the criticalSectionInd is created and not truely acting as a global variable.
If I'm going about it wrong I'll use the approaches mentioned in the other posts, its just this approach seems simpler to implement without having to refactor all of my buttons to use the jQuery One() feature.
You should set app.ajaxService.criticalSection(false); in the callback methods.
right now you are executing this line of code at the end of your if clause and not inside of the success or error callback, so it gets executed before your ajax call is finished.
vmProductsIndex.AddItemToCart = function (item) {
if (!app.ajaxService.inCriticalSection()) {
app.ajaxService.criticalSection(true);
app.ajaxService.ajaxPostJson("#Url.Action("AddItemToCart", "Products")",
ko.mapping.toJSON(item),
function (result) {
ko.mapping.fromJS(result, vmProductsIndex.CartSummary);
item.InCart(true);
item.QuantityOriginal(item.Quantity());
app.ajaxService.criticalSection(false);
},
function (result) {
$("#error-modal").modal();
app.ajaxService.criticalSection(false);
},
vmProductsIndex.ModalErrors);
}
}
you could use the "disable" binding from knockout to prevent the click binding of the anchor tag to be fired.
here is a little snippet for that. just set a flag to true when your action starts and set it to false again when execution is finished. in the meantime, the disable binding prevents the user from executing the click function.
function viewModel(){
var self = this;
self.disableAnchor = ko.observable(false);
self.randomList = ko.observableArray();
self.loading = ko.observable(false);
self.doWork = function(){
if(self.loading()) return;
self.loading(true);
setTimeout(function(){
self.randomList.push("Item " + (self.randomList().length + 1));
self.loading(false);
}, 1000);
}
}
ko.applyBindings(new viewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.0.0/knockout-min.js"></script>
Click me
<br />
<div data-bind="visible: loading">...Loading...</div>
<br />
<div data-bind="foreach: randomList">
<div data-bind="text: $data"></div>
</div>
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'm having an issue where I want to call GET on an endpoint, and based on the result of that, either render a modal or follow a link.
Currently, when I get the click event, I disable the default behavior of the anchor tag (I don't want to redirect before I check the result.).
I do a GET on the endpoint and throw an event from the callback if one of the return parameters is true. This event has a listener on it that
will trigger rendering and displaying the modal.
The issue with this methodology is: The GET callback doesn't allow me to redirect to the link unless I disable popup blockers and I would like my
users to have a good user experience.
I'm debating between a polling strategy (non-performant, not always accurate) or having the click event open a window that will either follow the anchor tag
or render the modal.
Would appreciate any other ideas or suggestions. Thanks!
Template is defined as follows:
var template = _.template('\
<a href="<%-linkUrl%>?fromHome=true" draggable="false" data-type="app-button" data-se="app-button" target="_blank" \
class="app-button">\
<img draggable="false" src="<%-logoUrl%>" class="logo">\
<span data-action="show-settings" class="icon-button <%-showIcon%>">\
<span class="icon icon-settings-dark"></span>\
</span>\
</a>\
<p class="app-button-name" data-se="app-button-name"><%-label%></p>\
');
Events are defined as follows:
events: function () {
var events = {};
events['click [data-type=app-button]'] = '_firstLoginSettings';
return events;
},
Now here's the function itself being called.
_firstLoginSettings: function (e) {
if (this.model.get('__notVerified__')) {
this.state.trigger(Events.SHOW_CONFIRMATION, this.model);
} else {
e.preventDefault();
e.stopPropagation();
this.state.trigger(Events.CHECK_VPN_DIALOG, this.model);
}
},
I have a listener on my main router.
this.listenTo(this.state, Events.CHECK_VPN_DIALOG, this._checkVpnDialog);
And here's the rest of the router code:
_checkVpnDialog: function (appLink, appLinkSettings) {
var self = this;
var vpnSettings = new VpnSettings({
appLink: appLink,
'__appInstanceId__' : appLink.get('__appInstanceId__')
});
vpnSettings.fetch({}).done(_.bind(function(vpnSettings) {
if (vpnSettings.checkVpn) {
self.state.trigger(Events.SHOW_VPN_DIALOG, appLink);
} else {
appLink._firstLoginSettings();
//This doesn't work because it's not associated with a user action, so it won't let me open this window. This isn't part of the click event loop any more.
var linkUrlTemplate = _.template('<%-linkUrl%>?fromHome=true');
window.open(linkUrlTemplate({linkUrl: appLink.get('__linkUrl__')}));
}
}));
},
_showVpnDialog: function (appLink, appLinkSettings) {
this.credsDialog && this.credsDialog.remove();
if (!appLinkSettings) {
appLinkSettings = new AppLinkSettings({
id: appLink.get('id'),
'__tab__': appLink.get('__tab__')
});
appLinkSettings.fetch().done(_.bind(this._renderVpnDialog, this, appLink, appLinkSettings));
} else {
this._renderVpnDialog(appLink, appLinkSettings);
}
},
_renderVpnDialog: function (appLink, appLinkSettings) {
if (appLink.get('__needsVpn__')) {
this.vpnDialog = new VpnDialog({
model: appLink,
appLink: appLink,
settings: this.settings,
state: this.state
});
this.vpnDialog.render();
}
},
So what I did instead was to open a new window with the click, and then change the location of the window so it would either go to the new location or close itself. Kind of a hacky solution, but it works!
I have a JQuery function that polls the server. I now wanted to add an onClick function to the loading icon so that if the user presses the icon it will refresh on demand.
The polling works fine until I press the icon. It then stops polling and every time I press the icon it creates double the amount of calls. So first time 1, second time 2 third time 4 and so on. I am a JQuery newbie so I am not sure if I should create two functions on for just polling and one for refresh.
var DO_POLLING = {
load: function doPoll(poll) {
//This part of the call is a PlayFramework spefic part but it works the same as vanila jquery
var ajax = jsRoutes.controllers.AjaxApplication.getAjaxReceipts(ll).ajax ( {
beforeSend : function () {
//
},
success : function ( data ) {
//
},
complete : function ( data ) {
//
},
always : function ( data ) {
if(poll){
setTimeout(doPoll,30000);
}
},
error : function ( ) {
//
}
}
);
//This is called when the refresh icon is called
$('#wid-id-2 i.fa-refresh').click( function() {
doPoll(false)
})
}
}
$(document).ready(function () {
DO_POLLING.load(true);
});
You are adding a click handler each time the doPoll method is called.
You need to either restructure your object, so the the init method will do the binding and another will do the polling or just unbind the handler before re-binding
So the easy solution would be to do
$('#wid-id-2 i.fa-refresh').unbind('click').click( function() {
doPoll(false);
});
Initially, I had a problem that a click event was firing multiple times, but I have managed to overcome that with a probably over use of unbind() and one() as you'll see in my code below!
What I have here is some code which opens up a universally usable Modal window which I use for various things, including, in some cases a password form.
I don't think you need the HTML so I won't post that.
When a button, or an action causes the window to be required, I call the function like this:
showModalAlert(type, theWidth, theHeight, title, html, confirmThis, denyThis)
The first three variables determine how the window will look, title and html determine the content and confirmThis and denyThis are functions set immediately prior to calling this function and determine what the action should be if this is a confirm window and the confirm or deny buttons are press.
In the case of a security window, the confirm button is replace by a "sign it" button which submits a simple password form and returns a User Id from database. If a User Id is successfully returned, the script programatically presses the confirm button and in turn runs it's function as per the call to the inital opening of the modal window.
My problem is that if an incorrect password is entered, or a user cancels the window and then later without refreshing the browser window, re-enters the password correctly, the confirmThis() function is performed twice (or as many times as the incorrect password/cancel action was performed).
So, clearly, what it is doing is "remembering" the confirmThis function each time.
As I said, initially, the password success function was clicking confirmIt twice, copious use of one() has fixed this, it is now definitely only clicking confirmIt once, but it is still performing the function multiple time.
How can I clear this function and ensure it is only performed once?
The function from which I am calling the modal window looks like this:
$('#saveDelivery').click(function () {
function confirmIt() {
formData = (JSON.stringify($('#delDetail').serializeObject()));
saveData(formData);
$('#saveDelivery').removeClass('centreLoader');
};
showModalAlert('security', '300px', '185px', 'Security!', 'You need to "Sign" this action.', confirmIt, '');
});
It's simply a click on the saveDelivery element, the confirmThis function is declared at this point and submits an AJAX form
the actual showModalAlert function is below:
function showModalAlert(type, theWidth, theHeight, title, html, confirmThis, denyThis) {
// stuff that opens the alert window \\
if (confirmThis == '') {
$('#confirmIt').one('click', function () { $('#closeAlert').one('click').click(); });
} else {
$('#confirmIt').one('click', function () { confirmThis(); $('#closeAlert').one('click').click(); });
};
if (denyThis == '') {
$('#denyIt').one('click', function () { $('#closeAlert').one('click').click(); $('#signIt').unbind(); });
} else {
$('#denyIt').one('click', function () { denyThis(); $('#closeAlert').one('click').click(); $('#signIt').unbind(); });
};
if (type == "confirm") {
$('.closeAlert, .signItForm').hide();
};
if (type == "alert") {
$('.alertConfirm, .signItForm').hide();
};
if (type == "fixedAlert") {
$('.closeAlert, .alertConfirm, .signItForm').hide();
};
if (type == "security") {
$('.signItForm').show();
$('.closeAlert').hide();
$('#confirmIt').hide();
$('#signIt').unbind().fadeTo('fast',1);
};
};
$('#signIt').live('click', function () {
var formData = (JSON.stringify($('.secureSign').serializeObject()));
var signitPwd = $('#signItpwd').val();
var jsonURL = "/jsonout/getdata.aspx?sql=SELECT id, password FROM users WHERE password ='" + signitPwd + "' LIMIT 1&output=json&usedb=new&labelName=any&fileName=";
$.getJSON(jsonURL, function (data) {
if (data.length > 0) {
$('.savingUserID').val(data[0].id);
$('#confirmIt').one('click').click();
$('#signIt').fadeTo('fast', 0);
$('#confirmIt').show();
} else {
$('#signIt').fadeTo('fast', 0);
$('#confirmIt').one('click').show();
$('.closeAlert').show();
$('.alertConfirm, .signItForm').hide();
$('#alertTitle').html("Error!");
$('#alertContent').css({ 'text-align': 'center' }).html("Password Denied");
};
});
});
From my understanding of $.one, it merely runs the event ONCE. If you bind it twice to the event, it will run twice instantaneously, but no more.
Example: http://jsfiddle.net/qCwMH/ (click the button, and it will run the event 4 times).
Each time you click saveDelivery, you are infact, binding another $.one event to #confirmIt.
What you could do is unbind your events from confirmIt and denyIt at the start of the modal function (i.e. $('#confirmIt, #denyIt').unbind('click');, and then you will assign them fresh each time that function is called, rather than building on top of them. Not ideal, as binding/unbinding uses more resources than other options, but just give that a try to start with perhaps?