Thursday, December 6, 2007

Move or Copy SharePoint Document Library Files Programmatically

In this post I published some code that took a look at using FrontPage Server Extentions to upload a file to a document library where the requirement was to be able to save metadata along with the document and not use the API. Here are a few more static methods that can be inserted into that code to provide simple file moving or copying functionality from a remote client. Documentation about the move RPC method can be found here. To copy rather than move the file, change the doCopy flag to true. An alternative method for remotely moving a file from one document library to another location is to use the copy web service which is documented on MSDN here. For local SharePoint calls, the appropriate API method is SPListItem.CopyTo or SPFile.MoveTo.

Update: You can download a comprehensive c# class library to automate RPC calls - including moving files (even between web sites or servers). See this blog post for more information.

        public static bool Move(string oldUrl, string newUrl)
        {
            string result = "";
            string webUrl = GetWebURL(oldUrl);
            oldUrl = oldUrl.Substring(webUrl.Length + 1);
            newUrl = newUrl.Substring(webUrl.Length + 1);
            return Move(webUrl, oldUrl, newUrl, out result);
        }
 
        public static bool Move(string webUrl, string oldUrl, string newUrl, out string result)
        {
            EnsureFolders(webUrl, newUrl);
            string renameOption = "findbacklinks";
            string putOption = "overwrite,createdir,migrationsemantics";
            bool doCopy = false;
            string method = "method=move+document%3a12.0.4518.1016&service_name=%2f&oldUrl={0}&newUrl={1}&url_list=[]&rename_option={2}&put_option={3}&docopy={4}";
            method = String.Format(method, oldUrl, newUrl, renameOption, putOption, doCopy.ToString().ToLower());
            try
            {
                using (WebClient webClient = new WebClient())
                {
                    webClient.Credentials = CredentialCache.DefaultCredentials;
                    webClient.Headers.Add("Content-Type", "application/x-vermeer-urlencoded");
                    webClient.Headers.Add("X-Vermeer-Content-Type", "application/x-vermeer-urlencoded");
                    result = Encoding.UTF8.GetString(webClient.UploadData(webUrl + "/_vti_bin/_vti_aut/author.dll", "POST", Encoding.UTF8.GetBytes(method)));
                    if (result.IndexOf("\n<p>message=successfully") < 0)
                        throw new Exception(result);
                }
            }
            catch (Exception ex)
            {
                result = ex.Message;
                return false;
            }
            return true;
        }
 
        public static void EnsureFolders(string rootUrl, string folderUrl)
        {
            StringBuilder sb = new StringBuilder(rootUrl.TrimEnd('/'));
            string[] segments = folderUrl.Split('/');
            for (int i = 0; i < segments.Length - 1; i++)
            {
                sb.Append("/");
                sb.Append(segments[i]);
                CreateFolder(sb.ToString());
            }
        }
 
        public static bool CreateFolder(string folderURL)
        {
            try
            {
                WebRequest request = WebRequest.Create(folderURL);
                request.Credentials = CredentialCache.DefaultCredentials;
                request.Method = "MKCOL";
                WebResponse response = request.GetResponse();
                response.Close();
                return true;
            }
            catch (WebException)
            {
                return false;
            }
        }

Tuesday, December 4, 2007

Redirecting to an Error Page

The SPUtility.TransferToErrorPage method is a convenient way to redirect users to an error page in the event that an unhandled exception occurs. This post (from Wellington no less!) covers this method and a few other static methods from the SPUtility class well. One thing worth pointing out is the use of the second overload to the TransferToErrorPage method. This allows a link to appear in the message so that users can be redirected to another page (for example to a long running job status list item for more information on the error). To use this override, pass the link text and link url and then include a '{0}' (like the composite format syntax) in your message where you need the link to appear.

Wednesday, November 14, 2007

Upload a File to a SharePoint Document Library - Part II

In Part I I posted code that used the SharePoint lists service to help in uploading files with meta data to a sharepoint document library. Here is another variation, this time using FrontPage Server Extensions and RPC. Note that this code won't create more than one new folder - if this is a problem you may want to ensure the full folder path with code like that from the first article.

Update: You can download a comprehensive c# class library to automate RPC calls - including uploading files to a SharePoint document library. See this blog post for more information.

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Net;
using System.Web;
 
