Customising CKEditors Link Plugin - javascript

I am editing the link plugin to allow staff to select links to internal content.
I have managed to add another tab to the link plugin dialog with a text input with an onKeyup event. The idea is, when they type it will list the results below where they can select the link they want. Once selected I was just going to update the info tab with the url and protocol.
Here is my code sections from the existing link plugin:
....
....
//Should update info tab with value
function AddLink(txtLink)
{
var dialog = this.getDialog();
dialog.setValueOf('info', 'url', txtLink);
dialog.setValueOf('info', 'protocol', '');
}
//called when the user types in the search box. currently just uses text for basic testing
var searchBoxChanged = function ()
{
var dialog = this.getDialog();
var href = $(this).attr('href');
var txt = dialog.getValueOf('article', 'searchWords');
$('#searchResults').html("Test Title");
}
....
....
{
//Adds extra tab to the link plugin for custom link searching
id: 'article',
label: linkLang.article,
title: linkLang.article,
elements:
[
{
type: 'text',
id: 'searchWords',
label: linkLang.articleSearch,
style: 'height:40px',
size: 29,
onKeyUp: searchBoxChanged
},
{
type: 'html',
html: '<div id="searchResults">Please start tying to get results</div>'
}
]
}
....
....
At the moment I am just using some basic static data from the textbox. The link in creating on the page ok, but when it is clicked I get the error:
CRIPT5009: 'AddLink' is undefined
Can anyone shed some light on where I am going wrong?

In my experience, ["x" is undefined] errors quite often mean there's a syntax error or, often, something in the function does not evaluate to what you think it does.
Possibly, this.getDialog() is out of context so it doesn't return anything. Then, dialog.setValueOf() won't work.

Related

Hiding field of custom-multi-field using javascript in listener

