jQuery.telligent.evolution.ui.components.theater
UI Component which handles automatic setup of content theaters. Transforms <span class="ui-theater"></span>
elements. The default implementation uses the evolutionTheater plugin. Overrides can be provided at the theme level to present theaters differently, either by different usage of evolutionTheater plugin or by an entirely different theater UI.
Used by:
This component is primarily used by rendered activity stories provided by IActivityStory plugins or rendered search results provided by ISearchableContentType which also implement ajax callbacks via IHttpCallback for providing theater content.
Next/Previous links
The evolutionTheater plugin provides the optional function parameters nextContent
and previousContent
which, when provided, instruct the plugin to render forward/back links and asynchronously return content to replace in the theater when those links are returned. The implementation of how this content is produced is provided by the plugin's consumer.
The default jQuery.telligent.evolution.ui.components.theater
implementation produces content for these function parameters via ajax requests to endpoints for forward/back theater content. The URLs it uses for endpoints are provided by the current theater content. If the theater content contains an anchor
element with the data-nexttheaterurl
and/or data-previoustheaterurl
, then previousContent
and/or nextContent
will be set on jQuery.evolutionTheater
with functions which perform ajax requests against these data attributes' values. The same process is followed for subsequently-loaded theater content. In this way, theater content is responsible for identifying what other items may be navigated to from itself.
Comments
The default ui component implementation also bundles automatic support for adding content comments when the theater content contains comment-related markup.
Options
Data made available to instances of the component:
theaterurl
: Ajax endpoint which returns rendered content to be shown within a theaterdisabletheater
: When true, theater does not renderminwidth
: Minimum window width in pixels at which theaters render default: 600
Example
Given the markup...
<span class="ui-theater" data-theaterurl="[AJAX ENDPOINT]">View a Preview</span>
... and an ajax endpoint which returns an html fragment ...
<div>
<!-- some preview image -->
<img src="..." />
</div>
to show within the theater, the UI component automatically handles click events and renders a theater with content from the ajax endpoint using jQuery.evolutionTheater.
If the theater callback contains more content such as the following, it will automatically make use of pre-defined styling. Note that this is not required, but the default jQuery.telligent.evolution.ui.components.theater
component implementation can make better use of content in this format.
Note the inclusion of data-previoustheaterurl
and data-nexttheaterurl
to instruct the default jQuery.telligent.evolution.ui.components.theater
component implementation to provide previousContent
and nextContent
ajax request function wrapper parameters to the jQuery.evolutionTheater plugin.
<div class="post-attachment-viewer">
<!-- some preview image -->
<img src="..." />
</div>
<div class="activity-story"
data-contentid="[CONTENT ID]"
data-contenttypeid="[CONTENT TYPE ID]">
<div class="full-post-header activity">
<a href="#"
data-previoustheaterurl="[PREVIOUS THEATER CONTENT AJAX ENDPOINT]">
Previous
</a>
<a href="#"
data-nexttheaterurl="[NEXT THEATER CONTENT AJAX ENDPOINT]">
Next
</a>
</div>
<div class="full-post activity">
<div class="post-author activity">
<span class="avatar">
<a href="[AUTHOR PROFILE URL]" class="internal-link view-user-profile">
<img src="[AUTHOR AVATAR]" border="0" />
</a>
</span>
</div>
<div class="post-content activity">
<div class="activity-summary">
<span class="user-name">
<a href="[AUTHOR PROFILE URL]"
class="internal-link view-user-profile">
<span></span>[AUTHOR DISPLAY NAME]
</a>
</span> in
<a href="[CONTENT CONTAINER URL]"><span></span>[CONTENT CONTAINER NAME]</a>
</div>
<div class="activity-content">
<a href="[CONTENT URL]" class="internal-link view-post">
<span></span>[CONTENT NAME]
</a>
<a href="[CONTENT APPLICATION URL]" class="internal-link view-application">
<span></span>[CONTENT APPLICATION NAME]
</a>
</div>
</div>
</div>
<div class="post-actions activity">
<div class="navigation-list-header"></div>
<ul class="navigation-list">
<li class="navigation-item">
<a class="internal-link comment" href="#">
<span></span>Comment
</a>
</li>
<li class="navigation-item">
<a class="internal-link view-post" href="[ACTIVITY STORY PERMALINK URL]">
<span class="post-date">9 hours ago</span>
</a>
</li>
</ul>
<div class="navigation-list-footer"></div>
</div>
</div>
If the <div class="activity-story">
element also contains comment markup, the default implementation of jQuery.telligent.evolution.ui.components.theater
will decorate it to be active.
<div class="content-list-header comments"></div>
<div class="content-list-name comments"></div>
<ul class="content-list comments">
<li class="content-item comment comment-form">
<div class="field-list-header"></div>
<fieldset class="field-list">
<ul class="field-list">
<li class="field-item">
<span class="field-item-input">
<span class="avatar">
<img src="" />
</span>
<textarea placeholder="Write a comment..." maxlength="512" rows="1"></textarea>
</span>
</li>
</ul>
</fieldset>
<div class="field-list-footer"></div>
</li>
</ul>
<div class="content-list-footer comments"></div>
Default Implementation
For reference purposes or as the basis for an override:
(function($){
var emptyTypeId = '00000000-0000-0000-0000-000000000000',
CommentService = (function(){
return {
add: function(contentId, contentTypeId, typeId, body, success, fail) {
var data = {
ContentId: contentId,
ContentTypeId: contentTypeId,
Body: body
};
if(typeof typeId !== 'undefined' && typeId !== null) {
data.CommentTypeId = typeId;
}
$.telligent.evolution.post({
url: $.telligent.evolution.site.getBaseUrl() + 'api.ashx/v2/comments.json',
data: data,
cache: false,
success: success,
error: fail
});
},
del: function(commentId, success, fail) {
$.telligent.evolution.del({
url: $.telligent.evolution.site.getBaseUrl() + 'api.ashx/v2/comments/{CommentId}.json',
data: {
CommentId: commentId
},
cache: false,
success: success,
error: fail
});
}
};
}());
var loadUrl = function(url, complete) {
$.telligent.evolution.get({
url: url,
cache: false,
success: function(response) {
complete(response);
}
});
},
initLikeAdjustments = function(context) {
var storyLikeMessageItem = context.content.find('.content-item.action.likes');
$.telligent.evolution.messaging.subscribe('ui.like', function(data){
if(data.contentId === context.contentId && ((typeof context.typeId === 'undefined' || context.typeId === '' || context.typeId === emptyTypeId) || data.typeId === context.typeId)) {
if(data.count > 0) {
storyLikeMessageItem.removeClass('without-likes').addClass('with-likes');
} else {
storyLikeMessageItem.removeClass('with-likes').addClass('without-likes');
}
} else {
var likedComment = context.content.find('li.comment[data-commentid="' + data.contentId + '"][data-contenttypeid="' + data.contentTypeId + '"]');
if(data.count > 0) {
likedComment.addClass('with-likes');
} else {
likedComment.removeClass('with-likes');
}
}
});
},
initCommenting = function(context) {
context.content.find('.internal-link.comment').bind('click',function(e){
e.preventDefault();
context.content.find('.comment-form').show().find('textarea').focus();
});
context.content.off('focus', '.comment-form textarea').on('focus', '.comment-form textarea', function(e) {
context.content.find('.comment-form').addClass('with-avatar');
var ta = $(e.target);
// when a text area is focused, set up its composer and set up handlers for submitting its results
if(!ta.data('theater_composer_inited')) {
ta.data('theater_composer_inited', true);
ta.evolutionComposer({
plugins: ['mentions','hashtags']
});
ta.evolutionComposer('onkeydown', function(e){
if (e.which === 13)
{
var body = $(e.target).evolutionComposer('val');
if($.trim(body).length > 0) {
context.content.find('.comment-form textarea').attr('disabled',true).blur();
CommentService.add(context.contentId, context.contentTypeId, context.typeId, body,
function(response) { // success
$.evolutionTheater.refresh(mergeComments);
$.telligent.evolution.messaging.publish('activity.commentadded',response);
},
function(response) { // error
context.content.find('.comment-form textarea')
.evolutionComposer('val','')
.trigger('keydown') // to trigger autoresize to collapse
.trigger('keyup') // to trigger autoresize to collapse
.removeAttr('disabled')
.closest('.comment-form')
.removeClass('with-avatar');
});
}
}
return true;
});
}
});
context.content.delegate('.comment-form textarea', 'blur', function(e) {
context.content.find('.comment-form').removeClass('with-avatar');
});
},
initConversation = function(context) {
context.content.find('.internal-link.start-conversation').bind('click',function(e) {
e.preventDefault();
var conversationUrl = $(e.target).data('conversationurl');
$.glowModal(conversationUrl, { width: 550, height: 360 });
});
},
mergeComments = function(container, newContent) {
var currentReplies = $('<div></div>').html(newContent).find('ul.content-list.comments');
container.find('ul.content-list.comments').replaceWith(currentReplies);
},
initCommentModeration = function(context) {
context.content.find('.content-item.comment').bind('evolutionModerateLinkClicked', function(e, link) {
var commentId = $(this).data('commentid');
CommentService.del(commentId,
function(response) { // success
$.evolutionTheater.refresh(mergeComments);
$.telligent.evolution.messaging.publish('activity.commentremoved',response);
},
function(response) { // failure
});
});
},
buildContext = function(content) {
var content = $(content),
story = content.find('.activity-story'),
context = {
content: content,
story: story
};
if(story !== null) {
$.extend(context, {
contentId: story.data('contentid'),
contentTypeId: story.data('contenttypeid'),
typeId: story.data('typeid')
});
}
return context;
};
$.telligent.evolution.ui.components.theater = {
setup: function() {
},
// set up instances of theaters using ajax-loaded callbacks for content
add: function(elm, options) {
elm = $(elm);
var inited = false;
elm.bind('click', function(e){
if (elm.data('disabletheater') === true) {
return;
}
e.preventDefault();
if($(window).width() < (options.minwidth || 600))
return;
$.evolutionTheater.show({
content: function(complete) {
loadUrl(options.theaterurl, complete);
},
nextContent: function(content) {
var nextUrl = content.find('a[data-nexttheaterurl]').data('nexttheaterurl');
if(nextUrl && nextUrl.length > 0) {
return function(complete) {
loadUrl(nextUrl, complete);
}
}
},
previousContent: function(content) {
var previousUrl = content.find('a[data-previoustheaterurl]').data('previoustheaterurl');
if(previousUrl && previousUrl.length > 0) {
return function(complete) {
loadUrl(previousUrl, complete);
}
}
},
loaded: function(content) {
var context = buildContext(content);
initLikeAdjustments(context);
initCommenting(context);
initConversation(context);
initCommentModeration(context);
}
});
});
}
};
}(jQuery));