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
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
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 %>
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
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 %>
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" );
}
});
});