How to add toggle button from jqgrid toolbar to autogenerated context menu - jqgrid

Toggle button in jqgrid top toolbar is defined using Oleg answer as
var autoedit;
$("#grid_toppager_left table.navtable tbody tr").append(
'<td class="ui-pg-button ui-corner-all" >' +
'<div class="ui-pg-div my-nav-checkbox">' +
'<input tabindex="-1" type="checkbox" id="AutoEdit" '+(autoedit ? 'checked ' : '')+'/>' +
'<label title="Toggle autoedit" for="AutoEdit">this text is ignored in toolbar</label></div></td>'
);
$("#AutoEdit").button({
text: false,
icons: {primary: "ui-icon-star"}
}).click(function () {
autoedit = $(this).is(':checked');
});
Answer from how to add standard textbox command to jqgrid context menu is used to autogenerate context menu for grid from toolbar.
In generated context menu for this item only text "this text is ignored in toolbar" appears and selecting it does nothing.
How to make it work or remove this item from context menu?

Look at the demo or the same demo with another themes: this and this.
First of all I modified the code of the jquery.contextmenu.js to support jQuery UI Themes. Then I modified the code more, to be able to create context menu more dynamically. In the modified version of jquery.contextmenu.js one can crate menu and the bindings not only in the onContextMenu, but also in onShowMenu. Inside of onContextMenu one can create just the empty menu
<div id="myMenu"><ul></ul></div>
It is important only if one use dynamically switching of the icons of the text of buttons from the navigator bar.
You can download the modified version of the file here.
In the demo I used the last modification of the code from the answer, so the standard context menu can be still used in the grid on selected text or in the enabled input/textarea fields. The context menu of the browser will be displayed in the case:
I modified the code of createContexMenuFromNavigatorButtons function from the answer to the following:
var getSelectedText = function () {
var text = '';
if (window.getSelection) {
text = window.getSelection();
} else if (document.getSelection) {
text = document.getSelection();
} else if (document.selection) {
text = document.selection.createRange().text;
}
return typeof (text) === 'string' ? text : text.toString();
},
createContexMenuFromNavigatorButtons = function (grid, pager) {
var menuId = 'menu_' + grid[0].id, menuUl = $('<ul>'),
menuDiv = $('<div>').attr('id', menuId);
menuUl.appendTo(menuDiv);
menuDiv.appendTo('body');
grid.contextMenu(menuId, {
bindings: {}, // the bindings will be created in the onShowMenu
onContextMenu: function (e) {
var p = grid[0].p, i, lastSelId, $target = $(e.target),
rowId = $target.closest("tr.jqgrow").attr("id"),
isInput = $target.is(':text:enabled') ||
$target.is('input[type=textarea]:enabled') ||
$target.is('textarea:enabled');
if (rowId && !isInput && getSelectedText() === '') {
i = $.inArray(rowId, p.selarrrow);
if (p.selrow !== rowId && i < 0) {
// prevent the row from be unselected
// the implementation is for "multiselect:false" which we use,
// but one can easy modify the code for "multiselect:true"
grid.jqGrid('setSelection', rowId);
} else if (p.multiselect) {
// Edit will edit FIRST selected row.
// rowId is allready selected, but can be not the last selected.
// Se we swap rowId with the first element of the array p.selarrrow
lastSelId = p.selarrrow[p.selarrrow.length - 1];
if (i !== p.selarrrow.length - 1) {
p.selarrrow[p.selarrrow.length - 1] = rowId;
p.selarrrow[i] = lastSelId;
p.selrow = rowId;
}
}
return true;
} else {
return false; // no contex menu
}
},
onShowMenu: function (e, $menu) {
var options = this, $menuUl = $menu.find('ul:first').empty();
$('table.navtable .ui-pg-button', pager).each(function () {
var $spanIcon, text, $td, id, $li, $a, button,
$div = $(this).children('div.ui-pg-div:first'),
gridId = grid[0].id;
if ($div.length === 1) {
text = $div.text();
$td = $div.parent();
if (text === '') {
text = $td.attr('title');
}
if (this.id !== '' && text !== '') {
id = 'menuitem_' + this.id;
if (id.length > gridId.length + 2) {
id = id.substr(0, id.length - gridId.length - 1);
}
} else {
// for custom buttons
id = $.jgrid.randId();
}
$li = $('<li>').attr('id', id);
$spanIcon = $div.children('span.ui-icon');
if ($spanIcon.length > 0) {
// standard navGrid button or button added by navButtonAdd
$li.append($('<a>')
.text(text)
.prepend($spanIcon.clone().css({
float: 'left',
marginRight: '0.5em'
})));
$menuUl.append($li);
options.bindings[id] = (function ($button) {
return function () { $button.click(); };
}($div));
} else {
button = $div.children("input").data("button");
if (button !== undefined) {
$a = $('<a>')
.text(button.options.label)
.prepend(
$('<label>').addClass("ui-corner-all").css({
float: 'left',
width: '16px',
borderWidth: '0px',
marginRight: '0.5em'//'4px'
}).append(
$('<span>').addClass("ui-button-icon-primary ui-icon " +
button.options.icons.primary)
.css({
float: 'left',
marginRight: '0.5em'
})
)
);
$li.append($a);
if (button.type === "checkbox" && button.element.is(':checked')) {
$a.find('label:first').addClass("ui-state-active");
}
$menuUl.append($li);
options.bindings[id] = (function ($button, isCheckbox) {
if (isCheckbox) {
return function () {
if ($button.is(':checked')) {
$button.siblings('label').removeClass("ui-state-active");
} else {
$button.siblings('label').addClass("ui-state-active");
}
$button.click();
$button.button("refresh"); // needed for IE7-IE8
};
} else {
return function () { $button.click(); };
}
}(button.element, button.type === "checkbox"));
}
}
}
});
return $menu;
}
});
},
autoedit = false;
and fill the check-button in the navigator bar with the code which is changed only a little:
$("#pager_left table.navtable tbody tr").append(
'<td class="ui-pg-button ui-corner-all">' +
'<div class="ui-pg-div my-nav-checkbox">' +
'<input tabindex="-1" type="checkbox" id="AutoEdit" />' +
'<label title="Checkx caption which should appear as button tooltip"' +
' for="AutoEdit">Autoedit</label></div></td>'
);
$("#AutoEdit").button({
text: false,
icons: {primary: "ui-icon-mail-closed"}
}).click(function () {
var iconClass, $this = $(this);
if (!autoedit) { // $this.is(':checked')) {
autoedit = true;
iconClass = "ui-icon-mail-open";
} else {
autoedit = false;
iconClass = "ui-icon-mail-closed";
}
$this.button("option", {icons: {primary: iconClass}});
});
createContexMenuFromNavigatorButtons($grid, '#pager');
UPDATED: One more demo which support buttons added by new inlineNav method you can find here. Additionally I included in the demo the function normalizePagers which I use to improve the look of the pager:
How you can see the contextmenu includes only enabled buttons from the navigator bar.

Related

Summernote custom button with dialog

I want to add a custom button to the Summernote toolbar that opens up a dialog that has a textbox for a URL and several checkboxes for settings. I then want to use the info from the dialog to scrape web pages and do processing on the content. The ultimate goal is to place the scraped content into the editor starting where the cursor is. I've searched and found some code on creating a custom button, but not any solid examples of implementing a dialog. I went through the summernote.js code to see how the Insert Image dialog works and that left me really confused. The test code I've got so far is in the code block, below. Thanks in advance to anyone who can help get me sorted out.
var showModalDialog = function(){
alert("Not Implemented");
};
var AddWiki = function(context) {
var ui = $.summernote.ui;
var button = ui.button({
contents: '<i class="fa fa-plus"/> Add Wiki',
tooltip: "Set a New Wiki",
class: "btn-primary",
click: function() {
showModalDialog();
}
});
return button.render();
};
$(".tw-summernote-instance textarea").summernote({
airMode: false,
dialogsInBody: false,
toolbar: [["mybutton", ["customButton"]]],
buttons: {
customButton: AddWiki
},
callbacks: {
onInit: function(e) {
var o = e.toolbar[0];
jQuery(o)
.find("button:first")
.addClass("btn-primary");
}
}
});
I found a good, simple example of what I wanted to do. Here's the code:
(function(factory) {
/* global define */
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['jquery'], factory);
} else if (typeof module === 'object' && module.exports) {
// Node/CommonJS
module.exports = factory(require('jquery'));
} else {
// Browser globals
factory(window.jQuery);
}
}(function($) {
$.extend($.summernote.plugins, {
'synonym': function(context) {
var self = this;
var ui = $.summernote.ui;
var $editor = context.layoutInfo.editor;
var options = context.options;
context.memo('button.synonym', function() {
return ui.button({
contents: '<i class="fa fa-snowflake-o">',
tooltip: 'Create Synonym',
click: context.createInvokeHandler('synonym.showDialog')
}).render();
});
self.initialize = function() {
var $container = options.dialogsInBody ? $(document.body) : $editor;
var body = '<div class="form-group">' +
'<label>Add Synonyms (comma - , - seperated</label>' +
'<input id="input-synonym" class="form-control" type="text" placeholder="Insert your synonym" />'
'</div>'
var footer = '<button href="#" class="btn btn-primary ext-synonym-btn">OK</button>';
self.$dialog = ui.dialog({
title: 'Create Synonym',
fade: options.dialogsFade,
body: body,
footer: footer
}).render().appendTo($container);
};
// You should remove elements on `initialize`.
self.destroy = function() {
self.$dialog.remove();
self.$dialog = null;
};
self.showDialog = function() {
self
.openDialog()
.then(function(data) {
ui.hideDialog(self.$dialog);
context.invoke('editor.restoreRange');
self.insertToEditor(data);
console.log("dialog returned: ", data)
})
.fail(function() {
context.invoke('editor.restoreRange');
});
};
self.openDialog = function() {
return $.Deferred(function(deferred) {
var $dialogBtn = self.$dialog.find('.ext-synonym-btn');
var $synonymInput = self.$dialog.find('#input-synonym')[0];
ui.onDialogShown(self.$dialog, function() {
context.triggerEvent('dialog.shown');
$dialogBtn
.click(function(event) {
event.preventDefault();
deferred.resolve({
synonym: $synonymInput.value
});
});
});
ui.onDialogHidden(self.$dialog, function() {
$dialogBtn.off('click');
if (deferred.state() === 'pending') {
deferred.reject();
}
});
ui.showDialog(self.$dialog);
});
};
this.insertToEditor = function(data) {
console.log("synonym: " + data.synonym)
var dataArr = data.synonym.split(',');
var restArr = dataArr.slice(1);
var $elem = $('<span>', {
'data-function': "addSynonym",
'data-options': '[' + restArr.join(',').trim() + ']',
'html': $('<span>', {
'text': dataArr[0],
'css': {
backgroundColor: 'yellow'
}
})
});
context.invoke('editor.insertNode', $elem[0]);
};
}
});
}));

kendo editor: prevent keydown not working

I would like to prevent the pressing of ENTER if the kendo editor body has a certain height (to limit the max height of the editor field). But none of my tries worked.
<textarea data-role="editor"
data-bind="events: { keydown: onEditorKeyDown }">
</textarea>
viewModel.onEditorKeyDown = function (e) {
var key = e.keyCode;
if (key == 13) {
var editor = e.sender;
var body = editor.body;
var height = body.scrollHeight;
if (height > 195) {
?? //tried e.preventDefault(), return false etc.
}
}
};
I've managed to find two solutions. One is dirty hack and other doesn't match your requirements 100%. But both perform what is needed more or less.
New paragraph is added via embedded editor insertParagraph command which overrides default keydown logic. So the first solution is to temporary override this command using keydown and keyup events like this:
<textarea data-role="editor"
data-bind="events: { keydown: onEditorKeyDown, keyup: onEditorKeyUp }">
</textarea>
// this should probably be moved to viewModel, it's here for demo puproses only
var savedCommand;
var viewModel = kendo.observable({
html: null,
isVisible: true,
onChange: function() {
kendoConsole.log("event :: change (" + kendo.htmlEncode(this.get("html")) + ")");
}
});
viewModel.onEditorKeyDown = function (e) {
var key = e.keyCode;
if (key == 13) {
var editor = e.sender;
var body = editor.body;
var height = body.scrollHeight;
if (height > 195) {
savedCommand = editor.toolbar.tools.insertParagraph.command;
editor.toolbar.tools.insertParagraph.command = function() {};
}
}
};
viewModel.onEditorKeyUp = function (e) {
var key = e.keyCode;
if (key == 13) {
if (savedCommand) {
var editor = e.sender;
editor.toolbar.tools.insertParagraph.command = savedCommand;
savedCommand = undefined;
}
}
};
kendo.bind($("#example"), viewModel);
This works fine, but looks a bit ugly.
Other solution is to execute editor undo command if needed. It works too, but empty line always flickers for a moment:
<textarea data-role="editor"
data-bind="events: { keyup: onEditorKeyUp }"></textarea>
var viewModel = kendo.observable({
html: null,
isVisible: true,
onChange: function() {
kendoConsole.log("event :: change (" + kendo.htmlEncode(this.get("html")) + ")");
}
});
viewModel.onEditorKeyUp = function (e) {
var key = e.keyCode;
if (key == 13) {
var editor = e.sender;
var body = editor.body;
while (body.scrollHeight > 195) {
editor.exec('undo');
}
}
};
kendo.bind($("#example"), viewModel);
UPD: I've found event better solution. You can use execute event, see http://docs.telerik.com/kendo-ui/api/javascript/ui/editor#events-execute
Then check conditions and filter by command name to cancel insert of new paragraph:
execute: function(e) {
alert(e.name);
e.preventDefault();
}

