AngularJS mouse leave trigger misses - angularjs

On my web page, I have an array of
<div ng-repeat='img in imgList'>
<div class='img_container' ng-mouseover="show(img)" ng-mouseleave="hide(img)">
<div>one thumbnail</div>
<div class='overlay_edit' ng-show='img.isShowEdit'></div>
</div>
</div>
// from controller
$scope.hide = function hide(img){
img.isShowEdit = false;
}
$scope.show = function show(img){
if(img.metas != undefined && img.metas.length > 0){
// a few lines of codes to use img.metas to
// format the edit div block, omitted for simplicity
// but it does involve calling a REST service call
// to retrieve all meta properties.
img.isShowEdit = true;
}
}
The overlay edit div shows when mouse enters the container div and hides when mouse leaves the container div.
The problem I have is that I see lots of ghost overlays when mouse moves too quickly across many containers.
What would be the best way to tackle this problem?
Edited to add additional info and correct errors.

The problem is that the show() takes some time to complete. If ng-mouseleave trigger takes place before show() completes, ng-mouseleave's trigger has no net effect. When I moved the "img.isShowEdit = true;" to the beginning of show() body it solved the ghost issues. Thanks to Narek for pointing out the show() function as the possible source of issue.

Related

ngDraggable avoid ng-click after drag end

i have ng-click on the ng-drag element, when i am starting to move the element it's everything ok, but when i stop it, that element activate my ng-click, how can i avoid that ng-click?
<div ng-click="unselectImage(image);" ng-drag="true" ng-drag-data="'multipleItems'" ng-center-anchor="true" data-allow-transform="false" ng-drag-success="selectImage(image);"></div>
I am using ngDraggable. I tried to use ng-drag-success without success.
I made a directive (alClick) which is merely ng-click but cancels any click if the press duration is longer than 750ms by default. Therefore, no click or tap event will fire when dragging or swiping.
<div al-click="unselectImage(image);" ng-drag="true" ng-drag-data="'multipleItems'" ng-center-anchor="true" data-allow-transform="false" ng-drag-success="selectImage(image);"></div>
What i did, with help of David Zemens. i figure it out that i can use
$rootScope.$on('draggable:start', function (data) {
isDragging=true;
});
and inside my ng-click function i add something like this:
$scope.unselectImage = function (image) {
if(isDragging) isDragging=false;
else{
// do the magic
}
}
and generally it do the trick. When drag start, it broadcast that it is starting, and when it end's ng-click work out, in our case it simply say that drag is over.

Update scope half way through a scope method

EDIT: I've put together a JSFiddle: http://jsfiddle.net/U3pVM/18137/
I want the black to go up AND THEN the green to slide across... I do not know how to achieve this... Read on for more information:
Side note: The code is in Typescript (re: () => {} )
The following context shows a web page that has an overlaying div, and a couple of divs behind it. It also denotes the kind of animation that is occuring via a css class.
I have the following code (see references to diagram):
$scope.Resizing = true; // This prevents animations from happening (as a css class is linked up to it (see below))
$scope.MoveToPage( col, row ); // 2
$scope.$applyAsync(() => { // Update the view to remove the class and then start sliding up the overlay div
$scope.FirstPageTop = -$scope.PageMap.WindowInfo.Height; // 3
$scope.Resizing = false; // Re-enable animations // Finish
});
What it is supposed to do is:
When Resizing is true, it removes a css class that animates stuff.
It then runs the MoveToPage method that makes a position left equal a new number.. usually it would animate to the new position but in this instance i don't want it to (thus the Resizing attribute).
We can see the above expressed in my html:
<div class="column-wrap"
ng-class="{ 'column-wrap-animation' : !Resizing }"
ng-style="{ 'width': PageMap.ContainerWidth + 'px',
'left': PageMap.ColumnWrap.Left + 'px'}">
The procedure then applies the scope asynchronously (because if i use a simple apply, it throws an error) and as a callback to this method, I am then doing pulling an overlay off of the top of the page... and then setting Resizing back to false to re-apply animations.
The problem:
I need to slide off an overlay AFTER positioning the content underneath it. What is actually happening is, it slides the overlay off of the top, while the under page animates to where i've told it to go. How do i get around this css animation digestion problem...
Perhaps an even simpler question is, why might the class not be being removed when Resizing is set to false and actively i am telling it to update the scope?
Explaining my context:
You can apparently use setTimeout to deal with the $scope.$apply issue:
http://jsfiddle.net/U3pVM/18138/
$scope.Resizing = true;
$scope.MoveToPage( col, row );
setTimeout( function() {
$scope.FirstPageTop = -$scope.PageMap.WindowInfo.Height;
$scope.Resizing = false;
$scope.$apply() // This is important
}, 1);

$anchorScroll not functioning properly

