Passing object between views (flash message) - javascript

What is the best way to pass message in the below scenario.
In the success scenario of $scope.p.$save, the result contains a message (res.message), which I like to display in the next view ($location.path("/test/"+res.reply.Id)). Without AngularJS, I may pass it in the url or save it in session cookies. But, I guess there might be a better way in AngularJS as there is no browser redirect and the state should be available. What is the best way to achieve this?
Setting it in rootScope shows it while I use browser back button, and the scope of the message should only for the first navigation to the new view.
function NewCtrl(Phone, $location, $rootScope, $scope) {
$scope.p = new Phone();
$scope.save = function () {
$scope.p.$save(
{},
function (res) {
$rootScope.message = res.message **//<-- this will cause message still set when using browser back button, etc**
$location.path("/test/"+res.reply.Id); **//<-- Req: needs to pass the message to next view**
}, function (res) {
//TODO
}
);
};
}
....
PhoneApp.factory('Phone', function ($resource) {
return $resource('/api/test/:_id')
});

You could use a service which displays the flash on $routeChangeSuccess.
Each time you set a flash message, add it to a queue, and when the route changes take the first item off the queue and set it to the current message.
Here's a demo:
http://plnkr.co/edit/3n8m1X?p=preview

I was looking to implement similar functionality, but actually wanted more of a growl style message.
I've updated the excellent plinkr code that Andy provided above to include a 'pop' method that leverages the toastr growl-style notification library.
My update also lets you to specify the notification type (info, warning, success, error) and title.
The 'pop' method skips adding the message to the queue, and instead pops it up on the screen immediately. The set/get functionality from Andy's previous plinkr remains mostly unchanged.
You can find my update here: http://plnkr.co/edit/MY2SXG?p=preview

I don't believe there's a way to do this default to AngularJS. Your best bet would just be passing the message (encoded) through a query string.

Related

How do I show a temporary transitory message/notification after redirecting in AngularJS?

