Tuesday, November 12, 2013

Access Denied: XXX needs the following permission(s) to perform this action: View collection-level information

Due to some mis-understanding, my windows account in server that hosts TFS 2010 was deleted, thru 'Computer Management->System Tools->Local Users & Groups'. Then I found I got problem to log-in.


After I added my account back, I encountered this error message when connect to VS2010:

Access Denied: 'XXX\gan' needs the following permission(s) to perform this action: View collection-level information.


At the TFS server, I launched the "Team Foundation Server Administration Console", I found my windows account is still there. I know my account is not associated with TFS properly anymore.


From MSDN: Team Foundation Server Permissions, it got something to do with 'Project-Level Permissions':

Permission Name
Name at Command Line
Description
Create test runs
PUBLISH_TEST_RESULTS
Users who have this permission can add and remove test results and add or modify test runs for the team project.
Delete team project
DELETE
Users who have this permission can delete the project for which they have this permission from Team Foundation Server.
Delete test runs
DELETE_TEST_RESULTS
Users who have this permission can delete a scheduled test for this team project.
Edit project-level information
GENERIC_WRITE
Users who have this permission can edit project-level permissions for users and groups on Team Foundation Server.
Manage test configurations
MANAGE_TEST_CONFIGURATIONS
Users who have this permission can create and delete test configurations for this team project.
Manage test environments
MANAGE_TEST_ENVIRONMENTS
Users who have this permission can create and delete test environments for this team project.
View project-level information
GENERIC_READ
Users who have this permission can view project-level group membership and the permissions of those project users.
View test runs
VIEW_TEST_RESULTS
Users who have this permission can view test plans in this node.


Browsing from 'Source Control Explorer', I really got no idea whether is my account still there. Can you tell??



No choice, I have to remove and re-add. After adding my windows account in the "Team Foundation Server Administration Console->Administration Console Users-Add", no error logged but my windows account is not there.


I opened the log, and found there's an error.
[Info   @01:36:05.790] -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
[Info   @01:36:05.790] Running Readiness Checks ...

[Info   @01:36:05.790] -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

[Info   @01:36:05.791] 

[Info   @01:36:05.791] -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

[Info   @01:36:05.791] Activity.Verify

[Info   @01:36:05.792] -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

[Info   @01:36:05.797] Verify: AccountsChecks(VCONTAINER): Starting Verification

[Info   @01:36:05.797] A generic container node that does not contribute to results

[Info   @01:36:05.797] "Verify: AccountsChecks(VCONTAINER): Exiting Verification with state Ignore and result Ignore"

[Info   @01:36:05.798] Verify: AccountsChecks\Verify Accounts Feature(VCHANGEFEATURE): Starting Verification
[Info   @01:36:05.798] Verifies a condition
[Info   @01:36:05.798] "Verify: AccountsChecks\Verify Accounts Feature(VCHANGEFEATURE): Exiting Verification with state Completed and result Success"

[Info   @01:36:05.798] Verify: AccountsChecks\Verify Accounts Account(VACCOUNTVALID): Starting Verification

[Info   @01:36:05.798] Verifies the account is a valid account

[Info   @01:36:05.799] "Verify: AccountsChecks\Verify Accounts Account(VACCOUNTVALID): Exiting Verification with state Completed and result Success"

[Info   @01:36:05.799] Verify: AccountsChecks\SqlLogin(VSQLLOGIN): Starting Verification

[Info   @01:36:05.799] Verifies the given account does not already have a SQL login that is denied access or with the wrong SID

[Info   @01:36:05.801] Verifying SQL login of account XXX\gan does not exist on XXX\SqlExpress, or if it exists, it does not have a different SID and it is not denied access to the server.

[Error  @01:36:05.805] The login for the given account has the wrong SID.

[Error  @01:36:05.805] !Verify Error!: TF255441: An orphaned SQL Server login is associated with the following account: XXX\gan. The login has an incorrect security identifier (SID). The server selected to host the databases for Team Foundation Server is: XXX SqlExpress. You must delete the login from the SQL Server instance on that server.

[Info   @01:36:05.805] "Verify: AccountsChecks\SqlLogin(VSQLLOGIN): Exiting Verification with state Completed and result Error"

[Info   @01:36:05.805] Verify: AccountsChecks\DBExists(VDBEXISTS): Starting Verification

[Info   @01:36:05.805] Sql Database Existance Verification

[Info   @01:36:05.809] "Verify: AccountsChecks\DBExists(VDBEXISTS): Exiting Verification with state Completed and result Success"

[Info   @01:36:05.809] !Verify Result!: 4 Completed, 0 Skipped: 3 Success, 1 Errors, 0 Warning
Looks like TFS only cater an account when it does not exist, but it cannot re-create the account due to security issue.

