In jsTree make last child selected after click on in with link open - jstree

I can't make my last child node selected after click on it.
Click open this node (href url) but on reloaded page it only select parent node.
Maybe there is also an option to prevent selection of parent node? I don't need it at all.
My tree have only 3 levels and only 3rd/last is used to open links.
jstree config:
$(() => {
$('#jstree').jstree({
core: {
multiple: false,
themes: {
dots: false,
},
data: {
url: '/jsonCats',
dataType: 'json',
},
},
state: {
key: 'navTree',
tree_state: false,
},
types: {
firstfloor: {
icon: false,
selected: false,
},
secfloor: {
icon: false,
},
hidden: {
icon: false,
},
element: {
icon: 'bi bi-file-earmark-text',
},
},
search: {
case_sensitive: false,
show_only_matches: true,
},
plugins: ['search', 'types', 'state', 'changed'],
});
$(document).ready(() => {
$('.search-input').keyup(function () {
const searchString = $(this).val();
$('#jstree').jstree('search', searchString);
});
});
$('#jstree').on('changed.jstree', (e, data) => {
if (data.event === undefined) {
return false;
}
if (data.node.a_attr.href !== '#' && data.event.bubbles) {
location.assign(data.node.a_attr.href);
}
});
});
example json last child:
{
"id": 1001,
"parent": "16",
"text": "Text",
"type": "element",
"li_attr": {
"elem": "element"
},
"a_attr": {
"href": "/KnowledgeBase/1001"
}
localStorage only select parent node:
{"state":{"core":{"open":["1","16"],"scroll":{"left":0,"top":0},"selected":["1"]}},"ttl":false,"sec":1673943166005}

Related

AG Grid - Add rows of data to Master Detail without using JSON file

I wanted to know how to add rows of data to the master detail table but not using an external json file and just write the row records inline via the JS
Anyone have any idea on how to go about this
https://www.ag-grid.com/documentation/javascript/master-detail/
var gridOptions = {
columnDefs: [
// group cell renderer needed for expand / collapse icons
{ field: 'name', cellRenderer: 'agGroupCellRenderer' },
{ field: 'account' },
{ field: 'calls' },
{ field: 'minutes', valueFormatter: "x.toLocaleString() + 'm'" },
],
defaultColDef: {
flex: 1,
},
masterDetail: true,
detailCellRendererParams: {
detailGridOptions: {
columnDefs: [
{ field: 'callId' },
{ field: 'direction', minWidth: 150 },
{ field: 'number' },
{ field: 'duration', valueFormatter: "x.toLocaleString() + 's'" },
{ field: 'switchCode', minWidth: 150 },
],
defaultColDef: {
flex: 1,
},
},
getDetailRowData: function (params) {
// simulate delayed supply of data to the detail pane
setTimeout(function () {
params.successCallback(params.data.callRecords);
}, 1000);
},
},
};
// setup the grid after the page has finished loading
document.addEventListener('DOMContentLoaded', function () {
var gridDiv = document.querySelector('#myGrid');
new agGrid.Grid(gridDiv, gridOptions);
// I dont want to use the external json file
agGrid
.simpleHttpRequest({
url: 'https://www.ag-grid.com/example-assets/master-detail-data.json',
})
.then(function (data) {
gridOptions.api.setRowData(data);
});
});
Set the rowData field on the gridOptions like so:
var gridOptions = {
columnDefs: [
// group cell renderer needed for expand / collapse icons
{ field: 'name', cellRenderer: 'agGroupCellRenderer' },
{ field: 'account' },
{ field: 'calls' },
{ field: 'minutes', valueFormatter: "x.toLocaleString() + 'm'" },
],
defaultColDef: {
flex: 1,
},
masterDetail: true,
detailCellRendererParams: {
detailGridOptions: {
columnDefs: [
{ field: 'callId' },
{ field: 'direction' },
{ field: 'number', minWidth: 150 },
{ field: 'duration', valueFormatter: "x.toLocaleString() + 's'" },
{ field: 'switchCode', minWidth: 150 },
],
defaultColDef: {
flex: 1,
},
},
getDetailRowData: function (params) {
params.successCallback(params.data.callRecords);
},
},
onFirstDataRendered: onFirstDataRendered,
rowData: myData
};
In this instance, myData would look like this:
var myData = [
{
name: 'Nora Thomas',
account: 177000,
calls: 24,
minutes: 25.65,
callRecords: [
{
name: 'susan',
callId: 555,
duration: 72,
switchCode: 'SW3',
direction: 'Out',
number: '(00) 88542069',
},
],
},
];
Demo.

How can I filter a TreeStore in ExtJS 6.2?

I need to filter a Tree (search for a node by comparing a text to a node attribute).
This fiddle is exactly what I need, but seems it is not working with ExtJS 6.2.
After a day trying to find out what is wrong in my code, I've decided to simply change the framework version in the fiddle from 4.2.1 (default) to 6.2.981 classic gray and can see the code is obsolete (removing all nodes or an arbitrary amount I can't see why).
Can someone verify that code and tell me how to port it to v6.2?
EDIT:
Almost there. This code is doing the job, but stops in deep level 2, not all the tree: JSFiddle
Ext.define('TreeFilter', {
extend: 'Ext.AbstractPlugin',
alias: 'plugin.treefilter',
collapseOnClear: true, // collapse all nodes when clearing/resetting the filter
allowParentFolders: false, // allow nodes not designated as 'leaf' (and their child items) to be matched by the filter
init: function(tree) {
var me = this;
me.tree = tree;
tree.filter = Ext.Function.bind(me.filter, me);
tree.clearFilter = Ext.Function.bind(me.clearFilter, me);
},
filter: function(value, property, re) {
var me = this,
tree = me.tree,
matches = [], // array of nodes matching the search criteria
root = tree.getRootNode(), // root node of the tree
property = property || 'text', // property is optional - will be set to the 'text' propert of the treeStore record by default
re = re || new RegExp(value, "ig"), // the regExp could be modified to allow for case-sensitive, starts with, etc.
visibleNodes = [], // array of nodes matching the search criteria + each parent non-leaf node up to root
viewNode;
if (Ext.isEmpty(value)) { // if the search field is empty
me.clearFilter();
return;
}
tree.expandAll(); // expand all nodes for the the following iterative routines
// iterate over all nodes in the tree in order to evalute them against the search criteria
root.cascadeBy(function(node) {
if (node.get(property).match(re)) { // if the node matches the search criteria and is a leaf (could be modified to searh non-leaf nodes)
matches.push(node) // add the node to the matches array
}
});
if (me.allowParentFolders === false) { // if me.allowParentFolders is false (default) then remove any non-leaf nodes from the regex match
Ext.each(matches, function(match) {
if (!match.isLeaf()) {
Ext.Array.remove(matches, match);
}
});
}
Ext.each(matches, function(item, i, arr) { // loop through all matching leaf nodes
root.cascadeBy(function(node) { // find each parent node containing the node from the matches array
if (node.contains(item) == true) {
visibleNodes.push(node) // if it's an ancestor of the evaluated node add it to the visibleNodes array
}
});
if (me.allowParentFolders === true && !item.isLeaf()) { // if me.allowParentFolders is true and the item is a non-leaf item
item.cascadeBy(function(node) { // iterate over its children and set them as visible
visibleNodes.push(node)
});
}
visibleNodes.push(item) // also add the evaluated node itself to the visibleNodes array
});
root.cascadeBy(function(node) { // finally loop to hide/show each node
viewNode = Ext.fly(tree.getView().getNode(node)); // get the dom element assocaited with each node
if (viewNode) { // the first one is undefined ? escape it with a conditional
viewNode.setVisibilityMode(Ext.Element.DISPLAY); // set the visibility mode of the dom node to display (vs offsets)
viewNode.setVisible(Ext.Array.contains(visibleNodes, node));
}
});
}
,
clearFilter: function() {
var me = this,
tree = this.tree,
root = tree.getRootNode();
if (me.collapseOnClear) {
tree.collapseAll();
} // collapse the tree nodes
root.cascadeBy(function(node) { // final loop to hide/show each node
viewNode = Ext.fly(tree.getView().getNode(node)); // get the dom element assocaited with each node
if (viewNode) { // the first one is undefined ? escape it with a conditional and show all nodes
viewNode.show();
}
});
}
});
// EXAMPLE
var store = Ext.create('Ext.data.TreeStore', {
root: {
expanded: true,
children: [{
text: "detention",
leaf: true
}, {
text: "homework",
expanded: false,
children: [{
text: "book report",
leaf: true
}, {
text: "algebra",
leaf: true
}]
}, {
text: "chores",
expanded: false,
children: [{
text: "do homework",
leaf: true
}, {
text: "walk dog",
leaf: true
}, {
text: "clean room",
leaf: true
}, {
text: "wash dishes",
leaf: true
}, {
text: "laundry",
leaf: true
}]
}, {
text: "buy lottery tickets",
leaf: true
}, {
text: "take over world",
leaf: true
}, {
text: "Sencha",
expanded: false,
children: [{
text: "Touch",
expanded: false,
children: [{
text: 'Viewport',
leaf: true
}, {
text: 'Panel',
leaf: true
}, {
text: 'Carousel',
leaf: true
}]
}, {
text: "ExtJS",
expanded: false,
children: [{
text: 'viewport.Viewport',
leaf: true
}, {
text: 'panel.Panel',
leaf: true
}, {
text: 'tree.Panel',
leaf: true
}]
}]
}]
}
});
Ext.create('Ext.tree.Panel', {
title: 'Simple Tree',
width: 200,
height: 150,
store: store,
rootVisible: false,
renderTo: Ext.getBody(),
plugins: [{
ptype: 'treefilter',
allowParentFolders: true
}],
dockedItems: [{
xtype: 'toolbar',
dock: 'top',
items: [{
xtype: 'trigger',
triggerCls: 'x-form-clear-trigger',
onTriggerClick: function() {
this.reset();
this.focus();
},
listeners: {
change: function(field, newVal) {
var tree = field.up('treepanel');
tree.filter(newVal);
},
buffer: 250
}
}]
}]
});
Without using filter plugin i have added some useful code in change event and it works for all depth.
"dockedItems": [{
xtype: 'toolbar',
dock: 'top',
items: [{
xtype: 'textfield', //As of Ext JS 5.0 trigger class has been deprecated. It is recommended to use {#link Ext.form.field.Text Text Field}.
triggerCls: 'x-form-clear-trigger',
width: 300,
onTriggerClick: function () {
this.reset();
this.focus();
},
listeners: {
change: function (field, newVal) {
var reportBuilderStore = field.up('panel').getStore();
if (!Ext.isEmpty(field.value)) {
reportBuilderStore.filterBy(function (rec) {
var childs = !Ext.isEmpty(rec.get('children')) ? rec.get('children').map(function (x) {
return x.text;
}) : [];
var matched = false;
for (var val of childs) {
if (val.toUpperCase().match((field.value).toUpperCase())) {
matched = true;
break;
}
}
if (!Ext.isEmpty(rec.get('text').toUpperCase().match((field.value).toUpperCase())) || rec.get('text').toUpperCase() == "ROOT" || matched)
return true;
});
} else {
reportBuilderStore.clearFilter();
}
},
buffer: 250
}
}
]
}
],

