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/
Related
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.
I have a CKEDITOR plugin that I'm having trouble disabling when there is no selected copy in the editor. Right now, the user can click the button without any highlighted text in the editor. I would like to modify it so that the plugin button is only active when there is copy highlighted in the editor. I've followed the suggestion here, but it isn't working.
Main Plugin Code:
CKEDITOR.plugins.add('cta', {
icons: 'cta',
init: function (editor) {
// Funciton depending on editor selection (taken from the scope) will set the state of our command.
function RefreshState() {
console.log('RefreshState');
var editable = editor.editable(),
// Command that we want to control.
command = editor.getCommand('source'),
range,
commandState;
if (!editable) {
// It might be a case that editable is not yet ready.
console.log("editable not ready yet");
return;
}
// We assume only one range.
range = editable.getDocument().getSelection().getRanges()[0];
console.log(`range: `);
console.log(range);
// The state we're about to set for the command.
commandState = (range && !range.collapsed) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED;
console.log('commandState');
console.log(commandState);
command.setState(commandState);
}
// We'll use throttled function calls, because this event can be fired very, very frequently.
var throttledFunction = CKEDITOR.tools.eventsBuffer(250, RefreshState);
// Now this is the event that detects all the selection changes.
editor.on('selectionCheck', throttledFunction.input);
// You'll most likely also want to execute this function as soon as editor is ready.
editor.on('instanceReady', function (evt) {
// Also do state refresh on instanceReady.
RefreshState();
});
editor.addCommand('ctabtn', new CKEDITOR.dialogCommand('ctaDialog'));
editor.ui.addButton('cta', {
label: 'Insert Call to Action button',
command: 'ctabtn',
toolbar: 'insert'
});
CKEDITOR.dialog.add('ctaDialog', this.path + 'dialogs/cta.js');
}
});
My dialog code is here:
CKEDITOR.dialog.add('ctaDialog', function (editor) {
return {
// Basic properties of the dialog window: title, minimum size.
title: 'Call to Action',
minWidth: 400,
minHeight: 200,
// Dialog window content definition.
contents: [{
// Definition of the Basic Settings dialog tab (page).
id: 'tab-basic',
label: 'Basic Settings',
// The tab content.
elements: [{
// Text input field for the Call to Action text.
type: 'text',
id: 'cta',
label: 'Call to Action',
// Validation checking whether the field is not empty.
validate: CKEDITOR.dialog.validate.notEmpty("Call to Action field cannot be empty.")
},
{
// Text input field for the link text.
type: 'text',
id: 'url',
label: 'URL',
// Validation checking whether the field is not empty.
validate: CKEDITOR.dialog.validate.notEmpty("URL field cannot be empty.")
}
]
}],
// method invoked when the dialog button is clicked
onShow: function () {
var element = editor.getSelection();
var link = CKEDITOR.plugins.link;
var _this = this.getParentEditor();
var CTAhref = link.getSelectedLink(_this);
this.setValueOf('tab-basic', 'cta', element.getSelectedText().toString());
if (CTAhref != '' && CTAhref !== null) {
this.setValueOf('tab-basic', 'url', CTAhref.$.href);
}
},
// This method is invoked once a user clicks the OK button, confirming the dialog.
onOk: function () {
var dialog = this;
var CTA = editor.document.createElement('a');
CTA.setAttribute('href', dialog.getValueOf('tab-basic', 'url'));
CTA.setAttribute('class', 'btn btn-special--lg');
CTA.setText(dialog.getValueOf('tab-basic', 'cta'));
editor.insertElement(CTA);
}
};
});
Any ideas on why the plugin icon button on the toolbar doesn't become inactive when there is no copy highlighted in the editor? I can see in the console that CKEDITOR.dom.range.collapsed is toggling between TRUE/FALSE depending upon whether text is highlighted or not. It's just not disabling the button.
As stated, the suggested way of handling this was not working for me. What was working was using range.collapsed in returning a true value if a selection was made in the editor. With that, I turned to using Jquery to handle the rest.
// Hacky. But it gets the job done.
// a.cke_button.cke_button__cta.cke_button_off is the selector for my toolbar button.
// The onclick function listed was pulled from looking at the CKeditor functions
// to initiate my plugins modal.
// The setting of the "onclick" prop to null is needed to override the modal onclick
// binding when there is no selection.
range = editable.getDocument().getSelection().getRanges()[0];
if (range.collapsed === false) {
$('a.cke_button.cke_button__cta.cke_button_off').attr("onclick", 'CKEDITOR.tools.callFunction(83,this);return false;');
$('a.cke_button__cta').toggleClass('cta_button_disabled');
} else {
$('a.cke_button.cke_button__cta.cke_button_off').prop("onclick", null);
}
What I'm trying to accomplish is to allow multiple rows inside a table to toggle on or off without affecting the other rows in that same table.
It works fine when I only have one row. But the moment I add another row , the switch starts turning off other rows.
Here's a video clip of what I mean->
https://www.youtube.com/watch?v=uLBrZND69Ps
And here's the code ->
// ClIENT CODE
Template.orionMaterializeLayout.events({
"change .switch input": function (event) {
var change = event.target.checked;
Meteor.call('toggleHidden', change);
}
});
// SERVER CODE
Meteor.methods({
'toggleHidden' : function(change){
console.log(change);
Banner.update({}, {$set:{hidden: change }});
}
});
// COLLECTIONS CODE, WHAT RENDERS THE ON/OFF SWITCH ON THE TABLE
Banner = new orion.collection('slideshow', {
title: 'Add Images', // The title of the page
link: {
title: 'Slideshow',
section: 'top',
image: '<i class="fa fa-picture-o"></i>'
},
tabular: {
columns: [
{ data: 'hidden', title: 'Visibility',
render: function(doc){
if (doc === true ){
return '<div class="switch"><label>Off<input type="checkbox" checked="checked"><span class="lever"></span>On</label></div>'
} else {
return '<div class="switch"><label>Off<input type="checkbox"><span class="lever"></span>On</label></div>'
}
}
}
]
}
});
It looks like you intend the toggling to write the change to the database on the backend (Mongo collection on the server). However, your Banner.update() call does not specify which document to update - so it updates every document in your collection!
You need to do two things (with your code as-is). First, capture the data context that has triggered the event handler. Normally, that will be this within your handler. So this._id should return the document ID. Second, you need to pass that ID to your method, to ensure it only updates that document.
Without all of your code, it is hard to guarantee a correct answer (especially not knowing the data context within the template) but the below is likely to work:
// ClIENT CODE
Template.orionMaterializeLayout.events({
"change .switch input": function (event) {
var change = event.target.checked;
Meteor.call('toggleHidden', change, this._id);
}
});
// SERVER CODE
Meteor.methods({
'toggleHidden' : function(change, docId){
console.log(change);
Banner.update({_id: docId}, {$set:{hidden: change }});
}
});
I have a GridPanel and within the toolbar I have two buttons "Reject Changes" and "Save Changes". The code below shows what each button does, and so far things work as expected.
Ext.define('APP.view.MyGrid' ,{
extend: 'Ext.grid.Panel',
...
initComponent: function() {
var me=this;
me.store = myStore;
me.plugins = [
Ext.create('Ext.grid.plugin.CellEditing', {
clicksToEdit: 1, autoCancel: false
}),
];
me.rejectBtn = {
xtype:'button', id:'kbsGridCancelBtn', text:'Reject changes',
handler: function() { me.store.rejectChanges(); }
},
me.saveBtn = {
xtype:'button', id:'kbsGridSaveBtn', text:'Save changes',
handler: function() { me.store.sync(); }
},
me.bbar = Ext.create('Ext.toolbar.Toolbar', {
items : [{ xtype: 'tbfill'},me.rejectBtn,'-',me.saveBtn]
});
me.callParent(arguments);
}
...
});
How to enable/disable the buttons (or any other action) only if the grid data has been modified by the user? I.e. only if any grid row/field becomes dirty (and vice-versa)? Which listener(s) should my code be listening to?
As shown in the question there's a CellEditing plugged-in to the Grid. By listening to the CellEditing Plugin's validateedit event, which is fired when data in the grid is changed, one can use the event's parameters to update the store's row using the Model instance's set function. Of course after the required validation is done. The code decides whether to enable/disable the buttons based on what getModifiedrecords returns.
Code:
...
listeners: {
'validateedit': function(cep, e, eOpts) {
var me = this,
rowIdx = e.rowIdx, // row index
fieldName = e.field,
newVal = e.value,
storeRow = this.store.getAt(rowIdx);
// assuming valid input, proceed with the below
storeRow.set(fieldName, newVal);
// if modified records > 0 then enable buttons
var enableButtons = Boolean(me.store.getModifiedRecords().length);
if (enableButtons) {
/* enable buttons */
} else { /* disable buttons */ }
}, scope: this
}
...
Ext.data.Store datachanged( this, eOpts ).
Fires whenever the records in the Store have changed in some way - this could include adding or removing records, or updating the data in existing records
http://docs.sencha.com/ext-js/4-1/#!/api/Ext.data.Store-event-datachanged
function dataChangedFun(store){
// code here
}
myStore.on("datachanged", dataChangedFun, this);
And when you change the store's records manually then call dataChangedFun(myStore);
I've written a program that includes a form that the user interacts with. Because there are lots of events bound to different buttons I have written a loop that parses some JS that contains the form input information. Here is some example data:
var value = 0,
forms = {
place_controls : {
attrs : {
'class' : 'place-form'
},
input : {
place_x : {
attrs : {
type : 'text',
},
events : {
change : function () {
value = 10;
}
}
},
place_y : {
attrs : {
type : 'text',
},
events : {
change : function () {
value = 50
}
}
}
}
}
}
The data is then parsed by this:
$.each(forms, function (form_index, form) {
var $form_markup = $('<form>').attr(form.attrs);
// Next: loop through each input element of the form we've reached
$.each(form.input, function (element_index, element) {
var $elem = $('<input>').attr(element.attrs);
$elem.appendTo($form_markup);
if (element.events !== undefined) {
$.each(element.events, function (event_index, event) {
$elem.bind(event_index, event);
//$form_markup.on(event_index, $elem, event);
});
}
});
$form_markup.appendTo($form_goes_here);
});
As you can see, I'm using .bind() at the moment, however I want to use .on(). Unfortunately, when I do this all of the items within a form are bound to the last event parsed by the function. When I use .bind() everything works as planned - i.e. Clicking on 'place_x' sets value to 10, clicking 'place_y' sets value to 50.
When using .on(), whichever I change sets value to 50, which I am assuming is because the last function is becoming bound to each event.
Can anybody see what I have done wrong?
Update: There are many different ways to do this, and I have subsequently changed how my code works, however this question is related to why .bind() is working and why .on() is not.
//$elem.bind(event_index, event);
//It looks like you should just be using .on() like this
$elem.on(event_index, event);
The way it looks like you are trying to use .on() is in the live -bubbling- event sort of way, it looks like only the last event you are created is sticking, why each value just gets set to 50.
//$form_markup.on(event_index, $elem, event);
You can create elements with property maps that include handler functions in one simple call:
var $elem = $('<input/>', properties);
The "properties" object can contain event handlers:
var $elem = $('<input/>', {
type: 'text',
name: 'somethingUseful',
click: function(ev) { /* click handler */ },
change: function(ev) { /* change handler */ },
css: { color: "red" }
});