As an experiment with React.js, I am trying to build a chrome extension that uses a content script to inject a input field (and now a button too) into certain websites. In this case I am trying to inject it into Twitter. It looks like this:
please note that the code puts the button and input below the text, but it will have the same result
The injection works, but I can't actually use the input. I inject the element into tweets on the home page, and when I click on the input to type in it, it triggers the tweet and expands or contracts. This removes the focus from the input rendering it useless. I have tried to call back focus onBlur, and stopPropagation() onClick, but onClick isn't triggered and I'm not sure how to bring back focus with onBlur. How can I stop losing focus?
If you want to test it yourself, react-with-addons.js came from the starter kit here
EDIT: I tried adding button to the code to see if I could click on that, and I can. I can click on it with out triggering the tweet itself. This means there is something specific to the input box that is causing trouble, or there is something in the button that is blocking propagation of the click.
Is there something special triggered when you click on an input field that other elements don't have that the tweets might have?
EDIT 2: I have now tried to add stopPropagation to a containing div and increasing it's z-index, neither of them will stop it.
EDIT 3: I have now tried onMouseDown(and onMouseUp, and onClick, all at the same time) with stopPropagation, with no luck. The strange part is I tried isPropagationStopped() afterwards, and it returns true. If this is so then why is Twitter still being triggered?
test_input.js
/**
* #jsx React.DOM
*/
var Test_Input = React.createClass({
maintain_focus: function(){
e.stopPropagation();
console.log("=====");
},
refocus: function(e){
e.stopPropagation();
},
handle_click: function{
console.log("clicked");
}
render: function(){
return (<div>
<button onclick={this.handle_click}>
<input className="new_note_input" placeholder="Note" onclick={this.maintain_focus} onBlur={this.refocus}></input>
</div>);
}
});
var elements_to_append_to = document.getElementsByClassName('content');
[].forEach.call(elements_to_append_to, function(element){
var container = $("<span />");
$(element).after(container);
React.renderComponent(<Test_Input />, container[0]);
});
manifest.json
{
"name": "Test Input",
"version": "0.1",
"manifest_version": 2,
"permissions": ["tabs", "bookmarks", "declarativeWebRequest", "*://*/*"],
"content_scripts": [
{
"matches": [ "https://twitter.com/*"],
"css":["src/social_notes.css"],
"js":[ "build/jquery-2.1.0.min.js", "build/react-with-addons.js", "build/test_input.js"]
}
]
}
e.stopPropagation() works on event handlers. Since input field click triggers default behaviour, instead of e.stopPropagation(), e.preventDefault() with a return false; in the input box click handler should do the trick.
In the handler, do a $(this).focus(); before returning false. This will keep the text box in focus.
eg:
$(".inputField").on("click", function(e){
e.preventDefault();
$(this).focus();
return false;
})
Try adding a click handler on the div you're inserting and do e.stopPropagation() in the handler, like this:
/**
* #jsx React.DOM
*/
var Test_Input = React.createClass({
maintain_focus: function(){
e.stopPropagation();
console.log("=====");
},
refocus: function(e){
e.stopPropagation();
},
handle_click: function(){
console.log("clicked");
},
div_click: function(e) {
e.preventDefault();
e.stopPropagation();
},
render: function(){
return (<div onclick={this.div_click}>
<button onclick={this.handle_click}>
<input className="new_note_input" placeholder="Note" onclick={this.maintain_focus} onBlur={this.refocus}></input>
</div>);
}
});
var elements_to_append_to = document.getElementsByClassName('content');
[].forEach.call(elements_to_append_to, function(element){
var container = $("<span />");
$(element).after(container);
React.renderComponent(<Test_Input />, container[0]);
});
BTW, there are some syntax errors in your code.
Related
I have the following code:
myInput.change(function (e) { // this triggers first
triggerProcess();
});
myButton.click(function (e) { // this triggers second
triggerProcess();
});
The problem with the above is when I click myButton both events are triggered and triggerProcess() is fired twice which is not desired.
I only need triggerProcess() to fire once. How can I do that?
Small demo
You can have a static flag that disables any more triggers once the first trigger has occurred. Might look something like this:
var hasTriggered = false;
myInput.change(function (e) { // this triggers first
triggerProcess();
});
myButton.click(function (e) { // this triggers second
triggerProcess();
});
function triggerProcess () {
// If this process has already been triggered,
// don't execute the function
if (hasTriggered) return;
// Set the flag to signal that we've already triggered
hasTriggered = true;
// ...
}
For resetting the hasTriggered flag, that's entirely up to you and how this program works. Maybe after a certain event occurring in the program you'd want to reenable the ability to trigger this event again — all you'd need to do it set the hasTriggered flag back to true.
You can use the mousedown event, which will fire before the input is blurred, and then check if the input has focus by checking if it's the activeElement, and if it does have focus, don't fire the mousedown event, as the change event will fire instead.
Additionally, if you want a mousedown event to occur when the value hasn't changed, and the change event doesn't fire, you'll need a check for that as well
var myInput = $('#test1'),
myButton = $('#test2'),
i = 0;
myInput.change(function(e) { // this triggers first
$(this).data('prev', this.value);
triggerProcess();
});
myButton.mousedown(function(e) { // this triggers second
var inp = myInput.get(0);
if (document.activeElement !== inp || inp.value === myInput.data('prev'))
triggerProcess();
});
function triggerProcess() {
console.log('triggered : ' + (++i))
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="test1">
<br />
<br />
<button id="test2">
Click
</button>
In a fairly typical scenario where you have an input with a button next to ie, eg quick search.
You want to fire when the input changes (ie onblur) but also if the user clicks the button.
In the case where the user changes the input then clicks the button without changing input focus (ie no blur), the change event fires because the text has changed and the click event fires because the button has been clicked.
One option is to debounce the desired event handler.
You can use a plugin or a simple setTimeout/clearTimeout, eg:
$('#inp').change(debounceProcess)
$('#btn').click(debounceProcess);
function debounceProcess() {
if (debounceProcess.timeout != null)
clearTimeout(debounceProcess.timeout);
debounceProcess.timeout = setTimeout(triggerProcess, 100)
}
function triggerProcess() {
console.log('process')
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<input id="inp">
<button id="btn">Click</button>
Use a real <button>BUTTON</button>. If you click on input text, alert is triggered, then once you leave the input text to click anywhere else, that unfocuses the input text which triggers the change event, so now 2 events have been triggered from the text input.
This is an assumption since the code provided is far from sufficient to give a complete and accurate answer. The HTML is needed as well as more jQuery/JavaScript. What is myInput and myButton actually referring to, etc.?
So I bet if you change...
var myButton = $('{whatever this is}'); and <input type='button'>
...TO:
var myButton = $("button"); and <button></button>
...you should no longer have an event trigger twice for an element.
This is assuming that triggerProcess() is a function that does something that doesn't manipulate the event chain or anything else involving events. This is an entirely different ballgame if instead of click() and change() methods you are using .trigger() or triggerHandler(), but it isn't. I'm not certain why such complex answers are derived from a question with very little info...?
BTW, if myInput is a search box and myButton is the button for myInput, as freedomn-m has mentioned, simply remove:
myButton.click(...
Leave myButton as a dummy. The change event is sufficient in that circumstance.
SNIPPET
var xInput = $('input');
var xButton = $('button'); //«———Add
xInput.on('change', alarm);
xInput.on('click', alarm);
xButton.on('click', alarm);
function alarm() {
return alert('Activated')
}
/* For demo it's not required */
[type='text'] {
width: 5ex;
}
b {
font-size: 20px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form id='f1' name='f1'>
<input type='text'>
<input type='button' value='BUTTON TYPE'>
<label><b>⇦</b>Remove this button</label>
<button>BUTTON TAG</button>
<label><b>⇦</b>Replace it with this button</label>
</form>
It's a rich-text editor,there is a div show the content users typed in, just like this:
<div class=‘content’>
<iframe data-role=text-editable.></iframe>
</div>
When i click a icon who's used to call the color panel,the panel will show.And i wish when i click outside the panel,it will be hide.so my code is:
var color_panel = $('.color-panel');
color_panel.on("clickoutside", function (e) {
var t = $(e.target);
if ($.contains(color_panel[0], t[0]))
return;
color_panel.hide();
})
It works well when i click outside the panel will disappear except i click the iframe.when i click the ifame,nothing happens,the panel is still shown,
Help......i want to know the reason .please...is the ifame not the dom element outside the panel?
You can listen for outside clicks by listening for clicks on the body element of the webpage (so, clicking on anything element) and then you can exclude your .color_panel from this event by making another event that says "when I get clicked on, ignore any other clicks". The effect will be exactly what you want, that you can listen for outside clicks:
var panelOpen = false;
$('body').click(function(event) {
if (panelOpen) {
panelOpen = false;
color_panel.hide();
}
});
$('.color_panel').click(function(event) {
panelOpen = true;
//callYourMethodToShowThePanelHere();
e.preventDefault(); // these methods will stop the body click event
e.stopPropagation();
return false;
});
Given the following markup, I want to detect when an editor has lost focus:
<div class="editor">
<input type="text"/>
<input type="text"/>
</div>
<div class="editor">
<input type="text"/>
<input type="text"/>
</div>
<button>GO</button>
EDIT: As the user tabs through the input elements and as each editor div loses focus (meaning they tabbed outside the div) add the loading class to the div that lost focus.
This bit of jquery is what I expected to work, but it does nothing:
$(".editor")
.blur(function(){
$(this).addClass("loading");
});
This seems to work, until you add the console log and realize it is triggering on every focusout of the inputs.
$('div.editor input').focus( function() {
$(this).parent()
.addClass("focused")
.focusout(function() {
console.log('focusout');
$(this).removeClass("focused")
.addClass("loading");
});
});
Here is a jsfiddle of my test case that I have been working on. I know I am missing something fundamental here. Can some one enlighten me?
EDIT: After some of the comments below, I have this almost working the way I want it. The problem now is detecting when focus changes to somewhere outside an editor div. Here is my current implementation:
function loadData() {
console.log('loading data for editor ' + $(this).attr('id'));
var $editor = $(this).removeClass('loaded')
.addClass('loading');
$.post('/echo/json/', {
delay: 2
})
.done(function () {
$editor.removeClass('loading')
.addClass('loaded');
});
}
$('div.editor input').on('focusin', function () {
console.log('focus changed');
$editor = $(this).closest('.editor');
console.log('current editor is ' + $editor.attr('id'));
if (!$editor.hasClass('focused')) {
console.log('switched editors');
$('.editor.focused')
.removeClass('focused')
.each(loadData);
$editor.addClass('focused');
}
})
A bit more complicated, and using classes for state. I have also added in the next bit of complexity which is to make an async call out when an editor loses focus. Here a my jsfiddle of my current work.
If you wish to treat entry and exit of the pairs of inputs as if they were combined into a single control, you need to see if the element gaining focus is in the same editor. You can do this be delaying the check by one cycle using a setTimeout of 0 (which waits until all current tasks have completed).
$('div.editor input').focusout(function () {
var $editor = $(this).closest('.editor');
// wait for the new element to be focused
setTimeout(function () {
// See if the new focused element is in the editor
if ($.contains($editor[0], document.activeElement)) {
$editor.addClass("focused").removeClass("loading");
}
else
{
$editor.removeClass("focused").addClass("loading");
}
}, 1);
});
JSFiddle: http://jsfiddle.net/TrueBlueAussie/8s8ayv52/18/
To complete the puzzle (get your initial green state) you will also need to also catch the focusin event and see if it is coming from the same editor or not (save the previous focused element in a global etc).
Side note: I recently had to write a jQuery plugin that did all this for groups of elements. It generates custom groupfocus and groupblur events to make the rest of the code easier to work with.
Update 1: http://jsfiddle.net/TrueBlueAussie/0y2dvxpf/4/
Based on your new example, you can catch the focusin repeatedly without damage, so tracking the previous focus is not necessary after all. Using my previous setTimeout example resolves the problem you have with clicking outside the divs.
$('div.editor input').focusin(function(){
var $editor = $(this).closest('.editor');
$editor.addClass("focused").removeClass("loading");
}).focusout(function () {
var $editor = $(this).closest('.editor');
// wait for the new element to be focused
setTimeout(function () {
// See if the new focused element is in the editor
if (!$.contains($editor[0], document.activeElement)) {
$editor.removeClass("focused").each(loadData);
}
}, 0);
});
Here's what worked for me:
$(".editor").on("focusout", function() {
var $this = $(this);
setTimeout(function() {
$this.toggleClass("loading", !($this.find(":focus").length));
}, 0);
});
Example:
http://jsfiddle.net/Meligy/Lxm6720k/
I think you can do this. this is an exemple I did. Check it out:
http://jsfiddle.net/igoralves1/j9soL21x/
$( "#divTest" ).focusout(function() {
alert("focusout");
});
I want to simulate the Google Search effect that even with the search box not focused, the user can start typing and the input box will capture all keyboard strokes.
I have looked for an ontype event, but haven't found anything. I know that the event object in callbacks for events like click has keyboard information, but I don't think this is what I'm after.
This does the job:
$(document).on('keydown', function() {
$('input').focus();
});
HTML:
<input type="text" id="txtSearch" />
Javascript:
var googleLikeKeyCapture = {
inputField : null,
documentKeydown: function(event) {
var inputField = googleLikeKeyCapture.inputField;
if(event.target != inputField.get(0)) {
event.target = inputField.get(0);
inputField.focus();
}
},
init: function() {
googleLikeKeyCapture.inputField = $('#txtSearch');
$(document).bind('keydown', googleLikeKeyCapture.documentKeydown);
googleLikeKeyCapture.inputField
.focus(function() {
$(document).unbind('keydown');
})
.blur(function() {
$(document).bind('keydown', googleLikeKeyCapture.documentKeydown);
});
googleLikeKeyCapture.init = function() {};
}
};
$(googleLikeKeyCapture.init);
Also you can find jsFiddle example here
EDIT :
And now it's a jQuery plugin. :) If keydown occures in a textarea or input field it doesn't capture keys, anything else goes to designated input field. If your selector matches more than one element it only uses the first element.
Usage: $('#txtSearch').captureKeys();
The event you are after is onkeypress.
Try this jQuery Text Change Event plugin:
http://www.zurb.com/playground/jquery-text-change-custom-event
I tried to use focus for first input field on the form. but
it doesn't work. When I call attr("id") for that input it worked. When I call focus for the same input, I didn't see any
result. I also tried to use native Javascript. Does anyone know how to
fix that?
You are all misunderstanding the question. When Colorbox opens you can't focus an input field?
...unless you add your focus to the Colobox onComplete key e.g.
$('#mydiv a').colorbox({ onComplete:function(){ $('form input:first').focus(); }});
You could also bind the focus to an event hook:
$('#mydiv a').bind('cbox_complete', function(){
$('form input:first').focus();
});
That should be enough to get started.
use
$(document).ready(function() {
// focus on the first text input field in the first field on the page
$("input[type='text']:first", document.forms[0]).focus();
});
It may be happening that when your colorbox is opened its focus goes onto the highest element i.e. body of page. use document.activeElement to find that focus went to which element. Then find iframe or id of your colorbox and then set focus on it
Try the first selector,
$("form input:first").focus();
http://jsfiddle.net/erick/mMuFc/
I've just stumbled on this problem.
I think it's best to have a single $.colorbox opener like this:
function showActionForColorBox(
_url,
_forFocus
) {
$.colorbox(
{
scrolling: false,
href: _url,
onComplete: function () {
idColorboxAjaxIndect1.appendTo($('#cboxOverlay'));
idColorboxAjaxIndect2.appendTo($('#cboxOverlay'));
idColorboxAjaxIndect3.appendTo($('#cboxOverlay'));
idColorboxAjaxIndect4.appendTo($('#cboxOverlay'));
// --> Possible element's ID for focus
if (_forFocus) {
$('#' + _forFocus).focus();
}
return;
},
onCleanup: function () {
// TODO: ?
return;
},
onClosed: function () {
if (shouldReloadPageAfterColorBoxAction) {
// --> Should we reload whole page?
shouldReloadPageAfterColorBoxAction = false; // NOTE: To be sure: Reset.
window.location.reload(false);
}
else if (cbEBillsActionReloadPopup) {
// --> Should we reload colorbox
cbEBillsActionReloadPopup = false;
showActionForColorBox(_url);
}
else if (cbShouldLoadAnotherContentAfterClosed) {
// --> Should we reload colorbox with custom content?
cbShouldLoadAnotherContentAfterClosed = false;
$.colorbox({ html: setupContentForcbShouldLoadAnotherContentAfterClosed });
setupContentForcbShouldLoadAnotherContentAfterClosed = '';
}
return;
}
}
);
return;
}
You can also use
$.colorbox({
...,
trapFocus: false
});
to disable focus inside colorbox