So, I have to go to MSSQL-Express and remove my windows account.


You can check the DB used in 'Data Tier Summary' at "Team Foundation Server Administration Console" (more info at 'Team Foundation Server Databases'):


After adding my windows account again in the "Team Foundation Server Administration Console->Administration Console Users-Add", everything is ok!
[Info   @01:51:33.399] -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
[Info   @01:51:33.399] Adding account to collection databases ...

[Info   @01:51:33.399] -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

[Info   @01:51:33.400] Getting collections...
[Info   @01:51:33.401] Changing collection: DefaultCollection
[Info   @01:51:33.403] ConnectionString: Data Source=XXX\SqlExpress;Initial Catalog=Tfs_DefaultCollection;Integrated Security=True

[Info   @01:51:33.405] ModifyExecRole: Add:XXX\gan

[Info   @01:51:34.498] result: Success

[Info   @01:51:34.499] Added XXX\gan to Tfs_DefaultCollection (XXX\SqlExpress)

[Info   @01:51:34.499] Changing collection: YYY

[Info   @01:51:34.502] ConnectionString: Data Source=XXX\SqlExpress;Initial Catalog=Tfs_YYY;Integrated Security=True

[Info   @01:51:34.503] ModifyExecRole: Add:XXX\gan

[Info   @01:51:35.381] result: Success

[Info   @01:51:35.381] Added XXX\gan to Tfs_YYY (XXX\SqlExpress)

[Info   @01:51:35.388] ADDCOLLACCOUNT Completed.

[Info   @01:51:35.389] Starting Node: ADDSYSTEMDBACCOUNT

[Info   @01:51:35.389] Add account to system dbs

Done. (You might need to re-map your work-space after this)

Thursday, September 26, 2013

Create failed for User 'XXX' when restoring a database

Recently I was doing the database (let's say: 'restoreTest') migration in MSSQL 2008 from one instance to another.


There's an existing login (let's say: 'restore' in picture below) that I need to bring along with this 'restoreTest' database migration.


1) When I went to create a new login and assign the access-right of the newly-created 'restore' account to the new 'restoreTest' database restored in the new instance,


2) I encounter this error message:

Create failed for User 'XXX'
User, group, or role 'XXX' already exists in the current database. (Microsoft SQL Server, Error: 15023)


3) The new 'restore' user is created but it's not associated with  'restoreTest' database.
I was so curious what went wrong. After a check, I  found the user 'restore' was backed-up along with the database.


4) After I deleted the user 'restore' that comes in the newly-restored 'restoreTest' database & , and re-assign it, then everything is OK.

Monday, August 26, 2013

Web-based Javascript Interactive map makes easy with SVG

Recently I was thinking what else can i come up with, after seeing the successful story of the Waze, by using the world map. With the advancement of the html5, it can be done easily with canvas and SVG.

Of course, there's a lot of ready-made powerful tile-based detailed map API, eg.
1) Google Maps JavaScript API v3.
2) jquerygeo
3) leafletjs

However, the subtleness of them is beyond comparable with svg path-based map, with the aid of GPS and millions of dollars pumps by them.

Few of the prominent sites in Javascript svg path-based interactive map are:
1) jvectormap
2) amMap

So sad that my country of origin, Malaysia, is too small to be included by them. "Hey, why don't I create my own Interactive map", struck my mind.

Let's get started.
1) I need a map of my country. With the help of Google Image Search, there's plenty of them. Any image format will do, the important thing is the plain background. Beware of the infringement of the propriety though.

2) Once you find the proper image, you can using all sorts of image converting software to turn it into a .svg file. One option is Adobe Illustrator, if you are a designer. If you are just some independent programmer, online tool like Vector Magic, might be a good friend of you. Choose high-resolution when you do conversion.



3) Verify the newly generated .svg file if all the details are there. If not, you can use SVG-edit to add/remove the detail accordingly. Click on the particular svg-path and give a meaningful id to it. Click  twice will allow you to twist the svg-path, that something similar to spline.


4) Embed the svg element in the html page. This can be done easily.
*You might need to set/clip the width and height on the svg element, due to browser compatibility. See more here.

5) However, there's no element of interactive there. So we're gonna add it now. What we need are:

  • display the name of the region when mouse hovers
  • able to zoom
  • able to pan

6) To display the name of the region when mouse hovers, we use jquery and can be achieved easily through following code:

$(function() {
  $(".icon svg path").bind("mouseover", function( evt ) {
    var currentMap = evt.target.getAttributeNS(null,'id');
    if(currentMap != "background" 
        && currentMap != "KualaLumpur_border" 
        && currentMap != "Putrajaya_border"){
   //$(this).css('fill','blue');                            
   //$("#" + currentMap).removeAttr("fill");
   $("#" + currentMap).css('fill','blue');
   //evt.target from viewbox; text.text1 from diff cooor; need to offset
   // more complicated at: http://msdn.microsoft.com/en-us/library/ie/hh535760(v=vs.85).aspx
   var targetedPath = evt.target.getBBox();
   $(".icon svg text.text1").css('display','block')
                            .text(currentMap)
                            .attr( "x", targetedPath.x + targetedPath.width / 2)
                            .attr( "y", targetedPath.y + targetedPath.height / 2);
    }
  })
  .bind("mouseleave", function( evt ) {
    //IE9 & FF not supported
    //var currentMap = evt.target.getAttributeNS(null,'id');
    //console.log("mouseleave:" + currentMap);
  })
  .bind("mouseout", function( evt ) {
    var currentMap = evt.target.getAttributeNS(null,'id');
    if(currentMap != "background" &&
        currentMap != "KualaLumpur_border" &&
        currentMap != "Putrajaya_border"){
      //$(this).css('fill','c9c9c9');//IE9 & FF not supported
      //$("#" + currentMap).css('fill','c9c9c9');//IE9 & FF not supported
      var theCurrentMap = document.getElementById(currentMap);
      theCurrentMap.setAttribute('style', '');
      $(".icon svg text").css('display','none');
    }
  });
 });

*FireFox & IE9 does not fire 'mouseleave' event in the event bubling, so have to bind at 'mouseout' event. See more here.

7) What it does is loop through all the svg-paths, and bind the mouseover & mouseleave events. When mouse hovers, change the current element's css, then display the id of the region using a svg text on cursor's tip, with the help of  the current box selection, getBBox(), . What mouse leaves does is restoring whatever you've done on mouseover event. You might need to append this svg text programmatically though.


8) To enable the zooming capability, we use the following codes when DOM body's loads:
// Must be greater than 1. Increase this value for faster zooming (i.e., less granularity).
var zoomRate         = 1.1; 

function zoom(zoomType)
    {
  var theSvgElement = document.getElementById('svgElement');
  var viewBox = theSvgElement.getAttribute('viewBox'); // Grab the object representing the SVG element's viewBox attribute.
  var viewBoxValues = viewBox.split(' ');    // Create an array and insert each individual view box attribute value (assume they're seperated by a single whitespace character).

  viewBoxValues[2] = parseFloat(viewBoxValues[2]);  // Convert string "numeric" values to actual numeric values.
  viewBoxValues[3] = parseFloat(viewBoxValues[3]);
      
  if (zoomType == 'zoomIn')
  {
   viewBoxValues[2] /= zoomRate; // Decrease the width and height attributes of the viewBox attribute to zoom in.
   viewBoxValues[3] /= zoomRate; 
  }
  else if (zoomType == 'zoomOut')
  {
   viewBoxValues[2] *= zoomRate; // Increase the width and height attributes of the viewBox attribute to zoom out.
   viewBoxValues[3] *= zoomRate; 
  }
  else
   alert("function zoom(zoomType) given invalid zoomType parameter.");
      
  // Convert the viewBoxValues array into a string with a white space character between the given values.
  theSvgElement.setAttribute('viewBox', viewBoxValues.join(' '));
    }
        
    function zoomViaMouseWheel(mouseWheelEvent)
    {      
      //check for detail first so Opera uses that instead of wheelDelta 
      var delta=mouseWheelEvent.detail? mouseWheelEvent.detail*(-120) : mouseWheelEvent.wheelDelta 
      //if (mouseWheelEvent.wheelDelta > 0)
        zoom('zoomIn');
      else
        zoom('zoomOut');
        
      /* When the mouse is over the webpage, don't let the mouse wheel scroll the entire webpage: */
      mouseWheelEvent.cancelBubble = true; 
      return false;       
    }

 function initialize()
    {        
      /* Add event listeners: */
      // Don't let the mousewheel event bubble up to stop native browser window scrolling.
      //window.addEventListener('mousewheel', zoomViaMouseWheel, false);
      
      //FF doesn't recognize mousewheel as of FF3.x
      var mousewheelevt=(/Firefox/i.test(navigator.userAgent))? "DOMMouseScroll" : "mousewheel" 
 
      if (document.attachEvent) //if IE (and Opera depending on user setting)
        document.attachEvent("on"+mousewheelevt, zoomViaMouseWheel)
      else if (document.addEventListener) //WC3 browsers
        document.addEventListener(mousewheelevt, zoomViaMouseWheel, false)
    }