On the click of a button i want the page to scroll a particular div bottom.
The 1st time i click, the page is going to the very top and the initial url http://localhost:8000 becomes http://localhost:8000/#/bottom(why is it not going to the div i mentioned).
The 2nd time i click the button, the url becomes
http://localhost:8000/#/bottom#bottom and it goes to the div bottom(which is what i want).
How do i deal with this ?
code snippet
$location.hash('bottom');
$anchorScroll();
Thanks in advance
Do ask for more explanation if required . . .
I don't know if you still need help, but have you checked this question?
Anyway, I also use one button to do a anchor scroll:
<button ng-click="app.gotoAnchor('destination')">GO!</button>
(...)
<div id="destination" style="height:1px;"></div>
And I just used a combination of AngularJS documentation examples and the SO question I mentioned before, to make this function:
function gotoAnchor(anchor){
if ($location.hash() !== newHash) {
// set the $location.hash to `newHash` and
// $anchorScroll will automatically scroll to it
$timeout(function() {
$location.hash(anchor);
});
} else {
// call $anchorScroll() explicitly,
// since $location.hash hasn't changed
$anchorScroll();
}
}
Being the $timeout call the one that made this work better.
Hope this helps.
BOTTOM
<div style="height:700px"> this is boby </div>
JOIN NOW!

angularJS doesn't scroll to top after changing a view [duplicate]

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.

Calendar Expand All by default

