Initializing Global Variables and Synchronous AJAX Calls in an Angular XPages Application

In a recent project (an XPages application with an Angular front end), I had a need to (a) run a script to determine user access and (b) store that value globally. In this post, I’ll show one way to achieve this with a synchronous AJAX call and a module run block.

Use Case

In this application, the front end pages need to know whether the user has Admin rights in the application in order to determine whether to display advanced options such as configuration links.

The security check needs to be run before the page is loaded and it needs to be stored globally so that the security check does not have to run constantly as many page elements are loaded.

Initial Implementation

I created a custom REST service that checks the user’s roles and returns a boolean value for whether the user is an Admin in the application. I called it via AJAX and stored the value in the $rootScope.

I then set up the logic from each admin-controlled feature call a function to check whether the value existed in $rootScope. If not, the function would make the AJAX call to look up the value and store it.

The code to call the REST service looks like this:

$http.get('rest.xsp/isUserAdmin').success(function (data) {
  $rootScope.isAdmin = (data == 'true') ? true : false;
})

The logic seemed to be set up properly. However, it didn’t initially work as I expected.

The Problem

Watching the Net panel in the browser tools made it clear that the AJAX call was being made many times as each page loaded.

The AJAX calls were fired off so rapidly that numerous calls were made before any of them had a return value available in the $rootScope.

It turns out that Angular’s AJAX calls are asynchronous by design. For the most part, this is great because it keeps the application more responsive. However, in this case, I needed the security check to complete before loading the rest of the page.

The Solution

Ultimately, I solved the problem by making two changes.

  1. Moved the role-checking code to a module run block so it would run before page load
  2. Replaced the Angular AJAX call with a standard jQuery AJAX call so I could add a parameter to force the call to be synchronous.

As the application loads, it will call the REST service and wait for the response. When done, it will store the value in the $rootScope.

Here is a link to the Angular.js module documentation.

Run blocks are the closest thing in Angular to the main method. A run block is the code which needs to run to kickstart the application. It is executed after all of the service have been configured and the injector has been created.

A run block generally goes in the app.js file rather than in a controller. Just add a run() method to the application and include the required directive and code.

This code sets up a function in the run block that includes a synchronous jQuery AJAX call to an XPages REST service, and stores the return value in the $rootScope for global access in the application. This runs immediately and the global variable is initialized and available when the rest of the page loads.

// Make the user access check available globally
PSApps.run(['$rootScope',                    
  function($rootScope) {
	 
      // Use jQuery for a synchronous http request -- angular hard-codes all requests to be asynchronous
      $.ajax('rest.xsp/isUserAdmin', {async: false})
        .success(function (data) {
          $rootScope.isAdmin = (data == 'true') ? true : false;
          console.log('is user admin? ' + data);
      });

  }
]);

Then, all places that need to check the value in order to determine whether to display an element can just reference the global property.

ng-show="$rootScope.isAdmin"

Fixing an SSLv3 Error Connecting to Bitbucket from SourceTree with Mercurial

I started receiving an SSLv3 Handshake error message when using SourceTree to synchronize a Mercurial repository to Bitbucket. In this post, I’ll show how the issue can be resolved.

The Error

The error message is a bit cryptic:

abort:error:_ssl.c:504:error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure

SourceTree SSL Error

The Cause

Atlassian’s Head of Security announced that they removed SSL V3 support from cloud platforms due to the POODLE vulnerability.

Earlier versions of Mercurial used SSL V3.0 and are now failing when connecting to Bitbucket as a result.

The Solution

My SourceTree application is up-to-date, but that doesn’t take care of the problem. The issue is that the underlying version of Mercurial needed to be updated.

Fortunately, the solution is an easy one.

You can check the version of Mercurial being used by SourceTree by selecting Tools > Options and then clicking on the Mercurial tab.

SourceTree - Mercurial version - Before

In my configuration, SourceTree is set up to use the system version of Mercurial, which was at version 2.7.1. I switched to the embedded version and ran the update via the button on the right of the screen, but the embedded version was only updated to Mercurial 2.6.1.

Ultimately, I had to download the latest version of Mercurial from Selenic’s site.

After I ran the installation, the problem was fixed.

SourceTree - Mercurial Version - After

Formatting Select2 Options in Angular

One of the great features of Select2 is that you can format the choices beyond just a list of text values. In this post, I’ll show how you can format the display of values in a Select2 in Angular.

