From dd55d165cd08ad2622c48d254ad1be1458023bf4 Mon Sep 17 00:00:00 2001 From: Justin DiRose Date: Wed, 4 Nov 2020 12:02:05 -0600 Subject: [PATCH] FIX: Filters did not reset when visiting route (#14) When using a single Ember route, the query param filters did not properly reset when, for example, clicking the Knowledge Explorer link in the hamburger menu. This commit introduces a subroute where most of the logic takes place. We needed a subroute so that refresh doesn't take out the entire UI part. This allows us to use the `refreshModel` option for query params on the route and remove the refresh controller action. Doing so also retains state of the search field instead of fully reloading the parent route. --- .../knowledge-explorer-index.js.es6 | 212 +++++++++++++++ .../controllers/knowledge-explorer.js.es6 | 243 +----------------- .../knowledge-explorer-route-map.js.es6 | 4 +- .../routes/knowledge-explorer-index.js.es6 | 41 +++ .../routes/knowledge-explorer.js.es6 | 30 +-- .../templates/knowledge-explorer-index.hbs | 86 +++++++ .../templates/knowledge-explorer.hbs | 88 +------ 7 files changed, 350 insertions(+), 354 deletions(-) create mode 100644 assets/javascripts/discourse/controllers/knowledge-explorer-index.js.es6 create mode 100644 assets/javascripts/discourse/routes/knowledge-explorer-index.js.es6 create mode 100644 assets/javascripts/discourse/templates/knowledge-explorer-index.hbs diff --git a/assets/javascripts/discourse/controllers/knowledge-explorer-index.js.es6 b/assets/javascripts/discourse/controllers/knowledge-explorer-index.js.es6 new file mode 100644 index 0000000..bb7ce4b --- /dev/null +++ b/assets/javascripts/discourse/controllers/knowledge-explorer-index.js.es6 @@ -0,0 +1,212 @@ +import Controller from "@ember/controller"; +import discourseComputed from "discourse-common/utils/decorators"; +import Category from "discourse/models/category"; +import { on } from "discourse-common/utils/decorators"; +import KnowledgeExplorer from "discourse/plugins/discourse-knowledge-explorer/discourse/models/knowledge-explorer"; +import { getOwner } from "@ember/application"; + +function mergeCategories(results) { + const categories = Category.list(); + const topics = results.topics.topic_list.topics.map((t) => { + t.category = categories.findBy("id", t.category_id); + return t; + }); + + results.topics.topic_list.topics = topics; + + return results; +} + +export default Controller.extend({ + queryParams: { + ascending: "ascending", + filterCategories: "category", + filterTags: "tags", + filterSolved: "solved", + orderColumn: "order", + searchTerm: "search", + selectedTopic: "topic", + }, + application: Ember.inject.controller(), + isLoading: false, + isLoadingMore: false, + loadMoreUrl: Ember.computed.alias("model.topics.load_more_url"), + isTopicLoading: false, + categories: Ember.computed.readOnly("model.categories"), + topics: Ember.computed.alias("model.topics.topic_list.topics"), + tags: Ember.computed.readOnly("model.tags"), + filterTags: null, + filterCategories: null, + filterSolved: false, + searchTerm: null, + selectedTopic: null, + topic: null, + expandedFilters: false, + ascending: null, + orderColumn: null, + + @on("init") + _setupFilters() { + if (!this.site.mobileView) { + this.set("expandedFilters", true); + } + }, + + @discourseComputed("topics", "isSearching", "filterSolved") + emptyTopics(topics, isSearching, filterSolved) { + const filtered = isSearching || filterSolved; + return topics.length === 0 && !filtered; + }, + + @discourseComputed("loadMoreUrl") + canLoadMore(loadMoreUrl) { + return loadMoreUrl === null ? false : true; + }, + + @discourseComputed("searchTerm") + isSearching(searchTerm) { + return !!searchTerm; + }, + + @discourseComputed("isSearching", "filterSolved") + isSearchingOrFiltered(isSearching, filterSolved) { + return isSearching || filterSolved; + }, + + @discourseComputed("isSearching", "model") + searchCount(isSearching, model) { + if (isSearching) { + return model.search_count; + } + }, + + emptySearchResults: Ember.computed.equal("searchCount", 0), + + @discourseComputed("topics") + emptyFilteredResults(topics) { + return topics.length === 0; + }, + + @discourseComputed("emptySearchResults", "emptyFilteredResults") + emptyResults(emptySearch, emptyFiltered) { + return emptySearch || emptyFiltered; + }, + + @discourseComputed + canFilterSolved() { + return ( + this.siteSettings.solved_enabled && + this.siteSettings.knowledge_explorer_add_solved_filter + ); + }, + + @discourseComputed("filterTags") + filtered(filterTags) { + return !!filterTags; + }, + + actions: { + setSelectedTopic(topicId) { + this.set("selectedTopic", topicId); + + window.scrollTo(0, 0); + }, + + onChangeFilterSolved(solvedFilter) { + this.set("filterSolved", solvedFilter); + }, + + updateSelectedTags(tag) { + let filter = this.filterTags; + if (filter && filter.includes(tag.id)) { + filter = filter.replace(tag.id, "").replace(/^\|+|\|+$/g, ""); + } else if (filter) { + filter = `${filter}|${tag.id}`; + } else { + filter = tag.id; + } + + this.setProperties({ + filterTags: filter, + selectedTopic: null, + }); + }, + + updateSelectedCategories(category) { + let filter = this.filterCategories; + if (filter && filter.includes(category.id)) { + filter = filter.replace(category.id, "").replace(/^\|+|\|+$/g, ""); + } else if (filter) { + filter = `${filter}|${category.id}`; + } else { + filter = category.id; + } + + this.setProperties({ + filterCategories: filter, + selectedTopic: null, + }); + }, + performSearch(term) { + if (term === "") { + this.set("searchTerm", null); + return false; + } + + if (term.length < this.siteSettings.min_search_term_length) { + return false; + } + + this.setProperties({ + searchTerm: term, + selectedTopic: null, + }); + }, + + sortBy(column) { + const order = this.orderColumn; + const ascending = this.ascending; + if (column === "title") { + this.set("orderColumn", "title"); + } else if (column === "activity") { + this.set("orderColumn", "activity"); + } + + if (!ascending && order) { + this.set("ascending", true); + } else { + this.set("ascending", ""); + } + }, + + loadMore() { + if (this.canLoadMore && !this.isLoadingMore) { + this.set("isLoadingMore", true); + + KnowledgeExplorer.loadMore(this.loadMoreUrl).then((result) => { + result = mergeCategories(result); + const topics = this.topics.concat(result.topics.topic_list.topics); + + this.setProperties({ + topics, + loadMoreUrl: result.topics.load_more_url || null, + isLoadingMore: false, + }); + }); + } + }, + + toggleFilters() { + if (!this.expandedFilters) { + this.set("expandedFilters", true); + } else { + this.set("expandedFilters", false); + } + }, + + returnToList() { + this.set("selectedTopic", null); + getOwner(this).lookup("router:main").transitionTo("knowledgeExplorer"); + }, + }, +}); diff --git a/assets/javascripts/discourse/controllers/knowledge-explorer.js.es6 b/assets/javascripts/discourse/controllers/knowledge-explorer.js.es6 index 943e593..88f59fd 100644 --- a/assets/javascripts/discourse/controllers/knowledge-explorer.js.es6 +++ b/assets/javascripts/discourse/controllers/knowledge-explorer.js.es6 @@ -1,248 +1,15 @@ import Controller from "@ember/controller"; -import discourseComputed from "discourse-common/utils/decorators"; -import Category from "discourse/models/category"; -import { on } from "discourse-common/utils/decorators"; -import KnowledgeExplorer from "discourse/plugins/discourse-knowledge-explorer/discourse/models/knowledge-explorer"; -import { getOwner } from "@ember/application"; - -function mergeCategories(results) { - const categories = Category.list(); - const topics = results.topics.topic_list.topics.map((t) => { - t.category = categories.findBy("id", t.category_id); - return t; - }); - - results.topics.topic_list.topics = topics; - - return results; -} export default Controller.extend({ - application: Ember.inject.controller(), - queryParams: { - ascending: "ascending", - filterCategories: "category", - filterTags: "tags", - filterSolved: "solved", - orderColumn: "order", - searchTerm: "search", - selectedTopic: "topic", - }, - isLoading: false, - isLoadingMore: false, - loadMoreUrl: Ember.computed.alias("model.topics.load_more_url"), - isTopicLoading: false, - categories: Ember.computed.readOnly("model.categories"), - topics: Ember.computed.alias("model.topics.topic_list.topics"), - tags: Ember.computed.readOnly("model.tags"), - filterTags: null, - filterCategories: null, - filterSolved: false, - searchTerm: null, - selectedTopic: null, - topic: null, - expandedFilters: false, - ascending: null, - orderColumn: null, - - @on("init") - _setupFilters() { - if (!this.site.mobileView) { - this.set("expandedFilters", true); - } - }, - - @discourseComputed("topics", "isSearching", "filterSolved") - emptyTopics(topics, isSearching, filterSolved) { - const filtered = isSearching || filterSolved; - return topics.length === 0 && !filtered; - }, - - @discourseComputed("loadMoreUrl") - canLoadMore(loadMoreUrl) { - return loadMoreUrl === null ? false : true; - }, - - @discourseComputed("searchTerm") - isSearching(searchTerm) { - return !!searchTerm; - }, - - @discourseComputed("isSearching", "filterSolved") - isSearchingOrFiltered(isSearching, filterSolved) { - return isSearching || filterSolved; - }, - - @discourseComputed("isSearching", "model") - searchCount(isSearching, model) { - if (isSearching) { - return model.search_count; - } - }, - - emptySearchResults: Ember.computed.equal("searchCount", 0), - - @discourseComputed("topics") - emptyFilteredResults(topics) { - return topics.length === 0; - }, - - @discourseComputed("emptySearchResults", "emptyFilteredResults") - emptyResults(emptySearch, emptyFiltered) { - return emptySearch || emptyFiltered; - }, - - @discourseComputed - canFilterSolved() { - return ( - this.siteSettings.solved_enabled && - this.siteSettings.knowledge_explorer_add_solved_filter - ); - }, - - @discourseComputed("filterTags") - filtered(filterTags) { - return !!filterTags; - }, - + indexController: Ember.inject.controller("knowledgeExplorer.index"), actions: { - setSelectedTopic(topicId) { - this.set("selectedTopic", topicId); - - window.scrollTo(0, 0); - - this.send("refreshModel"); - }, - - onChangeFilterSolved(solvedFilter) { - this.set("filterSolved", solvedFilter); - this.send("refreshModel"); - }, - - updateSelectedTags(tag) { - let filter = this.filterTags; - if (filter && filter.includes(tag.id)) { - filter = filter.replace(tag.id, "").replace(/^\|+|\|+$/g, ""); - } else if (filter) { - filter = `${filter}|${tag.id}`; - } else { - filter = tag.id; - } - - this.setProperties({ - filterTags: filter, - selectedTopic: null, - }); - - this.send("refreshModel"); - }, updateSelectedCategories(category) { - let filter = this.filterCategories; - if (filter && filter.includes(category.id)) { - filter = filter.replace(category.id, "").replace(/^\|+|\|+$/g, ""); - } else if (filter) { - filter = `${filter}|${category.id}`; - } else { - filter = category.id; - } - - this.setProperties({ - filterCategories: filter, - selectedTopic: null, - }); - - this.send("refreshModel"); + this.indexController.send("updateSelectedCategories", category); + return false; }, - performSearch(term) { - if (term === "") { - this.set("searchTerm", null); - this.send("refreshModel"); - return false; - } - - if (term.length < this.siteSettings.min_search_term_length) { - return false; - } - - this.setProperties({ - searchTerm: term, - selectedTopic: null, - }); - - this.send("refreshModel"); - }, - - sortBy(column) { - const order = this.orderColumn; - const ascending = this.ascending; - if (column === "title") { - this.set("orderColumn", "title"); - } else if (column === "activity") { - this.set("orderColumn", "activity"); - } - - if (!ascending && order) { - this.set("ascending", true); - } else { - this.set("ascending", ""); - } - - this.send("refreshModel"); - }, - - loadMore() { - if (this.canLoadMore && !this.isLoadingMore) { - this.set("isLoadingMore", true); - - KnowledgeExplorer.loadMore(this.loadMoreUrl).then((result) => { - result = mergeCategories(result); - const topics = this.topics.concat(result.topics.topic_list.topics); - - this.setProperties({ - topics, - loadMoreUrl: result.topics.load_more_url || null, - isLoadingMore: false, - }); - }); - } - }, - - refreshModel() { - this.set("isLoading", true); - - const params = this.getProperties( - "filterCategories", - "filterTags", - "filterSolved", - "searchTerm", - "ascending", - "orderColumn", - "selectedTopic" - ); - - KnowledgeExplorer.list(params).then((result) => { - result = mergeCategories(result); - this.setProperties({ - model: result, - topic: result.topic, - isLoading: false, - }); - }); - }, - - toggleFilters() { - if (!this.expandedFilters) { - this.set("expandedFilters", true); - } else { - this.set("expandedFilters", false); - } - }, - - returnToList() { - this.set("selectedTopic", null); - getOwner(this).lookup("router:main").transitionTo("knowledgeExplorer"); - this.send("refreshModel"); + this.indexController.send("performSearch", term); + return false; }, }, }); diff --git a/assets/javascripts/discourse/knowledge-explorer-route-map.js.es6 b/assets/javascripts/discourse/knowledge-explorer-route-map.js.es6 index 711f078..ebf25e7 100644 --- a/assets/javascripts/discourse/knowledge-explorer-route-map.js.es6 +++ b/assets/javascripts/discourse/knowledge-explorer-route-map.js.es6 @@ -1,3 +1,5 @@ export default function () { - this.route("knowledgeExplorer", { path: "/docs" }); + this.route("knowledgeExplorer", { path: "/docs" }, function () { + this.route("index", { path: "/" }); + }); } diff --git a/assets/javascripts/discourse/routes/knowledge-explorer-index.js.es6 b/assets/javascripts/discourse/routes/knowledge-explorer-index.js.es6 new file mode 100644 index 0000000..c270dae --- /dev/null +++ b/assets/javascripts/discourse/routes/knowledge-explorer-index.js.es6 @@ -0,0 +1,41 @@ +import Route from "@ember/routing/route"; +import Category from "discourse/models/category"; +import KnowledgeExplorer from "discourse/plugins/discourse-knowledge-explorer/discourse/models/knowledge-explorer"; + +export default Route.extend({ + queryParams: { + ascending: { refreshModel: true }, + filterCategories: { refreshModel: true }, + filterTags: { refreshModel: true }, + filterSolved: { refreshModel: true }, + orderColumn: { refreshModel: true }, + selectedTopic: { refreshModel: true }, + searchTerm: { + replace: true, + refreshModel: true, + }, + }, + model(params) { + this.controllerFor("knowledgeExplorer.index").set("isLoading", true); + return KnowledgeExplorer.list(params).then((result) => { + this.controllerFor("knowledgeExplorer.index").set("isLoading", false); + return result; + }); + }, + + setupController(controller, model) { + const categories = Category.list(); + + let topics = model.topics.topic_list.topics; + + topics = topics.map((t) => { + t.category = categories.findBy("id", t.category_id); + return t; + }); + + model.topics.topic_list.topics = topics; + + controller.set("topic", model.topic); + controller.set("model", model); + }, +}); diff --git a/assets/javascripts/discourse/routes/knowledge-explorer.js.es6 b/assets/javascripts/discourse/routes/knowledge-explorer.js.es6 index 6155332..f7f5da2 100644 --- a/assets/javascripts/discourse/routes/knowledge-explorer.js.es6 +++ b/assets/javascripts/discourse/routes/knowledge-explorer.js.es6 @@ -1,31 +1,3 @@ import Route from "@ember/routing/route"; -import Category from "discourse/models/category"; -import KnowledgeExplorer from "discourse/plugins/discourse-knowledge-explorer/discourse/models/knowledge-explorer"; -export default Route.extend({ - queryParams: { - searchTerm: { - replace: true, - }, - }, - - model(params) { - return KnowledgeExplorer.list(params); - }, - - setupController(controller, model) { - const categories = Category.list(); - - let topics = model.topics.topic_list.topics; - - topics = topics.map((t) => { - t.category = categories.findBy("id", t.category_id); - return t; - }); - - model.topics.topic_list.topics = topics; - - controller.set("topic", model.topic); - controller.set("model", model); - }, -}); +export default Route.extend({}); diff --git a/assets/javascripts/discourse/templates/knowledge-explorer-index.hbs b/assets/javascripts/discourse/templates/knowledge-explorer-index.hbs new file mode 100644 index 0000000..54dbc57 --- /dev/null +++ b/assets/javascripts/discourse/templates/knowledge-explorer-index.hbs @@ -0,0 +1,86 @@ +{{#conditional-loading-spinner condition=isLoading}} + {{#if emptyTopics}} + {{i18n 'knowledge_explorer.no_topics'}} + {{else}} +
+ {{#if site.mobileView}} + {{#unless selectedTopic}} + {{d-button class="knowledge-explorer-expander" icon=(if expandedFilters "angle-up" "angle-down") action=(action "toggleFilters") label="knowledge_explorer.filter_button"}} + {{/unless}} + {{/if}} +
+ {{#if expandedFilters}} + {{#if canFilterSolved}} +
+ +
+ + {{/if}} + {{#if categories}} +
+

{{i18n 'knowledge_explorer.categories'}}

+ {{#each categories as |category|}} + {{knowledge-explorer-category + category=category + selectCategory=(action "updateSelectedCategories" + tagName="") + }} + {{/each}} +
+ {{/if}} + {{#if tags}} +
+

{{i18n 'knowledge_explorer.tags'}}

+ {{#each tags as |tag|}} + {{knowledge-explorer-tag + tag=tag + selectTag=(action "updateSelectedTags") + }} + {{/each}} +
+ {{/if}} + {{/if}} +
+ {{#if selectedTopic}} + {{#conditional-loading-spinner condition=isTopicLoading}} + {{knowledge-explorer-topic topic=topic return=(action "returnToList")}} + {{/conditional-loading-spinner}} + {{else}} +
+ {{#if isSearchingOrFiltered}} + {{#if emptyResults}} +
+ {{i18n 'search.no_results'}} +
+ {{plugin-outlet name="after-knowledge-explorer-empty-results"}} + {{else}} +
+ {{i18n 'knowledge_explorer.search.results' + count=searchCount + }} +
+ {{/if}} + {{/if}} + {{#unless emptyResults}} + {{knowledge-explorer-topic-list + topics=topics + ascending=ascending + order=orderColumn + sortBy=(action "sortBy") + selectTopic=(action "setSelectedTopic") + loadMore=(action "loadMore") + loading=isLoadingMore + }} + {{/unless}} +
+ {{/if}} +
+ {{/if}} +{{/conditional-loading-spinner}} diff --git a/assets/javascripts/discourse/templates/knowledge-explorer.hbs b/assets/javascripts/discourse/templates/knowledge-explorer.hbs index 7ea01d7..9755e11 100644 --- a/assets/javascripts/discourse/templates/knowledge-explorer.hbs +++ b/assets/javascripts/discourse/templates/knowledge-explorer.hbs @@ -1,93 +1,9 @@
{{plugin-outlet name="before-knowledge-explorer-search" args=(hash selectCategory=(action "updateSelectedCategories"))}} {{knowledge-explorer-search - searchTerm=(readonly searchTerm) + searchTerm=(readonly indexController.searchTerm) onSearch=(action "performSearch") }} - {{#conditional-loading-spinner condition=isLoading}} - {{#if emptyTopics}} - {{i18n 'knowledge_explorer.no_topics'}} - {{else}} -
- {{#if site.mobileView}} - {{#unless selectedTopic}} - {{d-button class="knowledge-explorer-expander" icon=(if expandedFilters "angle-up" "angle-down") action=(action "toggleFilters") label="knowledge_explorer.filter_button"}} - {{/unless}} - {{/if}} -
- {{#if expandedFilters}} - {{#if canFilterSolved}} -
- -
- {{/if}} - {{#if categories}} -
-

{{i18n 'knowledge_explorer.categories'}}

- {{#each categories as |category|}} - {{knowledge-explorer-category - category=category - selectCategory=(action "updateSelectedCategories" - tagName="") - }} - {{/each}} -
- {{/if}} - {{#if tags}} -
-

{{i18n 'knowledge_explorer.tags'}}

- {{#each tags as |tag|}} - {{knowledge-explorer-tag - tag=tag - selectTag=(action "updateSelectedTags") - }} - {{/each}} -
- {{/if}} - {{/if}} -
- {{#if selectedTopic}} - {{#conditional-loading-spinner condition=isTopicLoading}} - {{knowledge-explorer-topic topic=topic return=(action "returnToList")}} - {{/conditional-loading-spinner}} - {{else}} -
- {{#if isSearchingOrFiltered}} - {{#if emptyResults}} -
- {{i18n 'search.no_results'}} -
- {{plugin-outlet name="after-knowledge-explorer-empty-results"}} - {{else}} -
- {{i18n 'knowledge_explorer.search.results' - count=searchCount - }} -
- {{/if}} - {{/if}} - {{#unless emptyResults}} - {{knowledge-explorer-topic-list - topics=topics - ascending=ascending - order=orderColumn - sortBy=(action "sortBy") - selectTopic=(action "setSelectedTopic") - loadMore=(action "loadMore") - loading=isLoadingMore - }} - {{/unless}} -
- {{/if}} -
- {{/if}} -{{/conditional-loading-spinner}} + {{outlet}}