namespace DevHoleDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            Dictionary<string, object> properties = new Dictionary<string, object>();
            properties.Add("Title", "Test Title");
            //Create or overwrite text file test.txt in 'Docs' document library creating folder 'Test Folder' as required.
            DocLibHelper.Upload("http://localhost/Docs/Test Folder/test.txt", System.Text.Encoding.ASCII.GetBytes("Test text."), properties);
        }
    }
 
    static class DocLibHelper
    {
        static string EncodeMetaInfo(Dictionary<string, object> metaInfo)
        {
            if (metaInfo == null) return "";
            StringBuilder sb = new StringBuilder();
            foreach (KeyValuePair<string, object> kvp in metaInfo)
            {
                if (kvp.Value != null)
                {
                    string fieldName = kvp.Key; // note: field names are case sensitive
                    switch (fieldName)
                    {
                        case "Title":
                            fieldName = "vti_title";
                            break;
                    }
                    string data = EscapeVectorChars(kvp.Value.ToString());
                    string dataTypeCode = "S";
                    switch (kvp.Value.GetType().FullName)
                    {
                        case "System.Boolean":
                            dataTypeCode = "B";
                            break;
                        case "System.DateTime":
                            data = ((DateTime)kvp.Value).ToString("s") + "Z";
                            break;
                    }
                    sb.AppendFormat("{0};{1}W|{2};", fieldName, dataTypeCode, data);
                }
            }
            return HttpUtility.UrlEncode(sb.ToString().TrimEnd(';'));
        }
 
        static string EscapeVectorChars(string value)
        {
            StringBuilder sb = new StringBuilder();
            foreach (char c in value)
            {
                switch (c)
                {
                    case ';':
                    case '|':
                    case '[':
                    case ']':
                    case '\\':
                        sb.Append("\\");
                        break;
                }
                sb.Append(c);
            }
            return sb.ToString();
        }
 
        public static string GetWebURL(string url)
        {
            try
            {
                url = url.Substring(0, url.LastIndexOf("/"));
                try
                {
                    using (WebClient webClient = new WebClient())
                    {
                        webClient.Credentials = CredentialCache.DefaultCredentials;
                        webClient.Headers.Add("Content-Type", "application/x-vermeer-urlencoded");
                        webClient.Headers.Add("X-Vermeer-Content-Type", "application/x-vermeer-urlencoded");
                        byte[] data = Encoding.UTF8.GetBytes("method=open+service%3a12.0.4518.1016&service_name=%2f");
                        string result = Encoding.UTF8.GetString(webClient.UploadData(url + "/_vti_bin/_vti_aut/author.dll", "POST", data));
                        if (result.IndexOf("\n<li>status=327684") == -1)
                            return url;
                        throw new Exception();
                    }
                }
                catch
                {
                    return GetWebURL(url);
                }
            }
            catch
            {
                return null;
            }
        }
 
        public static bool Upload(string destinationUrl, byte[] bytes, Dictionary<string, object> metaInfo)
        {
            string result = "";
            string webUrl = GetWebURL(destinationUrl);
            string documentName = destinationUrl.Substring(webUrl.Length + 1);
            return Upload(webUrl, documentName, bytes, metaInfo, out result);
        }
 
        public static bool Upload(string webUrl, string documentName, byte[] bytes, Dictionary<string, object> metaInfo, out string result)
        {
            string putOption = "overwrite,createdir,migrationsemantics"// see http://msdn2.microsoft.com/en-us/library/ms455325.aspx
            string comment = null;
            bool keepCheckedOut = false;
            string method = "method=put+document%3a12.0.4518.1016&service_name=%2f&document=[document_name={0};meta_info=[{1}]]&put_option={2}&comment={3}&keep_checked_out={4}\n";
            method = String.Format(method, documentName, EncodeMetaInfo(metaInfo), putOption, HttpUtility.UrlEncode(comment), keepCheckedOut.ToString().ToLower());
            List<byte> data = new List<byte>();
            data.AddRange(Encoding.UTF8.GetBytes(method));
            data.AddRange(bytes);
            try
            {
                using (WebClient webClient = new WebClient())
                {
                    webClient.Credentials = CredentialCache.DefaultCredentials;
                    webClient.Headers.Add("Content-Type", "application/x-vermeer-urlencoded");
                    webClient.Headers.Add("X-Vermeer-Content-Type", "application/x-vermeer-urlencoded");
                    result = Encoding.UTF8.GetString(webClient.UploadData(webUrl + "/_vti_bin/_vti_aut/author.dll", "POST", data.ToArray()));
                    if (result.IndexOf("\n<p>message=successfully") < 0)
                        throw new Exception(result);
                }
            }
            catch (Exception ex)
            {
                result = ex.Message;
                return false;
            }
            return true;
        }
    }
}
 

