วันอังคารที่ ๒๘ กรกฎาคม พ.ศ. ๒๕๕๒

ลองสร้าง Extension Method ใน VB9

ถ้าพูดถึง Visual Studio 2008 แล้ว พระเอกที่มีคนกล่าวขวัญถึงมากที่สุดไม่น่าจะมี feature ไหนเกิน LINQ ไปได้ครับ หลายๆคนคงได้นำ LINQ ไปใช้ในงานจริงกันบ้างแล้ว ส่วนตัวผมเองตอนที่ VS2008 ยังอยู่ในช่วง Beta และ CTP ก็มีคนพูดถึง LINQ ให้ฟังหลายคน แต่ไม่ค่อยได้สนใจครับ แต่พอมาลองใช้งานดูแล้วต้องบอกว่าเยี่ยมมากครับ

สำหรับเบื้องหลังของ LINQ นั้น เทคโนโลยีสำคัญอย่างหนึ่งก็คือ Extension Method นั่นเองครับ เพราะด้วย Extension Method ทำให้เราสามารถใช้ LINQ ได้ โดยที่ runtime ยังเป็น v2.0.50727 เหมือนเดิม ที่เป็นแบบนี้เพราะว่า Extension Method นั้นทำให้เราสามารถเพิ่ม behavior ให้กับ Type ของเราได้โดยไม่จำเป็นต้องมี source code เดิมครับ

สมมติผมต้องการเพิ่มความสามารถให้กับ String Type สำหรับงาน Web Application ถ้าเป็นเมื่อก่อนก็อาจจะสร้าง sealed class ขึ้นมาโดยให้มี static method ที่ต้องการ (เพื่อที่ว่าจะได้ไม่ต้อง new object เวลาใช้งาน) ลองดูตัวอย่างครับ


Public NotInheritable Class MyExtension
Public Shared Function ApplyStyle(ByVal s As String) As String
Return String.Format("<span style='FONT-WEIGHT: bold;font-family:Verdana;color:red;'>{0}</span>", s)
End Function