9) What it does is hooking-up the mousewheel event, and change the svg viewbox properties.
*Due to FireFox & Safari handles mousewheel event differently, we have to cater as well. See more here.

10) To enable panning, it needs a little more treatment.

// Number of pixels to pan per key press.  
var panRate          = 10;   

 var isDragging = false;
 var mouseCoords = { x: 0, y: 0 };

function hookEvent(element, eventName, callback)
    {
        if(typeof(element) == "string")
           element = document.getElementById(element);
        if(element == null)
           return;
        if(eventName == 'mousewheel')
        {
           element.addEventListener('DOMMouseScroll', callback, false); 
        }
        else
         {
           element.addEventListener(eventName, callback, false);
        }
    }
  
 function onMouseDown(e)
    {
        isDragging = true;
    }
        
    function onMouseUp(e)
    {
        isDragging = false;
    }
        
    function onMouseOver(e)
    {
        mouseCoords = {x: e.clientX, y: e.clientY};
    }
  
  function onMouseMove(e)
    {
        if(isDragging == true)
        {
           var xd = (e.clientX - mouseCoords.x);
           var yd = (e.clientY - mouseCoords.y);
           pan(xd, yd)
        }
            
        mouseCoords = {x: e.clientX, y: e.clientY};
            
        return cancelEvent(e);
   }
  
  function pan(x, y)
  {
   var theSvgElement = document.getElementById('svgElement');
   var viewBox = theSvgElement.getAttribute('viewBox'); // Grab the object representing the SVG element's viewBox attribute.
   var viewBoxValues = viewBox.split(' ');    // Create an array and insert each individual view box attribute value (assume they're seperated by a single whitespace character).

   viewBoxValues[0] = parseFloat(viewBoxValues[0]);  // Convert string "numeric" values to actual numeric values.
   viewBoxValues[1] = parseFloat(viewBoxValues[1]);
    
   viewBoxValues[0] -= x;//panRate; // Increase the x-coordinate value of the viewBox attribute to pan right.
   viewBoxValues[1] -= y;//panRate; // Increase the y-coordinate value of the viewBox attribute to pan down.
    
   theSvgElement.setAttribute('viewBox', viewBoxValues.join(' ')); // Convert the viewBoxValues array into a string with a white space character between the given values.
  }

 
  function cancelEvent(e)
     {
        e = e ? e : window.event;
        if(e.stopPropagation)
           e.stopPropagation();
        if(e.preventDefault)
           e.preventDefault();
        e.cancelBubble = true;
        e.cancel = true;
        e.returnValue = false;
        return false;
     }

function initialize()
    {        
        hookEvent('svgElement', 'mousedown', onMouseDown);
        hookEvent('svgElement', 'mouseup', onMouseUp);
        hookEvent('svgElement', 'mousemove', onMouseMove);
    }

11) What it does is quite similar to mousewheel event. The different is now it uses global variable to check the mouse-down and the distance of mouse move, and eventually apply that to svg viewbox properties.

12) That's it. Now a javascript  interactive map is created. Full codes here.


Thursday, July 11, 2013

Create, Load & Export Module in Node.js

Nowadays, javascript development is getting broader in web development.
Either on front-end (JQueryKnockoutJSTypeScript, etc) or back-end (Node.js, ect), more libraries are created with more compact, more light-weight.

Today, i'll show the basic setup of Node.js.
From wikipedia, Node.js is a server-side software system designed for writing scalable Internet applications, notably web servers. It's akin to IIS in Windows, or Apache Tomcat in world of Java.

1) Download and install from official website.

2) Upon successful of installation, you should able to see several shortcut created in the "Start" menu.


3) Launch the "Node.js" . This is the place where you can issue all the Node.js related commands.

4) To find out the current execution path, we can execute this "Path" command:

process.env.PATH.split(path.delimiter)

5) Two additional environment PATHs are created:


6) For "Loading a File Module", create a "circle.js" (see code below) from official user manual, and place in Node.js environment PATH:

var PI = Math.PI;

exports.area = function (r) {
  return PI * r * r;
};

exports.circumference = function (r) {
  return 2 * PI * r;
};


7) Execute this in "Node.js":

var circle = require('./circle.js');circle.area(4);

8) and you should get your result:


9)  For "Loading a Folder Module" or so called user-defined library, it needs few more steps. Create a folder called "a" in Node.js environment PATH.


10) Create a file called "package.json" (see code below):

{
 "name" : "some-library",
 "main" : "./lib/b.js"
}

11) Create a folder called "lib" in folder "a" and another file called "b.js" (this is the file mentioned above in "package.json"; see code below) in folder "lib":

console.log('module b initializing...');

