Friday, June 28, 2013

Gallery Server Pro 3.0.1 Released

Today I released 3.0.1, containing 35 bug fixes and 9 enhancements. They include fixes to the 2.6 => 3.0 upgrade process, performance improvements, media template tweaks, and more.

This release is a free upgrade to 3.0.0 license holders. Upgrading is easy – just copy the files from the upgrade package over your existing web application. Installations and upgrades for other scenarios are in the Quick Start Guide.

Upgrade improvements

Several issues with upgrading from 2.6 have been addressed.

Lost media type settings – The upgrade to 3.0.0 caused the list of enabled media object types to reset to the default values of .jpg and .jpeg. Upgrades now preserve the settings you selected on the Media Object Types page in the site admin area. Note that if you already upgraded to 3.0.0, applying 3.0.1 will not correct this – you still need to re-enable the desired file types. But anyone upgrading from 2.6 to 3.0.1 will now have their file type settings preserved.

SQL timeout during restore – For large galleries, the default SQL Server timeout value of 30 seconds was not enough time to delete  and/or insert records. The value was increased to 3600 seconds (one hour).

Tags not imported – Users upgrading galleries with images that had tags (also called keywords) extracted from the file metadata discovered they were missing in 3.0. That has now been fixed, along with a couple other bugs where tags longer than 100 characters or those having apostrophes caused the upgrade to fail. If you were affected by this issue, you can (1) rebuild the tags on the Metadata page or (2) install 3.0.1 and perform the upgrade again.

Out of memory error – The backup and restore operation is a memory intensive operation and can fail for very large galleries (e.g. hundreds of thousands of media files). If you experience an out of memory error while creating the 2.6 backup file, see this thread for a patch. Version 3.0.1 has a more efficient restore algorithm, which should reduce the chance of exhausting the memory.

Incorrect .mp4 encoder setting – When you upgraded from 2.6 to 3.0.0, the .mp4 encoder setting on the Video & Audio page in the site admin area began with this text:

-y -i "{SourceFilePath}" -s {Width}x{Height} -b:v 384k …

But if you installed from scratch you got this setting:

-y -i "{SourceFilePath}" -vf "scale=min(iw*min(640/iw\,480/ih)\,iw):min(ih*min(640/iw\,480/ih)\,ih)" -b:v 384k …

Using –s {Width}x{Height} works, but it creates the encoded video with the same width and height as the original. Just before I released 3.0.0, I realized it would be better to scale the videos to a reasonable size so that the browser wasn’t trying to render ridiculously large videos. For example, videos from my Samsung Galaxy S4 are 1920x1080, but for most users it is overkill to serve videos at this size, not to mention the difficulty showing a large video on a monitor smaller than the video. So for 3.0.0 I changed the setting to the value shown above, where it is scaled to around 640x480, with the exact dimensions adjusted to preserve the aspect ratio. However, I forgot to update the setting in the upgrade script, so upgraders got the original setting. When you upgrade to 3.0.1, the setting is automatically updated, but if you prefer the original value, you are free to change it back (or to anything else you see fit).

If you have any issues with your upgrade, I want to hear about it. I may be able to upgrade your gallery even if you continue getting an out of memory error.

Performance enhancements

A couple of performance improvements have helped make the gallery a little more snappy. When the left pane is disabled on the Gallery Control Settings page, the data for the album treeview is no longer required. Version 3.0.1 now detects this scenario and skips the calculation, JSON serialization, and sending of the data to the browser.

I was also able to eliminate a duplicate JavaScript variable containing gallery settings.

Media template tweaks

After 3.0.0 was released, I noticed a few of the media templates were unnecessary or incorrectly configured. For example, .m4v, .qt, and .moov files were configured to play with an object tag in all browsers except Safari, which specified the HTML5 video tag. After upgrading to 3.0.1, these files will behave the same as .mp4, using the HTML5 video tag in browsers that support it and a fallback to Flash in those that don’t.

Also, .mp3 files wouldn’t play in IE8 and earlier because it doesn’t support the HTML5 audio tag. I added a new template that uses a Silverlight player for IE 1-8.

Updated album thumbnail styling

Version 3.0.0 used a different background color and a CSS double border on album thumbnails to differentiate albums from media objects:

abm_thmb_double_border

A few days ago I was helping a fella named Joe Hakooz with his gallery and I noticed he had tweaked the CSS to look a little more like a photo album. He was kind enough to let me copy his technique, and you can see the results here:

