Wednesday, November 7, 2012

Anonymous Access and TaxonomyWebTaggingControl WebTaggingDialog

Trying to use TaxonomyWebTaggingControl with anonymous user? Control works fine, but the popup dialog (WebTaggingDialog). It will ask you for authentication no matter what. The reason is that it is an application page, which by default does not allow anonymous access.

So far I was able to find only one way to fix it, and it is described in Ironworks blog. You have to modify actual application page (WebTaggingDialog.aspx) located in LAYOUTS folder, by adding the following code:

<script runat="server" type="text/C#">
protected override bool AllowAnonymousAccess
{ 
    get
    { 
        return true; 
    } 
}
</script>

Please remember that this change is not supported by Microsoft and could be overridden during service pack deployment.

Thursday, November 1, 2012

SharePoint 20+ Items Lookup Positioning Issue

I was working with custom master page and stumbled on interesting problem, related to SharePoint lookup field with 20+ elements being rendered in IE in wrong place. There is plenty of information on how this control behaves (i.e. here), but not that much about how it actually works. I had to figure it out, in order to fix my problem, so hopefully this would help someone ;)Please read Boris Gomiunik's blog post before proceeding, cause it explains the basics of it.

When you click on the lookup field, the following script is called from core.js:

function ShowDropdown(textboxId)
{ULSsa6:;
 var ctrl=document.getElementById(textboxId);
 var str=ctrl.value;
 var opt=EnsureSelectElement(ctrl, ctrl.opt);
 ctrl.match=FilterChoice(opt, ctrl, "", ctrl.value);
 ctrl.focus();
}


function EnsureSelectElement(ctrl, strId)
{ULSsa6:;
 var select=document.getElementById(strId);
 if (select==null)
 {
  select=document.createElement("SELECT");
  ctrl.parentNode.appendChild(select);
  select.outerHTML="<select id=\""+strId+"\" ctrl=\""+ctrl.id+"\" class=\"ms-lookuptypeindropdown\" name=\""+strId+"\" style=\"display:none\" onfocusout=\"OptLoseFocus(this)\"></select>";
  FilterChoice(select, ctrl, ctrl.value, "");
 }
 return document.getElementById(strId);;
}