var PI = Math.PI;

exports.area = function (r) {
  return PI * r * r;
};

exports.circumference = function (r) {
  return 2 * PI * r;
};

console.log('b initialized.');

12) Execute the following command:

var circle = require('./a');circle.area(4);


13) That's it!

Wednesday, June 5, 2013

Dynamic columns in listview

Creating a GridView with dynamic columns generated is easy, it even comes without much effort by simply configuring the AutoGenerateColumns Property. Some even can try to use Meta-Data.

However, creating a ListView with dynamic columns needs a bit of trick. Here's the ways:

1) First, we create a  Employee-vs-Vendor table, using the Common Table Expressions(CTE) and PIVOT. Left-join it we get the result in tabular form.


2) On the listview layout template, we have to declare a placeholder:


    

Employee vs Vendor



the CSS style used is to break the words if you specify too many columns.

3) Then in the ItemTemplate, we no need to add extra dynamic columns we want, since it's going to be done programmatically.


4) Depends on the level of the query, you have to create a nested ItemTemplate accordingly. I believe this can be done thru' AJAX. However, I leave this to the future.

5) Then when we start binds the query to the listview, we bind it level-by-level:

DataTable dt = new DataTable();
dt = ((DataTable)ViewState["cachedTable"]).Clone();

if (((DataTable)ViewState["cachedTable"]).Rows.Count > 0)
{
    DataRow[] drResults = ((DataTable)ViewState["cachedTable"]).Select("GENERATION = 0");

    foreach (DataRow dr in drResults)
    {
        object[] row = dr.ItemArray;
        dt.Rows.Add(row);
    }
}

lsvHighLvlFormAccess.DataSource = dt;
lsvHighLvlFormAccess.DataBind();

6) When highest level binding is done, we can populate the header caption. This is the part when you set the column-span & generate the column header dynamically.

protected void lsvHighLvlFormAccess_DataBound(object sender, EventArgs e)
{
    PlaceHolder phDynamicHdr = (PlaceHolder)lsvHighLvlFormAccess.FindControl("phDynamicHdr");
    // check phDynamicHdr.Controls.Count; could be called twice when postback
    if (phDynamicHdr != null /*&& phDynamicHdr.Controls.Count == 0*/)
    {
        Literal ltrl = new Literal();

        DataTable dt = ((DataTable)ViewState["cachedTable"]);
        if (dt != null && dt.Rows.Count > 0)
        {
            foreach (DataColumn dc in dt.Rows[0].Table.Columns)
            {
                if (!(dc.ColumnName.Equals("GENERATION") ||
                        dc.ColumnName.Equals("hierarchy") ||
                        dc.ColumnName.Equals("rowNo") ||
                        dc.ColumnName.Equals("EmployeeID")))
                {
                    if (dc.ColumnName.Equals("LoginID"))
                    {
                        ltrl.Text += "" + dc.ColumnName + "

";
                    }
                    else
                        ltrl.Text += "" + dc.ColumnName + "

";
                }
            }
        }

        if (phDynamicHdr.Controls.Count > 0)
        {
            // if current dynamic columns is different with exisiting dynamic columns 
            if (!((Literal)phDynamicHdr.Controls[0]).Text.Equals(ltrl.Text))
            {
                // remove the whole previous dynamic columns
                phDynamicHdr.Controls.Remove(phDynamicHdr.Controls[0]);
                // replace with new dynamic columns
                phDynamicHdr.Controls.Add(ltrl);
            }
        }
        else
            phDynamicHdr.Controls.Add(ltrl);
    }

    HtmlTableCell td = (HtmlTableCell)lsvHighLvlFormAccess.FindControl("imgCollapseExpand");
    if (td != null)
    {
        DataTable dt = ((DataTable)ViewState["cachedTable"]);
        if (dt != null && dt.Rows.Count > 0)
        {
            td.ColSpan = dt.Rows[0].Table.Columns.Count + I_COLSPAN - 1;//I_COLSPAN - 1 : to put the 'minus.png'
        }
    }
}

7) On each high-level row is bound, we need to fire the event to populate its descendant. Here, checkboxes are used. You can replace with any other control too.

protected void lsvHighLvlFormAccess_ItemDataBound(object sender, ListViewItemEventArgs e)
{
    HtmlTableRow row = (HtmlTableRow)e.Item.FindControl("row");
    // http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.listviewdataitem.dataitem.aspx
    // http://forums.asp.net/t/1334142.aspx/1
    ListViewDataItem item = (ListViewDataItem)e.Item;
    System.Data.DataRowView drv = (System.Data.DataRowView)item.DataItem;

    dynamicPopulateRow(row, drv, 0);
 // more contents here
}

