perform function once element loaded - angular - javascript

I want to have an angular function fire after an element has displayed on the page.
The element is part of a SPA where what is displayed is controlled by a whole bunch of events (so doing things on page load won't work). The display of this element's parent (and therefore the element itself) is controlled by an ng-if which calls a separate function. As the parent doesn't display until that function has returned, there's no logical place to include the logic to alter this child element within that function, and since it's called by angular because of the ng-if, there's no parent function to place the code in once the previous function has returned.
I am currently achieving this by putting the function with my logic in it that always returns true within an ng-if on a child element of the element that has the proper, useful ng-if on it, as this will run as soon as the element has the option of being displayed. While this does work, I feel it's a very dodgy solution to the problem. Is there a more "proper" method of achieving this?
Snippet of the HTML (function names changed for the sake of the question):
<div data-ng-if="shouldTheButtonDisplay()">
<a class="btn"
data-ng-click="irrelevantToQuestion()"
data-ng-if="functionToPerformOnceElementLoaded()"
href="#">
button text
</a>
</div>
Snippet of JS (details changed because irrelevant to question):
$scope.shouldTheButtonDisplay() {
return booleanThatIsRelevantInContext;
}
$scope.functionToPerformOnceElementLoaded = function() {
// Edit state of button (technically an anchor)
var firstRowButton = document.querySelector("a.btn");
firstRowButton.style.background = "green";
return true;
}

I would have done so:
angular
.module('yourModuleName', [])
.controller('yourControllerName', yourControllerNameCtrl)
.directive('yourDirectiveName', yourDirectiveNameDirective);
yourControllerNameCtrl.$inject = ['$scope'];
function yourControllerNameCtrl($scope)
{
$scope.message = 'In controller';
$scope.irrelevantToQuestion = function() {};
}
function yourDirectiveNameDirective()
{
function doSomethingElse(scope)
{
scope.message = 'In directive';
}
return {
restrict: 'A',
scope: 'falsy',
link: function(scope, element)
{
element.find('a').addClass('green');
doSomethingElse(scope);
}
};
}
.btn {
background-color: red;
}
.btn.green {
background-color: green;
}
<html ng-app="yourModuleName">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.js"></script>
</head>
<body ng-controller="yourControllerName">
<div>{{ message }}</div>
<div your-directive-name>
<a class="btn"
data-ng-click="irrelevantToQuestion()"
href="#">
button text
</a>
</div>
</body>
</html>

Related

probably event bubbled on wrong element in custom directive

My goal is to flip a simple element on page on mouse click. It works but not on every click. I must click several times in different places across the tile and then the tile flip.
Code is executed as I see in the console, but it looks like click on wrong elements is fired.
I have custom directive with link method:
link: function (scope, element, attrs) {
scope.hideClicked = true;
scope.showClicked = false;
scope.backClicked = function () {
console.log('show front')
scope.hideClicked = false;
scope.showClicked = true;
}
scope.frontClicked = function () {
console.log('hide front');
scope.hideClicked = true;
scope.showClicked = false;
}
console.log(scope, element, attrs);
}
and in the template
<div class="tile" ng-class="{'hide-elem': hideClicked,'show-elem': showClicked }">
<span class="front" ng-click="frontClicked()">{{itemData.value}}</span>
<span class="back" ng-click="backClicked();"></span>
</div>
Full code to reproduct is here: https://codepen.io/lkurylo/pen/Laprjx
Both child-span-Elements use absolute positioning, so it seems as if the browser decides that the second element is displayed "on top" of the first. As a result, the click only registers on the back-Element, which happens to be the top-element.
I suggest you display only one element at any given time, e.g. by adding ng-show or ng-if to both elements.
<span class="front" ng-if="showClicked" ng-click="..."></span>
<span class="back" ng-if="hideClicked" ng-click="..."></span>

find <div> in a reloaded HTML page and apply function to it

I have an HTML page that lays out div components and every div component call a function and pass this as an object. The div looks like this:
<div id="pull-requests-view">
<div class="green-header">This is the 1st title</div>
# this is the div that will flash on double click
<div class="green" ondblclick="flashAndMessage(this)" onclick="unblink(this)" id="tile0">
<img class="profile-photo" src="someImageURL">
<div class="pill-green">
<a class="hypLink" href="UNIQUE_LINK_FOR_EACH_DIV" target="_blank">tag information</a>
</div>
</div>
</div>
What's really happening here is that there are multiple divs like that with different id. When I click double click on the div its calls the flashAndMessage() with argument this which results in my div to blink and flash using JQuery fadeIn() and fadeOut().
Details aside, the actual problem is that I have polling implemented that re-renders the HTML including all the divs. So basically the flashing and blinking is lost once the page updates.
I have tried global variables that store the this object before the page reloads and tries to pass the variables to jQuery find() function so that flashAndMessage() could be re-applied.
However it is not working for me. Here are my functions:
function unblink(selector) {
$(selector).stop()
$(selector).fadeIn({opacity: 1})
}
function blink(selector) {
$(selector).fadeOut('slow', function () {
$(this).fadeIn('slow', function () {
blink(this);
});
});
}
function flashAndMessage(selector) {
var pullReqURL = $(selector).find('.hypLink').attr('href')
blinkList.push(selector) # this is where the `div` is stored before the page reloads
blink(selector)
}
Note: The blinkList is a global array that is defined on top of everything else. I have a loop that runs after the divs are reloaded and I do see those divs that I stored in the list. However, when I iterate through that list and pass the obj to the flashAndMessage(), it does not work. It feels like the object is not the same when re-drawn or it loses some identity.
Code snippet not work because of restrictions, but code is tested and functional.
function unblink(selector) {
$(selector).stop();
$(selector).fadeIn({opacity: 1});
sessionStorage.href = "";
}
function blink(selector) {
$(selector).fadeOut('slow', function () {
$(this).fadeIn('slow', function () {
blink(this);
});
});
}
function flashAndMessage(selector) {
sessionStorage.href = $(selector).find('.hypLink').attr('href');
blink(selector);
}
if (sessionStorage.href) {
flashAndMessage($("a[href='"+sessionStorage.href+"']").parent().parent());
}
<div id="pull-requests-view">
<div class="green-header">This is the 1st title</div>
<div class="green" ondblclick="flashAndMessage(this)" onclick="unblink(this)" id="tile0">
<img class="profile-photo" src="someImageURL">
<div class="pill-green">
<a class="hypLink" href="UNIQUE_LINK_FOR_EACH_DIV" target="_blank">tag information</a>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Assuming you have a parent to <div id="pull-requests-view"> that is not getting reloaded with your polling code, you should use event delegation (and not inline event handlers).
<div id="parent-id-that-isnt-updated">
<div id="pull-requests-view">
...
</div>
</div>
You can attach your event listeners to the parent that doesn't update and have it only trigger when the clicked element matches the selector:
var $staticParent = $('#parent-id-that-isnt-updated');
$staticParent.on('dblclick', '.green', function() {
flashAndMessage(this);
});
$staticParent.on('click', '.green', function() {
unblink(this);
});

Hide any html element with Angularjs

With a controller I try to hide any html element that is clicked with function call like this:
<div class="well">
<h4><span class="label label-primary" ng-click="hideThis($event)" id="tag" hidden></span></h4>
<h4><span class="label label-default" ng-click="hideThis($event)" id="tag2" hidden></span></h4>
</div>
and this script should do the work
var App = angular.module('App', []);
App.controller('appCtrl', function($scope) {
$scope.hideThis = function($event) {
$event.target.hide=true;
//Code I've tried:
// $event.target.hide();
// $event.target.hide(true);
};
});
perhaps I'm not using $event.target.etc properties correctly?
ng-if will remove the element from the DOM; ng-hide will hide the element from the display only.
The other two answers already have the gist of it, but don't go into much detail on why other options are being suggested. They also don't incorporate how to relate those directives to the fact that you want things to happen on click.
To start by summarizing:
On ng-click your app should change the $scope.
On $scope changes Angular should change DOM element's visibility.
Let me repeat: your app should update the model (e.g. $scope), never the DOM itself. Let the latter be handled by Angular.
To add some more details...
AngularJS is a framework that handles "data binding" for you, meaning it will (and should) take charge of keeping your model (e.g. $scope) and view (the markup) in synch. You should usually not interfere with this behavior, unless there is a very specific reason to do so. A quite lengthy but interesting read on this and related topics can be found in this answer (which incidentally was answered to a question about when it is okay to use jQuery yourself).
Long story short: don't update the DOM inside your controller / scope.
Instead: work declaratively. Make sure that your controller and scope have all the info needed to base view-decisions (e.g. "show" vs "hide") on. Furthermore, make sure that your view is told when to show/hide based on the scope situation.
For completeness sake, let me end by repeating #JohnManko's suggestions, where the examples also show how you could handle ng-click to change the underlying properties.
The first is using ng-if:
var App = angular.module('App', []);
App.controller('appCtrl', function($scope) {
$scope.isTagOneActive = true;
$scope.isTagTwoActive = true;
$scope.hideTag1 = function() { $scope.isTagOneActive = false; }
$scope.hideTag2 = function() { $scope.isTagTwoActive = false; }
});
h4:hover { cursor: pointer; background-color: pink; }
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.js"></script>
<div ng-app="App" ng-controller="appCtrl">
<h4 ng-if="isTagOneActive" ng-click="hideTag1()" id="tag">Tag One!</h4>
<h4 ng-if="isTagTwoActive" ng-click="hideTag2()" id="tag">Tag Two!</h4>
</div>
This adds/removes elements from the DOM entirely.
To just let AngularJS toggle visibility, use ng-show and/or ng-hide:
var App = angular.module('App', []);
App.controller('appCtrl', function($scope) {
$scope.isTagOneActive = true;
$scope.isTagTwoActive = true;
$scope.hideTag1 = function() { $scope.isTagOneActive = false; }
$scope.hideTag2 = function() { $scope.isTagTwoActive = false; }
});
h4:hover { cursor: pointer; background-color: pink; }
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.js"></script>
<div ng-app="App" ng-controller="appCtrl">
<h4 ng-show="isTagOneActive" ng-click="hideTag1()" id="tag">Tag One!</h4>
<h4 ng-hide="!isTagTwoActive" ng-click="hideTag2()" id="tag">Tag Two!</h4>
</div>
it can be done much easier
<span class="label label-default" ng-show="showTag2=!showTag2" id="tag2" />

navigation between templates using directives angular JS

I am trying to navigate between different html templates on click of the button.
I have main html and sub html.
Main.html:
<div id="main"></div>
Sub.html:
<div id="sub"></div>
Index.html:
<my-clicker on-click="ButtonClicked($event)">
<button class="new_btn">Click Me</button>
</my-clicker>
Directive Code:
app.directive('myClicker', function () {
return {
restrict: 'E',
scope: {
onClick: "&"
},
link: function($scope, element, attrs) {
var button = $('.new_btn');
button.bind("click", $scope.onClick);
element.append(button);
}
};
});
Controller:
$scope.ButtonClicked = function($event) {
alert('hai');
};
Now how do I add my template urls to the button click event. Can anyone please help me out with the sample how we can achieve this scenario.
So according to the comments the usecase here is to swap out one specific part of the DOM when clicking a button.
In this case you don't need a custom directive, you should use ng-include. The ng-include directive is placed on a div (or other dom element) and makes that element load its content from a specified template. You can specify this template as a variable and when you want to swap the content of the div you just change the variable.
Here is an example:
<div id="swappable-content-area" ng-include src="currentTemplate">
<!-- The content will appear here -->
</div>
<button ng-click="currentTemplate = 'mytemplate.html'">Template 1</button>
<button ng-click="currentTemplate = 'myothertemplate.html'">Template 2</button>
The buttons could of course be a part of the included template.

Data from directive not displaying within ng-repeat

I have broken this problem down into it's simplest form. Basically I have a directive that, for the demo, doesn't yet really do anything. I have a div with the directive as an attribute. The values within the div, which come from an object array, are not displayed. If I remove the directive from the div, they are displayed OK. I am clearly missing something really obvious here as I have done this before without any problems.
Here's the Plunk: http://plnkr.co/edit/ZUXD4qW5hXvB7y9RG6sB?p=preview
Script:
app.controller('MainCtrl', function($scope) {
$scope.tooltips = [{"id":1,"warn":true},{"id":2,"warn":false},{"id":3,"warn":true},{"id":4,"warn":true}];
});
app.directive("cmTooltip", function () {
return {
scope: {
cmTooltip: "="
}
};
});
HTML
<div ng-repeat="tip in tooltips" class="titlecell" cm-tooltip="true">
A div element: {{ tip.id }}
</div>
<br><br>
Just to prove it works without the directive:
<div ng-repeat="tip in tooltips" class="titlecell">
A div element: {{ tip.id }}
</div>
There is a hack to make it working in earlier versions of angular by making use of transclusion, like that:
app.directive("cmTooltip", function () {
return {
scope: {
cmTooltip: "="
},
transclude: true,
template : '<div ng-transclude></div>'
};
});
PLNKR
As by Beyers' comment above and below, the behaviour the question is about no longer exists in at least 1.2.5
To be clearer; this has nothing to do with ng-repeat, you can remove it and there still will be no tip ( or tooltips ).
See this question on what the = and other configs mean and what it is doing for you.
Basically for your situation when you use = the scope of the directive will be used in the underlying elements, you no longer have your controller's scope. What this means for you is that there is no {{ tip.id }} or not even tip. Because the directive doesn't supply one.
Here's a plunker that demonstrates what you can do with it.
Basically all i did was
app.directive("cmTooltip", function () {
return {
scope: {
cmTooltip: "="
},
link: function($scope){ // <<
$scope.tip = { id: 1 }; // <<
} // <<
};
});
This creates the tip object on the scope so it has an id.
For your situation you would probably just not use = and look at this question for your other options depending on what you want.
In my opinion this isn't the way to go.
I would use Objects.
JS code:
function tooltip(id,warn){
this.id = id;
this.warn = warn;
}
tooltip.prototype.toString = function toolToString(){
return "I'm a tooltip, my id = "+this.id+" and my warn value = "+this.warn;
}
$scope.tooltips = [new tooltip(1,true),new tooltip(2,false),new tooltip(3,true),new tooltip(4,true)];
HTML:
<div ng-repeat="tip in tooltips" class="titlecell">
A div element: {{ tip.toString() }}
</div>

Categories

Resources