Tuesday, July 31, 2018

NPM and External Dependencies for PowerBI Custom Visuals

The custom slicer I built for Power BI has a few advanced UI features, clickable DIVs, a slider control, text input with validation.  It may have been possible to develop all of those interactions myself, but why intent the wheel.  jQuery and jQuery UI really add all of the features and simplify a lot of the coding.

Here's a step-by-step process that I used to get jQuery and jQuery-UI into my visual.
  1. Start with Use Developer tools to create custom visuals.
  2. Then add jQuery and the strong types to be TypeScript friendly:
    1. npm install jquery
    2. npm install "@types/jquery"
  3. Add jQuery UI (this was the hard part, see below).
I'm new to Node.js projects (do I capitalize Node.js, yes?), and I'm still getting familiar with the overall structure.  The thing I'm getting off the bat is that the node_modules folder is special.  For one, it should be 100% managed by npm.  That means that stuff placed there shouldn't be committed to your code repository (I'm not there 100%, but I'm getting close). 

Full disclosure, I started by putting some non-TypeScript code there that I had written (CSS, custom JavaScript, etc.).  But it's not an npm package, so it shouldn't be there. I moved it to my src folder. Next, I tried and tried to use an npm package for jQuery UI.  I tried the jquery-ui package, but it has to be built.  Then I got some new info and tried the jquery-ui-dist package.  But, I'm getting an error when compiling.  Near the very last line of jquery-ui.css there's a rule:

filter: Alpha(Opacity=.3);

But, Node.js doesn't understand that rule and throws the error "error  LESS  style/visual.less : (1307,23) Could not parse alpha".  I did some searches and the only way to fix it is to modify the code.  I don't want to modify the code in  node_modules.

My fix was to download the vanilla version of jQuery UI.  Then cherry pick only the assets I need to be successful.  I grabbed jquery-ui.js, jquery-ui.min.js, and the minimal items from the css folder in the distribution.  For the timebeing they are in a jquery-ui folder in node_modules.  I know I just got done writing that this was bad, so my next step will be to move them to the src folder.  Optimally, it would be great if I could just use the jquery-ui-dist package, but it seems that I can't, yet...


Removing Document Edit Protections from Word DOCX files

Ever wanted to edit a Word document that had editing disabled?  Typically you need to have a password to be able to edit the file, but if you really want to get rid of it, there's a pretty straight forward process.

  1. Unzip the DOCX file (it's just a ZIP file after all) into a directory some where.
      • This produces the following:
        • [Content_Types].xml
        • _rels (sub directory)
        • docProps (sub directory)
        • word (sub directory)
  2. Using your favorite text editor (I used VIM), edit the file word/settings.xml
  3. Remove the XML tag:  <w:documentProtection/>
  4. Zip up the three (3) directories and the [Content_Types].xml file and make sure the extension on the zip file is DOCX.

You're ready to go.  You've now removed the protections on a Word Document.  Be careful with your new found powers, young padawan.

Friday, July 6, 2018

Power BI Custom Visual Design Considerations

Back at the Power BI custom slicer... We ran into an intermittent bug that would happen when you first open a report and when you switch between tabs.  The root cause comes from a feature where the first or last item is automatically chosen, something handy when you want to see the latest data based on a range of dates.

Some background on Power BI Custom visuals, they are TypeScript (JavaScript) based, and are defined by the interface IVisual.  As of v1.11.0, the IVisual interface has three methods:
  • update(options: VisualUpdateOptions): void
  • destroy?(): void
  • eumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration
There's also a constructor that receives a VisualConstructorOptions object, and a bunch of methods that aren't documented, yet.  Turns out that update() is called immediately after the constructor, and after every call to ISelectionManager.applySelectionFilter() (APF here on, which changes the filters applied to any connected visuals).  When APF is called and the selection is the same as it was before, there's a good chance that it won't trigger an update, which is nice because people like to double click stuff.

Here the slider sent an on change when the selected value was updated programmatically.

The kicker is when your custom visual changes the selection and calls APF during a call to update().  Mine did, on every call, and the only thing keeping it from an infinite loop of updates was the under-the-covers check to see if the selection was the same.  But what if there was a delay in the selection updates, and it caused a cycle between an old selection and the one that was just forced?  Infinite loop...

I tried all kinds of things.  First, I thought that there was a direct path between APF and update(), so I put some Boolean locks around the code calling APF to avoid stack overflows and stop the loop.  But, even though it looks like it on the stack inspector, the selection changed events are detected in some sort of async loop instead inline with stack execution.  Then I tried to make the code as clean as possible, but that didn't help.  The piece of information I was missing?  The very first call to update() after the constructor restored the previous selection filter w/o having to call APF.  Support on the Power BI board, said to never call APF from update(), because it can cause an infinite loop, and testing showed that the previous selection was always pre-choosen when the report loads (as long as you store the selection under the property "general" when it changes, more on that later).  So don't call APF from update()

But I really wanted to pick the first or last item by default.  The change was to be sure to never call APF from update() more than once.  I was already detecting the first call to update() after the constructor, then it was just a matter of:
  • If it isn't the first call to update() don't call APF.
  • If it's the first call to update() and only if the current selection isn't the one you want call APF.
In this version, onChange() isn't called during a call to update()
With this change to the logic, APF could only be called w/o user intervention once after the report loads, and so no loop.