Laravel 5 - typeahead search: how to find words with accent or without - search

I have implemented bootstrap-typeahead and when doing a search of, for example, the word "vision" (without accent), I want typeahead to find the coincidences that there is both "visión" (with accent) and "vision".
I have seen several examples to do this, like: accent insensitive regex but I do not understand the form to implement it in typeahaead. And I saw this too: Typeahead insensitive accent and I have created a new file bootstrap3-typeahead-ci.min.js as in this answer is shown, but this not working. Some help? Thanks.
EDITED
To complement the question
this is my typeahead.js (reduced)
$(document).ready(function(){
function buscar(texto){
$('#texto').val(texto);
$('#buscar').submit();
}
if ($('.typetitulo').length) {
var lang = $("#lang_js").data('value');
var json_location = 'storage/json/';
var noticia_location = 'actualidad/';
var noticias = new Bloodhound({
prefetch: {
url: json_location + lang + '/' + 'noticia.json',
cache: false
},
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('title', 'lead'),
queryTokenizer: Bloodhound.tokenizers.whitespace
});
var documentos = new Bloodhound({
prefetch: {
url: json_location + '/' + 'documento.json',
cache: false
},
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name', 'description'),
queryTokenizer: Bloodhound.tokenizers.whitespace
});
$('.typetitulo').typeahead(
{
name: 'noticias',
display: 'title',
source: noticias,
templates: {
header: "<h3>"+ tit_actualidad +"</h3>",
suggestion: function (item) {
var enlace = noticia_location + item.id + '/' + item.slug;
return "<div><a href='"+enlace+"'>" + item.title + "</a></div>";
}
}
},
{
name: 'documentos',
display: 'name',
source: documentos,
templates: {
header: "<h3>"+ tit_documentos +"</h3>",
suggestion: function (item) {
var enlace = item.path;
return "<div><a href='"+enlace+"'>" + item.name + "</a></div>";
}
}
}).on('typeahead:selected', function(e){
e.target.form.submit();
});
}
});
In the view:
{!! Form::open([
'route' => 'buscar',
'id' => 'buscar',
'name' => 'buscar',
'class' => 'buscador col-xs-12',
'method' => 'POST',
'accept-charset' => 'utf-8'
]) !!}
<input name="texto" class="input_buscador typetitulo" autocomplete="off" type="text"/>
<input name="lang" type="hidden" value="{{$lang}}"/>
{!! HTML::image('images/web/icons/lupa.svg', '', array('height' => '30', 'class' => 'boton_buscador', 'onclick' => 'document.buscar.submit()') ) !!}
{!! Form::close() !!}
// .... //
#if(isset($data['noticias']) && $data['noticias']->count() !== 0)
<div class="col-xs-12 pad_inf_2">
<h3>#lang('header.actualidad')</h3>
#foreach($data['noticias'] as $value)
<span class="item">
{{$value['title']}}
</span>
#endforeach
</div>
#endif
#if(isset($data['docs']) && $data['docs']->count() !== 0)
<div class="col-xs-12 pad_inf_2">
<h3>#lang('header.biblioteca')</h3>
#foreach($data['docs'] as $value)
<span class="item">
{{$value['name']}}
</span>
#endforeach
</div>
#endif
This is the typeahead-insensitive.js as in this answer is shown: Typeahead insensitive accent
// function for making a string accent insensitive
$.fn.typeahead.Constructor.prototype.normalize = function (str) {
// escape chars
var normalized = str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
// map equivalent chars
normalized = normalized.replace(/[aãáàâ]/gi, '[aãáàâ]');
normalized = normalized.replace(/[eẽéèê]/gi, '[eẽéèê]');
normalized = normalized.replace(/[iĩíìî]/gi, '[iĩíìî]');
normalized = normalized.replace(/[oõóòô]/gi, '[oõóòô]');
normalized = normalized.replace(/[uũúùû]/gi, '[uũúùû]');
normalized = normalized.replace(/[cç]/gi, '[cç]');
// convert string to a regular expression
// with case insensitive mode
normalized = new RegExp(normalized, 'gi');
// return regular expresion
return normalized;
}
// change 'matcher' method so it became accent insensitive
$.fn.typeahead.Constructor.prototype.matcher = function (item) {
// get item to be evaluated
var source = this.displayText(item);
// make search value case insensitive
var normalized = this.normalize(this.query);
// search for normalized value
return source.match(normalized);
}
// change 'highlighter' method so it became accent insensitive
$.fn.typeahead.Constructor.prototype.highlighter = function (item) {
// get html output
var source = this.displayText(item);
// make search value case insensitive
var normalized = this.normalize(this.query);
// highlight normalized value in bold
return source.replace(normalized, '<strong>$&</strong>');
}
And in the layout I added:
{{-- Typeahead --}}
{!! HTML::script('https://cdnjs.cloudflare.com/ajax/libs/typeahead.js/0.11.1/typeahead.bundle.min.js') !!}
{!! HTML::script('js/web/typeahead-insensitive.js') !!}
{!! HTML::script('js/web/typeahead.js') !!}