How to build a quantity plus / minus composite control?

I try to build an input field for quantities with plus and minus buttons. More like to understand how composite controls are working. I found this documentation.
I thought I could use the new control like this:
new ex.perimental.Input({
width: "14em",
editable: true,
input: new sap.m.Input({
width: "8em",
value: {
path: "model>Quantity",
type: new sap.ui.model.type.Integer({}, {
minimum:0
})
},
description: "{model>Unit}"
}),
})
The control code looks like this:
sap.ui.define([
"sap/ui/core/Control",
"sap/m/Button",
"sap/m/Input"
], function(Control, Button, Input) {
"use strict";
return Control.extend("ex.perimental.Input", {
metadata: {
properties: {
width: {
type: "string",
defaultValue: "14em",
},
editable: {
type: "boolean",
defaultValue: false,
},
},
aggregations: {
_increaseButton: {
type: "sap.m.Button",
multiple: false,
visibility: "hidden",
},
_decreaseButton: {
type: "sap.m.Button",
multiple: false,
visibility: "hidden",
},
input: { type: "sap.m.Input", multiple: false },
_hBox: { type: "sap.m.HBox", multiple: false, visibility: "hidden" },
},
events: {
increase: {},
decrease: {},
},
},
_onDecrease: function(oEvent) {
var oResourceBundle = this.getModel("i18n").getResourceBundle();
var oldValue = this.getAggregation("input").getValue();
var newValue = 0;
if (!isNaN(Number(oldValue))) {
newValue = Number(oldValue) - 1;
}
oInput.setValue(newValue);
this.fireEvent("decrease", {
oldValue: oldValue,
newValue: newValue,
});
},
_onIncrease: function(oEvent) {
var oResourceBundle = this.getModel("i18n").getResourceBundle();
var oldValue = this.getAggregation("input").getValue();
var newValue = 0;
if (!isNaN(Number(oldValue))) {
newValue = Number(oldValue) + 1;
}
oInput.setValue(newValue);
this.fireEvent("increase", {
oldValue: oldValue,
newValue: newValue,
});
},
init: function() {
this.setAggregation(
"_decreaseButton",
new Button({
icon: "sap-icon://sys-minus",
press: this._onDecrease.bind(this),
})
);
this.setAggregation(
"_increaseButton",
new Button({
icon: "sap-icon://sys-add",
press: this._onIncrease.bind(this),
})
);
this.setAggregation(
"_hBox",
new sap.m.HBox({
items: [
this.getAggregation("_decreaseButton"),
this.getAggregation("_increaseButton"),
],
})
);
},
setEditable: function(sValue) {
debugger;
// aggregations will be null now
// I assume because they are reused in the HBox control
// this.getAggregation("_increaseButton").setEditable(sValue);
// this.getAggregation("_decreaseButton").setEditable(sValue);
// this.getAggregation("input").setEditable(sValue);
},
setWidth: function(sValue) {
this.getAggregation("_hBox").setWidth(sValue);
},
setInput: function(oInput) {
this.setAggregation("input", oInput);
var oHBox = this.getAggregation("_hBox");
oHBox.insertItem(oInput, 1);
},
renderer: function(oRenderManager, oControl) {
oRenderManager.write("<div");
oRenderManager.writeControlData(oControl);
oRenderManager.addClass("myStyle");
oRenderManager.writeClasses();
oRenderManager.write(">");
oRenderManager.renderControl(oControl.getAggregation("_hBox"));
oRenderManager.write("</div>");
}
});
});
It will be rendered but the setEditable is not working.
The buttons (used inside the HBox control again) are not reachable via getAggregation. Also the input field (set from outside) can't be accessed.
Not sure how to do it right. Anyone an idea?
Edit2
This is the latest version but still not working.
I am asking me how to put the externally defined input control into the right place inside the inner Hbox control and be able to access this control in methods like setEditable?
sap.ui.define([
"sap/ui/core/Control",
"sap/m/Button",
"sap/m/Input"
], function(Control, Button, Input) {
"use strict";
return Control.extend("ex.perimental.Input", {
metadata: {
properties: {
width: {
type: "string",
defaultValue: "14em",
},
editable: {
type: "boolean",
defaultValue: false,
},
},
aggregations: {
_hBox: { type: "sap.m.HBox", multiple: false, visibility: "hidden" },
},
associations: {
input: { type: "sap.m.Input", multiple: false, singularName: "input" },
},
events: {
increase: {},
decrease: {},
},
},
_onDecrease: function(oEvent) {
var oResourceBundle = this.getModel("i18n").getResourceBundle();
var oldValue = this._input.getValue();
var newValue = 0;
if (!isNaN(Number(oldValue))) {
newValue = Number(oldValue) - 1;
}
this._input.setValue(newValue);
this.fireEvent("decrease", {
oldValue: oldValue,
newValue: newValue,
});
},
_onIncrease: function(oEvent) {
var oResourceBundle = this.getModel("i18n").getResourceBundle();
var oldValue = this._input.getValue();
var newValue = 0;
if (!isNaN(Number(oldValue))) {
newValue = Number(oldValue) + 1;
}
this._input.setValue(newValue);
this.fireEvent("increase", {
oldValue: oldValue,
newValue: newValue,
});
},
init: function() {
this._decreaseButton = new Button({
icon: "sap-icon://sys-minus",
press: this._onDecrease.bind(this),
});
this._increaseButton = new Button({
icon: "sap-icon://sys-add",
press: this._onIncrease.bind(this),
});
this.setAggregation(
"_hBox",
new sap.m.HBox({
items: [
this._decreaseButton,
this.getAssociation("input"),
this._increaseButton,
],
})
);
},
setEditable: function(sValue) {
var bEditable = false;
if (sValue === true) {
bEditable = true;
}
this._decreaseButton.setEnabled(bEditable);
this._increaseButton.setEnabled(bEditable);
// seems not always working
this._input.setEditable(bEditable);
},
setWidth: function(sValue) {
this.getAggregation("_hBox").setWidth(sValue);
},
setInput: function(oInput) {
this.setAssociation("input", oInput);
this._input = oInput;
var oHBox = this.getAggregation("_hBox");
oHBox.insertItem(oInput, 1);
},
renderer: function(oRenderManager, oControl) {
oRenderManager.write("<div");
oRenderManager.writeControlData(oControl);
oRenderManager.addClass("myStyle");
oRenderManager.writeClasses();
oRenderManager.write(">");
oRenderManager.renderControl(oControl.getAggregation("_hBox"));
oRenderManager.write("</div>");
}
});
});
I still have problems with the association handling (updated code) I guess handling the association should be done different? Sometimes the input field is still null.
a control can only be aggregated by one control at a time.
This is the difference between associations (control may be at multiple at the same time) and aggregations.
What you can do in your init is:
this._decreaseButton = new Button (...)
Basically you only need one aggregation for your HBox.
If your buttons are then aggregated by the HBox they will know the models of the parent and also be destroyed.
The only thing you need to check if your Root control is a Ancestor of the created controls (use myControl.getParent()).
best regards,
Tobias