I'd like to show a temporary message right before I redirect using
$location.path("/customerList");
But persists the redirect and show for the same duration it would show as if I did not redirect.
Im using AngularJS 1.5.0 with bootstrap 3
The purpose is to allow the ability to change the view for the end user, but provide feedback on the operation that resulted in the redirect. "User XYZ Saved Successfully" , where XYZ could very well be some property of JSON returned from an API after saving.
The message needs to be dynamic when its shown.
how about using the events for $location ($locationChangeStart/$locationChangeSuccess) or using the below code
$scope.$on('$routeChangeStart', function($event, next, current) {
// ... you could trigger something here ...
});
Ok, So here are my suggestions
Check this plunkr (click on 'section 1' and then click the button which you'll see in that route). You cant pass a param using $location with a message because that would pollute your url. So, you can create a service to keep that data. Also, create a site level controller to handle site level logic.
app.js
app.controller('siteCtrl',function($rootScope,$scope,msgSvc,$timeout){
$scope.$on('$routeChangeStart', function($event, next, current) {
var timer ;
if(next.$$route.originalPath === '/two'){
$scope.msg = msgSvc.getMsg();
timer = $timeout(function(){
$scope.msg = '';
msgSvc.setMsg('');
},2000);
}
});
})
function OneController($scope,$location,msgSvc){
$scope.doSomething = function(){
// set some msg and then go the route
msgSvc.setMsg('Success navigation from Section 1');
$location.path("/two");
};
}
Create a component which can listen to this event, and displays accordingly.

Inform user that message is still being typed

I am using Laravel 5.6.7, Socket.IO and vue.js. I am not using Pusher and redis. Below is my code to send message to user chatting with me one to one.
var url = "http://localhost:6001/apps/My_appId/events?auth_key=My_Key";
var socketId = Echo.socketId();
var request = {
"channel": "private-Send-Message-Channel.2",
"name": "MessengerEvent",
"data": {
"msg": message
},
"socket_id": socketId
};
axios.post(url, JSON.stringify(request)).then((response) => {
//Message Sent
});
I am trying to inform user who is chatting with me that I am still typing. Should I use the same above code which emits xhr on each char type? Is it the only way to inform user that the message typing is still in progress?
Update 1
Is there any better way to post xhr as mentioned above for each key press? I meant if user types 200 chars. will I post xhr 200 times?
or
Do we have an event called whisper and listenForWhisper as shown here https://laravel.com/docs/5.6/broadcasting#client-events ? I am using vue.js and laravel 5.6.7 without pusher and without redis
If you look at the broadcasting documentation you will see two code code snippets which you can use in your Vue.js application.
To broadcast client events, you may use Echo's whisper method:
Echo.private('chat')
.whisper('typing', {
name: this.user.name
});
To listen for client events, you may use the listenForWhisper method:
Echo.private('chat')
.listenForWhisper('typing', (e) => {
console.log(e.name);
});
While the user is typing, you can debounce the whisper method above.
If you don't wish to use another library like lodash, you can implement the debounce by simply wrapping whisper in a timeout. The following method would broadcast the whisper every 300ms:
isTyping() {
let channel = Echo.private('chat');
setTimeout(function() {
channel.whisper('typing', {
name: this.user.name,
typing: true
});
}, 300);
}
The app needs to trigger isTyping() when an onkeydown event occurs in the chat application's input field.
You also need to listen for the whisper once the app is created. The following method will set the typing variable to true for 600ms after the event has been received.
created() {
let _this = this;
Echo.private('chat')
.listenForWhisper('typing', (e) => {
this.user = e.name;
this.typing = e.typing;
// remove is typing indicator after 0.6s
setTimeout(function() {
_this.typing = false
}, 600);
});
},
I am no Laravel expert, but I've faced this problem before.
First, let's define what "typing" means. The simplest way to define it is to say that a user is typing if and only if the input field to send a message is not empty.
This is not perfect, because the user can go away from keyboard in the middle of typing a message then not returning to complete and/or send it, but it is good enough.
More importantly, we now don't need to care about key strokes to know if the user is typing. In fact, "user is typing" now becomes as easy as chat_input_box.length > 0 to represent in code.
This boolean value is what needs to be synced across users/servers, not the act of hitting a key on the keyboard by the user. However, to keep the value up to date, we need to catch input events on chat_input_box and if the boolean value has changed since before this current event has occurred, socket.io should be able send a signal signifying whether the user has stopped or started typing.
On the receiving side, this signal toggles appropriate views to appear or disappear to indicate the state of the app to the user in human terms.
For the shortcoming of a user typing something then leaving, a timeout can be set so that when it is finished the boolean value "is typing" resets to false, while the act of typing something resets the timeout then starts it again automatically.
You don't have to send an xhr request to your app. You can just
broadcast events directly to the chat users without hitting your app.
From the Laravel docs:
Sometimes you may wish to broadcast an event to other connected clients without hitting your Laravel application at all. This can be particularly useful for things like "typing" notifications, where you want to alert users of your application that another user is typing a message on a given screen. To broadcast client events, you may use Echo's whisper method:
Echo.private('chat')
.whisper('typing', {
name: this.user.name
});
To listen for client events, you may use the listenForWhisper method:
Echo.private('chat')
.listenForWhisper('typing', (e) => {
console.log(e.name);
});
Yes you are right, it should not be emitting on every character change instead you could use debouncing to wait for a small time and then fire the function.
I would recommend using lodash library's debounce method. It should be something like this.
Kindly have a look at the documentation: https://lodash.com/docs/4.17.5#debounce
Laravel's Echo also sounds good, as you'll be doing nothing with typing action on the back-end thus just emitting from client to client is better than involving the server.

MEAN / AngularJS app check if object already posted

I have thig angularJS frontend and I use express, node and mongo on the backend.
My situation looks like:
//my data to push on server
$scope.things = [{title:"title", other proprieties}, {title:"title", other proprieties}, {title:"title", other proprieties}]
$scope.update = function() {
$scope.things.forEach(function(t) {
Thing.create({
title: t.title,
//other values here
}, function() {
console.log('Thing added');
})
})
};
//where Thing.create its just an $http.post factory
The HTML part looks like:
//html part
<button ng-click="update()">Update Thing</button>
Then on the same page the user has the ability to change the $scope.things and my problem is that when I call update() again all the things are posted twice because literally thats what I'm doing.
Can someone explain me how to check if the 'thing' its already posted to the server just to update the values ($http.put) and if its not posted on server to $http.post.
Or maybe its other way to do this?
I see a few decisions to be made:
1) Should you send the request after the user clicks the "Update" button (like you're currently doing)? Or should you send the request when the user changes the Thing (using ngChange)?
2) If going with the button approach for (1), should you send a request for each Thing (like you're currently doing), or should you first check to see if the Thing has been updated/newly created on the front end.
3) How can you deal with the fact that some Thing's are newly created and others are simply updated? Multiple routes? If so, then how do you know which route to send the request to? Same route? How?
1
To me, the upside of using the "Update" button seems to be that it's clear to the user how it works. By clicking "Update" (and maybe seeing a flash message afterwards), the user knows (and gets visual feedback) that the Thing's have been updated.
The cost to using the "Update" button is that there might be unnecessary requests being made. Network communication is slow, so if you have a lot of Thing's, having a request being made for each Thing could be notably slow.
Ultimately, this seems to be a UX vs. speed decision to me. It depends on the situation and goals, but personally I'd lean towards the "Update" button.
2
The trade-off here seems to be between code simplicity and performance. The simpler solution would just be to make a request for each Thing regardless of whether it has been updated/newly created (for the Thing's that previously existed and haven't changed, no harm will be done - they simply won't get changed).
The more complex but more performant approach would be to keep track of whether or not a Thing has been updated/newly created. You could add a flag called dirty to Thing's to keep track of this.
When a user clicks to create a new Thing, the new Thing would be given a flag of dirty: true.
When you query to get all things from the database, they all should have dirty: false (whether or not you want to store the dirty property on the database or simply append it on the server/front end is up to you).
When a user changes an existing Thing, the dirty property would be set to true.
Then, using the dirty property you could only make requests for the Thing's that are dirty:
$scope.things.forEach(function(thing) {
if (thing.dirty) {
// make request
}
});
The right solution depends on the specifics of your situation, but I tend to err on the side of code simplicity over performance.
3
If you're using Mongoose, the default behavior is to add an _id field to created documents (it's also the default behavior as MongoDB itself as well). So if you haven't overridden this default behavior, and if you aren't explicitly preventing this _id field from being sent back to the client, it should exist for Thing's that have been previously created, thus allow you to distinguish them from newly created Thing's (because newly created Thing's won't have the _id field).
With this, you can conditionally call create or update like so:
$scope.things.forEach(function(thing) {
if (thing._id) {
Thing.update(thing._id, thing);
}
else {
Thing.create(thing);
}
});
Alternatively, you could use a single route that performs "create or update" for you. You can do this by setting { upsert: true } in your update call.
In general, upsert will check to see if a document matches the query criteria... if there's a match, it updates it, if not, it creates it. In your situation, you could probably use upsert in the context of Mongoose's findByIdAndUpdate like so:
Thing.findByIdAndUpdate(id, newThing, { upsert: true }, function(err, doc) {
...
});
See this SO post.
#Adam Zemer neatly addressed concerns I raised in a comment, however I disagree on some points.
Firstly, to answer the question of having an update button or not, you have to ask yourself. Is there any reason why the user would like to discard his changes and not save the work he did. If the answer is no, then it is clear to me that the update should not be place and here is why.
To avoid your user from loosing his work you would need to add confirmations if he attempts to change the page, or close his browser, etc. On the other if everything is continuously saved he has the peace of mind that his work is always saved and you dont have to implement anything to prevent him from loosing his work.
You reduce his workload, one less click for a task may seem insignificant but he might click it many time be sure to have his work save. Also, if its a recurrent tasks it will definitely improve his experience.
Performance wise and code readability wise, you do small requests and do not have to implement any complicated logic to do so. Simple ng-change on inputs.
To make it clear to him that his work is continuously save you can simply say somewhere all your changes are saved and change this to saving changes... when you make a request. For exemple uses, look at office online or google docs.
Then all you would have to do is use the upsert parameter on your mongoDB query to be able to create and update your things with a single request. Here is how your controller would look.
$scope.update = function(changedThing) { // Using the ng-change you send the thing itself in parammeter
var $scope.saving = true; // To display the saving... message
Thing.update({ // This service call your method that update with upsert
title: changedThing.title,
//other values here
}).then( // If you made an http request, I suppose it returns a promise.
function success() {
$scope.saving = false;
console.log('Thing added');
},
function error() {
//handle errors
})
};