Before we dig in…

These examples were adapted from an XPages application that uses Angular, but there’s nothing related to XPages in the code shown here. (In reality, you’d likely call an XPages REST service to dynamically populate the list of choices, but it’s not necessary in these examples.)

If you’ve used a Select2 in any other context, then the solution here may not be surprising. If you haven’t, then you’ll see how Select2 choices can be formatted.

The examples in this post use the ui-select2 for Angular. (There’s a note at the top of the page that says this control has been deprecated in favor of a newer version.) The code needs to be added to the application, but that is outside of the scope of this post.

Getting up to Speed on Angular

If you don’t have any background in Angular, the angularjs.org tutorial is a great place to start.

Also check out Marky Roden’s great series on using Angular in XPages.

Basic Select2

These examples will all create a Select2 control in Angular that will display a list of fruit and allow the user to choose one.

Here is the basic Select2:

Angular Select2 1a

Once a value is selected, it is shown in the field and separately below the Select2.

Angular Select2 1b

The basic HTML for the template is as follows:

<select ui-select2="select2Config" ng-model="fruit"
        data-placeholder="Select a fruit..." style="min-width:150px">
    <option value=""></option>
    <option ng-repeat="option in options" value="{{option}}">{{option}}</option>
</select>

<br><br>
Selected Value: {{fruit}}

Lines 1-2 start the select tag.

  • The ui-select2 attribute references the configuration object in the Angular controller that sets properties for the control.
  • The ng-model attribute defines that the selected value will be bound to a “field” (if you will) named fruit in the model.
  • The data-placeholder attribute sets the placeholder text when no value has been selected.
  • The style attribute sets a minimum width for the control. (This is not necessary)

Line 3 includes an empty value at the start of the list.

Line 4 uses an ng-repeat directive to build the list of options to choose from. The options list is set in the controller (code shown below).

Line 8 is simply displays the selected value. It is not necessary, but it shows off the two-way binding feature of Angular.

The controller used with the template on this page is as follows:

//Select2 controller - basic
MyAppControllers.controller('select2Ctrl_basic', ['$scope', '$http', '$routeParams',
  function ($scope, $http, $routeParams) {
    $scope.options = ['apple', 'banana', 'grape', 'lime', 'orange'];
  }
]);

That’s it! All I did was define the controller and define an options property in the scope and set it to an array. The template reads the array and generates an option for each value in the array.

Text Formatting

In this example, I’ll show how to change the color of each option based on the value. We’ll use the same HTML template for the Select2.

Angular Select2 2

Here’s the controller for this example:

MyAppControllers.controller('select2Ctrl_formatted', ['$scope', '$http', '$routeParams',
  function ($scope, $http, $routeParams) {
    $scope.options = ['apple', 'banana', 'grape', 'lime', 'orange'];
    
    // Format the control to display a span with the text changed to the color of the fruit
    $scope.format = function (data) {
      var fontColor = 'black';
        	
      switch (data.id) {
        case 'apple':
          fontColor = 'DarkRed';
          break;
        case 'banana':
          fontColor = 'Gold';
          break;
        case 'grape':
          fontColor = 'Purple';
          break;
        case 'lime':
          fontColor = 'LimeGreen';
          break;
        case 'orange':
          fontColor = 'Orange';
          break;
      }
        	
      return  "<span style='color:" + fontColor + "'>" + data.text + "</span>";
    };

    $scope.select2Config = {
      formatResult: $scope.format,
      formatSelection: $scope.format
    };

}]);

In this controller, lines 6-28 create a function that is used to format the options in the list. The function accepts a data object that is the current option in the select2. (The function will be called once for each option as the list is built.) The data object contains a text property that is the actual display value for that option.

Lines 9-25 define a switch statement that sets the fontColor variable based on the fruit.

Line 27 is the key — it returns a span that includes a style attribute to set the font color and display the option.

Lines 30-33 define the configuration object used for the Select2. (This is specified in the ui-select2 attribute of the HTML template.) It tells the control to format each selection and the result using the format() function defined in the controller.

The difference between formatSelection and formatResult

The formatSelection function formats the values in the drop-down list. The formatResult function formats the selected value, shown at the top of the list when the rest of the list is dropped-down. (The example below shows ‘orange’ selected.)

Angular Select2 3

