Code Monkey home page Code Monkey logo

holmes's Introduction

Holmes.js

Fast and easy searching inside a page.

Build Status Coverage Status npm version Bower version CDNJS version Join the chat at https://gitter.im/Haroenv/holmes gzipped size

Holmes filters a list of elements based on the value of a input in just ~2KB.

Installation

You can install Holmes with either npm or bower under the package name holmes.js. For npm that looks like this:

$ yarn add holmes.js # or via npm

After which you can add it in your page with i.e. webpack, rollup, browserify or loading the module in a different script tag.

You have to make sure that you have a css rule for the class .hidden that hides elements however you want. One option is to have this:

.hidden {
  display: none;
}

but this could be any css you want.

Usage

demo

When should I use it?

You should use Holmes when

  • you have a limited amount of items
  • you don't need typo-tolerance
  • you only want to add a really small library
  • all items are already visible on the page

In cases where you have a more complicated expectation, I'd suggest using a service like Algolia.

Fair disclosure: I currently work at Algolia, does it seem interesting? Join us!

Simple example

holmes({
  input: '.search input', // default: input[type=search]
  find: '.results div' // querySelectorAll that matches each of the results individually
})

Options

full documentation

input

default: input[type=search]

querySelector for the input

examples: input, .search input

find required

querySelectorAll for elements to search in

examples: blockquote p, .result, .results div

class

class.hidden

default: hidden

Class to add when the a .find doesn't contain the search query.

examples: hidden, dn, none

class.visible

default: false

Class to add to visible items if they contain the search query.

examples: visible, vis, nohidden

placeholder

default: false

html to show when no results.

examples: <p> No results </p>, Didn't find anything.

dynamic

default: false

Enable this option if you want Holmes to query the value of the .find at every input.

examples: true, false

instant ⚠️ DEPRECATED

default: false

This option is deprecated. To use Holmes in an async environment, initialise it with:

holmes(options).start();
// or
const h = new holmes(options);
h.start();

This way it'll start immediately, just like it used to do with instant: true. Sorry for the inconvenience.

By default Holmes will wait on a DOMContentLoaded event to start searching. If you're loading the elements by AJAX for example this event comes too early. In that case you can enable instant, and start Holmes when your content is ready.

examples: true, false

minCharacters

default: 0

A minimum amount of characters need to be typed before Holmes starts filtering.

examples: 2, 5

mark

default: false

To start showing the result in a <mark> tag inside the .find, you should enable this. To change the colour this match is shown in, you should style the mark background-color.

❗ this will break event listeners on nested content

❗ this won't work if the character after the match is a literal >.

🗣️ If you really have to use this character, you can replace all occurences of > by &gt;

examples: true, false

hiddenAttr

default: true

Adds hidden="true" to hidden elements. Interesting link explaining its use.

shouldShow

default match judgment is a partial match of the input value.

function(htmlText, search) {
  return htmlText.indexOf(search) !== -1;
}

A custom matching function to be called with as first argument the text of an element, and as second argument the current input text. This should return true if you want the element to show, and false if it needs to be hidden.

var customMatching = function(htmlText, search) {
  return search.split(/\s+/).every(function(v, i) {
    if (htmlText.indexOf(v) === -1) {
      return false;
    }
    return true;
  });
}
holmes({
  shouldShow: customMatching
})

onHidden

Callback for when an item is hidden.

function(el) {
  console.log('hide',el);
}

onVisible

Callback for when an item is visible again.

function(el) {
  console.log('show',el);
}

onEmpty

Callback for when no items were found.

function(placeholder) {
  console.log('nothing found',placeholder);
}

onFound

Callback for when items are found after being empty.

function(placeholder) {
  console.log('something found',placeholder);
}

onInput

Callback for every input.

function(input) {
  console.log('current input',input);
}

Methods and members

For all of the methods you should initialise a new instance of Holmes like this:

var h = new holmes(options);

Then you can use the following methods:

.clear()

You can clear a holmes input programmatically, by using:

h.clear();

.count()

You can receive informations on what elements are visible, hidden and in total at any point:

h.count(); // {all: 41, hidden: 34, visible: 7}

.start()

Start an even listener for the specified options. Holmes always has .start() running on initialisation.

h.start();

.stop()

Stops the current running event listener. Resolves a Promise when this has been completed.

