Friday, October 12, 2007

The Multi-line Text box and its Malcontents - Part I

When using web browser enabled (InfoPath Forms Services) InfoPath templates that have been published to a SharePoint 2007 site, multi-line textboxes will only display with the height that was specified at design time. If the text box contains text that overflows the design size of the control, a scrollbar appears, and only the visible portion will print.

The alternative is the rich textbox control, however this requires a data source element of the xhtml data type, which may not always be appropriate (e.g. users can copy any html into the field), or available (e.g. when using 3rd party schemas). Fields using the rich textbox control are not editable in browsers other that Microsoft IE. In addition, using this data type can lead to complications when using forms for workflow related tasks. Xhtml data will not serialize to binary by default, requiring a custom serialization routine.

Despite these potential drawbacks, if you want your multi-line text to always print without clipping, you're pretty much going to be stuck with xhtml. What would be great is if the multi-line dynamically expanded as it can be made to do in the InfoPath client. It's a bit of a mystery why Microsoft didn't include this sort of functionality in the Forms Services rendering - after all the rich text box has this functionality and works well. The code below demonstrates how such functionality might work (in IE). In Part II, I'll show that it might be possible to modify the javascript that renders the InfoPath forms on the client to provide similar functionality, and why you probably won't want to do this.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title></title>
<style type="text/css">
    .textbox  {border-style:solid;border-width:1px;}
    .textbox1 {width:400px; height:100px;}
    .textbox2 {width:400px; height:100px;}
</style>
</head>
<body>
<div id="objHtmlContainer"></div>
<script type="text/javascript">
String.prototype.repeat = function(n) { return new Array(n + 1).join(this); }
var objTextBoxesData = [["TextBox1","textbox textbox1",false,"Fixed Multi-Line ".repeat(20)],["TextBox2","textbox textbox2",true,"Dynamic Multi-Line ".repeat(20)]]
var arrHtmlToInsertBuilder = new Array();
function TextBox_OnAfterCreate(objControl) {
      if(objControl.onkeyup)
            objControl.fireEvent("onkeyup");
}
function TextBox_Resize(obj) {
    var minHeight = obj.getAttribute("minHeight");
    if (minHeight == null) {
        minHeight = obj.offsetHeight;
        obj.setAttribute("minHeight", minHeight);
    }
    if (obj.scrollHeight != obj.clientHeight) {
      var height = obj.offsetHeight + obj.scrollHeight - obj.clientHeight
      obj.style.height = (height < minHeight)? minHeight : height;
    }
}
function TextBox_OnScroll() {
    var e = window.event.srcElement;
    var tr = e.createTextRange();
    if (tr.offsetTop > 0)
        tr.scrollIntoView();
}
function TextBox_OnCutOrPaste() {
    var e = window.event.srcElement;
    window.setTimeout(function(){TextBox_Resize(e);}, 25);
}
function TextBox(objTextBoxData) { 
    this._id = objTextBoxData[0];
    this._class = objTextBoxData[1];
    this._isDynamic = objTextBoxData[2];
    this._value = objTextBoxData[3]; 
}
TextBox.prototype.render = function() {
      arrHtmlToInsertBuilder.push("<textarea id=\"" + this._id + "\" class=\"" + this._class + "\" ");
      if (this._isDynamic)
            arrHtmlToInsertBuilder.push("style=\"overflow:hidden;\" oncut=\"TextBox_OnCutOrPaste();\" onpaste=\"TextBox_OnCutOrPaste();\" onkeydown=\"TextBox_Resize(this);\" onscroll=\"TextBox_OnScroll();\" onkeyup=\"TextBox_Resize(this);\" ");
      arrHtmlToInsertBuilder.push(">");
      arrHtmlToInsertBuilder.push(this._value);
      arrHtmlToInsertBuilder.push("</textarea><br />");
}
TextBox.prototype.doPostCreate = function () {
      var e = document.getElementById(this._id);
    window.setTimeout(function(){TextBox_OnAfterCreate(e);}, 500);
}
var staticTextBox = new TextBox(objTextBoxesData[0]);
var dynamicTextBox = new TextBox(objTextBoxesData[1]);
staticTextBox.render();
dynamicTextBox.render();
var objHtmlContainer = document.getElementById("objHtmlContainer");
objHtmlContainer.innerHTML = arrHtmlToInsertBuilder.join('');
staticTextBox.doPostCreate();
dynamicTextBox.doPostCreate();
</script>
</body>
</html>

2 comments:

RC said...

Thanks for the post. When will Part II be available?

txs8311 said...

Sure. Both Part II and III will be out this week.