How to dynamically generate options for RichCombo in CKEDITOR? - javascript

There is a form on my page with textarea (CKEDITOR) and select field <select id="_photogalleries" multiple="multiple"></select>. I'd like options in RichCombo to depend on the options that are selected in select with id #_photogalleries. Is there any way to regenerate RichCombo dynamically?
Thanks in advance.
CKEDITOR.plugins.add('style_plugin', {
requires: ['richcombo'],
init: function(editor) {
var pluginName = 'style_plugin';
var config = editor.config,
lang = editor.lang.format;
editor.ui.addRichCombo('photogalleries', {
label: "Фоторепортаж",
title: "Фоторепортаж",
voiceLabel: "Фоторепортаж",
className: 'cke_format',
multiSelect: false,
icon: CKEDITOR.plugins.getPath('style_plugin') + 'photo-list-horizontal.png',
panel: {
css: [config.contentsCss, CKEDITOR.getUrl(editor.skinPath + 'editor.css')],
voiceLabel: lang.panelVoiceLabel
},
init: function () {
this.startGroup("Фоторепортаж");
var list=this;
$("#_photogalleries option:selected").each(function(index, value){
console.log(index, value);
list.add("#HORIZONTAL_GALLERY_"+ $(value).val()+"#", "(Г) " + $(value).text(), "(Г) " + $(value).text());
list.add("#VERTICAL_GALLERY_"+ $(value).val()+"#", "(В) " + $(value).text(), "(В) " + $(value).text());
});
},
onClick: function (value) {
editor.focus();
editor.fire('saveSnapshot');
editor.insertHtml(value);
editor.fire('saveSnapshot');
}
});
}
});

