Stop event firing on child elements using EXT's mouseenter - extjs

The Problem:
I have an anchor tag with a class name 'hasChildren' which in turn has a span element containing the text. When using EXT's .on('mouseenter',function()) on the anchor tag, it fires the event on both the span and/or the anchor tag.
expected result:
hovering over either the span or the anchor tag, the class should be added to the anchor tag alone
current result:
hovering over either the span or the anchor tag, the class is added to the element which receives focus first.
As in the JS you'll see I tried the hover function but gives the same results.
The HTML:
<a class="hasChildren" href="#"><span>web 2.0</span></a>
The CSS:
.hasChildren {
display:block;
width:100px;
background-color:#333;
}
.hasChildren span {
background-color:#EEE;
display:block;
line-height:40px;
margin-left:10px;
padding:0 20px 10px 10px;
}
The JavaScript:
function over(e,t){
Ext.get(t).addClass('red');
}
function out(e,t){
Ext.get(t).removeClass('red');
}
Ext.onReady(function() {
//Ext.select('.hasChildren').hover(over,out,this);
Ext.select('.hasChildren').on('mouseenter',over);
Ext.select('.hasChildren').on('mouseleave',out);
});
FYI: I'm using ext-core-3.1.0 and I can get this working by using jQuery but as the lead developer requested that I only use extJS, I'd like to get this working without adding another javascript library.

Use this function of Ext.Element : addClassOnOver(). So, for your case, it will be:
Ext.onReady(function() {
Ext.select('.hasChildren').addClassOnOver('red');
});
It will automatically toggle the CSS class.

Try and surround your anchor tag in a div and then attach the listeners on the div. I think you want to try and attach the events to the outermost container in this case.

I eventually found what I was looking for. The javascript functions should change to look like this:
function over(e,t){
Ext.get(e.getTarget('a')).addClass('red');
}
function out(e,t){
Ext.get(e.getTarget('a')).removeClass('red');
}
The explanation: Previously I tried to add the class to 't' as in get(t), which rightfully could be either the parent or child element. But by using (e.getTarget('a')) I tell it to select the anchor tag and apply the class to that element alone.
This method gives one control over propagation, funny thing is the following could also work for the 'out' function and it would do exactly the same (in theory):
function out(e,t){
Ext.get(e.getTarget('span')).parent().removeClass('red');
}
Another thing I discovered: The Ext.onReady functions can also be written as:
Ext.select('.hasChildren').on( { mouseenter: { fn: over } } );
This way makes it easier to add more events to the target element(s)
Ext.select('.hasChildren').on( { mouseenter: { fn: over } }, { mouseleave: { fn: out } });

Related

HandsOnTable editor custom function

I'm using the autocomplete editor of HOT, but needed to have my own template of the option-list. I've been able to accomplish that, by removing the default display and replacing it with my own while doing a lazy load of its content. But I need to perform specific tasks on each of the options being clicked.
The issue is that I cannot find a way to have my <a ng-click='doSomething()'> or <a onclick = 'doSomething()'> tags to find my "doSomething" function.
I've tried the extend prototype of the autocomplete instance, have put my function out there on my controller to no avail. Is there any way I can insert a delegate function inside this editor that could be triggered from inside my custom-made template? (Using angularjs, HOT version 0.34)
Dropdown options cannot interpret HTML instead of Headers.
To perform action when an option is selected you can use Handsontable callback : AfterChange or BeforeChange
Here you can find all HOT callbacks https://docs.handsontable.com/0.34.0/tutorial-using-callbacks.html
This JSFiddle can help you http://jsfiddle.net/fsvakoLa/
beforeChange: function(source, changes){
console.log(source, changes)
},
afterChange: function(source, changes){
console.log(source, changes);
if(!source) return;
if(source[0][1] == 0){//if ocurs on col 0
let newsource = optionsWBS[source[0][3]];
cols[1] = {
type : 'dropdown',
source: newsource,
strict: false
};
hot.updateSettings({columns: cols});
hot.render();
};
}
Thanks, I actually needed actions specific to each area being clicked. What I did to make it work was this: while inserting the items for the list, I created the element and bound it to the function right away: liElement = document.createElement('li') .... liElement.onclick = doSomething(){} .... got it working this way ..

Event propagation does not happen like standard html

