Kendo Grid: Retaining navigational cell after a datasource sync - javascript

this is similar to a previous post where I wanted to sync my data source when the user changes rows in the grid (exactly as Access saves out a record)
In the post above am am shown how to do this when the user tabs into a new cell as follows...
function refreshFix1() {
kendo.ui.Grid.fn.refresh = (function (refresh) {
return function (e) {
this._refreshing = true;
refresh.call(this, e);
this._refreshing = false;
}
})(kendo.ui.Grid.fn.refresh);
kendo.ui.Grid.fn.current = (function (current) {
return function (element) {
// assuming element is td element, i.e. cell selection
if (!this._refreshing && element) {
this._lastFocusedCellIndex = $(element).index();
this._lastFocusedUid = $(element).closest("tr").data("uid");
// Added this For navigation mode
this._lastNavigationCell = this.tbody.find("tr:last td:last");
}
return current.call(this, element);
}
})(kendo.ui.Grid.fn.current);
kendo.ui.Grid.fn.refocusLastEditedCell = function () {
if (this._lastFocusedUid) {
var row = $(this.tbody).find("tr[data-uid='" + this._lastFocusedUid + "']");
var cell = $(row).children().eq(this._lastFocusedCellIndex);
this.editCell(cell);
}
};
The above gives us a function we can call (refocusLastEditedCell) after we sync the data source, and seems to work great.
I now want to do the same for when the grid is in Navigation mode. Following the above example, and the doco here , I added the following...
// Call this to go back to a cell in *navigation* mode
kendo.ui.Grid.fn.refocusLastNavigatedCell = function () {
var self = this;
if (this._lastNavigationCell) {
// try see if calling "async" using setTimeout will help
setTimeout (function(){
console.log("going back to navigation cell");
self.current(this._lastNavigationCell);
self.table.focus();
}, 10)
}
};
I then have the following code to call sync on the datasource...
vm.gridData.sync();
if (vm.editMode){
/ Go back to edit cell
grid.refocusLastEditedCell()
} else{
// Go back to navigation cell
grid.refocusLastNavigatedCell();
};
}
(full example here)
Unfortunately I do not seem to be going back to the same cell, it again just jumps to the top left cell.
Any ideas how to get it to work in this situation would be greatly appreciated.
Thanks in advance for any help!

You need to use the row uid and cell index as done in the original code; the <td> element you're trying to focus doesn't exist anymore once the grid gets rerendered. So you could do this:
kendo.ui.Grid.fn.current = (function (current) {
return function (element) {
// assuming element is td element, i.e. cell selection
if (!this._refreshing && element) {
this._lastFocusedCellIndex = $(element).index();
this._lastFocusedUid = $(element).closest("tr").data("uid");
// For navigation mode
var cell = current.call(this, element);
this._lastNavigationCellIndex = $(cell).index();
this._lastNavigationCellUid = $(cell).closest("tr").data("uid");
}
return current.call(this, element);
}
})(kendo.ui.Grid.fn.current);
and use it:
kendo.ui.Grid.fn.refocusLastNavigatedCell = function () {
if (this._lastNavigationCellUid) {
var row = $(this.tbody).find("tr[data-uid='" +
this._lastNavigationCellUid + "']");
var cell = $(row).children().eq(this._lastNavigationCellIndex);
this.current(cell);
}
};
You've got so many customizations now, you may want to extend the grid itself instead of replacing its methods one by one.
By the way, you could probably integrate all of this in the refresh method, so you don't have to explicitly call it:
kendo.ui.Grid.fn.refresh = (function (refresh) {
return function (e) {
this._refreshing = true;
refresh.call(this, e);
this._refreshing = false;
if (!this.options.editable) {
this.refocusLastNavigatedCell();
} else {
this.refocusLastEditedCell()
}
}
})(kendo.ui.Grid.fn.refresh);
I didn't understand why you want to refocus
this.tbody.find("tr:last td:last");
so I changed it for the (demo).

Related

Contextual Menu issue in office-fabric-ui on second click

