Adding additional data to Kendo UI Context Menu items - javascript

I want to utilize the Kendo UI Context Menu in my app. I was expecting the standard behavior of having text displayed in the menu itself but a different value (an ID or key) being returned to the select event handler.
For instance, the menu displays a list of names but when I click on one of them, I get the ID associated with the name.
I've tried adding additional properties besides text to the array of items in the context menu but I don't see them on the event object in the handler.
I can't use the text to find the appropriate id that matches it since there could be entries with the same text but different IDs.
Any ideas?
Edit:
Currently I build the context menu like this:
open: (e) => {
let itemKeys = [1, 2, 3];
let menu = e.sender;
menu.remove(".context-menu-item");
menu.append(itemKeys.map((itemKey) => {
return {
text: "<div data-item-key='" + itemKey + "'>Test Text</div>",
cssClass: "context-menu-item",
encoded: false
};
}));
}
While this solution does satisfy my needs, it adds an extra element to the DOM which, while being insignificant, isn't perfect...

It's undocumented, but ContextMenu actually inherits from Menu. Therefore, all options of Menu are available. In particular, you can add an attr object to your data items, cf the example in the documentation.
To complete your example:
open: (e) => {
let itemKeys = [1, 2, 3];
let menu = e.sender;
menu.remove(".context-menu-item");
menu.append(itemKeys.map((itemKey) => {
return {
text: "Test Text",
cssClass: "context-menu-item",
// add whatever attribute
attr: {
'data-item-key': itemKey
}
};
}));
}
Then, in your select handler:
select: (e) => {
console.log($(e.item).data('item-key'));
}

Option 1)
You might add a data that will specify your Id.
I made this with mvc wrapper but it can be done with pure javascript as well.
#(Html.Kendo()
.ContextMenu()
.Name("contextMenuGridTicketTestiMessaggi")
.Target("#gridTicketTestiMessaggi")
.Filter("tr")
.Orientation(ContextMenuOrientation.Vertical)
.Items(items =>
{
items.Add().Text("Update").HtmlAttributes(new { data_toggle = "update" });
items.Add().Text("Save").HtmlAttributes(new { data_toggle = "save" });
items.Add().Text("Delete").HtmlAttributes(new { data_toggle = "delete" });
})
.Events(e => {
e.Select("contextMenuGridTicketTestiMessaggiSelect");
}));
var contextMenuGridTicketTestiMessaggiSelect = function(e) {
var action = $(e.item).data("toggle");
var that = this;
if (action === "update") {}
...
Option 2) You might define with every item(throught html content) a function to be called in each onClick event for the specific item.
items.Add().Encoded(false).Text("<span onclick='update()'>Update</span>");
items.Add().Encoded(false).Text("<span onclick='delete()'>Delete</span>");
...
Update
<div id="target">Target</div>
<ul id="context-menu"></div>
<script>
$("#context-menu").kendoContextMenu({
target: "#target",
open: function(e) {
let itemKeys = [1, 2, 3];
let menu = e.sender;
menu.remove(".context-menu-item");
menu.setOptions({
dataSource: itemKeys.map((itemKey) => {
return {
text: "<div data-item-key='" + itemKey + "' style='white-space: nowrap'>Test Text</div>",
cssClass: "context-menu-item",
encoded: false
};
})
});
},
select: function(e) {
console.log($($(e.item).find("div")[0]).data("item-key"))
}
});
</script>

Related

How to solved always looping select option value from ajax