Anyone know how to have the calendar view expanded all by default?? Seen a lot of information on how to do this with SP 2007 but not SP 2010.
Kuldeep, thanks for this script! I have modified it slightly to expand all rows of the calendar by removing the break and checking to see if the anchor text is "collapse". If it's not "collapse", a click is executed. Note that you only need to click on one item per row. Otherwise, you might toggle back to collapsed.
Another problematic side effect of adding the CEWP to the Calendar page is that you lose the Change View dropdown. You have to use the calendar ribbon to change views.
I would also like to find a more robust solution, but here is my slighly improved solution:
<script type="text/javascript">
_spBodyOnLoadFunctionNames.push("myFuncAfterLoad");
function myFuncAfterLoad() {
var oldCalendarNotify4a = SP.UI.ApplicationPages.CalendarNotify.$4a;
SP.UI.ApplicationPages.CalendarNotify.$4a = function () {
oldCalendarNotify4a();
myFuncToExpandCalendar();
}
}
function myFuncToExpandCalendar() {
try {
var aTags = document.getElementsByTagName('A');
for (i = 0; i < aTags.length; i++) {
if ((aTags[i].evtid == "expand_collapse") && (aTags[i].innerText != "collapse")) {
(aTags[i]).click();
}
}
}
catch (err) {
alert(err.message);
}
}
</script>
Not the best approach but it works, add the following script on your page in CEWP or inline:
<script type="text/javascript">
_spBodyOnLoadFunctionNames.push("myFuncafterLoad");
function myFuncafterLoad(){var OldCalendarNotify4a = SP.UI.ApplicationPages.CalendarNotify.$4a;SP.UI.ApplicationPages.CalendarNotify.$4a = function (){myFunctoExpandCalendar();OldCalendarNotify4a();}}
function myFunctoExpandCalendar(){try{var aTags=document.getElementsByTagName('A');for(i=0;i<aTags.length;i++){try{if(aTags[i].evtid=="expand_collapse"){aTags[i].click();break;}}catch(err){ alert('Bad Call at' + aTags[i].href);}}}catch(err){alert(err.message);}}</script>
What it does is after the calendar is loaded it searches for the first expand link and simulates a click.
I had to use the traditional Javascript as I cannot trigger click using JQuery, because the click method is not added using JQuery.
Let me know if somefine finds a better solution.
Two options for this one. Stick one or the other of the following in a CEWP.
The first one will override the function that is used to determine how many items to display before showing the expand/collapse link. I have set this to 100 in the example below to ensure that in my use case, I never even see the expand/collapse buttons and I always get every item rendered all the time.
<script type="text/javascript">
_spBodyOnLoadFunctionNames.push('WaitForCalendarToLoad');
function WaitForCalendarToLoad()
{
SP.UI.ApplicationPages.SummaryItemRenderer.prototype.$2u = function ()
{
ULSvSp: ;
if (!this.$1A_1) this.$1A_1 = 100;
return this.$1A_1
}
}
</script>
The second option overrides the calendar notify event, so that after all the items are rendered, the script will find the first calendar instance, and call it's expand all function. This will ensure every cell is expanded by default but will still show the collapse links.
<script type="text/javascript">
_spBodyOnLoadFunctionNames.push('WaitForCalendarToLoad');
function WaitForCalendarToLoad()
{
var old$4a = SP.UI.ApplicationPages.CalendarNotify.$4a;
SP.UI.ApplicationPages.CalendarNotify.$4a = function ()
{
old$4a();
ctrl = SP.UI.ApplicationPages.CalendarInstanceRepository.firstInstance();
if (ctrl) {
ctrl.expandAll();
}
}
}
</script>
I'm using this (in 2013):
<style>
.ms-cal-nav { display:none; }
</style>
<script type="text/javascript">
LoadSodByKey("SP.UI.ApplicationPages.Calendar.js", function () {
window.setTimeout(ExpandCalendar, 500);
});
function ExpandCalendar() {
// expand always
$('.ms-cal-nav:has(img[src$="/expandbttn.gif"])').each(function() { this.click(); });
window.setTimeout(ExpandCalendar, 1000);
}
</script>
It runs every second, which isn't great, but seems to do the job
So based on the original solutions provided by Andrew Peacock (presumably for SP2010), I was able to use those as a stating point to put together a working solution for SP2016.
The big difference seems to be that CalendarNotify is deprecated in SP2016, which causes many of the SP2010-based solutions not to work in newer versions. Then there are additional complications if using overlaid calendars; it seems that the HTML elements for events on overlaid calendars are not rendered until very late in the page load process - even waiting on the last sourced script on the page (sp.ui.socialribbon.js) using SP.SOD does not wait long enough for those to be available. I believe this is why most, if not all of the solutions for SP2016 calendars that rely on these being available must end up using either setTimeout() or setInterval(). In our case, for the second solution, we need to wait until all overlaid events have been rendered on the calendar before calling the calendar's expandAll() function or it won't work.
With that said, these are the two SP2016-compatible solutions I came up with, based off the original SP2010 solutions noted above. The first removes the expand/collapse link altogether and ensures that the calendar will always be expanded to show all events.
The first method overrides the built-in function that sets the "collapse item limit" (the number of items that will force the "expand/collapse" link to appear).
<script type="text/javascript">
function setCalendarCollapseItemLimit() {
// Configure the calendar's "collapse item limit" so that it always shows
// all items by default and never collapses them.
SP.UI.ApplicationPages.SummaryItemRenderer.prototype.$3V_1 = function() {
ULSvSp:;
if(!this.$1B_1) {
// This sets the "collapse item limit" to 100 events
this.$1B_1 = 100;
}
return this.$1B_1;
}
}
// Call our function once the necessary script is loaded (sp.ui.applicationpages.calendar.js in this case).
ExecuteOrDelayUntilScriptLoaded(setCalendarCollapseItemLimit, "sp.ui.applicationpages.calendar.js");
</script>
The advantage of this method is that it stays expanded even if new events are added to the calendar after the page loads, with no need to manually re-expand or refresh the page. The downside is that this solution may need updating after an upgrade since the obfuscated variable names may change (this example was confirmed working w/ SP2016 w/ the 7/2017 CU - v16.0.4561.1000).
The second method calls the calendar's built-in expandAll() function once all the events are displayed. The advantage of this method is that it leaves the "collapse" link available for the user to collapse the calendar if desired.
<script type="text/javascript">
function expandAllCalendarItems() {
var cal = SP.UI.ApplicationPages.CalendarInstanceRepository.firstInstance();
if (cal) {
cal.expandAll();
return true;
} else {
return false;
}
}
// We need to use setInterval() here so that the calendar will be expanded when it becomes available and
// after any overlaid calendar events have been rendered. An interval of 1400 seems to be sufficient for this.
// We store the return value so that we can clear it (stop the loop) once we’ve successfully expanded it.
var expItemsInterval = setInterval(function() {
var retVal = expandAllCalendarItems();
if (retVal === true) {
// Calendar was expanded - clear interval
clearInterval(expItemsInterval);
}
}, 1400);
</script>
I'm not a huge fan of this solution though since it has a couple big downsides. One is that the calendar will re-collapse if a new event is added to it after the page is loaded. More importantly, however, is the fact that we are relying on the interval being long enough to wait for all overlaid calendar events to be rendered on the page (if using calendar overlays). Therefore we have a sort of race condition here; an interval of 1400 seems to be long enough in my testing for it to work properly, but theoretically if the overlaid events take too long to render, this solution might not work. If not using overlaid calendars then this should not be an issue.
A more proper solution would probably be to use something like a MutationObserver to watch for new elements being added to the page (specifically, divs with a class of .ms-acal-mdiv), and call expandAll() again if a new one is detected. This would also solve the problem of re-expanding the calendar after a new event is added.
All in all, if showing the "collapse" link to the user is not required, I prefer the first solution. I think its the cleanest and most foolproof, since it does not rely on setInterval() and works with both regular and overlaid calendars.
Big thanks to Andrew Peacock for the original solutions here; those put me on the right track for figuring these out for SP2016.
*EDIT: Removed the jQuery dependency since it was unnecessary, and added script tags around the code. This would need to be implemented via SharePoint Designer, right under the PlaceHolderMain tag (using a SEWP or CEWP would probably work as well but I didn't test using those methods).
function expandCalendar()
{
var calendar = SP.UI.ApplicationPages.CalendarInstanceRepository.firstInstance();
if (calendar && typeof calendar.expandAll === 'function') {
calendar.expandAll();
}

Resources