Handsontable - Unable to keep html element created by a custom renderer visible

I am using the open source version of handsontable (version 0.29.2). I created a custom renderer that creates a hidden SPAN element/icon on every row. When input fails validation, I use jQuery to programmatically unhide/show the SPAN tag/icon so that it appears in the right-hand side of the cell. It works great, but unfortunately when I enter an invalid value into another cell, the icon from the first cell disappears. The preferred behavior is to have all of the icons visible in cells where a validation issue exists.
Question: Is there a way to keep all of the icons visible?
If this is not possible, is there a different way in handsontable to display an image after validation? As you can see from the code below (and my jsfiddle example), I am not using the built-in handsontable validation hooks. With the built-in validation, I can't add an icon like I want - I can only override the default style of an invalid cell by using invalidCellClassName.
I have created a simple example with instructions demonstrating my issue:
http://jsfiddle.net/4g3a5kqc/15/
var data = [
["1", "abc"],
["2", "def"],
["3", "ghi"],
["4", "jkl"]
],
container = document.getElementById("example"),
hot1;
// This function is a custom renderer that creates a hidden SPAN element/
// icon. In this example, when a user changes the value, the SPAN element
// icon will appear.
function customRenderer(instance, td, row, col, prop, value, cellProperties) {
td.innerHTML = value +
'<span id="account-code-error-' + row + '-' + col + '" class="account-code-error ' +
'glyphicon glyphicon-exclamation-sign text-warning jzb-icon-md pull-right" ' +
'style="font-size: large; cursor: pointer; display: none;"></span>';
}
var hot1 = new Handsontable(container, {
data: data,
rowHeaders: true,
colHeaders: true,
stretchH: 'all',
cells:
function (row, col, prop) {
var cellProperties = {};
if (col == 0) {
cellProperties.renderer = customRenderer;
}
return cellProperties;
}
});
hot1.addHook('afterChange', afterChange);
// Show the SPAN tag with the icon
// in the right-hand side of the cell.
function afterChange(changes, source) {
console.log(changes, source);
if (source == 'edit' || source == 'autofill') {
$.each(changes,
function (index, element) {
var change = element;
var rowIndex = change[0];
var columnIndex = change[1];
var oldValue = change[2];
var newValue = change[3];
console.log(oldValue, newValue, rowIndex, columnIndex, change);
if (columnIndex != 0) {
return;
}
if (newValue >= 0) {
return;
}
var cellProperties = hot1.getCellMeta(rowIndex, hot1.propToCol(columnIndex));
var td = hot1.getCell(rowIndex, columnIndex, true);
var span = td.getElementsByTagName("span");
$("#" + span[0].id).show();
});
}
}
Due to customRenderer() being called after every change we have to store somewhere cells with spans visible and check for it at the rendering. On the other hand if the span should not be visible (input is valid) we need to remove it from the array of cells wit visible spans. Working fiddle:
http://jsfiddle.net/8vdwznLs/
var data = [
["1", "abc"],
["2", "def"],
["3", "ghi"],
["4", "jkl"]
],
container = document.getElementById("example"),
hot1,
visibleSpans = [];
// This function is a custom renderer that creates a hidden SPAN element/
// icon. In this example, when a user changes the value, the SPAN element
// icon will appear.
function customRenderer(instance, td, row, col, prop, value, cellProperties) {
if (visibleSpans.indexOf(td) > -1) {
td.innerHTML = value +
'<span id="account-code-error-' + row + '-' + col + '" class="account-code-error ' +
'glyphicon glyphicon-exclamation-sign text-warning jzb-icon-md pull-right" ' +
'style="font-size: large; cursor: pointer;"></span>';
} else {
td.innerHTML = value +
'<span id="account-code-error-' + row + '-' + col + '" class="account-code-error ' +
'glyphicon glyphicon-exclamation-sign text-warning jzb-icon-md pull-right" ' +
'style="font-size: large; cursor: pointer; display: none;"></span>';
}
}
var hot1 = new Handsontable(container, {
data: data,
rowHeaders: true,
colHeaders: true,
stretchH: 'all',
cells:
function (row, col, prop) {
var cellProperties = {};
if (col == 0) {
cellProperties.renderer = customRenderer;
}
return cellProperties;
}
});
hot1.addHook('afterChange', afterChange);
// Show the SPAN tag with the icon
// in the right-hand side of the cell.
function afterChange(changes, source) {
console.log(changes, source);
if (source == 'edit' || source == 'autofill') {
$.each(changes,
function (index, element) {
var change = element;
var rowIndex = change[0];
var columnIndex = change[1];
var oldValue = change[2];
var newValue = change[3];
var td = hot1.getCell(rowIndex, columnIndex, true);
console.log(oldValue, newValue, rowIndex, columnIndex, change);
if (columnIndex != 0) {
return;
}
if (newValue >= 0) {
var indexOfSpan = visibleSpans.indexOf(td);
if (indexOfSpan > -1) {
visibleSpans.splice(indexOfSpan, 1);
hot1.render();
return;
}
return;
}
var cellProperties = hot1.getCellMeta(rowIndex, hot1.propToCol(columnIndex));
visibleSpans.push(td);
var span = td.getElementsByTagName("span");
span[0].setAttribute('style', '');
});
}
}

