I'm using angular and ui-router with the extras so that I can use sticky state for maintaining listview scroll position on returning from the detail page in my mobile spa app.
My list is a kendo mobile listview. I'm implementing crud and would like to reflect the changes made in the listview when returning. I do not use the $state.go in my back buttons, but use $window.history.back because I want the back buttons to work "as expected", so I cannot use the reload parameter of that function.
I wired up the onReactivate and inject the service that has the flag to tell it whether crud operations have been done and to reload the list. This all works and I can successfully determine if I need to reload the screen.
So, I try to use the kendo.mobile.ui.ListView refresh method. I use angular.element("#idoflistview").data("kendoMobileListView").refresh(). It does find it and does refresh it, but all of the items are set to have a style="transform: translate3d(0px, 0px, 0px);", so they are all overlapping each other.
I originally did try to just do a $state.reload() and rebuild the page, and it did work in that it calls my init function in the controller and I could use the flag to know to request the data right way, but the listview would not build. The request for data was sent and retrieved, but the listview was empty.
I would prefer to just refresh the list if possible because the service has the kendo datasource with the updated data already, so there really is no need to make a call to the database again.
So, my setup for the listview works fine normally, but only breaks if I refresh in the onReactivate in that they are all there, but overlap each other because every li in the list starts at 0 (the update is in there though, so it did refresh).
I'm assuming that it is due to the fact that it needs to do it later. If so, how can I do that?
Any ideas on how to get the refresh method to work properly? Or is there a better way to do this?
Here is the abstract view:
<div ui-view="activityinquirylist" ng-show="$state.includes('activityinquiry.list')"></div>
<div ui-view="activityinquirydetails" ng-show="$state.includes('activityinquiry.details')"></div>
<div ui-view="activityinquirycrud" ng-show="$state.includes('activityinquiry.crud')"></div>
Here is how I'm declaring my listview:
<div id="activityListScroller" kendo-mobile-scroller="activityListScroller" k-pull-to-refresh="true" k-pull="vm.pullToRefresh" k-pull-offset="200" class="scroller-header-footer">
<ul id="activityListView" kendo-mobile-list-view="activityListView" k-data-source="vm.activityInquiryService.activityInqiuryDataSource" k-template="vm.activityTemplate" k-on-click="vm.listClicked(kendoEvent)" k-auto-bind="false" k-load-more="true"></ul>
</div>
Here is the code for the state:
.state('activityinquiry', {
//sticky: true,
abstract: true,
url: '/activityinquiry',
templateUrl: 'app/views/cm/activityinquiry.html'
})
.state('activityinquiry.list', {
url: '/activityinquirylist',
views: {
'activityinquirylist#activityinquiry': angularAMD.route({
templateUrl: 'app/views/cm/activityinquirylist.html',
controller: 'activityinquirylistController',
controllerUrl: 'controllers/cm/activityinquirylistController',
controllerAs: "vm"
})
},
sticky: true,
deepStateRedirect: false,
onReactivate: function (activityInquiryService) {
console.log("reactivating");
if (activityInquiryService.model.reloadList && activityInquiryService.model.reloadList == true) {
activityInquiryService.model.reloadList = false;
var item = angular.element("#activityListView").data("kendoMobileListView");
if (item) {
item.refresh();
}
}
}
})
EDIT:
After further investigation, I have found that it is due to the kendo.mobile.scroller. It is not available at the time that I am refreshing the listview, so it is somehow messing up how the listview does it's drawing. This makes no sense to me because the listview is inside the scroller (and still is if I inspect after).
If I remove the scroller, it works as expected, but I need that scroller because I am not using kendo views and have a footer on the page, so it will scroll with the content and I am using the pull to refresh functionality of the scroller (don't want to use the listviews pull to refresh because it does not allow me to handle how the refresh happens, while the scroller does).
Another Edit:
I have tracked it down to the fact that it only happens when I have loadMore true on the listview. If I turn this off, then it works fine.
So, in the code example above, if I set k-load-more="false" it works. I tried it with endless-scroll too and both have the same effect.
Related
This is an angular 1 app.
I have a directive that looks like this:
function addLiquidity() {
return {
restrict: 'E',
scope: {
expanded: '='
},
templateUrl: 'liquidity.html',
controller: 'addLiquidityCtrl'
};
}
And the html that looks like this:
<add-excess-liquidity expanded="addExpanded"></add-excess-liquidity>
In another html I have this:
expanded: {expanded}
This is false by default thanks to the controller that in its relevant part contains this:
$scope.addExpanded = false;
Now, when clicking a button to show form for adding new stuff, "addExpanded" is set to true.
When then the button to actually save the form is clicked "addExpanded" is set to false again:
$scope.addExpanded = false;
I can see in the controller that that function is called and that addExpanded behaves correctly in the controller.
Actually, it behaves correctly in the view too. So the value in html:
expanded: {expanded}
reflects the value in the controller.
However, as soon as I switch to another tab (this app is using nav-tabs) and come back to the tab I was before, then the value in html is not reflecting the controller anymore(which is still doing its job like before, going through the same steps as before, included setting the "expanded" to false).
I tried forcing view update with both timeout and "apply" but didn't help. Also, it seems somwehere else an apply is fired at the end of every function.
I really don't understand what's wrong here. Where should I look? Something happen (I guess) when leaving the tab the first time. But so many things happen so it is not easy to debug. Any clue anybody?
EDIT
I added a watch like this:
$scope.$watch( 'addExpanded',
function(newValue, oldValue){
console.log('addExpanded Changed');
console.log(newValue);
console.log(oldValue);
}
);
First time I go to the tab then it will print to console. I then leave the tab, then come back to it. And this time, when clicking on button, nothing is printed to the console.
Roughly, what happens when I change tab is that the div containing the tab-content is set to "display:none/block" (I guess) as it is pure css that is showing tab content upon selection.
In the main controller a few things are done depending on which tab was selected, but I cannot see things that would "destroy" the connection between the GUI and the controller/directive.
Also, each time I go to that tab, I see that the javascript connected to the directive is initiating. And the variable "expanded" is reset to false.
The issue is that, for some reason, the connection between javascript and the GUI is in some way "lost" when visiting the tab the second time (and for all the future until I refresh the page).
I have the following code in my html:
<li class="col-md-4 my-show-hide-animation" ng-hide="sr.hidden" ng-repeat="sr in gmCtrl.gmSRs" ...
The above html page has a button that makes a modal call.
I have another html/js page that can be accessed modally and non-modally.
I have this after coming back from the modal call:
if (!$rootScope.$$listenerCount['sr.CreateDone']) {
$rootScope.$on('sr.CreateDone', function(event, data) {
ctrl.gmSRs.push(data.sr);
$uibModalStack.dismissAll();
});
}
Now... this actually works over and over again. However, the moment I go into the target html/js in a non-modal way (which also works), the modal way no longer works. In the debugger, I can see the data.sr object being added to the array, and I can see the array being changed, but it's not reflected in the view. Any ideas??? I assume that either I have some sort of scoping issue or its some weird angularjs bug.
Here's the call:
ctrl.modalInstance = $uibModal.open({templateUrl: "standardresponse/standardresponse.html",
controller: "StandardresponseController",
controllerAs: "standardresponseCtrl",
size: "lg",
keyboard: true,
backdrop: 'static',
windowClass: "app-modal-window",
});
I also added a line of code that shows I only have the 1 listener running.
If the array is changing, but it isn't showing up in the view, this means that the digest cycle isn't properly running on the $scope. You could fix this manually by calling $scope.apply() but you might want to reconsider your design.
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! :-)
I'm building a dashboard system in AngularJS and I'm running into an issue with setting the url via $location.path
In our dashboard, we have a bunch of widgets. Each shows a larger maximized view when you click on it. We are trying to setup deep linking to allow users to link to a dashboard with a widget maximized.
Currently, we have 2 routes that look like /dashboard/:dashboardId and /dashboard/:dashboardId/:maximizedWidgetId
When a user maximizes a widget, we update the url using $location.path, but this is causing the view to re-render. Since we have all of the data, we don't want to reload the whole view, we just want to update the URL. Is there a way to set the url without causing the view to re-render?
HTML5Mode is set to true.
In fact, a view will be rendered everytime you change a url. Thats how $routeProvider works in Angular but you can pass maximizeWidgetId as a querystring which does not re-render a view.
App.config(function($routeProvider) {
$routeProvider.when('/dashboard/:dashboardId', {reloadOnSearch: false});
});
When you click a widget to maximize:
Maximum This Widget
or
$location.search('maximizeWidgetId', 1);
The URL in addressbar would change to http://app.com/dashboard/1?maximizeWidgetId=1
You can even watch when search changes in the URL (from one widget to another)
$scope.$on('$routeUpdate', function(scope, next, current) {
// Minimize the current widget and maximize the new one
});
You can set the reloadOnSearch property of $routeProvider to false.
Possible duplicate question : Can you change a path without reloading the controller in AngularJS?
Regards
For those who need change full path() without controllers reload
Here is plugin: https://github.com/anglibs/angular-location-update
Usage:
$location.update_path('/notes/1');
I realize this is an old question, but since it took me a good day and a half to find the answer, so here goes.
You do not need to convert your path into query strings if you use angular-ui-router.
Currently, due to what may be considered as a bug, setting reloadOnSearch: false on a state will result in being able to change the route without reloading the view. The GitHub user lmessinger was even kind enough to provide a demo of it. You can find the link from his comment linked above.
Basically all you need to do is:
Use ui-router instead of ngRoute
In your states, declare the ones you wish with reloadOnSearch: false
In my app, I have an category listing view, from which you can get to another category using a state like this:
$stateProvider.state('articles.list', {
url: '{categorySlug}',
templateUrl: 'partials/article-list.html',
controller: 'ArticleListCtrl',
reloadOnSearch: false
});
That's it. Hope this helps!
We're using Angular UI Router instead of built-in routes for a similar scenario. It doesn't seem to re-instantiate the controller and re-render the entire view.
How I've implemented it:
(my solution mostly for cases when you need to change whole route, not sub-parts)
I have page with menu (menuPage) and data should not be cleaned on navigation (there is a lot of inputs on each page and user will be very very unhappy if data will disappear accidentally).
turn off $routeProvider
in mainPage controller add two divs with custom directive attribute - each directive contains only 'templateUrl' and 'scope: true'
<div ng-show="tab=='tab_name'" data-tab_name-page></div>
mainPage controller contains lines to simulate routing:
if (!$scope.tab && $location.path()) {
$scope.tab = $location.path().substr(1);
}
$scope.setTab = function(tab) {
$scope.tab = tab;
$location.path('/'+tab);
};
That's all. Little bit ugly to have separate directive for each page, but usage of dynamic templateUrl (as function) in directive provokes re-rendering of page (and loosing data of inputs).
If I understood your question right, you want to,
Maximize the widget when the user is on /dashboard/:dashboardId and he maximizes the widget.
You want the user to have the ability to come back to /dashboard/:dashboardId/:maximizedWidgetId and still see the widget maximized.
You can configure only the first route in the routerConfig and use RouteParams to identify if the maximized widget is passed in the params in the controller of this configured route and maximize the one passed as the param. If the user is maximizing it the first time, share the url to this maximized view with the maximizedWidgetId on the UI.
As long as you use $location(which is just a wrapper over native location object) to update the path it will refresh the view.
I have an idea to use
window.history.replaceState('Object', 'Title', '/new-url');
If you do this and a digest cycle happens it will completely mangle things up. However if you set it back to the correct url that angular expects it's ok. So in theory you could store the correct url that angular expects and reset it just before you know a digest fires.
I've not tested this though.
Below code will let you change url without redirection such as: http://localhost/#/691?foo?bar?blabla
for(var i=0;i<=1000;i++) $routeProvider.when('/'+i, {templateUrl: "tabPages/"+i+".html",reloadOnSearch: false});
But when you change to http://localhost/#/692, you will be redirected.
I'm using AngularJS 1.1.5 and trying out the ng-animate directive with daneden's animate.css. I have a couple of views set up using routing. I'm using Twitter Bootstrap 3 RC1.
Here is the code for the ng-view:
<div class="container" ng-view ng-animate="{enter:'animated fadeInRightBig', leave:'animated fadeOutLeft'}"></div>
And here is the routing part:
$routeProvider
.when('/', {
templateUrl: '/Home/Home',
title: 'Home'
})
.when('/Home/Home', {
templateUrl: '/Home/Home',
title: 'Home'
})
.when('/Home/About', {
templateUrl: '/Home/About',
title: 'About'
})
.otherwise({
redirectTo: '/'
});
$locationProvider.html5Mode(false).hashPrefix('!');
The animation works (that is, I see the animation effects) and the view changes as well.
The thing I'm having trouble with is that the "leaving" view seems to be still taking up space while the "entering" view is animating in.
The effect is that the enter animation of the new view happens below the space previously taken up by the leaving view. It's as if the old view was still there, even though it has already "animated out". The new view then suddenly jerks up to the proper position once its animation finishes. I'm using fadeInRightBig for enter and fadeOutLeft for leave.
How can this be fixed? The expected result is a smooth transition with no jerking, like the animation for the ng-switch in slides 1 to 3 here. (Except that I'm using ng-view of course.)
Thanks!
Edit:
I take it back, the 'leave' animation hasn't completely finished while the 'enter' animation is running.
So I guess my question will change a bit.. But the expected result is the same. How do I achieve the smooth "sliding" effect?
I had the same issue when I first started. To achieve the smooth effect simply change the timings of the CSS. Go into the animate.css file, look for the one the one you need to delay (in your case it would be fadeInRightBig. Change delay (or add a delay to it of about 1 or 2 seconds).
Another alternative is to make the position of the exit animation absolute.
Let me know if this helps. It worked for me. I wish that angularjs already handled this for us. Maybe the next version will solve these issues for us.
Another common way to solve this problem is to deal with z-index. The ng-enter usually must be below the ng-leave.