Public Shared Function WriteLine(ByVal s As String) As String
Return String.Format("{0:#,##0}
", s)
End Function
End Class


แต่ถ้าเราสร้าง Extension Method ใน VB.NET นั้น เราจำเป็นต้องสร้างใน Module ครับ (C# จะสร้างใน Static Class ได้เลย แต่ VB.NET ไม่มี Static Class ครับ คือไม่สามารถใส่ "shared" keyword หน้า Class) และก็ใส่ Extension Attribute ด้วยครับ ลองมาดูโค้ดกัน


Public Module MyExtension2
<System.Runtime.CompilerServices.Extension()> _
Public Function EXApplyStyle(ByVal s As String) As String
Return String.Format("<span style='color:Red;font-weight:bold;font-family:Verdana'>{0}</span>", s)
End Function

<System.Runtime.CompilerServices.Extension()>
Public Function EXWriteLine(ByVal s As String) As String
Return String.Format("{0:#,##0} <br />", s)
End Function
End Module


คราวนี้มาดูเวลาการใช้งานกันครับ


Partial Class ModuleDisbursement_DBItemTest
Inherits System.Web.UI.Page

Protected Sub form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles form1.Load
Dim str As String = "Hello world."
Response.Write(MyExtension.WriteLine(MyExtension.ApplyStyle(str)))
Response.Write(str.EXApplyStyle().EXWriteLine())
End Sub

End Class


Reponse.Write บรรทัดแรก ใช้ Static Method ส่วนบรรทัดที่สองใช้ Extension Method ครับ สังเกตุได้ว่าเมื่อเราใช้ Extension Method แล้วเราจะเห็นว่า String Type ของเรามี method เพิ่มขึ้นมาอีก 2 ตัวครับ สะดวกดีจริงๆเลย

ปล. จากการ search ใน google พบว่า

  1. มี developer คนหนึ่งต้องการเพิ่ม method ใน BitMap Type แต่ทำไม่ได้ เพราะเป็น Sealed Class ครับ บางคนแนะนำให้สร้าง BitMap Type ของตัวเอง (ซึ่งเสียเวลาและต้องตามไปแก้โค้ดอีกเยอะเลย) และก็มีคนแนะนำให้ใช้ Extension Method มาแก้ปัญหานี้ครับ

  2. Microsoft MVP ชาวต่างชาติคนหนึ่งเขียนใน blog ว่า Module นี่ไง Static Class ของ VB.NET

  3. Microsoft MVP อีกคนเขียนอีก blog ว่า อยากให้มี Static Class ใน VB10 เพราะว่า Module มันยังไม่ใช่ Static Class จริงๆ (เค้าอธิบายว่ามันไม่เหมือนกันเพราะ Reflection - อืมม ยังไม่ค่อยเข้าใจครับ)



เพิ่มเติมมีคุยกันเรื่องนี้ที่ greatfreinds ครับ
มีวิธีที่จะใช้ Extension method โดยไม่ผ่าน Module ไหมครับ

วันศุกร์ที่ ๒๔ กรกฎาคม พ.ศ. ๒๕๕๒

parseInt ทำงานผิดพลาด

วันนี้ user โทรมาแจ้งว่ามี bug เกิดขึ้น คือเมื่อเลือกวันที่จากปฏิทินแล้วกลายเป็นวันอื่นไป ลองไปไล่โค้ดดูปรากฏว่าผมไปกำหนด onblur event ของ textbox ให้ไปตรวจสอบวันที่และทำการจัด format เป็น dd/MM/yyyy

ปรากฏว่าโค้ดส่วนที่มี bug คือคำสั่ง parseInt ครับ


function DMY2Date(dmyStr) {
var splitDateStr=dmyStr.split('/');
return new Date(splitDateStr[2],String(parseInt(splitDateStr[1])-1),splitDateStr[0]);
} //end function


คือผมสั่ง parseInt(splitDateStr[1]) เพื่อหาค่าเดือนเป็น Integer แต่ปรากฏว่า พอ split data string ออกมา มันได้ค่า "08" ซึ่งใน parseInt function มันมีเงื่อนไขว่าถ้าขึ้นต้นด้วย 0 แปลว่าเป็นเลขฐาน 8 ครับ พอลอง debug ดูปรากฏว่า parseInt("08") ได้ผลลัพธ์เป็น 0 เพราะเลขสูงสุดของเลขฐาน 8 คือ 7 ครับ ถ้า parseInt("010") ถึงจะได้ค่า 8

ลองดู Reference ที่ www.w3schools.com กันครับ

Syntax
parseInt(string, radix)

If the radix parameter is omitted, JavaScript assumes the following:
  • If the string begins with "0x", the radix is 16 (hexadecimal)

  • If the string begins with "0", the radix is 8 (octal). This feature is deprecated
  • If the string begins with any other value, the radix is 10 (decimal)



ก็เลยไปแก้โดยกำหนด radix = 10 เข้าไปด้วย เพื่อบอกว่าเราต้องการ parseInt แบบเลขฐาน 10 ไม่ใช่เลขฐาน 8 นะเฟ้ย


function DMY2Date(dmyStr) {
var splitDateStr=dmyStr.split('/');
return new Date(splitDateStr[2],String(parseInt(splitDateStr[1],10)-1),splitDateStr[0]);
} //end function


เสร็จแล้วก็เลยต้องไปไล่นั่งตรวจสอบดูว่ามีการใช้ parseInt function ตรงไหนบ้าง เพื่อที่จะไปใส่ radix ให้หมดเพื่อป้องกันปัญหาครับ

วันจันทร์ที่ ๒๐ กรกฎาคม พ.ศ. ๒๕๕๒

javascript กำหนด textbox ให้รับค่า time (hh:mm)

สมมติผมมี textbox id="txtTime" และต้องการให้ user สามารถคืย์ข้อมูลเฉพาะเวลา โดยให้ format = hh:mm (23:59) ครับ
ก่อนอื่นก็ต้อง register event handler ให้กับ textbox ก่อน


$(function() {
$("#txtTime").keypress(function(e) {txtTime_OnKeyPress(this,e)});
$("#txtTime").blur(function() { ValidateTime(this) });
});


จะเห็นว่าผมสร้าง function สำหรับจัดการ keypress event กับ blur event ให้ textbox ครับ สำหรับ txtTime_OnKeyPress สำหรับกำหนดให้ user สามารถคีย์เฉพาะ d(2).d(2) คือตัวเลขสองตัว ตามด้วยจุดและตัวเลขอีกสองตัว ส่วน ValidateTime สำหรับตรวจสอบว่าค่าที่คีย์ตรงกับ format ที่ต้องการหรือเปล่า


function txtTime_OnKeyPress(sender, e) {
var myTime = sender.value;
if(myTime.length>4) {
event.keyCode = 0;
return false;
}
var charCode = (e.which) ? e.which : e.keyCode
switch (myTime.length) {
case 0:
if (charCode < 48 || charCode > 50) event.keyCode = 0;
break;
case 1:
if (charCode<48||(myTime == 2&&charCode > 51)) event.keyCode = 0;
break;
case 2:
if (charCode != 46) event.keyCode = 0;
break;
case 3:
if (charCode<48||charCode > 53) event.keyCode = 0;
break;
default:
if (charCode < 48 || charCode > 57) event.keyCode = 0;
}
}

function ValidateTime(sender) {
if (sender.value.length == 0) return false;
var regEx = /^(\d{2}).(\d{2})$/;
var arrMatch = sender.value.match(regEx);
if (arrMatch == null) {
alert("Invalid time.");
sender.value = "";
return false;
}
var hh = arrMatch[1];
var mm = arrMatch[2];
if (hh >= 24 || mm >= 60) {
alert("Invalid time.");
sender.value = "";
return false;
}
return true;
}


ทดสอบกับ IE ได้ผล OK ครับ ใครจะนำไปใช้ก็ทดสอบกันก่อนนะ ว่างๆผมจะมาแก้ function ให้สามารถกำหนด format ของเวลาได้ เผื่อบางทีต้องใช้แบบ 11:59 A.M. แต่ตอนนี้ใช้แค่นี้ก่อนละกันครับ

วันเสาร์ที่ ๑๘ กรกฎาคม พ.ศ. ๒๕๕๒

Visual Studio 2008 Samples

ผมลองคุยกับหลายๆคน ส่วนใหญ่ไม่ทราบว่าทาง Microsoft ได้ทำการเผยแพร่ "Visual Studio 2008 Samples" ให้ดาวน์โหลดมาลองกันครับ ผมเองได้ลองดาวน์โหลด Visual Studio 2008 RTM Samples มาลองดูตัวอย่าง (โดยเฉพาะ LINQ เปิดอ่านดูบ่อยพอสมควร)

นอกจาก VS2008 Samples แล้วยังมี Code Samples อื่นๆเลือกให้ลองดาวน์โหลดมาศึกษาเยอะแยะมากครับ

สนใจเข้าไปดาวน์โหลดได้ที่ Visual Studio 2008 Samples

วันศุกร์ที่ ๑๗ กรกฎาคม พ.ศ. ๒๕๕๒

VBScript ดึงชื่อและอีเมลล์จาก Active Directory

เมื่อหลายวันก่อนผมต้องทำการดึง ชื่อพนักงาน และ email address ที่เก็บไว้ใน AD ออกมาเพื่อตรวจสอบ และ Import เข้า Database สำหรับไว้ใช้ในการทำ mailling list ใช้ภายในครับ ลอง search ดูใน google ก็พบว่ามีหลายวิธี และที่ปิ๊งที่สุดก็คือการเขียน VBScript เพื่อไปดึงข้อมูลจาก LDAP มาเขียนใส่ text file ครับ


Dim FileSystem
'Initialize global variables
Set FileSystem = WScript.CreateObject("Scripting.FileSystemObject")
Set OutPutFile = FileSystem.CreateTextFile("email.txt", True)
Set oContainer=GetObject("LDAP://OU=Department Users,DC=MyDomainName,DC=com")
'Enumerate Container
EnumerateUsers oContainer
'Clean up
OutPutFile.Close
Set FileSystem = Nothing
Set oContainer = Nothing
WScript.Echo "Finished"
WScript.Quit(0)

Sub EnumerateUsers(oCont)
Dim oUser
For Each oUser In oCont
Select Case LCase(oUser.Class)
Case "user"
OutPutFile.WriteLine oUser.givenname & " " & oUser.sn & vbtab & oUser.mail
Case "organizationalunit" , "container"
EnumerateUsers oUser
End Select
Next
End Sub


ถ้าอยากรู้ว่า oUser (User Class) มี property อะไรบ้าง ดูตามนี้เลยครับ ไม่แน่ใจว่าครบหรือเปล่านะ


'// User Property
'SamAccountName = oUser.samAccountName
'Cn = oUser.CN
'FirstName = oUser.GivenName
'LastName = oUser.sn
'initials = oUser.initials
'Descrip = oUser.description
'Office = oUser.physicalDeliveryOfficeName
'Telephone = oUser.telephonenumber
'EmailAddr = oUser.mail
'WebPage = oUser.wwwHomePage
'Addr1 = oUser.streetAddress
'City = oUser.l
'State = oUser.st
'ZipCode = oUser.postalCode
'Title = oUser.Title
'Department = oUser.Department
'Company = oUser.Company
'Manager = oUser.Manager
'Profile = oUser.profilePath
'LoginScript = oUser.scriptpath
'HomeDirectory = oUser.HomeDirectory
'HomeDrive = oUser.homeDrive
'AdsPath = oUser.Adspath
'LastLogin = oUser.LastLogin

วันพุธที่ ๑๕ กรกฎาคม พ.ศ. ๒๕๕๒

jQuery ทำ highlight ขัอมูลใน GridView

ผมเคยทำโปรแกรมค้นหาข้อมูลจากไฟล์เอกสารที่เก็บไว้ใน File Server โดย user สามารถใส่ key word ได้หลายตัว จากนั้นโปรแกรมจะไปค้นหาว่ามีไฟล์ไหนบ้างที่มีคำหรือประโยคตรงกับเงื่อนไขที่ต้องการ แล้วนำข้อมูลรวมถึง url มาใส่ใน GridView
ทีนี้ user ก็ต้องการให้โปรแกรมสามารถ highlight คำหรือประโยคใน GridView ที่ตรงกับ key word ด้วย ซึ่งก่อนหน้านี้ผมก็เขียน javascript วนลูปเพื่อทำ highlight ซึ่งโค้ดก็ยาวพอสมควร วันนี้จะลองมา modify โดยใช้ jQuery มาช่วยครับ โดยผมจะเพิ่ม Custom Attribute ให้กับ Control ที่ต้องการจะไปทำ highlight ครับ ซึ่งในแต่ละแถวของ GridView จะให้ไป highlight ที่ label 2 ตัว

<asp:TemplateField HeaderText="Title" HeaderStyle-Width="400px" HeaderStyle-HorizontalAlign="Left">
<ItemTemplate>
<asp:Label ID="lblDocTitle" runat="server" ForeColor="Blue" style="font-size: 10pt" Highlight="true" Font-Bold="False"><%# eval("DocTitle") %></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Abstract" HeaderStyle-Width="300px" HeaderStyle-HorizontalAlign="Left">
<ItemTemplate>
<asp:Label ID="lblAbstract" runat="server" Highlight="true" ><%# eval("Abstract") %></asp:Label>
</ItemTemplate>
</asp:TemplateField>

เนื่องจากผมใช้ ScriptManager และ UpdatePanel ด้วย จากบทความเก่าผมใช้วิธีเขียนคำสั่ง jQuery ที่ code behind แล้วสั่ง ScriptManager ให้ Register Script ให้ ตามตำราเรียกวิธีนี้ว่าเป็น Server-centric แต่คราวนี้เราจะมาลองทำ Client-centric ดูบ้าง แทนที่เราจะใช้ UpdateProgress Control ผมก็จะมาใช้ PageRequestManager ในการแสดงรูปภาพแทน รวมถึงทำการ register event handler function ครับ

<asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>

<script type="text/javascript" id="MSAjax">
var prm = Sys.WebForms.PageRequestManager.getInstance();
prm.add_beginRequest(beginRequest);
prm.add_pageLoaded(pageLoaded);

function beginRequest(sender, args) {
$("#divProgress").show();
$("#MainGridView").html("");
}

function pageLoaded(sender, args) {
//Add elements'event handler.
$("txtFreeText").keypress(function(e) { txtFreeText_OnKeyPress(e); });
$("btnSearch").click(function() { ValidateSearch(); });
$("#divProgress").hide();

HighlightSearch();
}
</script>

คราวนี้มาลองดูโค้ดที่สำหรับทำ Highlight กันครับ

<style type="text/css">
.highlight{font-weight: bold; font-size: 16px; color: blue; background-color: papayawhip};
</style>
<script type="text/javascript" src="Scripts/jquery-1.3.2-vsdoc.js"></script>

  
<script type ="text/javascript" id="MainScript">

function trim(str) {
return str.replace(/^\s*|\s*$/g, "");
}

function HighlightSearch(){
var strSearch = $("#txtFreeText").val();
if (trim(strSearch)=="") return false;

strSearch = strSearch.replace(/\"/gi,"");
strSearch = strSearch.replace(/\s*and\s*/gi, " ");
strSearch = strSearch.replace(/\s*or\s*/gi," ");
var arrSearch = strSearch.split(" ");

$("[Highlight=true]").each(function() {
var elm = $(this);
var strAbstarct = elm.html();
for (var i = 0; i < arrSearch.length; i++) {
var RE = new RegExp(arrSearch[i], "gi");
strAbstarct = strAbstarct.replace(RE, "<span class='highlight'>" + arrSearch[i] + "</span>");
}
elm.html(strAbstarct)
});
}

</script>

จากโค้ดด้านบน ผมใช้ Attribute Selector ในการเลือก element ที้ต้องการทำ Highlight มาจากนั้นมาวนลูปด้วย jQuery.each() ครับ แล้วก็ไปแก้ไข้ html ของ element ให้เพิ่ม span เพิ่อทำ highlight คำที่ต้องการ ลองรันดูได้ผลลัพธ์ตามต้องการครับ

วันอาทิตย์ที่ ๑๒ กรกฎาคม พ.ศ. ๒๕๕๒

IComparer กับ IComparable ใช้งานอย่างไร

วันก่อนมีกระทู้ฮอตที่ greatfriends ครับ
BLOG - Generic Function - MAX(Of T As IComparable(Of T))

สรุปคือมีการสับสนระหว่าง IComparer กับ IComparable ครับ ซึ่งไม่ใช่เรื่องแปลกเพราะว่าในเวบต่างประเทศเอง ก็มีคำถามเรื่องนี้บ่อยๆ

อาจารย์สุเทพสรุปดังนี้
IComparer ใช้สำหรับ
1. ไม่ต้องการ implement เข้าไปในคลาส เช่น มีรูปแบบการเปรียบเทียบไม่แน่นอน
2. ไม่สามารถ implement เข้าไปในคลาสได้ เช่น ไม่มี source code

ผมขอเพิ่มตามความเข้าใจนะครับ IComparable เป็น Interface สำหรับ Class ที่เราต้องการให้สามารถเปรียบเทียบได้ โดยมีลักษณะที่แน่นอน นั่นคือ Object ที่สร้างจาก class นี้ จะไปเปรียบเทียบกับ object อื่นที่สร้างจาก Class เดียวกัน

Public Class Employee
Implements IComparable(Of Employee)
...
End Class


ส่วน IComparer ชื่อก็ค่อนข้างสื่ออยู่แล้ว คือใช้สำหรับสร้าง Comparer Class หมายความว่าเราต้องการ Class สำหรับ Compare Object สรุปคือเป็น third party ที่ใช้เปรียบเทียบ object ที่1 และ object ที่2 ครับ

Public Class EmployeeComparer
Implements IComparer(Of Employee)

Public Enum compareField
age
hiredDate
joinedDate
End Enum

Private _compareField As compareField = compareField.age
Private _sortDirection As System.ComponentModel.ListSortDirection = System.ComponentModel.ListSortDirection.Ascending

Public Function Compare(ByVal x As [Class].Center.Employee, ByVal y As [Class].Center.Employee) As Integer Implements System.Collections.Generic.IComparer(Of [Class].Center.Employee).Compare

If _sortDirection = ComponentModel.ListSortDirection.Ascending Then
.....
Else
.....
End If

End Function

End Class



จากกระทู้มีการคุยกันถึงการใช้ Linq และการใช้ Lamda Expression ในการ Sort หรือเปรียบเทียบ Object ซึ่งจะสะดวกขึ้นมาก ลองเข้าไปอ่านกันดูครับ

Dim employees As New List(Of BAS.Class.Center.Employee)
employees.Add(New BAS.Class.Center.Employee With {.EmployeeID = "100", .NameEng = "tabd"})
employees.Add(New BAS.Class.Center.Employee With {.EmployeeID = "200", .NameEng = "dss"})
employees.Add(New BAS.Class.Center.Employee With {.EmployeeID = "300", .NameEng = "xxx"})
employees.Add(New BAS.Class.Center.Employee With {.EmployeeID = "400", .NameEng = "yyy"})

employees.Sort(Function(x, y) String.Compare(x.NameEng, y.NameEng)) '// Sort ascending
employees.Sort(Function(x, y) String.Compare(y.NameEng, x.NameEng)) '// Sort descending

วันพฤหัสบดีที่ ๙ กรกฎาคม พ.ศ. ๒๕๕๒

การจัดการเอกสาร pdf ด้วย iTextSharp (2)

หลังจากที่ผมได้นำโปรแกรมไปให้ user ทดสอบ ก็เลยได้ requirement เพิ่มเติมมาอีกครับ คือ user บางกลุ่มจะไม่สามารถ print pdf ออกไปที่ printer ได้ และห้าม save ด้วย

หลังจากเข้าไปอ่านเอกสารของ iTextSharp ก็พบว่ามันสามารถทำได้ครับ โดยตัว pdf เองนั้นเราสามารถทำการกำหนด permission ได้ และ iTextSharp มี class ชื่อ PdfWriter ซึ่งเราสามารถทำการ Set Encryption เพื่อกำหนด permission ของ pdf ได้ ดังนั้นจากโค้ดเดิมที่ทำไว้ ผมก็ไปเพิ่มคำสั่ง setEncryption ก่อนสั่ง document.open ครับ

เนื่องจากโค้ดเดิมผมใช้ PdfCopy object สำหรับสร้างเอกสาร pdf ใหม่ แต่ว่า PdfCopy มันไม่มีคำสั่ง setEncryption ครับ พอลองไป view object browser ดูก็พบว่าตัว PdfCopy นั้นมัน inherit มาจาก PdfWriter อีกที ดังนั้นเราสามารถ Casting ไปเป็น PdfWriter ได้ครับ จากนั้นก็สั่ง SetEncryption เพื่อกำหนด permission ว่าจะให้ print ได้หรือไม่ และก็สั่งซ่อน menu bar และ toolbar เพื่อที่ว่า user จะได้สั่ง save ไม่ได้ครับ



Dim writer As PdfWriter = TryCast(copy, PdfWriter)
'writer.SetEncryption(PdfWriter.STANDARD_ENCRYPTION_128, Nothing, Nothing, PdfWriter.ALLOW_SCREENREADERS) '// CANNOT PRINT
writer.SetEncryption(PdfWriter.STANDARD_ENCRYPTION_128, Nothing, Nothing, PdfWriter.ALLOW_PRINTING) '// CAN PRINT
writer.ViewerPreferences = PdfWriter.HideMenubar + PdfWriter.HideToolbar + PdfWriter.HideWindowUI

document.Open()
For i As Integer = 1 To reader.NumberOfPages
Dim ipage As Integer = i
If intIgnorePages Is Nothing OrElse intIgnorePages.Find(Function(c) c = ipage) = 0 Then
copy.AddPage(copy.GetImportedPage(reader, ipage))
End If
Next

document.Close()
reader.Close()
copy.Close()
fs.Close()
fs.Dispose()


เรียบร้อยครับ แหมมันช่างเยี่ยมยอดจริงๆ

วันพุธที่ ๘ กรกฎาคม พ.ศ. ๒๕๕๒

เขียนโปรแกรมจัดการเอกสาร pdf ด้วย iTextSharp

เมื่อไม่นานไม่นี้ผมได้รับ requirement ให้โปรแกรมที่พัฒนาขึ้นสามารถค้นหาและแสดงเอกสาร pdf โดยมีฟังก์ชันพิเศษ นั่นคือให้สามารถตัดหน้าบางหน้าของเอกสาร pdf ออก สำหรับ user บางกลุ่ม เช่นตัดหน้าสุดท้ายออก ถ้า user เป็นพนักงานของแผนก xxx ทำให้ผมต้องไปพึ่งบริการของ google ซึ่งพบว่ามี free .net-Pdf library ตัวหนึ่งน่าสนใจมากนั่นคือ iTextSharp ครับ

หลังจาก download มาลงที่เครื่องเรียบร้อยก็ลองทดสอบกันเลย เริ่มจาก Add Reference ใน ASP.net โปรเจคก่อน และทำการ Import Namespace ให้เรียบร้อย ขั้นตอนการทำงานก็คือ ผมสร้าง Web Form ขึ้นมาโดยให้รับค่า Location ของ pdf file ที่จะทำการเปิดให้ user ผ่าน QueryString จากนั้นก็ไปตรวจสอบสิทธิ์ของ user ว่าจะให้ซ่อนหน้าไหนบ้าง แล้วจึงใช้ iTextSharp.PdfCopy ทำการสร้างเอกสาร pdf ขึ้นมาใหม่ และ copy หน้าที่ user มีสิทธิเห็นไปใส่ในเอกสารใหม่ลองดูโค้ดกันครับ


Public intIgnorePages As List(Of Integer)

Protected Sub frmMain_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles frmMain.Load

Dim strLocation As String = Server.UrlDecode(Request("Location"))
Dim fileName As String = "OUTPUT/" & _userCredential.EmployeeID & "/" & fileInfo.Name
Dim reader As New PdfReader(strLocation)
Dim document As New iTextSharp.text.Document(reader.GetPageSizeWithRotation(1))
Dim fs As New FileStream(Server.MapPath(fileName), FileMode.Create)
Dim page As PdfImportedPage = Nothing
Dim copy As New PdfCopy(document, fs)

If File.Exists(fileName) Then File.Delete(fileName)
intIgnorePages = ValidateUserPermission
document.Open()
For i As Integer = 1 To reader.NumberOfPages
Dim ipage As Integer = i
If intIgnorePages Is Nothing OrElse intIgnorePages.Find(Function(c) c = ipage) = 0 Then
copy.AddPage(copy.GetImportedPage(reader, ipage))
End If
Next
document.Close()
reader.Close()
copy.Close()
fs.Close()
fs.Dispose()
Response.Redirect(fileName)

End Sub


สำหรับโค้ดด้านบนเป็นโค้ดที่ผมใช้ทดสอบโปรแกรมยังไม่ใช่โค้ดที่ใช้งานจริง แต่สำหรับผลลัพธ์เป็นที่น่าพอใจมาก สุดยอดจริงๆครับ

Reference:
iTextSharpby blowagie, geraldhenson, psoares33

วันอังคารที่ ๗ กรกฎาคม พ.ศ. ๒๕๕๒

jQuery อ้างถึง Control ใน gridview

วันก่อนเข้าไป http://www.blogger.com/www.greatfriends.biz พบคำถามนี้ในบอร์ดพอดีครับ เป็นคำถามที่น่าสนใจทีเดียว ผมตอบไว้แบบนี้ครับ
ผมสมมติว่า เมื่อผมคลิ๊กที่ column ของ gridview ผมจะให้โปรแกรม alert ค่าทั้งหมดของแถวที่คลิ๊ก และแสดงค่าของ column ที่คลิ๊ก
เนื่องจากผมใช้ MSAjax และเอา Gridview ไปใส่ใน UpdatePanel ดังนั้น แทนที่ผมจะใช้ $(document).ready(function(){}); ผมจะไป register click event ที่ gvDBItem_DataBound แทนครับ

และก็ใน click(function(){}); แทนที่ผมจะเขียนโค้ดไปโดยตรง ผมเปลี่ยนให้ไปเรียก function ใช้งานแทน เพื่อที่ว่าตอน debug จะได้ง่ายครับ

โค้ดส่วน Code Behind

Protected Sub gvDBItem_DataBound(ByVal sender As Object, ByVal e As System.EventArgs) Handles gvDBItem.DataBound

Dim sbScript As New Text.StringBuilder

sbScript.Append("$('#" & gvDBItem.ClientID & " tr').click(function(e){gv_RowClick(this,e);});")

ScriptManager.RegisterStartupScript(Page, Page.GetType, "GVDataBound", sbScript.ToString, True)

End Sub



โค้ดส่วน javascript

<script type="text/javascript" >

function gv_RowClick(sender, e) {

alert(sender.innerText);

alert(e.srcElement.innerHTML);

}

</script>



ในตัวอย่างผมใช้ gridview id กับ tr เป็น selector แต่ว่าถ้าเราต้องการรู้ ID ของแถวที่กด หรือต้องการเอาค่าใน column ที่ต้องการเท่านั้น ก็สามารถเปลี่ยน selector ได้ครับ

อีกวิธีที่เพิ่งนึกออก คือเราสามารถใช้ attribute เป็น selector ได้ ดังนั้นเราก็เพิ่ม custom attribute ให้ column ที่เราต้องการเช่น


<asp:TemplateField HeaderText="Item No">

<ItemTemplate>

<asp:Label id="lblItemNo" runat="server" RowIndex="true" Text='<%# Eval("ORDERNO") %>' ></asp:Label>

</ItemTemplate>

</asp:TemplateField>



แล้วใน jQuery เราก็กำหนด selector

<script type="text/javascript" >

$(document).ready(function() {

$("#gvDBItem [RowIndex='true']").click(function(e) { gv_RowIndexClick(this, e); });

});

function gv_RowClick(sender, e) {

alert(sender.innerText);

alert(e.srcElement.innerHTML);

}

</script>



อันนี้เป็นตัวอย่างแบบไม่ได้ใช้ UpdatePanel ครับ ผมเลยใช้ $(document).ready()

ลองอ่านกระทู้เต็มๆได้ที่นี่ครับ
jQuery อ้างถึง Control ใน GridView อย่างไรครับ
http://greatfriends.biz/?109412

วันจันทร์ที่ ๖ กรกฎาคม พ.ศ. ๒๕๕๒

jQuery กับการเรียก ASP.net Web Service

ต่อเนื่องจากบทความที่แล้ว เราได้ใช้ ScriptManager ในการ add Service Reference เพื่อที่จะให้ VS ทำการสร้าง Proxy สำหรับเรียกใช้ Web Service ซึ่งก็สะดวกดีครับ แต่ทีนี้ตัว jquery เองมีความสามารถของ ajax มาด้วย เราจะมาลองเขียน jQuery สำหรับเรียก Web Service โดยตรง ไม่ต้องผ่าน ScriptManager ดูครับ

วิธีการก็ไม่ยาก แค่เราเรียกใช้ $.ajax(option) แค่นี้เอง ส่วน option นั้นเรากำหนดค่าในรูปแบบของ json ซึ่งมี member อะไรที่กำหนดได้บ้างนั้น ไปดูได้จาก document ของ jQuery ครับ ดังนั้นเราลอง comment โค้ดเดิมที่เรียก proxy ของ Web Service แล้วมาเขียนคำสั่งของ jQuery ดู

function txtEmpName_OnKeyUp(e) {
if (e.keyCode != 13 || e.keyCode != 9) {
$("#txtEmpId").val("");
var empName = $("#txtEmpName").val();
if (empName.length > 2) {
//BAS.Web.Services.wsFiling.GetStaffsByKeyword(empName, GetStaffSuccess);
$.ajax({
type: "POST",
url: "../wsFiling.asmx/GetStaffsByKeyword",
data: "{keyWord:'" + empName + "'}",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(data) { GetStaffSuccess(data.d, "", "") }
});
} else {
$("#divStaff").hide();
}
}
}


ก็ไม่ยากอย่างที่คิดครับ ตรง success เราก็ไปเรียก GetStaffSuccess function ตัวเดิมนั่นแหละ ส่วน error ผมไม่ได้ใส่ไว้เหมือนเดิม และในการใช้งานจริงก็ควรสร้าง OnError function ไว้ด้วยครับ

แต่โดยส่วนตัวขอใช้ผ่าน ScriptManager เหมือนเดิมครับ

Reference:
Using jQuery with ASP.NET Web Services and JSON, www.prettycode.org
Using jQuery to Consume ASP.NET JSON Web Services, www.encosia.com

วันศุกร์ที่ ๓ กรกฎาคม พ.ศ. ๒๕๕๒

jQuery กับการทำ Multi Column Autocomplete

พอดีได้รับ requirement ให้ทำ auto complete function ครับ สมมติว่า user คีย์ชื่อพนักงาน ให้โปรแกรมไปดึงรายชื่อพนักงานทั้งหมดที่ใกล้เคียงมาแสดง พอ user เลือกพนักงานที่ต้องการจากใน list ก็ให้ไปแสดงข้อมูลอื่นๆของพนักงานใน field อื่นๆด้วย เช่นรหัสพนักงาน ชื่อ นามสกุล แผนก วันเริ่มงาน เป็นต้นครับ
ทีนี้ลอง search ดูพวก autocomplete จาก google พบว่าส่วนใหญ่มันเป็นแบบ single field คือใช้กับ textbox ตัวเดียวครับ เลยต้องเขียน javascript เพื่อไปดึงข้อมูลจาก Web Service มาให้ user เลือก แล้วพอเลือกแล้วก็เขียน javascript ให้ไปตัดข้อมูลลงใน element ต่างๆ
รู้สึกว่าการเขียน javascript เองนี่มันยาวพอสมควรครับ พอมีเวลาว่างก็เลยมาลองใช้ jQuery ดูว่ามันจะง่ายและดีสมคำร่ำลือหรือเปล่า ผมเลยสร้าง web page สำหรับทดสอบดู เอาเป็นว่ามีแค่ 2 field คือ ID กับ ชื่อนามสกุลก็พอครับ
ก่อนอื่นมาดูโค้ดหน้าเวบกันก่อน

<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
<Services>
<asp:ServiceReference Path="~/Employee.asmx" />
</Services>
</asp:ScriptManager>
<div>
<asp:TextBox ID="txtEmpId" runat="server" style="width:100px" MaxLength="6"></asp:TextBox>
<asp:TextBox ID="txtEmpName" runat="server" style="width:250px"></asp:TextBox>
</div>
</form>
</body>

จะเห็นว่ามี textbox แค่สองอัน และมีการใช้ ScriptManager เพื่อไปเรียก WebService ครับ
โค้ดส่วน WebService ขอไม่เขียนนะครับ เพราะไม่ยาก โดยตัว WebService จะคืน Employee Object มาให้ในรูปของ JSON (ตาม default ครับ)
ทีนี้มาดูโค้ดส่วน javascript กันบ้าง

<script type="text/javascript" src="../Scripts/jquery-1.3.2-vsdoc.js"></script>
<script type = "text/javascript">
$(function() {
$("#txtEmpName").keyup(function(event) {txtEmpName_OnKeyUp(event)});
});

function txtEmpName_OnKeyUp(e) {
if (e.keyCode != 13 || e.keyCode != 9) {
$("#txtEmpId").val("");
var empName = $("#txtEmpName").val();
if (empName.length > 2) {
jnithi.Employee.GetStaffsByKeyword(empName, GetStaffSuccess);
} else {
$("#divStaff").hide();

}
}
}

function GetStaffSuccess(result, methodName, context) {
if ($("#divStaff").length == 0) {
var txtEmpName = $("#txtEmpName");
var pos = txtEmpName.position();
var divStyle = {'position':'absolute','top': pos.top + txtEmpName.outerHeight() + 'px','left': pos.left + 'px','width': txtEmpName.outerWidth() +'px','border':'1px solid #666666' };
$(document.createElement("div")).attr("id", "divStaff").css(divStyle).appendTo("body");
}
var divStaff=$("#divStaff");
divStaff.empty();

if (typeof (result) != "undefinded" && result.length > 0) {
$(document.createElement("dl")).width(divStaff.width()).appendTo("#divStaff");
var i;
for (i = 0; i < result.length; i++) {
var employee = result[i];
var html = "<dt><a class='search' href='#' onclick='dtStaff_OnClick(this);' >" + employee.NewEmployeeId + ":" + employee.NameEng + "</a></dt>";
$("dl").append(html);
}
if (i > 0) {
divStaff.show();
}
} else {
divStaff.hide();
}
}
function dtStaff_OnClick(obj) {
result = obj.innerHTML.split(":");
$("#txtEmpId").val(result[0]);
$("#txtEmpName").val(result[1]);
$("#divStaff").hide();
}
</script>


เริ่มต้นก็ไปกำหนด Event Handler ให้กับ keyup event ของ txtEmpName ครับ
$(function() {
$("#txtEmpName").keyup(function(event) {txtEmpName_OnKeyUp(event)});
});

โดยปกติตัวอย่างของ jQuery จะเขียน function ไปเลย แต่ปรากฎว่า Visual Studio มัน debug ลำบาก ผมเลยแยกมาเขียน function ต่างหากครับ โดยมี window.event เป็น parameter หรือเราใช้ MSAjax อยู่แล้วอาจจะพิจารณาใช้ $addHandler() ของ MSAjax แทนก็ได้ครับ เมื่อเกิด keyup event มันก็จะไปเรียก txtEmpName_OnKeyUp ให้ทำงาน โดยจะไปเรียก WebService และกำหนด succeed callback function ไปที่ GetStaffSuccess ครับ (จริงๆควรจะกำหนด onFailed callback function ด้วยนะครับ)

ใน GetStaffSuccess function ผมให้มันทำการสร้าง div ที่บรรจุ dl element ไว้ครับ เพื่อเป็น list ให้ user เลือก สังเกตุว่าใน function นี้ผมทดลองทำการ creat element ไว้ 2 แบบ แบบแรกคือใช้ $(document.createElement(element)).appendTo(element) กับอีกแบบคือ $(element).append(html) แล้วก็ต้องกำหนด onclick event ด้วย เพื่อที่ว่าเมื่อ user เลือกรายชือพนักงานแล้ว จะได้ไปทำ dtStaff_OnClick ซึ่งจะทำการตัดข้อมูลไปใส่ใน textbox ทั้ง 2 ตัวครับ

ลองเอาโค้ดที่เขียน javascript เอง กับที่เขียนด้วย jQuery เห็นได้ว่าโค้ดสั้นลงประมาณ 30% เพราะใช้ประโยชน์ของ Chainability นั่นเองครับ

jQuery กับ Visual Studio 2008

ผมเคยลองเข้าไปด้อมๆมอง www.jquery.com มาตั้งนาน เพราะได้ยินกิตติศัพท์ว่าเป็นสุดยอด javascript framework ตัวหนึ่ง แต่เนื่องจากไม่ค่อยมีเวลาและขี้เกียจเลยไม่ได้สนใจมากนักครับ ลองอ่าน document ดูก็รู้สึกไม่ค่อยเข้าหัว แต่พอได้ข่าวจาก blognone ว่าตอนนี้ MS สนับสนุน jQuery แล้วเลยรู้สึกสนใจมากขึ้นครับ

พอดีเมื่อ 3 วันก่อน มี requirement จาก user ให้เพิ่ม function ใหม่ ก็เลยเขียน javascript เพื่อไปติดต่อกับ Webservice รู้สึกว่าโค้ดยาวพอสมควร เลยนึกถึง jquery ขึ้นมาครับ ในที่สุดก็มีโอกาสได้ศึกษาอย่างจริงๆจังๆซะที เริ่มต้นก็เลยไป download jQuery เวอร์ชันล่าสุด (1.3.2) มาก่อนครับ และก็ไม่ลืมที่จะ download ไฟล์ jquery-1.3.2-vsdoc2.js สำหรับให้ Visual Studio เพิ่ม IntelliSense ของ jQuery เข้าไป จะได้สะดวกเวลาพัฒนาโปรแกรม

ตอนแรกๆที่ลองเขียน jQuery ดูก็จะมี IntelliSense ขึ้นมาตามปกติครับ แต่พอใช้ไปประมาณ 1 ชั่วโมงตัว IntelliSense ของ jQuery ก็หายไป ผมนึกว่าเป็น bug ของ VS2008 (อีกแล้ว) ก็เลยลองปิดโปรแกรมแล้วเปิดใหม่ก็ไม่หาย ลอง restart Windows ก็ไม่หายครับ เลยไปลอง search ใน google ดู ก็พบว่า Scout Gu บอกให้ลง hot fix ตัวนี้ครับ “VS90SP1-KB958502-x86.exe” (อ่านจากชื่อ ผมเข้าใจว่าเป็น Service Pack1 ของ VS2008 นะ) ลงแล้วก็ยังไม่หายครับ สุดท้ายไปเจอใน blog หนึงบอกว่าให้ลบเลข 2 ออกจากไฟล์ vsdoc แล้วลองใหม่ ก็เลยลองทำตามครับ แก้ไฟล์เป็น jquery-1.3.2-vsdoc.js ทีนี้ก็ใช้งานได้ตามปกติ IntelliSense มาแล้วครับ ลองเล่นมา 3 วันก็ยังใช้ได้ ถ้าใครเจอปัญหาเดียวกันก็ลองวิธีนี้ดูครับ

Tips: ถ้าต้องการบังคับให้ Visual Studio 2008 ทำการ update JScript IntelliSense สามารถทำได้โดยกด Ctrl+Shift+j ครับ

วันพฤหัสบดีที่ ๒ กรกฎาคม พ.ศ. ๒๕๕๒

ทดสอบ Syntax Highlighter

ผมได้ลองเขียน blog เกี่ยวกับการเขียนโปรแกรมหลายๆที่ พบว่าปัญหาสำคัญอย่างหนึ่งคือการเขียน source code บน blog ซึ่งมันจัดยากมากครับ เพราะต้องเขียน html tag แล้วถ้าอยากให้มีสีสรรก็ต้องกำหนด style ให้มันอีกค่อนข้างเสียเวลาทีเดียวครับ หรือบางทีขี้เกียจก็ copy เป็นรูปมาแต่ว่ามันทำให้ copy source code ไม่ได้นี่สิ ทำให้รู้สึกไม่ค่อย work เลยหยุดเขียน blog เกี่ยวกับการเขียนโปรแกรมไปดื้อๆ

คราวนี้ได้มา search เจอ Syntax Highlighter ซึ่งพัฒนาด้วย javascript ที่ code.google.com น่าสนใจมากครับ ก็เลยเอามาลองที่ blogger ซะเลย แต่ว่าลองใช้เวอร์ชัน 2.x.x แล้วยังไม่ได้ เลยมาลองใช้เวอร์ชันยอดฮิตคือ 1.5.1 ครับ


ก่อนอื่นก็ไปตั้งค่ารูปแบบ (template) ซะก่อน โดยไปที่ รูปแบบ --> แก้ไข HTML และไปเพิ่ม tag ก่อน </head>ดังนี้ครับ



<b:skin>
<link href='http://alexgorbatchev.com/pub/sh/1.5.1/styles/SyntaxHighlighter.css' rel='stylesheet' type='text/css'/>
<script class='javascript' src='http://alexgorbatchev.com/pub/sh/1.5.1/scripts/shCore.js' type='text/javascript'/>
<script class='javascript' src='http://alexgorbatchev.com/pub/sh/1.5.1/scripts/shBrushCSharp.js' type='text/javascript'/>
<script class='javascript' src='http://alexgorbatchev.com/pub/sh/1.5.1/scripts/shBrushJScript.js' type='text/javascript'/>
<script class='javascript' src='http://alexgorbatchev.com/pub/sh/1.5.1/scripts/shBrushVb.js' type='text/javascript'/>
<script class='javascript' src='http://alexgorbatchev.com/pub/sh/1.5.1/scripts/shBrushSql.js' type='text/javascript'/>
<script class='javascript' src='http://alexgorbatchev.com/pub/sh/1.5.1/scripts/shBrushCss.js' type='text/javascript'/>
<script class='javascript' src='http://alexgorbatchev.com/pub/sh/1.5.1/scripts/shBrushXml.js' type='text/javascript'/>
</head>
<body>

และเพิ่ม script tag ก่อน </body> ครับ



<script class='javascript'>
//<![CDATA[
dp.SyntaxHighlighter.ClipboardSwf = 'http://alexgorbatchev.com/pub/sh/1.5.1/scripts/clipboard.swf';
dp.SyntaxHighlighter.BloggerMode();
dp.SyntaxHighlighter.HighlightAll('code');
//]]>
</script>
</body>


สังเกตุว่ามีการเรียกใช้ dp.SyntaxHighlighter.BloggerMode(); ซึ่งเป็น function สำหรับตัด <BR /> ของ Blogger โดยเฉพาะครับ ลอง comment บรรทัดนี้เพื่อดูความแตกต่างก็ได้ครับ

วิธีการใช้งานก็คือ ตอนเราเขียน blog ให้ไปที่ แก้ไข Html แล้วไปสร้าง pre tag คลุมโค้ดที่ต้องการครับ



<pre class="html" name="code">
เขียนโค้ดช่วงนี้ครับ
</pre>


โดยใน pre tag ให้กำหนด name attribute = "code" ครับ ส่วน class นั้น กำหนดตามประเภทโค้ดที่เราต้องการแสดง ถ้าอยากรู้ว่าต้องใส่ class เป็นอะไร ไปดูได้ที่ SyntaxHighlighter Wiki ครับ แต่ว่าเราต้องไปกำหนด <script src=...> ให้ครบนะครับ จากตัวอย่างด้านบนผมใช้แค่บางตัวเท่านั้นเอง

คราวนี้ลองทดสอบ javascript มั่ง



<script type="text/javascript">
alert('Hello world');
</script>

แหม มันเยี่ยมจริงๆครับ