Skip to content

Instantly share code, notes, and snippets.

@nikcree
Last active December 14, 2015 20:59
Show Gist options
  • Save nikcree/c467069a510f11452230 to your computer and use it in GitHub Desktop.
Save nikcree/c467069a510f11452230 to your computer and use it in GitHub Desktop.
Tool Tip Search in Primary Nav Location
// NB search for domain.com in this file and change it to your live domain.
jQuery(function($)
{
"use strict";
$(document).ready(function()
{
//creates search tooltip
new $.Tooltip();
//creates ajax search
new $.AjaxSearch();
});
$.AjaxSearch = function(options)
{
this.options = {
delay: 300, //delay in ms until the user stops typing.
minChars: 3, //dont start searching before we got at least that much characters
scope: 'body'
}
this.scope = $(this.options.scope);
this.timer = false;
this.lastVal = "";
this.bind_events();
}
$.AjaxSearch.prototype =
{
bind_events: function()
{
this.scope.on('keyup', '#s' , $.proxy( this.try_search, this));
},
try_search: function(e)
{
clearTimeout(this.timer);
//only execute search if chars are at least "minChars" and search differs from last one
if(e.currentTarget.value.length >= this.options.minChars && this.lastVal != $.trim(e.currentTarget.value))
{
//wait at least "delay" miliseconds to execute ajax. if user types again during that time dont execute
this.timer = setTimeout($.proxy( this.do_search, this, e), this.options.delay);
}
},
do_search: function(e)
{
var obj = this,
currentField = $(e.currentTarget).attr( "autocomplete", "off" ),
form = currentField.parents('form:eq(0)'),
results = form.find('.ajax_search_response'),
loading = $('<div class="ajax_load"><span class="ajax_load_inner"></span></div>'),
action = form.attr('action'),
values = form.serialize();
values += '&action=ajax_search';
//check if the form got get parameters applied and also apply them
if(action.indexOf('?') != -1)
{
action = action.split('?');
values += "&" + action[1];
}
if(!results.length) results = $('<div class="ajax_search_response"></div>').appendTo(form);
//return if we already hit a no result and user is still typing
if(results.find('.ajax_not_found').length && e.currentTarget.value.indexOf(this.lastVal) != -1) return;
this.lastVal = e.currentTarget.value;
$.ajax({
url: 'http://domain.com/wp-admin/admin-ajax.php', // add the domain of the site here
type: "POST",
data:values,
beforeSend: function()
{
loading.insertAfter(currentField);
},
success: function(response)
{
if(response == 0) response = "";
results.html(response);
},
complete: function()
{
loading.remove();
}
});
}
}
$.Tooltip = function()
{
this.options = {
delay: 1500, //delay in ms until the tooltip appears
delayOut: 300, //delay in ms when instant showing should stop
delayHide: 0, //delay hiding of tooltip in ms
"class": "search-tooltip", //tooltip classname for css styling and alignment
scope: "body", //area the tooltip should be applied to
data: "search-tooltip", //data attribute that contains the tooltip text
attach:"element", //either attach the tooltip to the "mouse" or to the "element" // todo: implement mouse, make sure that it doesnt overlap with screen borders
event: 'click', //mousenter and leave or click and leave
position:'bottom', //top or bottom
extraClass:'tooltip-class' //extra class that is defined by a tooltip element data attribute
}
this.body = $('body');
this.scope = $(this.options.scope);
this.tooltip = $('<div class="'+this.options['class']+'"><span class="arrow-wrap"><span class="arrow"></span></span></div>');
this.inner = $('<div class="inner_tooltip"></div>').prependTo(this.tooltip);
this.open = false;
this.timer = false;
this.active = false;
this.bind_events();
}
$.Tooltip.openTTs = [];
$.Tooltip.prototype =
{
bind_events: function()
{
this.scope.on(this.options.event + ' mouseleave', '[data-'+this.options.data+']', $.proxy( this.start_countdown, this) );
if(this.options.event != 'click')
{
this.scope.on('mouseleave', '[data-'+this.options.data+']', $.proxy( this.hide_tooltip, this) );
}
else
{
this.body.on('mousedown', $.proxy( this.hide_tooltip, this) );
}
},
start_countdown: function(e)
{
clearTimeout(this.timer);
if(e.type == this.options.event)
{
var delay = this.options.event == 'click' ? 0 : this.open ? 0 : this.options.delay;
this.timer = setTimeout($.proxy( this.display_tooltip, this, e), delay);
}
else if(e.type == 'mouseleave')
{
this.timer = setTimeout($.proxy( this.stop_instant_open, this, e), this.options.delayOut);
}
e.preventDefault();
},
reset_countdown: function(e)
{
clearTimeout(this.timer);
this.timer = false;
},
display_tooltip: function(e)
{
var target = this.options.event == "click" ? e.target : e.currentTarget,
element = $(target),
text = element.data(this.options.data),
newTip = element.data('created-tooltip'),
extraClass = element.data('tooltip-class'),
attach = this.options.attach == 'element' ? element : this.body,
offset = this.options.attach == 'element' ? element.position() : element.offset(),
position = element.data('tooltip-position'),
align = element.data('tooltip-alignment');
text = $.trim(text);
if(text == "") return;
if(position == "" || typeof position == 'undefined') position = this.options.position;
if(align == "" || typeof align == 'undefined') align = 'center';
if(typeof newTip != 'undefined')
{
newTip = $.Tooltip.openTTs[newTip]
}
else
{
this.inner.html(text);
newTip = this.options.attach == 'element' ? this.tooltip.clone().insertAfter(attach) : this.tooltip.clone().appendTo(attach);
if(extraClass != "") newTip.addClass(extraClass);
}
this.open = true;
this.active = newTip;
if((newTip.is(':animated:visible') && e.type == 'click') || element.is('.'+this.options['class']) || element.parents('.'+this.options['class']).length != 0) return;
var animate1 = {}, animate2 = {}, pos1 = "", pos2 = "";
if(position == "top" || position == "bottom")
{
switch(align)
{
case "left": pos2 = offset.left; break;
case "right": pos2 = offset.left + element.outerWidth() - newTip.outerWidth(); break;
default: pos2 = (offset.left + (element.outerWidth() / 2)) - (newTip.outerWidth() / 2); break;
}
}
else
{
switch(align)
{
case "top": pos1 = offset.top; break;
case "bottom": pos1 = offset.top + element.outerHeight() - newTip.outerHeight(); break;
default: pos1 = (offset.top + (element.outerHeight() / 2)) - (newTip.outerHeight() / 2); break;
}
}
switch(position)
{
case "top":
pos1 = offset.top - newTip.outerHeight();
animate1 = {top: pos1 - 10, left: pos2};
animate2 = {top: pos1};
break;
case "bottom":
pos1 = offset.top + element.outerHeight();
animate1 = {top: pos1 + 10, left: pos2};
animate2 = {top: pos1};
break;
case "left":
pos2 = offset.left - newTip.outerWidth();
animate1 = {top: pos1, left: pos2 -10};
animate2 = {left: pos2};
break;
case "right":
pos2 = offset.left + element.outerWidth();
animate1 = {top: pos1, left: pos2 + 10};
animate2 = {left: pos2};
break;
}
animate1['display'] = "block";
animate1['opacity'] = 0;
animate2['opacity'] = 1;
newTip.css(animate1).stop().animate(animate2,200);
newTip.find('input, textarea').focus();
$.Tooltip.openTTs.push(newTip);
element.data('created-tooltip', $.Tooltip.openTTs.length - 1);
},
hide_tooltip: function(e)
{
var element = $(e.currentTarget) , newTip, animateTo,
position = element.data('tooltip-position'),
align = element.data('tooltip-alignment');
if(position == "" || typeof position == 'undefined') position = this.options.position;
if(align == "" || typeof align == 'undefined') align = 'center';
if(this.options.event == 'click')
{
element = $(e.target);
if(!element.is('.'+this.options['class']) && element.parents('.'+this.options['class']).length == 0)
{
if(this.active.length) { newTip = this.active; this.active = false;}
}
}
else
{
newTip = element.data('created-tooltip');
newTip = typeof newTip != 'undefined' ? $.Tooltip.openTTs[newTip] : false;
}
if(newTip)
{
var animate = {opacity:0};
switch(position)
{
case "top":
animate['top'] = parseInt(newTip.css('top'),10) - 10;
break;
case "bottom":
animate['top'] = parseInt(newTip.css('top'),10) + 10;
break;
case "left":
animate['left'] = parseInt(newTip.css('left'), 10) - 10;
break;
case "right":
animate['left'] = parseInt(newTip.css('left'), 10) + 10;
break;
}
newTip.animate(animate, 200, function()
{
newTip.css({display:'none'});
});
}
},
stop_instant_open: function(e)
{
this.open = false;
}
}
});
/* Place in functions.php
****************************************************************************************** */
// Register and Enqueue Ajax Search JS
wp_register_script( 'nikcree-ajax-search', get_stylesheet_directory_uri() . '/js/ajax-search.js', array('jquery'), false, false );
wp_enqueue_script( 'nikcree-ajax-search' );
/* AJAX SEARCH */
function search_form( $form ) {
$search_params = array(
'placeholder' => __( 'Search', 'nikcree' ),
'search_id' => 's',
'form_action' => home_url( '/' ),
'ajax_disable' => false
);
$value = '';
if ( ! empty( $_GET['s'] ) )
$value = get_search_query();
$form = '<form action="' . $search_params['form_action'] . '" id="searchform" method="get">';
$form .= ' <div>';
$form .= ' <input type="submit" value="&#xf002;" id="searchsubmit" class="button" />';
$form .= ' <input type="text" id="s" name="' . $search_params['search_id'] . '" value="' . $value . '" placeholder=\'' . $search_params['placeholder'] . '\' />';
$form .= ' </div>';
$form .= '</form>';
return $form;
}
//first append search item to main menu
add_filter( 'wp_nav_menu_items', 'append_search_nav', 10, 2 );
function append_search_nav ( $items, $args ) {
if ( ( is_object( $args ) && $args->theme_location == 'primary' ) ) {
add_filter( 'genesis_search_form', 'search_form' );
$form = get_search_form( false );
remove_filter( 'genesis_search_form', 'search_form' );
$items .= '<li id="menu-item-mobile-search" class="mobile-nav menu-item menu-item-mobile-search">
' . get_search_form( false ) . '
</li>';
$items .= '<li id="menu-item-search" class="menu-item menu-item-search-dropdown">
<a class="search-icon" href="?s=" rel="nofollow" data-search-tooltip="' . htmlspecialchars( $form ) . '"><span class="hidden_link_text">' . __( 'Search' , 'nikcree' ) . '</span></a>
</li>';
}
return $items;
}
// now hook into wordpress ajax function to catch any ajax requests
add_action( 'wp_ajax_ajax_search', 'ajax_search' );
add_action( 'wp_ajax_nopriv_ajax_search', 'ajax_search' );
function ajax_search()
{
unset($_REQUEST['action']);
if(empty($_REQUEST['s'])) $_REQUEST['s'] = array_shift(array_values($_REQUEST));
if(empty($_REQUEST['s'])) die();
$defaults = array('numberposts' => 5, 'post_type' => 'any', 'post_status' => 'publish', 'post_password' => '', 'suppress_filters' => false);
$_REQUEST['s'] = apply_filters( 'get_search_query', $_REQUEST['s']);
$search_parameters = array_merge($defaults, $_REQUEST);
$search_query = apply_filters('avf_ajax_search_query', http_build_query($search_parameters));
$query_function = apply_filters('avf_ajax_search_function', 'get_posts', $search_query, $search_parameters, $defaults);
$posts = (($query_function == 'get_posts') || !function_exists($query_function)) ? get_posts($search_query) : $query_function($search_query, $search_parameters, $defaults);
$search_messages = array(
'no_criteria_matched' => __("Sorry, no posts matched your criteria", 'nikcree'),
'another_search_term' => __("Please try another search term", 'nikcree'),
'time_format' => get_option('date_format'),
'all_results_query' => http_build_query($_REQUEST),
'all_results_link' => home_url('?' . http_build_query($_REQUEST)),
'view_all_results' => __( 'View all results', 'nikcree' )
);
$search_messages = apply_filters('avf_ajax_search_messages', $search_messages, $search_query);
if(empty($posts))
{
$output = "<span class='ajax_search_entry ajax_not_found'>";
$output .= "<span class='ajax_search_image without_image info'>";
$output .= "</span>";
$output .= "<span class='ajax_search_content'>";
$output .= " <span class='ajax_search_title'>";
$output .= $search_messages['no_criteria_matched'];
$output .= " </span>";
$output .= " <span class='ajax_search_excerpt'>";
$output .= $search_messages['another_search_term'];
$output .= " </span>";
$output .= "</span>";
$output .= "</span>";
echo $output;
die();
}
//if we got posts resort them by post type
$output = "";
$sorted = array();
$post_type_obj = array();
foreach($posts as $post)
{
$sorted[$post->post_type][] = $post;
if(empty($post_type_obj[$post->post_type]))
{
$post_type_obj[$post->post_type] = get_post_type_object($post->post_type);
}
}
//now we got everything we need to preapre the output
foreach($sorted as $key => $post_type)
{
if(isset($post_type_obj[$key]->labels->name))
{
$label = apply_filters('avf_ajax_search_label_names', $post_type_obj[$key]->labels->name);
$output .= "<h4>".$label."</h4>";
}
else
{
$output .= "<hr />";
}
foreach($post_type as $post)
{
$image = get_the_post_thumbnail( $post->ID, 'thumbnail' );
$extra_class = $image ? "with_image" : "without_image";
$post_type = $image ? "" : get_post_format($post->ID) != "" ? get_post_format($post->ID) : "standard";
$excerpt = "";
if(!empty($post->post_excerpt))
{
/**
* This function shortens a string
*/
$string = $post->post_excerpt;
$limit = 70;
$break = " ";
$pad = "...";
$stripClean = true;
$excludetags = '';
$safe_truncate = true;
if($stripClean)
{
$string = strip_shortcodes(strip_tags($string, $excludetags));
}
if(strlen($string) <= $limit) return $string;
if(false !== ($breakpoint = strpos($string, $break, $limit)))
{
if($breakpoint < strlen($string) - 1)
{
if($safe_truncate || is_rtl())
{
$string = mb_strimwidth($string, 0, $breakpoint) . $pad;
}
else
{
$string = substr($string, 0, $breakpoint) . $pad;
}
}
}
// if there is no breakpoint an no tags we could accidentaly split split inside a word
if(!$breakpoint && strlen(strip_tags($string)) == strlen($string))
{
if($safe_truncate || is_rtl())
{
$string = mb_strimwidth($string, 0, $limit) . $pad;
}
else
{
$string = substr($string, 0, $limit) . $pad;
}
}
$excerpt = apply_filters( 'avf_ajax_search_excerpt', $string );
}
else
{
$excerpt = get_the_time($search_messages['time_format'], $post->ID);
}
$link = apply_filters('av_custom_url', get_permalink($post->ID), $post);
$output .= "<a class ='ajax_search_entry {$extra_class}' href='".$link."'>";
$output .= "<span class='ajax_search_image {$extra_class} {$post_type}'>";
$output .= $image;
$output .= "</span>";
$output .= "<span class='ajax_search_content'>";
$output .= " <span class='ajax_search_title'>";
$output .= get_the_title($post->ID);
$output .= " </span>";
$output .= " <span class='ajax_search_excerpt'>";
$output .= $excerpt;
$output .= " </span>";
$output .= "</span>";
$output .= "</a>";
}
}
$output .= "<a class='ajax_search_entry ajax_search_entry_view_all' href='".$search_messages['all_results_link']."'>".$search_messages['view_all_results']."</a>";
echo $output;
die();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment