Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Search: add component #424

Merged
merged 1 commit into from
Sep 19, 2016
Merged

Conversation

jeffcarbs
Copy link
Member

@jeffcarbs jeffcarbs commented Aug 23, 2016

I modeled this off of the Dropdown component since there were a lot of similarities. There are some open questions and things left to do:

Questions:

  • What's the selection class used for in Search? It seems to just makes the border less round. I've left it in but didn't know what to put for the description.
  • There's officially no "scrolling" class like there is for dropdown menus, but I've left it in because I could imagine wanting to use it for styling. This doesn't seem like a satisfactory answer to me: Search results scrollable Semantic-UI#3389
  • Keyboard functionality (up, down, enter) requires options in-memory. Does it make sense to render children at all? It does for dropdown since it may not be searchable, but this always is searchable so maybe we just require either source or results?

Remaining work

There are some more options and functionality that the SUI search component provides that I haven't done yet. I'm not sure what the stance is on ensuring 1-1 feature functionality with the jquery version. Here are the settings from the SUI search docs

Done or makes sense to do IMHO:

  • minCharacters - Minimum characters to query for results
  • maxResults - Maximum results to display when using local and simple search, maximum category count for category search
  • source - Specify a Javascript object which will be searched locally
  • selectFirstResult - Whether the search should automatically select the first search result after searching

Not sure:

  • searchFields - Specify object properties inside local source object which will be searched
  • showNoResults - Whether a "no results" message should be shown if no results are found. (These messages can be modified using the templateobject specified below)

Don't think it makes sense to do:

  • apiSettings - Settings for API call.
  • transition - Named transition to use when animating menu in and out. Fade and slide down are available without including ui transitions
  • duration - Duration of animation events
  • cache - Caches results locally to avoid requerying server
  • fields - List mapping display content to JSON property, either with API or source.
  • easing - Easing equation when using fallback Javascript animation
  • hideDelay - Delay before hiding results after search blur
  • searchDelay - Delay before querying results on inputchange
  • For API-related things, I kinda think we just assume that you'd wrap this component and provide your own results. Would just add a lot of unnecessary complexity.
  • For "delay" related things, it's difficult to do in the React world since everything is declarative. If you want to debounce you can do so yourself via onSearchChange.

Other TODOs

  • Category search
  • Specs

cc #195

@levithomason
Copy link
Member

What's the selection class used for in Search? It seems to just makes the border less round. I've left it in but didn't know what to put for the description.

I don't see a selection class for the Search in the component definition. Let me know if I've overlooked it.

Regarding descriptions in general, we use the same descriptions as found in the SUI docs. If a prop does not exist in the SUI docs, we try to maintain the same style when describing it.

There's officially no "scrolling" class like there is for dropdown menus, but I've left it in because I could imagine wanting to use it for styling. This doesn't seem like a satisfactory answer to me: Semantic-Org/Semantic-UI#3389

Stardust (Semantic-UI-React) excludes all styling. We create React components that render valid SUI markup. This way, SUI CSS will always work, including any user's customized themes. If we'd like to change the CSS, we'll need to do this on SUI core.

Keyboard functionality (up, down, enter) requires options in-memory. Does it make sense to render children at all? It does for dropdown since it may not be searchable, but this always is searchable so maybe we just require either source or results?

Sorry, I don't quite follow here. Here's my intuition on this component. Let me know if we're on the page.

Intuitively, I'd expect a component like to provide a callback for search query changes. I'd take that query and use my own method of searching (whether API or some local search). I'd pass the results as a prop back to the search. The search then would render the results in the results list. The user would then have keyboard navigation (up, down, enter) to choose a result. They could also simply click an item. There would be a callback fired on when the user chose an item (enter or click). That action would likely close the menu. I'd then take the user's selection and do what I may.

Does this make sense?

Done or makes sense to do IMHO:

Agreed.

Not sure:

In the React paradigm, I think most of these are best left to the developer choice of implementation. I think the showNoResults prop and the ability to customize it makes sense. Let' see how the component plays out and decide.

Don't think it makes sense to do:

Agreed again.

API Spec