I know that react uses its own synthetic implementation of events. However it appears to me that they are not exactly like standard html and this is a problem. In my case I have a checkbox that is a sibling of some img tags. There are two img tags, one for representing checked and another unchecked. I have some css styling that does a display none when the checkbox is in an unchecked state. Trying to get this html/css working with react is hard. It appears that the click event is not propagating onto the checkbox sibling with a react component, although it works from standard html. Note as there's some confusion I know that the non-react version of this is using css and not events. But I'm trying to implement a react evented version of the same thing, and was expecting normal html event propagation behavior--which I'm not seeing. Also note the checkbox is a sibling of the label. It's also transparent, so the user never actually clicks on the checkbox they click on the img tags.
.checkbox-image input[type="checkbox"] + label img.selected {
display: none;
}
.checkbox-image input[type="checkbox"] + label img.unselected {
display: block;
}
<div class="checkbox-image"><input id="portfolio-standard-dev" type="checkbox" data-name="PortfolioStandardDeviation"><label for="mp-chart3"><img class="unselected" src="/images/img-843599.png"><img class="selected" src="/images/img-1b9f30.png"><span>Portfolio Standard Deviation</span></label></div>
CSS has nothing to do with events. An event propagation cannot impact your styles.
You have to handle when the user check/uncheck the checkbox and manually update the class of the image.
Or you can try the following pure CSS:
.checkbox-image input[type="checkbox"]:checked + label img {
display: none;
}
.checkbox-image input[type="checkbox"] + label {
display: block;
}

AngularJS - Changing an element's ng-enter animation

I have multiple states in my page that change the following element :
<header ng-view></header>
I decieded to add animation to the page's transition so and decided to go with ng.animate + animate.css. so I added the following class to the header :
.slideInLeft.ng-enter
{
transition-animation: slideInLeft 1s ease-in-out;
}
.slideOutRight.ng-leave
{
transition-animation: slideOutRight 1s ease-in-out;
}
and changed my header to this :
<header ng-view class='slideInLeft slideOutRight'></header>
This works quite well but the problem comes when I want to change my animations on the run. Since the user can go to the next page or the previous page so the class should change from 'slideInLeft slideOutRight' to 'slideOutLeft slideInRight'
I tried changing the class attribute directly but the new element (the one which is entering) stayed with the same classes as before.
I tried using ng-class='someVar' and giving 'someVar' the names of the classes but that didn't work either, I'm guessing angular didn't have a chance to update the UI since I changed the properites of 'someVar' right before using $state.go to the new page.
Someone has done it before/has an ideas how to solve this?
I managed to do it using :
<header ng-view ng-class="getAnimation()"></header>
By putting a function in the ng-class attribute, I could just make it return a scope variable. That scope variable is changed right before I go to the next page/previous page.
$scope.getAnimation = funtion()
{
return $scope.classVar;
}
$scope.nextClick = function()
{
$scope.classVar = "slideInLeft slideOutRight";
}
$scope.prevClick = function()
{
$scope.classVar = "slideOutLeft slideInRight";
}

ng-animate: only add to the dom after the animation delay

I'm trying to use ng-animate with an ng-repeat (and ng-show) to fade out old content and replace it with new.
The problem I'm having is that during the remove and add animations, both the element(s) being added and the element(s) being removed have display:block.
I thought I could avoid this by using an animation-delay in the CSS, but that just delays the fade, and not the addition of the class that sets display on the element.
The result is a jerky transition.
This is my animation CSS (cut down):
.keyframe-fade.ng-enter,
.keyframe-fade.ng-move {
animation: 0.5s fade-in;
animation-delay: 1s;
animation-fill-mode: backwards;
}
.keyframe-fade.ng-leave {
animation: 0.5s fade-out;
}
But it's easier to demonstrate with this plunkr.
Any ideas?
NOTE: To be clear, the desired behaviour on the plunkr linked is that the coloured squares always take up the same space, i.e. they sit on the same line and the button doesn't move. If possible, I'd like to fix this without absolute positioning 'bodges' as the actual page I'm using this on is much more complex than the demo given.
The solution that I found for this is to augment the pure CSS animation with a very small amount of JavaScript.
To summarise the problem:
The entering element is added to the DOM with the ng-enter class at the same time that the leaving element is given the ng-leave class.
Though there is an animation delay, the entering element still takes up space
So this piece of javascript takes the element and adds ng-hide for the duration of the leave-animation, removing it afterwards.
.animation('.keyframe-fade', ['$timeout', function ($timeout){
return {
enter: function (element, done){
// Add ng-hide for the duration of the leave animation.
element.addClass('ng-hide');
$timeout(function(){
element.removeClass('ng-hide');
}, 500)
done();
}
}
}])
The duration is hard-coded here but I don't see any reason that you couldn't grab it from the element instead.
Improvements/suggestions welcomed.
Here's the original plunkr with the change.
This is awful, and for Angular 2+ but just for the record here's one idea.
I have two button elements, one for when the user has items in their shopping cart, and one for when they don't.
The easiest way by far is to put position: relative on the parent DIV and position: absolute on both the buttons. The main disadvantage is the parent DIV has to be sized manually, and things like centering becoming trickier.
If the intent is to delay adding to the DOM based on an Observable value, then I thought 'Why not just delay the observable value?' which will have the same end effect. This needs to be done only when the transition is from false > true though because you only want to hide it when it is coming into view. So I used a pipe to handle this.
<!-- This button for when item is IN the cart -->
<button [#cartIconAnimation] *ngIf="showCartIcon | delayTrue | async">View Cart</button>
<!-- This button for when item is NOT IN the cart -->
<button [#cartIconAnimation] *ngIf="showCartIcon | complement | delayTrue | async">Add to Cart</button>
This assumes showCartIcon is Observable<boolean>.
Then the pipes are as follows, and no delay is required on your animation criteria.
#Pipe({
name: 'delayTrue'
})
export class DelayTruePipe implements PipeTransform {
constructor() {}
transform(value: Observable<any> | any, delay: number): Observable<any> {
if (isObservable(value)) {
return value.pipe(distinctUntilChanged(), debounce((show) => show ? timer(delay || 500) : empty()));
} else {
throw Error('Needs to be an observable');
}
}
}
#Pipe({
name: 'complement'
})
export class ComplementPipe implements PipeTransform {
constructor() {}
transform(value: Observable<boolean> | any): Observable<any> {
if (isObservable(value)) {
return value.pipe(map(i => !i));
} else {
throw Error('Needs to be an observable');
}
}
}
Note: The delay used by the pipe must be greater than the time it takes for the previous item to disappear, or you'll have the same problem.
The complement pipe just inverts the boolean value.
This solution works, but it's hacky and the timing might be harder to get wrong and there may be race conditions as two different browser timers fire off at the same time. I'd only do something like this if you really can't use position: absolute.

