I have a javascript which works perfectly in chrome, FF2/3, and IE9
158: drop_area = $('#drop_area');
159: element = ui.helper;
however I get the following error in IE7 amd IE8:
SCRIPT438: Object doesn't support this property or method
dragdrop.js, line 158 character 2
My knowledge of IE7's debugging features is pretty limited but it doesn't look like I can really inspect the object in question from the console. Does anyone know what might be going on here or how I can better debug this error
EDIT:
Realized a little bit more context might be helpful
function on_element_drop(event, ui){
drop_area = $('#drop_area');
element = ui.helper;
on_element_drop is a callback method for a jQuery UI droppable 'drop' event
/*
* dragdrop.js
* Author: Casey Flynn
* June 10, 2011
* Global variables available to all functions
*/
//Keeps track of elements present in droppable and corresponding offsets/positions
var base_url = 'http://www.bla/';
var global_positions = new Array();
var current_item_group = 0;
var loaded = new Array();
var preloading = true;
var items = new Array(
new Array(
new Array(0, '2-Dollar'),
new Array(1, 'Cards'),
new Array(2, 'Cheese'),
new Array(3, 'Coupons'),
new Array(4, 'DogTags')),
new Array(
new Array(5, 'Doodle'),
new Array(6, 'Dreamcatcher'),
new Array(7, 'Fish'),
new Array(8, 'Fortune'),
new Array(9, 'Groucho')),
new Array(
new Array(10, 'HandTurkey'),
new Array(11, 'Key'),
new Array(12, 'Lolipop'),
new Array(13, 'LotteryTicket'),
new Array(14, 'Map')),
new Array(
new Array(15, 'Napkin'),
new Array(16, 'Notepad'),
new Array(17, 'OldPhoto'),
new Array(18, 'Oragami'),
new Array(19, 'Photo_Baby')),
new Array(
new Array(20, 'Photo_DJ'),
new Array(21, 'Photo_Dogs'),
new Array(22, 'Photo_Moustache'),
new Array(23, 'Pick'),
new Array(24, 'RabitsFoot')),
new Array(
new Array(25, 'Recipe'),
new Array(26, 'Reminder'),
new Array(27, 'Ribbon'),
new Array(28, 'SheetMusic'),
new Array(29, 'Smiley')),
new Array(
new Array(30, 'Spork'),
new Array(31, 'Tape'),
new Array(32, 'Ticket'),
new Array(33, 'USB'),
new Array(34, 'Viewmaster')
)
);
/*
* jQuery 'ready' handler, executes after DOM is ready and
* sets draggable/droppable properties of associated elements
*/
$(function(){
for(var i = 0; i < items.length; i++)
loaded[i] = false;
load_elements(0);
$('#drop_area').droppable({
//accept: '.draggable',
//hoverClass: 'vel_content_active',
drop: on_element_drop
});
$('#countdown').html((10 - global_positions.length)+'');
});
// preloads an array of images
function preload(arrayOfImages) {
$(arrayOfImages).each(function(){
console.log('Preloading ' + this);
$('<img/>')[0].src = this;
// Alternatively you could use:
// (new Image()).src = this;
});
}
/*
* Loads the first set of elements from 'items'
*/
function load_elements(arg0){
set = items[arg0];
box_handle = $('.bottom_content');
elements = '';
//construct html for elements to be added
for(i=0; i<set.length; i++){
elements += '<div class="draggable"><img alt="' + set[i][0] + '" src="' + base_url + 'application/assets/images/items/' + set[i][1] + '.png" /></div>';
}
//clear whatever was in there
box_handle.empty();
// element parent container
box_handle.html(elements);
//assign draggable status
$('.draggable').draggable({
revert: true,
revertDuration: 0,
helper: 'clone'
});
loaded[arg0] = true;
if(preloading){
var prev = next_elements(-1);
var next = next_elements(1);
if(!loaded[prev]){
preload(items[prev])
loaded[prev] = true;
}
if(!loaded[next]){
preload(items[next])
loaded[prev] = true;
}
}
}
function next_elements(arg0){
if((current_item_group + arg0) == -1){
return 6;
}else{
return ((current_item_group + arg0) % 7);
}
}
/*
* Cycles through element groups presented in .bottom_content
-1 to the left 1 to the right
*/
function cycle_elements(arg0){
if((current_item_group + arg0) == -1){
current_item_group = 6;
}else{
current_item_group = ((current_item_group + arg0) % 7);
}
load_elements(current_item_group);
}
/*
* Callback function on drop event for droppable
* Determines position of dropped element in droppable
*/
function on_element_drop(event, ui){
drop_area = $('#drop_area');
element = ui.helper;
//Find relative x/y position of element inside drop_area
var x = Math.floor((element.offset().left - drop_area.offset().left) / drop_area.width() * 100);
var y = Math.floor((element.offset().top - drop_area.offset().top) / drop_area.height() * 100);
//console.log(ui); return;
//console.log(ui.draggable.context.className.indexOf('draggable_dropped'));
if(ui.draggable.context.className.indexOf('draggable_dropped') == -1){
if(global_positions.length >= 10)
return;
//record element position and id
row = new Array(parseInt($(ui.draggable).find("img").attr('alt')),
x,
y);
//Add copy of item to drop_area at same x/y position where it was dropped
add_element_copy_to_div(row);
add_element_copy_to_global_positions(row);
}else{
//Item has already been dropped and is being adjusted, update global_positions
//update global_positions
id = ui.draggable.context.id;
update_global_positions(id, x, y);
}
//$('#countdown').html((10 - global_positions.length)+'');
console.log(global_positions);
}
/*
* Finds element in global_positions and updates x & y values
*/
function update_global_positions(id, newX, newY){
image_id = global_positions[id][0];
global_positions[id] = new Array(image_id, newX, newY);
/*
var old_array = global_positions[find_index(global_positions, index)];
var new_array = new Array(old_array[0], newX, newY);
//.splice(i,1,pos) -- remove 1 element at index i and replace with pos
global_positions.splice(index,1,new_array);
*/
}
/*
* Helper function, determines if element is already present in 'global_positions'
* Replaces if present, adds otherwise
*/
function add_element_copy_to_global_positions(pos){
global_positions.push(pos);
/*
var found = false;
for(i=0;i<global_positions.length;i++){
if(global_positions[i][0] == pos[0]){
//.splice(i,1,pos) -- remove 1 element at index i and replace with pos
global_positions.splice(i,1,pos);
found = true;
}
}
if(!found)
global_positions.push(pos);
*/
}
/*
* Helper function, adds a copy of the draggable that was dropped into the droppable
* for user visualization
*/
function add_element_copy_to_div(pos){
drop_area = $('#drop_area');
id = global_positions.length;
console.log('id: ' + id);
//Convert % x&y offsets into absolute pixel offsets based on div size
x = Math.floor(drop_area.width() * (pos[1] / 100));
y = Math.floor(drop_area.height() * (pos[2] / 100));
/*------- Find filename of image that has been dropped ------*/
index = find_index(items[current_item_group], pos[0]);
filename = items[current_item_group][index][1];
drop_area.append('<div style="position:absolute;" class="draggable_dropped" id="' + id + '"><img src="' + base_url + 'application/assets/images/items/' + filename + '.png" /></div>');
$('#'+id).css('left', x);
$('#'+id).css('top', y);
//Set the newly dropped element to draggable so it can be repositioned
$('#'+id).draggable({
stop:function(event, ui){
if(!is_valid_position(ui)) //invalid drop
delete_item(ui);
}
});
}
/*
* deletes element from global_positions and #drop_area when user drops item outside #drop_area
* also adjusts id attributes of all items
*/
function delete_item(ui){
id = ui.helper.context.id;
$('#'+id).remove();
global_positions.splice(id, 1);
$('#drop_area div').each(function(index){
if(parseInt($(this).attr('id')) > parseInt(id))
$(this).attr('id', parseInt($(this).attr('id')) - 1);
});
console.log(global_positions);
}
/*
* helper for add_element_copy_to_div
*/
function is_valid_position(ui){
drop_area = $('#drop_area');
element = ui.helper;
//Find relative x/y position of element inside drop_area
var x = Math.floor((element.offset().left - drop_area.offset().left) / drop_area.width() * 100);
var y = Math.floor((element.offset().top - drop_area.offset().top) / drop_area.height() * 100);
if( (x < -5) ||
(x > 105) ||
(y < -5) ||
(y > 105))
return false;
return true;
}
/*
* helper for add_element_copy_to_div
*/
function find_index(items, search_index){
for(i=0; i < items.length; i++){
if(items[i][0] == search_index)
return i;
}
}
/*
* Convert global_position array to JSON and submit to server via ajax along with csrf_token
*/
function update_layout(){
$.ajax({
url: '/projects/velcro/index.php/welcome/update_layout',
type: 'post',
data: {'layout_json' : $.toJSON(global_positions), 'ci_csrf_token' : $('input[name=ci_csrf_token]').val()},
success: function(data, textStatus, jqXHR){
//Redirect user to next page here...
if(data == '1'){
//alert('Layout successfully saved');
}else{
//alert('Layout save failed');
}
location.href = 'http://www.messageamplify.com/projects/velcro/index.php/welcome/create2';
},
error: function(jqXHR, textStatus, errorThrown){
console.log('error: '+jqXHR);
console.log(textStatus);
console.log(errorThrown);
}
});
}
//End file 'dragdrop.js'
drop_area = $('#drop_area');
This line will always throw a error in IE. That's because in IE every element with id will be accessible through the global window object, in this case, window.drop_area. But the fact that window is a global object makes possible to access the object without the global, in this case, just drop_area.
So, the sentence drop_area = $('#drop_area'); is not trying to assign a object to a variable, but is trying to overwrite a element reference with another object. This is the error you are seeing as a runtime exception.
To bypass this exception, you need to assign the jQuery object to a variable. To do this, you have two choices:
use a var statement to scope the variable inside the function that contains the code and hide the access to window.drop_area from the global, like var drop_area = $('#drop_area');, or
use another variable name, like var dropArea = $('#drop_area');
And, as a good advice, always give a scope to the variables you are using with var statement.
IE8 has a built in debugger. Press F12, click the Script tab, click Start Debugging, and debug away.
Related
I'm working on implementing a system where elements can be dragged and dropped to create flowcharts. My Issue is with saving the flowchart so that it could be reloaded when needed. For now I've created a method that saves all the previous data of the element onto the final array that holds only elements that are dropped on the container. But I'm getting a Trivial Error as Undefined variable on the debugging interface. Hence I'm not getting the intended output and the alert messages that I included are not being printed when the condition is met.
Code in Context
function saveFlowchart()
{
var nodes = [];
var matches = [];
var searchEles = document.getElementById("container").children;
for(var i = 0; i < searchEles.length; i++)
{
matches.push(searchEles[i]);
var idOfEl = searchEles[i].id;
if(searchEles[i].id !=null || searchEles[i].id !="")
{
var $element = $("#" + searchEles[i].id);
var dropElem = $("#" + searchEles[i].id).attr('class');
var position = $element.position();
position.bottom = position.top + $element.height();
position.right = position.left + $element.width();
alert("class:"+dropElem+"\nTop position: " + position.top + "\nLeft position: " + position.left + "\nBottom position: " + position.bottom + "\nRight position: " + position.right);
finalArray[idOfEl-1][0]= idOfEl;
finalArray[idOfEl-1][1]= dropElem;
var elId = parseInt(idOfEl);
if (dropElem == "stream ui-draggable")
{
for(var count=0;count<100;count++)
{
alert("One loop opened with count="+count);
if(createdImportStreamArray[count][0]==elId)
{
finalArray[elId-1][2]= createdImportStreamArray[count][1]; //Selected Stream from Predefined Streams
finalArray[elId-1][3]= createdImportStreamArray[count][2]; //asName
alert("createdImportStreamArray[count][0]==elId");
}
else if(createdExportStreamArray[count][0]==elId)
{
finalArray[elId-1][2]= createdExportStreamArray[count][1]; //Selected Stream from Predefined Streams
finalArray[elId-1][3]= createdExportStreamArray[count][2]; //asName
}
else if(createdDefinedStreamArray[count][0]==elId)
{
finalArray[elId-1][2]= createdDefinedStreamArray[count][1]; //Stream Name
finalArray[elId-1][3]= createdDefinedStreamArray[count][4]; //Number of Attributes
finalArray[elId-1][4]=[];
for(var f=0;f<createdDefinedStreamArray[r][4];f++)
{
finalArray[elId-1][4][f][0]=createdDefinedStreamArray[count][2][f][0]; //Attribute Name
finalArray[elId-1][4][f][1]=createdDefinedStreamArray[count][2][f][1]; // Attribute Type
}
}
alert("One loop closed with count="+count);
}
alert("Loop ended with count="+count);
}
else if (dropElem == "wstream ui-draggable")
{
ElementType="wstream";
}
// else if conditions...
alert(finalArray);
}
}
//Code to output the connection details in a json format
//The following is not affected by the above mentioned error
$(".node").each(function (idx, elem) {
var $elem = $(elem);
var endpoints = jsPlumb.getEndpoints($elem.attr('id'));
console.log('endpoints of '+$elem.attr('id'));
console.log(endpoints);
nodes.push({
blockId: $elem.attr('id'),
nodetype: $elem.attr('data-nodetype'),
positionX: parseInt($elem.css("left"), 10),
positionY: parseInt($elem.css("top"), 10)
});
});
var connections = [];
$.each(jsPlumb.getConnections(), function (idx, connection) {
connections.push({
connectionId: connection.id,
pageSourceId: connection.sourceId,
pageTargetId: connection.targetId
});
});
var flowChart = {};
flowChart.nodes = nodes;
flowChart.connections = connections;
flowChart.numberOfElements = numberOfElements;
var flowChartJson = JSON.stringify(flowChart);
//console.log(flowChartJson);
$('#jsonOutput').val(flowChartJson);
}
Debugging Interface
According to this the count variable in the for loop is undefined. I've tried taking the first statement var count=0 outside the loop declaration part and defining it in the very beginning of the method. But that simply checks for count=0 against the conditions and doesn't increment at all.
Any help in this regard will be highly appreciated as I've been working on this minor error for almost 2 days now.
I am trying to "extend" a DIV via Javascript by using a newly created div as prototype of my object.
As I understand Javascript, on creating a new instance of my Object via "new", the prototype-object is copied, assigned to "this" an then the function is executed (as the constructor).
Everything seems to work, except that whenever I create another object, and add it to the DOM, it "replaces" the original div. To be more exact: The constructor always changes the same div.
Using MyTest.prototype = document.createElement("div"); gives me the described behavior, the two commented lines after that in my code example are what I also tried, but to no avail.
I know trying to extend the DOM is frowned upon, but I want to understand this behavior, because I thought I knew how prototypes work and this simply does not fit my idea.
Here is a minimal example of what I am trying to do:
<!DOCTYPE html>
<html>
<head>
<title>Div-Prototype-Test</title>
<script type="text/javascript">
var height = 20;
var top = 0;
function MyTest() {
var r = Math.floor(Math.random() * 256);
var g = Math.floor(Math.random() * 256);
var b = Math.floor(Math.random() * 256);
this.style.backgroundColor = "rgb("+ r +","+ g +","+ b +")";
this.style.position = "absolute";
this.style.width = "500px";
this.style.height = height + "px";
this.style.top = top + "px";
top += height;
document.getElementsByTagName("body")[0].appendChild(this);
}
MyTest.prototype = document.createElement("div");
// MyTest.prototype = document.createElement("div").cloneNode(true);
// MyTest.prototype = new Element();
window.addEventListener(
"load",
function() {
var a = new MyTest();
var b = new MyTest();
var c = new MyTest();
var d = new MyTest();
}
);
</script>
</head>
<body>
</body>
</html>
PS: Because of a certain Javascript-Framework my search for anything that changes the prototype in Javascript always resulted in hundreds of results that had nothing to do with my problem - please tell me if I missed a question that already discusses this.
Edit:
To make my question clearer:
Here is an example where I use an object as prototype - its properties get copied.
function A() {
}
A.prototype = { property: 4 };
A.prototype.set = function(num) {
this.property = num;
}
window.addEventListener(
"load",
function() {
var message = "";
var x1 = new A();
message += "A1 : "+ x1.property +"\n";
x1.set(15);
message += "A1 : "+ x1.property +"\n";
var x2 = new A();
message += "A2 : "+ x2.property +"\n";
alert(message);
}
);
The alert then said:
A1 : 4
A1 : 15
A2 : 4
The Div in my first example however does not seem to be copied, it behaves like a Singleton or Monostate. Should it not go like this?
Protype object is copied into a new object
the new object is assigned to "this"
this is given to the constructor
this is returned by the constructor (if no return statement is specified)
MyTest.prototype = document.createElement("div");
This line is executed only once. It creates a MyTest.prototype object which is also a DOM element <div>. Every MyTest object will receive this same prototype. Therefore, every MyTest object you create will be associated with this single <div> you created only once. You will have to create a new <div> for every MyTest.
Try this pattern:
MyTest = function() {
var myDiv = document.createElement("div");
var r = Math.floor(Math.random() * 256);
var g = Math.floor(Math.random() * 256);
var b = Math.floor(Math.random() * 256);
myDiv.style.backgroundColor = "rgb("+ r +","+ g +","+ b +")";
myDiv.style.position = "absolute";
myDiv.style.width = "500px";
myDiv.style.height = height + "px";
myDiv.style.top = top + "px";
top += height;
document.getElementsByTagName("body")[0].appendChild(myDiv);
return myDiv;
}
This function creates a new <div>, using the createElement() call. Then, it sets all the properties you want on that new <div>. Finally, it returns your new <div>. As such, you can call it as
var myNewDiv = MyTest();
var myNewDiv = new MyTest();
Both options would work. In the second case a dummy new object is created by the new keyword, but it doesn't matter, as the new <div> created inside the function is actually returned.
You are mixing all kind of things. First, check my answer to this SO question. Second, Extending the Element Object can be done, but is is not supported by all browsers. Check this SO question.
Seems to me that you are planning to add elements to the document in some standardized way. Your code could be rewritten to (I modified it a bit):
function appendElement(content,id) {
var rgb = 'rgb('+ [Math.floor(Math.random() * 256),
Math.floor(Math.random() * 256),
Math.floor(Math.random() * 256)].join(',') +')';
var top = Math.floor( Math.random() * 300 + 20 );
var left = Math.floor( Math.random() * 100 + 10 );
this.style.backgroundColor = rgb;
this.style.position = "absolute";
this.style.width = "500px";
this.style.height = "200px";
this.style.left = left+"px";
this.style.top = top+"px";
this.innerHTML = content || '';
this.id = id || Math.Random*10000+1
document.getElementsByTagName("body")[0].appendChild(this);
}
Now you can use this to append any element to the document using `appendElement as follows:
appendElement.call(document.createElement('div'),'empty div');
appendElement.call(document.createElement('span'),'new empty span','span1');
Would that be an idea for what you aim at?
I found a workaround, it basically works the other way round - the prototype is a blank object and I copy the new objects data into a div in the constructor:
var height = 20;
var top = 0;
function deepCopy(fromObject, toObject, depth) {
if(typeof(fromObject) != "object" || typeof(toObject) != "object") {
// throw "deepCopy only copies objects"
return;
}
if (typeof(depth) == "undefined") {
depth = 0;
} else if (depth > 100) {
// Recursion depth too high. Abort.
// throw "deepCopy recursion depth cap hit"
return;
}
for (var key in fromObject) {
if (typeof(fromObject[key]) == "object" && fromObject[key] != null) {
if (typeof(fromObject[key].nodeType) != "undefined") {
toObject[key] = fromObject[key].cloneNode(true);
} else {
if (typeof(toObject[key]) != "object") {
toObject[key] = {};
}
deepCopy(fromObject[key], toObject[key], depth + 1);
}
}
toObject[key] = fromObject[key];
}
}
function MyTest() {
// This is ugly...
var self = document.createElement("div");
deepCopy(MyTest.prototype, self);
var r = Math.floor(Math.random() * 256);
var g = Math.floor(Math.random() * 256);
var b = Math.floor(Math.random() * 256);
self.style.backgroundColor = "rgb("+ r +","+ g +","+ b +")";
self.style.position = "absolute";
self.style.width = "500px";
self.style.height = height + "px";
self.style.top = top + "px";
top += height;
document.getElementsByTagName("body")[0].appendChild(self);
return self;
}
MyTest.prototype = {};
// MyTest.prototype = document.createElement("div").cloneNode(true);
// MyTest.prototype = new Element();
window.addEventListener(
"load",
function() {
var a = new MyTest();
var b = new MyTest();
var c = new MyTest();
var d = new MyTest();
}
);
Although I have the feeling that my deepCopy-function is a rather inelegant (and possibly very buggy) way to perform the task, but the other way round with using cloneNode() did not work.
My original problem came from this: When the prototype is copied, all scalar values are copied, while all objects are simply referenced (like copying pointers, the pointer value is duplicated, but not the data it points to).
Hope this helps someone.
I have a function that creates a gallery of flickr sets pulled from my flickr account. I am getting the set numbers from a database and using a while loop to display the first image from the set. Each element of the table has the same class and i am applying a Javascript function to them. Unfortunately each table element is displaying the same photo, the last one pulled from the database.
$(document).ready(function() {
var flickrUrl="";
$('.gallery_table_data').each(function(){
flickrUrl = $(this).attr('title');
$('.flickr_div').flickrGallery({
"flickrSet" : flickrUrl,
"flickrKey" : "54498f94e844cb09c23a76808693730a"
});
});
});
and the images dont show up at all? can anyone help??
Here is the flickr jquery in case that's the problem:
var flickrhelpers = null;
(function(jQuery) {
jQuery.fn.flickrGallery = function(args) {
var $element = jQuery(this), // reference to the jQuery version of the current DOM element
element = this; // reference to the actual DOM element
// Public methods
var methods = {
init : function () {
// Extend the default options
settings = jQuery.extend({}, defaults, args);
// Make sure the api key and setID are passed
if (settings.flickrKey === null || settings.flickrSet === null) {
alert('You must pass an API key and a Flickr setID');
return;
}
// CSS jqfobject overflow for aspect ratio
element.css("overflow","hidden");
// Get the Flickr Set :)
$.getJSON("http://api.flickr.com/services/rest/?format=json&method=flickr.photosets.getPhotos&photoset_id=" + settings.flickrSet + "&api_key=" + settings.flickrKey + "&jsoncallback=?", function(flickrData){
var length = 1;
var thumbHTML = '';
for (i=0; i<length; i++) {
var photoURL = 'http://farm' + flickrData.photoset.photo[i].farm + '.' + 'static.flickr.com/' + flickrData.photoset.photo[i].server + '/' + flickrData.photoset.photo[i].id + '_' + flickrData.photoset.photo[i].secret +'.jpg'
settings.imgArray[i] = photoURL;
settings.titleArray[i] = flickrData.photoset.photo[i].title;
}
// Get the position of the element Flickr jqfobj will be loaded into
settings.x = element.offset().left;
settings.y = element.offset().top;
settings.c = settings.x + (element.width() / 2);
settings.ct = settings.y + (element.height() / 2);
// When data is set, load first image.
flickrhelpers.navImg(0);
});
}
}
// Helper functions here
flickrhelpers = {
navImg : function (index) {
// Set the global index
currentIndex = index;
// Create an image Obj with the URL from array
var thsImage = null;
thsImage = new Image();
thsImage.src = settings.imgArray[index];
// Set global imgObj to jQuery img Object
settings.fImg = $( thsImage );
// Display the image
element.html('');
element.html('<img class="thsImage" src=' + settings.imgArray[index] + ' border=0>');
// Call to function to take loader away once image is fully loaded
$(".thsImage").load(function() {
// Set the aspect ratio
var w = $(".thsImage").width();
var h = $(".thsImage").height();
if (w > h) {
var fRatio = w/h;
$(".thsImage").css("width",element.width());
$(".thsImage").css("height",Math.round(element.width() * (1/fRatio)));
} else {
var fRatio = h/w;
$(".thsImage").css("height",element.height());
$(".thsImage").css("width",Math.round(element.height() * (1/fRatio)));
}
if (element.outerHeight() > $(".thsImage").outerHeight()) {
var thisHalfImage = $(".thsImage").outerHeight()/2;
var thisTopOffset = (element.outerHeight()/2) - thisHalfImage;
$(".thsImage").css("margin-top",thisTopOffset+"px");
}
if (settings.titleArray[currentIndex] != "") {
$(".flickr_count").append(settings.titleArray[currentIndex]);
}
});
},
toggleUp : function() {
$("#flickr_thumbs").slideUp("slow");
}
}
// Hooray, defaults
var defaults = {
"flickrSet" : null,
"flickrKey" : null,
"x" : 0, // Object X
"y" : 0, // Object Y
"c" : 0, // Object center point
"ct" : 0, // Object center point from top
"mX" : 0, // Mouse X
"mY" : 0, // Mouse Y
"imgArray" : [], // Array to hold urls to flickr images
"titleArray" : [], // Array to hold image titles if they exist
"currentIndex" : 0, // Default image index
"fImg" : null, // For checking if the image jqfobject is loaded.
}
// For extending the defaults!
var settings = {}
// Init this thing
jQuery(document).ready(function () {
methods.init();
});
// Sort of like an init() but re-positions dynamic elements if browser resized.
$(window).resize(function() {
// Get the position of the element Flickr jqfobj will be loaded into
settings.x = element.offset().left;
settings.y = element.offset().top;
settings.c = settings.x + (element.width() / 2);
settings.ct = settings.y + (element.height() / 2);
});
}
})(jQuery);
The big problem is in your $.each loop. I am going to assume the plugin will work for all the elements you are looping over although have doubts that it will.
WHen you select $('.flickr_div') on each pass it affects all the elements in page with that class...so only the last pass of loop is valid
$(document).ready(function() {
var flickrUrl="";
$('.gallery_table_data').each(function(){
flickrUrl = $(this).attr('title');
/* this is your problem , is selecting all ".flickr_div" in page on each loop*/
//$('.flickr_div').flickrGallery({
/* without seeing your html structure am assuming
next class is inside "this"
try: */
$(this).find('.flickr_div').flickrGallery({
"flickrSet" : flickrUrl,
"flickrKey" : "54498f94e844cb09c23a76808693730a"
});
});
});
EDIT This same concept of using find() should also be refactoered into code within the plugin. Plugin should have all ID's replaced with classes.
Plugin really does not look well constructed for multiple instances within a page
I might be wrong here, but won't this (in your flickrGallery object)
$("body").append('<div id="flickr_loader"></div>');`
create multiple elements with the same ID? And the same for images in flickrhelpers:
element.html('<img id="thsImage" src=' + settings.imgArray[index] + ' border=0>');
I have a problem with settings on multiple instances on a jquery plugin.
If I cast my plugin multiple times and alert them on an onclick binding, it always alerts the same parameter.
This is my plugin.
/**
* Provide user search for input elements.
*
* Call this jQuery plugin on an input element to add a button
* for searching the active directory on userdata.
* Returned data are saved in declared target input elements.
*
* Depends:
* jQuery UI Dialog
*
* #license http://www.opensource.org/licenses/mit-license.php
* #version 1.0
*/
;(function ( $, window, document, undefined ){
var PROP_NAME = 'userdata';
var IS_IFRAME = ( self !== top ) ? true : false;
var rfuuid = new Date().getTime();
var $jParent = ( self !== top ) ? window.parent.jQuery.noConflict() : $;
/**
* Userdata manager.
* Use the singleton instance of this class, $.userdata, to interact with the date picker.
* Settings for (groups of) date pickers are maintained in an instance object,
* allowing multiple different settings on the same page.
*/
function Userdata() {
this.regional = [];
this.regional[''] = {
abortText: 'Abbrechen',
acceptText: 'Hinzufügen',
errorTitle: 'Fehler beim Suchen',
errorFilterText: 'Suchkriterien einschränken.',
errorVoidText: 'Der gesuchte Kontakt konnte nicht gefunden werden.',
errorScriptText: 'Bei der Suche ist ein Fehler aufgetreten. Falls der Fehler wieder auftritt, wenden Sie sich bitte an Ihren Webadministrator.',
searchText: 'Durchsuchen',
selectionTitle: 'Namen überprüfen',
selectionInfoText: '"%s" kommt mehrmals vor.',
selectionDescText: 'Wählen Sie die Adresse aus, die Sie verwenden möchten:'
};
this._defaults = {
ajaxURL: 'userdata.php',
buttonClass: 'rf-button secondary',
buttonContainer: '<div>',
buttonContainerClass: 'grid_2',
targets: {}
}
$.extend(this._defaults, this.regional['']);
}
$.extend(Userdata.prototype, {
/**
* Override the default settings for all instances of the userdata plugin.
*
* #param object settings The new settings to use as defaults (anonymous object).
* #return the manager object.
*/
setDefaults: function( settings ){
$.extend(this._defaults, settings);
return this;
},
/**
*
*
* #param object input DOM input element.
* #param object settings Settings for attaching new userdata functionality.
*/
_attachDialog: function( input, settings ){
var settings = $.extend(this._defaults, settings);
if ( !document.getElementById('rf-userdata-dialog') ){
var inst = $jParent('<div id="rf-userdata-dialog"></div>').appendTo('body');
inst.dialog({ autoOpen: false, close: function(){ $jParent('body').css('overflow', 'auto'); input.focus(); }, modal: true, resizable: false }).after('<span class="ui-dialog-footer" /><span class="ui-dialog-footer-edge" />');
}
else {
var inst = $('#rf-userdata-dialog');
}
input.settings = settings;
$(input).data('settings', settings);
this._attachButton(input, inst);
},
/**
*
*
* #param object input DOM input element.
* #param object inst jQuery dialog instance.
*/
_attachButton: function( input, inst ){
var manager = this,
$input = $(input);
// Create search button.
var $button = $('<button>').attr('type', 'button').html(input.settings.searchText).addClass(input.settings.buttonClass);
alert(dump($(input).data('settings'))); // WORKS FINE, everything is unique.
/**
* Bind manager._searchUserdata() function on button click.
*/
$button.bind('click', { settings : $(input).data('settings') }, function(e){
alert(dump(e.data.settings)); // DOES NOT WORK, always settings from last plugin call...
//manager._searchUserdata(input, inst);
});
/**
* Insert container with search button after input field.
*/
$input.closest('[class*="grid_"]').after(function(){
return $(input.settings.buttonContainer).addClass(input.settings.buttonContainerClass).append($button);
});
/**
* Change default enterkey behaviour on triggering the userdata button.
*/
$input.bind('focusin', function(){
$input.bind('keydown.enterOpen', function(e){
if ( e.keyCode === 13 ){
$button.trigger('click');
return false;
}
});
});
/**
* Unbind keydown event with enterOpen namespace.
*/
$input.bind('focusout', function(){
$input.unbind('keydown.enterOpen');
});
},
/**
*
*
* #param object input DOM input element.
* #param object inst jQuery dialog instance.
*/
_searchUserdata: function( input, inst ){
var manager = this,
$input = $(input),
value = $input.val();
/**
* Perform an Ajax request to get the userdata of specified user.
*/
$.ajax({
url: input.settings.ajaxURL,
dataType: 'json',
data: 'value=' + encodeURIComponent(value),
/**
* A pre-request callback function.
* Returning false in the beforeSend function will cancel the request.
*/
beforeSend: function(){
// If value is smaller than two characters is equal space character
// call showError and cancel ajax call.
if ( value.length <= 2 || value === ' ' || value === '' ){
manager._showError(input.settings.errorFilterText, inst, input);
return false;
}
},
/**
* A function to be called if the request succeeds.
*
* #see manager._showError() for error display.
* #see manager._checkName() for selecting dialog.
*
* #param object data LDAP userdata returned from server.
*/
success: function( data ){
if ( $.isEmptyObject(data) ){
manager._showError(input.settings.errorVoidText, inst, input);
}
else if ( data.error === 4 ){
manager._showError(input.settings.errorFilterText, inst, input);
}
else {
// If request returned more than one user, call checkName() function.
if ( data.length > 1 ){
manager._checkName(input, inst, data);
}
else {
manager._setUserdata(inst, data[0], input);
}
}
},
/**
* A function to be called if the request fails.
*
* #see manager._showError() for more information.
*
* #param object jqXHR XMLHttpRequest object.
* #param string textStatus Description of occurred error.
* #param object errorThrown Exception object.
*/
error: function( jqXHR, textStatus, errorThrown ){
manager._showError(input.settings.errorScriptText, inst, input);
}
});
},
/**
*
*
* #param string error Error to display.
* #param object inst jQuery dialog instance.
* #param object input DOM input element.
*/
_showError: function( error, inst, input ){
inst.html('<div class="ui-dialog-container">' + error + '</div>')
inst.dialog({ title: input.settings.errorTitle, width: 400 });
inst.dialog('open');
},
/**
*
*
* #param object input DOM input element.
* #param object inst jQuery dialog instance.
* #param array data LDAP userdata.
*/
_checkName: function( input, inst, data ){
var manager = this,
$container = $('<div>').addClass('ui-dialog-container').html('<p>' + sprintf(input.settings.selectionInfoText, $(input).val()) + '</p><p>' + input.settings.selectionDescText + '</p>'),
$tableWrapperOuter = $('<div>').addClass('rf-select-list-wrapper-outer'),
$tableWrapperInner = $('<div>').addClass('rf-select-list-wrapper-inner').css('height', 240),
$table = $('<table>').addClass('rf-select-list'),
$thead = $('<thead>').html('<tr><th>Name</th><th>Position</th><th>Telefon</th><th>Ort</th><th>E-Mail</th><th>Alias</th></tr>'),
$tbody = $('<tbody>');
// Loop trough userdata and create a table row for each entry.
for ( var i = 0, length = data.length; i < length; i++ ){
var $row = $('<tr>').html(function(){
this.onselectstart = function(){ return false; }
//return '<td class="user-icon">' + data[i].sn + ' ' + data[i].givenname + '</td><td>' + data[i].title + '</td><td>' + data[i].telephonenumber + '</td><td>' + data[i].location + '</td><td>' + data[i].mail + '</td><td>' + data[i].cn + '</td>';
return '<td class="user-icon">' + data[i].sn + ' ' + data[i].givenname + '</td><td>' + data[i].title + '</td><td>' + data[i].location + '</td><td>' + data[i].cn + '</td>';
});
$row.bind('click', { obj: data[i] }, function(e){
var $this = $(this);
// Temp-save data from selection for ok-button.
inst.selection = e.data.obj;
$this.siblings().removeClass('ui-state-active');
$this.addClass('ui-state-active');
});
$row.bind('dblclick', { obj: data[i] }, function(e){
inst.dialog('close');
manager._setUserdata(inst, e.data.obj, input);
});
$row.appendTo($tbody);
}
// Trigger first row selection.
$tbody.find('tr').first().trigger('click');
// Hide scrollbar on form body to prevent scrolling problem.
$jParent('body').css('overflow', 'hidden');
// Create buttons and append them to a container.
var $buttonAccept = $('<button>').addClass("rf-button primary").html(input.settings.acceptText).bind('click', function(){
inst.dialog('close');
manager._setUserdata(inst, inst.selection, input);
});
var $buttonAbort = $('<button>').addClass("rf-button secondary").html(input.settings.abortText).bind('click', function(){
inst.dialog('close');
});
// Toggle 'rf-button-hover' class on buttons hover state.
$buttonAccept.hover(function(){ $buttonAccept.toggleClass('rf-button-hover'); });
$buttonAbort.hover(function(){ $buttonAbort.toggleClass('rf-button-hover'); });
var $buttonContainer = $('<div>').addClass('float-right').append($buttonAccept, $buttonAbort);
// Append dialog html to container.
$container.append($tableWrapperOuter.append($tableWrapperInner.append($table.append(/*$thead,*/ $tbody))), $buttonContainer);
inst.html($container);
inst.dialog({ title: input.settings.selectionTitle, width: 800 });
inst.dialog('open');
},
/**
*
*
* #param object inst jQuery dialog instance.
* #param array data LDAP userdata.
*/
_setUserdata: function( inst, data, input ){
for ( var target in input.settings.targets ){
var values = [];
if ( typeof(input.settings.targets[target]) === 'object' ){
for ( var i = 0, length = input.settings.targets[target].length; i < length; i++ ){
values.push(data[input.settings.targets[target][i]]);
}
}
else {
values.push(data[input.settings.targets[target]]);
}
$(target).val(values.join(' '));
}
}
});
/**
* Invoke the userdata functionality.
*
* #param object options Settings for attaching new userdata functionality.
* #return jQuery object.
*/
$.fn.userdata = function( options ){
// Verify an empty collection wasn't passed.
if ( !this.length ){
return this;
}
/**
* Loop through each plugin object.
*/
return this.each(function(){
$.userdata._attachDialog(this, options);
});
};
$.userdata = new Userdata();
$.userdata.uuid = new Date().getTime();
})( jQuery, window, document );
I call it in my html multiple times:
$('#inputid_1').userdata({ targets: {'#targetid_1': 'cn'} });
$('#inputid_2').userdata({ targets: {'#targetid_2': 'phone'} });
Now if you look at the _attachButton method, there are two alerts. One outside the click bind and one inside the click bind. Outside the click bind, the settings are unique foreach plugin call. Inside the click bind, it always alerts the settings from the last call, even if I pass them with event.data.
Extend the settings like this:
var settings = $.extend(settings, this._defaults); // invert the params
or
var settings = $.extend({}, this._defaults, settings);
Why ?
The $.extend() takes as first param a target. So you were merging the properties of this._default with the settings and not the contrary.
The second form (with {}) says: ignore target, let both this._default and settings untouched, simply return a merged object (hope i'm clear ^^).
See jQuery documentation about .extend().
I'm currently trying to carry variables over in a GM script when a page refreshes. Basically I'm using the script from "Using Greasemonkey and jQuery to intercept JSON/AJAX data from a page, and process it." -- which I used and added to quite a bit already.
I've singled out some of the variables, and would like to carry them over when the page refreshes, but they don't. It resets the variables to 0 every time it refreshes, and doesn't carry over.
This is basically what I've got...or rather the important pieces, the script is too getting too long to paste the whole script for this question.
var A12_old1 = GM_getValue('A12_old1', 0);
var A12_old2 = GM_getValue('A12_old2', 0);
var A12_old3 = GM_getValue('A12_old3', 0);
//then further on...
A12_current = parseFloat(singleAuctionData[8]);
A12_rest = singleAuctionData[1];
if (t_int < 1) {
if (t_test) {
alert_test = true;
t_test = false;
A12reset_go = true;
A12_old3 = A12_old2;
A12_old2 = A12_old1;
A12_old1 = A12_current;
}
}
/* so basically doing some calculations as to what the values should be then to
carry them over, at almost the end of the script, but still running every
second, there is:
*/
if (alert_test) {
alert_test = false;
alert(A12_old1 + ' ' + A12_old2 + ' ' + A12_old3);
}
GM_setValue('A12_old1', A12_old1);
GM_setValue('A12_old2', A12_old2);
GM_setValue('A12_old3', A12_old3);
}
/*but it isn't carrying the 3 values over when the page refreshes.
It resets to '0'....
*/
Can anyone please just show me where I might be going wrong?
Update:
Right.. here is a shortened version of the script that gives me trouble, still with the same problems:
// ==UserScript==
// #name setvalue test
// #include http://www.trada.net/*
// #require http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js
// ==/UserScript==
var auctiontyp = 0;
var my_test = GM_getValue("tsttst", 0);
var my_test2 = GM_getValue("tsttst2", 0);
var h = 0;
var m = 0;
var s = 0;
var t_int = 0;
var t_str = '';
var A12_current = 0;
var a_tst = true;
var a_tst2 = true;
var A12_old1 = GM_getValue("A12_old1", 0);
var A12_old2 = GM_getValue("A12_old2", 0);
var A12_old3 = GM_getValue("A12_old3", 0);
if (a_tst) {
alert(my_test);
GM_setValue("tsttst", 5);
a_tst = false;
}
//--- Create a cell for transmitting the date from page scope to GM scope.
$('body').prepend('<div id="LatestJSON_Data"></div>');
var J_DataCell = $('#LatestJSON_Data');
//--- Evesdrop on the page's AJAX calls and paste the data into our special div.
unsafeWindow.$('body').ajaxSuccess(
function (event, requestData) {
J_DataCell.text(requestData.responseText);
} );
//--- Listen for changes to the special div and parse the data.
J_DataCell.bind('DOMSubtreeModified', ParseJSON_Data);
function ParseJSON_Data() {
//--- Get the latest data from the special cell and parse it.
var myJson = J_DataCell.text();
var jsonObj = $.parseJSON(myJson);
//--- The JSON should return a 2-D array, named "d".
var AuctionDataArray = jsonObj.d;
//--- Loop over each row in the array.
$.each(AuctionDataArray, function (rowIndex, singleAuctionData) {
//--- Print the 7th column.
console.log('Row: ' + (parseInt(rowIndex) + 1) + ' Column: 7 Value: ' + singleAuctionData[6]);
if (a_tst2) {
alert(my_test2);
GM_setValue("tsttst2", 15);
alert(A12_old1 + ' ' + A12_old2 + ' ' + A12_old3);
a_tst2 = false;
}
t_str = singleAuctionData[10];
var time = t_str.split(":");
h = 3600 * parseInt(time[0], 10);
m = 60 * parseInt(time[1], 10);
s = parseInt(time[2], 10);
t_int = h + m + s;
auctiontyp = parseInt(singleAuctionData[4]);
if (auctiontyp == 4) {
A12_current = parseFloat(singleAuctionData[8]);
if (t_int < 1) {
A12_old3 = A12_old2;
A12_old2 = A12_old1;
A12_old1 = A12_current;
GM_setValue("A12_old1", A12_old1);
GM_setValue("A12_old2", A12_old2);
GM_setValue("A12_old3", A12_old3);
}
}
});
}
The variable "my_test" is carried over, but "my_test2", which run in the json array, as well as my other variables isn't carried over by GM_setvalue. I'm unsure why, but this is to what I was able to narrow it down to.
A few things:
Those scripts are trying to store floats. GM_setValue() only works on: strings, integers and booleans.
Fortunately, there is an extension for that; more below.
The later calls to GM_setValue failed because they were inside an event handler.
If you had been watching with the Firebug console (always debug that way!), a red error message scrolls past:
Greasemonkey access violation: unsafeWindow cannot call GM_setValue.
On a similar vein, isn't testing with the alerts() annoying? Use the console functions and the script won't have to stop, and you won't have those pesky popups.
So, how to fix:
First, a test.
Install this script:
// ==UserScript==
// #name Super GM_setValue and GM_getValue TEST SHELL
// #namespace DEBUG
// #include https://stackoverflow.com/questions/*
// #require http://userscripts.org/scripts/source/107941.user.js
// ==/UserScript==
/*--- Run the test cases to make sure that the GM_setValue and GM_getValue
extensions are able to run on this browser.
*/
GM_SuperValue.runTestCases (0);
Then navigate to this page (stackoverflow.com/q/6802750/).
With Firebug's console open, reload the page.
What are the results?
Use the enhanced GM_setValue library. Add this line to your script(s):
// #require http://userscripts.org/scripts/source/107941.user.js
Replace all GM_setValue with GM_SuperValue.set
Replace all GM_getValue with GM_SuperValue.get
To address the fact that GM won't let GM_setValue run in event handlers set from the GM scope (this may be a bug), change the way ParseJSON_Data is called...
Comment out the J_DataCell.bind ('DOMSubtreeModified' ... line.
Add timerHandle = setInterval (function() { ParseJSON_Data (); }, 444);, below it.
Add some more logic around J_DataCell.
Putting it all together, the test script becomes:
// ==UserScript==
// #name _setvalue test
// #include http://www.trada.net/*
// #require http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js
// #require http://userscripts.org/scripts/source/107941.user.js
// ==/UserScript==
var auctiontyp = 0;
var my_test = GM_SuperValue.get("tsttst", 0);
var my_test2 = GM_SuperValue.get("tsttst2", 0);
var h = 0;
var m = 0;
var s = 0;
var t_int = 0;
var t_str = '';
var A12_current = 0;
var a_tst = true;
var a_tst2 = true;
var A12_old1 = GM_SuperValue.get("A12_old1", 0);
var A12_old2 = GM_SuperValue.get("A12_old2", 0);
var A12_old3 = GM_SuperValue.get("A12_old3", 0);
if (a_tst) {
console.log(my_test);
GM_SuperValue.set("tsttst", 5);
a_tst = false;
}
//--- Create a cell for transmitting the date from page scope to GM scope.
$('body').prepend('<div id="LatestJSON_Data"></div>');
var J_DataCell = $('#LatestJSON_Data');
//--- Evesdrop on the page's AJAX calls and paste the data into our special div.
unsafeWindow.$('body').ajaxSuccess(
function (event, requestData) {
J_DataCell.text(requestData.responseText);
} );
//--- Listen for changes to the special div and parse the data.
//J_DataCell.bind ('DOMSubtreeModified', {StoreValFunc: GM_SuperValue.set}, ParseJSON_Data);
timerHandle = setInterval (function() { ParseJSON_Data (); }, 444);
function ParseJSON_Data () {
//--- Get the latest data from the special cell and parse it.
var myJson = J_DataCell.text();
if (!myJson || /^\s*$/.test (myJson) )
return
else
J_DataCell.text (" ");
var jsonObj = $.parseJSON(myJson);
//--- The JSON should return a 2-D array, named "d".
var AuctionDataArray = jsonObj.d;
//--- Loop over each row in the array.
$.each(AuctionDataArray, function (rowIndex, singleAuctionData) {
//--- Print the 7th column.
//console.log('Row: ' + (parseInt(rowIndex) + 1) + ' Column: 7 Value: ' + singleAuctionData[6]);
if (a_tst2) {
console.log('******** ', my_test2);
GM_SuperValue.set ("tsttst2", 15);
console.log (A12_old1 + ' ' + A12_old2 + ' ' + A12_old3);
a_tst2 = false;
}
t_str = singleAuctionData[10];
var time = t_str.split(":");
h = 3600 * parseInt(time[0], 10);
m = 60 * parseInt(time[1], 10);
s = parseInt(time[2], 10);
t_int = h + m + s;
auctiontyp = parseInt(singleAuctionData[4]);
if (auctiontyp == 4) {
A12_current = parseFloat(singleAuctionData[8]);
if (t_int < 1) {
A12_old3 = A12_old2;
A12_old2 = A12_old1;
A12_old1 = A12_current;
GM_SuperValue.set ("A12_old1", A12_old1);
GM_SuperValue.set ("A12_old2", A12_old2);
GM_SuperValue.set ("A12_old3", A12_old2);
}
}
});
}
GM_addStyle ('#LatestJSON_Data {display:none;}');