Refresh(F5) ngview with angularJS - node.js

I've got an issue with my Angular application. The application is running well the first time, it is possible to navigate inside, but it fails when I try to refresh the page with F5. When F5 key is pressed, the partial is called to the server, and the server obviously responds with the partial view, but not the whole application.
Node routing:
app.get('/', node_routes.welcome);
...
app.get('/profile', pass.ensureAuthenticated, profile_routes.profile);
app.get('/profile/:name', pass.ensureAuthenticated, profile_routes.partials);
app.post('/profile/update', pass.ensureAuthenticated, profile_routes.update);
...
Controller:
exports.profile = function(req, res) {
var user = req.user;
Profile.findOne({ username: user.username }, function(err, profile) {
if (err) {
res.redirect('/');
}
res.render("profile");
});
};
exports.partials = function(req, res) {
var name = req.params.name;
var user = req.user;
Profile.findOne({ username: user.username }, function(err, profile) {
if (err) {
res.redirect('/');
}
res.render(path.join(__dirname + '/../views/profile/' + name));
});
};
exports.update = function(req, res){
var profile = req.body;
delete profile._id;
Profile.update({'username':profile.username},profile,{safe:true}, function(err, result){
if(err) {
console.log('Error updating profile. ' + err);
res.redirect('/profile');
}
else{
console.log('' + result + ' profile updated for user: ' + profile.username);
res.redirect('/profile');
}
});
};
Angular application
myApp.config(['$routeProvider','$locationProvider', function($routeProvider,$locationProvider){
$routeProvider
.when('/profile/update', {
controller: 'myJobOfferListCtrl',
templateUrl: '/profile/update',
reloadOnSearch: false
})
.when('/profile', {
controller: 'myJobOfferListCtrl',
templateUrl: '/profile/dashboard'
})
.when('/profile/dashboard', {
controller: 'myJobOfferListCtrl',
templateUrl: '/profile/dashboard',
reloadOnSearch: false
})
.when('/profile/offers', {
controller: 'myJobOfferListCtrl',
templateUrl: '/profile/offers',
reloadOnSearch: false
})
.otherwise({redirectTo: '/profile'});
$locationProvider.html5Mode(true);
}]);
My profile page
extends layout
block content
div.container-fluid(ng-app="appProfile", ng-controller="myJobOfferListCtrl", ng-init="initProfile()")
div.row-fluid
div.span3(ng-controller="navBarCtrl")
div.well.sidebar-nav
ul.nav.nav-list
li.well.nav-header.
My Profile ({{data.profile.username}})
li(ng-class="{ active: isActive('dashboard')}")
a(href="/profile/dashboard") Dashboard
li(ng-class="{ active: isActive('update')}")
a(href="/profile/update") Update profile
li.divider
li.well.nav-header My Jobs
li(ng-class="{ active: isActive('offers')}")
a(href="/profile/offers") Offers
li(ng-class="{ active: isActive('application')}")
a(href="/profile/application") Applications
div.span9(ng-view, ng-cloak)
And a sample partial view page
div.row-fluid
div(ng-init="initMyOffers()")
accordion.span8.offset1(close-others="true")
accordion-group(ng-repeat="item in data.myJobs", heading="{{item.title}}")
accordion-heading
{{item.title}}
i.pull-right.icon-remove-circle.removeButton(ng:click="remove(item)")
p {{item.description}}
hr
div.footer
div.pull-left
i.icon-calendar.calendar
{{item.dueDate | date:'d MMMM yyyy'}}
div.pull-right
i.icon-user
{{item.author}}
How can I reload a partial page when refreshing with F5 ?? What I expected with angular is that when trying to refresh for example the page /profile/dashboard, the partial /views/profile/dashboard.jade is called but also the views/profile.jade. And what about the $scope ?
Sorry, but I'm a little confioused... thanks for you help !

I think it is a common problem and the user is not supposed to use F5 in your application. I do not think, it is possible to stop default browser action after F5 has been pressed.
One simple solution is to add this or similar script to every view:
<script>
if (angular == undefined)
// alert("It is SPA (Single Page Application) -- do not press F5 anymore, please.");
window.location.replace("your/main/url");
</script>
A bit more complicated would be realization of the following scenario.
user hits f5
server sends partial view for current url current_url
client codes detects lack of angular and send request to be redirected to current_url
server sends modified version of the index.html file [with e.g. some hidden input field, that stores current_url]
after angular has been loaded, the application checks the hidden field and change the location accordingly

