Philip Hendry's Blog

April 22, 2013

A Facebook AngularJS Directive

Filed under: AngularJS — philiphendry @ 8:39 am

I’ve been developing a dashboard for the application I’m working on and one of the things it needed to include were Social Media controls such as Twitter, LinkedIN, Google +1 and Facebook. Integrating them into ASP.NET WebForms and MVC was mostly a painless experience but that was because of the predominant PostBack mechanism. I’ve recently chosen to look at AngularJS since it’s two-way binding is spectacular and looked instantly better than KnockoutJS which seemed to require to much boilerplate code.

Most of the problems I experienced when initially trying to integrate the Facebook code revolved around AngularJS binding variables not resolving and javascript code not executing at the right time. The steps below highlight how I resolved each of these issues.

First up was initialising the Facebook SDK. I’ve placed the code below at the top of my aspx page that delivers my dashboard. The Facebook application ID is currently being fetched by .Net code at line 6. The Facebook documentation describes how to set this up as an async process and therefore does not disrupt the loading of the rest of the page.

   1: <%-- Facebook Javascript SDK --%>

   2: <div id="fb-root"></div>

   3: <script>

   4:     window.fbAsyncInit = function() {

   5:         FB.init({

   6:             appId: '<%=ConfigurationHelper.GetMandatorySetting<String>("facebookApplicationId")%>',

   7:             status: true, // check login status

   8:             cookie: true, // enable cookies to allow the server to access the session

   9:             xfbml: true,  // parse XFBML

  10:             oauth: true

  11:         });

  12:     };

  13:     (function (d, s, id) {

  14:         var js, fjs = d.getElementsByTagName(s)[0];

  15:         if (d.getElementById(id)) return;

  16:         js = d.createElement(s); js.id = id;

  17:         js.src = "//connect.facebook.net/en_GB/all.js";

  18:         fjs.parentNode.insertBefore(js, fjs);

  19:     }(document, 'script', 'facebook-jssdk'));

  20: </script>

The next step is to define the AngularJS directive. There are three things to note :

  1. The template includes the binding {{urlToLike}} on line 5 which is resolved during the linking phase and must appear on the scope of the containing controller.
  2. The template would never turn into a Facebook plugin unless we tell it to change and therefore on line 13 I call the Facebook SDK to parse the element just added. Thanks to echong for that hint. The check for the existence of FB is required because sometimes the SDK hasn’t been configured yet and if that’s the case the SDK will render the like button itself.
  3. The final trick on line 11 was to make the call to parse inside a $timeout() – this adds it to the javascript queue and is processed when everything else has finished. This particular change solved problems for me when I refreshed the entire page by pressing F5 – the refresh caused the SDK to be loaded after the directive and therefore the $timeout() sorts the ordering out nicely. I’ve used this trick before to render Highcharts after the rest of the AngularJS digest loop has completed.

 

   1: .directive('facebookLike', ['$timeout', function ($timeout) {

   2:     return {

   3:         template:

   4:             '<div class="fb-like" ' +

   5:                 'data-href="{{marketingSiteUrl}}" ' +

   6:                 'data-send="false" ' +

   7:                 'data-layout="button_count" ' +

   8:                 'data-width="450" ' +

   9:                 'data-show-faces="false"></div>',

  10:         link: function (scope, element, attributes) {

  11:             $timeout(function () {

  12:                 return typeof FB !== "undefined" && FB !== null

  13:                     ? FB.XFBML.parse(element.parent()[0])

  14:                     : void 0; });

  15:         }

  16:     };

  17: }])

This same technique above can be used for LinkedIN, Twitter and Google +1 but the parse functions are slighty different (IN.init(), twttr.widgets.load() and gapi.plusone.go() respectively.)

December 23, 2011

Create a contents page with page numbers from html input using Websupergoo Abcpdf

Filed under: ASP.NET — philiphendry @ 9:25 am