Monday, November 5, 2007

Provide Status Updates for Long Running Operation Jobs

A simple way to give users some feedback on the progress status of long running jobs is provided by the Microsoft.SharePoint.Publishing assembly. When a class that inherits from the LongRunningOperationJob class has its start method invoked, a list item is added to the Long Running Operation Status list in the root web. Providing a link to the LongRunningOperationProgress.aspx page along with the guid of this list item will display an update page with a progress bar similar to that shown below:

Long Running Job Status Page

A LongRunningOperationJob might not necessary be designed to run as a SPJobDefinition timer job, although there's no reason you couldn't combine the two to provide more granular information than the _admin/ServiceRunningJobs.aspx page provides.

Some example code follows. Before running it, make sure that your site collection includes a list named 'Long Running Operation Status' at the root web. This list is created when you install the Office SharePoint Server Publishing Infrastructure feature at the Site Collection Features level. The list apparently isn't removed when this feature is uninstalled so it's not necessary to have the Publishing Infrastructure feature activated if you don't want it to be - just activate then deactivate it.

The Microsoft.SharePoint.Publishing assembly can be found here:
%Program Files%\Microsoft Office Servers\12.0\Bin\Microsoft.SharePoint.Publishing.dll

using System;
using System.Diagnostics;
using System.Threading;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Publishing;
using Microsoft.SharePoint.Publishing.Internal;
 
namespace DevHoleDemo
{
    class Program
    {
        const string STATUS_LIST_NAME = "Long Running Operation Status";
        const string PROGRESS_PAGE_URL = "/_layouts/LongRunningOperationProgress.aspx";
        static void Main(string[] args)
        {
            string webURL = "http://localhost/";
            using (SPSite site = new SPSite(webURL))
            {
                using (SPWeb web = site.RootWeb)
                {
                    LongRunningJob longRunningJob = new LongRunningJob();
                    longRunningJob.Title = "Demo Long Running Job";
                    longRunningJob.TotalOperationsToBePerformed = 30;
                    longRunningJob.RedirectWhenFinished = true;
                    longRunningJob.NavigateWhenDoneUrl = web.Url + "/" + STATUS_LIST_NAME;
                    longRunningJob.Start(web);
                    Process.Start("iexplore.exe", string.Format("{0}{1}?JobId={2}", web.Url, PROGRESS_PAGE_URL, longRunningJob.JobId));
                    LongRunningOperationStatus jobStatus;
                    do
                    {
                        jobStatus = LongRunningOperationStatus.GetJob(site, longRunningJob.JobId);
                        Console.WriteLine(jobStatus.StatusDescription);
                        Thread.Sleep(1000);
                    } while (jobStatus.Status != LongRunningOperationJob.OperationStatus.Successful);
                }
            }
        }
    }
 
    class LongRunningJob : LongRunningOperationJob
    {
        public override void DoWork()
        {
            for (this.OperationsPerformed = 0; this.OperationsPerformed < this.TotalOperationsToBePerformed; this.OperationsPerformed++)
            {
                this.StatusDescription = string.Format("im in ur long running job, doing ur work {0} of {1}...", this.OperationsPerformed, this.TotalOperationsToBePerformed);
                this.UpdateStatus();
                Thread.Sleep(1000);
            }
        }
    }
}

Tuesday, October 23, 2007

Upload a File to a SharePoint Document Library - Part I

The following helper class demonstrates a few techniques that allow documents to be uploaded to a SharePoint document library programmatically without using the API or a custom web service. You don't need to specify a document library name, and it will create any folders specified in the URL as required. File meta data will be updated if any properties are passed.

To use this code add a reference to the SharePoint Lists service (/_vti_bin/Lists.asmx) and name it ‘ListsService’. The code was written against MOSS 2007.

To download files use the GetItem method of the SharePoint Copy service (/_vti_bin/Copy.asmx). While it’s possible to upload files using the CopyIntoItems method of this service, it won’t create folders as needed, and you’d probably want to remove the copy link that is created.

It's also possible to use Front Page Server Extensions and RPC calls to upload files with meta data - the code for which is a bit more efficient as it doesn't require web service calls. Using RPC calls is covered in Part II.

