Cédric Canguilhem

PI Vision Easter Eggs - Step 4 : Create the log feature from scratch

Blog Post created by Cédric Canguilhem on Oct 19, 2020

Hi all, PI Vision developers !

 

This blog post is part of a mini-series about adding a log feature to the native Value symbol, and hidding it into an easter egg. I suggest reading the previous blog posts if not done yet :

 

Step 4 : How to create a simple javascript console log feature from scratch

Debugging your custom symbols and extensions is fundamental, even in Production environments.

Everyone agrees that third-party developments SHOULD NEVER push debug traces to the javascript console, unless they are explicitly told to. And this feature MUST be able to be deactivated.

 

The javascript "console" object has many functions very useful : error(), warn(), debug(), group(), table()...

(see Console specifications on the Mozilla Developer Network)

 

For this example, we will only use the "console.log(..)" statement.

 

Step 4.1 : Create a new option group dedicated to the log feature

  • Edit the "sym-value-config.html" file.
  • Duplicate the "<li> ... </li>" code block dedicated to the "About" option group.
  • The new group will be placed between "Visibility" and "About", rename its title as "Log"
  • Remove everything inside the div

 

your code should now look like :

<!-- LOG -->
<li class="k-state-active k-state-active-custom" ng-show="config.ShowEasterEggOptions">
<span class="panel-bar-label-no-wrap">Log</span>

<div
style
="margin: 10px 20px 15px 10px;"
>

</div>
</li>

<!-- ABOUT -->
[...]

 

And the configuration pane should be :

 

Step 4.2 : Create a new attribute to store the activation status for the log feature

Similarly to what we've done in the previous blog post, edit the "PIVisualization.sym-value.js" file, then add a new attribute named "LogIsActivated" inside the "getDefaultConfig" function :

// CUSTOM ATTRIBUTES
config.ShowEasterEggOptions = false;
config.LogIsActivated = false;
// END CUSTOM ATTRIBUTES

 

Step 4.3 : Add a checkbox for activating / deactivating the log feature

In the "Visibility" option group, checkboxes are encapsulated into labels.

 

To be consistent, we'll do the same :

  • Create a new label inside the div, update its content to "Log is activated" :
    <div
    style
    ="margin: 10px 20px 15px 10px;"
    >


    <label class="flex-align-center">Log is activated</label>
    </div>
  • Insert a input field of type "checkbox" above the text of the label, as below :
    <label class="flex-align-center">
    <input type="checkbox" class="c-config-checkbox"
    ng-model="config.LogIsActivated" />

    Log is activated
    </label>

The "ng-model" AngularJS directive creates a double-way binding between this checkbox and the attribute in the symbol's configuration.

 

This means that every change on one side will be reflected to the other :

  • If you change the value of the attribute (by javascript code), the checkbox will be checked or unchecked accordingly in the user interface
  • If you click on the checkbox to check or uncheck it, the attribute's value will be updated

 

This synchronization is done automatically by the AngularJS framework.

Direct benefit : you don't need to add a callback to the checkbox in order to catch click or status change events (like we did for the button).

 

Bonus : Test the double-way binding

If you want to bypass this optional paragraph, you can go directly to step 4.4.

You may make a copy of what you've done till now, since the following steps have to be undone at the end of the process.

 

  1. From checkbox to attribute
    Add the following line at the end of the configuration file :
    LogIsActivated = {{config.LogIsActivated}}

    The aim of the double brackets around "config.LogIsActivated" is to evaluate its contents.

    The symbol's configuration pane should have the following behavior :

    The checkbox status masters the value of the attribute.
  2. From attribute to checkbox
    Replace the div containing the button at the end of the configuration file by the following lines :
    <div
    style
    ="margin: 10px 15px; padding: 6px 0"
    >


    <input
    type="button" class="c-primary-button" value="Change Log Status"
    ng-click="config.LogIsActivated = !config.LogIsActivated;"/>

    </div>

    The aim of this code is to invert the value of the configuration attribute "LogIsActivated" every time the user clicks on the button "Change Log Status". 

    The symbol's configuration pane should have the following behavior :

    The attribute's value masters the checkbox status.

 

Now don't forget to undo the changes made in this bonus paragraph

 

Step 4.4 : Create a new attribute for the log level

When I debug my custom symbols, I usually start with 3 common log levels :

  • None : no log at all is pushed to the console (*)
  • Basic : the log feature logs a new line in the console each time a function is called.
  • Full : all detailed information required for my debugging

 

(*) You probably wonder why this option is required, since deactivating the whole log will have the same effect ?!

I'll later show you why I think it is useful.

 