You can use different formatting functions, but it seems common to use the same formatting function for both.

Adding an Image

In this example, I’ll show how to display an image next to each option, based on the value. We’ll use the same HTML template for the Select2.

Angular Select2 4

Here’s the controller for this example:

MyAppControllers.controller('select2Ctrl_image', ['$scope', '$http', '$routeParams',
  function ($scope, $http, $routeParams) {

    $scope.options = ['apple', 'banana', 'grape', 'lime', 'orange'];

    // Format the control to display an image corresponding with the name of the fruit
    $scope.format = function (data) {
      	
      var url = '';
        	
      switch (data.text) {
        case 'apple':
          url = 'apple.png';
          break;
        case 'banana':
          url = 'banana.png';
          break;
        case 'grape':
          url = 'grapes.gif';
          break;
        case 'lime':
          url = 'lime.png';
          break;
        case 'orange':
          url = 'orange.png';
          break;
      }
        	
      return  "<img src='" + url +"' />&nbsp;&nbsp;" + data.text;
    };           
        
    $scope.select2Config = {
      formatResult: $scope.format,
      formatSelection: $scope.format
    };

}]);

This is very similar to the last example, with the exception that it determines the url and returns an img tag to display next to the text value. This adds a very nice touch to the UI of the control. (Note: The images referenced in this example are all icons that I added to the NSF as image resources.)

Including a Java Class in SSJS

You’re most likely aware that SSJS really is really turned into Java under the covers, but did you realize that you can use Java classes in SSJS? In this post, I’ll show how with a simple example.

Using a Java class in SSJS

There are examples similar to this in the DDE help when looking up how to get a document from a view:

var vec = new java.util.Vector();
vec.addElement('firstLookupKey');
vec.addElement('secondLookupKey');
var doc - myView.getDocumentByKey(vec);

And so on. Several view lookup methods take a vector as a way to look up documents or view entries with multiple keys for multiple sorted columns.

The first line specifies that it’s creating a new java.util.Vector() object. After that, you can use methods of that Java class right from SSJS.

Using Type Ahead Autocompletion for Java Class Methods

If you specify the type of the variable that you create, Domino Designer provide type ahead. Take this example:

var vec:java.util.Vector = new java.util.Vector();

Just type the variable name and a period, and (maybe after a slight pause), you’ll see a list of properties and methods to choose from:

Java Class in SSJS

Importing a Java Class into SSJS

If you want to refer to an object more easily in SSJS, you can import the class you want to use and then reference it just by the class name, rather than by the fully-qualified name. To do this, use importPackage().

Here’s how this would look different when working with a Vector:

importPackage (java.util.Vector);	
var vec = new Vector();

And now you have code that’s a little easier to follow and also has the ability to display the properties and methods of the class:

Java Class in SSJS 2

Clearly, there is far more opportunity to use this than the simple examples I’ve shown here, but I hope this gets the gears turning and becomes a useful feature for you.

XPages Tip: Using _dump to write information to the server console

I recently showed how to use a built-in client-side JavaScript function to dump the contents of an object for review. In this post, I’ll show a somewhat similar method available in Server-Side JavaScript.

_dump() is a global method available in SSJS. You can use it to write the contents of an object out to the server console (and log).

The XPages Portable Command Guide mentions using it to dump the contents of scope variable maps.

Since the requestScope automatically has a bunch of properties, I can see the results if I run this beforePageLoad:

_dump(requestScope);

Here is the output on the server console — basic URL, user, server, and page info:


HTTP JVM: com.sun.faces.context.RequestMap
HTTP JVM: =
HTTP JVM: {database=BlogTesting.nsf, __xspconvid=null, opensession=CN=XDev9/O=Xcellerant, userScope={}, session=CN=XDev9/O=Xcellerant, cookie={SessionID=javax.servlet.http.Cookie@7190719}, component
HTTP JVM: arameters=com.ibm.xsp.application.ComponentParameters@3f953f95, context=com.ibm.xsp.designer.context.ServletXSPContext@56fc56fc,
identityScope={}, serverScope={Anonymous={}}, opendatabase=BlogTesting.nsf, com.ibm.xsp.SESSION_ID=B7117B555E750057C2668FD5A46
HTTP JVM: 51CB288B7621}

The sessionScope is empty by default, but I can add a few properties and then look at the output:

