Secrets of the Data-Dojo-Attach-Point (when building custom widgets)

2704
4
Jump to solution
02-05-2020 09:53 AM
Arne_Gelfert
Regular Contributor

The title of this thread may be confounding to someone who either has either never heard of dojo attach or else has long ago mastered the science of dojo. I have been trying to wrap my brain around some of it, and am forking this from a separate thread. Thanks for Robert Scheitlin, GISP‌ and Ken Buja‌ for chiming in there.

Full disclosure: I'm working within the confines of widget.cs/widget.html trying to build custom widgets for WAB. So I may be falsely attributing behavior or issues to Dojo that are actually rooted in some of the jimu overhead that I'm inheriting.

Say, you have a super simple widget.html that looks like this:

<div id="main" data-dojo-attach-point="main">

(The following code snippets come from my startup function in widget.js. )

I thought i would be able to create a new div inside "main" as follows:

var existingDiv = document.getElementById("main");
var newDiv = document.createElement("div");
existingDiv.appendChild(newDiv);
var newStuff = document.createTextNode("Such a pretty Div"); 
newDiv.appendChild(newStuff);  ‍‍‍

But that doesn't work.

TypeError: Cannot read property 'appendChild' of null

So, regardless of what 'id' I give my element, it gets assigned an id that looks something like this:

widgets_MyNewWidget_Widget_19

Wow, once I figured that out and used the above as id, appendChile works just fine. Much simpler even using the dojo/dom-construct and the data-dojo-attach-point is to do this:

domConstruct.place("<div>dojo created div</div>",this.main,"last")‍‍

Now, I want to create something a little more useful than just a text div. I want to have a dropdown to select from.

