CKEditor - get editor data and custom widget - javascript

I have following custom ckeditor widget code:
(function()
{
"use strict";
var jQuery = require("jquery"),
Underscore = require("underscore"),
$template = jQuery('<div class="section-wrapper">' +
'<div class="section-label"><span class="section-label-user"></span><span class="cricon cricon-lock"></span><span class="status-icon cricon"></span><span class="section-label-text"></span><span class="section-label-loader"></span></div>' +
'<div class="clearfix"></div>' +
'<div class="section-content">' +
'</div>' +
'</div>'),
bindEvents = function bindEvents(editor, section)
{
if(typeof editor.config.sectionPlugin.handlers !== "undefined")
{
Underscore.each(editor.config.sectionPlugin.handlers, function(callback, eventName)
{
section.on(eventName, callback);
});
}
};
CKEDITOR.plugins.add('section', {
requires: 'widget',
init: function(editor)
{
var self = this;
// Register the section widget.
editor.widgets.add('section', {
inline: false,
allowedContent: 'section[!data-cid]',
draggable: false,
// button is required for UPCAST processing? stupid bug?
button: 'sectionbtn',
init: function()
{
var sectionContent;
this.$element = jQuery(this.element.$);
sectionContent = this.$element.html();
// create html structure
this.$template = $template.clone();
this.$template.find(".section-content").html(sectionContent);
this.$element.html(this.$template);
// set editable content
this.initEditable("content", {
selector: ".section-content"
});
bindEvents(editor, this);
},
bindToContract: function(contract, options)
{
this.section_class = contract.get("sections").get(this.$element.attr("data-cid"));
if(!this.section_class)
{
this.$element.addClass("is-corrupted");
return false;
}
this.section_class.on("change:name", this.update, this);
this.update();
},
update: function()
{
this.$element.find(".section-label-text").text(this.section_class.get("name") + " header" + Math.random());
},
upcast: function(element)
{
return element.name === 'section';
},
downcast: function(widgetElement)
{
return widgetElement;
},
destroy: function(offline)
{
CKEDITOR.plugins.widget.prototype.destroy.call(this, offline);
}
});
}
});
})();
When I am using ckeInstance.getData() method then whole code (widget template) is returned.
Is there any way to define what widget/getData() should return?
I don't want to parse code returned by .getData() method. I think it should be done with ckeditor.

You got to expand your downcast function in the definition of the widget. It should return element or text and this is where you control the the widget's representation in data. Of course, once you define it, make sure the upcast function is able to decode such an representation from data back into DOM (i.e. your template).
For instance, your downcast could be like
function( widgetElement ) {
var el = new CKEDITOR.htmlParser.element( 'div', {
'data-content': this.editables.content.getData()
} );
el.setHtml( 'foo' );
return el;
}
if you were interested in nothing but the content of nested editable in a data attribute. It would convert your widget into
<div data-content="HTML of nested editable">foo</div>
once you call editor.getData(). If you write a corresponding upcast that extracts data-content and re–builds the DOM so it again looks like your widget template, then you have a full state machine that converts the widget between data and DOM.
In short downcast function is a kind of encoder (DOM->data) and upcast – a decoder (data->DOM).

Related

Access originally clicked element in jQuery plugin

I have the following plugin, and while I wish it to be able to be applied to multiple elements, I do not wish to create a new dialog for each element.
But in the dialog.open callback or when the button is clicked, I wish to be able to access the element which was clicked and opened the dialog.
If I wanted to create multiple dialogs, I suppose I could put this.each(function () {...} in the init method and then this would be the individually clicked element, but as stated earlier, I only one one dialog.
EDIT. I revised the code so that it does what I need it to do. It just seems like a bit of a hack using data as I did. Is there a more proper way to do so?
How is this accomplished?
(function($){
var defaults = {};
var methods = {
init : function (options) {
var settings = $.extend({}, defaults, options);
var dialog = $('<div/>').dialog({
open: function( event, ui ) {
console.log(dialog.data('elementThatWasClicked'));
},
buttons: [
{
text: 'click',
click: function() {console.log(dialog.data('elementThatWasClicked'));}
}
]
});
return this.each(function () {
var $this=$(this);
$this.click(function(){dialog.data('elementThatWasClicked',$this).dialog('open')});
});
}
};
$.fn.test = function(method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || ! method) {
return methods.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist on jQuery.test');
}
};
}(jQuery));
$(function(){
$('.bla').test();
});

Post from dynamic form to dynamic iframe in Chrome opens a new window

