FilteringSelect Pt 2.
Category Dojo Domino
Bookmark :
In a previous post (http://dojomino.com/dojomino/blog.nsf/d6plinks/DBOS-7A3TSW) I touched on the subject of using the FilteringSelect with Domino dialog list fields. That post talked about the format of the html created by Domino as well as an issue with Firefox. My solution was to have the options for the field provided by a json data store. Now this works fine but, as I new would happen, I came across a situation where I really didn't want to handle it that way. So, I decided to see if I could come up with a solution that would allow us to setup the field in Domino that way we normally would and have it just work.
To do this, I started by rooting through the Dojo source code for to find the part that takes the options sent to the browser and convert them for use in the FilteringSelect widget. I started by looking in the dijit/form/FilteringSelect.js. I didn't find the code I was looking for there, but found that the FilteringSelect extends "dijit.form.MappedTextBox, dijit.form.ComboBoxMixin". So I then go into dijit/form/ComboBox.js and review the ComboBoxMixin. It was there that I found the function "postMixInProperties" function. This function checks to see if there is a store for this widget already defined. If not, it creates one from the options for the select box. The code is:
if(!this.hasDownArrow){
this.baseClass = "dijitTextBox";
}
if(!this.store){
// if user didn't specify store, then assume there are option tags
var items = this.srcNodeRef ? dojo.query("> option", this.srcNodeRef).map(function(node){
node.style.display="none";
return { value: node.getAttribute("value"), name: String(node.innerHTML) };
}) : {};
this.store = new dojo.data.ItemFileReadStore({data: {identifier:this._getValueField(), items:items}});
// if there is no value set and there is an option list,
// set the value to the first value to be consistent with native Select
if(items && items.length && !this.value){
// For <select>, IE does not let you set the value attribute of the srcNodeRef (and thus dojo.mixin does not copy it).
// IE does understand selectedIndex though, which is automatically set by the selected attribute of an option tag
this.value = items[this.srcNodeRef.selectedIndex != -1 ? this.srcNodeRef.selectedIndex : 0]
[this._getValueField()];
}
}
As we can see, there is a dojo.query lookup for the option tags. It then goes through them and sets the value and name attributes in the "items" object. This is then used to create a new data store that is used by the widget. For our purposes there are a couple of problems that needed to be addressed. The first is if we don't have a "value" set for our options. In Domino dialog list field if we don't alias our values then they are sent to the brower as this:
<option>One
<option>Two
<option>Three
No value attribute is set. To address this I modified this portion of the code:
var items = this.srcNodeRef ? dojo.query("> option", this.srcNodeRef).map(function(node){
node.style.display="none";
return { value: node.getAttribute("value"), name: String(node.innerHTML) };
}) : {};
to this:
var items = this.srcNodeRef ? dojo.query("> option", this.srcNodeRef).map(function(node){
node.style.display="none";
var fName=String(node.innerHTML).replace(/\n/g,'');
var fValue=node.getAttribute("value");
if (fValue==null || fValue=="")
fValue=fName;
return { value: fValue, name: fName };
}) : {};
What we do here is check to see if there is a value for the option and if not, set it to be the same as the display text. We have to check to see if the value is both null or empty because of browser differences. In Firefox it is returned as null, in IE is comes back as an empty string.
The second problem that I ran into was specific to Firefox. I had referenced this in my previous post. My thinking at that time was that FF wasn't dealing with the fact that the option tags weren't being closed. However, after digging deeper I found that wasn't the issue at all. Doing a little deeper debugging, I used the console.log function in Dojo to print out the values in the fName and fValue variables. When I did I got the following:
-In IE-
fValue=One
fName=One
fValue=Two
fName=Two
fValue=Three
fName=Three
-In FF-
fValue=One\n
fName=One\n
fValue=Two\n
fName=Two\n
fValue=Three
fName=Three
Notice that in Firefox we got a newline character "\n" at the end of the first two values, the last value doesn't have this. IE apparently strips these out. So to correct, I added the replace on this line:
var fName=String(node.innerHTML).replace(/\n/g,'');
This strips out the newlines and we are golden. So, at this point I can now use the FilteringSelect widget on Domino dialog list fields and don't have to do anything special to populate the options. I also no longer need to set the value (the selected option) though the html attributes of the field as I showed in the previous post on this ("dojoType='dijit.form.FilteringSelect' autocomplete='true' store='myKeywordStore' value='" + @If(myField!=""; myField; "Choose One") + "'").
The last thing I needed to do was decided how to get these code changes into my app. One option would be to change the source code, but I don't like that option as it makes maintenance more difficult. I have to remember the source changes I made and make sure that future updates don't overwrite them. So in this case I decided to create my own FilteringSelect widget. This is a simpler task than it might seem on the surface. In this case all I really want to do is override a single function. I created a new js file (stored as a page in my Domino app) called "dojomino/dijit/form/FilteringSelect.js". The entire code in the file is:
dojo.provide("dojomino.dijit.form.FilteringSelect");
dojo.require("dijit.form.FilteringSelect");
dojo.declare(
"dojomino.dijit.form.FilteringSelect",
dijit.form.FilteringSelect,
{
postMixInProperties: function(){
if(!this.hasDownArrow) {
this.baseClass = "dijitTextBox";
}
if(!this.store) {
var items = this.srcNodeRef ? dojo.query("> option", this.srcNodeRef).map(function(node){
node.style.display="none";
var fName=String(node.innerHTML).replace(/\n/g,'');
var fValue=node.getAttribute("value");
if (fValue==null || fValue=="")
fValue=fName;
return { value: fValue, name: fName };
}) : {};
this.store = new dojo.data.ItemFileReadStore({data: {identifier:this._getValueField(), items:items}});
if(items && items.length && !this.value){
this.value = items[this.srcNodeRef.selectedIndex != -1 ? this.srcNodeRef.selectedIndex : 0]
[this._getValueField()];
}
}
dijit.form.MappedTextBox.prototype.postMixInProperties.apply(this, arguments);
}
}
);
I needed to include the original FilteringSelect javascript and did so through the dojo.require statement. In my declaration I told it to inherit from "dijit.form.FilteringSelect". At this point I found the "postMixInProperties" function in the FilteringSelect source file which was as follows:
postMixInProperties: function(){
dijit.form.ComboBoxMixin.prototype.postMixInProperties.apply(this, arguments);
dijit.form.MappedTextBox.prototype.postMixInProperties.apply(this, arguments);
}
The line:
dijit.form.ComboBoxMixin.prototype.postMixInProperties.apply(this, arguments);
is what calls the ComboBox postMixInProperties where the code we want to update is. In this case I simply remove that call with my new version of the code and follow that with the call to the MappedTextBox's postMixInProperties. That done, everything works as expected.
Bookmark :
In a previous post (http://dojomino.com/dojomino/blog.nsf/d6plinks/DBOS-7A3TSW) I touched on the subject of using the FilteringSelect with Domino dialog list fields. That post talked about the format of the html created by Domino as well as an issue with Firefox. My solution was to have the options for the field provided by a json data store. Now this works fine but, as I new would happen, I came across a situation where I really didn't want to handle it that way. So, I decided to see if I could come up with a solution that would allow us to setup the field in Domino that way we normally would and have it just work.
To do this, I started by rooting through the Dojo source code for to find the part that takes the options sent to the browser and convert them for use in the FilteringSelect widget. I started by looking in the dijit/form/FilteringSelect.js. I didn't find the code I was looking for there, but found that the FilteringSelect extends "dijit.form.MappedTextBox, dijit.form.ComboBoxMixin". So I then go into dijit/form/ComboBox.js and review the ComboBoxMixin. It was there that I found the function "postMixInProperties" function. This function checks to see if there is a store for this widget already defined. If not, it creates one from the options for the select box. The code is:
if(!this.hasDownArrow){
this.baseClass = "dijitTextBox";
}
if(!this.store){
// if user didn't specify store, then assume there are option tags
var items = this.srcNodeRef ? dojo.query("> option", this.srcNodeRef).map(function(node){
node.style.display="none";
return { value: node.getAttribute("value"), name: String(node.innerHTML) };
}) : {};
this.store = new dojo.data.ItemFileReadStore({data: {identifier:this._getValueField(), items:items}});
// if there is no value set and there is an option list,
// set the value to the first value to be consistent with native Select
if(items && items.length && !this.value){
// For <select>, IE does not let you set the value attribute of the srcNodeRef (and thus dojo.mixin does not copy it).
// IE does understand selectedIndex though, which is automatically set by the selected attribute of an option tag
this.value = items[this.srcNodeRef.selectedIndex != -1 ? this.srcNodeRef.selectedIndex : 0]
[this._getValueField()];
}
}
As we can see, there is a dojo.query lookup for the option tags. It then goes through them and sets the value and name attributes in the "items" object. This is then used to create a new data store that is used by the widget. For our purposes there are a couple of problems that needed to be addressed. The first is if we don't have a "value" set for our options. In Domino dialog list field if we don't alias our values then they are sent to the brower as this:
<option>One
<option>Two
<option>Three
No value attribute is set. To address this I modified this portion of the code:
var items = this.srcNodeRef ? dojo.query("> option", this.srcNodeRef).map(function(node){
node.style.display="none";
return { value: node.getAttribute("value"), name: String(node.innerHTML) };
}) : {};
to this:
var items = this.srcNodeRef ? dojo.query("> option", this.srcNodeRef).map(function(node){
node.style.display="none";
var fName=String(node.innerHTML).replace(/\n/g,'');
var fValue=node.getAttribute("value");
if (fValue==null || fValue=="")
fValue=fName;
return { value: fValue, name: fName };
}) : {};
What we do here is check to see if there is a value for the option and if not, set it to be the same as the display text. We have to check to see if the value is both null or empty because of browser differences. In Firefox it is returned as null, in IE is comes back as an empty string.
The second problem that I ran into was specific to Firefox. I had referenced this in my previous post. My thinking at that time was that FF wasn't dealing with the fact that the option tags weren't being closed. However, after digging deeper I found that wasn't the issue at all. Doing a little deeper debugging, I used the console.log function in Dojo to print out the values in the fName and fValue variables. When I did I got the following:
-In IE-
fValue=One
fName=One
fValue=Two
fName=Two
fValue=Three
fName=Three
-In FF-
fValue=One\n
fName=One\n
fValue=Two\n
fName=Two\n
fValue=Three
fName=Three
Notice that in Firefox we got a newline character "\n" at the end of the first two values, the last value doesn't have this. IE apparently strips these out. So to correct, I added the replace on this line:
var fName=String(node.innerHTML).replace(/\n/g,'');
This strips out the newlines and we are golden. So, at this point I can now use the FilteringSelect widget on Domino dialog list fields and don't have to do anything special to populate the options. I also no longer need to set the value (the selected option) though the html attributes of the field as I showed in the previous post on this ("dojoType='dijit.form.FilteringSelect' autocomplete='true' store='myKeywordStore' value='" + @If(myField!=""; myField; "Choose One") + "'").
The last thing I needed to do was decided how to get these code changes into my app. One option would be to change the source code, but I don't like that option as it makes maintenance more difficult. I have to remember the source changes I made and make sure that future updates don't overwrite them. So in this case I decided to create my own FilteringSelect widget. This is a simpler task than it might seem on the surface. In this case all I really want to do is override a single function. I created a new js file (stored as a page in my Domino app) called "dojomino/dijit/form/FilteringSelect.js". The entire code in the file is:
dojo.provide("dojomino.dijit.form.FilteringSelect");
dojo.require("dijit.form.FilteringSelect");
dojo.declare(
"dojomino.dijit.form.FilteringSelect",
dijit.form.FilteringSelect,
{
postMixInProperties: function(){
if(!this.hasDownArrow) {
this.baseClass = "dijitTextBox";
}
if(!this.store) {
var items = this.srcNodeRef ? dojo.query("> option", this.srcNodeRef).map(function(node){
node.style.display="none";
var fName=String(node.innerHTML).replace(/\n/g,'');
var fValue=node.getAttribute("value");
if (fValue==null || fValue=="")
fValue=fName;
return { value: fValue, name: fName };
}) : {};
this.store = new dojo.data.ItemFileReadStore({data: {identifier:this._getValueField(), items:items}});
if(items && items.length && !this.value){
this.value = items[this.srcNodeRef.selectedIndex != -1 ? this.srcNodeRef.selectedIndex : 0]
[this._getValueField()];
}
}
dijit.form.MappedTextBox.prototype.postMixInProperties.apply(this, arguments);
}
}
);
I needed to include the original FilteringSelect javascript and did so through the dojo.require statement. In my declaration I told it to inherit from "dijit.form.FilteringSelect". At this point I found the "postMixInProperties" function in the FilteringSelect source file which was as follows:
postMixInProperties: function(){
dijit.form.ComboBoxMixin.prototype.postMixInProperties.apply(this, arguments);
dijit.form.MappedTextBox.prototype.postMixInProperties.apply(this, arguments);
}
The line:
dijit.form.ComboBoxMixin.prototype.postMixInProperties.apply(this, arguments);
is what calls the ComboBox postMixInProperties where the code we want to update is. In this case I simply remove that call with my new version of the code and follow that with the call to the MappedTextBox's postMixInProperties. That done, everything works as expected.

