terça-feira, 17 de fevereiro de 2009

.NET Framework 3.5 - LINQ

Introdução ao LINQ (Language-Integrated Query)

LINQ é realmente um passo inovador no Visual Studio 2008 e .NET Framework 3.5, servindo como [ponte] entre o mundo dos objetos e o mundo dos dados.

O interessante deste recurso é o fato de construir suas [Queries] usando coleções fortemente tipadas de objetos, como uma linguagem de programação.

A ilustração abaixo mostra uma [LINQ Query] parcialmente completa, efetuando uma busca no banco de dados Sql Server, com total checagem de tipo e suporte a IntelliSense:



No Visual Studio você pode escrever LINQ Queries no [Visual Basic] ou [C#] com Sql Server, XML Documents, ADO.NET DataSets, e qualquer coleção de objetos que suporta [IEnumerable] ou a Interface Genérica [IEnumerable(T)].

Para aprendermos mais sobre a Tecnologia LINQ, segue artigos sobre suas quatro principais atuações:

- LINQ to SQL (http://gtezini.blogspot.com/2009/02/net-framework-35-linq-to-sql.html)

- LINQ to XML (http://gtezini.blogspot.com/2009/02/net-framework-35-linq-to-xml.html)

- LINQ to DataSet (http://gtezini.blogspot.com/2009/02/net-framework-35-linq-to-dataset.html)

- LINQ to Objects (http://gtezini.blogspot.com/2009/02/net-framework-35-linq-to-objects.html)

.NET Framework 3.5 - LINQ to Objects

O termo [LINQ to Objects] se refere ao uso de LINQ queries diretamente com qualquer coleção IEnumerable ou IEnumerable(T) sem o uso de qualquer provedor intermediário ou API, como no caso do [LINQ to SQL] ou [LINQ to XML].

Assim como no uso dos outros recursos do LINQ (SQL, XML e DataSet) LINQ to Objects proporciona uma mudança significativa no desenvolvimento, de forma a simplificar a codificação, comparando por exemplo, ao uso do foreach.

Em adição, LINQ queries oferecem três vantagens principais sobre os tradicionais foreach loops:

1 - Eles são mais concisos e legíveis, especialmente quando filtram múltiplas condições;
2 - Eles provêem mais poder de filtragem, ordenação e agrupamento com um mínimo de codificação;
3 - Eles podem ser portados para outras fontes de dados com pequena ou nenhuma modificação.

O exemplo a seguir mostra uma query sobre um ArrayList. Note que este exemplo usa inicializadores de objeto quando o código chama o método [Add], mas isto não é requerido.


[Visual Basic]
Imports System.Collections
Imports System.Linq

Module Module1

Public Class Student
Public FirstName As String
Public LastName As String
Public Scores As Integer()
End Class

Sub Main()

Dim student1 As New Student With {.FirstName = "Svetlana", _
.LastName = "Omelchenko", _
.Scores = New Integer() {98, 92, 81, 60}}
Dim student2 As New Student With {.FirstName = "Claire", _
.LastName = "O'Donnell", _
.Scores = New Integer() {75, 84, 91, 39}}
Dim student3 As New Student With {.FirstName = "Cesar", _
.LastName = "Garcia", _
.Scores = New Integer() {97, 89, 85, 82}}
Dim student4 As New Student With {.FirstName = "Sven", _
.LastName = "Mortensen", _
.Scores = New Integer() {88, 94, 65, 91}}

Dim arrList As New ArrayList()
arrList.Add(student1)
arrList.Add(student2)
arrList.Add(student3)
arrList.Add(student4)

' Use an explicit type for non-generic collections
Dim query = From student As Student In arrList _
Where student.Scores(0) > 95 _
Select student

For Each student As Student In query
Console.WriteLine(student.LastName & ": " & student.Scores(0))
Next
' Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.")
Console.ReadKey()
End Sub

End Module
' Output:
' Omelchenko: 98
' Garcia: 97




[C#]
using System;
using System.Collections;
using System.Linq;

namespace NonGenericLINQ
{
public class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int[] Scores { get; set; }
}

class Program
{
static void Main(string[] args)
{
ArrayList arrList = new ArrayList();
arrList.Add(
new Student
{
FirstName = "Svetlana", LastName = "Omelchenko", Scores = new int[] { 98, 92, 81, 60 }
});
arrList.Add(
new Student
{
FirstName = "Claire", LastName = "O’Donnell", Scores = new int[] { 75, 84, 91, 39 }
});
arrList.Add(
new Student
{
FirstName = "Sven", LastName = "Mortensen", Scores = new int[] { 88, 94, 65, 91 }
});
arrList.Add(
new Student
{
FirstName = "Cesar", LastName = "Garcia", Scores = new int[] { 97, 89, 85, 82 }
});

var query = from Student student in arrList
where student.Scores[0] > 95
select student;

foreach (Student s in query)
Console.WriteLine(s.LastName + ": " + s.Scores[0]);

// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
}
/* Output:
Omelchenko: 98
Garcia: 97
*/

________________________________
101 Visual Basic LINQ Samples! - http://msdn.microsoft.com/en-us/vbasic/bb688088.aspx

.NET Framework 3.5 - LINQ to DataSet

LINQ to DataSet faz com que a busca de dados em [objetos DataSet] se tornem ainda mais fáceis e rápidas.

Este recurso traz maior produtividade e flexibilidade para os desenvolvedores já que trabalham com [Queries] na sua própria linguagem de programação.

LINQ to DataSet expõe, primariamente, os métodos adicionais nas classes [DataRowExtensions] and [DataTableExtensions]. Também se utiliza da arquitetura do ADO.NET 2.0, não tendo a intenção da substituir o ADO.NET 2.0 no código da aplicação.

O relacionamento do [LINQ to DataSet] com o [ADO.NET 2.0] e o [Data Store] é ilustrado no diagrama abaixo:



Querying DataSets Usando LINQ to DataSet

No exemplo abaixo podemos notar a facilidade de busca e manipulação dos dados em um DataSet do ADO.NET 2.0. Como a classe [DataTable] não implementa a interface [IEnumerable(T)], chamamos o método [AsEnumerable] para usar o DataTable com uma [fonte] para a cláusula [From] da LINQ query.

O exemplo busca todos os pedidos [online] da tabela [SalesOrderHeader] e mostra os atributos [order ID], [order date], e [order number] .


[Visual Basic]
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)

Dim orders As DataTable = ds.Tables("SalesOrderHeader")

Dim query = _
From order In orders.AsEnumerable() _
Where order.Field(Of Boolean)("OnlineOrderFlag") = True _
Select New With { _
.SalesOrderID = order.Field(Of Integer)("SalesOrderID"), _
.OrderDate = order.Field(Of DateTime)("OrderDate"), _
.SalesOrderNumber = order.Field(Of String)("SalesOrderNumber") _
}

For Each onlineOrder In query
Console.Write("Order ID: " & onlineOrder.SalesOrderID)
Console.Write(" Order date: " & onlineOrder.OrderDate)
Console.WriteLine(" Order number: " & onlineOrder.SalesOrderNumber)
Next



[C#]
// Fill the DataSet.
DataSet ds = new DataSet();
ds.Locale = CultureInfo.InvariantCulture;
FillDataSet(ds);

DataTable orders = ds.Tables["SalesOrderHeader"];

var query =
from order in orders.AsEnumerable()
where order.Field("OnlineOrderFlag") == true
select new
{
SalesOrderID = order.Field("SalesOrderID"),
OrderDate = order.Field("OrderDate"),
SalesOrderNumber = order.Field("SalesOrderNumber")
};

foreach (var onlineOrder in query)
{
Console.WriteLine("Order ID: {0} Order date: {1:d} Order number: {2}",
onlineOrder.SalesOrderID,
onlineOrder.OrderDate,
onlineOrder.SalesOrderNumber);
}

________________________________
101 Visual Basic LINQ Samples! - http://msdn.microsoft.com/en-us/vbasic/bb688088.aspx

.NET Framework 3.5 - LINQ to XML

LINQ to XML provê uma interface de programação [in-memory] com [Modelo de Objeto de Documentos - DOM].

O interessante deste novo recurso é que ficou mais fácil a manipulação de XMLs (comparando por exemplo ao uso do XPATH) pois este tipo de objeto é manipulado através de Queries, similar a manipulação no SQL.

O exemplo abaixo mostra a facilidade de uso. Está sendo retornada uma lista, ordernado pelo campo partnumber, dos itens com valor maior do que 100.


[C#]
IEnumerable partNos =
from item in purchaseOrder.Descendants("Item")
where (int) item.Element("Quantity") *
(decimal) item.Element("USPrice") > 100
orderby (string)item.Element("PartNumber")
select item;


[Visual Basic]
Dim partNos = _
From item In purchaseOrder... _
Where (item..Value * _
item..Value) > 100 _
Order By item..Value _
Select item


A melhoria na manipulação da interface de programação XML é realmente significativa. Inclusive podemos fazer todo tipo de manipulação, como fariamos em uma programação XML, sendo elas:

- Carregar XML de arquivos ou streams.
- Serializar XML para arquivos ou streams.
- Query XML usando [XPath-like].
- Manipular a [árvore XML] in-memory usando métodos comm Add, Remove, ReplaceWith, e SetValue.
- Validar a [árvore XML] usando XSD.

Para exemplificar a facilidade na criação da árvore em XML, note o código abaixo:


[C#]
XElement contacts =
new XElement("Contacts",
new XElement("Contact",
new XElement("Name", "Patrick Hines"),
new XElement("Phone", "206-555-0144",
new XAttribute("Type", "Home")),
new XElement("phone", "425-555-0145",
new XAttribute("Type", "Work")),
new XElement("Address",
new XElement("Street1", "123 Main St"),
new XElement("City", "Mercer Island"),
new XElement("State", "WA"),
new XElement("Postal", "68042")
)
)
);


E no Visual Basic o uso é ainda mais simplificado, pois usa-se o XML de forma literal:


[Visual Basic]
Dim contacts = _
< Contacts >
< Contact >
< Name >Patrick Hines< /Name >
< Phone Type="Home" >206-555-0144< /Phone >
< Phone Type="Work">425-555-0145< /Phone >
< Address >
< Street1 >123 Main St< /Street1 >
< City> Mercer Island< /City >
< State >WA< /State >
< Postal >68042< /Postal >
< /Address >
< /Contact >
< /Contacts >


________________________________
101 Visual Basic LINQ Samples! - http://msdn.microsoft.com/en-us/vbasic/bb688088.aspx

.NET Framework 3.5 - LINQ to SQL

LINQ to SQL é um componente do .NET Framework 3.5 que provê uma Infraestrutura em tempo de execução para gerenciar dados relacionais como [objetos].

Por exemplo, o objeto [nw], no código abaixo, é criado para representar o Banco de Dados [Northwind] que faz um "Select" na Tabela [Customers], retornando os registros da cidade de [Londres].


[Visual Basic]
' NorthwndDataContext herda de System.Data.Linq.DataContext.
Dim nw As New NorthwndDataContext("C:\SQL Server 2000 Sample Databases\northwnd.mdf")

Dim companyNameQuery = _
From cust In nw.Customers _
Where cust.City = "London" _
Select cust.CompanyName

For Each customer In companyNameQuery
Console.WriteLine(customer)
Next


[C#]
// NorthwndDataContext herda de System.Data.Linq.DataContext.
NorthwndDataContext nw = new NorthwndDataContext(@"C:\SQL Server 2000 Sample Databases\northwnd.mdf");

var companyNameQuery =
from cust in nw.Customers
where cust.City == "London"
select cust.CompanyName;

foreach (var customer in companyNameQuery)
{
Console.WriteLine(customer);
}


Adicionando [LINQ to SQL Data Classes] em um Projeto

Para trabalharmos de forma orientada a objetos com o LINQ to SQL, como o exemplo acima mostrou no uso da classe [NorthwndDataContext], o Visual Studio 2008 disponibiliza duas ferramentas de apoio para a geração da classes de dados:

- Object Relational Designer (http://msdn.microsoft.com/en-us/library/bb384429.aspx);

- SQLMetal (http://msdn.microsoft.com/pt-br/library/bb386987.aspx);

Com o O/R Designer, temos a disposição uma rica interface, dentro do Visual Studio 2008, para criarmos nossas classes de dados. Repare que existe um [Template] para isto no [VS 2008], chamado [LINQ to SQL Classes], quando se adiciona um item novo no projeto (.dbml).

Com o SQLMetal, fazemos o mesmo, só que fora do Visual Studio 2008, na linha de comando do DOS.

O seguinte exemplo gera a classe de dados (em Visual Basic) baseado no Banco de Dados Northwind:


sqlmetal /code:northwind.vb /language:vb "c:\northwnd.mdf" /sprocs /functions


P.S.: Note que nos exemplos trabalhamos com o Banco de Dados [Northwind] do Sql Server 2000. Ele ainda continua sendo um bom banco para testes (principalmente devido seu tamanho). Para usar este banco no Sql Server 2005, faça o seguinte:

1 - Efetue o download em: http://www.microsoft.com/downloads/details.aspx?familyid=06616212-0356-46a0-8da2-eebc53a68034&displaylang=en

2 - Execute o arquivo [SQL2000SampleDb.msi], e adicione (Attach) o banco [Northwnd.mdf], localizado em [C:\SQL Server 2000 Sample Databases].

________________________________
101 Visual Basic LINQ Samples! - http://msdn.microsoft.com/en-us/vbasic/bb688088.aspx

terça-feira, 10 de fevereiro de 2009

>> CRM 4.0 - Preenchimento Automático de PickList (REVISADO)

------------ REVISÃO ---------------
Efetuei algumas alterações/correções pois, ao salvar o registro, estava gerando erro (explicado no passo 2 abaixo).
------------------------------------

Ainda na atual versão do CRM (4.0) não se pode configurar objetos para preenchimento automático de outros, via configuração básica do produto. Por exemplo, ao selecionar uma Conta, em um [Lookup], retornar em um [Picklist] os Contatos da Conta.

Segue então solução, via desenvolvimento JavaScript, para isto.

1 - Criar um PickList vazio;

2 - Carregar o Picklist com uma quantidade "razoável" de linhas "VAZIAS". Porquê? Acontece que estamos carregando o Picklist dinamicamente. Internamente, o CRM busca em seu [METADADOS] as informações do Picklist tanto no [OnLoad] do Formulário quanto no [OnSave]. Ignorando este processo interno, ao carregar aleatóriamente os dados no PickList vai gerar um erro, pois o CRM nos dirá que [incialmente, ao carregar o formulário, o metadados para este Picklist não continha informação, porém agora contém, gerando uma exceção de índice do Dropdownlist].

3 - No [OnChange] do Lookup desejado (no meu exemplo, de Conta) adicionar o código:

window.getAccountContacts();


4 - E no [OnLoad] do Form do CRM, adicionar o código abaixo:

window.getAccountContacts = function (e)
{
crmForm.all.new_pickcontact.DataValue = null;
crmForm.all.new_pickcontact.Disabled = true;

if (crmForm.all.customerid.DataValue==null) return;

var customerid = crmForm.all.customerid.DataValue[0].id;
var authenticationHeader = GenerateAuthenticationHeader();

var xml = "<" + "?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<" + "soap:Envelope xmlns:soap="+
"\"http://schemas.xmlsoap.org/soap/envelope/\" "+
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "+
"xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">" +
authenticationHeader+
"<" + "soap:Body>" +
"<" + "RetrieveMultiple xmlns="+
"\"http://schemas.microsoft.com/crm/2007/WebServices\">" +
"<" + "query xmlns:q1="+
"\"http://schemas.microsoft.com/crm/2006/Query\" "+
"xsi:type=\"q1:QueryByAttribute\">" +
"<" + "q1:EntityName>contact" + "<" + "/q1:EntityName>" +
"<" + "q1:ColumnSet xsi:type=\"q1:ColumnSet\">" +
"<" + "q1:Attributes>" +
"<" + "q1:Attribute>fullname" + "<" + "/q1:Attribute>" +
"<" + "/q1:Attributes>" +
"<" + "/q1:ColumnSet>" +
"<" + "q1:Attributes>" +
"<" + "q1:Attribute>parentcustomerid" + "<" + "/q1:Attribute>" +
"<" + "/q1:Attributes>" +
"<" + "q1:Values>" +
"<" + "q1:Value xsi:type=\"xsd:string\">"+
customerid+
"<" + "/q1:Value>" +
"<" + "/q1:Values>" +
"<" + "/query>" +
"<" + "/RetrieveMultiple>" +
"<" + "/soap:Body>" +
"<" + "/soap:Envelope>";

xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");
xmlHttpRequest.Open(
"POST",
"/mscrmservices/2007/CrmService.asmx",
true
);

xmlHttpRequest.setRequestHeader(
"SOAPAction",
"http://schemas.microsoft.com/crm/2007/WebServices/RetrieveMultiple" );

xmlHttpRequest.setRequestHeader(
"Content-Type", "text/xml; charset=utf-8" );

xmlHttpRequest.setRequestHeader(
"Content-Length", xml.length );

xmlHttpRequest.onreadystatechange = StateChangeAccountContacts;
xmlHttpRequest.send(xml);
}

function StateChangeAccountContacts()
{
if (xmlHttpRequest.readyState == 4)
{
var resultXml = xmlHttpRequest.responseXML;
var oXmlDoc = new ActiveXObject("Microsoft.XMLDOM");
oXmlDoc.async = false;
oXmlDoc.loadXML(resultXml.xml);
var businessEntities = oXmlDoc.getElementsByTagName('BusinessEntity');

var oTempArray = new Array();
oTempArray[0] = crmForm.all.mit_pickcontact.originalPicklistOptions[0];

for (i=0;i < businessEntities.length;i++)
{
var fullName = businessEntities[i].selectSingleNode('./q1:fullname');
if (fullName != null)
{
var opt = crmForm.all.mit_pickcontact.originalPicklistOptions[i+1];
opt.Text = fullName.text;
oTempArray[i+1] = opt;
}
}

crmForm.all.new_pickcontact.Options = oTempArray;
crmForm.all.new_pickcontact.Disabled = false;
crmForm.all.new_pickcontact.DataValue = crmForm.all.new_pickcontact.originalSelectedIndex;

}
}

crmForm.all.new_pickcontact.originalPicklistOptions = crmForm.all.new_pickcontact.Options;
crmForm.all.new_pickcontact.originalSelectedIndex = crmForm.all.new_pickcontact.selectedIndex;
window.getAccountContacts();

quinta-feira, 5 de fevereiro de 2009

>> CRM 4.0 - Manipulação de Anexos (SDK)

A versão 4.0 do Dynamics CRM simplificou para nós, desenvolvedores, o uso de diversos recursos do SDK. Um deles é a busca e manipulação de Anexos nas entidades do produto.

Para este caso, note que em cada Anotação (relacionada a uma entidade do CRM) existe um campo chamado documentbody que armazena o conteúdo do arquivo anexado pelo usuário (BASE64 Format).

Para demonstrar isto, segue função genérica (C#) que retorna as Anotações (de uma determinada Entidade) que contém anexos. A função retorna o conteúdo de cada anexo (pelo processo de Decoding do .NET).

P.S.: Lembrando que a função consegue ler o conteúdo de anexos no formato do CRM (BASE64). Arquivos com formatos diferentes precisam de tratamentos específicos para leitura. Exemplo, os arquivos do Office2007, como xlsx (http://michaelmalloy.blogspot.com/2008/04/c-read-excel-2007-xlsx-files.html).

Chamada da função que busca as Anotações:

BusinessEntityCollection notes = getEntityNotes(new Guid("272526EA-ED6B-43CC-A8DC-A2965405A463"));

foreach (BusinessEntity be in notes.BusinessEntities)
{
annotation annot = be as annotation;
if (!string.IsNullOrEmpty(annot.documentbody))
{
string attachBody = DecodeByteArryToString(annot.mimetype, annot.documentbody);
}
}



Função que busca as Anotações da Entidade:


public static BusinessEntityCollection getEntityNotes(Guid entityId)
{
// Set up the CRM Service.
CrmAuthenticationToken token = new CrmAuthenticationToken();
token.AuthenticationType = 0;
token.OrganizationName = "OrgName";
CrmService service = new CrmService();
service.Url = "http://localhost:5555/mscrmservices/2007/crmservice.asmx";
service.CrmAuthenticationTokenValue = token;
service.Credentials = System.Net.CredentialCache.DefaultCredentials;

BusinessEntityCollection notes = null;

ConditionExpression caseCondition = new ConditionExpression();
caseCondition.AttributeName = "objectid";
caseCondition.Operator = ConditionOperator.Equal;
caseCondition.Values = new object[] { entityId };

FilterExpression filter = new FilterExpression();
filter.FilterOperator = LogicalOperator.And;
filter.Conditions = new ConditionExpression[] { caseCondition };

QueryExpression query = new QueryExpression();
query.EntityName = EntityName.annotation.ToString();
query.ColumnSet = new AllColumns();
query.Criteria = filter;

RetrieveMultipleRequest retrieve = new RetrieveMultipleRequest();
retrieve.Query = query;

RetrieveMultipleResponse retrieved = (RetrieveMultipleResponse)service.Execute(retrieve);
notes = retrieved.BusinessEntityCollection;

return notes;
}

public static string DecodeByteArryToString(string mimeType, string documentbody)
{
Byte[] ByteArry = System.Convert.FromBase64String(documentbody);
Decoder byteArryDecoder = null;

if (mimeType.IndexOf("text/") >= 0)
{
byteArryDecoder = Encoding.UTF7.GetDecoder();
}
else
{
byteArryDecoder = Encoding.Unicode.GetDecoder();
}

int charCount = byteArryDecoder.GetCharCount(ByteArry, 0, ByteArry.Length);
char[] bodyChars = new Char[charCount];
int charsDecodedCount = byteArryDecoder.GetChars(ByteArry, 0, ByteArry.Length, bodyChars, 0);
return new string(bodyChars);
}

quarta-feira, 4 de fevereiro de 2009

>> CRM 4.0 - PickList Multi-Value

O objeto Picklist do CRM é um recurso interessante para montagem de listas rápidas, porém, dependendo do projeto em que está, vai ser necessário o uso de alguns recursos adicionais do objeto, como o de [multipla escolha]...Então desenvolvi um método (totalmente em JavaScript) que [transforma] o Picklist original em um [Novo], com múltipla escolha.

Segue design de um Picklist [transformado]:







Como funciona a transformação

1 - Crie normalmente seu Picklist e adicione-o no form do crm;

2 - Crie um atributo Auxiliar (nvarchar,texto,100) para armazenar os valores do Picklist Multi-Value; adicione-o também no form do CRM;

3 - Copie a função abaixo no OnLoad do Form do CRM. A função (createMultiValuePickList) possui os seguintes parâmetros:

pickList - Informar o objeto Picklist original do CRM;
objAux - Informar o objeto criado no passo 2 citado acima.

P.S.1.: O Novo Picklist ficará no lugar do [objAux] e o Picklist Original ficará [oculto] no Form do CRM.

P.S.2.: Como a função [createMultiValuePickList] cria objetos [novos] no Form do CRM e não altera e/ou reutiliza qualquer recurso padrão do produto, este procedimento pode se enquadrar na forma [Suportada] de customização.

Segue chamada da função:


createMultiValuePickList(crmForm.all.new_services, crmForm.all.new_services_aux);


Segue função completa:


function createMultiValuePickList(pickList, objAux)
{
/* Desabilitar o PickList do CRM e o campo aux que guardará os valores do Picklist Multi-Value */
var pickLabel = document.getElementById(pickList.id + "_c");
pickLabel.style.visibility = "hidden";
pickList.style.width = "0px";
objAux.style.width = "0px";

/* Cria a Table principal, que conterá o Picklist e o Botão para habilitar o Picklist */
var oMainTable = document.createElement("TABLE");
oMainTable.style.width = '100%';
oMainTable.setAttribute('table-layout', 'fixed');
/* Adiciona a Table Principal */
objAux.insertAdjacentElement("BeforeBegin", oMainTable);

var oMainTHead = document.createElement("THEAD");
oMainTable.appendChild(oMainTHead);

var input1 = document.createElement('input');
input1.setAttribute('type', 'text');
input1.setAttribute('name', 'input_txt');
input1.setAttribute('id', 'input_txt');
input1.setAttribute('readOnly', 'true');

input1.onclick = function()
{
document.getElementById('divPicklist').style.visibility = 'hidden';
document.getElementById('input_btn').style.backgroundImage = 'url(/_imgs/selectOn.gif)';
}


var input2 = document.createElement('input');
input2.style.width = '18px';
input2.setAttribute('type', 'button');
input2.setAttribute('name', 'input_btn');
input2.setAttribute('id', 'input_btn');
input2.style.backgroundImage = 'url(/_imgs/selectOn.gif)';
input2.style.backgroundColor="#D6E8FF";
input2.style.backgroundPosition="center center";

input2.onclick = function()
{
var div1 = document.getElementById('divPicklist');
div1.style.visibility = (div1.style.visibility=='visible' ? 'hidden' : 'visible');
this.style.backgroundImage = (div1.style.visibility=='hidden' ? 'url(/_imgs/selectOn.gif)' : 'url(/_imgs/ico/16_succeeded.png)');
var txt = document.getElementById('input_txt');
txt.value='';
objAux.value='';

for(i=0; i < pickList.options.length; i++)
{
var chk = document.getElementById('chk_' + String(pickList.options[i].value));
if (chk)
{
if (chk.checked)
{
txt.value+=pickList.options[i].text+',';
objAux.value += pickList.options[i].value+',';
}
}
}

if (txt.value.indexOf(',')>0) txt.value= txt.value.substr(0, txt.value.length-1);
if (objAux.value.indexOf(',')>0) objAux.value= objAux.value.substr(0, objAux.value.length-1);

}

var input3 = document.createElement('input');
input3.style.width = '18px';
input3.setAttribute('type', 'button');
input3.setAttribute('name', 'input_btn2');
input3.setAttribute('id', 'input_btn2');
input3.style.backgroundImage = 'url(/_imgs/ico/16_L_remove.gif)';
input3.style.backgroundColor="#D6E8FF";
input3.style.backgroundPosition="center center";

input3.onclick = function()
{
document.getElementById('divPicklist').style.visibility = 'hidden';
document.getElementById('input_btn').style.backgroundImage = 'url(/_imgs/selectOn.gif)';
document.getElementById('input_txt').value='';
objAux.value='';

for(i=0; i < pickList.options.length; i++)
{
var chk = document.getElementById('chk_' + String(pickList.options[i].value));
if (chk) chk.checked=false;
}

}

var oRow1 = document.createElement("TR");
var oRow2 = document.createElement("TR");

var oDivRow2 = document.createElement("DIV");
oDivRow2.setAttribute('id', 'divPicklist');
oDivRow2.style.position = 'absolute';
oDivRow2.style.height = '180px';
oDivRow2.style.width = '100%';
oDivRow2.style.border = 'solid 1px gray';
oDivRow2.style.backgroundColor = 'white';
oDivRow2.style.overflow = 'scroll';
oDivRow2.style.visibility='hidden';

oMainTHead.appendChild(oRow1);
oMainTHead.appendChild(oRow2);

/* 1a Coluna - TextBox */
var oCell1 = document.createElement("TH");
oCell1.style.width = '90%';
oCell1.appendChild(input1);
oRow1.appendChild(oCell1);

/* 2a Coluna - Botão */
var oCell2 = document.createElement("TH");
oCell2.style.width = '10%';
oCell2.appendChild(input2);
oRow1.appendChild(oCell2);

/* 3a Coluna - Botão */
var oCell3 = document.createElement("TH");
oCell3.style.width = '10%';
oCell3.appendChild(input3);
oRow1.appendChild(oCell3);

/* Cria a Table que terá o conteúdo do Picklist Original */
var oTablePick = document.createElement("TABLE");
oTablePick.setAttribute('width', '100%');
oTablePick.setAttribute('table-layout', 'fixed');

var oTHeadPick = document.createElement("THEAD");
oTablePick.appendChild(oTHeadPick);

oDivRow2.appendChild(oTablePick);

var defaultPick = crmForm.all.mit_services;
for(i=0; i < defaultPick.options.length; i++)
{
oRowPick = document.createElement("TR");
oTHeadPick.appendChild(oRowPick);

/* 1a Coluna - CheckBox */
oCellPick1 = document.createElement("TH");
oCellPick1.setAttribute('width', '10%');

if (defaultPick.options[i].text!="")
{
var chk = document.createElement('input');
chk.style.border = 'none';
chk.type = 'checkbox';
chk.id = 'chk_' + String(defaultPick.options[i].value);
chk.name = 'chk_' + String(defaultPick.options[i].value);
oCellPick1.appendChild(chk);
}

oRowPick.appendChild(oCellPick1);

/* 2a Coluna - Texto do PickList Original */
oCellPick2 = document.createElement("TH");
oCellPick2.setAttribute('width', '90%');
oCellPick2.innerHTML = defaultPick.options[i].text;
oRowPick.appendChild(oCellPick2);
}

/* Adicionar oTablePick na 2a linha da oMainTable */
var oCellPick1_line2 = document.createElement("TH");
oCellPick1_line2.setAttribute('width', '90%');
oCellPick1_line2.appendChild(oDivRow2);
oRow2.appendChild(oCellPick1_line2);

/* 2a coluna em branco */
var oCellPick2_line2 = document.createElement("TH");
oCellPick2_line2.style.width = '10%';
oRow2.appendChild(oCellPick2_line2);

/* 3a coluna em branco */
var oCellPick3_line2 = document.createElement("TH");
oCellPick3_line2.style.width = '10%';
oRow2.appendChild(oCellPick3_line2);

/* DataBind do componente */
if (objAux.value.indexOf(",")>0)
{
var txt_width = String(input1.clientWidth) + 'px';
var aBind = objAux.value.split(",");
for(i=0; i <= aBind.length; i++)
{
if (defaultPick.options[aBind[i]])
{
input1.value += defaultPick.options[aBind[i]].text + ',';
var chk = document.getElementById('chk_' + String(aBind[i]));
if (chk) chk.checked=true;
}
}
if (input1.value.indexOf(',')>0)
{
input1.value= input1.value.substr(0, input1.value.length-1);
input1.style.width = txt_width;
}
}

}