* @fixme can't submit file uploads
*
* @param {jQuery} form: jQuery object whose first element is a form
+ * @param function onSuccess: something extra to do on success
*
* @access public
*/
- FormXHR: function(form) {
+ FormXHR: function(form, onSuccess) {
$.ajax({
type: 'POST',
dataType: 'xml',
.attr(SN.C.S.Disabled, SN.C.S.Disabled);
},
error: function (xhr, textStatus, errorThrown) {
- alert(errorThrown || textStatus);
+ // If the server end reported an error from StatusNet,
+ // find it -- otherwise we'll see what was reported
+ // from the browser.
+ var errorReported = null;
+ if (xhr.responseXML) {
+ errorReported = $('#error', xhr.responseXML).text();
+ }
+ alert(errorReported || errorThrown || textStatus);
+
+ // Restore the form to original state.
+ // Hopefully. :D
+ form
+ .removeClass(SN.C.S.Processing)
+ .find('.submit')
+ .removeClass(SN.C.S.Disabled)
+ .removeAttr(SN.C.S.Disabled);
},
success: function(data, textStatus) {
if (typeof($('form', data)[0]) != 'undefined') {
form_new = document._importNode($('form', data)[0], true);
form.replaceWith(form_new);
+ if (onSuccess) {
+ onSuccess();
+ }
}
- else {
+ else if (typeof($('p', data)[0]) != 'undefined') {
form.replaceWith(document._importNode($('p', data)[0], true));
+ if (onSuccess) {
+ onSuccess();
+ }
+ }
+ else {
+ alert('Unknown error.');
}
}
});
var replyItem = form.closest('li.notice-reply');
if (replyItem.length > 0) {
- // If this is an inline reply, insert it in place.
+ // If this is an inline reply, remove the form...
+ var list = form.closest('.threaded-replies');
+ var placeholder = list.find('.notice-reply-placeholder');
+ replyItem.remove();
+
var id = $(notice).attr('id');
if ($("#"+id).length == 0) {
- var parentNotice = replyItem.closest('li.notice');
- replyItem.replaceWith(notice);
- SN.U.NoticeInlineReplyPlaceholder(parentNotice);
+ $(notice).insertBefore(placeholder);
} else {
// Realtime came through before us...
- replyItem.remove();
}
+
+ // ...and show the placeholder form.
+ placeholder.show();
} else if (notices.length > 0 && SN.U.belongsOnTimeline(notice)) {
// Not a reply. If on our timeline, show it at the top!
.css({display:'none'})
.fadeIn(2500);
SN.U.NoticeWithAttachment($('#'+notice.id));
- SN.U.NoticeReplyTo($('#'+notice.id));
+ SN.U.switchInputFormTab("placeholder");
}
} else {
// Not on a timeline that this belongs on?
});
},
+ FormProfileSearchXHR: function(form) {
+ $.ajax({
+ type: 'POST',
+ dataType: 'xml',
+ url: form.attr('action'),
+ data: form.serialize() + '&ajax=1',
+ beforeSend: function(xhr) {
+ form
+ .addClass(SN.C.S.Processing)
+ .find('.submit')
+ .addClass(SN.C.S.Disabled)
+ .attr(SN.C.S.Disabled, SN.C.S.Disabled);
+ },
+ error: function (xhr, textStatus, errorThrown) {
+ alert(errorThrown || textStatus);
+ },
+ success: function(data, textStatus) {
+ var results_placeholder = $('#profile_search_results');
+ if (typeof($('ul', data)[0]) != 'undefined') {
+ var list = document._importNode($('ul', data)[0], true);
+ results_placeholder.replaceWith(list);
+ }
+ else {
+ var _error = $('<li/>').append(document._importNode($('p', data)[0], true));
+ results_placeholder.html(_error);
+ }
+ form
+ .removeClass(SN.C.S.Processing)
+ .find('.submit')
+ .removeClass(SN.C.S.Disabled)
+ .attr(SN.C.S.Disabled, false);
+ }
+ });
+ },
+
+ FormPeopletagsXHR: function(form) {
+ $.ajax({
+ type: 'POST',
+ dataType: 'xml',
+ url: form.attr('action'),
+ data: form.serialize() + '&ajax=1',
+ beforeSend: function(xhr) {
+ form.find('.submit')
+ .addClass(SN.C.S.Processing)
+ .addClass(SN.C.S.Disabled)
+ .attr(SN.C.S.Disabled, SN.C.S.Disabled);
+ },
+ error: function (xhr, textStatus, errorThrown) {
+ alert(errorThrown || textStatus);
+ },
+ success: function(data, textStatus) {
+ var results_placeholder = form.parents('.entity_tags');
+ if (typeof($('.entity_tags', data)[0]) != 'undefined') {
+ var tags = document._importNode($('.entity_tags', data)[0], true);
+ $(tags).find('.editable').append($('<button class="peopletags_edit_button"/>'));
+ results_placeholder.replaceWith(tags);
+ } else {
+ results_placeholder.find('p').remove();
+ results_placeholder.append(document._importNode($('p', data)[0], true));
+ form.removeClass(SN.C.S.Processing)
+ .find('.submit')
+ .removeClass(SN.C.S.Disabled)
+ .attr(SN.C.S.Disabled, false);
+ }
+ }
+ });
+ },
+
normalizeGeoData: function(form) {
SN.C.I.NoticeDataGeo.NLat = form.find('[name=lat]').val();
SN.C.I.NoticeDataGeo.NLon = form.find('[name=lon]').val();
}
},
+
/**
* Fetch an XML DOM from an XHR's response data.
*
* @access private
*/
NoticeReply: function() {
- if ($('#content .notice_reply').length > 0) {
- $('#content .notice').each(function() { SN.U.NoticeReplyTo($(this)); });
- }
+ $('#content .notice_reply').live('click', function(e) {
+ e.preventDefault();
+ var notice = $(this).closest('li.notice');
+ SN.U.NoticeInlineReplyTrigger(notice);
+ return false;
+ });
},
/**
- * Setup function -- DOES NOT trigger actions immediately.
- *
- * Sets up event handlers on the given notice's reply button to
- * tweak the new-notice form with needed variables and focus it
- * when pushed.
- *
- * (This replaces the default reply button behavior to submit
- * directly to a form which comes back with a specialized page
- * with the form data prefilled.)
- *
- * @param {jQuery} notice: jQuery object containing one or more notices
+ * Stub -- kept for compat with plugins for now.
* @access private
*/
NoticeReplyTo: function(notice) {
- notice.find('.notice_reply').live('click', function(e) {
- e.preventDefault();
- var nickname = ($('.author .nickname', notice).length > 0) ? $($('.author .nickname', notice)[0]) : $('.author .nickname.uid');
- SN.U.NoticeInlineReplyTrigger(notice, '@' + nickname.text());
- return false;
- });
},
/**
// and we'll add on the end of it. Will add if needed.
list = $('ul.threaded-replies', notice);
if (list.length == 0) {
- list = $('<ul class="notices threaded-replies xoxo"></ul>');
- notice.append(list);
+ SN.U.NoticeInlineReplyPlaceholder(notice);
+ list = $('ul.threaded-replies', notice);
+ } else {
+ var placeholder = $('li.notice-reply-placeholder', notice);
+ if (placeholder.length == 0) {
+ SN.U.NoticeInlineReplyPlaceholder(notice);
+ }
}
}
var nextStep = function() {
// Override...?
replyForm.find('input[name=inreplyto]').val(id);
+ replyForm.find('#notice_to').attr('disabled', 'disabled').hide();
+ replyForm.find('#notice_private').attr('disabled', 'disabled').hide();
+ replyForm.find('label[for=notice_to]').hide();
+ replyForm.find('label[for=notice_private]').hide();
// Set focus...
var text = replyForm.find('textarea');
// Update the existing form...
nextStep();
} else {
- // Remove placeholder if any
- list.find('li.notice-reply-placeholder').remove();
+ // Hide the placeholder...
+ var placeholder = list.find('li.notice-reply-placeholder').hide();
// Create the reply form entry at the end
var replyItem = $('li.notice-reply', list);
var intermediateStep = function(formMaster) {
var formEl = document._importNode(formMaster, true);
replyItem.append(formEl);
- list.append(replyItem);
+ list.append(replyItem); // *after* the placeholder
var form = replyForm = $(formEl);
SN.Init.NoticeFormSetup(form);
NoticeInlineReplyPlaceholder: function(notice) {
var list = notice.find('ul.threaded-replies');
+ if (list.length == 0) {
+ list = $('<ul class="notices threaded-replies xoxo"></ul>');
+ notice.append(list);
+ list = notice.find('ul.threaded-replies');
+ }
var placeholder = $('<li class="notice-reply-placeholder">' +
- '<input class="placeholder">' +
+ '<input class="placeholder" />' +
'</li>');
placeholder.find('input')
- .val(SN.msg('reply_placeholder'))
- .focus(function() {
- SN.U.NoticeInlineReplyTrigger(notice);
- return false;
- });
+ .val(SN.msg('reply_placeholder'));
list.append(placeholder);
},
/**
* Setup function -- DOES NOT apply immediately.
*
- * Sets up event handlers for favor/disfavor forms to submit via XHR.
+ * Sets up event handlers for inline reply mini-form placeholders.
* Uses 'live' rather than 'bind', so applies to future as well as present items.
*/
NoticeInlineReplySetup: function() {
- $('.threaded-replies').each(function() {
- var list = $(this);
- var notice = list.closest('.notice');
- SN.U.NoticeInlineReplyPlaceholder(notice);
- });
+ $('li.notice-reply-placeholder input')
+ .live('focus', function() {
+ var notice = $(this).closest('li.notice');
+ SN.U.NoticeInlineReplyTrigger(notice);
+ return false;
+ });
+ $('li.notice-reply-comments a')
+ .live('click', function() {
+ var url = $(this).attr('href');
+ var area = $(this).closest('.threaded-replies');
+ $.get(url, {ajax: 1}, function(data, textStatus, xhr) {
+ var replies = $('.threaded-replies', data);
+ if (replies.length) {
+ area.replaceWith(document._importNode(replies[0], true));
+ }
+ });
+ return false;
+ });
},
/**
* popout before submitting.
*
* Uses 'live' rather than 'bind', so applies to future as well as present items.
+ *
*/
NoticeRepeat: function() {
$('.form_repeat').live('click', function(e) {
}
var NGW = form.find('.notice_data-geo_wrap');
- var geocodeURL = NGW.attr('title');
- NGW.removeAttr('title');
+ var geocodeURL = NGW.attr('data-api');
label
.attr('title', label.text());
switchInputFormTab: function(tag) {
// The one that's current isn't current anymore
$('.input_form_nav_tab.current').removeClass('current');
- $('#input_form_nav_'+tag).addClass('current');
+ if (tag == 'placeholder') {
+ // Hack: when showing the placeholder, mark the tab
+ // as current for 'Status'.
+ $('#input_form_nav_status').addClass('current');
+ } else {
+ $('#input_form_nav_'+tag).addClass('current');
+ }
+
+ // Don't remove 'current' if we also have the "nonav" class.
+ // An example would be the message input form. removing
+ // 'current' will cause the form to vanish from the page.
+ var nonav = $('.input_form.current.nonav');
+ if (nonav.length > 0) {
+ return;
+ }
$('.input_form.current').removeClass('current');
- $('#input_form_'+tag).addClass('current');
+ $('#input_form_'+tag)
+ .addClass('current')
+ .find('.ajax-notice').each(function() {
+ var form = $(this);
+ SN.Init.NoticeFormSetup(form);
+ })
+ .find('.notice_data-text').focus();
}
},
*/
NoticeForm: function() {
if ($('body.user_in').length > 0) {
- $('.ajax-notice').each(function() {
- var form = $(this);
- SN.Init.NoticeFormSetup(form);
+ // SN.Init.NoticeFormSetup() will get run
+ // when forms get displayed for the first time...
+
+ // Hack to initialize the placeholder at top
+ $('#input_form_placeholder input.placeholder').focus(function() {
+ SN.U.switchInputFormTab("status");
});
// Make inline reply forms self-close when clicking out.
$('body').bind('click', function(e) {
+ var currentForm = $('#content .input_forms div.current');
+ if (currentForm.length > 0) {
+ if ($('#content .input_forms').has(e.target).length == 0) {
+ // If all fields are empty, switch back to the placeholder.
+ var fields = currentForm.find('textarea, input[type=text], input[type=""]');
+ var anything = false;
+ fields.each(function() {
+ anything = anything || $(this).val();
+ });
+ if (!anything) {
+ SN.U.switchInputFormTab("placeholder");
+ }
+ }
+ }
+
var openReplies = $('li.notice-reply');
if (openReplies.length > 0) {
var target = $(e.target);
if (cur == '' || cur == textarea.data('initialText')) {
var parentNotice = replyItem.closest('li.notice');
replyItem.remove();
- SN.U.NoticeInlineReplyPlaceholder(parentNotice);
+ parentNotice.find('li.notice-reply-placeholder').show();
}
}
});
}
});
+
+ // Infield labels for notice form inputs.
+ $('.input_forms fieldset fieldset label').inFieldLabels({ fadeOpacity:0 });
+
}
},
* @param {jQuery} form
*/
NoticeFormSetup: function(form) {
- SN.U.NoticeLocationAttach(form);
- SN.U.FormNoticeXHR(form);
- SN.U.FormNoticeEnhancements(form);
- SN.U.NoticeDataAttach(form);
+ if (!form.data('NoticeFormSetup')) {
+ SN.U.NoticeLocationAttach(form);
+ SN.U.FormNoticeXHR(form);
+ SN.U.FormNoticeEnhancements(form);
+ SN.U.NoticeDataAttach(form);
+ form.data('NoticeFormSetup', true);
+ }
},
/**
*/
EntityActions: function() {
if ($('body.user_in').length > 0) {
+ $('.form_user_subscribe').live('click', function() { SN.U.FormXHR($(this)); return false; });
+ $('.form_user_unsubscribe').live('click', function() { SN.U.FormXHR($(this)); return false; });
+ $('.form_group_join').live('click', function() { SN.U.FormXHR($(this)); return false; });
+ $('.form_group_leave').live('click', function() { SN.U.FormXHR($(this)); return false; });
+ $('.form_user_nudge').live('click', function() { SN.U.FormXHR($(this)); return false; });
+ $('.form_peopletag_subscribe').live('click', function() { SN.U.FormXHR($(this)); return false; });
+ $('.form_peopletag_unsubscribe').live('click', function() { SN.U.FormXHR($(this)); return false; });
+ $('.form_user_add_peopletag').live('click', function() { SN.U.FormXHR($(this)); return false; });
+ $('.form_user_remove_peopletag').live('click', function() { SN.U.FormXHR($(this)); return false; });
+
SN.U.NewDirectMessage();
}
},
+ ProfileSearch: function() {
+ if ($('body.user_in').length > 0) {
+ $('.form_peopletag_edit_user_search input.submit').live('click', function() {
+ SN.U.FormProfileSearchXHR($(this).parents('form')); return false;
+ });
+ }
+ },
+
/**
* Run setup code for login form:
*
});
},
+ /**
+ * Called when a people tag edit box is shown in the interface
+ *
+ * - loads the jQuery UI autocomplete plugin
+ * - sets event handlers for tag completion
+ *
+ */
+ PeopletagAutocomplete: function(txtBox) {
+ var split = function(val) {
+ return val.split( /\s+/ );
+ }
+ var extractLast = function(term) {
+ return split(term).pop();
+ }
+
+ // don't navigate away from the field on tab when selecting an item
+ txtBox.live( "keydown", function( event ) {
+ if ( event.keyCode === $.ui.keyCode.TAB &&
+ $(this).data( "autocomplete" ).menu.active ) {
+ event.preventDefault();
+ }
+ }).autocomplete({
+ minLength: 0,
+ source: function(request, response) {
+ // delegate back to autocomplete, but extract the last term
+ response($.ui.autocomplete.filter(
+ SN.C.PtagACData, extractLast(request.term)));
+ },
+ focus: function() {
+ return false;
+ },
+ select: function(event, ui) {
+ var terms = split(this.value);
+ terms.pop();
+ terms.push(ui.item.value);
+ terms.push("");
+ this.value = terms.join(" ");
+ return false;
+ }
+ }).data('autocomplete')._renderItem = function(ul, item) {
+ // FIXME: with jQuery UI you cannot have it highlight the match
+ var _l = '<a class="ptag-ac-line-tag">' + item.tag
+ + ' <em class="privacy_mode">' + item.mode + '</em>'
+ + '<span class="freq">' + item.freq + '</span></a>'
+
+ return $("<li/>")
+ .addClass('mode-' + item.mode)
+ .addClass('ptag-ac-line')
+ .data("item.autocomplete", item)
+ .append(_l)
+ .appendTo(ul);
+ }
+ },
+
+ /**
+ * Run setup for the ajax people tags editor
+ *
+ * - show edit button
+ * - set event handle for click on edit button
+ * - loads people tag autocompletion data if not already present
+ * or if it is stale.
+ *
+ */
+ PeopleTags: function() {
+ $('.user_profile_tags .editable').append($('<button class="peopletags_edit_button"/>'));
+
+ $('.peopletags_edit_button').live('click', function() {
+ var form = $(this).parents('dd').eq(0).find('form');
+ // We can buy time from the above animation
+
+ $.ajax({
+ url: _peopletagAC,
+ dataType: 'json',
+ data: {token: $('#token').val()},
+ ifModified: true,
+ success: function(data) {
+ // item.label is used to match
+ for (i=0; i < data.length; i++) {
+ data[i].label = data[i].tag;
+ }
+
+ SN.C.PtagACData = data;
+ SN.Init.PeopletagAutocomplete(form.find('#tags'));
+ }
+ });
+
+ $(this).parents('ul').eq(0).fadeOut(200, function() {form.fadeIn(200).find('input#tags')});
+ });
+
+ $('.user_profile_tags form .submit').live('click', function() {
+ SN.U.FormPeopletagsXHR($(this).parents('form')); return false;
+ });
+ },
+
/**
* Set up any generic 'ajax' form so it submits via AJAX with auto-replacement.
*/
SN.U.FormXHR($(this));
return false;
});
+ $('form.ajax input[type=submit]').live('click', function() {
+ // Some forms rely on knowing which submit button was clicked.
+ // Save a hidden input field which'll be picked up during AJAX
+ // submit...
+ var button = $(this);
+ var form = button.closest('form');
+ form.find('.hidden-submit-button').remove();
+ $('<input class="hidden-submit-button" type="hidden" />')
+ .attr('name', button.attr('name'))
+ .val(button.val())
+ .appendTo(form);
+ });
},
/**
}
}
});
- }
+ },
+
+ CheckBoxes: function() {
+ $("span[class='checkbox-wrapper']").addClass("unchecked");
+ $(".checkbox-wrapper").click(function(){
+ if($(this).children("input").attr("checked")){
+ // uncheck
+ $(this).children("input").attr({checked: ""});
+ $(this).removeClass("checked");
+ $(this).addClass("unchecked");
+ $(this).children("label").text("Private?");
+ }else{
+ // check
+ $(this).children("input").attr({checked: "checked"});
+ $(this).removeClass("unchecked");
+ $(this).addClass("checked");
+ $(this).children("label").text("Private");
+ }
+ });
+ }
}
};
$(document).ready(function(){
SN.Init.AjaxForms();
SN.Init.UploadForms();
+ SN.Init.CheckBoxes();
if ($('.'+SN.C.S.FormNotice).length > 0) {
SN.Init.NoticeForm();
}
if ($('#form_login').length > 0) {
SN.Init.Login();
}
+ if ($('#profile_search_results').length > 0) {
+ SN.Init.ProfileSearch();
+ }
+ if ($('.user_profile_tags .editable').length > 0) {
+ SN.Init.PeopleTags();
+ }
});
+