Some Facebook Javascript Bookmarklets

September 10, 2014

Bookmarklets are pieces of JavaScript code that are stored as bookmarks in your browser, and execute locally (i.e. inside the downloaded page) as the bookmark is clicked.

Using the Facebook Graph API, you can take a look “behind the scenes” to retrieve the raw information of what is being displayed when you browse Facebook.

Requests under the URL http://graph.facebook.com/ return JSONified data about the requested object, which is identified by it’s ID.

Posts and Threads

Let’s look at threads (posts) and their IDs. There are a couple of ways the thread ID is stored in the thread URL, depending on where it is posted (page, group) and how you browse it (permalink or notification):

https://www.facebook.com/[name]/posts/[thread id]
https://www.facebook.com/groups/[group id]/permalink/[thread id]/
https://www.facebook.com/photo.php?fbid=[thread id]&set=[...]

Since the thread ID is numeric, a simple regex \d+ would be sufficient to retrieve it. However, group IDs may also be numeric, and names may contain digits.

After a bit of experimenting, the regex that I came up with to extract the thread ID from a Facebook URL, is

(/[\/=](\d+)[&\/]?/.exec(window.location.toString().replace(/\/groups\/\d+\//),""))[1]

Using this regex, we can now craft a JavaScript routine to open a new window containing the Graph API result:

window.open("http://graph.facebook.com/" +
    (/[\/=](\d+)[&\/]?/.exec(window.location.toString().replace(/\/groups\/\d+\//),""))[1] +
    "/comments", "_blank")

and create a bookmark for it. Since this WordPress installation does not allow to include bookmarklet links, you need to

  • Create a bookmark in your browser
  • Give it a name, such as “View Comments in FB Graph”
  • Set the URL or location to
javascript:window.open("http://graph.facebook.com/" + (/[\/=](\d+)[&\/]?/.exec(window.location.toString().replace(/\/groups\/\d+\//),""))[1] + "/comments", "_blank")
  • Click OK

But this does not give you the contents of the whole thread, just the comments.

To retrieve the whole post, we can use the Graph API Explorer.

The Graph API Explorer retrieves the details of a Facebook object, such as a post or thread, using the URL

https://developers.facebook.com/tools/explorer/?method=GET&path=[object id]

So, as we know how to extract the thread ID from a FB URL, let’s create a bookmarklet with the URL

javascript:window.open("https://developers.facebook.com/tools/explorer/?method=GET&path="+ (/[\/=](\d+)[&\/]?/.exec(window.location.toString().replace(/\/groups\/\d+\//,"")))[1], "_blank")

This opens the Graph Explorer with the desired ID. Click Submit to retrieve the data. Probably you need to click Get Access Token first.

Remove the Right Column

If you want to take screenshots of Facebook pages, you probably want to remove the right column before screenshotting, since it only expands the image, but does not include the content you want to save.

The top-most HTML container for right column content is called “rightCol” (yes, surprising).

To remove it from display, simply add this code to a bookmarklet:

javascript:var rc=document.getElementById("rightCol");rc.parentElement.removeChild(rc);

Clean up the Likes Page

To get a screenshot of “selected” Likes on a Like Page, there is a way to delete the Likes we don’t like (haha).

Simply scroll down until the list of likes is complete, then run this bookmarklet:

javascript:var li=document.getElementsByClassName("_5rz");for(var i=0;i<li.length;i++){var l=li[i];l.onclick=(function(el){return function() { el.parentElement.removeChild(el);return false;};})(l);}

Now clicking on a Like preview image will remove the entry from the list, allowing you to retain only the desired entries, ready to screenshot.


Using .resx Strings in Javascript

February 7, 2014

If you output a string in ASP.Net MVC using @ (Razor) or <%: %> (aspx), the string will automatically get HTML-encoded.

So if you reference a resource string (.resx) in Javascript,

<script type="text/javascript">
var x = "@resx.StringId";
</script>

AND the string contains characters the HTML-encoded (such as “&” => “&amp;”, umlauts, accented characters, etc.), you’ll end up with a lot of ampersands.

The way I solved this problem in a recent project was to define an HtmlHelper extension like this:

public static IHtmlString JsString<TModel>(this HtmlHelper<TModel> html, string s)
{
  return html.Raw(HttpUtility.JavaScriptStringEncode(s));
}

The string is first Javascript-encoded, and then written to the response stream unchanged using HtmlHelper.Raw().


Retrieving the data() Value of Multiple JQuery Objects

August 29, 2013

I have a web page with a (generated) list of checkboxes, each representing a database record

                <input type="checkbox" data-oid="<%= record.OID %>" />

After checking the desired check boxes, the concatenated values of the data- attribute should be posted to the server.

Since this selector

$("input[type='checkbox']:checked")

would return all the checked checkboxes, I expected the expression

$("input[type='checkbox']:checked").data("oid")

to return all data- values as an array. But NO:

Store arbitrary data associated with the matched elements or return the value at the named data store for the first element in the set of matched elements.

says the jQuery documentation quite unexpectedly.

I need a map(), I thought, and after a couple of tries convinced the JS interpreter to let me have my map():

$("input[type='checkbox']:checked")
    .map(function(i,el) { return $(this).data("oid"); })

Great. This returns something that looks like an integer array, e.g.

[82, 86]

but it is not, since I cannot join() it:

TypeError: Object [object Object] has no method ‘join’

and the Chrome Developer Tool claims it looks more like a stack than an array. Make it so!

$("input[type='checkbox']:checked")
    .map(function() { return $(this).data("oid"); })
    .toArray()

The result not only looks like an array, it really seems to be an array, and

$("input[type='checkbox']:checked")
    .map(function() { return $(this).data("oid"); })
    .toArray()
    .join(",")

finally returns a string of concatenated integer values:

"82,86"

WCF WebGet JSON DateTime.MinValue => “Connection Reset”

July 9, 2013

I created a WCF web service that implements a method that returns an object containing a DateTime value (not nullable, according to spec, don’t ask).

The web service worked fine, and I wanted to add GET support using the WebGet attribute and some web.config magic. Still worked fine.

Then I tried out some failure scenarios (if you have a status query that tries to connect to a database which happens to be offline, or misconfigured, you want to know how your status query behaves), and suddenly the only response I got from the browser was “connection reset”.

After switching on WCF tracing, the failing WCF call generated a 168kB XML file containing 160 lines of stack trace for a single call!

Despite its size, the information in the file is quite useful, as it pointed to an internal exception:

System.Runtime.Serialization.SerializationException

DateTime values that are greater than DateTime.MaxValue or smaller than DateTime.MinValue when converted to UTC cannot be serialized to JSON.

   at System.Runtime.Serialization.Json.JsonWriterDelegator.WriteDateTime(DateTime value)
   at WriteTimestampTOToJson(XmlWriterDelegator , Object , XmlObjectSerializerWriteContextComplexJson , ClassDataContract , XmlDictionaryString[] )
   at System.Runtime.Serialization.Json.JsonClassDataContract.WriteJsonValueCore(XmlWriterDelegator jsonWriter, Object obj, XmlObjectSerializerWriteContextComplexJson context, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.Json.XmlObjectSerializerWriteContextComplexJson.WriteDataContractValue(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithoutXsiType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.Json.DataContractJsonSerializer.InternalWriteObjectContent(XmlWriterDelegator writer, Object graph)
   at System.Runtime.Serialization.Json.DataContractJsonSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph)
   at System.Runtime.Serialization.XmlObjectSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)
   at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)
   at System.Runtime.Serialization.Json.DataContractJsonSerializer.WriteObject(XmlDictionaryWriter writer, Object graph)
   at System.ServiceModel.Dispatcher.SingleBodyParameterMessageFormatter.SingleParameterBodyWriter.OnWriteBodyContents(XmlDictionaryWriter writer)
   at System.ServiceModel.Channels.BodyWriter.WriteBodyContents(XmlDictionaryWriter writer)
   at System.ServiceModel.Channels.BodyWriterMessage.OnWriteBodyContents(XmlDictionaryWriter writer)
   at System.ServiceModel.Channels.Message.OnWriteMessage(XmlDictionaryWriter writer)
   at System.ServiceModel.Channels.Message.WriteMessage(XmlDictionaryWriter writer)
   at System.ServiceModel.Channels.BufferedMessageWriter.WriteMessage(Message message, BufferManager bufferManager, Int32 initialOffset, Int32 maxSizeQuota)
   at System.ServiceModel.Channels.JsonMessageEncoderFactory.JsonMessageEncoder.WriteMessage(Message message, Int32 maxMessageSize, BufferManager bufferManager, Int32 messageOffset)
   at System.ServiceModel.Channels.WebMessageEncoderFactory.WebMessageEncoder.WriteMessage(Message message, Int32 maxMessageSize, BufferManager bufferManager, Int32 messageOffset)
   at System.ServiceModel.Channels.HttpOutput.SerializeBufferedMessage(Message message)
   at System.ServiceModel.Channels.HttpOutput.Send(TimeSpan timeout)
   at System.ServiceModel.Channels.HttpRequestContext.OnReply(Message message, TimeSpan timeout)
   at System.ServiceModel.Activation.HostedHttpContext.OnReply(Message message, TimeSpan timeout)
   at System.ServiceModel.Channels.RequestContextBase.Reply(Message message, TimeSpan timeout)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.Reply(MessageRpc&amp;amp; rpc)

with an internal exception of type System.ArgumentOutOfRangeException:

Specified argument was out of the range of valid values.
Parameter name: value

and many more stack traces.

Back to my code: If there is an error connecting to the database, I return an object that contains a field set to DateTime.MinValue.

It seems that MinValue causes problems when converted to or from UTC, or at least the subsequent JSON serialization does, even though the ECMA Date range is greater than the .Net DateTime range.

Whatever the causes of this boundary problem are, returning DateTime.MinValue.AddDays(1) solved the problem.


Inserting and Updating List Items with ASP.Net MVC and JQuery

May 16, 2013

In ASP.Net MVC you can display lists of data either as a grid with paging, just as in ASP.Net, or, as I prefer most often, using Infinite Scrolling.

Editing and item or adding an item to the list needs a different approach in Infinite Scrolling as opposed to the paging grid: after adding or updating, you do not want the user to have to scroll back to the original location of the record inside the grid.

Let’s have a look at how Infinite Scrolling can be implemented:

$.get("/More/Items/" + page /* + other options, such as sort order */ , 
  function(data) {    if (data != "") {
      $("#myList").append(data);
    }
  }
);

So we need a controller method returning a partial view filled with the data model of the list.

To support adding a newly created record, or displayed an updated record in the list, we split the list’s partial view into an enumeration part and an item part

List.ascx:

<table><thead><tr><th>.....</th></tr></thead><tbody>
<% 
  foreach(var item in Model.Items) {
    Html.RenderPartial("ListItem", item);
  }
%>
</tbody></table>

The ListItem.ascx then contains the record inside a <tr>:

<tr><td>.....</td>....</tr>

Easy.

Next. we want to support adding and editing records. Personally I prefer jQuery UI’s $.dialog() to open a form for editing the record, and upon closing the dialog, the data is stored using an Ajax or Web service request, and finally the list item is updated:

var tbl = $("#myTable tbody");

$.get('Ajax/GetInsertedRecord?id=' + data.Id
  function (data) {
    tbl.prepend(data);    // insert as first record in list
  }
);

where data is the record returned from the Ajax call to insert the data.

Analogously for updating the edited record, use

var tr = hl.parent().parent();  // retrieve the <tr> of the Edit button

$.get('Ajax/GetUpdatedRecord?id=' + data.Id,
  function (data) {
    tr.replaceWith(data);
  }
);

This in-place adding and updating just require 2 controller methods, and the record partial view extracted from the original list view! Both controller methods return the same partial view as the one referenced in the list partial view.


What is ‘this’? Confusion in JavaScript and Typescript

December 23, 2012

Since Typescript supports “true” object-oriented programming using the ‘class’ and ‘interface’ keywords, there seems to be an expectation that the semantics of JavaScript is suspended and replaced by Something Completely New. This is not the case!

Remember, Typescript is a syntactic extension of JavaScript and compiles to JavaScript. The underlying concepts remain unchanged. (Un)fortunately (depending on your point of view and your expectations), this is also true for the ‘this’ keyword.

Let’s have a look at some StackOverflow questions regarding the meaning of ‘this':

The questions assume that a callback function passed to another function will be executed in the context of the original object, therefore retaining the semantics of ‘this’ identified the original object:

myFoo(){
    otherFoo.otherBar(this.myBar);
}

In JavaScript, however, the function myBar is executed with ‘this’ referring, depending on the implementation of otherBar, to the otherBar function, or something completely different.

Do not despair, though, there are a couple of possible solutions:

 

 


Typescript Support for JavaScript Libraries such as jQuery

November 15, 2012

Most JavaScript code relies on functionality implemented by the numerous JavaScript libraries and frameworks available, such as jQuery.

To use these libraries from Typescript, you can either create so-called ambient definitions for the JS library manually, or use existing Typescript definition files (extension .d.ts).

The TypeScript source code repository contains a jquery.d.ts in the /bin directory, and a definition file for jquery.ui can be found in /samples/warship. However, those samples need not cover the respective library completely.

This question on SO points us to the github repository DefinitelyTyped

TypeScript type definitions repository for popular JavaScript libraries.

The project aims to provide high quality definitions for the most popular libraries out there.

which lists about 50 definition files covering major JS libraries and frameworks.


Follow

Get every new post delivered to your Inbox.

Join 78 other followers