Per our contributing guidelines, here's a start on the API spec. I think it really helps to hash through the code for these things.

Standard

I propose we always use an Input component over a HTML input. This way, any Input props are valid here as well.

<Search />
<div class="ui search">
  <div class="ui icon input">
    <input class="prompt" type="text">
    <i class="search icon"></i>
  </div>
  <div class="results"></div>
</div>

Not supported:

<div class="ui search">
  <input class="prompt" type="text">
  <div class="results"></div>
</div>

Category

This would be used to choose the correct result renderer callback. Also, it would influence the filter function for local searches. See below for more details on callbacks and local searches.

const source = {
  category1: {
    name: 'Category 1',
    results: [{}, ...{}],
  },
  category2: {
    name: 'Category 2',
    results: [{}, ...{}],
  }
}

<Search category source={source} />
<div class="ui category search">
  <div class="ui icon input">
    <input class="prompt" type="text">
    <i class="search icon"></i>
  </div>
  <div class="results"></div>
</div>

Loading

<Search loading />
<div class="ui loading search">
  <div class="ui icon input">
    <input class="prompt" type="text">
    <i class="search icon"></i>
  </div>
  <div class="results"></div>
</div>

Fluid

<Search fluid />
<div class="ui fluid category search">
  <div class="ui icon input">
    <input class="prompt" type="text">
    <i class="search icon"></i>
  </div>
  <div class="results"></div>
</div>

Aligned

<Search aligned='left|right' />
<div class="ui <left|right> aligned category search">
  <div class="ui icon input">
    <input class="prompt" type="text">
    <i class="search icon"></i>
  </div>
  <div class="results"></div>
</div>

Callbacks

Loosely based on our Dropdown callbacks and the SUI Search callbacks.

  • onSelect(e, result, source) - Called on enter or result element click.
  • onSearchChange(e, query) - Called onChange of the search input.

Formatting Results

Our Table accepts an array of objects (table row data). We provided cellRenderer(rowData) and headerRenderer(rowData) callbacks to allow the user to return any JSX for the headers and cells. We could do something similar here, also loosely base on the SUI Search Templates.

Renderer callbacks would be called with each result/category and return JSX for each.

const renderCategory = (category) => (/* ... */)
const renderResult = (result) => (/* ... */)

<Search
  categoryRenderer={this.renderCategory}  // called if category={true}
  standardRenderer={this.renderResult}    // called otherwise
/>

Internal vs External Searching?

This is an unresolved area for me. Should we/how would we handle searching within the component vs outside of the component? If you're using the search change event to retrieve new results and passing those in via props, then there is no way/reason for the search to filter the items in the list internally. Search changes would trigger new external searches and new results for the list.

So, there are two mutually exclusive use cases for this component.

Local search

Except for source, these props only make sense when searching is handled inside the component. I can see this being useful for some use cases where you want to simply filter an object/array without writing your own filter and wiring change handlers to data props. You could just pass some data and get sane search filters out of the box.

I think this is also where we could address some of the search shortcomings raised for the Dropdown search. This is where we could provide an optional searchFilter={fn} that receives the query and source and returns a new results object. The renderer callbacks would still render those results as necessary.

const source = [
  { first: 'John', last: 'Doe', age: 58, city: 'Nashville' },
  { first: 'Jane', last: 'Doe', age: 29, city: 'Seattle' },
  // {...}
]

<Search
  source={source}                   // data to search
  searchFullText                    // options for searching the results
  searchFields={['first', 'last']}  // which fields to search in the results
/>

Remote search

I think this is probably the more common React use case. All searching/filter would be handled outside of the component. Whether those were API calls, or simple filter functions.

<Search
  source={this.state.searchResults}
  onSearchChange={this.handleSearch}
/>

It may help to call this prop results actually. Perhaps that is what differentiates an internal search from an external one, source vs results?

That's a lot to take in, thoughts?

@levithomason levithomason changed the title Search: Update to v1 API Search: add component Aug 23, 2016
@codecov-io
Copy link

codecov-io commented Aug 23, 2016

Current coverage is 98.73% (diff: 99.09%)

