How to limit upload files in dropzone.js? - ruby-on-rails

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

Rails: direct upload and create has_one_attached parent

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)
})
},

Rails - StimulusJS: how to set target value from within setTimeout?

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)
}
}

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 %>

Stripe SCA one-time payments Rails

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

Dynamic menu select Rails don't work

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

Resources