Unable to render a Ext.form.TextField into the output of an XTemplate

I want to render some Ext components into the output of an XTemplate. We want to have the flexibility of using an XTemplate to render the HTML but retain the styling, behaviour, and handlers of using Ext components rather than plain old HTML elements.
I am currently successfully doing this with an Ext.Button. In the template I am writing a placeholder div like so:
<div id="paceholder-1"></div>
After I have called apply() on the template I then create a new Ext component and render it in like so:
this._replacePlaceholders.defer(1, this, [html, 'placeholder-1', collection]);
The _replacePlaceholders function looks like this:
_replacePlaceholders: function(html, id, collection) {
var emailField = new Ext.form.TextField({
emptyText: 'Email address',
hideLabel: true
});
var downloadButton = new Ext.Button({
text: 'Download as...',
icon: 'images/down.png',
scope: this,
menu: this._createDownloadOptionsMenu(collection) // Create Menu for this Button (works fine)
});
var form = new Ext.form.FormPanel({
items: [emailField, downloadButton]
});
downloadButton.render(html, id);
}
This works and renders the button into the html correctly. The button menu behaves as expected.
But if I change the last line of replacePlaceholders to emailField.render(html, id); or form.render(html, id); I get a javascript error.
TypeError: ct is null
ct.dom.insertBefore(this.el.dom, position);
ext-all-debug.js (line 10978)
I'm a bit confused because from what I can tell from the docs the render() method called is going to be the same one (from Ext.Component). But I've had a bit of a play around and can't seem to track down what is happening here.
So is there any good reason why these components behave differently from Ext.Button? and is it possible to render an Ext.form.TextField or an Ext.form.FormPanel or anything that will let me use an Ext text field in mt XTemplate html?
NB. I am using ExtJS 3.3.1 and don't have the opportunity to upgrade the version. I believe ExtJS 4 has functionality which would make doing what I doing much easier.
Thanks!
Solution is quite simple - use form.render(id) instead of form.render(html, id).
See [api][1] if you have doubts.
The reason why button is rendering properly is that it has weird onRender implementation, different from Component.
onRender : function(ct, position){
[...]
if(position){
btn = this.template.insertBefore(position, targs, true);
}else{
btn = this.template.append(ct, targs, true);
}
[...]
}
As you can see in code above, if you provide position (which is basically second argument provided to render) it doen't use ct (which is first argument passed to render).
In normal component onRender method looks like this:
onRender : function(ct, position){
[...]
if(this.el){
this.el = Ext.get(this.el);
if(this.allowDomMove !== false){
ct.dom.insertBefore(this.el.dom, position);
if (div) {
Ext.removeNode(div);
div = null;
}
}
}
}
In code above, you can see, that ct is called always, despite the position is not null.
The bottom line is that rendering of button works by accident.

Resources