The simplest and most elegant way to avoid losing stuff when refreshing an Angular app is to store whatever values you need in a cookie by using $cookies service. Here's an example. Hope this helps.
"use strict";
angular.module('myApp').factory('SessionSrv', [ '$cookies', function($cookies){
var _myString = $cookies.get('myString'); // e.g. "foo"
var _myObject = $cookies.getObject('myObject'); // e.g. {foo: "bar"}
return {
setMyString: function(myString){
_myString = myString;
$cookies.put('myString', _myString);
},
getMyString: function(){
return _myString;
},
setMyObject: function(myObject){
_myObject = myObject;
$cookies.putObject('myObject', _myObject);
},
getMyObject: function(){
return _myObject;
},
};
}]);

Finally implemented the solution as proposed by artur :
<script>
if (typeof angular == 'undefined')
window.location.replace("/main_url");
</script>

Related

React role based authentication if the two users are from two different tables

I'm new to React/Node and working on a learning project. It's a platform that connects users (freelancers) with nonprofit companies. I would like users to sign up and login as A) user or B) company. I can't figure out how to do this, and all the guides I found are for when your users are all coming from the same table, but with different auth levels (eg. user, admin, etc..).
In my case, it's different. users and companies are two different resources. A user can view /companies and click a button to connect to that company. A user can view a page that lists all their connections. Likewise, a company can login and view a page that lists all the users that connected with them.
Right now, the backend is working successfully. Both users/companies can signup/login, and you get a token back as expected (tested in Insomnia). I'm using JSON Web Tokens.
On the frontend, users can signup, login, make connections, and view their connections successfully. Now I just want companies to do the same, but have no idea how. I made an attempt at doing it, but when a company tries to login, they're directed to the homepage and they're not logged in. No error messages show up.
Not sure what code to post, but I will keep this concise. This is all the relevant code (shortened). I would appreciate any help, or pointers.
schema
CREATE TABLE companies (
company_handle VARCHAR(25) PRIMARY KEY,
password TEXT NOT NULL,
company_name TEXT NOT NULL
role TEXT DEFAULT 'company'
);
CREATE TABLE users (
username VARCHAR(25) PRIMARY KEY,
password TEXT NOT NULL,
role TEXT DEFAULT 'user'
);
CREATE TABLE connections (
username VARCHAR(25)
REFERENCES users ON DELETE CASCADE,
company_handle VARCHAR(25)
REFERENCES companies ON DELETE CASCADE,
PRIMARY KEY (username, company_handle)
);
Frontend
App.js
function App() {
const [currentUser, setCurrentUser] = useState(null);
const [currentCompany, setCurrentCompany] = useState(null);
const [token, setToken] = useLocalStorage(TOKEN_LOCAL_STORAGE_ID);
const [connectionHandles, setConnectionHandles] = useState([]);
// Load user info from the API
useEffect(function loadUserInfo() {
async function getCurrentUser() {
if (token) {
try {
let { username } = jwt.decode(token);
let { companyHandle } = jwt.decode(token);
VolunteerApi.token = token;
if (username) {
let currentUser = await VolunteerApi.getCurrentUser(username);
setCurrentUser(currentUser);
}
if (companyHandle) {
let currentCompany = await VolunteerApi.getCurrentCompany(companyHandle);
setCurrentCompany(currentCompany);
}
} catch (err) {
console.error("Problem with the loadUserInfo function", err);
setCurrentUser(null);
setCurrentCompany(null);
}
}
}
getCurrentUser();
}, [token]);
// Login user function
async function loginUser(loginData) {
try {
let token = await VolunteerApi.loginUser(loginData);
setToken(token);
return {
success: true
};
} catch (err) {
console.error("Problem with the login function", err);
return {
success: false, err
};
}
}
// Login company function
async function loginCompany(loginData) {
try {
let token = await VolunteerApi.loginCompany(loginData);
setToken(token);
return {
success: true
};
} catch (err) {
console.error("Problem with the login function", err);
return {
success: false, err
};
}
}
return (
<BrowserRouter>
<UserContext.Provider value={{ connectionHandles, setConnectionHandles, currentUser, setCurrentUser, currentCompany, setCurrentCompany }}>
<div>
<Navigation />
<Routes loginUser={loginUser} loginCompany={loginCompany} />
</div>
</UserContext.Provider>
</BrowserRouter>
);
}
api.js
class VolunteerApi {
static token;
static async request(endpoint, data = {}, method = "get") {
console.debug("API Call:", endpoint, data, method);
const url = `${BASE_URL}/${endpoint}`;
const headers = { Authorization: `Bearer ${VolunteerApi.token}` };
const params = (method === "get")
? data
: {};
try {
return (await axios({ url, method, data, params, headers })).data;
} catch (err) {
console.error("API Error:", err.response);
let message = err.response.data.error.message;
throw Array.isArray(message) ? message : [message];
}
}
// Login company
static async loginCompany(data) {
let res = await this.request(`auth/login-company`, data, "post");
return res.token;
}
// Login user
static async loginUser(data) {
let res = await this.request(`auth/login-user`, data, "post");
return res.token;
}
}
Backend
auth.js
router.post("/login-company", async function (req, res, next) {
try {
const { companyHandle, password } = req.body;
const company = await Company.authenticate(companyHandle, password);
const token = createToken(company);
return res.json({ token });
} catch (err) {
return next(err);
}
});
router.post("/login-user", async function (req, res, next) {
try {
const { username, password } = req.body;
const user = await User.authenticate(username, password);
const token = createToken(user);
return res.json({ token });
} catch (err) {
return next(err);
}
});
token.js
function createToken(user) {
console.assert(undefined,
"createToken passed user with an undefined user");
let payload = {
username: user.username,
companyHandle: user.companyHandle
};
return jwt.sign(payload, SECRET_KEY);
}
If I understand correctly what you wish to achieve is that your same app can be viewed with 2 different perspectives (User view or Company view) using who logged in as your flag to show the correct data. Having different roles for the same page can be tricky but thankfully there are a number of ways to achieve this.
What I recommend as the simplest approach would be conditional rendering.
When someone logs in as a user or a company you can save that detail to the browsers local storage using localStorage.setItem("UserType", "Example"); and you can get this information using localStorage.getItem("UserType");
Then when the user or company is in your page using that detail you can render the right elements like so:
{condition == true && (<> <Module/> </>)}
Now since we are using react we can import whole js files as modules. so you can have something that looks like this:
import UserPage from 'somewhere/User.js'
import CompanyPage from 'somewhere/Company.js'
function MainApp() {
const userOrCompany = localStorage.getItem("UserType")
return(
<>
{userOrCompany === 'User' && (<> <UserPage/> </>)}
{userOrCompany === 'Company' && (<> <CompanyPage/> </>)}
</>
);
}
export default MainApp;
Also, I recommend handling tokens from the backend for security reasons. That way you can condition your backend data to needing a token before returning anything :D

