Parent/child click relationships in AngularJS directives - angularjs-directive

I have a custom directive placed on a Kendo UI treeview widget.
It seems to be working fine side-by-side, except that I'm trying to simply display the custom icons next to the tree node which is clicked on (see sample image below).
So my directive is data-toggle-me, placed next to the Kendo k-template directive as follows :
<div class="reports-tree" kendo-tree-view="nav.treeview"
k-options="nav.treeOptions"
k-data-source="nav.reportsTreeDataSource"
k-on-change="nav.onTreeSelect(dataItem)" >
<span class="tree-node" k-template data-toggle-tree-icons>{{dataItem.text}}</span>
</div>
and the directive code here inserts some custom icons next to the tree node when a user clicks on that tree node :
.directive('toggleMe', function ($compile) {
// Kendo treeview, use the k-template directive to embed a span.
// Icons appear on Click event.
return {
restrict: 'AE',
transclude: true,
template: '<span ng-show="nav.displayIcons" id="myIcons" class="reptIcons" style="display:none;width:50px;align:right;">' +
' <a title="add new folder" ng-click="nav.addAfter(nav.selectedItem)"><i class="fa fa-folder-open"></i></a> ' +
'<a title="add report here" ng-click="nav.addBelow(nav.selectedItem)"><i class="fa fa-plus"></i></a> ' +
'<a title="remove" ng-click="nav.remove(nav.selectedItem)"><i class="fa fa-remove"></i></a> ' +
'<a title="rename" onclick="showRename(this);"><i class="fa fa-pencil"></i></a>' +
'</span>',
link: function (scope, elem, attrs) {
var icons = elem.find("#myIcons");
elem.on('click', function (e) {
$('.reptIcons').css('display', 'none');
icons.css("display", "inline");
icons.css("margin-left", "5px");
});
}
}
})
My biggest problem at this point is getting the icons to appear on the treenode which is clicked on. Then once the user clicks on a different node, the icons will only render again on the newly-clicked node.
This fiddle represents a partially-working example but the icons are appearing on every single treenode - click tree item to show icons
**** UPDATED TREE IMAGE - All child nodes now show icons (not what I want) ****

I'm not sure to understand your issue, you should try to reduce the code to the minimum and have a snippet/jsfiddle that works.
If all you want is not trigger click events when $scope.disableParentClick is set to true, simply add
elem.on('click', function (e) {
// Do not execute click event if disabled
if (!$scope.disableParentClick) { return; }
...
});
Now that seems all not very angular friendly to me. You should externalize your HTML in either the template or templateUrl of your directive, potentially adding to it a ng-if="displayTemplate" which would only display the node when a click would set $scope.displayTemplate = true;
Also, instead of listening for click events this way, you should use the ng-click directive. Everything is doable with directives. I can give more information when you better understand your problem: I suspect you are not approaching it the right way.
UPDATE: if all you want is display the icons list of the clicked element, you could do it way easier. You actually don't need the toggle-me directive, but even if you keep it you can solve all your troubles the angular-way, which is by using ng-click, ng-repeat, etc. Please have a look at the following jsFiffle to see one way of doing that. There are many other ways, but really try using ng-click to avoid troubles:
http://jsfiddle.net/kau9jnoe/

