Errors with manual hiding twitter bootstrap tooltips with HTML5 <object> elements - javascript

For my first JavaScript application I am building a widget based designer, and use a variety of widgets based on SVG included in the main HTML body with object tags. I'm trying to associate a bootstrap tooltip when the user clicks on the widget object, however I'm getting strange errors that don't crop up when using tooltips on other non object HTML elements.
As an object tag swallows mouse events it's not possible to trigger a tooltip with a hover and the only combination I can get to work to show the tooltip is the following code (nothing else works, bootstrap complains about ):
document.getElementById(widgetName).setAttribute("title", param0);
setTimeout(function () {
$("#" + widgetName).tooltip("show");
}, 1); // attribute needs to be set after the next tick so the DOM is refreshed.
setTimeout(function () { $("#" + widgetName).tooltip("hide"); }, 2000); // 2 sec delay before hiding
This code shows the tooltip but errors out with Unable to get property 'originalTitle' of undefined or null reference in IE10 after the 2 second timeout to hide the tooltip. I can't use tooltip object options (eg. the delay option) as I get the same error. Same problem in Chrome although Chrome does not report the error (no hiding though).
I'm sure it has something to do with trying to use tooltips on object tags as tooltips work normally otherwise. However I'm fairly new to JavaScript so let me know if I'm doing something dumb, and the bootstrap/Jquery code is too complex for me to trace the error.
Using: HTML5, IE10, latest twitter bootstrap, visual studio 2012 web
Thanks for any help / pointers.
UPDATE: Added code that inserts the object into the DOM
var objWidget = document.createElement("object");
objWidget.type = "image/svg+xml";
objWidget.data = "widgets/" + widgetFile + "." + widgetExt // location of widget
objWidget.className = "widget"
var widgetName = "objWidget" + widgetNum;
targetDiv = "widgetContainer"
objWidget.id = widgetName;
document.getElementById(targetDiv).appendChild(objWidget); // Add to Div
var objID = document.getElementById(widgetName);
objID.addEventListener("load", function () { // access properties once the file is loaded
var objDoc = objID.contentDocument;
objDoc.addEventListener("dragstart", drag_start, false);
objDoc.addEventListener("dragover", drag_over, false)
objDoc.addEventListener("drop", drop, false)
objDoc.getElementById("widget").setAttribute("data-widgetName", widgetName); // set a data attribute on the SVG that points to the parent object as object that created the event isn't recorded in the event.currentTarget
objID.setAttribute("draggable", "true")
objID.style.setProperty("position", "absolute");
objID.style.setProperty("left", objLeft + "px");
objID.style.setProperty("top", objTop + "px");
objID.style.setProperty("z-index", widgets[widgetNum].zOrder);
objDoc.defaultView.loaded(widgetName); // run widget startup routine only if it isn't a new widget
objDoc.defaultView.addEventListener("click", widgetClick, false);
objDoc.defaultView.addEventListener("dblclick", widgetDblClick, false);
}, false);

i test your code (add the tooltips to the svg tag) see: http://bootply.com/64598 FF and chrome don't have a problem

Related

Modifying chrome extension for new Twitter UI

