Inline editing with AJAX - how do I create multiple editable areas on the same page? - ajax

I found a tutorial on how to create editable regions on a page using AJAX.
This is great, except it was written for a single element with a unique ID. I'd like to be able to click on multiple elements on the same page and have them also be editable (e.g., I'd like to alter the script below so it works not with a single element, but with multiple elements of a particular class).
Here is my HTML:
<h2>Edit This</h2>
<p class="edit">This is some editable content</p>
<p class="edit">This is some more editable content</p>
<p class="edit">I could do this all day</p>
Here is the JS file I'm working with (I updated the script per Rex's answer below): This script is, unfortunately, not working - can anyone point me in the right direction?
Event.observe(window, 'load', init, false);
function init() {
makeEditable('edit');
}
function makeEditable(className) {
var editElements = document.getElementsByClassName(className);
for(var i=0;i<editElements.length;i++) {
Event.observe(editElements[i], 'click', function(){edit($(className))}, false);
Event.observe(editElements[i], 'mouseover', function(){showAsEditable($(className))}, false);
Event.observe(editElements[i], 'mouseout', function(){showAsEditable($(className), true)}, false);
}
}
function showAsEditable(obj, clear) {
if (!clear) {
Element.addClassName(obj, 'editable');
} else {
Element.removeClassName(obj, 'editable');
}
}
function edit(obj) {
Element.hide(obj);
var textarea ='<div id="' + obj.id + '_editor"><textarea cols="60" rows="4" name="' + obj.id + '" id="' + obj.id + '_edit">' + obj.innerHTML + '</textarea>';
var button = '<input type="button" value="SAVE" id="' + obj.id + '_save"/> OR <input type="button" value="CANCEL" id="' + obj.id + '_cancel"/></div>';
new Insertion.After(obj, textarea+button);
Event.observe(obj.id+'_save', 'click', function(){saveChanges(obj)}, false);
Event.observe(obj.id+'_cancel', 'click', function(){cleanUp(obj)}, false);
}
function cleanUp(obj, keepEditable) {
Element.remove(obj.id+'_editor');
Element.show(obj);
if (!keepEditable) showAsEditable(obj, true);
}
function saveChanges(obj) {
var new_content = escape($F(obj.id+'_edit'));
obj.preUpdate = obj.innerHTML // stow contents prior to saving in case of an error
obj.innerHTML = "Saving…";
cleanUp(obj, true);
var success = function(t){editComplete(t, obj);}
var failure = function(t){editFailed(t, obj);}
var url = 'http://portal.3roadsmedia.com/scripts/edit.php';
var pars = 'id=' + obj.id + '&content=' + new_content + '&pre=' + obj.preUpdate;
var myAjax = new Ajax.Request(url, {method:'post',
postBody:pars, onSuccess:success, onFailure:failure});
}
function editComplete(t, obj) {
obj.innerHTML = t.responseText;
showAsEditable(obj, true);
}
function editFailed(t, obj) {
obj.innerHTML = 'Sorry, the update failed.';
cleanUp(obj);
}

