KnockoutJs Cursor position lost parent click event - binding

Nested div with click events.
I have a couple nested divs which are bound to a common viewModel object which I am using knockoutjs to bind with. The parent and child divs have several bindings including a click event on the parent. The parent div click event acts a way to select the element showing some visual cues that are hidden by default as a result.
The binding code is as follows:
<div data-bind="foreach : cardElements">
<div data-bind="if: isArea(),
attr: { id : ElementName },
visible: isArea(),
click : selectElement.bind($data),
css : { layoutContainer : isEditMode() && isSelected(),
drag: isEditMode(),
selectedElement : isSelected() && isEditMode() },
style : defaultStyle()">
... visuals elided for clarity(hidden by default viewable when parent is selected)
#* Regular display fields. *#
<div data-bind="if: isInput(),
attr : { contenteditable : isEditMode() },
visible : isInput(),
css: { layoutDiv : isEditMode() },
style: { cursor : 'text' },
text: Value">
</div>
... visuals elided for clarity
</div>
</div>
Focus troubles after click event.
Holding the mouse button down reveals the cursor (text cursor) showing in the contenteditable div where it is supposed to end up after the click. Upon release the focus is lost and the cursor is gone (I have also tried using an input element and the same problem occurs). The idea is that the mousedown/click should place the cursor at the desired position for typing text.
Removal of the parent click event
Removing the click event on the parent reveals that, without it, the selecting inside the contenteditable div works as intended and typing can continue as normal after the click. However, the visual cues aren't set, of course which is the main job of the click event handler!
Does anyone know why the focus is lost during the click event?
Maybe a hack could work
One hack, which I haven't attempted yet, would be to set focus on the contenteditable div, based on the cursor position over top of the div itself. This doesn't seem to be a trivial hack nor do I know it is even possible.
Any help would be greatly appreciated on this subject.
Getting closer perhaps
I was able to narrow it down by doing a little of regression testing. If I remove the css style from the parent div it seems to click as usual.
removed the following:
css : { layoutContainer : isEditMode() && isSelected(),
drag: isEditMode(),
selectedElement : isSelected() && isEditMode() },
This still leaves me with incomplete behavior.
Could it be?
It appears that when the parent class is added the focus is lost from the contenteditable div.
Here is a little more of the code that is used.
... // mapped functions.
this.isSelected = ko.dependentObservable(function () {
return (this === viewModel.selectedCardElement());
}, this);
this.selectElement = function (element, evt) {
viewModel.selectedCardElement(element);
},
The selectElement is the called function and note that isSelected is a dependentObservable attached to the card elements that are bound in the template. It seems to me that as soon as the viewModel.selectedCardElement is set the caret position focus and are lost in the view.

Related

Can I get a button in Shadow DOM to submit a form not in Shadow DOM?

I just ran into an interesting situation where I have a submit <button> inside the Shadow DOM of a native custom element that is placed inside a <form>.
<form id="one" action="" method="get">
<s-button>Select</s-button>
#shadow-root
<button>...</button>
<button>Outside</button>
</form>
I also have a <button> as a direct child of the <form>.
The child <button> causes the form to submit.
But the <button> in the shadow-root does not.
In a way I guess this makes sense. But has anyone figured out a way to tell the shadow-root <button> to work correctly with the <form> or is this something I will have to handle through JS?
I know click events are blocked at the Shadow DOM layer, but I am surprised that there is no way to allow the button to still be a part of the form, something that can be set up through an attribute or a property.
Sure I can capture the click event and then send a new one from this but that does not do the same thing since my event will no longer be user generated and there are a huge set of rules associated with that.
A button triggers a submit Event (on the FORM element)
Since Events can not pass the shadow DOM boundary (do not bubble up into the parent DOM)
I presume that is why a shadowDOM button (dispatching a submit event) is not received by the FORM element.
Requires Supersharps workaround with a hidden button in the light DOM (which then dispatches a submit event in the parent DOM)
Or (starting from light DOM) you find the (parent) FORM tag and dispatch a submit event yourself:
this.closest('FORM').dispatchEvent(new Event('submit'))
Follow the experts on shadowDOM and FORMs at: https://github.com/w3c/webcomponents/issues/187
customElements.define( 'my-button', class extends HTMLElement {
connectedCallback() {
this.attachShadow({mode:'open'}).innerHTML=`<button>Button In Shadow DOM</button>`
this.onclick = _ => this.closest('FORM').dispatchEvent(new Event('submit'))
}
})
<form onsubmit="return console.log('submit Event occured')">
<my-button></my-button>
<button>button in Document DOM</button>
</form>
Nested shadowDOMs
If the FORM is not a direct ancestor, you can find it with something like: How to reference to a method in parent component from child component with vanilla JS Web Components? (Not any framework or Library)
You'll have to handle it through Javascript anyway.
A simple solution is to add a (masked) <button> in the light DOM, and transfer the click event to it.
customElements.define( 's-button', class extends HTMLElement {
connectedCallback() {
this.attachShadow( {mode: 'open'})
.innerHTML = `<button>In Shadow</button>`
var submit = this.appendChild( document.createElement( 'button' ) )
this.onclick = () => submit.click()
}
} )
<form onsubmit="console.log('submitted');return false">
<s-button>Select</s-button>
<button>Outside</button>
</form>
Something else you can do that is not exactly a button in the ShadowDOM, is if your button[type=submit] has been slotted into the ShadowDOM. So if you have something like:
<some-component>
<button type="submit" slot="buttonSlot"></button>
</some-component>
That button can be used to trigger your form.
That button is in the light DOM but is easily handled from within your component via the slot. It will also retain all the appropriate keyboard, click, focus, etc. events without any trouble.
To minimize the light DOM html you don't even need it to be a type=submit you can set that from within your component and it will still be treated as the submit button for any parent form in the light DOM.
Bonus (or maybe trouble depending on how you look at it), it will retain the styling of other buttons on the page (unless you change that in your component).