I have a chrome extension for Twitter where I add a button to all those tweets which have images in them. The code in the content script is something like this:
$(".tweet, .js-tweet").each(function() {
var username = $(this).attr('data-screen-name');
var tweetid = $(this).attr('data-tweet-id');
if (username != null) {
var mediaLinkContainer = $(this).find(".card2")
addButtonToMediaLinkTweets(tweetid, mediaLinkContainer)
// addOverlayToMediaLinks()
var outerContainer = $(this).find(".AdaptiveMediaOuterContainer")
var mediaContainer = $(this).find(".AdaptiveMedia-photoContainer")
if (mediaContainer.length) {
console.log("Photo found in link")
addButtonToPosts(tweetid, outerContainer)
// addOverlayToPosts(outerContainer)
}
}
});
This worked perfectly with the older UI of Twitter, but this doesn't work after the new UI was rolled out. All the class names have changed and the view hierarchy is different as well. I'm trying to navigate through all of that to make my extension work again.
The following class name appears a lot of times - css-1dbjc4n I tried the following to iterate over all the tweets:
$('.css-1dbjc4n.r-1ila09b.r-qklmqi.r-1adg3ll').each(function() {
console.log($(this).html())
})
.css-1dbjc4n r-1ila09b r-qklmqi r-1adg3ll are the classes assigned to the div that is at the second to top most level of a tweet (the topmost div does not have a class or id). However, console.log does not print anything in this case. I need to iterate over the tweets to be able to add my UI element.
Edit:
It seems that jQuery has issues with the new Twitter UI. When I wrote the following in vanilla JavaScript:
var tweetDivs = document.getElementsByClassName("css-1dbjc4n r-1ila09b r-qklmqi r-1adg3ll")
console.log(tweetDivs)
//get images inside the tweets and add button on top of these images
$(tweetDivs).each(function(tweet) {
console.log(tweet)
})
I get a HTML collection in tweetDivs. However, I'm unable to iterate over its elements as its length is 0. The elements show up in console though, but that's probably because the DOM hasn't loaded when this gets called. That's weird because I'm calling the above code from document.ready. I tried replacing document.ready with document.addEventListener("DOMContentLoaded", function(){}) and changed run_at in manifest.json file to document_start but it did not make a difference.

Apple iOS browsers randomly won't render HTML objects loaded dynamically

We have a problem that is only evident on iOS browsers (iOS 12.0) with our SPA application that uses HTML object tags to load widgets (HTML/CSS/JS files) through JavaScript onto the page.
The issue is an intermittent one when the page is loaded some of the widgets don't display/render on the screen, yet are loaded into the DOM and can be viewed/highlighted with full element properties in the Safari Web Inspector. but are “invisible” to their user. The problem will occur about 50% of the time if there are 4 widgets to load on a page, 2 typically won't display and it will be different widgets not displaying each time, with no detectable pattern.
The widget javascript load events run properly and there are no errors in the console. In the Safari Web Inspector, we can see some of the HTML elements from the non-rendering object are loaded at position 0,0 but their style is correct in the DOM (left and top set correctly, display: inline, etc.).
Here is the code that loads the widgets (the fragment is added to the DOM after all widgets are setup):
function loadWidget(myFrag, widgetName) {
var widgetObj = document.createElement("object");
widgetObj.data = "widgets/" + widgets[widgetName].type + ".html"; // location of widget
widgetObj.className = "widget unselectable";
widgetObj.id = widgetName;
widgetObj.name = widgetName;
myFrag.appendChild(widgetObj); // build widgets onto fragment
widgetObj.addEventListener("load", widgetLoaded, false); // Run rest of widget initialisation after widget is in DOM
widgetObj.addEventListener("error", badLoad, true);
}
Here is the code in the load event that configures the widget once loaded (we work around a Chrome bug that also affects Safari where the load event is fired twice for every object loaded):
function widgetLoaded(e) {
var loadObj = e.target;
if (loadObj === null) {
// CHROME BUG: Events fire multiple times and error out early if widget file is missing so not loaded (but this still fires), force timeout
return;
}
var widgetName = loadObj.id;
// CHROME BUG: Workaround here is to just set the style to absolute so that the event will fire a second time and exit, then second time around run the entire widgetLoaded
if ((parent.g.isChrome || parent.g.isSafari) && !widgets[widgetName].loaded) {
widgets[widgetName].loaded = true; // CHROME: WidgetLoaded will get run twice due to bug, exit early first time.
loadObj.setAttribute("style", "position:absolute"); // Force a fake style to get it to fire again (without loading all the other stuff) and run through second time around
return;
}
var defView = loadObj.contentDocument.defaultView; // Pointer to functions/objects inside widget DOM
loadObj.setAttribute("style", "position:absolute;overflow:scroll;left:" + myWidget.locX + "px;top:" + myWidget.locY + "px;z-index:" + zIndex);
loadObj.width = myWidget.scaleX * defView.options.settings.iniWidth; // Set the width and height of the widget <object> in dashboard DOM
loadObj.height = myWidget.scaleY * defView.options.settings.iniHeight;
}
The code performs correctly in Chrome (Mac/Windows), IE and Safari (Mac), however, presents the random invisible loading issue in iOS Safari and also in iOS Chrome.
Any ideas what causes this and what the workaround could be?
We couldn't find the exact source of this issue after a lot of investigation and are fairly sure this is a webkit bug. However there is an acceptable workaround, which is to replace the object tag with an iframe tag, and it looks to be working exactly the same way (replace .data with .src) with a bonus it doesn't exhibit the chrome bug where onload events are fired twice, so Chrome runs our app faster now.

