How to make javascript flow wait till an element is completely rendered - javascript

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.

Related

How to prevent rebind on kendo grid data item change?

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");

Ember.js - I want an Action event (on="") to trigger when there is a transition to a new Route

I want an Action event (on="") to trigger when there is a transition to a new Route.
I've seen the list of Action event handlers and closest I could find is attaching the action to the largest HTML element on the page and triggering it with 'Mousemove". This is a terribly flawed away of going about what I want to do.
So just to draw it out.
<div {{action 'displayEitherHtml1or2'}} class="largestDOMelement">
{{#if showHtml1}}
// html 1 inside
{{/if}}
{{#if showHtml2}}
// html 2 inside
{{/if}}
</div>
'/objects' is a list of objects and clicking one leads to 'object/somenumber'. The action should automatically trigger when I enter the 'object/somenumber' page.
UPDATE: I've taken the contents from the previous update and dumped them into my DocRoute, but nothing it being triggered when I transition to 'doc' through {{#link-to 'doc' this.docID}} {{docTitle}}{{/link-to}}
VpcYeoman.DocRoute = Ember.Route.extend(VpcYeoman.Authenticated,{
toggleLetterSwitch: false,
togglePermitSwitch: false,
activate: function () {
var docTemplateID = this.get('docTemplateID');
if ( docTemplateID == 2) {
this.set('toggleLetterSwitch', true);
this.set('togglePermitSwitch', false);
console.log('docTemplateID equals 2');
} else {
this.set('toggleLetterSwitch', false);
this.set('togglePermitSwitch', true);
}
}
});
UPDATE DOS: setDocID is set in the DocsController to 1. Here's the whole thing.
VpcYeoman.DocsController = Ember.ArrayController.extend({
tempDocId: 1,
actions: {
addDoc: function (params) {
var docTitle = this.get('docTitle');
var docTemplateID = 1;
var docTemplateID = this.get('tempDocId');
console.log(this.get('tempDocId'));
var store = this.store;
var current_object = this;
var doc = current_object.store.createRecord('doc', {
docTitle:docTitle,
docTemplateID:docTemplateID
});
doc.save();
return true;
},
setDocId: function (param) {
this.set('tempDocId', param);
console.log(this.get('tempDocId'))
},
}
});
As #fanta commented, it seems like you're looking for the activate hook within your Route. This gets called when you enter the route where you define it. If you want to call it on every transition, you might consider defining a base route for your application and extending that instead of Em.Route:
App.BaseRoute = Em.Route.extend(
activate: function () {
// Do your thing
}
);
App.YourRoutes = App.BaseRoute.extend()
It's possible that there's a more appropriate place/time to do this, but without knowing quite what your action does, this is probably the best guess.
ETA: Looking at your edit, you won't want all your routes to extend App.BaseRoute the way I did it above; you should probably just include that activate hook explicitly in the routes which need it.

Backbone.View.model.on('change:...') trigged only once

I'm doing ajax file upload for post via modal window with preview in post. Every post has it's own model and a view. Modal window is also a separate view, binded to existing DOM element.
When Attach button in post view is clicked, I call .open() from modal view, passing post model to modal view as settings:
POST VIEW:
======================
ModalAttach.open({
postModel : this.model
});
When file in modal view is uploaded, I add server response to passed Post model to render it later in post itself as a preview:
MODAL VIEW:
======================
// file upload success
success: function(data) {
// if it's first call, set []
var imagesUploaded = self.postModel.get('images_uploaded') || [];
// add server response to array
imagesUploaded.push(data);
// rewrite current model array to new array
self.postModel.set({ 'images_uploaded' : imagesUploaded });
}
To render preview in post (before real submitting), I've got a function
POST VIEW:
======================
renderUploadedImages: function () {
var self = this;
this.$uploadedImagesWrapper = this.$('.b-uploaded__images');
if (this.model.get('images_uploaded')) {
this.$uploadedImagesWrapper.empty();
this.model.get('images_uploaded').forEach(function (uploadedImage) {
self.$uploadedImagesWrapper.append(
uploadedImageTemplate({
'source': uploadedImage.source
})
)
});
}
}
And to trigger image render, I bind a listner to track when model.images_uploaded is changed by modal view:
POST VIEW:
======================
initialize: function () {
this.addEvents();
this.renderUploadedImages();
},
addEvents: function () {
var self = this;
this.model.on('change:images_uploaded', function () {
self.renderUploadedImages();
})
},
The problem is renderUploadedImages() in Post view is trigged only once, at first upload. Other changes are not caught (when postModel.get('images_uploaded').length becomes 2,3,etc..). What I am doing wrong?
Thanks.
When you do like this :
var imagesUploaded = self.postModel.get('images_uploaded') || [];
// add server response to array
imagesUploaded.push(data);
// rewrite current model array to new array
self.postModel.set({ 'images_uploaded' : imagesUploaded });
The first time, if you check self.postModel.get('images_uploaded') you will find it undefined, that's why when you set it the event change:images_uploaded is triggered.
But the second time you call the success method you don't change the the model attribute, it's always pointing to the same object (array), you just change the array value.
Here's an example

Save data from a jQuery Accordion

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.

how to wait for loading data into the store

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')));
}
},
...

Categories

Resources