Triggering events when clicking away from a focused Select2

I have a need to capture an event (like a click) on a object that is elsewhere on the page and not a child of the select2 object while the select2 object has focus and is showing results. When the select2 results object is focused, I cannot click on a button/anchor elsewhere on the page and have it perform its action. It merely closes the select2 object.
Is there a way to do this? Here is a demo that shows this behavior: http://codepen.io/anon/pen/JoJobW
<!-- Javascript snippet -->
$(document).ready(function() {
$("#e1").select2();
$("#e2").click(function(){
alert("click!");
e.preventDefault();
});
});
<!-- HTML snippet -->
<select id="e1"></select>
Click here when select2 is focused
Using select2 v3.5.2 and jQuery 1.11.0
Select2 places a "mask" element behind the drop-down that captures the mouse click. Others have complained about this.
Here is a jsfiddle that shows the mask element.
You could try to remove the mask element when the drop-down is opened.
$('#e1').select2().on('select2-open', function() {
$('.select2-drop-mask').remove();
});
Of course, this means the drop-down will not close when the user clicks off of it, but you could add an event handler on the document (or body) to do that.
$(document).click(function() {
$("#e1").select2('close');
});
You have to make sure the click event on the other element does not propagate to the document in that case.
$('#e2').click(function() {
alert('click!');
return false; // Prevent default action and stop propagation.
});
jsfiddle

jQuery UI droppable - tolerance/greedy not working as expected

