Masonry: Creating a grid on AJAX callback - javascript

So I'm pretty familiar with Masonry I'd say, but I haven't had to do this before.
I'm aware of the "appended" method to add items to the grid after page load through ajax, but that's not exactly what I'm trying to do.
My grid structure doesn't exist at all until it's called via ajax.
For example (pseudocode):
Page Loads
User Clicks a Link
AJAX call, HTML string is returned and added to the dom via jQuery.
Need to instantiate the masonry grid based on this HTML.
Basically the HTML that is returned via AJAX looks something like this:
<div class="grid-57">
<a class="item" href="http://www.example.com">
<img src="http://fpoimg.com/350x350"/></a>
</a>
<a class="item" href="http://www.example.com">
<img src="http://fpoimg.com/350x250"/></a>
</a>
<a class="item" href="http://www.example.com">
<img src="http://fpoimg.com/350x450"/></a>
</a>
</div>
So as you can see I'm not adding to an existing grid or masonry, I need to create a new one on successful return of the AJAX.
My JS looks something like this:
$('.get-grid').click(function(e){
var id = $(this).data('id');
$.post(ajaxurl, { action: 'get_grid', id: id }, function(response) {
// Append this html to the area of the site where I have the grids.
$('.all-the-grids').append(response);
// Here is where i need to actually turn this HTML that I just added into a masonry object and lay it out.
var $container = $('.grid-' + id);
msnry = new Masonry( $container[0], {
columnWidth: 188,
itemSelector: '.item',
gutter: 10
});
msnry.on('layoutComplete', function(){
// never making it in here.
});
});
});
Any idea how to do this?
EDIT
It appears my grid is getting laid out correctly, no errors, but it's never reaching the layoutComplete callback here.

Do it the same way as you would usually with Masonry, the fact that you're doing it in a callback doesn't really change anything.
$('.get-grid').click(function(e){
var id = $(this).data('id');
$.post(ajaxurl, { action: 'get_grid', id: id }, function(response) {
// Append this html to the area of the site where I have the grids.
var grids = $('.all-the-grids');
grids.append(response);
var msnry = new Masonry(grids[0], {
});
});
});

When you are using Images in Layout, first thing you have to do is wait till all images loaded in that element and then instantiate. Try below code
$containers = $('.all-the-grids');
$containers.append(response).imagesLoaded(function() {
$containers.masonry({
itemSelector: '. a'
});
});
Note: imagesLoaded is a jQuery plugin

Related

sortable and new created content [duplicate]

