Ionic router: determining how a view has been entered - ionic-framework

I am using Ionic v1.0.0-rc.2.
As controller + views are cached in Ionic, one has to take care to properly initialize the controller $scope.
I am using the following callback to do this:
$scope.$on('$ionicView.beforeEnter', function(){
...
}
However, to know how to initialize the $scope I need to know how the view was called:
Was it called by a back button (A --> B --> back to A)
in this case I want to leave most items as is
Was it returned to after pressing a back button (A --> B --> back to A --> return to B)
in this case I want to leave most items as is
Was it entered following a new path (not back or forward) ... note that the the view may have already been instantiated from previous navigations
in this case I want to re-initialize the view
I have been looking at the documentation below, but could not find a way of doing this, or what parameters, if any, are passed into the callback function. Any documentation pointers would be great.
http://ionicframework.com/docs/api/directive/ionView/
http://blog.ionic.io/navigating-the-changes/

To determine what's going on in routes I chain this:
// Add state change hooks to log issues to console
.run(['$rootScope', '$state', '$urlMatcherFactory', function($rootScope, $state, $urlMatcherFactory) {
$rootScope.$state = $state;
function message(to, toP, from, fromP) { return from.name + angular.toJson(fromP) + " -> " + to.name + angular.toJson(toP); }
$rootScope.$on("$stateChangeStart", function(evt, to, toP, from, fromP) { console.log("Start: " + message(to, toP, from, fromP)); });
$rootScope.$on("$stateChangeSuccess", function(evt, to, toP, from, fromP) { console.log("Success: " + message(to, toP, from, fromP)); });
$rootScope.$on("$stateChangeError", function(evt, to, toP, from, fromP, err) { console.log("Error: " + message(to, toP, from, fromP), err); });
}])
This should get you going in the right direction with some customization.

Related

Meteor/Blaze/Mongo/Leaflet - Dynamically filled Leaflet popups do not pass data to button for entry into database?

I'm working with a Leaflet map that displays marker data based on a MongoDB query. The query results are saved into a variable (I know, bad form for large volumes of info but okay if you only have ~25 pieces) as an array, and then I've iterated over that variable and it's stored information using a for loop to create my leaflet map markers and populate their popups with the information specific to each entry. This part works great.
this.autorun(function(){
fsqresults = FsqResults.find().fetch({});
container = $('<div />');
for (i=0; i < fsqresults.length; i++) {
marker = L.marker([fsqresults[i].geometry.coordinates[1], fsqresults[i].geometry.coordinates[0]], {icon: violetIcon}).addTo(mymap);
container.html("<b>" + "Name: " + "</b>" + fsqresults[i].properties.name + "<br>" +
"<b>" + "Address: " + "</b>" + fsqresults[i].properties.address + "<br>" +
"<b>" + "Checkins: " + "</b>" + fsqresults[i].properties.checkIns + "<br>" +
"<b>" + "Users: " + "</b>" + fsqresults[i].properties.usersCount + "<br>" +
"<b>" + "Tips: " + "</b>" + fsqresults[i].properties.tips + "<br>");
marker.bindPopup(container[0]);
} // end for loop
For each marker, there is a button to log a "checkin" event to another Mongo collection to house the checkin entries. The button fires an event successfully, and creates the entry into the second database, but will not bind the dynamically populated data to the entry so I can see which marker the user has clicked on.
container.append($('<button class="btn btn-sm btn-outline-primary js-checkin">').text("Check In"));
container.on('click', '.js-checkin', function() {
var currentVenue = fsqresults[i].properties.name;
console.log(currentVenue);
console.log("You clicked the button!");
if (!Meteor.user()) {
alert("You need to login first!");
}
if (Meteor.user()) {
console.log("Meteor User Verified");
Checkins.insert({user: Meteor.user(), name: currentVenue});
}
});
}); // end this.autorun
The console tells me that currentVenue is undefined. I know this has something to do with the fact that fsqresults is a dynamically populated variable. I have tried to find ways to "solidify" the information in it (i.e. - creating a second variable with an empty array, pushing the data from fsqresults into it, and then having the markers iterate over that variable) but that hasn't worked as the MongoDB query results, despite being in an array format themselves, will not push or concat into the variable with an empty array successfully.
I've been searching for an answer to this problem and I'm coming up short. I'm lost; is there any other solution which could be staring me in the face?
Some things to note: All of this code lives in the Template.map.onRendered() function. Leaflet has scoping issues if I delegate the code into helpers and events, which is why I haven't created a {markers} template and just done {{#each markers}} over it for iteration. Therefore I am relegated to jQuery style coding for creating DOM elements and firing event triggers. The code above is wrapped in a this.autorun function to ensure it does indeed run upon map rendering. I don't think this is the issue (although one can never rule it out!).
As pointed out in the question comments, you have a scope issue with your i index iterator, and there should be no technical problem in integrating Leaflet with Meteor (although with Blaze that may not be totally trivial nor interesting).
1. Iteration scope issue
The console tells me that currentVenue is undefined.
That is because you try to access fsqresults[i].properties.name in your container.on('click' event listener / callback, which will be called on user click, i.e. after your for loop is complete, hence your i index iterator variable will be equal to fsqresults.length.
You are in the case of Example 6 of the accepted answer of: How do JavaScript closures work?
2. Leaflet integration with Meteor (Blaze)
Since you mention having tried helpers, events, and {{#each markers}}, I assume you use Blaze as your Meteor rendering engine.
While React-Leaflet and Vue2Leaflet indeed offer the possibility to use a kind of "<Marker>" component (same for other types of Leaflet Layer), the latter is only for template declaration purpose, i.e. it does not directly render any DOM / HTML, but only calls some Leaflet methods, which will be in charge of manipulating the DOM. As stated on React-Leaflet limitations:
The components exposed are abstractions for Leaflet layers, not DOM elements.
Side note: interesting to see that angular-leaflet-directive and #asymmetrik/ngx-leaflet did not fall into the same temptation and sticked to JS declaration of Leaflet layers.
Therefore trying to create a Template.Marker (used as {{> Marker}}) in Blaze might be overkill, as you would basically just call some Leaflet factories (like L.marker) within your Template.Marker.onCreated (and needing to access somehow the parent map object to add your Marker into…), without rendering any DOM node yourself (i.e. you would have an HTML file with empty <template name="Marker"></template>).
While we forget about a Marker template in Blaze (as you have already done), we can still leverage Blaze events management to handle user clicks in your Leaflet Popup. For that, we would need a few Blaze features, that I admit could benefit being better documented:
We can attach arbitrary JS data to our template instance.
Template events are delegated, hence we do not need to attach them to each <button> before hand.
We can easily access the template instance in event handlers (as the 2nd argument of the event listener).
2.1. Attaching arbitrary JS data to our template instance
As stated in the Template Instances API:
[…] you can assign additional properties of your choice to the object. Use the onCreated and onDestroyed methods to add callbacks performing initialization or clean-up on the object.
Therefore you can store your fsqresults on your Template instance, so that you can access it later on (typically in your event listener):
Template.myTemplate.onRendered(function () {
this.autorun(() => { // Using an arrow function to keep the same `this`, but you could do `const self = this` beforehand.
const fsqresults = this.fsqresults = FsqResults.find().fetch();
});
});
But since we want to access specific Features later on, it might be more interesting to convert fsqresults to a dictionary. Since your ID seems to be feature.properties.name, you could do:
Template.myTemplate.onCreated(function () {
this.autorun(() => {
const fsqresults = this.fsqresults = FsqResults.find().fetch();
const markersDict = this.markersDict = {};
L.geoJSON(fsqresults, {
pointToLayer(feature, latlng) {
const props = feature.properties;
const markerName = props.name;
// Save a direct reference to the Feature data,
// using the `markerName` as key (ID).
markersDict[markerName] = feature;
// Store the `markerName` in the button `dataset`
// (i.e. as a `data-` attribute),
// as already suggested in the question comments,
// so that we can easily retrieve the ID / key
// of the Marker data associated with the button the user clicked on.
return L.marker(latlng).bindPopup(`
<p>${markerName}</p>
<button role="popupClick" data-marker-name="${markerName}">
Popup action
</button>
`);
},
});
});
});
2.2. Template event handler delegation
As stated on the Blaze Overview Details:
DOM engine […] which features […] event delegation
(sorry there does not seem to be any other mention of this feature in the official doc… let me know if you find a better one!)
Therefore, as long as we create a Template event handler with the appropriate selector, we do not need to attach the event listener on each button, which anyway we may not create as a Node but leave it as String passed to Leaflet .bindPopup (as done in the above code sample).
For example:
Template.myTemplate.events({
// Even if the `<button role="popupClick">` are not DOM nodes yet
// (because Leaflet will create them from the HTML String
// only when the user opens the popup by clicking on the Marker),
// the "click" event will bubble up to the template instance,
// which will call this event handler if it matches the selector.
'click button[role="popupClick"]'() {
console.log('clicked on a button that has been built in a Leaflet Popup');
}
});
2.3. Access the template instance, and our Feature data
The Blaze event handler are called with an extra 2nd argument, which is the current template instance:
The handler function receives two arguments: event, an object with information about the event, and template, a template instance for the template where the handler is defined.
Therefore in our case we can easily retrieve the markersDict variable that we have defined in onCreated, and use it to retrieve the exact Marker's Feature data associated with the button the user clicked on:
Template.myTemplate.events({
'click button[role="popupClick"]'(event, templateInstance) {
const button = event.currentTarget;
const markerName = button.dataset.markerName;
const markerFeature = templateInstance.markersDict[markerName];
// Do something with `markerFeature`…
console.log(markerFeature);
}
});
If you only need the property name, then you could even skip step 2.1 and directly use the markerName string retrieved from the <button> dataset.
I've come up with a solution to the first part of my issue - at first a javascript closure/scope issue to the inner and outer function scopes. I spent about 2 days wrapping my head around this SO answer: the concept of using the first for loop to produce individual instances of the function (if this were a play, the first for loop would "set the stage" for the show), and using the second for loop to execute each instance of the function ("lights, camera, action!").
I also decided that I could maintain scope if I declared my variables inside the first for loop - but I still had this issue of it only pulling the last value. Then I tried simply redeclaring my variables as constants. To my surprise, using const allowed me to write each instance to each map marker, and I could reliably access the correct iteration of the data upon each correspondent map marker! So no need for a second for loop.
this.autorun(function(){
fsqresults_fetch = FsqResults.find().fetch({});
// console.log(fsqresults_fetch);
for (i = 0; i < fsqresults_fetch.length; i++) {
container = $('<div />');
const fsq_marker = L.marker([fsqresults_fetch[i].geometry.coordinates[1], fsqresults_fetch[i].geometry.coordinates[0]], {icon: blueIcon}).addTo(mymap);
const fsq_venueAddress = fsqresults_fetch[i].properties.address;
const fsq_venueName = fsqresults_fetch[i].properties.name;
const fsq_geometry = {type: "Point",
coordinates: [fsqresults_fetch[i].geometry.coordinates[0], fsqresults_fetch[i].geometry.coordinates[1]]};
container.html("<b>" + "Name: " + "</b>" + fsqresults_fetch[i].properties.name + "<br>" +
"<b>" + "Address: " + "</b>" + fsqresults_fetch[i].properties.address + "<br>");
container.append($('<button class="btn btn-sm btn-outline-primary" id="js-checkin">').text("Check In"));
fsq_marker.bindPopup(container[0]);
container.on('click', '#js-checkin', function() {
console.log("You clicked the button!");
if (!Meteor.user()) {
alert("You need to login first!");
}
if (Meteor.user()) {
console.log("Meteor User Verified");
Checkins.insert({type: "Feature", geometry: fsq_geometry, properties: {name: fsq_venueName, address: fsq_venueAddress, user: Meteor.user()}});
}
}); //end container.on
} //end for loop
}); //end this.autorun
As I said in the comment on the last response, it's a bit hack-y, but functional enough to do the job successfully.
Now what I'm really curious to try is the solution that #ghybs posted so I have my events grouped and firing as Blaze is supposed to work!

sap.m.TileContainer scrollIntoView issue

I have an XML view that contains a TileContainer which is bound to a model that is used to create StandardTiles. The XML snippet is:
<TileContainer id="tilelist" tiles="{Applications}">
<tiles>
<StandardTile name="{ID}" icon="{Icon}" title="{Name}" press="doNavigation" info="{Description}"
number="{path : 'Number', formatter: 'linxas.com.fiori.launchpad.util.Formatter.formatUsingURL'}"
numberUnit="{NumberUnit}"/>
</tiles>
</TileContainer>
This is working perfectly, the correct tiles are getting displayed etc. When I click on a tile, there is navigation that occurs and I want to "remember" which tile was clicked (by index) so when returning I can scroll to that tile. This is done on the tile's press event handler (doNavigation function) and stores the index in sessionStorage. This is also working properly.
doNavigation : function (evt) {
if (sessionStorage && this.getView().byId('tilelist')) {
sessionStorage.setItem("selected_tile", this.getView().byId('tilelist').indexOfTile(evt.getSource()));
}
...
}
The proper value is stored. So when navigating back, within the onAfterRendering function of the page that contains the TileContainer I have the following code. It is attempting to see if there is a "selected_tile" value stored in sessionStorage, if so it calls scollIntoView passing in the tile index. The issue is that this code is executed, but doesn't work and I suspect it is because at the time of calling this function, the TileContainer's tiles aggregation is returning 0 length.
onAfterRendering : function (evt) {
var theList = this.getView().byId("tilelist");
if (sessionStorage && theList) {
var tile_index = sessionStorage.getItem("selected_tile");
console.log(tile_index + " of " + theList.getTiles().length);
if (tile_index) {
theList.scrollIntoView(+tile_index, true);
sessionStorage.removeItem("selected_tile");
}
}
}
My console output looks something like this (based on the tile that was clicked):
5 of 0
Any help would be appreciated. I assume that there is somewhere else that I need to execute this last bit of code as the TileContainer does not seem to be finished processing its tiles at this point, at least that is my assumption of why the tiles aggregation is 0.
Are you using Routing in your project?
If yes, you can try to register a method to handle the routePatternMatched event of the router. This method will be called after the onAfterRendering method - if the proper route pattern is matched.
To achieve this, just create the following:
onInit: function() {
sap.ui.core.UIComponent.getRouterFor(this).getRoute("NameOfYourCurrentRoute").attachPatternMatched(this._routePatternMatched, this);
},
_routePatternMatched: function(oEvent) {
//do your stuff here
},
Hopefully the TileList is ready at this point to navigate to the correct tile.

jQuery - Refresh Contents of a DIV

I have a form in a jQuery popup on a webpage. The jQuery popup is a div named .vote-form and the form inside it has the name "#form".
When the form is submitted, the content inside the jQuery popup changes to a success message. I need to make it so that when the jQuery popup is closed, the success message is removed and the form is refreshed back to the original form, so that when the jQuery popup is opened again, the form is showing again and NOT the success message.
My feeble attempt to get this result involved refreshing the ENTIRE page when the jQuery popup is closed. This PARTLY has the desired result, but when the page is refreshed, most browsers get a popup asking if the user wants to resubmit the form content. I need to avoid this.
This was my code handling the closing of the .vote-form:
$('.vote-form-close').click(function(event) {
event.stopPropagation();
$(".vote-form").fadeOut("normal");
$("#the-lights").fadeTo("slow",0);
$("#the-lights").css({'display' : 'none'});
window.location.reload();
});
I suspect that its possible to refresh ONLY the div, and not the entire page, but I do not know how to accomplish it.
Can someone assist me?
EDIT: Based on one of the answers below, I modified my code. I also wanted to show the code used to open the form up too:
$('.vote').click(function() {
$(this).parent().find(".vote-form").fadeIn("normal");
$("#the-lights").css({'display' : 'block'});
$("#the-lights").fadeTo("slow",0.7);
});
$('.vote-form-close').click(function(event) {
event.stopPropagation();
$(".vote-form").fadeOut("normal");
$("#the-lights").fadeTo("slow",0);
$("#the-lights").css({'display' : 'none'});
$(".vote-form").load(window.location.href + " .vote-form-container");
});
Here is the problem - I have 3 forms on the page. When "vote-form-container" is loaded, its loading ALL THREE forms into the .vote-form box - how do I modify the code to only load the .vote-form-container that is part of the specific .vote-form - I suspect I have to use $(this) but I tried modifying the code to this and it didnt work:
$(".vote-form")(this).load(window.location.href + " .vote-form-container");
I am thinking I did it wrong.
EDIT 2: Now the "Close" button dosen't work after the form is reloaded the first time:
$('.vote').click(function() {
$(this).parent().find(".vote-form").fadeIn("normal");
$("#the-lights").css({'display' : 'block'});
$("#the-lights").fadeTo("slow",0.7);
});
$('.vote-form-close').click(function(event) {
event.stopPropagation();
$(".vote-form").fadeOut("normal");
$("#the-lights").fadeTo("slow",0);
$("#the-lights").css({'display' : 'none'});
var current_form = $(this).closest('.vote-form'),
index = $('.vote-form').index(current_form)
current_form.load(window.location.href + " .vote-form-container:eq(" + index + ")");
});
Don't reload the page but redirect your user:
window.location.href = window.location.href.toString()
Or load the new form with ajax:
$(".vote-form").load(window.location.href + " .vote-form");
For more information on the ajax approach see api.jquery.com/load/#loading-page-fragments
Update:
Using jQuery the index function you are able to replace only the current form.
// I asume your button is in the form
var current_form = $(this).closest('.vote-form'),
index = $('.vote-form').index(current_form)
current_form.load(window.location.href + " .vote-form:eq(" + index + ")");
... I need to make it so that when the jQuery popup is closed, the success message is removed and the form is refreshed back to the original form...
So, if I well understood:
$('.vote-form-close').click(function(event) {
event.stopPropagation();
var vf = $(".vote-form");
/* fadeout and remove inner content of the popup */
vf.fadeOut("normal", function() { vf.empty(); });
/* reset the form */
document.getElementById('form').reset();
...
});

Jstree dblclick binding problem [duplicate]

I try to use good lib jstree but i have some strange problem with dblclick binding.
Here is my code
$("#basic_html").jstree({
themes: {
url: "http://mywork/shinframework/shinfw/themes/redmond/css/jstree/default/style.css"
},
"plugins" : ["themes","html_data","ui","crrm","hotkeys", "core"],
});
$("#basic_html").bind("dblclick.jstree", function (e, data) {
alert(e);
alert(data);
});
When this code runs and i make dblclick for some node i can see 2 alerts. The first is object -right, the second is undefined - BUT i want receive data information.
Please, if some specialist solve this problem give me right way for correct use dblclick and receive "data" information about node who is i clicked.
Thanks
I recommend this approach . . .
$("#basic_html li").live("dblclick", function (data) {
//this object is jsTree node that was double clicked
...
});
First, you usually only need to know if the li was clicked so monitoring the event on the li will give you everything you need. Secondly, use live or delegate for the event binding so you can manipulate the tree without breaking the event.
Once you have the node that was double clicked (the this object) you can then use the built-in functions like this . . .
if (!jsAll.is_selected(this)) { return false; } //cancel operation if dbl-clicked node not selected
Where . . .
jsAll = $.jstree._reference("basic_html")
$("#basic_html").bind("dblclick.jstree", function (event) {
var node = $(event.target).closest("li");//that was the node you double click
});
that's the code you want.

Jstree : dblclick binding parameter data is undefined

I try to use good lib jstree but i have some strange problem with dblclick binding.
Here is my code
$("#basic_html").jstree({
themes: {
url: "http://mywork/shinframework/shinfw/themes/redmond/css/jstree/default/style.css"
},
"plugins" : ["themes","html_data","ui","crrm","hotkeys", "core"],
});
$("#basic_html").bind("dblclick.jstree", function (e, data) {
alert(e);
alert(data);
});
When this code runs and i make dblclick for some node i can see 2 alerts. The first is object -right, the second is undefined - BUT i want receive data information.
Please, if some specialist solve this problem give me right way for correct use dblclick and receive "data" information about node who is i clicked.
Thanks
I recommend this approach . . .
$("#basic_html li").live("dblclick", function (data) {
//this object is jsTree node that was double clicked
...
});
First, you usually only need to know if the li was clicked so monitoring the event on the li will give you everything you need. Secondly, use live or delegate for the event binding so you can manipulate the tree without breaking the event.
Once you have the node that was double clicked (the this object) you can then use the built-in functions like this . . .
if (!jsAll.is_selected(this)) { return false; } //cancel operation if dbl-clicked node not selected
Where . . .
jsAll = $.jstree._reference("basic_html")
$("#basic_html").bind("dblclick.jstree", function (event) {
var node = $(event.target).closest("li");//that was the node you double click
});
that's the code you want.