abm_thmb_triple_border

The neat thing is that it’s done all in CSS, but instead of specifying a double border, it uses the CSS3 box-shadow property:

box-shadow:0 0 0 0 #545454, 2px 2px 0 0 rgb(167,167,167), 4px 4px 0 0 #545454, 6px 6px 0 0 rgb(167,167,167)

By specifying a comma separated list of properties with alternating colors, I can create a nifty album look without custom images or the extra HTTP request it would require. Thanks Joe!

By the way, in case you are wondering why I mixed hex and RGB values, that is necessary for the MSBuild script I use when creating the light skin CSS file automatically from the dark skin. I have a standard set of replacement colors for hex values, while any colors specified in RGB are either not converted or handled in a separate step. The details are beyond the scope of this post, but I didn’t want you to think I am a sloppy programmer. Smile

The full list

Below is the full list of bugs and enhancements. I am still working on creating detailed reports with additional details. They’ll be posted on the release history page when finished.

Bug fixes

  • UI appears to allow editing album meta even when user does not have edit album permission
  • SQL timeout during restore
  • jQuery updated to 1.10.0 instead of 1.10.1 during 2.6 migrations
  • Saving changes on User Settings page unsets 'send an e-mail to the following admins when account is created' option
  • Edit options are sometimes incorrect
  • Image used when watermark can't be applied is not present
  • Media object not visible in IE8
  • Media object header section left aligned when toolbar disabled
  • Media template incorrect for M4V files
  • MP3 audio files do not play in IE8 and earlier
  • Thumbnails do not flow correctly on edit titles page
  • Web.API calls return 404 in some configurations
  • Cannot add a ZIP file on the add objects page
  • Concurrency error when trimming event log
  • Cannot assign thumbnail when gallery is read only
  • Download/share dialog hidden when viewing PDF file on Safari
  • Redundant MP3 media template for Safari
  • .qt and .moov files should use same media template as .mov
  • Keywords not correctly imported in 2.6 to 3.0 upgrade
  • Keywords longer than 100 characters cause 2.6 to 3.0 upgrade to fail
  • Keyword containing apostrophe causes 2.6 to 3.0 upgrade to fail
  • Link to product key page is incorrect
  • Space missing from 'Date Added' metadata label
  • GetDataSchemaVersion function very inefficient
  • Label incorrect on on Gallery Control Settings page
  • Duplicate settings object sent to browser
  • Can't edit title or caption of root album after 2.6 to 3.0 upgrade
  • Web.API call to /api/albums/{AlbumId}/galleryitems stores sort request as user's preferred sort order
  • Incorrect MP4 encoder string in 2.6 to 3.0 upgrade
  • A user is shown an Add link in an empty album even when they do not have add media object permission
  • Subsequent failed logins shows an empty green message box
  • Sync error can occur when filename is different than the metadata filename value
  • NullReferenceException if user without edit permission navigates to transfer objects page
  • List of enabled media objects is reset during upgrade
  • Thumbnail assigned to album during synchronization is not always the first file in directory

Enhancements

  • Improve ability to format dates and numbers within UI template
  • Make common jsRender helper functions and converters globally available
  • Silently handle errors that occur during metadata extraction and record file path
  • Add ability to specify sort on Web.API method /api/albums/{AlbumId}/mediaitems
  • Lock icon has misleading text when anonymous browsing is disabled
  • Perf improvement: Calculate and send album tree data only when required
  • Periodically persist the synchronization status to the database
  • Improve thumbnail distinction between albums and media objects
  • Update to jsRender 1.0 beta

Thursday, June 20, 2013

Deep dive into UI Templates

One of the best new features in 3.0  is UI Templates. The main user interface for browsing a gallery is built from jsRender templates you can edit using only HTML and JavaScript skills. In this post we’ll take a deep look at how it works and how you can harness its power.

Overview

Let’s start by identifying the five sections of the gallery that are built from UI templates.

uitmpl_sections_mo

uitmpl_sections1

These images show the five templates: header, left pane, right pane, album, and media object. The center pane shows either the album or media object template, depending on whether the user is looking at an album or an individual media object.

I should point out that the Actions menu and the album breadcrumb links are the only portion of the page that is not part of any template. Instead, its HTML is generated on the server and inserted into the page under the header. A future version of GSP may merge this portion into the header template so that the entire page is 100% template driven.

