Dynamic menu select Rails don't work - ruby-on-rails

I'm trying to build a chained select menu. This is the tutorial I used: http://railscasts.com/episodes/88-dynamic-select-menus
Something goes wrong, I don't know what I'm missing.
javascript
var sottocategorie = new Array();
<% for element in #sottocategorie -%>
sottocategorie.push(new Array(<%= element.idcategoria %>, <%= element.c2 %>));
<% end -%>
function selezionacategoria() {
categoriaid = $('segnalazione_categoria1').getValue();
options = $('segnalazione_categoria2').options;
options.length = 1;
sottocategorie.each(function(elementement) {
if (element[0] == categoriaid) {
options[options.length] = new Option(element[1]);
}
});
if (option.length == 1) {
$('sottocategoria_field').hide();
} else {
$('sottocategoria_field').show();
}
}
document.observe('dom:loaded', function() {
//selezionacategoria();
$('segnalazione_categoria1').observe('change', selezionacategoria);
});
html
<label for="segnalazione_categoria1">Categoria:</label>
<%= f.collection_select :categoria1, Categorium.find(:all), :id, :c1, :prompt => "Seleziona categoria" %>
<p id="sottocategoria_field">
<label for="segnalazione_categoria2">Categoria:</label>
<%= f.collection_select :categoria2, Sottocategoria1.find(:all), :id, :c2, :prompt => "Seleziona sottocategoria" %>
</p>
routes:
match '/:controller(/:action(/:id))'
The chained select menu doesn't run, the "filter" doesn't work, and also
if (option.length == 1) {
$('sottocategoria_field').hide();
} else {
$('sottocategoria_field').show();
}
doesn't work.

SOLUTION:
I've changed the javascript file using jQuery:
var sottocategorie = new Array();
<% for sottocategoria in #sottocategorie %>
sottocategorie.push(new Array('<%= sottocategoria.idcategoria %>', '<%= escape_javascript(sottocategoria.c2) %>', <%= sottocategoria.id %>));
<% end %>
function menuSelected(orig_menu, new_menu, item_array) {
orig_value = $('#segnalazione_categoria1 :selected').text();
//alert(orig_value);
$(new_menu).empty();
jQuery.each(item_array, function(i, val) {
if (val[0] == orig_value) {
$(new_menu).append($("<option></option>").attr("value",val[2]).text(val[1]));
}
});
}
$(document).ready(function(){
//bind the click event to the city submit button
$('#submit_button').bind('click', function () {
menuSelected('#segnalazione_categoria1', '#segnalazione_categoria2', sottocategorie);
});
});
SOLUTION WITH THREE LEVELS OF CHAINING:
var sottocategorie = new Array();
var subsottocategorie = new Array();
<% for sottocategoria in #sottocategorie %>
sottocategorie.push(new Array('<%= sottocategoria.idcategoria %>', '<%= escape_javascript(sottocategoria.c2) %>', <%= sottocategoria.id %>));
<% end %>
<% for subsottocategoria in #subsottocategorie %>
subsottocategorie.push(new Array('<%= subsottocategoria.idsottocategoria1s %>', '<%= escape_javascript(subsottocategoria.c3) %>', <%= subsottocategoria.id %>));
<% end %>
function menuSelected(orig_menu, new_menu, item_array) {
orig_value = $(''+ orig_menu + ' :selected').text();
//alert(orig_value);
$(new_menu).empty();
jQuery.each(item_array, function(i, val) {
if (val[0] == orig_value) {
$(new_menu).append($("<option></option>").attr("value",val[1]).text(val[1]));
}
});
}
$(document).ready(function(){
$(".nascosto").hide();
$(".nascosto1").hide();
//bind the click event to the city submit button
$('#segnalazione_categoria1').bind('click', function () {
$(".nascosto").show();
$('#segnalazione_categoria3').empty();
menuSelected('#segnalazione_categoria1', '#segnalazione_categoria2', sottocategorie);
});
$('#segnalazione_categoria2').bind('click', function (event) {
$(".nascosto1").show();
menuSelected('#segnalazione_categoria2', '#segnalazione_categoria3', subsottocategorie);
});
});

Related

How to limit upload files in dropzone.js?