We are working on Office UI Fabric JS 1.2.0. Issue is with context menu having submenu items. When you open Second level, and then click anywhere on menu, first level menu was closing and second menu was re-aligning to Top-Left of page. We couldn't find the solution in next version of fabric also.
The issue gets resolved after overriding two methods from fabric.js of ContextualHost as follows:
fabric.ContextualHost.prototype.disposeModal = function () {
if (fabric.ContextualHost.hosts.length > 0) {
window.removeEventListener("resize", this._resizeAction, false);
document.removeEventListener("click", this._dismissAction, true);
document.removeEventListener("keyup", this._handleKeyUpDismiss, true);
this._container.parentNode.removeChild(this._container);
if (this._disposalCallback) {
this._disposalCallback();
}
// Dispose of all ContextualHosts
var index = fabric.ContextualHost.hosts.indexOf(this);
fabric.ContextualHost.hosts.splice(index, 1);
//Following is original code removed
//var i = ContextualHost.hosts.length;
//while (i--) {
// ContextualHost.hosts[i].disposeModal();
// ContextualHost.hosts.splice(i, 1);
//}
}
};
fabric.ContextualHost.prototype._dismissAction = function (e) {
var i = fabric.ContextualHost.hosts.length;
while (i--) { //New added
var currentHost = fabric.ContextualHost.hosts[i];
if (!currentHost._container.contains(e.target) && e.target !== this._container) {
if (currentHost._children !== undefined) {
var isChild_1 = false;
currentHost._children.map(function (child) {
if (child !== undefined) {
isChild_1 = child.contains(e.target);
}
});
if (!isChild_1) {
currentHost.disposeModal();
}
}
else {
currentHost.disposeModal();
}
}
}
};
Hope this question + answer helps someone looking to override fabric.js original code.

Making bootstrap-tags responsive, jquery events lost

I am trying to change this demo:
http://maxwells.github.io/bootstrap-tags.html
into a responsive version in which I can set it to readOnly and remove it from readOnly as I like. This code:
var alltags = ["new tag", "testtag", "tets", "wawa", "wtf", "wtf2"];
$(document).ready(function() {
var tagbox = $('#my-tag-list').tags({
suggestions: alltags
});
var tagenable = true;
$('#my-tag-list').focusout(function() {
if (tagenable) {
tagbox.readOnly = true;
$('#my-tag-list').empty();
tagbox.init();
tagenable = false;
}
});
$('#my-tag-list').click(function() {
if(!tagenable) {
tagbox.readOnly = false;
$('#my-tag-list').empty();
tagbox.init();
tagenable = true;
}
});
});
seems to work fairly well, it makes everything readonly after focusout and editable when I click it. However, the editing does not work since I cannot insert new tags nor delete them (seems to be like event handling was lost or something like that).
I am guessing that emptying the #my-tag-list div is causing this, but I cannot yet find a way to use for instance "detach" instead that removes everything inside (not the element itself) and putting it back in again.
I tried to make a JS Fiddle, but it isn't really working so well yet:
http://jsfiddle.net/tomzooi/cLxz0L06/
The thing that does work is a save of the entire website, which is here:
https://www.dropbox.com/sh/ldbfqjol3pppu2k/AABhuJA4A6j9XTxUKBEzoH6za?dl=0
this link has the unminimized JS of the bootstrap-tags stuff I am using:
https://github.com/maxwells/bootstrap-tags/blob/master/dist/js/bootstrap-tags.js
So far I managed to do this with some modifications of the bootstrap javascript code. I use two different tagbox which I hide and unhide with some click events.
var tagbox = $('#my-tag-list').tags({
suggestions: alltags,
tagData: tmp_tags,
afterAddingTag: function(tag) { tagboxro.addTag(tag); },
afterDeletingTag: function(tag) {tagboxro.removeTag(tag); }
});
var tagboxro = $('#my-tag-listro').tags({
suggestions: alltags,
tagData: tmp_tags,
readOnly: 'true',
tagSize: 'sm',
tagClass: 'btn-info pull-right'
});
$(document).mouseup(function (e) {
var container = $("#my-tag-list");
if (!container.is(e.target) // if the target of the click isn't the container...
&& container.has(e.target).length === 0) { // ... nor a descendant of the container
if (tagsave) {
$("#my-tag-listro").show();
$("#my-tag-list").hide();
var tags = tagbox.getTags();
$.post("%basedir%/save.php", {
editorID:"new_tags",
tags:tags
}, function(data,status){
//alert("Data: " + data + "\nStatus: " + status);
});
tagsave = false;
}
}
});
$('#my-tag-listro').click(function() {
tagsave = true;
//$(".tag-list").toggle();
$("#my-tag-list").show();
$("#my-tag-listro").hide();
});
I had to modify the code of bootstrap-tags.js to allow for this since it normally deletes all of the usefull functions when it is considered readonly in the init function:
if (this.readOnly) {
this.renderReadOnly();
this.removeTag = function(tag) {
if (_this.tagsArray.indexOf(tag) > -1) {
_this.tagsArray.splice(_this.tagsArray.indexOf(tag), 1);
_this.renderReadOnly();
}
return _this;
};
this.removeTagClicked = function() {};
this.removeLastTag = function() {};
this.addTag = function(tag) {
_this.tagsArray.push(tag);
_this.renderReadOnly();
return _this;
};
this.addTagWithContent = function() {};
this.renameTag = function() {};
return this.setPopover = function() {};
}
would be awesome if this feature was incorporated in a somewhat less hacky way though :)