function FilterChoice(opt, ctrl, strVal, filterVal)
{ULSsa6:;
 var i;
 var cOpt=0;
 var bSelected=false;
 var strHtml="";
 var strId=opt.id;
 var strName=opt.name;
 var strMatch="";
 var strMatchVal="";
 var strOpts=ctrl.choices;
 var rgopt=strOpts.split("|");
 var x=AbsLeft(ctrl);
 var y=AbsTop(ctrl)+ctrl.offsetHeight;
 var elmWorkspace=document.getElementById("s4-workspace");
 if(elmWorkspace)
  y -=AbsTop(elmWorkspace);
 var strHidden=ctrl.optHid;
 var iMac=rgopt.length - 1;
 var iMatch=-1;
 var unlimitedLength=false;
 var strSelectedLower="";
 if (opt !=null && opt.selectedIndex >=0)
 {
  bSelected=true;
  strSelectedLower=opt.options[opt.selectedIndex].innerText;
 }
 for (i=0; i < rgopt.length; i=i+2)
 {
  var strOpt=rgopt[i];
  while (i < iMac - 1 && rgopt[i+1].length==0)
  {
   strOpt=strOpt+"|";
   i++;
   if (i < iMac - 1)
   {
    strOpt=strOpt+rgopt[i+1];
   }
   i++;
  }
  var strValue=rgopt[i+1];
  var strLowerOpt=strOpt.toLocaleLowerCase();
  var strLowerVal=strVal.toLocaleLowerCase();
  if (filterVal.length !=0)
   bSelected=true;
  if (strLowerOpt.indexOf(strLowerVal)==0)
  {
   var strLowerFilterVal=filterVal.toLocaleLowerCase();
   if ((strLowerFilterVal.length !=0) && (strLowerOpt.indexOf(strLowerFilterVal)==0) && (strMatch.length==0))
    bSelected=false;
   if (strLowerOpt.length > 20)
   {
    unlimitedLength=true;
   }
   if (!bSelected || strLowerOpt==strSelectedLower)
   {
    strHtml+="<option selected value=\""+strValue+"\">"+STSHtmlEncode(strOpt)+"</option>";
    bSelected=true;
    strMatch=strOpt;
    strMatchVal=strValue;
    iMatch=i;
   }
   else
   {
    strHtml+="<option value=\""+strValue+"\">"+STSHtmlEncode(strOpt)+"</option>";
   }
   cOpt++;
  }
 }
 var strHandler=" ondblclick=\"HandleOptDblClick()\" onkeydown=\"HandleOptKeyDown()\"";
 var strOptHtml="";
 if (unlimitedLength)
 {
  strOptHtml="<select tabIndex=\"-1\" ctrl=\""+ctrl.id+"\" name=\""+strName+"\" id=\""+strId+"\""+strHandler;
 }
 else
 {
  strOptHtml="<select class=\"ms-lookuptypeindropdown\" tabIndex=\"-1\" ctrl=\""+ctrl.id+"\" name=\""+strName+"\" id=\""+strId+"\""+strHandler;
 }
 if (cOpt==0)
 {
  strOptHtml+=" style=\"display:none;position:absolute;z-index:2;left:"+x+   "px;top:"+y+   "px\" onfocusout=\"OptLoseFocus(this)\"></select>";
 }
 else
 {
  strOptHtml+=" style=\"position:absolute;z-index:2;left:"+x+   "px;top:"+y+   "px\""+   " size=\""+(cOpt <=8 ? cOpt : 8)+"\""+   (cOpt==1 ? "multiple=\"true\"" : "")+   " onfocusout=\"OptLoseFocus(this)\">"+   strHtml+   "</select>";
 }
 opt.outerHTML=strOptHtml;
 var hid=document.getElementById(strHidden);
 if (iMatch !=0 || rgopt[1] !="0" )
  hid.value=strMatchVal;
 else
  hid.value="0";
 if (iMatch !=0 || rgopt[1] !="0" )
  return strMatch;
 else return "";
}

It is generating a new <select> element, and placing it right after the input box that you have clicked. It is setting its position as absolute, and calculating the left and top value from the start of the page. This is done using AbsLeft and AbsTop functions.

function AbsLeft(obj)
{ULSxSy:;
 var x=obj.offsetLeft;
 var parent=obj.offsetParent;
 while (parent !=null && parent.tagName !="BODY")
 {
  x+=parent.offsetLeft;
  parent=parent.offsetParent;
 }
 if (parent !=null)
  x+=parent.offsetLeft;
 return x;
}
function AbsTop(obj)
{ULSxSy:;
 var y=obj.offsetTop;
 var parent=obj.offsetParent;
 while (parent !=null && parent.tagName !="BODY")
 {
  y+=parent.offsetTop;
  parent=parent.offsetParent;
 }
 if (parent !=null)
  y+=parent.offsetTop;
 return y;
}

It sets an exact absolute calculated position for this element:

<SELECT 
style="Z-INDEX: 2; POSITION: absolute; DISPLAY: none; TOP: 436px; LEFT: 414px" onkeydown=HandleOptKeyDown() 
id=_Select 
onfocusout=OptLoseFocus(this) 
ondblclick=HandleOptDblClick() 
tabIndex=-1 
size=8 
name=_Select ctrl="ctl00_m_g_a0c896e4_02dd_41b4_87ea_58cdb333059b_ctl00_ctl05_ctl04_ctl00_ctl00_ctl04_ctl00_ctl01">

The problem is that if you have a div driven master page and have a div with the position:relative somewhere in between of the top of the page and select element, our select element is being placed relative to it, instead of relative to the top left corner of the page. If you have this problem, you need to find this element and override it to be "static".