where dynamicPopulateRow() is:

private void dynamicPopulateRow(HtmlTableRow row, System.Data.DataRowView drv, int iGeneration)
{
    if (row != null)
    {
        // http://www.pcreview.co.uk/forums/do-enumerate-all-columns-dataviewrow-t1244448.html
        foreach (DataColumn dc in drv.Row.Table.Columns)
        {
            string sEmployeeID = drv["LoginID"].ToString();

            if (dc.ColumnName.Equals("LoginID"))
            {
                // http://msdn.microsoft.com/en-US/library/e5daxzcy(v=vs.80).aspx
                // Define a new HtmlTableCell control.
                HtmlTableCell cell = new HtmlTableCell("td");

                // Create the text for the cell.
                cell.Controls.Add(new LiteralControl(Convert.ToString(drv[dc.ColumnName])));
                cell.ColSpan = dc.ColumnName.Equals("LoginID") ? I_COLSPAN - iGeneration : 1;

                // Add the cell to the HtmlTableRow Cells collection. 
                row.Cells.Add(cell);
            }
            else if (!(dc.ColumnName.Equals("GENERATION") ||
                        dc.ColumnName.Equals("hierarchy") ||
                        dc.ColumnName.Equals("rowNo") ||
                        dc.ColumnName.Equals("EmployeeID")))
            {
                // http://msdn.microsoft.com/en-US/library/e5daxzcy(v=vs.80).aspx
                // Define a new HtmlTableCell control.
                HtmlTableCell cell = new HtmlTableCell("td");

                bool bIsNull = drv[dc.ColumnName] is System.DBNull;

                Literal ltrl = new Literal();
                ltrl.Text += " 0 ? " checked>" : ">");

                cell.Controls.Add(ltrl);
                // Add the cell to the HtmlTableRow Cells collection. 
                row.Cells.Add(cell);
            }
            else
            {
                //other rows
            }
        }
    }
}

8) If you more nested level, just add these codes:

var lst1stLevel = (ListView)e.Item.FindControl("lst1stLevel");
populateLV(lst1stLevel, 1, (string)drv["hierarchy"], Convert.ToInt32(drv["rowNo"]));

where populateLV() is:

private void populateLV(ListView lv, int iNextGeneration, string sHierarchy, int iCurrRowNo)
{
    if (lv != null)
    {
        DataTable dt = new DataTable();
        dt = ((DataTable)ViewState["cachedTable"]).Clone();// clone schema only

        List levels = ((DataTable)ViewState["cachedTable"]).
                            Select("GENERATION = " + (iNextGeneration > 0 ? iNextGeneration - 1 : 0) +
                            " AND hierarchy LIKE '" + sHierarchy + "%'"). // no space
                            AsEnumerable().
                            Select(al => Convert.ToInt32(al.Field("rowNo"))).Distinct().ToList();

        // duplicate hierarchy, display at smallest rowNo will do
        if (levels.Count > 0 && levels.Min() == iCurrRowNo)
        {
            DataRow[] drResults = ((DataTable)ViewState["cachedTable"]).
                                Select("GENERATION = " + iNextGeneration + " AND hierarchy LIKE '" + sHierarchy + " %'");

            foreach (DataRow dr in drResults)
            {
                object[] obRow = dr.ItemArray;
                dt.Rows.Add(obRow);
            }
            lv.DataSource = dt;
            lv.DataBind();
        }
    }
}

9) Repeat this for second-level, third-level, etc.

protected void lst2ndLevel_ItemDataBound(object sender, ListViewItemEventArgs e)
{
    HtmlTableRow row = (HtmlTableRow)e.Item.FindControl("row");
    ListViewDataItem item = (ListViewDataItem)e.Item;
    System.Data.DataRowView drv = (System.Data.DataRowView)item.DataItem;
    int iCurrLvl = 2;
    //populate dynamic cells
    dynamicPopulateRow(row, drv, iCurrLvl);

    //populate nested LV
    var lst3rdLevel = (ListView)e.Item.FindControl("lst3rdLevel");
    populateLV(lst3rdLevel, iCurrLvl + 1, (string)drv["hierarchy"], Convert.ToInt32(drv["rowNo"]));
}

10) Done. This is what it looks like in Visual Studio Designer. Press F5 to go.



11) if you choose 4 columns, you get this:


12) If you choose 10 columns, you get this:


13) Lastly, a whopping 55 columns.



You can get the source-code here (DynamicColumns_LV.zip).
You can download the AdventureWorks database through Microsoft Download Center.

Thursday, April 11, 2013

JavaScript search() != indexOf() Method

