I have a kendo grid, and when an item is selected I want to modify the underlying dataitem so i'm doing this ...
selectionChange: function(e)
{
var component = $(this.table).closest('.component');
var grid = this;
var val = !component.hasClass("secondary");
var selection = grid.dataItems(grid.select());
selection.forEach(function () {
this.set("SaleSelected", val);
});
}
I also have 2 buttons that allow me to push items between the 2 grids which do this ...
select: function (e) {
e.preventDefault();
var sender = this;
// get kendo data source for the primary grid
var source = $(sender).closest(".component")
.find(".component.primary")
.find(".details > [data-role=grid]")
.data("kendoGrid")
.dataSource;
// sync and reload the primary grid
source.sync()
.done(function () {
source.read();
my.Invoice.reloadGridData($(sender).closest(".component").find(".component.secondary").find(".details > [data-role=grid]"));
});
return false;
},
deselect: function (e) {
e.preventDefault();
var sender = this;
debugger;
// get kendo data source for the secondary grid
var source = $(sender).closest(".component")
.find(".component.secondary")
.find(".details > [data-role=grid]")
.data("kendoGrid")
.dataSource;
// sync and reload the primary grid
source.sync()
.done(function () {
source.read();
my.Invoice.reloadGridData($(sender).closest(".component").find(".component.primary").find(".details > [data-role=grid]"));
});
return false;
}
Essentially the "selected items" from grid1 can be marked as such on the server then the grids get reloaded to move the items over.
All good I thought, but apparently Kendo has other ideas.
Editing a data item causes its owning grid to rebind losing the selection state resulting in some confusing behaviour for the user.
Is there a way to tell kendo "i'm going to edit this unbound property right now, don't go messing with binding"?
Ok it turns out kendo is a bit of a wierdo and I still have no idea why they insist you call all their "api stuff" to do simple tasks when doing things more directly actually works better.
In my case I removed the selection change call altogether and let kendo handle that, then in my selection button handlers to move the data between grids I updated the properties directly on the data items instead of calling
"item.set("prop", value)" i now have to do "item.prop = value".
The net result is this ...
select: function (e) {
e.preventDefault();
var sender = this;
// get some useful bits
var component = $(sender).closest(".component");
var primaryGrid = component.find(".component.primary").find(".details > [data-role=grid]").data("kendoGrid");
// get the new selection, and mark the items with val
var selection = $(primaryGrid.tbody).find('tr.k-state-selected');
selection.each(function (i, row) {
primaryGrid.dataItem(row).SaleSelected = true;
primaryGrid.dataItem(row).dirty = true;
});
// sync and reload the primary grid
primaryGrid.dataSource.sync()
.done(function () {
primaryGrid.dataSource.read();
component.find(".component.secondary")
.find(".details > [data-role=grid]")
.data("kendoGrid")
.dataSource
.read();
});
return false;
},
deselect: function (e) {
e.preventDefault();
var sender = this;
// get some useful bits
var component = $(sender).closest(".component");
var secondaryGrid = component.find(".component.secondary").find(".details > [data-role=grid]").data("kendoGrid");
// get the new selection, and mark the items with val
var selection = $(secondaryGrid.tbody).find('tr.k-state-selected');
selection.each(function (i, row) {
secondaryGrid.dataItem(row).SaleSelected = false;
secondaryGrid.dataItem(row).dirty = true;
});
// sync and reload the primary grid
secondaryGrid.dataSource.sync()
.done(function () {
secondaryGrid.dataSource.read();
component.find(".component.primary")
.find(".details > [data-role=grid]")
.data("kendoGrid")
.dataSource
.read();
});
return false;
}
kendo appears to be taking any call to item.set(p, v) as a trigger to reload data so avoiding the kendo wrapper and going directly to the item properties allows me direct control of the process.
Moving the code from the selection change event handler to the button click handler also means i only care about that data being right when it actually needs to be sent to the server, something I just need to be aware of.
I don't like this, but it's reasonably clean and the ui shows the right picture even if the underlying data isn't quite right.
My other option would be to create a custom binding but given that the binding would have to result in different results depending on weather it was binding to the primary or the secondary grid I suspect that would be a lot of js code, this feels like the lesser of 2 evils.
I think you can bind the dataBinding event to just a "preventDefault" and then unbind it and refresh at your leisure
var g = $("#myGrid").data("kendoGrid");
g.bind("dataBinding", function(e) { e.preventDefault(); });
then later...
g.unbind("dataBinding");
Related
test.view.js
timeDBox = new sap.ui.commons.DropdownBox({layoutData: new sap.ui.layout.GridData({linebreak: true}),
change: function(oEvent){
oController.getKeyEqChart();
},
}),
new sap.ui.core.HTML({
content: "<div id=\"chart1\"></div>",
afterRendering: function(e){
console.log("chart1 create"+timeDBox.getValue());
chart1DivReady = true;
oController.getchart();
}
})
test.controller.js
onInit: function() {
var modelDataEvent = {"genericTableModel":[{"xtime":"1"},{"xtime":"2"},{"xtime":"3"},{"xtime":"4"},{"xtime":"5"},{"xtime":"8"},{"xtime":"10"}]}
var oTemplate11 = new sap.ui.core.ListItem({text : "{xtime}", key : "{xtime}"});
timeDBox.setModel(new sap.ui.model.json.JSONModel(modelDataEvent));
timeDBox.bindItems("/genericTableModel", oTemplate11);
timeDBox.getModel().refresh();
this.getchart();
},
getchart: function(){
var jsonObjToSend = {} ;
jsonObjToSend["dialogue"] = "terminal";
jsonObjToSend["cid"] = "key_equipment ";
var srachmap = {} ;
srachmap["xtime"] = timeDBox.getValue();
jsonObjToSend["search"] = srachmap; this.doAjax("/uri/uri",jsonObjToSend).done(this.updateKeyEqChart);
},
updateKeyEqChart: function(modelData) {
var svg = d3.select("#chart1").append("svg")
1) if i call getchart method from onInit, chart1 id is not created when executing this method
2) if i call getchart chart from oController.getchart() at that time timeDBox.getValue() value is not created which is required to get chart data
},
I am using a drop down list in my application which is populated from database.
Following things happen after the drop down gets populated:
Once the drop down gets populated I use the value of the drop down to render a chart by doing another ajax call to the db.
If the drop down is not populated by the time the flow reaches there then later the chart is not rendered but with time the drop down gets rendered as the ajax where I send param from drop down is null as the drop down is not ready.
So how to make the control wait till the drop down is populated and then go the chart call.
I am not 100% sure that I understand your questions right and the code sample being almost unreadable doesn't help.
But I think onInit might not be the lifecycle hook you are looking for.
If it is a one time deal, I would use onAfterRendering:
onAfterRendering: function() {
// Code
}
If this has to be executed everytime you navigate to this page, then I would add onAfterShow/onBeforeShow delegates in the onInit function.
onInit: function () {
view.addEventDelegate({
/**
* use either or in your case
*/
onAfterShow: function (oEvt) {
// If you use a busy dialog, you want to close it here
},
onBeforeShow: function (oEvt) {
}
});
},
Hope this helps.
So I'm working on a website using the DataTables API, which hosts information on troubleshooting videos. There are all status, approved, certified, and rejected video status tabs on my data table that allow you to navigate through videos with varying statuses.
For efficiency, I'm passing in one "All Videos" JSON object instead of passing in a JSON object for each category of vidoes. So what I'm trying to do is filter through the first column, which is status, on my "All Videos" table and find the status I'm looking for. For example, when you hit the "Approved" tab, I want to display all table rows where GRID.column(0).data() == "Approved".
Here's the code I have in the pageSetUp() function:
$("#VideoReviewTabs li").on("click", function (e) {
$("#VideoReviewTabs li").removeClass("active");
$(this).addClass("active");
loadSelectedList($(this).data("listname"));
});
And here's the loadApprovedVideosTab() function:
function loadApprovedVideosTab() {
var GRID = $("#VIDEO_GRID").DataTable();
var typeCol = GRID.column(0).data();
GRID.column(0).data().filter(function (value, index) {
return value == "Approved" ? true : false;
});
}
This filter function is definitely not working and selecting the various tabs does nothing to the table grid. The loadApprovedVideosTab() function does get called, but the filtering obviously doesn't work.
Any advice would be great. Thanks.
I think it's because your function doesn't return anything... try
function filterApprovedVideosTab() {
var GRID = $("#VIDEO_GRID").DataTable();
// var typeCol = GRID.column(0).data();
return GRID.column(0).data().filter(function (value, index) {
return value == "Approved" ? true : false;
});
}
var approved = filterApprovedVideosTab();
In order to get Approved videos you can use the function search on the correspond column, then call draw upon it:
function loadApprovedVideosTab() {
var GRID = $("#VIDEO_GRID").DataTable();
GRID.column(0).search('Approved').draw();
}
Reference.
I have a sortable accordion loaded with a foreach-template loop over a ko.observableArray() named "Tasks".
In the accordion I render the TaskId, the TaskName, and a task Description - all ko.observable().
TaskName and Description is rendered in input/textarea elements.
Whenever TaskName or Description is changed, an item is de-selected, or another item is clicked on, I want to call a function saveEdit(item) to send the updated TaskName and Description to the database via an ajax request.
I need to match the TaskId with the Tasks-array to fetch the actual key/value-pair to send to the saveEdit().
This is the HTML:
<div id="accordion" data-bind="jqAccordion:{},template: {name: 'task-template',foreach: Tasks,afteradd: function(elem){$(elem).trigger('valueChanged');}}"></div>
<script type="text/html" id="task-template">
<div data-bind="attr: {'id': 'Task' + TaskId}" class="group">
<h3><b><span data-bind="text: TaskId"></span>: <input name="TaskName" data-bind="value: TaskName /></b></h3>
<p>
<label for="Description" >Description:</label><textarea name="Description" data-bind="value: Description"></textarea>
</p>
</div>
</script>
This is the binding:
ko.bindingHandlers.jqAccordion = {
init: function(element, valueAccessor) {
var options = valueAccessor();
$(element).accordion(options);
$(element).bind("valueChanged",function(){
ko.bindingHandlers.jqAccordion.update(element,valueAccessor);
});
},
update: function(element,valueAccessor) {
var options = valueAccessor();
$(element).accordion('destroy').accordion(
{
// options put here....
header: "> div > h3"
, collapsible: true
, active: false
, heightStyle: "content"
})
.sortable({
axis: "y",
handle: "h3",
stop: function (event, ui) {
var items = [];
ui.item.siblings().andSelf().each(function () {
//compare data('index') and the real index
if ($(this).data('index') != $(this).index()) {
items.push(this.id);
}
});
// IE doesn't register the blur when sorting
// so trigger focusout handlers to remove .ui-state-focus
ui.item.children("h3").triggerHandler("focusout");
if (items.length) $("#sekvens3").text(items.join(','));
ui.item.parent().trigger('stop');
}
})
.on('stop', function () {
$(this).siblings().andSelf().each(function (i) {
$(this).data('index', i);
});
})
.trigger('stop');
};
};
My first thought was to place the line
$root.SelectedTask( ui.options.active );
in an .on('click') event function where SelectedTask is a ko.observable defined in my viewModel. However, the .on('click') event seems to be called a lot and it's generating a lot of traffic. Also, I canĀ“t quite figure out where to put the save(item) call that sends the selected "item" from Tasks via an ajax-function to the database.
Any help is highly appreciated. Thanks in advance! :)
Whenever TaskName or Description is changed, an item is de-selected, or another item is clicked on, I want to call a function saveEdit(item) to send the updated TaskName and Description to the database via an ajax request.
This sounds like the core of what you want to do. Let's start out with a Task model
function Task (data) {
var self = this;
data = data || {};
self.id = ko.observable(data.id);
self.name = ko.observable(data.name);
self.description = ko.observable(data.description);
}
And then we need our View Model:
function ViewModel () {
var self = this;
self.tasks = ko.observableArray();
self.selectedTask = ko.observable();
self.saveTask = function (task) {
$.ajax({ ... });// ajax call that sends the changed data to the server
};
var taskSubscription = function (newValue) {
self.saveTask(self.selectedTask());
};
var nameSubscription, descriptionSubscription;
self.selectedTask.subscribe(function (newlySelectedTask) {
if (newlySelectedTask instanceof Task) {
nameSubscription =
newlySelectedTask.name.subscribe(taskSubscription);
descriptionSubscription =
newlySelectedTask.description.subscribe(taskSubscription);
self.saveTask(newlySelectedTask);// But why?
}
});
self.selectedTask.subscribe(function (currentlySelectedTask) {
if (currentlySelectedTask instanceof Task) {
nameSubscription.dispose();
descriptionSubscription.dispose();
self.saveTask(currentlySelectedTask);// But why?
}
}, null, 'beforeChange');
}
So what's going on here? Most of this should be pretty self explanatory so I'm just going to focus on the subscriptions. We created a taskSubscription function so we're not constantly having it defined every time the self.selectedTask changes.
We have two subscriber functions. The first fires after the selectedTask's value has changed and the second fires before it changes. In both, we verify that the new value is an instance of a Task object. In the after change subscription, we set up two subscriptions on the name and description properties. Then I capture the return value from the subscription function into two private variables. These are used in the before change function to dispose of those subscriptions so that if those Tasks are ever updated when they're not currently selected, then we don't continue to fire off the saveTask function.
I've also added self.saveTask in each of the subscriptions to the selectedTask observable. I asked why in here because, why save it if we don't know if the value has changed or not? You may be making ajax requests needlessly here.
Also, as demonstrated by this code, you can set up these subscriptions to make ajax requests every time the value changes but that may end up making a LOT of requests. A better option might be to set up functionality in your Task model that can track whether or not it is 'dirty' or not. Meaning one or more of its values have changed that requires updating.
function Task (data) {
var self = this;
// Make a copy of the data object coming in and use this to save previous values
self._data = data = $.extend(true, { id: null, name: null, description: null }, data);
self.id = ko.observable(data.id);
self.name = ko.observable(data.name);
self.description = ko.observable(data.description);
for (var prop in data) {
if (ko.isSubscribable(self[prop])) {
self[prop].subscribe(function (oldValue) {
data[prop] = oldValue;
}, null, 'beforeChange');
}
}
}
Task.prototype.isDirty = function () {
var self = this;
for (var prop in self._data) {
if (ko.isSubscribable(self[prop])) {
if (self._data[prop] !== self[prop]())
return true;
}
}
return false;
};
And of course you need a way to save it, or make it not dirty
Task.prototype.save = function () {
var self = this;
for (var prop in self._data) {
if (ko.isSubscribable(self[prop])) {
self._data[prop] = self[prop]();
}
}
};
Using the same concept you can also create Task.prototype.revert that does the opposite of what .save does. With all this in place, you could forego setting up the subscriptions on the individual name and description properties. I wanted to show that option to just demonstrate how one might want to use the .dispose method on a subscription. But now you can just subscribe to the selectedTask observable ('beforeChange') and see if the currently selected task that you're about to swap out isDirty. If it is, call the saveTask function, and when that completes, call the .save function on the Task so that it is no longer dirty.
This is probably the route I would go in implementing something like this. The beauty of it is, I haven't written a single line of code that has anything to do with the manipulating the View. You can set the selectedTask any way you see fit. What I would do is, bind the selectedTask observable to a click binding on the <h3> element inside of the accordion. That way, every time a user clicks on any of the accordions, it will potentially save the previously selected task (if any of the property values had changed).
Hopefully that addresses your scenario here of trying to save a Task when certain events are triggered.
I have list of item IDs on page load:
var itemIds = [1, 5, 10, 11];
IDs are rendered into list and shown to user. I have function which loads detailed information about item, it returns Promise/A.
function loadInfo(id) { ... }
Loading of info for all items is initiated on page load:
val infos = {};
$.each(itemIds, function(i, id) { infos[id] = loadInfo(id); }
Now the problem itself.
When user clicks on item ID:
if item info is loaded, info must be shown
if item info is not yet loaded, it must be shown when it is loaded
Looks easy:
$('li').click(function() {
var id = $(this).data('item-id');
infos[id].then(function(info) { $('#info').text(info); });
});
But if user cliked on another item before current one is loaded, then I have to cancel handler on current item promise and schedule on new one.
How to properly do it?
I have several working solutions (like maintaining currentItemId variable and checking it in promise handler), but they are ugly. Should I look to reactive programming libraries instead?
Kriomant,
I guess there's a number of ways you could code this. Here's one :
var INFOS = (function(){
var infoCache = {},
promises = {},
fetching = null;
var load = function(id) {
if(!promises[id]) {
promises[id] = $ajax({
//ajax options here
}).done(function(info){
infoCache[id] = info;
delete promises[id];
});
}
return promises[id];
}
var display = function(id, $container) {
if(fetching) {
//cancel display (but not loading) of anything latent.
fetching.reject();
}
if(infoCache[id]) {
//info is already cached, so simply display it.
$container.text(infoCache[id]);
}
else {
fetching = $.Deferred().done(function() {
$container.text(infoCache[id]);
});
load(id).done(fetching.resolve);
}
}
return {
load: load,
display: display
};
})();
As you will see, all the complexity is bundled in the namespace INFOS, which exposes two methods; load and display.
Here's how to call the methods :
$(function() {
itemIds = ["p7", "p8", "p9"];
//preload
$.each(itemIds, function(i, id) { INFOS.load(id); });
//load into cache on first click, then display from cache.
$('li').on('click', function() {
var id = $(this).data('item-id');
INFOS.display(id, $('#info'));
});
});
DEMO (with simulated ajax)
The tricks here are :
to cache the infos, indexed by id
to cache active jqXHR promises, indexed by id
to display info from cache if previously loaded ...
... otherwise, each time info is requested, create a Deferred associated with display of the info, that can be resolved/rejected independently of the corresponding jqXHR.
There are menu button ("clients"), tree panel with clients list (sorted by name) and viewer with selected client details. There is also selectionchange action..
My task - on button click switch to client view and select and load details for first client every time button has been clicked. My problem - store is not loaded, how waiting until ext js will autoload data to the store?
my controller code:
me.control({
'#nav-client': {
click: me.onNavClientClick
},
...
'clientlist': {
// load: me.selectClient,
selectionchange: me.showClient
}
});
onNavClientClick: function(view, records) {
var me = this,
content = Ext.getCmp("app-content");
content.removeAll();
content.insert(0, [{xtype: 'clientcomplex'}]);
var first = me.getClientsStore().first();
if (first) {
Ext.getCmp("clientList").getSelectionModel().select(me.getClientsListStore().getNodeById(first.get('clientId')));
}
},
...
Two main questions:
is it good solution in my case? (to select first client in tree panel)
var first = me.getClientsStore().first();
// i use another store to get first record because of i dont know how to get first record (on root level) in TreeStore
...
Ext.getCmp("clientList").getSelectionModel().select(me.getClientsListStore().getNodeById(first.get('clientId')));
i know this code works ok in case of "load: me.selectClient," (but only once),
if i place this code on button click - i see error
Uncaught TypeError: Cannot read property 'id' of undefined
because of me.getClientsListStore() is not loaded.. so how to check loading status of this store and wait some until this store will be completely autoloaded?..
Thank you!
You can listen the store 'load' event. Like this:
...
onNavClientClick: function(view, records) {
var me = this;
// if the store isn't loaded, call load method and defer the 'client view' creation
if (me.getClientsStore.getCount() <= 0) {
me.getClientsStore.on('load', me.onClientsStoreLoad, me, { single : true});
me.getClientsStore.load();
}
else {
me.onClientsStoreLoad();
}
},
onClientsStoreLoad : function () {
var me = this,
content = Ext.getCmp("app-content");
content.removeAll();
content.insert(0, [{xtype: 'clientcomplex'}]);
var first = me.getClientsStore().first();
if (first) {
Ext.getCmp("clientList").getSelectionModel().select(me.getClientsListStore().getNodeById(first.get('clientId')));
}
},
...