How to achieve a queue of object instances of jQuery modals, assuring only one instance is on per time?

I've a task of building a modal prompt, that's been simple so far describing its methods like "show", "hide" when it comes down just to DOM manupulation.
Now comes the hardship for me... Imagine we have a page on which there are several immediate calls to construct and show several modals on one page
//on page load:
$("browser-deprecated-modal").modal();
$("change-your-city-modal").modal();
$("promotion-modal").modal();
By default my Modal (and other libraries i tried) construct all of these modals at once and show them overlapping each other in reverse order -
i.e $(promotion-modal) is on the top, while the
$("browser-deprecated-modal") will be below all of them. that's not what i want, let alone overlapping overlays.
I need each modal to show up only when the previous one (if there'are any) has been closed. So, first we should see "browser-deprecated-modal" (no other modals underneath), upon closing it there must pop up the second one and so on.
I've been trying to work it out with this:
$.fn.modal = function(options) {
return this.each(function() {
if (Modal.running) {
Modal.toInstantiateLater.push({this,options});
} else {
var md = new Modal(this, options);
}
});
}
destroy :function () {
....
if (Modal.toInstantiateLater.length)
new Modal (Modal.toInstantiateLater[0][0],Modal.toInstantiateLater[0][1]);
}
keeping a track of all calls to construct a Modal in a array and in the "destroy" method make a check of this array is not empty.
but it seems awkward and buggy me thinks.
i need a robust and clear solution. I've been thinking about $.Callbacks or $.Deferred,
kinda set up a Callback queue
if (Modal.running) { //if one Modal is already running
var cb = $.Callbacks();
cb.add(function(){
new Modal(this, options);
});
} else { //the road is clear
var md = new Modal(this, options);
}
and to trigger firing cb in the destroy method, but i'm new to this stuff and stuck and cannot progress, whether it's right or not, or other approach is more suitable.
Besides, I read that callbacks fire all the functions at once (if we had more than one extra modal in a queue), which is not right, because I need to fire Modal creation one by one and clear the Callback queue one by one.
Please help me in this mess.
My code jsfiddle
I got rid of the counter variable, as you can use toInstantiateLater to keep track of where you are, and only had to make a few changes. Give this a try...
Javscript
function Modal(el, opts){
this.el = $(el);
this.opts = opts;
this.overlay = $("<div class='overlay' id='overlay"+Modal.counter+"'></div>");
this.wrap = $("<div class='wrap' id='wrap"+Modal.counter+"'></div>");
this.replace = $("<div class='replace' id='replace"+Modal.counter+"'></div>");
this.close = $("<span class='close' id='close"+Modal.counter+"'></span>")
if (Modal.running) {
Modal.toInstantiateLater.push(this);
}
else {
Modal.running = true;
this.show();
}
}
Modal.destroyAll = function() {
Modal.prototype.destroyAll();
};
Modal.prototype = {
show: function() {
var s = this;
s.wrap.append(s.close);
s.el.before(s.replace).appendTo(s.wrap).show();
$('body').append(s.overlay).append(s.wrap);
s.bindEvents();
Modal.currentModal = s;
},
bindEvents: function() {
var s = this;
s.close.on("click.modal",function(e){
s.destroy.call(s,e);
});
},
destroy: function(e) {
var s = this;
s.replace.replaceWith(s.el.hide());
s.wrap.remove();
s.overlay.remove();
if (Modal.toInstantiateLater.length > 0) {
Modal.toInstantiateLater.shift().show();
}
else {
Modal.running = false;
}
},
destroyAll: function(e) {
Modal.toInstantiateLater = [];
Modal.currentModal.destroy();
}
}
Modal.running = false;
Modal.toInstantiateLater = [];
Modal.currentModal = {};
$.fn.modal = function(options) {
return this.each(function() {
var md = new Modal(this, options);
});
}
$("document").ready(function(){
$("#browser-deprecated-modal").modal();
$("#change-your-city-modal").modal();
$("#promotion-modal").modal();
$("#destroy-all").on("click", function() {
Modal.destroyAll();
});
});
jsfiddle example
http://jsfiddle.net/zz9ccbLn/4/

Location.hash causes infinite loop

I am using location.hash in javascript, to allow the user to switch between ajax screens (divs that are added and removed dynamically within the same html page).
The problem is, when I update location.hash by javascript, the window listener immediately fires! I need this event to fire only when the back button is actually clicked, not when I change the history by javascript.
My window listener code:
window.onhashchange = function() {
var s;
if (location.hash.length > 0) {
s = parseInt(location.hash.replace('#',''),10);
} else {
s = 1;
}
main.showScreen(s);
}
And my screen update code:
main.showScreen = function(i) {
// allow the back button to switch between screens
location.hash = i;
// but setting location.hash causes this same function to fire again!
//
// here follows the code that adds a new div with new text content
// ...
}
To clarify: showScreen can be called from anywhere in the application, for example by clicking a "next" button somewhere on the page.
In your main.showScreen function, you can:
if (location.hash != i)
location.hash = i;
OR, you can set up a document-scoped variable with the last hash value.
var lastHash = -1;
window.onhashchange = function() {
var s;
if (location.hash.length > 0) {
s = parseInt(location.hash.replace('#',''),10);
} else {
s = 1;
}
if (lastHash != s) {
lastHash = s;
main.showScreen(s);
}
}

How to detect if some text box is changed via external script?

I have some jQuery plugin that changes some elements, i need some event or jQuery plugin that trigger an event when some text input value changed.
I've downloaded jquery.textchange plugin, it is a good plugin but doesn't detect changes via external source.
#MSS -- Alright, this is a kludge but it works:
When I call boxWatcher() I set the value to 3,000 but you'd need to do it much more often, like maybe 100 or 300.
http://jsfiddle.net/N9zBA/8/
var theOldContent = $('#theID').val().trim();
var theNewContent = "";
function boxWatcher(milSecondsBetweenChecks) {
var theLoop = setInterval(function() {
theNewContent = $('#theID').val().trim();
if (theOldContent == theNewContent) {
return; //no change
}
clearInterval(theLoop);//stop looping
handleContentChange();
}, milSecondsBetweenChecks);
};
function handleContentChange() {
alert('content has changed');
//restart boxWatcher
theOldContent = theNewContent;//reset theOldContent
boxWatcher(3000);//3000 is about 3 seconds
}
function buttonClick() {
$('#theID').value = 'asd;lfikjasd;fkj';
}
$(document).ready(function() {
boxWatcher(3000);
})
try to set the old value into a global variable then fire onkeypress event on your text input and compare between old and new values of it. some thing like that
var oldvlaue = $('#myInput').val();
$('#myInput').keyup(function(){
if(oldvlaue!=$('#myInput').val().trim())
{
alert('text has been changed');
}
});
you test this example here
Edit
try to add an EventListner to your text input, I don't know more about it but you can check this Post it may help
Thanks to #Darin because of his/her solution I've marked as the answer, but i have made some small jQuery plugin to achieve the same work named 'txtChgMon'.
(function ($) {
$.fn.txtChgMon = function (func) {
var res = this.each(function () {
txts[0] = { t: this, f: func, oldT: $(this).val(), newT: '' };
});
if (!watchStarted) {
boxWatcher(200);
}
return res;
};
})(jQuery);
var txts = [];
var watchStarted = false;
function boxWatcher(milSecondsBetweenChecks) {
watchStarted = true;
var theLoop = setInterval(function () {
for (var i = 0; i < txts.length; i++) {
txts[i].newT = $(txts[i].t).val();
if (txts[i].newT == txts[i].oldT) {
return; //no change
}
clearInterval(theLoop); //stop looping
txts[i].f(txts[i], txts[i].oldT, txts[i].newT);
txts[i].oldT = $(txts[i].t).val();
boxWatcher(milSecondsBetweenChecks);
return;
}
}, milSecondsBetweenChecks);
}

Categories