I am building a headless crawler running JavaFX Webkit, it definitely is not as powerful as chrome's v8.
However I've run into problems lately, wherein I am trying to input a value to react rendered input fields.
Here's what I have done till now and failed.[ Note: I don't have control over the source / React code. Since I am trying to crawl a destination site ]
jQuery - $('input.r_input').val("2");
Vanila JS - document.querySelector("input.r_input").value = "2";
Trigger change events through jquery trigger - change, blur , keyup, keydown, etc.
Creating a manual event like :
event = new Event( event, {target: obj, bubbles: true} );
event.simulated = true;
return obj ? obj.dispatchEvent(event) : false;
and triggering an input event.
None of the above works.
I am adding parts of react code from the JS file on the website if it may help to add some more context.
Create:
t.prototype.createInputProps = function(e) {
return {
disabled: this.props.submitting || e > this.focusIndex,
className: "r_input",
type: "tel",
name: "tan-" + e,
maxLength: 1,
pattern: "[\\d]*",
tabIndex: 0,
placeholder: "ยท",
autoComplete: "off"
}
}
Render :
t.prototype.render = function() {
var e = this.props,
t = e.meta,
n = t.touched,
r = t.error,
o = (e.input.value, sa()("r_input", {
"has-error": r && n
}));
return us("article", {
className: o
}, void 0, us("div", {
className: "r_inputs"
}, void 0, ro.a.createElement("input", as({
onPaste: this.handleOnPaste,
ref: this.addInputToList,
onKeyUp: this.handleKeyUp,
value: this.getValue(0)
}, this.createInputProps(0))), ro.a.createElement("input", as({
ref: this.addInputToList,
onKeyUp: this.handleKeyUp,
value: this.getValue(1)
}, this.createInputProps(1))), ro.a.createElement("input", as({
ref: this.addInputToList,
onKeyUp: this.handleKeyUp,
value: this.getValue(2)
}, this.createInputProps(2))), ro.a.createElement("input", as({
ref: this.addInputToList,
onKeyUp: this.handleKeyUp,
value: this.getValue(3)
}, this.createInputProps(3))), ro.a.createElement("input", as({
ref: this.addInputToList,
onKeyUp: this.handleKeyUp,
value: this.getValue(4)
}, this.createInputProps(4))), ro.a.createElement("input", as({
ref: this.addInputToList,
onKeyUp: this.handleKeyUp,
value: this.getValue(5)
}, this.createInputProps(5)))), n && r && us(is.a, {}, void 0, r))
}
Not sure If I need to add handleKeyUp, but that contains some validation code.
Any help will be appreciated.
In your case, it looks like you can fire a keyup event on the element after changing the value. You may also have to wait for the element to appear in the DOM, based on a React state change. Try this
setTimeout(function() {
var event = new Event('keyup', { bubbles: true });
$('input.r_input').val("2")[0].dispatchEvent(event);
}, 3000);
It will of course be hard to provide something definitive without access to the page in question.
Note: I came up with the voodoo to dispatch a keyup event via this thread which says the React onChange handler listens for input events and by looking at the code you provided which calls onKeyUp. My sample code uses onChange handler in React & dispatches input events as prescribed in the linked thread.
Also Note: I failed to get this working with jQuery's trigger method, maybe you can figure out what I was missing there, but event dispatching with native Javascript is working in my snippet below.
Read on for details!
There is no single answer for your question, because how a given React component reads in values from input fields can vary, so you'll need to look at each React implementation you are trying to manipulate. I'd hazard a guess that onChange is the most common way for React to capture input in a text field. The React onChange handler listens for input events.
It's also possible React's state is updating to cause the elements you are looking for to be added to the DOM after jQuery's .ready() callback fires. This would mean your selectors aren't finding anything because the elements they are looking for are not present in the DOM when they are looking. (This is why I added the setTimeout in my suggested solution).
Here I illustrate the issue of elements appearing in the DOM after a React state change, and a working example where jQuery is used to set the value of an input element managed by React. (Make sure to look at the console output when you run it to get an idea what's happening), you should see this in the HTML when it's all finished
React thinks the value is 50
jQuery(function() {
console.log('jquery loaded');
function searchForNodeOfInterest() {
console.log('jQuery searching for nodes of interest');
if(jQuery('#interesting-node').length === 0) {
console.log('jQuery did not find the selector of interest');
} else {
console.log('jQuery found the selector of interest, setting input value...');
var event = new Event('input', { bubbles: true });
jQuery('#interesting-node').val(50)[0].dispatchEvent(event);
}
}
searchForNodeOfInterest();
setTimeout(searchForNodeOfInterest, 4000);
console.log('jQuery - taking a nap, I will search again soon...');
});
var App = React.createClass({
getInitialState: function() {
return {
hidden: true
};
},
componentDidMount: function() {
console.log('react mounted');
},
// Imagine something happens inside react (AJAX response comes in for example),
// causing the nodes of interest to be rendered in DOM
componentWillMount: function() {
var that = this;
setTimeout(function() {
console.log('react updating the state...');
that.setState({
hidden: false,
value: null
})
}, 2000);
},
handleInput: function(e) {
console.log('react handling input...', e.target.value);
this.setState({
value: e.target.value
})
},
render: function() {
return this.state.hidden ? null : <form>React Powered Input: <input type="text" id="interesting-node" onChange={this.handleInput}/><p>React thinks the value is {this.state.value}</p></form>
}
});
ReactDOM.render(<App />, document.getElementById('react'));
<body>
<div id="react"></div>
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
A more reliable technique than setTimeout for some random amount of time is a wrapper that periodically checks for the presence of a selector, like this.
I can imagine you simply can't.
It depends how it is implemented and what events it is listening to.
If React Component is controlled and registering 'onChange' event, then it might be possible.
If it's uncontrolled component, then I guess it might not work.
Even if you fill input somehow. On form submit, it might be still using value from it's internal state.
https://reactjs.org/docs/uncontrolled-components.html
https://reactjs.org/docs/forms.html#controlled-components
first of all verify that the element exists with a vanilla if. If it exists then programatically fill it it. Note some UI libraries might require an initialization or upgrade upon dynamic add of value.
if(document.getElementById("my-input")){
document.getElementById("my-input").value = "foo";
}
If you're adding elements with scripting, you will probably need to attach event listeners after the element is created.
Related
I have a Kendo UI Toolbar:
$("#toolbar").kendoToolBar({
items : [ {
type : "button",
text : "List"
} ]
})
and I have a script in my app that will translate strings according to the chosen language; i.e. it will find the word 'List' and change it to 'Liste'.
The problem is with timing. There is a finite time that the Toolbar takes to render, so calling my translation function inside
$(document).ready(function() { })
Is too early.
The Kendo Toolbar component doesn't have an onRendered event handler. Otherwise I could use that.
Is there any way to define an event that occurs after all Kendo components, including Toolbar have been rendered?
First of all: Ain't there a better way to localize your page?
Besides that: I've created a small JavaScript function which waits until a given list of elements exist. Just call it as shown in the comment in $(document).ready(function() { }).
// E.g. waitUntilKendoWidgetsLoaded({ "toolbar": "kendoToolBar" }, doTranslation);
function waitUntilKendoWidgetsLoaded(widgets, action) {
var allLoaded = true;
for (var key in widgets) {
if (widgets.hasOwnProperty(key)) {
allLoaded = allLoaded && $("#" + key).data(widgets[key]) !== undefined;
}
}
if (allLoaded) {
action();
}
else {
setTimeout(waitUntilKendoWidgetsLoaded, 500, widgets, action);
}
}
But be aware: The only thing you know for sure is that the element exists. It does not ensure that the element has finished loading. Especially with Kendo widgets which use a datasource you should use the existing events to trigger your function at the right moment.
I have a page http://www.projectdemocracy.in/ where some elements are dynamically added (example - CSS class ".fyre-comment-count" showing "X Comment").
I wanted to modify the text once page is loaded.
I was able to change the text using console of jQuery
$(".fyre-comment-count").html($(".fyre-comment-count").text().split(" ")[0]+" Conversations");
When I type the same in my page, it doesn't work.
I also tried $(document).ready(function(){}); but no luck (same with document.ajaxSuccess).
Similarly, document.on would be working only with 'click' events but I want it to be done by default.
$(document).on('click', '.fyre-comment-article', function() {
//Code
});
Does it have any 'load' type event in document.on?
What should I do to accomplish this?
Thanks,
I analyzed your web page and saw the following code:
var articleId = fyre.conv.load.makeArticleId(null);
fyre.conv.load({}, [{
el: 'livefyre-comments',
network: "livefyre.com",
siteId: "351251",
articleId: articleId,
signed: false,
collectionMeta: {
articleId: articleId,
url: "http://projectdemocracy.in",//fyre.conv.load.makeCollectionUrl(),
}
}], function() {});
It seems like the 3rd argument of fyre.conv.load method is a callback function that will be executed when all elements are generated.
So put your code inside this function. It will be like this:
...
}], function() {
console.log('callback');
$(".fyre-comment-count").html($(".fyre-comment-count").text().split(" ")[0]+" Conversations");
});
Hope this helps!
EDIT
If it still doesn't work it may be because livefyre runs this callback before DOM elements are being created. The workaround is to put $(".fyre-comment-count").html(...); inside setTimeout with minimal delay:
...
}], function() {
setTimeout(function() {
$(".fyre-comment-count").html($(".fyre-comment-count").text().split(" ")[0]+" Conversations");
}, 10); //minimal delay. wait till livefyre generates all needed elements
});
EDIT 2
This is another variant how to make it work:
...
}], function(widget) {
widget.on('commentCountUpdated', function() {
setTimeout(function() {
$(".fyre-comment-count").html($(".fyre-comment-count").text().split(" ")[0]+" Conversations");
}, 10);
});
});
This widget has a commentCountUpdated event, so you have to subscribe on it, and every time the comments number is changing, your callback will be executed
By the way, commentCountUpdated callback recieves a single argument - the number of comments, so you can rewrite your code as follows:
}], function(widget) {
widget.on('commentCountUpdated', function(commentsNum) {
setTimeout(function() {
$(".fyre-comment-count").html(commentsNum + " Conversations");
}, 10);
});
});
jQuery has a on load event, if this is what you are looking for:
$(window).load(function(){ ... });
I have the following JavaScript code, which works as expected...
/** #jsx React.DOM */
var TreeView = React.createClass({
render: function() {
return <div ref="treeview"></div>;
},
componentDidMount: function() {
console.log(this.props.onChange);
var tree = $(this.refs.treeview.getDOMNode()).kendoTreeView({
dataSource: ...,
dataTextField: "Name",
select: this.props.onChange
}
});
}
});
var MyApp = React.createClass({
onChange: function(e) {
console.log(e);
this.setState({key: e});
},
render: function() {
return (
<div>
<TreeView onChange={this.onChange}/>
<GridView />
</div>
);
}
});
However, with the kendo treeview, on selecting a tree node, the whole node is passed. To get at the underlying key, I would need to process the node as follows:
select: function(e) {
var id = this.dataItem(e.node).id;
this.props.onChange(id);
}
However I've obviously not quite got it right since, and here please excuse my noobness, it seems that in the working instance a reference to the function is being used, whereas in the non-working instance, the function is actually being executed... Or something like that: the error message being returned is:
Cannot call method 'onChange' of undefined.
So what would I need to do to be able to reference the function which extracts the key before calling the onChange method? Note that, if my understanding is correct, onChange needs to be executed in the context of the MyApp class so that any child components will get notified on the change.
EDIT: I've tried using partial application but am still not quite there. I've updated the onChange routine to take a function which returns the key from the node
onChange: function(getKey, e) {
this.setState({Key: getKey(e)});
},
But am not sure how to wire it all up.
Your code looks mostly right. I believe your only problem is that the select callback you're passing to the treeview is being called with the wrong scope. Note that you're using this to mean two different things within the function (once for the actual tree view object and the other for the React component). Easiest solution is probably to store a reference to the onChange function like so:
componentDidMount: function() {
var onChange = this.props.onChange;
var tree = $(this.refs.treeview.getDOMNode()).kendoTreeView({
dataSource: ...,
dataTextField: "Name",
select: function(e) {
var id = this.dataItem(e.node).id;
onChange(id);
}
});
}
Hope this helps.
I'm adding the virtual keyboard from http://www.greywyvern.com/code/javascript/keyboard to a text field of an extjs 4.2 form.
It basically works, see here: http://jsfiddle.net/g5VN8/1/
1) My first question is: is this really the best way to connect them? Looks ugly to me with a timer instead of events to keep the extjs value up to date.
Plus I can't overcome the following two issues:
2) the keyboard icon is wrapped to a new line. It should instead be at the end of the field, on the right side, just as in the examples here: http://www.greywyvern.com/code/javascript/keyboard
3) The field focus doesn't work. I have it in a show listener. Even when wrapped in a window.setTimeout() it doesn't work, so it's not a timing issue. No error is thrown.
Here is a copy-paste (stackoverflow's rules). I'll keep both places up to date.
Ext.onReady(function() {
Ext.QuickTips.init();
var formPanel = Ext.create('Ext.form.Panel', {
renderTo: Ext.getBody(),
bodyStyle: 'padding: 5px 5px 0 5px;',
defaults: {
anchor: '100%',
},
items: [{
xtype:'textfield',
name: 'string',
fieldLabel: 'String',
maxLength:30, enforceMaxLength:true,
allowBlank: false,
listeners: {
show: function(field) {
//focus the field when the window shows
field.focus(true, 1000); //TODO: doesn't work, no error
},
afterrender:function(cmp){
cmp.inputEl.set({ //see http://jsfiddle.net/4TSDu/19/
autocomplete:'on'
});
//attach the keyboard
//because it modifies the dom directly we need to hack it to
//inform extjs (really, ext has no such listener option?)
var interval = window.setInterval(function() {
try {
var newValue = cmp.inputEl.dom.value;
var oldValue = cmp.getValue();
if (newValue != oldValue) {
//only do it then, cause it also moves the cursor
//to the end and that sucks.
cmp.setValue( newValue );
}
} catch (e) {
//form was removed
window.clearInterval(interval);
}
}, 100);
// see http://www.greywyvern.com/code/javascript/keyboard
VKI_attach(cmp.inputEl.dom);
}
}
}],
buttons: [{
text: 'Alert string',
handler: function() {
var stringField = this.up('form').getForm().findField('string');
alert(stringField.getValue());
}
}]
});
});
You can attach a listener to the keyboard and when the user clicks on a VKI key you trigger the textfield change event.
Ext.getBody().on({
mousedown: function (ev) {
if (ev.target.tagName === 'TD') {
// We trigger change event only on textfield with the focus
if (document.activeElement) {
if (document.activeElement.id === cmp.inputEl.dom.id) cmp.fireEvent('change');
}
}
},
delegate: '#keyboardInputMaster'
});
This is because ExtJS 4 writes the input field with "style=width:100%".
An easy way is to add a negative margin to the textfield
fieldStyle: 'margin-right:-40px'
Weird ExtJS behaviour. You must focus the input element, not te thextfield component
Ext.defer(function () {
cmp.inputEl.dom.focus();
}, 100);
You can see the whole solution here: http://jsfiddle.net/EGbLn/3/
Avoid timers. Use regular dom event listeners instead.
afterrender: function (cmp) {
...
// simply attach this to the change event from dom element
cmp.inputEl.dom.addEventListener('change', function(){
cmp.setValue(this.value);
});
...
}
(answered already by Samy Rancho)
fieldStyle: 'margin-right:-40px',
Again, avoid timers and anything similar. Simply add this:
afterrender: function (cmp) {
...
//focus on field
cmp.inputEl.dom.focus();
...
}
Find updated fiddle here: http://jsfiddle.net/g5VN8/11/
I was wondering if it's possible to do as follows:
In my site I am using a lot of jQuery plugins that fire different events that I don't know about.
Is there a way - a program, a browser add-on, or something else - that I can browse the site and get a list of the exact javascript events that were fired with every click?
For example, I have a jQuery plugin that when I right click on any element a custom contextMenu shows and then when I click on one of the options other things come up. I need to know exactly what Javascript basic events were fired:
$('input:submit, button:submit').rightClick(function (e) {
$(this).contextMenu('contextMenuInput', {
'Capture This': {
click: function (element) { // element is the jquery obj clicked on when context menu launched
doSomething();
},
klass: "kgo" // a custom css class for this menu item (usable for styling)
},
'Create List': {
click: function (element) {
},
klass: "kfilter kdisabled"
},
'Collect Data': {
click: function (element) {
},
klass: "kcapture kdisabled"
}
},
{ disable_native_context_menu: true }
);
});
Does anyone have any idea?
You can use the following code to show events currently bound ....
here is an example of using this code : http://jsfiddle.net/manseuk/CNjs3/
(function($) {
$.eventReport = function(selector, root) {
var s = [];
$(selector || '*', root).andSelf().each(function() {
var e = $.data(this, 'events');
if(!e) return;
s.push(this.tagName);
if(this.id) s.push('#', this.id);
if(this.className) s.push('.', this.className);
for(var p in e) s.push('\n', p);
s.push('\n\n');
});
return s.join('');
}
$.fn.eventReport = function(selector) {
return $.eventReport(selector, this);
}
})(jQuery);
Use it like this ->
// all events
alert($.eventReport());
// just events on inputs
alert($.eventReport('input'));
// just events assigned to this element
alert($.eventReport('#myelement'));
// events assigned to inputs in this element
alert($.eventReport('input', '#myelement'));
alert($('#myelement').eventReport('input')); // same result
// just events assigned to this element's children
alert($('#myelement').eventReport());
alert($.eventReport('*', '#myelement'); // same result
Updated as per comments
If you want to see what is bound to these events this is an excellent tool -> http://www.sprymedia.co.uk/article/Visual+Event
It's not quite what your looking for, but with firebug, you can log events for a given DOM element.
You can do this by right clicking on the element in the html tab and clicking log events:
The event log:
You may also find the firebug extension "EventBug" useful:
http://getfirebug.com/wiki/index.php/Firebug_Extensions#Eventbug
http://www.softwareishard.com/blog/firebug/eventbug-alpha-released/