I have a data, when I update the data (using modal), select option work correctly
When I close the modal, and I click the edit button again, something wrong with select option:
This is my edit modal ajax:
// Function for edit modal plan schedule
$('body').on('click', '.editPlanSchedule', function() {
var Item_id = $(this).data('id');
$.get("/quotation/getEditPlanSchedule" + '/' + Item_id, function(data) {
console.log(data['product_plan']);
$('.modal-title-edit').html("Edit Plan Schedule Item");
$('#saveBtn').val("Update");
$('#updatePlanSchedule').modal('show');
$('#id').val(data['data'].id);
$('#qno').val(data['data'].qno);
$('#b_amount').val(data['data'].b_amount);
// $('#product_plan_edit').val(data.product_plan);
data['product_plan'].forEach(function(item, index) {
$('#product_plan_edit').append($('<option>', {
id: item.id,
value: item.productplanID,
text: item.productplanID
}));
if(data['data'].product_plan == item.productplanID){
$('#'+item.id).attr('selected',true);
}
});
})
});
This is the method from controller:
public function getEditPlanSchedule($id)
{
$item['data'] = QuotationPlanSchedule::find($id);
$item['product_plan'] = ProductPlan::orderby('id', 'asc')->get();
return response()->json($item);
}
You have to clear all options before adding again:
$("#product_plan_edit").empty();
Either .empty the container or don't use append (better for the DOM update)
I am a little confused about your use of data['data'].product_plan to test the ID. In any case you can see the principle
$('#product_plan_edit').html(
data['product_plan'].map(item => `<option value="${item.productplanID}">${item.productplanID}</option>`).join('')
);
$('#product_plan_edit').val(data['data'].product_plan); // select item.productplanID === data['data'].product_plan

Obtain all items in a select2 dropdown list using javascript where data is loaded from ajax call

The example I would like to replicate is this: https://jsfiddle.net/bindrid/hpkqxto6/ . I need to load data from the server, form a grouping of them and when selecting one item from one group all other items inside this particular group would become disabled. The difference is that I must load data from the server using an ajax call inside select2, instead of listing the < option > elements inside the select element and form a grouping this way. I have trouble selecting all available items in the dropdown list during the javascript manipulation in order to disable items from the same group (line 29 in the above example fails due to there being no < option > elements in html, instead I think they are loaded later and javascript is not able to find them).
The html part looks as follows:
<select class="form-control attributoSelect2" name="attributiSelezionati" id="attributoSelect2" value="#Model.AttributiSelezionati"></select>
There are no < option > items inside < select > because the ajax call takes care of populating the list, like this:
$('.attributoSelect2').select2({
placeholder: "Cerca Attributo",
multiple: true,
allowClear: true,
minimumInputLength: 0,
ajax: {
dataType: 'json',
delay: 150,
url: "#Url.Action(MVC.Configurazione.Attributi.CercaAttributi())",
data: function (params) {
return {
search: params.term
};
},
processResults: function (data) {
return {
results: data.map(function (item) {
return {
id: item.Id,
text: item.Descrizione,
children: item.Children.map(function (itemC) {
return {
id: itemC.Id,
groupid: itemC.GroupId,
text: itemC.Descrizione,
};
})
};
})
};
}
}
});
Finally my javascript looks as follows:
$('.attributoSelect2').on("select2:selecting", function (evt, f, g) {
disableSel2Group(evt, this, true);
});
$('.attributoSelect2').on("select2:unselecting", function (evt) {
disableSel2Group(evt, this, false);
});
function disableSel2Group(evt, target, disabled) {
var selId = evt.params.args.data.id;
var group = evt.params.args.data.groupid;
var aaList = $(".attributoSelect2 option");
$.each(aaList, function (idx, item) {
var data = $(item).data("data");
var itemGroupId = data.groupid;
if (itemGroupId == group && data.id != selId) {
data.disabled = disabled;
}
})
}
The problem is that this line:
var aaList = $(".attributoSelect2 option");
does not load all the option elements because they are not loaded yet. They would be loaded later on using the ajax call. Does anyone have an idea of how to resolve this problem?

Show Message When DataSource Returns Empty for a Kendo ComboBox

