Only one Dropzone working when multiple dinamically initialized - javascript

I'm writing a web application where I need to initialize multiple dropzones basing on server content, I currently have a code similar to this:
<script src="/js/dropzone.min.js"></script>
<script>
Dropzone.autoDiscover = false;
window.dropzones = {};
function checkDropzones(container) {
var possibleDropzones = container.querySelectorAll('.needs-to-be-dropzoned');
possibleDropzones.forEach(function (zone) {
if (zone.id.length === 0) {
zone.id = 'dropzone_filled_' + (new Date()).getTime();
window.dropzones[zone.id] = new Dropzone(
'#' + zone.id,
{
paramName: 'image',
addRemoveLinks: true,
}
);
}
})
}
function renderServerContent() {
window.customSections.forEach(function (custom_section) {
var container = document.getElementById(custom_section);
$.ajax({
method: 'GET',
url: '/customRenderUrl?section=' + custom_section,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
success: function (response) {
container.innerHTML = container.innerHTML + response.component;
if (response.component_type === 'image_uploader') {
checkDropzones(container);
}
// ... other marginal stuff ...
},
dataType: 'json'
})
})
}
// ... other scripts ...
window.customSections = [/* server stuff */];
renderServerContent();
</script>
Basically, I have some stuff to dynamically render from the server, so I send a request asking for component rendered data and when I get the answer-back, I check if the inserted content contains an element with .needs-to-be-dropzoned, if so, I assign it a time-based ID (component rendering has a delay between each component so the IDs are unique) and instance the dropzone. This works well with just one dropzone, but when multiple are on the same page it won't generate errors but will work for just the last element on the page.
The server content is something like this:
<form
class="col-12 container-fluid pb-2 dropzone needs-to-be-dropzoned"
method="POST"
action="/imageUploadUrl"
>
<input type="hidden" name="_token" value="..." >
<div class="dz-default dz-message">
Upload files
</div>
<div class="fallback">
<input name=image" type="file" />
</div>
</form>
If I launch a console.dir(window.dropzones) I get 2 objects:
Object { dropzone_filled_1624370363574: {…}, dropzone_filled_1624370363803: {…} }
​
dropzone_filled_1624370363574: Object { element: form#dropzone_filled_1624370363574.col-12.container-fluid.pb-2.dropzone.needs-to-be-dropzoned.dz-clickable, version: "5.9.2", clickableElements: (1) […], … }
​
dropzone_filled_1624370363803: Object { element: form#dropzone_filled_1624370363803.col-12.container-fluid.pb-2.dropzone.needs-to-be-dropzoned.dz-clickable, version: "5.9.2", clickableElements: (1) […], … }
What am I doing wrong or missing?

I did various attempts to fix this, and finally, I fixed it by wrapping all server calls into promises, waited for all promises to solve and only after that, I checked for dropzones. The code is approximately this:
<script src="/js/dropzone.min.js"></script>
<script>
Dropzone.autoDiscover = false;
window.dropzones = {};
window.containersToCheck = [];
function checkDropzones(container) {
var possibleDropzones = container.querySelectorAll('.needs-to-be-dropzoned');
possibleDropzones.forEach(function (zone) {
if (zone.id.length === 0) {
zone.id = 'dropzone_filled_' + (new Date()).getTime();
window.dropzones[zone.id] = new Dropzone(
'#' + zone.id,
{
paramName: 'image',
addRemoveLinks: true,
}
);
}
})
}
function renderServerContent() {
return new Promise(function(resolve, reject) {
window.customSections.forEach(function (custom_section) {
var container = document.getElementById(custom_section);
$.ajax({
method: 'GET',
url: '/customRenderUrl?section=' + custom_section,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
success: function (response) {
container.innerHTML = container.innerHTML + response.component;
if (response.component_type === 'image_uploader') {
window.containersToCheck.push(container);
}
resolve();
// ... other marginal stuff ...
},
dataType: 'json'
})
})
});
}
// ... other scripts ...
window.customSections = [/* server stuff */];
var promises = [];
promises.push(renderServerContent());
// other renderings
Promise.all(promises).then(function (results) {
window.containersToCheck.forEach(function(container) {
checkDropzones(container);
});
})
</script>
This way, all Dropzones work.

Related

Why is the ajax request not executed?

Explain what is wrong here. First, the run function and its ajax request must be executed. But for some reason the function is executed, and the ajax request is not. It runs right at the very end of the script - after all the functions ... Why is this happening and how to fix it?..
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form id="id_product_create_form" method="post" enctype="multipart/form-data"></form>
<div class="media_preview_wrap">
<div class="addPhoto">
<div class="addPhotoHeader">
<button type="button" class="button product_images_button">Add</button>
<button id="id_submit" type="button">Submit</button>
</div>
</div>
</div>
<input type="file" name="image" style="display: none" required="" class="product_images" id="">
<script>
var files = [];
$('.product_images_button').click(function() {
$('.product_images').click();
});
$('.product_images').change(function() {
handleFiles(this);
});
$('.media_preview_wrap').on('click', '.thumb', function() {
removeFile($(this).data('id'));
});
$('#id_submit').click(function() {
event.preventDefault();
var $form = $('form'),
formdata = new FormData($form[0]),
$button = $('#id_submit');
formdata.append('content', CKEDITOR.instances.id_content.getData());
function run() {
var product_id = null;
$.ajax($form.attr('action'),{
type: 'POST',
data: formdata,
processData: false,
contentType: false,
success: function(data) {
product_id = data.product_id;
}, error: function(error) {
console.log(error)
}
});
return product_id}
product_id = run();
files.forEach(function(file, index) {
var data = new FormData();
data.append('name', file.name);
data.append('gallery_image', file.file);
uploadFile(event.target.action, data)
.done(function(response) {
removeFile(file.id);
})
.fail(function(error) {
console.log(error);
});
});
});
function handleFiles(input) {
var URL = window.URL || window.webkitURL;
var uniqueId = (new Date()).getTime()
for (var i = 0; i < input.files.length; i++) {
var file = input.files[i];
if (file && file.type.startsWith('image/')) {
uniqueId++;
files.push({
id: uniqueId,
file: file,
name: file.name // задел для возможности переименования файла.
});
var img = $('<img src="'+ URL.createObjectURL(file) +'" class="thumb" data-id="'+ uniqueId +'">');
$('.media_preview_wrap').append(img);
img.on('load', function() {
URL.revokeObjectURL(this.src);
});
}
}
$(input).val('');
}
function removeFile(id) {
files = files.filter(function(file) {
return id !== file.id;
})
$('img[data-id="'+ id +'"]').remove();
}
function uploadFile(url, data) {
return $.ajax({
headers: {'X-CSRFToken': '{{ csrf_token }}' },
type: 'POST',
url: url,
data: data,
processData: false,
contentType: false,
cache: false
});
}
</script>
<style>
.thumb {
width: 150px;
height: auto;
opacity: 0.9;
cursor: pointer;
}
.thumb:hover {
opacity: 1;
}
.product_images {
display: none;
}
</style>
The initial problem is likely due to some browsers having a global event object while others don't.
You are likely getting an error that event is undefined and that would prevent the remaining code to run
Use the argument of the event handler function which always passes in an event object:
$('#id_submit').click(function(event) {
// ^^^
event.preventDefault();
Once that issue is solved... you need to realize that $.ajax is asynchronous and you can't use the new value of product_id until first request completes in the success callback
See How do I return the response from an asynchronous call?

HTTP Error 415 when sending POST request with JSON data from HTML to Java Rest web service

I am trying to send the data from HTML form inside index.html to Java REST web service as a JSON data, using javascript and ajax. I tried whatever I found online so far, and still every time I get HTTP error 415.
I know the meaning of that error but can't figure out where I am wrong as I am not yet that much familiar with javascript.
Here is the code:
index.html
div style="text-align:center">
<form action="http://localhost/MyTaskTest/servicea" method="post"
id="test">
<fieldset>
<legend>Add to Your balance</legend>
<label for="amount">Money amount</label>
<input type="text" name="Amount" /> <br/>
<label for="currency">Select currency</label>
<select name="Currency">
<option value="eur">EUR</option>
</select>
<input type="submit" value="Add">
</fieldset>
</form>
</div>
<script
src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js">
</script>
<script>
(function() {
function toJSONString( form ) {
var obj = {};
var elements = form.querySelectorAll( "input, select" );
for( var i = 0; i < elements.length; ++i ) {
var element = elements[i];
var name = element.name;
var value = element.value;
if( name ) {
obj[ name ] = value;
}
}
return JSON.stringify( obj );
}
window.onload = function() {
var form = document.getElementById("test");
var output = document.getElementById("output");
form.onsubmit = function( e ) {
e.preventDefault();
var json = toJSONString( this );
console.log(json);
console.log("TEST");
$.ajax({
url: form.getAttribute( 'action' ),
headers: {
'Accept': 'application/json',
'Content-Type': "application/json; charset=utf-8"
},
type: 'POST',
data: json,
success: function(data) {
alert("data saved")
console.log("SUCCESS");
},
error: function() {
console.log("ERROR");
console.log(errorThrown);
}
})
};
}
})();
</script>
And here is the REST service:
#Path("/")
public class ServiceA {
#POST
#Path("/servicea")
//#Consumes(MediaType.APPLICATION_JSON)
public Response postRequest(String obj) {
String res = "hii";
return Response.status(200).entity(obj).build();
}
EDIT
I edited my code...now it is working. but why do I need to set argument to String instead of JSONObject and remove #Consumes? In console I get this data from the variable that I send in ajax: {"Amount":"213","Currency":"eur"} ... So I am sending JSON,but service only works if argument is String.
FINAL EDIT
I managed to fix the problem, I was missing dependency for jersey-json. Thanks :)
There are some errors in your code, try to debug it in chrome developer tools. I have made some changes and it is works
(function() {
function toJSONString( form ) {
var obj = {};
var elements = form.querySelectorAll( "input, select" );
for( var i = 0; i < elements.length; ++i ) {
var element = elements[i];
var name = element.name;
var value = element.value;
if( name ) {
obj[ name ] = value;
}
}
return JSON.stringify( obj );
}
window.onload = function() {
var form = document.getElementById("test");
var output = document.getElementById("output");
form.onsubmit = function( e ) {
e.preventDefault();
var json = toJSONString( this );
console.log(json);
$.ajax({
url: form.getAttribute( 'action' ),
headers: {
'Accept': 'application/json',
'Content-Type': "application/json; charset=utf-8"
},
type: 'POST',
data: json,
success: function(data) {
alert("data saved")
},
error: function() {
console.log(errorThrown);
}
})
};
}
})();
try to add
dataType: "json",
contentType : "application/json"
to your $.ajax method
$.ajax({
url: $form.attr( 'action' ),
dataType: "json",
contentType : "application/json"
type: 'POST',
data: json,
success: function(data) {
alert("data saved")
},
error: function() {
console.log(errorThrown);
}
})
also You had ; in your url field in $.ajax method - so better to remove it ;)
Also its always better to check if service works corectly using for example PostMan so You are sure its not service side, I dont know what framework did You used, but You can also try to replace
public Response postRequest(JSONObject object) {
with
public Response postRequest(String object) {
to check if there is not a problem with mapping

Evolutive string substitution

I builded a JQuery component to make a dropdown-accordion element. Each time i click on a item in a accordion, it will save the selection, close this accordion andopen the next one if exists.
To add data, it work as it :
$(window).ready(function () {
$(selector).data('dropdownaccordion').enqueueAccordion({ // Ajax parameter
name: "Commune",
data: {
url: "/trends/list",
method: 'GET',
datatype: "json",
timeout: 5000
}
},
{
name: "Batiment",
data: {
url: "/trends/list/"+this.path[0], // this.path[0] refer to the first selection, as this.path[1] will refer to the second etc
method: 'GET',
datatype: "json",
timeout: 5000
}
},
{ //Note that we can pass array instead of Ajax parameter
name: "Dummy",
data: ["One", "Two", "Three"]
})
});
I designed the component to work good with Rest API.
The problem is exposed for the second element i added. As you can see, i'm trying to pass this.path[0], refering to the first item selected. But the property path doesn't exist in the $(windows).ready scope, but in the $(selector).data('dropdownaccordion') scope.
I can replace it by $(selector).data('dropdownaccordion').path[0], but i want something more global, like ExpressJS do in URL (http://www.dummy.com/path/:item1/:item2 and replacing :itemx by the user selection)
Do you know a better solution, and if not, do you know a way to make it good ?
EDIT
My Component is designed as follow :
+function ($) {
'use strict';
/**
* Dropdown_Accordion CLASS DEFINITION
*/
function DropdownAccordion(component, ...accordions) {
this.accordions = [];
for(var i=0;i<accordions.length;i++){
this.accordions.push(accordions[i])
}
...
this.component = $(component);
...
// Render the accordions as DOM
this.render.call(this);
//Events
this.component
.on("click", ".accordion-item-body", $.proxy(function(event){
this.path.push($(event.target).text())
this.component.trigger('click.selection.dropdownaccordion', [$(event.target)])
this.openNext(event);
}, this))
.on('show.bs.dropdown', $.proxy(this.showMenu, this))
.on('shown.bs.dropdown', $.proxy(this.openNext, this))
.on('hide.bs.dropdown', $.proxy(function (event) {
this.component.find('.dropdown-menu').first().stop(true, true).slideUp();
this.component.find(".accordion-item .panel-collapse.collapse").each(function(){
$(this).collapse('hide');
});
this.opened = null;
(this.path.length !== this.accordions.length) ? this.component.trigger("abort.selection.dropdownaccordion", [this.path]):null
this.path = []
}, this));
}
DropdownAccordion.prototype = {
constructor: DropdownAccordion,
// Collapse accordion every time dropdown is shown
showMenu: function () {
// show the accordion
},
openNext: function (clickEvent) {
// Open the next accordion
},
render: function () {
// Will render the dropdown-accordion
// The following is how the AJAX request is performed
...
// When the accordion is opened, make the ajax request
DOMaccordion.on("open.accordion.dropdownaccordion", function () {
// The DOM element contains in data the options for the ajax request, or nothing if the accordion is an array
**$.ajax($(this).data('content.accordion.dropdownaccordion'))**
});
...
}
},
enqueueAccordion: function (...accordions) {
for(var i=0; i<accordions.length;i++)
this.accordions.push(accordions[i]);
this.render()
},
dequeueAccordion: function (name) {
this.accordions = this.accordions.filter(function (obj) {
return obj.name !== name;
})
this.render()
}
}
Look at the ** ** in the code, this is where the Ajax options is stored. I want that when i click on a item, there is a substitution on the .url to include the first item clicked.
I hope this seems clearer to you.
You can let enqueueAccordion accept a callback and pass the current accordion as a parameter:
DropdownAccordion.prototype = {
// ...
enqueueAccordion: function (makeAccordions) {
var accordions = makeAccordions(this);
for (var i = 0; i < accordions.length; i++)
this.accordions.push(accordions[i]);
this.render();
}
}
$(selector).data('dropdownaccordion').enqueueAccordion(function (_this) {
return [
{
name: "Batiment",
data: {
url: "/trends/list/" + _this.path[0],
method: 'GET',
datatype: "json",
timeout: 5000
}
];
});
If you want to use this directly, you can temporarily attach the callback to the current accordion:
DropdownAccordion.prototype = {
// ...
enqueueAccordion: function (makeAccordions) {
this.makeAccordions = makeAccordions;
var accordions = this.makeAccordions();
delete this.makeAccordions;
for (var i = 0; i < accordions.length; i++)
this.accordions.push(accordions[i]);
this.render();
}
}
$(selector).data('dropdownaccordion').enqueueAccordion(function () {
return [ // Ajax parameters
{
name: "Batiment",
data: {
url: "/trends/list/" + this.path[0],
method: 'GET',
datatype: "json",
timeout: 5000
}
}
];
});
I dug through the expressjs code, and found how they do the URL substituion. It work with the path-to-regex module. It seems not to be developped for browserside so i had to found another solution.
#aaron inspired me, and i finally add the possibility to have a callback in place of the url :
{
name: "Capteur",
data: {
url: function (path) {
return "/trends/list/" + path[0] + "/" + path[1]
},
method: 'GET',
datatype: "json",
timeout: 5000
}
}
By doing this, people are now able to modify the path based on the previous selection.

AngularJS forEach not working on $http response

Getting the values in response as:
[{
"Pf": "something1",
"label": ""
}, {
"Pf": "something1",
"label": ""
}]
JS
$scope.display = false;
$scope.getPanel = $http({
mode: 'cors',
method: 'GET',
url: '/url/',
headers: {
'Content-Type': 'application/json'
}
}).success(function(response) {
$scope.original = function() {
angular.forEach(response, function(value, key) {
if (value == "something1") {
dispaly = false;
} else if (value == "something2") {
display = false;
} else {
display = true;
}
});
return display;
};
});
HTML
<td>
<img src="image.png" uib-tooltip="{{status}}" ng-show="original()"/>
</td>
not getting key and value from response
First of all, you do have an error in if display writing dispaly instead display.
Secondly, the angular foreach second argument is a function taking as first argument the iterator on your collection. Here, your collection is composed of two object, so on each iteration, value will be an object , for example :
{
"Pf": "something1",
"label": ""
}
So, if you want to test if it is equal to "something1", you have to compare it with value.pf and not value.
You also need to call response.data because response is a structure that stores different informations about your request answer.
To finish, i advice you to store the value display in your scope and bind it on ng-show directive like this :
JS
$scope.display = false;
$http({
mode: 'cors',
method: 'GET',
url: '/url/',
headers: {
'Content-Type': 'application/json'
}
}).success(function(response) {
angular.forEach(response.data, function(value, key) {
if (value.pf == "something1") {
$scope.display= false;
} else if (value.pf == "something2") {
display = false;
} else {
display = true;
}
});
});
HTML
<td>
<img src="image.png" uib-tooltip="{{status}}" ng-show="display"/>
</td>
Hope it helps !
Note that display get overwritten on each loop. Ensure your logic is correct. While display is an $scope variable you need to access this param by using $scope.display instead of display. Your codes should look like this:
View
<div class="container" ng-controller="ApplicationController">
<h1>Output: {{ display }}</h1>
<div ng-show="display">
Now I'm here
</div>
</div>
AngularJS application
var app = angular.module('plunker', []);
app.controller('ApplicationController', function($scope, $http) {
$scope.display = false;
$scope.getPanel = $http({
mode: 'cors',
method: 'GET',
url: './data.json',
headers: {
'Content-Type': 'application/json'
}
}).success(function(response) {
original(response);
});
function original (response) {
angular.forEach(response, function(value, key) {
if (value.Pf == "something1") {
$scope.display = false;
} else if (value.Pf == "something2") {
$scope.display = false;
} else {
$scope.display = true;
}
});
console.log($scope.display);
};
});
--> Demo plnkr

How to refresh div after AJAX request in MVC?

I'm not sure how to refresh data after I use AJAX. Here's what I already have:
Frontend:
#model TFU.Model.DB_USER
<div id="listTelNumbers">
#foreach (var item in Model.DB_USER_PHONES)
{
<dl class="dl-horizontal">
<dt>
#item.PHONE
</dt>
<dd>
<button id="removeButton" class="btn btn-default" onclick="sendRequestToRemove('#item.USER_ID', '#item.PHONE')">Usuń</button>
</dd>
</dl>
}
</div>
Script - fadeOut works fine but I don't know what should I fadeIn. So I guess is missing a small part of code there.
Also how can I fadeOut only the record which I currently removing instead all list.
<script>
function sendRequestToRemove(id, phone) {
var data = {
"USER_ID": id,
"PHONE": phone
}
$.ajax({
url: '/User/RemoveTelNumber',
data: JSON.stringify(data),
type: 'POST',
contentType: 'application/json; charset=utf-8',
error: function (err) {
alert('Error: ' + err.statusText);
},
success: function (result) {
$('#listTelNumbers').fadeOut(800, function () {
form.html('#listTelNumbers').fadeIn().delay(2000);
});
},
async: true,
processData: false
});
}
</script>
Backend:
public bool RemoveTelNumber(DB_USER_PHONES model)
{
var user = db.DB_USER_PHONES.First(x => x.USER_ID == model.USER_ID && x.PHONE == model.PHONE);
db.DB_USER_PHONES.Remove(user);
db.SaveChanges();
return true;
}
Firstly, your loop is generating duplicating invalid html because of the duplicate id attributes. And you should not be polluting your markup with behavior - use Unobtrusive JavaScript. Change the html to remove the id attribute, add a class name for selection and add data-* attributes for the values to be posted
#foreach (var item in Model.DB_USER_PHONES)
{
<dl class="dl-horizontal">
<dt>#item.PHONE</dt>
<dd><button class="btn btn-default delete" data-id="#item.USER_ID" data-phone="#item.PHONE">Usuń</button></dd>
</dl>
}
Then change the script to
var url = '#Url.Action("RemoveTelNumber", "User")'; // always use Url.Action()
$('.delete').click(function() {
var container = $(this).closest('dl');
var data = { user_Id: $(this).data('id'), phone: $(this).data('phone') };
$.post(url, data, function(response) {
if (response) {
// fadeout, then remove
container.fadeOut(800, function() { $(this).remove(); })
} else {
// Oops - display an error message?
}
}).fail(function() {
// Oops
});
});
And finally, change the action method to return a JsonResult indicating success or otherwise
[HttpPost]
public JsonResult RemoveTelNumber(DB_USER_PHONES model)
{
var user = db.DB_USER_PHONES.First(x => x.USER_ID == model.USER_ID && x.PHONE == model.PHONE);
db.DB_USER_PHONES.Remove(user);
db.SaveChanges();
return Json(true);
// or if something went wrong, return Json(null);
}
I also recommend you rename you classes and properties to follow conventional naming practices - UserPhone, not DB_USER_PHONES, UserId, not USER_ID etc.
Partially reload that div
$("#listTelNumbers").load(location.href + " #dl-horizontal");
Or reload the entire page w/o refreshing it
$(document.body).load(location.href);
For a complete reference I found a demo here Partially load a div without refreshing page in javascript and php.
You can use jQuery to find the <dt> element which was marked for deletion and fade it out(or remove it completely from the DOM):
$.ajax({
url: '/User/RemoveTelNumber',
data: JSON.stringify(data),
type: 'POST',
contentType: 'application/json; charset=utf-8',
error: function (err) {
alert('Error: ' + err.statusText);
},
success: function (result) {
var dtCollection = $("dt");
for (var i = 0; i < dtCollection.length; i++) {
var text = $(dtCollection[i]).text();
text = text.trim();
if (text == phone) {
$(dtCollection[i]).parent().fadeOut('slow');
//$(dtCollection[i]).parent().remove();
}
}
},
async: true,
processData: false
});

Categories