Algolia - Search with a condition to look into an array of string - ruby-on-rails-4

I am using rails and algolia gem with mongoid datastore.
I am sending data to algolia for a model Question. One of the doc example in Algolia system is
objectID: 5691e056410213a381000000
text: "what is #cool about your name Mr. John? #name #cool"
asked_to: ["565571704102139759000000", "i7683yiq7r8998778346q686", "kjgusa67g87y8e7qtwe87qwe898989"]
asked_by: "564a9b804102132465000000"
created_at: "2016-01-10T04:38:46.201Z"
card_url: "http://localhost:3000/cards/5691e056410213a381000000"
answerers: []
has_answer: false
requestor_count: 0
status: "active"
popularity_point: 0
created_at_i: 1452400726
_tags: ["cool", "name"]
I want to find all those documents, where it meets these two conditions:
1) text contains your name
2) asked_to contains i7683yiq7r8998778346q686
I am using Twitter's typeahead javascript library. And my UI's javascript to implement algolia search is as follows:
<input class="typeahead ui-widget form-control input-md search-box tt-input" id="typeahead-algolia" placeholder="Search questions" spellcheck="false" type="text" autocomplete="off" dir="auto" style="position: relative; vertical-align: top;">
$(document).on('ready page:load', function () {
var client = algoliasearch("APPLICATION_ID", "SEARCH_KEY");
var index = client.initIndex('Question');
$('#typeahead-algolia').typeahead(
{
hint: false,
highlight: true,
minLength: 1
},
{
source: index.ttAdapter({hitsPerPage: 10}),
displayKey: 'text'
}
).on('keyup', this, function (event) {
if (event.keyCode == 13) {
$('#typeahead-algolia').typeahead('close');
window.location.href = "/?keyword="+encodeURIComponent($('#typeahead-algolia').val());
}
});
$('.typeahead').bind('typeahead:select', function(ev, suggestion) {
window.location.href = suggestion.card_url;
});
});
So my question is:
This code works perfectly. But how to add condition for asked_to contains i7683yiq7r8998778346q686 in above javascript to filter out result.

You can use a facet filter on the asked_to attribute in your query.
You first need to declare the attribute asked_to as an attribute for faceting in your index settings and then pass asked_to:i7683yiq7r8998778346q686 as a facet filter in your query via the facetFiltersquery parameter.
When your index settings are changed, you can change your source to add the facetFilters parameter:
$('#typeahead-algolia').typeahead(
{
hint: false,
highlight: true,
minLength: 1
},
{
source: index.ttAdapter({hitsPerPage: 10, facetFilters: "asked_to:i7683yiq7r8998778346q686"}),
displayKey: 'text'
}
).on('keyup', this, function (event) {
if (event.keyCode == 13) {
$('#typeahead-algolia').typeahead('close');
window.location.href = "/?keyword="+encodeURIComponent($('#typeahead-algolia').val());
}
});

Related

Ember.js update model attribute

I do not have the best understanding of how ember.js works. I am currently working on updating an attribute called features (an array of strings) that each owner has using a multi select checkbox. So far everything seems to be working except for the updated features attribute is not saving. When I click the checkbox it updates the selected computed property in the multi-select-checkboxes component. I thought if I was passing model.owner.features as selected to the component it would directly update the model when the component changes.
(function() {
const { shroud } = Sw.Lib.Decorators;
Sw.FranchisorNewAnalyticsConnectionsUsersRoute = Ember.Route.extend({
currentFranchisorService: Ember.inject.service('currentFranchisor'),
#shroud
model({ account_id }) {
console.log("load model in route")
return Ember.RSVP.hash({
account: this.get('store').find('account', account_id),
owners: Sw.AccountOwner.fetch(account_id),
newAccountOwner: Sw.AccountOwner.NewAccountOwner.create({ accountID: account_id }),
});
},
actions: {
#shroud
accountOwnersChanged() {
this.refresh();
},
close() {
this.transitionTo('franchisor.newAnalytics.connections');
},
},
});
})();
users controller:
(function() {
const { shroud, on, observer } = Sw.Lib.Decorators;
Sw.FranchisorNewAnalyticsConnectionsUsersController = Ember.Controller.extend(Sw.FranchisorControllerMixin, {
isAddingUser: false,
adminOptions: [{
label: 'Employee Advocacy',
value: 'employee advocacy'
}, {
label: 'Second Feature',
value: 'other'
}, {
label: 'Can edit users?',
value: 'edit_users'
}],
});
})();
users.handlebars
{{#each model.owners as |owner|}}
<tr
<td>
{{owner.name}}
</td>
<td>
{{owner.email}}
</td>
<td>{{if owner.confirmedAt 'Yes' 'No'}}</td>
<td>
{{log 'owner.features' owner.features}}
{{multi-select-checkboxes
options=adminOptions
selected=owner.features
owner=owner
model=model
}}
</td>
<td>
<button class="btn light-red-button"
{{action "remove" owner}}>
Remove
</button>
</td>
</tr>
{{/each}}
multi-select-checkboxes.handlebar:
{{#each checkboxes as |checkbox|}}
<p>
<label>
{{input type='checkbox' checked=checkbox.isChecked}}
{{checkbox.label}}
</label>
</p>
{{/each}}
multi_select_checkboxes.jsx:
// Each available option becomes an instance of a "MultiSelectCheckbox" object.
var MultiSelectCheckbox = Ember.Object.extend({
label: 'label',
value: 'value',
isChecked: false,
changeValue: function () { },
onIsCheckedChanged: Ember.observer('isChecked', function () {
var fn = (this.get('isChecked') === true) ? 'pushObject' : 'removeObject';
this.get('changeValue').call(this, fn, this.get('value'));
})
});
Sw.MultiSelectCheckboxesComponent = Ember.Component.extend({
labelProperty: 'label',
valueProperty: 'value',
// The list of available options.
options: [],
// The collection of selected options. This should be a property on
// a model. It should be a simple array of strings.
selected: [],
owner: null,
model: null,
checkboxes: Ember.computed('options', function () {
console.log("CHECKBOXES", this.get('model'))
var _this = this;
var labelProperty = this.get('labelProperty');
var valueProperty = this.get('valueProperty');
var selected = this.get('selected');
var model = this.get('model');
return this.get('options').map(function (opt) {
var label = opt[labelProperty];
var value = opt[valueProperty];
var isChecked = selected.contains(value);
return MultiSelectCheckbox.create({
label: label,
value: value,
isChecked: isChecked,
model: model,
changeValue: function (fn, value, model) {
_this.get('selected')[fn](value);
console.log("model in middle", this.get('model'))
this.get('model').save();
}
});
});
}),
actions: {
amountChanged() {
const model = this.get(this, 'model');
this.sendAction('amountChanged', model);
}
}
});
Seems like you have a pretty decent understanding to me! I think your implementation is just missing a thing here or there and might be a tad more complex than it has to be.
Here's a Twiddle that does what you're asking for. I named the property on the model attrs to avoid possible confusion as attributes comes into play with some model methods such as rollbackAttributes() or changedAttributes().
Some things to note:
You don't need to specify a changeValue function when creating your list of checkbox objects. The onIsCheckedChanged observer should be responsible for updating the model's attribute. Just pass the model or its attribute you want to update (the array of strings) into each checkbox during the mapping of the options in the multi select checkbox:
return Checkbox.create({
label: option.label,
value: option.value,
attributes: this.get('owner.attrs') // this array will be updated by the Checkbox
});
If the model you retrieve doesn't have any data in this array, Ember Data will leave the attribute as undefined so doing an update directly on the array will cause an error (e.g., cannot read property 'pushObject' of undefined) so be sure the property is set to an array before this component lets a user update the value.
The observer will fire synchronously so it might be worthwhile to wrap it in a Ember.run.once() (not sure what else you will be doing with this component / model so I note this for completeness).
If you want to save the changes on the model automatically you'll need to call .save() on the model after making the update. In this case I would pass the model in to each checkbox and call save after making the change.

Kendo Multi-select in cascading scenario unable to populate initial values

I'm using Telerik for MVC and trying to get the multi-select to populate with the initial values in an Edit scenario.
<script>
function filterProducts() {
return {
manufacturerId: $("#ServiceBulletinItem_ManufacturerId").val()
};
}
function onManufacturerChange(e) {
var v = e.sender.dataItem().Value;
$.post("#Url.Action("GetCascadeProducts", "Components")", { manufacturerId: v }, function (result) {
var grid = $("#ServiceBulletinItem_ApplicableProducts").data("kendoMultiSelect")
grid.setDataSource(result)
});
}
function InitialPopulate(manId) {
$.post("#Url.Action("GetCascadeProducts", "Components")", { manufacturerId: manId }, function (result) {
var grid = $("#ServiceBulletinItem_ApplicableProducts").data("kendoMultiSelect")
grid.setDataSource(result)
});
}
$(document).ready(function () {
$('.control-datepicker').Zebra_DatePicker();
var m = $("#ServiceBulletinItem_ManufacturerId").val();
InitialPopulate(m);
});
</script>
<div class="form-group">
#Html.LabelFor(m => m.ManufacturerList, "Manufacturer", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#(Html.Kendo().DropDownListFor(m => m.ServiceBulletinItem.ManufacturerId)
.HtmlAttributes(new { #class = "col-md-6 form-control" })
.Filter("contains")
.DataValueField("Value")
.DataTextField("Text")
.BindTo((IEnumerable<SelectListItem>)Model.ManufacturerSelectList)
.HtmlAttributes(new { style = "width:70%;" }).Events(e =>
{
e.Change("onManufacturerChange");
})
)
</div >
</div >
<div class="form-group">
#Html.LabelFor(m => m.ProductList, "Product", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#(Html.Kendo().MultiSelectFor(m => m.ServiceBulletinItem.ApplicableProducts)
.AutoClose(false)
.DataTextField("ProductName")
.DataValueField("ProductId")
.Placeholder("Select products...")
)
</div>
</div>
I'm trying to populate the manufacturer drop down and the Product multiSelect. The ApplicableProducts item is an IEnumerable representing the ProductId's of all those previously selected and I know that when I select the manufacturer and it calls the GetCascadeProducts controller method it will return back a collection of ProductId and ProductName for all the manufacturers products of which those productId is the ApplicableProducts property should exist.
On document.ready I can call the InitialPopulate method with the manufacturerID which will populate the multiSelect items but can't seem to populate the initial values.
I couldnt get the binding working correctly so ended up using
#(Html.Kendo().MultiSelect()
.Name("ServiceBulletinItem_ApplicableProducts")
.AutoClose(false)
.DataTextField("ProductName")
.DataValueField("ProductId")
.Placeholder("Select products 2...")
.AutoBind(false)
)
and then on the using the following code on document ready to make an ajax call to populate the manufacturer and product controls
function PopulateProductsInitial(manId) {
$.post("#Url.Action("GetCascadeProducts", "Components")", { manufacturerId: manId }, function (result) {
var grid = $("#ServiceBulletinItem_ApplicableProducts").data("kendoMultiSelect")
grid.setDataSource(result);
var s = $("#ServiceBulletinItem_Id").val();
$.post("#Url.Action("GetSBProducts", "ServiceBulletins")", { Id: s}, function (result) {
var arr = [];
result.forEach(function (element) {
arr.push(element.ProductId);
});
var grid = $("#ServiceBulletinItem_ApplicableProducts").data("kendoMultiSelect")
grid.value(arr);
});
});
}
}
$(document).ready(function () {
//Populate Initial Values
PopulateProductsInitial($("#ServiceBulletinItem_ManufacturerId").val());
$('#YourButton').click(SendForm);
});
The problem then became sending the selected items back to the controller when the edit was complete which again seemed convoluted because the control was not bound and therefore I had to make an Ajax call to submit the data.
function SendForm() {
var items = $("#ServiceBulletinItem_ApplicableProducts").data("kendoMultiSelect").value();
//Manipulate into ServiceBulletinViewModel for the save
var data = {
Id: $("#ServiceBulletinItem_Id").val(),
ServiceBulletinItem: {
Id: $("#ServiceBulletinItem_Id").val(),
ManufacturerId: $("#ServiceBulletinItem_ManufacturerId").val(),
IssueDate: $('#ServiceBulletinItem_IssueDate').val(),
Heading: $('#ServiceBulletinItem_Heading').val(),
Details: $('#ServiceBulletinItem_Details').val(),
Url: $('#ServiceBulletinItem_Url').val(),
SelectedProducts: items
}
}
$.ajax({
type: 'POST',
url: '/ServiceBulletins/Edit',
contentType: 'application/json',
data: JSON.stringify(data),
success: function (result) {
//Your success code here..
if (result.redirectUrl != null) {
window.location = result.redirectUrl;
}
},
error: function (jqXHR) {
if (jqXHR.status === 200) {
alert("Value Not found");
}
}
});
}
It all seemed a lot more convoluted than any of the demo's that teleriks and I couldnt find any good examples of binding from remote sources which looked similar.
As the binding is convention based I'm wondering if its possible to simplify the ajax calling for the post functionality with the correct naming of the controls so that I can simply get the selected items on the multiselect control or if the ajax post is the way to go.

Bloodhound - typeahead not showing suggestions that start with the typed search string

I've been troubleshooting this for couple hours to no avail.
Basically, in the following code, I get the right results from the remote suggestions provider,
var descuentos = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.nonword,
queryTokenizer: Bloodhound.tokenizers.nonword,
remote: {
url: 'http://localhost:30045/Home/Suggest?q=%QUERY&type=name&fuzzy=false',
wildcard: "%QUERY",
filter: function (items) {
return $.map(items, function (item) {
return {
value: item.NombreComercio
};
});
}
}
});
$('#bloodhound .typeahead').typeahead({
hint: true,
highlight: true,
minLength: 0
},
{
name: 'descuentos',
source: descuentos
});
<body>
<div id="bloodhound">
<input class="typeahead" type="text" placeholder="Comercio Adherido...">
</div>
</body>
But, when the suggestion STARTS WITH the search string, is not displayed... Ideas?
Thanks!
Add displayKey to typeahead datasets parameter.
$('#bloodhound .typeahead').typeahead({
hint: true,
highlight: true,
minLength: 0
},{
name: 'descuentos',
displayKey: 'value',
source: descuentos
});
Hope this helps.

twitter typeahead.js autocomplete remote not working

I have a site with stocks. I would like to add typeahead functionality to my bootstrap template. Since there are about 5000 stocks and will be even more in the future. I am using haystack with whoosh index. I should be using remote version of typeahead.js, but it is not working. Could you please take a look and tell me what am I missing?
<script type="text/javascript">
var stocks = new Bloodhound({
datumTokenizer: function (datum) {
return Bloodhound.tokenizers.whitespace(datum.name);
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
limit: 5,
remote: {
url: "/search/autocomplete/",
replace: function(url, query) {
return url + "?q=" + query;
},
filter: function(stocks) {
return $.map(stocks, function(data) {
return {
tokens: data.tokens,
symbol: data.symbol,
name: data.name
}
});
}
}
});
stocks.initialize();
$('.typeahead').typeahead(null, {
name: 'stocks',
displayKey: 'name',
minLength: 1, // send AJAX request only after user type in at least X characters
source: stocks.ttAdapter()
});
</script>
This is my form
<form class="input-prepend" method="get" action="/search/">
<div id="remote">
<button type="submit" class="btn">Search</button>
<input type="text" class="typeahead" id="id_q" placeholder="Stock symbol or name" autocomplete="off" name="q">
</div>
</form>
Urls.py
url(r'^search/autocomplete/', 'stocks.views.autocomplete'),
url(r'^search/', include('haystack.urls')),
autocomplete view
from haystack.query import SearchQuerySet
import json
def autocomplete(request):
sqs = SearchQuerySet().autocomplete(content_auto=request.GET.get('q', ''))[:5]
array = []
for result in sqs:
data = {"symbol": str(result.symbol),
"name": str(result.name),
"tokens": str(result.name).split()}
array.insert(0, data)
return HttpResponse(json.dumps(array), content_type='application/json')
json response:
[{"tokens": ["Arbor", "Realty", "Trus"], "symbol": "ABR", "name": "Arbor Realty Trus"}, {"tokens": ["ABM", "Industries", "In"], "symbol": "ABM", "name": "ABM Industries In"}, {"tokens": ["AmerisourceBergen"], "symbol": "ABC", "name": "AmerisourceBergen"}, {"tokens": ["ABB", "Ltd", "Common", "St"], "symbol": "ABB", "name": "ABB Ltd Common St"}, {"tokens": ["Allianceberstein"], "symbol": "AB", "name": "Allianceberstein "}]
This is my domain name: digrin.com and this is autocomplete url.
What am I missing?
I can see two problems:
1) Your script declaration is missing a type attribute:
<script src="http://code.jquery.com/jquery-1.11.0.js"></script>
<script src="http://netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.js"></script>
<script type='text/javascript' src="http://twitter.github.io/typeahead.js/releases/latest/typeahead.bundle.js"></script>
add "type='text/javascript'" to the script declarations for jquery and bootstrap.
A more modern way of declaring your script tags can be found here.
2) To initialise Typeahead you need to place the code into your jQuery ready method i.e.
$(function(){
var stocks = new Bloodhound({
datumTokenizer: function (datum) {
return Bloodhound.tokenizers.whitespace(datum.name);
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
limit: 5,
remote: {
url: "/search/autocomplete/",
replace: function(url, query) {
return url + "?q=" + query;
},
filter: function(stocks) {
return $.map(stocks, function(data) {
return {
tokens: data.tokens,
symbol: data.symbol,
name: data.name
}
});
}
}
});
stocks.initialize();
$('.typeahead').typeahead(null, {
name: 'stocks',
displayKey: 'name',
minLength: 1, // send AJAX request only after user type in at least X characters
source: stocks.ttAdapter()
});
});
As it is currently the typeahead code wont get loaded.

handlebars, checked checkbox if the permission is true by an object array

I am working with backbone and handlebars for a month and I have this problem.
I have this object that I get from my model:
Object {list: Object, permissions: Object}
list: Object
0: Object
id: "1"
name: "cms"
__proto__: Object
1: Object
2: Object
3: Object
permissions: Object
analytics: true
categories: false
cms: true
coupons: false
Now with handlebars I'm trying to find out how I can checked the box when the relative permission value is true like this:
{{#each list}}
<tr>
<th><input type="checkbox" id="{{id}}"
{{#each permission}}
{{#ifCond this {{name}} }}//that's where my current error is when I try to precompile this template,the syntax is wrong
checked
{{/ifCond}}
{{/each}}
/>{{name}}
</th>
</tr>
{{/each}}
ifCond is a function that I builded in handlebars.js:
Handlebars.registerHelper('ifCond', function(v1, v2, options) {
if(v1 === v2) {
return options.fn(this);
}
return options.inverse(this);
});
Hope someone can help me out!!
Thank you!
This is how you would have written your template if there was no helper in the first place.
Start from this point
Without Helper
In the template you were trying to iterate over objects, which is not the right approach.
You should leave that to the template helper to take care of that passing the correct context.
---- This is again wrong ( something in these lines)
| compare="name"
{{#ifCond this {{name}} }}
^
^ ----- Context
|
Name of helper
under which it is
registered
When you pass a value to compare=name the value will be replaces by that key value of that object.
But remember that you are already inside each loop that iterates over list object.
So this will point to that object. So you need to go back to the parent object 1 step using ../ which will give you access to the permissions object.
{{#ifCond this compare=name }}
Will map to the arguments of the helper.
'ifCond', function(v1, v2, options) {
v1 will be this context
v2 will map to the handlebar options
options will be undefined as no 3rd argument was passed in the helper
So the template will be of this form
{{#ifCond this obj=../this compare=this.name}}
this - context of each (current object in loop)
obj= will be dumped into the options.hash object. It is the main object.
compare will be the name attribute inside this context
maps to
'ifCond', function (context, options)
So your template structure will look like below
Template
<script type="text/template" id="handlebar-template">
{{#each list}}
<tr>
<th>
<span>
<input type="checkbox" id="{{id}}"
{{#ifCond this obj=../this compare=this.name}}
checked
{{/ifCond}} />
</span>
<span class="name">{{name}}</span>
</th>
</tr>
{{/each}}
</script>
Helper
Handlebars.registerHelper('ifCond', function (context, options) {
var permissions = options.hash.obj.permissions,
name = options.hash.compare;
if (permissions[name]){
return options.fn(this);
}
return options.inverse(this);
});
Full Code
// The object in question
var obj = {
"list": [{
"id": 1,
"name": "cms"
}, {
"id": 2,
"name": "analytics"
}, {
"id": 3,
"name": "coupons"
}, {
"id": 4,
"name": "categories"
}],
"permissions": {
"analytics": true,
"categories": false,
"cms": true,
"coupons": false
}
};
// Model for the Object
var HandlebarModel = Backbone.Model.extend({});
var handlebarModel = new HandlebarModel(obj);
// View that will render the template inside the table
var HandlebarView = Backbone.View.extend({
el: $('#table'),
initialize: function () {
var source = $('#handlebar-template').html();
this.template = Handlebars.compile(source);
},
render: function () {
var $elem = $('tbody', this.$el);
$elem.empty();
$elem.append(this.template(this.model.toJSON()));
}
});
Handlebars.registerHelper('ifCond', function (context, options) {
console.log("this context " + this);
console.log("name -" + options.hash.compare);
console.log("Main Object - " + options.hash.obj);
var permissions = options.hash.obj.permissions,
name = options.hash.compare;
if (permissions[name]){
return options.fn(this);
}
return options.inverse(this);
});
var handlebarView = new HandlebarView({
model: handlebarModel
});
handlebarView.render();
Check Fiddle