From b7aaef3eb9c7b3f917779e69ecdbc67b2213160f Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 10 Nov 2016 00:20:39 +0100 Subject: [PATCH 1/9] Decouple milestone select code. --- app/assets/javascripts/milestone_select.js | 183 -------------- .../javascripts/milestone_select.js.es6 | 239 ++++++++++++++++++ 2 files changed, 239 insertions(+), 183 deletions(-) delete mode 100644 app/assets/javascripts/milestone_select.js create mode 100644 app/assets/javascripts/milestone_select.js.es6 diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js deleted file mode 100644 index d1cd38ad1104..000000000000 --- a/app/assets/javascripts/milestone_select.js +++ /dev/null @@ -1,183 +0,0 @@ -/* eslint-disable */ -(function() { - this.MilestoneSelect = (function() { - function MilestoneSelect(currentProject) { - var _this; - if (currentProject != null) { - _this = this; - this.currentProject = JSON.parse(currentProject); - } - $('.js-milestone-select').each(function(i, dropdown) { - var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, showAny, showNo, showUpcoming, useId, showMenuAbove; - $dropdown = $(dropdown); - projectId = $dropdown.data('project-id'); - milestonesUrl = $dropdown.data('milestones'); - issueUpdateURL = $dropdown.data('issueUpdate'); - selectedMilestone = $dropdown.data('selected'); - showNo = $dropdown.data('show-no'); - showAny = $dropdown.data('show-any'); - showMenuAbove = $dropdown.data('showMenuAbove'); - showUpcoming = $dropdown.data('show-upcoming'); - useId = $dropdown.data('use-id'); - defaultLabel = $dropdown.data('default-label'); - issuableId = $dropdown.data('issuable-id'); - abilityName = $dropdown.data('ability-name'); - $selectbox = $dropdown.closest('.selectbox'); - $block = $selectbox.closest('.block'); - $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon'); - $value = $block.find('.value'); - $loading = $block.find('.block-loading').fadeOut(); - if (issueUpdateURL) { - milestoneLinkTemplate = _.template('<%- title %>'); - milestoneLinkNoneTemplate = 'None'; - collapsedSidebarLabelTemplate = _.template(' <%- title %> '); - } - return $dropdown.glDropdown({ - showMenuAbove: showMenuAbove, - data: function(term, callback) { - return $.ajax({ - url: milestonesUrl - }).done(function(data) { - var extraOptions = []; - if (showAny) { - extraOptions.push({ - id: 0, - name: '', - title: 'Any Milestone' - }); - } - if (showNo) { - extraOptions.push({ - id: -1, - name: 'No Milestone', - title: 'No Milestone' - }); - } - if (showUpcoming) { - extraOptions.push({ - id: -2, - name: '#upcoming', - title: 'Upcoming' - }); - } - if (extraOptions.length) { - extraOptions.push('divider'); - } - - callback(extraOptions.concat(data)); - if (showMenuAbove) { - $dropdown.data('glDropdown').positionMenuAbove(); - } - }); - }, - filterable: true, - search: { - fields: ['title'] - }, - selectable: true, - toggleLabel: function(selected, el, e) { - if (selected && 'id' in selected && $(el).hasClass('is-active')) { - return selected.title; - } else { - return defaultLabel; - } - }, - defaultLabel: defaultLabel, - fieldName: $dropdown.data('field-name'), - text: function(milestone) { - return _.escape(milestone.title); - }, - id: function(milestone) { - if (!useId && !$dropdown.is('.js-issuable-form-dropdown')) { - return milestone.name; - } else { - return milestone.id; - } - }, - isSelected: function(milestone) { - return milestone.name === selectedMilestone; - }, - hidden: function() { - $selectbox.hide(); - // display:block overrides the hide-collapse rule - return $value.css('display', ''); - }, - vue: $dropdown.hasClass('js-issue-board-sidebar'), - clicked: function(selected, $el, e) { - var data, isIssueIndex, isMRIndex, page; - page = $('body').data('page'); - isIssueIndex = page === 'projects:issues:index'; - isMRIndex = (page === page && page === 'projects:merge_requests:index'); - if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) { - e.preventDefault(); - return; - } - if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar')) { - gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = selected.name; - gl.issueBoards.BoardsStore.updateFiltersUrl(); - e.preventDefault(); - } else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) { - if (selected.name != null) { - selectedMilestone = selected.name; - } else { - selectedMilestone = ''; - } - return Issuable.filterResults($dropdown.closest('form')); - } else if ($dropdown.hasClass('js-filter-submit')) { - return $dropdown.closest('form').submit(); - } else if ($dropdown.hasClass('js-issue-board-sidebar')) { - if (selected.id !== -1) { - Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'milestone', new ListMilestone({ - id: selected.id, - title: selected.name - })); - } else { - Vue.delete(gl.issueBoards.BoardsStore.detail.issue, 'milestone'); - } - - $dropdown.trigger('loading.gl.dropdown'); - $loading.fadeIn(); - - gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update')) - .then(function () { - $dropdown.trigger('loaded.gl.dropdown'); - $loading.fadeOut(); - }); - } else { - selected = $selectbox.find('input[type="hidden"]').val(); - data = {}; - data[abilityName] = {}; - data[abilityName].milestone_id = selected != null ? selected : null; - $loading.fadeIn(); - $dropdown.trigger('loading.gl.dropdown'); - return $.ajax({ - type: 'PUT', - url: issueUpdateURL, - data: data - }).done(function(data) { - $dropdown.trigger('loaded.gl.dropdown'); - $loading.fadeOut(); - $selectbox.hide(); - $value.css('display', ''); - if (data.milestone != null) { - data.milestone.namespace = _this.currentProject.namespace; - data.milestone.path = _this.currentProject.path; - data.milestone.remaining = gl.utils.timeFor(data.milestone.due_date); - $value.html(milestoneLinkTemplate(data.milestone)); - return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone)); - } else { - $value.html(milestoneLinkNoneTemplate); - return $sidebarCollapsedValue.find('span').text('No'); - } - }); - } - } - }); - }); - } - - return MilestoneSelect; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/milestone_select.js.es6 b/app/assets/javascripts/milestone_select.js.es6 new file mode 100644 index 000000000000..5e00e7b9a788 --- /dev/null +++ b/app/assets/javascripts/milestone_select.js.es6 @@ -0,0 +1,239 @@ +/* eslint-disable */ +(function (global) { + class MilestoneSelect { + constructor(currentProject) { + this.currentProject = currentProject ? JSON.parse(currentProject) : null; + this.dropdown = document.querySelectorAll('.js-milestone-select'); + this.dataset = this.dropdown.dataset; + // worth checking if this is being run for each time it's called. + const $selectbox = $dropdown.closest('.selectbox'); + const $block = $selectbox.closest('.block'); + const $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon'); + const $value = $block.find('.value'); + const $loading = $block.find('.block-loading').fadeOut(); + // either make member or lookup as needed. + // one dom query total plus jquery plugin + // hasClass add it to the utils + // get rid of all the dom queries + + let milestoneLinkTemplate; + let milestoneLinkNoneTemplate; + let collapsedSidebarLabelTemplate; + + + this.initTemplates(); + this.initDropdown(); + } + initTemplates() { + if (this.dataset.issueUpdateURL) { + this.tpl = { + milestoneLinkTemplate: _.template(` + <%- title %> + + `), + milestoneLinkNoneTemplate: `None`, + collapsedSidebarLabelTemplate: _.template(` + <%- title %> + ` + ), + }; + } + } + + initDropdown() { + const showMenuAbove = this.dataset['showMenuAbove']; + const fieldName = this.dataset['fieldName']; + const selectedMilestone = this.dataset['selected']; + const isSelected = milestone => milestone.name === selectedMilestone; + const vue = this.dropdown.hasClass('js-issue-board-sidebar'); + const searchFields = { fields: ['title'] }; + const defaultLabel = dropdownDataset['defaultLabel']; + + $(this.dropdown).glDropdown({ + vue, + showMenuAbove, + defaultLabel, + fieldName, + isSelected, + filterable: true, + selectable: true, + id: this.idFilter, + search: searchFields, + data: this.fetchData, + text: this.escapeText, + hidden: this.hideStuff, + toggleLabel: this.toggleLabel, + clicked: this.handleDropdownClick, + }); + } + + hideStuff() { + $selectbox.hide(); + // display:block overrides the hide-collapse rule + $value.css('display', ''); + + } + + escapeText(milestone) { + return _.escape(milestone.title); + } + + idFilter(milestone) { + const useId = this.dataset['useId']; + return (!useId && !$dropdown.is('.js-issuable-form-dropdown')) ? milestone.name : milestone.id; + } + + fetchData(term, callback) { + const milestonesUrl = this.dataset['milestonesUrl']; + + return $.ajax({ url: milestonesUrl }) + .done((data) => { + const extraOptions = prepExtraOptions(); + callback(extraOptions.concat(data)); + this.positionMenuAbove(); + }); + } + + toggleLabel() { + // Question: What are the main datastructures at work. How are the classes organized? + const defaultLabel = dropdownDataset['defaultLabel']; + + return (selected, el, e) => (selected && selected.id && el.hasClass('is-active')) ? + selected.title : defaultLabel; + } + + positionMenuAbove() { + if (showMenuAbove) { + this.dropdown.positionMenuAbove(); + } + } + + pushExtraOptions(extraOptions, id, name, title) { + const divider = 'divider'; + const pushable = id === divider ? divider : { id, name, title }; + extraOptions.push(pushable); + } + + prepExtraOptions() { + const showNo = this.dataset['showNo']; + const showAny = this.dataset['showAny']; + const showMenuAbove = this.dataset['showMenuAbove']; + const showUpcoming = this.dataset['showUpcoming']; + + var extraOptions = []; + if (showAny) { + pushExtraOptions(extraOptions, 0, '', 'Any Milestone'); + } + if (showNo) { + pushExtraOptions(extraOptions, -1, 'No Milestone', 'No Milestone'); + } + // DRY ME UP (these configs) + if (showUpcoming) { + pushExtraOptions(extraOptions, -2, '#upcoming', 'Upcoming') + } + if (extraOptions.length) { + pushExtraOptions(extraOptions, 'divider'); + } + } + + initLoadingUi() { + $loading.fadeIn(); + $dropdown.trigger('loading.gl.dropdown'); + } + // much better method name + // adds documention to configuration + handlePut(data) { + $dropdown.trigger('loaded.gl.dropdown'); + $loading.fadeOut(); + $selectbox.hide(); + $value.css('display', ''); + if (data.milestone != null) { + data.milestone.namespace = this.currentProject.namespace; + data.milestone.path = this.currentProject.path; + data.milestone.remaining = gl.utils.timeFor(data.milestone.due_date); + $value.html(milestoneLinkTemplate(data.milestone)); + return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone)); + } else { + $value.html(milestoneLinkNoneTemplate); + return $sidebarCollapsedValue.find('span').text('No'); + } + } + + putIssueBoardPage() { + gl.issueBoards.BoardsStore.state.filters[this.dataset['fieldName']] = selected.name; + gl.issueBoards.BoardsStore.updateFiltersUrl(); + e.preventDefault(); + } + + putIssueBoardSidebar() { + if (selected.id !== -1) { + Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'milestone', new ListMilestone({ + id: selected.id, + title: selected.name + })); + } else { + Vue.delete(gl.issueBoards.BoardsStore.detail.issue, 'milestone'); + } + + $dropdown.trigger('loading.gl.dropdown'); + $loading.fadeIn(); + + gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update')) + .then(function () { + $dropdown.trigger('loaded.gl.dropdown'); + $loading.fadeOut(); + }); + } + + putSubmittableIndex() { + const selectedMilestone = this.dataset['selected']; + // Pay attention here... looks like this is mutating selected milestone + selectedMilestone = selected.name ? select.name : ''; + return Issuable.filterResults($dropdown.closest('form')); + } + + putSubmittableNonIndex() { + return $dropdown.closest('form').submit(); + } + + handleDropdownClick(selected, $el, e) { + // No jq needed here. + const page = $('body').data('page'); + const isIssueIndex = page === 'projects:issues:index'; + const isMRIndex = page === 'projects:merge_requests:index'; + const isInvalidMilestone = this.dropdown.hasClass('js-filter-bulk-update') || this.dropdown.hasClass('js-issuable-form-dropdown'); + + if (isInvalidMilestone) { + return e.preventDefault(); + } + + const isBoardPage = $('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar'); + const isSubmittableIndex = $dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex); + const isSubmittableNonIndex = $dropdown.hasClass('js-filter-submit'); + const isBoardSidebar = $dropdown.hasClass('js-issue-board-sidebar'); + // all reasons not to PUT ^^ + if (isBoardPage) { + this.putIssueBoardPage(); + } else if (isSubmittableIndex) { + this.putSubmittableIndex(); + } else if (isSubmittableNonIndex) { + this.putSubmittableNonIndex(); + } else if (isBoardSidebar) { + this.putIssueBoardSidebar(); + } else { + // PUT it + const abilityName = this.dataset['abilityName']; + + const milestone_id = $selectbox.find('input[type="hidden"]').val(); + const milestonePayload = { [abilityName]: { milestone_id } }; + // Swap out for vue resource. + $.ajax({ type: 'PUT', url: issueUpdateURL, data: milestonePayload }) + .done(data => this.handlePut); + } + } + } + + global.MilestoneSelect = MilestoneSelect; +})(window.gl || (window.gl = {})); -- GitLab From d571cbf16133390c171964775526f4237f187afd Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 10 Nov 2016 13:43:09 +0100 Subject: [PATCH 2/9] Break up milestone select according to SRP. --- .../javascripts/milestone_select.js.es6 | 300 ++++++++++-------- 1 file changed, 167 insertions(+), 133 deletions(-) diff --git a/app/assets/javascripts/milestone_select.js.es6 b/app/assets/javascripts/milestone_select.js.es6 index 5e00e7b9a788..debc8fdfb9df 100644 --- a/app/assets/javascripts/milestone_select.js.es6 +++ b/app/assets/javascripts/milestone_select.js.es6 @@ -2,165 +2,209 @@ (function (global) { class MilestoneSelect { constructor(currentProject) { - this.currentProject = currentProject ? JSON.parse(currentProject) : null; - this.dropdown = document.querySelectorAll('.js-milestone-select'); - this.dataset = this.dropdown.dataset; - // worth checking if this is being run for each time it's called. + this.$el = {}; + this.state = {}; + this.templates = {}; + this.config = {}; + + this.storeDomRefs(); + this.initConfig(); + this.storePageContext(); + this.initTemplates(); + this.initDropdown(); + } + + storeDomRefs() { + const $document = $(document); + const $body = $document.find('body'); + const $dropdown = $document.find('.js-milestone-select'); const $selectbox = $dropdown.closest('.selectbox'); const $block = $selectbox.closest('.block'); - const $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon'); const $value = $block.find('.value'); - const $loading = $block.find('.block-loading').fadeOut(); - // either make member or lookup as needed. - // one dom query total plus jquery plugin - // hasClass add it to the utils - // get rid of all the dom queries + const $loading = $block.find('.block-loading'); + const $collapsedValue = $block.find('.sidebar-collapsed-icon'); - let milestoneLinkTemplate; - let milestoneLinkNoneTemplate; - let collapsedSidebarLabelTemplate; + _.extend(this.$el, { + body: $body, + document: $document, + dropdown: $dropdown, + dropdownSelectBox: $selectbox, + containerBlock: $block, + valueDisplay: $value + loadingDisplay: $loading, + collapsedValue: $collapsedValue + }); + } - - this.initTemplates(); - this.initDropdown(); + storePageContext() { + const $dropdown = this.$el.dropdown; + const currentPage = this.$el.body.data('page'); + + this.config.page = { + isIssueIndex: currentPage === 'projects:issues:index', + isMRIndex: currentPage === 'projects:merge_requests:index', + isBoardSidebar: $dropdown.hasClass('js-issue-board-sidebar'), + isSubmittableNonIndex: $dropdown.hasClass('js-filter-submit'), + isSubmittableIndex: $dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex), + isBoardIndex: $('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar'), + isInvalidMilestone: this.dropdown.hasClass('js-filter-bulk-update') || this.dropdown.hasClass('js-issuable-form-dropdown'), + }; + + const dataset = this.config.dataset = this.$el.dropdown.dataset; + + this.config.display = { + showMenuAbove: dataset['showMenuAbove'], + showNo: dataset['showNo'], + showAny: dataset['showAny'], + showUpcoming: dataset['showUpcoming'], + extraOptions: [] + }; + } + + initState() { + this.state.currentProject = currentProject ? JSON.parse(currentProject) : null; } + initTemplates() { if (this.dataset.issueUpdateURL) { - this.tpl = { - milestoneLinkTemplate: _.template(` - <%- title %> `), - milestoneLinkNoneTemplate: `None`, - collapsedSidebarLabelTemplate: _.template(` - <%- title %> + milestoneLinkNone: `None`, + collapsedSidebarLabel: _.template(` + <%- title %> ` ), - }; + }; } } initDropdown() { - const showMenuAbove = this.dataset['showMenuAbove']; - const fieldName = this.dataset['fieldName']; - const selectedMilestone = this.dataset['selected']; - const isSelected = milestone => milestone.name === selectedMilestone; - const vue = this.dropdown.hasClass('js-issue-board-sidebar'); + const dataset = this.config.dataset; + const selectedMilestone = dataset['selected']; const searchFields = { fields: ['title'] }; - const defaultLabel = dropdownDataset['defaultLabel']; + const isSelected = milestone => milestone.name === selectedMilestone; $(this.dropdown).glDropdown({ - vue, - showMenuAbove, - defaultLabel, - fieldName, isSelected, filterable: true, selectable: true, - id: this.idFilter, search: searchFields, - data: this.fetchData, + defaultLabel: dataset.defaultLabel, + fieldName: dataset.fieldName, + vue: this.config.page.isBoardSidebar, + showMenuAbove: this.config.display.showMenuAbove, + id: this.filterSelected, + data: this.fetchMilestones, text: this.escapeText, - hidden: this.hideStuff, + hidden: this.renderDisplayState, toggleLabel: this.toggleLabel, clicked: this.handleDropdownClick, }); } - hideStuff() { + renderDisplayState() { $selectbox.hide(); // display:block overrides the hide-collapse rule $value.css('display', ''); } - + escapeText(milestone) { return _.escape(milestone.title); } - - idFilter(milestone) { + + filterSelected(milestone) { const useId = this.dataset['useId']; - return (!useId && !$dropdown.is('.js-issuable-form-dropdown')) ? milestone.name : milestone.id; + return (!useId && !this.$el.dropdown.is('.js-issuable-form-dropdown')) ? milestone.name : milestone.id; } - - fetchData(term, callback) { + + fetchMilestones(term, callback) { const milestonesUrl = this.dataset['milestonesUrl']; return $.ajax({ url: milestonesUrl }) .done((data) => { - const extraOptions = prepExtraOptions(); - callback(extraOptions.concat(data)); + this.prepExtraOptions(); + callback(this.config.extraOptions.concat(data)); this.positionMenuAbove(); }); } - + toggleLabel() { - // Question: What are the main datastructures at work. How are the classes organized? const defaultLabel = dropdownDataset['defaultLabel']; - return (selected, el, e) => (selected && selected.id && el.hasClass('is-active')) ? + return (selected, el, e) => (selected && selected.id && el.hasClass('is-active')) ? selected.title : defaultLabel; } - + positionMenuAbove() { - if (showMenuAbove) { - this.dropdown.positionMenuAbove(); + if (this.config.display.showMenuAbove) { + this.$el.dropdown.positionMenuAbove(); } } - - pushExtraOptions(extraOptions, id, name, title) { + + prepExtraOptions() { + if (showAny) this.storeExtraDropdownOptions(0, '', 'Any Milestone'); + if (showNo) this.storeExtraDropdownOptions(-1, 'No Milestone', 'No Milestone'); + if (showUpcoming) this.storeExtraDropdownOptions(-2, '#upcoming', 'Upcoming'); + if (extraOptions.length) this.storeExtraDropdownOptions('divider'); + } + + storeExtraDropdownOptions(id, name, title) { const divider = 'divider'; const pushable = id === divider ? divider : { id, name, title }; - extraOptions.push(pushable); + this.extraOptions.push(pushable); } - - prepExtraOptions() { - const showNo = this.dataset['showNo']; - const showAny = this.dataset['showAny']; - const showMenuAbove = this.dataset['showMenuAbove']; - const showUpcoming = this.dataset['showUpcoming']; - - var extraOptions = []; - if (showAny) { - pushExtraOptions(extraOptions, 0, '', 'Any Milestone'); + + renderLoadingState() { + this.$el.loadingDisplay.fadeIn(); + $dropdown.trigger('loading.gl.dropdown'); + } + + renderLoadedState() { + this.$el.loadingDisplay.fadeOut(); + $dropdown.trigger('loaded.gl.dropdown'); + } + + handleDropdownClick(selected, $el, e) { + const pageConfig = this.config.page; + + if (pageConfig.isInvalidMilestone) { + return e.preventDefault(); } - if (showNo) { - pushExtraOptions(extraOptions, -1, 'No Milestone', 'No Milestone'); + + if (pageConfig.isBoardPage) { + return this.putIssueBoardPage(); } - // DRY ME UP (these configs) - if (showUpcoming) { - pushExtraOptions(extraOptions, -2, '#upcoming', 'Upcoming') + + if (pageConfig.isSubmittableIndex) { + return this.putSubmittableIndex(); } - if (extraOptions.length) { - pushExtraOptions(extraOptions, 'divider'); + + if (pageConfig.isSubmittableNonIndex) { + return this.putSubmittableNonIndex(); } - } - - initLoadingUi() { - $loading.fadeIn(); - $dropdown.trigger('loading.gl.dropdown'); - } - // much better method name - // adds documention to configuration - handlePut(data) { - $dropdown.trigger('loaded.gl.dropdown'); - $loading.fadeOut(); - $selectbox.hide(); - $value.css('display', ''); - if (data.milestone != null) { - data.milestone.namespace = this.currentProject.namespace; - data.milestone.path = this.currentProject.path; - data.milestone.remaining = gl.utils.timeFor(data.milestone.due_date); - $value.html(milestoneLinkTemplate(data.milestone)); - return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone)); - } else { - $value.html(milestoneLinkNoneTemplate); - return $sidebarCollapsedValue.find('span').text('No'); + + if (pageConfig.isBoardSidebar) { + return this.putIssueBoardSidebar(); } + + return this.putGeneric(); + } + + putGeneric() { + const abilityName = this.config.dataset['abilityName']; + const milestone_id = this.$el.dropdownSelectBox.find('input[type="hidden"]').val(); + const milestonePayload = { [abilityName]: { milestone_id } }; + // Swap out for vue resource. + $.ajax({ type: 'PUT', url: issueUpdateURL, data: milestonePayload }) + .done(data => this.handlePut); } - + putIssueBoardPage() { gl.issueBoards.BoardsStore.state.filters[this.dataset['fieldName']] = selected.name; gl.issueBoards.BoardsStore.updateFiltersUrl(); @@ -177,14 +221,10 @@ Vue.delete(gl.issueBoards.BoardsStore.detail.issue, 'milestone'); } - $dropdown.trigger('loading.gl.dropdown'); - $loading.fadeIn(); + this.renderLoadingState(); - gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update')) - .then(function () { - $dropdown.trigger('loaded.gl.dropdown'); - $loading.fadeOut(); - }); + gl.issueBoards.BoardsStore.detail.issue.update(this.config.dataset('issueUpdate')) + .then(() => this.renderLoadedState()); } putSubmittableIndex() { @@ -198,42 +238,36 @@ return $dropdown.closest('form').submit(); } - handleDropdownClick(selected, $el, e) { - // No jq needed here. - const page = $('body').data('page'); - const isIssueIndex = page === 'projects:issues:index'; - const isMRIndex = page === 'projects:merge_requests:index'; - const isInvalidMilestone = this.dropdown.hasClass('js-filter-bulk-update') || this.dropdown.hasClass('js-issuable-form-dropdown'); + handlePutSuccess(data) { + this.renderLoadedState(); + this.$el.dropdownSelectBox.hide(); + this.$el.selectedMilestone.css('display', ''); - if (isInvalidMilestone) { - return e.preventDefault(); + const newMilestone = this.parsePutValue(); + this.writePutValue(newMilestone); + } + + parsePutValue() { + if (data.milestone != null) { + data.milestone.namespace = this.currentProject.namespace; + data.milestone.path = this.currentProject.path; + data.milestone.remaining = gl.utils.timeFor(data.milestone.due_date); } + return data.milestone; + } - const isBoardPage = $('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar'); - const isSubmittableIndex = $dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex); - const isSubmittableNonIndex = $dropdown.hasClass('js-filter-submit'); - const isBoardSidebar = $dropdown.hasClass('js-issue-board-sidebar'); - // all reasons not to PUT ^^ - if (isBoardPage) { - this.putIssueBoardPage(); - } else if (isSubmittableIndex) { - this.putSubmittableIndex(); - } else if (isSubmittableNonIndex) { - this.putSubmittableNonIndex(); - } else if (isBoardSidebar) { - this.putIssueBoardSidebar(); - } else { - // PUT it - const abilityName = this.dataset['abilityName']; - - const milestone_id = $selectbox.find('input[type="hidden"]').val(); - const milestonePayload = { [abilityName]: { milestone_id } }; - // Swap out for vue resource. - $.ajax({ type: 'PUT', url: issueUpdateURL, data: milestonePayload }) - .done(data => this.handlePut); + writePutValue(newMilestone) { + const $valueDisplay = this.$el.valueDisplay; + const $collapsedValue = this.$el.collapsedValue; + + if (newMilestone != null) { + $valueDisplay.html(this.templates.milestoneLink(data.milestone)); + $sidebarCollapsedValue.find('span').html(this.templates.collapsedSidebarLabel(data.milestone)); + } else { + $valueDisplay.html(this.templates.milestoneLinkNone); + $sidebarCollapsedValue.find('span').text('No'); } } } - global.MilestoneSelect = MilestoneSelect; })(window.gl || (window.gl = {})); -- GitLab From f6365e9a3343b591fca217e8ec732bce966eeb07 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 10 Nov 2016 16:04:10 +0100 Subject: [PATCH 3/9] Fix up error causing refs. --- .../javascripts/milestone_select.js.es6 | 118 ++++++++++-------- app/views/shared/issuable/_sidebar.html.haml | 2 +- 2 files changed, 66 insertions(+), 54 deletions(-) diff --git a/app/assets/javascripts/milestone_select.js.es6 b/app/assets/javascripts/milestone_select.js.es6 index debc8fdfb9df..6bed807097fd 100644 --- a/app/assets/javascripts/milestone_select.js.es6 +++ b/app/assets/javascripts/milestone_select.js.es6 @@ -3,21 +3,21 @@ class MilestoneSelect { constructor(currentProject) { this.$el = {}; + this.config = {}; this.state = {}; this.templates = {}; - this.config = {}; - this.storeDomRefs(); - this.initConfig(); - this.storePageContext(); + this.storeElements(); + this.storeConfig(); + this.initState(currentProject); this.initTemplates(); this.initDropdown(); } - storeDomRefs() { + storeElements() { const $document = $(document); const $body = $document.find('body'); - const $dropdown = $document.find('.js-milestone-select'); + const $dropdown = $('.js-milestone-select'); const $selectbox = $dropdown.closest('.selectbox'); const $block = $selectbox.closest('.block'); const $value = $block.find('.value'); @@ -30,43 +30,47 @@ dropdown: $dropdown, dropdownSelectBox: $selectbox, containerBlock: $block, - valueDisplay: $value + valueDisplay: $value, loadingDisplay: $loading, collapsedValue: $collapsedValue }); } - storePageContext() { + storeConfig() { const $dropdown = this.$el.dropdown; const currentPage = this.$el.body.data('page'); - this.config.page = { - isIssueIndex: currentPage === 'projects:issues:index', - isMRIndex: currentPage === 'projects:merge_requests:index', + const isIssue = currentPage === 'projects:issues:index'; + const isMergeRequest = currentPage === 'projects:merge_requests:index'; + + this.config.context = { + isIssue, + isMergeRequest, isBoardSidebar: $dropdown.hasClass('js-issue-board-sidebar'), isSubmittableNonIndex: $dropdown.hasClass('js-filter-submit'), - isSubmittableIndex: $dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex), - isBoardIndex: $('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar'), - isInvalidMilestone: this.dropdown.hasClass('js-filter-bulk-update') || this.dropdown.hasClass('js-issuable-form-dropdown'), + isSubmittableIndex: $dropdown.hasClass('js-filter-submit') && (isIssue || isMergeRequest), + isBoard: $('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar'), + shouldPreventSubmission: $dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown'), }; - const dataset = this.config.dataset = this.$el.dropdown.dataset; + const dataset = this.config.dataset = this.$el.dropdown.get(0).dataset; this.config.display = { - showMenuAbove: dataset['showMenuAbove'], - showNo: dataset['showNo'], - showAny: dataset['showAny'], - showUpcoming: dataset['showUpcoming'], + showMenuAbove: dataset.showMenuAbove, + showNo: dataset.showNo, + showAny: dataset.showAny, + showUpcoming: dataset.showUpcoming, extraOptions: [] }; } - initState() { + initState(currentProject) { this.state.currentProject = currentProject ? JSON.parse(currentProject) : null; + this.state.selectedMilestone = this.config.dataset.selected; } initTemplates() { - if (this.dataset.issueUpdateURL) { + if (this.config.dataset.issueUpdateURL) { this.templates = { milestoneLink: _.template(` milestone.name === selectedMilestone; - $(this.dropdown).glDropdown({ + this.$el.dropdown.glDropdown({ isSelected, filterable: true, selectable: true, search: searchFields, defaultLabel: dataset.defaultLabel, fieldName: dataset.fieldName, - vue: this.config.page.isBoardSidebar, + vue: this.config.context.isBoardSidebar, showMenuAbove: this.config.display.showMenuAbove, - id: this.filterSelected, - data: this.fetchMilestones, + id: milestone => this.filterSelected(milestone), + data: (term, callback) => this.fetchMilestones(term, callback), text: this.escapeText, - hidden: this.renderDisplayState, - toggleLabel: this.toggleLabel, - clicked: this.handleDropdownClick, + hidden: () => this.renderDisplayState(), + toggleLabel: () => this.toggleLabel(), + clicked: (selected, $el, e) => this.handleDropdownClick(selected, $el, e), }); + + this.renderLoadedState(); } renderDisplayState() { - $selectbox.hide(); + this.$el.dropdownSelectBox.hide(); // display:block overrides the hide-collapse rule - $value.css('display', ''); + this.$el.valueDisplay.css('display', ''); } @@ -119,23 +125,23 @@ } filterSelected(milestone) { - const useId = this.dataset['useId']; + const useId = this.config.dataset.useId; return (!useId && !this.$el.dropdown.is('.js-issuable-form-dropdown')) ? milestone.name : milestone.id; } fetchMilestones(term, callback) { - const milestonesUrl = this.dataset['milestonesUrl']; - + const milestonesUrl = this.config.dataset.milestonesUrl; return $.ajax({ url: milestonesUrl }) .done((data) => { this.prepExtraOptions(); - callback(this.config.extraOptions.concat(data)); + callback(this.config.display.extraOptions.concat(data)); this.positionMenuAbove(); }); } toggleLabel() { - const defaultLabel = dropdownDataset['defaultLabel']; + const defaultLabel = this.config.dataset.defaultLabel; + const selected = this.state.selectedMilestone; return (selected, el, e) => (selected && selected.id && el.hasClass('is-active')) ? selected.title : defaultLabel; @@ -148,32 +154,33 @@ } prepExtraOptions() { - if (showAny) this.storeExtraDropdownOptions(0, '', 'Any Milestone'); - if (showNo) this.storeExtraDropdownOptions(-1, 'No Milestone', 'No Milestone'); - if (showUpcoming) this.storeExtraDropdownOptions(-2, '#upcoming', 'Upcoming'); - if (extraOptions.length) this.storeExtraDropdownOptions('divider'); + const displayConfig = this.config.display; + if (displayConfig.showAny) this.storeExtraDropdownOptions(0, '', 'Any Milestone'); + if (displayConfig.showNo) this.storeExtraDropdownOptions(-1, 'No Milestone', 'No Milestone'); + if (displayConfig.showUpcoming) this.storeExtraDropdownOptions(-2, '#upcoming', 'Upcoming'); + if (displayConfig.extraOptions.length) this.storeExtraDropdownOptions('divider'); } storeExtraDropdownOptions(id, name, title) { const divider = 'divider'; const pushable = id === divider ? divider : { id, name, title }; - this.extraOptions.push(pushable); + this.config.display.extraOptions.push(pushable); } renderLoadingState() { this.$el.loadingDisplay.fadeIn(); - $dropdown.trigger('loading.gl.dropdown'); + this.$el.dropdown.trigger('loading.gl.dropdown'); } renderLoadedState() { this.$el.loadingDisplay.fadeOut(); - $dropdown.trigger('loaded.gl.dropdown'); + this.$el.dropdown.trigger('loaded.gl.dropdown'); } handleDropdownClick(selected, $el, e) { - const pageConfig = this.config.page; + const pageConfig = this.config.context; - if (pageConfig.isInvalidMilestone) { + if (pageConfig.shouldPreventSubmission) { return e.preventDefault(); } @@ -193,20 +200,24 @@ return this.putIssueBoardSidebar(); } - return this.putGeneric(); + return this.putGeneric(selected, $el); } - putGeneric() { - const abilityName = this.config.dataset['abilityName']; + putGeneric(selected, $el) { + const abilityName = this.config.dataset.abilityName; const milestone_id = this.$el.dropdownSelectBox.find('input[type="hidden"]').val(); const milestonePayload = { [abilityName]: { milestone_id } }; + const issueUpdateURL = this.config.dataset.issueUpdate; // Swap out for vue resource. + debugger; $.ajax({ type: 'PUT', url: issueUpdateURL, data: milestonePayload }) - .done(data => this.handlePut); + .done(data => { + this.handleSuccess(data); + }); } putIssueBoardPage() { - gl.issueBoards.BoardsStore.state.filters[this.dataset['fieldName']] = selected.name; + gl.issueBoards.BoardsStore.state.filters[this.config.dataset.fieldName] = selected.name; gl.issueBoards.BoardsStore.updateFiltersUrl(); e.preventDefault(); } @@ -223,19 +234,20 @@ this.renderLoadingState(); - gl.issueBoards.BoardsStore.detail.issue.update(this.config.dataset('issueUpdate')) + gl.issueBoards.BoardsStore.detail.issue.update(this.config.dataset.issueUpdate) .then(() => this.renderLoadedState()); } putSubmittableIndex() { - const selectedMilestone = this.dataset['selected']; + // THIS IS MISPLACED WILL BE NEED SOMEWHERe + let selectedMilestone = this.state.selected; // Pay attention here... looks like this is mutating selected milestone selectedMilestone = selected.name ? select.name : ''; return Issuable.filterResults($dropdown.closest('form')); } putSubmittableNonIndex() { - return $dropdown.closest('form').submit(); + return this.$el.dropdown.closest('form').submit(); } handlePutSuccess(data) { diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 7363ead09ff7..2be1331f9190 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -167,7 +167,7 @@ = clipboard_button(clipboard_text: project_ref) :javascript - new MilestoneSelect('{"namespace":"#{@project.namespace.path}","path":"#{@project.path}"}'); + new gl.MilestoneSelect('{"namespace":"#{@project.namespace.path}","path":"#{@project.path}"}'); new LabelsSelect(); new IssuableContext('#{escape_javascript(current_user.to_json(only: [:username, :id, :name]))}'); new Subscription('.subscription') -- GitLab From 0ac3f962b855960cd70ff7f75f8619b4dc6c7b07 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 10 Nov 2016 17:06:35 +0100 Subject: [PATCH 4/9] Fix remaining errors, have all use cases working. --- .../javascripts/milestone_select.js.es6 | 87 ++++++++++--------- app/views/shared/issuable/_sidebar.html.haml | 2 +- 2 files changed, 46 insertions(+), 43 deletions(-) diff --git a/app/assets/javascripts/milestone_select.js.es6 b/app/assets/javascripts/milestone_select.js.es6 index 6bed807097fd..35d9340b796f 100644 --- a/app/assets/javascripts/milestone_select.js.es6 +++ b/app/assets/javascripts/milestone_select.js.es6 @@ -23,6 +23,7 @@ const $value = $block.find('.value'); const $loading = $block.find('.block-loading'); const $collapsedValue = $block.find('.sidebar-collapsed-icon'); + const $form = $dropdown.closest('form'); _.extend(this.$el, { body: $body, @@ -32,7 +33,8 @@ containerBlock: $block, valueDisplay: $value, loadingDisplay: $loading, - collapsedValue: $collapsedValue + collapsedValue: $collapsedValue, + form: $form }); } @@ -70,7 +72,7 @@ } initTemplates() { - if (this.config.dataset.issueUpdateURL) { + if (this.config.dataset.issueUpdate) { this.templates = { milestoneLink: _.template(` this.fetchMilestones(term, callback), text: this.escapeText, hidden: () => this.renderDisplayState(), - toggleLabel: () => this.toggleLabel(), + toggleLabel: (selected, $el, e) => this.toggleLabel(selected, $el, e), clicked: (selected, $el, e) => this.handleDropdownClick(selected, $el, e), }); @@ -130,7 +132,7 @@ } fetchMilestones(term, callback) { - const milestonesUrl = this.config.dataset.milestonesUrl; + const milestonesUrl = this.config.dataset.milestones; return $.ajax({ url: milestonesUrl }) .done((data) => { this.prepExtraOptions(); @@ -139,9 +141,8 @@ }); } - toggleLabel() { + toggleLabel(selected, $el, e) { const defaultLabel = this.config.dataset.defaultLabel; - const selected = this.state.selectedMilestone; return (selected, el, e) => (selected && selected.id && el.hasClass('is-active')) ? selected.title : defaultLabel; @@ -185,44 +186,48 @@ } if (pageConfig.isBoardPage) { - return this.putIssueBoardPage(); + return this.putIssueBoardPage(selected, $el, e); } if (pageConfig.isSubmittableIndex) { - return this.putSubmittableIndex(); + return this.putSubmittableIndex(selected, $el, e); } if (pageConfig.isSubmittableNonIndex) { - return this.putSubmittableNonIndex(); + return this.putSubmittableNonIndex(selected, $el, e); } if (pageConfig.isBoardSidebar) { - return this.putIssueBoardSidebar(); + return this.putIssueBoardSidebar(selected, $el, e); } - return this.putGeneric(selected, $el); + return this.putGeneric(selected, $el, e); } - putGeneric(selected, $el) { + putGeneric(selected, $el, e) { + const selectedMilestone = this.$el.dropdownSelectBox.find('input[type="hidden"]').val() || selected.id; + const milestonePayload = {}; const abilityName = this.config.dataset.abilityName; - const milestone_id = this.$el.dropdownSelectBox.find('input[type="hidden"]').val(); - const milestonePayload = { [abilityName]: { milestone_id } }; + + milestonePayload[abilityName] = {}; + milestonePayload[abilityName].milestone_id = selectedMilestone; + const issueUpdateURL = this.config.dataset.issueUpdate; // Swap out for vue resource. - debugger; + this.renderLoadingState(); $.ajax({ type: 'PUT', url: issueUpdateURL, data: milestonePayload }) .done(data => { - this.handleSuccess(data); + this.handlePutSuccess(data); }); } - putIssueBoardPage() { + putIssueBoardPage(selected, $el, e) { gl.issueBoards.BoardsStore.state.filters[this.config.dataset.fieldName] = selected.name; gl.issueBoards.BoardsStore.updateFiltersUrl(); e.preventDefault(); } - putIssueBoardSidebar() { + putIssueBoardSidebar(selected, $el, e) { if (selected.id !== -1) { Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'milestone', new ListMilestone({ id: selected.id, @@ -238,48 +243,46 @@ .then(() => this.renderLoadedState()); } - putSubmittableIndex() { - // THIS IS MISPLACED WILL BE NEED SOMEWHERe - let selectedMilestone = this.state.selected; - // Pay attention here... looks like this is mutating selected milestone - selectedMilestone = selected.name ? select.name : ''; - return Issuable.filterResults($dropdown.closest('form')); + putSubmittableIndex(selected, $el, e) { + return Issuable.filterResults(this.$el.form); } - putSubmittableNonIndex() { - return this.$el.dropdown.closest('form').submit(); + putSubmittableNonIndex(selected, $el, e) { + return this.$el.form.submit(); } handlePutSuccess(data) { this.renderLoadedState(); - this.$el.dropdownSelectBox.hide(); - this.$el.selectedMilestone.css('display', ''); - const newMilestone = this.parsePutValue(); - this.writePutValue(newMilestone); + data.milestone = this.parsePutValue(data); + this.writePutValue(data); } - parsePutValue() { - if (data.milestone != null) { - data.milestone.namespace = this.currentProject.namespace; - data.milestone.path = this.currentProject.path; - data.milestone.remaining = gl.utils.timeFor(data.milestone.due_date); + parsePutValue(data) { + const milestoneData = data.milestone; + if (milestoneData != null) { + const currentProject = this.state.currentProject; + milestoneData.namespace = currentProject.namespace; + milestoneData.path = currentProject.path; + milestoneData.remaining = gl.utils.timeFor(milestoneData.due_date); } - return data.milestone; + return milestoneData; } - writePutValue(newMilestone) { + writePutValue(data) { const $valueDisplay = this.$el.valueDisplay; const $collapsedValue = this.$el.collapsedValue; + const milestoneData = data.milestone; - if (newMilestone != null) { - $valueDisplay.html(this.templates.milestoneLink(data.milestone)); - $sidebarCollapsedValue.find('span').html(this.templates.collapsedSidebarLabel(data.milestone)); + if (milestoneData != null) { + $valueDisplay.html(this.templates.milestoneLink(milestoneData)); + $collapsedValue.find('span').html(this.templates.collapsedSidebarLabel(milestoneData)); } else { $valueDisplay.html(this.templates.milestoneLinkNone); - $sidebarCollapsedValue.find('span').text('No'); + $collapsedValue.find('span').text('No'); } } } global.MilestoneSelect = MilestoneSelect; -})(window.gl || (window.gl = {})); +})(window); + diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 2be1331f9190..7363ead09ff7 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -167,7 +167,7 @@ = clipboard_button(clipboard_text: project_ref) :javascript - new gl.MilestoneSelect('{"namespace":"#{@project.namespace.path}","path":"#{@project.path}"}'); + new MilestoneSelect('{"namespace":"#{@project.namespace.path}","path":"#{@project.path}"}'); new LabelsSelect(); new IssuableContext('#{escape_javascript(current_user.to_json(only: [:username, :id, :name]))}'); new Subscription('.subscription') -- GitLab From 2a76c66d85e651dcfc7793015e268579f950ffeb Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 10 Nov 2016 17:22:41 +0100 Subject: [PATCH 5/9] Update verbiage for propagating state. --- app/assets/javascripts/milestone_select.js.es6 | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/milestone_select.js.es6 b/app/assets/javascripts/milestone_select.js.es6 index 35d9340b796f..672ba700132c 100644 --- a/app/assets/javascripts/milestone_select.js.es6 +++ b/app/assets/javascripts/milestone_select.js.es6 @@ -216,8 +216,8 @@ // Swap out for vue resource. this.renderLoadingState(); $.ajax({ type: 'PUT', url: issueUpdateURL, data: milestonePayload }) - .done(data => { - this.handlePutSuccess(data); + .done(issuableData => { + this.handlePutSuccess(issuableData); }); } @@ -251,11 +251,11 @@ return this.$el.form.submit(); } - handlePutSuccess(data) { + handlePutSuccess(issuableData) { this.renderLoadedState(); - data.milestone = this.parsePutValue(data); - this.writePutValue(data); + issuableData.milestone = this.parsePutValue(issuableData); + this.renderUpdatedState(issuableData); } parsePutValue(data) { @@ -269,10 +269,10 @@ return milestoneData; } - writePutValue(data) { + renderUpdatedState(issuableData) { const $valueDisplay = this.$el.valueDisplay; const $collapsedValue = this.$el.collapsedValue; - const milestoneData = data.milestone; + const milestoneData = issuableData.milestone; if (milestoneData != null) { $valueDisplay.html(this.templates.milestoneLink(milestoneData)); @@ -282,6 +282,10 @@ $collapsedValue.find('span').text('No'); } } + + updateState(issuableData) { + this.renderUpdatedState(issuableData); + } } global.MilestoneSelect = MilestoneSelect; })(window); -- GitLab From 346df8fb516a657b64d970deea2c6d543693c736 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 10 Nov 2016 17:31:16 +0100 Subject: [PATCH 6/9] Add todos, improve data variables. --- app/assets/javascripts/milestone_select.js.es6 | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/milestone_select.js.es6 b/app/assets/javascripts/milestone_select.js.es6 index 672ba700132c..8171ec87c24d 100644 --- a/app/assets/javascripts/milestone_select.js.es6 +++ b/app/assets/javascripts/milestone_select.js.es6 @@ -17,7 +17,7 @@ storeElements() { const $document = $(document); const $body = $document.find('body'); - const $dropdown = $('.js-milestone-select'); + const $dropdown = $document.find('.js-milestone-select'); const $selectbox = $dropdown.closest('.selectbox'); const $block = $selectbox.closest('.block'); const $value = $block.find('.value'); @@ -100,13 +100,13 @@ filterable: true, selectable: true, search: searchFields, - defaultLabel: dataset.defaultLabel, fieldName: dataset.fieldName, + defaultLabel: dataset.defaultLabel, vue: this.config.context.isBoardSidebar, showMenuAbove: this.config.display.showMenuAbove, id: milestone => this.filterSelected(milestone), data: (term, callback) => this.fetchMilestones(term, callback), - text: this.escapeText, + text: item => this.escapeText(item), hidden: () => this.renderDisplayState(), toggleLabel: (selected, $el, e) => this.toggleLabel(selected, $el, e), clicked: (selected, $el, e) => this.handleDropdownClick(selected, $el, e), @@ -119,7 +119,6 @@ this.$el.dropdownSelectBox.hide(); // display:block overrides the hide-collapse rule this.$el.valueDisplay.css('display', ''); - } escapeText(milestone) { @@ -134,9 +133,9 @@ fetchMilestones(term, callback) { const milestonesUrl = this.config.dataset.milestones; return $.ajax({ url: milestonesUrl }) - .done((data) => { + .done((milestones) => { this.prepExtraOptions(); - callback(this.config.display.extraOptions.concat(data)); + callback(this.config.display.extraOptions.concat(milestones)); this.positionMenuAbove(); }); } @@ -213,7 +212,7 @@ milestonePayload[abilityName].milestone_id = selectedMilestone; const issueUpdateURL = this.config.dataset.issueUpdate; - // Swap out for vue resource. + // TODO: Use issuable pub/sub resource method to propagate changes this.renderLoadingState(); $.ajax({ type: 'PUT', url: issueUpdateURL, data: milestonePayload }) .done(issuableData => { @@ -284,6 +283,7 @@ } updateState(issuableData) { + // TODO: Pass this to a pub/sub resource to update async this.renderUpdatedState(issuableData); } } -- GitLab From bd2612c89ecf9e23295adb63fbb7a3223115863b Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 10 Nov 2016 17:47:56 +0100 Subject: [PATCH 7/9] Arrange methods in a more logical order. --- .../javascripts/milestone_select.js.es6 | 170 +++++++++--------- 1 file changed, 87 insertions(+), 83 deletions(-) diff --git a/app/assets/javascripts/milestone_select.js.es6 b/app/assets/javascripts/milestone_select.js.es6 index 8171ec87c24d..acb125050b2f 100644 --- a/app/assets/javascripts/milestone_select.js.es6 +++ b/app/assets/javascripts/milestone_select.js.es6 @@ -2,10 +2,10 @@ (function (global) { class MilestoneSelect { constructor(currentProject) { - this.$el = {}; - this.config = {}; - this.state = {}; - this.templates = {}; + this.$el = null; + this.config = null; + this.state = null; + this.templates = null; this.storeElements(); this.storeConfig(); @@ -25,7 +25,7 @@ const $collapsedValue = $block.find('.sidebar-collapsed-icon'); const $form = $dropdown.closest('form'); - _.extend(this.$el, { + this.$el = { body: $body, document: $document, dropdown: $dropdown, @@ -34,8 +34,8 @@ valueDisplay: $value, loadingDisplay: $loading, collapsedValue: $collapsedValue, - form: $form - }); + form: $form, + }; } storeConfig() { @@ -67,8 +67,10 @@ } initState(currentProject) { - this.state.currentProject = currentProject ? JSON.parse(currentProject) : null; - this.state.selectedMilestone = this.config.dataset.selected; + this.state = { + currentProject: currentProject ? JSON.parse(currentProject) : null, + selectedMilestone: this.config.dataset.selected, + }; } initTemplates() { @@ -104,10 +106,10 @@ defaultLabel: dataset.defaultLabel, vue: this.config.context.isBoardSidebar, showMenuAbove: this.config.display.showMenuAbove, - id: milestone => this.filterSelected(milestone), - data: (term, callback) => this.fetchMilestones(term, callback), text: item => this.escapeText(item), hidden: () => this.renderDisplayState(), + id: milestone => this.filterSelected(milestone), + data: (term, callback) => this.fetchMilestones(term, callback), toggleLabel: (selected, $el, e) => this.toggleLabel(selected, $el, e), clicked: (selected, $el, e) => this.handleDropdownClick(selected, $el, e), }); @@ -115,12 +117,6 @@ this.renderLoadedState(); } - renderDisplayState() { - this.$el.dropdownSelectBox.hide(); - // display:block overrides the hide-collapse rule - this.$el.valueDisplay.css('display', ''); - } - escapeText(milestone) { return _.escape(milestone.title); } @@ -147,40 +143,10 @@ selected.title : defaultLabel; } - positionMenuAbove() { - if (this.config.display.showMenuAbove) { - this.$el.dropdown.positionMenuAbove(); - } - } - - prepExtraOptions() { - const displayConfig = this.config.display; - if (displayConfig.showAny) this.storeExtraDropdownOptions(0, '', 'Any Milestone'); - if (displayConfig.showNo) this.storeExtraDropdownOptions(-1, 'No Milestone', 'No Milestone'); - if (displayConfig.showUpcoming) this.storeExtraDropdownOptions(-2, '#upcoming', 'Upcoming'); - if (displayConfig.extraOptions.length) this.storeExtraDropdownOptions('divider'); - } - - storeExtraDropdownOptions(id, name, title) { - const divider = 'divider'; - const pushable = id === divider ? divider : { id, name, title }; - this.config.display.extraOptions.push(pushable); - } - - renderLoadingState() { - this.$el.loadingDisplay.fadeIn(); - this.$el.dropdown.trigger('loading.gl.dropdown'); - } - - renderLoadedState() { - this.$el.loadingDisplay.fadeOut(); - this.$el.dropdown.trigger('loaded.gl.dropdown'); - } - handleDropdownClick(selected, $el, e) { const pageConfig = this.config.context; - if (pageConfig.shouldPreventSubmission) { + if (pageConfig.shouldPrevntSubmission) { return e.preventDefault(); } @@ -203,6 +169,61 @@ return this.putGeneric(selected, $el, e); } + renderDisplayState() { + this.$el.dropdownSelectBox.hide(); + // display:block overrides the hide-collapse rule + this.$el.valueDisplay.css('display', ''); + } + + updateState(issuableData) { + // TODO: Pass this to a pub/sub resource to update async + this.renderUpdatedState(issuableData); + } + + renderUpdatedState(issuableData) { + const $valueDisplay = this.$el.valueDisplay; + const $collapsedValue = this.$el.collapsedValue; + const milestoneData = issuableData.milestone; + + if (milestoneData != null) { + $valueDisplay.html(this.templates.milestoneLink(milestoneData)); + $collapsedValue.find('span').html(this.templates.collapsedSidebarLabel(milestoneData)); + } else { + $valueDisplay.html(this.templates.milestoneLinkNone); + $collapsedValue.find('span').text('No'); + } + } + + renderLoadingState() { + this.$el.loadingDisplay.fadeIn(); + this.$el.dropdown.trigger('loading.gl.dropdown'); + } + + renderLoadedState() { + this.$el.loadingDisplay.fadeOut(); + this.$el.dropdown.trigger('loaded.gl.dropdown'); + } + + positionMenuAbove() { + if (this.config.display.showMenuAbove) { + this.$el.dropdown.positionMenuAbove(); + } + } + + prepExtraOptions() { + const displayConfig = this.config.display; + if (displayConfig.showAny) this.storeExtraDropdownOptions(0, '', 'Any Milestone'); + if (displayConfig.showNo) this.storeExtraDropdownOptions(-1, 'No Milestone', 'No Milestone'); + if (displayConfig.showUpcoming) this.storeExtraDropdownOptions(-2, '#upcoming', 'Upcoming'); + if (displayConfig.extraOptions.length) this.storeExtraDropdownOptions('divider'); + } + + storeExtraDropdownOptions(id, name, title) { + const divider = 'divider'; + const pushable = id === divider ? divider : { id, name, title }; + this.config.display.extraOptions.push(pushable); + } + putGeneric(selected, $el, e) { const selectedMilestone = this.$el.dropdownSelectBox.find('input[type="hidden"]').val() || selected.id; const milestonePayload = {}; @@ -220,6 +241,24 @@ }); } + handlePutSuccess(issuableData) { + this.renderLoadedState(); + + issuableData.milestone = this.parsePutValue(issuableData); + this.renderUpdatedState(issuableData); + } + + parsePutValue(data) { + const milestoneData = data.milestone; + if (milestoneData != null) { + const currentProject = this.state.currentProject; + milestoneData.namespace = currentProject.namespace; + milestoneData.path = currentProject.path; + milestoneData.remaining = gl.utils.timeFor(milestoneData.due_date); + } + return milestoneData; + } + putIssueBoardPage(selected, $el, e) { gl.issueBoards.BoardsStore.state.filters[this.config.dataset.fieldName] = selected.name; gl.issueBoards.BoardsStore.updateFiltersUrl(); @@ -250,42 +289,7 @@ return this.$el.form.submit(); } - handlePutSuccess(issuableData) { - this.renderLoadedState(); - - issuableData.milestone = this.parsePutValue(issuableData); - this.renderUpdatedState(issuableData); - } - - parsePutValue(data) { - const milestoneData = data.milestone; - if (milestoneData != null) { - const currentProject = this.state.currentProject; - milestoneData.namespace = currentProject.namespace; - milestoneData.path = currentProject.path; - milestoneData.remaining = gl.utils.timeFor(milestoneData.due_date); - } - return milestoneData; - } - renderUpdatedState(issuableData) { - const $valueDisplay = this.$el.valueDisplay; - const $collapsedValue = this.$el.collapsedValue; - const milestoneData = issuableData.milestone; - - if (milestoneData != null) { - $valueDisplay.html(this.templates.milestoneLink(milestoneData)); - $collapsedValue.find('span').html(this.templates.collapsedSidebarLabel(milestoneData)); - } else { - $valueDisplay.html(this.templates.milestoneLinkNone); - $collapsedValue.find('span').text('No'); - } - } - - updateState(issuableData) { - // TODO: Pass this to a pub/sub resource to update async - this.renderUpdatedState(issuableData); - } } global.MilestoneSelect = MilestoneSelect; })(window); -- GitLab From 8bd03e1dbcfc14b56831c8b00625ed870b148aab Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 10 Nov 2016 17:57:39 +0100 Subject: [PATCH 8/9] Use cached html ref, and move methods into chron order. --- .../javascripts/milestone_select.js.es6 | 68 ++++++++++--------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/app/assets/javascripts/milestone_select.js.es6 b/app/assets/javascripts/milestone_select.js.es6 index acb125050b2f..94114d22a38a 100644 --- a/app/assets/javascripts/milestone_select.js.es6 +++ b/app/assets/javascripts/milestone_select.js.es6 @@ -16,6 +16,7 @@ storeElements() { const $document = $(document); + const $html = $document.find('html'); const $body = $document.find('body'); const $dropdown = $document.find('.js-milestone-select'); const $selectbox = $dropdown.closest('.selectbox'); @@ -26,8 +27,9 @@ const $form = $dropdown.closest('form'); this.$el = { - body: $body, document: $document, + html: $html, + body: $body, dropdown: $dropdown, dropdownSelectBox: $selectbox, containerBlock: $block, @@ -51,7 +53,7 @@ isBoardSidebar: $dropdown.hasClass('js-issue-board-sidebar'), isSubmittableNonIndex: $dropdown.hasClass('js-filter-submit'), isSubmittableIndex: $dropdown.hasClass('js-filter-submit') && (isIssue || isMergeRequest), - isBoard: $('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar'), + isBoard: this.$el.html.hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar'), shouldPreventSubmission: $dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown'), }; @@ -108,7 +110,7 @@ showMenuAbove: this.config.display.showMenuAbove, text: item => this.escapeText(item), hidden: () => this.renderDisplayState(), - id: milestone => this.filterSelected(milestone), + id: milestone => this.displaySelected(milestone), data: (term, callback) => this.fetchMilestones(term, callback), toggleLabel: (selected, $el, e) => this.toggleLabel(selected, $el, e), clicked: (selected, $el, e) => this.handleDropdownClick(selected, $el, e), @@ -117,11 +119,7 @@ this.renderLoadedState(); } - escapeText(milestone) { - return _.escape(milestone.title); - } - - filterSelected(milestone) { + displaySelected(milestone) { const useId = this.config.dataset.useId; return (!useId && !this.$el.dropdown.is('.js-issuable-form-dropdown')) ? milestone.name : milestone.id; } @@ -129,18 +127,27 @@ fetchMilestones(term, callback) { const milestonesUrl = this.config.dataset.milestones; return $.ajax({ url: milestonesUrl }) - .done((milestones) => { - this.prepExtraOptions(); - callback(this.config.display.extraOptions.concat(milestones)); - this.positionMenuAbove(); - }); + .done((milestones, callback) => this.handleFetchSuccess(milestones, callback)); } - toggleLabel(selected, $el, e) { - const defaultLabel = this.config.dataset.defaultLabel; + handleFetchSuccess(milestones, callback) { + this.prepExtraOptions(); + callback(this.config.display.extraOptions.concat(milestones)); + this.positionMenuAbove(); + } - return (selected, el, e) => (selected && selected.id && el.hasClass('is-active')) ? - selected.title : defaultLabel; + prepExtraOptions() { + const displayConfig = this.config.display; + if (displayConfig.showAny) this.storeExtraDropdownOptions(0, '', 'Any Milestone'); + if (displayConfig.showNo) this.storeExtraDropdownOptions(-1, 'No Milestone', 'No Milestone'); + if (displayConfig.showUpcoming) this.storeExtraDropdownOptions(-2, '#upcoming', 'Upcoming'); + if (displayConfig.extraOptions.length) this.storeExtraDropdownOptions('divider'); + } + + storeExtraDropdownOptions(id, name, title) { + const divider = 'divider'; + const pushable = id === divider ? divider : { id, name, title }; + this.config.display.extraOptions.push(pushable); } handleDropdownClick(selected, $el, e) { @@ -169,6 +176,17 @@ return this.putGeneric(selected, $el, e); } + toggleLabel(selected, $el, e) { + const defaultLabel = this.config.dataset.defaultLabel; + + return (selected, el, e) => (selected && selected.id && el.hasClass('is-active')) ? + selected.title : defaultLabel; + } + + escapeText(milestone) { + return _.escape(milestone.title); + } + renderDisplayState() { this.$el.dropdownSelectBox.hide(); // display:block overrides the hide-collapse rule @@ -210,20 +228,6 @@ } } - prepExtraOptions() { - const displayConfig = this.config.display; - if (displayConfig.showAny) this.storeExtraDropdownOptions(0, '', 'Any Milestone'); - if (displayConfig.showNo) this.storeExtraDropdownOptions(-1, 'No Milestone', 'No Milestone'); - if (displayConfig.showUpcoming) this.storeExtraDropdownOptions(-2, '#upcoming', 'Upcoming'); - if (displayConfig.extraOptions.length) this.storeExtraDropdownOptions('divider'); - } - - storeExtraDropdownOptions(id, name, title) { - const divider = 'divider'; - const pushable = id === divider ? divider : { id, name, title }; - this.config.display.extraOptions.push(pushable); - } - putGeneric(selected, $el, e) { const selectedMilestone = this.$el.dropdownSelectBox.find('input[type="hidden"]').val() || selected.id; const milestonePayload = {}; @@ -288,8 +292,6 @@ putSubmittableNonIndex(selected, $el, e) { return this.$el.form.submit(); } - - } global.MilestoneSelect = MilestoneSelect; })(window); -- GitLab From ccc5c1558a9facd593c92b49f1ad9d04d4782300 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 10 Nov 2016 18:36:19 +0100 Subject: [PATCH 9/9] Shush eslint. --- .eslintrc | 1 + .../javascripts/milestone_select.js.es6 | 57 +++++++++---------- 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/.eslintrc b/.eslintrc index fd26215b8439..d5cabbe4fd9b 100644 --- a/.eslintrc +++ b/.eslintrc @@ -7,6 +7,7 @@ "filenames/match-regex": [2, "^[a-z0-9_]+(.js)?$"] }, "globals": { + "Vue": false, "$": false, "_": false, "beforeEach": false, diff --git a/app/assets/javascripts/milestone_select.js.es6 b/app/assets/javascripts/milestone_select.js.es6 index 94114d22a38a..c43a397182b3 100644 --- a/app/assets/javascripts/milestone_select.js.es6 +++ b/app/assets/javascripts/milestone_select.js.es6 @@ -1,5 +1,5 @@ -/* eslint-disable */ -(function (global) { +/* eslint-disable no-undef */ +(() => { class MilestoneSelect { constructor(currentProject) { this.$el = null; @@ -50,11 +50,11 @@ this.config.context = { isIssue, isMergeRequest, - isBoardSidebar: $dropdown.hasClass('js-issue-board-sidebar'), + isBoardSidebar: $dropdown.hasClass('js-issue-board-sidebar'), isSubmittableNonIndex: $dropdown.hasClass('js-filter-submit'), isSubmittableIndex: $dropdown.hasClass('js-filter-submit') && (isIssue || isMergeRequest), isBoard: this.$el.html.hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar'), - shouldPreventSubmission: $dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown'), + shouldPreventSubmission: $dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown'), }; const dataset = this.config.dataset = this.$el.dropdown.get(0).dataset; @@ -64,7 +64,7 @@ showNo: dataset.showNo, showAny: dataset.showAny, showUpcoming: dataset.showUpcoming, - extraOptions: [] + extraOptions: [], }; } @@ -83,7 +83,7 @@ data-container='body' title='<%- remaining %>'><%- title %> `), - milestoneLinkNone: `None`, + milestoneLinkNone: 'None', collapsedSidebarLabel: _.template(` <%- title %> @@ -108,11 +108,11 @@ defaultLabel: dataset.defaultLabel, vue: this.config.context.isBoardSidebar, showMenuAbove: this.config.display.showMenuAbove, - text: item => this.escapeText(item), + text: milestone => _.escape(milestone.title), hidden: () => this.renderDisplayState(), id: milestone => this.displaySelected(milestone), data: (term, callback) => this.fetchMilestones(term, callback), - toggleLabel: (selected, $el, e) => this.toggleLabel(selected, $el, e), + toggleLabel: (selected, $el) => this.toggleLabel(selected, $el), clicked: (selected, $el, e) => this.handleDropdownClick(selected, $el, e), }); @@ -127,7 +127,7 @@ fetchMilestones(term, callback) { const milestonesUrl = this.config.dataset.milestones; return $.ajax({ url: milestonesUrl }) - .done((milestones, callback) => this.handleFetchSuccess(milestones, callback)); + .done(milestones => this.handleFetchSuccess(milestones, callback)); } handleFetchSuccess(milestones, callback) { @@ -176,15 +176,9 @@ return this.putGeneric(selected, $el, e); } - toggleLabel(selected, $el, e) { + toggleLabel(selected, $el) { const defaultLabel = this.config.dataset.defaultLabel; - - return (selected, el, e) => (selected && selected.id && el.hasClass('is-active')) ? - selected.title : defaultLabel; - } - - escapeText(milestone) { - return _.escape(milestone.title); + return (selected && selected.id && $el.hasClass('is-active')) ? selected.title : defaultLabel; } renderDisplayState() { @@ -206,7 +200,7 @@ if (milestoneData != null) { $valueDisplay.html(this.templates.milestoneLink(milestoneData)); $collapsedValue.find('span').html(this.templates.collapsedSidebarLabel(milestoneData)); - } else { + } else { $valueDisplay.html(this.templates.milestoneLinkNone); $collapsedValue.find('span').text('No'); } @@ -228,7 +222,7 @@ } } - putGeneric(selected, $el, e) { + putGeneric(selected) { const selectedMilestone = this.$el.dropdownSelectBox.find('input[type="hidden"]').val() || selected.id; const milestonePayload = {}; const abilityName = this.config.dataset.abilityName; @@ -237,17 +231,18 @@ milestonePayload[abilityName].milestone_id = selectedMilestone; const issueUpdateURL = this.config.dataset.issueUpdate; + // TODO: Use issuable pub/sub resource method to propagate changes this.renderLoadingState(); $.ajax({ type: 'PUT', url: issueUpdateURL, data: milestonePayload }) - .done(issuableData => { - this.handlePutSuccess(issuableData); - }); + .done(issuableData => this.handlePutSuccess(issuableData)); } - handlePutSuccess(issuableData) { + handlePutSuccess(data) { this.renderLoadedState(); + const issuableData = data; + issuableData.milestone = this.parsePutValue(issuableData); this.renderUpdatedState(issuableData); } @@ -263,17 +258,17 @@ return milestoneData; } - putIssueBoardPage(selected, $el, e) { + putIssueBoardPage(selected, e) { gl.issueBoards.BoardsStore.state.filters[this.config.dataset.fieldName] = selected.name; gl.issueBoards.BoardsStore.updateFiltersUrl(); e.preventDefault(); } - putIssueBoardSidebar(selected, $el, e) { + putIssueBoardSidebar(selected) { if (selected.id !== -1) { Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'milestone', new ListMilestone({ id: selected.id, - title: selected.name + title: selected.name, })); } else { Vue.delete(gl.issueBoards.BoardsStore.detail.issue, 'milestone'); @@ -282,17 +277,17 @@ this.renderLoadingState(); gl.issueBoards.BoardsStore.detail.issue.update(this.config.dataset.issueUpdate) - .then(() => this.renderLoadedState()); + .then(() => this.renderLoadedState()); } - putSubmittableIndex(selected, $el, e) { + putSubmittableIndex() { return Issuable.filterResults(this.$el.form); } - putSubmittableNonIndex(selected, $el, e) { + putSubmittableNonIndex() { return this.$el.form.submit(); } } - global.MilestoneSelect = MilestoneSelect; -})(window); + window.MilestoneSelect = MilestoneSelect; +})(); -- GitLab