h.stop();
h.start(); // could accidentally start too soon

h.stop().then(h.start); // might take a small time

.hidden

There's also a member .hidden that gives the count without a function call:

console.log(h.hidden); // 34

.elements

A NodeList of all of the elements that holmes considers. There's also .elementsLength for the amount of elements and .elementsArray with an array of the elements.

.input

The input that holmes looks in. There's also the last search string as .searchString

.placeholder

The current placeholder (DOM Node).

.running

Whether or not this instance is running.

.options

Shows the options chosen chosen for this instance of holmes. You can also set options like this after initialisation.

console.log(h.options); // specified options

note: setting options after it's running might require h.stop().then(h.start)

Showcase

What who image
bullg.it @haroenv screenshot of bullg.it
family.scss @lukyvj screenshot of family.scss
wikeo.be @bistory searching on wikeo.be for pages
lunchbreakapp.be @AndreasBackx searching on lunchbreak for items

I'd love to find out how people use my project, let me know if you want to be featured!

Questions?

Compatible up to IE11. For support of older browsers you'll need to polyfill classList, addEventListener and the input event with for example remy/polyfills. I haven't tried this myself yet, so let me know what you used if you support older browsers!

For IE11 you need to polyfill Object.assign and String.includes, you can do that as described in #90

Let me know on twitter: @haroenv, or in an issue.

Contributing

Contributions are always welcome! Here are some loose guidelines:

  • use feature branches
  • don't make it slower
  • explain why you want a feature
  • npm run doc to recreate the documentation

Building to a UMD is done via rollup (npm run build).

But I don't bite, if you have any questions or insecurities, hit me up for example on gitter.

License

Apache 2.0

holmes's People

Contributors

alexhoma avatar andreasbackx avatar chimame avatar extend1994 avatar gitter-badger avatar haroenv avatar lukyvj avatar marcobiedermann avatar reneviering avatar twin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

holmes's Issues

Object.assign() & String.prototype.includes() not working on IE11

Hi guys!

I recently had a problem running a webapp in IE11 which includes holmes. IE doesn't recognize some ES6 methods and properties of your source code and it was blocking part of my own js.

Errors come when IE is trying to recognize Object.assign() and String.prototype.includes()

I tried to add the polyfills included in the MDN documentation at the start of your source code. And it works! Now holmes.js runs fine in IE11 with this hack:

if (typeof Object.assign != 'function') {
  Object.assign = function (target, varArgs) {
    'use strict';
    if (target == null) {
      throw new TypeError('Cannot convert undefined or null to object');
    }

    var to = Object(target);

    for (var index = 1; index < arguments.length; index++) {
      var nextSource = arguments[index];

      if (nextSource != null) {
        for (var nextKey in nextSource) {
          if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
            to[nextKey] = nextSource[nextKey];
          }
        }
      }
    }
    return to;
  };
}

if (!String.prototype.includes) {
    String.prototype.includes = function() {
        'use strict';
        return String.prototype.indexOf.apply(this, arguments) !== -1;
    };
}

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes

I hope you can find this fix useful for adding it in a future upgrade :)

Regards!

dynamic content

if the content is dynamic, on the places aligned in the code the elements should be queried again.

If you want this feature, leave a comment or a thumbs up and I'll do it.

Question about clearing search field.

Hello,

I read previous issues and couldn't find an answer to my exact question. I apologize if I overlooked a sufficient answer. My question is how, when clearing the searching field using an "X" like in the demo(s) provided, can all of the searchable divs/elements return to the page? For me, currently, if I were to search a string that did not exist in any searchable div, the placeholder would appear is should "e.g., "No results found." However, unlike the demos, when I clear the field with the "X," the place holder remains and no divs return. It is only when I begin to type again that the elements reappear. It may also be beneficial to note that backspacing after the placeholder appears works as expected, i.e., the elements reappear if the new shortened String is found in any div. Let me know if you have any suggestions or methods to solve this.

Thank you!

Use holmes after DOMContentLoaded