Merging #424 into master will increase coverage by 0.05%

@@             master       #424   diff @@
==========================================
  Files           102        106     +4   
  Lines          1524       1744   +220   
  Methods           0          0          
  Messages          0          0          
  Branches          0          0          
==========================================
+ Hits           1504       1722   +218   
- Misses           20         22     +2   
  Partials          0          0          

Powered by Codecov. Last update 3633853...43a356b

@jeffcarbs
Copy link
Member Author

jeffcarbs commented Aug 23, 2016

Update: Thanks for the feedback! I'll take a second pass at some point in the next few days.


@levithomason - I'll go through your comment in more detail, but wanted to show you something I just added for the search examples and get your thoughts. I'm thinking of it as a "Component Explorer"
screen shot 2016-08-23 at 3 43 40 pm.

Full commit here (TechnologyAdvice/stardust@e54e008) but going to paste in the message:

One of the dropdown examples had the ability to toggle certain props which
I found super useful in exploring the component. I think this could be
something powerful that could be extended to all components.

I think this would this would have a few benefits:

  • make it much easier for someone to get a sense of what the component does
  • enable someone to see how various props affect each other
  • remove the need for us to come up with a bunch of different examples for
    a specific component. We could just write one example that can show all the
    options.

Eventually these could possibly be auto-generated from the propTypes, although
there may need to be some manual tweaking to handle interplay b/w different
props.

Thoughts?

@levithomason
Copy link
Member

Hahaha, this is awesome! We're very much on the same page. I've got new docs in mind once we hit v1 components. This is one of the reasons we are pulling all enum props into the static _meta.props on every component. We want to provide a component explorer that shows all the props and their options with form controls.

This is not the first time I've gotten a doc suggestion related to work I've kept in the background. Due to this, I'm bringing that work forward now. I've pushed it to https://github.com/TechnologyAdvice/stardust/pull/427, will leave all future communication on docs to that PR.

@jeffcarbs jeffcarbs force-pushed the feature/search branch 2 times, most recently from 76599cf to 1e7d83a Compare August 26, 2016 05:21
@jeffcarbs
Copy link
Member Author

jeffcarbs commented Aug 26, 2016

Made the updates discussed above. I also updated it to use the new ElementType functionality. Custom formatting is the only thing still outstanding other than getting some test coverage.

I started writing more specs but got hung up a little in terms of how exactly to test everything. Also, I'm not sure I understand this failing spec:

<Search onFocus={handleFocus} />
                     ^ was not called on "focus".You may need to hoist your event handlers up to the root element.

It seems to be implying that the root element of the Search component should have the onFocus attached to it, but that's just the wrapping div. The Input component is what gets the onFocus handler attached.

Then it was saying the same thing but for the onSelect handler, which I don't think should be attached to the root Search element either. Any thoughts here?

@levithomason
Copy link
Member

 ✖ handles events transparently

    <Search onFocus={handleFocus} />
                     ^ was not called on "focus".You may need to hoist your event handlers up to the root element.

This is one of the common.isConformant() tests. It asserts that all components handle events transparently, or in other words, all the event handlers callback when the event is fired on the node.

This failure indicates that the Search component is consuming the onFocus prop, but it is not calling the handleFocus function when it passed to onFocus={handleFocus}.

So, if a user does this then focuses the Search, the handler is never called.

const handleFocus = () => console.log('Search was focused')

<Search onFocus={handleFocus} />

I'll take a look at the component and give some suggestions.

@levithomason
Copy link
Member

levithomason commented Aug 26, 2016

Indeed, onFocus is consumed (defined propTypes), but it is applied to a nested component. The suggestion in the assertion You may need to hoist your event handlers up to the root element. should work. Move the even handlers from the input:

https://github.com/jcarbo/stardust/blob/1e7d83afb8af6ff8789e1cfcb5b46217d43f0717/src/modules/Search/Search.js#L506

To the root element:

https://github.com/jcarbo/stardust/blob/1e7d83afb8af6ff8789e1cfcb5b46217d43f0717/src/modules/Search/Search.js#L643

