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);
}
Related
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 several "Purchase Now" buttons of different articles. When the button has the class "sold-out" it shouldn't do anything otherwise it should open a jQuery Magnific Popup. So far that works. My problem is, that when I click for the first time I visit the homepage the purchaseable "Purchase Now" button, it isn't doing anything. When I click for the second time on it, it opens the jQuery window. But why it doesn't work for the first time already ??
My HTML:
Purchase Now
My JQuery:
$('.open-popup-link').magnificPopup({
type:'inline',
midClick: true,
mainClass: 'mfp-fade'
});
$('.ajax-popup').click(function(e){
e.preventDefault();
if($(this).hasClass("sold-out")) {
return false;
}
var region = $(this).data('region');
var quantity = $(this).data('quantity');
if(typeof quantity == 'undefined') quantity = $(this).parent().find('select').val();
var packageid = $(this).data('packageid');
$(this).magnificPopup({
type: 'ajax',
ajax: {
settings: {
data : {
region : region,
quantity : quantity,
packageid : packageid,
}
}
},
closeOnContentClick: false,
closeOnBgClick: false
});
});
It might be because you are declaring the popup definition within the click function. Can you try declaring the function outside the click function?
It doesn't open on first click cause this is the time you BIND it to the element.
Hovewer, we see in documentation that we can instantly open MagnificPopup.
// Open popup immediately. If popup is already opened - it'll just overwite the content (but old options will be kept).
// - first parameter: options object
// - second parameter (optional): index of item to open
$.magnificPopup.open({
items: {
src: 'someimage.jpg'
},
type: 'image'
// You may add options here, they're exactly the same as for $.fn.magnificPopup call
// Note that some settings that rely on click event (like disableOn or midClick) will not work here
}, 0);
http://dimsemenov.com/plugins/magnific-popup/documentation.html#options
If the user presses the down key while a custom popup is displayed, I would like this down event to be cancelled from the editor and handled manually.
However, if the popup is disactivated, the 'down' key should perform as usual.
For that, I wrote this:
editor.commands.addCommand({
name: 'nav_down.',
bindKey: {win: 'Down', mac: 'Down'},
exec: function(editor) {
if(myPopupIsOpen()) {
// Do whatever I want with the popup.
return false;
} else {
// just leave the key.
return true;
}
readOnly: true
});
Unfortunately, I can return false or true, the result is the same, it always capture the down event, which is annoying. How can I prevent that?
I already tried the following:
Add a key binding to the DOM. But after that, the interaction always happen (i.e. I cannot capture it).
Return false or true as suggested for common events but this does not work here.
EDIT
The solution from #a user works very well.
Instead of the above command, I wrote:
var HashHandler = require("ace/keyboard/hash_handler").HashHandler;
keyboardHandler = new HashHandler();
keyboardHandler.addCommand({
name: 'nav_down.',
bindKey: {win: 'Down', mac: 'Down'},
exec: function(editor) {
if(myPopupIsOpen()) {
// Do whatever I want with the popup.
return true; // CHANGE HERE ! true is when it capture it.
} else {
// just leave the key.
return false; // CHANGE HERE ! false is when I don't capture it.
}
readOnly: true
});
editor.keyBinding.addKeyboardHandler(keyboardHandler);
In the current version ace only keeps one command for each key so your addCommand call removes default binding for down.
You can add new keyboard handler similar to what autocompletion does https://github.com/ajaxorg/ace/blob/v1.1.3/lib/ace/autocomplete.js#L221
var HashHandler = require("ace/keyboard/hash_handler").HashHandler;
keyboardHandler = new HashHandler();
keyboardHandler.addCommand(/*add your command with return false*/)
editor.keyBinding.addKeyboardHandler(keyboardHandler);
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 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);