Hi !
I am testing holmes for a webapp but it is impossible to make it work if the holmes function is called after DOMContentLoaded.
I'm loading dynamic parts of the app using Ajax and I'm unable to use holmes :-(

Do you have an idea to improve holmes elegantly ?

Thanks !

Does main.js need to be hosted?

Hi, I'd like to host your library, holmes on cdnjs — It is one of the most famous free and public web front-end CDN services which is used by ~1,143,000 websites worldwide.

As far as I can comprehend, the following files need to be hosted:

js/holmes.js
js/holmes.es.js

Should the file js/main.js also be included? Also, please let me know if any other files should be hosted so developers can directly use holmes from CDNJS.

Create tests

Currently this repository is untested, but it should be possible with jsdom or phantomjs. I'd prefer this to be done with ava, but if someone has another opinion, it's good too.

Add more tests

  • callbacks
  • .mark
  • .minCharacters
  • contenteditable
  • some "unreachable" errors
  • deprecated _inputHandler() needs to be checked like .search

Some of this code is already partially written and commented out.

Add option to create a new instance without starting to listen

Implementation-wise, this is quite simple, it's just an if for that option before starting, similar to .instant.

I have no idea what to call it though.

This is useful for when you want to set h.options.xxx after initialisation of the instance, but don't want to use the options object or stop and then start again.

fix jsnext:main

jsnext:main or module in the package.json should point to a version of main.js, but without flow and without the relative imports. This should be made with a separate rollup config, or by using rollup via the API and publishing es5 and es6 like that.

I want to use custom stringIncludes

Thank you for a wonderful product.

Problems and suggestions

The current implementation is a search that shows or hides the HTML element by partially matching the search value.
AND, OR search can not be performed.

Countermeasure

Set stringIncludes as the default search, pass the search function as an option, and display or hide the HTML element according to the result of the passed search function.

Search started/ended events?

Possibly a feature request, but perhaps there's a current way to support this?

I have a page with a large number of inputs that I'm using holmes to filter down. Currently when you enter a character into the search box it will freeze up for 2-3 seconds while it searches through the elements. Each subsequent character takes less time (as there's fewer elements to search through).

The experience is awkward because the character you type doesn't display until after the 2-3 second period.

I'm interested in any possible improvements (asynchronous behavior somehow?).

However, a quick fix would be to have an event that fires before the search happens and another that fires after it's finished, that way I can throw up a loading indicator/spinner while the process completes.

How to use with masonry?

Trying to use Holmes with masonry grid but the absolute position required on <li> stuck the search result. Any ideas?

onFound is being called too often

Now it calls at the same time as onVisible, so for every element, but it should only call with the very first element (except the placeholder) that’s visible again.

Fix eager.io

Install.json didn't work yet and it probably changed.

Broken? Firefox issue?

I found this package today and it seems to do exactly what I want. Except.. I can't get it to work.

I installed it via npm, put a basic <input type="search"> and set it up like so

var h = holmes({
                input: 'input[type=search]',
                find: 'label',
                class: {
                    visible: 'highlight',
                    hidden: 'hidden'
                },
                hiddenAttr: true,
                mark: true
            });

Nothing appears to happen, and nothing shows up in console either.

The demo page doesn't work properly for me either. As I type I get TypeError: t.textContent.toLowerCase(...).includes is not a function errors in the console.

These may be separate issues, I'm not sure.

Add .clear() function

This would be a function available on a certain instance of a Holmes. Initialisation will need to be adjusted so it'd become:

var h = new holmes({options});
h.clear();

It should still work with:

homes({options});

so it doesn't break the api.

.clear() should set the value of .search to '', and run .search() again.

Suggested in #42 and on gitter

Highlight search term

Wouldn’t it be cool to highlight the searchterm within in the result?

Like this:
image

Catch when a .find isn’t a valid queryselector

Let’s say you accidentally add find: ‘.’ or other invalid values. Now it would throw and not clean up properly.

Checking should happen somewhere around here

A try/catch should do the trick I think. In the catch block the cleaning up could happen (calling h.stop could not work, so manually do what already needs to be removed like placeholder) then we should throw with a descriptive error saying that .find was bad

Hide elements other than the ones matched

Hi, thanks for the handy library.

I have listings to search like this picture. As you can see, so much elements in there.
Search successfully runs, however, in my case I want to hide parent element.

my find: '.row .col p', works well for the targeted text to search, however I want not only hide p element but the whole row.

optional: is there a way to search in two elements ? say for example : find: ['.row .col p', '.row .col h3']. (Probably I am asking too much for what is intended, excuse me for this)

