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.
This commit is contained in:
Justin DiRose 2020-11-04 12:02:05 -06:00 committed by GitHub
parent aac4af9d96
commit dd55d165cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 350 additions and 354 deletions

View File

@ -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");
},
},
});

View File

@ -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;
},
},
});

View File

@ -1,3 +1,5 @@
export default function () {
this.route("knowledgeExplorer", { path: "/docs" });
this.route("knowledgeExplorer", { path: "/docs" }, function () {
this.route("index", { path: "/" });
});
}

View File

@ -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);
},
});

View File

@ -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({});

View File

@ -0,0 +1,86 @@
{{#conditional-loading-spinner condition=isLoading}}
{{#if emptyTopics}}
<span class="no-topics-found">{{i18n 'knowledge_explorer.no_topics'}}</span>
{{else}}
<div class="knowledge-explorer-browse">
{{#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}}
<div class="knowledge-explorer-filters">
{{#if expandedFilters}}
{{#if canFilterSolved}}
<div class="knowledge-explorer-items knowledge-explorer-solved">
<label class="checkbox-label knowledge-explorer-item">
{{input
type="checkbox"
checked=(readonly filterSolved)
change=(action "onChangeFilterSolved" value="target.checked")
}}
{{i18n "knowledge_explorer.filter_solved"}}
</label>
</div>
{{/if}}
{{#if categories}}
<div class="knowledge-explorer-items knowledge-explorer-categories">
<h3>{{i18n 'knowledge_explorer.categories'}}</h3>
{{#each categories as |category|}}
{{knowledge-explorer-category
category=category
selectCategory=(action "updateSelectedCategories"
tagName="")
}}
{{/each}}
</div>
{{/if}}
{{#if tags}}
<div class="knowledge-explorer-items knowledge-explorer-tags">
<h3>{{i18n 'knowledge_explorer.tags'}}</h3>
{{#each tags as |tag|}}
{{knowledge-explorer-tag
tag=tag
selectTag=(action "updateSelectedTags")
}}
{{/each}}
</div>
{{/if}}
{{/if}}
</div>
{{#if selectedTopic}}
{{#conditional-loading-spinner condition=isTopicLoading}}
{{knowledge-explorer-topic topic=topic return=(action "returnToList")}}
{{/conditional-loading-spinner}}
{{else}}
<div class="knowledge-explorer-results">
{{#if isSearchingOrFiltered}}
{{#if emptyResults}}
<div class="result-count no-result">
{{i18n 'search.no_results'}}
</div>
{{plugin-outlet name="after-knowledge-explorer-empty-results"}}
{{else}}
<div class="result-count">
{{i18n 'knowledge_explorer.search.results'
count=searchCount
}}
</div>
{{/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}}
</div>
{{/if}}
</div>
{{/if}}
{{/conditional-loading-spinner}}

View File

@ -1,93 +1,9 @@
<div class="knowledge-explorer">
{{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}}
<span class="no-topics-found">{{i18n 'knowledge_explorer.no_topics'}}</span>
{{else}}
<div class="knowledge-explorer-browse">
{{#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}}
<div class="knowledge-explorer-filters">
{{#if expandedFilters}}
{{#if canFilterSolved}}
<div class="knowledge-explorer-items knowledge-explorer-solved">
<label class="checkbox-label knowledge-explorer-item">
{{input
type="checkbox"
checked=(readonly filterSolved)
change=(action "onChangeFilterSolved" value="target.checked")
}}
{{i18n "knowledge_explorer.filter_solved"}}
</label>
</div>
{{/if}}
{{#if categories}}
<div class="knowledge-explorer-items knowledge-explorer-categories">
<h3>{{i18n 'knowledge_explorer.categories'}}</h3>
{{#each categories as |category|}}
{{knowledge-explorer-category
category=category
selectCategory=(action "updateSelectedCategories"
tagName="")
}}
{{/each}}
</div>
{{/if}}
{{#if tags}}
<div class="knowledge-explorer-items knowledge-explorer-tags">
<h3>{{i18n 'knowledge_explorer.tags'}}</h3>
{{#each tags as |tag|}}
{{knowledge-explorer-tag
tag=tag
selectTag=(action "updateSelectedTags")
}}
{{/each}}
</div>
{{/if}}
{{/if}}
</div>
{{#if selectedTopic}}
{{#conditional-loading-spinner condition=isTopicLoading}}
{{knowledge-explorer-topic topic=topic return=(action "returnToList")}}
{{/conditional-loading-spinner}}
{{else}}
<div class="knowledge-explorer-results">
{{#if isSearchingOrFiltered}}
{{#if emptyResults}}
<div class="result-count no-result">
{{i18n 'search.no_results'}}
</div>
{{plugin-outlet name="after-knowledge-explorer-empty-results"}}
{{else}}
<div class="result-count">
{{i18n 'knowledge_explorer.search.results'
count=searchCount
}}
</div>
{{/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}}
</div>
{{/if}}
</div>
{{/if}}
{{/conditional-loading-spinner}}
{{outlet}}
</div>