Each template consists of a chunk of HTML containing jsRender syntax and some JavaScript that tells the browser what to do with the HTML. Typically the script invokes the jsRender template engine and appends the generated HTML to the page. A robust set of gallery data is available on the client that gives you access to important information such as the album and media object data, user permissions, and gallery settings. We’ll get into the structure of this data later in this post.

You can view the UI template definitions on the UI Templates page in the site admin area. In these two images you can see the HTML and JavaScript values:

uitmpl_lp_html1

uitmpl_lp_js1

You can assign which albums this particular template applies to on the Target Albums tab. If multiple templates target the same album, the most specific one wins. For example, if the default template is assigned to ‘All albums’ and a second template is assigned to the Samples album, the second template will be used for the Samples album and any of its children because that template definition is ‘closer’ to the album.

uitmpl_lp_ta

The preview tab lets you see the result of any edits you make before saving the changes.

uitmpl_lp_preview

Anatomy of the left pane

Let’s take a close look at how one of the templates works. We’ll choose the left pane template first. The HTML is simple:

<div id='{{:Settings.ClientId}}_lptv'></div>
It defines a single, empty div tag and gives it a unique ID. The text between the double brackets is jsRender syntax that refers to the ClientId property of the Settings object. This particular property provides a string that is unique to the current instance of the Gallery user control on the page. When it is rendered on the page you end up with HTML similar to this:

<div id='gsp_g_lptv'></div>

Note: Defining a unique ID is not required for most galleries, but it is there for admins who want to include two instances of the gallery control on a page. For example, you might have a slideshow running in one part of a web page and a video playing in another.

Astute observers will notice that a single div tag doesn’t look anything like a complex treeview. So how does that div tag eventually become the treeview? Let’s look at the JavaScript that is part of the template:

// Render the left pane, but not for touchscreens UNLESS the left pane is the only visible pane
var isTouch = window.Gsp.isTouchScreen();
var renderLeftPane = !isTouch  || (isTouch && ($('.gsp_tb_s_CenterPane:visible, .gsp_tb_s_RightPane:visible').length == 0));

if (renderLeftPane ) {
 $('#{{:Settings.LeftPaneClientId}}').html( $.render [ '{{:Settings.LeftPaneTmplName}}' ]( window.{{:Settings.ClientId}}.gspData ));

 var options = {
  albumIdsToSelect: [{{:Album.Id}}],
  navigateUrl: '{{:App.CurrentPageUrl}}'
 };

 // Call the gspTreeView plug-in, which adds an album treeview
 $('#{{:Settings.ClientId}}_lptv').gspTreeView(window.{{:Settings.ClientId}}.gspAlbumTreeData, options);
}

Technically this text is not pure JavaScript. Do you see the jsRender syntax in there? That’s right, it is ALSO a jsRender template that will be run through the jsRender engine to produce pure JavaScript just before execution. This is an extraordinarily powerful feature and can be harnessed to produce a wide variety of UI possibilities. Imagine writing script that loops through the images in an album to calculate some value or invoke a callback to the server to request data about a specific album.

The first thing the script does is decide whether the left pane template should even be appended to the page. We decide not to show it on touchscreens for two reasons: (1) touchscreens typically have small screens and cannot afford the real estate required by the left pane (2) the splitter control that separates the left, center, and right panes does not work well on touchscreens (that’s something I need to work on).

The script refers to the function window.Gsp.isTouchScreen(). You’ll find this function in the minified file gallery.min.js. If you are editing the gallery you will probably prefer to have un-minified script files loaded into the browser. You can easily accomplish this by switching the debug setting to ‘true’ in web.config.

If the script decides the left pane is to be rendered, it executes this line:

$('#{{:Settings.LeftPaneClientId}}').html( $.render [ '{{:Settings.LeftPaneTmplName}}' ]( window.{{:Settings.ClientId}}.gspData ));
This will look familiar to anyone with jsRender experience or other types of template engines. In plain English, it says to take the template having the name LeftPaneTmplName and the data in the variable gspData, run in through the jsRender engine, and assign the resulting HTML to the HTML element named LeftPaneClientId. In other words, it takes the string <div id='{{:Settings.ClientId}}_lptv'></div>, converts it to <div id='gsp_g_lptv'></div>, and adds it to the page’s HTML DOM.