sessionScope.property1 = 'abc';
sessionScope.property2 = 'def';
_dump(sessionScope);

HTTP JVM: com.sun.faces.context.SessionMap
HTTP JVM: =
HTTP JVM: {__XSP_STATE_FILE=com.ibm.xsp.application.FileStateManager$SessionState@70647064,
__notescontext_publicaccess=com.ibm.domino.xsp.module.nsf.NotesContext$AccessPrivileges@2a0b2a0b, property2=def, xsp.sessionData=com.ibm.xsp.designer.context.PersistentSessi
HTTP JVM: nData@34fb34fb, property1=abc}

Not exactly easy to read, but the information is there.

But you’re not limited to scope variables. Here’s an example of creating my own object and writing it out:

var myObject = {};
myObject.foo = 'xyz';
myObject.bar = 'uvw';
_dump(myObject);

HTTP JVM: Object
HTTP JVM: =
HTTP JVM: [object Object]
HTTP JVM:
HTTP JVM: +-
HTTP JVM: foo
HTTP JVM: :
HTTP JVM: string
HTTP JVM: =
HTTP JVM: xyz
HTTP JVM:
HTTP JVM: +-
HTTP JVM: bar
HTTP JVM: :
HTTP JVM: string
HTTP JVM: =
HTTP JVM: uvw

This seems ridiculously spread out, but it’s pretty easy to see the contents of the object.

All in all, MarkLeusink’s Debug Toolbar is a much nicer tool to use when working with scope variables, but if you’re doing some quick debugging — or setting up a consistent method of handling errors, the _dump() method can come in handy.

Article Published in The VIEW: Improve Application Performance with JSON-RPC

An article that I wrote on improving XPages application performance using JSON RPC has been published in The VIEW journal (subscription required).

One of the best-kept secrets of XPages is JSON-RPC control. It is the ideal solution for application developers who want:

  • Their XPages applications to be more efficient and run faster
  • To easily implement client-side and server-side JavaScript interaction

Abstract

If you want your XPages applications to perform more efficiently, or if you’ve ever struggled with executing server-side JavaScript from client-side JavaScript (or taken it a step further and plotted hacktastic workarounds for passing information back and forth), then you need to get to know JSON-RPC. Compared to workarounds that involve partial refreshes, JSON-RPC is a much cleaner method of client and server code interaction and is much more efficient because there is no form submission. JSON-RPC is also asynchronous, so it’s faster and appears more responsive because it doesn’t block user interaction. In this article, get an introduction to JSON-RPC and learn how to reap the benefits in your XPages applications.

Client-Side JavaScript Debugging Tip — Using XSP.dumpObject() and XSP.log()

The XSP object is a client-side JavaScript object that is built and delivered by the XPages runtime and it contains a number of useful functions, some of which are always available and some of which are context-specific. In this post, I’ll show two functions that are useful for client-side JavaScript debugging.

XSP.dumpObject()

The dumpObject() method returns a string representation of all properties of any object that you pass to it. If you want to track down why something isn’t working as expected, it may be helpful to inspect the properties of the object.

The object can be a variable that you programmatically create and modify or it can be an element on the page.

I used to make frequent use of a small script to display all properties of a JavaScript object, but this is a much simpler way to get that information.

Here’s an example of client-side JavaScript to build an object and use XSP.dumpObject() to write out all properties to the browser console:

var myObject = {}
myObject.property1 = 'One';
myObject.property2 = 'Two';
myObject.property3 = 'Three';
myObject.arrayProperty = ['a', 'b', 'c'];

console.log(XSP.dumpObject(myObject));

This information can be very helpful in debugging, so I would recommend using it when handling errors in a try-catch block.

XSP.log()

In addition to logging to the console, you have another similar option available by another XSP function. XSP.log() is analogous to the standard console.log() function that you use when debugging client-side

JavaScript, but the log() method opens up a new browser window and logs messages there, in descending order.

All we have to do is change the last line in the previous snippet:

XSP.log(XSP.dumpObject(myObject));

And this is what we see in a separate browser window:

XSPLog - 1

The log() method will continue appending any messages that you send until you close the window. The next call to XSP.log() will open a new, blank window and start logging again.

Using dumpObject on an HTML field and XPages control

You can also use dumpObject on an HTML element.

