I currently have a giant table of "auditpoints", some of those points are "automated". If they are automated they receive a gear icon in their row. The gear icon is not the only icon each row receives. Each row, no matter if it's automated or not receives two other icons, a pencil and a toggle button. When an automated point "runs" the gear icon rotates until it is finished "running". I've have implemented some code to ran all of these points at once but I have a small problem. When you click my button to run all these points all three of the icons I have mentioned rotate and this is not the result I am looking for. The line commented out in my code snippet (and it's matching bracket) will prevent the code from running all of the automated points. Commenting out the line is what causes all the icons to rotate. I know this line is required to get the automated points to run properly as it used in the single execution of automated points I just don't know what to change it to. It obviously shouldn't be click because you are no longer clicking the gear icon to get a point to run I just don't know what to change it to but the classes in that click function are related to the gear icon.
Hopefully this is a very easy question to solve and doesn't waste anyone's time. Thank you!
private updateAuto() {
var self = this;
$(".auditPointRow").each(function () {
//self.el.on("click", ".update, .edit", function () {
var row = $(this).closest(".auditPointRow");
var id = row.data("id");
var automated = (<string>row.data("automated")).toLowerCase() == "true";
var running = true;
if (automated && $(this).closest(".edit").length == 0) {
var gear = $(this).find(".fa");
var maxTurns = 120;
gear.css("transition", "transform linear " + maxTurns * 2 + "s");
gear.css("transform", "rotate(" + (maxTurns * 360) + "deg)");
var request = $.ajax(self.root + "api/sites/" + self.site.ID + "/auditpoints/" + id, {
"type": "PATCH", data: JSON.stringify([
{
Op: "Replace"
, Path: "/score"
, Value: "run"
}
])
});
request.done(function () {
gear.css("transition", "").css("transform", "rotate(0deg)");
row.prev().find("td").css("background-color", "");
if (row.prev().qtip("api")) {
row.prev().qtip("api").destroy(true);
}
});
}
//}
});
}
I think I found a solution to my problem. I used .each again to go through all of the "gears" and only rotate them.
private updateAuto() {
var self = this;
//$(".auditPointRow").each(function () {
$(".update, .edit").each(function () {
//Left out the rest of the code so this answer isn't too
//long, none of it changed if that matters.
});
//});
}
For some reason the result runs very slowly (but it runs!) and I'm not sure why so if anyone has any better suggestion/optimizations please feel free to leave those here.
Edit: I realized I didn't to go through .each twice, that's what was slowing to down so I removed that first each that went over auditPoints and just did the ones with gears instead.
Hoping this doesn't get flagged as a duplicate because none of the other q/as on SO have helped me fix this, I think I need a more specific line of help.
I have a profile page on my site that allows the user to change their profile picture without page reloads (via AJAX / jQuery).
This all works fine. The user opens the "Change Profile Picture" modal, selects a file to upload and presses "Crop this image". When this button is pressed, it uploads a file to the website, using the typical way of sending the file and formData (which I append the file data to).
It gets sent backend with the following jQuery:
// Upload the image for cropping (Crop this Image!)
$("#image-upload").click(function(){
// File data
var fileData = $("#image-select").prop("files")[0];
// Set up a form
var formData = new FormData();
// Append the file to the new form for submission
formData.append("file", fileData);
// Send the file to be uploaded
$.ajax({
// Set the params
cache: false,
contentType: false,
processData: false,
// Page & file information
url: "index.php?action=uploadimage",
dataType: "text",
type: "POST",
// The data to send
data: formData,
// On success...
success: function(data){
// If no image was returned
// "not-image" is returned from the PHP script if we return it in case of an error
if(data == "not-image"){
alert("That's not an image, please upload an image file.");
return false;
}
// Else, load the image on to the page so we don't need to reload
$(profileImage).attr("src", data);
// If the API is already set, then we should apply a new image
if(jCropAPI){
jCropAPI.setImage(data + "?" + new Date().getTime());
}
// Initialise jCrop
setJCrop();
//$("#image-profile").show();
$("#send-coords").show();
}
})
});
setJcrop does the following
function setJCrop(){
// Get width / height of the image
var width = profileImage.width();
var height = profileImage.height();
// Var containing the source image
var imgSource = profileImage.attr("src");
// New image object to work on
var image = new Image();
image.src = imgSource;
// The SOURCE (ORIGINAL) width / height
var origWidth = image.width;
var origHeight = image.height;
// Set up the option to jCrop it
$(profileImage).Jcrop({
onSelect: setCoords,
onChange: setCoords,
setSelect: [0, 0, 51, 51],
aspectRatio: 1, // This locks it to a square image, so it fits the site better
boxWidth: width,
boxHeight: height, // Fixes the size permanently so that we can load new images
}, function(){jCropAPI = this});
setOthers(width, height, origWidth, origHeight);
}
And once backend, it does the following:
public function uploadImage($file){
// See if there is already an error
if(0 < $file["file"]["error"]){
return $file["file"]["error"] . " (error)";
}else{
// Set up the image
$image = $file["file"];
$imageSizes = getimagesize($image["tmp_name"]);
// If there are no image sizes, return the not-image error
if(!$imageSizes){
return "not-image";
}
// SIZE LIMIT HERE SOON (TBI)
// Set a name for the image
$username = $_SESSION["user"]->getUsername();
$fileName = "images/profile/$username-profile-original.jpg";
// Move the image which is guaranteed a unique name (unless it is due to overwrite), to the profile pictures folder
move_uploaded_file($image["tmp_name"], $fileName);
// Return the new filename
return $fileName;
}
}
Then, the user selects their area on the image with the selector and pressed "Change Profile Picture" which does the following
// Send the Coords and upload the new image
$("#send-coords").click(function(){
$.ajax({
type: "POST",
url: "index.php?action=uploadprofilepicture",
data: {
coordString: $("#coords").text() + $("#coords2").text(),
imgSrc: $("#image-profile").attr("src")
},
success: function(data){
if(data == "no-word"){
alert("Can not work with this image type, please try with another image");
}else{
// Append a date to make sure it reloads the image without using a cached version
var dateNow = new Date();
var newImageLink = data + "?" + dateNow.getTime();
$("#profile-picture").attr("src", newImageLink);
// Hide the modal
$("#profile-picture-modal").modal("hide");
}
}
});
})
The backend is:
public function uploadProfilePicture($coordString, $imgSrc){
// Target dimensions
$tarWidth = $tarHeight = 150;
// Split the coords in to an array (sent by a string that was created by JS)
$coordsArray = explode(",", $coordString);
//Set them all from the array
$x = $coordsArray[0];
$y = $coordsArray[1];
$width = $coordsArray[2];
$height = $coordsArray[3];
$newWidth = $coordsArray[4];
$newHeight = $coordsArray[5];
$origWidth = $coordsArray[6];
$origHeight = $coordsArray[7];
// Validate the image and decide which image type to create the original resource from
$imgDetails = getimagesize($imgSrc);
$imgMime = $imgDetails["mime"];
switch($imgMime){
case "image/jpeg":
$originalImage = imagecreatefromjpeg($imgSrc);
break;
case "image/png":
$originalImage = imagecreatefrompng($imgSrc);
break;
default:
return "no-work";
}
// Target image resource
$imgTarget = imagecreatetruecolor($tarWidth, $tarHeight);
$img = imagecreatetruecolor($newWidth, $newHeight);
// Resize the original image to work with our coords
imagecopyresampled($img, $originalImage, 0, 0, 0, 0,
$newWidth, $newHeight, $origWidth, $origHeight);
// Now copy the CROPPED image in to the TARGET resource
imagecopyresampled(
$imgTarget, // Target resource
$img, // Target image
0, 0, // X / Y Coords of the target image; this will always be 0, 0 as we do not want any black nothingness
$x, $y, // X / Y Coords (top left) of the target area
$tarWidth,
$tarHeight, // width / height of the target
$width,
$height // Width / height of the source image crop
);
$username = $_SESSION["user"]->getUsername();
$newPath = "images/profile/$username-profile-cropped.jpg";
// Create that shit!
imagejpeg($imgTarget, $newPath);
// Return the path
return $newPath;
}
So basically this the returns the path of the new file, which gets changed to the user's profile picture (same name every time) and uploaded live with a time appended after ? to refresh the image properly (no cache).
This all works fine, however if the user selects another image to upload, after already uploading one, the coords get all messed up (e.g. they go from 50 to 250) and they end up cropping a totally different part of the image, leaving most of it black nothing-ness.
Really sorry for the ridiculous amount of code that is in this question but I'd appreciate any help from people who might have worked around this before.
Some of the code may seem out of place but that's just me trying to debug it.
Thanks, and again, sorry for the size of this question.
-Edit-
My setCoords() and setOthers() functions look like so:
//Set the coords with this method, that is called every time the user makes / changes a selection on the crop panel
function setCoords(c){
$("#coords").text(c.x + "," + c.y + "," + c.w + "," + c.h + ",");
}
//This one adds the other parts to the second div; they will be concatenated in to the POST string
function setOthers(width, height, origWidth, origHeight){
$("#coords2").text(width + "," + height + "," + origWidth + "," + origHeight);
}
I have now resolved this issue.
The problem for me was that when using setJCrop(); - it was not re-loading the image. The reason for this is that the image uploaded and then loaded in to the JCrop window had the same name every time (username as a prefix, and then profile-cropped.jpg).
So to try and resolve this, I used the setImage method which loaded a full-sized image instead.
I got around this by setting the boxWidth / boxHeight params but they just left me with the issue of the coordinates being incorrect every time I loaded a new image in.
Turns out, it was loading the image from the cache every time, even when I was using new Image(); within jQuery.
To solve this, I have now used destroy(); on the jCropAPI and then re-initialised it every time, witout using setImage();
I set a max-width in the CSS on the image itself, which stopped it from being locked to a specific width.
The next problem was that every time I loaded an image a second time, it left the width / height of the old image there, which made the image look all skewed and wrong.
To solve this, I reset the width & height of the image that I use jCrop on, to "" with $(profileImage).css("width", ""); $(profileImage).css("height", ""); before re-setting the source of the image from the new uploaded image.
But I was still left with the issue of using the same name on the images, and then causing it to load from cache every time.
My solution to this was to add an "avatar" column in the database and save the image name in the Database each time. The image was named as $username-$time.jpg and $username-$time.jpg-cropped.jpg where $username is the username of the user (derp) and $time is simply time(); inside PHP.
This meant that every time I uploaded an image, it had a new name, so when any calls were made to this image, there was no cache of it.
Appending like imageName + ".jpg?" + new Date.getTime(); worked for some things but then when sending the image name backend, it didn't work properly, and deciding when to append it / not append it was a pain, and then one thing required it to be appended to force a re-load, but then when appended it didn't work properly, so I had to re-work it.
So the key: (TL;DR)
Don't use the same image name with jCrop, if you are loading a new image; upload an image with a different name and then refer to that one. Cache problems are a pain, and you can't really work around them properly without just using a new name every time, as this ensures that there will be absolutely no more problems (so long as the name is always unique).
Then, when you initialise jCrop, destroy the previous one beforehand if there is one. Use max-width instead of width on an image to stop it from locking the width, and re-set the width / height of the image if you're loading a new one in to the same <img> or <div>
Hope this helps somebody!
I used jcrop and I think this has happened to me. When there is a new image, you have to "reset" jcrop. Try something like this:
function resetJCrop()
{
if (jCropAPI) {
jCropAPI.disable();
jCropAPI.release();
jCropAPI.destroy();
}
}
$("#image-upload").click(function(){
success: function(data){
...
resetJCrop(); // RESETTING HERE
// If the API is already set, then we should apply a new image
if(jCropAPI){
jCropAPI.setImage(data + "?" + new Date().getTime());
}
// Initialise jCrop
setJCrop();
...
}
});
I can't remember the details about why I have to use disable() AND release() AND destroy() in my particular case. May be you can use only one of those. Just try it, and see if that works for you!
My goal is to call an API, which returns JSON data. On successfully retrieving the data, I would like to put the retrieved image into a gallery, organized with the Masonry library. This should work for initially loading data, and endless scrolling to load more data when the user scrolls down the page.
The code below works well for grabbing and organizing the first set of data. It loads nine images and uses Masonry to organize them. But, when I scroll down the page to load more images, all of the current images get pushed down the page. When I scroll down again the same thing happens. So I end up with a bunch of white space at the top of the page.
I was attempting to roughly follow the example that I found here;
http://codepen.io/desandro/pen/iHevA
Thanks for any help you can provide.
function loadThumbnailImages(thumbDate) {
// Format the date into YYYY-mm-dd format for the API
formattedDate = this.formatDate(thumbDate);
$.jsonp({
// The URL to parse the JSON from
url: 'dataURLGoesHere' + formattedDate, // any JSON endpoint
// if URL above supports CORS (optional)
corsSupport: false,
// if URL above supports JSONP (optional)
jsonpSupport: false,
// If the JSON from the URL was successfully parsed
success: function(data){
$(document).ready(function() {
if(data.media_type == "image")
{
var item = '<div class="masonry-item"><img src=' + data.url + ' class="thumbImage img-responsive"/></div>';
items += item;
if(doneLoading)
{
var $container = $('#masonry-list').masonry({
itemSelector: '.masonry-item',
columnWidth: 50
})
$container.append(items);
$container.imagesLoaded().progress( function ( imgLoad, image) {
var $item = $(image.img).parents('.masonry-item');
$container.masonry('appended', $item);
});
items = "";
}
}
})
}
I managed to figure it out. Adding the two lines $container.masonry('reloadItems') and $container.masonry('layout') manages to load the images and responsively place them in the correct place. Hopefully this can help someone in the future!
var $container = $('#masonry-list').masonry({
itemSelector: '.masonry-item',
columnWidth: 50
})
$container.append(items);
$container.imagesLoaded().progress( function ( imgLoad, image) {
var $item = $(image.img).parents('.masonry-item');
$container.masonry('appended', $item);
$container.masonry('reloadItems'); //<--- This line and
$container.masonry('layout'); //<-------- This line fixed the problem
});
I want to save each slide in a powerpoint-file into seperate powerpoint-files, as well as store png images of each slide. In the end I want to create an archive of individual unique slides, so that all slides that are shown on some info screens are stored in this archive. This is what I have to far:
function exportSingleSlides(pathname, filename){
var app = new ActiveXObject("PowerPoint.Application");
app.Visible = true;
// Open the presentation
var presentation = app.Presentations.Open(pathname+filename, true);
var tmpPresentation; // Used to store a new presentation for each slide.
var e = new Enumerator(presentation.Slides);
var slide;
e.moveFirst();
var i = 0;
while (!e.atEnd()) {
i++;
slide = e.item(); // gets this slide
// Export slide to png
slide.Export(pathname + 'slide' + (i < 10 ? '0' + i : i) + '.png', 'PNG', 1920, 1080);
// Open new presentation
tmpPresentation = app.Presentations.Add();
// Copy current slide and paste into new presentation
slide.Copy();
tmpPresentation.Slides.Paste(1);
tmpPresentation.SaveAs(pathname + 'slide' + (i < 10 ? '0' + i : i));
tmpPresentation.Close();
e.moveNext();
}
// Close the presentation. (But how do I close powerpoint entirely?)
presentation.Close();
app.Quit();
}
// Which file are we looking at?
var pathname = 'C:\\Users\\Stian\\Dropbox\\jobb\\RB\\powerpoint parser\\';
var filename = 'test.pptx';
exportSingleSlides(pathname, filename);
This saves the slides to png as I want, and it also saves each slide as it's own powerpoint-file. But it's not copying it as it should. I think it might be missing the template, because headers and footers aren't included in the new copy. I'm using the powerpoint developer reference pages, but I can't seem to find a good way to copy/duplicate a slide as-is. Any suggestions?
I've figured it out. Doing as below instead of just copying will do the trick.
// Get the layout from the source slide
layout = slide.CustomLayout;
// Copy current slide and paste into new presentation
slide.Copy();
tmpPresentation.Slides.Paste(1);
// Set the layout
tmpPresentation.Slides(1).CustomLayout = layout;
Now I'm getting an unspecified error when trying to save the new file, but that's a whole other problem.
I recently downloaded a nice mootools plugin to provide a rating system for search results on my site: MooStarRating.
It works quite well, but it is very slow to initialize. Here are the execution times I have logged (for pulling back 50 search results).
======== starrating ========
init: 0.06ms 50
img: 5.40ms 50
str: 0.54ms 50
each: 3.04ms 50
inject: 0.86ms 50
end: 1.52ms 50
subtotal: 11.42ms 50
-----------------
total: 571.00ms
Here is the initialize function these logs refer to (just for reference):
initialize: function (options) {
lstart("starrating");
// Setup options
this.setOptions(options);
// Fix image folder
if ((this.options.imageFolder.length != 0) && (this.options.imageFolder.substr(-1) != "/"))
this.options.imageFolder += "/";
// Hover image as full if none specified
if (this.options.imageHover == null) this.options.imageHover = this.options.imageFull;
lrec("init");
// Preload images
try {
Asset.images([
this.options.imageEmpty,
this.options.imageFull,
this.options.imageHover
]);
} catch (e) { };
lrec("img");
// Build radio selector
var formQuery = this.options.form;
this.options.form = $(formQuery);
if (!this.options.form) this.options.form = $$('form[name=' + formQuery + "]")[0];
if (this.options.form) {
var uniqueId = 'star_' + String.uniqueID();
this.options.form.addClass(uniqueId);
this.options.selector += 'form.' + uniqueId + ' ';
}
this.options.selector += 'input[type=radio][name=' + this.options.radios + "]";
// Loop elements
var i = 0;
var me = this;
var lastElement = null;
var count = $$(this.options.selector).length;
var width = this.options.width.toInt();
var widthOdd = width;
var height = this.options.height.toInt();
if (this.options.half) {
width = (width / 2).toInt();
widthOdd = widthOdd - width;
}
lrec("str");
$$(this.options.selector).each(function (item) {
// Add item to radio list
this.radios[i] = item;
if (item.get('checked')) this.currentIndex = i;
// If disabled, whole star rating control is disabled
if (item.get('disabled')) this.options.disabled = true;
// Hide and replace
item.setStyle('display', 'none');
this.stars[i] = new Element('a').addClass(this.options.linksClass);
this.stars[i].store('ratingIndex', i);
this.stars[i].setStyles({
'background-image': 'url("' + this.options.imageEmpty + '")',
'background-repeat': 'no-repeat',
'display': 'inline-block',
'width': ((this.options.half && (i % 2)) ? widthOdd : width),
'height': height
});
if (this.options.half)
this.stars[i].setStyle('background-position', ((i % 2) ? '-' + width + 'px 0' : '0 0'));
this.stars[i].addEvents({
'mouseenter': function () { me.starEnter(this.retrieve('ratingIndex')); },
'mouseleave': function () { me.starLeave(); }
});
// Tip
if (this.options.tip) {
var title = this.options.tip;
title = title.replace('[VALUE]', item.get('value'));
title = title.replace('[COUNT]', count);
if (this.options.tipTarget) this.stars[i].store('ratingTip', title);
else this.stars[i].setProperty('title', title);
}
// Click event
var that = this;
this.stars[i].addEvent('click', function () {
if (!that.options.disabled) {
me.setCurrentIndex(this.retrieve('ratingIndex'));
me.fireEvent('click', me.getValue());
}
});
// Go on
lastElement = item;
i++;
}, this);
lrec("each");
// Inject items
$$(this.stars).each(function (star, index) {
star.inject(lastElement, 'after');
lastElement = star;
}, this);
lrec("inject");
// Enable / disable
if (this.options.disabled) this.disable(); else this.enable();
// Fill stars
this.fillStars();
lrec("end");
return this;
},
So, the slowest part of the function is this:
// Preload images
try {
Asset.images([
this.options.imageEmpty,
this.options.imageFull,
this.options.imageHover
]);
} catch (e) { };
Which is strange. What does Asset.images do? Does the script block until these images have been loaded by the browser? Is there a way to preload images that runs faster?
How can I make the scripts on my page execute faster? It is a big problem for them to take 800ms to execute, but 200ms is still quite bad. At the moment, my search results all pop into existence at once. Is it possible to make it so that individual search results are created separately, so that they don't block the browser while being created? Similarly, is it possible to do this for the individual components of the search results, such as the MooStarRating plugin?
no. Asset.images is non-blocking as each of these gets loaded separately and a singular event is being dispatched when all done.
the speed for loading will be dependent on the browser but it be will multi-threaded to whatever capability it has for parallel downloading from the same host.
https://github.com/mootools/mootools-more/blob/master/Source/Utilities/Assets.js#L115-129
immediately, it returns an Element collection with the PROMISE of elements that may still be downloading. that's fine - you can use it to inject els, attach events, classes etc - you just cannot read image properties yet like width, height.
each individual image has it's own onload that fires an onProgress and when all done, an onComplete for the lot - i would advise you to enable that, remove the try/catch block and see which image is creating a delay. you certainly don't need to wait for anything from Asset.images to come back.
you also seem to be using it as a 'prime the cache' method, more than anything - as you are NOT really saving a reference into the class instance. your 'each' iteration can probably be optimised so it uses half the time if objects and functions are cached and so are references. probably more if you can use event delegation.
To answer your questions about not freezing the browser due to the single-threaded nature of javascript, you defer the code via setTimeout (or Function.delay in mootools) with a timer set to 0 or a 10ms due to browser interpretations. You also write the function to to exec a callback when done, in which you can pass the function result, if any (think ajax!).