So far all the script has done is add a div tag to the page, but we still need to convert it into the album tree. That’s what the last section does:

var options = {
 albumIdsToSelect: [{{:Album.Id}}],
 navigateUrl: '{{:App.CurrentPageUrl}}'
};

// Call the gspTreeView plug-in, which adds an album treeview
$('#{{:Settings.ClientId}}_lptv').gspTreeView(window.{{:Settings.ClientId}}.gspAlbumTreeData, options);

The JavaScript file I mentioned above contains several jQuery plug-ins to help reduce the complexity of the templates and to maximize the amount of script that is cached by the browser (inline script is never cached). Here we use jQuery to grab a reference to our generated div tag and we invoke the gspTreeView plug-in on it, passing along the album treeview data and a few options. The tree data is a JSON object containing the album structure and is included in every page request. In turn, the gspTreeView plug-in is a wrapper around the third party jQuery tree control jsTree, the code for which is in the lib.min.js file (or lib.js if you are running with debug=true). There are several third party script libraries in that file.

The treeview plug-in builds an HTML tree from the data and appends it to the div tag, resulting in the tree view you see in the left pane. You can play with the HTML and JavaScript and then use the preview tab to see how those edits affect the output.

Adding a logo to the header

A common requirement is to add your logo to the top of the page. This was a little tricky to do in previous versions of GSP because you had to first figure out how the header was constructed, which code file to edit, and then make your change. Some changes required editing C# code and recompiling due to the use of server controls. Now all you have to do is edit the header UI template. Let’s say we want to replace the title with a logo, like this:

uitmpl_ex_logo1

Go to the UI Templates page and choose Header from the gallery item dropdown. We want to preserve the original header template in case we want to revert to it, so select Copy as new, enter a name, then click Save:

uitmpl_ex_logo2

Now go back to the default header template by selecting Default from the Name dropdown. Click the Target Albums tab and uncheck the albums. Save. This forces the new template to take over for all albums.

uitmpl_ex_logo3

Return to the new template and activate the HTML tab. Scroll down until you find the place where the title is rendered:

{{if Settings.Title}}
 <p class='gsp_bannertext'>
 {{if Settings.TitleUrl}}<a title='{{:Settings.TitleUrlTt}}' href='{{:Settings.TitleUrl}}'>{{:Settings.Title}}</a>{{else}}{{:Settings.Title}}{{/if}}</p>
{{/if}}

Replace this section with an img tag that points to your logo and save:

<img src='http://www.galleryserverpro.com/images/gsp_logo_346x80.png' />

That’s it! The logo now appears in the top left corner as shown in the image shown earlier.

The client data model

Each page contains a rich set of data that is included with the browser request. A global JavaScript variable named gspData exists that is scoped to the current gallery user control instance. In a default installation, you can find it at window.gsp_g.gspData, as seen in this image from Chrome:

uitmpl_api2

Let’s take a brief look at each top-level property:

ActiveGalleryItems – An array of GalleryItem instances that are currently selected. A gallery item can represent an album or media object (photo, video, audio, document, YouTube snippet, etc.)

ActiveMetaItems – An array of MetaItem instances describing the currently selected item(s).

Album – Information about the current album. It has two important properties worth explaining: GalleryItems and MediaItems. Both represent albums and media objects, but a GalleryItem instance contains only basic information about each item while a MediaItem instance contains all the metadata and other details about an item. Because a GalleryItem instance is lightweight, it is well suited for album thumbnail views where you only need basic information. Therefore, to optimize the data passed to the browser, the MediaItems property is null when viewing an album and the GalleryItems property is null when viewing a single media object.

App – Information about application-wide settings, such as the skin path and current URL.

MediaItem – Detailed information about the current media object. Has a value only when viewing a single media object.

Resource – Contains language resources.

Settings – Contains gallery-specific settings.

User – Information about the current user.

Here is a complete list of client objects and their properties (click the first image for a larger version):

uitmpl_api3a

uitmpl_api3b

Most properties are self-explanatory, but a few can use explanation:

Album.SortById – An integer that indicates which metadata field the album is sorted by. It maps to the enumeration values of MetadataItemName. The ID values are listed in the table on the Metadata page in the site admin area.

Album.VirtualType – An integer that indicates the type of the current album. It maps to the enumeration values of VirtualAlbumType (NotSpecified=0, NotVirtual=1, Root=2, Tag=3, People=4, Search=5)