Picking up meteor.js user logout

Is there any way to pick up when a user logs out of the website? I need to do some clean up when they do so. Using the built-in meteor.js user accounts.
I'll be doing some validation using it, so I need a solution that cannot be trigger on behalf of other users on the client side - preferably something completely server side.
You may use Deps.autorun to setup a custom handler observing Meteor.userId() reactive variable changes.
Meteor.userId() (and Meteor.user()) are reactive variables returning respectively the currently logged in userId (null if none) and the corresponding user document (record) in the Meteor.users collection.
As a consequence one can track signing in/out of a Meteor application by reacting to the modification of those reactive data sources.
client/main.js :
var lastUser=null;
Meteor.startup(function(){
Deps.autorun(function(){
var userId=Meteor.userId();
if(userId){
console.log(userId+" connected");
// do something with Meteor.user()
}
else if(lastUser){
console.log(lastUser._id+" disconnected");
// can't use Meteor.user() anymore
// do something with lastUser (read-only !)
Meteor.call("userDisconnected",lastUser._id);
}
lastUser=Meteor.user();
});
});
In this code sample, I'm setting up a source file local variable (lastUser) to keep track of the last user that was logged in the application.
Then in Meteor.startup, I use Deps.autorun to setup a reactive context (code that will get re-executed whenever one of the reactive data sources accessed is modified).
This reactive context tracks Meteor.userId() variation and reacts accordingly.
In the deconnection code, you can't use Meteor.user() but if you want to access the last user document you can use the lastUser variable.
You can call a server method with the lastUser._id as argument if you want to modify the document after logging out.
server/server.js
Meteor.methods({
userDisconnected:function(userId){
check(userId,String);
var user=Meteor.users.findOne(userId);
// do something with user (read-write)
}
});
Be aware though that malicious clients can call this server method with anyone userId, so you shouldn't do anything critical unless you setup some verification code.
Use the user-status package that I've created: https://github.com/mizzao/meteor-user-status. This is completely server-side.
See the docs for usage, but you can attach an event handler to a session logout:
UserStatus.events.on "connectionLogout", (fields) ->
console.log(fields.userId + " with connection " + fields.connectionId + " logged out")
Note that a user can be logged in from different places at once with multiple sessions. This smart package detects all of them as well as whether the user is online at all. For more information or to implement your own method, check out the code.
Currently the package doesn't distinguish between browser window closes and logouts, and treats them as the same.
We had a similar, though not exact requirement. We wanted to do a bit of clean up on the client when they signed out. We did it by hijacking Meteor.logout:
if (Meteor.isClient) {
var _logout = Meteor.logout;
Meteor.logout = function customLogout() {
// Do your thing here
_logout.apply(Meteor, arguments);
}
}
The answer provided by #saimeunt looks about right, but it is a bit fluffy for what I needed. Instead I went with a very simple approach like this:
if (Meteor.isClient) {
Deps.autorun(function () {
if(!Meteor.userId())
{
Session.set('store', null);
}
});
}
This is however triggered during a page load if the user has not yet logged in, which might be undesirable. So you could go with something like this instead:
if (Meteor.isClient) {
var userWasLoggedIn = false;
Deps.autorun(function (c) {
if(!Meteor.userId())
{
if(userWasLoggedIn)
{
console.log('Clean up');
Session.set('store', null);
}
}
else
{
userWasLoggedIn = true;
}
});
}
None of the solutions worked for me, since they all suffered from the problem of not being able to distinguish between manual logout by the user vs. browser page reload/close.
I'm now going with a hack, but at least it works (as long as you don't provide any other means of logging out than the default accounts-ui buttons):
Template._loginButtons.events({
'click #login-buttons-logout': function(ev) {
console.log("manual log out");
// do stuff
}
});
You can use the following Meteor.logout - http://docs.meteor.com/#meteor_logout

AngularJS/Parse.com : $scope.currentUser variable not updated

I'm using parse with angularjs to authenticate users. Here is the login function.
$scope.doLogin = ->
Parse.User.logIn $scope.currentUser.username, $scope.currentUser.password,
success: (user) ->
console.log user
$scope.currentUser = user
error: (user, error) ->
console.log error
And here is the form (used twice in same page, navbar dropdown and in page content):
%form{"ng-submit" => "doLogin()"}
%input{"ng-model" => "currentUser.username", type: "text"}
%input{"ng-model" => "currentUser.password", type: "password"}
%button.btn.btn-block
%center Connexion
The problem is that whenever the form is submitted, I can see the user object in console, but $scope.currentUser doesn't always get updated. Sometimes I have to submit the form 3 or 4 times in a row for it to get updated.
What am I doing wrong ? Thank you.
perhaps you should include $scope.apply() in your success callback. From the Angularjs docs:
"$apply() is used to execute an expression in angular from outside of the angular framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). Because we are calling into the angular framework we need to perform proper scope life-cycle of exception handling, executing watches."
I had a similar problem with Parse and I'd solved it with $scope.apply().
Here is my controller
ps. note that I'm relatively new to angularjs and the above code may not be the most efficient :)

Categories

Resources