JqGrid Context menu and sub menus

I am using Jqgrid 4.5.4 version. To achieve right click context menu functionality I am using Jquery Plugin for right click context menus (http://www.trendskitchens.co.nz/jquery/contextmenu/).
My requirement is to show the sub menu inside main right click context menu. For example:
--------------------------
Delete
Add
Highlight > ---------------
Highlight doc1
Highlight doc 2
----------------
Edit
----------------------------
When I click on "Highlight" context menu option, sub menu opens up with "Highlight doc 1" and "Highlight doc 2". I could not achieve the same functionality with context menu plugin.
Could anyone please provide some solution.
Here is my code:
loadComplete : function(data) {
$("tr.jqgrow", this)
.contextMenu('rightClickMenu', {
bindings : createContextMenuBindings(),
onContextMenu : function(event) {
var rowId = $(event.target).closest("tr.jqgrow").attr("id");
return createContextMenu(myMap[rowId].ctxMenu);
}
});
}
function createContextMenuBindings() {
var bindings = {
'delete' : function(trigger) {
handleDelete(trigger.id);
},
'add' : function(trigger) {
handleAccept(codeType, trigger.id, true);
},
'highlight' : function(trigger) {
// This case I have to show sub menu with different options
}
};
return bindings;
}
function createContextMenu(ctxMenu) {
var menuItem = '';
if (ctxMenu != null) {
for ( var i = 0; i < ctxMenu.length; i++) {
if (ctxMenu[i].display) {
var subMenu = ctxMenu[i].subCtxMenu;
if(subMenu != null && subMenu.length > 0)
{
menuItem += "<li class='menuitem dropdown' id= "+ ctxMenu[i].handler + ">";
var ulTag = '<a class="btn btn-sm btn-link" data-toggle="dropdown">' + ctxMenu[i].name +
'<span class="caret"></span>'+
'</a>';
ulTag += "<ul class='subrowul dropdown-menu'>";
$.each(subMenu, function( index, value ) {
ulTag += "<li class='submenuitem' id= '"+ value.handler + "'>" + value.name + "</li>";
});
ulTag += "</ul>";
menuItem += ulTag;
}
else
{
menuItem += "<li class='menuitem' id= "+ ctxMenu[i].handler + ">" + ctxMenu[i].name;
}
menuItem += "</li>";
}
$('#rightClickMenu > ul li.menuitem').remove();
$("#rightClickMenu > ul").append(menuItem);
if (menuItem == '') {
return false;
} else {
return true;
}
}
I would appreciate your answers.
Thanks
UPDATE:
I am answering to my own question. I could able to integrate Jquery context menu to jqgrid. Now I could able to see menu and sub menus. I am attaching some code for anybody looking for the answer:
Below logic will be called after jqgrid load complete by row.
$.contextMenu({
selector: 'tr#'+rowId,
build: function($trigger, e){
// ctxMenu is rendered from server.
var ctxMenu = jqGridJsonResponseMap[code_type] [$trigger[0].id].rowInfo.ctxMenu;
// ctx menu options constructed for each row. It is dynamic based on each row.
var _items = getCtxMenuOptionsByRowId(ctxMenu, code_type, $trigger[0].id);
return {
callback: function(key, options) {
// each context menu has differnt callback
ctx_bindings[key](key, options, code_type);
},
items: _items
};
}
});

Add checkbox to the ckeditor toolbar to addClass/removeClass in img elements

I want to write a ckeditor plugin that adds a checkbox to the toolbar.
When an img element is selected, the checkbox should reflect whether the image has a certain class or not.
When something else than an image is selected, the checkbox should be
disabled, or maybe invisible.
When I check or uncheck the checkbox, the class should be added/removed to/from the img.
In other words, I want to add something else than a button to the toolbar (those that are added with editor.ui.addButton), and that something should be a checkbox.
How do I do that?
I managed to do it anyway, using editor.ui.add and editor.ui.addHandler. Here is a screenshot:
plugin.js:
CKEDITOR.plugins.add('add_zoomable_to_image',
{
init: function( editor )
{
var disabled_span_color = "#cccccc";
var enabled_span_color = "#000000";
var cb;
var span;
var html =
"<div style='height: 25px; display: inline-block'>" +
"<div style='margin: 5px'><input class='add_zoomable_cb' type='checkbox' disabled='disabled'>" +
"<span class='add_zoomable_span' style='color: " + disabled_span_color + "'> Add zoomable to image</span>" +
"</div>" +
"</div>";
editor.ui.add('add_zoomable_to_image', "zoomable_button", {});
editor.ui.addHandler("zoomable_button",
{
create: function ()
{
return {
render: function (editor, output)
{
output.push(html);
return {};
}
};
}
});
editor.on("selectionChange", function()
{
var sel = editor.getSelection();
var ranges = sel.getRanges();
if (ranges.length == 1)
{
var el = sel.getStartElement();
if (el.is('img'))
{
enable_input();
if (el.hasClass('zoomable'))
check_cb();
else
uncheck_cb();
}
else
disable_input();
}
else
disable_input();
});
editor.on("instanceReady", function ()
{
cb = $('.add_zoomable_cb');
span = $('.add_zoomable_span');
cb.change(function()
{
var element = editor.getSelection().getStartElement();
if (cb.is(':checked'))
element.addClass('zoomable');
else
element.removeClass('zoomable');
});
});
function enable_input()
{
cb.removeAttr('disabled');
span.css('color', enabled_span_color);
}
function disable_input()
{
uncheck_cb();
cb.attr('disabled', 'disabled');
span.css('color', disabled_span_color);
}
function check_cb()
{
cb.attr('checked', 'checked');
}
function uncheck_cb()
{
cb.removeAttr('checked');
}
}
});
CKEditor doesn't include the option to add a checkbox to the toolbar, so your first step is to look at their code and extend it to add the checkbox.
Then look at how other buttons work to modify the content and detect when the selection in the editor changes to reflect its new state, and apply those ideas to your checkbox.

Resources