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
Related
I'm making an image library type thing in Rails and Vue, and I'm using DirectUpload to manage attachments.
# photo.rb
class Photo < ApplicationRecord
has_one_attached :file
# ...
end
# photos_controller.rb
class PhotosController < ApplicationController
load_and_authorize_resource
before_action :set_photo, only: %i[show update destroy]
protect_from_forgery unless: -> { request.format.json? }
def index
#photo = current_user.photos.new
render action: 'index'
end
def create
#photo = current_user.photos.create(photo_params)
render json: PhotoBlueprint.render(#photo, root: :photo)
end
# ...
def photo_params
params.require(:photo).permit(:id, :file)
end
end
# photos/index.html.erb
<%= simple_form_for(#photo, url: photos_path(#photo), html: { multipart: true }) do |f| %>
<%= f.file_field :file, multiple: true, direct_upload: true, style: 'display: none;' %>
<% end %>
<div id='photos-app'></div>
// UserFileLib.vue
<script>
import { mapState, mapActions } from 'pinia'
import { usePhotoStore } from '#stores/photo'
import { DirectUpload } from '#rails/activestorage'
export default {
name: 'UserFileLib',
computed: {
...mapState(usePhotoStore, [
'photos'
]),
url () {
return document.getElementById('photo_file').dataset.directUploadUrl
},
input () {
return document.getElementById('file-input')
},
},
mounted () {
this.getPhotos()
},
methods: {
...mapActions(usePhotoStore, [
'addPhoto',
'getPhotos',
]),
activestorageURL (blob) {
return `/rails/active_storage/blobs/redirect/${blob.signed_id}/${blob.filename}`
},
uploadToActiveStorage () {
const file = this.input.files[0]
const upload = new DirectUpload(file, this.url)
upload.create((error, blob) => {
if (error) {
console.log(error)
} else {
const url = this.activestorageURL(blob)
console.log(url)
this.getPhotos()
}
})
},
openFileBrowser () {
this.input.click()
},
formatSize (bytes) {
return Math.round(bytes / 1000)
}
}
}
</script>
<template>
<div
#click="openFileBrowser"
class="card p-3">
Click or drop files here
</div>
<input
type="file"
:multiple="true"
#change="uploadToActiveStorage"
id="file-input" />
<div class="grid is-inline-grid mt-2">
<div
class="image-container"
v-for="image in photos"
:key="image.id">
<img :src="image.url" :alt="image.label" />
<div class="filename">
<strong>{{ image.label }}</strong>
<br />
{{ formatSize(image.size) }} kb
</div>
<div class="close">
×
</div>
</div>
</div>
</template>
Now, the uploads work fine, the blob is stored correctly.
My issue is that a new Photo object is not created to wrap the attachment, meaning the uploads are lost in the system and have no parent record.
What am I doing wrong?
I've solved this for anyone else looking for help. The logic is to create or update the parent record after the upload is done. I missed this in the official documentation.
upload.create((error, blob) => {
if (error) {
// Handle the error
} else {
// ** This is the way **
// Add an appropriately-named hidden input to the form with a
// value of blob.signed_id so that the blob ids will be
// transmitted in the normal upload flow
// ** End of **
//
const hiddenField = document.createElement('input')
hiddenField.setAttribute("type", "hidden");
hiddenField.setAttribute("value", blob.signed_id);
hiddenField.name = input.name
document.querySelector('form').appendChild(hiddenField)
}
})
Since I'm using Vue and Pinia I made a solution in keeping with that logic:
// UserImageLib.vue
uploadToActiveStorage (input, file) {
const url = input.dataset.directUploadUrl
const upload = new DirectUpload(file, url)
upload.create((error, blob) => {
if (error) {
console.log(error)
} else {
const params = { [input.name]: blob.signed_id }
this.createPhoto(params)
}
})
},
// stores/photo.js
addPhoto (payload) {
this.photos.unshift(payload)
},
createPhoto (payload) {
http.post(`/photos`, payload).then(res => {
const photo = res.data.photo
this.addPhoto(photo)
})
},
In my Rails-Stimulus controller, I fetch data in a loop from a function in which I've setTimeout. How do I set the target value from within the setTimeout?
My form partial
<%= text_field_tag :first_date, "Select Date One", data: {controller: 'flatpickr'} %>
<div data-controller="hist">
<div data-hist-target="one"></div>
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 border border-blue-700 rounded"
data-action='hist#get_history'>
Hist
</button>
</div>
My controller
import { Controller } from "#hotwired/stimulus"
export default class extends Controller {
static targets = [ "one" ]
connect() {
}
get_date_history(aDate) {
setTimeout(function() {
var history = new Array()
let month = aDate.slice(5,7)
let date = aDate.slice(8,10)
let url = 'http://numbersapi.com/' + month + '/' + date + '/date'
for (let step = 0; step < 5; step++) {
fetch(url)
.then(response => response.text())
.then(function(data) {
history.push(data)
})
.catch(err => console.log('Request Failed', err)) // Catch errors
}
// can we set oneTarget value here? how?
}, 2500)
}
get_history() {
var date = document.getElementById("first_date").value
this.get_date_history(date)
}
}
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'm integrating Stripe SCA with payment intents into my rails 5.2.3 (ruby 2.5.1) app. I successfully had one-time payments working properly, however after I integrated subscriptions (successfully working), the one-time payments are receiving an incomplete status on Stripe "The customer has not entered their payment method". Looking at the JSON I can see my payment intent with ID successfully being created, however my charges, data is showing up as null. I can't figure out why the data is not being passed to stripe. Here are the corresponding files:
purchases.show.html.erb
<div class="container">
<h1>Purchasing <%= #recipe.title %> for <%= number_to_currency(#recipe.price) %></h1>
<%= form_with url: recipe_purchase_path(#recipe.id), local: true, id: "payment-form", data: { payment_intent_id: #payment_intent.client_secret } do |form| %>
<div class="form-group">
<label for="card-element">
Credit or debit card
</label>
<div id="card-element" class="form-control">
</div>
<div id="card-errors" role="alert">
</div>
</div>
<div class="form-group">
<label>Name on Card</label>
<%= form.text_field :name_on_card, placeholder: "Full name", class: "form-control" %>
</div>
<div class="form-group">
<%= form.hidden_field :payment_intent_id, value: #payment_intent.id %>
<button class="btn btn-outline-primary buy-recipe">Submit Payment</button>
</div>
<% end %>
</div>
purchases_controller.rb
class PurchasesController < ApplicationController
before_action :authenticate_user!
before_action :set_recipe, only:[:show, :create]
def receipt
#purchase = Purchase.find_by_uuid(params[:id])
#recipe = Recipe.find(#purchase.recipe_id)
end
def show
#payment_intent = Stripe::PaymentIntent.create(
amount: #recipe.price_in_cents,
currency: 'usd',
payment_method_types: params['card'],
metadata: {integration_check: 'accept_a_payment'},
)
end
def create
#payment_intent = Stripe::PaymentIntent.retrieve(params[:payment_intent_id])
if #payment_intent.status == "succeeded"
charge = #payment_intent.charges.data.first
card = charge.payment_method_details.card
purchase = Purchase.create(
customer_id: charge.id,
user_id: current_user.id,
recipe_id: #recipe.id,
uuid: SecureRandom.uuid,
amount: #recipe.price
)
current_user.favorites << #recipe
redirect_to recipe_path(#recipe.slug), notice: "#{#recipe.title} has been added to your Cookbook, thanks for purchasing!"
else
flash[:alert] = "Your order was unsuccessful. Please try again."
redirect_to recipe_purchase_path(#recipe.id)
end
end
private
def set_recipe
#recipe = Recipe.find(params[:recipe_id])
end
end
purchases.index.js
document.addEventListener("turbolinks:load", () => {
const form = document.querySelector("#payment-form")
if (form == null) { return }
const public_key = document.querySelector("meta[name='stripe-key']").getAttribute("content")
const stripe = Stripe(public_key)
const elements = stripe.elements()
const card = elements.create('card')
card.mount('#card-element')
card.addEventListener("change", (event) => {
var displayError = document.getElementById('card-errors')
if (event.error) {
displayError.textContent = event.error.message
} else {
displayError.textContent = ''
}
})
form.addEventListener("submit", (event) => {
event.preventDefault()
let data = {
payment_method: {
card: card,
billing_details: {
name: form.querySelector("#name_on_card").value
}
}
}
stripe.confirmCardPayment(form.dataset.paymentIntentId, data).then((result) => {
if (result.error) {
var errorElement = document.getElementById('card-errors')
errorElement.textContent = result.error.message
} else {
//
//
form.submit()
}
})
})
})
and a screenshot of JSON
here is my subscriptions.js file
document.addEventListener("turbolinks:load", () => {
let cardElement = document.querySelector("#card-element")
if (cardElement !== null) { setupStripe() }
})
function setupStripe() {
const stripe_key = document.querySelector("meta[name='stripe-key']").getAttribute("content")
const stripe = Stripe(stripe_key)
const elements = stripe.elements()
const card = elements.create('card')
card.mount('#card-element')
var displayError = document.getElementById('card-errors')
card.addEventListener('change', (event) => {
if (event.error) {
displayError.textContent = event.error.message
} else {
displayError.textContent = ''
}
})
const form = document.querySelector("#payment-form")
let paymentIntentId = form.dataset.paymentIntent
let setupIntentId = form.dataset.setupIntent
if (paymentIntentId) {
if (form.dataset.status == "requires_action") {
stripe.confirmCardPayment(paymentIntentId, { setup_future_usage: 'off_session' }).then((result) => {
if (result.error) {
displayError.textContent = result.error.message
form.querySelector("#card-details").classList.remove("d-none")
} else {
form.submit()
}
})
}
}
form.addEventListener('submit', (event) => {
event.preventDefault()
let name = form.querySelector("#name_on_card").value
let data = {
payment_method_data: {
card: card,
billing_details: {
name: name,
}
}
}
// Complete a payment intent
if (paymentIntentId) {
stripe.confirmCardPayment(paymentIntentId, {
payment_method: data.payment_method_data,
setup_future_usage: 'off_session',
save_payment_method: true,
}).then((result) => {
if (result.error) {
displayError.textContent = result.error.message
form.querySelector("#card-details").classList.remove("d-none")
} else {
form.submit()
}
})
// Updating a card or subscribing with a trial (using a SetupIntent)
} else if (setupIntentId) {
stripe.confirmCardSetup(setupIntentId, {
payment_method: data.payment_method_data
}).then((result) => {
if (result.error) {
displayError.textContent = result.error.message
} else {
addHiddenField(form, "payment_method_id", result.setupIntent.payment_method)
form.submit()
}
})
} else {
//subscribing w no trial
data.payment_method_data.type = 'card'
stripe.createPaymentMethod(data.payment_method_data).then((result) => {
if (result.error) {
displayError.textContent = result.error.message
} else {
addHiddenField(form, "payment_method_id", result.paymentMethod.id)
form.submit()
}
})
}
})
}
function addHiddenField(form, name, value) {
let input = document.createElement("input")
input.setAttribute("type", "hidden")
input.setAttribute("name", name)
input.setAttribute("value", value)
form.appendChild(input)
}
So special thanks to Chris Oliver on this...but what needed to be done was on the show.html.erb I had to change the payment_intent_id to :payment_intent in the form data.
<%= form_with url: recipe_purchase_path(#recipe.id), local: true, id: "payment-form", data: { payment_intent: #payment_intent.client_secret } do |form| %>
then in my show action in purchases_controller.rb I needed to add customer
def show
#payment_intent = Stripe::PaymentIntent.create(
amount: #recipe.price_in_cents,
currency: 'usd',
payment_method_types: ['card'],
customer: current_user.stripe_id || current_user.stripe_customer.id
)
end
Then I completely removed my purchases.js since the one-time payments are being handled in the subscription.js
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);
});
});