Update: You can download a comprehensive c# class library to automate RPC calls - including uploading files to a SharePoint document library. See this blog post for more information.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using System.Xml;
 
namespace DevHoleDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            DocLibHelper docLibHelper = new DocLibHelper();
            Dictionary<string, object> properties = new Dictionary<string, object>();
            properties.Add("Title", "Test Title");
            //Create or overwrite text file test.txt in 'Docs' document library creating folder 'Test Folder' as required.
            docLibHelper.Upload("http://localhost/Docs/Test Folder/test.txt", System.Text.Encoding.ASCII.GetBytes("Test text."), properties);
        }
    }
 
    public class DocLibHelper
    {
        ListsService.Lists m_listService;
        ICredentials m_credentials;
        ListInfoCollection m_lists;
 
        public DocLibHelper()
        {
            m_credentials = CredentialCache.DefaultCredentials;
            m_listService = new ListsService.Lists();
            m_listService.Credentials = m_credentials;
            m_lists = new ListInfoCollection(m_listService);
        }
 
        public class ListInfo
        {
            public string m_rootFolder;
            public string m_listName;
            public string m_version;
            public string m_webUrl;
            public ListInfo(XmlNode listResponse)
            {
                m_rootFolder = listResponse.Attributes["RootFolder"].Value + "/";
                m_listName = listResponse.Attributes["ID"].Value;
                m_version = listResponse.Attributes["Version"].Value;
            }
            public bool IsMatch(string url)
            {
                try
                {
                    url += "/";
                    return url.Substring(0, m_rootFolder.Length) == m_rootFolder;
                }
                catch { }
                return false;
            }
        }
 
        public class ListInfoCollection : IEnumerable<ListInfo>
        {
            ListsService.Lists m_listService;
            Dictionary<string, ListInfo> m_lists = new Dictionary<string, ListInfo>();
            public ListInfoCollection(ListsService.Lists listService)
            {
                m_listService = listService;
            }
            public IEnumerator<ListInfo> GetEnumerator()
            {
                return m_lists.Values.GetEnumerator();
            }
            IEnumerator IEnumerable.GetEnumerator()
            {
                return this.GetEnumerator();
            }
            public ListInfo Find(FileInfo fileInfo)
            {
                if (m_lists.ContainsKey(fileInfo.LookupName))
                    return m_lists[fileInfo.LookupName];
                foreach (ListInfo li in m_lists.Values)
                    if (li.IsMatch(fileInfo.LookupName)) return li;
                string webUrl = fileInfo.m_URL;
                if (fileInfo.m_listInfo != null && !string.IsNullOrEmpty(fileInfo.m_listInfo.m_listName))
                {
                    ListInfo listInfo = new ListInfo(CallService(ref webUrl, delegate { return m_listService.GetList(fileInfo.LookupName); }));
                    listInfo.m_webUrl = webUrl;
                    return listInfo;
                }
                else
                {
                    XmlNode lists = CallService(ref webUrl, delegate { return m_listService.GetListCollection(); });
                    if (lists == null) throw new Exception("Could not find web.");
                    //Find list by RootFolder (which doesn't seem to be populated in GetListCollection response so must iterate GetList response)
                    foreach (XmlNode list in lists.ChildNodes)
                    {
                        ListInfo listInfo = new ListInfo(m_listService.GetList(list.Attributes["Name"].Value));
                        listInfo.m_webUrl = webUrl;
                        m_lists.Add(listInfo.m_listName, listInfo);
                        if (listInfo.IsMatch(fileInfo.LookupName))
                            return listInfo;
                    }
                }
                throw new Exception("Could not find list.");
            }
            private delegate XmlNode ServiceOperation();
            private XmlNode CallService(ref string webURL, ServiceOperation serviceOperation)
            {
                try
                {
                    webURL = webURL.Substring(0, webURL.LastIndexOf("/"));
                    try
                    {
                        m_listService.Url = webURL + "/_vti_bin/Lists.asmx";
                        return serviceOperation();
                    }
                    catch
                    {
                        return CallService(ref webURL, serviceOperation);
                    }
                }
                catch
                {
                    webURL = null;
                    return null;
                }
            }
        }
 
        public class FileInfo
        {
            public string m_URL;
            public byte[] m_bytes;
            public Dictionary<string, object> m_properties;
            public ListInfo m_listInfo;
            public bool m_ensureFolders = true;
            private Uri m_uri;
            public bool HasProperties
            {
                get { return m_properties != null && m_properties.Count > 0; }
            }
            public string RelativeFilePath
            {
                get { return m_URL.Substring(m_URL.IndexOf(m_listInfo.m_rootFolder) + 1); }
            }
            public Uri URI
            {
                get
                {
                    if (m_uri == null) m_uri = new Uri(m_URL);
                    return m_uri;
                }
            }
            public string LookupName
            {
                get
                {
                    if (m_listInfo != null && !string.IsNullOrEmpty(m_listInfo.m_listName))
                        return m_listInfo.m_listName;
                    return URI.LocalPath;
                }
            }
            public FileInfo(string url, byte[] bytes, Dictionary<string, object> properties)
            {
                m_URL = url.Replace("%20", " ");
                m_bytes = bytes;
                m_properties = properties;
            }
        }
 
        public bool Upload(string destinationUrl, byte[] bytes, Dictionary<string, object> properties)
        {
            return Upload(new FileInfo(destinationUrl, bytes, properties));
        }
 
        public bool Upload(FileInfo fileInfo)
        {
            if (fileInfo.HasProperties)
                fileInfo.m_listInfo = m_lists.Find(fileInfo);
            bool result = TryToUpload(fileInfo);
            if (!result && fileInfo.m_ensureFolders)
            {
                string root = fileInfo.URI.AbsoluteUri.Replace(fileInfo.URI.AbsolutePath, "");
                for (int i = 0; i < fileInfo.URI.Segments.Length - 1; i++)
                {
                    root += fileInfo.URI.Segments[i];
                    if (i > 1) CreateFolder(root);
                }
                result = TryToUpload(fileInfo);
            }
            return result;
        }
 
        private bool TryToUpload(FileInfo fileInfo)
        {
            try
            {
                WebRequest request = WebRequest.Create(fileInfo.m_URL);
                request.Credentials = m_credentials;
                request.Method = "PUT";
                byte[] buffer = new byte[1024];
                using (Stream stream = request.GetRequestStream())
                using (MemoryStream ms = new MemoryStream(fileInfo.m_bytes))
                    for (int i = ms.Read(buffer, 0, buffer.Length); i > 0; i = ms.Read(buffer, 0, buffer.Length))
                        stream.Write(buffer, 0, i);
                WebResponse response = request.GetResponse();
                response.Close();
                if (fileInfo.HasProperties)
                {
                    StringBuilder sb = new StringBuilder();
                    sb.Append("<Method ID='1' Cmd='Update'><Field Name='ID'/>");
                    sb.AppendFormat("<Field Name='FileRef'>{0}</Field>", fileInfo.m_URL);
                    foreach (KeyValuePair<string, object> property in fileInfo.m_properties)
                        sb.AppendFormat("<Field Name='{0}'>{1}</Field>", property.Key, property.Value);
                    sb.Append("</Method>");
                    System.Xml.XmlElement updates = (new System.Xml.XmlDocument()).CreateElement("Batch");
                    updates.SetAttribute("OnError", "Continue");
                    updates.SetAttribute("ListVersion", fileInfo.m_listInfo.m_version);
                    updates.SetAttribute("PreCalc", "TRUE");
                    updates.InnerXml = sb.ToString();
                    m_listService.Url = fileInfo.m_listInfo.m_webUrl + "/_vti_bin/Lists.asmx";
                    XmlNode updatesResponse = m_listService.UpdateListItems(fileInfo.m_listInfo.m_listName, updates);
                    if (updatesResponse.FirstChild.FirstChild.InnerText != "0x00000000")
                        throw new Exception("Could not update properties.");
                }
                return true;
            }
            catch (WebException)
            {
                return false;
            }
        }
 
        private bool CreateFolder(string folderURL)
        {
            try
            {
                WebRequest request = WebRequest.Create(folderURL);
                request.Credentials = m_credentials;
                request.Method = "MKCOL";
                WebResponse response = request.GetResponse();
                response.Close();
                return true;
            }
            catch (WebException)
            {
                return false;
            }
        }
    }
}