I’ve created a report pdf from an html page output from our ASP.NET based product but I needed to change a contents list that was rendered as a hyperlink list on the page into a list of section headings and page number since the pdf was primarily for printing. The solution wasn’t immediately obvious but I’ve come up with the following which I was running in a unit test for simple quick prototyping. The key is using the HtmlOptions.AddTags property in ABCpdf which allows areas of the HTML to be retrieve during pdf rendering and modified.

 

   1: using System.Diagnostics;

   2: using System.IO;

   3: using System.Collections.Generic;

   4: using System.Linq;

   5: using Microsoft.VisualStudio.TestTools.UnitTesting;

   6: using WebSupergoo.ABCpdf8;

   7:  

   8: namespace TestPdfFormFields

   9: {

  10:    [TestClass]

  11:    public class PdfPrototyping

  12:    {

  13:       struct Tag

  14:       {

  15:          public string name;

  16:          public string rectString;

  17:          public int pagenumber;

  18:       }

  19:  

  20:          

  21:       [TestMethod]

  22:       public void testCreatingAPdfWithContentsPageFromHtml()

  23:       {

  24:          // Create Websupergoo ABCpdf document and set up the page size

  25:          var theDoc = new Doc();

  26:          theDoc.Rect.Inset(100, 100);

  27:          theDoc.Rect.Top = 700;

  28:  

  29:          // The following HtmlOption instructs ABCpdf to interpret the style tags 'abcpdf-tag-visible' and

  30:          // create a array of the id's take from the html and the rectangles representing the size of the 

  31:          // html element. 

  32:          theDoc.HtmlOptions.AddTags = true;

  33:  

  34:          // The basis of this solution therefore is to create placeholders where page numbers will be rendered

  35:          // in the contents page and back-fill them once we know where the sections/chapters have been rendered

  36:          // since it won't be known until ABCpdf has chained pages together.

  37:          var theID = theDoc.AddImageHtml(

  38:                   @"

  39:                      <h1>Contents</h1>

  40:                      <ul>

  41:                         <li><span id='contents1' style='abcpdf-tag-visible: true; width: 20px;'></span>.......First paragraph</li>

  42:                         <li><span id='contents2' style='abcpdf-tag-visible: true; width: 20px;'></span>.......Second paragraph</li>

  43:                         <li><span id='contents3' style='abcpdf-tag-visible: true; width: 20px;'></span>.......Third paragraph</li>

  44:                      </ul>

  45:                      <h1 id='heading1' style='abcpdf-tag-visible: true; page-break-before:always;'>Section One</h1>

  46:                      <h1 id='heading2' style='abcpdf-tag-visible: true; page-break-before:always;'>Section Two</h1>

  47:                      <h1 id='heading3' style='abcpdf-tag-visible: true; page-break-before:always;'>Section Three</h1>

  48:                   ");

  49:  

  50:          var tagCache = new List<Tag>();

  51:          var pagenumber = 1;

  52:          while (true)

  53:          {

  54:             // Fetch all the tags and rectangles and add them to a tagCache for the current theID. Chaining

  55:             // creates a new theID which will contain more tags to add

  56:             var tags = theDoc.HtmlOptions.GetTagIDs(theID);

  57:             var tagRects = theDoc.HtmlOptions.GetTagRects(theID);

  58:             tagCache.AddRange(tags.Select((t, i) => new Tag {name = t, pagenumber = pagenumber, rectString = tagRects[i].String}));

  59:  

  60:             if (!theDoc.Chainable(theID))

  61:                break;

  62:  

  63:             theDoc.Page = theDoc.AddPage();

  64:             theID = theDoc.AddImageToChain(theID);

  65:             pagenumber++;

  66:          }

  67:  

  68:          // Now we have a cache of all contents and heading tags we can iterate through the

  69:          // contents tags, find the smallest page number of the corresponding section then

  70:          // render the page number into the contents.

  71:          theDoc.HPos = 1.0;   // Right justify

  72:          theDoc.VPos = 0.5;   // Centre vertically

  73:          theDoc.FontSize = 8;

  74:          foreach (var tag in tagCache.Where(t => t.name.StartsWith("contents")))

  75:          {

  76:             var paragraphName = "heading" + tag.name.Substring("contents".Length);

  77:             var paragraphPageNumber = tagCache.Where(t => t.name == paragraphName).Select(t => t.pagenumber).Min();

  78:  

  79:             theDoc.PageNumber = tag.pagenumber;

  80:             theDoc.Rect.String = tag.rectString;

  81:             theDoc.AddText(paragraphPageNumber.ToString());

  82:          }

  83:  

  84:          // Now iterate through all the pages, add page numbers and flatten the layers.

  85:          theDoc.Rect.String = "100 70 500 150";

  86:          var pageCount = theDoc.PageCount;

  87:          for (var pageNumber = 1; pageNumber <= pageCount; pageNumber++)

  88:          {

  89:             theDoc.PageNumber = pageNumber;

  90:             theDoc.AddText("Page " + pageNumber + " of " + pageCount);

  91:             theDoc.Flatten();

  92:          }

  93:  

  94:          const string testFilename = @"c:\temp\HtmlOptionsGetTagRects.pdf";

  95:          if (File.Exists(testFilename))

  96:             File.Delete(testFilename);

  97:          theDoc.Save(testFilename);

  98:          Process.Start(testFilename);

  99:       }

 100:    }

 101: }

July 25, 2011

User Interface progress?

Filed under: Design, Rant, Uncategorized — philiphendry @ 10:59 am

I really feel the need to rant… I’ve just seen the following interface design:

image

So, you tell me… does this mean Column Guides is already turned on or when you flick the switch you are turning it on? Now I would assume it’s already turned on but the trouble I have here is what is wrong with using old-fashioned interface elements like the ones below? Aren’t they obvious? They may not be pretty but at least you know exactly what it means. Mind you… I have a gripe about the check box control too – sometimes it contains an ‘x’! You know it means the Column Guides are turned on but and ‘x’!!!!

image

image

July 20, 2011

Highlighting Selected Text automatically in Visual Studio

Filed under: Dev Tools, Tip, Visual Studio — philiphendry @ 1:40 pm

I’ve used Eclipse and always loved the way that any selected text is automatically discovered elsewhere in the current file and highlighted – this is also available in Notepad++.

Plugins to the rescue! There’s WordLight for Visual Studio 2008 and another called Highlight all occurrences of selected word.

June 13, 2011

Problems Compiling Android Project After Importing Into A New Workspace

Filed under: Android, Eclipse — philiphendry @ 6:34 pm

A couple of times now I’ve had compilation problems trying to compile an Android project after having just imported it into a new Workspace – and usually it seems to concern references to system packages. The solution seems to be:

  • Checking Windows –> Preferences –> Android –> SDK location which has blanked out. By resetting the path to the SDK then the SDK Targets re-appear.
  • Selecting Package Explorer –> <project> –> Android Tools –> Fix Project Properties. I can’t remember the exact reason this helped but it certainly did when loading source code that had accompanied a book.

June 1, 2011

Internet Explorer Developer Toolbar Formatting Javascript

Filed under: Dev Tools, Tip, Web — philiphendry @ 3:19 pm

I’ve seen screens like this all the time:

image

Jumbles of javascript spewd out by the ASP.NET AJAX and the like. However I’ve just noticed this very handy toolbar menu option:

image

Which produces formatted code like this:

image

I wish I had found that months and months ago!!

April 8, 2011

Create SQL Scripts for Data in a table

Filed under: T-SQL — philiphendry @ 11:58 am

On occasion I want to get data from a table and into a script – particularly when I want to create a deployment script for standing data that needs to be migrated between servers. Rather than hand-craft it this last time I’ve created the script below which, when given the name of a table, generates a union’d list of select statements that represent the data. With a slight modification this list can then be selected into a temporary table then joined back to the original in an update/insert to create a script that automatically maintains the data.

There are some caveats to this script though :

  • You have to delete the first ‘union’ in the resulting SQL.
  • It doesn’t handle dates correctly and potentially some other data types – I did write this in a hurry!

1 declare @tablename nvarchar(max) 2 set @tablename = 'testtable' 3 4 if not exists ( select [TABLE_NAME] from INFORMATION_SCHEMA.TABLES where [TABLE_NAME] = @tablename ) 5 begin 6 declare @message nvarchar(max) 7 set @message = 'The table ' + @tablename + ' does not exist' 8 raiserror ( @message, 18, 0 ); 9 return; 10 end 11 12 declare @sql nvarchar(max) 13 set @sql = '' 14 15 declare @newline nvarchar(2) 16 set @newline = char(13) + char(10) 17 18 select @sql = @sql 19 + case when ORDINAL_POSITION > 1 then ' + '', '' + ' else '' end 20 + case when IS_NULLABLE = 'YES' then 'isnull(' else '' end 21 + case when NUMERIC_SCALE is not null or DATA_TYPE = 'bit' then 'convert(nvarchar, ' else '' end 22 + case when CHARACTER_MAXIMUM_LENGTH is not null then ' '''''''' + ' else '' end 23 + '[' + isc.COLUMN_NAME + ']' 24 + case when CHARACTER_MAXIMUM_LENGTH is not null then ' + ''''''''' else '' end 25 + case when NUMERIC_SCALE is not null or DATA_TYPE = 'bit' then ')' else '' end 26 + case when IS_NULLABLE = 'YES' then ', ''null'')' else '' end 27 + ' + '' as [' + COLUMN_NAME + ']' + '''' 28 + @newline 29 from INFORMATION_SCHEMA.COLUMNS isc 30 where 31 isc.TABLE_NAME = @tablename 32 order by 33 ORDINAL_POSITION 34 35 set @sql = 'select ''union select '' + ' + @sql + ' from [' + @tablename + ']' 36 exec sp_executesql @sql

March 23, 2011

Configuring Event Log Permission for Asp.net applications

Filed under: ASP.NET, Security — philiphendry @ 2:40 pm

I’ve been having some problems with writing to a custom event log as well as the standard ‘Application’ event log where it failed with the following error :

    Stack Trace: 
    
    
    [Win32Exception (0x80004005): Access is denied]
    
    [InvalidOperationException: Cannot open log for source 'Application'. You may not have write access.]
    System.Diagnostics.EventLog.OpenForWrite(String currentMachineName) +1008783
    System.Diagnostics.EventLog.InternalWriteEvent(UInt32 eventID, UInt16 category, EventLogEntryType type, String[] strings, Byte[] rawData, String currentMachineName) +216
    System.Diagnostics.EventLog.WriteEntry(String message, EventLogEntryType type, Int32 eventID, Int16 category, Byte[] rawData) +264
    System.Diagnostics.EventLog.WriteEntry(String source, String message, EventLogEntryType type, Int32 eventID, Int16 category, Byte[] rawData) +87
    System.Diagnostics.EventLog.WriteEntry(String source, String message, EventLogEntryType type) +14
    ASP.test_testeventlogaccess_aspx.WriteToApplication(Object sender, EventArgs e) in c:\Inetpub\wwwroot\connect\Test\TestEventLogAccess.aspx:20
    System.Web.UI.WebControls.Button.OnClick(EventArgs e) +111
    System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument) +110
    System.Web.UI.WebControls.Button.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(String eventArgument) +10
    System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument) +13
    System.Web.UI.Page.RaisePostBackEvent(NameValueCollection postData) +36
    System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +1565

What made this harder to figure was it worked in one environment but not another! The difference was impersonation was turned on in the web.config for the failing web site :


  <system.web>
    <identity impersonate="true" />
  </system.web>

This meant that the user being used to access the event log was not the AppPool identity but rather the ASP.NET IUSR_ identity (which has not been overridden with the optional username/password attributes above in the config above.)

Microsoft has a knowledge base article describing how to configure event log permissions but there were two extra steps I had to perform before I could add the IUSR identity to the event log permissions :

Fetching the SID for an account

The SDDL string that needs to be added according to the knowledge base article referenced above requires the SID for the IUSR account. There are a couple of ways to do this listed below. Either script block should be saved to a .vbs file and run as a parameter to cscript.exe from a command line :

    strComputer = "."
    Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
    Set objAccount = objWMIService.Get("Win32_UserAccount.Name='IUSR_<COMPUTER NAME>',Domain='<COMPUTER NAME>'")
    Wscript.Echo objAccount.SID
    
    
    On Error Resume Next
    strComputer = "."
    Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
    Set colItems = objWMIService.ExecQuery("Select * from Win32_UserAccount",,48)
    For Each objItem in colItems
        Wscript.Echo "AccountType: " & objItem.AccountType
        Wscript.Echo "Caption: " & objItem.Caption
        Wscript.Echo "Description: " & objItem.Description
        Wscript.Echo "Disabled: " & objItem.Disabled
        Wscript.Echo "Domain: " & objItem.Domain
        Wscript.Echo "FullName: " & objItem.FullName
        Wscript.Echo "InstallDate: " & objItem.InstallDate
        Wscript.Echo "Lockout: " & objItem.Lockout
        Wscript.Echo "Name: " & objItem.Name
        Wscript.Echo "PasswordChangeable: " & objItem.PasswordChangeable
        Wscript.Echo "PasswordExpires: " & objItem.PasswordExpires
        Wscript.Echo "PasswordRequired: " & objItem.PasswordRequired
        Wscript.Echo "SID: " & objItem.SID
        Wscript.Echo "SIDType: " & objItem.SIDType
        Wscript.Echo "Status: " & objItem.Status
    Next

Remove the IUSR Identity from the Guest Users Group

The last modification which is not mentioned in the knowledge base article is that the Guest Users group is already explicitly denied access to the event log (at least on my Windows Server 2003 machine) and therefore adding the SID for this account will not have an account. To solve this I removed the IUSR account from the Guest Users group and everything worked.

Debugging

As an aside to be able to test and debug this on both a production and test server I created an .aspx file with no code-behind with the following code :

    <%@ Page Language="C#" %>
    <%@ Import Namespace="System.Diagnostics"%>
    
    
    <!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>Test Event Log Access</title>
        
        <script runat="server">
        
        protected void WriteToMyEventLog(object sender, EventArgs e)
        {
            EventLog.WriteEntry("MyEventLog", "This is a test", EventLogEntryType.Error);
        }
    
        protected void WriteToApplication(object sender, EventArgs e)
        {
            EventLog.WriteEntry("Application", "This is a test", EventLogEntryType.Error);
        }        
        </script>
        
    </head>
    <body>
        <form id="form" runat="server">
        <div>
            <asp:Button runat="server" ID="btnWriteToMyEventLog" Text="Write to My Event Log" OnClick="WriteToMyEventLog" />
            <asp:Button runat="server" ID="btnWriteToApplication" Text="Write to Application" OnClick="WriteToApplication" />
        </div>
        </form>
    </body>
    </html>

March 8, 2011

Randomising data in a table using T-sql

Filed under: SQL Server, T-SQL — philiphendry @ 8:48 am

I needed to obfuscate some data for a demo server but rather than re-invent all the data I simply wanted to randomly swap data so associations could not be made between a store and pay rates for example. After a bit of head scratching I came to the conclusion that I simply needed to be able to correlate one set of data with a random set. The simplest way I thought of was to use a row_number() to correlate one list of data with another where one set of row numbers were ordered by a primary key whilst the other used a little trick of ordering by newid() which is recalculated for each row and therefore randomly distributes the data. Here’s the code :

select *
--update s1 set s1.StoreName = s2.StoreName, s1.StoreAddress1 = s2.StoreAddress1, s1.County = s2.County, s1.Town = s2.Town, s1.PostCode = s2.PostCode, s1.Telephone = s2.Telephone
from (select row_number() over(order by StoreID) as RowNumber, StoreId, StoreName, StoreAddress1, County, Town, PostCode, Telephone from Stores s1) as s1 
join (select row_number() over(order by newid()) as RowNumber, StoreId, StoreName, StoreAddress1, County, Town, PostCode, Telephone from Stores s2) as s2 on s1.RowNumber = s2.RowNumber 

March 7, 2011

Mapping SkyDrive as a Network drive in windows

Filed under: Tip, Windows — philiphendry @ 9:06 pm

I didn’t realise you could do it until I found an article describing exactly how to do it and it works a treat! I’m thinking of using my SkyDrive account to backup photos off a NetGear ReadyNAS Duo storage device onto the cloud so they’re all doubly safe!!

Older Posts »

Theme: Shocking Blue Green. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.