From a610836e1cfa85d5bad1599e911c08433fab412a Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 15 Apr 2011 15:07:17 -0700 Subject: [PATCH] Better QnA ajax insert - work in progress --- plugins/QnA/actions/qnanewanswer.php | 66 +++++--- plugins/QnA/js/qna.js | 217 +++++++++++++++++++++++++-- plugins/QnA/lib/qnanewanswerform.php | 2 +- 3 files changed, 248 insertions(+), 37 deletions(-) diff --git a/plugins/QnA/actions/qnanewanswer.php b/plugins/QnA/actions/qnanewanswer.php index f5eedbfac9..e94b86fb38 100644 --- a/plugins/QnA/actions/qnanewanswer.php +++ b/plugins/QnA/actions/qnanewanswer.php @@ -49,7 +49,7 @@ class QnanewanswerAction extends Action protected $error = null; protected $complete = null; - protected $question = null; + public $question = null; protected $content = null; /** @@ -162,30 +162,10 @@ class QnanewanswerAction extends Action $this->elementStart('body'); - $nli = new NoticeListItem($notice, $this); + + $nli = new NoticeAnswerListItem($notice, $this); $nli->show(); - //$this->raw($answer->asHTML()); - - /* - $question = $this->question; - - $nli = new NoticeListItem($notice, $this); - $nli->showNotice(); - - $this->elementStart('div', array('class' => 'entry-content answer-content')); - - if (!empty($answer)) { - $form = new QnashowanswerForm($this, $answer); - $form->show(); - } else { - $this->text(_m('Answer data is missing.')); - } - - $this->elementEnd('div'); - - // @fixme - //$this->elementStart('div', array('class' => 'entry-content')); - */ + $this->elementEnd('body'); $this->elementEnd('html'); } else { @@ -299,3 +279,41 @@ class QnanewanswerAction extends Action } } + +class NoticeAnswerListItem extends NoticeListItem +{ + + protected $question; + + /** + * constructor + * + * Also initializes the profile attribute. + * + * @param Notice $notice The notice we'll display + */ + function __construct($notice, $out=null) + { + parent::__construct($notice, $out); + $this->question = $out->question; + } + + function show() + { + if (empty($this->notice)) { + common_log(LOG_WARNING, "Trying to show missing notice; skipping."); + return; + } else if (empty($this->profile)) { + common_log(LOG_WARNING, "Trying to show missing profile (" . $this->notice->profile_id . "); skipping."); + return; + } + + $this->showStart(); + $this->showNotice(); + $this->showNoticeInfo(); + $notice = $this->question->getNotice(); + $this->out->hidden('inreplyto', $notice->id); + $this->showEnd(); + } + +} \ No newline at end of file diff --git a/plugins/QnA/js/qna.js b/plugins/QnA/js/qna.js index 5fa329f893..c83440452f 100644 --- a/plugins/QnA/js/qna.js +++ b/plugins/QnA/js/qna.js @@ -24,9 +24,6 @@ var QnA = { $('input[name=best]').live('click', function() { that.close(this); }); - - - }, /** @@ -88,8 +85,10 @@ var QnA = { var openAnswers = $('li.notice-answer'); if (openAnswers.length > 0) { + console.log("Found and open answer to close"); var target = $(e.target); openAnswers.each(function() { + console.log("found an open answer"); // Did we click outside this one? var answerItem = $(this); if (answerItem.has(e.target).length == 0) { @@ -107,6 +106,7 @@ var QnA = { }); text.focus(); + console.log('finished dealing with body click'); }; placeholder.hide(); @@ -159,15 +159,6 @@ var QnA = { }, - - AnswerFormSetup: function(form) { - console.log("AnswerFormSetup - begin"); - if (!form.data('AnswerFormSetup')) { - form.data('AnswerFormSetup', true); - } - console.log("AnswerFormSetup - exit"); - }, - /** * Setup function -- DOES NOT apply immediately. * @@ -183,6 +174,208 @@ var QnA = { return false; }); console.log("NoticeInlineAnswerSetup - exit"); + }, + + AnswerFormSetup: function(form) { + console.log("AnswerFormSetup"); + $('input[type=submit]').live('click', function() { + console.log("AnswerFormSetup click"); + QnA.FormAnswerXHR(form); + }); + }, + + + /** + * Setup function -- DOES NOT trigger actions immediately. + * + * Sets up event handlers for special-cased async submission of the + * notice-posting form, including some pre-post validation. + * + * Unlike FormXHR() this does NOT submit the form immediately! + * It sets up event handlers so that any method of submitting the + * form (click on submit button, enter, submit() etc) will trigger + * it properly. + * + * Also unlike FormXHR(), this system will use a hidden iframe + * automatically to handle file uploads via + * controls. + * + * @fixme tl;dr + * @fixme vast swaths of duplicate code and really long variable names clutter this function up real bad + * @fixme error handling is unreliable + * @fixme cookieValue is a global variable, but probably shouldn't be + * @fixme saving the location cache cookies should be split out + * @fixme some error messages are hardcoded english: needs i18n + * @fixme special-case for bookmarklet is confusing and uses a global var "self". Is this ok? + * + * @param {jQuery} form: jQuery object whose first element is a form + * + * @access public + */ + FormAnswerXHR: function(form) { + console.log("FormAanwerXHR - begin"); + //SN.C.I.NoticeDataGeo = {}; + form.append(''); + console.log("appended ajax flag"); + + // Make sure we don't have a mixed HTTP/HTTPS submission... + form.attr('action', SN.U.RewriteAjaxAction(form.attr('action'))); + console.log("rewrote action"); + + /** + * Show a response feedback bit under the new-notice dialog. + * + * @param {String} cls: CSS class name to use ('error' or 'success') + * @param {String} text + * @access private + */ + var showFeedback = function(cls, text) { + form.append( + $('

') + .addClass(cls) + .text(text) + ); + }; + + /** + * Hide the previous response feedback, if any. + */ + var removeFeedback = function() { + form.find('.form_response').remove(); + }; + + console.log("doing ajax"); + + form.ajaxForm({ + dataType: 'xml', + timeout: '60000', + beforeSend: function(formData) { + console.log("beforeSend"); + if (form.find('.notice_data-text:first').val() == '') { + form.addClass(SN.C.S.Warning); + return false; + } + form + .addClass(SN.C.S.Processing) + .find('.submit') + .addClass(SN.C.S.Disabled) + .attr(SN.C.S.Disabled, SN.C.S.Disabled); + + SN.U.normalizeGeoData(form); + + return true; + }, + error: function (xhr, textStatus, errorThrown) { + form + .removeClass(SN.C.S.Processing) + .find('.submit') + .removeClass(SN.C.S.Disabled) + .removeAttr(SN.C.S.Disabled, SN.C.S.Disabled); + removeFeedback(); + if (textStatus == 'timeout') { + // @fixme i18n + showFeedback('error', 'Sorry! We had trouble sending your notice. The servers are overloaded. Please try again, and contact the site administrator if this problem persists.'); + } + else { + var response = SN.U.GetResponseXML(xhr); + if ($('.'+SN.C.S.Error, response).length > 0) { + form.append(document._importNode($('.'+SN.C.S.Error, response)[0], true)); + } + else { + if (parseInt(xhr.status) === 0 || jQuery.inArray(parseInt(xhr.status), SN.C.I.HTTP20x30x) >= 0) { + form + .resetForm() + .find('.attach-status').remove(); + SN.U.FormNoticeEnhancements(form); + } + else { + // @fixme i18n + showFeedback('error', '(Sorry! We had trouble sending your notice ('+xhr.status+' '+xhr.statusText+'). Please report the problem to the site administrator if this happens again.'); + } + } + } + }, + success: function(data, textStatus) { + console.log("FormAnswerHXR success"); + removeFeedback(); + var errorResult = $('#'+SN.C.S.Error, data); + if (errorResult.length > 0) { + showFeedback('error', errorResult.text()); + } + else { + + // New notice post was successful. If on our timeline, show it! + var notice = document._importNode($('li', data)[0], true); + + var notices = $('#notices_primary .notices:first'); + var replyItem = form.closest('li.notice-reply'); + + if (replyItem.length > 0) { + console.log("I found a reply li to append to"); + // If this is an inline reply, remove the form... + var list = form.closest('.threaded-replies'); + var placeholder = list.find('.notice-answer-placeholder'); + replyItem.remove(); + + var id = $(notice).attr('id'); + console.log("got notice id " + id); + if ($("#"+id).length == 0) { + console.log("inserting before placeholder"); + $(notice).insertBefore(placeholder); + } else { + // Realtime came through before us... + } + + // ...and show the placeholder form. + placeholder.show(); + console.log('qqqq made it this far') + } else if (notices.length > 0 && SN.U.belongsOnTimeline(notice)) { + // Not a reply. If on our timeline, show it at the top! + if ($('#'+notice.id).length === 0) { + console.log("couldn't find a notice id for " + notice.id); + var notice_irt_value = form.find('#inreplyto').val(); + var notice_irt = '#notices_primary #notice-'+notice_irt_value; + console.log("notice_irt value = " + notice_irt_value); + if($('body')[0].id == 'conversation') { + console.log("found conversation"); + if(notice_irt_value.length > 0 && $(notice_irt+' .notices').length < 1) { + $(notice_irt).append(''); + } + $($(notice_irt+' .notices')[0]).append(notice); + } + else { + console.log("prepending notice") + notices.prepend(notice); + } + $('#'+notice.id) + .css({display:'none'}) + .fadeIn(2500); + } + } else { + // Not on a timeline that this belongs on? + // Just show a success message. + // @fixme inline + showFeedback('success', $('title', data).text()); + } + + //form.resetForm(); + //SN.U.FormNoticeEnhancements(form); + } + }, + complete: function(xhr, textStatus) { + form + .removeClass(SN.C.S.Processing) + .find('.submit') + .removeAttr(SN.C.S.Disabled) + .removeClass(SN.C.S.Disabled); + + form.find('[name=lat]').val(SN.C.I.NoticeDataGeo.NLat); + form.find('[name=lon]').val(SN.C.I.NoticeDataGeo.NLon); + form.find('[name=location_ns]').val(SN.C.I.NoticeDataGeo.NLNS); + form.find('[name=location_id]').val(SN.C.I.NoticeDataGeo.NLID); + form.find('[name=notice_data-geo]').attr('checked', SN.C.I.NoticeDataGeo.NDG); + } + }); } }; diff --git a/plugins/QnA/lib/qnanewanswerform.php b/plugins/QnA/lib/qnanewanswerform.php index ac8d2793bf..73765b3639 100644 --- a/plugins/QnA/lib/qnanewanswerform.php +++ b/plugins/QnA/lib/qnanewanswerform.php @@ -81,7 +81,7 @@ class QnanewanswerForm extends Form */ function formClass() { - return 'form_settings ajax'; + return 'form_settings qna_answer_form'; } /**