ngGrid afterSelectionChange not firing - angularjs

I have my ngGrid in a partial page, and I'm trying to access the selected items on the main page, I assume the best way to get this done is with $emit(to parent scope) and $on (to catch the event). For some reason my afterSelectionChange doesn't appear to be firing and I'm wondering if I'm just doing something wrong. Or if there's a better way to track changes to a child grid selection even.
I have
{{mySelectedItems}}
On my partial, and it updates just fine. But also have
{{selectedDevices}}
On my main view, and it's never getting updated.
A breakpoint on the function within the "afterSelectionChange" also is not getting hit. here's the gridoptions. zz is just my previously defined data, not really relevant, also chopped out my columnDefs to minimize space.
$scope.mySelectedItems = [];
$scope.gridItems = #Html.Raw(Json.Encode(zz))
$scope.gridOptions = {
selectedItems:$scope.mySelectedItems,
afterSelectionChange: function (data) {
$scope.$emit("selectionChanged", $scope.mySelectedItems)},
multiSelect:true,
data: 'gridItems',
enableColumnResize: true
};
And finally on my main View, I have
$scope.$on("selectionChanged",function(event,data){
$scope.selectedDevices=data;
})
to catch the changes.

Ok so apparently you're not able to set a breakpoint where I was trying, I added a debugger line and it successfully broke in the afterselectionchange. It was the emit that was not actually working correctly. but once I was able to successfully get into debug, i was able to trace the problem down to a scope issue. I was catching the event in the wrong parent scope.
It was a bit confusing for me at first because we are using razor #sections so the partial view was not being included in the same parent scope, it was parenting to the layout page.
I moved my ng-controller to a div surrounding the partial in that section and that solved it.
The lesson here... watch your scopes.

Related

How do I return focus to an element when the entire page changes?

I have a complicated setup. My application is driven by a set of "rules" which dictate what the user interface is. The UI is rendered by looping through the rules and creating the individual dropdowns. Initially, everything renders properly. However, once a user makes a change to the UI, other rules may be affected. In my application, an api call is made, which then returns a modified set of rules. In the attached plunker, I've simplified things such that only the new set of rules is applied, which causes the page to re-render. The problem is that my users would like to be able to tab between all of the entries on the page and make changes. However, once the page is re-rendered, the currently selected page element is now gone nothing has the focus. I've tried to put the focus back on the proper element by tracking a common Id, but to no avail.
Using either of these doesn't seem to work.
var el = document.getElementById(focusId);
el.focus();
angular.element(el).focus();
I've also tried using the autofocus attribute on the dropdown that I want to have focus, but that didn't work either. I'm using angularjs 1.2. Any ideas are appreciated.
http://plnkr.co/edit/ND9PKqULIOlixWR4XChN?p=preview
If you want to assign auto focus dynamically to a element on the DOM from angular you can use this:
var myEl = angular.element(document.querySelector('select'));
myEl.attr('autofocus',"attr val");
You can also pass in an id like: angular.element(document.querySelector('#focusId'));
You can look here for a prior answer which may be of some more help!
-Cheers!
Problem here is, you are trying to focus the element before the blur event completes. So you need to execute the focus code after blur event. Async execution of your focus code would solve the problem. You can use either setTimeout or $timeout.
setTimeout(function(){
angular.element('#'+focusId).focus();
/* //or
var el = document.getElementById(focusId);
el.focus();
*/
});
or
$timeout(function(){
angular.element('#'+focusId).focus();
/* //or
var el = document.getElementById(focusId);
el.focus();
*/
});
dont forgot to inject $timeout to your controller if you are using second code. Hope this helps :)

dynamic header/menu in angularjs

