From d644cba38ff06369cc43c1ebd08d344fd1d248ea Mon Sep 17 00:00:00 2001 From: Maxime Quandalle Date: Mon, 31 Aug 2015 15:09:53 +0200 Subject: Replace the component bounded `cachedValue` by a global `UnsavedEdits` This new draft saving system is currently only implemented for the card description and comment. We need better a component inheritance/composition model to support this for all editable fields. Fixes #186 --- client/components/activities/comments.jade | 1 + client/components/activities/comments.js | 104 +++++++++++++++++++---------- client/components/cards/cardDetails.jade | 12 +++- client/components/cards/cardDetails.js | 35 ++++++++-- client/components/forms/cachedValue.js | 22 ------ client/components/forms/inlinedform.js | 90 ------------------------- client/components/lists/listHeader.jade | 2 +- 7 files changed, 112 insertions(+), 154 deletions(-) delete mode 100644 client/components/forms/cachedValue.js delete mode 100644 client/components/forms/inlinedform.js (limited to 'client/components') diff --git a/client/components/activities/comments.jade b/client/components/activities/comments.jade index 3b47cbf6..405778de 100644 --- a/client/components/activities/comments.jade +++ b/client/components/activities/comments.jade @@ -4,5 +4,6 @@ template(name="commentForm") +userAvatar(userId=currentUser._id) form.js-new-comment-form +editor(class="js-new-comment-input") + | {{getUnsavedValue 'cardComment' currentCard._id}} .add-controls button.primary.confirm.clear.js-add-comment(type="submit") {{_ 'comment'}} diff --git a/client/components/activities/comments.js b/client/components/activities/comments.js index d41b86dd..a3af7ba6 100644 --- a/client/components/activities/comments.js +++ b/client/components/activities/comments.js @@ -1,47 +1,83 @@ -var commentFormIsOpen = new ReactiveVar(false); +let commentFormIsOpen = new ReactiveVar(false); -Template.commentForm.helpers({ - commentFormIsOpen: function() { - return commentFormIsOpen.get(); - } -}); +BlazeComponent.extendComponent({ + template() { + return 'commentForm'; + }, -Template.commentForm.events({ - 'click .js-new-comment:not(.focus)': function() { - commentFormIsOpen.set(true); + onDestroyed() { + commentFormIsOpen.set(false); }, - 'submit .js-new-comment-form': function(evt, tpl) { - var input = tpl.$('.js-new-comment-input'); - if ($.trim(input.val())) { - CardComments.insert({ - boardId: this.boardId, - cardId: this._id, - text: input.val() - }); - input.val(''); - input.blur(); - commentFormIsOpen.set(false); - Tracker.flush(); - autosize.update(input); - } - evt.preventDefault(); + + commentFormIsOpen() { + return commentFormIsOpen.get(); }, - // Pressing Ctrl+Enter should submit the form - 'keydown form textarea': function(evt, tpl) { - if (evt.keyCode === 13 && (evt.metaKey || evt.ctrlKey)) { - tpl.find('button[type=submit]').click(); - } + + getInput() { + return this.$('.js-new-comment-input'); + }, + + events() { + return [{ + 'click .js-new-comment:not(.focus)': function() { + commentFormIsOpen.set(true); + }, + 'submit .js-new-comment-form': function(evt) { + let input = this.getInput(); + if ($.trim(input.val())) { + CardComments.insert({ + boardId: this.boardId, + cardId: this._id, + text: input.val() + }); + resetCommentInput(input); + Tracker.flush(); + autosize.update(input); + } + evt.preventDefault(); + }, + // Pressing Ctrl+Enter should submit the form + 'keydown form textarea': function(evt) { + if (evt.keyCode === 13 && (evt.metaKey || evt.ctrlKey)) { + this.find('button[type=submit]').click(); + } + } + }]; } -}); +}).register('commentForm'); -Template.commentForm.onDestroyed(function() { +// XXX This should be a static method of the `commentForm` component +function resetCommentInput(input) { + input.val(''); + input.blur(); commentFormIsOpen.set(false); -}); +} + +// XXX This should handled a `onUpdated` callback of the `commentForm` component +// but since this callback doesn't exists, and `onRendered` is not called if the +// data is not destroyed and recreated, we simulate the desired callback using +// Tracker.autorun to register the component dependencies, and re-run when these +// dependencies are invalidated. A better component API would remove this hack. +Tracker.autorun(() => { + Session.get('currentCard'); + Tracker.afterFlush(() => { + autosize.update($('.js-new-comment-input')); + }); +}) EscapeActions.register('inlinedForm', function() { - commentFormIsOpen.set(false); - $('.js-new-comment-input').blur(); + const draftKey = { + fieldName: 'cardComment', + docId: Session.get('currentCard') + }; + let commentInput = $('.js-new-comment-input'); + if ($.trim(commentInput.val())) { + UnsavedEdits.set(draftKey, commentInput.val()); + } else { + UnsavedEdits.reset(draftKey); + } + resetCommentInput(commentInput); }, function() { return commentFormIsOpen.get(); }, { noClickEscapeOn: '.js-new-comment' diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade index 01b56894..a5dcb47b 100644 --- a/client/components/cards/cardDetails.jade +++ b/client/components/cards/cardDetails.jade @@ -34,11 +34,11 @@ template(name="cardDetails") //- XXX We should use "editable" to avoid repetiting ourselves if currentUser.isBoardMember h3.card-details-item-title Description - +inlinedForm(classNames="card-description js-card-description") + +inlinedCardDescription(classNames="card-description js-card-description") +editor(autofocus=true) - = description + | {{getUnsavedValue 'cardDescription' _id description}} .edit-controls.clearfix - button.primary(type="submit") {{_ 'edit'}} + button.primary(type="submit") {{_ 'save'}} a.fa.fa-times-thin.js-close-inlined-form else a.js-open-inlined-form @@ -47,6 +47,12 @@ template(name="cardDetails") = description else | {{_ 'edit'}} + if (hasUnsavedValue 'cardDescription' _id) + p.quiet + | You have an unsaved description. + a.js-open-inlined-form View it + = ' - ' + a.js-close-inlined-form Discard else if description h3.card-details-item-title Description +viewer diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index c0ea6a05..caa5993f 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -1,7 +1,3 @@ -// XXX Obviously this shouldn't be a global, but this is currently the only way -// to share a variable between two files. - - BlazeComponent.extendComponent({ template: function() { return 'cardDetails'; @@ -80,6 +76,37 @@ BlazeComponent.extendComponent({ } }).register('cardDetails'); +// We extends the normal InlinedForm component to support UnsavedEdits draft +// feature. +(class extends InlinedForm { + _getUnsavedEditKey() { + return { + fieldName: 'cardDescription', + docId: Session.get('currentCard'), + } + } + + close(isReset = false) { + if (this.isOpen.get() && ! isReset) { + UnsavedEdits.set(this._getUnsavedEditKey(), this.getValue()); + } + super(); + } + + reset() { + UnsavedEdits.reset(this._getUnsavedEditKey()); + this.close(true); + } + + events() { + const parentEvents = InlinedForm.prototype.events()[0]; + return [{ + ...parentEvents, + 'click .js-close-inlined-form': this.reset, + }]; + } +}).register('inlinedCardDescription'); + Template.cardDetailsActionsPopup.events({ 'click .js-members': Popup.open('cardMembers'), 'click .js-labels': Popup.open('cardLabels'), diff --git a/client/components/forms/cachedValue.js b/client/components/forms/cachedValue.js deleted file mode 100644 index a2898d85..00000000 --- a/client/components/forms/cachedValue.js +++ /dev/null @@ -1,22 +0,0 @@ -var emptyValue = ''; - -Mixins.CachedValue = BlazeComponent.extendComponent({ - onCreated: function() { - this._cachedValue = emptyValue; - }, - - setCache: function(value) { - this._cachedValue = value; - }, - - getCache: function(defaultValue) { - if (this._cachedValue === emptyValue) - return defaultValue || ''; - else - return this._cachedValue; - }, - - resetCache: function() { - this.setCache(''); - } -}); diff --git a/client/components/forms/inlinedform.js b/client/components/forms/inlinedform.js deleted file mode 100644 index 09c75ce1..00000000 --- a/client/components/forms/inlinedform.js +++ /dev/null @@ -1,90 +0,0 @@ -// A inlined form is used to provide a quick edition of single field for a given -// document. Clicking on a edit button should display the form to edit the field -// value. The form can then be submited, or just closed. -// -// When the form is closed we save non-submitted values in memory to avoid any -// data loss. -// -// Usage: -// -// +inlineForm -// // the content when the form is open -// else -// // the content when the form is close (optional) - -// We can only have one inlined form element opened at a time -// XXX Could we avoid using a global here ? This is used in Mousetrap -// keyboard.js -var currentlyOpenedForm = new ReactiveVar(null); - -BlazeComponent.extendComponent({ - template: function() { - return 'inlinedForm'; - }, - - mixins: function() { - return [Mixins.CachedValue]; - }, - - onCreated: function() { - this.isOpen = new ReactiveVar(false); - }, - - onDestroyed: function() { - currentlyOpenedForm.set(null); - }, - - open: function() { - // Close currently opened form, if any - EscapeActions.executeUpTo('inlinedForm'); - this.isOpen.set(true); - currentlyOpenedForm.set(this); - }, - - close: function() { - this.saveValue(); - this.isOpen.set(false); - currentlyOpenedForm.set(null); - }, - - getValue: function() { - var input = this.find('textarea,input[type=text]'); - return this.isOpen.get() && input && input.value; - }, - - saveValue: function() { - this.callFirstWith(this, 'setCache', this.getValue()); - }, - - events: function() { - return [{ - 'click .js-close-inlined-form': this.close, - 'click .js-open-inlined-form': this.open, - - // Pressing Ctrl+Enter should submit the form - 'keydown form textarea': function(evt) { - if (evt.keyCode === 13 && (evt.metaKey || evt.ctrlKey)) { - this.find('button[type=submit]').click(); - } - }, - - // Close the inlined form when after its submission - submit: function() { - if (this.currentData().autoclose !== false) { - Tracker.afterFlush(() => { - this.close(); - this.callFirstWith(this, 'resetCache'); - }); - } - } - }]; - } -}).register('inlinedForm'); - -// Press escape to close the currently opened inlinedForm -EscapeActions.register('inlinedForm', - function() { currentlyOpenedForm.get().close(); }, - function() { return currentlyOpenedForm.get() !== null; }, { - noClickEscapeOn: '.js-inlined-form' - } -); diff --git a/client/components/lists/listHeader.jade b/client/components/lists/listHeader.jade index 0528514e..d87078cd 100644 --- a/client/components/lists/listHeader.jade +++ b/client/components/lists/listHeader.jade @@ -11,7 +11,7 @@ template(name="listHeader") template(name="editListTitleForm") .list-composer - input.full-line(type="text" value=title autofocus) + input.full-line(type="text" value="{{../trySomething}}" autofocus) .edit-controls.clearfix button.primary.confirm(type="submit") {{_ 'save'}} a.fa.fa-times-thin.js-close-inlined-form -- cgit v1.2.3-1-g7c22