Category: Developer

งานพัฒนาระบบ, เขียนโปรแกรม

  • ASP.NET MVC Part 1 : ทำความรู้จักกับ ASP.NET MVC

    สวัสดีค่ะ วันนี้ผู้เขียนจะมาแนะนำให้รู้จักกับแนวทางการพัฒนา Web Application ด้วย ASP.NET MVC ก่อนหน้านี้ผู้เขียนจะพัฒนา Web Application ด้วย ASP.NET Web Forms มาตลอด และช่วงที่ผ่านมา ผู้เขียนเองมีโอกาสได้ศึกษาและทำความเข้าใจกับ ASP.NET MVC มาซักระยะหนึ่ง จึงมาเขียนบทความเล่าสู่กันฟังเกี่ยวกับ การพัฒนา Web Application ด้วย ASP.NET MVC ซึ่งในบทความนี้ อ้างอิงและแปลมาจากบทความ WebForms vs MVC ซึ่งจะประกอบไปด้วย

    • ASP.NET คืออะไร?
    • ASP.NET Web Forms คืออะไร?
    • MVC คืออะไร?
    • ASP.NET MVC คืออะไร?
    • เปรียบเทียบระหว่าง Web Forms และ MVC
    • เลือกใช้อะไรดี ระหว่าง Web Forms และ MVC ?

    ASP.NET คืออะไร?

       คือ Web application framework ที่ถูกพัฒนาโดย Microsoft สำหรับนักพัฒนาในการพัฒนา web application ขึ้นมา โดยโปรแกรมด้วยภาษา C#,VB.NET และอื่นๆ ณ ปัจจุบัน มีอยู่ 2 รูปแบบในการเลือกใช้พัฒนา ได้แก่ Web Forms และ MVC
    ASP.NET Web Forms คืออะไร?
    คือ framework ในการพัฒนา web application ที่ถูกออกแบบมาในลักษณะ RAD(Rapid Application Development) คือ สามารถพัฒนา Web application ได้อย่างรวดเร็ว โดยใช้วิธีการลาก control มาวางบนพื้นที่ design หน้า page และเขียน code ภายใต้ control เหล่านั้น โดยอาศัย concept การทำงานของ Postback(การส่งข้อมูลไปมาระหว่าง server และ client) และ ViewState(การเก็บค่าให้คงไว้ระหว่างการทำ postback)
    วิธีการสร้างหน้า page ของ Web Forms
    การทำงานของ Web Forms

    MVC คืออะไร?
    คือ design pattern ที่ใช้ในการสร้าง Web Application แนวความคิดของ MVC design pattern จะจัดการแยกหน้าที่ขององค์ประกอบใน application ออกเป็นส่วนๆ(separation) เพื่อให้สะดวก รวดเร็ว และง่ายขึ้น ในการสร้าง พัฒนา และขยายระบบเพิ่มเติม รวมถึงมันจะทำให้เราทดสอบ application นี้เป็นส่วนๆได้โดยไม่กระทบ หรือกระทบน้อยที่สุดกับส่วนอื่น โดย MVC ย่อมาจาก Model, View และ Controller

    Model คือ คือส่วน Business Model หรือส่วนที่ติดต่อกับฐานข้อมูล
    Controller คือ ส่วนควบคุมและรับ request จาก user มาและไปดึงข้อมูลจาก Model มาเพื่อแสดงผลข้อมูลกลับไปยัง user ที่ส่วน View
    View คือ ส่วนที่แสดงผลข้อมูล

    ASP.NET MVC คืออะไร?
    คือ framework ในการพัฒนา web application ที่ถูกออกแบบให้รองรับ MVC pattern โดยจัดการแยกหน้าที่ขององค์ประกอบใน application ออกเป็นส่วนๆ(separation of concerns) ด้วยการจัดการที่แยกออกเป็นส่วนๆ ทำให้การทดสอบระบบ(Unit Testing)ที่ซํบซ้อน ทำได้ง่ายขึ้น

    วิวัฒนาการ ASP.NET Framework

    การทำงานของ ASP.NET MVC

    เปรียบเทียบระหว่าง Web Forms และ MVC
    1. Web Forms
    ข้อดี

    •      Web Form support Rich server control คือ มี control ฝั่ง server มาให้ใช้งานมากมายและหลากหลาย
    Tool box Control ที่ ASP.Net Web Forms เตรียมไว้ให้
    • เป็นการเขียนโปรแกรมแบบ Event Driven Programming คือ การเขียนโปรแกรมในลักษณะว่า “ถ้าเหตุการณ์นี้เกิดขึ้น เราจะให้โปรแกรมของเราจัดการกับเหตุการณ์นั้นๆ อย่างไร”  สำหรับ ASP.NET Web Forms จะมีตัวช่วยในการเขียนลำดับเหตการณ์ ประกอบด้วย Code behind,กลไก postback ตัวเอง และ ViewState ซึ่งด้วยวิธีการนี้ นักพัฒนาไม่จำเป็นต้องพึ่งพา Get/Post method ในการติดต่อกับ Server
    ตัวอย่าง Code Behind ภายใต้เหตุการณ์หลังจากการกดปุ่ม
    • Rapid Application Development จากการที่ Web forms มีการเตรียม Server control มาให้ใช้มากมาย และการเขียนโปรแกรมในลักษณะ Event driven จึงช่วยให้การพัฒนา Application เป็นไปได้รวดเร็วขึ้น ตัวอย่างเช่น นักพัฒนาสามารถลาก control Button(ปุ่ม) มาวาง และ double click เพื่อเขียน code logic การทำงานเข้าไปได้เลย โดยไม่จำเป็นต้องรู้การทำงานเบื้องหลังของ event นั้นๆ ก็สามารถสร้าง Application ให้ทำงานได้
    • Less Learning Effort จากการที่ Web forms มีการเตรียม Server control มาให้ใช้มากมาย และประกอบกับมีการทำงานด้วย ViewState ทำให้ผู้พัฒนาที่มี Skill ทางด้าน HTML และ Java script เล็กน้อย ก็สามารถสร้าง Application ขึ้นได้

    ข้อเสีย

    • Project Architecture 
                 code ทุกอย่างจะอยู่ภายใต้ code behind ซึ่งจะผูกติดกันอย่าแน่นหนากับ หน้า UI
    • Unit Testing  เนื่องจากภายใต้ code behind ที่มีจำนวน event ที่เยอะมาก ทำให้การทำ automatic unit testing ทำได้ยาก
    • Performance ด้วยการที่ Web forms ใช้ ViewState ในการเก็บค่าต่างๆของหน้า page ส่งผลให้ ขนาดของ ViewState ใหญ่ขึ้น ทำให้ performance ของระบบลดลงไปด้วย
    • Reusability  เนื่องจากการผูก code behind เข้ากับ UI ทำให้การ reuse code ทำได้ยาก
         จากรูป หากเราต้องการ binding ข้อมูลในลักษณะเดียวกับกับ Customer ทุกอย่างให้กับ UI อื่นๆ เราก็จะต้องสร้าง method binding ที่เหมือนกันกับในรูปขึ้นมาอีก เนื่องจาก code ถูกผูกเข้ากับ control gridview คนละตัวกัน
    • Less control over HTML control ที่มี่อยู่อาจไม่ support การทำงาน จำเป็นต้องนำเอา java script framework เข้ามาใช้ร่วมด้วย เช่น Jquery, AJAX เป็นต้น

    2. MVC

    ข้อดี
    Project Structure แบ่งแยกส่วนต่างๆ ของ code ออกจากกันชัดเจน
    • Test Driven Development and Reusability
      • ด้วยการแบ่งส่วนการทำงานของ code ออกจากกันชัดเจน ทำให้สามารถทำ Unit Testing ได้ง่ายขึ้น
      • Controller ไม่จำเป็นต้องผูกกับ View ใด View หนึ่ง สามารถ reuse ใช้ได้กับหลายๆ View ได้
    • Performance ใน MVC จะไม่มี ViewState และ event ทำให้ลดขนาด page size ลงไป ทำให้ได้ performance ที่ดีกว่า
    • Full control over HTML ใน MVC ไม่มี server control มาให้เหมือน Web Forms ทำให้เราสามารถใช้ control ของ HTML และ java script ได้อย่างอิสระ
    • SEO,URL Routing and REST มีการเรียก url แบบ REST service และการทำ SEO(Search Engine Optimization) ง่ายกว่า
    •  Support Parallel Development  ใน MVC มีการแบ่งส่วนการทำงานออกจากกันชัดเจน ทำให้ผู้พัฒนาสามารถพัฒนาคู่ขนานกันไปได้ เช่น คนที่รับผิดชอบส่วน View ก็ทำส่วน Design wxพร้อมกับที่ทำส่วน Controller ได้เลย ไม่จำเป็นต้องรอใครทำเสร็จก่อนแล้วจึงจะทำต่อได้

    ข้อเสีย

    • More Learning Effort จำเป็นต้องมีประสบการณ์ในการทำ web application มาพอสมควร เนื่องจากจะไม่มี ViewState, Event และ Server control ที่อำนวยความสะดวกเหมือนใน Web Forms
    • ไม่มี server control ให้ลากวาง เหมือนใน Web Forms ต้องเขียน HTML ขึ้นมาเอง

    เลือกใช้อะไรดีระหว่าง Web Forms และ MVC?

        ในการพัฒนา web application เราจำเป็นต้องพิจารณาจากหลายๆปัจจัย เช่น หากเราต้องการ web application ที่ต้องการให้เสร็จเร็วและง่าย ก็ควรเลือกใช้แบบ Web Forms เนื่องจาก Web Forms ถือว่าเป็น RAD(Rapid Application Development) คือ เตรียมทุกอย่างมาให้พร้อมที่จะพัฒนาได้เลยเช่น Server control,ViewState,Event เป็นต้น แต่หากเราต้องการพัฒนา web application ที่ซับซ้อนและต้องการ performance ที่ดี ประกอบกับทีมพัฒนามีความรู้ทางด้านการพัฒนา web application เป็นอย่างดี ก็ควรเลือกใช้ MVC มาเป็นตัวเลือกในการพัฒนา

    ที่มา: https://www.facebook.com/photo.php?fbid=685327044884788

    ในบทความถัดไปจะแนะนำการเริ่มต้นสร้าง web application ด้วย ASP.NET MVC ร่วมกับ Bootstrap >> ASP.NET MVC Part2: เริ่มต้นสร้างเว็บด้วย MVC with Bootstrap

    อ้างอิง :

    • http://www.codeproject.com/Articles/528117/WebForms-vs-MVC
    • http://www.codeproject.com/Articles/821275/Webforms-vs-MVC-and-Why-MVC-is-better
  • Export ข้อมูลไฟล์ Excel ในแบบ Single และ Multiple sheet ด้วย ASP.NET(C#)

                ความเดิมตอนที่แล้ว ผู้เขียนได้พูดถึงความสำคัญของข้อมูล และวิธีการ Import ข้อมูลจากไฟล์ Excel กันไปพอสมควร สำหรับในบทความนี้ ผู้เขียนจะขอพูดถึงวิธีการส่งออกข้อมูล หรือที่เรามักเรียกกันติดปากว่า “Export ข้อมูล” กันบ้าง เพื่อให้ผู้พัฒนาที่มีความสนใจสามารถพัฒนาโปรแกรมได้ครบวงจรทั้งแบบนำเข้าและส่งออกข้อมูล โดยผู้เขียนจะไม่ขอพูดถึงในรายละเอียดที่ได้กล่าวไว้แล้วก่อนหน้า ผู้อ่านสามารถอ่านรายละเอียดเพิ่มเติมได้ในบทความ “เรียนรู้วิธีการ Import ข้อมูลในรูปแบบไฟล์ Excel ด้วย ASP.NET (C#)” สำหรับในบทความนี้ผู้เขียนจะเน้นในส่วนของการ Export ข้อมูลเท่านั้น ซึ่งจะมีการอธิบายใน 2 ลักษณะเช่นกัน คือ แบบ Single sheet และแบบ Multiple sheet เพื่อให้เห็นเป็นแนวทางและสามารถนำไปต่อยอดการพัฒนาเพิ่มเติมสำหรับแต่ละท่านได้

    กรณีส่งออกข้อมูล(Export) ซึ่งในบทความนี้จะแบ่งเป็น 2 ลักษณะ ดังนี้

    • การส่งออกข้อมูลแบบ Single-sheet ซึ่งมีขั้นตอนการทำงานไม่ซับซ้อนมากนัก ดังนี้
    protected void btnExport_Click(object sender, EventArgs e)
    
    {
    //// ตารางสมมติ สร้างขึ้นเพื่อให้ผู้พัฒนาเห็นภาพ หากเป็นกรณีใช้งานจริงจะเป็นข้อมูลที่ดึงจากฐานข้อมูลเพื่อ Export ในรูปแบบไฟล์ Excel
    DataTable table = new DataTable();
    table.Columns.Add("Name", typeof(string));
    table.Columns.Add("Latitude", typeof(decimal));
    table.Columns.Add("Longitude", typeof(decimal));
    table.Columns.Add("Description", typeof(string));
    table.Rows.Add("University1", 7.006923, 100.500238, "Desc1");
    table.Rows.Add("University2", 7.172661, 100.613726, "Desc2");
    
    
    
    StringBuilder sb = new StringBuilder();
    if (table.Rows.Count > 0)
    {
    string fileName = Path.Combine(Server.MapPath("~/ImportDocument"), DateTime.Now.ToString("ddMMyyyyhhmmss") + ".xls");
    
    //// ลักษณะการตั้งค่าเพื่อเชื่อมต่อด้วย OleDb ซึ่งในกรณีนี้ไฟล์ Excel จะต้องมีนามสกุลเป็น .xls แต่หากเป็นนามสกุลแบบ .xlsx ต้องเปลี่ยนการกำหนดค่าให้เป็น
    conString = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + 
    fileName + ";Extended Properties='Excel 12.0;HDR=YES;IMEX=1;';";  แทน
    
    string conString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + fileName + ";Extended Properties=\"Excel 8.0;HDR=Yes;IMEX=2\"";
    
    
     using (OleDbConnection con = new OleDbConnection(conString))
    {
    ////เขียนคำสั่งในการสร้างตาราง ซึ่งในที่นี้คือ WorkSheet ที่ต้องการ พร้อมทั้งกำหนดชื่อและชนิดของข้อมูลในแต่ละคอลัมน์
    string strCreateTab = "Create table University (" +
    " [Name] varchar(50), " +
    " [Latitude] double, " +
    " [Longitude] double, " +
    " [Description] varchar(200)) ";
    
    
    
    if (con.State == ConnectionState.Closed)
    {
    con.Open();
    }
    ////รันคำสั่งที่เขียนในการสร้างตาราง
    OleDbCommand cmd = new OleDbCommand(strCreateTab, con);
    cmd.ExecuteNonQuery();
    
    ////เขียนคำสั่งในการเพิ่มข้อมูล(insert) ข้อมูลในแต่ละฟิลด์ รวมทั้งประกาศพารามิเตอร์ที่ใช้ในการรับค่าข้อมูลที่อ่านได้
    string strInsert = "Insert into University([Name],[Latitude]," +
    " [Longitude], [Description]" +
    ") values(?,?,?,?)";
    
    OleDbCommand cmdIns = new OleDbCommand(strInsert, con);
    cmdIns.Parameters.Add("?", OleDbType.VarChar, 50);
    cmdIns.Parameters.Add("?", OleDbType.Double);
    cmdIns.Parameters.Add("?", OleDbType.Double);
    cmdIns.Parameters.Add("?", OleDbType.VarChar, 200);
    
    ////วนค่าที่ได้จากฐานข้อมูลและกำหนดค่าให้กับพารามิเตอร์และรันคำสั่งในการเพิ่มข้อมูลทีละรายการ
    foreach (DataRow  i in table.Rows)
    {
    cmdIns.Parameters[0].Value = i["Name"];
    cmdIns.Parameters[1].Value = i["Latitude"];
    cmdIns.Parameters[2].Value = i["Longitude"];
    cmdIns.Parameters[3].Value = i["Description"];
    cmdIns.ExecuteNonQuery();
    
    }
    }
    
    ////สร้างไฟล์ที่ต้องการดาวน์โหลดจากการอ่านที่ได้ทั้งหมด เพื่อบันทึกเป็นไฟล์ Excel ที่มีชื่อว่า University.xls
    
    byte[] content = File.ReadAllBytes(fileName);
    HttpContext context = HttpContext.Current;
    context.Response.BinaryWrite(content);
    context.Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
    context.Response.AppendHeader("Content-Disposition", "attachment; filename=University.xls");
    Context.Response.End();
    }
    }
    • การส่งออกข้อมูลแบบ Multiple sheet ใช้ในกรณีที่มีข้อมูลที่ Export จำนวนมากและต้องการแบ่งข้อมูลที่มีอยู่ออกเป็น WorkSheet ย่อย ซึ่งหลักการทำงานจะคล้ายกับการส่งออกข้อมูลแบบ Single-sheet แต่จะซับซ้อนกว่าในส่วนของการวนค่าจากในฐานข้อมูลมาใส่ใน  WorkSheet โดยมีการกำหนดจำนวนแถวที่จะให้บันทึกในแต่ละ WorkSheet หากเกินค่าที่กำหนดจะสร้าง WorkSheet ใหม่และบันทึกข้อมูลลงไปใน WorkSheet นั้น
    static StringBuilder sqlInsert = new StringBuilder();
    static StringBuilder strScript = new StringBuilder();
    
    public static void ExportToMultipleXLSheets( System.String OutputFileName)
    {
    string connstr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" +
    mOutputFileName + ";Extended Properties='Excel 8.0'";
    OleDbConnection xlConn = new OleDbConnection(connstr);
    
    try
    {
    xlConn.Open();
    ////ตารางสมมติเช่นเดียวกับที่ได้กล่าวไว้ในแบบการส่งออกข้อมูลแบบ Single sheet
    DataTable table = new DataTable();
    table.Columns.Add("EmployeeID", typeof(string));
    table.Columns.Add("Name", typeof(string));
    table.Columns.Add("LastName", typeof(string));
    table.Columns.Add("Position", typeof(string));
    
    table.Rows.Add("0001", "Jack", "Rayman", "Programmer");
    table.Rows.Add("0002", "John", "Wicky", "Programmer");
    table.Rows.Add("0003", "Jenifer", "Wincy", "Programmer");
    table.Rows.Add("0004", "Kate", "Wrapper", "Manager");
    table.Rows.Add("0005", "Bella", "Cole", "Programmer");
    table.Rows.Add("0006", "Sandy", "Stick", "Programmer");
    table.Rows.Add("0007", "Tom", "Runner", "Programmer");
    
    ////เป็นการเรียกใช้เมธอดที่ใช้ในการเตรียมคำสั่งที่จะใช้วนสร้างตาราง/Worksheet
    PrepareScript(table);
    
    ////เป็นเมธอดที่ใช้ในการเริ่มต้นทำงานคำสั่งในการ Export โดยจะมีการคำนวณจำนวนแถวของข้อมูลว่าเกินที่ระบุไว้หรือไม่ หากเกินกำหนดจะสร้าง WorkSheet ใหม่นั่นเอง
    StartExport(table, xlConn);
    
    if (xlConn != null)
    {
    if (xlConn.State == ConnectionState.Open) xlConn.Close();
    xlConn.Dispose();
    }
    
    ////อ่านค่าและ Export ไปยังไฟล์ที่มีชื่อว่า ExportMultiSheetData.xls
    byte[] content = File.ReadAllBytes(mOutputFileName);
    HttpContext context = HttpContext.Current;
    context.Response.BinaryWrite(content);
    context.Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
    context.Response.AppendHeader("Content-Disposition", "attachment; filename=ExportMultiSheetData.xls"  );
    context.Response.End();
    
    }
    catch (Exception exp)
    {
    throw new Exception("ImportToMultipleXLSheets", exp.InnerException);
    }
    finally
    {
    if (xlConn != null)
    {
    if (xlConn.State == ConnectionState.Open) xlConn.Close();
    xlConn.Dispose();
    }
    }
    }
    o เมธอดในการเตรียมคำสั่งที่ใช้ในการสร้างตารางและเพิ่มข้อมูล
    private static string PrepareScript(DataTable DTable)
    {
    // เตรียมคำสั่งในการสร้าง WorkSheet
    sqlInsert.Length = 0;
    strScript.Length = 0;
    
    ////วนค่าเพื่ออ่านค่าคอลัมน์ที่มีในแต่ละตาราง
    for (int i = 0; i < DTable.Columns.Count; i++)
    {
    sqlInsert.Append( "[" + DTable.Columns[i].ColumnName + "],");
    strScript.Append("[" + DTable.Columns[i].ColumnName.Replace("'", "''") + "]");
    
    ////กำหนดชนิดของข้อมูลแต่ละคอลัมน์
    if (DTable.Columns[i].DataType.ToString().ToLower().Contains("int") || DTable.Columns[i].DataType.ToString().ToLower().Contains("decimal"))
    strScript.Append(" double");
    else
    strScript.Append(" text");
    strScript.Append(", ");
    }
    
    sqlInsert.Remove(sqlInsert.Length - 1, 1);
    strScript.Remove(strScript.Length - 2, 1);
    strScript.Append(") ");
    ////ผลที่ได้ : 
    sqlInsert >> [EmployeeID],[Name],[LastName],[Position]
    strScript >> [EmployeeID] text, [Name] text, [LastName] text, [Position] text ) 
    เพื่อนำไปใช้ในการ run คำสั่งการทำงานสร้าง (create table) และเพิ่มข้อมูล (insert)
    
    return strScript.ToString();
    }
    o เมธอดในการสร้างตาราง/WorkSheet ตามคำสั่งที่เตรียมไว้
    private static void CreateXLSheets(DataTable DTable,
    OleDbConnection xlConn, System.String XLSheetName)
    {
    // สร้าง WorkSheet ใหม่
    System.Text.StringBuilder SqlFinalScript = new System.Text.StringBuilder();
    
    OleDbCommand cmdXl = new OleDbCommand();
    try
    {
    SqlFinalScript.Length = 0;
    cmdXl.Connection = xlConn;
    
    ///สร้างคำสั่งในการสร้างตาราง/WorkSheet ขึ้นใหม่ โดยตั้งชื่อตามพารามิเตอร์ที่ได้รับมา
    SqlFinalScript.Append("CREATE TABLE " + XLSheetName + " (");
    SqlFinalScript.Append(strScript.ToString());
    cmdXl.CommandText = SqlFinalScript.ToString();
    cmdXl.ExecuteNonQuery();
    }
    catch (Exception xlSheetExp)
    {
    throw (new Exception("CreateXLSheetException", xlSheetExp.InnerException));
    }
    finally
    {
    cmdXl.Dispose();
    }
    }
    
    private static void StartExport(DataTable DTable, OleDbConnection xlConn)
    {
    Int64 rowNo = 0, xlSheetIndex = 1, TotalNoOfRecords = 0;
    System.String NewXLSheetName = "Sheet";
    System.Text.StringBuilder strInsert = new System.Text.StringBuilder();
    TotalNoOfRecords = DTable.Rows.Count;
    OleDbCommand cmdXl = new OleDbCommand();
    cmdXl.Connection = xlConn;
    for (int count = 0; count < DTable.Rows.Count; count++)
    {
    strInsert.Length = 0;
    ////กรณีที่เป็นแถวแรกจะทำการสร้าง WorkSheet ขึ้นใหม่ โดยเรียกใช้เมธอด CreateXLSheets
    if (rowNo == 0 )
    { CreateXLSheets(DTable, xlConn, NewXLSheetName + xlSheetIndex);
    }
    rowNo += 1;
    
    ////กรณีที่จำนวนแถวใน 1 WorkSheet เกินที่กำหนดไว้จะสร้างชีทใหม่ ในกรณีนี้คือจะสร้าง WorkSheet ใหม่ทุกๆ 3 แถว และเพิ่มลำดับของ index ของ WorkSheet คราวละ 1
    if (TotalNoOfRecords > 3 && rowNo > 3
    {
    xlSheetIndex += 1;
    CreateXLSheets(DTable, xlConn, NewXLSheetName + xlSheetIndex);
    rowNo = 1;
    }
    
    ////เขียนคำสั่งในการเพิ่มข้อมูลไปยัง WorkSheet ที่กำหนด โดยมีการแทนที่ค่าของคำสั่งที่เตรียมไว้แล้วก่อนหน้านี้ในตอนเรียกใช้เมธอด PrepareScript
    strInsert.Append("Insert Into [" + NewXLSheetName + xlSheetIndex.ToString() +  "$](" + sqlInsert.ToString() + ") Values (");
    
    
    foreach (DataColumn dCol in DTable.Columns)
    {
    if (dCol.DataType.ToString().ToLower().Contains("int"))
    {
    if (DTable.Rows[count][dCol.Ordinal].ToString() == "")
    strInsert.Append("NULL");
    else
    strInsert.Append(DTable.Rows[count][dCol.Ordinal]);
    }
    else if (dCol.DataType.ToString().ToLower().ToLower().Contains("decimal"))
    {
    if (DTable.Rows[count][dCol.Ordinal].ToString() == "")
    strInsert.Append("NULL");
    else
    strInsert.Append(DTable.Rows[count][dCol.Ordinal]);
    }
    else
    strInsert.Append("\"" +
    DTable.Rows[count][dCol.Ordinal].ToString().Replace("'",
    "''") + "\"");
    strInsert.Append(",");
    }
    strInsert.Remove(strInsert.Length - 1, 1);
    strInsert.Append(");");
    
    ////run คำสั่งที่สร้างขึ้นในการเพิ่มข้อมูลทีละรายการ
    cmdXl.CommandText = strInsert.ToString();
    cmdXl.ExecuteNonQuery();
    
    }
    }
    
    o เขียนการทำงานเมื่อกดปุ่ม “Export”
    protected void btnExport_Click(object sender, EventArgs e)
    { 
     string fileName = Path.Combine(Server.MapPath("~/ImportDocument"), Guid.NewGuid().ToString() + ".xls");
    //เรียกใช้เมธอดในการ Export ข้อมูลแบบ Multiple sheet
    ExportToMultipleXLSheets(fileName);
    
    }

    หลักการทำงานคร่าวๆในการ Export ข้อมูลใน 2 ลักษณะจะมีหลักการพื้นฐานคล้ายกัน ซึ่งจะสรุปได้ดังนี้

    • ดึงข้อมูลจากฐานข้อมูลมาเก็บไว้ใน Datadatable หรือ Dataset
    • กำหนดชื่อไฟล์ที่จะใช้ในการเชื่อมต่อกับ OleDb
    • เชื่อมต่อกับ OleDb โดยการกำหนดค่าต่างๆ เพื่อใช้ในการทำงานกับไฟล์ Excel นั้นๆ
    • เตรียมกำหนดคำสั่ง sql command ในการสร้างโครงสร้างตาราง(create table) และ เพิ่มข้อมูล (insert) ในตารางที่สร้างไว้ หากเป็นกรณีที่ต้องการสร้างแบบ Multiple sheet
      ก็จะทำการเตรียมชุดคำสั่งไว้ และนำไปวน run คำสั่งนั้นๆตามจำนวนรอบที่มีการสร้าง WorkSheet ใหม่นั่นเอง
    • สั่ง run คำสั่งดังกล่าว หากจำนวนแถวของข้อมูลเกินกว่าที่กำหนดต่อ 1 WorkSheet (ซึ่งในกรณีสมมติให้เป็น 3 แถว) จะทำการสร้าง WorkSheet ใหม่
    • อ่านค่าที่ได้จากไฟล์ที่เชื่อมต่อกับ OleDb และผ่านกระบวนการ Export ข้อมูล  มาเขียนเป็นไฟล์ Excel เพื่อให้ผู้ใช้สามารถบันทึกข้อมูลในรูปแบบไฟล์ที่ Export ได้
    • หากเป็นการ Export ข้อมูลแบบ Multiple sheet จำนวนของ WorkSheet จะขึ้นอยู่กับข้อมูลที่ดึงมาและจำนวนที่กำหนดไว้ต่อ 1 WorkSheet นั่นเอง
    หมายเหตุ : Namespace ที่ต้องอ้างอิงเพิ่มเติมในการใช้งานโค้ดที่กล่าวไว้ข้างต้น มีดังนี้
                –  System.IO
                –  System.Data.OleDb
                –  System.Data
                –  System.Text

                บทความนี้ถือเป็นเพียงการแนะนำวิธีเบื้องต้นในการ Export ข้อมูลไฟล์ในรูปแบบ Excel เท่านั้น ซึ่งผู้พัฒนาสามารถนำความรู้หรือข้อมูลที่ได้จากบทความนี้ไปประยุกต์ ดัดแปลงหรือเพิ่มลูกเล่นให้กับงานที่ท่านกำลังพัฒนาอยู่ได้ เพื่อเพิ่มความสามารถในกระบวนการนำ-เข้าส่งออกดังกล่าว นอกจากนี้ผู้เขียนหวังเป็นอย่างยิ่งว่าบทความนี้อาจเป็นอีกหนึ่งทางเลือกสำหรับผู้พัฒนามือใหม่หรือผู้ที่กำลังค้นหาวิธีการนี้อยู่ สามารถนำไปพัฒนาต่อได้โดยง่าย หากผิดพลาดประการใด ผู้เขียนขออภัยไว้ ณ ที่นี้ค่ะ

    แหล่งข้อมูลอ้างอิง :
    http://www.codeproject.com/Articles/33271/Import-and-Export-to-Multiple-Worksheets
    http://dotnetawesome.blogspot.com/2013/11/how-to-import-export-database-data-from_18.html

  • เรียนรู้วิธีการ Import ข้อมูลในรูปแบบไฟล์ Excel ในแบบ Single และ Multiple sheet ด้วย ASP.NET (C#)

                “ข้อมูล” นับว่าเป็นส่วนหนึ่งที่มีความสำคัญในการทำงานหรือการแสดงผลข้อมูลของระบบหรือเว็บไซต์ในปัจจุบัน ซึ่งอาจมีที่มาจากการจัดการเองผ่านระบบจัดการข้อมูล (Back office) หรือมีการนำเข้าจากแหล่งอื่นเนื่องจากข้อมูลที่ต้องการบันทึกเข้าสู่ระบบดังกล่าวอาจมีจำนวนมาก ทำให้การป้อนข้อมูลผ่านระบบทีละรายการเป็นไปอย่างลำบากและเกิดความผิดพลาดได้โดยง่าย เช่น ข้อมูลการเงิน ข้อมูลการสั่งซื้อสินค้า เป็นต้น อีกทั้งยังพบว่ามีบางกรณีที่ผู้ใช้มีความต้องการดึงข้อมูลจากฐานข้อมูลและส่งออกมาในรูปแบบไฟล์อื่นๆเพื่อนำไปประมวลผลต่อไปได้โดยง่าย ซึ่งโดยทั่วไปผู้ใช้มักเก็บข้อมูลเหล่านี้ในรูปแบบไฟล์ต่างๆ เช่น ไฟล์ Excel หรือ ไฟล์ Access ดังนั้น นักพัฒนาของระบบที่มีความต้องการจัดการข้อมูลในลักษณะนี้จึงจำเป็นที่จะต้องมีความรู้เกี่ยวกับกระบวนการดังกล่าวเพื่อให้ได้ข้อมูลตามรูปแบบที่ผู้ใช้ต้องการ ซึ่งในบทความนี้ ผู้เขียนขอเสนอวิธีการ Import ข้อมูลให้ออกมาในรูปแบบไฟล์ Excel พัฒนาโดยใช้ ASP.NET ด้วย C# เนื่องจากเป็นกรณีความต้องการที่พบบ่อยและสามารถนำข้อมูลจากไฟล์ไปใช้งานต่อได้โดยง่าย ซึ่งจะพูดทั้งในลักษณะแบบ Single sheet และแบบ Multiple sheet เพื่อที่จะช่วยให้ผู้พัฒนาที่มีความสนใจสามารถเห็นภาพการทำงานโดยรวม และสามารถนำไปประยุกต์ใช้กับระบบที่ท่านกำลังพัฒนาอยู่ได้

    กรณีนำเข้าข้อมูล(Import) ซึ่งในบทความนี้จะแบ่งเป็น 2 ลักษณะ ดังนี้

    • การนำเข้าข้อมูลแบบ Single sheet ถือเป็นการนำเข้าในแบบทั่วไป ไม่ซับซ้อนมากนัก
    ฝั่ง Client
    <%@ Page Language="C#" AutoEventWireup="true" 
    CodeFile="Excel.aspx.cs" Inherits="ExcelTest" %>
    <!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 runat="server">
    <title></title>
    </head>
    <body>
    <form id="form1" runat="server">
    <asp:FileUpload ID="FileUpload1" runat="server" />
    <div>
    <asp:GridView ID="GridView" runat="server">
    </asp:GridView>
    <asp:Button ID="btnImport" runat="server" 
    onclick="btnImport_Click" Text="Import" />
    </div>
    </form>
    </body>
    </html>
    ฝั่งเซิร์ฟเวอร์
    protected void btnImport_Click(object sender, EventArgs e)
    {
    try
    {
    ////เป็นการกำหนดชื่อของไฟล์ที่ต้องการจะบันทึกลงเซิร์ฟเวอร์ ซึ่งมีการระบุพาธรวมทั้งนามสกุลของไฟล์ตามไฟล์ที่รับเข้ามา
    string fileName =  Path.Combine(Server.MapPath("~/ImportDocument"), Guid.NewGuid().ToString() + Path.GetExtension(FileUpload1.PostedFile.FileName));
    
    ////บันทึกไฟล์ดังกล่าวลงเซิร์ฟเวอร์
    FileUpload1.PostedFile.SaveAs(fileName);
    
    string conString = "";
    
    string ext = Path.GetExtension(FileUpload1.PostedFile.FileName);
    
    ////เป็นส่วนของเงื่อนไขในการตั้งค่า ConnectionString ในการอ่านไฟล์ Excel ด้วย OleDb ซึ่งจะแยกด้วยนามสกุลของไฟล์ Excel ที่รับมา
    if (Path.GetExtension(ext) == ".xls")
    {
    conString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" +
     fileName + ";Extended Properties=\"Excel 8.0;HDR=Yes;IMEX=2\"";
    }
    else if (Path.GetExtension(ext) == ".xlsx")
    {
    conString = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + 
    fileName + ";Extended Properties='Excel 12.0;HDR=YES;IMEX=1;';";
    }
    
    ////เป็นการเปิดการเชื่อมต่อผ่าน OleDb
    OleDbConnection con = new OleDbConnection(conString);
    
    if (con.State == System.Data.ConnectionState.Closed)
    {
    con.Open();
    }
    
    
    DataTable dtExcelSchema;
    
    dtExcelSchema = con.GetOleDbSchemaTable(OleDbSchemaGuid.Tables,
     null);
    ////ดึงค่าชื่อของ Worksheet ที่อ่านมาจากไฟล์ Excel ที่รับเข้ามา
    string SheetName = dtExcelSchema.Rows[0]["TABLE_NAME"].ToString();
    
    ////เขียนคำสั่งในการดึงข้อมูลจาก Worksheet ดังกล่าว ซึ่งลักษณะการทำงานจะคล้ายกับการเขียนคำสั่ง sql command ในการดึงข้อมูลตารางโดยทั่วไป และเปรียบ Worksheet นั้นเป็นตาราง
    string query = "Select *  from [" + SheetName + "]";
    
    OleDbCommand cmd = new OleDbCommand(query, con);
    OleDbDataAdapter da = new OleDbDataAdapter(cmd);
    DataSet ds = new DataSet();
    da.Fill(ds);
    da.Dispose();
    con.Close();
    con.Dispose();
    ////เป็นการสมมติโครงการสร้างตาราง หากเป็นการทำงานจริงส่วนนี้จะหมายถึงตารางในฐานข้อมูลของแต่ละระบบ
    DataTable table = new DataTable();
    table.Columns.Add("EmployeeID", typeof(string));
    table.Columns.Add("EmployeeName", typeof(string));
    ////เป็นการวนค่าเพื่อบันทึกลงฐานข้อมูล แต่ในกรณีนี้จะเป็นเพียงแค่การเพิ่มแถวข้อมูลลงใน datatable ที่ชื่อ table เท่านั้น
    foreach (DataRow dr in ds.Tables[0].Rows)
    {
    //// dr["EmployeeID"].ToString() ชื่อของค่าฟิลด์ต้องตรงกับชื่อของคอลัมน์ใน Worksheet ที่อ่านมาจากไฟล์ Excel
    table.Rows.Add(dr["EmployeeID"].ToString(), dr["EmployeeName"].ToString());
    }
    ////นำค่าที่ได้แสดงในกริดวิว
    GridView.DataSource = table;
    GridView.DataBind();
    
    }
    catch (Exception)
    {
    throw;
    }
    }
    • การนำเข้าข้อมูลแบบ Multiple Sheet จะใช้ในกรณีที่มีจำนวนของ Worksheet ไม่จำกัด ขึ้นอยู่กับข้อมูล ซึ่งจะมีความซับซ้อนกว่าแบบแรก โดยหลักการทำงานโดยสรุปจะเป็นในลักษณะของการดึงข้อมูล Worksheet ที่มีทั้งหมดในไฟล์ Excel ที่อ่านได้ และนำไปเพื่อวนอ่านค่าข้อมูลในแต่ละชีทและนำค่าเหล่านั้นลงฐานข้อมูล ซึ่งจะอธิบายเป็นส่วนๆดังนี้
    1. การดึงข้อมูลชื่อ Worksheet ที่มีทั้งหมดในไฟล์ที่รับเข้ามา
     public static string[] getExcelSheets(string mFile)
    {
    try
    {
    string strXlsConnString;
    strXlsConnString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + mFile + ";Extended Properties='Excel 8.0;HDR=Yes;IMEX=1'";
    OleDbConnection xlsConn = new OleDbConnection(strXlsConnString);
    xlsConn.Open();
    
    ////เป็นการดึงค่าชื่อ Worksheet ของไฟล์ excel ที่กำลังอ่าน โดยตารางหรือชีทใน Excel จะมีสัญลักษณ์ $ ต่อท้ายชื่อเสมอ
    DataTable xlTable = new DataTable();
    xlTable = xlsConn.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null);
    System.String strExcelSheetNames = "";
    string sheetName;
    for (int lngStart = 0; lngStart < xlTable.Rows.Count; 
    lngStart++)
    {
    ////เป็นการเอา '' ออกจากชื่อตาราง/worksheet ที่ดึงมาได้
    sheetName = xlTable.Rows[lngStart][2].ToString().Replace("'", "");
    
    ////เป็นการคัดกรองเฉพาะตัวที่เป็นตารางหรือworksheet เนื่องจากจบด้วย $
    if (sheetName.EndsWith("$"))
    {
    ////เป็นการเชื่อมตัวสุดท้ายด้วย ~ เพื่อใช้ในการตัดคำในขั้นตอนถัดไป
    strExcelSheetNames += sheetName.Substring(0, sheetName.Length - 1) + "~";
    }
    }
    ////เป็นการตัด  ~ ตัวสุดท้ายออกจากการเชื่อมคำ
    if (strExcelSheetNames.EndsWith("~"))
    {
    strExcelSheetNames = strExcelSheetNames.Substring(0,
    strExcelSheetNames.Length - 1);
    }
    xlsConn.Close();
    xlsConn.Dispose();
    char[] chrDelimter = { '~' };
    ////เป็นการตัดคำด้วย ~ และส่งค่าตัวแปร array ของ string ที่เป็นชื่อ worksheet ทั้งหมดที่อ่านได้กลับไป
    return strExcelSheetNames.Split(chrDelimter);
    
    }
    catch (Exception exp)
    {
    throw new Exception("Error while listing the excel" +
    " sheets from upload file " + exp.Message, exp);
    }
    }
    2. การอ่านค่าข้อมูลใน Worksheet ที่มีทั้งหมดในไฟล์ที่รับเข้ามา โดยมีการส่งค่าของชื่อ Worksheet และชื่อของไฟล์ที่อ่าน รวมทั้งฟิลด์เพิ่มเติมที่ต้องการระบุในการอ่านค่า ซึ่งจะทำงานในลักษณะเดียวกับการ Import แบบ Single sheet นั่นเอง
    public static DataSet getXLData(string xlSheetName,
    string xlFileName, string AdditionalFields)
    
    {
    try
    {
    string connstr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" 
    +  xlFileName + ";Extended Properties='Excel 8.0;HDR=Yes;IMEX=1'";
    OleDbConnection xlConn = new OleDbConnection(connstr);
    DataSet xlTDS = new DataSet("xlDataSet");
    xlConn.Open();
    OleDbDataAdapter xlDA = new OleDbDataAdapter("Select" + AdditionalFields +  " * from [" + xlSheetName + "$] ", xlConn);
    xlDA.Fill(xlTDS);
    xlConn.Close();
    xlConn.Dispose();
    
    ////เป็นการลบแถวที่มีค่าว่างออกจากการอ่านข้อมูลในไฟล์
    RemoveEmptyRows(xlTDS.Tables[0], (AdditionalFields.Length -
    
    AdditionalFields.ToLower().Replace(" as ", "").Length) / 4);
    
    return xlTDS;
    }
    catch (Exception e)
    {
    throw new Exception("Error while reading data from excel sheet", e);
    }
    }
    
    public static void RemoveEmptyRows(DataTable dtbl,
    
    System.Int32 intNumberOfFieldsToIgnore) ////เป็นการตรวจสอบค่าว่างในแต่ละแถว
    System.String strFilter = "";
    System.Int32 intAvgColsToCheck =
    Convert.ToInt32((dtbl.Columns.Count - intNumberOfFieldsToIgnore) * 0.75);
    if (intAvgColsToCheck < 3)
    {
    intAvgColsToCheck = dtbl.Columns.Count;
    }
    System.Int32 lngEnd = dtbl.Columns.Count;
    lngEnd = lngEnd - intAvgColsToCheck;
    
    ////เป็นการเชื่อมเงื่อนไขในการดึงข้อมูลว่าให้ฟิลด์ใดบ้างที่ห้ามเป็นค่าว่าง ในที่นี้จะทำการวนดูคอลัมน์ที่มีใน worksheet นั้นๆ และเชื่อมเป็นเงื่อนไข
    for (int lngStartColumn = dtbl.Columns.Count;
    lngStartColumn > lngEnd; lngStartColumn--)
    {
    strFilter += "[" + dtbl.Columns[lngStartColumn - 1].ColumnName +
    "] IS NULL AND ";
    
    }
    
    ////ทำในกรณีที่มีอย่างน้อย 1 คอลัมน์ถูกเพิ่มเป็นเงื่อนไขในการตรวจสอบค่าว่าง และลบคำว่า “AND” สุดท้ายออก เพื่อนำไปใช้งานในการกรองข้อมูลตามเงื่อนไขนี้
    if (strFilter.Length > 1)
    {
    strFilter = strFilter.Remove(strFilter.Length - 4);
    
    }
    DataRow[] drows = dtbl.Select(strFilter);
    ////ลบแถวเมื่อพบว่าค่าของฟิลด์นั้นๆ เป็นค่าว่าง
    foreach (DataRow drow in drows)
    {
    dtbl.Rows.Remove(drow);
    }
    }
    3. เขียนการทำงานเมื่อกดปุ่ม “Import”
    protected void btnImport_Click(object sender, EventArgs e)
    
    {
    ////เรียกใช้เมธอดในการบันทึกไฟล์ Excel ที่รับเข้ามาตามพาธของโฟลเดอร์ที่กำหนด
    string fileName = uploadXLFile(FileUpload, Server.MapPath("~/ImportDocument"));
    
    ////เรียกใช้เมธอดในการดึงค่าชื่อ WorkSheet ที่มีทั้งหมดในไฟล์ โดยมีตัวแปร array ชนิด string มารับข้อมูลดังกล่าว
    string[] listExcelSheet = getExcelSheets(fileName);
    
    DataSet DSTotal = new DataSet();
    ////วนลูปข้อมูล WorkSheet ตามชื่อในตัวแปร array และส่งค่าให้กับเมธอดที่ใช้ในการอ่านค่าข้อมูลในแต่ละ WorkSheet นั้น
    for (int i = 0; i < listExcelSheet.Count(); i++)
    {
    DataSet DS = getXLData(listExcelSheet[i], fileName, "");
    DSTotal.Merge(DS);
    
    }
    
    ////แสดงผลตัวอย่างข้อมูลที่อ่านได้ในกริดวิว ซึ่งในการใช้งานจริงในส่วนนี้ผู้พัฒนาจะต้องนำข้อมูลที่อ่านได้เหล่านี้วนบันทึกลงฐานข้อมูลเช่นเดียวกับที่กล่าวไว้ในการ Import ข้อมูลแบบ Single sheet นั่นเอง
    if (DSTotal.Tables[0].Rows.Count > 0)
    {
    GvData.DataSource = DSTotal.Tables[0];
    GvData.DataBind();
    
    }
    }
    }
    
     public static string uploadXLFile(FileUpload fileUpload, string mPath)
     {
     mPath = Path.Combine(mPath ,Guid.NewGuid().ToString() + Path.GetExtension(fileUpload.PostedFile.FileName));
     fileUpload.SaveAs(mPath);
     return mPath;
     }
    
    

     

    จะเห็นว่าจริงๆแล้วการทำงานใน 2 ลักษณะจะมีหลักการพื้นฐานคล้ายกัน ซึ่งจะสรุปได้ดังนี้

    • บันทึกไฟล์ Excel บนเซิร์ฟเวอร์ตามพาธที่กำหนดเพื่อให้สามารถเรียกอ่านค่าได้
    • เชื่อมต่อกับ OleDb โดยการกำหนดค่าต่างๆ เพื่อใช้ในการอ่านค่าจากไฟล์ Excel นั้นๆ
    • กำหนดคำสั่ง sql command ในการดึงข้อมูลจาก WorkSheet ซึ่งต้องมีการระบุชื่อของ WorkSheet นั้นๆ
    • สั่ง run คำสั่งดังกล่าวและนำค่าที่ได้ไปประมวลผลต่อไป
    • หากเป็นกรณีแบบ Multiple sheet เราจะไม่สามารถทราบจำนวนและชื่อของ WorkSheet ตายตัว จึงต้องเพิ่มการทำงานที่ทำการวนค่าเพื่อดึงข้อมูลชื่อ WorkSheet และทำตามกระบวนการต่อไป
    หมายเหตุ : Namespace ที่ต้องอ้างอิงเพิ่มเติมในการใช้งานโค้ดที่กล่าวไว้ข้างต้น มีดังนี้
                –  System.IO
                –  System.Data.OleDb
                –  System.Data
                –  System.Text

                สำหรับในบทความนี้ผู้เขียนจะขอเสนอวิธีการ Import ข้อมูลด้วยไฟล์ Excel ไว้เพียงเท่านี้ก่อน หากมีผู้รู้ท่านใดมีข้อเสนอแนะที่ต้องการแลกเปลี่ยนความรู้ร่วมกัน สามารถชี้แจงเพิ่มเติมได้เป็นกรณีศึกษาเพื่อการเรียนรู้ หากผิดพลาดประการใด ผู้เขียนขออภัยไว้ ณ ที่นี้ค่ะ และสำหรับท่านผู้พัฒนาที่มีความสนใจเกี่ยวกับการ Export ข้อมูลไฟล์ Excel ด้วย ASP.NET(C#) สามารถติดตามต่อได้ใน Part II นะคะ

    แหล่งข้อมูลอ้างอิง :
     http://www.codeproject.com/Articles/33271/Import-and-Export-to-Multiple-Worksheets
    http://dotnetawesome.blogspot.com/2013/11/how-to-import-export-database-data-from_18.html

     

  • รวมเทคนิคการออกแบบ UI ให้สวยงามสำหรับ Designer มือใหม่ (ตอนที่ 3 จบ)

    สำหรับผู้ที่ต้องการอ่านบทความก่อนหน้านี้ ตามลิงค์ด้านล่างได้เลยครับ:
    รวมเทคนิคการออกแบบ UI ให้สวยงามสำหรับ Designer มือใหม่ (ตอนที่ 1)
    รวมเทคนิคการออกแบบ UI ให้สวยงามสำหรับ Designer มือใหม่ (ตอนที่ 2)

    มาถึงตอนสุดท้ายของบทความรวมเทคนิคการออกแบบ UI ให้สวยงามสำหรับ Designer มือใหม่ ตอนนี้มาช้าเนื่องจากผู้เขียนติดธุระทั้งวัน กว่าจะได้กลับมาเขียนก็ค่ำแล้ว ต้อขอโทษผู้อ่านด้วย เพื่อไม่ให้เสียเวลาเราไปติดตามแนวคิดข้อถัดไปกันเลยครับ

    แนวคิดที่ 21 : Try Exposing Options instead of hiding them.
    _____แนวคิดนี้กล่าวถึงการนำเอา DropdownList มาใช้สำหรับให้ผู้ใช้เลือกตัวเลือกสำคัญๆในหน้าจอนั้น อาจทำให้ผู้ใช้เลือกตัวเลือกไม่ตรงตามที่ต้องการหรือเกิดข้อผิดพลาดจากการคลิก ทำให้ข้อมูลที่ได้ไม่ตรงตามต้องการ หรือส่งผลให้เป็นการโน้มน้าวใจผู้ใช้ให้เลือกตัวเลือกที่ถูกตั้งค่าเริ่มต้นกรณีที่ผู้ใช้ยังตัดสินใจไม่ได้ หรือในกรณีตัวเลือกที่มีให้เลือกนั้นมีจำนวนน้อย นอกจากการใช้ Dropdownlist ก็อาจเปลี่ยนมาใช้ Radio Button แทนได้ จะทำให้ผู้ใช้สามารถเห็นตัวเลือกได้อย่างชัดเจน ลดขั้นตอนในการทำงานของผู้ใช้ได้อีกด้วย DropdownList จึงเหมาะกับกรณีที่มีตัวเลือกที่หลากหลายมากกว่าidea014

    แนวคิดที่ 22 : Try Showing State instead of being state agnostic.
    _____ในหัวข้อที่ผ่านมาได้มีการกล่าวถึงการออกแบบให้รองรับสำหรับการแสดงผลแบบตารางกรณีที่ไม่มีค่าข้อมูลหรือเท่ากับ 0 นั้นเอง ซึ่งในหัวข้อนี้จะกล่าวถึงการออกแบบสำหรับกรณีที่มีข้อมูลที่นอกจากจะนำข้อมูลมาแสดงผลแล้ว การแจ้งสถานะของข้อมูลในแต่ละรายการ ก็เป็นส่วนหนึ่งที่จะช่วยให้ผู้ใช้รับทราบถึงผลการทำงานได้ เช่นการแสดงผลของรายการอีเมล์พร้อมทั้งแสดงสถานะว่าอ่านแล้วหรือยังไม่อ่าน รายการเรียกเก็บภาษีพร้อมแสดงสถานะว่ารายการนี้ชำระแล้ว เป็นต้น ส่งผลให้ผู้ใช้รู้ว่าควรจะดำเนินการส่วนไหนต่อ หรือสิ่งที่ได้กระทำลงไปนั้นได้ผลลัพธ์เป็นเช่นไรidea017

    แนวคิดที่ 23 : Try Direct Manipulation instead of contextless menus.
    _____ก่อนหน้านี้มีแนวคิดเกี่ยวกับการให้รวมเมนูต่างๆที่ซ้ำกัน มาไว้ในที่เดียวกันทั้งหมด เพื่อผู้ใช้จะสะดวกในการเรียกใช้เมนู แต่ในกรณีของแนวคิดนี้จะเป็นการให้เราแยกเมนูต่างๆลงไปยังรายการนั้นๆ เมื่อข้อมูลในแต่ละรายการ มีการเรียกใช้เมนูที่ไม่เหมือนกันหรือผู้ใช้สามารถจัดการกับข้อมูลแต่ละรายการได้มากน้อยไม่เท่ากัน การออกแบบให้เมนูอยู่คู่กับรายการจึงเป็นสิ่งที่ทำให้ผู้ใช้ไม่สับสนได้ว่าทำไมบางรายการถึงกดใช้เมนูในแถบเมนูไม่ได้ และลดขั้นตอนที่ผู้ใช้ต้องทำการเลือกรายการก่อนถึงจะไปเรียกใช้เมนูได้ ถามว่าแนวคิดไหนผิด ผู้เขียนขอตอบว่าไม่มีแบบไหนผิด แต่ขึ้นอยู่กับการนำไปใช้งานมากกว่าครับidea019

    แนวคิดที่ 24 : Try Opt-Out instead of opt-in.
    _____ในการออกแบบเงื่อนไขแบบ 2 ตัวเลือกนั้น ปัจจุบันส่วนใหญ่นักออกแบบจะใช้ Checkbox มาใช้ในการเลือกเงื่อนไขกันอย่างแพร่หลาย เช่น ให้ติ๊กยอมรับผล หรือให้ติ๊กถ้าต้องการอีเมล์ตอบรับจากระบบ เป็นต้น แต่ในความเป็นจริงแล้ว การที่เราออกแบบโดยใช้ Checkbox จะทำให้ผู้ใช้เสียโอกาสต่างๆพอสมควรและเป็นการแสดงตัวเลือกได้ไม่ชัดเจนนักเนื่องจากบางครั้งผู้ใช้อาจลืมไม่ได้เลือกติ๊กรายการต่างๆ การเปลี่ยนมาใช้ Radio Button เราจะสามารถทราบถึงผลลัพธ์ ที่แน่นอนกว่าและลดกรณีที่ผู้ใช้ลืมไม่ได้ติ๊กเลือกรายการที่ต้องการได้ และไม่เป็นการเอารัดเอาเปรียบผู้ใช้อีกด้วยidea026

    แนวคิดที่ 25 : Try Smart Defaults instead of asking to do extra work.
    _____มาตั้งค่าเริ่มต้นกันเถอะ เมื่อพูดถึงค่าเริ่มต้นในการกรอกข้อมูลแล้ว อาจเป็นสิ่งที่ทำได้ค่อนข้างยาก ยิ่งเป็นระบบที่กลุ่มผู้ใช้หลากหลาย การกำหนดค่าเริ่มต้นจึงทำได้ยากมาก โดยส่วนใหญ่จะยึดค่าเริ่มต้นตามกลุ่มผู้ใช้งานส่วนใหญ่ ซึ่งค่าเหล่านี้ต้องได้มาจากการรวบรวมข้อมูลของกลุ่มผู้ใช้และใช้ประสบการณ์ของผู้ออกแบบมากพอสมควร ซึ่งข้อนี้ผู้เขียนคิดว่าต้องระวังเป็นพิเศษ ถ้าออกแบบมาดีตรงตามกลุ่มผู้ใช้ส่วนใหญ่ ก็จะส่งผลให้ผู้ใช้สะดวกสบายในการกรอกข้อมูลยิ่งขึ้นไปด้วย แถมเป็นการลดเวลาในการใช้งานระบบไปในตัว แต่ถ้าออกแบบมาไม่ดี จะส่งผลให้ผู้ใช้เสียเวลามากขึ้นตามไปด้วยเช่นกันidea028

    แนวคิดที่ 26 : Try Inline Validation instead of delaying errors.
    _____การกรอกข้อมูลมักจะมาคู่กับการออกแบบเงื่อนไขเพื่อรองรับความผิดพลาดจากการกรอกข้อมูล ซึ่งผู้ออกแบบสามารถใช้เครื่องมือ Validater มาช่วยในการตรวจสอบข้อมูลตามเงื่อนไขที่เราต้องการได้ และเมื่อผู้ใช้งานกรอกข้อมูลไม่ตรงตามเงื่อนไข ก็ควรที่จะแจ้งเตือนความผิดพลาดทันทีเมื่อจบการทำงานในส่วนของการกรอกข้อมูลช่องนั้นๆ แทนการแจ้งเตือนความผิดพลาดทั้งหมดภายหลัง เพื่อผู้ใช้ไม่ต้องเสียเวลาในการย้อนกลับมาตรวจสอบข้อมูลว่าส่วนไหนของรายการที่ผิดพลาดไป และลดความกลัวหรือกังวลให้กับผู้ใช้ได้กรณีที่ผู้ใช้ทราบผลการแจ้งเตือนของข้อมูลที่ผิดพลาดหลายรายการพร้อมๆกันidea033

    แนวคิดที่ 27 : Try Forgiving Inputs instead of being strict with data.
    _____แนวคิดนี้ต่อเนื่องมาจากข้อก่อนหน้านี้ที่พูดถึงการตั้งค่าเงื่อนไขเพื่อตรวจสอบข้อมูลที่ผู้ใช้กรอกเข้าสู่ระบบ ซึ่งควรออกแบบให้มีความหลากหลายในการกรอกข้อมูลแทนที่การจำกัดเงื่อนไขในการกรอกแบบตายตัว ซึ่งจะทำให้ผู้ใช้รู้สึกไม่เป็นมิตรกับระบบได้ แต่การออกแบบให้รองรับการกรอกข้อมูลหลายรูปแบบนั้น ค่อยข้างจะทำได้ยากและเสียเวลาในส่วนของการพัฒนาโปรแกรม เช่นการกรอกข้อมูลเบอร์โทรศัพท์ รูปแบบในการกรอกค่อนข้างมีหลากหลาย ไม่ว่าจะใส่วงเล็บ ใส่เครื่องหมายขีดกลาง แม้กระทั้งเว้นวรรคระหว่างกลุ่มตัวเลข ในด้านการเขียนโปรแกรมอาจเขียนได้ยากพอสมควร แต่สำหรับด้านการใช้งานถือว่าเป็นการอำนวยความสะดวกและลดเวลาในการกรอกข้อมูลแก่ผู้ใช้ได้เช่นกัน idea034

    แนวคิดที่ 28 : Try Progressive Disclosure instead of overwhelming.
    _____ในการออกแบบแบบสอบถามผ่านระบบนั้น ผู้ออกแบบไม่ควรสร้างแบบสอบถามที่แสดงคำถามทั้งหมดให้ผู้ใช้ตอบถึงแม้บางเงื่อนไข ผู้ใช้ไม่จำเป็นที่จะตอบคำถามในข้อนั้นๆ หรือคำถามดังกล่าวไม่เกี่ยวข้องกับผู้ใช้เลย การออกแบบให้การตอบคำถามข้อนั้นๆเป็นการนำไปสู่คำถามข้อถัดไปจะช่วยลดคำถามที่ไม่จำเป็นกับผู้ใช้ได้ ส่งผลให้ผู้ใช้ไม่เกิดความสับสนหรือรู้สึกไม่ดีกับการตอบแบบสอบถามidea043

    แนวคิดที่ 29 : Try Useful Calculations instead of asking to do math.
    _____แนวคิดนี้เป็นตัวช่วยที่เอื้ออำนวยความสะดวกให้กับผู้ใช้ได้คล้ายๆกับกรณีที่ให้มีการแจ้งเตือนสถานะของรายการข้อมูล โดยจะกล่าวถึงการคำนวนค่าต่างๆที่สามารถทำได้ในระบบให้ผู้ใช้ทราบได้ เช่นการแจ้งเตือนหมดอายุการเป็นสมาชิกในรายการต่างๆ การบอกระยะเวลาของอีเมล์ที่ได้รับแทนการบอกเป็นวันเวลาแบบตรงๆซึ่งผู้ใช้ต้องเสียเวลาในการคิดคำนวนว่าอีเมล์ดังกล่าวมาถึงนานแล้วหรือไม่ เราอาจนำเอาแนวคิดก่อนหน้านี้มาใช้ร่วมกันได้ เพื่อเพิ่มความสะดวกสบายให้แก่ผู้ใช้งานมากยิ่งขึ้นidea053

    แนวคิดที่ 30 : Try Responsive Layouts instead of static ones.
    _____เนื่องด้วยปัจจุบันความก้าวหน้าทางเทคโนโลยีและการให้บริการอินเตอร์เน็ตความเร็วสูงมีการพัฒนาอย่างต่อเนื่อง การเข้าถึงข้อมูลข่าวสารเป็นไปได้อย่างรวดเร็วและทั่วถึง รวมถึงในปัจจุบันได้มีเครื่องมือสื่อสารหลายขนาดและรูปแบบ ไม่ว่าจะเป็นโทรศัพท์ Tablet หรือแม้แต่โทรทัศน์เองก็สามารถใช้งานอินเตอร์เน็ตได้เช่นกัน ซึ่งมีคุณสมบัติที่แตกต่างกันออกไป เช่น ความกว้าง ความสูง ความละเอียดหน้าจอ เป็นต้น ทำให้การออกแบบหน้าจอต้องคำนึงถึงการแสดงผลที่รองรับกับอุปกรณ์ต่างๆเช่นกันแทนการออกแบบให้หน้าจอแสดงผลได้เพียงบนคอมพิวเตอร์อย่างเดียว ส่งผลให้ผู้ใช้ได้รับความสะดวกสบายในการใช้งานไม่ว่าจากคอมพิวเตอร์หรือโทรศัพท์ก็ตามidea070

    สรุปส่งท้าย
    _____สุดท้ายนี้ต้องขอขอบคุณทุกท่านที่อ่านมาจนจบทั้ง 3 ตอน ซึ่งจริงๆแล้วยังมีอีกหลายแนวคิดที่ผู้เขียนไม่ได้นำเอามาเขียนในชุดบทความนี้ ผู้ที่สนใจสามารถเข้าไปอ่านได้ที่ GoodUI.com เลยครับ และขออภัยถ้าอ่านแล้วไม่เข้าใจหรือผู้เขียนแปลความหมายผิดไปจากต้นฉบับ และถ้าต้องการเสริมในหัวข้อไหน สามารถมา Comment บอกกันได้ครับ จะรู้สึกเป็นเกียรติอย่างยิ่ง สุดท้ายนี้หวังว่าชุดบทความนี้จะเป็นประโยชน์กับผู้อ่านไม่มากก็น้อยนะครับ

    ขอบคุณครับ

  • รวมเทคนิคการออกแบบ UI ให้สวยงามสำหรับ Designer มือใหม่ (ตอนที่ 2)

    ใครยังไม่ได้อ่านตอนที่ 1 แนะนำให้อ่านก่อนครับ ที่: รวมเทคนิคการออกแบบ UI ให้สวยงามสำหรับ Designer มือใหม่ (ตอนที่ 1)

    มาต่อกันกับบทความรวมเทคนิคการออกแบบ UI ให้สวยงามสำหรับ Designer มือใหม่ ตอนที่ 2 ซึ่งในชุดบทความนี้จะมีทั้งหมด 3 ตอน เพื่อไม่ให้เป็นการเสียเวลาเราไปเริ่มข้อต่อไปกันเลยดีกว่าครับ

    แนวคิดที่ 11 : Try Merging Similar Functions instead of fragmenting the UI.
    _____แนวคิดนี้จะพูดถึงการจัดกลุ่มเมนูหรือลิงค์ที่มีการทำงานไปยังส่วนเดียวกันหรือเปิดไปทำงานในอีกหน้าจอ การออกแบบให้สิ่งเหล่านี้กระจายไปอยู่ตามจุดต่างๆของหน้าจอ อาจทำให้ดูเหมือนเข้าถึงได้ง่าย แต่จริงๆแล้วกลับส่งผลให้หน้าจอดูรกขึ้นมาโดยทันที และอาจจะทำให้ผู้ใช้งานสับสนได้กับเมนูที่มีซ้ำกันในหน้าจอ การย้ายเมนูหรือลิงค์ดังกล่าวมาอยู่รวมกันเป็นเมนูเดียวหรือสร้างเป็นเมนูย่อยจะทำให้หน้าจอของเรามีพื้นที่ใช้งานมากขึ้นด้วยครับidea003

    แนวคิดที่ 12 : Try Repeating Your Primary Action instead of showing it just once.
    _____สำหรับนักออกแบบ UI หลายท่าน การออกแบบให้มีปุ่มหรือลิงค์หลักกับผู้ใช้เพียงจุดเดียวเป็นสิ่งที่ดูเหมาะสมแล้ว แต่เมื่อนำไปใช้งานจริงบางกรณีอาจทำให้ผู้ใช้งานหน้าจอไม่สะดวกในการใช้งาน มักจะเกิดกับหน้าจอที่มีข้อมูลมากส่งผลให้เกิดสกอร์บาร์ขึ้น เมื่อผู้ใช้งานเลื่อนหน้าจอขึ้นลง ปุ่มหรือลิงค์หลักอาจหายไปจากหน้าจอได้ ส่งผลให้ผู้ใช้งานต้องเลื่อนหน้าจอไปมาเพื่อที่จะกลับไปกดปุ่มหรือลิงค์ดังกล่าว กรณีนี้ควรเพิ่มปุ่มหรือลิงค์ขึ้นมาในส่วนล่างสุดและบนสุดของหน้าจอ โดยปุ่มหรือลิงค์ดังกล่าวควรมีหน้าตาที่เหมือนหรือคล้ายกันเพื่อไม่ให้ผู้ใช้เกิดความสับสนidea005

    แนวคิดที่ 13 : Try Suggesting Continuity instead of false bottoms.
    _____จากที่กล่าวไปแล้วก่อนหน้านี้ในการออกแบบรูปแบบการแสดงผลของบทความแบบคอลัมน์เดียวนั้น กรณีที่บทความนั้นยาวมาก หรือเป็นบทความหลายๆเรื่องต่อๆกันไปนั้น ผู้ออกแบบหน้าจอควรมีการเพิ่มช่องว่างหรือลำดับที่บอกถึงการเปลี่ยนแปลงของบทความเมื่อมีการขึ้นเรื่องใหม่หรือย่อหน้าใหม่ แทนการออกแบบให้บทความนั้นๆยาวต่อกันไปเรื่อยๆ เมื่อผู้ใช้เข้ามาอ่านบทความแล้วจะได้ไม่เกิดความสับสนหรือหาจุดที่อ่านต่อไม่เจอเมื่อมีการเลื่อนสอร์บาร์ แต่ข้อควรระวังคือ อย่าใส่ช่องว่างระหว่างบทความมากจนเกินไป จนทำให้ผู้ใช้รุ้สึกว่าบทความนั้นๆจบแล้วidea015

    แนวคิดที่ 14 : Try Visual Hierarchy instead of dullness.
    _____แนวคิดนี้เป็นอีกวิธีหนึ่งที่น่าสนใจในการออกแบบหน้าจอแสดงผลบทความแบบคอมลัมน์เดียว คือการจัดตัวนำสายตาหรือก็คือการจัดหน้าจอแบบการย่อหน้าเป็นลำดับชั้น ถ้านึกภาพไม่ออกให้นึกถึงการใส่เลขที่หัวข้อข้อย่อยในเอกสารจำพวก MS Word จะมีการเลื่อนระดับย่อหน้าเข้าไปเรื่อยๆตามลำดับชั้นที่เล็กลงไป วิธีนี้จะง่ายกับผู้ที่กำลังอ่านบทความได้ เพราะผู้อ่านจะรับรู้ได้ว่าบทความนี้จะจบลงที่ส่วนไหนของหน้าจอและผู้ใช้อาจไม่ต้องเรียนรู้เพิ่มเติมเลย เราสามารถนำวิธีการนี้ไปใช้ร่วมกับการออกแบบในข้อก่อนหน้านี้ได้idea031

    แนวคิดที่ 15 : Try Grouping Related Items instead of disordering.
    _____การจัดกลุ่มรายการเครื่องมือการใช้งานพื้นฐานก็เป็นอีกสิ่งหนึ่งที่จำเป็นสำหรับการออกแบบเพื่อผู้ใช้โดยเฉพาะ ซึ่งการจัดกลุ่มควรยึดตามหลักพื้นฐานทั่วไปในชีวิตประจำวัน เช่นส้อมต้องคู่กับช้อน เป็นต้น หรือจัดกลุ่มตามประสบการ์ณการทำงานของผู้ใช้ เพราะเป็นสิ่งที่ผู้ใช้เข้าใจได้โดยไม่ต้องเรียนรู้เพิ่มเติม การจัดกลุ่มและเรียงลำดับเครื่องมือจึงช่วยให้ผู้ใช้เข้าใจการทำงานของเครื่องมือๆนั้นได้มากขึ้นและสะดวกยิ่งขึ้นidea032

    แนวคิดที่ 16 : Try Thanking instead of simply confirming completion.
    _____การขอบคุณดูเป็นเรื่องปกติทั่วไป แต่เมื่อนำมาใช้ใน UI ของเราจะเป็นสิ่งหนึ่งที่ช่วยยกระดับ UI ของเราให้ดูดียิ่งขึ้น โดยให้มีการออกแบบการแสดงผลข้อความขอบคุณเมื่อผู้ใช้มีการกระทำบางอย่างที่เราต้องการในหน้าจอนั้นๆ นอกจากการแสดงผลรับหรือรายงานผลว่าเสร็จสิ้นแล้ว การเพิ่มข้อความขอบคุณผู้ใช้ จะทำให้ผู้ใช้เกิดความรู้สีกว่าตัวเองมีคุณค่าและได้รับความสนใจจากระบบของเรา ส่งผลให้ความรู้สึกของผู้ใช้ต่อระบบในแง่ดีเพิ่มมากขึ้นตามไปด้วย และเป็นการกระตุ้นให้ผู้ใช้กลับมาใช้งานระบบของเราอีกครั้งidea052

    แนวคิดที่ 17 : Try Softer Prompts instead of modal windows.
    _____Popup ถือเป็นส่วนหนึ่งในการตอบโต้กับผู้ใช้ ซึ่งผู้เขียนได้กล่าวถึงไปแล้วในข้อก่อนหน้านี้ ซึ่งในปัจจุบัน popup ที่นักออกแบบนิยมใช้งานกันอย่างแพร่หลายก็คงไม่พ้น modal popup ข้อดีของมันคือไม่มีการเรียกใช้จาวาสคริปแบบ popup รุ่นก่อนๆส่งผลให้รองรับการทำงานทุกเบราเซอร์และจุดเด่นอีกอย่างคือการล๊อคหน้าจอไม่ให้ผู้ใช้งานทำงานในส่วนของเบื้องหลังได้กรณีที่ popup ทำงานอยู่ แต่จุดเด่นข้อนี้จะกลายเป็นข้อเสียทันทีเมื่อเรานำไปใช้แบบผิดวิธี เช่นการ popup ให้กรอกข้อมูล ที่มีความเกี่ยวเนื่องกับข้อมูลที่อยู่ด้านหลัง popup ซึ่งส่งผลให้ผู้ใช้ย้อนไปดูข้อมูลหรือนำข้อมูลดังกล่าวมาอ้างอิงในการตัดสินใจได้ หรือกรณี popup ทำการแจ้งเตือนอัตโนมัติเมื่อผู้ใช้ไม่ทำการบันทึกข้อมูลเป็นเวลานานๆ หรือผู้ใช้ที่กำลังจดจ่อกับการกรอกข้อมูลหรือกำลังทำงานบางอย่างกับหน้าจออยู่ เมื่อมีการแสดผล popup ขึ้นมา จะเป็นการรบกวนสมาธิหรือขัดจังหวะผู้ใช้งานได้ ส่งผลให้ผู้ใช้งานไม่พอใจหรือตกใจได้ ดังนั้นการนำ popup ประเภทนี้ไปใช้งาน ผู้ออกแบบควรพิจารณาว่าเหมาะสมกับหน้าจอนั้นๆด้วยหรือไม่idea045

    แนวคิดที่ 18 : Try Expectation Setting instead of being ignorant.
    _____การออกแบบหน้าจอให้มีการแจ้งเตือนสถานะของผู้ใช้ในปัจจุบันหรือการแสดงให้ผู้ใช้ทราบว่ากำลังทำงานอยู่ในขั้นตอนไหน และยังเหลืออีกกี่ขั้นตอน หรืออาจออกแบบไปถึงขั้นตอนที่บอกว่าจะได้อะไรในการทำงานในหน้าจอนี้ ส่งผลให้ผู้ใช้งานระบบรู้ถึงจุดประสงค์ของการทำงานในแต่ละขั้นตอนหรือหน้าจอได้ และยังทำให้ผู้ใช้ไม่รู้สึกเป็นกังวลว่ายังเหลือสิ่งที่ต้องทำอีกมากน้อยเพียงใดหรือทำไปเพื่ออะไรidea059

    แนวคิดที่ 19 : Try Providing Feedback instead of silence.
    _____สำหรับแนวคิดนี้ อาจจะดูเป็นเรื่องที่ขาดไม่ได้ในการออกแบบหน้าจอ เพื่อใช้ในการตอบโต้กับผู้ใช้ ทำให้ผู้ใช้รู้สึกว่าระบบมีความเป็นมนุษย์มากขึ้น ซ้ำยังช่วยให้ผู้ใช้ทราบถึงผลลัพธ์จากการกระทำบางอย่างของผู้ใช้ด้วย เช่นเมื่อมีการบันทึกหรือลบข้อมูล หลังจากทำกระบวนการเสร็จสิ้นแล้ว ก็ควรมีข้อความแจ้งเตือนบอกผู้ใช้ให้ทราบถึงผลลัพธ์ว่าสำเร็จลุล่วงหรือไม่ เป็นต้น ส่วนรูปแบบการแสดงผลตอบสนองกับผู้ใช้ก็สามารถออกแบบได้หลายรูปแบบไม่ว่าจะเป็น ในส่วนของ popup หรือแถบข้อความแจกเตือนในหน้าจอidea061

    แนวคิดที่ 20 : Try Explaining instead of assuming the obvious.
    _____แนวคิดข้อนี้จะพูดถึงเกี่ยวกับการใช้คำแนะนำมาช่วยขยายความหัวข้อของการกรอกฟอร์มข้อมูล โดยปกติการออกแบบฟอร์มกรอกข้อมูลจะประกอบไปด้วยส่วนของหัวข้อและส่วนของกล่องข้อความหรือเครื่องมืออื่นๆเช่น dropdownlist checkbox เป็นต้น เพื่อทำให้ฟอร์มนั้นๆดูกระชับน่าใช้งาน แต่บางครั้งคำที่เรานำมาเขียนเป็นหัวข้อนั้นเราคิดว่าอธิบายความหมายหรือสิ่งที่เราต้องการได้หมดครบถ้วนแล้ว แต่สำหรับผู้ใช้บางคำอาจทำให้เกิดความสับสนหรือเข้าใจผิด ส่งผลให้ข้อมูลที่ได้มาจากฟอร์มอาจผิดพลาด การใช้คำมาช่วยอธิบายเพิ่มเติมหรือเขียนเป็นคำถามปลายเปิด จะช่วยให้ผู้ใช้เข้าใจสิ่งที่เราต้องการจากผู้ใช้มากขึ้นidea068


    สำหรับบทความชุด “รวมเทคนิคการออกแบบ UI ให้สวยงามสำหรับ Designer มือใหม่” ตอนที่ 2 ขอจบแต่เพียเท่านี้ สามารถอ่านตอนที่ 3 ได้ตามลิงคด้านล่างเลยครับ

    รวมเทคนิคการออกแบบ UI ให้สวยงามสำหรับ Designer มือใหม่ (ตอนที่ 3 จบ)

    ขอบคุณครับ

  • มาทำความรู้จักกับ Validator ใน ASP.NET กันเถอะ

                ในการพัฒนาโปรแกรมโดยทั่วไป สิ่งหนึ่งที่นักพัฒนาควรคำนึงถึงและให้ความสำคัญในการรับค่าข้อมูลจากผู้ใช้ คือ การตรวจสอบความถูกต้องของข้อมูลที่รับเข้า เพื่อให้มั่นใจว่าข้อมูลที่ได้มาจะเป็นประโยชน์ สามารถนำไปใช้ในการประมวลผลต่อไปได้ และลดปัญหาการเก็บข้อมูลขยะไว้ในฐานข้อมูลซึ่งอาจทำให้ฐานข้อมูลมีขนาดใหญ่เกินความจำเป็น อีกทั้งยังถือเป็นการป้องกันความผิดพลาดที่อาจจะเกิดขึ้นจากการเก็บข้อมูลที่ไม่ถูกต้อง ในกรณีเลวร้ายอาจมีผลกระทบทำให้ฐานข้อมูลเกิดความเสียหายได้ ดังนั้น การตรวจสอบความถูกต้องของข้อมูลก่อนบันทึกถือเป็นสิ่งหนึ่งที่นับว่ามีความสำคัญสำหรับนักพัฒนาที่จะต้องคำนึงถึงในการเก็บข้อมูลจากผู้ใช้
                วิธีที่ใช้ในการตรวจสอบสามารถทำได้หลายวิธี ซึ่งเดิมผู้พัฒนาจะต้องเขียน Client-Script (อาจจะเป็น JavaScript VBScript หรือ JQuery) เพื่อใช้ในการตรวจสอบเองทั้งหมด โดยวิธีการนี้ผู้พัฒนาจะต้องมีความรู้เกี่ยวกับ Client Script เหล่านั้นพอสมควร และอาจต้องใช้เวลานานในกรณีที่มีการตรวจสอบที่ซับซ้อน แต่นับว่าเป็นโชคดีของนักพัฒนาที่ใช้เครื่องมือ ASP.NET ในการพัฒนาโปรแกรม เนื่องจาก .NET framework ได้จัดเตรียมเครื่องมือในการตรวจสอบเหล่านี้ไว้ให้เรียบร้อยแล้วตั้งแต่เวอร์ชั่น Visual Studio .NET 2003 เพื่ออำนวยความสะดวกให้กับผู้พัฒนาสามารถหยิบมาใช้ได้โดยง่าย ทำให้การตรวจสอบไม่ใช่เรื่องยากและซับซ้อนอีกต่อไป และยังถือเป็นการประหยัดเวลา ไม่จำเป็นต้องเขียนสคริปต์ตรวจสอบเองทั้งหมด เพียงกำหนด properties ต่างๆที่จำเป็นให้ validator เหล่านั้นและเลือกใช้ให้เหมาะสมกับความต้องการก็จะสามารถตรวจสอบความถูกต้องของข้อมูลได้โดยง่ายดาย ผู้เขียนจึงเห็นว่าการทำความเข้าใจกับความสามารถของ validator เหล่านี้ถือเป็นสิ่งสำคัญเพื่อช่วยในการตัดสินใจเลือก validator มาใช้ให้เหมาะสมกับงานและความต้องการในการตรวจสอบนั้นๆ
                หากแบ่ง Validator control ที่มีใน ASP.NET ตามลักษณะการทำงาน สามารถแบ่งออกได้เป็น 2 ลักษณะ ดังนี้

    การตรวจสอบข้อมูลในฝั่ง Client โดยการตรวจสอบข้อมูลในลักษณะนี้ จะเกิดขึ้นทันทีเมื่อผู้ใช้มีการป้อนข้อมูลเข้ามาและการตอบโต้นี้จะยังไม่มีการส่งค่าไปยังฝั่ง server ซึ่งข้อดีของการตรวจสอบในลักษณะนี้ คือ สามารถโต้ตอบกับผู้ใช้ในเวลาอันรวดเร็วและแสดงให้ผู้ใช้ทราบได้ทันทีว่าเกิดข้อผิดพลาดในการป้อนข้อมูล โดยไม่ต้องรอให้มีการเรียกใช้ไปยังฝั่งเซิร์ฟเวอร์ก่อนจึงจะตอบสนองกลับมายังผู้ใช้ และยังถือเป็นการลดจำนวนครั้งในเรียกใช้ไปยังฝั่งเซิร์ฟเวอร์โดยไม่จำเป็น ซึ่งหลักการทำงานของ Validator ของ ASP.NET ในลักษณะนี้ คือ Validator จะทำหน้าที่แปลง Validation เป็น JavaScript และถูกทำงานที่ฝั่ง Client ซึ่งจะขึ้นอยู่กับการทำงานของ Validation Control แต่ละประเภทที่ใช้นั่นเอง
    การตรวจสอบข้อมูลในฝั่งเซิร์ฟเวอร์ โดยการตรวจสอบในลักษณะนี้ จะเกิดขึ้นที่ฝั่งเซิร์ฟเวอร์ ซึ่งมีข้อดี คือ สามารถเพิ่มความปลอดภัยและความแม่นยำในการตรวจสอบอีกระดับก่อนจะบันทึกข้อมูลเหล่านั้นลงฐานข้อมูลในกรณีที่อาจมีข้อมูลผิดพลาดที่ตกสำรวจมาจากการตรวจสอบในฝั่ง Client โดยการตรวจสอบในลักษณะนี้ ผู้พัฒนาจะเป็นผู้เขียนโค้ดในการตรวจสอบตามเงื่อนไขที่กำหนดขึ้นเอง ซึ่ง ASP.NET ก็ได้มีการเตรียม validation control เพื่อรองรับการตรวจสอบในลักษณะนี้ไว้แล้วเช่นกัน

    โดย Validation control ที่มีเตรียมไว้ใน ASP.NET ประกอบด้วย Validator ชนิดต่างๆ ดังนี้
    • RequiredFieldValidator
    • RegularExpressionValidator
    • RangeValidator
    • CompareValidator
    • CustomValidator
    • Validation Summary

                เพื่อให้ง่ายต่อการทำความเข้าใจ ผู้เขียนจะขอพูดถึงการกำหนดค่า Properties ให้กับ validator หลักๆที่สำคัญที่ทุกตัวต้องมีไว้เป็นเบื้องต้น และจะเพิ่ม properties เพิ่มเติมที่แตกต่างจาก properties หลักของ validator แต่ละตัวดังนี้

    ID : เป็นการกำหนดชื่อ ID ให้กับตัว validator ซึ่งผู้เขียนแนะนำให้ตั้งชื่อให้สื่อความหมาย เพื่อง่ายต่อการต่อยอดในการพัฒนาร่วมกับผู้อื่น
    ControlToValidate : เป็นการกำหนดว่าจะให้ validator ดังกล่าวทำการควบคุมและตรวจสอบ control หรือ element ตัวใดในหน้าจอ
    Display : เป็นการกำหนดลักษณะในการแสดงผลของ validator ซึ่งมีด้วยกัน 3 แบบคือ

    o Static : แบบจองพื้นที่ในการแสดงผล validator กรณีมีข้อผิดพลาด แม้ไม่พบข้อผิดพลาด validator ก็จะยังคงจองพื้นที่ส่วนนั้นไว้เช่นกัน
    o Dynamic : จะแสดงผล validator เมื่อพบข้อผิดพลาด และเมื่อมีการแก้ไขข้อผิดพลาดดังกล่าวแล้ว Error message ของ validator ตัวดังกล่าวจะหายไปและลดพื้นที่ส่วนการแสดงผลให้เหมือนก่อนจะพบข้อผิดพลาด
    o None : ไม่แสดงผลบนหน้าจอ

    Error Message : เป็นการกำหนดข้อความที่ต้องการให้แสดง เมื่อผู้ใช้กรอกข้อมูลผิดพลาด ซึ่งถือเป็นการชี้แนะให้ผู้ใช้ทราบว่าความผิดพลาดดังกล่าวเกิดขึ้นจากสาเหตุใด และควรแก้ไขอย่างไร
    SetFocusOnError : เป็นการกำหนด Focus ให้กับ control ที่พบความผิดพลาด แต่หากในหน้าจอดังกล่าวพบข้อผิดพลาดหลายจุดและ validator ทุกตัวมีการกำหนดค่า SetFocusOnError เป็น true จะทำให้ Focus เลื่อนไปยังจุดที่มีข้อผิดพลาดจุดแรกที่พบทันที
    Text : เป็นการกำหนดในส่วนที่จะแสดงผล เมื่อ validator พบข้อผิดพลาดนั่นเอง ซึ่งโดยทั่วไปแล้วมักกำหนดให้แสดงเป็นเครื่องหมายดอกจันท์ หรือ * และวางไว้หลัง control ตัวที่ validator ดังกล่าวกำลังควบคุม เพื่อแจ้งให้ผู้ใช้ทราบว่าข้อมูลที่กรอกส่วนใดกำลังมีปัญหาหรือพบข้อผิดพลาดจากการตรวจสอบอยู่นั่นเอง
    ToolTip : เป็นการกำหนดข้อความที่ต้องการให้แสดงเมื่อผู้ใช้นำเมาส์ไปชี้ที่ validator ที่กำลังแสดงข้อผิดพลาดอยู่
    Validation group : เป็นการจัดกลุ่มให้กับ validator ที่ใช้ในการตรวจสอบ ซึ่งโดยปกติหากไม่กำหนดค่าในส่วนนี้จะถือว่า control ทั้งหมดในหน้าจอถูกจัดกลุ่มการควบคุมไว้เป็นกลุ่มเดียวกันทั้งหน้าจอ แต่หากมีบางกรณีที่ต้องการแยกกลุ่มในการตรวจสอบ ต้องมีการระบุ ValidationGroup ของปุ่มและ validator ที่ต้องการตรวจสอบเหล่านั้นให้เป็นกลุ่มเดียวกัน ซึ่งจะทำให้ validator ตัวอื่นที่ไม่ใช่กลุ่มดังกล่าวไม่ถูกนำมาตรวจสอบ เพราะถือว่าเป็นการตรวจสอบคนละส่วนกัน

                หลังจากที่ทราบ properties หลักที่จำเป็นต้องใช้ในการกำหนดค่าให้กับ validator ที่จะใช้งานแล้ว ผู้เขียนขออธิบายในรายละเอียดเพิ่มเติมทั้งในส่วนของหน้าที่การทำงานและการกำหนดค่า properties ที่จำเป็นอื่นๆของแต่ละ validator control ดังนี้

    RequiredFieldValidator
                การทำงานโดยทั่วไปของ validator นี้ จะใช้ในการตรวจสอบค่าว่างในการกรอกข้อมูล โดยหากกำหนดว่าต้องการควบคุมตัวใด การกรอกข้อมูลของ control ดังกล่าวจะถือเป็นข้อมูลบังคับกรอก หากไม่กรอกระบบจะแสดงข้อความแจ้งเตือนให้ทราบและไม่บันทึกข้อมูลให้ โดยการกำหนดค่า properties ต่างๆเพิ่มเติมจากที่กล่าวไว้แล้วข้างต้น คือ

    • InitialValue : ใช้เปรียบเทียบค่าจาก InitialValue ที่กำหนดใน validator กับค่าที่ผู้ใช้กรอกมาใน control ที่ validator ตัวนี้ควบคุมอยู่ ซึ่งโดยทั่วไปการใช้งาน RequiredFieldValidator อาจไม่จำเป็นต้องกำหนดค่าให้กับ  properties นี้ แต่ผู้เขียนจะขอยกตัวอย่างเพื่อให้ผู้พัฒนาเห็นภาพการทำงานของลักษณะการกำหนดค่า InitialValue  เพื่อนำไปประยุกต์ใช้งานได้ ดังนี้

    ตัวอย่างที่ 1 การเรียกใช้งาน RequiredFieldValidator  แบบทั่วไป(ไม่ได้กำหนดค่า InitialValue )

    <asp:TextBox ID="TxtEmail" runat="server"></asp:TextBox>
    <asp:RequiredFieldValidator ID="ReqEmail" runat="server"
    ErrorMessage="Please enter your email-address" 
    ControlToValidate="TxtEmail" SetFocusOnError="True">*
    </asp:RequiredFieldValidator>

    ตัวอย่างที่ 2  เช่น กรณีที่มีการกำหนดค่า InitialValue และมีการเลือกประเภทจาก dropdownlist ที่มีชื่อว่า DdlType และบังคับเลือก

    <asp:DropDownList ID="DdlType" runat="server"> 
    <asp:ListItem Value="--Select--">--Select--</asp:ListItem>
    <asp:ListItem Value="Item1" />
    <asp:ListItem Value="Item2" />
    <asp:ListItem Value="Item3" />
    </asp:DropDownList>
    <asp:RequiredFieldValidator ID="ReqType" runat="server"
    ErrorMessage="Please select type" ControlToValidate="DdlType" 
    SetFocusOnError="True" InitialValue="--Select--" >*
    </asp:RequiredFieldValidator>

    จากตัวอย่างที่ 2 จะเห็นว่า ค่า InitialValue คือ “–Select–” นั่นหมายถึงว่า หากผู้ใช้เลือกรายการ “–Select–” จาก dropdownlist แล้วกดปุ่มบันทึก validator ดังกล่าวจะแสดงข้อผิดพลาดทันที

    RegularExpressionValidator

                เป็น validator ที่ใช้ในการตรวจสอบรูปแบบการกรอกของข้อมูล เช่น อีเมล์ หมายเลขโทรศัพท์ URL ของเว็บไซต์ หรือรหัสไปรษณีย์ เป็นต้น โดย properties  ที่สำคัญเพิ่มเติมจากที่กล่าวไว้แล้วในข้างต้นของ validator นี้คือ

    • ValidationExpression ซึ่งทาง .NET ได้มีการกำหนดรูปแบบ regular expression สำเร็จรูปในการตรวจสอบไว้ให้แล้วในเบื้องต้น แต่ผู้พัฒนาสามารถประยุกต์ใช้ และปรับแก้ให้รูปแบบของ regular expression ที่ใช้ในการตรวจสอบเป็นไปในแบบที่ต้องการได้

    ตัวอย่าง การใช้งาน ValidationExpression ในการตรวจสอบรูปแบบ URL ของเว็บไซต์

    <asp:TextBox ID="txtURL" runat="server"></asp:TextBox>
    <asp:RegularExpressionValidator ID="RegExURL" runat="server"
    ControlToValidate="txtURL" ErrorMessage="Internet URL 's format is incorrect" SetFocusOnError="True" ValidationExpression="http(s)?://([\w-]+\.)+[\w-]+(/[\w- ./?%&amp;=]*)?">*</asp:RegularExpressionValidator>

    จากตัวอย่าง พบว่า regular expression ที่ใช้ในการตรวจสอบรูปแบบของข้อมูล คือ  http(s)?://([\w-]+\.)+[\w-]+(/[\w- ./?%&amp;=]*)? ซึ่งเป็นแบบสำเร็จรูปที่ ASP.NET ได้เตรียมไว้ให้แล้ว

    RangeValidator

                เป็น validator ที่ใช้ในการตรวจสอบข้อมูลว่าอยู่ในช่วงของข้อมูลที่กำหนดหรือไม่ ซึ่งสามารถกำหนดชนิดของช่วงข้อมูลได้  โดย validator ตัวนี้จะไม่พบข้อผิดพลาดหากผู้ใช้ไม่กรอกข้อมูล หากต้องการตรวจสอบข้อมูลห้ามเป็นค่าว่างด้วยจะต้องทำงานร่วมกับ RequiredFieldValidator  สำหรับการใช้งาน validator ชนิดนี้มี properties ที่สำคัญในการกำหนดการใช้งานเพิ่มเติม ดังนี้

    • MaximumValue : เป็นการกำหนดค่าสูงสุดที่สามารถกรอกได้
    • MinimumValue : เป็นการกำหนดค่าข้อมูลต่ำสุดที่สามารถกรอกได้
    • Type :เป็นการกำหนดชนิดของข้อมูลที่จะทำการตรวจสอบช่วงของข้อมูลว่าให้มองเป็นชนิดใด โดยชนิดของข้อมูลประกอบด้วย
      • String : การตรวจสอบช่วงข้อมูลชนิดที่เป็นชนิด String นี้จะคำนวณค่าโดยเทียบกับ Ascii Code (http://www.lookuptables.com/)
      • Integer
      • Double
      • Date

    ตัวอย่างที่ 1 : การตรวจสอบช่วงของข้อมูลที่เป็นชนิด string

    <asp:DropDownList ID="DdlChoice" runat="server">
    
    <asp:ListItem Value="--Select--">--Select--</asp:ListItem>
    <asp:ListItem Value="A" >A</asp:ListItem>
    <asp:ListItem Value="B" >B</asp:ListItem>
    <asp:ListItem Value="C" >C</asp:ListItem>
    <asp:ListItem Value="D">D</asp:ListItem>
    <asp:ListItem Value="E">E</asp:ListItem>
    </asp:DropDownList>
    <asp:RangeValidator ID="RangeChoice" runat="server"
    ControlToValidate="DdlChoice" 
    ErrorMessage="Please select between A-D"
    MaximumValue="D" MinimumValue="A">*</asp:RangeValidator>

    จากตัวอย่างจะพบข้อผิดพลาดหากผู้ใช้เลือกรายการ “E” เนื่องจากเมื่อนำค่าดังกล่าวไปเปรียบเทียบกับค่าแบบ Ascii code แล้วจะมีค่าเกินช่วงที่กำหนดใน validator คือ A-D นั่นเอง

    ตัวอย่างที่ 2 : การตรวจสอบช่วงของข้อมูลที่เป็นชนิดตัวเลข

    Enter number (between 1 - 10) :
    <asp:TextBox ID="txtAmount" runat="server"></asp:TextBox>
    <asp:RangeValidator ID="RangeNumber" runat="server"
    ControlToValidate="txtAmount" 
    ErrorMessage="The data must be from 1 to 10"
    MaximumValue="10" MinimumValue="1" Type="Integer">*</asp:RangeValidator>

    CompareValidator

                เป็น validator ที่ใช้ในการเปรียบเทียบค่าหรือตรวจสอบชนิดของข้อมูลที่กรอก โดยตัวอย่างที่ใช้กันบ่อยมักเป็นการเปรียบเทียบค่า 2 ค่า เช่น กรณีการระบุรหัสผ่าน และยืนยันรหัสผ่าน หรือกรณีต้องการตรวจสอบชนิดของจำนวนเงินว่าต้องเป็นตัวเลขเท่านั้น เป็นต้น โดย properties เพิ่มเติมที่ใช้ในการกำหนด มีดังนี้

    • ValueToCompare : เป็น properties ที่ใช้ในกรณีที่ต้องการเปรียบเทียบค่าที่รับจากผู้ใช้กับค่าคงที่ที่ระบุ
    • ControlToCompare : เป็น properties ที่ใช้กำหนดคอนโทรลตัวที่ต้องการเปรียบเทียบค่า(ในกรณีที่มีการเปรียบเทียบค่าจาก 2 คอนโทรล)
    • Operator :เป็นpropertiesที่ใช้ในการกำหนดการปฏิบัติการในการเปรียบเทียบค่า มีดังนี้
      • Equal : ค่าของข้อมูลที่รับมาต้องมีค่าเท่ากันกับค่าที่ต้องการเปรียบเทียบ
      • NotEqual : ค่าของข้อมูลที่รับมาต้องไม่เท่ากับค่าที่ต้องการเปรียบเทียบ
      • GreaterThan : ค่าของข้อมูลที่รับมาต้องมีค่ามากกว่าค่าที่ต้องการเปรียบเทียบ
      • GreaterThanEqual : ค่าของข้อมูลที่รับมาต้องมีค่ามากกว่าหรือเท่ากับค่าที่ต้องการเปรียบเทียบ
      • LessThan : ค่าของข้อมูลที่รับมาต้องมีค่าน้อยกว่าค่าที่ต้องการเปรียบเทียบ
      • LessThanEqual : ค่าของข้อมูลที่รับมาต้องมีค่าน้อยกว่าหรือเท่ากับค่าที่ต้องการเปรียบเทียบ
      • DataTypeCheck : เป็นการระบุการตรวจสอบชนิดของข้อมูล และทำงานร่วมกับ properties ที่ชื่อว่า Type โดยการกำหนด properties นี้ จะส่งผลให้ค่าที่ถูกกำหนดไว้ให้กับ properties ที่มีชื่อว่า ValueToCompare และ ControlToCompare ไม่ทำงาน
    • Type : ชนิดของข้อมูลที่ต้องการตรวจสอบ
    หมายเหตุ : ค่าที่ต้องการเปรียบเทียบอาจเป็นค่าที่มาจากคอนโทรลอีกตัวที่ต้องการเปรียบเทียบ(ControlToCompare) หรือมาจากค่าที่กำหนดไว้ใน ValueToCompare นั่นเอง ซึ่งค่า properties ทั้งสองนี้ไม่ควรกำหนดร่วมกัน ควรเลือกใช้เพียงตัวใดตัวหนึ่งเท่านั้น แต่หากมีการกำหนดทั้ง 2 ค่าพร้อมกัน การทำงานของ ControlToCompare จะมีลำดับในการทำงานก่อน


    ตัวอย่างที่
    1
    การเปรียบเทียบค่าข้อมูล 2 ค่าจากคอนโทรล เช่น กรณีการพิมพ์รหัสผ่านและยืนยันรหัสผ่าน เป็นต้น

    Password :<asp:TextBox ID="txtPassword" runat="server">
    </asp:TextBox><br />
    Retype-password :<asp:TextBox ID="txtConfirmPassword" 
    runat="server"></asp:TextBox> 
    <asp:CompareValidator ID="CmpPassword" runat="server" 
    ErrorMessage="Password must be the same" 
    ControlToCompare="txtPassword" ControlToValidate="txtConfirmPassword">*</asp:CompareValidator>
    
    

    ตัวอย่างที่ 2 การเปรียบเทียบค่าของข้อมูลกับค่าคงที่ที่กำหนด กรณีที่ต้องกรอกข้อมูลที่มีค่ามากกว่าหรือเท่ากับ 18

    Age :<asp:TextBox ID="txtAge" runat="server"></asp:TextBox>
    <asp:CompareValidator ID="CmpAge" runat="server" 
    ControlToValidate="txtAge" 
    ErrorMessage="Age must greater than or equal 18 years old"
    Operator="GreaterThanEqual" ValueToCompare="18">*
    </asp:CompareValidator>
    
    

    ตัวอย่างที่ 3 การตรวจสอบชนิดของข้อมูล กรณีกรอกข้อมูลราคาต้องเป็นตัวเลขเท่านั้น

    Price : <asp:TextBox ID="txtPrice" runat="server"></asp:TextBox>
    
    <asp:CompareValidator ID="CmpType" runat="server" 
    ControlToValidate="txtPrice" ErrorMessage="Price must be number" 
    Operator="DataTypeCheck" Type="Double">*</asp:CompareValidator>

    CustomValidator

                หากการตรวจสอบด้วย validator ที่กล่าวมาก่อนหน้านี้ ยังไม่ครอบคลุมการตรวจสอบที่ต้องการ นักพัฒนาสามารถกำหนดเงื่อนไขในการตรวจสอบ และการทำงานนอกเหนือจากที่ validator ที่ ASP.NET เตรียมไว้ให้ขึ้นได้เอง โดยใช้ validator ประเภท CustomValidator ซึ่ง validator ประเภทนี้ จะมีช่องทางในการตรวจสอบสำหรับผู้พัฒนาเตรียมไว้ให้ทั้ง 2 ฝั่ง คือทั้งฝั่ง client และฝั่ง server โดยมี properties ที่จำเป็นเพิ่มเติมจาก properties หลัก ดังนี้

    • ClientValidationFunction : เป็นการกำหนดค่าของฟังก์ชั่นในฝั่ง client ที่ใช้ในการตรวจสอบข้อมูล
    • OnServerValidate : เป็นการกำหนดฟังก์ชั่น event หรือเมธอดที่ใช้ในการตรวจสอบข้อมูลในฝั่งเซิร์ฟเวอร์

    ตัวอย่างที่ 1 การใช้งาน custom validator ในการตรวจสอบค่าข้อมูลจากฝั่ง client

    <script type="text/javascript">
    function CheckData(oSrc, args) {
    if(args.Value.length >= 5 && args.Value.search('-') == -1)
    args.IsValid = true;
    else
    args.IsValid = false;
    oSrc.innerText = "(Your custom message here) ";
    }
    </script>
    
    <asp:TextBox ID="txtUserName" runat="server"></asp:TextBox>
    <asp:CustomValidator ID="CustomUsername" runat="server" 
    ClientValidationFunction="CheckData" ControlToValidate="txtUserName" 
    ErrorMessage="Username must be greater than or equal 5 letters and cannot contain contain '-'">*</asp:CustomValidator>
    
    หมายเหตุ : หากต้องการเปลี่ยนแปลงข้อความผิดพลาดที่ต้องการแสดงสามารถทำได้โดยกำหนด      oSrc.innerText ในฟังก์ชั่น JavaScript นั้น

    ตัวอย่างที่ 2 การใช้งาน custom validator ในการตรวจสอบค่าข้อมูลจากทั้งฝั่ง client และฝั่งเซิร์ฟเวอร์

    ฝั่ง Client

    <script type="text/javascript">
    function CheckData(oSrc, args) {
    if(args.Value.length >= 5 && args.Value.search('-') == -1)
    args.IsValid = true;
    else
    args.IsValid = false;
    
    </script>
    <asp:TextBox ID="txtUserName" runat="server"></asp:TextBox>
    <asp:CustomValidator ID="CustomUsername" runat="server"
    ClientValidationFunction="CheckData" 
    ControlToValidate="txtUserName"
    ErrorMessage="Username must greater than or equal 5 letters and cannot contain '-' " onservervalidate="CustomUsername_ServerValidate">*
    </asp:CustomValidator>
    
    

    ฝั่งเซิร์ฟเวอร์

    protected void CustomUsername_ServerValidate(object source, ServerValidateEventArgs args)
    {
    string username = args.Value;
    string[] Users = {"Amanda","Robert","John","Jennie"};
    if (Users.Contains(username))
    {
    args.IsValid = false;
    
    CustomUsername.ErrorMessage = "Username is already exists in database.";
    }
    else
    args.IsValid = true;
    }
    

                จากตัวอย่างจะมีการตรวจสอบค่าข้อมูลรหัสผู้ใช้จากฝั่ง client ก่อน ว่าต้องมีความยาวตั้งแต่ 5 ตัวอักขระขึ้นไปและต้องไม่ประกอบด้วย “-” หากมีเงื่อนไขครบตามกำหนดจะทำการตรวจสอบอีกครั้งในฝั่งเซิร์ฟเวอร์ว่าจะต้องไม่ซ้ำกับค่าที่มีอยู่แล้วจากข้อมูลรหัสผู้ใช้สมมติที่มีอยู่ และหากพบข้อผิดพลาดจะมีการแสดงข้อความแจ้งบอก

    Validation Summary

                เป็น validator ที่ใช้ในการสรุปข้อผิดพลาดทั้งหมดที่เกิดขึ้นในหน้าจอดังกล่าว ซึ่งอาจมีการระบุ ValidationGroup เพื่อแยกกลุ่มการตรวจสอบตามที่ได้กล่าวไว้แล้วข้างต้นได้ โดยการใช้งาน validator ตัวนี้จะประกอบด้วย properties ที่สำคัญ ดังนี้

    • DisplayMode :เป็นรูปแบบในการแสดงผลสรุปรวมข้อผิดพลาด ซึ่งประกอบด้วย
      • List : เป็นการแสดงผลแบบเว้นบรรทัด
      • BulletList : เป็นการแสดงผลแบบ bullet ซึ่งโดยปกติจะมีค่าตั้งต้นเป็นการแสดงผลในรูปแบบนี้
      • SingleParagraph : เป็นการแสดงผลข้อผิดพลาดแบบเว้นวรรคต่อกันในบรรทัดเดียวกันไปเรื่อยๆ

    ตัวอย่างผลลัพธ์การแสดงผลของ DisplayMode ในแต่ละแบบ

    DisplayMode

    • HeaderText : เป็นการระบุข้อความที่ต้องการให้แสดงในส่วนบนของข้อสรุปผิดพลาดทั้งหมดที่เกิดขึ้น
    • ShowMessageBox : เป็นการกำหนดว่าต้องการให้แสดงผลสรุปในรูป MessageBox หรือไม่ ซึ่ง properties ดังกล่าวจะไปทำการสร้างฟังก์ชั่นที่เรียกใช้งาน alert() ของ JavaScript เพื่อการแสดงผลสรุปข้อผิดพลาดนี้
    • ShowSummary : เป็นการแสดงผลรายการข้อผิดพลาดในรูปแบบ HTML ในตำแหน่งที่ validator ตัวนี้ถูกวางอยู่

    ตัวอย่างที่ 1 การใช้ ValidationSummary ในการสรุปข้อผิดพลาดในหน้าจอโดยแสดงผลในรูปแบบ MessageBox

    Username : <asp:TextBox ID="txtUserName" runat="server">
    </asp:TextBox>
    <asp:CustomValidator ID="CustomUsername" runat="server" 
    ClientValidationFunction="CheckData" ControlToValidate="txtUserName" 
    ErrorMessage="Username must greater than or equal 5 letters and cannot contain '-' "  onservervalidate="CustomUsername_ServerValidate">*
    </asp:CustomValidator><br />
    Password :
    <asp:TextBox ID="txtPass" runat="server"></asp:TextBox>
    <asp:RequiredFieldValidator ID="ReqPass" runat="server" 
    ControlToValidate="txtPass" ErrorMessage="Password">*
    </asp:RequiredFieldValidator>
    <asp:ValidationSummary ID="ValidationSummary" runat="server"
    HeaderText="Errors:" ShowMessageBox="True" />
    
    

                จากบทความข้างต้น หากผู้พัฒนาเลือกใช้ validator ให้ตรงและเหมาะสมกับงาน น่าจะเป็นประโยชน์และช่วยอำนวยความสะดวกในการตรวจสอบข้อมูลให้ง่ายและรวดเร็วขึ้น และผู้เขียนหวังเป็นอย่างยิ่งว่าข้อมูลการใช้งาน validator จากบทความนี้จะมีส่วนช่วยในการตัดสินใจของผู้พัฒนาในการเลือกหยิบ validator มาใช้งานไม่มากก็น้อย หากมีข้อผิดพลาดประการใด ผู้เขียนขออภัยไว้ ณ ที่นี้ด้วยค่ะ

    แหล่งข้อมูลอ้างอิง :
    http://www.codeproject.com/Articles/3882/ASP-NET-Validators-Unclouded
    http://www.codeproject.com/Articles/334310/Understanding-ASP-NET-Validation-Techniques
    http://www.codeproject.com/Articles/7943/Validator-Controls-in-ASP-NET

  • รวมเทคนิคการออกแบบ UI ให้สวยงามสำหรับ Designer มือใหม่ (ตอนที่ 1)

         ปัจจุบันในการทำงานด้าน IT นั้น ผู้ทำงานต้องมีความสามารถหลายๆด้านเพื่อให้รองรับกับงานที่ได้รับ โดยงานส่วนใหญ่ก็คือการพัฒนาเว็บไซต์เป็นหลัก ซึ่งทำตั้งแต่เก็บ Requirement จนถึงส่งมอบลูกค้า ปัญหาใหญ่ของคนที่เป็นโปรแกรมเมอร์  เวลาทำเว็บไซต์ / แอพ คือจะขาดเซ้นส์ด้านดีไซน์  ไม่ว่าจะลองออกแบบยังไงก็เลือกสีได้ไม่โดน เลือกฟ้อนต์ได้ไม่สวยสักที หน้าจอผสมกันออกมาดูเละเทะ

         วันนี้ผู้เขียนจึงเขียนบทความนี้ซึ่งอาจมีประโยชน์กับผู้ที่สนใจและกำลังศึกษาเกี่ยวกับการออกแบบ UI โดยเฉพาะกับคนที่ประสบปัญหาเดียวกับผู้เขียน  เลยนำบทความเทคนิคต่างๆมาเรียบเรียงเป็นภาษาไทยให้อ่านกัน ไม่ว่าคุณจะไม่ได้เป็นดีไซเนอร์ หรือเป็นดีไซเนอร์ก็อยากแนะนำให้ลองอ่านดูครับ

         ก่อนที่จะเข้าสู่รายละเอียดเรามาดูกันก่อนว่าภาพรวมทั้งหมดแล้วเกี่ยวกับอะไรบ้าง
    ________• การจัดวางเครื่องมือและองค์ประกอบต่างๆ
    ________• ส่วนตอบสนองกับผู้ใช้
    ________• การนำเสนอและอำนวยความสะดวกกับผู้ใช้

    แนวคิดที่ 1 : Try A One Column Layout instead of multicolumns.
    _____การจัดรูปแบบบทความให้มีเพียงคอลัมน์เดียวจะช่วยทำให้เราสามารถควบคุมความต่อเนื่องของบทความได้ดี ช่วยอำนวยความสะดวกและสามารถกำหนดทิศทางการอ่านบทความของผู้อ่านได้อย่างแม่นยำ เนื่องจากมีเพียงการเลื่อนขึ้นและลงเท่านั้น ในขณะที่การจัดบทความแบบหลายคอลัมน์จะทำให้ผู้อ่านเกิดความสับสน ส่งผลให้ผู้อ่านเสียสมาธิหรือหมดความสนใจในบทความดังกล่าวได้idea001

    แนวคิดที่ 2 : Try Distinct Clickable/Selected Styles instead of blurring them.
    _____ในการออกแบบหน้าจอโดยเฉพาะส่วนของ links, buttons สิ่งที่กำลังถูกเลือก(chosen items) และข้อความ(text)หรือบทความ(content) ควรออกแบบให้ไปในรูปแบบเดียวกันหมดทุกๆหน้าจอ เพื่อช่วยให้ผู้ใช้งานไม่สับสนหรือต้องทำความเข้าใจเพิ่มเติมในรูปแบบพื้นฐานที่ได้ออกแบบไว้ ดังตัวอย่างภาพทางด้านซ้าย โดยผู้ออกแบบเลือกสีฟ้าแทนในส่วนของ links, buttonsและสีดำแทนส่วนที่กำลังถูกเลือก(chosen items) และสีเทาแทนข้อความโดยในแต่ละองค์ประกอบใช้รูปแบบเดียวกันภายในองค์ประกอบนั้น ส่วนในภาพทางด้านขวา เป็นการเลือกสีและรูปแบบที่หลากหลายในองค์ประกอบเดียวกันซึ่งจะส่งผลให้ผู้ใช้สับสนกับหน้าจอดังกล่าวได้idea006

    แนวคิดที่ 3 : Try More Contrast instead of similarity.
    _____การเพิ่มความน่าสนใจหรือการยกระดับความคมชัดในส่วนขององค์ประกอบสำคัญๆส่งผลให้เกิดความแตกต่างจากองค์ประกอบรวมอื่นๆในหน้าจอจะเป็นการยกระดับ UI ของคุณให้มีประสิทธ์ภาพมากขึ้น ไม่ว่าจะเป็นการใช้โทนสีที่เข้มขึ้น การไล่เฉดสีหรือการใส่เงาให้กับองค์ประกอบนั้นๆทำให้ผู้ใช้งานรับรู้ถึงองค์ประกอบสำคัญนั้นได้ทันทีจากการเข้าใช้งาน ช่วยให้ผู้ใช้งานสะดวกและเข้าใจการทำงานของหน้าจอได้ง่ายยิ่งขึ้นidea011

    แนวคิดที่ 4 : Try Fewer Borders instead of wasting attention.
    _____การจัดรูปแบบองค์ประกอบโดยใช้เส้นเป็นอีกสิ่งหนึ่งที่มีการนำมาใช้เพื่อเพิ่มจุดน่าสนใจให้กับตัวUI ทั้งยังสามารถนำมาจัดหรือแบ่งขอบเขตของกลุ่มองค์ประกอบในหน้าจอได้อย่างชัดเจน จนบางครั้งนักออกแบบก็ใช้งานการจัดองค์ประกอบแบบนี้มากจนเกินจำเป็นไปในแต่ละส่วนของหน้าจอส่งผลให้กลุ่มองค์ประกอบนั้นถูกตัดขาดออกจากกันอย่างสิ้นเชิงและทำให้การควบคุมทิศทางของหน้าจอผิดจากที่ได้ตั้งเอาไว้ ดังนั้นการเลือกใช้เส้น ควรใช้แค่พอจำเป็นจนไม่ทำให้หน้าจอดูรกจนเกินไป เราอาจจะใช้วิธีอื่นๆมาช่วยในการจัดกลุ่มองค์ประกอบได้ ไม่ว่าจะใช้ช่องว่างระหว่างกลุ่มองค์ประกอบ การเน้นตัวอักษรหรือสีเป็นต้นidea023

    แนวคิดที่ 5 : Try Designing For Zero Data instead of just data heavy cases.
    _____โดยทั่วไปแล้วเรามักจะออกแบบหน้าจอให้รองรับกับการแสดงข้อมูล ไม่ว่าเป็น 1, 10, 100 หรือเป็น 1000 ข้อมูลโดยบางทีเราอาจลืมออกแบบสำหรับกรณีที่ข้อมูลเป็น 0 ส่งผลให้เวลาแสดงหน้าจอ อาจเป็นหน้าจอว่างๆหรือมีการแจ้งเตือนว่าไม่พบรายการหรือข้อมูล โดยสำหรับนักออกแบบแล้วอาจคิดว่าไม่ส่งผลกระทบใดๆกับหน้าจอมากนัก แต่สำหรับผู้ใช้งานระบบที่เจอหน้าจอที่ว่างเปล่าแล้วอาจเกิดข้อสงสัยได้ว่าเกิดอะไรขึ้นหรือเกิดความสับสนว่าจะทำอะไรในขั้นตอนต่อไป ดังนั้นการออกแบบในส่วนของกรณีที่ไม่พบข้อมูลหรือรายการ อาจใส่ข้อความอธิบายถึงสาเหตุที่ทำให้ไม่พบข้อมูลหรือแนะนำขั้นตอนที่จะทำให้เกิดข้อมูลต่างๆได้ ส่งผลให้ผู้ใช้ไม่สะดุดและสะดวกกับการใช้งานระบบได้มากยิ่งขึ้นidea025

    แนวคิดที่ 6 : Try Conventions instead of reinventing the wheel.
    _____การสื่อสารกับผู้ใช้ถือเป็นอีกส่วนที่มีความสำคัญในการออกแบบหน้าจอ ซึ่งในการออกแบบนั้น เราควรออกแบบให้สอดคล้องกับการใช้งานของผู้ใช้ระบบหรือความเคยชินที่ผู้ใช้เคยได้ทำมาโดยตลอด ส่งผลให้หน้าจอที่ได้ออกแบบไว้ตอบสนองความต้องการและลดเวลาในการเรียนรู้หน้าจอเพิ่มเติม ซึ่งโดยทั่วไปแล้วจะมีรูปแบบหลักๆอยู่พอสมควร เช่นการให้มีปุ้มปิดหน้าจอมุมบนขวา ปุ่มกดถัดไปอยุ่ด้านขวาและย้อนกลับอยู่ด้านซ้าย สัญลักษณ์รูปเฟืองสื่อถึงการตั้งค่า เป็นต้นidea029

    แนวคิดที่ 7 : Try Bigger Click Areas instead of tiny ones.
    _____จากหัวข้อที่ 3 นี้ก็เป็นอีกแนวคิดหนึ่งที่จะเพิ่มความน่าสนใจให้กับองค์ประกอบประเภท links, buttons ได้ คือการเพิ่มขยายหรือขอบเขตในการกดองค์ประกอบนั้นๆ เพราะในปัจจุบัน หน้าจอที่ได้ออกแบบไว้ถูกนำไปใช้งานในอุปกรณ์ที่หลากหลายมากขึ้นการออกแบบให้สิ่งเหล่านี้มีขนาดที่เหมาะสมในหน้าจอหนึ่ง อาจจะไม่สะดวกที่จะใช้งานในอีกหน้าจอหนึ่ง หรือการออกแบบให้ปุ่มกดหรือลิงค์เล็กจนเกินไป อาจส่งผลให้ผู้ใช้ไม่สะดวกกับการหาหรือกดสิ่งเหล่านั้นได้การขยายขนาดหรือขอบเขตของการกดจะช่วยให้ผู้ใช้สะดวกมากอีกขึ้น และยังมีวิธีการเพิ่มข้อความให้มีความยาวมากขึ้น หรือใช้ไอคอนร่วมกับข้อความ เป็นต้นidea038

    แนวคิดที่ 8 : Try Icon Labels instead of opening for interpretation.
    _____ถ้าพุดถึงเรื่องของไอคอนแล้ว ไอคอนมีส่วนช่วยให้หน้าจอของเราดูดีขึ้นได้และยังทำให้ผู้ใช้งานสามารถเข้าใจถึงการทำงานของไอคอนนั้นได้เกือบทันที แต่ในบางครั้งกลุ่มผู้ใช้งานบางกลุ่ม อาจจะไม่สามารถตีความหมายของไอคอนตามวัตถุประสงค์การใช้งานที่เราได้ออกแบบเอาไว้ หรือไอคอนที่เรานำมาใช้ อาจไม่แสดงความหมายได้คลุมเครือ ดังนั้นวิธีที่จะช่วยให้ไอคอนสามารถแสดงวัตถุได้อย่างชัดเจนคือการเพิ่มข้อความควบคู่ไปกับตัวไอคอนด้วย จะทำให้ผู้ใช้งานหน้าจอเข้าใจได้ทันทีและไม่สับสนกับความหมายที่จะสื่อถึง และบางกรณีไอคอนที่นำมาใช้อาจเล็กหรือสีที่ใช้ดูกลมกลืนไปกับองค์ประกอบอื่นๆ การใส่ข้อความจึงเป็นการช่วยให้ไอคอนดูคมชัดมากขึ้นidea047

    แนวคิดที่ 9 : Try Natural Language instead of dry text.
    _____แนวคิดข้อนี้ออกจะแปลกตาสำหรับผู้เขียนสักหน่อย เพราะเป็นการนำภาษาธรรมชาติ (ภาษาพูด)มาใช้เป็นคำอธิบายแทนการใช้คำทางการหรือราชการที่ปัจจุบันเราใช้กันอย่างแพร่หลาย ซึ่งการนำภาษาธรรมชาติมาใช้ช่วยเขียนคำชี้แจ้ง จะทำให้ผู้ใช้เข้าใจถึงจุดหมายที่ผู้ใช้จะต้องกระทำกับหน้าจอ แต่ในเว็บไซต์ที่ใช้งานในเชิงราชการ อาจดูไม่ค่อยเหมาะสมหรือไม่เป็นที่ชอบใจของผู้ใช้งานได้ ข้อนี้จึงขึ้นอยู่กับว่าเราจะไปใช้ในลักษณะไหน มากน้อยเพียงไหนขึ้นอยุ่กับกลุ่มผู้ใช้งานระบบด้วย แต่อาจนำมาใช้ผสมกับคำที่เป็นทางการในบางจุดได้ เพื่อให้ผู้ใช้งานเข้าใจมากขึ้นและไม่ดูน่าเกลียดจนเกินไป แต่ในอนาคตอาจการเป็นที่นิยมแทนการใช้คำทางราชการก็เป็นได้idea048

    แนวคิดที่ 10 : Try Extra Padding instead of overcrowding elements.
    _____ช่องว่างสำคัญไฉน เมื่อพูดถึงช่องว่างนักออกแบบบางท่านอาจบอกว่าไม่ค่อยสำคัญมากนัก แต่จริงๆแล้วช่องว่างก็เป็นส่วนหนึ่งที่จะทำให้หน้าจอเราดูสะอาดตามากขึ้น และสามารถนำช่องว่างมาใช้สำหรับแยกกลุ่มองค์ประกอบได้ นอกจากการใช้เส้นแล้ว เราสามารถนำช่องว่างมาแยกข้อความในตารางให้รับรู้ได้ง่ายยิ่งขึ้นด้วย เพราะบางกรณีที่มีการแสดงผลแบบตาราง จะมีการนำข้อมูลจำนวนมากมาแสดงให้ผู้ใช้งานรับรู้ แต่กลับไม่ได้ออกแบบส่วนของการแบ่งแยกขอบเขตของcolumn หรือ row ไว้เลย ส่งผลให้ข้อมูลที่นำมาแสดงอาจติดกันยาวเหยียดจนผู้ใช้สับสนกับจุดสิ้นสุดของข้อมูลได้ การเพิ่มช่องว่างก็เป็นอีกวิธีที่สามารถนำมาใช้ได้ หรือเราอาจนำมาใช้ควบคู่กับเส้นก็เป็นอีกวิธีที่ดีเช่นกันidea063


    สำหรับบทความชุด “รวมเทคนิคการออกแบบ UI ให้สวยงามสำหรับ Designer มือใหม่” ตอนที่ 1 ขอจบที่ 10 ข้อแรกติดตามตอนที่ 2 ได้ตามลิงค์ด้านล่างเลยครับ

    รวมเทคนิคการออกแบบ UI ให้สวยงามสำหรับ Designer มือใหม่ (ตอนที่ 2)

    ขอบคุณครับ

  • ทำอย่างไรให้สามารถกำหนดจุดพิกัดบนแผนที่ Google map แบบจุดเดียวและหลายจุดจากฐานข้อมูลได้ด้วย ASP.NET C#

          การพัฒนาเว็บไซต์ในปัจจุบัน พบว่ามีบางเว็บไซต์มีความต้องการในการแสดงผลตำแหน่ง ที่ตั้งบนแผนที่ Google map เพื่ออำนวยความสะดวกให้กับผู้เยี่ยมชมเว็บไซต์ในการค้นหาตำแหน่งที่ตั้งของสถานที่นั้นๆมากกว่าการบอกเพียงที่อยู่อย่างเช่นแต่ก่อน อาธิเช่น เว็บไซต์ที่เป็นศูนย์รวมในการจองที่พัก ที่มีความจำเป็นต้องแสดงที่ตั้งของโรงแรมที่มีในบริเวณหรือละแวกนั้นๆที่เข้ามาร่วมให้ข้อมูลกับเว็บไซต์ในการจองที่พัก หรือแม้กระทั่งโรงพยาบาล ห้างสรรพสินค้า โรงเรียน มหาวิทยาลัย ที่เว็บไซต์ต้องการแสดงที่ตั้งเพื่อให้ผู้เยี่ยมชมเว็บไซต์สามารถทราบได้ว่าสถานที่เหล่านั้นมีที่ตั้งอยู่ในบริเวณใดบ้าง เพื่อเป็นประโยชน์ให้ผู้ที่เข้ามาเยี่ยมชมเว็บไซต์สามารถเรียกดูได้จากแผนที่เพื่อศึกษาเส้นทาง หรือหาตำแหน่งที่จะสามารถไปยังจุดนั้นๆได้โดยง่ายและใช้ระยะทางใกล้ที่สุดนั่นเอง
          ในบทความนี้ ผู้เขียนจึงขอพูดถึงวิธีการแสดงผลตำแหน่งที่ตั้งบน Google map ซึ่งมีทั้งแบบกำหนดตายตัว โดยมีการระบุตำแหน่งที่ตั้งทั้งละติจูดและลองจิจูด และแบบที่มีการดึงค่าของละติจูดและลองจิจูดมาจากฐานข้อมูลของเว็บไซต์ที่พัฒนาโดยใช้เครื่องมือ ASP.NET ด้วย C# และแบบที่มีการกำหนดจุดแสดงตำแหน่งเพียงจุดเดียวและหลายจุดพร้อมกัน เพื่อประโยชน์กับนักพัฒนาท่านอื่นๆที่มีความสนใจสามารถนำไปประยุกต์ใช้กับเว็บไซต์ของตนได้

          โดยผู้เขียนขอเสนอวิธีการเบื้องต้นในการแสดงผลแบบกำหนดค่าตายตัวให้ผู้อ่านลองศึกษาการทำงานเพื่อทำความเข้าใจในเบื้องต้นก่อน ดังนี้

    การแสดงผลแบบจุดเดียว

    1. อ้างอิงพาธที่ตั้งของ Google API ซึ่งเป็นส่วนหนึ่งของการแสดงผลบนแผนที่ Google map และไฟล์จาวาสคริปต์ที่ใช้ในการแสดงผล(ถ้ามี)
    <script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false"></script>
     
    <script src="js/mapwithmarker.js" type="text/javascript"></script>
    1. กำหนดสไตล์ชีทที่ใช้ในการแสดงผล เมื่อมีการคลิกตำแหน่งที่ได้ทำการกำหนดพิกัดไว้
    <style type="text/css">  
    .labels { color: black; background-color: #FF8075; font-family: Arial; font-size: 11px; font-weight: bold; text-align: center; width: 12px; }  </style>
    1. กำหนดพิกัดที่ต้องการให้แผนที่ค้นหาจุดกึ่งกลางของการแสดงผล ซึ่งโดยปกติจะถือเอาจุดแรกที่ต้องการแสดงเป็นตำแหน่งกึ่งกลางของการแสดงผลตำแหน่งบนแผนที่นั้นๆ เพื่อให้ตำแหน่งดังกล่าวอยู่กึ่งกลางของแผนที่ที่ต้องการแสดงนั่นเอง
    var mapOptions = {  
    center: new google.maps.LatLng(ค่าละติจูด, ค่าลองจิจูด),  
    zoom: 12, 
    ///ขนาดที่ต้องการให้ซูมเป็นค่าตั้งต้น  
    mapTypeId: google.maps.MapTypeId.ROADMAP  };
    1. กำหนดส่วนที่ต้องการให้แสดงแผนที่ ว่าต้องการให้แสดงในส่วนใดของเว็บไซต์
    var map = new google.maps.Map(document.getElementById("dvMap"), mapOptions);
     ///ในที่นี้พื้นที่ที่ต้องการให้แสดงผลในเว็บไซต์ คือ dvMap โดยนำค่าที่กำหนดกึ่งกลางไว้ในขั้นตอนที่ 3 (mapOptions) มาเป็นค่าพารามิเตอร์ในการแสดงผลด้วย
    
    1. การกำหนดจุดพิกัดที่ต้องการแสดงผล ซึ่งค่าที่ต้องการคือ ชื่อสถานที่ ค่าละติจูด ลองจิจูด และคำอธิบายในการแสดงผลตำแหน่งสถานที่ที่เราทำการกำหนดไว้ ดังนี้
      • กำหนดค่าพิกัดลองจิจูดและละติจูดของจุดที่เราต้องการกำหนดบนแผนที่
      var infoWindow = new google.maps.InfoWindow();
       var myLatlng = new google.maps.LatLng(ค่าละติจูด, ค่าลองจิจูด);
      
      
      • กำหนดค่าพิกัดตำแหน่งของจุดและพารามิเตอร์ต่างๆที่จำเป็นต้องใช้ในการกำหนดจุดที่เราต้องการกำหนดบนแผนที่
      var marker = new MarkerWithLabel({
       position: myLatlng, //เป็นการกำหนดค่าพิกัดตำแหน่งของจุดที่เราต้องการกำหนดบนแผนที่
       map: map, //เป็นการกำหนดพื้นที่ที่ต้องการแสดงแผนที่ ในที่นี้คือ dvMap
       title: title, //เป็นการกำหนดชื่อสถานที่
       labelContent:1,  //เป็นการกำหนดหมายเลขลำดับของตำแหน่งแสดงผล
       labelAnchor: new google.maps.Point(7, 30),
       labelClass: "labels", //เป็นการกำหนดรูปแบบในการแสดงผลด้วยสไตล์ชีท
       labelInBackground: false
       });
      
      
      • กำหนดการแสดงผลเมื่อผู้เยี่ยมชมมีการคลิกบนจุดดังกล่าว
      (function(marker) {
       google.maps.event.addListener(marker, "click", function(e) {
       infoWindow.setContent(description);
       //เป็นการกำหนดข้อความที่ต้องการแสดง เมื่อผู้เยี่ยมชมเว็บไซต์คลิกบนจุดดังกล่าว
       infoWindow.open(map, marker);
       });
       })(marker);
      
      
    1. สร้างพื้นที่ที่กำหนดการแสดงผลในส่วน body
    <div id="dvMap" style="width: 800px; height: 700px;">
     </div>
    1. แสดง code ทั้งหมดที่ใช้
    <%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>
     <!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 runat="server">
     <title>Google map Test for one point</title>
     <style type="text/css">
     .labels { color: black; background-color: #FF8075; font-family: Arial; font-size: 11px; font-weight: bold; text-align: center; width: 12px; }
     </style>
     <script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false"></script>
     <script src="js/mapwithmarker.js" type="text/javascript"></script>
     <script type="text/javascript">
     window.onload = function() {
     var mapOptions = {
     center: new google.maps.LatLng(7.006923, 100.500238),
     zoom: 12,
     mapTypeId: google.maps.MapTypeId.ROADMAP
     };
     var map = new google.maps.Map(document.getElementById("dvMap"), mapOptions);
     var infoWindow = new google.maps.InfoWindow();
     var myLatlng = new google.maps.LatLng(7.006923, 100.500238);
     var marker = new MarkerWithLabel({
     position: myLatlng,
     map: map,
     title: 'มหาวิทยาลัยสงขลานครินทร์',
     labelContent: 1,
     labelAnchor: new google.maps.Point(7, 30),
     labelClass: "labels", // the CSS class for the label
     labelInBackground: false
     });
     (function(marker ) {
     google.maps.event.addListener(marker, "click", function(e) {
     infoWindow.setContent('มหาวิทยาลัยสงขลานครินทร์ วิทยาเขตหาดใหญ่');
     infoWindow.open(map, marker);
     });
     })(marker );
     }
     </script>
     </head>
     <body>
     <form id="form1" runat="server">
     <div id="dvMap" style="width: 800px; height: 700px;">
     </div>
     </form>
     </body>
     </html>

    ตัวอย่างภาพผลลัพธ์ที่ได้ :
    Map1point

    แบบหลายจุดและดึงค่าละติจูด ลองจิจูดจากฐานข้อมูลมาแสดงผล

    โดยลักษณะการเขียนจะคล้ายๆกับแบบกำหนดจุดเพียงจุดเดียวอย่างที่กล่าวไว้แล้วในตอนต้น แต่จะแตกต่างกันในส่วนของการดึงข้อมูล ซึ่งจะเน้นเฉพาะส่วนที่แตกต่าง และแสดง code สรุปรวมให้ดูอีกครั้ง

    ไฟล์ default.aspx ในฝั่ง Client

    1. กำหนดค่าตัวแปรที่ใช้ในแสดงผลจาก Repeater ซึ่งเป็นเครื่องมือที่มีอยู่แล้วใน .NET โดยตัวแปรที่จะใช้ที่นี้ให้มีชื่อว่า markers และค่าที่ต้องการนำมาใช้จะเป็นฟิลด์ของ Name, Latitude, Longitude และ Description ซึ่งดึงมาจากฐานข้อมูล(โดยชื่อฟิลด์สามารถเปลี่ยนแปลงได้ตามข้อมูลที่มีอยู่จริงในฐานข้อมูลของแต่ละท่าน)
      <script type="text/javascript">
       var markers = [
       <asp:Repeater ID="rptMarkers" runat="server">
       <ItemTemplate>
       {
       "title": '<%# Eval("Name") %>',
       "lat": '<%# Eval("Latitude") %>',
       "lng": '<%# Eval("Longitude") %>',
       "description": '<%# Eval("Description") %>'
       }
       </ItemTemplate>
       <SeparatorTemplate>
       ,
       </SeparatorTemplate>
       </asp:Repeater>
       ];
       </script>
    2. กำหนดพิกัดที่ต้องการให้แผนที่ค้นหาจุดกึ่งกลางของการแสดงผล ซึ่งโดยปกติจะถือเอาจุดแรกที่ต้องการแสดงเป็นตำแหน่งกึ่งกลางของการแสดงผลตำแหน่งบนแผนที่นั้นๆ เพื่อให้ตำแหน่งดังกล่าวอยู่กึ่งกลางของแผนที่ที่ต้องการแสดงนั่นเอง
      var mapOptions = {
       center: new google.maps.LatLng(markers[0].lat, markers[0].lng),
       zoom: 8,
       mapTypeId: google.maps.MapTypeId.ROADMAP};
    3. กำหนดค่าต่างๆ และวนค่าตัวแปร markers ที่ดึงมาจากฐานข้อมูลและตัว Repeater ที่กล่าวไว้ในข้างต้น และทำการกำหนดค่าต่างๆให้กับแต่ละจุดที่ต้องการ โดยรายละเอียดในการกำหนดค่าจะคล้ายกับวิธีกำหนดแบบจุดเดียว(แบบแรก) แต่จะมีการวนซ้ำให้ครบจำนวนตัวแปรที่ดึงมาจากฐานข้อมูล
      var infoWindow = new google.maps.InfoWindow();
       var map = new google.maps.Map(document.getElementById("dvMap"), mapOptions);
       for (i = 0; i < markers.length; i++) {
       //เป็นการวนค่าตัวแปร markers ที่ดึงมาจากฐานข้อมูลและตัว Repeater ที่กล่าวไว้ในข้างต้น
      
        var data = markers[i]
       //นำค่าจากตัวแปร markers  ที่ดึงมาทีละรายการตามลำดับมาใส่ไว้ในตัวแปร data เพื่อนำไปใช้ในการกำหนดค่าต่อไป
      var myLatlng = new google.maps.LatLng(data.lat, data.lng);
       //กำหนดค่าละติจูดและลองจิจูด
      var marker = new MarkerWithLabel({
       position: myLatlng, //เป็นการกำหนดตำแหน่งที่ต้องการแสดงผล
      map: map,
       title: data.title, //เป็นการกำหนดชื่อที่ต้องการแสดงผลเมื่อนำเมาส์ไปชี้ยังจุดดังกล่าว
      labelContent: i+1//เป็นการกำหนดหมายเลขลำดับให้กับจุดดังกล่าว
       labelAnchor: new google.maps.Point(7, 30),
       labelClass: "labels", // the CSS class for the label
       labelInBackground: false}
       );(function (marker, data) {google.maps.event.addListener(marker, "click", function (e) {
       infoWindow.setContent(data.description);
       //เป็นการกำหนดข้อความที่ต้องการแสดง เมื่อผู้เยี่ยมชมเว็บไซต์คลิกบนจุดดังกล่าว
      infoWindow.open(map, marker);
       });
       })(marker, data);
       }
    4. Code ทั้งหมดในฝั่งไฟล์ Default.aspx
      <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
      
      <!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 runat="server">
       <title></title>
       </head>
       <body>
       <form id="form1" runat="server">
       <script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false"></script><script src="js/mapwithmarker.js" type="text/javascript"></script>
      <script type="text/javascript">
       var markers = [
       <asp:Repeater ID="rptMarkers" runat="server">
       <ItemTemplate>
       {
       "title": '<%# Eval("Name") %>',
       "lat": '<%# Eval("Latitude") %>',
       "lng": '<%# Eval("Longitude") %>',
       "description": '<%# Eval("Description") %>'
       }
       </ItemTemplate>
       <SeparatorTemplate>
       ,
       </SeparatorTemplate>
       </asp:Repeater>
       ];
       </script>
       <script type="text/javascript">
       window.onload = function() {
       var mapOptions = {
       center: new google.maps.LatLng(markers[0].lat, markers[0].lng),
       zoom: 8,
       mapTypeId: google.maps.MapTypeId.ROADMAP
       };
       var infoWindow = new google.maps.InfoWindow();
       var map = new google.maps.Map(document.getElementById("dvMap"), mapOptions);
       var j = 1;
       for (i = 0 ; i <= markers.length-1; i++) {
       var data = markers[i]
       var myLatlng = new google.maps.LatLng(data.lat, data.lng);
       // var marker = new google.maps.Marker({
       var marker = new MarkerWithLabel({
       position: myLatlng,
       map: map,
       title: data.title,
       labelContent: i+1,
       labelAnchor: new google.maps.Point(7, 30),
       labelClass: "labels", // the CSS class for the label
       labelInBackground: false
       });
       (function(marker, data) {
       google.maps.event.addListener(marker, "click", function(e) {
       infoWindow.setContent(data.description);
       infoWindow.open(map, marker);
       });
       })(marker, data);
       }
       }
       </script>
       <div id="dvMap" style="width: 500px; height: 500px">
       </div>
       </form>
       </body>
       </html>

     

    ไฟล์ default.cs ในฝั่ง server

    using System;
     using System.Collections.Generic;
     using System.Web;
     using System.Web.UI;
     using System.Web.UI.WebControls;
     using System.Data;public partial class _Default : System.Web.UI.Page
     {
     protected void Page_Load(object sender, EventArgs e)
     {
     if (!this.IsPostBack)
     {
     rptMarkers.DataSource = GetData();
    //กำหนดแหล่งข้อมูลให้กับตัว Repeater ที่เราต้องการใข้ในการแสดงผล
     rptMarkers.DataBind();
     }
     }
      private DataTable GetData()
    //เมธอดที่ใช้ในการดึงค่าข้อมูลจากฐานข้อมูล ซึ่งในที่นี้มีการส่งค่ากลับเป็น datatable และมีการสมมุติค่าข้อมูลใน datatable เพื่อให้เห็นภาพการดึงจากฐานข้อมูลเท่านั้น แต่สามารถนำไปประยุกต์ใช้กับการติดต่อฐานข้อมูลจริงได้  {
     DataTable table = new DataTable();
     table.Columns.Add("Name", typeof(string));
     table.Columns.Add("Latitude", typeof(decimal));
     table.Columns.Add("Longitude", typeof(decimal));
     table.Columns.Add("Description", typeof(string));
     table.Rows.Add("มหาวิทยาลัยสงขลานครินทร์", 7.006923, 100.500238, "Hatyai");
     table.Rows.Add("มหาวิทยาลัยราชภัฏ สงขลา", 7.172661, 100.613726, "Songkla");
     return table;
     }
     }
    1. ดึงข้อมูลจากแหล่งข้อมูลที่ต้องการ ซึ่งประกอบด้วยข้อมูลตามที่ต้องการให้แสดงผล เช่น ค่าละติจูด ลองจิจูด และชื่อสถานที่ โดยในกรณีนี้ผู้เขียนขอสมมติค่าและสร้าง datatable ขึ้นมา เพื่อใช้ในการทดสอบ โดยแสดงในเมธอด GetData() หากเป็นข้อมูลที่ใช้จริงผู้พัฒนาสามารถนำไปประยุกต์กับการดึงข้อมูลของท่านได้
    2. กำหนดค่าแหล่งข้อมูลให้กับ repeater เพื่อสร้างตัวแปร marker ตามที่กล่าวไว้ในข้างต้น
    3. ผลลัพธ์ที่ได้ :

    Mapmultipoint

           ผู้เขียนหวังเป็นอย่างยิ่งว่า บทความนี้จะเป็นประโยชน์กับนักพัฒนาบางท่านที่กำลังสนใจ และต้องการนำวิธีดังกล่าวไปใช้กับเว็บไซต์ที่ตนกำลังพัฒนาอยู่ หากมีข้อผิดพลาดประการใดผู้เขียนขออภัยไว้ ณ ที่นี้ด้วย และหากผู้รู้ท่านใดมีคำแนะนำหรือข้อเสนอแนะเพิ่มเติมเกี่ยวกับเรื่องนี้ สามารถแลกเปลี่ยนความรู้กันได้เพื่อประโยชน์ในการต่อยอดความรู้ร่วมกันค่ะ

    แหล่งข้อมูลอ้างอิง :
    http://www.dotnetbull.com/2013/06/multiple-marker-with-labels-in-google.html
    http://www.codeproject.com/Articles/36151/Show-Your-Data-on-Google-Map-using-C-and-JavaScrip

  • มาติดตั้ง ADLdap เพื่อใช้ยืนยันตัวตนให้กับ Owncloud 8.0 กันดีกว่า

    การติดตั้ง ADLdap เพื่อใช้ยืนยันตัวตนกับ Owncloud 8.0 นี้จะไม่อธิบายการติดตั้ง owncloud ครับ ซึ่งสามารถเข้าไปดูการติดตั้งได้ที่นี้
    How to: install Owncloud (Easy method) – เกรียงไกร หนูทองคำ
    หรือ
    ติดตั้ง ownCloud เลือกใช้ user PSU Passport หรือ ftp server หรือ RADIUS Server – วิบูลย์ วราสิทธิชัย
    การแบ่งปันความรู้ครั้งนี้จะแนะนำวิธีการนำเอา ADLdap (PHP Library) มาใช้งานร่วมกับ Owncloud เท่านั้นครับ ว่ามีวิธีการและขั้นตอนอย่างไร ซึ่งไม่ยากเกินไปครับ ^^


    ภาพรวมเมื่อทำการติดตั้ง/ตั้งค่า ADLdap สำเร็จ

    1. ลดขั้นตอนการยืนยันตัวตน ซึ่งขั้นตอนนั้นจากเริ่มจาก เซิร์ฟเวอร์ที่ติดตั้ง Owncloud ไปยัง เซิร์ฟเวอร์ Ldap โดยตรง ทำให้ไม่จำเป็นต้องติดตั้งระบบ Authen ในเซิร์ฟเวอร์ Owncloud นี้
    2. สามารถจำกัดบุคลากรที่จะเข้ามาใช้งานได้ ตามคณะ/หน่วยนั้นๆ


    ซอร์ฟแวร์ที่นำมาใช้งาน

    – ubuntu server 14.04.02 (x64)
    – Owncloud 8.0.2
    – PHP 5.5
    – ADLdap 4.0.4 – ดาวน์โหลดได้ที่นี้

    คำแนะนำ หากมีการอัพเดทจาก เวอร์ชั่น 8.0.2 ไปเป็น 8.0.3 หรือเวอร์ชั่นที่สูงกว่า เรียบร้อยแล้ว
    จะต้องทำตามขั้นตอนที่ 4 อีกครั้งครับ 

    ขั้นตอนการติดตั้ง
    1. เข้าสู่ระบบด้วย username ของ admin จากนั้นให้ไปเปิดใช้งาน App โดยคลิ๊กที่เมนู Apps จากนั้นเลือก +Apps ให้คลิ๊กเมนู not enable ดูที่ชื่อ App External user support ให้คลิ๊ก Enable

    12
    ** ขั้นตอนที่ 2-5 จะต้องทำการ ssh ไปยังเซิร์ฟเวอร์ที่ติดตั้ง owncloud ครับ
    ** path ที่ใช้ติดตั้ง owncloud ของบทความนี้ /var/www/owncloud/ ครับ

    2. ให้แตกไฟล์  adLDAP_4.0.4r2.zip จากนั้นให้ Rename ชื่อแฟ้มเป็น adldap (จะ Rename หรือไม่ก็ได้ แต่ต้องอ้างเส้นทางและชื่อแฟ้มให้ถูกต้อง) แล้วไปวางไว้ตำแหน่งนี้ /var/www/owncloud/3rdparty/

    3. เพิ่ม code โดยเข้าไปที่ไฟล์ /var/www/owncloud/config/config.php

    /// AD LDAP ///
    ‘user_backends’ => array(
    0 => array(
    ‘class’ => ‘OC_User_ADLDAP’,
    ‘arguments’ => array(
    0 => ‘dc7.psu.ac.th’,
    ),
    ),
    ),
    /// END /// 

    เมื่อเพิ่มแล้วจะได้ตามนี้

    <?php
    $CONFIG = array (
    ‘instanceid’ => ”,
    ‘passwordsalt’ => ”,
    ‘secret’ => ”,
    ‘trusted_domains’ =>
    array (
    0 => ‘xxx.xxx.psu.ac.th’,
    ),
    ‘datadirectory’ => ‘/var/www/owncloud/data’,
    ‘overwrite.cli.url’ => ‘https://xxx.xxx.psu.ac.th/owncloud’,
    ‘dbtype’ => ‘mysql’,
    ‘version’ => ‘8.0.2.0’,
    ‘dbname’ => ‘owncloud_db’,
    ‘dbhost’ => ‘localhost’,
    ‘dbtableprefix’ => ‘oc_’,
    ‘dbuser’ => ‘xxxxx’,
    ‘dbpassword’ => ‘xxxxx’,
    ‘installed’ => true,
    ‘ldapIgnoreNamingRules’ => false,
    ‘theme’ => ”,
    ‘maintenance’ => false,
    ‘mail_smtpmode’ => ‘smtp’,

    /// AD LDAP ///
    ‘user_backends’ => array(
    0 => array(
    ‘class’ => ‘OC_User_ADLDAP’,
    ‘arguments’ => array(
    0 => ‘dc7.psu.ac.th’,
    ),
    ),
    ),
    /// END ///
    );

     

    4.เพิ่ม code โดยเข้าไปที่ไฟล์ /var/www/owncloud/apps/user_external/appinfo/app.php ไว้บรรทัดล่างสุด

    OC::$CLASSPATH[‘OC_User_ADLDAP’]=’user_external/lib/adldap.php’;

    เมื่อเพิ่มแล้วจะได้ตามนี้

    <?php
    OC::$CLASSPATH[‘OC_User_IMAP’]=’user_external/lib/imap.php’;
    OC::$CLASSPATH[‘OC_User_SMB’]=’user_external/lib/smb.php’;
    OC::$CLASSPATH[‘OC_User_FTP’]=’user_external/lib/ftp.php’;
    OC::$CLASSPATH[‘OC_User_ADLDAP’]=’user_external/lib/adldap.php’;

     

    5. สร้างไฟล์ชื่อ adldap.php นำไปไว้ที่ /var/www/owncloud/apps/user_external/lib/
    จากนั้นให้ทำการเพิ่ม code เข้าไปดังนี้

    หมายเหตุ สำหรับท่านใดที่นำไปใช้งาน ก่อนวันที่ 10-04-58 ขอให้ copy php code ทั้งหมดไปแทนของเดิมด้วยนะครับ

    <?php
    /*
    | ——————————————————————————
    | Owncloud 8 Authentication With ADLDAP 
    | ——————————————————————————
    |
    | Create Date : 08-04-2015
    | Update : 18-10-2016

    | Developer : Aussadayut Ubonkan
    | e-mail : aussadayut.u@psu.ac.th
    | Khunying Long Athakravisunthorn Learning Resources Center, Prince of Songkla University
    |
    */
    require_once(‘3rdparty/adldap/src/adLDAP.php’);

    class OC_User_ADLDAP extends \OCA\user_external\Base{

    private $ad_ldap;

    public $authUser = NULL;
    public $user_authen = NULL;
    public $port = NULL;
    public $basedn = NULL;
    public $ssl = NULL;
    public $account_suffix = NULL;
    public $attribute_mapping = NULL;
    public $addgroup = NULL;
    public $departid = NULL;
    public $set_quota = NULL;


    public function checkPassword($uid,$password){

    $this->set_quota = ‘5 GB’; // Ex. ’12 GB’ , ‘100 MB’
    $this->addgroup = ‘Staffs’; // Group Name
    $this->departid = ‘294’; // Department id , 294 = clib psu

    $this->port = ‘636’; // ldap = 389 , ldaps = 636
    $this->basedn = ‘dc=psu,dc=ac,dc=th’;
    $this->ssl = true;
    $this->account_suffix = ‘@psu.ac.th’;
    $this->attribute_mapping = array(
    “cn”, “dn”, “samaccountname”, “employeeid”, “citizenid”, “company”,
    “campusid”, “department”, “departmentid”, “physicaldeliveryofficename”, “positionid”,
    “description”, “displayname”, “title”, “personaltitle”, “personaltitleid”, “givenname”,
    “sn”, “sex”, “userprincipalname”,”mail”);

    $this->ad_ldap = new adLDAP(
    array(
    ‘domain_controllers’ => array(‘dc7.psu.ac.th’,’dc5.psu.ac.th’),
    ‘ad_port’ => $this->port,
    ‘base_dn’ => $this->basedn,
    ‘use_ssl’ => $this->ssl,
    ‘account_suffix’ => $this->account_suffix,
    )
    );

     

    $this->authUser = $this->ad_ldap->user()->authenticate($uid , $password);
    $this->user_authen = $this->ad_ldap->user()->info($uid,$this->attribute_mapping);

    $check_departid = $this->user_authen[0][“departmentid”][0]; // department id
    $uid = $this->user_authen[0][“samaccountname”][0]; // username
    $uemail = $this->user_authen[0][“userprincipalname”][0]; // e-mail

    /*
    | Print User information
    */
    //echo “<pre>”;
    //print_r($this->user_authen);
    //exit;
    if($check_departid == $this->departid){

    if(!$this->user_Exists($uid)){

    $this->storeUser($uid);

    OC_Group::addToGroup($uid, $this->addgroup); // Add User to Group
    OC_DB::executeAudited(
    ‘INSERT INTO `*PREFIX*preferences` ( `userid`, `appid` , `configkey` ,`configvalue`)’
    . ‘ VALUES( ?, ? , ? ,?)’,
    array($uid, ‘files’, ‘quota’ , $this->set_quota)
    );
    OC_DB::executeAudited(
    ‘INSERT INTO `*PREFIX*preferences` ( `userid`, `appid` , `configkey` ,`configvalue`)’
    . ‘ VALUES( ?, ? , ? ,?)’,
    //array($uid, ‘files’, ‘quota’ , $this->set_quota),
    array($uid, ‘settings’, ’email’ , $uemail)
    ); // Set E-Mail

    return $uid;

    }else{

    $this->storeUser($uid);
    return $uid;
    }
    }else{

    return false;

    }

    } // End checkPassword
    public function user_Exists($uid) {
    $result = OC_DB::executeAudited(
    ‘SELECT COUNT(*) FROM `*PREFIX*users_external`’
    . ‘ WHERE LOWER(`uid`) = LOWER(?)’,
    array($uid)
    );
    return $result->fetchOne() > 0;
    } // user_Exists
    } // End extends \OCA\user_external\Base

    ในที่นี้ขออธิบายเพิ่มเติมสำหรับ adldap.php แค่ 4 ส่วน คือ

    สวนที่ 1. require_once(‘3rdparty/adldap/src/adLDAP.php’);  เส้นทางที่ใช้อ้างอิงไฟล์ ADLdap (PHP Libray) เพื่อนำมาใช้ร่วมกับ owncloud

    สวนที่ 2. $this->set_quota = ‘5 GB’;  กำหนดเนื้อที่เริ่มต้นของของผู้ใช้งาน

    ส่วนที่ 3. $this->addgroup = ‘Staffs’;  ใส่ชื่อกลุ่มที่ต้องการ เพื่อใช้สำหรับเพิ่มกลุ่มให้กับผู้ใช้งานโดยอัตโนมัติ
    เงื่อนไข จะต้องการสร้างชื่อกลุ่มไว้ก่อนจึงจะใช้งานได้ โดยเข้าไปที่เมนู Admin จากนั้นกดเมนูด้านซ้าย Add Group แล้วใส่ชื่อกลุ่มที่ต้องการ จากนั้นกด ปุ่ม +

    ส่วนที่ 4. $this->departid = ‘294’; กำหนดหมายเลขของหน่วยงาน เพื่อใช้กรองว่าต้องการให้หน่วนงานใด สามารถเข้าสู่ระบบมาใช้งานได้ ในที่นี้ 294 คือ หมายเลขของห้องสมุดครับ

    ผลลัพธ์ที่เกิดขึ้น
    เมื่อติดตั้งตามขั้นตอนดังกล่าวครบแล้วนั้น เมื่อมีการเข้าระบบ owncloud ด้วย psu passport หากเป็นบุคลากร ของคณะ/หน่วยงาน ตามหมายเลขกำหนด ก็จะสามารถเข้ามาใช้งานได้และ username นั้น ก็จะถูกเพิ่มเข้าไปในกลุ่มที่กำหนดโดยอัตโนมัติ ครับ

    ขอขอบคุณทุกท่าน ที่อ่านมาถึงตรงนี้ครับ 

    ^_^