Issues with drag and drop in CKEditor

I'm writing a small notetaking webapp which uses CKEditor as a HTML editor. I've been able to integrate it well, with one exception - drag and drop functionality. It mostly works - but there are two quirks I haven't been able to solve despite trying a whole lot of different approaches.
My requirements are simple - I drag any file into CKEditor from outside the app, upon which it should insert a simple anchor tag in the editable text with a local link to the file. Currently, I am implementing drag and drop in my own CKEditor plugin called filemanager:
CKEDITOR.plugins.add( 'filemanager', {
init: function( editor ) {
editor.on('contentDom', function(contentDom) {
//Runs upon dropping a file into the editor
contentDom.editor.document.on('drop', function(e) {
//prevents default behavior as long as it's an actual file drag and drop (if it's dragging text etc. inside the editor, keep default behavior)
if(e.data.$.dataTransfer.files.length)
{
e.data.preventDefault();
}
//Goes through all the dropped files and insert HTML with links
for (var i = 0; i < e.data.$.dataTransfer.files.length; ++i) {
CopyData(e.data.$.dataTransfer.files[i].path);
}
});
});
//Creates the link in the editor
function CopyData(path, range)
{
//Link HTML
var fileHTML;
/* Code to parse the path into HTML to be inserted */
editor.insertHtml(fileHTML);
}
}
});
I have omitted most of the CopyData function as the formatting isn't relevant, but it generates a div such as this:
fileHTML = ""+fileName+"";
This does work perfectly fine, so the basic implementation of the feature seems to work. However, here are my two issues:
Dropping a file into the editor but outside the actual text area (such as below the final line of text in the editor) does not disable default behavior. How can I run a preventDefault() for this area of the editor? Been trying events in all sort of places without results.
The HTML isn't inserted at the location of the mouse cursor, even though CKEditor previews a caret there. Rather, it is inserted where the caret currently is in the document, independently of where you drop. This disrupts core functionality of the feature - is there a way to insert HTML at the mouse position? I would assume so since CKEditor actually previews a caret where the mouse is, but I looked through the documentation without results.
Does anyone have ideas of how to solve the two above issues? If it helps, I am using nw.js so the webapp will always run in Chromium. Grateful for any responses!
I eventually managed to solve these issues.
To prevent default behavior outside the DOM elements, I added this code to my CKEditor plugin:
var iframeWin = window.document.getElementsByTagName('iframe')[0].contentWindow;
iframeWin.addEventListener("dragover",function(e){
e = e || iframeWin.event;
if(e.dataTransfer.files.length)
{
e.preventDefault();
}
},false);
iframeWin.addEventListener("drop",function(e){
e = e || iframeWin.event;
if(e.dataTransfer.files.length)
{
e.preventDefault();
}
},false);
This correctly identified the iframe I needed to disable default behavior in. The if(e.dataTransfer.files.lenght) condition makes sure this only applies to files dragged into the app, and not dragging things within the editor.
Inserting the HTML at the mouse cursor was trickier! This cannot be credited to me - I found a function doing most of the work on the CKEditor bug tracker, and edited it to work in this scenario:
function moveSelectionToDropPosition( editor, dropEvt )
{
var $evt = dropEvt.data.$,
$range,
range = editor.createRange();
// Make testing possible.
if ( dropEvt.data.testRange ) {
dropEvt.data.testRange.select();
return;
}
// Webkits.
if ( document.caretRangeFromPoint ) {
$range = editor.document.$.caretRangeFromPoint( $evt.clientX, $evt.clientY );
range.setStart( CKEDITOR.dom.node( $range.startContainer ), $range.startOffset );
range.collapse( true );
}
// FF.
else if ( $evt.rangeParent ) {
range.setStart( CKEDITOR.dom.node( $evt.rangeParent ), $evt.rangeOffset );
range.collapse( true );
}
// IEs.
else if ( document.body.createTextRange ) {
$range = editor.document.getBody().$.createTextRange();
$range.moveToPoint( $evt.clientX, $evt.clientY );
var id = 'cke-temp-' + ( new Date() ).getTime();
$range.pasteHTML( '<span id="' + id + '">\u200b</span>' );
var span = editor.document.getById( id );
range.moveToPosition( span, CKEDITOR.POSITION_BEFORE_START );
span.remove();
}
range.select();
}
In the drop event I then simply added
moveSelectionToDropPosition(editor, e);
And it all worked as intended!
Hopefully this may help anyone else having similar issues in the future :)