Thursday, October 18, 2007

The Multi-line Text Box and its Malcontents - Part III

In Part I and Part II I posted about the problem caused by the static size of the InfoPath Forms Services multi-line text box. Generally the workaround is to use the rich text control, and in fact this is the workaround suggested by Microsoft for a separate issue - KB931426 - whereby the malformed content of a multi-line text box (actually just any old words that include a space) can cause Forms Services to lose it when the form has been designed to be submitted via email.

You'll know something's up when you get the message "There has been an error while processing the form." returned to the browser, along with "Exception Message: Reference to undeclared entity 'nbsp'" or "System.Xml.XmlException: Reference to undeclared entity 'nbsp'" in the diagnostic log.

So the suggested workaround is to change your schema and use the rich edit control. If you'd really rather not do this, there is actually an alternative method. If the text box doesn't contain spaces - no issue, so the workaround is to strip the text box of spaces, replace it with something that looks like a space (ASCII 160 - the code for the HTML non-breaking space character '&nbsp;'), submit the form using your email data connection, then reverse out the character replacement.

Easy enough. You can even do all this through rules. This post on the InfoPath Team Blog demonstrates a method for using a secondary data source to reference non printable characters (in this case to insert carriage return / line feed characters). First up, you'll want to add a resource file with the following content (the only attribute that you'll actually be using is the nbsp - the others are just for fun)...