MediaItem.Index - The one-based index of this media object among the others in the containing album.

MediaItem.Views and GalleryItem.Views – The Views property is an array of DisplayObject instances. Each DisplayObject represents a thumbnail, optimized, or original view of a media object or album.

MediaItem.ViewIndex and GalleryItem.ViewIndex – The zero-based index of the view currently being rendered in the browser. This value can be used to access the matching view in the Views property. The thumbnail is always be at index 0.

DisplayObject.HtmlOutput – The HTML for the display object. For thumbnails this is a <img> tag; the optimized/original HTML is generated from the media template defined for this MIME type on the Media Templates page in the site admin area.

DisplayObject.ScriptOutput – The JavaScript to execute to help render the display object. For example, it may contain the script necessary to initialize and run FlowPlayer for a Flash video. This property is populated from the script defined for this MIME type on the Media Templates page in the site admin area.

DisplayObject.Url – An URL that links directly to the media object file. For example, this value can be assigned to the src attribute of an img, video, or audio tag.

DisplayObject.ViewSize – This is an integer that maps to the enumeration values of DisplayObjectType (Unknown=0, Thumbnail=1, Optimized=2, Original=3, External=4).

DisplayObject.ViewType – An integer that maps to the enumeration values of MimeTypeCategory (NotSet=0, Other=1, Image=2, Video=3, Audio=4). For example, imagine a video MediaItem or GalleryItem. The Views property will have two or three DisplayObject instances (three if a web-optimized version has been created; otherwise two).The DisplayObject instance having ViewSize=1 represents the thumbnail image, so you can expect the ViewType to be 2 (image). But the remaining DisplayObject instances will have ViewType=3 (video).

MetaItem.GTypeId – Indicates the kind of gallery object the meta item describes. It is an integer that maps to the GalleryObjectType enumeration (None=0, All=1, MediaObject=2, Album=3, Image=4, Audio=5, Video=6, Generic=7, External=8, Unknown=9). Typically the client will never have the values of None (0), All (1), or Unknown (9).

The default UI templates use this data model, so they are an excellent place to look for examples of how to do things. It can be a little tricky, though, because much of the data access is done within the jQuery plug-ins.

UI Template examples

To get you up and running quickly, let’s look at a few examples. Paste these samples into a template to see it in action.

Show username or login link

<p>{{if User.IsAuthenticated}}
 Welcome, {{:User.UserName}}
{{else}}
 <a href='{{:App.CurrentPageUrl}}?g=login'>Log In</a>
{{/if}}
</p>

Determine if user has permission to delete media objects in the current album

<p>You {{if !Album.Permissions.DeleteMediaObject}}do not{{/if}} have permission to delete media objects in the album '{{:Album.Title}}'.</p>

Show the number of albums and media objects in the current album

<p>This album contains {{:Album.NumAlbums}} child albums and {{:Album.NumMediaItems}} media objects ({{:Album.NumGalleryItems}} total).</p>

Show a bulleted list of hyperlinked album and media object titles

<ul>
 {{for Album.GalleryItems}}
 <li>
   <a href='{{: ~getGalleryItemUrl(#data) }}'>{{:Title}} ({{getItemTypeDesc:ItemType}})</a>
 </li>
 {{/for}}
</ul>

This example shows a few interesting things:

  • for loop – The template loops through each gallery item of the album. Inside the loop the data context changes to that of the GalleryItem instance. For example, the {{:Title}} refers to the title of the gallery item being iterated on.
  • getGalleryItemUrl  – The call to getGalleryItemUrl is a jsRender helper function defined in gallery.js. Helper functions are useful when you need some JavaScript to figure out how to render something, like here to help calculate the URL to the album or media object. You can define your own helper function in the window.Gsp.Init function of gallery.js.
  • #data – A reference to the currently scoped data instance is passed to getGalleryItemUrl. In this example it is an instance of GalleryItem. You can refer to parent data items with the parent keyword. For example, using {{:#parent.parent.data.Album.Title}} from inside the GalleryItems loop gets the current album title by navigating up to the root data structure and then back down to the current Album title.
  • jsRender converter – The string {{getItemTypeDesc:ItemType}} says to run the ItemType property through a jsRender converter named getItemTypeDesc. A converter takes a property as input and massages it in some way. For example, you might want to do this to format a date/time value. The converter in this example converts an integer to a description such as Image, Album, etc. It is defined in gallery.js and, as with helper functions, you can define you own.