The Event.observe method currently attaches to a single element with the ID specified. You should change this to iterate over a collection of elements located by classname and attach to each of them. According to the Prototype documentation, you can provide an element object as the first parameter, instead of an ID.
Currently, id is a string:
function makeEditable(id) {
Event.observe(id, 'click', function(){edit($(id))}, false);
//...
Which means Event.observe is attaching to the click event of the element with the ID provided. You want to attach to all elements with a class. Try:
function makeEditable(className) {
var editElements = document.getElementsByClassName(className);
for(var i=0;i<editElements.length;i++) {
Event.observe(editElements[i], 'click', function()
//...
}
//...

Related

Jest only - addEventListener ~ click assertion

I want to mock a normal dom click as indicated in the Jest docs:
test('displays a user after a click', () => {
document.body.innerHTML =
'<div>' +
' <span id="username" />' +
' <button id="button" />' +
'</div>';
// Use jquery to emulate a click on our button
$('#button').click();
expect($('#username').text()).toEqual('Johnny Cash - Logged In');
});
My function is as follows:
function clickTrack() {
const data = {};
document.addEventListener('click', function clicked(e) {
if (e.target.matches('a')) {
e.preventDefault();
data['click.Classes'] = e.target.classList;
data['click.ID'] = e.target.id;
data['click.Destination'] = e.target.href;
data['click.ElementText'] = e.target.innerText;
}
}, false);
return data;
}
And the test:
describe('Click Tracking', () => {
test('Clicking on an a tag will collect its information', () => {
clickTrack();
document.body.innerHTML = '<a class="j1 j2 j3" id="j" href="http://www.google.com/">Jest</a>';
document.getElementById('j').click();
expect(clickTrack()).toEqual({a:'b'});
});
});
I expect the output to be vaguely:
{
click.Classes: ["j1 j2 j3"]
click.Destination: "https://www.google.com/"
click.ElementText: "Jest"
click.ID: "j"
}
But an empty object is returned instead.
clickTrack hooks up a click event listener and returns a data object that will be updated whenever a click event happens, so you only have to call clickTrack once.
Right now you are calling it again after your click event so it is creating an additional click listener and returning a new, empty data object.
You'll also want to use e.target.text for the text and get the class names by calling split(' ') on e.target.className:
function clickTrack() {
const data = {};
document.addEventListener('click', function clicked(e) {
if (e.target.matches('a')) {
e.preventDefault();
data['click.Classes'] = e.target.className.split(' ');
data['click.ID'] = e.target.id;
data['click.Destination'] = e.target.href;
data['click.ElementText'] = e.target.text;
}
}, false);
return data;
}
describe('Click Tracking', () => {
test('Clicking on an a tag will collect its information', () => {
const data = clickTrack();
document.body.innerHTML = '<a class="j1 j2 j3" id="j" href="http://www.google.com/">Jest</a>';
document.getElementById('j').click();
expect(data).toEqual({
'click.Classes': ['j1', 'j2', 'j3'],
'click.Destination': 'http://www.google.com/',
'click.ElementText': 'Jest',
'click.ID': 'j'
}); // Success!
});
});

Re-trigger event from event

Here's a search box module (heavily sanitised to satisfy boss). It all works, apart from the more event. I get the results, they appear, and the "load more" div appears.
On clicking more i would like the search event to rerun. In more, i do some trivial session calculations which i need to pass to the search event, which works. The click() does not.
I've tried moving the more event outside of the search event, without success.
This fiddle seems to work, albeit with the node ID instead.
define([
'dojo/dom',
'dojo/dom-construct',
'dojo/on',
'dojo/query'
'dojo/domReady!'
], function(
dom,
domConstruct,
on,
query
) {
var search = {
display: function(){
sb = '<div id="searchWrapper" class="searchWrapper"><h3>Search</h3>
<input id="searchString" type="text" />';
sb += '<h3>Search</h3><select id="sBy">';
sb += '<option value="0">Choose...</option>';
sb += '<option value="f">Foo</option>';
sb += '<option value="b">Bar</option>';
sb += '<option value="fb">Foo & Bar</option>';
sb += '</select>';
sb += '<div class="searchBtn">
<button type="button" id="search">Search</button></div>';
sb += '</div>';
domConstruct.place(sb, dom.byId('body'), 1);
on(dom.byId('search'), 'click', function(e){
var r = thisIsWhereDataIsRetrieved(params, whatever);
// r is the response from the db call
if(typeof r === 'object'){
var sr = 'here are the results!';
for (var result in r) {
sr += 'this particular result';
}
if(r.length === 50) {
sr += '<div id="more" class="more">Load More</div>';
}
domConstruct.place(sr, dom.byId('searchWrapper'), 'last');
}
on(query('.more'), 'click', function(e){
// get next results having done stuff
// to track the state of the search, which works
dom.byId('search').click(); <<<<< not working
});
});
}
}
return search;
});
EDIT: I got around this by re-calling thisIsWhereDataIsRetrieved inside the more event and appending the next results before the more node, but it still seems as though it'd be better to do what i can't!
EDIT2: added more node!
You need to use something else than an anonymous function.
For instance, something like this:
(note you may need to have a better construction than the actual method, using query may have side effects if you do not clean properly the dom. I encourage you to write a proper widget and to use attach-points)
define([
'dojo/dom',
'dojo/dom-construct',
'dojo/on',
'dojo/query'
'dojo/domReady!'
], function(
dom,
domConstruct,
on,
query
) {
var search = {
display: function() {
sb = '<div id="searchWrapper" class="searchWrapper"><h3>Search</h3>
<input id="searchString" type="text" />';
sb += '<h3>Search</h3><select id="sBy">';
sb += '<option value="0">Choose...</option>';
sb += '<option value="f">Foo</option>';
sb += '<option value="b">Bar</option>';
sb += '<option value="fb">Foo & Bar</option>';
sb += '</select>';
sb += '<div class="searchBtn">
<button type="button" id="search">Search</button></div>';
sb += '</div>';
domConstruct.place(sb, dom.byId('body'), 1);
var onSearchClick = function(e) {
var r = thisIsWhereDataIsRetrieved(params, whatever);
// r is the response from the db call
if (typeof r === 'object') {
var sr = 'here are the results!';
for (var result in r) {
sr += 'this particular result';
}
if (r.length === 50) {
sr += '<div id="more" class="more">Load More</div>';
}
domConstruct.place(sr, dom.byId('searchWrapper'), 'last');
}
on(query('.more'), 'click', function(e) {
// get next results having done stuff
// to track the state of the search, which works
onSearchClick();
});
}
on(dom.byId('search'), 'click', function(e) {
// get next results having done stuff
// to track the state of the search, which works
onSearchClick();
});
}
}
return search;
});

Google Map doesn't appear on load

I am developing an app where I use 2 API's a.k.a Instagram API and Google Map API. Using AJAX, I get the first set of Images filtered by a tag name. In the 1st set we receive 20 images. Among the received images, the images that have the latitude and longitude info (geotagged images) are displayed on the map.
Now the first time when my page loads, I cannot see the map. But when I press the load more button to get the next set of images, the Map works fine showing my previous images too.
Here is the code for what happens on page load:
$( window ).load(function() {
$.ajax({
type: "GET",
url: "https://api.instagram.com/v1/tags/nyc/media/recent?client_id=02e****",
dataType:'JSONP',
success: function(result) {
onAction(result, 2, tag);
instaMap(result, 2, from);
}
});
});
These are the functions being called:
/**
* [initialize description]
* Initialize the map with markers showing all photos that are geotagged.
*/
var initialize = function(markers) {
var bounds = new google.maps.LatLngBounds(),
mapOptions = {
scrollwheel: false,
mapTypeId: 'roadmap',
center: new google.maps.LatLng(22.50, 6.50),
minZoom: 2
},
gmarkers = [],
map,
positions,
markCluster;
markers = remDuplicate(markers);
// Info Window Content
var infoWindowContent = [];
for (var j = 0; j < markers.length; j++ ) {
var content = [
'<div class="info_content">' +
'<h3>' + markers[j][2] + '</h3>' +
'<a href="' + markers[j][3] + '" target="_blank">' +
'<img src="' + markers[j][4] + '" style="z-index:99999">' + '</a>' +
'</div>'
];
infoWindowContent.push(content);
}
// Display a map on the page
map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions);
map.setTilt(45);
// Display multiple markers on a map
var oms = new OverlappingMarkerSpiderfier(map);
var infoWindow = new google.maps.InfoWindow(), marker, i;
// Loop through our array of markers & place each one on the map
for( i = 0; i < markers.length; i++ ) {
positions = new google.maps.LatLng(markers[i][0], markers[i][1]);
marker = new google.maps.Marker({
position: positions,
map: map,
animation:google.maps.Animation.BOUNCE,
title: markers[i][2]
});
oms.addMarker(marker);
// Allow each marker to have an info window
google.maps.event.addListener(marker, 'click', (function(marker, i) {
return function() {
infoWindow.close();
infoWindow.setContent(infoWindowContent[i][0]);
infoWindow.open(map, marker);
map.setCenter(marker.getPosition());
};
})(marker, i));
gmarkers.push(marker);
}
google.maps.event.addListener(map, 'click', function() {
infoWindow.setMap(null);
});
markCluster = new MarkerClusterer(map, gmarkers);
// Override our map zoom level once our fitBounds function runs (Make sure it only runs once)
var boundsListener = google.maps.event.addListener((map), 'bounds_changed', function(event) {
map.setZoom(2);
google.maps.event.removeListener(boundsListener);
});
};
/**
* [onAction]
* OnAction() function helps in loading non-geotagged pics.
*
* #param {[type]} result [Result retruned from the Instagram API in json format]
* #param {[type]} likey [hearts the user has entered as per which the posts will be filtered]
*/
var onAction = function (result, likey, tag) {
$('.load-pics').remove();
if (result.pagination.next_url) {
paginate = removeURLParameter(result.pagination.next_url, 'count');
}
$.each(result, function(key, value) {
if (key === 'data') {
$.each(value, function(index, val) {
liked = val.likes.count;
link = val.link;
imgUrl = val.images.low_resolution.url;
locations = val.location;
if (liked >= likey) {
if (locations === null) {
output = '<li class="img-wrap">' + '<div class="main-img">' +
'<a href="' + link + '" target="_blank">' +
'<img src="' + imgUrl + '" ><span class="hover-lay"></span></a>' +'<p>' +
'<span class="heart"></span><span class="likes-no">' + liked + '</span>' +
'<span class="comment-box"></span><span class="comment-no">' +
val.comments.count + '</span> ' + '</p>' + '</div>' +
'<div class="img-bottom-part">'+ '' + '<div class="headin-hastag">' +
'by ' + '<h2>Sebastien Dekoninck</h2>#hello <span>#kanye</span> #helloagain #tagsgohere</div>'
+'</div></li>';
$('#instafeed').append(output);
}
}
});
}
});
if ($('#instafeed').children().length === 0) {
alert('There are no pics with ' + likey + ' likes or #' + tag + ' was not found.');
} else {
// $('.not-geo').remove();
// $('#instafeed').before('<button class="not-geo">Click To See Images That Are Not Geotagged <img src="assets/imgs/down.png" ></button>');
}
$('#instafeed').append('<div class="load-pics"><button id="show-more">Show more <span></span></button> </div>');
};
/**
* [instaMap]
* instaMap() will be the function which will deal with all map based functionalities.
*/
var instaMap = function(result, likey, from) {
$('.load-mark').remove();
if (result.pagination.next_url) {
pagiMap = removeURLParameter(result.pagination.next_url, 'count');
}
$.each(result, function(key, value) {
if (key === 'data') {
$.each(value, function(index, val) {
liked = val.likes.count;
link = val.link;
imgUrl = val.images.low_resolution.url;
locations = val.location;
if (liked >= likey) {
if (locations && locations.latitude !== null) {
tempArr = [
locations.latitude,
locations.longitude,
val.user.username,
val.link,
val.images.low_resolution.url
];
mark.push(tempArr);
}
}
});
}
});
if (mark.length) {
initialize(mark);
$('.map-parent-wrapper').append('<div class="load-mark"><button id="show-mark">See More </button></div>');
} else {
alert('No geotagged pics found in the retrieved set. Click see more');
$('.map-parent-wrapper').append('<div class="load-mark"><button id="show-mark">See More </button></div>');
}
};
I have created a See More button to retrieve the next set of images and load those on the Map. When clicking see more, everything seems to work fine. Not sure why it's happening so. Console.log does not show any error. Also, all the values I feed does flow appropriately. I even tried clearing cache. Not sure, why it's happening.
If instaMap is the function which is going to handle all your map based functionality, it has to be the one that loads map in your $( window ).load function ();
Otherwise, if you want Google maps to load on initial window load you need to put below in there:
google.maps.event.addDomListener(window, 'load', initialize);

add a variable in an id attribute in ajax's div.append

I am developing a web app with Django and i have this ajax where i'm refreshing some images from the db in order to display them in a template.
function refreshUploadedImages() {
var inputs = ['Designer Name', 'Color', 'Fabric', 'Type', 'Tag', 'Subtag'];
$.getJSON('/admin/image-uploader/images', function(data) {
$('#uploadedFiles').empty();
for (uiid in data) {
ui = data[uiid];
var div = $('<div>');
div.data('id', ui.id);
// image
var image = new Image();
image.src = ui.url
image.width = 180;
div.append($('<div>').append(image));
// list
var ul = $('<ul>')
div.append(ul)
// inputs
for (input in inputs) {
ul.append(
$('<li>').append(
$('<label>').append(
$('<span>').append(document.createTextNode(inputs[input] + ':'))
).append($('<input>'))));
}
$('#uploadedFiles').append(div);
div.append('<li><input type="button" class="delete-img-btn" id = <<ui.id>> img-id=image.id value="Delete"/></li>');
}
$(window).trigger('uploadedImagesRefresh');
});
$(function(){
//
$('.delete-img-btn').live('click', function() {
//asign the image id from the button attribute 'img-id'
var id= $(this).attr('img-id');
//The data to be send via ajax the server will recieve 2 POST variables ie. 'action' and 'id'(which is the img id)
var data={
'action':'/admin/image-uploader/',
'pk' : id,
'success':refreshUploadedImages
};
//The ajax request.
vary = $('.delete-img-btn').attr('id');
$.post("/admin/image-uploader/delete/"+vary , data);
});
});
}
My problem is, in this line
div.append('<li><input type="button" class="delete-img-btn" id = <<ui.id>> img-id=<<<image.id>>> value="Delete"/></li>');
I want to assign id a variable ui.id i.e (id = <<ui.id>> ) which is defined somewhere outside the div.append. Can you help me on how to do it please.
Is this all you're trying to do?
div.append(
'<li><input type="button" class="delete-img-btn" id="'
+ ui.id + '" img-id="'
+ image.id + '" value="Delete"/></li>');
image.id isn't defined though.

Ajax load multiple divs (WordPress)

I'm using a ajax script to load content from other pages, without having to reload the browser.
For now I'm retrieving the content of the #inside div, but I'm using a full-background slideshow (#full) wich needs to be loaded as wel.
Maybe this can be achieved by loading the content of the #full div also, but I don't know how I could do that.
This is my code:
// Self-Executing Anonymous Function to avoid more globals
(function() {
// Home link isn't dynamic, so default set class name to it to match how dynamic classes work in WordPress
$(".home li.home").removeClass("home").addClass("current_page_item");
// Add spinner via JS, cuz would never need it otherweise
$("body").append("<img src='http://themeclubhouse.digwp.com/images/ajax-loader.gif' id='ajax-loader' />");
var
$mainContent = $("#wrapper"),
$ajaxSpinner = $("#ajax-loader"),
$searchInput = $("#s"),
$allLinks = $("a"),
$el;
// Auto-clear search field
$searchInput.focus(function() {
if ($(this).val() == "Search...") {
$(this).val("");
}
});
$('a:urlInternal').live('click', function(e) {
// Caching
$el = $(this);
if ((!$el.hasClass("comment-reply-link")) && ($el.attr("id") != 'cancel-comment-reply-link')) {
var path = $(this).attr('href').replace(base, '');
$.address.value(path);
$(".current_page_item").removeClass("current_page_item");
$allLinks.removeClass("current_link");
$el.addClass("current_link").parent().addClass("current_page_item");
return false;
}
// Default action (go to link) prevented for comment-related links (which use onclick attributes)
e.preventDefault();
});
// Fancy ALL AJAX Stuff
$.address.change(function(event) {
if (event.value) {
$ajaxSpinner.fadeIn();
$mainContent
.empty()
.load(base + event.value + ' #content', function() {
$ajaxSpinner.fadeOut();
$mainContent.fadeIn();
});
}
var current = location.protocol + '//' + location.hostname + location.pathname;
if (base + '/' != current) {
var diff = current.replace(base, '');
location = base + '/#' + diff;
}
});
})(); // End SEAF
try to repeat the procedure:
// Fancy ALL AJAX Stuff
$.address.change(function(event) {
if (event.value) {
//load ajax image
$ajaxSpinner.fadeIn();
$mainContent.empty().load(base + event.value + ' #content', function() {
$ajaxSpinner.fadeOut();
$mainContent.fadeIn();
});
// repeat here
//load another div
$mainContent.empty().load(base + event.value + ' #mydiv1', function() {
$mainContent
});
//load another div
$mainContent.empty().load(base + event.value + ' #mydiv2', function() {
$mainContent
});
}
let me know if it works, Ciao! :)

Resources