Events in the DOM are always bubbling up. That is a click on a link would trigger an onclick handler on every element up the hierarchy, e.g. also the body element. After all the click happened within body.
The same is true for your directive. Any click within your element triggers its event handler. To circumvent this either attach the event handler somewhere else or ignore clicks from the links.
The event object has a target property that tells you what element initiated the event. So you could do something like this:
elem.on('click', function (e) {
if (e.target.nodeName.toLowerCase() == 'a') return; //ignore click on links

Related

What is the HTML <dialog> tag used for and when to use it?

The way I've understood it, the tag is used to open and close content like a popup alert. What I fail to understand is what advantages the tag has compared to just using a "div" and styling it with css and adding functionality to it with js. It also seems counter intuitive to manipulate the "open" property in order to show/hide the content instead of using display:none/block; with css.
I also don't understand exactly which scenarios would be considered a dialog box. Is a form login box a dialogbox? What about a popup telling you to disable adblock? Are all popups that can be hidden considered dialog boxes?
The traditional, hacky way to create a dialog, via designing a div via CSS only seems to be intuitive for you because you are used to it. However, you need to implement every functionality related to it, such as:
opening it
closing it
Also, in the future, this will be enhanced by standard functionalities, so, while it's not urgent for already existent code, but when you write code, especially when you start a project, it makes sense to start using it. Let's see an example from [Mozilla's page][1]:
var updateButton = document.getElementById('updateDetails');
var favDialog = document.getElementById('favDialog');
var outputBox = document.querySelector('output');
var selectEl = document.querySelector('select');
var confirmBtn = document.getElementById('confirmBtn');
// "Update details" button opens the <dialog> modally
updateButton.addEventListener('click', function onOpen() {
if (typeof favDialog.showModal === "function") {
favDialog.showModal();
} else {
alert("The <dialog> API is not supported by this browser");
}
});
// "Favorite animal" input sets the value of the submit button
selectEl.addEventListener('change', function onSelect(e) {
confirmBtn.value = selectEl.value;
});
// "Confirm" button of form triggers "close" on dialog because of [method="dialog"]
favDialog.addEventListener('close', function onClose() {
outputBox.value = favDialog.returnValue + " button clicked - " + (new Date()).toString();
});
<!-- Simple pop-up dialog box containing a form -->
<dialog id="favDialog">
<form method="dialog">
<p><label>Favorite animal:
<select>
<option></option>
<option>Brine shrimp</option>
<option>Red panda</option>
<option>Spider monkey</option>
</select>
</label></p>
<menu>
<button value="cancel">Cancel</button>
<button id="confirmBtn" value="default">Confirm</button>
</menu>
</form>
</dialog>
<menu>
<button id="updateDetails">Update details</button>
</menu>
<output aria-live="polite"></output>
However, at the time of this writing (February the 3rd, 2022), this is not supported in all browsers, so it is perfectly feasible to avoid using it for now, until it will become supported everywhere.
[1]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog

Event listener for button does not work unless page is refreshed in React

In my index.html, I have the following code:
<head>
...
<script type="text/javascript"
src="https://mysrc.com/something.js&collectorId=f8n0soi9"
</script>
<script type="text/javascript">window.ATL_JQ_PAGE_PROPS = {
'f8n0soi9': {
"triggerFunction": function (showCollectorDialog) {
document.getElementById("button1").addEventListener("click", function (e) {
e.preventDefault();
showCollectorDialog();
});
}
}
};</script>
</head>
and then in my myComponent.tsx file, I have a button somewhere on the page that looks like this:
function myComponent() {
return (
...
<button id="button1">
Button Text
</button>
...
);
}
export default myComponent;
It's probably also important to note that I'm using react-routing to navigate between various components, and the button above is just in one of those components
So the issue seems to be that if you load in to a site on any other webpage and later navigate to the page with the button on it, the button won't work unless you refresh that specific page, since presumably it wasn't on the first page loaded and perhaps no element with id "button1" was found to bind the event listener to. React-routing doesn't refresh the page by default when navigating through the site.
Putting the code I have in the index.html file into the myComponent.tsx file also does not work, since (I think) the index.html file allows for any raw html but the tsx file isn't truly html? Is there perhaps a way to define this as a function in the index.html file and then assign an onClick event to the button? Thank you all in advance!
Yes, it should be possible to bind the function that shows the dialog for later use and then use the recommended React event on the button.
In this example bound globally to the window object for simplicity:
"triggerFunction": function (showCollectorDialog) {
window.showCollectorDialog = showCollectorDialog;
}
// ...
<button id="button1" onClick={e => {e.preventDefault(); window.showCollectorDialog();}}>
If you run into Type errors with that, try (on the button):
=> {...; (window as any).showCollectorDialog();}
Or declare only the property (possible be more specific than any here if the signature is known):
declare global {
interface Window { showCollectorDialog: any; }
}
It should be fine to have this just somewhere in your TS source, your index.html without TS should just assign it.

Disable showModal auto-focusing using HTML attributes

As I am aware, the showModal() method runs the following steps which end up focusing elements within an HTML dialog (emphasis mine) :
Let subject be the dialog element on which the method was invoked.
If subject already has an open attribute, then throw an "InvalidStateError"
DOMException.
If subject is not connected, then throw an
"InvalidStateError"
DOMException`.
Add an open attribute to subject, whose value is the empty string.
Set the dialog to the centered alignment mode.
Let subject's node document be
blocked by the modal
dialog subject.
If subject's node document's top
layer does not already
contain subject, then
add subject to
subject's node document's top
layer.
Run the dialog focusing steps for subject.
So the last step, 8, will run the following dialog focusing steps on the dialog. From my understanding (which could be completely wrong), these three steps from the dialog focusing-steps section of the spec specify that the element should only be focused if the element is not inert and is auto-focusable:
If subject is inert, return.
Let control be the first descendant element of the subject, in tree order, that is not inert and has the autofocus attribute specified.
If there isn't one, then let control be the first non-inert descendant
element of subject, in tree order.
If there isn't one of those either, then let control be subject.
Run the focusing steps for control.
...
So, to me, it seems as though if my button below (see snippet) has the inert attribute or is not auto-focusable then it shouldn't get focused when the dialog opens. However, when I try and apply both attributes, it still ends up being focused.
Attempt with the inert boolean attribute (which I thought would've made the dialog focusing steps return above, hence performing no focusing):
const dialog = document.querySelector("#dialog");
document.querySelector("#open-btn").addEventListener('click', () => {
dialog.showModal();
});
document.querySelector("#close-btn").addEventListener('click', () => {
dialog.close();
});
#close-btn:focus {
background: red;
}
<button id="open-btn">Open</button>
<dialog id="dialog">
<button id="close-btn" inert="inert">×</button>
</dialog>
Attempt with the autofocus boolean attribute set to false (I believe this is how you set it to false, I also tried autofocus="false" which didn't work either):
const dialog = document.querySelector("#dialog");
document.querySelector("#open-btn").addEventListener('click', () => {
dialog.showModal();
});
document.querySelector("#close-btn").addEventListener('click', () => {
dialog.close();
});
#close-btn:focus {
background: red;
}
<button id="open-btn">Open</button>
<dialog id="dialog">
<button id="close-btn" autofocus="">×</button>
</dialog>
With both of these failing to work, I searched SO and found this answer which suggested that I might also be able to use tabindex="-1", which didn't work either.
I'm aware that I can blur the button once it is focused using .blur(), but my question specifically is:
Why don't the two fiddles above disable the button from being automatically focused?
Is there an HTML attribute of some sort that I can use to stop my button from being focused?
Disabled elements cannot be focused on. You could add the disabled attribute to close-btn.
But, disabled elements cannot be clicked. Add the onclick attribute to open-btn. Set the onclick to this: setTimeout(function(){document.getElementById('close-btn').disabled = false}). This just enables the button 1 millisecond after the button is clicked. The timeout is required so that it does not enable close-btn before the dialog is opened.
If the dialog is re-opened, The button is automatically focused. We could add another onclick attribute to close-btn. Set onclick on close.btn to this: this.disabled = true. This disables close-btn when it is clicked.
Final result:
const dialog = document.querySelector("#dialog");
document.querySelector("#open-btn").addEventListener('click', () => {
dialog.showModal();
});
document.querySelector("#close-btn").addEventListener('click', () => {
dialog.close();
});
#close-btn:focus {
background: red;
}
<button id="open-btn" onclick='setTimeout(function(){document.getElementById("close-btn").disabled = false})'>Open</button>
<dialog id="dialog">
<button disabled id="close-btn" inert="inert" onclick='this.disabled = true'>×</button>
</dialog>

How to close an Angular2 modal with a boolean?

I'm new to html and Angular2. Any help with this problem would be much appreciated.
I am placing a form component inside a ng2-modal and I would like the modal to close when the form returns a boolean from an event.
The form is for adding a new class to a database. Its first usage is on another page where it is not inside a modal.
There, upon clicking cancel/submit in the component, I have it return true and then redirect to another url.
For the new implementation I want the modal to close when the form returns true. The problem is that the save/cancel buttons are in the component containing the form. So I can't just use the modal's click event to close it.
Here is my current code:
<modal #addNewClassModal>
<modal-header>
<h4 class="modal-title"><i class="fa fa-cube"></i> Add a new class</h4>
</modal-header>
<modal-content>
<div>
<add-new-class (closeModal)="finishAddingNewClass($event)">
</add-new-class>
</div>
</modal-content>
</modal>
My problem is that I can't figure out how to get the modals close() method to rely on the boolean.
I've tried putting closeModal="addNewClassModal.close()" in different places and switching around the syntax but its not working and I can't find anything online.
You can pass a reference to addNewClassModal to your AddNewClass component:
<add-new-class [modal]="addNewClassModal">
</add-new-class>
And in your AddNewClass component add Input() for modal:
import { Input } from '#angular/core';
import { ModalDirective } from 'ng2-bootstrap/components/modal';
#Input()
modal: ModalDirective; // you can also set type to any instead of ModalDirective
Then in your component you can close modal with hide() function:
this.modal.hide();

How can i click a span in Behat?

I am using Behat to test an third-party webshop. I have a item in the shoppingcart that i want to delete. A confirmation pop-up shows that asks me if i really want to do it. The structure of this dialog looks as following:
<div>
<strong class="title">Remove item from shoppingcart</strong>
<p>Are you sure you want to delete this product?</p>
<div class="button-container">
<span class="button" data-confirm="true">Yes</span>
<span class="button alt right" data-mfp-close-link="true">No</span>
</div>
</div>
I was able to select the span using xpath with the following code:
public function iConfirmTheWindow()
{
$session = $this->getSession();
$element = $session->getPage()->find(
'xpath',
$session->getSelectorsHandler()->selectorToXpath('css', 'span.button')
);
if (null === $element) {
throw new \InvalidArgumentException(sprintf('Could not find confirmation window'));
}
$element->click();
}
The selecting works, but Behat seems to be unable to click the span.
supports clicking on links and submit or reset buttons only. But "span" provided
I need to click this item, how can i rewrite my function so that it can be clicked?
The answer from #bentcoder doesn't make any different. It uses a different a selector to find the element, but the Minkcontext click functionality doesn't support clicking on span elements.
Which i find quite strange, because with jQuery you can add the button class to and span element and there is your button.
Context code:
/**
* #Given I click the :arg1 element
*/
public function iClickTheElement($selector)
{
$page = $this->getSession()->getPage();
$element = $page->find('css', $selector);
if (empty($element)) {
throw new Exception("No html element found for the selector ('$selector')");
}
$element->click();
}
CLI output:
And I click the "#new_account" element # tests/behat/features/account.feature:14
Behat\Mink\Driver\GoutteDriver supports clicking on links and submit or reset buttons only. But "span" provided (Behat\Mink\Exception\UnsupportedDriverActionException)
I'm assuming that you have have a Behat driver for interpreting javascript. So i've added #javascript to the feature:
like so:
#javascript
Scenario: Create new account
Given I am logged in as "user" user
And I am on "/user/settings"
And I click the ".new_account" element
The code snippet I use is this:
/**
* #Then /^I click on "([^"]*)"$/
*/
public function iClickOn($element)
{
$page = $this->getSession()->getPage();
$findName = $page->find("css", $element);
if (!$findName) {
throw new Exception($element . " could not be found");
} else {
$findName->click();
}
}
As an example, I would write something like this in my scenario:
Feature: Test Click
#javascript
Scenario: Clicking on spans
Given I go to "http://docs.behat.org/en/v2.5/"
And wait "3000"
When I click on "span:contains('behat.yml')"
And wait "3000"
Then I should be on "http://docs.behat.org/en/v2.5/guides/7.config.html"
I hope this helps.