I have customized form of multi-field in a component having two variations.
In one variation of my component I want to hide a field (title) which is inside custom-multi-field . I am using the following JavaScript code in listener.
This code is not working. Where am I wrong?
function() {
var dialog = this.findParentByType('dialog');
var contenttype = dialog.getField("./type").getValue();
var teaserlinks = dialog.getField("./teaserlinks");
var title = dialog.getField("./teaserlinks").getField("./title");
alert(title);
if(contenttype == 'variation-1'){
teaserlinks.show();
title.hide();
}
else if(contenttype == 'variation-2'){
teaserlinks.show();
}
}
Try using the hidden property of node. Initially set the hidden property to true and in javascript file change the hidden property to false (or as per your requirement).
Few imp points first before answer:
you have to write listener in your widget file only.
below is the sample code where in I have 2 fields. 1st field is mytext field and another field is myselection. On changing the value in myselection field I am toggling visibility of my text field.
Below is snippet:
this.mytext = new CQ.form.textField({...})
this.myselection = new CQ.form.Selection({
fieldLabel:"my selection",
type:"select",
width : "325",
allowBlank:false,
defaultType:"String[]",
fieldDescription : "Select value from dropdown",
options: "/a/b/c.json",
listeners : {
selectionchanged : function(){
var mytext = this.findParentByType('mywidget').mytext;
mytext.hide();
}
}
});
I hope this will be helpful.
I have no knowledge about aem and Adobe CQ5 but I can give you some hints how to debug your script.
First of all don't use alert for debugging! (BTW what does alert(title); show?)
I would recommend to open the browser console (e.g. Press <F12> on Firefox and switch to the tab "Console").
Herein the browser displays all exceptions and error messages. Additionally you can output some text with console.log("...");` from your script.
Here is my edit of your program. Perhaps the output can help you.
function()
{
var dialog = this.findParentByType('dialog');
var contenttype = dialog.getField("./type").getValue();
var teaserlinks = dialog.getField("./teaserlinks");
var title = dialog.getField("./teaserlinks").getField("./title");
console.dir(title);
console.log(contenttype);
teaserlinks.show();
if(contenttype == 'variation-1')
{
title.hide();
}
else if(contenttype == 'variation-2')
{
title.show();
}
}
And, console.dir(<object>); shows you the object structure to one level deep.

Displaying content based on radio button selection

I’m pretty new to backbonejs and i’m trying to create a basic application.
The application is something like this:
I have 5 sections: A, B, C, D and E
Each section has 2 radio buttons.
Section A - Radio1, Radio2
Section B - Radio3, Radio4
Section C - Radio5, Radio6
Section D - Radio7, Radio8
Section E - Radio9, Radio10
Depending on what radio button is selected, I need to display a section (previous sections must also display)
I have had a look at maybe using a model to determine which radio was selected and also what section is displayed. Is this the correct approach?
var State = Backbone.Model.extend({
defaults: {
id: null,
name: "",
isOn: false
}
});
var Section = Backbone.View.extend({
model: State,
events: {
'change [type="checkbox"]': function (event) {
var $checkbox = $(event.target);
this.model.set("isOn", $checkbox.is(":checked"));
this.model.get("dispatcher").trigger("toggle", this.model.get("id"));
}
},
initialize: function (options) {
this.listenTo(this.model, "change:isOn", function (model, isOn) {
if ( isOn ) {
this.$el.find(".section").show();
this.$el.find("input").prop("checked", true);
}
else {
this.$el.find(".section").hide();
this.$el.find("input").prop("checked", false);
}
});
this.listenTo(dispatcher, "toggle", function (id) {
if ( this.model.get("id") < id ) {
this.model.set("isOn", true);
}
if ( this.model.get("id") > id ) {
this.model.set("isOn", false);
}
});
},
render: function () {
this.$el.html('<div>' + this.model.get("name") + '</div><input type="checkbox"><div class="section" style="min-height:100px; background-color:grey; display:none;"></div>');
this.$el.appendTo("body");
}
});
var dispatcher = _.extend({}, Backbone.Events);
_.each([
{id: 1, name: "A", isOn: false, dispatcher: dispatcher},
{id: 2, name: "B", isOn: false, dispatcher: dispatcher},
{id: 3, name: "C", isOn: false, dispatcher: dispatcher},
{id: 4, name: "D", isOn: false, dispatcher: dispatcher},
{id: 5, name: "E", isOn: false, dispatcher: dispatcher}
], function (item) {
var view = new Section({model: new State(item)});
view.render();
});
I didn't understand the meaning of two radio.. so I used checkbox per section.. Hope this will uncover some basics of backbone.
Yes this approach will work. I recommend if you need to communicate between views to use models via events - this will generally result in better architecture (your views will be more decoupled).
You can react to the change event in the view (using the events hash) and update an attribute on a model for each group, e.g. this.model.set('termsAccepted', true/false) then as long as the other view(s) have access to this model you can react to the change event of that attribute, e.g. this.listenTo(this.model, 'change:termsAccepted', this.onTermsAcceptedChange).
There may be a very simple solution to your objective. If you monitor the radio-button state via a javascript function, then you can use that function to change a class statement for the parent div. In CSS, you can then define actions to hide or display a div based on it's class. This would only take a few lines of javascript and a few lines of CSS.
For example, this example in codepen shows a way to accomplish the hide/show for a variably sized div. The number of divs in this approach is arbitrary - you can have as many or few as you like, and the only action required of the user is to click on the header to expand or collapse the associated div. In this example, clicking on the header acts as a toggle to expand or collapse the associated div. The example is set up so that only one div is expanded at a time and clicking on a different header automatically collapses any open div so that only one div is open at a time. If you do not want that behavior, just remove the for loop in the accOff() function.
It's kinda like a choose-your-own-adventure, ya sipher_z?
I advise using the Backbone Router with route parameters storing the current state, that is, which section is currently showing.
Each component of the view should be a Backbone.View.extend({...}). Components might be Section and Radio.
On each Radio button, in HTML, put a data-go-to attribute, with a value of the section to go to next. Then, in your RadioView code, put a click event. When clicked, extract this data-go-to, and do something like location.hash = '/section/' + section to trigger your router.
Then, all your router does is hide all the Sections except the selected one whenever triggered. If there's no selection, it just shows the first one!
I'm not 100% sure of this strategy, but this is definitely "the Backbone way". Let me know if I can clear anything up.

TinyMCE Enable button while in read only mode

I have a TinyMCE 4.x instance where the text should be in read only mode. But I still have some buttons that I want to have enabled. For example, one button could provide a character count for the part of the text I've selected.
But when I turn on read only mode for TinyMCE all buttons are disabled. Can I enable just my buttons while still retaining read only mode?
It's probably too late for you but other people may pass by here.
I came up by writing this function
function enableTinyMceEditorPlugin(editorId, pluginName, commandName) {
var htmlEditorDiv = document.getElementById(editorId).previousSibling;
var editor = tinymce.get(editorId);
var buttonDiv = htmlEditorDiv.querySelectorAll('.mce-i-' + pluginName.toLowerCase())[0].parentElement.parentElement;
buttonDiv.className = buttonDiv.className.replace(' mce-disabled', '');
buttonDiv.removeAttribute('aria-disabled');
buttonDiv.firstChild.onclick = function () {
editor.execCommand(commandName);
};
}
It does the trick in 2 steps:
make the button clickable (remove mce-disabled CSS class and remove the aria-disabled property)
assign the good command to the click event
And in my editor init event I call the function.
editor.on('init', function () {
if (readOnly) {
editor.setMode('readonly');
enableTinyMceEditorPlugin(htmlEditorId, 'preview', 'mcePreview');
enableTinyMceEditorPlugin(htmlEditorId, 'code', 'mceCodeEditor');
}
});
Current version of TinyMCE for which I wrote this code is 4.4.3. It may break in a future version, specifically about the selectors to get and modify the good HTML elements.
Command identifiers can be found at this page otherwise you can also find them under tinymce\plugins\PluginName\plugin(.min).js
Here is a simple way to enable your custom toolbar button and attach a click event handler inside a read only TinyMCE editor using JQUERY:
//Initialize read only Tinymce editor so that Lock button is also disabled
function initReadOnlyTinyMCE() {
tinymce.init({
selector: '#main'
, toolbar: 'myLockButton'
, body_class: 'main-div'
, content_css: 'stylesheets/index.css'
, readonly: true
, setup: function (readOnlyMain) {
readOnlyMain.addButton('myLockButton', { //Lock button is disabled because readonly is set to true
image: 'images/lock.png'
, tooltip: 'Lock Editor'
});
}
});
}
function displayReadOnlyTinyMCEwithLockButtonEnabled() {
var edContent = $('main').html();
$("#main").empty();
initReadOnlyTinyMCE(true);
tinyMCE.activeEditor.setContent(edContent);
//enable the lock button and attach a click event handler
$('[aria-label="Lock Editor"]').removeClass("mce-disabled");
$('[aria-label="Lock Editor"]').removeAttr("aria-disabled");
$('[aria-label="Lock Editor"]').attr("onclick", "LockEditor()");
}
function LockEditor() {
alert("Tiny mce editor is locked by the current user!!");
//Write your logic to lock the editor...
}
I couldn't find an easy way to do this. The simplest way is to remove the contenteditable attribute from the iframe body instead and substitute a read only toolbar set. It also means that people will still be able to copy content from the editor.
$("iframe").contents().find("body").removeAttr("contenteditable");
How about this :
editor.addButton('yourButton', {
title: 'One can Enable/disable TinyMCE',
text: "Disable",
onclick: function (ee) {
editor.setMode('readonly');
if($(ee.target).text() == "Disable"){
var theEle = $(ee.target).toggle();
var edit = editor;
var newBut = "<input type='button' style='opacity:1;color:white; background-color:orange;' value='Enable'/>";
$(newBut).prependTo($(theEle).closest("div")).click(function(e){
edit.setMode('design');
$(e.target).remove();
$(theEle).toggle();
});
}
}
});
You can try to run the code below:
$("#tinymce").contentEditable="false";
if you have more than one editors, you can use their id like below
$("#tinymce[data-id='idOfTheEditor']").contentEditable="false";

Phonejs toolbar menu text shows twice

Recently, I use the Html5 framework: "phonejs" to develop a mobile project,
when I want to make a toolbar menusheet in empty views,
It seems menu text will show up twice in this demo:
http://phonejs.devexpress.com/Documentation/ApiReference/Widgets/dxToolbar/Configuration?version=13_2#menuItemRender
I use it like this:
homeToolbarItems = [
{ location: 'menu', text: 'Logout',clickAction:logout },
{ location: 'center', text: 'Subscribe Manage' }
];
and the menu shows 'Logout' twice, what can I do?​
It appears that the menuItemRenderer is triggering twice. Once on initial view and once on click. I was able to remedy this buggy behavior by added a Initialize flag to the first view render. Then if the flag was tripped do not render the menu items again on additional request. Example...
var menuInit = false;
var viewModel = {
menuItemRenderer: function(itemData, itemIndex, itemElement){
if(menuInit == false){
itemElement.dxButton({ text: "Execute \"" + itemData.text + "\" action" });
menuInit = true;
}
}}
It seems it's a bug, I just asked the devExpress Phonejs develop team. And it have been solve several days ago.
Here is the question link: http://www.devexpress.com/Support/Center/Question/Details/Q571866

ExtJS Change Event Listener failing to fire

I was asked to post this as a question on StackOverflow by http://twitter.com/jonathanjulian which was then retweeted by several other people. I already have an ugly solution, but am posting the original problem as requested.
So here's the back story. We have a massive database application that uses ExtJS exclusively for the client side view. We are using a GridPanel (Ext.grid.GridPanel) for the row view loaded from a remote store.
In each of our interfaces, we also have a FormPanel (Ext.form.FormPanel) displaying a form that allows a user to create or edit records from the GridPanel. The GridPanel columns are bound to the FormPanel form elements so that when a record is selected in the GridPanel, all of the values are populated in the form.
On each form, we have an input field for the table row ID (Primary Key) that is defined as such:
var editFormFields = [
{
fieldLabel: 'ID',
id: 'id_field',
name: 'id',
width: 100,
readOnly: true, // the user cannot change the ID, ever.
monitorValid: true
} /* other fields removed */
];
So, that is all fine and good. This works on all of our applications. When building a new interface, a requirement was made that we needed to use a third-party file storage API that provides an interface in the form of a small webpage that is loaded in an IFrame.
I placed the IFrame code inside of the html parameter of the FormPanel:
var editForm = new Ext.form.FormPanel({
html: '<div style="width:400px;"><iframe id="upload_iframe" src="no_upload.html" width="98%" height="300"></iframe></div>',
/* bunch of other parameters stripped for brevity */
});
So, whenever a user selects a record, I need to change the src attribute of the IFrame to the API URL of the service we are using. Something along the lines of http://uploadsite.com/upload?appname=whatever&id={$id_of_record_selected}
I initially went in to the id field (pasted above) and added a change listener.
var editFormFields = [
{
fieldLabel: 'ID',
id: 'id_field',
name: 'id',
width: 100,
readOnly: true, // the user cannot change the ID, ever.
monitorValid: true,
listeners: {
change: function(f,new_val) {
alert(new_val);
}
}
} /* other fields removed */
];
Nice and simple, except that it only worked when the user was focused on that form element. The rest of the time it failed to fire at all.
Frustrated that I was past a deadline and just needed it to work, I quickly implemented a decaying poller that checks the value. It's a horrible, ugly hack. But it works as expected.
I will paste my ugly dirty hack in an answer to this question.
"The GridPanel columns are bound to
the FormPanel form elements so that
when a record is selected in the
GridPanel, all of the values are
populated in the form."
As I understand it from the quote above, the rowclick event is what actually triggers the change to your form in the first place. To avoid polling, this could be the place to listen, and eventually raise to your custom change event.
Here is the ugly hack that I did to accomplish this problem:
var current_id_value = '';
var check_changes = function(offset) {
offset = offset || 100;
var id_value = document.getElementById('id_field').value || '';
if ( id_value && ( id_value != current_id_value ) ) {
current_id_value = id_value;
change_iframe(id_value);
} else {
offset = offset + 50;
if ( offset > 2500 ) {
offset = 2500;
}
setTimeout(function() { check_changes(offset); }, offset);
}
};
var change_iframe = function(id_value) {
if ( id_value ) {
document.getElementById('upload_iframe').src = 'http://api/upload.php?id=' + id_value;
} else {
document.getElementById('upload_iframe').src = 'no_upload.html';
}
setTimeout(function() { check_changes(100); }, 1500);
};
It's not pretty, but it works. All of the bosses are happy.
If you took a moment to read the source, you would see that the Ext.form.Field class only fires that change event in the onBlur function

Categories

Resources