Iframe nomenucontext - Modify img tag after creation

Hi I am adding a iframe dynamically, It displays an image from a server. I need to disable the context menu for this item. In chrome I can inspect element and if I add oncontextmenu="return false" I do get the wanted affect. However I am unable to do this while the page is generated. Here is an example of the working html.
However I can not reproduce this when i frame is being created. Here is my code.
$(window).scrollTop(0);
$('#secVerify').show();
$("#popWaitLoad").modal("hide");
imgLoading.hide();
dvIframe.empty();
//else load deposit data into interface
$("#spanType").text(deposit.DepositType);
$("#spanReference").text(deposit.Reference);
$("#spanAmount").text("R " + deposit.Amount.toFixed(2));
$("#spanDate").text(deposit.DateCreatedOffsetS);
imageID = deposit.Deposit_Doc_imageID;
var url = imageUrl + '/' + deposit.Deposit_Doc_imageID + '/false';
var imgFrame = document.createElement("iframe");
imgFrame.src = url;
imgFrame.frameBorder = '0';
imgFrame.scrolling = 'no';
imgFrame.width = '100%';
imgFrame.height = '100%';
imgFrame.align = 'middle';
imgFrame.id = "iframeImg";
dvIframe.append(imgFrame);
I have tried examples like.
$("#iframeImage").contents().find("img").attr("oncontextmenu", 'return false');
$('#iframeImage img').on('contextmenu', function (e) {
e.stopPropagation();
// Your code.
return false;
});
But because the img element seems to be only created is done after page load it seems to not work. I know disabling the the menu will not help much and I have explained all the other methods of obtaining the image that is still available but the client really wants this.
I have added nocontextmenu to the body tag and it works everywhere except for the iframe.
So let me clarify, My iframe is working like it should however I would like to disable the right click aka context menu on the specific iframe.
I have used setAttribute to set the attributes and targeted a container to appendChild.
function example(){
var target = document.getElementById('container');
var element = document.createElement('img');
element.setAttribute('src', 'http://gopalshenoy.files.wordpress.com/2011/04/product_demos.jpg');
//element.setAttribute('width','100%');
//element.setAttribute('height','100%');
element.setAttribute('id','iframeImage');
element.setAttribute("oncontextmenu","return false;");
target.appendChild(element);
}
// Demo-Snippet use.
window.onload=example;
<!-- Demo Snippet use -->
<div id="container"></div>
If you build more than one element using this function you might find further issues due to duplicated ID's.
ID's are used to target a specific element 'one of' so if you want to build multiple elements I would recommend giving them unique ID's.
I hope this helps. Happy coding!

ZeroClipboard user script adding in mouse over, working in firefox, but not chrome

