Polymer core-menu double event - javascript

I'm using Polymer for my web-app and I'm currently having some issues with page linking. It's the first site I'm making using all ajax/javascript, thus I haven't used the history functions of javascript a lot yet.
Anyhow, I have a main menu in the left sidebar. When one of those is pressed, it should change the url of the browser and also put it in the history of the browser. To do so I have the following code:
Polymer('my-app', {
mainMenu: function(){
this.$.mainPages.selected = this.$.mainMenu.selected;
console.log("Pusing state " + this.$.mainMenu.selected);
history.pushState(null , "title", this.$.mainMenu.selected);
}
});
Now the problem is that it gets called twice, so if you click once the pushState is called twice. Needless to say this is not good.
I have made a sample code here: click me.
In this sample code you can see after pressing a couple of menu-items, first of all the event gets fired two times. I've also noticed when pressing the back-button, updating the page seems to also refire the pushState.
So in short, my first concern is that the event of pressing an element from the core-menu, the mainMenu function is called twice. Second concern is that I am repushing states when going back, which I presume should be prevented as well.
Updated code:
Polymer('my-app', {
ready: function(){
var link = document.URL.split("/");
this.$.mainMenu.selected = link[link.length-1];
console.log("ready with link: "+link[link.length-1]);
window.onpopstate = function(){
var link = document.URL.split("/");
console.log("Calling onpopstate. New link is: "+link[link.length-1]);
this.$.mainMenu.selected = link[link.length-1];
this.$.mainPages.selected = this.$.mainMenu.selected;
}
},
nav: function(){
this.$.drawerPanel.togglePanel();
},
back: function(){
window.history.back();
},
mainMenu: function(){
console.log("Pusing state " + this.$.mainMenu.selected);
this.$.mainPages.selected = this.$.mainMenu.selected;
history.pushState(null , "title", this.$.mainMenu.selected);
}
}

You are much better off using data-binding. The idea here is to make your element model-driven, an model-view-presenter (MVP) pattern. The model is made up of properties in your element, the view is described by the template, and the presenter is in your script. Data-binding allows us to loosely couple the view from the logic.
Let's decide the current page will be controlled by a property called page.
Then we can set up our UI to be driven by the page property:
<core-menu selected="{{page}}" valueattr="id">
...
</core-menu>
<core-animated-pages class="main" selected="{{page}}" valueattr="id" transitions="slide-from-right">
This is good because we have decoupled the UI from the code. The code never talks directly to the core-animated-pages or the core-menu (notice I removed the ids). We don't listen to any events. This way we can change the UI at will without having to modify the script.
Now, we want the page to be initially selected by the URL, so we'll initialize it that way, or default to 'home'. We also want to synchronize page to back events, so we'll listen to `onpopstate'.
ready: function() {
// scrape the initial page off the window location
this.page = location.hash.slice(1) || 'home';
// listen for 'back' events
addEventListener('popstate', this.popstate.bind(this));
},
popstate: function(event) {
// comes here whether we went 'back' from code or UI
this.poppedPage = this.page = event.state;
}
We keep track of poppedPage so we can differentiate back and forward. We only want to push a new state when we go forward.
Now we need the page to be reflected in the history, so we need to push state when the page changes. As above, the one caveat is that we need to only push the state if we are going forward.
pageChanged: function() {
// if the selected page changes, push a state (unless we are going backward)
if (this.poppedPage !== this.page) {
history.pushState(this.page, "Title", '#' + this.page);
}
}
Here it is all put together:
http://jsbin.com/luwitudu/9/edit

You get two events, one for the deselection of the currently selected item and one for the new selected item. Check the event object that is passed to your callback function:
mainMenu: function(e) {
console.log(e.detail.isSelected);
console.log(e.detail.item);
}
But if you use on-core-activate your callback is only called once when a menu item is tapped by the user (it is not called when you change the menu selection programmatically.) This prevents the unwanted window history push when you change the menu selection in your program.
Then you should setup a window.onpopstate callback in which you select the (previous) menu item. If you call document.URL in you back() function, it will return the current URL, not the new one set by window.history.back(). Instead only pop from the history in this function.
This should work:
<core-menu ... on-core-activate="{{mainMenu}}">
Polymer("my-app", {
ready: function() {
var self = this;
this.$.mainMenu.selected = "home";
history.pushState(null, "Title", this.$.mainMenu.selected);
window.onpopstate = function() {
var link = document.URL.split("/");
self.$.mainMenu.selected = link[link.length-1];
self.$.mainPages.selected = self.$.mainMenu.selected;
};
},
mainMenu: function() {
this.$.mainPages.selected = this.$.mainMenu.selected;
history.pushState(null, "Title", this.$.mainMenu.selected);
},
back: function() {
window.history.back();
}
});

Related

J-Query Context Menu updating via Knockout call

We are implementing a rather complex single page application and decided to use the "Jquery-context-menu" toolbox. However, we have a fundamental question, and after two days of searching, and reading stuff on the web we are somewhat out of ideas.
The basic question is: If and if yes, how, it will be possible to access the update function (i.e the disable function) from outsite the menu, while the menu is still upon?
The scenario:
We are implementing a game. We use the "jquery context menu" as a context menu for an icon that can be activated by the user (think of a rpg type of icon). After activating it via a click it will be deactvitated automatically (after a few seconds) and then be ready for reactivation again after a while. What we what is that the menu is capturing this state of the icon, by enabling or disabling the menu, while the menu is held open.
There is one example of how to change the visibility of a menu item, via a button press in the same menu, as can be found here:
http://medialize.github.io/jQuery-contextMenu/demo/disabled-changing.html
However, we aim to update the menu from outsite (via a knockout subscribe call). So we would like to do something like:
myknockoutobservable.subscribe(function(newValue){
correctRefernceToThis.data('disableItem1', newvalue)});
while the disabled function looks somehitng like:
disabled: function(key, opt) {
return !this.data('cutDisabled')};
OR, if this does not work we maybe could directly call the update fucntion
myKnockoutobservable.subscribe(function(newValue){
$.contextMenu.op.update(correctRefernceToOpt,correctRefernceToroot)});
and then querying the knockout observable in the callback
if (!myKnockoutobservable) {
return true;}
else {
return false;
The main problem seems to be that we don't reference the context correctly, so we don't have a handle on the correct this, opt, to root, variables, from outside of the Jquery context menu (At least that is our current opinion). We would be very happy if someone could help us finding a solution, or even some good ideas what to try (what we haven't yet).
Thanks you for your help! I understand your approach and it is indeed exactly what we have done:-) I might have not been so clear on this issue but this is the current code od the disabled callback.
disabled: function(key, opt) {
if (!self.item._blocks.Feature._processedStack().canBeActivated()) {
return true;
}
else {
return false;
}
}
This works such that when the menu in closed and reopned again the state is updated. However the update is NOT working while the menu is still open because nothing is triggering the menu to update to the new value.
At the moment we are trying to solve the issue with a different library, will keep you updated.
best, Caspar
You won't subscribe, you'll just have an observable in your viewmodel that you set to true or false, and the menu item will toggle in response. The disabled member of your menu item will look like this:
disabled: function() {
return myobservable();
}
As James Thorpe commented, you'll want to create a custom binding handler to set up your context menu.
It sounds like you're working with several unfamiliar tools. Here is a Fiddle with a minimal example: http://jsfiddle.net/sv3m7ok8/
Update
It occurred to me that since the context menu doesn't bind to a single element, but uses a selector, it makes more sense to do the binding on the body tag. So an updated example: http://jsfiddle.net/sv3m7ok8/1/
Updated again
I now understand that you are specifically trying to get the menu item to enable/disable while the menu is open (and that it doesn't do that normally). I had to go under the covers to get at the menu item node, and hook up a subscription to set the disabled class on it.
init: function (element, data, allbindings, data) {
var dynamicDisable = false;
$.contextMenu({
selector: '.context-menu-one',
callback: function (key, options) {
var m = "clicked: " + key;
window.console && console.log(m) || alert(m);
},
items: {
"edit": {
name: "Clickable",
icon: "edit",
disabled: function (opt, key) {
if (!dynamicDisable) {
var node = key.items.edit.$node[0];
data.disableMenu.subscribe(function (dis) {
$(node)[dis ? 'addClass' : 'removeClass']('disabled')
});
dynamicDisable = true;
}
return data.disableMenu();
}
}
}
});
}
My fiddle sets an interval to toggle disableMenu. You can watch the menu item become active and gray.
http://jsfiddle.net/sv3m7ok8/3/

popupbeforeposition event not firing after page refresh

In a nutshell..
the popupbeforeposition event fires every time I open a popup on a page in my jquery mobile project UNTIL I use
$.mobile.changePage(urlObj.hrefNoSearch, {data: postData, type: 'post', reloadPage: forceReload});
to force a refresh of the current page, after-which the 'popupbeforeposition' event never fires.
To elaborate and show my code..
I have a page (id="page_manage-buildings") in my jquery mobile project.
This page contains a link that opens a popup:
open popup
The html for the 'manage_buildings_image_picker' popup contents is pretty straight forward:
<div data-role="popup" id="manage_buildings_image_picker" data-transition="pop" class="ui-content" data-history="false" data-corners="false" data-theme="d" data-overlay-theme="a" style="width:300px; height:150px;">
... popup contents here ...
</div>
I have the 'pageinit' event bound to the '#page_manage-buildings' page and within that I am binding the 'popupbeforeposition' event to the 'manage_buildings_image_picker' popup, like so:
$(document).on("pageinit", "#page_manage-buildings", function() {
console.log('#page_manage-buildings page - pageinit event -- (only once for THIS page)');
$( "#manage_buildings_image_picker" ).on({
popupbeforeposition: function(event, ui) {
console.log('popupbeforeposition event');
}
});
});
Everything works fine until I click a a button labeled 'refresh' that actually runs custom code to reload the current page using $.mobile.changePage to force a reload of the current page. (I need to retain a session variable that is passed from page to page). Anyway, after I click on my refresh button, the 'popupbeforeposition' event is no longer fired when opening the 'manage_buildings_image_picker' popup.
This is the code that is executed when the 'refresh' button is clicked:
convert_get_to_post($(location).attr('href'), true);
function convert_get_to_post(urlStr, forceReload){
postData = new Object;
var urlObj = $.mobile.path.parseUrl(urlStr);
if(urlObj.search){
// -- ensure any query string parameters are sent as post data
var dataStr = urlObj.search.toString();
dataStr = dataStr.replace(/^\?/,'');
dataPairs = dataStr.split('&');
//console.log('here is the dataPairs');
//console.log(dataPairs);
var avsessionFound=0;
for (x in dataPairs) {
dataPair = dataPairs[x];
//console.log(dataPair);
var dataSet = dataPair.split('=');
//console.log('here is the dataSet');
//console.log(dataSet);
postData[dataSet[0]]=dataSet[1];
if(dataSet[0]=='avsession'){
avsessionFound=1;
console.log('avsession found: '+dataSet[1]);
}
}
if(avsessionFound==0){
// inject the avsession value into the post data
postData['avsession'] = $.jStorage.get('avsession', '');
}
//console.log('here is the postData');
//console.log(postData);
// send this request as a post
$.mobile.changePage(urlObj.hrefNoSearch, {data: postData, type: 'post', reloadPage: forceReload});
}else{
// no query string to worry about jsut send this as a post with the avsession value injected
postData['avsession'] = $.jStorage.get('avsession', '');
$.mobile.changePage(urlObj.hrefNoSearch, {data: postData, type: 'post', reloadPage: forceReload});
}
}
UPDATE #1
I changed the syntax of my ON binding and it fixed the 'popupbeforeposition' event issue but now the 'popupafterclose' event is firing twice when I close the popup. I can deal with that but was wondering if it was caused from something obvious?
$(document).on("pageinit", "#page_manage-buildings", function() {
console.log('#page_manage-buildings page - pageinit event -- (only once for THIS page)');
$(document).on("popupbeforeposition", "#manage_buildings_image_picker", function(event, ui) {
console.log('*** popupbeforeposition event ***');
});
$(document).on("popupafterclose", "#manage_buildings_image_picker", function(event, ui) {
console.log('*** popupafterclose event ***');
});
});
Update #2
Actually the code in update #1 caused the bind events to get bound pageview+(n-1) times where n is equal to the number of times that page was viewed. This is because the events are bound at the document level (which stays the same in JQM) and is only ever updated as pages are loaded/unloaded. I used Omar's suggestion and unbound the event with OFF befor binding the event which ensures there is only ever one event firing.
$(document).off("popupbeforeposition", "#manage_buildings_image_picker").on("popupbeforeposition", "#manage_buildings_image_picker", function(event, ui) {
console.log('*** popupbeforeposition event ***');
});
binding events used only on a specific page at the document level seems messy and I would still like to know why it won't work when binding it to an element that exists within that page ??

Windows 8 Grid Template JS App, CSS manipulations dont show on all selected items in groupedItems view

I'm using the Win8 Grid View Template to display infos from a news site. In the lower menu bar i have implemented a function wich shuts off the titles, so that only the pictures are still visible.
This function is in a "global.js" file which is included in the "default.html" so it's available everywhere and it looks like this:
//function to turn titles off and on
function titleToggle() {
var titles = document.getElementsByClassName("item-overlay");
for (var i = 0; i < titles.length; i++) {
if (Global.titlesAreOn) {
titles[i].style.display = "none";
}
else {
titles[i].style.display = "";
}
}
Global.titlesAreOn = !Global.titlesAreOn;
};
So when i call this function from the menu bar it works for the first items, but when i scroll the end of the groupedItems view (hubview) the titles are still there. When i then scroll back to the beginning the titles are there again too.
I'm also calling the titleToggle function from the ready() function of the "groupedItems.js" to check whether or not to display the titles depending on a global variable. When i do that (whenever i come back to the hubpage) it works all the way, just as expected.
ui.Pages.define("/pages/groupedItems/groupedItems.html", {
navigateToGroup: function (key) {
nav.navigate("/pages/groupDetail/groupDetail.html", { groupKey: key });
},
ready: function (element, options) {
appbar.winControl.disabled = false;
appbar.winControl.hideCommands(["fontSizeBt"]);
appbar.winControl.showCommands(["titleToggle"]);
if (Global.titlesAreOn == false) {
Global.titlesAreOn = true;
Global.titleToggle();
}
I made a short video to show the problem, because its kinda hard to explain --> http://youtu.be/h4FpQf1fRBY I hope you get the idea?
Why does it work when i call it from the ready() function?
Does anyone have an idea? Is it some kind of automatic item caching in order to have better performance? And how could this be solved?
Greets and thanks!
First, here is why this might be happening - WinJS is using single page navigation for the app experience. This means that when you navigate to a new page, actually you don't. Instead the content is removed from the page and the new content is loaded in the same page. It is possible that at the moment you press the button not all elements have been loaded in the DOM and therefore they cannot be manipulated by your function. This is why when you call it from the ready() function it works - all contents are loaded in the DOM. It is generally better to do things in the ready() function.
About the behavior when you slide back left and the items are again reloaded with titles - for some reason the listView items are reloading. Maybe you are using live data from the news site and they are refreshing with the listView control's template again. I cannot know, but it doesn't matter. Hiding the elements is not the best approach I think. It is better to have two templates - one with a title element and one without. The button click handler should get the listView controls(they have to be loaded) and change their templates.
ready: function (element, options) {
var button = document.getElementById('btn');
button.addEventListener("click", btnClickHandler);
}
And the handler:
function btnClickHandler(e) {
var listView = document.getElementById("listView").winControl;
var template2 = document.getElementById("template2");
listView.itemTemplate = template2;
};

YUI TabView: "add tab" button stuck on the left side when all tabs are closed

I'm using the YUI TabView widget for adding and removing tabs like described in yuilibrary-tabview-add-remove.
I've noticed a "bug" or maybe just a missing functionality: When you close all tabs and then add a new tab, the "add tab" button will get stuck on the left side of the tab bar, and all new tabs will be sorted on the right side. If you don't close all tabs, the button will always stay on the right side no matter what.
Now, I've added a workaround: When adding a new tab, the no-tabs state will be detected and the DOM li-item will be sorted with the jQuery after() method. Finally, the newly added tab will be selected:
onAddClick : function(e) {
e.stopPropagation();
var tabview = this.get('host'), input = this.getTabInput();
tabview.add(input, input.index);
// When previously no tabs present, move 'add button' to end after adding a new tab
if ( tabview.size() == 1) {
var addTabButton = $('#addTabButton');
addTabButton.next().after(addTabButton);
tabview.selectChild(0);
};
}
However, I'm not happy with this solution. Might there be a more elegant way to solve this issue?
Your solution is definitely valid. I'd just write it using YUI because loading YUI and jQuery is really expensive in kweight and maintenance cost (you and your coworkers need to master two libraries).
One clean option is to create a node in the initializer and keep a reference to it so that you can move it around later:
initializer: function (config) {
var tabview = this.get('host');
// create the node before rendering and keep a reference to it
this._addNode = Y.Node.create(this.ADD_TEMPLATE);
tabview.after('render', this.afterRender, this);
tabview.get('contentBox')
.delegate('click', this.onAddClick, '.yui3-tab-add', this);
},
_appendAddNode: function () {
var tabview = this.get('host');
tabview.get('contentBox').one('> ul').append(this._addNode);
},
afterRender: function (e) {
this._appendAddNode();
},
onAddClick: function (e) {
e.stopPropagation();
var tabview = this.get('host'), input = this.getTabInput();
tabview.add(input, input.index);
// When previously no tabs present, move 'add button' to end after adding a new tab
if ( tabview.size() == 1) {
// _addNode will already be present, but by using append() it'll be moved to the
// last place in the list
this._appendAddNode();
};
}
Here's a working version: http://jsbin.com/iLiM/2/

Sencha Touch: update view during function

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

Categories