The component will continue to work due to event bubbling, but so will the conformance tests as firing a focus event on the root node will also call the onFocus prop.

@levithomason
Copy link
Member

These comments were edited after my first two responses, so I'll respond now.

It seems to be implying that the root element of the Search component should have the onFocus attached to it, but that's just the wrapping div. The Input component is what gets the onFocus handler attached.

Then it was saying the same thing but for the onSelect handler, which I don't think should be attached to the root Search element either. Any thoughts here?

You are correct on all counts here. It is suggesting to hoist them, and the nested input is actually what is firing the change event. The awkwardness here is warranted and is due to the limitations of the current common test for events. I'll try to explain that.

The decision to hoist handlers was made for a few reasons:

  1. Common tests need to assert that components no not consume event handlers (all callbacks work). To do this, we need to be able to place a callback on the component, fire the associated event, and assert the spy was called. So, <Input onChange={spy} /> then fire a change even on <Input />.
  2. We can only fire events on the root node. We don't know what the children may look like for the component. This means, in order to be tested with a common test, components must implement change handlers on the root node.
  3. Given the need for 1, the limitation of 2, and the fact that events will bubble to the root node regardless of what child they were placed on, we decided to place the event handlers at the root node.

Admittedly, this is not optimal. There are a number of ways we could go:

  1. Continue as is, placing handlers at the root node
  2. Update the common test to accept something like a hash of listener props to functions that return the child it is assigned to. This way we can fire the event on the correct node.
  3. Remove common event testing and implement specific individual event tests for every component / event listener prop.

1 is not idea, 2 adds complexity, and 3 IMHO is the most correct approach but also the most costly time wise.

@jeffcarbs
Copy link
Member Author

Added specs and squashed commits. I think this is ready for review. The specs are failing on circle because phantomjs crashed, which seemed like an intermittent issue unrelated to this. They're passing locally for me - could you maybe pull this branch down and see if it's something introduced here or just a circle issue?

One thing this didn't address was the result formatting but I think that could be handled via the as props for both categories and results.

I'm eager to start using this component in my app so it'd be great to be able to get this to the finish line :)

@levithomason
Copy link
Member

There's an intermittent failure issue with webpack and react-highlightjs. A rebuild seems to have passed. Though, looks like we need more coverage.

I suggest using the codecov browser extension to help find missing coverage. It augments the github interface. PRs show coverage highlights right on the diff.

Also,you can open the index.html in the coverage/ directory to drill into coverage info. It is updated after every test run.

I'm out today, but will review this as soon as I can.

@jeffcarbs jeffcarbs force-pushed the feature/search branch 3 times, most recently from 6f71c1c to 83f74b3 Compare September 11, 2016 18:37
@jeffcarbs
Copy link
Member Author

Ok made some updates:

  • Got rid of my implementation of the "component explorer" in favor of just mirroring the examples from semantic-ui.com. In retrospect I realized that while the explorer will be super useful on the whole, it's almost more valuable to give a couple of vanilla examples that someone can use as a starting point. Also figured I'd just leave the explorer functionality for feat(docs): add component explorer #427 👍
  • Increased coverage so codecov looks happy :) However, looks like codecov only reports when circle has a green build and phantomjs keeps crashing :|
  • Squashed everything down.

@levithomason
Copy link
Member

Hm, seems it is crashing on the Search tests. The last test to complete, just before the crash, is: moves up on arrow up when open. I'd check the test that comes after that.

I've also retried the build without cache to ensure it has a complete run.

Lastly, it crashes locally for me as well :/ Just after Item "has sub component ItemContent". Not sure what is going on there, though, master doesn't crash nor other branches that I know of. There could be something in this PR causing it.

    ✔ has sub component ItemContent
11 09 2016 13:32:03.350:ERROR [phantomjs.launcher]: PhantomJS has crashed. Please read the bug reporting guide at
<http://phantomjs.org/bug-reporting.html> and file a bug report.

11 09 2016 13:32:03.930:ERROR [launcher]: PhantomJS crashed.

