Here is what should happen:
I have a button with a label and an icon.
When I tap the button some actions will take place which will take some time. Therefore I want to replace the icon of the button with some loading-icon during the processing.
Normal Icon:
Icon replaced by loading gif:
So in pseudo code it would be:
fancyFunction(){
replaceIconWithLoadingIcon();
doFancyStuff();
restoreOldIcon();
}
However the screen isn't updated during the execution of the function. Here ist my code:
onTapButton: function(view, index, target, record, event){
var indexArray = new Array();
var temp = record.data.photo_url;
record.data.photo_url = "img/loading_icon.gif";
alert('test1');
/*
* Do magic stuff
*/
}
The icon will be replaced using the above code, but not until the function has terminated. Meaning, when the alert('1') appears, the icon is not yet replaced.
I already tried the solution suggested here without success.
I also tried view.hide() followed by view.show() but these commands weren't executed until the function terminated, too.
Let me know if you need further information. Any suggestions would be far more than welcome.
I finally found a solution displaying the mask during my actions are performed. The key to my solution was on this website.
In my controller I did the following:
showLoadingScreen: function(){
Ext.Viewport.setMasked({
xtype: 'loadmask',
message: 'Loading...'
});
},
onTapButton: function(view, index, target, record, event){
//Show loading mask
setTimeout(function(){this.showLoadingScreen();}.bind(this),1);
// Do some magic
setTimeout(function(){this.doFancyStuff(para,meter);}.bind(this),400);
// Remove loading screen
setTimeout(function(){Ext.Viewport.unmask();}.bind(this),400);
},
The replacing of the icons worked quite similar:
onTapButton: function(view, index, target, record, event){
//Replace the icon
record.data.photo_url = 'img/loading_icon.gif';
view.refresh();
// Do some magic
setTimeout(function(){this.doFancyStuff(para,meter);}.bind(this),400);
},
doFancyStuff: function(para, meter){
/*
* fancy stuff
*/
var index = store.find('id',i+1);
var element = store.getAt(index);
element.set('photo_url',img);
}
Thank you for your help Barrett and sha!
I think the main problem here is that your execution task is executing in the main UI thread. In order to let UI thread do animation you need to push your doFancyStuff() function into something like http://docs.sencha.com/touch/2.2.1/#!/api/Ext.util.DelayedTask
Keep in mind though, that you would need to revert it your icon only after fancy stuff is complete.
To update any button attributes you shoudl try to access the button itself. Either with a ComponentQuery or through the controllers getter. For Example:
var button = Ext.ComponentQuery.query('button[name=YOURBUTTONNAME]')[0];
button.setIcon('img/loading_icon.gif');
that shold update your button's icon.
also when you get a ref to the button you will have access to all the methods availble to an Ext.Button object:
http://docs.sencha.com/touch/2.2.1/#!/api/Ext.Button-method-setIcon
Related
I am extending a cloud-hosted LMS with javascript. Therefore, we can add javascript to the page, but cannot modify the vendor javascript for different components.
The LMS uses tinyMCE frequently. The goal is to add a new button on to the toolbar of each tinyMCE editor.
The problem is that since the tinyMCE modules are initialized in the vendor's untouchable code, we cannot modify the init() call. Therefore, we cannot add any text on to the "toolbar" property of the init() object.
So I accomplished this in a moderately hacky way:
tinyMCE.on('AddEditor', function(e){
e.editor.on('init', function(){
tinyMCE.ui.Factory.create({
type: 'button',
icon: 'icon'
}).on('click', function(){
// button pressing logic
})
.renderTo($(e.editor.editorContainer).find('.mce-container-body .mce-toolbar:last .mce-btn-group > div')[0])
});
});
So this works, but needless to say I am not totally comfortable having to look for such a specific location in the DOM like that to insert the button. Although this works, I do not believe it was the creator's intention for it to be used like this.
Is there a proper way to add the button to a toolbar, after initialization, if we cannot modify the initialization code?
I found a more elegant solution, but it still feels a bit like a hack. Here is what I got:
// get an instance of the editor
var editor=tinymce.activeEditor; //or tinymce.editors[0], or loop, whatever
//add a button to the editor buttons
editor.addButton('mysecondbutton', {
text: 'My second button',
icon: false,
onclick: function () {
editor.insertContent(' <b>It\'s my second button!</b> ');
}
});
//the button now becomes
var button=editor.buttons['mysecondbutton'];
//find the buttongroup in the toolbar found in the panel of the theme
var bg=editor.theme.panel.find('toolbar buttongroup')[0];
//without this, the buttons look weird after that
bg._lastRepaintRect=bg._layoutRect;
//append the button to the group
bg.append(button);
I feel like there should be something better than this, but I didn't find it.
Other notes:
the ugly _lastRepaintRect is needed because of the repaint
method, which makes the buttons look ugly regardless if you add new
controls or not
looked in the code, there is no way of adding new controls to the
toolbar without repainting and there is no way to get around it
without the ugly hack
append(b) is equivalent to add(b).renderNew()
you can use the following code to add the button without the hack, but you are shortcircuiting a lot of other stuff:
Code:
bg.add(button);
var buttonElement=bg.items().filter(function(i) { return i.settings.text==button.text; })[0];
var bgElement=bg.getEl('body');
buttonElement.renderTo(bgElement);
I build a small color-picker module. But it only opens up (and then works) when pickColor is called a second time. I also tried to wrap the _openColorPicker into a setTimeout but that didn't work either. In fact, the color-picker didn't show up at all when I did that.
What I found interesting is that the binding to the change event works, so the $ selector must have found the element already.
So I have two questions:
1) why is the picker only showing after the second call to _openColorPicker?
2) why didn't the picker open at all when I wrapper the _openColorPicker call in a setTimeout?
Edit: The _openColorPicker functions gets executed after the user has right-clicked into the document and then clicked on context-menu which is now showing.
Complete Code:
const ColorUtils = {
_initialized: false,
_openColorPicker: function () {
$('#color-picker').click();
},
pickColor: function (onChangeCallback, context) {
if (!this._initialized) {
$('<input/>').attr({
type: 'color',
id: 'color-picker',
display: 'hidden',
value: '#ffffff'
}).appendTo('#centralRow');
this._initialized = true;
$('#color-picker').on('change', onChangeCallback.bind(context));
}
this._openColorPicker();
// version with timeOut
const scope = this;
setTimeout(function () {
scope._openColorPicker();
}, 1000);
}
};
export default ColorUtils;
Above code is used like ColorUtils.pickColor(onColorPicked, this);
Check out this post. Looks like you can't trigger a click on an invisible color picker. That answer suggests giving the element an absolute position and placing it off screen, like so:
position:absolute;
left:-9999px;
top:-9999px;
I tried to replicate your case (for what I understood) : JSFIddle
I made some changes.
I moved the $('<input/>') in a property of the object ColorUtils and appended it to the DOM with absolute position and outside the screen.
(And also commented display:'hidden' because it's either display:none or visibility:hidden and as a CSS property, not Html attribute)
On right clic on the document I instantiate the picker (and register the callback + context) then add a button to the DOM to trigger the picker again.
Does it fulfill your requirements ?
I have a panel within which I have two more panels. When you click on panel1 then information in panel2 is loaded. Since the information is quite huge there is some delay when its being loaded. During this interim period I wish to add a loading mask which intimates the user that its getting loaded.
For the same I have done this:
var myMask = new Ext.LoadMask(Ext.getCmp('eventsPanel'), {
msg:"Please wait..."
});
myMask.show();
// eventsPanel is the main panel under which panel1 and panel2 lie.
// This code is in the selectionchange listener of panel1 whose code
// is inside the main eventsPanel code.
However, nothing is being displayed on the screen. Its still the same, i.e., for some amount of time the screen freezes and then after a delay of like 2-3 seconds the information is loaded. Can you please advise as to where am I going wrong?
I would suggest you to first show your masking like the way you are doing:
var myMask = new Ext.LoadMask(Ext.getCmp('eventsPanel'), {
msg:"Please wait..."
});
myMask.show();
Then make a delayed task
var task = new Ext.util.DelayedTask(function(){
//your loading panel2 with heavy data goes here
myMask.hide();
});
//start the task after 500 miliseconds
task.delay(500);
This should solve your problem.
I make a custom mask as follows:
var componentToMasK = Ext.ComponentQuery.query('#myChildComponent')[0];
var customMask = Ext.get(componentToMasK.getEl()).mask('My mask text...');
var task = new Ext.util.DelayedTask(function() {
customMask.fadeOut({
duration : 500,
remove:true
});
});
task.delay(1000);
Normally when a event is triggered in a first component, caused, for example, the loading of a grid in the second component, the mask appears in both components in order to avoid user errors by clicking on the first component as the second component is loading the grid or is loading the mask.
In this case:
var componentToMasK = Ext.ComponentQuery.query('#myParentComponent')[0]; //HBox, BBox layout, tab, etc. with the two child components
Hope this helps!
Edit: 10-06-2015
The 'duration:500' and the 'delay(1000)' is only to illustrate. You can adjust these values to the needs of each component that you apply a mask.
If you remove the mask abruptly the user can not even see
loading the message, that's why I use fadeOut.
Thus, you can apply a mask on virtually any component such as, for example, a fieldset, when you add it fields dynamically.
task -> http://docs.sencha.com/extjs/5.1/5.1.0-apidocs/#!/api/Ext.util.DelayedTask
Ex.get -> http://docs.sencha.com/extjs/5.1/5.1.0-apidocs/#!/api/Ext-method-get
fadeOut - > http://docs.sencha.com/extjs/5.1/5.1.0-apidocs/#!/api/Ext.dom.Element-method-fadeOut
You can also do the following:
var task = new Ext.util.DelayedTask(function() {
Ext.getBody().unmask();
});
task.delay(1000);
You can read more about this technique in the book: Mastering Ext JS - Second Edition (Loiane Groner)
Edit: 10-06-2015
One more detail:
If we apply one mask on a Hbox layout, containing as one of the childs a grid, we have two mask: HBOX mask and grid mask.
In these cases, I turn off dynamically the grid mask:
var grid = Ext.ComponentQuery.query('#griditemId')[0];
if(grid){
grid.getView().setLoading(false);
}
Hope this helps.
I want to scroll my ListView on a specific item automatically. The ListView must auto-scroll to an item from his index.
listView.ensureVisible(itemIndex);
But it's not working. Another alternative:
yourListView.currentItem = { index: 8, hasFocus: true, showFocus: true }
And it's failed also.
How can this be solved?
Generally you have to wrap your call to ensureVisible(index) in a call to msSetImmediate. Not sure exactly why this is the case, probably a bug, but works for me. Example:
msSetImmediate(function (){ listView.ensureVisible(4);} );
If you look at the documentation for setImmediate (msSetImmediate being a Microsoft specific implentation), the function is described as:
Requests that a function be called when current or pending tasks are complete, such as events or screen updates.
This does make a bit of sense as it sounds like it ensures that all list view animating etc is completed before making your call to ensure an item is visible.
See this thread for a related post: http://social.msdn.microsoft.com/Forums/en-US/winappswithhtml5/thread/2f11e46f-9421-4e31-93d3-fca06563ec41/
I might have an answer for you. You can't set the scroll position of a ListView immediately because the layout for the ListView has not been done yet and so attempting to scroll it to a position is futile. So you have to wait until the ListView gets to that state where its layout has been calculated. Here's how...
myListView.onloadingstatechanged = function () {
if (app.sessionState.homeScrollPosition && myListView.loadingState == "viewPortLoaded") {
myListView.scrollPosition = app.sessionState.homeScrollPosition;
app.sessionState.homeScrollPosition = null;
}
};
You can see this in context by looking at the /pages/home/home.js file in my open source codeSHOW project.
Hope that helps.
I'm creating a notification system for a game to work similar to how notifications might work in a phone.
The notifications are all created initially, hidden, and later the game is supposed to "activate" certain ones from in-game triggers.
I'm running into problems when trying to keep the notifications separate in terms of their classes. Each notification starts off as a small rectangular box with only the title visible. Upon clicking, the notification expands and the description becomes visible.
Right now, clicking a notification does expand that notification and display its notification, but any other notifications also show their descriptions as well.
Example code:
var NotificationItems = new Array();
scope.registerNotification = function(title, description)
{
//add it to the array
NotificationItems.push(new scope.Application(title, description));
var $NotificationContainer = $("#NotificationContainer");
$NotificationContainer.append('<div class="Notification" title="'+title+'"></div>');
var $thisNotification = $NotificationContainer.children('.Notification[title='+title+']');
$thisNotification.append('<div class="NotificationTitle">'+title+'</div>');
$thisNotification.append('<div class="NotificationDescription">'+description+'</div>');
$(".NotificationDescription").hide();
$thisNotification.click(function()
{
$(this).toggleClass('expanded');
$('.NotificationDescription').slideToggle('slow');
});
}
How can I get the .NotificationDescription to be uniquely recognized for each notification?
You could try the .children() method: jQuery docs for children method
$thisNotification.click(function()
{
$(this).toggleClass('expanded').children('.NotificationDescription').slideToggle('slow');
});
Just find the right one for the clicked element:
$thisNotification.click(function()
{
$(this).toggleClass('expanded');
$(this).find('.NotificationDescription').slideToggle('slow');
});
You can chain the calls if you like:
$thisNotification.click(function()
{
$(this).toggleClass('expanded').find('.NotificationDescription').slideToggle('slow');
});
You might want to try out event delegations.
$('#NotificationContainer > div.Notification').live('click',function()
{
$(this).toggleClass('expanded').find('div.NotificationDescription').slideToggle('slow');
});
This way you only need to attach the event once (on init), and a single event handles all the notifications.
You also should add all your html at one time:
var $NotificationContainer = $("#NotificationContainer");
var $Notification = $('<div class="Notification" title="'+title+'"></div>');
$Notification.append('<div class="NotificationTitle">'+title+'</div>');
$Notification.append('<div class="NotificationDescription">'+description+'</div>');
$NotificationContainer.append($Notification);
notice the subtle difference, we're building the elements in jquery rather than the dom, and append them all at once.