Restrict multiple image within div with JS/Jquery/CSS - javascript

On my webpage there are Gridster widgets.In these widgets initially the images are displayed from JSON(the name of image comes from JSON which I then put in src of image)
The users can also add images by clicking + button.User can also delete an image by clicking X button on the image.
The Problem I am facing
When the images coming from JSON are more or when the user manually adds more images then the images go out of widgets.
My Desired Output
Now I was trying to restrict those images in widget such that images will lay only in boundaries of div.
When there are more images the other existing images will resize and all of the images will fit in that area.
When I delete an image the other images will get bigger.In any case the entire area will be occupied by the images.
JS:
//JSON which I get from backend
var json = [{
"html": "https://d30y9cdsu7xlg0.cloudfront.net/png/802768-200.png,https://d30y9cdsu7xlg0.cloudfront.net/png/802768-200.png,https://d30y9cdsu7xlg0.cloudfront.net/png/802768-200.png", //3 Images
"col": 1,
"row": 1,
"size_y": 2,
"size_x": 2
}
];
//Loop which runs over JSON to generate <li> elements in HTML
for (var index = 0; index < json.length; index++) {
var images = json[index].html.split(',');
var imageOutput = "";
for (var j = 0; j < images.length; j++) {
imageOutput += '<div class="imagewrap"><img src=' + images[j] + '> <input type="button" class="removediv" value="X" /></div></div>';
}
gridster.add_widget('<li class="new" ><button class="addmorebrands" style="float: left;">+</button><button class="delete-widget-button" style="float: right;">-</button>' + imageOutput + '<textarea>' + json[index].html + '</textarea></li>', json[index].size_x, json[index].size_y, json[index].col, json[index].row);
}
//Function to delete an image from widget
$(document).on('click', '.removediv', function() {
$(this).closest('div.imagewrap').siblings('textarea')
$(this).closest('div.imagewrap').remove();
});
//Function to delete a widget
$(document).on("click", ".delete-widget-button", function() {
var gridster = $(".gridster ul").gridster().data('gridster');
gridster.remove_widget($(this).parent());
});
//Function to add mode Images to widgets from Modal
var parentLI;
$(document).on("click", ".addmorebrands", function() {
parentLI = $(this).closest('li');
$('#exampleModalCenter').modal('show');
$('#exampleModalCenter img').click(function() {
$(this).addClass('preselect');
$(this).siblings().removeClass('preselect');
selectedImageSRC = $(this).attr('src');
})
});
$('#add-image').click(function() {
parentLI.append('<div class="imagewrap"><img src="' + selectedImageSRC + '"> <input type="button" class="removediv" value="X" /></div>');
parentLI.children('textarea').append(', ' + selectedImageSRC);
$('#exampleModalCenter').modal('hide');
})
HTML
<div class="gridster">
<!-- <li> from JSON are placed here images are a part of li -->
<ul>
</ul>
</div>
The Fiddle so far
I am not sure if this can achieved just with CSS or will require any JS along with that
Update 1
I have tried with a lot of different CSS but still not able to get the expected output so if someone can help me with it would be really helpful

Maybe Gridster has a built in way to arrange items inside the grid cells, in case you have not found a way yet, try this.
I added some css:
.image-wrap-container{
min-height: 70%
}
.image-wrap-container div.imagewrap{
width: 33%
}
.text-area-wrap{
bottom: 0px;
left: 0px;
margin: 0px;
padding: 0px;
width: 100%;
height: 30%;
display: inline-flex;
}
.new.gs-w{
width: 200px;
min-height: 200px
}
.addmorebrands{
position: absolute;
left:0
}
.delete-widget-button{
position: absolute;
right:0
}
and restructured a little bit your html so images fit good within the cell, I hope that does not break anything, javascript was the least modified, only to add the images according to the new html structure.
Note: I tried to make the lis' height adjust to the amount of elements it contains, but [data-sizey="2"] kept getting in my way, so before throwing some probably unnecessary hack on it, try and achieve that using the library's own options, good luck.
Also, I noticed you were using this to update your textareas:
parentLI.children('.imagenames').val(function(i, selectedImageSRC) {return selectedImageSRC + ', '});
which won't work because you are using the same name for the argument, conflicting with the original selectedImageSRC variable. In case you are still having problems in that front, I replaced it with:
parentLI.children('.imagenames').val(function(i, currentContent) {return currentContent + ',' + selectedImageSRC + ', '});
Bonus Feature
The buttons for removing an image were to big for the images and covered quite a big part, so I took the liberty:
.removediv{
visibility: hidden
}
.imagewrap:hover .removediv{
visibility: visible
}
hope it helps

