I'm using AngularJS, UI-Grid (http://ui-grid.info/), and Bootbox for a project. What I'd like to do is have the user click a button on the screen, and bring up a dialog box where they select an item from a UI-Grid table.
I put together the simplest example I could make:
http://plnkr.co/edit/YvsiAQhUrNqnSse1O0oh?p=preview
In this, I generate the dialog box HTML like this:
var tplCrop = '<div ng-controller="MyTableDialogCtrl">{{ someText }}' +
'<div ui-grid="gridOptions" ui-grid-pagination class="grid"></div>' +
'</div>';
var template = angular.element(tplCrop);
var linkFn = $compile(template);
var html= linkFn($scope);
If you click the "My Dialog" button, a dialog comes up, and there is a region for the table, but it's empty. The "someText" value proves my Angular $compile is doing what it should.
If you uncomment the commented section, you'll see that the UI-Grid mechanics also seem to be working properly. But, for some reason, the UI-Grid doesn't want to render inside the dialog.
Any ideas? I'm really stumped.
Thanks,
John
http://plnkr.co/edit/bbNVgwoTRtHZp3cKcoiB?p=preview
onRegisterApi: function( gridApi ) {
$scope.gridApi = gridApi;
$timeout( function() {
$scope.gridApi.core.handleWindowResize();
}, 1000);
}
In the modal tutorial it mentions that the bootstrap modals seem to run animations on initial render, and that this causes the grid problems with getting an accurate size.
I noted that in your plunker when I open the chrome debugger the grid shows up - indicating that it's a sizing issue of one sort or another.
I can get it to show in the above plunker by using a $timeout of 1s, and a handleWindowResize. Usually I can get away with a lot less than 1s, but in this case at 100ms it didn't work.
I'd have to say I'd be uncomfortable with a timeout this long, as it may turn out that different browsers and devices run different speeds, so you'll end up with an intermittent app. You could however consider a $interval that runs every 100ms for the first 3 seconds after window open.....that probably would cover most situations.
Related
We are working on an HTML page which makes use of a Bootstrap tooltip on a certain <span> tag. For those who have not heard of tooltip, it is a popup of sorts which appears when hovering over the element to which it is attached. Here is a screenshot showing the <span> in question, and what happens on hover:
The premise behind adding the tooltip was that in the event that we truncate the text, the tooltip would provide an option for viewing the entire text.
However, we would now like to condtionally show the tooltip only when there is no ellipsis in the text. We defined the tooltip-enable property in the <span>:
<span uib-tooltip="{{someName}}" tooltip-placement="right" tooltip-enable="{{showToolTip}}">{{someNameShortened}}</span>
The key thing here is tooltip-enable="{{showToolTip}}", which binds the property to a scoped variable in the controller for this page. And here is the relevant (and abbreviated) controller code:
mainApp.controller('repoListController',['$scope', '$rootScope', ...,
function($scope,$rootScope, ...) {
$scope.showToolTip = false;
var repositoryList= function(){
repositoryService.getRepositoryList(function(data) {
var repoList = data;
repoList.shortenedDisplayName = repositoryService.getShortRepoName(repoList.repoName, DISPLAY_NAME_MAX_LENGTH);
// if the repository's name be sufficiently large (i.e. it has an ellipsis)
// then show the tooltip. Otherwise, the default value is false (see above)
if (repoList.repoName.length > DISPLAY_NAME_MAX_LENGTH) {
$scope.showTooltip = true;
}
});
}
repositoryList();
}]);
Based on the research I have done, the common solution for why a change to a scoped variable is not reflected in the UI is to run $scope.$apply(), or some variation on this. Running apply(), as I understand it, will tell Angular JS to do a digest cycle, which will propagate changes in the scope to the UI. However, trying to do an apply() from the code which toggles showToolTip resulted in errory. I inspected the value of $scope.$root.$$phase while running the code which updates the showToolTip variable, and the phase was digest.
So now I am at a loss to explain this. If the code is already in a digest, then why would changes not be reflected in the UI? Also, if the code is already in digest, then how could I force Angular to sync the changes to the UI?
Two things need fixing...
Don't use string interpolation for your boolean showToolTip
<span uib-tooltip="{{someName}}" tooltip-placement="right"
tooltip-enable="showToolTip">{{someNameShortened}}</span>
JavaScript variables / properties are case sensitive. In your getRepositoryList handler, you have $scope.showTooltip. It should be $scope.showToolTip (two capital "T"s)
Crappy Plunker demo ~ http://plnkr.co/edit/W7tgJmeQAJj0fmfT72PR?p=preview
I have a page built in angular. The page has a set of data displayed that's paginated. I have the page searchable with a search box. The pagination is build by hand due to some custom rules about what comprises each page. But building the pagination is set in a buildPages() method, and from there it's a simple call to build the pages that directly updates the scope the page draws from.
So typically a search works like so:
Type in search.
Watcher catches the change on search.
Watcher sets search var.
Watcher calls buildPages();
buildPages rebuilds pagination and updates scope.
Scope is update, pagination and displayed data is redrawn.
Now all of this works just fine. But I needed to add in a dialog to ask the user if they want to change the search or leave it, and this dialog only appears sometimes based on certain criteria.
My problem is that if I open a dialog and call buildPages() from a button click in the dialog, it properly updates the scope, but it doesn't redraw the page.
So, for example, you have 50 pages and you're displaying page 1 of 50. You then type in a search. The search filters out 25 of those 50 pages, leaving you with 25 pages left. The page you were initially on is also filtered out. Without the dialog, typing in the search will result in you appearing on page 1/25 on the first filtered page. However, triggering the search from the dialog will still show you on page 1 of the unfiltered page, at page 1/50. Clicking next on the pagination control will properly take you to the second filtered page 2/25, and clicking back from there will properly show you on page 1/25 of the filtered pages.
Even doing something as simple as triggering a hover will cause the page to properly redraw correctly.
What's going on with this? Why is the dialog preventing the redraw?
Here's the code I'm using in my watcher (slightly simplified).
$scope.$watch('searchText', function(newVal, oldVal) {
currentSearch = newVal;
if(newVal !== oldVal){
if(displayFlag){
j$( "#dialog-confirm").dialog({
resizable: false,
width:450,
height:240,
modal: true,
buttons: {
"Search": function() {
j$( this ).dialog( "close" );
buildPages();
},
Cancel: function() {
j$( this ).dialog( "close" );
return false;
}
}});
}
else
{
buildPages();
}
}
});
I've tried including a return of either true or false in the search click. I've also tried switching the order of the dialog close and buildPages in the search click.
I am not sure if j$() is an angular library. It appears that it may not be.
Remember that Angular does not know about any type of events or changes except its own, therefore, if you are performing an action with jQuery or another library like that .dialog, after you performe the call, you may need to call $scope.$digest(); or $scope.$apply() to trigger the angular digest cycle.
scope.$digest() will only fire watchers on current scope, scope.$apply (recommended) will evaluate passed function and run $rootScope.$digest().
buttons: {
"Search":
function() {
j$( this ).dialog( "close" );
buildPages();
$scope.$apply(); //After function is executed a cycle will occur.
},
Cancel: function() {
j$( this ).dialog( "close" );
return false;
}
}
I believe in your situation that the view may not being updated because angular isn't being told to perform a digest cycle.
I'm currently altering a website which was devided into iframes to now being embedded (with AngularJS), without any iframes.
There is a big problem with this: I had a Kendo UI auto-complete drop-down element for selecting locations. The behavior with iframe and embedded is totally different concerning scrolling in the area around/beneath the auto-complete drop-down.
Old app: the site (iframe) around scrolled and the drop-down still was visible and moved with the rest of the site until you selected an item.
New app: the drop-down box closes immediately and you have to retype some input to get it open again. Unacceptable usability!
How do I get an auto-complete drop-down (doesn't have to be Kendo if not possible) which does have the OLD scrolling behavior in embedded mode?
Well, I found a workaround which works fine for me:
In the directive html, I added a callback for the event k-close. In this callback in the controller I prevented the default behavior of close event (of course under specific conditions) with the following code in the controller:
$scope.closeCallback= function (e) {
if (someConditionForWhichDropdownShouldntBeClosed) {
e.preventDefault();
}
};
and here's the HTML of the directive:
<input
ng-model="model"
kendo-auto-complete="source"
k-data-source="locationDataSource"
k-select="selectLocation"
k-close="closeCallback">
In my case, I prevented the Dropdown being closed as long as no item was selected.
For this I added a new boolean scope variable which was false by default, was set true if dropdown opened:
$scope.locationDataSource = new kendo.data.DataSource({
type: "json",
serverFiltering: true,
transport: {
read: function (options) {
$scope.keepKendoDropdownOpen = true;
someOtherFuncionalityAfterSelectingAnItem();
}
}
});
and set false again after selecting (in the callback of the directive's k-select).
Would be nice to also watch if the user presses ESC or something, but until now it's okay enough.
Please feel free to make my solution better or post other solutions! :-)
This question already has answers here:
Changing route doesn't scroll to top in the new page
(18 answers)
Closed 8 years ago.
For example:
A user scrolls down on view A;
Then the user clicks on a link, which takes the user to view B;
The view is changes,
but the user's vertical location remains lthe same, and must scroll manually to the top of the screen.
Is it an angular bug?
I wrote a small workaround that uses jquery to scroll to the top; but I don't find the correct event to bind it to.
edit after seeing the comment:
How and WHEN do i pull myself to the top? i'm using jquery but the $viewContentLoaded event is too soon (the method runs, but the page doesn't scroll at that time)
The solution is to add autoscroll="true" to your ngView element:
<div class="ng-view" autoscroll="true"></div>
https://docs.angularjs.org/api/ngRoute/directive/ngView
Angular doesn't automatically scroll to the top when loading a new view, it just keeps the current scroll position.
Here is the workaround I use:
myApp.run(function($rootScope, $window) {
$rootScope.$on('$routeChangeSuccess', function () {
var interval = setInterval(function(){
if (document.readyState == 'complete') {
$window.scrollTo(0, 0);
clearInterval(interval);
}
}, 200);
});
});
Put it in your bootstrap (usually called app.js).
It's a pure javascript solution (it's always better not to use jQuery if it's easy).
Explanation: The script checks every 200ms if the new DOM is fully loaded and then scrolls to the top and stops checking. I tried without this 200ms loop and it sometimes failed to scroll because the page was just not completely displayed.
It seems that you understand why the problem is happening based on #jarrodek's comment.
As for a solution, you could either follow #TongShen's solution of wrapping your function in a $timeout or you can put the function call within the partial that you're loading.
<!-- New partial-->
<div ng-init="scrollToTop()">
</div>
If you view change is fired after a click event, you could also put the function call on that element. Just comes down to timing though. Just depends on how things are set up.
Requirement
I want a textarea that expands or contracts vertically as the user types, alla Facebook comment box.
When the textarea loses focus it contracts to one line (with ellipsis if content overflows) and re-expands to the size of the entered text upon re-focus (this functionality not found on Facebook)
Note: Clicking on the textarea should preserve caret position exactly where user clicked, which precludes any dynamic swapping of div for textarea as the control receives focus
Attempted Solution
I'm already well into an AngularJS implementation, so...
Use Monospaced's Angular Elastic plugin. Nice.
Two attempts...
Attempt A: <textarea ng-focus="isFocussed=true" ng-blur="isFocussed=false" ng-class="'msd-elastic': isFocussed"></textarea> Fails because ng-class triggers no re-$compile of the element after adding the class, so Angular Elastic is never invoked
Attempt B: create a custom directive that does the needed re-$compile upon class add. I used this solution by hassassin. Fails with the following problems
Attempt B problems
Here's a JSFiddle of Attempt B Note that Angular v1.2.15 is used
I. Disappearing text
go to the fiddle
type into one textarea
blur focus on that textarea (eg click in the other textarea)
focus back on the text-containing textarea
result: text disappears! (not expected or desired)
II. Increasingly excessive looping and eventual browser meltdown
click into one textarea
click into the other one
repeat the above for as long as you can until the browser stops responding and you get CPU 100% or unresponsive script warnings.
you'll notice that it starts out OK, but gets worse the more you click
I confirmed this using: XP/Firefox v27, XP/Chrome v33, Win7/Chrome v33
My investigations so far
It seems that traverseScopesLoop in AngularJS starting at line 12012 of v1.2.15, gets out of control, looping hundreds of times. I put a console.log just under the do { // "traverse the scopes" loop line and clocked thousands of loops just clicking.
Curiously, I don't get the same problems in Angular v1.0.4. See this JSFiddle which is identical, except for Angular version
I logged this as an AngularJS bug and it was closed immediately, because I'd not shown it to be a bug in Angular per se.
Questions
Is there another way to solve this to avoid the pattern in Attempt B?
Is there a better way to implement Attempt B? There's no activity on that stackoverflow issue after hassassin offered the solution I used
Are the problems with Attempt B in my code, angular-elastic, hassassin's code, or my implementation of it all? Appreciate tips on how to debug so I can "fish for myself" in future. I've been using Chrome debug and Batarang for a half day already without much success.
Does it seem sensible to make a feature request to the AngularJS team for the pattern in Attempt A? Since you can add directives by class in Angular, and ngClass can add classes dynamically, it seems only natural to solve the problem this way.
You are severely overthinking this, and I can't think of any reason you would ever need to dynamically add / remove a directive, you could just as easily, inside the directive, check if it should do anything. All you need to do is
Use the elastic plugin you are using
Use your own directive to reset height / add ellipsis when it doesn't have focus.
So something like this will work (not pretty, but just threw it together):
http://jsfiddle.net/ss6Y5/8/
angular.module("App", ['monospaced.elastic']).directive('dynamicClass', function($compile) {
return {
scope: { ngModel: '=' },
require: "?ngModel",
link: function(scope, elt, attrs, ngModel) {
var tmpModel = false;
var origHeight = elt.css('height');
var height = elt.css('height');
var heightChangeIndex = 0;
scope.$watch('ngModel', function() {
if (elt.css('height') > origHeight && !heightChangeIndex) {
heightChangeIndex = scope.ngModel.length;
console.log(heightChangeIndex);
}
else if (elt.css('height') <= origHeight && elt.is(':focus')) {
heightChangeIndex = 0;
}
});
elt.on('blur focus', function() {
var tmp = elt.css('height');
elt.css('height', height);
height = tmp;
});
elt.on('blur', function() {
if (height > origHeight) {
tmpModel = angular.copy(scope.ngModel);
ngModel.$setViewValue(scope.ngModel.substr(0, heightChangeIndex-4) + '...');
ngModel.$render();
}
});
elt.on('focus', function() {
if (tmpModel.length) {
scope.ngModel = tmpModel;
ngModel.$setViewValue(scope.ngModel);
ngModel.$render();
tmpModel = '';
}
});
}
};
})