using findOne in mongodb to find by id

I'm making a tapTracker app with MEVN stack and there is a page with all of songs
and i want that when i click on a single song make app to open this single song only ...
IN THE FRONT END ==>
so i make route from songs.vue '{that songs page}' to viewSong.vue
<v-btn dark class="cyan darken-3" v-on:click="navigateTo({name: 'song',
params: {songId: song._id}})"> View </v-btn>
after that in viewsong.vue i make a show function with id param
<template>
<h1></h1>
</template>
<script>
import songsServices from '#/services/songsServices'
export default {
data () {
return {
song: null
}
},
async mounted () {
const songId = this.$store.state.route.params.songId
this.song = await songsServices.show(songId)
}
}
</script>
then i make an api with it to send that to back-end server in
songServices.js
show (songId) {
return Api().get(`songs/${songId}`)
}
IN THE BACK END 'SERVER' ==>
i make get http req in router.js
app.get('/songs/:songId', songsController.show)
songsController.js
async show(req,res) {
try {
const Song = await Songs.findOne({_id: req.params.songId})
res.send(song)
} catch (error) {
res.status(400).send('error: error when tring to fetch songs!!!')
console.log(error)
}
}
in postman when i make a get req with this param it give me error
where is the problem ??
hint this is the project url on github
https://github.com/mohamedAdel01/tapTracker

Sailsjs: Multiple Layouts

I realize this is the same issue raised here: How to use multiple layout within a SailsJS app?. I'm not sure if something has changed with Sails.js or I'm just a numbskull. Hoping someone can help...
The application I'm developing has two sides to it, a public side, and an admin side. For the life of me, I cannot get a view to use a different layout than the default. I'm using the default "ejs" templating engine.
The location of the alternate layout resides here:
/views/layoutadmin.ejs
Here's my /config/routes.js file:
module.exports.routes = {
'/': {
view: 'home/index'
},
'/dashboard': {
view: 'admin/index'
,controller: 'dashboard'
,action: 'index'
}
};
Here's my /api/controllers/DashboardController.js file:
module.exports = {
index: function (req, res) {
res.view({ layout: 'layoutadmin' });
}
};
The problem here is that you're specifying both a view and a controller/action in your route config. You can only do either/or. You want:
module.exports.routes = {
'/': {
view: 'home/index'
},
'/dashboard': {
controller: 'dashboard'
,action: 'index'
}
};
And in DashboardController:
module.exports = {
index: function (req, res) {
res.view('admin/index', { layout: 'layoutadmin' });
}
};
You could create a policy and change the layout in it:
Check this out:
https://github.com/vimia/blew/blob/master/api/policies/isPjaxRequest.js
I change the layout with the param...
You can create a isAdminModule policy, at config/policies.js, put something like:
AdminController: [ '*': 'isAdminModule']
Then all the admin requests will have another layout...