Related

Is It Possible for this Script to be Generalised?

I'm very new to jQuery with little programming experience so please take that into consideration.
I created a timesaving script that will take the following two variables:
1) An element (which contains a single image) - imgelement
2) An image URL for the hover image - hoverimageurl
Code:
/* Image Hover Script (Start) */
var imgelement = "#element"; /* Element containing the original image */
var hoverimageurl = "http://www.domain.com/image.png2"; /* Image URL of the hover image */
jQuery(document).ready(function() {
/* Add CSS and a class to the original image to fix positioning and give it an identification */
jQuery(imgelement + " img").addClass("originalimage");
/* Prepend hover image to the element. Set the SRC to the hover image URL */
jQuery(imgelement).prepend('<img class="hoverimage" src="' + hoverimageurl + '">');
/* Fade out original image and fade in hover image on hover */
jQuery(imgelement).hover(function() {
jQuery(imgelement + " .originalimage").stop(true).fadeTo(1000, 0);
jQuery(imgelement + " .hoverimage").stop(true).fadeTo(1000, 1);
}, function() {
jQuery(imgelement + " .hoverimage").stop(true).fadeTo(1000, 0);
jQuery(imgelement + " .originalimage").stop(true).fadeTo(1000, 1);
});
});
/* Image Hover Script (End) */
/* Image Hover CSS (Start) */
#pix-fe .originalimage {
position: relative;
}
#pix-fe .hoverimage {
position: absolute;
width: 100%;
height: 100%;
}
/* Image Hover CSS (End) */
<div class="element">
<img src="http://www.domain.com/image1.png">
</div>
What the script does is fade to the hover image given from the hoverimageurl variable when the element is hovered. This works perfectly but I will want to use multiple instances of this script and to do so I would need to append the variable names with an incrementing number for each instance that I require. This is inefficient because the bulk of the script will need to be repeated per instance when ideally I would just like a variable list and one instance of the main script.
Is there any way at all that I can achieve generalisation of this code? I am aware of this but imgelement will always refer to the value against the imgelement variable specifically so with my knowledge I cannot see how this can be done.
Thank you very much for your time.
Nothing that little css can't handle :)
.image-switch {
position: relative;
}
.image-switch img {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
opacity: 1;
transition: opacity 1s
}
.image-switch:hover img:last-child{
opacity: 0
}
<span class="image-switch">
<img src="http://placehold.it/200x200/FF0000">
<img src="http://placehold.it/200x200">
</span>
You should really do something like this using css as it will be more reliable than js and easier to understand. That said this is a good opportunity to learn about jQuery plugins and how you can utilize it to make your code more DRY and reusable.
To add a new method to the jQuery object you can declare a function on jQuery.fn which is it's prototype.
in your function this will be bound to the collection of elements that you have selected, so you need to call each over it if you are planning on making the api work on collections.
From there you simply need to add your functionality to the function. Any arguments will be passed into your function so you can pass in settings at runtime. here it is used to pass in the img url.
The other basic function I used was jQuery.fn.find as we have the reference element that we can traverse the dom from, rather than using strings which are more confusing to my eyes.
To run the plugin you simply need to make a jQuery selector and call your new method. $('#selector').hoverImage('url')
Now that you know the basics of creating a jQuery plugin, you should really figure out how to achieve the same outcome with pure css.
/*
* jQuery hoverImage plugin
*
* #param {string} url url of the hover image
*/
(function() {
jQuery.fn.hoverImage = function(url) {
// loop over the element collection
this.each(function() {
// bind your functionality
var $this = $(this)
var $img = $this.find('img')
var $hover = $('<img class="hoverimage" src="' + url + '">')
$this.prepend($hover)
$this.hover(function() {
$img.stop().fadeTo(1000,0)
$hover.stop().fadeTo(1000,1)
}, function() {
$img.stop().fadeTo(1000,1)
$hover.stop().fadeTo(1000,0)
})
})
}
})()
// run your plugin
$('#element').hoverImage('http://lorempixel.com/400/200/')
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="element"></div>