For example, if I have a pure HTML select with 3 options and an onchange event, this will be the output of if I get a handle to the element (the id is HTMLComboBox) and dump the object properties:

XSP.log(XSP.dumpObject(dojo.byId('htmlComboBox')));

object
  0: [object HTMLOptionElement] (Maximum Depth Reached)
  1: [object HTMLOptionElement] (Maximum Depth Reached)
  2: [object HTMLOptionElement] (Maximum Depth Reached)
  autofocus: false
  disabled: false
  form: [object HTMLFormElement] (Maximum Depth Reached)
  multiple: false
  name: 
  required: false
  size: 0
  type: select-one
  options: [object HTMLOptionsCollection] (Maximum Depth Reached)
  length: 3
  selectedOptions: [object HTMLCollection] (Maximum Depth Reached)
  selectedIndex: 0
  value: v1
  willValidate: true
  validity: [object ValidityState] (Maximum Depth Reached)
  validationMessage: 
  title: 
  lang: 
  dir: 
  dataset: [object DOMStringMap] (Maximum Depth Reached)
  itemScope: false
  itemType:  (Maximum Depth Reached)
  itemId: 
  itemRef:  (Maximum Depth Reached)
  itemProp:  (Maximum Depth Reached)
  properties: [object HTMLPropertiesCollection]... (Maximum Depth Reached)
  itemValue:  (Maximum Depth Reached)
  hidden: false
  tabIndex: 0
  accessKey: 
  accessKeyLabel: 
  draggable: false
  contentEditable: inherit
  isContentEditable: false
  contextMenu:  (Maximum Depth Reached)
  spellcheck: false
  style: [object CSS2Properties] (Maximum Depth Reached)
  className: 
  oncopy:  (Maximum Depth Reached)
  oncut:  (Maximum Depth Reached)
  onpaste:  (Maximum Depth Reached)
  offsetParent: [object HTMLBodyElement] (Maximum Depth Reached)
  offsetTop: 39
  offsetLeft: 0
  offsetWidth: 67
  offsetHeight: 19
  onabort:  (Maximum Depth Reached)
  onblur:  (Maximum Depth Reached)
  onfocus:  (Maximum Depth Reached)
  oncanplay:  (Maximum Depth Reached)
  oncanplaythrough:  (Maximum Depth Reached)
  onchange:  (Maximum Depth Reached)
  onclick:  (Maximum Depth Reached)
  oncontextmenu:  (Maximum Depth Reached)
  ondblclick:  (Maximum Depth Reached)
  ondrag:  (Maximum Depth Reached)
  ondragend:  (Maximum Depth Reached)
  ondragenter:  (Maximum Depth Reached)
  ondragleave:  (Maximum Depth Reached)
  ondragover:  (Maximum Depth Reached)
  ondragstart:  (Maximum Depth Reached)
  ondrop:  (Maximum Depth Reached)
  ondurationchange:  (Maximum Depth Reached)
  onemptied:  (Maximum Depth Reached)
  onended:  (Maximum Depth Reached)
  oninput:  (Maximum Depth Reached)
  oninvalid:  (Maximum Depth Reached)
  onkeydown:  (Maximum Depth Reached)
  onkeypress:  (Maximum Depth Reached)
  onkeyup:  (Maximum Depth Reached)
  onload:  (Maximum Depth Reached)
  onloadeddata:  (Maximum Depth Reached)
  onloadedmetadata:  (Maximum Depth Reached)
  onloadstart:  (Maximum Depth Reached)
  onmousedown:  (Maximum Depth Reached)
  onmouseenter:  (Maximum Depth Reached)
  onmouseleave:  (Maximum Depth Reached)
  onmousemove:  (Maximum Depth Reached)
  onmouseout:  (Maximum Depth Reached)
  onmouseover:  (Maximum Depth Reached)
  onmouseup:  (Maximum Depth Reached)
  onpause:  (Maximum Depth Reached)
  onplay:  (Maximum Depth Reached)
  onplaying:  (Maximum Depth Reached)
  onprogress:  (Maximum Depth Reached)
  onratechange:  (Maximum Depth Reached)
  onreset:  (Maximum Depth Reached)
  onscroll:  (Maximum Depth Reached)
  onseeked:  (Maximum Depth Reached)
  onseeking:  (Maximum Depth Reached)
  onselect:  (Maximum Depth Reached)
  onshow:  (Maximum Depth Reached)
  onstalled:  (Maximum Depth Reached)
  onsubmit:  (Maximum Depth Reached)
  onsuspend:  (Maximum Depth Reached)
  ontimeupdate:  (Maximum Depth Reached)
  onvolumechange:  (Maximum Depth Reached)
  onwaiting:  (Maximum Depth Reached)
  onmozfullscreenchange:  (Maximum Depth Reached)
  onmozfullscreenerror:  (Maximum Depth Reached)
  onmozpointerlockchange:  (Maximum Depth Reached)
  onmozpointerlockerror:  (Maximum Depth Reached)
  onerror:  (Maximum Depth Reached)
  tagName: SELECT
  id: htmlComboBox
  classList:  (Maximum Depth Reached)
  attributes: [object MozNamedAttrMap] (Maximum Depth Reached)
  onwheel:  (Maximum Depth Reached)
  scrollTop: 0
  scrollLeft: 0
  scrollWidth: 67
  scrollHeight: 19
  clientTop: 0
  clientLeft: 0
  clientWidth: 67
  clientHeight: 19
  scrollTopMax: 0
  scrollLeftMax: 0
  innerHTML: <option value="v1">Value 1</option><option value="v2">Value 2</option><option value="v3">Value 3</option>
  outerHTML: <select id="htmlComboBox"><option value="v1">Value 1</option><option value="v2">Value 2</option><option value="v3">Value 3</option></select>
  previousElementSibling: [object HTMLBRElement] (Maximum Depth Reached)
  nextElementSibling: [object HTMLBRElement] (Maximum Depth Reached)
  children: [object HTMLCollection] (Maximum Depth Reached)
  firstElementChild: [object HTMLOptionElement] (Maximum Depth Reached)
  lastElementChild: [object HTMLOptionElement] (Maximum Depth Reached)
  childElementCount: 3
  nodeType: 1
  nodeName: SELECT
  baseURI: http://127.0.0.2/BlogTesting.nsf/dumpObject.xsp
  ownerDocument: [object HTMLDocument] (Maximum Depth Reached)
  parentNode: [object HTMLFormElement] (Maximum Depth Reached)
  parentElement: [object HTMLFormElement] (Maximum Depth Reached)
  childNodes: [object NodeList] (Maximum Depth Reached)
  firstChild: [object HTMLOptionElement] (Maximum Depth Reached)
  lastChild: [object HTMLOptionElement] (Maximum Depth Reached)
  previousSibling: [object Text] (Maximum Depth Reached)
  nextSibling: [object HTMLBRElement] (Maximum Depth Reached)
  nodeValue:  (Maximum Depth Reached)
  textContent: Value 1Value 2Value 3
  namespaceURI: http://www.w3.org/1999/xhtml
  prefix:  (Maximum Depth Reached)
  localName: select
  ELEMENT_NODE: 1
  ATTRIBUTE_NODE: 2
  TEXT_NODE: 3
  CDATA_SECTION_NODE: 4
  ENTITY_REFERENCE_NODE: 5
  ENTITY_NODE: 6
  PROCESSING_INSTRUCTION_NODE: 7
  COMMENT_NODE: 8
  DOCUMENT_NODE: 9
  DOCUMENT_TYPE_NODE: 10
  DOCUMENT_FRAGMENT_NODE: 11
  NOTATION_NODE: 12
  DOCUMENT_POSITION_DISCONNECTED: 1
  DOCUMENT_POSITION_PRECEDING: 2
  DOCUMENT_POSITION_FOLLOWING: 4
  DOCUMENT_POSITION_CONTAINS: 8
  DOCUMENT_POSITION_CONTAINED_BY: 16
  DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC: 32

If I have a similar Combo Box control in XPages, the output is the same because the HTML element that is generated is virtually identical.

Note: You must pass a fully-qualified client-side ID to dumpObject(), so if you’re passing it an XPages element, use the pass-thru EL syntax "#{id:myControl}" or a client-side query that looks for the element id ending with the name of the control, such as dojo.query('[id~=myControl]')[0].id

console.dir(object)

There’s a method built into browser development tools that performs a similar function. This can be similarly handy when troubleshooting something directly on the page.

For example, to see all of the properties of the first input field on the form, you can use it like this:

console.dir(dojo.query('input')[0]);

Follow

Get every new post delivered to your Inbox.

Join 53 other followers