backbone.js and express: trouble searching a mongodb collection by field with a query string

I am new to backbone, express, and mongodb.
I am trying to pass a query string to search a mongodb collection by field.
I am doing something wrong. If I comment out the "fetch" from my router, the page is found.
If I try to fetch, then I get a page not found error.
I've tried to isolate where it's breaking, but the backbone architecture is still confusing to me. Thanks in advance. (I'm betting it's a syntax issue in my mongodb call)
kristin
Here is my code.
this URL should return a collection where "type" = 3.
localhost:8888/#content/3
model/models.js:
window.Content = Backbone.Model.extend({
urlRoot: "/content",
idAttribute: "_id"
});
window.ContentCollection = Backbone.Collection.extend({
model: Content,
url: "/content"
});
views/content.js
window.ContentListView = Backbone.View.extend({
initialize: function () {
this.render();
},
render: function () {
//return this;
this.$el.append('<ul class="thumbnails">');
this.collection.each(function(model) {
this.$('.thumbnails').append(new ContentView({model: model}).render().el);
}, this);
return this;
} });
window.ContentView = Backbone.View.extend({
tagName: "li",
initialize: function () {
this.model.bind("change", this.render, this);
this.model.bind("destroy", this.close, this);
},
render: function () {
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
views/main.js
var AppRouter = Backbone.Router.extend({
routes: { "content/:type" : "contentType" },
contentType: function(type) {
var contentList = new ContentCollection({type : type});
contentList.fetch({success: function(){
$("#content").empty().append(new ContentListView({collection: contentList}).el);
}});
this.headerView.selectMenuItem('build-menu');
},
utils.loadTemplate([
'ContentView'
], function() {
app = new AppRouter();
Backbone.history.start(); });
contentView.html
name (<% tag won't print here)
routes/modules.js
exports.findContentByType = function(req, res) {
var type = req.params.type;
db.collection('content', function(err, collection) {
collection.find({'type': type.toString()}).toArray(function(err, items) {
res.send(items);
});
});
};
server.js
app.get('/content/:type', module.findContentByType);
I can see a couple of problems here:
this.headerView.selectMenuItem('build-menu'); (in the router) implies you've defined headerView in the router object, but it's not defined.
Similarly, this.template inside ContentView is not defined
When I remove the line in #1, and and define a dummy template in ContentView:
template: _.template("<div> Test: <%= version %> </div>"),
Then the view at least renders -- see here. (This is with dummy data -- I can't confirm that your server is returning valid/expected JSON.)

Backbone views not rendering in order

In my app I have layouts and views for those layouts. Layouts only change on login/logout, but the other special case is on pageload I need to load the proper layout. However, in my defaultAction my layout does not actually render after it returns and so when the view tries to render, the el it is supposed to be contained in does not exist.
// Filename: router.js
var app_router;
define( [ 'views/layouts/beta', 'views/beta/requestInvite', 'views/beta/login', 'views/app/dashboard' ],
function(betaLayout, requestInviteView, loginView, dashboardView) {
var AppRouter = Backbone.Router.extend( {
routes : {
// Pages
'login' : 'login',
'dashboard' : 'dashboard',
// Default
'*actions' : 'defaultAction'
},
// Pages
login : function() {
loginView.render();
},
dashboard : function() {
dashboardView.render();
},
// Default
defaultAction : function(actions) {
betaLayout.render();
requestInviteView.render();
}
});
var initialize = function() {
app_router = new AppRouter;
$('a').live('click', function() {
var href = $(this).attr('href');
// only navigate to real links
if(href == undefined)
return;
app_router.navigate(href, {trigger: true});
return false;
});
Backbone.history.start({pushState: true});
};
return {
initialize : initialize
};
});
How can I have my layout render completely before my view?
Define a callback for betaLayout.render() to take as an argument, that gets executed when the render is actually complete.
E.g., betaLayout.render() would look something like:
render: function(callback) {
/* code to render your element,
using the following line when all
asynchronous calls are complete */
if (callback) callback();
}
and your defaultAction would use it like so, passing the second render() as its callback.
betaLayout.render(requestInviteView.render);
The problem was that because my layout didn't render before my view was initialized, the el was empty. What I did was convert all of my objects to return the object instead of the instance, and let them render once they were initialized. This way, when I declare a new MyLayout and then a new MyView, I can be guaranteed that MyView's el is valid.

Resources