Related

Uncaught TypeError: Cannot read property 'addEventListener' of null in .net on clear filter

I'm using Tabulator to implement search
Here's my html -- no problems until I try to search, then, I receive the above-captioned error:
<div>
<select id="filter-field">
<option></option>
<option value="custId">Customer ID</option>
<option value="custType">Customer Type</option>
<option value="custName">Customer Name</option>
<option value="groupId">Group ID</option>
</select>
<select id="filter-type">
<option value="=">=</option>
<option value="<"><</option>
<option value="<="><=</option>
<option value=">">></option>
<option value=">=">>=</option>
<option value="!=">!=</option>
<option value="like">like</option>
</select>
<input id="filter-value" type="text" placeholder="value to filter">
</div>
<div id="example-table"></div>
I'm receiving an error in the JavaScript:
````<script>
var table;
function handleCellUpdated(cell) {
console.log(cell);
console.log(cell.getRow());
console.log(cell.getRow().getData());
var record = cell.getRow().getData();
$.ajax({
url: "api/SalesTrackerCustomers/" + record.id,
data: JSON.stringify(record),
contentType: 'application/json',
type: "PUT",
success: function (response, textStatus, xhr) {
console.log("success")
},
error: function (XMLHttpRequest, textStatus, error) {
console.log("error")
}
});
}
function initTable() {
//Build Tabulator
table = new Tabulator("#customers", {
height: "90vh",
placeholder: "Loading...",
addRowPos: "bottom",
columns: [
{ title: "Customer ID", field: "custId", width: 150, editor: "input" },
{ title: "Customer Type", field: "custType", width: 130, editor: "input" },
{ title: "Customer Name", field: "customerName", editor: "input" },
{ title: "Group ID", field: "groupId", editor: "number" }
],
cellEdited: handleCellUpdated
});
loadCustomers();
}
function loadCustomers() {
console.log("loading data");
$.ajax({
url: "/api/SalesTrackerCustomers",
method: "GET"
}).done(function (result) {
table.setData(result);
});
}
// javascript for add/delete
//Add row on "Add Row" button click
document.getElementById("add-row").addEventListener("click", function () {
table.addRow({});
});
//Delete row on "Delete Row" button click
document.getElementById("del-row").addEventListener("click", function () {
table.deleteRow(1);
});
// javascript for search
//Define variables for input elements
var fieldEl = document.getElementById("filter-field");
var typeEl = document.getElementById("filter-type");
var valueEl = document.getElementById("filter-value");
//Custom filter example
function customFilter(data) {
return data.car && data.rating < 3;
}
//Trigger setFilter function with correct parameters
function updateFilter() {
var filterVal = fieldEl.options[fieldEl.selectedIndex].value;
var typeVal = typeEl.options[typeEl.selectedIndex].value;
var filter = filterVal == "function" ? customFilter : filterVal;
if (filterVal == "function") {
typeEl.disabled = true;
valueEl.disabled = true;
} else {
typeEl.disabled = false;
valueEl.disabled = false;
}
if (filterVal) {
table.setFilter(filter, typeVal, valueEl.value);
}
}
//Update filters on value change
document.getElementById("filter-field").addEventListener("change", updateFilter);
document.getElementById("filter-type").addEventListener("change", updateFilter);
document.getElementById("filter-value").addEventListener("keyup", updateFilter);
//Clear filters on "Clear Filters" button click
document.getElementById("filter-clear").addEventListener("click", function () {
fieldEl.value = "";
typeEl.value = "=";
valueEl.value = "";
table.clearFilter();
});
Can anyone add insight on this error? I have tried moving JavaScript around, and I think it may have to do with the placement of the JavaScript. It is displaying above captioned error on on //Clear filters on "Clear Filters" button click; it could also be on the load tabulator javascript function on table
The error you are receiving is because you are trying to add an eventListener to a null value. When using document.getElementById(''), if it does not find an element it returns null. Because you are not checking that you found an element, your .addEventListener tries to attach to a null value, so the error is thrown.
Looking at your code, there are three areas that do not have an html element (from what is included in the question)
There is not a filter-clear, add-row, or del-row element in your HTML. Based on you seeing the error above the document.getElementById('filter-clear').addEventListener(), it looks like your filter-clear element does not exist.
Here is an example that catches the error and appends the error to the body.
https://jsfiddle.net/nrayburn/58he2jr6/6/

Show the item hit content only when the search box is not empty

I have this in my algolia file for my jekyll site.
<script>
const search = instantsearch({
appId: '{{ site.algolia.application_id }}',
apiKey: '{{ site.algolia.search_only_api_key }}',
indexName: '{{ site.algolia.index_name }}',
searchParameters: {
restrictSearchableAttributes: [
'title',
'content'
],
facetFilters: ['type:post']
},
});
const hitTemplate = function(hit) {
let date = '';
if (hit.date) {
date = moment.unix(hit.date).format('L');
// date = moment.unix(hit.date).format('MMM Do YY');
modifiedDate = moment.unix(hit.last_modified_at).format('MMM Do YY');
}
const url = hit.url;
const title = hit._highlightResult.title.value;
const content = hit._highlightResult.html.value;
return `
<div class="post-list">
<span class="post-date-list-wrap">
<span class="post-date-list">${date}
<span class="post-title"> ${title} </span>
</span>
${content}
</div>
`;
}
const hitTemplateTrans = function(hit) {
let date = '';
if (hit.date) {
date = moment.unix(hit.date).format('MMM DD YYYY');
}
const url = hit.url;
const title = hit._highlightResult.title.value;
const content = hit._highlightResult.html.value;
return `
<div class="post-list">
<span class="post-date-list-wrap">
<span class="post-date-list">${date}
<span class="post-title"> ${title}</span>
</span>
</span>
</div>
`;
}
search.addWidget(
instantsearch.widgets.searchBox({
container: '#search-searchbar',
placeholder: 'search notes',
autofocus: true
})
);
search.addWidget(
instantsearch.widgets.hits({
container: '#search-hits',
templates: {
empty: 'No results',
item: hitTemplate
},
})
);
search.start();
</script>
Without typing anything in the search box I have the list of articles
with the excerpt, the short introduction of the article.
That's because I have ${content} to show the highlights when someone
types the search term.
That's fine and everything is working but... I don't want to show the contents of each item when the search box is empty.
If the search box is empty I would like to keep only the title and the date
but if the search box is not empty just show the title/date and the excerpt as it's usual.
It seems like an easy task but I'm stuck right now, I tried removed the content tag and put in the hit transformation function, but it doesn't work.
The instantsearch.js library has a function hook, called searchFunction, you can define when instanciating the library. That is called right before any search is performed. You can use it to check if the current query is empty or not, and adapt your layout based on this knowledge.
Here is a slightly edited script (irrelevant parts removed) that should let you do what you're looking for:
let additionalClass = '';
const search = instantsearch({
[…]
searchFunction: function(helper) {
if (helper.getState().query === '') {
additionalClass = 'post-item__empty-query';
} else {
additionalClass = '';
}
helper.search()
}
});
[…]
const hitTemplate = function(hit) {
return
`<div class="post-item ${additionalClass}">
[…]
</div>`
;
}
.post-item__empty-query .post-snippet {
display: none;
}
What it does is defining a global variable (additionalClass) that will be used in the hit template (added alongside item-post, at the root level).
Right before everysearch, we check if the query is empty. If so, we set additionalClass to item-post__empty_query. We also defined in CSS that when this class is applied, the content should be hidden.
All of that together makes the title + date displayed when no search is performed, and the content displayed only when an actual keyword is searched for.
Hope that helps,

Localize Unix Time Value Using Moment.js + jQuery Datatables w/ pagination

I'm using moment.js and jquery datatables. Specifically, I have a a list of cells that all contain a Unix Timestamp.
What I'd like to do is convert this timestamp to the user's localized time (based on his/her timezone).
I am able to get the timezone to localize, but it only works for the first group of paginated results in my table...if I navigate to another page, the timestamp still shows up as the raw unix value.
I've made a JS fiddle to illustrate.
Could someone kindly let me know 1) if there's a better way to do what I'm doing 2) how I can localize my times even after actions like a) searching the table 2) sorting the table 3) paginating the table?
Huge thanks in advance!
My JS:
// Do Datatables
$('.my-datatable').DataTable({
"order": [[ 1, 'desc' ],],
"aoColumnDefs": [
{ "bSortable": false, "aTargets": [ 0 ] }
]
});
// Loop through class to localize unix time based on user's time zone
function localizeTime(){
$( ".localize_time" ).each(function() {
if (typeof moment !== 'undefined' && $.isFunction(moment)) {
var userMomentTz = moment().format("Z");
var userTimeZone = userMomentTz.replace(":", "");
var elementSiteUnixTimeText = $(this).find('.localize_time_unix').text();
var elementSiteUnixTimeVal = parseInt(elementSiteUnixTimeText.trim());
if (userTimeZone.substring(0, 1) == "-") {
var userTimeZoneHr = parseInt(userTimeZone.substring(1,3));
var userTimeZoneMin = parseInt(userTimeZone.slice(-2));
var userTimeOffset = (userTimeZoneHr + '.' + (userTimeZoneMin/60))*(-1);
} else {
var userTimeZoneHr = parseInt(userTimeZone.substring(0,2));
var userTimeZoneMin = parseInt(userTimeZone.slice(-2));
var userTimeOffset = userTimeZoneHr + '.' + (userTimeZoneMin/60);
}
var momentDateUserOffset = moment.unix(elementSiteUnixTimeVal).utcOffset(userTimeOffset);
var momentDateFormattedOffset = moment(momentDateUserOffset).format('ddd, D MMM YYYY, h:mm A');
$(this).find('.localize_time_display').text(momentDateFormattedOffset);
};
});
};
// Run time localization function
if ( $( ".localize_time" ).length ) {
localizeTime()
};
My HTML
<table class="my-datatable">
<thead>
<tr>
<th>Time</th>
<th>Stuff</th>
</tr>
</thead>
<tbody>
<tr>
<td>Stuff</td>
<td>
<span class="localize_time">
<span class="localize_time_unix">UNIX Time n++</span>
<span class="localize_time_display"></span>
</span>
</td>
</tr>
</tbody>
</table>
Ok, well fortunately this was easier than I thought using 'data rendering'
Working JS Fiddle
Hope this helps someone!
My updated JS
// Do Datatables
$('.my-datatable').DataTable( {
"order": [[ 1, 'desc' ],],
"columnDefs": [{
"targets": 1,
"render": function (data, type, full, meta) {
if (typeof moment !== 'undefined' && $.isFunction(moment)) {
var userMomentTz = moment().format("Z");
var userTimeZone = userMomentTz.replace(":", "");
var elementSiteUnixTimeText = data;
var elementSiteUnixTimeVal = parseInt(elementSiteUnixTimeText.trim());
if (userTimeZone.substring(0, 1) == "-") {
var userTimeZoneHr = parseInt(userTimeZone.substring(1,3));
var userTimeZoneMin = parseInt(userTimeZone.slice(-2));
var userTimeOffset = (userTimeZoneHr + '.' + (userTimeZoneMin/60))*(-1);
} else {
var userTimeZoneHr = parseInt(userTimeZone.substring(0,2));
var userTimeZoneMin = parseInt(userTimeZone.slice(-2));
var userTimeOffset = userTimeZoneHr + '.' + (userTimeZoneMin/60);
}
var momentDateUserOffset = moment.unix(elementSiteUnixTimeVal).utcOffset(userTimeOffset);
var momentDateFormattedOffset = moment(momentDateUserOffset).format('ddd, D MMM YYYY, h:mm A');
$(this).find('.localize_time_display').text(momentDateFormattedOffset);
return momentDateFormattedOffset;
};
}
}]
} );

Angularjs jQuery FIle Upload

I'm new at Angularjs and I'm trying to create an AngularJS project with jQuery File Upload but I could not distinguish between directives file controllers file and the view.
Can anyone help me by providing me a clear structure of how files should be placed? (controllers, directives, and view)
I wrote something for my very first Angular.js project. It's from before there was an Angular.js example, but if you want to see the hard way, you can have it. It's not the best, but it may be a good place for you to start. This is my directives.js file.
(function(angular){
'use strict';
var directives = angular.module('appName.directives', []);
directives.directive('imageUploader', [
function imageUploader() {
return {
restrict: 'A',
link : function(scope, elem, attr, ctrl) {
var $imgDiv = $('.uploaded-image')
, $elem
, $status = elem.next('.progress')
, $progressBar = $status.find('.bar')
, config = {
dataType : 'json',
start : function(e) {
$elem = $(e.target);
$elem.hide();
$status.removeClass('hide');
$progressBar.text('Uploading...');
},
done : function(e, data) {
var url = data.result.url;
$('<img />').attr('src', url).appendTo($imgDiv.removeClass('hide'));
scope.$apply(function() {
scope.pick.photo = url;
})
console.log(scope);
console.log($status);
$status.removeClass('progress-striped progress-warning active').addClass('progress-success');
$progressBar.text('Done');
},
progress : function(e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
$progressBar.css('width', progress + '%');
if (progress === 100) {
$status.addClass('progress-warning');
$progressBar.text('Processing...');
}
},
error : function(resp, er, msg) {
$elem.show();
$status.removeClass('active progress-warning progress-striped').addClass('progress-danger');
$progressBar.css('width', '100%');
if (resp.status === 415) {
$progressBar.text(msg);
} else {
$progressBar.text('There was an error. Please try again.');
}
}
};
elem.fileupload(config);
}
}
}
]);
})(window.angular)
I didn't do anything special for the controller. The only part of the view that matters is this:
<div class="control-group" data-ng-class="{ 'error' : errors.image }">
<label class="control-label">Upload Picture</label>
<div class="controls">
<input type="file" name="files[]" data-url="/uploader" image-uploader>
<div class="progress progress-striped active hide">
<div class="bar"></div>
</div>
<div class="uploaded-image hide"></div>
</div>
</div>

Adding fields dynamically in JQuery-Jtable

How can I add fields dynamically in Jtable. I want to have multiple values for Cities
Please Refer the image attached
Thanks
Yes this is not built-in with jQuery jTable. To deal with this I've created a script for the same purpose. This handles (a) adding more controls OR group of controls and (b) remove control(s).
Here is the script:
//add '.add_more' class to
$(".add_more").on('click', function () {
// creates unique id for each element
var _uniqueid_ = +Math.floor(Math.random() * 1000000);
var new_ele_id = $(this).attr("data-clone-target") + _uniqueid_;
var cloneObj = $("#" + $(this).attr("data-clone-target"))
.clone()
.val('')
.attr("id", new_ele_id);
// if the control is grouped control
if ($(this).hasClass('group_control') == true) {
$($(cloneObj).children()).each(function () {
$(this).attr("id", $(this).attr("id") + _uniqueid_).val("");
});
}
$(cloneObj).insertBefore($(this));
//creates a 'remove' link for each created element or grouped element
$("<a href='javascript:void(0);' class='remove_this' data-target-id='" + new_ele_id + "'></a>")
.on('click', function () {
if ($(this).is(":visible") == true) {
if (confirm("Are you sure?")) {
$("#" + $(this).attr("data-target-id")).remove();
$(this).remove();
}
}
else {
$("#" + $(this).attr("data-target-id")).remove();
$(this).remove();
}
}).insertBefore($(this));
$("#" + new_ele_id).focus();
});
//remove element script
$(".remove_this").on('click', function () {
if ($(this).is(":visible") == true) {
if (confirm("Are you sure?")) {
$("#" + $(this).attr("data-target-id")).remove();
$(this).remove();
}
}
else {
$("#" + $(this).attr("data-target-id")).remove();
$(this).remove();
}
});
Usage: Single Element http://jsfiddle.net/vkscorpion1986/ktbn4qLg/2/
<input class="" id="<ELEMENT-ID>" type="text" name="input1">
Add More
Usage: Grouped Elements http://jsfiddle.net/vkscorpion1986/ktbn4qLg/4/
<div id="<ELEMENT-ID>">
<input class="" id="input1" type="text" name="input1">
<input class="" id="input2" type="text" name="input2">
</div>
Add More
attributes
href = javascript:void(0); // just to disable the anchor tag default behaviour
data-clone-target = id of the target element
css classes
.add_more = to implement the add more/remove controls functionality
.group_control = for indicating that this is group of elements which have to be repeted
Hope this works for you.
No, it's not made with jTable. You can use input option (http://jtable.org/ApiReference#fopt-input) and this: http://jqueryui.com/autocomplete/#multiple Or you can create your own dialog.

Resources