define([...,"dijit/form/Select",...],
function(...,Select, ...)
...

var mySelect = new Select({ 
  name: 'myselect',
  dropDown: true,
  options : [
   {label: 'Bernie',value: "Option1"},
   {label: 'Buttie',value: "Option2"},
   {label: 'Bidey',value: "Option3"}
   ]
},<needs some id here>);
‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Oh, wait now, my new <dojo-created-div> needs an id to know where to go.

  • If I use "main", nothing happens. I don't see my select.
  • if I use the "widgets_MyNewWidget..." as 'id', I get an error:
Tried to register widget with id==widgets_MyNewWidget_Widget_19 but that id is already registered

So instead, I add a new 'id' to my domConstruct:

domConstruct.place("<div>id='newdiv' dojo created div</div>",this.main,"last")‍‍‍‍‍

Now, I can use the "newdiv" id inside the definition of the new Select, and it gets created.

Now, here is the problem I haven't figured out how to solve.  On my event listeners, previously when the select would have been set up as <select> in HTML, I have been using something like:

on(this.myselect,"change",lang.hitch(this, function(evt){
console.log("select has changed);
}));‍‍‍

... where again, 'myselect' would be a reference to a dojo-attach points. So now I am trying to figure out if there is a way to create the attach point programmatically. I've tried adding it to the domConstruct.place():

domConstruct.place("<div data-dojo-attach-point='myselect' id='newdiv'></div>",this.main,"last"); ‍‍

That doesn't work. I've also tried the following once the Select has been created:

mySelect.set("data-dojo-attach-point","myselect");// - doesnt' work

That doesn't work either, likely because ...

set() and get()

In general attributes can be both set at initialization and modified after the widget is created, although some attributes, like “id” and “type”, which are marked [const]... (from  Dojo documentation)

So is there a way to do this in javascript? Or do I have to set this up in my HTML? And if so, how do I do it?

Every time I create another HTML element to contain my programmatically created Select, the Select shows up but I can't reference it using "this.myselect".

What am I doing wrong? What's the best practice that get the best of both worlds - hrml and javascript - in this case?

0 Kudos
1 Solution

Accepted Solutions
RobertScheitlin__GISP
MVP Esteemed Contributor

Arne,

   Hmm.. There are so many things I want to teach you here. 

I'll try and start at the top of your thread and work down.

You seem to be giving up on having the select in the widget.html and instead creating is programmatically. So let me address the programatic creation of dojo dijits.

You are mixing standard javascript techniques with dojo (which is OK but very muddy).


document.createElement is a vanilla JS way of creating a basic HTML element. In dojo we use domConstruct.create.

But these methods are both for creating simple html element in js code. What you really want to do is create a new 

dijit/form/Select like you have but then give it a div to attach to. Notice I set this.mySelect to equal the new select dijit.

this.myselect = new Select({
  options: [
    { label: "Volvo", value: "volvo" },
    { label: "Saab", value: "saab", selected: true },
    { label: "Mercedes", value: "mercedes" },
    { label: "Audi", value: "audi" }
  ]
}, this.mySelectDiv);‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

widget.html

<div id="main" data-dojo-attach-point="main">
  <div data-dojo-attach-point="mySelectDiv" />
</div>‍‍‍‍‍‍

Notice at the end I place this new dijit using this.mySelectDiv this is the div in your widget.html that has a dojo attach point of "mySelectDiv" You do not need to worry about trying to assign a dojo attach point to a programmatically, they are only used for elements already created in the widget.html. If you need get back to your element you created in code you do it by the id property. So I probably confused you in my previous thread about when to use data-dojo-attach-point vs. id. 

data-dojo-attach-point is for elements defined in the html. You use id when you create element in code and then add them to the widget DOM.

So if you add your 

on(this.myselect, "change", lang.hitch(this, function(evt){
  console.log("select has changed);
}));‍‍‍‍‍‍

now it will work because your this.myselect equals your dijit/form/Select.

View solution in original post

4 Replies
RobertScheitlin__GISP
MVP Esteemed Contributor

Arne,

   Hmm.. There are so many things I want to teach you here. 

I'll try and start at the top of your thread and work down.

You seem to be giving up on having the select in the widget.html and instead creating is programmatically. So let me address the programatic creation of dojo dijits.

You are mixing standard javascript techniques with dojo (which is OK but very muddy).


document.createElement is a vanilla JS way of creating a basic HTML element. In dojo we use domConstruct.create.

But these methods are both for creating simple html element in js code. What you really want to do is create a new 

dijit/form/Select like you have but then give it a div to attach to. Notice I set this.mySelect to equal the new select dijit.

this.myselect = new Select({
  options: [
    { label: "Volvo", value: "volvo" },
    { label: "Saab", value: "saab", selected: true },
    { label: "Mercedes", value: "mercedes" },
    { label: "Audi", value: "audi" }
  ]
}, this.mySelectDiv);‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

widget.html

<div id="main" data-dojo-attach-point="main">
  <div data-dojo-attach-point="mySelectDiv" />
</div>‍‍‍‍‍‍

Notice at the end I place this new dijit using this.mySelectDiv this is the div in your widget.html that has a dojo attach point of "mySelectDiv" You do not need to worry about trying to assign a dojo attach point to a programmatically, they are only used for elements already created in the widget.html. If you need get back to your element you created in code you do it by the id property. So I probably confused you in my previous thread about when to use data-dojo-attach-point vs. id. 

data-dojo-attach-point is for elements defined in the html. You use id when you create element in code and then add them to the widget DOM.

So if you add your 

on(this.myselect, "change", lang.hitch(this, function(evt){
  console.log("select has changed);
}));‍‍‍‍‍‍

now it will work because your this.myselect equals your dijit/form/Select.

Arne_Gelfert
Regular Contributor

Well, that was easy! - Haha. So I was looking for a way to do something that you don't or can't do.

As for the vanilla vs. dojo, yes, I understand. Reading dojo documentation cleared that up.

Got the select working now, and once I remembered to instantiate as "this.mySelect" instead of saying "var mySelect", the last domino fell in place.

(And, if you remember the last thread, now i can get to my selected value in the Select and tie that in with my Select getting populated from a service.)

Thanks a bunch, Robert!

0 Kudos
KenBuja
MVP Honored Contributor

In one of my widgets, I'm adding Select dijits to my widget. Since the user defines how many Selects will be added when setting up the widget (this particular version of the widget pictured below has six Selects programmatically added), this code is a little more dynamic. I use this function to create the various Selects:

  _createSelector(data, title, node, className, defaultSelection) {
    const store = new oldMemory({
      data: data
    });
    const os = new ObjectStore({
      objectStore: store
    });
    const s = new Select(
      {
        class: className,
        store: os,
        required: true,
        sortByLabel: false,
        title: title,
        style: "margin-top: 5px;",
        defaultSelection: defaultSelection
      },
      node
    );
    s.startup();
    return s;
  },‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Here, the oldMemory is a "dojo/store/Memory", ObjectStore is a "dojo/data/ObjectStore," and Select is a "dijit/form/Select".

In this example, I'm creating a Select to choose a renderer to display data (highlighted as the "Change legend" portion of the widget).

The html file contains a div that will hold the text and the Select.

<div data-dojo-attach-point="divSelections" style="margin-top: 10px"></div>‍‍‍

I populate an array of Renderers to be used as the data variable.

this._arrayRenderers.push({
  id: primaryLabel,
  label: primaryLabel,
  renderer: primaryRenderer
});‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

I create divs to contain the text and the Select, create the Select using the above function, and add an event listener for the Select.

domConstruct.create(
  "div",
  {
    style: "margin-top: 20px;",
    innerHTML: this.nls.changeAttributeDisplay
  },
  this.divSelections
);
const rendererSelector = domConstruct.create(
  "div",
  {},
  this.divSelections
);
const s = this._createSelector(
  this._arrayRenderers,
  this.nls.renderer,
  rendererSelector,
  "Renderer",
  null
);
//create the event listener for the Select
s.on(
  "change",
  lang.hitch(this, function(evt) {
    const filter = array.filter(this._arrayRenderers, function(item) {
      return item.id === evt;
    });
    this._layerSPGrid.setRenderer(filter[0].renderer);
    this._layerSPGrid.redraw();
  })
);‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
0 Kudos
Arne_Gelfert
Regular Contributor

Thanks, Ken... looks very intriguing but probably a little more than I need right now. I will come back to this though. Appreciate your time responding!!

0 Kudos