I am using zeroclipboard to add a "copy" link to each row in a fairly large list, within a user script. To accomplish that, I using a method similar to the one listed on this page, where the ZeroClipboard.Client() element for each row is created when the user mouses over the row. This is working great in FireFox, but not in Chrome.
Also as a note: I copied the contents of the ZeroClipboard.js file into the user script itself instead of including it in an external file.
Here is the markup that creates the copy button for each element
<span style="color:blue; text-decoration:underline; cursor:pointer" id="copy_'+id+'" class="CopyLink" link="'+url+'" onmouseover="clipboard.add(this)">Copy</span>
Here is the code segment that adds the clipboard's client object:
function main(){
window.clipboard = {
load: function (){
if(!clipboard.initialized){
ZeroClipboard.setMoviePath("http://www.swfcabin.com/swf-files/1343927328.swf");
clipboard.initialized=true;
console.log("Clipboard intialized");
}
},
add: function(element){
clipboard.load();
var clip = new ZeroClipboard.Client();
console.log('Clipboard client loaded: ' + element.id);
clip.glue(element, element.parentNode);
console.log('Clipboard glued: ' + element.id);
clip.setText(element.getAttribute('link'));
console.log('Clipboard text set: ' + element.getAttribute('link'));
clip.addEventListener('complete',function(client,text) {
console.log('Clipboard copied: ' + text);//doesn't fire in chrome
});
clip.addEventListener('load',function(client) {
console.log('Clipboard loaded: ' + element.getAttribute('link'));
});
}
}
//other code in user script including injecting above markup
//as well as contents of ZeroClipboard.js
window.ZeroClipboard = { ... }
}
var script = document.createElement("script");
script.appendChild(document.createTextNode('('+main+')()'));
(document.head || document.body || document.documentElement).appendChild(script);
In this block, every console.log fires in FireFox when I mouse over and click the copy span, but in chrome, all except the 'complete' listener fire. I was able to verify that ZeroClipboard is working in my Chrome by using the example on this page. I am also able to verify that the flash object is being added to the page in the correct location, but it is simply not responding to a click.
Since the zeroclipboard code is no longer being maintained according to the site, I'm hoping someone out there can help me out. I'm thinking there is possibly some issue with dynamically adding the embedded flash objects in chrome on mouseover, or perhaps some difference between user scripts in chrome vs firefox with greasemonkey? Any help would be greatly appreciated, thanks
I'm not sure the reason behind it but I have been running into this on Chrome as well. I had two zeroclipboard implementations, one that was visible on page load, and one that was only visible when the user opened a dialog. The one that was visible on page load worked as expected, but the other one didn't. In order to "solve" the issue, I had to render the zeroclipboard link, set its absolute position to be off the screen (-500 px), then add some javascript to move the link into place when the dialog opens. This is an ugly solution but I think is the only way to get it to work in Chrome. Your case is particularly hairy since you have lots of dynamic zeroclipboards on your page whereas I only had one, but it seems to me that there's no reason this won't work for you.
<!-- <script type="text/javascript" src="http://davidwalsh.name/demo/ZeroClipboard.js"></script> -->
function copyText(fieldName,buttonName){
var fieldNameTemp =fieldName;
var buttonNameTemp =buttonName;
var val = "";
try{
val = navigator.userAgent.toLowerCase();
}catch(e){}
var swfurl = "js/ZeroClipboard.swf";
setTimeout(function () {
ZeroClipboard.setMoviePath(swfurl);
var clip = new ZeroClipboard.Client();
clip.addEventListener('mousedown', function () {
clip.setText(document.getElementById(fieldNameTemp).value);
});
clip.addEventListener('complete', function (client, text) {
try{
if(val.indexOf("opera") > -1 || val.indexOf("msie") > -1 || val.indexOf("safari") > -1 || val.indexOf("chrome") > -1){
alert('Your text has been copied');
}
}catch(e){
alert('Please alert not use on fireFox');
}
});
clip.glue(buttonNameTemp);
}, 2000);
}

Categories

Resources