To create a dedicated attribute, same thing as previously : edit the "PIVisualization.sym-value.js" file, then add a new attribute named "LogLevel" inside the "getDefaultConfig" function :

// CUSTOM ATTRIBUTES
config.ShowEasterEggOptions = false;
config.LogIsActivated = false;
config.LogLevel = "None";
// END CUSTOM ATTRIBUTES

By default, the log is deactivated (line 3), so the log level must default to "None" to be consistent.

 

Step 4.5 : Add dropdown list for the log level

Paste the following lines under the "LogIsActivated" checkbox (actually, under the label containing its input field) :

<select
class="config-option-list"
style
="width: 50%;"

ng-model="config.LogLevel"
ng-model-options="{ getterSetter: true }">


<option value="None">None</option>
<option value="Basic">Basic</option>
<option value="Full">Full</option>

</select>

 

The result is :

 

Available values are :

 

OK, but not fully satisfying.

  1. The list needs a text to indicate its purpose. The text will be located at its left.
  2. Since the list depends on the checkbox, I like indenting it and aligning the first letter of both texts (the one of the checkbox, and the one next to the list).
    One option is to nest the list into a single-row div that has specific margins, and to put the text "Log Level" inside a new div, before the list :
    <div
    class="config-option-single-row"
    style
    ="margin: 0px 15px 0 28px;"
    >


    <div class="config-label">Log level</div>

    <select
    class="config-option-list"
    style
    ="width: 50%;"

    ng-model="config.LogLevel"
    ng-model-options="{ getterSetter: true }">


    <option value="None">None</option>
    <option value="Basic">Basic</option>
    <option value="Full">Full</option>

    </select>
    </div>
  3. Moreover, when the log is deactivated, we expect the dropdown list not to be displayed.
    To achieve this, just add the following "ng-show" directive to the div with margins :
    <div
    class="config-option-single-row"
    style
    ="margin: 0px 15px 0 28px;"

    ng-show="config.LogIsActivated">

    This directive will map the visibility of the Log Level list to the value of the "LogIsActivated" attribute.

Result :

 

Note : I assume there are various different solutions for this.

I'm far from an experienced web developer, so if you have better implementation, please advise ! 

 

Step 4.6 : Add log statements to the implementation file

Some functions are commonly used in native and custom symbols :

  • getDefaultConfig
  • loadConfig
  • configInit
  • configChange
  • dataUpdate

 

Add the following lines at the very beginning of the "dataUpdate" function :

// LOG
if (this.scope.config.LogIsActivated) {

if (this.scope.config.LogLevel != "None") {
console.log("[VALUE] dataUpdate(data)");
}

if (this.scope.config.LogLevel == "Full") {
console.log("data = ");
console.log(data);
}
}

What does this code do ?

  1. If the log is not activated, nothing at all
  2. If the log is activated :
    1. if the log level is set to "None", nothing at all
    2. if the log level is set to "Basic", it pushes one single line to the console window in order to log the function call
    3. if the log is set to "Full", it logs the function call (same as 2.b) and logs the entire content or the "data" parameter that is passed to the "dataUpdate" function by the PI Vision framework.

 

Note : the object "this.scope" represents the whole symbol's characteristics : its metadata, its configuration, its runtime data, etc...

 

Let's see the result of each case.

You'll need to display the developpement tools pane in your web browser (F12 in Chrome).

 

Here you can see that the log level's value (Full) was kept when I deactivated the log.

Then, when I reactivated the log, it restarted to push the data's content to the console.

 

This may not be the expected behavior, especially when you have many traces in your javascript code.

 

What we are going to do, is to set the log level to "None" when the log is deactivated.

 

4.7 Track configuration changes

Let's add some log to the "configChange" function, that is called every time... the symbol's configuration is changed (thanks Captain !!!).

 

Add the following lines at the very beginning of the "configChange" function :

// LOG
if (this.scope.config.LogIsActivated) {

if (this.scope.config.LogLevel != "None") {
console.log("[VALUE] configChange(newConfig, oldConfig)");
}

if (this.scope.config.LogLevel == "Full") {
console.log("oldConfig = ");
console.log(oldConfig);
console.log("newConfig = ");
console.log(newConfig);
}
}

 

Here, the Full log level will display the content of the symbol's previous and current configuration settings.

 

4.8 Make the symbol's configuration consistent

Inside the "configChange" function, add the following line just after the log feature :

// Check log activation status
if (!newConfig.LogIsActivated) {

// Update log level
newConfig.LogLevel = "None";
}

 

Now your code should look like :

 

Check the whole result of steps 4.7 and 4.8 :

 

In the next blog post, we will implement the final eater egg trick to hide everything that is non-native.

 

Don't hesitate to comment if you have any suggestion !

 

 

Outcomes