reinit flickity after page update by ajax call

I'm giving flickity by metafizzy a try. Working great but after updating the page the newly loaded gallery won't function. How can I reinitialize flickity after ajax load?
I use the js-flickity class to initialize the script.
<div class="gallery js-flickity">
...
</div>
I know it's a bit too late, but I'll post it anyway as it might help someone else.
Haven't tried the resize solution submitted above, but this is how I did it.
After you append your elements to your container, look for the js-flickity elements, see if you can get the object data using data method, and if it's undefined initialise flickity on that element.
var nodeList = document.querySelectorAll('.js-flickity');
for (var i = 0, t = nodeList.length; i < t; i++) {
var flkty = Flickity.data(nodeList[i]);
if (!flkty) {
new Flickity(nodeList[i]);
}
}
Update:
Noticed that there's still quite few people still looking at this question. So here's an updated solution, that also supports flickity carousel options defined in data attribute (optional).
var nodeList = document.querySelectorAll('.js-flickity');
for (var i = 0, t = nodeList.length; i < t; i++) {
var flkty = Flickity.data(nodeList[i]);
if (!flkty) {
// Check if element had flickity options specified in data attribute.
var flktyData = nodeList[i].getAttribute('data-flickity');
if (flktyData) {
var flktyOptions = JSON.parse(flktyData);
new Flickity(nodeList[i], flktyOptions);
} else {
new Flickity(nodeList[i]);
}
}
}
You can do it like this-
//Destroy
$carousel.flickity('destroy');
//Re-init
$carousel.flickity();
Full example code-
var $carousel = $('.carousel').flickity();
var isFlickity = true;
// toggle Flickity on/off
$('.button--toggle').on( 'click', function()
{
//Clearing previous contents
$carousel.flickity('destroy');
$('.carousel').empty();
$(".carousel").append('<div class="carousel-cell"></div>');
$(".carousel").append('<div class="carousel-cell"></div>');
// init new Flickity
$carousel.flickity();
});
.carousel
{
width: 100%;
height: 200px;
}
.carousel-cell {
width: 66%;
height: 200px;
margin-right: 10px;
margin-bottom: 10px;
background: #8C8;
border-radius: 5px;
counter-increment: carousel-cell;
}
.flickity-enabled .carousel-cell {
margin-bottom: 0;
}
/* cell number */
.carousel-cell:before {
display: block;
text-align: center;
content: counter(carousel-cell);
line-height: 200px;
font-size: 80px;
color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/flickity/2.0.5/flickity.pkgd.min.js" type="text/javascript" charset="utf-8"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/flickity/2.0.5/flickity.min.css">
<button class="button button--toggle">Re Create Flickity</button>
<div class="carousel">
<div class="carousel-cell"></div>
<div class="carousel-cell"></div>
<div class="carousel-cell"></div>
<div class="carousel-cell"></div>
<div class="carousel-cell"></div>
</div>
Try calling resize after the content has loaded:
.flickity('resize');
Happened to me and I figured out you have to make sure you Select an Element or it will just show the last one without functioning.
Do something like this where selectIndex is the slide you want selected by default:
$carousel.flickity('selectCell', selectIndex, true, true);
Here's my solution: on success, we pass in the data response from the AJAX request, then I empty the carousel wrapper (an external div that wraps the whole carousel), and then append the product carousel which initially it'll be empty. Then I append each product through a for loop inside this product carousel. On complete, I load the carousel options, then I create the Flickity object asigned to the carousel, no need to run any other scripts for loading the carousel.
success: function(data) {
let productsCarousel = '<div id="productsCarousel" class="products-slider carousel" products-carousel></div>';
$("#productsWrapper").empty().append(productsCarousel);
for (let i = 0; i < data.products.length; i++) {
// Carousel logic
let foundProduct = '<div class="...classes here..."> <div class="carousel-cell carousel-cell-centered"> ... data.products[i] and other HTML here ...</div></div>';
$("#productsCarousel").append(foundProduct);
}
},
complete: function() {
// Products slider options
var productOptions = {
imagesLoaded: true,
wrapAround: true,
autoPlay: 5000,
prevNextButtons: true,
pageDots: false,
};
// Products carousel setup
var productsCarousel = document.querySelector('#productsCarousel');
var productsFlickity = new Flickity(productsCarousel, productOptions);
Also had problems with this and this post helped me resolve issue for Vanilla JS. Vanilla JS solution - 'reinitializing' Flickity carousel after dynamically cleared and reloaded data and without removing Flickity carousel / reloading page)
function reloadCarousel(data) {
var oldCells = document.getElementsByClassName("carousel-cells");
yourFlickityCarousel.remove(oldCells); // takes an array of elements
for (var i = 0; i < data.length; i++) {
var newCell = processData(data[i]);
yourFlickityCarousel.append(newCell);
}
// this makes sure viewport realigns to first item
// in doing so, normal carousel functionality is restored
flkty.selectCell(0, true);
}
Had a very similar problem problem as well - using Vanilla JS I needed to clear out a a Flickity carousel and repopulate it with new or modified cells via JSON via AJAX Load (some may be edited, others may be deleted, may also have new cells, etc)
Issue was after clearing out the Carousel via flkty.remove() the scrolling was 'stuck' on a single slide. Seems even though the slides were re-appended Flickity still thought 'size' of carousel was the same after the remove. (Could pull it but would always bounce back to same single view).
This ended up working as a good way to just 'clear out existing carousel and refresh it'
source: https://flickity.metafizzy.co/api.html#remove

jquery .append iframe animation

I am trying to write a page that allows multiple buttons to be clicked on within my webpage and for iframes to load the content sliding down and knocking other content further down as they load.
I have tried various different ways to make this scroll up and down by using the jquery .animate functions but for some reason I just cannot get it to work.
Does anyone have any suggestions?
$(document).ready(function() {
$(".iframe").click(function() {
var file = $(this).attr("data-url");
var size = $(this).attr("data-size");
$(this).parent('article').append("<iframe class='articleframe' height='" + size + "' src='" + file + "'>Moo</iframe>");
$(this).siblings('.open').css('display','inline-block');
$(this).hide();
});
$(".open").click(function() {
$(this).hide();
$(this).siblings('.iframe').css('display','inline-block');
$(this).siblings('.articleframe').remove();
});
});
<article>
<h2>Querybox</h2>
<h5 class="button iframe" data-url="http://www.bbc.co.uk" data-size="900px" >Load another file</h5>
<h5 class='button open'>Hide Frame</h5>
</article>
For those of a visual dependence :) fiddlywiddly
You may have to tweak this to your specifications but this will do the trick:
Javascript:
Change '.append()' to '.prepend()'
$(document).ready(function () {
$(".iframe").click(function () {
var file = $(this).data("url");
var size = $(this).data("size");
var $one = $('#one');
$one.prepend("<h5 class='button open'>Hide Frame</h5><div class='frame-contain'><iframe class='articleframe' height=0 src='" + file + "'></iframe></div>");
var $wrap = $one.find('.frame-contain:first');
$wrap.animate({
height: size
}, {
duration: 1000,
step: function (now) {
$(this).height(now);
$(this).nextAll('.frame-contain').offsetParent().top += now;
$(this).find('iframe').height(now);
},
complete: function () {
$(".open").click(function () {
$(this).next('div').slideUp(1000);
$(this).fadeOut('slow');
});
}
});
});
});
I would also suggest re-arranging your elements but that's up to you. You can put your <h5> buttons into a <header> and the <iframe> creations into a <section>. Food for thought. That way your buttons don't go flying all over the place. My example utilizes this idea.
HTML:
<article>
<header>
<h2>Querybox</h2>
<h5 class="button iframe" data-url="http://www.bbc.co.uk" data-size="900">Load another file</h5>
</header>
<section id="one"></section>
</article>
CSS:
iframe, header, section, div {
clear: left;
position: relative;
display: block;
}
section, div {
width: 100%;
max-height: 100%;
}
.open {
clear: left;
display: block;
}
.articleframe {
float: left;
}
Since I am uncertain of your end-game here I made my best interpretation. Keep in mind this code is horribly inefficient the way it is set-up (so many selectors utilizing jQuery is very code intensive).
I would suggest you put more id tags on things and reference those using $(document.getElementById('id')) or $(document.querySelector('#id')). Also, using jQuery .width() and .height() takes a huge hit as well. You should use a javascript native selector and then apply .innerWidth = XX or .innerHeight = XX.
Normally performance at this small of scale isn't hugely important but given what you're displaying and the impact it has on the browser, it may help you.
For examples on the performance gain: JSPerf - innerHeight vs. .height()