Quite frequently we need to find a specific word from a large chunk of string. With more focus on web development and the advancement of client-side scripting capability, more efforts have to focus on javascript nowadays.

I came across with this and bump into these 2 methods, both methods look identical (almost) to me to achieve what I was looking for.
  • search()
  • indexOf()
I thought they can achieve the same result, but eventually they aren't.

First, let's use the search() to perform some testing.
Test 1: Using the w3schools's Tryit Editor with default function to search "W3Schools":

function myFunction()
{
var str="Visit W3Schools!"; 
var n=str.search("W3Schools");
document.getElementById("demo").innerHTML=n;
}

You'll get result:
6 (which is correct)

Test 2: then use another method indexOf() on the same function to search same string [MUST click "Submit Code" after change in "Tryit Editor"]:

function myFunction()
{
var str="Visit W3Schools!"; 
var n=str.indexOf("W3Schools");
document.getElementById("demo").innerHTML=n;
}

You'll get result:
6 (which is correct too)

Nothing fancy, nothing wrong.
OK, what about change the string to search from "W3Schools" to "W3|Schools" (adding a "|" character, since it's frequently used as a delimiter)

Test 3: if using search() method [MUST click "Submit Code" after change in "Tryit Editor"]:

function myFunction()
{
var str="Visit W3Schools!"; 
var n=str.search("W3|Schools");
document.getElementById("demo").innerHTML=n;
}

You'll get result:
6 (which is wrong)

Test 4: if using indexOf() method [MUST click "Submit Code" after change in "Tryit Editor"]:

function myFunction()
{
var str="Visit W3Schools!"; 
var n=str.indexOf("W3|Schools");
document.getElementById("demo").innerHTML=n;
}

You'll get result:
-1 (which is still correct)

From Test 3 & Test 4, you get different result if the string your search contains special character "|". Looking back, I was overlooked by its definition (see fonts in read).

search() Method:
The search() method searches a string for a specified value, or regular expression, and returns the position of the match.
This method returns -1 if no match is found.

indexOf() Method:
The indexOf() method returns the position of the first occurrence of a specified value in a string.
This method returns -1 if the value to search for never occurs.

Looks like special character "|" has caused some effect on the regular expression and need special treatment on it. Lesson learnt.

Friday, March 8, 2013

"The process cannot access the file because it is being used by another process"

There'll be time when you need to enabling SSL on IIS 7.0 Using Self-Signed Certificates.
RobBagbyscottgu has 2 great tutorials on that topic. Looks like IIS7 simplify a lots.
I go to IIS and set the binding using the default port 443.


But life is not always a bed of roses.Then I hit this error message:
"There was an error while performing this operation."
"The process cannot access the file because it is being used by another process. (Exception from HRESULT: 0x80070020)"


I was wonder what caused it to happen  After confirmed with network guy it's not their issue, i have to check what could be the possible reason for that.
From Microsoft Support, and also someone from stack-overflow pointed out this could be port is being used by other application.

Then I work it out from there:
1) run the "netstat" command from command prompt, to display active TCP connections, ports on which the computer is listening


2) then run the "Tasks Manager". The PID column is not displyed by default. I have to go to "View">"Select Column" and check it.


3) Compare the PID.

4) there's another process, called "nhttp.exe" is using that port. It's from Lotus Domino. :(


5) no choice but have to use another port 444. Back to the IIS site binding, and edit it.

6) of course, have to allow the firewall to let that port through by adding a new inbound rule.


7) Finally, I can browse my site.



Monday, March 4, 2013

ListView Grouping On Demand using AJAX and stored-procedure

Matt Berseth has a series of tutorials on building a Grouping Grid with the ASP.NET 3.5 LinqDataSource and ListView Controls. It's quite a good control that able to display the data in master-detail format.

While using LinQ can help you to encapsulate the database layer, it might incur additional workload. And not every coder can write good LinQ query, thus it turns out to be an ugly resource-hunger monster. Personally, i found it's not easy to fine-tune when u need to reduce the page loading time after the data grows huge, if compare with ADO .NET using stored-procedure. At least, you can analyse the execution plan.

You can reduce the page size through paging, but in some scenario, your client might prefer loading all the data in one time (a bit ridiculous though). Then AJAX comes into the picture.


Muhammad Mosa has a series of tutorials on Building a grouping Grid with GridView and ASP.NET AJAX toolkit CollapsiblePanel


By combining the two, we can create Grouping ListView using AJAX and stored procedure.
1) we create the stored procedure to create the grouping data, with the help of GROUPING SETS.