So I use jQuery UI sortable plugin to sort photos in a small gallery.
$(function() {
$("#area").sortable({
items: '.sort-wrapper',
cursor: "move",
handle: ".photo-handler",
opacity: 0.5,
scroll: true,
scrollSensitivity: 100,
scrollSpeed: 5,
revert: 100,
tolerance: "pointer",
update : function () {
var order = $('#area').sortable('serialize');
$.ajax({
type : 'POST',
url : '/file.php',
data : order
});
}
}).disableSelection();
On the same page I dynamically add and remove photos. PHP script returns HTML code with div's with image links and buttons "remove" and "move" (sortable handler).
Example:
<div class="sort-wrapper">
<img src="photo001m.jpg" alt="">
<button type="button" class="remove-this-photo">Remove</button>
<button type="button" class="photo-handler">Move</button>
</div>
The problem is sortable stops working when I add new files or remove a file(s) from #area. I've been looking for solution, have found sortable('refresh') method, but it's not working for me.
The script which adds new photos in the #area is quite standard. I use $.ajax({}) and .done, .fail, .always methods.
I managed to use sortable('destroy') method when starting uploading new files and do something like this after uploading is finished:
.always(function() {
$("#area").sortable();
});
The code above makes the #area sortable again, but I must copy all the settings again to configure it my way.
How to bind sortable to make it work after dynamic content is loaded or how to store all the settings outside sortable() and be able to use it again on the same page?
If you add new content to the sortable element you'll need to refresh the initialised instance. To do that, call the refresh option, like this:
.always(function() {
$("#area").sortable('refresh');
});

Random duplicate content bug with Bootstrap popovers and FullCalendar

Note: I understand there's a bug in bootstrap that causes popovers/tooltips to call the content function() twice, but I do not think that is the case here.
I am using bootstrap popovers in conjunction with the FullCalendar gem to update events.
Inside the popover, there're multiple divs that get hidden/shown depending on which view the user is on (i.e. there is a view for displaying the event info and another for updating the view). The way I'm handling this is by using addClass('hide') and removeClass('hide'), so correct me if this is improper.
When I click on an event, it displays the popover with the content added it to once (good). When I navigate to the update div inside the popover and send an .ajax({type: 'PUT'}), the popover for the updated event displays two of the content divs (bad); however, this happens randomly.
If I reload the entire DOM and redo the process over and over again, this duplication happens ~half the time.
When popover is clicked just after DOM loads (good)
When popover is clicked after ajax PUT is called, this SOMETIMES HAPPENS (even if no fields are changed)
Here's how I send the .ajax request:
// SAVE button for LESSONS, POPOVER ***/
$('body').on('click', 'button.popover-lesson-save', function() {
var title = $('.popover input.popover-lesson-title').val();
var note = $('.popover input.popover-lesson-note').val();
// Retrieve event data from popover
var idStr = $('.popover div.popover-body').data('id');
var id = parseInt(idStr);
// Store event data into a hash for AJAX request and refetches events
var data = {
title: title,
note: note
};
// Make PUT AJAX request to database
$.ajax({
url: '/lessons/' + id,
type: 'PUT',
data: data,
dataType: 'json',
success: function(resp){
$('.popover div.popover-lessons-edit').addClass('hide');
$('.popover div.popover-lessons-view').removeClass('hide');
$('.popover').popover('hide');
$('#calendar').fullCalendar( 'refetchEvents' );
},
error: function(resp){
alert('Error code 2-502-1. Oops, something went wrong.');
}
});
});
And here is what I do in my FullCalendar callbacks:
// calendar options
$('#calendar').fullCalendar({
..
eventRender: function(calEvent, element, view){
if (typeof $(element).data('bs.popover') === 'undefined'){
// Find content html (append and clone prevents parent div from being overwritten)
var content = $("<div />").append($('.popover-lessons-content').clone());
var id = calEvent.id;
// Attach popover to html element
$(element).popover({
container: 'body',
placement: 'left',
html: true,
content: function(){
return content.html(); // This randomly duplicates
}
});
}
}
}
Something tells me that instead of append() here
var content = $("<div />").append($('.popover-lessons-content').clone());
you should use appendTo(). And yet not sure about purpose of clone() so I think it should look like as:
var content = $("<div />").appendTo('.popover-lessons-content');
and to avoid duplicates that $('.popover-lessons-content') thing should be cleared before appending to it.
Thanks to #c-smile, I've realised that the bug lied in how I retrieve my html template from my view file:
var content = $("<div />").append($('.popover-lessons-content').clone());
Instead, I should have done this (and this worked after much tinkering):
var content = $('.popover-lessons-content').html();
$(element).popover({
container: 'body',
placement: 'left',
html: true,
content: function(){
return content;
}
});
It takes the direct html instead of cloning the div and making a big mess.

Allow Meteor.js to Wait for Images to Load before calling Plugin

When using the Isotope plugin in Meteor, Isotope always apply a style of height: 0 to the Isotope div .grid-container. This can be due to the plugin initializing before the images have loaded. Running $('.grid-container').isotope() in the console manually causes Isotope to make the div visible.
Question: How can we trigger the plugin initialization only after all the images in div .item have loaded? Calling Isotope's imagesLoaded method from within Template.name.rendered does not seem to work.
main.js
Template.main.rendered = function () {
$('.grid-container').imagesLoaded(function() {
$('.grid-container').isotope({
itemSelector: '.item',
layoutMode: 'masonry',
masonry: {
columnWidth: 200
}
})
})
};
main.html
<template name="main">
<div class="grid-container">
{{#each items}}
{{> item}}
{{/each}}
</div>
</template>
As requested in the comment above...
Here's how to use an intermediate DOM element for isotope image loading before doing a final append and layout.
This example is for Backbone but should work fine in Meteor. The crucial aspect in both cases is initializing isotope() on an empty element. For example: <div class="isotope"></div>
As you can see below, I have a grid_view in Backbone that handles all of this and renders items one at a time as they are added to the collection. Naturally, in Meteor that's not the approach to take.
For the record, I'm not sure if the Meteor example would work out of the box but it's not far off. I haven't used isotope in Meteor as I mentioned. The Backbone example is solid and works in production for us.
Here are the examples...
Meteor Example:
// In Meteor, you want your collection and the DOM element
Template.main.rendered = function() {
// Pretty sure this.$ selector works in Meteor rendered
this.$container = this.$('.isotope').isotope({
itemSelector: '.gridItem',
masonry: {
columnWidth: '.gridSizer',
gutter: '.gutterSizer'
}
});
var items = CollectionName.find().fetch();
var $holder = $('<div></div>')
_.each(items, function(item) {
// However you load external or partial templates in Meteor (not sure)
$holder.append(partialTemplate(item));
}
// Load images
$holder.imagesLoaded(function() {
// Append and layout on isotope
self.$container.append(item).isotope('appended', $holder);
});
}
In Meteor, you could use the added callback in your publish function, to send the models to the client one at a time if you wanted. I haven't dug into pagination with Meteor to know the best way to handle that.
Backbone Example:
// Collection Add event
renderItem: function(model) {
var self = this;
// Run it through template
var item = $(self._template(model.toJSON()));
// Load images
item.imagesLoaded(function() {
// Append and layout on isotope
self.$container.append(item).isotope('appended', item);
});
}
// Standard view render function
render: function() {
this.$container = this.$el.isotope({
itemSelector: '.gridItem',
masonry: {
columnWidth: '.gridSizer',
gutter: '.gutterSizer'
}
});
}

Load External Panel To Jquery Mobile Page

JQuery Mobile template which utilises a side panel. For simplicity I want to be able to load this panel from an 'external page' / seperate document so that it can be built once and used on many pages.
A bit of research has resulted in me getting the following code to load a string of html into a variable and then loading that variable into each page as the side panel:
/* Get the sidebar-panel as a var */
var side_var = '<div data-role="panel" id="left-panel" data-position="left" data-display="push"><h1>Panel</h1><p>stuff</p></div>';
/* Load the side-panel var to the DOM / page */
$(document).one('pagebeforecreate', function () {
$.mobile.pageContainer.prepend( side_var );
$("#left-panel").panel();
});
This works perfectly, however it still doesn't address the need to build the sidebar / panel as a separate html file.
A bit more work and I discovered this snippet to load a page into a variable:
$.get("http://www.somepage.com", function( side_var ) {}, 'html');
So in theory adding the two together should give me the desired result;
/* Get the sidebar-panel as a var */
$.get("mypage.html", function( side_var ) {}, 'html');
/* Load the side-panel var to the DOM / page */
$(document).one('pagebeforecreate', function () {
$.mobile.pageContainer.prepend( side_var );
$("#left-panel").panel();
});
However, all I get when running this is an eternally rotating AJAX loader.
What am I missing &/or doing wrong??
COMPLETE SOLUTION
Omar's solution below goes most of the way but needs one small but important addition to make the JQM elements display as expected. Here is the complete solution:
$(document).one("pagebeforecreate", function () {
$.get('side-panel.html', function(data) {
//$(data).prependTo($.mobile.pageContainer); //or .prependTo("body");
$.mobile.pageContainer.prepend(data);
$("[data-role=panel]").panel().enhanceWithin(); // initialize panel
}, "html");
});
Each element to be enhanced as a JQM element needs the data-theme adding to tell it which theme to use. Additionally in the JavaScript the initialised panel widget needs to .enhanceWithin() in order for the elements to be rendered with the desired style.
the $.get() function should be wrapped in pagebeforecreate event, to append contents directly once retrieved. Also, you need to initialize retrieved contents.
$(document).one("pagebeforecreate", function () {
$.get('mypage.html', function(data){
$(data).prependTo("body"); // or .prependTo($.mobile.pageContainer);
$("[data-role=panel]").panel(); // initialize panel
}, "html");
});

Display a chart in a pop-up/modal on mouse click (jquery?) without loading chart first? (mvc3/razor)

I'm using the FileStreamResult (or FileResult) method to display a chart on a razor view page (from a Controller action) and this works as expected.
However, I'm wondering if it's possible to display the chart ONLY when an html element is clicked.
e.g. Click on an element, display chart in modal pop-up.
I can get a modal to display using Jquery...but ONLY if I have already loaded the chart into a div.
I'd like to have the page load without the chart (for performance reasons), and then only generate/load the chart when the is clicked.
I guess I need an Ajax call of somekind...but I'm a little stuck as to how to proceed and googling didn't return anything useful (just lightbox for photos)
Pseudo Code:
HTML:
<img src='small chart icon.jpg' alt='click me to see chart' id='showchart'/>
<div>
rest of page goes here...
</div>
C#:
public FileStreamResult GetChart(){
//generate any chart
return FileStreamResult(newchartstream, "image/png");
}
JS:
<script>
$(document).ready(function(){
$('#showchart').click(function(){
//make ajax call to generate chart
//show in modal with "close" button
//OR
//open a new page as a modal?
});
});
</script>
EDIT
Clarification:
Controller generates a ViewModel (from an EF4 model) which contains the results of lots of calculations, and some "summary" rows (totals, averages etc)
View displays the results (ViewModel) as tables.
Requirement:
Click on one of the summary rows, opens modal window displaying a chart for that summary row.
Would like to avoid sendind the parameters for the ViewModel and re-generating it from scratch (doing all the calcs again etc) for two reasons
1) The figures in the back-end db may have changed...so the chart doesn't reflect whats being shown in the tables.
2) It takes time to do the calcs!
I'd also like to ONLY generate the chart if the row is clicked, as opposed to loading the chart always and hide() show() with jquery.
I've thought about an ajax call, but unsure about how to return an image, or how to open a new page.
Can't see a way to pass a complex model in the Url.Action() method.
Think caching may be the best option, and have the click method call "old fashioned" javascript for a new window, passing over the parameters to get the object from cache?
You have many ways to do that, but here is what I would do.
1/ Create an action which returns a tag with the good src (assuming that your controller's name is ChartsController).
public ActionResult GetImage(int chartId = 0)
{
var image = #"<img id='chart' src='" + Url.Action("GetChart", new {controller = "Charts", chartId = chartId}) + #"'/>";
return this.Content(image);
}
2/ I assume that, in your view, you have a div somewhere which contains your modal's content (like jQuery-ui would do) with an img inside, for the chart.
<div id="modal-div">
<div id="chart-div"></div>
</div>
3/ For every row, you have to create an Ajax link who will call your "GetImage" action and replace "chart-div" with your . You can specify a "OnBegin" Javascript function, this is where you will put your code to launch the modal.
#Ajax.ActionLink("Show me the chart!", "GetImage", "User", new { chartId = #Model.Rows[i]}, new AjaxOptions()
{
HttpMethod="GET",
OnBegin = "showModalWindow",
UpdateTargetId="modal-div",
InsertionMode = InsertionMode.Replace
});
Everytime you will click on an Ajax link, the modal will be launched and the inside will be replaced (in fact, all the content of the modal will be replaced).
Don't forget to include "Scripts/jquery.unobtrusive-ajax.js" and "Scripts/jquery.validate.unobtrusive.js" for the Ajax links to work.
You have some difference question mixed in one. You should split them each in one question. For your question's title, -loading an image by AJAX- you can use code-snippet below:
HTML:
<div id="chartholder"></div>
<a id="showchart">Show the chart</a> <!-- or any element else you want to fire show event -->
JS:
$(document).ready(function(){
$("#showchart").click(function(){
// Put your controller and action (and id if presented) below:
var file = "/YourControllerToGetChart/GetChart/id"
$("<img />").attr("src", file).load(function () {
$(this) // the loaded image
.css({"some-css-you-want":"some-value"})
.appendTo($("#chartholder")); // fixed syntax
});
});
});
Let me know if you have any questions or need clarifications on any part or you want to know how to css the div id="chartholder" to display it as an modal. Cheers.

Categories

Resources