Using Javascript and CSS to align left to right

I have this line of javascript which creates a listing of store locations, and creates a DIV beneath the map. The items are displayed on the page top to bottom.
I would like to display the items as 3 in a row, left to right, top to bottom.
The function is this:
function createSidebarEntry(marker, name, address, city, state, zipcode, telephone, images, url) {
var div = document.createElement('div');
var html = "<p><a href='http://" + url + "'>" + name + "</a><br/>" + address + "<br/>" + city + ", " + state + " " + zipcode + "<br/>" + (formatPhone(telephone)) + "</p>";
div.innerHTML = html;
div.style.marginBottom = '5px';
return div;
}
As a side bar question, would tables be the preferred method?
I have tried to set the DIV as:
#sidebar {
text-align: left;
margin: 0px auto;
padding: 0px;
border:0;
width:665px;
font-size:10pt;
font-family:Arial;
color:#656668;
display: table-cell;
}
And unfortunately after working with margins, etc, I have seemed to run out of luck. Has anyone been able to use dynamic returned data and apply formatting with CSS in three columns? I have been googling and everything I see points me to creating three column styles within my DIV container.
try setting the parent element with a fixed width and apply float to the childs and a width of 33% for them. Don't forget to use a clear afterwards.
I would suggest creating your elements with jquery. It's alot easier and more elegant.
But the root of you issue is that you need to add a float:left; style to your div. This will put all your divs in the same row.
Oh, and try to use tables as little as possible to layout out elements on your page. Divs and CSS are the way to go.