When I drag the draggable, the element containing the droppables keeps getting triggered (because it's also a droppable), even though it's behind the droppables and they're stacked right on each other. It's behaving like there's a gap between the droppables, and there isn't one.
I've made an example on jsFiddle and here's the screenshot of the offending behavior.
If I add padding to .parent (for example padding: 0.2em 0.2em 0em 0.2em the behavior is even worse.
First of all, I hope you found out a solution in these months, I'm answering this because I'm working with jQueryUI in this period and I thought it would be a good exercise to try to find an answer. Also, I hate unanswered questions ^_^
Unfortunately, it really looks like the browser reacts as if between those .child elements there's some space, enough to trigger the *over events for the .parent. The only idea I came up with is to try to detect if, when the dropover events triggers on the parent, the mouse position is actually inside a child element. If so, you should give the accepting_drops class to the child element instead of the parent.
Here's the code (I made a jsFiddle to show my solution in action):
HTML and CSS are unaltered, so I won't copy them again
Javascript
$('.dragme').draggable({
helper: 'clone'
});
$('.parent,.child').droppable({
greedy: true,
tolerance: 'pointer',
});
$(".parent").on("dropover", function(event, ui) {
var first = $(".child:first");
var last = $(".child:last");
if((event.originalEvent.pageX > first.offset().left) &&
(event.originalEvent.pageY > first.offset().top) &&
(event.originalEvent.pageX <= (last.offset().left + last.width())) &&
(event.originalEvent.pageY <= (last.offset().top + last.height()))) {
$(this).removeClass("accepting_drops");
}
else {
$(this).addClass("accepting_drops");
$(".child").removeClass("accepting_drops");
}
}).on("dropout", function() {
$(this).removeClass("accepting_drops");
});
$(".child").on("dropover", function() {
$(".parent").removeClass("accepting_drops");
$(".child").removeClass("accepting_drops");
$(this).addClass("accepting_drops");
}).on("dropout", function() {
$(this).removeClass("accepting_drops");
});
I removed the hoverClass: 'accepting_drops' line because we're overriding the default behaviour of the draggable component. For the parent div, if when a dropover event triggers I'm also inside a child element, I remove the accepting_drops class from it, otherwise I remove it from any child who could have it and add it to the parent instead. When a dropout event triggers on it, I remove the accepting_drops class.
For the child, the behaviour is almost standard, on a dropover event I remove the accepting_drops class from everything else and add it to the child, on a dropout event I remove it.
The behaviour is still a bit of a mistery, but this workaround should do the trick.

slide up remaining items

I am working on a tool based on jQuery UI draggable functionality.
I have a number of boxes in the left column of the table. When they are dragged in yellow area, I would expect the remaining divs to move upwards to fill the space left by the box that was moved.
But it's not happening. Why?
It is pretty difficult to test but from my knowledge on the question here is a possible cause/solution to this.
The droppable plugin does not remove the dragged element from its original markup position, it is visually moved to the droppable element (with some option allowing to accept to drop certain elements or not, events, etc).
The elements have a position: relative css rule, which represents the "normal flow" for elements (in the order they appear in the markup). So even if the element is visually placed elsewhere on the page with css, its place in the markup is still the same and it is still taking the space it normally should.
This fiddle illustrate what i'm trying to explain :-)
By looking at the source code form the "working website", they actually remove the dragged element from the original draggable list and re-create it in the droppable list !
When they define the .droppable() they do this:
h.droppable({
tolerance: "intersect",
accept: ".card",
greedy: true,
drop: function (a, b) {
card = b.draggable;
putCardIntoStack(card, c)
}
});
On the drop event, they call putCardIntoStack(card, c) passing the currently dragged element as the card parameter. Within this method, they remove the original "card" (a.remove()) and re-create it in the dropzone (newcard = createCard();):
function putCardIntoStack(a, b) {
progressVal = $('#progBarRd').width();
card_idDOM = a.attr('id');
card_idDB = card_idDOM.substr(IDPREFIX_CARD.length, card_idDOM.length - IDPREFIX_CARD.length);
stack_idDB = b.substr(IDPREFIX_STACK.length, b.length - IDPREFIX_STACK.length);
$.ajax({
url: URL_DRAGDROPAJAX,
type: 'POST',
data: 'action=movecard&cardid=' + card_idDB + '&tostack=' + stack_idDB + '&prog=' + progressVal
});
// 'a' is the card
// they extract the id/content from the dragged card
cardId = a.attr('id');
cardLabel = a.text();
// they remove the card from the DOM
a.remove();
// they create a new card with the extracted info
newcard = createCard(cardId, cardLabel);
newcard.addClass('stackcard');
// and append it to the dropzone
$('#' + b).removeClass("empty").find('.stackDrop').append(newcard);
globalcheck()
}
jQuery UI does a similar thing on the droppable demo page. On the drop event, they call a function deleteImage() which removes the dragged image from the original markup and appends it to the drop zone.
I hope I'm clear enough :-)
I also hope I'm right, it is pretty difficult to test quickly but it makes sense :-)

message in empty <div> to drag

I am working on drag and drop tool using jQuery UI's sortable widget.
I'd like to add a message into an empty div where something can be dragged into, like: "drag here". I'd like to remove this message as soon as something is in that div. There will be times when the page loads with something already in that div, so it can't be only on action, but onload needs to check it too.
How do I go about it?
Here's my code:
$("#divFrom, #divTo").sortable({
connectWith: '.connectedSortable'
}).disableSelection();
You should be able to set up a draggable, and droppable and tap into droppable's drop event handler, which is fired when an item is dropped:
$("#target").droppable({
drop: function() {
// Empty the droppable div:
$(".message").remove();
}
});
Demo here: http://jsfiddle.net/andrewwhitaker/rUgJF/2/
As for doing something similar on load, if you provided your markup it would make providing a solution a little easier (is there a specific element inside the droppable div that you could check for?)
Hope that helps.

Resources