Note: This example requires that the GalleryItems property of the Album is populated. We discussed earlier that this property will have a value when viewing an album but is null when viewing an individual media object. To get this to work when a single media object is visible, change GalleryItems to MediaItems in the template.

Note: In 3.0.0 getGalleryItemUrl and getItemTypeDesc are defined in the gspThumbnails plug-in, so they are only available when that plug-in is invoked (as it is when viewing album thumbnails). In 3.0.1, I intend to refactor these to the generic page load function window.Gsp.Init in gallery.js, making them available to the entire page, regardless of which templates are visible.

 

Add image preview on thumbnail hover

When you hover over a thumbnail image, you see a larger version of the image appear in a popup that disappears automatically when you move the cursor away:

uitmpl_ex_img_pvw

This example requires adding to both the HTML and JavaScript album UI template. Open the album UI template and insert this HTML as the first line in the HTML tab:

<div id='pvw' style='display:none;'><img id='imgpvw' style='width:400px;'></div>

Then add this JavaScript to the end of the script in the JavaScript tab:

$('#pvw').dialog({
 appendTo: $('.gsp_floatcontainer'),
 autoOpen: false,
 position: { my: "left top", at: "left center" } 
});

$('.thmb[data-it=' + window.Gsp.Constants.ItemType_Image + '] .gsp_thmb_img').hover(
 function () {
  $('#imgpvw').prop('src', this.src.replace('dt=1', 'dt=2'));
  $('#pvw').dialog("option", "width", 425)
   .dialog( "option", "position", { my: "left+30 top+20", at: "right bottom", of: $(this)} )
   .dialog('open');
 }
);

$('.gsp_floatcontainer', $('#{{:Settings.ClientId}}')).mouseleave(function() {
  $('#pvw').dialog('close');
});

It works by opening a jQuery UI dialog window on the hover event of the thumbnail image.

 

Add virtual album links

Enhance the left pane by adding some links to popular tags or people in your gallery:

uitmpl_ex_tags

This is easily accomplished by adding the following to the end of the HTML text for the left pane UI template:

<p style='color:#B2D6A2;' class='gsp_addtopmargin5 gsp_addleftmargin4'>TAGS</p>
<div class='gsp_addleftmargin10'>
 <p><a href='{{:App.CurrentPageUrl}}?tag=Party'>Party</a></p> 
 <p><a href='{{:App.CurrentPageUrl}}?tag=Vacation'>Vacation</a></p>
 <p><a href='{{:App.CurrentPageUrl}}?tag=Family'>Family</a></p>
</div>

<p style='color:#B2D6A2;' class='gsp_addtopmargin5 gsp_addleftmargin4'>PEOPLE</p>
<div class='gsp_addleftmargin10'>
 <p><a href='{{:App.CurrentPageUrl}}?people=Roger%20Martin%2bMargaret'>Roger & Margaret</a></p>
 <p><a href='{{:App.CurrentPageUrl}}?people=Margaret'>Margaret</a></p>
 <p><a href='{{:App.CurrentPageUrl}}?people=Skyler'>Skyler</a></p>
</div>

When creating the hyperlink, notice spaces are encoded with %20 (e.g. Roger%20Martin). To create a link for multiple tags, separate them with %2b (an encoded + sign). The gallery does not support searching for both tags and people at the same time.

 

Show last 5 objects added in album

Show the five most recently added items in an album at the top of the center pane:

uitmpl_ex_last5

Before editing the template, go to the Metadata page and make sure the DateAdded property is visible for albums and media objects. If not, make it visible and click the rebuild action.

Open the album UI template and look for the following text in the HTML tab:

<div class='gsp_floatcontainer'>
Paste this text just before it:

<div id='divRecent' class='gsp_floatcontainer'></div>
Now paste the following at the end of the script on the JavaScript tab:

var getUrl = function(gItem) {
 var qs = { aid: gItem.IsAlbum ? gItem.Id : null, moid: gItem.IsAlbum ? null : gItem.Id };

 if (gItem.IsAlbum) {
 // Strip off the tag and people qs parms for albums if present, since we want them to link directly to the album.
 // We don't do this for media objects so they can be browsed within the context of their tag/people.
 qs.tag = null;
 qs.people = null;
 qs.search = null;
 }

  return Gsp.GetUrl(document.location.href, qs);
};

$.ajax({
  type: "GET",
  url: window.Gsp.AppRoot + '/api/albums/' + window.{{:Settings.ClientId}}.gspData.Album.Id + '/galleryitems/?sortByMetaNameId=111&sortAscending=false',
  dataType: 'json',
  success: function (galleryItems) {
   galleryItems.splice(5); // Keep the first 5 elements; get rid of the rest
   var html = "<p style='color:#B2D6A2;' class='gsp_addleftmargin2'>LAST 5 ITEMS</p><div class='gsp_addleftmargin5'";
   $.each(galleryItems, function(idx, galleryItem) {
    html += "<p><a href='" + getUrl(galleryItem) + "'>" + galleryItem.Title + "</a></p>";
   });
  html += "</div>";
  $('#divRecent').html(html);
  },
  error: function (response) {
    $.gspShowMsg("Action Aborted", response.responseText, { msgType: 'error', autoCloseDelay: 0 });
  }
});

What we’re doing here is first adding a div tag to receive the HTML we generate. In the JavaScript we make a call to the server to retrieve the items in this album sorted in descending order on the DateAdded field (sortByMetaNameId=111). You can see the ID values of other fields on the Metadata page.

When the server returns the data (which is an array of GalleryItem instances), we get rid of everything except the first 5 items, then we iterate through them and build up an HTML string. Finally we append the HTML to the div tag, resulting in it being shown on the screen.

NOTE: When you request a sorted list as shown above, the gallery tries to be smart and saves this sort preference in your profile, causing the album to be sorted on this field whenever you view it. This is probably not what you want. I intend to change this behavior in a future version. Meanwhile, if you don’t mind editing the source code you can stop this behavior by commenting out the call to the PersistUserSortPreference function in GalleryObjectController.GetGalleryItemsInAlbum().

What if you wanted to include the date added property in the HTML? For example, instead of the title ‘Road to nowhere’, you have ‘Road to nowhere (Added 2013-08-27)’. Unfortunately, the AJAX method we are using returns an array of GalleryItem instances, which is a lightweight data structure that doesn’t have any metadata in it. Refer to the screenshot earlier in this post to see its properties. What we really need is an array of MediaItem properties, which includes the metadata. And there is an API call we can make to get that data. It looks like this:

/api/albums/22/mediaitems/

The 22 is the album ID. But there’s a problem. Version 3.0.0 does not allow us to request a custom sort through this method, so we just get the items in the same order as they are shown in the album view. I intend to fix this in a future version.

AJAX API calls

The previous example made a call to the web server to retrieve data. There are a number of API calls available for reading and writing data in the gallery. Here’s a brief overview:

HTTP Method URL Description
ALBUMS
GET /api/albums/{AlbumId}/inflated Returns a GalleryData instance. Album.GalleryItems will be populated; Album.MediaItems will be null. Optional parms: top (int), skip (int) Can be used for paged results. Example: api/albums/22/inflated/?top=5&skip=5 returns the 6th – 10th gallery items in album 22.
GET /api/albums/{AlbumId}/galleryitems Returns an array of GalleryItem instances representing the albums and media objects in the album. Optional parms: sortByMetaNameId (int), sortAscending (bool) Can be used to return items in the album in a custom sort. Note that 3.0.0 will cause the user’s profile to be updated with this sort preference for this album. Example: /api/albums/22/galleryitems/?sortByMetaNameId=111&sortAscending=false returns the items in album 22 sorted in descending order on the DateAdded metadata property.
GET /api/albums/{AlbumId}/mediaitems Returns an array of MediaItem instances representing the albums and media objects in the album. Optional parms: none
GET /api/albums/{AlbumId}/meta Returns an array of MetaItem instances representing the metadata for the album. Optional parms: none
POST /api/albums Updates a limited set of properties for the album: DateStart, DateEnd, SortByMetaName, SortAscending, IsPrivate, Owner
POST /api/albums/{AlbumId}/sortalbum?sortbyMetaNameId={{MetaNameId}}&sortAscending={{true|false}} Resort the items in the album and persist to the database. No value is returned. Perform a GET to retrieve the sorted items.
POST /api/albums/{AlbumId}/getsortedalbum Resort the items in the album but DO NOT persist to the database. Requires an instance of AlbumAction to be POSTed. See example in gallery.js.
DELETE /api/albums/{AlbumId} Deletes the specified album, including the media files and directory.
MEDIA
GET api/mediaitems/{MediaObjectId}/inflated Returns a GalleryData instance. The MediaItem property contains data for the specified media item. The Album.MediaItems property contains data for the remaining items in the album. Album.GalleryItems is null.
GET api/mediaitems/{MediaObjectId}/meta Returns an array of MetaItem instances belonging to the specified media object.
POST /api/mediaitems/createfromfile Adds a media file to an album. Prior to calling this method, the file should exist in App_Data\_Temp. Requires an instance of AddMediaObjectSettings to be POSTed. See example usage in gs\pages\task\addobjects.ascx.
PUT /api/mediaitems/ Persists changes to the database about the MediaItem instance PUT to the method. Current implementation saves the title only and requires that the media item exist.
DELETE /api/mediaitems/{MediaObjectId} Permanently deletes the media object from the file system and data store.
META ITEMS
GET /api/meta/tags/?galleryId={GalleryId} Gets an array of Tag instances containing all tags used in the specified gallery. Optional parms: q (string) Specifies a search string to filter the tags by. Example: /api/meta/tags/?galleryId=1&q=bob returns all tags with the text bob in them.
GET /api/meta/people/?galleryId={GalleryId} Gets an array of Tag instances containing all people tagged in the specified gallery. Optional parms: q (string) Specifies a search string to filter the tags by. Example: /api/meta/tags/?galleryId=1&q=bob returns all tags with the text bob in them
POST /api/meta/rebuildmetaitem?metaNameId={MetaNameId}&galleryId={GalleryId} Rebuild the data entries for the specified meta property for all media items in the gallery.
PUT /api/meta/ Persists the POSTed MetaItem instance to the database.
SYNCHRONIZATION
GET /api/task/startsync/?albumId={AlbumId}&isRecursive={true|false}&rebuildThumbnails={true|false}&rebuildOptimized={true|false}&password={Password} Begins a synchronization. Requires enabling the remote sync option on the Admin page in the site admin area.
GET /api/task/statussync/?id={GalleryId} Gets the status of the synchronization. Requires an HTTP header variable named X-ServerTask-TaskId to be set to the sync task ID. See example on gs/pages/task/synchronize.ascx
GET /api/task/abortsync/?id={GalleryId} Cancels a synchronization. Requires an HTTP header variable named X-ServerTask-TaskId to be set to the sync task ID. See example on gs/pages/task/synchronize.ascx
POST /api/task/startsync/ Begins a synchronization. Requires an instance of SyncOptions to be POSTed and an HTTP header variable named X-ServerTask-TaskId to be assigned a value. The sync page uses this method to start a sync.
MISCELLANEOUS
GET /api/task/purgecache/ Purges the cache on the web server. The next HTTP request will retrieve data from the database.
POST /api/task/logoff/ Logs off the current user

Friday, June 14, 2013

Using Active Directory with GSP 3.0

Some users have reported trouble integrating 3.0 with their Active Directory architecture. Over the last couple of days I had time to fire up my AD virtual machines and dig into this. I have good news and bad news. The good news is that integration is still possible. The bad news is that there are extra steps to make it happen.

I updated the 3.0 Quick Start Guide to include step by step instructions for integrating AD with your gallery. This information will eventually be in the full Admin Guide. When it is complete, the quick start guide will be discontinued.

So what happened?

The additional difficulty in getting GSP 3.0 working with AD comes down to two issues outside of my control:

  • The new role provider is not compatible with Active Directory.
  • The .NET Users applet in IIS Manager does not work with .NET 4.0 or higher.

I came up with workarounds for both issues. It was a pain in the rear figuring out the steps and you’ll do a bit of grumbling implementing them, but in the end they work. To add insult to injury, when I tried to report the role provider incompatibility to Microsoft, I couldn’t find the right place to do it on MS Connect. I’ll have to look harder when I have some extra time – they need to know about it.

There is a silver lining. During my investigation I think I figured out a way to streamline the integration in the future. A lot of the extra work involves adding the first AD account to the System Administrator role so that you can log into the gallery’s admin functions. I can write a function that does this programmatically from code, so it may be possible to temporarily add some kind of trigger (e.g. app setting in web.config) that does this for you.

Also, I should be able to tweak the Manage Users page so that you can edit the role membership of users without requiring the application to have edit permission to AD. Look for these improvements in one of the next releases.