Kendo UI Grid create data not making it to controller

I am having difficulty getting data to my controller using the MVVM method as shown in this Kendo Dojo example
I can see in my parameterMap function that the data is in the options.models but when I look for data at the controller, FAC_FuelReceipts is null. I can manually us an ajax call but I want this to work "Out of the Box" first. What am I doing wrong?
Grid:
$("#grid").kendoGrid({
height: 430,
columns: [
{ field: "FuelReceiptID" },
{ field: "ReceiptDate", title: "Receipt Date", width: 110, format: "{0:MM/dd/yyyy}" },
{ field: "FuelType", title: "Fuel Type", width: 110, editor: fuelTypeDropDownEditor },
{ field: "Qty", width: 110 },
{ field: "ReceivedBy", width: 110 }
],
editable: true,
pageable: true,
sortable: true,
filterable: true,
navigatable: true,
toolbar: ["create", "save", "cancel"],
dataSource: viewModel.receipts
});
ViewModel Code:
var viewModel;
$(function () { //On Ready
viewModel = kendo.observable({
receipts: new kendo.data.DataSource({
schema: {
model: {
id: "FuelReceiptID",
fields: {
FuelReceiptID: { editable: false, nullable: true },
ReceiptDate: { type: "date", validation: { required: true } },
FuelType: { type: "string", defaultValue:"Diesel" },
Qty: { type: "number", validation: { required: true } },
ReceivedBy: { type: "string" }
}
}
},
batch:true,
transport: {
read: {
cache:false,
url: "/Fuels/GetFuelReceipts",
dataType: "json"
},
create: {
url: "/Fuels/Create",
dataType: "json",
type: "POST"
},
parameterMap:function(options,operation){
if (operation == "read") {
return{
SiteID: SiteID,
ReceiptMonth: ReceiptMonth,
ReceiptYear: ReceiptYear
}
}
if (operation !== "read" && options.models) {
return { FAC_FuelReceipts: kendo.stringify(options.models) };
}
} //parameterMap fuction
} //transport
})
});
Controller Code:
[HttpPost]
public JsonResult Create(IEnumerable<FAC_FuelReceipts> FAC_FuelReceipts) //**empty here**
{
//Do something with data here
return Json(FAC_FuelReceipts, JsonRequestBehavior.AllowGet);
}
Use String instead of IEnumerable, As your parameter data is in string format.
Once you get data in string format deserialize into your object
[HttpPost]
public JsonResult Create(string FAC_FuelReceipts)
{
IList<FAC_FuelReceipts> Items= new JavaScriptSerializer().Deserialize<IList<FAC_FuelReceipts>>(FAC_FuelReceipts);
/**your code*/
return Json(FAC_FuelReceipts);
}

Datatables with Jeditable, how do I setup initially in edit mode? (or always be in edit mode)

Is it possible to have the Jeditable field start off in edit mode when using the datatables plugin?
Jeditable website says the solution (not using thru Datatables) :
You can trigger the used event when your document loads. For example:
$(function() {
$("#editable").trigger("click");
How do I access it in datatables, here's my code:
var oTable;
$(function () {
oTable = $('#calendarTable').dataTable({
"bPaginate": false,
"bSort": false,
"bFilter": false,
"bInfo": false,
"aoColumns": [
null,
null,
null,
null,
{ "bVisible": false },
{ "bVisible": false },
{ "bVisible": false },
{ "bVisible": false },
{ "bVisible": false },
{ "bVisible": false },
{ "bVisible": false },
{ "bVisible": false },
{ "bVisible": false },
{ "bVisible": true }
]
});
//$("#editable").trigger("click");
// oTable.fnGetNodes()).editable.trigger("click");
oTable.editable("disable");
var year;
var lobid;
var officeid;
year = $('#hv_year').val();
lobid = $('#hv_lob').val();
officeid = $('#hv_office').val();
var url;
url = "save.asp";
url = url + "?year=" + year;
url = url + "&lobid=" + lobid;
url = url + "&officeid=" + officeid;
/* Apply the jEditable handlers to the table */
$('td:eq(4)', oTable.fnGetNodes()).editable(url, {
"callback": function (sValue, y) {
var aPos = oTable.fnGetPosition(this);
oTable.fnUpdate(sValue, aPos[0], aPos[1]);
},
"submitdata": function (value, settings) {
return {
"row_id": this.parentNode.getAttribute('id'),
"column": oTable.fnGetPosition(this)[2]
};
},
tooltip: 'Click to Edit',
height: "40px",
type: 'textarea',
onblur: 'ignore',
cancel: 'Cancel',
submit: 'Save',
indicator: '<img src="images/loader.gif">'
});
I think you can just chain the click:
$('td:eq(4)', oTable.fnGetNodes()).editable(url, {
"callback": function (sValue, y) {
var aPos = oTable.fnGetPosition(this);
oTable.fnUpdate(sValue, aPos[0], aPos[1]);
},
"submitdata": function (value, settings) {
return {
"row_id": this.parentNode.getAttribute('id'),
"column": oTable.fnGetPosition(this)[2]
};
},
tooltip: 'Click to Edit',
height: "40px",
type: 'textarea',
onblur: 'ignore',
cancel: 'Cancel',
submit: 'Save',
indicator: '<img src="images/loader.gif">'
}).trigger("click");