In input of function is an object who has this structure:
{
tag: 'a', //type of html object
content: "blabal", //inner content
attr: {
href: "vk.com",
id: 'someId'
},
events: {
click: 'alert(this.href)',
focus: 'this.className="active"'
},
style: {
width:"100px"
}
}
It describes an HTML element. It has to return an HTML element with specified properties. How to parse it? I have something like this:
elemen={
tag:'a',
content:"blabal",
attr:{
href:"vk.com",
id:'someId'
},
events:{
click:'alert(this.href)',
focus:'this.className="active"'
},
style:{
width:"100px"
}
};
console.log(elemen.tag);
var node = document.createElement(elemen.tag);
node.innerHTML= elemen.content;
for(var prop in elemen.events){
var fun =new Function(elemen.events[prop]);
console.log(fun);
node.bind(prop, fun);
// divv.bind(prop, fun);
}
Use addEventListener to register events on Element and .bind(thisArg) to have specified argument as this-context
var elemen = {
tag: 'a',
content: "blabal",
attr: {
href: "vk.com",
id: 'someId'
},
events: {
click: 'alert(this.href)',
focus: 'this.className="active"'
}
};
var node = document.createElement(elemen.tag);
node.innerHTML = elemen.content;
for (var attr in elemen.attr) {
node.setAttribute(attr, elemen.attr[attr]);
}
for (var prop in elemen.events) {
node.addEventListener(prop, new Function(elemen.events[prop]).bind(node));
}
document.body.appendChild(node);
.active {
color: red;
}
Using only javascript
var _createElem = document.createElement(""+_elem.tag+"");
_createElem.innerHTML = _elem.content;
//set attributes
for(var keys in _elem.attr){
_createElem.setAttribute(''+keys+'',_elem.attr[keys])
}
//set style
for(var keys in _elem.style){
_createElem.setAttribute(''+keys+'',_elem.style[keys])
}
//set events
for(var keys in _elem.events){
_createElem.setAttribute('on'+keys,_elem.events[keys])
}
document.getElementById("demoDiv").appendChild(_createElem)
Note: The elem has got both onclick & href , you may need to return true/false as per your requirement
Use the following function:
const objectToHTML = function(obj) {
const element = document.createElement(obj.tag)
element.innerHTML = obj.content
for (const name in obj.attr) {
const value = obj.attr[name]
element.setAttribute(name, value)
}
for (const name in obj.events) {
const listener = new Function(obj.events[name]).bind(element)
element.addEventListener(name, listener)
}
for (const property in obj.style) {
const value = obj.style[property]
element.style[property] = value
}
return element
}
To create an event listener from string, you have to convert it to function using the Function constructor, bind context to it using Function.prototype.bind() (otherwise the function would be executed with window as context), and finally, use element.addEventListener().
The rest is rather obvious.
You can use this function like that:
const element = objectToHTML({
tag: 'a',
content: "blabal",
attr: {
href: "vk.com",
id: 'someId'
},
events: {
click: 'alert(this.href)',
focus: 'this.className="active"'
},
style: {
width: "100px"
}
})
document.body.appendChild(element)
See demo:
const objectToHTML = function(obj) {
const element = document.createElement(obj.tag)
element.innerHTML = obj.content
for (const name in obj.attr) {
const value = obj.attr[name]
element.setAttribute(name, value)
}
for (const name in obj.events) {
const listener = new Function(obj.events[name]).bind(element)
element.addEventListener(name, listener)
}
for (const property in obj.style) {
const value = obj.style[property]
element.style[property] = value
}
return element
}
const element = objectToHTML({
tag: 'a',
content: "blabal",
attr: {
href: "vk.com",
id: 'someId'
},
events: {
click: 'alert(this.href)',
focus: 'this.className="active"'
},
style: {
width: "100px"
}
})
document.body.appendChild(element)
I recommend this form, is more adaptable.
window.onload = function() {
function init_() {
function action__(type, element, convert, a) {
if (type == "function") {
if (convert.create[a] != null) {
try {
var new_ = convert.create[a](element[a]);
} catch (rrr) {
rrr = (rrr.toString());
if (rrr.indexOf('2 arguments') != -1 && Object.keys(element[a]).length != 0) {
for (v in element[a]) {
convert.create[v] = element[a][v];
var new_ = convert.create;
}
};
}
convert['create'] = new_;
}
};
if (type == "object") {
for (f in element[a]) {
convert.create[a][f] = element[a][f];
}
}
if (type == "string" && a != "events") {
convert.create[a] = (element[a]);
} else if (type == "string" && a == "events") {
for (ev in element[a]) {
var type = convert.detectType(convert.create, ev);
if (type == "function") {
convert.create.addEventListener(ev, new Function(element[a][ev]));
}
};
};
return convert.create;
};
function createElement(elements) {
var finished = [];
if (typeof elements.tagName == "undefined" && !elements.length) {
elements = [elements];
}
for (r = 0; r < elements.length; r++) {
var element = elements[r];
if (element) {
var convert = {
create: document,
detectType: function(element, attribute) {
var type = "string";
if (typeof element[attribute] != "undefined") {
type = typeof element[attribute];
};
return type;
},
add: function(new_) {
if (new_ && new_ != "undefined") {
this.create = new_;
}
}
};
for (a in element) {
var type = convert.detectType(convert.create, a);
var returner = action__(type, element, convert, a);
convert.add(returner);
}
finished.push(convert.create);
};
}
return (finished);
};
var minifi_function = init_.toString();
var elements = [{
createElement: 'a',
innerHTML: "blabal",
setAttribute: {
href: "vk.com",
id: 'someId',
style: "height:200px;"
},
events: {
click: 'alert(this.href)',
focus: 'this.className="active"'
},
style: {
width: "100px"
}
}, {
createElement: 'div',
innerHTML: "see my content",
setAttribute: {
['data-link']: "vk.com",
id: 'someId2',
style: "height:200px;background:red;"
},
events: {
click: 'prompt("Copy",' + minifi_function + ')',
focus: 'this.className="activediv"'
},
style: {
width: "100px"
}
}];
var elementos = createElement(elements);
console.log(elementos);
for (p = 0; p < elementos.length; p++) {
document.body.appendChild(elementos[p]);
}
}
init_();
}
Related
I'm creating a map based on this example:
https://labs.mapbox.com/education/impact-tools/finder-with-filters/
In their example, they have two dropdown filters and one checkbox filter. I would like to have three checkbox filters. I created three checkbox filters, and on their own, they seem to work well. The issue is that the filters seem to override each other in the order clicked. In their example, the filters seem to be working together, so I can't figure out why it's not working anymore when I changed the filter type.
Here's the code for my project:
https://codepen.io/flyinginsect2/pen/eYdyqxZ
Here are snippets of the code relevant to filtering:
const config = {
style: "mapbox://styles/mapbox/light-v10",
accessToken: "pk.eyJ1IjoibGF1cmFqZWFudGhvcm5lIiwiYSI6ImNraXl5M29oMDEyMjgzM3BhNTh1MGc1NjkifQ.g4IAFIrXPpl3ricw3f_Onw",
CSV: "https://docs.google.com/spreadsheets/d/106xm254us29hAUEtR7mTo0hwbDJv8dhyQs9rxY601Oc/gviz/tq?tqx=out:csv&sheet=Attributes",
center: [-104.339, 46.869],
zoom: 2,
title: "ENVIROlocity Mapper",
description: "Environmental Networking, Volunteering, Internship, and R.... Opportunities",
sideBarInfo: ["Org_name", "CityState"],
popupInfo: ["Org_name"],
filters: [
{
type: "checkbox",
title: "Sector: ",
columnHeader: "Sector",
listItems: ["Local Government", "Nonprofit"]
},
{
type: "checkbox",
title: "Industry: ",
columnHeader: "Industry_type",
listItems: ["Conservation", "Policy"]
},
{
type: "checkbox",
title: "Internships: ",
columnHeader: "internships_YN",
listItems: ["Yes"]
}
]
};
const selectFilters = [];
const checkboxFilters = [];
function createFilterObject(filterSettings) {
filterSettings.forEach(function (filter) {
if (filter.type === 'checkbox') {
columnHeader = filter.columnHeader;
listItems = filter.listItems;
const keyValues = {};
Object.assign(keyValues, { header: columnHeader, value: listItems });
checkboxFilters.push(keyValues);
}
if (filter.type === 'dropdown') {
columnHeader = filter.columnHeader;
listItems = filter.listItems;
const keyValues = {};
Object.assign(keyValues, { header: columnHeader, value: listItems });
selectFilters.push(keyValues);
}
});
}
function applyFilters() {
const filterForm = document.getElementById('filters');
filterForm.addEventListener('change', function () {
const filterOptionHTML = this.getElementsByClassName('filter-option');
const filterOption = [].slice.call(filterOptionHTML);
const geojSelectFilters = [];
const geojCheckboxFilters = [];
filteredFeatures = [];
filteredGeojson.features = [];
filterOption.forEach(function (filter) {
if (filter.type === 'checkbox' && filter.checked) {
checkboxFilters.forEach(function (objs) {
Object.entries(objs).forEach(function ([key, value]) {
if (value.includes(filter.value)) {
const geojFilter = [objs.header, filter.value];
geojCheckboxFilters.push(geojFilter);
}
});
});
}
if (filter.type === 'select-one' && filter.value) {
selectFilters.forEach(function (objs) {
Object.entries(objs).forEach(function ([key, value]) {
if (value.includes(filter.value)) {
const geojFilter = [objs.header, filter.value];
geojSelectFilters.push(geojFilter);
}
});
});
}
});
if (geojCheckboxFilters.length === 0 && geojSelectFilters.length === 0) {
geojsonData.features.forEach(function (feature) {
filteredGeojson.features.push(feature);
});
} else if (geojCheckboxFilters.length > 0) {
geojCheckboxFilters.forEach(function (filter) {
geojsonData.features.forEach(function (feature) {
if (feature.properties[filter[0]].includes(filter[1])) {
if (
filteredGeojson.features.filter(
(f) => f.properties.id === feature.properties.id
).length === 0
) {
filteredGeojson.features.push(feature);
}
}
});
});
if (geojSelectFilters.length > 0) {
const removeIds = [];
filteredGeojson.features.forEach(function (feature) {
let selected = true;
geojSelectFilters.forEach(function (filter) {
if (
feature.properties[filter[0]].indexOf(filter[1]) < 0 &&
selected === true
) {
selected = false;
removeIds.push(feature.properties.id);
} else if (selected === false) {
removeIds.push(feature.properties.id);
}
});
});
removeIds.forEach(function (id) {
const idx = filteredGeojson.features.findIndex(
(f) => f.properties.id === id
);
filteredGeojson.features.splice(idx, 1);
});
}
} else {
geojsonData.features.forEach(function (feature) {
let selected = true;
geojSelectFilters.forEach(function (filter) {
if (
!feature.properties[filter[0]].includes(filter[1]) &&
selected === true
) {
selected = false;
}
});
if (
selected === true &&
filteredGeojson.features.filter(
(f) => f.properties.id === feature.properties.id
).length === 0
) {
filteredGeojson.features.push(feature);
}
});
}
map.getSource('locationData').setData(filteredGeojson);
buildLocationList(filteredGeojson);
});
}
function filters(filterSettings) {
filterSettings.forEach(function (filter) {
if (filter.type === 'checkbox') {
buildCheckbox(filter.title, filter.listItems);
} else if (filter.type === 'dropdown') {
buildDropDownList(filter.title, filter.listItems);
}
});
}
function removeFilters() {
let input = document.getElementsByTagName('input');
let select = document.getElementsByTagName('select');
let selectOption = [].slice.call(select);
let checkboxOption = [].slice.call(input);
filteredGeojson.features = [];
checkboxOption.forEach(function (checkbox) {
if (checkbox.type == 'checkbox' && checkbox.checked == true) {
checkbox.checked = false;
}
});
selectOption.forEach(function (option) {
option.selectedIndex = 0;
});
map.getSource('locationData').setData(geojsonData);
buildLocationList(geojsonData);
}
function removeFiltersButton() {
const removeFilter = document.getElementById('removeFilters');
removeFilter.addEventListener('click', function () {
removeFilters();
});
}
createFilterObject(config.filters);
applyFilters();
filters(config.filters);
removeFiltersButton();
I read this Mapbox documentation on combining filters, but I can't figure out how to work it in.
https://docs.mapbox.com/mapbox-gl-js/style-spec/other/#other-filter
I know there are many other Stack Exchange posts out there that address filtering on multiple criteria, but I can't find one that seems to address this specific issue.
The issue is in the space in value for "Local Government"
If you look at the generated HTML you will see a space in the id, which is not valid HTML
<input class="px12 filter-option" type="checkbox" id="Local Government" value="Local Government">
Just remove the whitespaces when building the HTML id attribute
input.setAttribute('id', listItems[i].replace(/\s/g,''));
I tried to create a simple todo app with Cyclejs/xstream. The app works fine. Only thing I not able to understand is after adding each todo the input should clear, which is not happening.
todo.js
import {
div, span, p, input, ul, li, button, body
}
from '#cycle/dom'
import xs from 'xstream'
import Utils from './utils'
export function Todo(sources) {
const sinks = {
DOM: view(model(intent(sources)))
}
return sinks
}
function intent(sources) {
return {
addTodo$: sources.DOM.select('input[type=text]').events('keydown').filter((ev) => {
return ev.which == 13 && ev.target.value.trim().length > 0;
}).map((ev) => {
return ev.target.value;
}),
deleteTodo$: sources.DOM.select('.delete').events('click').map((ev) => {
return Number(ev.target.getAttribute('data-id'));
}).filter((id) => {
return !isNaN(id);
}),
completeTodo$: sources.DOM.select('.complete').events('click').map((ev) => {
return Number(ev.target.getAttribute('data-id'));
}).filter((id) => {
return !isNaN(id);
})
};
}
function model(action$) {
let deleteTodo$ = action$.deleteTodo$.map((id) => {
return (holder) => {
let index = Utils.findIndex(holder.currentTodos, 'id', id);
if (index > -1) holder.currentTodos.splice(index, 1);
return {
currentTodos: holder.currentTodos,
value: ''
};
};
});
let completeTodo$ = action$.completeTodo$.map((id) => {
return (holder) => {
let index = Utils.findIndex(holder.currentTodos, 'id', id);
if (index > -1) holder.currentTodos[index].completed = !holder.currentTodos[index].completed;
return {
currentTodos: holder.currentTodos,
value: ''
};
};
});
let addTodo$ = action$.addTodo$.map((item) => {
return (holder) => {
let todo = {
value: item,
id: holder.currentTodos.length + 1,
completed: false
};
holder.currentTodos.push(todo);
return {
currentTodos: holder.currentTodos,
value: ''
};
};
});
return xs.merge(deleteTodo$, addTodo$, completeTodo$)
.fold((holder, modifier) => {
return modifier(holder);
}, {
currentTodos: [],
value: ''
});
}
function view(state$) {
return state$.map((state) => {
console.log(state);
return div({
attrs: {
class: 'todo'
}
}, [
input({
props: {
type: 'text',
value: state.value
}
}),
ul({
attrs: {
class: 'text'
}
}, state.currentTodos.map((todo) => {
return li({
attrs: {
class: `${todo.completed ? 'completed' : 'open'}`
}
}, [
span(todo.value),
button({
attrs: {
class: 'delete',
'data-id': todo.id
}
}, 'XXXXX'),
button({
attrs: {
class: 'complete',
'data-id': todo.id
}
}, 'CCCCC')
]);
}))
]);
});
}
utils.js
var Utils = {
filter: function(array, fn) {
var results = [];
var item;
for (var i = 0, len = array.length; i < len; i++) {
item = array[i];
if (fn(item)) results.push(item);
}
return results;
},
findItem: function(array, fn) {
for (var i = 0, len = array.length; i < len; i++) {
var item = array[i];
if (fn(item)) return item;
}
return null;
},
findIndex: function(array, prop, value) {
var pointerId = -1;
var index = -1;
var top = array.length;
var bottom = 0;
for (var i = array.length - 1; i >= 0; i--) {
index = bottom + (top - bottom >> 1);
pointerId = array[index][prop];
if (pointerId === value) {
return index;
} else if (pointerId < value) {
bottom = index;
} else if (pointerId > value) {
top = index;
}
}
return -1;
}
};
export default Utils;
You need to put hook inside input element. It will work as expected. If you want you can send another default value (in this case it is empty string).
input({
props: {
type: 'text'
},
hook: {
update: (o, n) => n.elm.value = ''
}
}),
#cycle/dom driver should be > 11.0.0 which works with Snabbdom. But if you use earlier version you need:
var Hook = function(){
this.arguments=arguments;
}
Hook.prototype.hook = function(node) {
node.value=this.arguments[0];
}
input({ attributes: {type: 'text'},
'my-hook':new Hook('')
})
In SAPUI5 I have a Model ("sModel") filled with metadata.
In this model I have a property "/aSelectedNumbers".
I also have a panel, of which I want to change the visibility depending on the content of the "/aSelectedNumbers" property.
update
first controller:
var oModelMeta = cv.model.recycleModel("oModelZAPRegistratieMeta", that);
//the cv.model.recycleModel function sets the model to the component
//if that hasn't been done so already, and returns that model.
//All of my views are added to a sap.m.App, which is returned in the
//first view of this component.
var aSelectedRegistratieType = [];
var aSelectedDagdelen = ["O", "M"];
oModelMeta.setProperty("/aSelectedRegistratieType", aSelectedRegistratieType);
oModelMeta.setProperty("/aSelectedDagdelen", aSelectedDagdelen);
First Panel (Which has checkboxes controlling the array in question):
sap.ui.jsfragment("fragments.data.ZAPRegistratie.Filters.RegistratieTypeFilter", {
createContent: function(oInitData) {
var oController = oInitData.oController;
var fnCallback = oInitData.fnCallback;
var oModel = cv.model.recycleModel("oModelZAPRegistratieMeta", oController);
var oPanel = new sap.m.Panel( {
content: new sap.m.Label( {
text: "Registratietype",
width: "120px"
})
});
function addCheckBox(sName, sId) {
var oCheckBox = new sap.m.CheckBox( {
text: sName,
selected: {
path: "oModelZAPRegistratieMeta>/aSelectedRegistratieType",
formatter: function(oFC) {
if (!oFC) { return false; }
console.log(oFC);
return oFC.indexOf(sId) !== -1;
}
},
select: function(oEvent) {
var aSelectedRegistratieType = oModel.getProperty("/aSelectedRegistratieType");
var iIndex = aSelectedRegistratieType.indexOf(sId);
if (oEvent.getParameters().selected) {
if (iIndex === -1) {
aSelectedRegistratieType.push(sId);
oModel.setProperty("/aSelectedRegistratieType", aSelectedRegistratieType);
}
} else {
if (iIndex !== -1) {
aSelectedRegistratieType.splice(iIndex, 1);
oModel.setProperty("/aSelectedRegistratieType", aSelectedRegistratieType);
}
}
// arrays update niet live aan properties
oModel.updateBindings(true); //******** <<===== SEE HERE
if (fnCallback) {
fnCallback(oController);
}
},
width: "120px",
enabled: {
path: "oModelZAPRegistratieMeta>/bChanged",
formatter: function(oFC) {
return oFC !== true;
}
}
});
oPanel.addContent(oCheckBox);
}
addCheckBox("Presentielijst (dag)", "1");
addCheckBox("Presentielijst (dagdelen)", "2");
addCheckBox("Uren (dagdelen)", "3");
addCheckBox("Tijd (dagdelen)", "4");
return oPanel;
}
});
Here is the panel of which the visibility is referred to in the question. Note that it DOES work after oModel.updateBindings(true) (see comment in code above), but otherwise it does not update accordingly.
sap.ui.jsfragment("fragments.data.ZAPRegistratie.Filters.DagdeelFilter", {
createContent: function(oInitData) {
var oController = oInitData.oController;
var fnCallback = oInitData.fnCallback;
var oModel = cv.model.recycleModel("oModelZAPRegistratieMeta", oController);
var oPanel = new sap.m.Panel( {
content: new sap.m.Label( {
text: "Dagdeel",
width: "120px"
}),
visible: {
path: "oModelZAPRegistratieMeta>/aSelectedRegistratieType",
formatter: function(oFC) {
console.log("visibility");
console.log(oFC);
if (!oFC) { return true; }
if (oFC.length === 0) { return true; }
return oFC.indexOf("2") !== -1;
}
}
});
console.log(oPanel);
function addCheckBox(sName, sId) {
var oCheckBox = new sap.m.CheckBox( {
text: sName,
selected: {
path: "oModelZAPRegistratieMeta>/aSelectedDagdelen",
formatter: function(oFC) {
if (!oFC) { return false; }
console.log(oFC);
return oFC.indexOf(sId) !== -1;
}
},
select: function(oEvent) {
var aSelectedDagdelen = oModel.getProperty("/aSelectedDagdelen");
var iIndex = aSelectedDagdelen.indexOf(sId);
if (oEvent.getParameters().selected) {
if (iIndex === -1) {
aSelectedDagdelen.push(sId);
oModel.setProperty("/aSelectedDagdelen", aSelectedDagdelen);
}
} else {
if (iIndex !== -1) {
aSelectedDagdelen.splice(iIndex, 1);
oModel.setProperty("/aSelectedDagdelen", aSelectedDagdelen);
}
}
if (fnCallback) {
fnCallback(oController);
}
},
enabled: {
path: "oModelZAPRegistratieMeta>/bChanged",
formatter: function(oFC) {
return oFC !== true;
}
},
width: "120px"
});
oPanel.addContent(oCheckBox);
}
addCheckBox("Ochtend", "O", true);
addCheckBox("Middag", "M", true);
addCheckBox("Avond", "A");
addCheckBox("Nacht", "N");
return oPanel;
}
});
The reason that the model doesn´t trigger a change event is that the reference to the Array does not change.
A possible way to change the value is to create a new Array everytime you read it from the model:
var newArray = oModel.getProperty("/aSelectedNumbers").slice();
// do your changes to the array
// ...
oModel.setProperty("/aSelectedNumbers", newArray);
This JSBin illustrates the issue.
On the slickgrid view I have to add the button to do file upload. I have added that button using a custom formater. But I couldnt add the onchange event handler to capture the file. Could anyone have any idea of how we can add the onchange listener to the cell.?
In the formatter display just the name of the file or something like that. To achieve your goal you need to write custom editor which implements isValueChanged method. You can modify this or any other editor taken from:
https://github.com/mleibman/SlickGrid/blob/master/slick.editors.js
function myCustomEditor(args) {
var $input;
var defaultValue;
var scope = this;
this.init = function () {
$input = $("<INPUT type=text class='editor-text' />")
.appendTo(args.container)
.bind("keydown.nav", function (e) {
if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) {
e.stopImmediatePropagation();
}
})
.focus()
.select();
};
this.destroy = function () {
$input.remove();
};
this.focus = function () {
$input.focus();
};
this.getValue = function () {
return $input.val();
};
this.setValue = function (val) {
$input.val(val);
};
this.loadValue = function (item) {
defaultValue = item[args.column.field] || "";
$input.val(defaultValue);
$input[0].defaultValue = defaultValue;
$input.select();
};
this.serializeValue = function () {
return $input.val();
};
this.applyValue = function (item, state) {
item[args.column.field] = state;
};
this.isValueChanged = function () {
return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue);
};
this.validate = function () {
if (args.column.validator) {
var validationResults = args.column.validator($input.val());
if (!validationResults.valid) {
return validationResults;
}
}
return {
valid: true,
msg: null
};
};
this.init();
}
the editor can be then assigned in your columns object:
var columns = [
{id: "title", name: "Title", field: "title", width: 70, minWidth: 50, cssClass: "cell-title", sortable: true, editor: myCustomEditor},
];
I am dynamically adding rows to kendo gid. Now i need a reorder button ,where i can able to move a row up and down . i don't want drag and drop functionality. Im able to get each row id .need some help...
<script>
$(document).ready(function () {
var grid = $("#grid").kendoGrid({
columns: [
{ field: "Control", title: "Web Control Name" },
{ field: "Value", title: "Drag & Drop Variable" },
{
command: [
{ title: "create", template: "<img class='ob-image' src='../DefaultAssets/Images/New.png' style='padding: 0 15px 0 5px;' />" },
{ title: "reorder", template: "<img class ='up-image' src='../DefaultAssets/Images/Upimages.jpg' style='padding: 0 15px 0 5px;' />" },
{ "name": "destroy", title: "" }
],
},
],
dataSource: {
data: [
{
Control: "Web Control name",
Value: "Drag & Drop Variable"
},
],
schema: {
model: {
Control: "Web Control name",
Value: "Drag & Drop Variable"
}
}
},
reorderable: true,
editable: {
// confirmation: "Are you sure that you want to delete this record?",
createAt: "bottom"
},
remove: function (e) {
}
});
var grid = $("#grid").data("kendoGrid");
$("#grid").on("click", ".ob-image", function () {
var grid = $("#grid").data("kendoGrid");
grid.addRow();
});
$("#grid").on("click", ".up-image", function () {
var row = $(this).closest("tr");
var rowIdx = $("tr", grid.tbody).index(row);
alert(rowIdx);
});
});
You can create a template column and use the data source insert and remove methods to rearrange the data items. The grid will be refreshed automatically.
$("#grid").kendoGrid({
dataSource: [
{ foo: "foo" },
{ foo: "bar" },
{ foo: "baz" }
],
columns: [
{ field: "foo" },
{ template: '<button onclick="return up(\'#=uid#\')">up</button><button onclick="return down(\'#=uid#\')">down</button>' }
]
});
function up(uid) {
var grid = $("#grid").data("kendoGrid");
var dataItem = grid.dataSource.getByUid(uid);
var index = grid.dataSource.indexOf(dataItem);
var newIndex = Math.max(0, index - 1);
if (newIndex != index) {
grid.dataSource.remove(dataItem);
grid.dataSource.insert(newIndex, dataItem);
}
return false;
}
function down(uid) {
var grid = $("#grid").data("kendoGrid");
var dataItem = grid.dataSource.getByUid(uid);
var index = grid.dataSource.indexOf(dataItem);
var newIndex = Math.min(grid.dataSource.total() - 1, index + 1);
if (newIndex != index) {
grid.dataSource.remove(dataItem);
grid.dataSource.insert(newIndex, dataItem);
}
return false;
}
Here is a live demo: http://jsbin.com/ExOgiPib/1/edit
Once upon a time I was a Kendo UI user. I had a problem with sorting as well and this is how I solved it back then (after a lot of suffering):
//Sort Hack
/*
Changes all dataSources to case insensitive sorting (client side sorting).
This snipped enable case insensitive sorting on Kendo UI grid, too.
The original case sensitive comparer is a private and can't be accessed without modifying the original source code.
tested with Kendo UI version 2012.2.710 (Q2 2012 / July 2012).
*/
var CaseInsensitiveComparer = {
getterCache: {},
getter: function (expression) {
return this.getterCache[expression] = this.getterCache[expression] || new Function("d", "return " + kendo.expr(expression));
},
selector: function (field) {
return jQuery.isFunction(field) ? field : this.getter(field);
},
asc: function (field) {
var selector = this.selector(field);
return function (a, b) {
if ((selector(a).toLowerCase) && (selector(b).toLowerCase)) {
a = selector(a).toLowerCase(); // the magical part
b = selector(b).toLowerCase();
}
return a > b ? 1 : (a < b ? -1 : 0);
};
},
desc: function (field) {
var selector = this.selector(field);
return function (a, b) {
if ((selector(a).toLowerCase) && (selector(b).toLowerCase)) {
a = selector(a).toLowerCase(); // the magical part
b = selector(b).toLowerCase();
}
return a < b ? 1 : (a > b ? -1 : 0);
};
},
create: function (descriptor) {
return this[descriptor.dir.toLowerCase()](descriptor.field);
},
combine: function (comparers) {
return function (a, b) {
var result = comparers[0](a, b),
idx,
length;
for (idx = 1, length = comparers.length; idx < length; idx++) {
result = result || comparers[idx](a, b);
}
return result;
};
}
};
kendo.data.Query.prototype.normalizeSort = function (field, dir) {
if (field) {
var descriptor = typeof field === "string" ? { field: field, dir: dir} : field,
descriptors = jQuery.isArray(descriptor) ? descriptor : (descriptor !== undefined ? [descriptor] : []);
return jQuery.grep(descriptors, function (d) { return !!d.dir; });
}
};
kendo.data.Query.prototype.sort = function (field, dir, comparer) {
var idx,
length,
descriptors = this.normalizeSort(field, dir),
comparers = [];
comparer = comparer || CaseInsensitiveComparer;
if (descriptors.length) {
for (idx = 0, length = descriptors.length; idx < length; idx++) {
comparers.push(comparer.create(descriptors[idx]));
}
return this.orderBy({ compare: comparer.combine(comparers) });
}
return this;
};
kendo.data.Query.prototype.orderBy = function (selector) {
var result = this.data.slice(0),
comparer = jQuery.isFunction(selector) || !selector ? CaseInsensitiveComparer.asc(selector) : selector.compare;
return new kendo.data.Query(result.sort(comparer));
};
kendo.data.Query.prototype.orderByDescending = function (selector) {
return new kendo.data.Query(this.data.slice(0).sort(CaseInsensitiveComparer.desc(selector)));
};
//Sort Hack
You can implement your own solution, you can add your own functions and the order change will happen as you want.