11 09 2016 13:32:03.936:INFO [launcher]: Trying to start PhantomJS again (1/2).
11 09 2016 13:32:14.319:WARN [PhantomJS 2.1.1 (Mac OS X 0.0.0)]: Disconnected (1 times), because no message in 10000 ms.
PhantomJS 2.1.1 (Mac OS X 0.0.0) ERROR
    Disconnected, because no message in 10000 ms.

I'll have time tomorrow morning to check into these things.

@jeffcarbs
Copy link
Member Author

jeffcarbs commented Sep 11, 2016

Added this branch locally to my app and quickly realized as may not work well for results rendering for a few reasons, specifically:

  • All the props aren't passed to the result, so you'd need to pass your own props specifically for your custom component to render.
  • Some props (e.g. className) will likely be clobbered which affects functionality like arrowing up/down through results.

I think I'm gonna take your advice and use the renderer approach. SUI-proper (I believe) lets you define functions that return HTML for the entire category or the entire results list. That wouldn't really be feasible here to maintain functionality (e.g. arrowing up/down) and would also put more onus on the user to not screw up the markup (e.g. the active class).

Currently:

// Search.Category
<ElementType {...rest} className={classes}>
  <div className='name'>{name}</div>
  {children /* Array of Search.Result */}
</ElementType>

// Search.Result
<ElementType {...rest} className={classes} onClick={handleClick}>
  {image && <div className='image'>{createImg(image)}</div>}
  <div className='content'>
    {price && <div className='price'>{price}</div>}
    {title && <div className='title'>{title}</div>}
    {description && <div className='description'>{description}</div>}
  </div>
</ElementType>

I'm thinking just move the internal stuff to a defaultRenderer and allow passing categoryRenderer and resultRenderer to the <Search> component which would be passed down to the subcomponent appropriately. Something like:

// Search.Category
const defaultRenderer = () => (
  <div className='name'>{name}</div>
)

// Within render()
<ElementType {...rest} className={classes}>
  {props.renderer ? props.renderer(props) : defaultRenderer()}
  {children /* Array of Search.Result */}
</ElementType>

// Search.Result
const defaultRenderer = () => (
  {image && <div className='image'>{createImg(image)}</div>}
  <div className='content'>
    {price && <div className='price'>{price}</div>}
    {title && <div className='title'>{title}</div>}
    {description && <div className='description'>{description}</div>}
  </div>
)

// Within render()
<ElementType {...rest} className={classes} onClick={handleClick}>
  {props.renderer ? props.renderer(props) : defaultRenderer()}
</ElementType>

This would prevent things like rendering categories/results in different orders which would mess up the component's functionality. Basically we render the "shell" of the results and enable people to customize the internals. I'll give this a shot and see how it works in my app, but would love any feedback if you have any.

@jeffcarbs
Copy link
Member Author

Found the bug! At some point I had added an image prop to the search results in the tests using faker. On tests where the component was getting mounted, karma would try to render the image via the URL which would call out to the internet, cause it to hang and eventually crash. I fixed the issue here: TechnologyAdvice/stardust@df45aa8

Looks like all the checks are passing now... finally haha.

I implemented the renderers mostly as I had described. The one slight change was keeping the name div as a wrapper for the categoryRenderer because that div is actually needed for style purposes so the categories are rendered to the left of the results.

@levithomason
Copy link
Member

I'm traveling today, hope to review this at the airport!

