I'm using Meteor with Iron-router (if that's relevant). I've made some changes to accommodate the 0.8 update, and I'm still struggling with some things.
Instead of using conditional attributes inside a div in html, I'm using .rendered to set those attributes. But now, when a user logs in, I must either refresh the page or navigate away and back (I don't think the refresh is necessary) so elements behave correctly.
Does anyone know how I can make these attributes behave correctly based on whether a user is logged in or logged out, IMMEDIATELY WHEN THE USER LOGS IN OR OUT?
Template.pt_entry.rendered = function() {
$('#signin').popover(),
$('#reviewModal').on('shown.bs.modal', function () {
$('#review-text').focus();
});
if(Meteor.userId()){
$('#signin').attr( 'data-toggle', 'modal' ),
$('#signin').attr( 'data-target', '#reviewModal' )
} else {
$('#signin').attr( 'data-toggle', 'popover' ),
$('#signin').attr( 'data-container', 'body' ),
$('#signin').attr( 'data-content', 'you must sign in to review' ),
$('#signin').attr( 'data-trigger', 'click' )
//add some logic for displaying error template.
}
}
Actually you have different options:
If you are using iron-router you can generally show different templates in the same route. You can check if the user is logged in and based on this check you can switch between templates. You can maybe check it with a beforeHook:
beforeHooks = {
isLoggedIn: function() {
if(!(Meteor.loggingIn() || Meteor.user())) {
Router.go('landingPage');
this.stop();
}
}
}
Router.onBeforeAction(beforeHooks.isLoggedIn);
Another option is to use the default {{currentUser}} helper
{{#if currentUser}}
<p>User is logged in</p>
{{else}}
<p>User is not logged in</p>
{{/if}}
Maybe the reason why your code doesnt work could be the new blaze engine. The rendered-callback is just fired once Blaze. Normally Meteor.userId() is a reactive datasource so your template gets rerendered but if the rendered callback is just fired once the changes dont get applied. Maybe these minds help you!
Related
Depending on certain conditions, I have to hide a button in the header of my app. The coding in the controller is as follows:
if (condition === true) {
this.byId(buttonId).setVisible(false); //works fine
} else {
this.byId(buttonId).setVisible(true); //also works fine
}
//Code edit: Button is created on the go to check if Adapt UI can still find it.
//However, it does not work still as expected. Button is visible to Adpat UI.
if (condition === true) {
var oButton = new sap.m.Button
({
text : "Save",
type :sap.m.ButtonType.Accept
});
oButton.placeAt('content');
}
Moreover, the inital visiblilty of the button is set to false in the view definition.
<Button id="buttonId" text="Button1" press="onPress" visible="false"/>
In my app, this works as intended; button is hidden. However, if user goes to Adapt UI settings, it shows list of all the available buttons in the header. It also shows button1, even if it is not required in header atm.
What I want to do?
I want to hide button in adapt UI settings also. Button's visibility should be handled only via controller file.
Does anyone know how to achieve this?
Thank you.
You could inject the button with code at run-time when you want to provide user access to it - this way the button does not form part of the View at the outset.
I suspect the Adapt UI will allow access to anything in the Static View as it stands.
In my web app there is a form where users put in data. If they want to navigate to other component or back, I want to notify them that changes were made to form and show them confirmation dialog.
I know how to implement this if users click on back button on top of the form, but don't have any idea how to handle it when users click on sidebar menu options. (there is a sidebar with multiple links to other components - using react-router).
I have tried something using componentWillunmount() but no luck since component gets unmounted in any case.
If anyone has experience in this I would really need some help.
Try the routerWillLeave hook, here is a example from react-router's documentation:
const Home = withRouter(
React.createClass({
componentDidMount() {
this.props.router.setRouteLeaveHook(this.props.route, this.routerWillLeave)
},
routerWillLeave(nextLocation) {
// return false to prevent a transition w/o prompting the user,
// or return a string to allow the user to decide:
if (!this.state.isSaved)
return 'Your work is not saved! Are you sure you want to leave?'
},
// ...
})
)
https://github.com/ReactTraining/react-router/blob/master/docs/guides/ConfirmingNavigation.md
I'm trying to open a modal dialog when someone tries to navigate away from a page with a form that isn't complete. I have the modal template built, but I can't figure out how to implement it. Here's what I have:
actions: {
willTransition: function( transition ){
var model = this.currentModel;
if( model.get( 'isDirty' ) ){
this.render( 'my-modal', {
into: 'application',
outlet: 'modal'
} );
if(!this.get(abortConfirmed) {
transition.abort();
} else {
model.rollback();
}
}
}
}
NOTE: The dirty checking works and I can generate a prompt, but this modal thing is not working
So here's the workflow I use.
1). in the willTransition(transition) hook, do the check to see if you should show the modal.
2). If you should show the modal (in your case, when the model isDirty), call transition.abort(). You must do this to prevent the transition from happening. You also though need a second property on your controller that determines whether or not the transition has been authorized. So really, you check model.get('isDirty) && this.controller.get('transitionAuthorized')
3). You need a way to pass state to your modal or for your modal to be able to communicate back with the page that has created the modal. I personally pass a continueFn and a cancelFn to my modals that close over the current context. Something like
var continueFn = this.createUnsavedDialogContinueFn(this, transition);
where that function is:
createUnsavedDialogContinueFn: function(context, transition){
return function(){
context.controller.set('transitionAuthorized', true);
transition.retry();
}
}
I pass this continueFn to the modal, whose I don't care if I have Pending changes button calls via an action. You can, though, delegate this work back to the controller/route if that feels easier for you. What's important is that you set the transitionAuthorized to true and call transition.retry()
4). calling transition.retry will pass back thru the willTransition but this time you have set transitionAuthorized to true and everything passes through.
You need to stop the transition from occurring. Add transition.abort() at the bottom of your 'isDirty' check.
I'm currently using Backbone.Marionette to create a SPA and in one of the views it is possible for the user to navigate away with unsaved changes. I can control some of these events, like buttons and menu options within the view that would take the user away, but some others would require to manipulate either Backbone.Router or work with the DOM events directly.
I already tried listening to beforeunload (doesn't work as the application is still loaded) and hashchange (doesn't work as you cannot stop the browser from navigating away). These solutions (1, 2, 3) don't work in this case, the Javascript is never unloaded.
Changing the Backbone.Router seems to be the best option, but because of how it is initialized I don't think it is possible to introduce this feature or at least I cannot find a way of doing it. This solution, for example, doesn't work because hashchange is not cancelable (you cannot call stopPropagation on it), and this other solution doesn't work because navigate is not defined on the Backbone.Router object.
Any suggestions?
I've managed to find a solution to this, although some more work is required. For this solution, I am assuming that you keep track when a view is dirty.
There are 4 main ways of moving out of a view;
Click on a link on the view
Click on link outside the view
Click on refresh or external link
Click on back/forward on the browser
1. Application link
This is the easiest case. When you click on your own link, you have to check if your view is dirty. For example, I have an in-app back button that is handled by a historyBack function. On the view:
historyBack: function() {
if (this.isDirty) {
answer = confirm("There are unsaved changes.\n\nDo you wish to continue?")
if (answer) {
this.isDirty = false
window.history.back()
}
}
else {
window.history.back()
}
}
2. Links outside your view
This type of interaction can be handled by extending the Router prototype's execute method, not the navigate method as proposed in other places.
There should be a variable somewhere accessible by the Router that stores the state of the view. In my case, I'm using the Router itself and I update this variable every time I change the dirty flag on the view.
The code should look something like this:
_.extend(Backbone.Router.prototype, {
execute: function (callback, args, name) {
if (Backbone.Router.isDirty) {
answer = confirm "There are unsaved changes.\n\nDo you wish to continue?";
if (!answer) {
return false;
}
}
Backbone.Router.isDirty = false
if (callback) callback.apply(this, args)
}
}
3. Refresh or external link
Refresh and external links actually unload your Javascript so here the solutions based on beforeunload (see question) actually work. Wherever you manage your view, I use a controller but let's assume it's on the same view, you add a listener on show and remove it on destroy:
onShow: function() {
$(window).bind("beforeunload", function (e) {
if (this.isDirty) {
return "There are unsaved changes.";
}
}
}
onDestroy: function() {
$(window).unbind("beforeunload");
}
4. Back/Forward on the browser
This is the trickiest case and the one I haven't figured out completely yet. When hitting back/forward, the user can navigate out of the app or within the app, both cases are covered by the code on 1 and 3, but there is an issue I can't figure out and I will create another question for it.
When hitting back/forward, the browser changes the address bar before calling the router so you end up with an inconsistent state: The address bar shows a different route to the application state. This is a big issue, if the user clicks again on the back button, after saving or discarding the changes, she will be taken to another route, not the previous one.
Everything else works fine, it shows a pop up asking the user if she wants to leave or continue and doesn't reload the view if the user chooses to stay.
In a Meteor app, a large collection containing 1000 records is published to the client. However users loading the {{loginButtons} will experience a 3-5 second lag as it fully renders only after all the large collection loads.
It appears that the div #login-buttons rendered by {{ loginButtons }} is rendered instantly on page load, but the div #login-dropdown-list is what's taking some time to start rendering. #login-dropodown-list template
The site is using Meteor 0.7.0.1 with Iron Router.
Update
Here's the template code for the accounts-ui-bootstrap-3 dropdown menu that take a few seconds to load after the rest of the page renders. It's just the basic template from the Meteor package, nothing special.
<ul class="nav navbar-nav navbar-right">
{{ loginButtons }}
</ul>
I used to think the problem is due to that this dropdown menu using the Meteor.users collection, so here's my Fast Render route.
FastRender.onAllRoutes(function(urlPath) {
this.subscribe(Meteor.users);
this.subscribe(users);
})
This does not seem to help with the problem. I also found out that Meteor.userId() is already defined when the dropdown menu is still not rendered. The dropdown menu only appears/renders at the point in time pointed to by the red arrow, which is the point where all the collections have loaded.
Furthermore, the div #login-buttons rendered by {{ loginButtons }} is rendered instantly on page load, but the div #login-dropdown-list is what's taking some time to start rendering.
Maybe it's how accounts-ui-bootstrap-3 handles the rendering?
You can use iron-router's waiton on specific routes and loadingTemplate properties to show the user a progress indicator while the subscription gets ready. As seen on https://github.com/EventedMind/iron-router#waiting-on-subscriptions-waiton
Router.configure({
layoutTemplate: 'layout',
notFoundTemplate: 'notFound',
loadingTemplate: 'loading'
});
Router.map(function () {
this.route('postShow', {
path: '/posts/:_id',
waitOn: function () {
return Meteor.subscribe('posts');
}
});
});
Also, https://atmosphere.meteor.com/package/fast-render is a third party package which integates into iron-router and sends down the initial data along with the template therefore making the page appear loaded with data instantly.
There is a good tutorial about this at http://meteorhacks.com/integrating-iron-router-based-apps-with-fast-render.html
I see two other packages that could help you :
Paginated subscription : https://atmosphere.meteor.com/package/paginated-subscription
The idea here would be to set a small limit for the initial load, and when the rest is loaded, change the limit to load the rest
Lazy subscription : https://atmosphere.meteor.com/package/lazy-subscription
The idea here is not to subscribe at all to your large collection in the first time ; only init it like that :
Post = new Meteor.Lazy('post', function() {
Meteor.subscribe('posts');
});
(it does not subscribe to anything)
And then when ready :
Template.some_template.get_posts = function() {
return Post().find({}); //Note that Post is now a function.
};
=> it doe subscribe
The second solution may seem more straight forward, but you can manage it way better with the first one.