Select a.*, b.OrderDate, b.ShipName, 
c.FirstName + ' ' + c.LastName as fullName
from
(
 SELECT CASE WHEN (GROUPING(CustomerID) = 1) THEN NULL--'...'--'ALL CustomerID'
    ELSE ISNULL(CustomerID, 'UNKNOWN')
     END AS CustomerID,
     CASE WHEN (GROUPING(EmployeeID) = 1) THEN NULL--'...'--'ALL EmployeeID'
    ELSE ISNULL(EmployeeID, 'UNKNOWN')
     END AS EmployeeID,
     CASE WHEN (GROUPING(OrderID) = 1) THEN NULL--'ALL OrderID'
    ELSE ISNULL(OrderID, 'UNKNOWN')
     END AS OrderID,
     COUNT(*) as [itemCount],
     SUM(freight) AS totalFreight
 FROM #temp l
 GROUP BY GROUPING SETS
 (
  (CustomerID, EmployeeID, OrderID),
  (CustomerID, EmployeeID),
  (CustomerID),
  ()
 )
) a

2) Then we need a extended AJAX toolkit CollapsiblePanel from Muhammad Mosa's article.

3) Embed the newly extended CollapsiblePanel in the ItemTemplate of the host listview.

4) Next we need to invoke the nested listview through WebMethod.

[System.Web.Services.WebMethod()]
public static string GetEmployees(string sParams)
{
    Page page = new Page();
    // restart Casini build-in IIS if error 
    COM.UsrCtrl_nestedListView ctl = (COM.UsrCtrl_nestedListView)page.LoadControl(PAGE_EMPLOYEE);
    page.Controls.Add(ctl);
    ctl.Params = sParams;
    System.IO.StringWriter writer = new System.IO.StringWriter();
    HttpContext.Current.Server.Execute(page, writer, false);
    string output = writer.ToString();
    writer.Close();
    return output;
}

5) Eventually we have our final result.

Few things to take note:
i) ToolkitScriptManager has to enable the PageMethods



ii) Since host listView and nested listview are 2 separate html tables, we have to enforce the CSS table-layout Property.



You can get the source-code here (ListViewGrouping_OnDemand.zip).
You can download the Northwind database through Microsoft Download Center.
If you want to customize the CSS of the listView,  this article is a great place to start with.

multi-select dropdownlist in ASP.NET

(this one is heavily followed the article in dotnetfunda; credits go to the developer)

While doing the coding, at times you will need your user to select few options.
Using check-box or radio button is a good option, but they consume quite a space.
Reporting service comes with the multi-select dropdown-list control,



but you could not find the similar one in standard ASP .NET controls.
While searching for the multi-select dropdown list in ASP .NET, I bumped into this article. It's almost similar to what you might expected.

By adding extra ASP checkbox (let's call it: ID="checkAll") on top of CheckBoxList:

   
       

and some javascript:

    

    // http://stackoverflow.com/questions/5821993/checkboxlist-items-using-javascript
    function CheckAll() {
        var intIndex = 0;
        var rowCount = document.getElementById('<%=cblItems.ClientID %>').getElementsByTagName("input").length;
        for (intIndex = 0; intIndex < rowCount; intIndex++) {
            if (document.getElementById('<%=checkAll.ClientID %>').checked == true) {
                if (document.getElementById('<%=cblItems.ClientID %>' + "_" + intIndex)) {
                    if (document.getElementById('<%=cblItems.ClientID %>' + "_" + intIndex).disabled != true)
                        document.getElementById('<%=cblItems.ClientID %>' + "_" + intIndex).checked = true;
                }
            }
            else {
                if (document.getElementById('<%=cblItems.ClientID %>' + "_" + intIndex)) {
                    if (document.getElementById('<%=cblItems.ClientID %>' + "_" + intIndex).disabled != true)
                        document.getElementById('<%=cblItems.ClientID %>' + "_" + intIndex).checked = false;
                }
            }
        }
    }

    function ClearAll() {
        var intIndex = 0;
        var flag = 0;
        var rowCount = document.getElementById('<%=cblItems.ClientID %>').getElementsByTagName("input").length;
        for (intIndex = 0; intIndex < rowCount; intIndex++) {
            if (document.getElementById('<%=cblItems.ClientID %>' + "_" + intIndex)) {
                if (document.getElementById('<%=cblItems.ClientID %>' + "_" + intIndex).checked == true) {
                    flag = 1;
                }
                else {
                    flag = 0;
                    break;
                }
            }
        }
        if (flag == 0)
            document.getElementById('<%=checkAll.ClientID %>').checked = false;
        else
            document.getElementById('<%=checkAll.ClientID %>').checked = true;

    }

A re-usable user-control is at your disposal:



You can get the full source-code.

And you have more options:
1) dropdown-check-list
2) jQuery UI MultiSelect Widget