While transitioning an existing angular site, I encountered an annoying problem. The initial symptom was that a certain controller was not running it's initialize function immediately following the login. I logged and I tracked, and eventually I realized it was a design flaw of the page. Essentially, index.html contains a <header>, <ng-view>, and <footer>. There are a couple of ng-if attributes that live in the header that I want to evaluate after the login, but since the view is the only thing that is reloaded, it was not reinitializing the header controller, and thus not updating the ng-if values.
Then I was reminded of ngInclude, which seems like the perfect solution, until I got it hooked up and realize that doesn't work either. It loads the template the first time, and doesn't reinitialize when the view changes. So then I got the bright idea of passing the HeaderController to another controller or service, and controlling this one stubborn boolean value through a proxy of sorts. That also didn't work. Then I tried putting a function and a boolean into another service, and mirroring that property in the header controller, but thus far I have not gotten this working.
I have done plenty of research about multiple views in the index, and so far I hear a lot about this ui-router, but I'm still not convinced that is the way I want to go. It does not seem to be a simple solution. I have not tried putting the ng-include into the templates yet either, because then I feel like that is going back in time to when we had to update 100 pages every time we changed the menu.
I lost a whole day to this. If anyone could tell me how to trigger the evaluation of this one property in my header controller which I would like to live outside the other templates, please let me know!
Ok so you need to know in your HeaderController when the view has reloaded. There's a number of ways of doing this but the easier and maybe the more correct in this particular case is with an event.
So when you are refreshing the view you just do this, let's say you need the new value of ob1 and ob2 variables.
// ViewController
$rootScope.$emit('viewRefresh', {ob1: 'newvalue1', ob2: 'newvalue2'});
And in your HeaderController you need to listen for that event, and set on your $scope the new values for those attrs (if you're not using controller as syntax).
// HeaderController
$rootScope.$on('viewRefresh', function onRefresh(event, data) {
$scope.ob1 = data.ob1;
$scope.ob2 = data.ob2;
})
Another Solution
Sharing a Promise through a Service (using $q)
function HeaderService($q) {
var defer = $q.defer();
return {
getPromise: function() {return defer.promise},
notify: function(data) {defer.notify(data)}
}
}
function HeaderController(HeaderService) {
var vm = this;
HeaderService.getPromise().then(function(data) {
vm.ob1 = data.ob1;
vm.ob2 = data.ob2;
})
}
function ViewController(HeaderService) {
var data = {ob1: 'newvalue1', ob2: 'newvalue2'};
HeaderService.notify(data)
}

Controller returning un rendered view

I am facing an issue with my controllers refs and the returned object being invalid. And by invalid it returning me an object that says it has not been rendered even though i am in the process of using it.
In my controller I have a ref defined as the following.
refs : [ {
ref : 'selectDeltaView',
selector : '[itemId=selectDeltaView]'
}]
and later in my controller I access this view using the created accessor
this.getSelectDeltaView()
What is strange is that the first time this view is presented, inside of an Ext.Window it works just fine. However, on the second launch the view I get back is not rendered.
this.getSeletDeltaView().rendered === false
Reading the documentation over at Sencha it seems that this simply passes my selector to the Ext.ComponentQuery.query function. However, if I call Ext.ComponentQuery.query('[itemId=selectDeltaView]') I get an array with a single element of which rendered is true.
Am i missing something? Why is my controllers reference returning me invalid data.
Update
Some additional details not mentioned in the original post. My initial assumption was that my view was not being destroyed. However, I have log statements in my ondestory and beforedestory events and can confirm that on closing the window my view is being destroyed.
What is the most confusing is that if refs simply use Ext.ComponentQuery.query, why does Ext.ComponentQuery.query only return one view when the window is re-opened and it returns the correct view. I understand from the question below that refs will catch the view, but that view no longer exist.. or shouldn't.. If the view is destroyed, can I force the controller to clear its catch?
The controllers reference is cached after the first evaluation.
What happens is this:
You open your view a first time.
getSeletDeltaView() finds that view, and stores a reference to it in the reference lookup cache.
You close the view, but you do not destroy it completely.
You open the view a second time, but it is a different instance.
getSeletDeltaView() finds the first view, that is no longer rendered, but still exists, because it was not destroyed. You cannot use this reference, because it point to the wrong view.
Solution:
Make sure to destroy() your view, when you hide it. You can also use autoDestroy as a property. Usually, autoDestroy is true by default.

How does $timeout cause the afterSelectionChange function to be called?

I'm trying here to get enough info to go fix this problem, just wanting some help understanding what is going on inside angular.
ng-grid has issues, lots of them, but I've found a "fix" to this one that I don't understand.
I have a grid with enough rows that it fills the visible area. If I click on the different rows, the afterSelectionChange method is called. If after clicking in the grid I move the focus with the arrow keys, it only calls that callback if the grid scrolls.
So I put in a $timeout to print out the selected row every half second to see if it was changing the selected row and just not calling the callback, and THAT fixed the problem. Now every time I move the cursor with the keyboard, the callback fires, even though the only thing happening in the callback is $log.debug().
Is this because $timeout is causing something to happen within the framework like a $apply or a $digest?
If that's the case, why isn't the keyboard causing that to happen?
Edit: Options for #tasseKATT
$scope.callGridOptions = {
data: 'callRecords',
multiSelect: false,
sortInfo: {fields:['startOn'], directions:['asc']},
columnDefs: [ ...
],
afterSelectionChange: $scope.onCallChange,
selectedItems: $scope.selectedCalls
};
In the end, I could reduce the timeout code to this:
function ngGridFixer() {
// Presence of this timer causes the ngGrid to correctly react to up/down arrow and call the
// afterSelectionChange callback like it is supposed to.
$timeout(ngGridFixer, 500);
}
ngGridFixer();
I put this in the rootscope because the problem happens on all the pages of the app.
$log is part of the Angular framework, anything processed by it is might execute watches laid down earlier. In other words by calling $log.debug() to print out the structure, you might be basically running scope.$digest every half second, which cause the callback(s) to fire. If you take out everything inside the $timeout function, or use console.log instead, the callback(s) probably won't fire
A way to do this semi-properly would be to use something like ngKeydown.
EDIT:
$timeout execute the function in scope.$apply by default. https://docs.angularjs.org/api/ng/service/$timeout (invokeApply). I was not aware of this. So essentially your code is calling scope.$apply every half second.

Webshims - Show invalid form fields on initial load

(Follow on questions from Placeholder Hidden)
I'd like my form to validate existing data when it is loaded. I can't seem to get that to happen
I jQuery.each of my controls and call focus() and blur(), is there a better way than this? I tried to call ctrl.checkValidity(), but it wasn't always defined yet. When it was, it still didn't mark the controls.
I seem to have a timing issue too, while the focus and blur() fire, the UI does not update. It's as if the Webshims are not fully loaded yet, even though this fires in the $.webshims.ready event.
I also tried to call $('#form').submit(), but this doesn't fire the events as I expected. The only way I could make that happen was to include an input type='submit'. How can I pragmatically case a form validation like clicking a submit button would?
Here's a jsFiddle that demonstrates the problem. When the form loads, I want the invalid email to be marked as such. If you click the add button it will be marked then, but not when initially loaded. Why?
Focus and blur in the control will cause it to be marked.
BUT, clicking ADD will too (which runs the same method that ran when it was loaded). Why does it work the 2nd time, but not when initially loaded?
updateValidation : function () {
this.$el.find('[placeholder]').each(function (index, ctrl) {
var $ctrl = $(ctrl);
if( $ctrl.val() !== "" && (ctrl.checkValidity && !ctrl.checkValidity()) ) {
// alert('Do validity check!');
$ctrl.focus();
$ctrl.blur();
}
});
}
I see this in FF 17.0.5. The problem is worse in IE9, sometimes taking 2 or 3 clicks of ADD before the fields show in error. However, I get errors on some of the js files I've liked 'due to mime type mismatch'.
This has to do with the fact, that you are trying to reuse the .user-error class, which is a "shim" for the CSS4 :user-error and shouldn't be triggered from script. The user-error scripts are loaded after onload or as soon as a user seems to interact with an invalid from.
From my point of view, you shouldn't use user-error and instead create your own class. You can simply check for validity using the ':invalid' selector:
$(this)[ $(this).is(':invalid') ? 'addClass' : 'removeClass']('invalid-value');
Simply write a function with similar code and bind them to events like change, input and so on and call it on start.
In case you still want to use user-error, you could do the following, but I would not recommend:
$.webshims.polyfill('forms');
//force webshims to load form-validation module as soon as possible
$.webshims.loader.loadList(['form-validation']);
//wait until form-validation is loaded
$.webshims.ready('DOM form-validation', function(){
$('input:invalid')
.filter(function(){
return !!$(this).val();
})
.trigger('refreshvalidityui')
;
});

Resources