I have ruby on rails application with stimulus.js and dropzone.js for uploading attachment. There is now a limit on uploading one file, but this allows you to upload more than one file and just shows an error message on them. I need that it is not possible to upload more than one file and if after that the user tries to upload another one, a replacement occurs.
dropzone_controller.js
import Dropzone from "dropzone";
import { Controller } from "stimulus";
import { DirectUpload } from "#rails/activestorage";
import {
getMetaValue,
toArray,
findElement,
removeElement,
insertAfter
} from "helpers";
export default class extends Controller {
static targets = ["input"];
connect() {
this.dropZone = createDropZone(this);
this.hideFileInput();
this.bindEvents();
Dropzone.autoDiscover = false; // necessary quirk for Dropzone error in console
}
// Private
hideFileInput() {
this.inputTarget.disabled = true;
this.inputTarget.style.display = "none";
}
bindEvents() {
this.dropZone.on("addedfile", file => {
setTimeout(() => {
file.accepted && createDirectUploadController(this, file).start();
}, 500);
});
this.dropZone.on("removedfile", file => {
file.controller && removeElement(file.controller.hiddenInput);
});
this.dropZone.on("canceled", file => {
file.controller && file.controller.xhr.abort();
});
}
get headers() {
return { "X-CSRF-Token": getMetaValue("csrf-token") };
}
get url() {
return this.inputTarget.getAttribute("data-direct-upload-url");
}
get maxFiles() {
return this.data.get("maxFiles") || 1;
}
get maxFileSize() {
return this.data.get("maxFileSize") || 256;
}
get acceptedFiles() {
return this.data.get("acceptedFiles");
}
get addRemoveLinks() {
return this.data.get("addRemoveLinks") || true;
}
}
class DirectUploadController {
constructor(source, file) {
this.directUpload = createDirectUpload(file, source.url, this);
this.source = source;
this.file = file;
}
start() {
this.file.controller = this;
this.hiddenInput = this.createHiddenInput();
this.directUpload.create((error, attributes) => {
if (error) {
removeElement(this.hiddenInput);
this.emitDropzoneError(error);
} else {
this.hiddenInput.value = attributes.signed_id;
this.emitDropzoneSuccess();
}
});
}
createHiddenInput() {
const input = document.createElement("input");
input.type = "hidden";
input.name = this.source.inputTarget.name;
insertAfter(input, this.source.inputTarget);
return input;
}
directUploadWillStoreFileWithXHR(xhr) {
this.bindProgressEvent(xhr);
this.emitDropzoneUploading();
}
bindProgressEvent(xhr) {
this.xhr = xhr;
this.xhr.upload.addEventListener("progress", event =>
this.uploadRequestDidProgress(event)
);
}
uploadRequestDidProgress(event) {
const element = this.source.element;
const progress = (event.loaded / event.total) * 100;
findElement(
this.file.previewTemplate,
".dz-upload"
).style.width = `${progress}%`;
}
emitDropzoneUploading() {
this.file.status = Dropzone.UPLOADING;
this.source.dropZone.emit("processing", this.file);
}
emitDropzoneError(error) {
this.file.status = Dropzone.ERROR;
this.source.dropZone.emit("error", this.file, error);
this.source.dropZone.emit("complete", this.file);
}
emitDropzoneSuccess() {
this.file.status = Dropzone.SUCCESS;
this.source.dropZone.emit("success", this.file);
this.source.dropZone.emit("complete", this.file);
}
}
function createDirectUploadController(source, file) {
return new DirectUploadController(source, file);
}
function createDirectUpload(file, url, controller) {
return new DirectUpload(file, url, controller);
}
function createDropZone(controller) {
return new Dropzone(controller.element, {
url: controller.url,
headers: controller.headers,
maxFiles: controller.maxFiles,
maxFilesize: controller.maxFileSize,
acceptedFiles: controller.acceptedFiles,
addRemoveLinks: controller.addRemoveLinks,
autoQueue: false
});
}
_form.html.erb
<div data-lite-visibility-target="dynamic" class="space-y-8 <%= #automate_task_report.attachment.present? ? '' : "hidden" %>" >
<div class="form-group inverted">
<%= form.label :attachment, "Upload test execution results", class: "form-label" %>
<button type="button" class="dropzone dropzone-default dz-clickable form-control form-file form-file-btn" data-controller="dropzone" data-dropzone-max-file-size="10" data-dropzone-max-files="1" data-dropzone-accepted-files=".xml,.html,.jpg,.jpeg,.png,.gif">
<%= form.file_field :attachment, direct_upload: true, data: { target: 'dropzone.input' } %>
<div class="dropzone-msg dz-message needsclick flex m-0">
<% if #automate_task_report.attachment.attached? %>
<%= form.hidden_field :attachment, value: #automate_task_report.attachment.signed_id %>
<div class="mx-5 attachment vertical">
<%= link_to #automate_task_report.attachment, target: "_blank", class:"attachment-thumb" do %>
<%= image_tag(#automate_task_report.attachment) %>
<% end %>
<%= link_to #automate_task_report.attachment.filename.to_s, #automate_task_report.attachment, target: "_blank", class:"attachment-name" %>
<%= link_to #automate_task_report.attachment, download: #automate_task_report.attachment, class:"btn btn-primary attachment-btn" do %>
<span class="icon text-icon-default icon-download"></span>
<% end %>
</div>
<% end %>
<span class="icon text-icon-lg icon-file-image-plus-lg mr-3"></span>
<div class="text-left mt-0">
<p>Upload a file or drag and drop</p>
<p class="text-xs">XML, HTML, PNG, JPG, GIF up to 10MB</p>
</div>
</div>
</button>
</div>
</div>
</div>
</div>
I'm already tried things like
= {
maxFiles: 1
};
but it didn't work for me. How can I achive expected result?
Use the "accept" callback to check for the max amount of files:
accept: function(file, done) {
if (/* do a check here */) {
done(error message);
// File is cancel.
file.status = Dropzone.CANCELED;
}
}
key point here is change the file status to cancelled to let dropzone know about it

trying to implement retrieve attachments on update action

I'm following the Andy Leverenz's (WebCrunch) Dropzone/Stimulus tutorial and don't know how to implement the update action. I can upload multiple files with ease but when I try to edit it, all my attachments disappear and I have to re-upload. I think that what I need to do is fetch my object attachments on stimulus controller (#rails/ujs) and check if browser URL match with edit_path, then append files on the form when it loads (document.ready).
dropzone_controller.js
import Dropzone from "dropzone";
import { Controller } from "stimulus";
import { DirectUpload } from "#rails/activestorage";
import {
getMetaValue,
toArray,
findElement,
removeElement,
insertAfter
} from "helpers";
export default class extends Controller {
static targets = ["input"];
connect() {
this.dropZone = createDropZone(this);
this.hideFileInput();
this.bindEvents();
Dropzone.autoDiscover = false;
}
// Private
hideFileInput() {
this.inputTarget.disabled = true;
this.inputTarget.style.display = "none";
}
bindEvents() {
this.dropZone.on("addedfile", file => {
setTimeout(() => {
file.accepted && createDirectUploadController(this, file).start();
}, 500);
});
this.dropZone.on("removedfile", file => {
file.controller && removeElement(file.controller.hiddenInput);
});
this.dropZone.on("canceled", file => {
file.controller && file.controller.xhr.abort();
});
}
get headers() {
return { "X-CSRF-Token": getMetaValue("csrf-token") };
}
get url() {
return this.inputTarget.getAttribute("data-direct-upload-url");
}
get maxFiles() {
return this.data.get("maxFiles") || 1;
}
get maxFileSize() {
return this.data.get("maxFileSize") || 256;
}
get acceptedFiles() {
return this.data.get("acceptedFiles");
}
get addRemoveLinks() {
return this.data.get("addRemoveLinks") || true;
}
}
class DirectUploadController {
constructor(source, file) {
this.directUpload = createDirectUpload(file, source.url, this);
this.source = source;
this.file = file;
}
start() {
this.file.controller = this;
this.hiddenInput = this.createHiddenInput();
this.directUpload.create((error, attributes) => {
if (error) {
removeElement(this.hiddenInput);
this.emitDropzoneError(error);
} else {
this.hiddenInput.value = attributes.signed_id;
this.emitDropzoneSuccess();
}
});
}
createHiddenInput() {
const input = document.createElement("input");
input.type = "hidden";
input.name = this.source.inputTarget.name;
insertAfter(input, this.source.inputTarget);
return input;
}
directUploadWillStoreFileWithXHR(xhr) {
this.bindProgressEvent(xhr);
this.emitDropzoneUploading();
}
bindProgressEvent(xhr) {
this.xhr = xhr;
this.xhr.upload.addEventListener("progress", event =>
this.uploadRequestDidProgress(event)
);
}
uploadRequestDidProgress(event) {
const element = this.source.element;
const progress = (event.loaded / event.total) * 100;
findElement(
this.file.previewTemplate,
".dz-upload"
).style.width = `${progress}%`;
}
emitDropzoneUploading() {
this.file.status = Dropzone.UPLOADING;
this.source.dropZone.emit("processing", this.file);
}
emitDropzoneError(error) {
this.file.status = Dropzone.ERROR;
this.source.dropZone.emit("error", this.file, error);
this.source.dropZone.emit("complete", this.file);
}
emitDropzoneSuccess() {
this.file.status = Dropzone.SUCCESS;
this.source.dropZone.emit("success", this.file);
this.source.dropZone.emit("complete", this.file);
}
}
function createDirectUploadController(source, file) {
return new DirectUploadController(source, file);
}
function createDirectUpload(file, url, controller) {
return new DirectUpload(file, url, controller);
}
function createDropZone(controller) {
return new Dropzone(controller.element, {
url: controller.url,
headers: controller.headers,
maxFiles: controller.maxFiles,
maxFilesize: controller.maxFileSize,
acceptedFiles: controller.acceptedFiles,
addRemoveLinks: controller.addRemoveLinks,
autoQueue: false
});
}
index.js
// Load all the controllers within this directory and all subdirectories.
// Controller files must be named *_controller.js.
import { Application } from "stimulus";
import { definitionsFromContext } from "stimulus/webpack-helpers";
const application = Application.start();
const context = require.context("controllers", true, /_controller\.js$/);
application.load(definitionsFromContext(context));
_form.html.erb
<%= form_for #ad, :html => { :multipart => true } do |form| %>
<% if ad.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(ad.errors.count, "error") %> prohibited this ad from being saved:</h2>
<ul>
<% ad.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="form-group">
<%= form.label :title %>
<%= form.text_field :title, class: 'form-control' %>
</div>
<div class="form-group">
<%= form.label :description %>
<%= form.text_area :description, class: 'form-control' %>
</div>
<div class="form-group">
<%= form.label :images %>
<div class='dropzone dropzone-default dz-clickable' data-controller='dropzone' data-max-filesize='2'
data-dropzone-max-files='4'>
<%= form.file_field :images, multiple: true, direct_upload: true, data: { target: 'dropzone.input' } %>
<div class='dropzone-msg dz-message needsclick'>
<h3 class='dropzone-msg-title'>Drag here to upload or click here to browse</h3>
<span class='dropzone-msg-desc'>2 MB file size maximum. Allow file types png, jpg.</span>
</div>
</div>
</div>
<div class="form-group">
<%= form.submit class: 'btn btn-primary mb-2' %>
</div>
<% end %>

Rails collection select when collection is way big

I have a form partial inside which I select associated users through a multiple: true collection select:
= f.collection_select(:user_ids, User.all, :id, :email, {selected: #limit_group.user_ids, include_blank: true}, {multiple: true, "data-placeholder" => "Add users to group"})
But how can I do this more efficiently to avoid big load times when the database has like thousands of users?
You'll be better using something called AutoComplete / LiveSearch with a text box (like Pardeep Saini mentioned).
We've done this before:
You could achieve this relatively simply:
= f.text_field :user_ids, placeholder: "Search for users"
You'd then have to use javascript:
#app/assets/javascripts/application.js
$(document).on("keyup", "input[type=text]#user_ids", function(){
$.getJSON("users/search", {name: $(this).val()}).done(function(json){
var users = [];
$.each(json.users, function(user) {
users.push("" + user.name + "");
});
$(".search").html(users).show();
});
});
$(document).on("click", ".search a", function(e) {
e.preventDefault();
// add hidden field with user name to form
});
You'd have to back it up with the relevant controller action:
#config/routes.rb
resources :users do
get :search, on: :collection
end
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def search
#users = User.where("name LIKE ?", "%" + params[:name] + "%")
respond_to do |format|
format.json (render json: #users.to_json)
end
end
end
The above code should be refactored.
--
To get this working with multiple values would be a little bit more involved. It could be done, but you'd have to do it like the tags setup in StackOverflow...
The way they do that is to basically use a similar principle to the above (each tag will be a returned piece of data from the search).
Here's the actual code we used in the cosmetics example above:
#app/assets/javascripts/extra/jquery.livesearch.js
(function($) {
$.searchbox = {}
$.extend(true, $.searchbox, {
settings: {
url: 'search',
param: 'search',
dom_id: '#livesearch',
minChars: 2,
loading_css: '#livesearch_loading',
del_id: '#livesearch_del'
},
loading: function() {
$($.searchbox.settings.loading_css).show()
},
idle: function() {
$($.searchbox.settings.loading_css).hide()
},
start: function() {
$.searchbox.loading()
$(document).trigger('before.searchbox')
},
stop: function() {
$.searchbox.idle()
$(document).trigger('after.searchbox')
},
kill: function() {
$($.searchbox.settings.dom_id).fadeOut(50)
$($.searchbox.settings.dom_id).html('')
$($.searchbox.settings.del_id).fadeOut(100)
},
reset: function() {
$($.searchbox.settings.dom_id).html('')
$($.searchbox.settings.dom_id).fadeOut(50)
$('#SearchSearch').val('')
$($.searchbox.settings.del_id).fadeOut(100)
},
process: function(terms) {
if(/\S/.test(terms)) {
$.ajax({
type: 'GET',
url: $.searchbox.settings.url,
data: {search: terms.trim()},
complete: function(data) {
$($.searchbox.settings.del_id).fadeIn(50)
$($.searchbox.settings.dom_id).html(data.responseText)
if (!$($.searchbox.settings.dom_id).is(':empty')) {
$($.searchbox.settings.dom_id).fadeIn(100)
}
$.searchbox.stop();
}
});
return false;
}else{
$.searchbox.kill();
}
}
});
$.fn.searchbox = function(config) {
var settings = $.extend(true, $.searchbox.settings, config || {})
$(document).trigger('init.searchbox')
$.searchbox.idle()
return this.each(function() {
var $input = $(this)
$input
.keyup(function() {
if ($input.val() != this.previousValue) {
if(/\S/.test($input.val().trim()) && $input.val().trim().length > $.searchbox.settings.minChars){
$.searchbox.start()
$.searchbox.process($input.val())
}else{
$.searchbox.kill()
}
this.previousValue = $input.val()
}
})
})
}
})(jQuery);
... and ...
#app/assets/javascripts/application.js
$(document).ready( function() {
var base_url = window.location.protocol + "//" + window.location.host;
$('#SearchSearch').searchbox({
url: base_url + '/search/',
param: 'search',
dom_id: '#livesearch',
loading_css: '#livesearch_loading'
})
});
$(document).on('click', '#livesearch_del', function() { $.searchbox.reset(); })
$(document).on('submit', '#SearchForm', function() { $.searchbox.kill(); });
$(document).on('click', '.livesearch_results tr', function() { window.location = $('a:first', this).attr('href'); });
The routes & controller:
#config/routes.rb
match 'search(/:search)', :to => 'products#search', :as => :search, via: [:get, :post]
#app/models/product.rb
class Product < ActiveRecord::Base
def self.search(search)
where("name LIKE ? OR description LIKE ?", "%#{search}%", "%#{search}%").take(5)
end
end
#app/controllers/product_controller.rb
class ProductsController < ApplicationController
def search
#products = Product.search params[:search]
respond_to do |format|
format.js { render :partial => "elements/livesearch", :locals => {:search => #products, :query => params[:search]} }
format.html {
render :index
}
end
end
end
The views:
#app/views/elements/_livesearch.html.erb
<div class="livesearch_container">
<table class="livesearch_results">
<% unless search.blank? %>
<% search.each_with_index do |item,i| %>
<% pos ||= '' %>
<% if (i == 0) then pos = 'first' end %>
<% if (i == search.size - 1) then pos += ' last' end %>
<tr data-link="<%= "/#{item.slug}" %>" class="<%= "#{pos}" %>">
<td class="image">
<% model = item.images.first || item.images.build %>
<%= image_tag(model.image.url(:thumb), :title => item.name, data: {"placement" => "left"}, :height => "85") %><br/>
</td>
<td class="information">
<%= link_to image_tag(item.brand.images.first.image.url(:thumb), :width => "55", :title => "View #{item.brand.name}"), "/#{item.brand.slug}", :class => "brand" if defined?(item.brand.images.first) %>
<div class="name"><%= link_to item.name, "/#{item.slug}" %></div>
</td>
<td class="price">
<%= number_to_currency(item.price, unit: "£") %>
</td>
</tr>
<% end %>
<tr class="results"><td colspan="3"><%= link_to "See all #{search.size} results here »", search_path(query) %></td></tr>
<% else %>
<tr class="results"><td colspan="3"><%= link_to 'No results found', search_path(query) %></td></tr>
<% end %>
</table>
</div>
I also made a gist here: https://gist.github.com/richpeck/2310ff3ab1ffcd6a9138

How to add link to marker using gmaps4rails gem

I would like to add a marker with link in marker, so, when i click that marker, i will redirect to another page
Is anybody know to do it?
Thanks
I already added my code like this :
In controller:
#json = #businesses.results.to_gmaps4rails do |object|
"\"link\": \"#{root_url}\""
end
In view :
<%= gmaps4rails(#json) %>
<% content_for :scripts do %>
<script type="text/javascript">
function redirect_to(url) {
window.location = url;
};
Gmaps4Rails.callback = function() {
function say_yo(arg) { return function(){alert('yo '+ arg + '!' );};};
for (var i = 0; i < Gmaps4Rails.markers.length; ++i) {
google.maps.event.addListener(Gmaps4Rails.markers[i].google_object, 'click', redirect_to(Gmaps4Rails.markers[i].link));
}
}
</script>
<% end %>
Is it any wrong? because there just an info window that show after i clicked the marker(Not redirect to any page)
First include the link inside the json:
Model.all.to_gmaps4rails do |object|
"\"link\": \"your link as string\""
end
Then add the extra listeners in your view (beware to include this AFTER your call to the gmaps method):
<%= gmaps(whatever you need here) %>
<% content_for :scripts do %>
<script type="text/javascript">
function redirect_to(url) {
window.location = url;
};
Gmaps4Rails.callback = function() {
function say_yo(arg) { return function(){alert('yo '+ arg + '!' );};};
for (var i = 0; i < Gmaps4Rails.markers.length; ++i) {
google.maps.event.addListener(Gmaps4Rails.markers[i].google_object, 'click', redirect_to(Gmaps4Rails.markers[i].link));
}
}
</script>
<% end %>

Using ajax pagination (will_paginate) for multiple groups of the same model

I have gotten ajax pagination working using mislav-will_paginate with the js found here: https://github.com/mislav/will_paginate/wiki/Ajax-pagination but I have run into a situation that I am not sure how to handle. I have a number of groups (of the same model) and each group has a number of lists that are paginated. I cannot tell which group to load the lists for in the ajax pagination call. This is the code I am using:
function ajaxPagination(){
var container = $(document.body)
if (container) {
container.observe('click', function(e) {
var el = e.element();
if (el.match('.pagination a')) {
new Ajax.Request(el.href, { method: 'get' })
e.stop()
}
})
}
}
<% #groups.each do |group| %>
<% lists = group.lists.paginate(:page => params[:group_page], :per_page => 5) %>
<% lists.each .... %>
<%= will_paginate lists, :params_name => "group_page" %>
<% end %>
You need to improve your JS to know which 'group' you're clicking.
$("#<%= group %>").html("<%= escape_javascript(render("group")) %>");
See: http://railscasts.com/episodes/174-pagination-with-ajax or read it at: http://asciicasts.com/episodes/174-pagination-with-ajax
$('.group').click(function(){
$.ajax({
type: "GET",
url: "/group",
data: { 'group' : $(this).attr('id') }
success: function(data){
alert( "Success" );
}
});
});

Resources