I have an angular-powered table on my page, with sorting. When I click a column to change the sorting, it makes a $http request for the new data and reapplies it. All works well, but it annoyingly scrolls the page to the top.
My sort links are rendered by a directive: <a ng-click='sort()' href='#' ng-transclude></a>
My sort handler is defined in the directive too:
link: function (scope, el, attrs) {
scope.sort = function () {
// Want to check the pagerInfo of the controller
scope.$parent.onsort(scope.sortby, sortDir);
return false;
};
}
This just calls through to a function on the contoller to refetch the data.
I've tried:
Returning false from the sort function
Adding this to my module to disable angular's scrolling: var ngGrid = angular.module("ngGrid", []).value('$anchorScroll', angular.noop);
Setting a min-height in css on the table in case that was causing it
Using ng-href instead of href in the anchor link.
Found it.
In my sort function, instead of returning false, I needed to event.preventDefault();
Full function:
link: function (scope, el, attrs) {
scope.sort = function () {
// Want to check the pagerInfo of the controller
var sortDir = "desc";
if (scope.$parent.pagerInfo.sortby == scope.sortby) {
sortDir = scope.$parent.pagerInfo.sortdir == "asc" ? "desc" : "asc";
}
scope.$parent.onsort(scope.sortby, sortDir);
event.preventDefault();
};
}
Related
Is there anyway to trigger opening the match results on a typeahead input text box from the controller?
use case:
user goes to https://example.com/search/searchText
controller of page sets the input text to "searchText" (ng-model) on initialization
trigger showing the typeahead results from the controller
I can only seem to get the typeahead results, obviously, while typing in the input text box.
I got it to work in a couple ways, but both require changes to ui-bootstrap. I suppose I could create a pull request but not sure if my particular use case is a common one.
1) Custom directive and calling UibTypeaheadController.scheduleSearchWithTimeout method on focus of input element.
Directive:
.directive("showSearchResultsOnFocus", function($stateParams) {
return {
require: ['uibTypeahead', 'ngModel'],
link: function (scope, element, attr, ctrls) {
var typeaheadCtrl = ctrls[0];
var modelCtrl = ctrls[1];
element.bind('focus', function () {
if (!$stateParams.search || !modelCtrl.$viewValue) return;
typeaheadCtrl.exportScheduleSearchWithTimeout(modelCtrl.$viewValue);
});
}
}
Update to ui-bootstrap:
this.exportScheduleSearchWithTimeout = function(inputValue) {
return scheduleSearchWithTimeout(inputValue);
};
Bad: Requires making the method public on controller. Only method available is the init method and scope is isolated. Not meant to call from outside controller.
2) Add new typeahead attribute to allow setting default value and show results on focus:
Update to ui-bootstrap:
var isAllowedDefaultOnFocus = originalScope.$eval(attrs.typeaheadAllowDefaultOnFocus) !== false;
originalScope.$watch(attrs.typeaheadAllowedDefaultOnFocus, function (newVal) {
isAllowedDefaultOnFocus = newVal !== false;
});
element.bind('focus', function (evt) {
hasFocus = true;
// this was line before: if (minLength === 0 && !modelCtrl.$viewValue) {
if ((minLength === 0 && !modelCtrl.$viewValue) || isAllowedDefaultOnFocus) {
$timeout(function() {
getMatchesAsync(modelCtrl.$viewValue, evt);
}, 0);
}
});
Bad: Pull Request to ui-bootstrap but change perhaps not a common use feature. Submitted a PR here: https://github.com/angular-ui/bootstrap/pull/6353 Not sure if will be merged or not but using fork until then.
Any other suggestions?
Versions
Angular: 1.5.8, UIBS: 2.2.0, Bootstrap: 3.3.7
Below Angular JS code works fine on Mouseover & Mouseout. Need help regard adding conditional logic on JS code.
If class name "active" exists, img src path have to be in "overImg" even if user mouseover & mouseout. But, present behaviour removes overImg once user mouseout from element. Active state have to be different from the rest of navigation element.
AngularJS:
.directive('eleHoverAction', function() {
return {
link: function (scope, elem, attrs) {
var imgObj = $(elem).find('img');
var upImg = attrs.eleUpImgSrc;
var overImg = attrs.eleOverImgSrc;
elem.bind('mouseover', function () {
$(imgObj).attr("src", overImg);
scope.$apply();
});
elem.bind('mouseout', function() {
$(imgObj).attr("src", upImg);
scope.$apply();
});
}
};
});
HTML:
<li class="menu-item menu-item--category active" ele-hover-action ele-up-img-src="images/test1.png" ele-over-img-src="images/test1-over.png">
<img src="images/test1.png" oversrc="images/test1-over.png" alt=""/><span>Test1</span>
</li>
<li class="menu-item menu-item--category" ele-hover-action ele-up-img-src="images/test2.png" ele-over-img-src="images/test2-over.png">
<img src="images/test2.png" oversrc="images/test2-over.png" alt=""/><span>Test2</span>
</li>
The most obvious method is to add an if statement to your "mouseout" handler that checks if the element hasClass active:
.directive('eleHoverAction', function() {
return {
link: function (scope, elem, attrs) {
var imgObj = elem.find('img');
var upImg = attrs.eleUpImgSrc;
var overImg = attrs.eleOverImgSrc;
elem.bind('mouseover', function () {
imgObj.attr("src", overImg);
scope.$apply();
});
elem.bind('mouseout', function() {
if (!elem.hasClass("active")) {
imgObj.attr("src", upImg);
}
scope.$apply();
});
if (elem.hasClass("active")) {
imgObj.attr("src", overImg);
} else {
imgObj.attr("src", upImg);
}
}
};
});
I went ahead and set the src attribute of the image based on the directive attributes. You could just take that part out if you don't want it. Also, wrapping elem in a jQuery call is redundant because Angular elements are already wrapped in either jQuery (if available when Angular loads) or its own jQLite. Otherwise, you wouldn't be able to call elem.bind.
Try it in a fiddle.
in my application i have a controller and a directive which i use to draw a chart.
so my model is like this: $scope.d3DataGraph ={"selected":{"node":"","impiega":[],"impiegato": []} , "nodes":[],"links":[]};
in the controller i've set up a function that adds some data to the model:
$scope.d3DataGraph.nodes.push(articolo);
then i have the directive which is responsible to draw the graph by adding some svg tags to the dom:
in my directive i have a render function that have to be triggered when the model changed...
angular.module('myApp.directives')
.directive('d3Graph', ['d3', function(d3) {
return {
restrict: 'EA',
scope: {
data: "=",
query: "=",
label: "#",
onClick: "&"
},
link: function(scope, iElement, iAttrs) {
var svg = d3.select(iElement[0]).append("svg")
.attr("width", "100%")
.attr("height", "800px");
var datatree = {};
scope.$watch(function(){ return scope.data.nodes; }, function(){
return scope.render(scope.data, scope.query);
}
);
scope.render = function(datatreex, query){....
the directive is "called" whit this tag
<d3-graph data="d3DataGraph" selected = "selected" query = "selezionati"></d3-graph>
the problem is that the render function is called only when the page is loaded, but not when the controller updates the model ...
where i get wrong?
the overall set up seems to be correct, what do you think of it?
That's because $watch is just watching the reference of scope.data.nodes, so no matter what you push or pop, the reference will not change.
Instead of using $watch, you can use $watchCollection. It will detect the length of the array.
scope.$watchCollection('data.nodes', function(){
return scope.render(scope.data, scope.query);
});
I'm using a directive to implement scrolling on a page from an a to a div. Meaning that by clicking on an a looks like:
<a data-scroll-on-click="" href="#projects">Projects</a>
The page smoothly scrolls to:
<div id="projects">
To make this happen, I am using an attribute scroll-on-click via the following directive:
consortiumApp.directive('scrollOnClick', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var idToScroll = attrs.href;
element.on('click', function(event) {
event.preventDefault();
var $target;
if (idToScroll) {
$target = $(idToScroll);
} else {
$target = element;
}
$("body").animate({scrollTop: $target.offset().top}, 1500, 'easeInOut
});
}
}
});
The preventDefault stops the router from kicking in.
The a with the data-scroll-on-click attribute is part of a navbar that I would like to include on other pages via ng-include. However, this means that the a element will have the scroll-on-click attribute on pages where scrolling does not make sense. Meaning that when the navbar is on other pages besides the main page, I want the anchors in it to function like links back to the main page and not to trigger scrolling.
I'm not sure what a good solution is: I'm not sure if it is possible to have scroll-on-click appear only when there is a certain active controller (a sort of conditional attribute)? Or if it is possible to indicate the current active controller in a directive?
Worst comes to worst, I will just write two navbars - one for the main page that implements scrolling via a directive and one for subsidiary pages that implements linking via the router, but I have a feeling there is a more concise way to do this.
I think you can pass boolean value along with the data-scroll-on-click='true/false' and check this in the directive if its true than proceed for scroll otherwise ignore it.
<a data-scroll-on-click="" href="#projects">Projects</a>
And
consortiumApp.directive('scrollOnClick', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var idToScroll = attrs.href;
var scroll_needed = attrs.scrollOnClick;
if(scroll_needed === true){
element.on('click', function(event) {
event.preventDefault();
var $target;
if (idToScroll) {
$target = $(idToScroll);
} else {
$target = element;
}
$("body").animate({scrollTop: $target.offset().top}, 1500, 'easeInOut
});
}
}
}
});
my html is taking input in two form, input and contenteditable div . I want to write one directive that handles both, but I cannot find a way to figure out which tag has called the function (because Angular's JQLite doesnt provide a is() or get() function). The following code will be complete if I can figure out to evaluate IS_INPUT_TAG:
function funct() { return {
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
// view -> model
element.bind('input', function() {
scope.$apply(function() {
if(IS_INPUT_TAG)
ctrl.$setViewValue(element.val());
else
ctrl.$setViewValue(element.text());
scope.watchCallback(element.attr('data-ng-model'));
});
});
// model -> view
ctrl.$render = function() {
if(IS_INPUT_TAG)
element.val(ctrl.$viewValue);
else
element.text(ctrl.$viewValue);
};
}};
}
app.directive('input', funct);
app.directive('contenteditable', funct);
In your directive, you can make use of the element parameter of the linking function to identify the tag on which the directive is applied. You can then use that in your IF condition as follows:
ctrl.$render = function() {
var tagname = element["0"].tagName;
if(tagName === "INPUT")
element.val(ctrl.$viewValue);
else
element.text(ctrl.$viewValue);
};
After, this you can simply attach the directive to the input and the div tags as an attribute to the tags to identify the tag to which the directive is applied.