Algolia Typeahead retrieve results - ruby-on-rails

I am trying to create a discussion tool in my Rails app. I'd like to add users to a discussion like in Facebook messenger.
To do so, I implemented Algolia instant search sith Typeahead. It works fine to show th results but when I click on a result it does nothing at all. It looks like if the event I am using was not working.
I cannot use my keyboard to go up or down the results, it's very strange. Could you help me? I tried all the events on the Typeahead documentation but no one is working.
Thank you so much.
<div class="uk-grid message">
<div class="uk-width-medium-2-6"></div>
<div class="uk-width-medium-3-6 bubble">
<%= form_tag create_discussion_path, method: :post, remote: true, class: 'uk-form', id: 'createDiscussionForm' do %>
<div class="form-group">
<div class="uk-grid">
<div class="uk-width-1-6">
<span><%= t('to') %></span>
</div>
<div class="uk-width-5-6">
<%= text_field_tag 'recipients', '', class: 'recipients typeahead', id: 'typeahead-algolia-template', spellcheck: false %>
</div>
</div>
<%= text_area_tag 'body', nil, cols: 3, class: 'form-control uk-width-1-1 body', placeholder: 'Ask something or just say hi!', required: true %>
</div>
<%= submit_tag t('dashboard.discussions.send_message'), class: 'uk-button uk-width-1-1' %>
<% end %>
<div class="arrow-right"></div>
</div>
<div class="uk-width-medium-1-6 text-center">
<%= link_to user_path(current_user) do %>
<%= image_tag(current_user.pictures.first.image(:square_thumb)) %>
<% end %>
<p><%= current_user.first_name %></p>
</div>
</div>
<!-- Algolia search callback -->
<script type="text/javascript">
function searchCallback(success, content) {
if (content.query != $("#typeahead-algolia-template").val()) {
// do not take out-dated answers into account
return;
}
if (content.hits.length == 0) {
// no results
$('#hits').empty();
return;
}
} // end search callback
</script>
<!-- end Algolia search callback -->
<!-- Algolia with typehead and hogan -->
<script type="text/javascript">
$(document).ready(function() {
// Algolia initialization
var $inputfield = $("#typeahead-algolia-template");
var algolia = algoliasearch('<%= ENV['ALGOLIA_ID'] %>', '<%= ENV['ALGOLIA_PUBLIC_KEY'] %>');
var index = algolia.initIndex('<%= 'User_' + Rails.env %>');
var ttAdapterParams = {
hitsPerPage: 5,
facets: '*',
tagFilters: ''
};
// Templating
var templates = {
algolia: {
hit: Hogan.compile(
'<div class="uk-grid">' +
'<div class="uk-width-1-4">' +
'<img src="{{{_highlightResult.picture.value}}}">' +
'</div>' +
'<div class="uk-width-3-4">' +
'<span class="name">' +
'{{{_highlightResult.first_name.value}}}, ' +
'</span>' +
'<span class="occupation">' +
'{{{_highlightResult.occupation.value}}}' +
'</span>' +
'</div>' +
'</div>'
)
}
};
// Search?
$inputfield.typeahead({
highlight: true,
hint: true,
minLength: 1
},
{
source: index.ttAdapter(ttAdapterParams),
displayKey: 'name',
templates: {
suggestion: function(hit) {
var tpl = templates.algolia.hit.render(hit);
//console.log(tpl, 'hit');
return tpl;
}
}
}).on('typeahead:open', function(event, data) {
// var $form = $(this).closest('form');
// $form.data('sendToServer', true);
// $form.submit();
});
var val = $inputfield.typeahead('val');
console.log(val);
$("#typeahead-algolia-template").bind('typeahead:open', function(ev, suggestion) {
console.log('Selection: ' + suggestion);
});
});
</script>

Thanks to the great Algolia support team I managed to make it work!
As suggested, I switched to https://github.com/algolia/autocomplete.js and it's just perfect. I changed a few things, basically replacing .typeahead by .autocomplete and the event listener name.
Here is the snippet:
$inputfield.autocomplete({
highlight: true,
autoselect: true,
openOnFocus: true
}, [
{
source: $.fn.autocomplete.sources.hits(index, ttAdapterParams),
displayKey: 'first_name',
templates: {
suggestion: function(hit) {
var tpl = templates.algolia.hit.render(hit);
//console.log(tpl, 'hit');
return tpl;
}
}
}]).on('autocomplete:selected', function(event, suggestion, dataset) {
console.log(suggestion);
});

Related

Rails 5: Combine a normal form with vue2-dropzone

I'm trying to combine a normal form with vue2-dropzone in a Rails 5 app I'm working on.
I think I've almost gotten it to work but the form data isn't being sent across with the Dropzone images.
My _form.html.erb looks a bit like this:
<%= form_for(listing, html: { multipart: true, id: 'listing_form', data: { listing: listing.to_json } }) do |f| %>
<div class="row">
<div class="col">
<div class="form-group">
<strong><%= f.label :address %></strong><br>
<%= f.text_field :address, class: 'form-control', 'v-model': 'address' %>
</div>
</div>
</div>
<!-- Other input fields here -->
<div class="row">
<div class="col">
<h2>Images</h3>
<vue-dropzone
ref="listingDropzone"
id="dropzone"
#vdropzone-success-multiple="listingRedirect"
:options="dropzoneOptions">
</vue-dropzone>
</div>
</div>
<%= f.submit class: 'btn btn-success', 'v-on:click.stop.prevent': 'submitListing' %>
<% end %>
I am submitting the data with the submitListing which I define in a Vue method. That, in turn, calls Dropzone's processQueue which handles the submission. listingRedirect Is then called to perform redirection on success.
if(document.getElementById('listing-multistep') != null) {
Vue.http.headers.common['X-CSRF-Token'] = document.querySelector('input[name="authenticity_token"]').getAttribute('value');
var listingForm = document.getElementById('listing_form');
var listing = JSON.parse(listingForm.dataset.listing);
const myForm = new Vue({
el: '#listing-multistep',
components: {
vueDropzone: vue2Dropzone
},
data: function () {
return {
id: listing.id,
// Other options here...
address: listing.address,
dropzoneOptions: {
url: '/listings',
method: 'post',
acceptedFiles: 'image/*',
uploadMultiple: true,
autoProcessQueue: false, // Dropzone should wait for the user to click a button to upload
parallelUploads: 15, // Dropzone should upload all files at once (including the form data) not all files individually
maxFiles: 15, // this means that they shouldn't be split up in chunks
addRemoveLinks: true,
thumbnailWidth: 150,
maxFilesize: 5,
dictDefaultMessage: "<i class='fa fa-cloud-upload'></i> Drop files here to upload (max. 15 files)",
headers: { 'X-CSRF-Token': Vue.http.headers.common['X-CSRF-Token'] }
}
}
},
mounted: function() {
// Dropzone actually performs the form submission.
// Make a PUT request if the listing id exists.
if(this.id !== null) {
this.dropzoneOptions['url'] = `/listings/${this.id}`
this.dropzoneOptions['method'] = 'put'
}
},
methods: {
submitListing: function() {
// Do some form processing...
// listingObj contains the rest of the form data.
// I'd like to send this object across in the payload.
var listingObj = {
address: this.address,
// more fields...
}
this.$refs.listingDropzone.processQueue()
},
listingRedirect: function(files, response) {
window.location = `/listings/${response.body.id}`
}
}
})
I can see the image being sent across in the controller. The problem is that none of the other form data is being sent. What am I doing wrong here?
Thanks in advance!

find method is not waiting for elements that will appear from a json file

Currently, i have this test:
spec/features/cardx/cardx_spec.rb
it 'can edit cardx with the form' do
product = FactoryGirl.create(:product)
store = FactoryGirl.create(:store)
click_link(class: 'link-record-edit')
page.find(class: 'record-form')
fill_in 'field-start-date', with: Date.tomorrow.to_s
page.find(id: 'cardx-edit-form').fill_in 'field-end-date', with: (Date.tomorrow + 7).to_s
page.find(id: 'cardx-edit-form').select(product.name, from: { id: 'field-product' })
page.find(id: 'cardx-edit-form').select(store.name, from: { id: 'field-store' })
page.find(id: 'cardx-edit-form').click_button(t('button.save'))
changed = Cardx.find(#cardx.id)
result = changed.start_date == Date.tomorrow && changed.end_date == (Date.tomorrow + 7) && changed.product.name == product.name && changed.store.name == store.name
expect(result).to eq(true)
end
This test is supposed to check if i can submit a form, but this form is being rendered and added to the DOM via an AJAX request that happens when i click on a link in the page. This render is coming via a JSON file.
When I'm using the browser normally as a user, everything works, but when i run this test (even when i set max_timeout with ridiculous values like 50 seconds), it immediately fails saying that it cannot find 'record-form' element. Isn't find supposed to wait until max_timeout runs out?
What apparently happens in my test is that it isn't trying to load and render the JSON file with the form code. Is there a solution to this problem?
EDIT: Code I have so far
javascript that loads form
$(document).on('click', '.link-record-edit', function(e) {
//diferentiate between multiple searches on page
setRecord(this);
//set selected id
record[record_type].selected_id = $(this).attr('data-id');
//mark selected row
markSelectedRow();
if(record_type !== "default") {
//if in tab, open modal
$(record[record_type].form_placeholder).modal();
}
//get ID for edition
(loadForm || console.error)(e);
e.preventDefault();
});
function loadForm(ev, callback) {
//validate
if (typeof record[record_type].path_edit === 'undefined') {
return console.error("No definition for 'record[record_type].path_edit' on your page's js");
}
target = [];
_event = null;
if(typeof event === 'undefined' || event === null) {
_event = ev;
} else {
_event = event;
}
if(_event.srcElement !== undefined && _event.srcElement.className !== undefined) {
target = _event.srcElement.className.split(" ");
} else if(_event.target !== undefined && _event.target.className !== undefined) {
target = _event.target.className.split(" ");
}
//show form block
$form_placeholder = "";
if(record_type === "default") {
//is nome cases, with tabs, preselect first tab
$('[role="tabpanel"] a:first').tab('show');
//if selected in default list, empty tab placeholders
$('.tab-pane [data-placeholder="true"]').html('');
//add sidebar form
$('.width-switcher').filter(':visible').removeClass('col-sm-12').addClass('col-sm-6');
$('.width-switcher:hidden').fadeIn('fast');
$form_placeholder = record[record_type].form_placeholder;
} else if(target.indexOf("link-record-edit") > -1 || target.indexOf("link-record-new") > -1) {
$form_placeholder = record[record_type].form_placeholder + ' .modal-content';
}
//last case cenario
if(record[record_type].inline_placeholder != null && $($form_placeholder).length == 0) {
$form_placeholder = record[record_type].form_placeholder;
}
loadSpinner($form_placeholder);
//enable/disable tabs
if(record[record_type].selected_id > 0 && record_type === "default") {
$(".nav-tabs").show();
} else if(record_type === "default") {
$(".nav-tabs").hide();
}
// Remove temporary buttons from hooks
$('.temporary').remove();
// pass var to form of view/edit mode
$edit_mode = $('.editor-mode.active').length;
//get form data and place it on placeholder
$.ajax({
url: record[record_type].path_edit.replace(':id', record[record_type].selected_id),
type: "GET",
dataType: "html",
data: { parent_id: record[record_type].parent_id, edit_mode: $edit_mode },
success: function(result) {
//add form
$($form_placeholder).fadeOut(100, function() {
$(this).html(result).fadeIn(400, function() {
//as it fades in, set title and prettify form objects
//change title
setFormTitle();
//refresh scripts for forms
formPluginRegresh();
//new html on page, redistribute record types attributes
distributeRecordAttributes();
removeSpinner();
//callback if anyone waits
if(callback != null) { callback(); }
});
});
},
error: function(jqXHR, textStatus, errorThrown) {
showMessage('error', $.parseJSON(jqXHR.responseText));
}
});
}
function update_inline_combos() {
//case inline, update dropdownlist
if($(record[record_type].form_placeholder).parents('.overflow').length > 0) {
//check existance of drops
drops = $('.input-group[data-record="'+record_type+'"]').find('> select');
//fill in new data
$.ajax({
url: record[record_type].path_list_drop,
type: "GET",
dataType: "html",
success: function(result) {
$.each(drops, function(index, value) {
//element's id
$id = $(value).attr('id');
//add new data with :id, :name
setBoot('#'+$id, result);
});
},
error: function(jqXHR, textStatus, errorThrown) {
showMessage('error', $.parseJSON(jqXHR.responseText));
}
});
}
}
HTML I have
index.html
<div class="col-sm-6 col-xs-12 width-switcher not-visible">
<div class="box-container">
<div class="box-header">
<span class="title" id="edit-cardx-title"> </span>
<!-- Split button -->
<button class="btn btn-xs btn-default pull-right close-width-switcher" type="button">
<i class="fa fa-close"></i>
</button>
</div>
<div class="box-content no-padding">
<div id="placeholder-form-cardx">
<!-- FORM PLACEHOLDER -->
</div>
</div>
</div>
</div>
_form.etml (partial that will be put on placeholder)
<%= render partial: 'partials/form_objects/form_title', format: :erb, locals: { optional: true } %>
<%= simple_form_for(#cardx, remote: true,
url: url_for(action: 'update_or_create', controller: 'cardxes', format: 'json'),
html: { method: 'post', id: 'cardx-edit-form' },
authenticity_token: true) do |f| %>
<%= f.error_notification %>
<%= f.hidden_field :id %>
<div class="form-inputs form-group">
<div class="col-lg-2 col-md-3 col-sm-4 control-label">
<%=t("label.start_date")%>
</div>
<div class="col-lg-10 col-md-9 col-sm-8">
<%= f.input :start_date, as: :string,
input_html: {class: "date-picker day-datepicker", id: "field-start-date"},
label: false, required: true, class: "form-control" %>
</div>
</div>
<div class="form-inputs form-group">
<div class="col-lg-2 col-md-3 col-sm-4 control-label">
<%=t("label.end_date")%>
</div>
<div class="col-lg-10 col-md-9 col-sm-8">
<%= f.input :end_date, as: :string,
input_html: {class: "date-picker day-datepicker", id: "field-end-date"},
label: false, required: true, class: "form-control" %>
</div>
</div>
<!--
<div class="form-inputs form-group">
<div class="col-lg-2 col-md-3 col-sm-4 control-label">
<%#=t("label.end_date")%>
</div>
<div class="col-lg-10 col-md-9 col-sm-8">
<%= f.collection_select :store_id,
Store.to_collection.list, :id, :name,
{ include_blank: t("form.choose") },
{ class: "required" } %>
</div>
</div>
-->
<div class="form-inputs form-group">
<div class="col-lg-2 col-md-3 col-sm-4 control-label">
<%=t("label.store.one")%>
</div>
<div class="col-lg-10 col-md-9 col-sm-8">
<%= f.collection_select :store_id,
Store.to_collection.list, :id, :name,
{ include_blank: t("form.choose") },
{ class: "required selectpicker", id: "field-store" } %>
</div>
</div>
<div class="form-inputs form-group">
<div class="col-lg-2 col-md-3 col-sm-4 control-label">
<%=t("label.product.one")%>
</div>
<div class="col-lg-10 col-md-9 col-sm-8">
<%= f.collection_select :product_id,
Product.to_collection.list, :id, :name,
{ include_blank: t("form.choose"), required_field: true },
{ class: "selectpicker required", id: "field-product" } %>
</div>
</div>
<script>
$(function() {
$('.selectpicker').selectpicker({
liveSearch: true,
liveSearchNormalize: true,
size: 6,
width: '100%',
dropupAuto: true,
actionsBox: false
});
});
</script>
<%= render partial: "partials/form_objects/form_actions",
format: :erb,
locals: { f: f, record: #cardx } %>
<% end %>
Thank you in advance
EDIT:
I'm using this test (for simplicity) to detect whether the form is loaded and i get ' Unable to find css "#cardx-edit-form" '
it 'show edit form when a cardex record is clicked' do
page.find('.link-record-edit', match: :first).click
page.find('#cardx-edit-form', wait: 50)
expect(page).to have_selector('#cardx-edit-form')
end
The fact that your test isn't tagged with js: true metadata, and that no waiting behavior is happening points to the probability that you're using the rack_test driver for this test. The rack_test driver doesn't support JS and hence doesn't do any waiting either (since without JS support there are no asynchronous actions to wait for). You should be able to confirm that from the stacktrace of your error which will probably reference files with 'capybara/rack_test' in their name. See - https://github.com/teamcapybara/capybara#drivers and https://github.com/teamcapybara/capybara#using-capybara-with-rspec

Starting owlCarousel in rails

I am new to rails and this is the first time I want to use a carousel in a web application. My carousel appears, but it does not auto-play.
This is what I have in my application.js :
//= require owl.carousel
$(function(){ $(document).foundation(); });
var owl = $('.owl-carousel');
owl.owlCarousel({
items:5,
loop:true,
margin:10,
autoplay:true,
autoplayTimeout:1000,
autoplayHoverPause:true
});
And this is from my view:
<div id="owl" class="owl-carousel">
<% #artists.each do |artist| %>
<div class="artist-card ">
<%= link_to artist, class: "poster" do %>
<%= image_tag artist.image.url(:thumb) %>
<% end %>
<div class="artist-info ell glassy-bg padmy padlx">
<div class="artist-card ">
<h6><%= artist.name %> <span>(<%= artist.instrument %>)</span></h6>
</div>
</div>
</div>
<% end %>
</div>
Is there also a way to display every artist from my database in the carousel? I've seen that the default number of items for the carousel is 5. Can I make it dynamic ?
I managed to solve the issue. Here is what I've done:
(function ($) {
$(document).ready(function() {
if ($('.carousel .owl-wrapper-outer').length === 0) {
var owl = $('.carousel').owlCarousel({
items:5,
loop:true,
margin:10,
autoPlay:1000,
autoplayHoverPause:true,
responsive: true,
responsiveRefreshRate : 200,
responsiveBaseWidth: window,
});
$('.carousel').hover(function() {
owl.trigger('owl.stop');
}, function(){
owl.trigger('owl.play', 1000);
});
}

save position of draggable li jQuery Rails 3

I have page with a map on my web-site where administrator can drag and drop icons to the specific position. After reading some examples, icons can be draggable, but when i try to refresh the page all icons revert to their default positions. I can't figure out how to save the position of elements to the database. I'm using jQuery and Rails 3. Help me please!
Thank you!
Here's the code of my page:
<div class="map">
<ul class="departments-map" id="departments-map">
<%= render :partial => 'departments/departments_short_map', :collection => #departments %>
</ul>
</div>
Departments_short_map:
<% department ||= departments_short_map %>
<% if department.map_x.nil? or department.map_y.nil? %>
<li id="<%= department.id %>" class="department-short-map">
<%= department_for_collage(department) %>
</li>
<% else %>
<li id="<%= department.id %>" class="department-short-map" style="position:absolute; top:#{ department.map_y}px; left:#{ department.map_x}px;">
<%= department_for_collage(department) %>
</li>
<% end %>
And here is my jQuery code:
var Collage = {
init: function() {
$("#departments-map li").draggable({
scroll: false,
stop: function(event, ui) {
var img = {
"id": $(this).attr('id'),
"map_y": $(this).position().top.toString(),
"map_x": $(this).position().left.toString()
};
$.ajax({
type: "put",
url: "/departments/" + $(this).attr('id') + ".json",
data: JSON.stringify(img),
contentType: 'application/json',
dataType: 'json',
})
}
});
}
}
What am i doing wrong?

Binding backbone collection to view and underscore template on front end

I am using backbone + jquery mobile and couldn't get my array of models (collections) to output to the UI via underscore template.
My model is as follows:
var shortcake = Backbone.Model.extend({
defaults: function() {
return {
name: "No Slot",
butter: false,
time: "3pm",
icon: "plus",
deladd: "ADD"
};
},
initialize: function() {
this.bind("change:butter", function(){
if (this.model.butter == false) {
this.set({name: this.defaults.name});
};
}),
}
});
My collection is as follows:
var shortcakes = Backbone.Collection.extend ({
model: shortcake
});
var shortcake1 = new shortcake({ name: "How Bizarre", butter: "true", time: "1", icon:"plus", deladd:"ADD" });
var shortcake2 = new shortcake({ name: "Sexual Healing", butter: "true", time: "1", icon:"plus", deladd:"ADD" });
var shortcakeAlbum = new shortcakes([ shortcake1, shortcake2]);
And my view:
var shortcakeUI = Backbone.View.extend ({
tagName: "li",
template: _.template($('#shortcakeTemplate').html()),
initialize: function(){
this.render();
},
render: function() {
var variables = { namee: name };
if (this.model.butter == false) {
this.model.deladd = "ADD";
this.model.icon= "plus";
this.el.html( template );
}
else {
this.model.deladd = "DELETE";
this.model.icon= "minus";
this.el.html( template );
}
},
)};
var ShortcakeUI = new shortcakeUI({
collection : shortcakeAlbum,
el: $("#shortcakeinterface")[0]
});
ShortcakeUI.render();
And my html is:
<ul data-role="listview" data-split-icon="gear" data-split-theme="d">
<div id="shortcakeinterface"></div>
</ul>
<!---Templates--->
<script id="shortcakeTemplate" type="text/template">
<% for(var i = 0; i < shortcakeAlbum.length; i++){ %>
<% var shortcakez = shortcakeAlbum[i]; %>
<fieldset class="ui-grid-a">
<div class="ui-block-a">
<h3><%= shortcakez.time %></h3>
<p><%= shortcakez.name %></p>
</div>
<div class="ui-block-b">
<div id="8" data-role="controlgroup" data-type="horizontal" >
<%= shortcakez.deladd %>
Test
</div>
</div>
</fieldset>
<% } %>
</script>
So with these, my UI does not show the list of models on load.
Just starting out on backbone and js, am I doing things right here?
You need to pass the model to your template in render().
Instead of
this.el.html( template );
do this
$(this.el).html( this.template(this.model.toJSON()) );

Resources