name: 'Search',
type: META.TYPES.MODULE,
props: {
size: ['mini', 'small', 'large', 'big', 'huge', 'massive'],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use the SUI constant here for the sizes. It looks like all except medium are supported. You can reference the Rail for example:

https://github.com/TechnologyAdvice/stardust/blob/master/src/elements/Rail/Rail.js#L41

@levithomason
Copy link
Member

levithomason commented Sep 12, 2016

There's an odd behavior when entering the initial search. All results are shown, then after the timeout, only the matching result or the no results message is shown.

http://g.recordit.co/uzumVUoITG.gif

EDIT

It looks like when clearing the search query, all results are added to state, then the initial next query change shows them all before the search is actually performed in the timeout. Here's the state after clearing the query:

{
  "isLoading": false,
  "results": [
    {
      "title": "Williamson and Sons",
      "description": "Future-proofed 4th generation monitoring",
      "image": "https://s3.amazonaws.com/uifaces/faces/twitter/mrxloka/128.jpg",
      "price": "$55.00"
    },
    {
      "title": "Berge Inc",
      "description": "Virtual reciprocal groupware",
      "image": "https://s3.amazonaws.com/uifaces/faces/twitter/rtyukmaev/128.jpg",
      "price": "$46.00"
    },
    {
      "title": "Heathcote - Kassulke",
      "description": "Self-enabling discrete orchestration",
      "image": "https://s3.amazonaws.com/uifaces/faces/twitter/nacho/128.jpg",
      "price": "$5.00"
    },
    {
      "title": "Bernhard Inc",
      "description": "Customizable 3rd generation methodology",
      "image": "https://s3.amazonaws.com/uifaces/faces/twitter/jonkspr/128.jpg",
      "price": "$75.00"
    },
    {
      "title": "Keeling, Kihn and Schinner",
      "description": "Cross-platform mobile info-mediaries",
      "image": "https://s3.amazonaws.com/uifaces/faces/twitter/buleswapnil/128.jpg",
      "price": "$37.00"
    }
  ],
  "value": ""
}

const re = new RegExp(_.escapeRegExp(value), 'i')

const isMatch = (result) =>
_.some(searchFields, (field) => result[field] && re.test(result[field]))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the regex will correctly pass/fail against any field value, the extra result[field] is not necessary.

@levithomason
Copy link
Member

levithomason commented Sep 12, 2016

Overall, I'm not sure it makes sense to any local search/filtering in this component. Wouldn't this be left to the API's search that you are searching? I don't have the depth of visibility you do here, but my question is, how is it possible to reconcile a query if it is both applied internally to the objects and externally against some API? It seems it can only either filter the list locally, or else be used externally by some other search mechanism (an API).

I'm thinking if I searched google for 'videos', i'd expect a results list of videos. Those results would be what ever that engine decided to return. The component displaying those results shouldn't again "search" the search results. LMK if I'm totally missing something here.


return _.map(categories, (category, name, index) => {
const categoryProps = {
key: `${category.name}-${index}`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're removing use of index or any other dynamically changing data in key props. It is an antipattern. See https://github.com/TechnologyAdvice/stardust/pull/452 for more.

We are instead using any part of the object we can expect to be unique, like category name here. Additionally, just in case that fails, then we use the user's childKey prop. We accept a childKey prop instead of key as the key prop is removed by React. In order to pass in a key value, it must be on a different prop name.

This then would look something like this:

return _.map(categories, ({childKey, ...category}, name, index) => {
  const categoryProps = {
    key: childKey || category.name,
// ...

@jeffcarbs
Copy link
Member Author

Re: local search: I implemented it because it's something SUI supports, however I'm fine with not doing things that don't make sense in the React world. It would simplify this component quite a bit. I could see a case being made that it gives the developer an option between a controlled or uncontrolled component.

// Uncontrolled
import longListOfItems from 'lib/data'

const search = () => <Search source={longListOfItems} />
// Controlled
class Search extends Component {
  handleChange = (e, result) => this.setState({ value: result.title })

  handleSearchChange = (e, value) => {
    this.setState({ isLoading: true, value })
    // this.makeApiRequest
  }

  render() {
    const { isLoading, value, results } = this.state

    return (
      <Search
        loading={isLoading}
        onChange={this.handleChange}
        onSearchChange={this.handleSearchChange}
        results={results}
        value={value}
      />
    )
  }

I don't personally have a use-case for the uncontrolled input since I'm going to be relying on a server to filter the data via a query param.

Re: The odd behavior within the search during loading: I think that's more of an issue with the example. It'll be up to the developer to determine which results to show when. In that example, when you start typing it uses the results from the previous query until new ones are fetched. I can update the example so it wipes the results once you go lower than the minCharacters.

*
* @returns {string} local url
*/
const safeImageUrl = () => '/base/test/images/logo.png'
Copy link
Member

@levithomason levithomason Sep 13, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'd prefer to simplify things for images instead. Since we don't need the images to actually render, how about just using foo.png instead of real faker urls or the safeImageUrl?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That doesn't work. Looking for the non-existent URL returns a 404 and crashes phantomjs:

    active item
      ✔ defaults to no result active
      ✔ defaults to the first item with selectFirstResult
      ✔ moves down on arrow down when open
19 09 2016 10:41:45.317:WARN [web-server]: 404: /foo.png
      ✔ moves up on arrow up when open
      ✔ skips over items filtered by search
      ✔ scrolls the selected item into view
      ✔ closes the menu
      ✔ uses custom renderer
    category
      ✔ defaults to the first item with selectFirstResult
      ✔ moves down on arrow down when open
19 09 2016 10:41:45.380:ERROR [launcher]: PhantomJS crashed.

19 09 2016 10:41:45.382:ERROR [launcher]: PhantomJS failed 2 times (crashed). Giving up.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm, how about http://placehold.it/100 or http://unsplash.it/100?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could try, although it's still going to go out to the internet to download an image which I'm not sure is best practice in a test suite.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, it's definitely not ideal. I haven't checked the Karma config for this, but I wonder if we could tell it to ignore foo.png. Perhaps, serve: false would prevent the 404 from happening?

I'll defer to you on this since you've got the deeper perspective. LMK what we should do here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I removed the safeImageUrl stuff and updated karma to load foo.png locally.

@jeffcarbs jeffcarbs force-pushed the feature/search branch 3 times, most recently from 3372772 to b7246db Compare September 19, 2016 18:06
@jeffcarbs
Copy link
Member Author

@levithomason - updated based on the most recent feedback. Haven't removed local search just yet, I wanted to get your thoughts on my last comment about that.

Otherwise, lmk what else there is to do

@levithomason
Copy link
Member

Regarding local search. It seems that is what differentiates the Search from the Dropdown in the React paradigm. If you already have items, you're probably "selecting" one of them. If you need to fetch the items from some remote location, based on the query, you are Searching for them. If this makes sense to you as well, let's drop the local search and uncontrolled pattern for this component.

I think that is all for this PR. In the future, I'd like to explore a base component that handles the commonalities with the Dropdown. Though, I think that is beyond scope for this PR.

@jeffcarbs
Copy link
Member Author

jeffcarbs commented Sep 19, 2016

In the future, I'd like to explore a base component that handles the commonalities with the Dropdown. Though, I think that is beyond scope for this PR.

YES! I actually started working on this but struggled with the appropriate level of abstraction. I'll open a new issue with some general thoughts and roadblocks I ran into.

@levithomason
Copy link
Member

Ready for a final here?

@jeffcarbs
Copy link
Member Author

Not sure if "reactions" translate to notifications or anything, but yea it's ready for a final review 👍

@levithomason
Copy link
Member

Thanks, just saw that :) For the record, no notifications were fired for the "reaction".

@levithomason
Copy link
Member

levithomason commented Sep 19, 2016

One request for the docs before we ship, can we update handleSearchChange to clear the results immediately on change? This is the easiest way I could find to clear up the issue where all results are shown after clearing the query and entering a new one.

this.setState({ isLoading: true, value, results: [] })

* Use Input, clean up unused props
* Category search
* Update Search parts to use ElementType
* Add specs
* Add examples to mirror semantic-ui.com
* Adds custom renderers for category and result

Update for tests: Whitelist foo.png in karma so it can be served locally.
@jeffcarbs
Copy link
Member Author

jeffcarbs commented Sep 19, 2016

I updated the examples to clear the results when the search query was cleared but after the timeout, see: https://github.com/TechnologyAdvice/stardust/pull/424/files#diff-a361c4ea0f5d24c0ed1827c86f6313acR26, which I think should be sufficient. Clearing the results immediately on change makes it kinda weird because "no results" is shown between each query. Let me know if that's cool.

Just rebased and squashed.

@levithomason levithomason merged commit f1acf9b into Semantic-Org:master Sep 19, 2016
@levithomason
Copy link
Member

Thanks much! Released in stardust@0.44.6.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants