I'm trying to rewrite my functional code to module pattern js, and I have this issue - When I try to delete input field which is dynamically created, I use jQuery $(this) to access dom element and delete its parent 'div'. But this refers to the Modal object, not the component I clicked. How to solve it, without making some field counter and creating fields with unique ID's, then catching ids on click and deleting that input field?
My modal:
var s,
Modal = {
settings: {
addInputBtn: $("#add-input"),
inputContainer: $("#modal-input-form"),
checkBoxesList: $(".check-box"),
inputFieldsList: $(".form-control"),
inputFieldsOptionalList: $(".optional-modal"),
inputHtml: `
<div class="input-group mb-3 optional-modal">
<div class="input-group-prepend">
<div class="input-group-text">
<input type="checkbox" class="check-box">
</div>
</div>
<input type="text" class="form-control">
<button type="button" class="close">
<span>×</span>
</button>
</div>`
},
init: function () {
s = this.settings;
this.bindUIActions();
},
bindUIActions: function () {
s.addInputBtn.on("click", () => Modal.addInput());
s.inputContainer.on("click", ".close", () => Modal.deleteInput());
},
addInput: function () {
s.inputContainer.append(s.inputHtml);
},
deleteInput: function () {);
$(this).parent('div').remove();
}
}
Modal.init();
deleteInput: function (e) {);
$(e.target).parent('div').remove();
}
Event handlers are passed an event argument. Among its useful properties is target, which is the html element that the event originated on. Note that it could be different from the this of the event handler, which is the element that the event handler is attached to, as events bubble up through the DOM. So if you have this html:
<div id="parent">
<div id="child"></div>
</div>
then for this JS function:
$("#parent").on('click', function(e){
//this will equal <div id="parent"> unless you bind the handler to something else
//e.target will equal the clicked element:
//if the user clicked the child, it will equal the child;
//if the user clicked parent directly, it will equal parent.
$(e.target).closest('#parent').doSomething();
});
Did you try with:
deleteInput: function () {;
$(this.settings).parent('div').remove();
}
Also you have a typo at deleteInput: function () { ); ... the rest of the code, delete that extra ) after function. I suggest you trying $(this.settings).. because, this gets the Modal, and then you need to access the other object inside that object, which is settings. So your modal object, consists of other object, and I'm thinking this way you should be able to get it :)
Related
I'm new to vuejs and I'm wondering what would be the best way to keep track of the latest focused input/textarea in a form in order to programatically modify their values from the parent component.
Example
Form
Input1
Input2 -> focused
Textarea
Button (onclick -> appends "hello" to the focused input)
Answer:
You can create a data property that tracks the currently focused/last focused input element. In the below example this is called current_focus.
To do this you could use the focus event - but because focus doesn't bubble you would have to apply it to each individual input element manually.
It is easier to provide a focusin event handler to the parent. This event, unlike focus, bubbles up the DOM from any child to its parent. This allows you to utilize an event delegation pattern.
Event Delegation means that you apply one handler to a parent for an event, then do something depending on the source of the event. This means when we receive a focusin event, we can simply check if the focused element is an input element, then update our data property ( current_focus )
Code Sandbox Example:
https://codesandbox.io/s/focus-handler-vzip0
Code Example:
FocusDemo.js
<template>
<div v-on:focusin="updateFocus">
<input name="one">
<br>
<input name="two">
<br>
<input name="three">
<br>
<button #click="handleClick">Add Text To Focused</button>
</div>
</template>
<script>
export default {
name: "FocusDemo",
data: function() {
return {
current_focus: undefined
};
},
methods: {
updateFocus: function(e) {
let element = e.target;
if (element.matches("input")) {
this.current_focus = element;
}
},
handleClick: function(e) {
if (this.current_focus) {
this.current_focus.value = "Button was clicked!";
}
}
}
};
</script>
Given some simple HTML such this, where one element has an onclick function and it's child also has an onclick function:
<div style='background-color:blue;width:500px;height:500px;'
onclick='thing1();'>
<div style='background-color:red;width:100px;height:100px;'
onclick='thing2(57);'></div>
</div>
What would be the correct approach so that when a user clicks the child element, only the child's onclick is executed and not the parent's, but when the parent is clicked, it's onclick is still executed? I see that event.stopPropagation() would be the correct way to go, but since I'm passing an argument to the function thing2(), I can't seem to pass the event as well. For example:
function thing2(a,ev) {
// Do something with a
ev.stopPropagation();
}
Doesn't work, failing with the error TypeError: ev is undefined.
JQuery is fine.
The event is the first param.
function thing2(ev) {
var a = ev.target
ev.stopPropagation()
}
Secondly, it's best not to use onclick=. Instead, give your div classes or ids and do something like this:
<div class="thing-1" data-thingy="57">
<div class="thing-2" data-thingy="65"></div>
</div>
<script>
$('.thing-1').click(function (ev) {
ev.stopPropagation()
parseInt($(ev.target).data('thingy'), 10) // 57
})
$('.thing-2').click(function (ev) {
ev.stopPropagation()
parseInt($(ev.target).data('thingy'), 10) // 65
})
</script>
When you call a function on click, no event will be passed as argument and it somehow you can do that, that is not a Jquery object and that will not have stopPropagation property. SO you need to define jQuery click event handler for both divs, let's give them ids div1 and div2.
HTML
<div id="div1" style='background-color:blue;width:500px;height:500px;'>
<div id="div2" style='background-color:red;width:100px;height:100px;'></div>
</div>
In Javascript,
function thing2(ev) {
// Do something with a
console.log(ev);
alert('hi2');
ev.stopPropagation();
}
function thing1(ev) {
// Do something with a
alert('hi1');
}
$('#div1').click(thing1);
$('#div2').click(thing2);
I'm using the following directive to detect when a click is made outside a div:
app.directive('clickOut', function ($window, $parse) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var clickOutHandler = $parse(attrs.clickOut);
angular.element($window).on('click', function (event) {
if (element[0].contains(event.target)) return;
clickOutHandler(scope, {$event: event});
scope.$apply();
});
}
};
});
In this div:
<div class="panel-body" click-out="closeMyPopup()">
<div class="row clearfix">
<div class="col-md-12">
<div class="form-inline pull-right">
<button type="button" class="form-control btn" ng-click="onCancelAnnouncement()">Cancel</button>
<button type="submit" class="form-control btn" ng-click="onSaveAnnouncement()">Save</button>
</div>
</div>
</div>
</div>
It works well, when you click outside the div, the function closeMyPopup() is triggered, the issue is that the div has buttons that triggers other functions. By some reason when other function is called, (like when the buttons are clicked) the event click outside is triggered calling the closeMyPopup(), the buttons are inside the div so the event click outside should not be called. There's another directive that I can use, that has the correct behavior and not trigger the click outside when you fire another function? Or how can I workaround this?
I also use this other directive, with the same issue:
app.directive("outsideClick", ['$document', '$parse', function ($document, $parse) {
return {
link: function ($scope, $element, $attributes) {
var scopeExpression = $attributes.outsideClick,
onDocumentClick = function (event) {
var isChild = $element.find(event.target).length > 0;
if (!isChild) {
$scope.$apply(scopeExpression);
}
};
$document.on("click", onDocumentClick);
$element.on('$destroy', function () {
$document.off("click", onDocumentClick);
});
}
}
}]);
Its because the event is being propagated to Window object.
- Window
- document
- dialog
- button
In the above hierarchy, if a click event happens on the last button element, the event will be propagated to the parent element until it reaches Window and then will close your dialog.
Solution 1:
Stop event propagation in each controller function by passing the event as a parameter and calling event.stopPropagation() function:
<button type="button" class="form-control btn" ng-click="onCancelAnnouncement($event)">Cancel</button>
...
$scope.onCancelAnnouncement($event) {
$event.stopPropagation();
}
Solution 2:
Let the event be propagated and check the target element:
angular.element($window).on('click', function (event) {
var target = $(event.target);
if(target.attr("id") === "anyid") { // or target.hasClass("someclass")
// or target.closest(".some-selector")
// just ignore
} else {
// Do whatever you need
}
});
Exactly: events will be presented to every object in the nested DOM-hierarchy unless and until you stop their propagation. This, of course, is by design: JavaScript doesn't assume that "the innermost guy I can find who's listening for this event" is the only guy who might be interested in it. Everyone who says he's listening for it, who is in the position to hear it, is going to hear it, each in their turn ... unless one of them explicitly quashes further propagation, at which JS will stop looking for anyone else to send it to. (No one has to "send the event to the outer container." Instead, they only have to tell JS not to send it on.)
I have a generic template that I use multiple times.
{{#each item}}
{{> genericTemplate}}
{{/each}}
Inside of this template I have a button that when it is clicked fires a hidden file input in the generic template.
$(".upload").click();
Unfortunately for each template the ".upload" class gets fired. So if I had four items, it would give me 4 file inputs. I can't give the buttons a unique id="" because then I would have to explicitly define each event for each id, negating the entire reason for creating the generic template in the first place. What is the proper way to achieve something like this?
EDIT:
My template events look like this:
Template.generic.events({
'click .fileUpload' : function () {
$(".upload").click(); // sets off the 4 templates .upload class
},
'change .upload' : function (e) {
console.log('upload')
}
})
HTML:
<template name="generic">
<!--Hidden Inputs that get fired on click events -->
<div class="hiddenFile">
<input type="file" class="upload"/>
</div>
<button class="btn btn-success fileUpload">UPLOAD FILE </button>
</template>
Try this trick :
Template.generic.events({
'click .fileUpload' : function (event,template) {
// fires only the template instance .upload
template.$(".upload").click();
},
'change .upload' : function (e) {
console.log('upload')
}
});
You can use Template.instance to fire the event in only the appropriate instance:
'click .fileUpload' : function () {
var template = Template.instance();
template.$(".upload").click(); // should only set off this specific upload input.
}
That said, is there really no way you can achieve the same effect without manufacturing an event in the DOM? It's up to you, but can't you just run the code that's going to replace console.log('upload') directly?
Maybe smth like this?
Template.generic.events({
'click .fileUpload' : function (event, target) {
e.currentTarget.parent().find(".upload").click(); // sets off the 4 templates .upload class
},
'change .upload' : function (e) {
console.log('upload')
}
})
I want to call a function when a certain field gets blurred, but only if a certain element is clicked. I tried
$('form').click(function() {
$('.field').blur(function() {
//stuff
});
});
and
$('.field').blur(function() {
$('form').click(function() {
//stuff
});
});
But neither works, I reckon it's because the events happen simultaneously?
HTML
<form>
<input class="field" type="textarea" />
<input class="field" type="textarea" />
</form>
<div class="click-me-class" id="click-me">Click Me</div>
<div class="click-me-class">Click Me Class</div>
jQuery
$('.field').blur(function() {
$('#click-me').click(function(e) {
foo = $(this).data('events').click;
if(foo.length <= 1) {
// Place code here
console.log("Hello");
}
$(this).unbind(e);
});
});
You can test it out here: http://jsfiddle.net/WfPEW/7/
In most browsers, you can use document.activeElement to achieve this:
$('.field').blur(function(){
if ($(document.activeElement).closest('form').length) {
// an element in your form now has focus
}
});
I have edited my answer because we have to take into account that the event is asigned every time.
It is not 100% satisfactory, and I don't recommend this kind of complicated way of doing things, but it is the more approximate.
You have to use a global variable to take into account the fact that the field was blurred. In the window event, it is automatically reset to 0, but if the click on "click-me" is produced, it is verified before the window event, becase window event is bubbled later, it happens inmediately after the "click-me" click event
Working code
$(window).click(function(e)
{
$("#result").html($("#result").html()+" isBlurred=0<br/>");
isBlurred=0;
});
var isBlurred=0;
$('.field').blur(function() {
$("#result").html($("#result").html()+" isBlurred=1<br/>");
isBlurred=1;
});
$('#click-me').click(function(e) {
if(isBlurred==1)
{
$("#result").html($("#result").html()+" clicked<br/>");
}
});
".field" would be the input and "#click-me" would be the element clicked only just once.