<?xml version="1.0" encoding="UTF-8"?>

<characters cr="&#xD;" lf="&#xA;" crlf="&#xD;&#xA;" nbsp="&#xA0;" />



Next, edit the form's submit options to submit using custom rules and add a rule that has a series of actions that first strips the spaces, e.g. set each multi-line text box field's value with a formula like the following, using the technique from the Team Blog post:

translate(address, " ", @nbsp)

Follow these actions with an action that submits the form.

Lastly, add however many "Set a field's value" actions as required to reverse out the character replacement, e.g. using a formula like:

translate(address, @nbsp, " ")

Wednesday, October 17, 2007

The Multi-line Text Box and its Malcontents - Part II

In Part I I posted an example that demonstrated dynamic resizing (past a minimum height) - giving multiline text box resizing functionality (in IE7 at least) similar to the rich edit text box. So the next question is, how difficult would it be to modify the javascript that renders InfoPath forms on the client to get around this print clipping problem? Well unfortunately although it might not be that hard, it's unlikely you're going to want to try it. According to this post by Liam Cleary (a SharePoint MVP) you shouldn't really modify server files - presumably because of the EULA or the risk of breaking all the SharePoint web applications on your IIS server.

Any other options? Well, you might think that using an expression box (which can be set to expand to show all text) on a print view could be a way to get around the problem of overflow text being clipped. There's only one issue - the expression box in all probability renders as an html <span> element - so any carriage return / line feeds are going to be ignored. It looks like any html is also escaped, so again, aside from messing with the rendering javascript (replacing \r\n with <br /> say), it's probably not going to give you the result you're after.

It looks like unless an update is released, the rich text box is it as far as text edit controls that will resize dynamically on a browser form. Form designers not wanting to use this control who want all text to be visible on screen or in print will have to make sure that control is sufficiently large at design time.

All of that said, the TextBox Render function is right there in plain text for everyone to see, so if I had to take a bit of a guess as to how the SharePoint developers might go about creating a quick fix for this problem in the future - one scenario could see code along the lines of that at the bottom of this post.

In Part III I’ll address another issue with the maligned text box and browser-enabled forms - KB931426. The Microsoft workaround is to use the rich text box, but for people who want to avoid this there is an alternative workaround.

9333:

ErrorVisualization.ShowAsterisk(objControl);}}if(objControl.onkeyup){objControl.fireEvent("onkeyup");}}


9405:

{if(UserAgentInfo.strBrowser==1){arrHtmlToInsertBuilder.push(" style=\"overflow:hidden;\" oncut=\"TextBox_OnCutOrPaste();\" onpaste=\"TextBox_OnCutOrPaste();\" onkeydown=\"TextBox_Resize(this);\" onscroll=\"TextBox_OnScroll();\" onkeyup=\"TextBox_Resize(this);\" ");}arrHtmlToInsertBuilder.push(arrTemplate[1]);


9414:

function TextBox_OnScroll(){var e=window.event.srcElement;var tr=e.createTextRange();if(tr.offsetTop>0)tr.scrollIntoView();TextBox_Resize(e);}function TextBox_OnCutOrPaste(){var e=window.event.srcElement;window.setTimeout(function(){TextBox_Resize(e);}, 25)}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;}};TextBox.OnFocus = function (objControl, objEvent)