Draw arrow between lists

Is there any way to dynamically draw an arrow between the two highlighted list items?
So if I hovered over "Item 2" it would do this (but a straight arrow):
Item 1 Highlight 3
Item 2-----\ Highlight 1
Item 3 ----->Highlight 2
This is the code from the answer I got here a few mins ago:
Highlight item in two lists when mouseover
$(".list1 li, .list2 li").hover(function () {
var n = this.id.substr(2);
$("#qq" + n + ", #aa" + n).toggleClass("highlight");
});
jsfiddle: http://jsfiddle.net/e37Yg/1/
<ul class="list1">
<li id="qq1">sdfsdv</li>
<li id="qq2">bnvnvb</li>
<li id="qq3">nmnutymnj7</li>
<li id="qq4">cvbc</li>
<li id="qq5">45tsgd</li>
</ul>
<ul class="list2">
<li id="aa3">fgtbrtgb</li>
<li id="aa1">vbn xgbn</li>
<li id="aa5">vdgver</li>
<li id="aa4">asdasdv</li>
<li id="aa2">nvfbnfn</li>
</ul>
You don't have to use 2D drawing here. Check this out: http://jsfiddle.net/vjYuW/
I just forked and updated the fiddle you have posted above.
Here is the essential code, it handles 3 DIVs 1 pixel wide or tall to draw the lines:
HTML:
...left list...
<div id="segment1" class="hline"></div>
<div id="segment2" class="vline"></div>
<div id="segment3" class="hline"></div>
...right list...
CSS:
... formatting for ULs here, both have to be float:left...
.highlight { background-color: red; }
.hline {
display:block;
position:relative;
float:left;
height: 1px;
width: 7em;
}
.vline {
display:block;
position:relative;
float:left;
height: 1px;
width: 1px;
}
JavaScript:
$(".list1 li, .list2 li").hover(function () {
var n = this.id.substr(2);
var leftY = $('#qq' + n).position().top;
var rightY = $('#aa' + n).position().top;
var H = Math.abs(rightY-leftY);
if (H == 0) H = 1;
$('#segment1').css('top',leftY+'px');
$('#segment3').css('top',rightY+'px');
$('#segment2').css('top',Math.min(leftY,rightY)+'px');
$('#segment2').css('height',H+'px');
$("#qq" + n + ", #aa" + n + ",#segment1,#segment2,#segment3").toggleClass("highlight");
});
Note: you will probably have to tweak it a little to support all browsers - I didn't check IE6 & Co.
You can use the HTML5 canvas element to achieve this.
I'm not sure if this is the best way to do it, but I fiddled around and got this.
What I did is first I enclosed the lists in a div. The div is styled with CSS to have a relative position. This is so when you get the position with jQuery, it will give a position relative to that. Next, I put a canvas in front of the lists and disabled pointer events on it. I also added something to adjust the height of the canvas to the height of the lists. Then I added another handler for hover. When you hover over it, it will draw the arrow, and when you hover out, it'll clear the canvas.
To draw the arrow is fairly simple. First it gets the positions of the items. Then it draws a line and uses some math to orient the arrow. To get the positions is fairly easy. For the right list, you can just use the position method. For the left list, I created a temporary span and appended it to the list item, and then got the position of that.
I think for something like this you may want to use a third party drawing library such as Vector Draw Library.
You can download the library from the link and add it to your app. Then:
Include it on your page:
<script type="text/javascript" src="wz_jsgraphics.js"></script>
Then add to your hover function:
$(".list1 li, .list2 li").hover(function () {
var n = this.id.substr(2);
$("#qq" + n + ", #aa" + n).toggleClass("highlight");
//canvas is your drawing div
var jg = new jsGraphics("canvas");
jg.drawLine($("#qq" + n).offset().left + 30, $("#qq" + n).offset().top , $("#aa" + n).offset().left, $("#aa" + n).offset().top );
jg.paint();
Note that you will have to write the code to remove the line in the hover function otherwise once it is drawn it will remain. Also, I am using offset() to calculate the position of the items in the list. This should work but you may have to tweak a bit to get it to look right.
The above code works but is not complete. Maybe the second function in the hover can call clear() on the canvas. Canvas here is the enclosing div that encloses the two lists.
<script src='www.walterzorn.de/en/scripts/wz_jsgraphics.js'> </script>
function drawLine(element1, element2) {
var jg = new jsGraphics("renderGraph");
var ele1 = document.getElementById(element1);
var ele2 = document.getElementById(element2);
jg.setColor("#DDD");
jg.setStroke(5);
jg.drawLine(ele1.offsetLeft + ele1.offsetWidth/2 , ele1.offsetTop + ele1.offsetHeight/2, ele2.offsetLeft + ele2.offsetWidth/2, ele2.offsetTop + ele2.offsetHeight/2);
jg.paint();
}

Categories