2

I am developing an Angular application that is hosted in SharePoint 2013, and (I think) am running into issues with ngAria and $ariaProvider.

The problem is, when I click a link to open an <md-dialog> element as a modal, the UI thread is locked for ~30-40 seconds. Through debugging, I am able to trace it down to a single method, which is included in the Angular Material .js, named walkDOM(element).

The application works fine locally (without SharePoint) but hangs inside the SharePoint environment. I think this method is problematic because the DOM tree that it traverses in SharePoint is significantly larger than the one in development. After the modal open event fires, the walkDOM() method is fired, and adds an aria-hidden="true" value to most of the DOM elements. Because of the complex DOM structure SharePoint has, I think this is causing the hangup. I am able to debug and see the breakpoints firing continuously. I can also see that the attributes are added after the hangup and not present beforehand.

I'd like to disable this traversal, but so far have not found an acceptable way to do so. I have followed this thread and come to the realization that I might not be able to access the methods I am interested in: https://github.com/angular/material/issues/600. I understand that disabling Aria is bad practice, but I am simply trying to prevent the traversal of the DOM that causes the UI thread to be locked for such a long time. I have tried the following code to overwrite/configure the Aria components for the Angular app (the latter snippet suggested in this thread: How do I disable ngAria in ngMaterial?):

angular
.module('app', ['ui.router', 'ngMaterial', 'ngAria'])
...
.config(function ($ariaProvider) {
  $ariaProvider.config({
    ariaValue: true,
    ariaHidden: false,
    tabindex: false
  });
})
.decorator('$mdAria', function mdAriaDecorator($delegate) {
  $delegate.expect = angular.noop;
  $delegate.expectAsync = angular.noop;
  $delegate.expectWithText = angular.noop;
  return $delegate;
});

Is there any way I can do what I am trying to do? I'd like to avoid rewriting the application to not use modals if possible. I thought about writing a global walkDOM() method, but had little success.

Any help would be greatly appreciated. Thanks!

EDIT: This is a problem with the way that aria attributes are added to the DOM structure and the way the walkDOM() method works. This has nothing to do with routing/model state management as addressed in this question: How to prevent view redraw when changing route in AngularJS.

Splaktar
  • 5,506
  • 5
  • 43
  • 74
awh112
  • 1,466
  • 4
  • 22
  • 34
  • [walkDom](http://www.javascriptcookbook.com/article/Traversing-DOM-subtrees-with-a-recursive-walk-the-DOM-function/) is a DOM Traversing function from Doug (AFAIK). Don't mess-up that as it may be used from other places as well. Do you have a reference to any code in ngMaterial that implements walkDOM? – sabithpocker Sep 16 '16 at 18:23
  • @sabithpocker the method I am looking at looks like it is specifically for dialogs: https://github.com/angular/material/blob/fc7e9b3fc87713c6fde3b82c0df358650ec9aafc/src/components/dialog/dialog.js – awh112 Sep 16 '16 at 18:25
  • Thanks for the comment @PaulSweatte, but I think the link you posted is a different issue than what I am running into. The issue I ran into above is an issue with the Angular Material code and how it traverses large DOM structures. – awh112 Oct 14 '16 at 16:32

1 Answers1

0

Use a getter to intercept the parentNode attribute check inside of walkDOM. For example:

function getter() 
  {
  return document.documentElement; 
  }

 function isNodeOneOf(elem, nodeTypeArray) 
   {
   if (nodeTypeArray.indexOf(elem.nodeName) !== -1) 
     {
     return true;
     }
   }

 function walkDOM(element) 
  {
  var isHidden;
  var children = element.parentNode.children;

  console.log(Date() + element.innerText);
    
  while (element.parentNode) 
    {
    if (element === document.body) 
      {
      console.log(Date());
      return;
      }
  
  
    for (var i = 0; i < children.length; i++) 
      {
      console.log(Date());
    // skip over child if it is an ascendant of the dialog
    // or a script or style tag
      if (element !== children[i] && 
        !isNodeOneOf(children[i], ['SCRIPT', 'STYLE'])
       ) 
         {
         children[i].setAttribute('aria-hidden', isHidden);
         }
      }

    walkDOM(element = element.parentNode);
    }
  }

Object.defineProperty(HTMLElement.prototype, 'parentNode', 
                      { get: getter });

walkDOM(document.getElementById("foo") );
<section>
  <p>
    <span>
      <a>
        <strong>
          <span id="foo">Hi</span>
        </strong>
      </a>
    </span>
  </p>
</section>

In the above context, getter is:

A function which serves as a getter for the property, or undefined if there is no getter. The function return will be used as the value of the property.

References

Paul Sweatte
  • 24,148
  • 7
  • 127
  • 265