Udostępnij za pośrednictwem


Building the LightSwitch Twitter Control

This is the second part of my LightSwitch Twitter Feed Control series.  For an introduction to the control and how it can be used in your applications, please see the first part of this series.

Twitter Embedded Timelines

Twitter embedded timelines are a great, interactive way to bring tweets into any webpage.  You can read about them on Twitter’s developer documentation for embedded timelines.  In the “Creating an Embeddable Timeline” section, you can see that using the embedded timelines involves two pieces:

  • An HTML anchor “<a />” tag that contains:
    • a special “twitter-timeline” class
    • a list of “data-“ attributes that provide input into the control, for example the widget id.
  • Some JavaScript that loads the “platform.twitter.com/widgets.js” script

When the widgets.js script is loaded it looks through your HTML document and will replace any anchor elements that are marked with class=“twitter-timeline” with an iframe.  The iframe hosts the actual timeline.  The HTML inside the iframe is created by Twitter itself, and is outside the scope of this article to explain.  Just know that whatever HTML is created in the iframe will show the specified tweets correctly.

The last piece to understand about Twitter embedded timelines is how to update them once they have already been loaded.  There is a Twitter JavaScript API that will search through your HTML document and convert any anchor tags with the class=”twitter-timeline” into iframes.  This API hangs off the global “twttr” object: twttr.widgets.load().  Optionally, you can pass a DOM element, and only that DOM element and its descendants will be searched.

Now, you should be able to see the strategy that will be used in order to create a reusable Twitter embedded timeline control.  All that is needed is to generate an anchor tag with the appropriate attributes.  Then call the twttr.widgets.load() function, which will turn the anchor into the appropriate iframe with the necessary HTML that shows the Twitter feed.  When data on the screen is changed:  remove the old iframe, put in a new anchor tag with updated “data-“ attributes, and call twttr.widgets.load() again.

Creating a Custom LightSwitch HTML Control

One goal I had for my custom control was to be as simple as possible for a LightSwitch developer to use.  Ideally, I wish the LightSwitch developer wouldn’t have to write any code at all to use the control.  However, I couldn’t find a way around needing at least one line of code.  To use it, a developer creates a “Custom Control” in the LightSwitch screen designer, clicks the “Edit Render Code” link and writes:

     TwitterControl.render(element, contentItem);

The TwitterControl’s render function gets passed the two parameters that are passed to the developer’s render function:

  • element – an HTML DOM element, the <div> where a custom control can add new content
  • contentItem – the LightSwitch view model object that provides the context to the control

I feel this is an acceptable solution in order to get this reusable control in an application.  Maybe someday custom HTML controls can be registered as extensions to Visual Studio LightSwitch and a developer would see “Twitter Feed” as an option to when adding new controls in the screen designer.  Maybe I am just wishful thinking.

Another goal I had was that any input value could be changed at any time and this would cause the control to update automatically.  The Twitter Feed Control depends on the LightSwitch runtime for its data binding functionality.  This functionality comes from the LightSwitch “ContentItem” objects.  ContentItem objects have a “stringValue” property.  According to Joe Binder’s excellent custom control blog, “The string value is a formatted string representation of the underlying property value.”  This is the property the Twitter control is using to get the current values for the “data-“ attributes in the anchor tags.  In order to get notified when this value is changed, there is a ‘dataBind’ function on a ContentItem that registers a callback function to be invoked whenever the value is changed.  So the Twitter control calls

     contentItem.dataBind('stringValue', loadFeed);

This tells the LightSwitch runtime to invoke the loadFeed function when the contentItem.stringValue changes.  The loadFeed function gets the current values, rebuilds the anchor tag and calls twttr.widgets.load().

Now here is where a design decision was made.  The Twitter Feed doesn’t just take a single value as an input.  Instead there are 17 possible inputs:

  • widgetId (required)
  • width
  • height
  • userId
  • theme
  • etc  (you can get the full list in the first part of this series)

In a lot of cases, a custom control will display a single value.  Using the built in “Specify the data for the new custom control” dialog

image

to bind to a single value is great when you only need a single value, or all your inputs comes from a single source.  However, I wanted to be flexible enough that the widgetId could come from one place (like a data property directly on the LightSwitch screen) and the userId could come from another (like the “TwitterId” property on an “Author” entity).

In order to be this flexible, I needed a way for the developer to tell the control where to get the data for each input.  Ideally, all 17 inputs would show up in the “Properties” sheet in Visual Studio.  A developer could bind each property to its source as appropriate.  However, since there is no custom control registration for HTML controls, there is no way to customize the Properties sheet when a developer puts one of these controls on his/her screen.