While using the Kendo MVC Helper control & I am managing other aspects of a form using JavaScript (JS). I have a JS reference to the Kendo DropDown control which passes 'search text' to an API call. Obviously, the search text may return no results.
If no results are returned, I want to show a message (and/or do other stuff). However, I am having trouble finding the right event to use.
These are the only events that 'seem' useful for this purpose:
CHANGE: Fires only when data exists and they change the drop-downs choice
DATABOUND: Fires only if API data exists
QUESTION:
Using JavaScript, how do I show a message when the API call returns an empty dataset?
I will go ahead and include some code...although you don't need it to answer this particular question.
SAMPLE MARKUP:
<div class="input-group input-group-sm" style="width: 600px">
<span class="input-group-addon input-sm text-align">Search</span>
#(Html.Kendo().ComboBox()
.Name("ddlMeter")
.Filter("contains")
.Placeholder("Type Meter Name or Number...")
.DataTextField("Text")
.DataValueField("Value")
.AutoBind(false)
.MinLength(4)
.DataSource(source => source.Read(read => read.Action("findmeter", "rtf", new { area = "documents" }))
.ServerFiltering(true))
.HtmlAttributes(new { style = "width:100%;" }))
</div>
SAMPLE JAVASCRIPT:
This is just a sample controller...
function PageController(options) {
var that = this,
empty = {},
dictionary = {
elements: {
form: null,
ddlMeter: null,
},
instances: {
ddlMeter: null
},
selectors: {
form: 'form',
ddlMeter: '#ddlMeter'
}
};
var initialize = function (options) {
that.settings = $.extend(empty, $.isPlainObject(options) ? options : empty);
// Elements
dictionary.elements.form = $(dictionary.selectors.form);
dictionary.elements.ddlMeter = $(dictionary.selectors.ddlMeter, dictionary.elements.form);
// Kendo Objects
dictionary.instances.ddlMeter = dictionary.elements.ddlMeter.data('kendoComboBox');
// Events
dictionary.instances.ddlMeter.bind('change', that.on.change.ddlMeter);
dictionary.instances.ddlMeter.bind('dataBind', that.on.databind.ddlMeter);
};
this.on = {
change: {
ddlMeter: function (e) {
// This only fires if they CHANGE the controls choice on an existing dataset
},
databind: {
ddlMeter: function (e) {
// This wont do it if the set is empty
}
},
};
initialize(options);
};
I think you need to use the RequestEnd event instead. If I remember correctly, you can get a handle to the datasource inside the event using the following syntax:
this.dataSource.data();
or
.DataSource(source => source.Read(read => read.Action("findmeter", "rtf", new { area = "documents" }))
.Events(e=>e.RequestEnd("requestEnd"))
.ServerFiltering(true))

Rewrite the code in a loop

I'm just learning JS and jQuery, so I can not reduce the normal code is shown below:
var menuBtn = '#menu',
classMenuOpen = 'side_menu_open',
aboutBtn = '#about',
classAboutOpen = 'side_about_open',
dateBtn = '#date',
classDateOpen = 'side_date_open',
closeBtn = '.header__menu a, .close';
// Menu Side
$(menuBtn).on('click', function() {
$('html').toggleClass(classMenuOpen);
});
$(closeBtn).not(menuBtn).on('click', function() {
$('html').removeClass(classMenuOpen);
});
// About Side
$(aboutBtn).on('click', function() {
$('html').toggleClass(classAboutOpen);
});
$(closeBtn).not(aboutBtn).on('click', function() {
$('html').removeClass(classAboutOpen);
});
// Date Side
$(dateBtn).on('click', function() {
$('html').toggleClass(classDateOpen);
});
$(closeBtn).not(dateBtn).on('click', function() {
$('html').removeClass(classDateOpen);
});
I would like to write a loop (example below) , but knowledge is not enough. I hope someone can help, thanks in advance ;)
['menu', 'about', 'date'].forEach((selector) => {
$('.' + selector + ' .scrollbar-inner').scrollbar({
onScroll: function(y, x){
$('.' + selector + ' .scrollbar-inner').toggleClass('scroll-shadow', y.scroll >= 5);
}
});
});
maybe something like this:
// wrap in IIFE for scope containment
(function($) {
// Setup button keys
const buttons = ['menu', 'about', 'date'];
// click handler
const createClickHandler = value => {
// set loop variables
let selector = `#${value}`
, className = `side_${value}_open`;
// create event triggers
$(selector).on('click', e => $('html').toggleClass(className));
$('.header__menu a, .close').not(selector).on('click', e => $('html').removeClass(className))
};
// iterate keys and apply handler method
buttons.forEach(createClickHandler);
})(jQuery);
Here is the loop you are looking for!
In the forEach() loop you are looping through the array of strings, component holds the string element (so here 'menu', 'about', etc...). Then inside the loop's body set two variables:
selector is the selector string
classOpen is the class name of an element you have associated with the component
Then you basically write the same code using only those two variables instead of writing the code three times with set strings.
let closeBtn = '.header__menu a, .close'
['menu', 'about', 'date'].forEach(function(component) {
let selector = '#' + component;
let classOpen = '.side_' + component + '_open';
$(selector).on('click', function() {
$('html').toggleClass(classOpen);
});
$(closeBtn).not(selector).on('click', function() {
$('html').removeClass(selector);
});
});

