(function (context, $) { "use strict"; context.JK = context.JK || {}; context.JK.Feed = function (app) { var logger = context.JK.logger; var rest = new context.JK.Rest(); var EVENTS = context.JK.EVENTS; var ui = new context.JK.UIHelper(JK.app); var recordingUtils = context.JK.RecordingUtils; var userId = null; var currentFeedPage = 0; var feedBatchSize = 10; var $screen = null; var $scroller = null; var $content = null; var $noMoreFeeds = null; var $refresh = null; var $sortFeedBy = null; var $includeDate = null; var nextPage = 1; var $includeType = null; var didLoadAllFeeds = false, isLoading = false; function defaultQuery() { var query = { limit: feedBatchSize }; if(nextPage) { query.next_page = nextPage; } if(userId) { query.user = userId; } return query; } function buildQuery() { var currentQuery = defaultQuery(); // specify search criteria based on form currentQuery.sort = $sortFeedBy.val(); currentQuery.time_range = $includeDate.val(); currentQuery.type = $includeType.val(); return currentQuery; } function clearResults() { didLoadAllFeeds = false; currentFeedPage = 0; $content.empty(); // TODO: do we need to delete audio elements? $noMoreFeeds.hide(); nextPage = 1; } function handleFeedResponse(response) { nextPage = response.next_page; renderFeeds(response); if(nextPage == null) { didLoadAllFeeds = true; // if we got less results than asked for, end searching logger.debug("end of feeds") if(currentFeedPage == 0 && response.entries.length == 0) { $content.append("
This user has no history.
") ; } if(currentFeedPage > 0) { $noMoreFeeds.show(); } } else { currentFeedPage++; } } function setUser(_userId) { userId = _userId; } function refresh() { clearResults(); populate(); } function setLoading(yn) { isLoading = yn; if (yn) { $('#'+screenID()+'-loading-feeds').show(); } else { $('#'+screenID()+'-loading-feeds').hide(); } } function populate() { if (isLoading || didLoadAllFeeds) return; setLoading(true); rest.getFeeds(buildQuery()) .done(function(response) { handleFeedResponse(response); setLoading(false); }) .fail(function(jqXHR) { setLoading(false); app.notifyServerError(jqXHR, 'Feed Unavailable') }); } function toggleSessionDetails() { var $detailsLink = $(this); var $feedItem = $detailsLink.closest('.feed-entry'); var $musicians = $feedItem.find('.musician-detail'); var $description = $feedItem.find('.description'); var toggledOpen = $detailsLink.data('toggledOpen'); if(toggledOpen) { $feedItem.css('height', $feedItem.height() + 'px') $feedItem.animate({'height': $feedItem.data('original-max-height')}).promise().done(function() { $feedItem.css('height', 'auto').css('max-height', $feedItem.data('original-max-height')); $musicians.hide(); $description.css('height', $description.data('original-height')); $description.dotdotdot(); }); } else { $description.trigger('destroy.dot'); $description.data('original-height', $description.css('height')).css('height', 'auto'); $musicians.show(); $feedItem.animate({'max-height': '1000px'}); } toggledOpen = !toggledOpen; $detailsLink.data('toggledOpen', toggledOpen); return false; } function startSessionPlay($feedItem) { var img = $('.play-icon', $feedItem); var $controls = $feedItem.find('.session-controls'); img.attr('src', '/assets/content/icon_pausebutton.png'); $controls.trigger('play.listenBroadcast'); $feedItem.data('playing', true); } function stopSessionPlay($feedItem) { var img = $('.play-icon', $feedItem); var $controls = $feedItem.find('.session-controls'); img.attr('src', '/assets/content/icon_playbutton.png'); $controls.trigger('pause.listenBroadcast'); $feedItem.data('playing', false); } function toggleSessionPlay() { var $playLink = $(this); var $feedItem = $playLink.closest('.feed-entry'); var $status = $feedItem.find('.session-status') var playing = $feedItem.data('playing'); if(playing) { $status.text('SESSION IN PROGRESS'); stopSessionPlay($feedItem); } else { startSessionPlay($feedItem); } return false; } function stateChangeSession(e, data) { var $controls = data.element; var $feedItem = $controls.closest('.feed-entry'); var $status = $feedItem.find('.session-status'); if(data.displayText) $status.text(data.displayText); if(data.isEnd) stopSessionPlay($feedItem); if(data.isSessionOver) { $controls.removeClass('inprogress').addClass('ended') } } function startRecordingPlay($feedItem) { var img = $('.play-icon', $feedItem); var $controls = $feedItem.find('.recording-controls'); img.attr('src', '/assets/content/icon_pausebutton.png'); $controls.trigger('play.listenRecording'); $feedItem.data('playing', true); } function stopRecordingPlay($feedItem) { var img = $('.play-icon', $feedItem); var $controls = $feedItem.find('.recording-controls'); img.attr('src', '/assets/content/icon_playbutton.png'); $controls.trigger('pause.listenRecording'); $feedItem.data('playing', false); } function toggleRecordingPlay() { var $playLink = $(this); var $feedItem = $playLink.closest('.feed-entry'); var playing = $feedItem.data('playing'); if(playing) { stopRecordingPlay($feedItem); } else { startRecordingPlay($feedItem); } return false; } function isOwner() { return userId == context.JK.currentUserId; } function screenID() { return $screen.attr('id'); } function obtainCandidate(recording) { if(isOwner()) { var candidate = null; context._.each(recording.claimed_recordings, function(claimedRecording) { if(claimedRecording.user_id == context.JK.currentUserId) { candidate = claimedRecording; return false; } }) if(!candidate) throw "unable to find candidate claimed recording, yet we can see this recording. server error..." return candidate; } else { return recording.claimed_recordings[0] } } function toggleOpen($feedItem, $name, $description, $musicians) { $description.trigger('destroy.dot'); $description.data('original-height', $description.css('height')).css('height', 'auto'); $name.trigger('destroy.dot'); $name.data('original-height', $name.css('height')).css('height', 'auto'); $musicians.show(); $feedItem.animate({'max-height': '1000px'}); } function toggleClose($feedItem, $name, $description, $musicians, immediate) { $feedItem.css('height', $feedItem.height() + 'px') $feedItem.animate({'height': $feedItem.data('original-max-height')}, immediate ? 0 : 400).promise().done(function() { $feedItem.css('height', 'auto').css('max-height', $feedItem.data('original-max-height')); $musicians.hide(); $description.css('height', $description.data('original-height')); $description.dotdotdot(); $name.css('height', $name.data('original-height')); $name.dotdotdot(); }); } function stateChangeRecording(e, data) { var $controls = data.element; var $feedItem = $controls.closest('.feed-entry'); var $sliderBar = $('.recording-position', $feedItem); var $statusBar = $('.recording-status', $feedItem); var $currentTime = $('.recording-current', $feedItem); var $status = $('.status-text', $feedItem); var $playButton = $('.play-button', $feedItem); if(data.isEnd) stopRecordingPlay($feedItem); if(data.isError) { $sliderBar.hide(); $playButton.hide(); $currentTime.hide(); $statusBar.show(); $status.text(data.displayText); } } function toggleRecordingDetails() { var $detailsLink = $(this); var $feedItem = $detailsLink.closest('.feed-entry'); var $musicians = $feedItem.find('.musician-detail'); var $description = $feedItem.find('.description'); var $name = $feedItem.find('.name'); var toggledOpen = $detailsLink.data('toggledOpen'); if(toggledOpen) { toggleClose($feedItem, $name, $description, $musicians) } else { toggleOpen($feedItem, $name, $description, $musicians) } toggledOpen = !toggledOpen; $detailsLink.data('toggledOpen', toggledOpen); return false; } function updateRecordingName($feedEntry, name) { $feedEntry.find('.name-text').text(name); } function updateRecordingDescription($feedEntry, description) { $feedEntry.find('.description').text(description); } function updateIsPublic($feedEntry, isPublic) { var $isPrivate = $feedEntry.find('.is_private') if(isPublic) { $isPrivate.removeClass('enabled') } else { if(!isOwner()) { $feedEntry.find('.play-button').hide(); // still show play button if private && isOwner() } $isPrivate.addClass('enabled') } } function updateComments($feedEntry, comments) { $feedEntry.find('.comments').html(comments) } function updatePlays($feedEntry, plays) { $feedEntry.find('.plays').html(plays); } function updateLikes($feedEntry, likes) { $feedEntry.find('.likes').html(likes); } function updateStats($feedEntry) { if($feedEntry.is('.recording-entry')) { var id = $feedEntry.attr('data-claimed-recording-id'); rest.getClaimedRecording(id) .done(function(claimedRecording) { updateComments($feedEntry, claimedRecording.recording.comment_count); updateLikes($feedEntry, claimedRecording.recording.like_count); updatePlays($feedEntry, claimedRecording.recording.play_count); }) .fail(app.ajaxError) } else { var id = $feedEntry.attr('data-music-session'); rest.getSessionHistory(id) .done(function(music_session) { updateComments($feedEntry, music_session.comment_count); updateLikes($feedEntry, music_session.like_count); updatePlays($feedEntry, music_session.play_count); }) .fail(app.ajaxError) } } function updateGenre($feedEntry, genre) { $feedEntry.find('.genre').text(context.JK.GenreSelectorHelper.getNameForId(genre)); } function renderFeeds(feeds) { $.each(feeds.entries, function(i, feed) { if(feed.type == 'music_session') { var options = { feed_item: feed, status_class: feed['is_over?'] ? 'ended' : 'inprogress', mount_class: feed['has_mount?'] ? 'has-mount' : 'no-mount' } var $feedItem = $(context._.template($('#template-feed-music-session').html(), options, {variable: 'data'})); var $controls = $feedItem.find('.session-controls'); // do everything we can before we attach the item to the page $('.timeago', $feedItem).timeago(); context.JK.prettyPrintElements($('.duration', $feedItem).show()); context.JK.setInstrumentAssetPath($('.instrument-icon', $feedItem)); $('.details', $feedItem).click(toggleSessionDetails); $('.details-arrow', $feedItem).click(toggleSessionDetails); $('.play-button', $feedItem).click(toggleSessionPlay); if (!feed.session_removed_at) { $('.btn-share', $feedItem).click(function() { ui.launchShareDialog(feed.id, 'session'); }); } else { $('.btn-share', $feedItem).hide(); } $('.btn-comment', $feedItem).click(function() { var result = ui.launchCommentDialog({ session_id: feed.id, entity_type: 'session' }).one(EVENTS.DIALOG_CLOSED, function() { updateStats($feedItem); }) }); $('.btn-like', $feedItem).click(function() { ui.addSessionLike(feed.id, JK.currentUserId, $('.likes', $feedItem), $('.btn-like', $feedItem)) }); // put the feed item on the page renderFeed($feedItem); // these routines need the item to have height to work (must be after renderFeed) $controls.listenBroadcast(); $controls.bind('statechange.listenBroadcast', stateChangeSession); $('.dotdotdot', $feedItem).dotdotdot(); $feedItem.data('original-max-height', $feedItem.css('height')); context.JK.bindHoverEvents($feedItem); context.JK.bindProfileClickEvents($feedItem); } else if(feed.type == 'recording') { if(feed.claimed_recordings.length == 0) { logger.error("a recording in the feed should always have one claimed_recording") return; } // pump some useful data about mixing into the feed item feed.mix_info = recordingUtils.createMixInfo({state: feed.mix_state}) var options = { feed_item: feed, candidate_claimed_recording: obtainCandidate(feed), mix_class: feed['has_mix?'] ? 'has-mix' : 'no-mix', } var $feedItem = $(context._.template($('#template-feed-recording').html(), options, {variable: 'data'})); var $controls = $feedItem.find('.recording-controls'); $controls.data('mix-state', feed.mix_info) // for recordingUtils helper methods $controls.data('server-info', feed.mix) // for recordingUtils helper methods $controls.data('view-context', 'feed') $('.timeago', $feedItem).timeago(); context.JK.prettyPrintElements($('.duration', $feedItem)); context.JK.setInstrumentAssetPath($('.instrument-icon', $feedItem)); $('.details', $feedItem).click(toggleRecordingDetails); $('.details-arrow', $feedItem).click(toggleRecordingDetails); $('.play-button', $feedItem).click(toggleRecordingPlay); updateIsPublic($feedItem, options.candidate_claimed_recording.is_public); $('.btn-share', $feedItem).click(function() { ui.launchShareDialog(options.candidate_claimed_recording.id, 'recording'); }); $('.btn-comment', $feedItem).click(function() { ui.launchCommentDialog({ recording_id: feed.id, claimed_recording_id: options.candidate_claimed_recording.id, entity_type: 'recording' }) .one(EVENTS.DIALOG_CLOSED, function() { updateStats($feedItem); }); }); $('.btn-like', $feedItem).click(function() { ui.addRecordingLike(feed.id, options.candidate_claimed_recording.id, JK.currentUserId, $('.likes', $feedItem), $('.btn-like', $feedItem)); }); if(isOwner()) { $('.edit-recording-dialog', $feedItem).data('claimed_recording_id', options.candidate_claimed_recording.id).click(function() { app.layout.showDialog('edit-recording', {d1: $(this).data('claimed_recording_id')}) .one(EVENTS.DIALOG_CLOSED, function() { $(this).unbind('recording_updated').unbind('recording_deleted'); }) .one('recording_updated', function(e, data) { // find recording by claimed recording id var $feedEntry = $screen.find('.feed-entry.recording-entry[data-claimed-recording-id="'+ data.id +'"]'); var $musicians = $feedEntry.find('.musician-detail'); var $description = $feedEntry.find('.description'); var $name = $feedEntry.find('.name'); var $detailsLink = $feedEntry.find('.details'); var toggledOpen = $detailsLink.data('toggledOpen'); if(toggledOpen) { toggleClose($feedEntry, $name, $description, $musicians, true); } $description.trigger('destroy.dot'); $name.trigger('destroy.dot'); updateRecordingName($feedEntry, data.name); updateRecordingDescription($feedEntry, data.description); updateIsPublic($feedEntry, data.is_public); updateGenre($feedEntry, data.genre); $name.dotdotdot(); $description.dotdotdot(); $feedItem.data('original-max-height', $feedEntry.css('height')); $detailsLink.data('toggledOpen', false); }) .one('recording_deleted', function(e, data) { var $feedEntry = $screen.find('.feed-entry.recording-entry[data-claimed-recording-id="'+ data.id +'"]'); $feedEntry.remove(); }) return false; }).show(); } context.JK.helpBubble($feedItem.find('.help-launcher'), recordingUtils.onMixHover, {}, {width:'450px', closeWhenOthersOpen: true, positions:['top', 'left', 'bottom', 'right'], offsetParent: $screen.parent()}) // put the feed item on the page renderFeed($feedItem); // these routines need the item to have height to work (must be after renderFeed) $controls.listenRecording({recordingId: feed.id, claimedRecordingId: options.candidate_claimed_recording.id, sliderSelector:'.recording-slider', sliderBarSelector: '.recording-playback', currentTimeSelector:'.recording-current'}); $controls.bind('statechange.listenRecording', stateChangeRecording); $('.dotdotdot', $feedItem).dotdotdot(); $feedItem.data('original-max-height', $feedItem.css('height')); context.JK.bindHoverEvents($feedItem); context.JK.bindProfileClickEvents($feedItem); } else { logger.warn("skipping feed type: " + feed.type); } context.JK.bindProfileClickEvents(); }); } function renderFeed(feed) { $content.append(feed); } function search() { logger.debug("Searching for feeds..."); refresh(); return false; } function events() { $refresh.on("click", search); $sortFeedBy.on('change', search); $includeDate.on('change', search); $includeType.on('change', search); var scrollerID = '#'+screenID()+'-feed-scroller'; $(scrollerID).scroll(function() { var wintop = $(scrollerID).scrollTop(); var winheight = $(scrollerID).height(); var docheight = $('#'+screenID()+'-feed-entry-list').height(); var scrollTrigger = 0.90; //console.log("feed scroll: wintop="+wintop+" docheight="+docheight+" winheight="+winheight+" ratio="+(wintop / (docheight - winheight))); if ((wintop / (docheight - winheight)) > scrollTrigger) { populate(); } }); } function initialize(_$parent, _$scroller, _$content, _$noMorefeeds, _$refresh, _$sortFeedBy, _$includeDate, _$includeType, defaults) { $screen = _$parent; $scroller = _$scroller; $content = _$content; $noMoreFeeds = _$noMorefeeds; $refresh = _$refresh; $sortFeedBy = _$sortFeedBy; $includeDate = _$includeDate; $includeType = _$includeType; if($screen.length == 0) throw "$screen must be specified"; if($scroller.length == 0) throw "$scroller must be specified"; if($content.length == 0) throw "$content must be specified"; if($noMoreFeeds.length == 0) throw "$noMoreFeeds must be specified"; if($refresh.length == 0) throw "$refresh must be specified"; if($sortFeedBy.length == 0) throw "$sortFeedBy must be specified"; if($includeDate.length == 0) throw "$includeDate must be specified"; if($includeType.length ==0) throw "$includeType must be specified"; defaults = $.extend({}, {sort: 'date', time_range: 'month', type: 'all'}, defaults) // set default search criteria $sortFeedBy.val(defaults.date) $includeDate.val(defaults.time_range) $includeType.val(defaults.type) events(); } this.initialize = initialize; this.refresh = refresh; this.setUser = setUser; return this; } })(window, jQuery)