So the technique I devised was to use the same approach as any other “Group” control and inspect the child items under the current control.  If the control finds a child ContentItem with a name that looks like one of its inputs, it uses that child ContentItem for the input value.  Looking at the child ContentItems is pretty simple in JavaScript:

     // find the child ContentItem based on a naming convention
    contentItem.children.forEach(function (e) {
        var name = e.name.toLowerCase();

        // inspect the child's name to see if it should be used as an input
         if (name.indexOf(inputName.toLowerCase()) >= 0) {

 
        ...
    });

When a ContentItem is found to match an input, it gets its “stringValue” registered for data binding.  When any of the bound values change, the feed is rebuilt.

This technique works great when the input value can be changed dynamically.  However, I found many cases where I wanted to hard code a static value to be passed to the control.  Most often this was the most important input value: the widgetId.  When using a “User Timeline” feed where the user being displayed is dynamic, you only need one widget registered with Twitter.  In that case, you want to just say widgetId = 12345, and not mess around with ContentItems.  Although I could have forced a developer to add a screen data item, and hard code the value during “screen.created” function, this was breaking my first goal of making this control as simple as possible.

In order to support easily providing static information to the control, I added an optional parameter to the TwitterControl.render function: “options”.  This options object can contain any of the 17 input values in properties that have the same name as the input.  So, if a developer wanted to statically define the width and height input values, the code they would write would simply be:

     TwitterControl.render(element, contentItem, {
        width: 450,
        height: 600
    });

Internally to the control, these static input values are placed in “FakeContentItem” objects, which allows the rest of the control code to handle both real ContentItem objects and these static values in the same way.  Sometimes this is the best feature of JavaScript and sometimes it is the worst feature: having such a loose typing system.  In this case, it really worked out great.  I didn’t need to try using the real ContentItem “class” for something it wasn’t designed to do.  But I didn’t need to write an “adapter” layer that could support both real and fake ContentItem objects.  My code just uses two things from a ContentItem: the stringValue property and the dataBind function.  So the fake objects just need to expose these two things, and they act like a real ContentItem to the rest of the system.  Obviously there are plenty of cases where you can “shoot yourself in the foot” doing something like this.  But when used at appropriate times, this really simplifies JavaScript coding.

The rest of the Twitter Feed code is pretty self-explanatory.  It uses jQuery to create an anchor tag, add the necessary attributes, and then calls twttr.widgets.load() on the DOM element passed into the render function.

Creating the NuGet Package

I don’t think I can sing my praises of NuGet loud enough to describe how much value I think it adds to the development community.  It is such a simple, yet powerful platform to share components.  Just having a searchable catalogue of things you can use in your applications adds immeasurable value.  But to be able to install them correctly in your Visual Studio projects with a click of a button seriously blows me away.  It is like the first time I experienced a mobile device’s “app store”.  There was no wizard asking me where to install the app.  There wasn’t an archaic README.txt file explaining how to set the app up.  I just searched for an app, clicked a button and I was using the application in seconds.

So deciding to distribute the Twitter Feed control over NuGet was a non-decision.  It was a given.

However, I had never done it before and I was willing to learn.  NuGet has a great set of documentation explaining how to download NuGet.exe, create a NuGet package, and publish it to a NuGet repository.

A question I have gotten asked is “How do you get the twitterControl.js script registered in the default.htm page automatically?”  This relates back to one of my goals above: making this control as simple as possible to use.  Even adding a <script /> line in the default.htm page seemed like too much work for me.

To get the script file automatically added to the application’s HTML, I used a feature that the LightSwitch HTMLClient project uses itself.  During build time, the LightSwitch HTMLClient project looks for all .js files under the “UserCode” folder.  It creates a generated ‘usercode.js’ file under the hidden “GeneratedArtifacts” folder.  This usercode.js file looks like following:

     // Warning: AutoGenerated file, Don't edit.
    document.writeln("<script type=\"text/javascript\" src=\"UserCode/twitterControl.js\"></script>");
    document.writeln("<script type=\"text/javascript\" src=\"Screens/ViewEmployee.lsml.js\"></script>");

The usercode.js file gets added to the DOM by a file named “generatedAssets.js”.  A script tag for the “Scripts/Generated/generatedAssets.js” file is in the default.htm file.  This is how the Twitter Feed control “automatically” gets registered in the application’s default.htm file.  Since it is placed in the “UserCode” folder of a LightSwtich HTMLClient project, LightSwitch will automatically load this .js file at runtime.

Now, will this feature be supported forever?  I don’t know.  Someday there might not be a “UserCode” folder in a LightSwitch HTMLClient project.  Or, the generatedAssets.js file might not get created anymore.  So, just because I used this “hack” doesn’t mean that it is a publicly supported feature.  It could change in a future release.  However, knowing this, I still decided to take advantage of this functionality in order to support my above goals.

To get the .js file installed to the UserCode folder by NuGet, a NuGet package author just needs to put the .js file under “Content\UserCode” in their NuGet folder structure.  When the NuGet package gets installed on an HTMLClient project, the .js file will automatically be put in the project’s “UserCode” folder.

A drawback to this approach is that I don’t ship both a full version and a minified version of the code.  Adding both to the “UserCode” folder would cause problems because they both would be automatically registered in an app.  I didn’t tackle this problem, but if there is enough feedback (probably like one person asking for it) I would ship a minified version of the Twitter Feed control and then have to address this issue.

Conclusion

That’s all for now.  I hope this gives someone insight into how I built the Twitter Feed control.  Feel free to download the code from NuGet and look through it yourself.  Maybe it will give you ideas on how you can build a reusable control for yourself and the rest of the LightSwitch developers out there.

Eric