I am doing some tests to create a JavaScript widget that allows communication across domain.
I have a script tag that could be placed on any site that will inject a dynamic form and iframe into their page.
The idea I had was to set the dynamic form's target attribute to the name of the dynamic iframe, so that data can be posted from a third party site to my backend for processing.
Put it together and works great in Firefox, but in Chrome the submission forces a new window to open.
If I use a non dynamic iframe, it suddenly works. However it is not very elegant if I want my widget to be easy for third parties to integrate into their sites.
Anyone know how to get this working? I don't think it's my code because, as I say, the code works when the iframe is static. I have verified that the name and ID of the dynamic iframe are set as expected.
Cheers.
I have added a JSFiddle demoing this behaviour. Note that the JavaScript would actually be placed in a script tag beneath the div id Widget in the HTML. https://jsfiddle.net/n67w5tm7/
(function($) {
var TEST = (function() {
var param = {
source: '//localhost',
gateway: '/gateway.php'
},
_target,
_gateway,
get = function(id) {
var n = document.getElementById(id);
return (typeof n === 'undefined' ? $() : $(n));
},
init = function() {
_target = get('Widget');
_gateway = $(document.body).append('iframe', {
src : param['source'] + param['gateway'],
name : 'Gateway',
id : 'Gateway'
})
.node();
_submitter = _target.top()
.append('form', {
id: 'Form',
target: 'Gateway',
action: param['source'] + param['gateway'],
method: 'POST'
}).node();
$(_submitter)
.append('fieldset', {
'class': 'three'
})
.append('div', {
'class': 'row'
})
.append('label')
.addText('Some field')
.up()
.append('input', {
type: 'text',
name: 'field',
id: 'field'
})
.up()
.up()
.append('input', {
type: 'submit',
value: 'Submit'
})
.up()
.up()
.up();
ready();
},
ready = function() {
// Some work
};
function TEST() {
init();
};
return TEST;
})();
return new TEST();
})(ezJS);
I discovered the solution it seems, by accident.
The iframe name property needed to be set presumably before it was appended to the DOM. I have updated my JsFiddle ( https://jsfiddle.net/n67w5tm7/1/ ) with the following:
append: function(tag, attrs) {
var el = document.createElement(tag);
if (tag === 'iframe' && attrs && attrs.name) {
try {
el.name = attrs.name; // This seems to fix it!
el = document.createElement('<iframe name="' + attrs.name + '">');
} catch (e) {}
}
_last(_ref).appendChild(el);
_ref.push(el);
if (attrs && typeof attrs === 'object') {
_each(attrs, functions.attr);
}
return functions;
},

JQuery plugin - callback breaks other features

I'm trying to build a basic color picker plugin (mainly as an exercise to learn about plugin development). I have a callback called "onSelected" that fires when you pick a color, but it breaks another feature of the plugin (the ability to toggle the visibility of the swatch list).
I am new to plugin development so I'm sure it's a simple mistake I'm making...
jsfiddle
Plugin:
(function ($) {
$.colorPicker2 = function (el, options) {
// the wrapper around the colors
var $pickerContainer = $("<div>");
// To avoid scope issues, use 'base' instead of 'this'
// to reference this class from internal events and functions.
var base = this;
// Access to jQuery and DOM versions of element
base.$el = $(el);
base.el = el;
// Add a reverse reference to the DOM object
base.$el.data("colorPicker2", base);
base.init = function () {
console.log("base.init");
base.options = $.extend({}, $.colorPicker2.defaultOptions, options);
// Put your initialization code here
// code goes here
$.each(base.options.colors, function (index, value) {
var $item = $('<div class="colorPicker-colorOption">').css({
"background-color": "#" + value
})
$item.click(function () {
console.log("item.click");
base.selectColor(value);
})
$pickerContainer.append($item);
});
//$pickerContainer.hide();
base.$el.append($pickerContainer);
if (base.options.toggleElement != null) {
base.options.toggleElement.click(function (e) {
base.togglePicker();
e.preventDefault();
});
}
};
base.togglePicker = function()
{
$pickerContainer.toggle();
}
base.selectColor = function (color) {
base.togglePicker();
if (typeof base.options.onSelected == 'function') {
base.options.onSelected.call(this, color);
}
}
// Sample Function, Uncomment to use
// base.functionName = function(paramaters){
//
// };
// Run initializer
base.init();
};
$.colorPicker2.defaultOptions = {
colors: [
'000000', '993300', '333300', '000080', '333399', '333333', '800000', 'FF6600',
'808000', '008000', '008080', '0000FF', '666699', '808080', 'FF0000', 'FF9900',
'99CC00', '339966', '33CCCC', '3366FF', '800080', '999999', 'FF00FF', 'FFCC00',
'FFFF00', '00FF00', '00FFFF', '00CCFF', '993366', 'C0C0C0', 'FF99CC', 'FFCC99',
'FFFF99', 'CCFFFF', '99CCFF', 'FFFFFF'
],
toggleElement: null,
onSelected: function (color) { }
};
$.fn.colorPicker2 = function (options) {
return this.each(function () {
(new $.colorPicker2(this, options));
});
};
})(jQuery);
How I hook into the onSelected event:
$(function () {
$('#primaryColorPicker').colorPicker2({
toggleElement: $('#selectPrimaryColor'),
onSelected: function (color) {
$('#selectedPrimaryColor').html("(#" + color + ")");
}
});
});
The HTML:
<a id="selectPrimaryColor">Toggle Color Picker</a>
<span id="selectedPrimaryColor" />
<div id="primaryColorPicker"></div>
You just have to learn how to write valid HTML
replace
<span id="selectedPrimaryColor" />
with
<span id="selectedPrimaryColor"></span>
FIDDLE

Hide all instances of element except one currently being written / displayed

I have this notification system that works with the following jQuery / javascript and displays a notification when called.
What I am having some trouble doing and what I am trying to do is once a new notification is create to hide and remove / destroy any existing notifications.
I've tried something like this: $('.notification').not(this).hide().remove();, but that didn't work.
Here is the jQuery behind the notifications:
;(function($) {
$.notificationOptions = {
className: '',
click: function() {},
content: '',
duration: 5000,
fadeIn: 400,
fadeOut: 600,
limit: false,
queue: false,
slideUp: 200,
horizontal: 'right',
vertical: 'top',
afterShow: function(){},
afterClose: function(){}
};
var Notification = function(board, options) {
var that = this;
// build notification template
var htmlElement = $([
'<div class="notification ' + options.className + '" style="display:none">',
'<div class="close"></div>',
options.content,
'</div>'
].join(''));
// getter for template
this.getHtmlElement = function() {
return htmlElement;
};
// custom hide
this.hide = function() {
htmlElement.addClass('hiding');
htmlElement.animate({ opacity: .01 }, options.fadeOut, function() {
var queued = queue.shift();
if (queued) {
$.createNotification(queued);
}
});
htmlElement.slideUp(options.slideUp, function() {
$(this).remove();
options.afterClose();
});
};
// show in board
this.show = function() {
// append to board and show
htmlElement[options.vertical == 'top' ? 'appendTo' : 'prependTo'](board);
htmlElement.fadeIn(options.fadeIn, options.afterShow());
//$('.notification').css('marginLeft', -$('.notification').outerWidth()/2);
$('.notification-board.center').css('marginLeft', -($('.notification-board.center').width()/2));
$(window).on('resize', function(){
$('.notification-board.center').css('marginLeft', -($('.notification-board.center').width()/2));
});
};
// set custom click callback
htmlElement.on('click', function() {
options.click.apply(that);
});
// helper classes to avoid hide when hover
htmlElement.on('mouseenter', function() {
htmlElement.addClass('hover');
if (htmlElement.hasClass('hiding')) {
// recover
htmlElement.stop(true);
// reset slideUp, could not find a better way to achieve this
htmlElement.attr('style', 'opacity: ' + htmlElement.css('opacity'));
htmlElement.animate({ opacity: 1 }, options.fadeIn);
htmlElement.removeClass('hiding');
htmlElement.addClass('pending');
}
});
htmlElement.on('mouseleave', function() {
if (htmlElement.hasClass('pending')) {
// hide was pending
that.hide();
}
htmlElement.removeClass('hover');
});
// close button bind
htmlElement.children('.close').on('click', function() {
that.hide();
});
if (options.duration) {
// hide timer
setTimeout(function() {
if (htmlElement.hasClass('hover')) {
// hovering, do not hide now
htmlElement.addClass('pending');
} else {
that.hide();
}
}, options.duration);
}
return this;
};
var queue = [];
$.createNotification = function(options) {
options = $.extend({}, $.notificationOptions, options || {});
// get notification container (aka board)
var board = $('.notification-board.' + options.horizontal + '.' + options.vertical);
if (!board.length) {
board = $('<div class="notification-board ' + options.horizontal + ' ' + options.vertical + '" />');
board.appendTo('body');
}
if (options.limit && board.children('.notification:not(.hiding)').length >= options.limit) {
// limit reached
if (options.queue) {
queue.push(options);
}
return;
}
// create new notification and show
var notification = new Notification(board, options)
notification.show(board);
return notification;
};
})(jQuery);
and here is how the notifications are called / created:
$.createNotification({
horizontal:'center',
vertical:'top',
content:'No more cards at this time.',
duration:6000,
click:function(){
this.hide();
}
});
The code:
$('.notification').not(this).hide().remove();
will work just fine to remove all .notification DOM elements currently in the DOM except the current one IF this is the current notification DOM element. If that code isn't working, then it's likely because this isn't the desired notification DOM element that you want to keep. If this is an instance of your Notification class, then that's the wrong type of object. For that above code to work, this has to be the notification DOM object.
If you want to just remove all old notification DOM elements BEFORE you insert your new one, then you can just do this before your new one is in the DOM:
$('.notification').remove();
That will clear out the old ones before you insert your new one.
Since you don't have this line of code in your currently posted code, I can't tell where you were trying to use it so can't advise further on what might be wrong. Please describe further where in your code you were trying to use this.

Reusing a modal template

On my current project, there are starting to be a few views that are modal views that are being used to delete items on the site. They are currently generic in that it's just a text description of the item they are deleting. Maybe in the future there will be an icon or a short description as well. There are now tasks to have that functionality to delete other stuff on our site. I'm new to the web, MVC, asp.net, etc, and what I want to know is if it's better to reuse our current modal view somehow, and pass in the objects we need to show in the view. Because the view needs to send the url back to the server on which items to delete, that part of code would need to be different for the view as well. Here is some of the stuff in our view along with a .cshtml template that's pretty generic that I didn't include.
Views.DeleteGiftModal = (function () {
return Backbone.View.extend({
template: Templates["template-gift-delete-modal"],
tagName: 'div',
initialize: function (options) {
$(window).bind("disposeModal", _.bind(this.disposeModal, this));
_.bindAll(this, "showDialog", "disposeModal", "displayResults");
this.eventAggregator = options.eventAggregator;
this.itemsToDelete = options.model;
this.errors = {};
this.render();
return this;
},
events: {
"click #delete-btn": "deleteItems",
"click #ok-btn": "disposeModal",
"click #cancel-btn": "disposeModal"
},
disposeModal: function (event, refresh) {
this.$el.modal("hide");
if (event != null && event.currentTarget != null && event.currentTarget.id == 'ok-btn')
refresh = true;
this.trigger("modalClosed", refresh);
this.remove();
this.unbind();
},
showDialog: function () {
this.$el.modal("show");
},
deleteItems: function () {
var self = this;
var element = this.$el;
var numberGifts = this.getKeys(this.itemsToDelete).length;
this.results = [];
var hasError = false;
element.find("#actions").hide();
element.find("#ok-actions").show();
$.each(this.itemsToDelete, function(i, v) {
// tell model to go away
var gift = new Gift({ id: i });
gift.destroy({
success: function (model, response) {
self.results.push({ id: model.id, response: response });
numberGifts--;
if (numberGifts <= 0) {
if (!hasError) {
self.disposeModal(null, true);
} else {
self.displayResults();
}
}
}
});
});
},
displayResults: function () {
var element = this.$el;
$.each(this.results, function(i, v) {
// to do check response for error message
var list = element.find("#delete-item-" + v.id);
if (v.response.message == "Deleted") {
list.append(" - <span align='right' style='color: green'>Deleted</span>");
} else {
hasError = true;
list.append(" - <span align='right' style='color: red'>" + v.response.message + "</span>");
}
});
},
render: function () {
this.$el.append(this.template);
this.$el.find("#ok-actions").hide();
// show list of item names
var list = this.$el.find("#items-to-delete-list");
$.each(this.itemsToDelete, function (i, v) {
$("<li id='delete-item-" + i + "'>" + v.name + "</li>").appendTo(list);
});
this.$el.attr('id', 'delete-gift-dialog');
return this;
}
});
})();
As I am looking through the code, and this being my first real project, it seems like a lot of things that could be quite similar, like deleting a Gift, deleting a Toy, etc have different Controllers for each (GiftController, ToyController), and hit different URLs. So currently things are all in their own class like that. I was wondering if that's the more standard way to approach these types of problems as well with views. Thanks in advance!
The app we're developing at work had a similar issue. We're using Backbone too so I can completely relate to this. What I ended up doing is have a sort of ModalBuilder that builds a form in a modal for you and binds events on the form elements for submit. The initialization of it could look like this:
new ModalBuilder({
form: [
{
tag: 'select[name="id"]',
options: [
{ name: 'Item 1', id: 12 },
{ name: 'Item 2', id: 32 }
]
},
{
tag: 'input[type="submit"]',
value: 'Delete'
}
],
events: function(){
$('input[type="submit"]').on('click', function(){
// Delete via ajax
})
}
})
What we do is we have different templates for every form element, inputfields and textareas and so on and we reuse it all over the place. ModalBuilder takes these arguments and builds a form
Also for certain cases it might be better to render the form server-side and deliver it to your modal via ajax. You have to weigh what makes your app more performant I suppose.

Categories