Location.hash returns empty string

I am currently using jQuery, Twitter Bootstrap and CanJS for my web application. I'm trying to implement routing with CanJS. I'm using Bootstrap's tab, and when I click on a tab, it was supposed to bring #tabSearch, #tabUser or #tabPermissions, but the hash is returned with an empty string.
What am I doing wrong?
I´m using this to change the tabs:
TabControl = can.Control.extend({}, {
init: function (element, options) {
element.find('a[href="' + options.defaultTab + '"]').tab('show');
this.userSearch = null;
this.userForm = null;
}
, 'a[data-toggle="tab"] show': function (e) {
this.options.tabChangeCallback(e);
}
});
UserTab = new TabControl('#tabctrlUser', {
defaultTab: '#tabSearch',
tabChangeCallback: function (e) {
if (e.context.hash == '#tabSearch') {
if (!this.userSearch) {
this.userSearch = new FormUserQuery('#tabSearch', {});
}
} else if (e.context.hash == '#tabUser') {
if (!this.userForm) {
this.userForm = new FormUser('#tabUser', {});
}
this.userForm.renderView(currentUser);
} else if (e.context.hash == '#tabPermissions') {
new FormPermissions('#tabPermissions', {});
}
}
});
Here is the HTML part:
<ul id="tabctrlUser" class="nav nav-tabs">
<li>Pesquisar</li>
<li>Cadastrar/Alterar</li>
<li>Acessos</li>
</ul>
<div class="tab-content">
<div class="tab-pane" id="tabSearch"></div>
<div class="tab-pane" id="tabUser"></div>
<div class="tab-pane" id="tabPermissions"></div>
</div>
You need to use can.route or can.Control.Route, the way you do it is complicated take a look at this question
Also there's 2 good articles about routing
look at this gist
http://jsfiddle.net/Z9Cv5/2/light/
var HistoryTabs = can.Control({
init: function( el ) {
// hide all tabs
var tab = this.tab;
this.element.children( 'li' ).each(function() {
tab( $( this ) ).hide();
});
// activate the first tab
var active = can.route.attr(this.options.attr);
this.activate(active);
},
"{can.route} {attr}" : function(route, ev, newVal, oldVal){
this.activate(newVal, oldVal)
},
// helper function finds the tab for a given li
tab: function( li ) {
return $( li.find( 'a' ).attr( 'href' ) );
},
// helper function finds li for a given id
button : function(id){
// if nothing is active, activate the first
return id ? this.element.find("a[href=#"+id+"]").parent() :
this.element.children( 'li:first' );
},
// activates
activate: function( active, oldActive ){
// deactivate the old active
var oldButton = this.button(oldActive).removeClass('active');
this.tab(oldButton).hide();
// activate new
var newButton = this.button(active).addClass('active');
this.tab(newButton).show();
},
"li click" : function(el, ev){
// prevent the default setting
ev.preventDefault();
// update the route data
can.route.attr(this.options.attr, this.tab(el)[0].id)
}
});
// configure routes
can.route(":component",{
component: "model",
person: "mihael"
});
can.route(":component/:person",{
component: "model",
person: "mihael"
});
// adds the controller to the element
new HistoryTabs( '#components',{attr: 'component'});
new HistoryTabs( '#people',{attr: 'person'});
I am not familiar with CanJS, but this could have something to do that anchor click event happens before navigation, and location.hash is set after. E.g. if (as a test) you define your link as
Pesquisar
And in plain vanilla JS try
function tabChangeCallback() {
alert(location.hash)
}
It will display blank.
But if you try something like
function tabChangeCallback() {
setTimeout(function() {
alert(location.hash)
}, 100)
}
It will display the hash. So in your case either set your code to be executed on timeout after main event or (and I think it's a better option) instead of hash you should read href attribute of the anchor element that caused the event.
Update The plain JS example below shows how to get the hash value without timeout:
function tabChangeCallback(e) {
var elem = e ? e.target : event.srcElement
alert(elem.getAttribute("href"))
}

Categories