This works for me and you dont have to keep a global variable.
CKEDITOR.plugins.add('systemdata', {
init: function (editor) {
var fnData = editor.config.fnData;
if (!fnData || typeof (fnData) != 'function')
throw "You must provide a function to retrieve the list data.";
editor.ui.addRichCombo('systemDataCmb',
{
allowedContent: 'abbr[title]',
label: "System Data",
title: "System Data",
multiSelect: false,
init: function () {
var self = this;
var content = fnData();
$.each(content, function(index, value) {
// value, html, text
self.add(value.name, value.name, value.name)
});
}
}
Then to set the function to get the data put this somewhere where you setup the ckeditor
CKEDITOR.replaceAll(function(element, config) {
config.startupFocus = true;
config.fnData = function() {
var returnData = null;
$.ajax({
url: "/GetData",
async: false,
data: { id: 1 },
}).done(function(result) { returnData= result; });
return returnData;
};
});
It assumes you bring back a json response that has an array of items that have a value property, that can be easily changed though.

I guess I found a solution that worked for me. It was to keep a list object in a global variable and then modify it when onchange event fires in the external select.

I solved this trouble with a single line:
YOURCOMBO.createPanel(editor);
For example:
var comboTeam = editor.ui.get("team");
comboTeam.createPanel(editor);//This is important, if not, doesnt works
Now you can add items to the combo
comboTeam.add("name","name","name");
comboTeam.add("name2","name2","name2");
comboTeam.add("name3","name3","name3");

Related

Summernote custom button pass context and data

I am using summernote in angular I want to create a custom button.
I would like to pass the listHit in customButton as parameter like this 'testBtn': this.customButton(context, listHit) but I am not sure how to do this since the function looks like this 'testBtn': this.customButton any help would be appreciated thank you.
My custom button looks something like this.
customButton(context) {
var listHit = ['One', 'Two', 'Tree'];
const ui = ($ as any).summernote.ui;
var i;
var listHitHtml = "";
for (i = 0; i < listHit.length; i++) {
listHitHtml += "<li>" + listHit[i] + "</li>";
}
var button = ui.buttonGroup([
ui.button({
className: 'dropdown-toggle',
contents: '<i class="fa fa-comments"/><span class="caret"></span>',
tooltip: '#erp_colombia.Lang.Resource.conAvailableComments',
data: {
toggle: 'dropdown'
}
}),
ui.dropdown({
className: 'drop-default summernote-list',
contents: "<div id=\"container-comentario\"><div id=\"dialog\" title=\"Comentarios\" ><h1 class=\"header-comentario\">" + 'Comment' + "</h1><ul id=\"liste-comentarios\"><ul>" + listHitHtml + "</ul></div></div>",
callback: function ($dropdown) {
$dropdown.find('li').each(function () {
$(this).click(function () {
context.invoke("editor.insertText", $(this).html() + "\n");
});
});
}
})
]);
return button.render(); // return button as jquery object
}
Here is my pdfmaker config
this.config = {
placeholder: placeholder,
shortcuts: false,
disableDragAndDrop: true,
//tabsize: 2,
hint: {
mentions: this.quoteCommentsForSummerNote,
match: /\b(\w{1,})$/,
search: function (keyword, callback) {
callback($.grep(this.mentions, function (item: any) {
return item.indexOf(keyword) == 0;
}));
},
content: function (item) {
return item;
}
},
height: 200,
toolbar: [
['myotherbutton', ['testBtn']],
],
buttons: {
'testBtn': this.customButton
}
}
And this is my angular html
Here you can fiddle with a example I created a list that we will assume comes from a service I would like to pass this list to customButton
listStringFromDbService = ['one', 'two', 'three'];
https://stackblitz.com/edit/angular-summernote-demo-gdvvbn?file=src%2Fapp%2Fapp.component.ts
I believe I figured this out.
You can change your button declaration to a function that returns a button function. That way you can pass data to it before constructing the function that Evernote binds to testBtn.
Change the function declaration (expression or declaration will work, like you pointed out)
function customButtonGenerator(arr) {
return function (context) {
const ui = $.summernote.ui;
const button = ui.button({
contents: '<i class="note-icon-magic"></i> Hello',
tooltip: 'Custom button',
container: '.note-editor',
className: 'note-btn',
click: function () {
context.invoke('editor.insertText', 'Hello from test btn!!! ' + arr);
},
});
return button.render();
};
};
then when you create the ui config you can generate the button function instead:
buttons: {
testBtn: customButtonGenerator(this.listStringFromDbService),
},
Here's an updated stackblitz showing a working example.
I have modified the answer of MPawlak like this in it there is a dropdown where you can click the items you want to add. Also I have the passing of parameter to the custom button that still works. Thanks to MPawlak
https://stackblitz.com/edit/angular-summernote-demo-yq8t4t

How to add render and create methods to Selectize element

I have a HTML 'select' element that I want to use as 'AutoSuggest' by using Selectize.js and this is how I initialize the selectize
jQuery(ele).selectize({
//options: initData,
addPrecedence: false,
persist: false,
maxItems: 1,
create: function (input) {
return {
value: input,
text: input
};
},
render: {
option_create: function (data, escape) {
return '<div class="create"><strong>' + escape(data.input) + '</strong></div>';
}
}
});
Now, the issue is if the 'ele' is already initialized as a 'Selectize' control without the 'render' and 'create' options, how can I add these options?
I figured it out. Here is how you do this
if (ele.selectize) {
var selectizeCtrl = jQuery(ele)[0].selectize;
selectizeCtrl.settings.create = function (input) {
return {
value: input,
text: input
};
};
selectizeCtrl.settings.render.option_create = function (data, escape) {
return '<div class="create"><strong>' + escape(data.input) + '</strong></div>';
};
}

kendo grid inline edit dropdown will not expand on tab navigation

I'm having an issue with Kendo Grid in Angular where the custom drop down I've implemented will not open when tab navigating to that column. The built in text and number editor fields are editable on tab navigation but my custom drop down will not expand. I have to click on it to get the drop down effect.
My goal here is to allow the user to log an an entire row of data without having to take their hands off the keyboard.
My column is defined like so:
gridColumns.push({
field: currentField.FieldName.replace(/ /g, "_"),
title: currentField.FieldName,
editor: $scope.dropDownAttEditor,
template: function (dataItem) {
return $scope.dropDownTemplate(dataItem, currentField.FieldName);
}
});
My gridOptions are defined as follows:
$scope.gridOptions = {
dataSource: new kendo.data.DataSource({
transport: {
read: {
url: appconfig.basePath + '/api/DrillHoleManager/DrillHoleInterval',
type: 'POST',
contentType: 'application/json'
},
update: {
url: appconfig.basePath + '/api/DrillHoleManager/DrillHoleIntervalUpdate',
type: 'POST',
contentType: 'application/json'
},
parameterMap: function (data, operation) {
if (operation === "read") {
data.DrillHoleId = $scope.entity.Id;
data.DrillHoleIntervalTypeId = $stateParams.ddhinttypeid;
// convert the parameters to a json object
return kendo.stringify(data);
}
if (operation === 'update') {
// build DrillHoleIntervalDto object with all ATT/CMT values to send back to server
var drillHoleInterval = { Id: data.Id, Name: data.Name, From: data.From, To: data.To };
drillHoleInterval.Attributes = [];
drillHoleInterval.Comments = [];
var attributeFields = $.grep($scope.currentFields, function (e) { return e.DrillHoleTabFieldType == DrillHoleTabFieldTypeEnum.IntervalAttribute });
$.each(attributeFields, function (idx, attributeField) {
drillHoleInterval.Attributes.push({
Id: attributeField.AttributeDto.Id,
LookupId: data[attributeField.FieldName.replace(/ /g, "_")]
});
});
var commentFields = $.grep($scope.currentFields, function (e) { return e.DrillHoleTabFieldType == DrillHoleTabFieldTypeEnum.IntervalComment });
$.each(commentFields, function (idx, commentField) {
drillHoleInterval.Comments.push({
Id: commentField.CommentDto.Id,
Value: ((data[commentField.FieldName.replace(/ /g, "_")] != "") ? data[commentField.FieldName.replace(/ /g, "_")] : null)
});
});
return kendo.stringify(drillHoleInterval);
}
// ALWAYS return options
return options;
}
},
schema: { model: { id : "Id" }},
serverPaging: false,
serverSorting: false,
serverFiltering: false
//,autoSync: true
}),
columns: gridColumns,
dataBound: onDataBound,
autoBind: false,
navigatable: true,
scrollable: false,
editable: true,
selectable: true,
edit: function (e) {
var grid = $("#ddhintgrid").data("kendoGrid");
//grid.clearSelection();
grid.select().removeClass('k-state-selected');
// select the row currently being edited
$('[data-uid=' + e.model.uid + ']').addClass('k-state-selected');
e.container[0].focus();
}
};
Here is a custom event to handle the 'Tab' keypress. The point of this is I want a new record automatically added to the grid if the user presses 'Tab' at the end of the last line:
$("#ddhintgrid").keydown(function (e) {
if (e.key == "Tab") {
var grid = $("#ddhintgrid").data("kendoGrid");
var data = grid.dataSource.data();
var selectedItem = grid.dataItem(grid.select());
var selectedIndex = null
if (selectedItem != null) {
selectedIndex = grid.select()[0].sectionRowIndex;
if (selectedIndex == data.length - 1) { // if the bottom record is selected
// We need to manually add a new record here so that the new row will automatically gain focus.
// Using $scope.addRecord() here will add the new row but cause the grid to lose focus.
var newRecord = { From: grid.dataSource.data()[selectedIndex].To };
var currentCmtFields = $.grep($scope.currentFields, function (e) { return e.DrillHoleTabFieldType == DrillHoleTabFieldTypeEnum.IntervalComment; });
$.each(currentCmtFields, function (idx, currentCmtField) {
newRecord[currentCmtField.FieldName.replace(/ /g, "_")] = null;
});
grid.dataSource.insert(data.length, newRecord);
// edit the new row
grid.editRow($("#ddhintgrid tr:eq(" + (data.length) + ")"));
}
}
}
});
Here is my template for the drop down column:
$scope.dropDownTemplate = function (dataItem, fieldName) {
var currentLookups = $.grep($scope.currentFields, function (e) { return e.FieldName == fieldName; })[0].AttributeDto.Lookups;
var selectedLookup = $.grep(currentLookups, function (e) { return e.Id == dataItem[fieldName.replace(/ /g, "_")]; })[0];
// With the custom dropdown editor when going from null to a value the entire lookup object (id, name) is placed in the
// dataItem[field_name] instead of just the id. We need to replace this object with just the id value and return the name of
// the lookup to the template.
if (typeof selectedLookup == 'undefined' && dataItem[fieldName.replace(/ /g, "_")] != null) {
selectedLookup = angular.copy(dataItem[fieldName.replace(/ /g, "_")]);
dataItem[fieldName.replace(/ /g, "_")] = selectedLookup.Id;
}
if (selectedLookup != null && selectedLookup != '') {
return selectedLookup.Name;
}
else {
return '';
}
};
And finally here is the custom editor for the drop down column:
$scope.dropDownAttEditor = function (container, options) {
var editor = $('<input k-data-text-field="\'Name\'" k-data-value-field="\'Id\'" k-data-source="ddlDataSource" data-bind="value:' + options.field + '"/>')
.appendTo(container).kendoDropDownList({
dataSource: $.grep($scope.currentFields, function (e) { return e.FieldName == options.field.replace(/_/g, ' '); })[0].AttributeDto.Lookups,
dataTextField: "Name",
dataValueField: "Id"
});
};
I know this is a lot to take in but if you have any questions just let me know.
My ultimate goal is that I want the user to be able to navigate the grid using the 'Tab' key and edit every field without having to use the mouse.

Cannot reload data in Fuelux Datagrid

I have tried to reload the data populated by an ajax call but I cant get it to work, it shows the old data even after using the reload method. The thing is that if I change some variables to populate a different data and try to call the following code without refreshing the page it does not reload the updated data =/ Here is my code:
function populateDataGrid() {
$.ajaxSetup({async: false});
var gridinfo="";
$.post("lib/function.php",{activity: activity, shift: shift, date: date},
function (output){
gridinfo = JSON.parse(output);
});
$.ajaxSetup({async: true});
// INITIALIZING THE DATAGRID
var dataSource = new StaticDataSource({
columns: [
{
property: 'id',
label: '#',
sortable: true
},
{
property: 'date',
label: 'date',
sortable: true
},
....
],
formatter: function (items) {
var c=1;
$.each(items, function (index, item) {
item.select = '<input type="button" id="select'+c+'" class="select btn" value="select" onclick="">';
c=c+1;
});
},
data: gridinfo,
delay:300
});
$('#grid').datagrid({
dataSource: dataSource
});
$('#grid').datagrid('reload');
$('#modal-fast-appointment-results').modal({show:true});
}
I found a solution... I had to create a new DataSource (lets call it "AjaxDataSource") and add the ajax request functionality within the data constructor:
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(['underscore'], factory);
} else {
root.AjaxDataSource = factory();
}
}(this, function () {
var AjaxDataSource = function (options) {
this._formatter = options.formatter;
this._columns = options.columns;
this._delay = options.delay || 0;
this._data = options.data;
};
AjaxDataSource.prototype = {
columns: function () {
return this._columns;
},
data: function (options, callback) {
var self = this;
setTimeout(function () {
var data;
$.ajax({
url: 'getdata.php',
type: 'POST',
data: 'param1:param1,param2,param2,...,paramN:paramN', // this is optional in case you have to send some params to getdata.php
dataType: 'json',
async: false,
success: function(result) {
data = result;
},
error: function(data){
//in case we want to debug and catch any possible error
// console.log(data);
}
});
// SEARCHING
if (options.search) {
data = _.filter(data, function (item) {
var match = false;
_.each(item, function (prop) {
if (_.isString(prop) || _.isFinite(prop)) {
if (prop.toString().toLowerCase().indexOf(options.search.toLowerCase()) !== -1) match = true;
}
});
return match;
});
}
var count = data.length;
// SORTING
if (options.sortProperty) {
data = _.sortBy(data, options.sortProperty);
if (options.sortDirection === 'desc') data.reverse();
}
// PAGING
var startIndex = options.pageIndex * options.pageSize;
var endIndex = startIndex + options.pageSize;
var end = (endIndex > count) ? count : endIndex;
var pages = Math.ceil(count / options.pageSize);
var page = options.pageIndex + 1;
var start = startIndex + 1;
data = data.slice(startIndex, endIndex);
if (self._formatter) self._formatter(data);
callback({ data: data, start: start, end: end, count: count, pages: pages, page: page });
}, this._delay)
}
};
return AjaxDataSource;
}));
After defining the new DataSource, we just need to create it and call the datagrid as usual:
function populateDataGrid(){
// INITIALIZING THE DATAGRID
var dataSource = new AjaxDataSource({
columns: [
{
property: 'id',
label: '#',
sortable: true
},
{
property: 'date',
label: 'date',
sortable: true
},
....
],
formatter: function (items) { // in case we want to add customized items, for example a button
var c=1;
$.each(items, function (index, item) {
item.select = '<input type="button" id="select'+c+'" class="select btn" value="select" onclick="">';
c=c+1;
});
},
delay:300
});
$('#grid').datagrid({
dataSource: dataSource
});
$('#grid').datagrid('reload');
$('#modal-results').modal({show:true});
}
So now we have our datagrid with data populated via ajax request with the ability to reload the data without refreshing the page.
Hope it helps someone!

Jquery Context Menu ajax fetch menu items

I have a jquery context menu on my landing page where I have hardcode menu items. Now I want to get the menu items from server. Basically the idea is to show file names in a specified directory in the context menu list and open that file when user clicks it...
This is so far I have reached..
***UPDATE***
C# code
[HttpPost]
public JsonResult GetHelpFiles()
{
List<Manuals> manuals = null;
var filesPath = Server.MapPath(#"\HelpManuals");
var standardPath = new DirectoryInfo(filesPath);
if (standardPath.GetFiles().Any())
{
manuals = standardPath.GetFiles().Select(x => new Manuals
{
Name = GetFileNamewithoutExtension(x.Name),
Path = x.Name
}).ToList();
}
return Json(manuals, JsonRequestBehavior.AllowGet);
}
private string GetFileNamewithoutExtension(string filename)
{
var extension = Path.GetExtension(filename);
return filename.Substring(0, filename.Length - extension.Length);
}
JavaScript Code
$.post("/Home/GetHelpFiles", function (data) {
$.contextMenu({
selector: '#helpIcon',
trigger: 'hover',
delay: 300,
build: function($trigger, e) {
var options = {
callback: function(key) {
window.open("/HelpManuals/" + key);
},
items: {}
};
$.each(data, function (item, index) {
console.log("display name:" + index.Name);
console.log("File Path:" + index.Path);
options.items[item.Value] = {
name: index.Name,
key: index.Path
}
});
}
});
});
Thanks to Matt. Now, the build function gets fire on hover.. but im getting illegal invocation... and when iterating through json result, index.Name and this.Name gives correct result. But item.Name doesn't give anything..
to add items to the context menu dynamically you need to make a couple changes
$.contextMenu({
selector: '#helpIcon',
trigger: 'hover',
delay: 300,
build: function($trigger, e){
var options = {
callback: function (key) {
var manual;
if (key == "adminComp") {
manual = "AdminCompanion.pdf";
} else {
manual = "TeacherCompanion.pdf";
}
window.open("/HelpManuals/" + manual);
},
items: {}
}
//how to populate from model
#foreach(var temp in Model.FileList){
<text>
options.items[temp.Value] = {
name: temp.Name,
icon: 'open'
}
</text>
}
//should be able to do an ajax call here but I believe this will be called
//every time the context is triggered which may cause performance issues
$.ajax({
url: '#Url.Action("Action", "Controller")',
type: 'get',
cache: false,
async: true,
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (_result) {
if (_result.Success) {
$.each(_result, function(item, index){
options.items[item.Value] = {
name: item.Name,
icon: 'open'
}
});
}
});
return options;
}
});
so you use build and inside of that define options and put your callback in there. The items defined in there is empty and is populated in the build dynamically. We build our list off of what is passed through the model but I believe you can put the ajax call in the build like I have shown above. Hopefully this will get you on the right track at least.
I solved this problem the following way.
On a user-triggered right-click I return false in the build-function. This will prevent the context-menu from opening. Instead of opeing the context-menu I start an ajax-call to the server to get the contextMenu-entries.
When the ajax-call finishes successfully I create the items and save the items on the $trigger in a data-property.
After saving the menuItems in the data-property I open the context-menu manually.
When the build-function is executed again, I get the items from the data-property.
$.contextMenu({
build: function ($trigger, e)
{
// check if the menu-items have been saved in the previous call
if ($trigger.data("contextMenuItems") != null)
{
// get options from $trigger
var options = $trigger.data("contextMenuItems");
// clear $trigger.data("contextMenuItems"),
// so that menuitems are gotten next time user does a rightclick
// from the server again.
$trigger.data("contextMenuItems", null);
return options;
}
else
{
var options = {
callback: function (key)
{
alert(key);
},
items: {}
};
$.ajax({
url: "GetMenuItemsFromServer",
success: function (response, status, xhr)
{
// for each menu-item returned from the server
for (var i = 0; i < response.length; i++)
{
var ri = response[i];
// save the menu-item from the server in the options.items object
options.items[ri.id] = ri;
}
// save the options on the table-row;
$trigger.data("contextMenuItems", options);
// open the context-menu (reopen)
$trigger.contextMenu();
},
error: function (response, status, xhr)
{
if (xhr instanceof Error)
{
alert(xhr);
}
else
{
alert($($.parseHTML(response.responseText)).find("h2").text());
}
}
});
// This return false here is important
return false;
}
});
I have finally found a better solution after reading jquery context menu documentation, thoroughly..
C# CODE
public JsonResult GetHelpFiles()
{
List<Manuals> manuals = null;
var filesPath = Server.MapPath(#"\HelpManuals");
var standardPath = new DirectoryInfo(filesPath);
if (standardPath.GetFiles().Any())
{
manuals = standardPath.GetFiles().Select(x => new Manuals
{
Name = GetFileNamewithoutExtension(x.Name),
Path = x.Name
}).ToList();
}
return Json(manuals, JsonRequestBehavior.AllowGet);
}
HTML 5
<div id="dynamicMenu">
<menu id="html5menu" type="context" style="display: none"></menu>
</div>
JavaScript Code
$.post("/Home/GetHelpFiles", function (data) {
$.each(data, function (index, item) {
var e = '<command label="' + item.Name + '" id ="' + item.Path + '"></command>';
$("#html5menu").append(e);
});
$.contextMenu({
selector: '#helpIcon',
trigger: 'hover',
delay: 300,
items: $.contextMenu.fromMenu($('#html5menu'))
});
});
$("#dynamicMenu").on("click", "menu command", function () {
var link = $(this).attr('id');
window.open("/HelpManuals/" + link);
});
Here's my solution using deferred, important to know that this feature is supported for sub-menus only
$(function () {
$.contextMenu({
selector: '.SomeClass',
build: function ($trigger, e) {
var options = {
callback: function (key, options) {
// some call back
},
items: JSON.parse($trigger.attr('data-storage')) //this is initial static menu from HTML attribute you can use any static menu here
};
options.items['Reservations'] = {
name: $trigger.attr('data-reservations'),
icon: "checkmark",
items: loadItems($trigger) // this is AJAX loaded submenu
};
return options;
}
});
});
// Now this function loads submenu items in my case server responds with 'Reservations' object
var loadItems = function ($trigger) {
var dfd = jQuery.Deferred();
$.ajax({
type: "post",
url: "/ajax.php",
cache: false,
data: {
// request parameters are not importaint here use whatever you need to get data from your server
},
success: function (data) {
dfd.resolve(data.Reservations);
}
});
return dfd.promise();
};

Categories

Resources