thanks a lot !!

Using Holmes with Pagination libraries

Hi :)

I have only just started using Holmes and find it easy to use and very 'Ronsil'.

The trouble I have is when I have a paginated list, Holmes seems to only search the rows that are visible - so I wanted to know if this was by design or something I have to overcome?

For Example - I am using the "jquery-paginate.js"

When you specify how many rows to display in each 'page' the visible rows are given the style attribute of style="display: table-row;" . The non-visible rows are given a style attribute of style="display: none;"

Holmes.js adds the classes 'visible' and 'hidden' as per the setup, but I am unsure whether Holmes actually searches 'rows' that are not displayed and wondered if this was an issue ?

Thanks for your help in advance.

Add hidden attribute when it's hidden

Should be the default since it then won't be spoken by screen readers (it also won't with display none or opacity hidden)

Fixed in #67 but still needs a good name

Not really an issue, but a couple of questions

I am curious if there is a way to fix, I am loading all of my elements as hidden by default, however when I start typing into the search field, my elements start showing... What I would like is as I delete all text from the search field, it shows all elements because "" is found in all search terms, instead I want everything to be hidden?

Second question I would prefer to add the ability to set a minimum number of characters in the search field before it starts revealing my hidden elements, like the ability to have to type 3 characters then it starts filtering?

Not filtering content...

I am not sure what I am doing wrong, as it appears that everything on the plugin is working fine for me other than, changing the classes on my elements from visible to hidden.

I have tried basically to make a complete replica of your page, minus that my content is being loaded from the database, on load each of the elements gets the class of "visible" applied, but then I get nothing as I start to type in my input field.

Please feel free to look here: http://ncm.web50.iwmstage.com/index.php/association-lookup

I am hoping that you may notice something that I have done wrong, and can just as easily point out the solution for me?

How to use it with Vue.

It seems NOT work with Vue's 'v-for'.
simple example:

<div class="result">
    <blockquote v-for="item in items">
       {{ item }}
    </blockquote>
</div>
<script>
    data:{items:['aaa','bbb','ccc','abc']}
</script>

Is there a way to trigger filtering manually??

Hi!

I'm trying to trigger Holmes filtering manually, but I can't found anything to do that in the docs.

My problem is that I'm rewriting the DOM elements to filter every X seconds with and AJAX request, and when I do, if I have a matching element on the search input, the DOM ignores this, of course, because I'm regenerating all the elements from a JSON response manually using Handlebars.

So that's the reason why I need some way to make the holmes filtering manually after Handlebars rewrites my DOM.

Is there any method on the docs that I'm missing or I'm asking for something impossible? hehehe

Regards!

add empty class option

By default this shouldn't be enabled, but if a empty class is given it should apply when the .value is ''.

suggested in #39

Show only a fixed amount of elements and a "show more" button

I could use this myself, but I'm wondering if it should be in the main bundle or in a separate plugin-type thing.

In ES5 and outside of Holmes it looks kinda like this:

var MAX = 0;

function showMore(n) {
    MAX += n;
    paginate();
}

var h = holmes({
    hiddenAttr: true,
    find: '.item',
    placeholder: '<tr class="center">niets gevonden</tr>',
    onInput: paginate
});

function paginate() {
    h.elementsArray.forEach(function (el, index) {
        if (index > MAX) {
            el.setAttribute("hidden", "true");
        } else if (!el.classList.contains("hidden")) {
            el.removeAttribute("hidden");
        }
    });
}

setTimeout(paginate, 10);

in the real version there wouldn't be a need for the timeouts

Add result count

While one can easily build an extension to count the results, I think it would be sensible if holmes would keep track of how many results are visible, hidden and how many items exist overall.

Add docs, etc to bower ignore

Hey,

when installing via bower you are pulling quite a load from the server. I think the following should be added to the bower.json ignore.

  • docs/
  • css/
  • images/
  • js/microlight.js
  • index.html
  • deploy.sh
  • install.json (?)
  • package.json
  • .bower.json (? what does this do?)

Maybe it would make sense to move to a more comment js structure like so:

  • src
  • dist
  • test
  • docs/
  • examples/
    • css/
    • ...

Using some kind of simple gulp script you could minify the js file and move it to dist and than ignore everything but fist for bower.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.