-
Notifications
You must be signed in to change notification settings - Fork 1
Feature/search form #106
Feature/search form #106
Changes from 20 commits
3b8cdef
a5b7ec5
b9a4843
057aa9b
e1ffb47
54e9521
50087c8
8e84910
3ae623d
d198b6e
e329f1a
cec6722
605b734
0f286db
2c253eb
80ed90a
d873bbd
bc33f8c
ad3baa1
acc85b4
dabcf0d
28f4a3d
9d4f15d
9059917
04e5600
a654b60
1ea2c44
9e06c33
6219d8a
3abbd9f
774a40f
988c488
9625676
22902e6
85e8fc9
35de578
203b2e3
c3f090e
804adf4
a126e38
b799f8d
8060dcc
adfd226
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,28 @@ | ||
{ | ||
"name": "winthrop-django", | ||
"description": "Django web application for the Winthrop Family on the Page Project", | ||
"repository": "https://github.com/Princeton-CDH/winthrop-django.git", | ||
"author": "The Center for Digital Humanities at Princeton <cdhdevteam@princeton.edu>", | ||
"license": "Apache-2.0", | ||
"main": "sitemedia/js/index.js", | ||
"dependencies": { | ||
"autoprefixer": "^8.5.0", | ||
"babel-core": "^6.26.3", | ||
"babel-preset-es2015": "^6.24.1", | ||
"babelify": "^8.0.0", | ||
"browserify": "^16.2.2", | ||
"node-sass": "^4.9.0", | ||
"postcss-cli": "^5.0.0" | ||
} | ||
"name": "winthrop-django", | ||
"description": "Django web application for the Winthrop Family on the Page Project", | ||
"repository": "https://github.com/Princeton-CDH/winthrop-django.git", | ||
"author": "The Center for Digital Humanities at Princeton <cdhdevteam@princeton.edu>", | ||
"license": "Apache-2.0", | ||
"main": "sitemedia/js/index.js", | ||
"dependencies": { | ||
"babel-polyfill": "^6.26.0", | ||
"lodash": "^4.17.10", | ||
"rxjs": "^6.2.2", | ||
"rxjs-compat": "^6.2.2", | ||
"semantic-ui-vue": "^0.2.11", | ||
"vue-router": "^3.0.1", | ||
"vue2-filters": "^0.3.0", | ||
"vuex": "^3.0.1", | ||
"vuex-router-sync": "^5.0.0" | ||
}, | ||
"devDependencies": { | ||
"@babel/core": "^7.0.0-beta.54", | ||
"@babel/preset-env": "^7.0.0-beta.54", | ||
"autoprefixer": "^8.5.0", | ||
"babelify": "^9.0.0", | ||
"browserify": "^16.2.2", | ||
"node-sass": "^4.9.0", | ||
"postcss-cli": "^5.0.0" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
export default Vue.component ('FilterChoice', { | ||
template: ` | ||
<label :active="active" is="sui-button" role="checkbox" basic circular compact> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so the <label class="ui button basic circular compact"></label> this is coming from semantic-ui-vue. we can talk more about the library - it seems like it might not be well-maintained, but I'm not sure if there is an alternative to make the semantic UI behaviors play well with Vue. |
||
{{ label }} | ||
<input | ||
type="checkbox" | ||
:value="value" | ||
@input="$emit('input', $event.target.value)" | ||
:name="name" | ||
v-model="active" | ||
hidden | ||
> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I prefer closed tags. Not sure if it actually matters here (I know it does for django templates if we want to use the |
||
</label> | ||
`, | ||
props: { | ||
name: String, | ||
label: String, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess label and value are different to allow for displaying facet counts? Maybe warrants a comment? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "label" is the user-friendly display name; "value" is the |
||
value: String | ||
}, | ||
data() { | ||
return { | ||
active: false | ||
} | ||
} | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { mapActions } from 'vuex' | ||
|
||
export default Vue.component('RangeFacet', { | ||
template: ` | ||
<div class="range-facet"> | ||
<div class="inputs"> | ||
<sui-input :placeholder="minVal" /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The reason using semantic-ui vue is necessary at all is because the behaviors that semantic UI provides (like the js used for the interactivity on the site search bar and mobile menu) might not play well if used inside a Vue component. |
||
<label>to</label> | ||
<sui-input :placeholder="maxVal" /> | ||
</div> | ||
<div class="histogram"> | ||
</div> | ||
</div> | ||
`, | ||
props: { | ||
label: String, | ||
name: String, | ||
width: Number, | ||
choices: Array | ||
}, | ||
computed: { | ||
minVal() { | ||
return this.choices | ||
.map(choice => parseInt(choice.value)) | ||
.reduce((acc, cur) => cur < acc ? cur : acc, Infinity) | ||
}, | ||
maxVal() { | ||
return this.choices | ||
.map(choice => parseInt(choice.value)) | ||
.reduce((acc, cur) => cur > acc ? cur : acc, -Infinity) | ||
} | ||
}, | ||
methods: { | ||
...mapActions([ | ||
'setRangeFacet', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am probably missing something here, but I see getters for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, these are placeholders - I started out using a similar approach to the normal facets (one |
||
]), | ||
}, | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import { mapActions } from 'vuex' | ||
import RangeFacet from './RangeFacet' | ||
|
||
export default Vue.component('SearchFacet', { | ||
template: ` | ||
<sui-grid-column :width="width" class="field"> | ||
<label :for="label">{{ label }}</label> | ||
<template v-if="type === 'text'"> | ||
<sui-input v-if="search" iconPosition="left" icon="search" placeholder="Search or select" v-model="filter" /> | ||
<div v-if="search" class="rolodex"> | ||
<button | ||
v-for="letter in alphabet" | ||
:key="letter" | ||
class="letter" | ||
:class="{active: alphaFilter == letter}" | ||
@click.prevent="alphaFilter = alphaFilter == letter ? '' : letter" | ||
> | ||
{{ letter }} | ||
</button> | ||
</div> | ||
<div class="facets"> | ||
<label v-if="availableChoices.length == 0">No results</label> | ||
<div | ||
class="ui checkbox" | ||
v-for="choice of choices" | ||
v-show="availableChoices.includes(choice.value)" | ||
:key="choice.value" | ||
> | ||
<input | ||
type="checkbox" | ||
@input="toggleFacetChoice(choice)" | ||
:name="choice.facet" | ||
:value="choice.value" | ||
:checked="choice.active" | ||
> | ||
<label>{{ choice.value }} <span class="count">{{ choice.count }}</span></label> | ||
</div> | ||
</div> | ||
</template> | ||
<range-facet v-if="type === 'range'" :label="label" :name="name" :width="width" :choices="choices"> | ||
</range-facet> | ||
</sui-grid-column> | ||
`, | ||
components: { | ||
RangeFacet | ||
}, | ||
data() { | ||
return { | ||
filter: '', | ||
alphaFilter: '', | ||
alphabet: String.fromCharCode(...Array(91).keys()).slice(65) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you need a bit more here to determine which letters are actually present in the data so you can disable the letter choices that aren't. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe this would be good to extrapolate away as a Vuex getter that takes an alphabetical facet's results and returns an alphabet with letters not present marked (or vice-versa)? |
||
} | ||
}, | ||
computed: { | ||
availableChoices() { | ||
return this.choices | ||
.map(choice => choice.value) | ||
.filter(value => this.normalize(value).startsWith(this.alphaFilter)) | ||
.filter(value => this.match(value, this.filter)) | ||
} | ||
}, | ||
props: { | ||
type: String, | ||
label: String, | ||
name: String, | ||
search: { | ||
type: Boolean, | ||
default: false | ||
}, | ||
width: Number, | ||
choices: Array | ||
}, | ||
methods: { | ||
...mapActions([ | ||
'toggleFacetChoice', | ||
]), | ||
/** | ||
* Utility that normalizes strings for simple comparison. | ||
* Removes special characters, trims whitespace, and uppercases. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm curious why you're removing whitespace. This is probably reasonable enough for a simple facet filter for now. |
||
* | ||
* @param {String} str | ||
* @returns {String} | ||
*/ | ||
normalize(str) { | ||
return str.replace(/[^\w\s]/gi, '').trim().toUpperCase() | ||
}, | ||
/** | ||
* Compares two strings using normalize(). | ||
* Return true if str2 matches str1. | ||
* | ||
* @param {String} str1 | ||
* @param {String} str2 | ||
* @returns {Boolean} | ||
*/ | ||
match(str1, str2) { | ||
return this.normalize(str1).includes(this.normalize(str2)) | ||
}, | ||
} | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import FilterChoice from './FilterChoice' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comments or docstrings would be really helpful to help understand the difference between |
||
|
||
export default Vue.component('SearchFilter', { | ||
template: ` | ||
<sui-segment> | ||
<label :for="label">{{ label }}</label> | ||
<filter-choice | ||
v-for="choice in choices" | ||
@input="$emit('input')" | ||
:key="choice" | ||
:name="label" | ||
:label="fieldLabels[choice]" | ||
:value="choice" | ||
/> | ||
<label>books</label> | ||
</sui-segment> | ||
`, | ||
components: { | ||
FilterChoice | ||
}, | ||
props: { | ||
label: String, | ||
fieldLabels: Object, | ||
choices: Array | ||
}, | ||
data() { | ||
return { | ||
activeFilters: [] | ||
} | ||
} | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { mapState } from 'vuex' | ||
|
||
export default Vue.component('SearchResults', { | ||
template: `<div class="search-results" v-html="results" />`, | ||
computed: { | ||
...mapState([ | ||
'results', | ||
'totalResults', | ||
]), | ||
} | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { mapState, mapActions } from 'vuex' | ||
|
||
export default Vue.component('SearchSort', { | ||
template: ` | ||
<div class="search-sort"> | ||
<label>Sort By</label> | ||
<sui-dropdown | ||
:value="activeSort" | ||
:options="options" | ||
@input="changeSort($event)" | ||
selection | ||
/> | ||
</div> | ||
`, | ||
computed: { | ||
...mapState([ | ||
'activeSort', | ||
'query' | ||
]), | ||
options() { | ||
return [ | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will this be pulled from / generated via the django form in future so we don't have to duplicate options? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, that's my hope. Should be able to just pass it into the component using html. |
||
text: 'Author A-Z', | ||
value: 'author_asc' | ||
}, | ||
{ | ||
text: 'Author Z-A', | ||
value: 'author_desc' | ||
}, | ||
{ | ||
text: 'Year Oldest-Newest', | ||
value: 'pub_year_asc' | ||
}, | ||
{ | ||
text: 'Year Newest-Oldest', | ||
value: 'pub_year_desc' | ||
}, | ||
{ | ||
text: 'Relevance', | ||
value: 'relevance', | ||
disabled: this.query ? false : true | ||
} | ||
] | ||
}, | ||
}, | ||
methods: { | ||
...mapActions([ | ||
'changeSort' | ||
]), | ||
}, | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should components have a doc string or comment? It seems like it would be helpful to orient in terms of context and use case or goals for a component.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this idea; makes sense to document them at that level. I'll add a docstring-like comment at the top of the files.