My front-end is Angular5. Back-end is REST API NODE.JS and the database is POSTGRES.
Table named organization in my postgres contain 100k ( 100 000) rows.
I have table that contains list of all organizations with information such as name of the organization,location etc.
At this moment my frontend only displays 50 organization. I have console logged this and my backend is only sending array of 50.
My goal is to display all organizations( 100 000 of them) in the frontend table using server-side pagination.
Pagination is already made for the current values that are displayed and thats 50 of them. Code is below
Frontend table below that works ( currently displaying only 50 organization sent from backend)
<div class="card-body">
<div class="row">
<div class="table-col">
<div class="card table">
<div class="row header">
<div class="col col-md-6">Name</div>
<div class="col col-md-2">ID</div>
<div class="col col-md-2">Location</div>
<div class="col col-md-2">Person in charge</div>
</div>
<div *ngFor="let group of organizations">
<div *ngIf="group.name == 'all'">
<div class="row content-row" *ngFor="let organization of group.array | search: term | orderBy: order | paginate: {itemsPerPage: 13, currentPage:page1, id: '1'}"
[routerLink]="['/organization/edit', organization.id]">
<div class="col col-md-6">{{organization.name}}</div>
<div class="col col-md-2">{{organization.id}}</div>
<div class="col col-md-2" *ngIf="organization.place?.fullName != null">{{organization.place?.fullName}}</div>
<div class="col col-md-2" *ngIf="organization.place?.fullName == null"><span class="badge badge-danger">-- N/A -- </span></div>
<div *ngIf="organization.person?.fullname != null" class="col col-md-2">{{organization.person?.fullname}}
</div>
<div *ngIf="organization.person?.fullname == null" class="col col-md-2"><span class="badge badge-danger">-- N/A -- </span></div>
</div>
</div>
<div *ngIf="group.name != 'all'">
<div class="row content-row">
<div class="col col-md-12 group-name" (click)="toggleGroup(group.name)">{{group.name + ':'}}</div>
</div>
<div *ngIf="showGroup == group.name">
<div class="row content-row" *ngFor="let organization of group.array | search: term | paginate: {itemsPerPage: 5, currentPage:page2, id: '2'}"
[routerLink]="['/organization/edit', organization.id]">
<div class="col col-md-6" style="padding-left:23px;">{{organization.name}}</div>
<div class="col col-md-2">{{organization.id}}</div>
<div class="col col-md-2">{{organization.place?.fullName}}</div>
<div class="col col-md-2">{{organization.person?.fullname}}
</div>
</div>
</div>
</div>
</div>
<div *ngIf="!(organizations[0].array.length > 0)" class="row noResults">
<label>No results..</label>
</div>
<div *ngIf="organizations[0].name == 'all'" class="paggination-row">
<div class="paggination">
<pagination-controls (pageChange)="page1 = $event" id="1" maxSize="5" directionLinks="true" autoHide="true" previousLabel="Previous"
nextLabel="Next">
</pagination-controls>
</div>
</div>
<div *ngIf="organizations[0].name != 'all' && showGroup != ''" class="paggination-row">
<div class="paggination">
<pagination-controls (pageChange)="page2 = $event" id="2" maxSize="5" directionLinks="true" autoHide="true" previousLabel="Previous"
nextLabel="Next">
</pagination-controls>
</div>
</div>
</div>
</div>
</div>
</div>
Now on backend in my config I have the following code:
"host": "0.0.0.0",
"port": 3030,
"public": "../public/",
"paginate": {
"default": 50,
"max": 50
},
Note: host ip changed on purpose.
Paginate is the reason why my front is only receiving 50 values when there is 100 000 of them.
Now if I go on about and change default and max value to 100k my frontend should show all organizations but will probably crash the client and the server in process of doing this.
If i do paginate false that won't be much of a help either.
Now I am wondering how can I keep having limited requests like 50 or 100(so my server is alive) but still show all organizations in my front-end using server side pagination or whatever necessary.
Any help is appreciated and thank you so much for taking your time to read this.
edit1: service to load all organizations:
import { Injectable } from '#angular/core';
import { Http, Headers } from "#angular/http";
import 'rxjs/add/operator/map';
import { AppSettings } from '../../../../../../app.settings';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
//service made to comunicate with backend
#Injectable()
export class CreateOrganizacijeService {
private dataSource = new BehaviorSubject<any>("default")
currentData = this.dataSource.asObservable();
constructor(
private http: Http
) { }
updateData(data) {
this.dataSource.next(data);
}
getAll() {
let headers = new Headers();
let token = localStorage.getItem('feathers-jwt');
headers.append('Content-Type', 'application/json');
headers.append('Authorization', 'Bearer ' + token)
return this.http.get(AppSettings.API_ENDPOINT + '/organization', { headers: headers }).map(res => res.json())
}
getOne(id) {
let headers = new Headers();
let token = localStorage.getItem('feathers-jwt');
headers.append('Content-Type', 'application/json');
headers.append('Authorization', 'Bearer ' + token)
return this.http.get(AppSettings.API_ENDPOINT + '/organization?id=' + id, { headers: headers }).map(res => res.json())
}
createNew(organization) {
let headers = new Headers();
let token = localStorage.getItem('feathers-jwt');
headers.append('Content-Type', 'application/json');
headers.append('Authorization', 'Bearer ' + token)
return this.http.post(AppSettings.API_ENDPOINT + '/organization', organization, { headers: headers }).map(res => res.json())
}
delete(id) {
let headers = new Headers();
let token = localStorage.getItem('feathers-jwt');
headers.append('Content-Type', 'application/json');
headers.append('Authorization', 'Bearer ' + token)
return this.http.delete(AppSettings.API_ENDPOINT + '/organization?id=' + id, { headers: headers }).map(res => res.json())
}
edit(id, organization) {
let headers = new Headers();
let token = localStorage.getItem('feathers-jwt');
headers.append('Content-Type', 'application/json');
headers.append('Authorization', 'Bearer ' + token)
return this.http.patch(AppSettings.API_ENDPOINT + '/organization?id=' + id, organization, { headers: headers }).map(res => res.json())
}
}
component ngonit ( where I load organizations)
ngOnInit() {
this.currentUser = JSON.parse(localStorage.getItem('user'));
if(this.currentUser.roleId == '2') {
this.isAdmin = true; //check if user is admin
}
if(JSON.parse(localStorage.getItem('user')).roleId == '2') {
this.isAdmin = true; //check if user is admin
}
this.organizacijeService.currentData.subscribe(res => {
this.loadOrgs();
})
}
loadOrgs() {
this.organizacijeService.getAll().subscribe(res => {
this.organizations[0].array = res.data;
console.log(this.organizations[0])
}, err => {
this.toast.error('Organization: ' + JSON.parse(err._body).message, 'Failed')
})
}
You can load subsequent pages by increasing the $skip query parameter in increments of 50 until the page.total - 50.
I would keep in mind that showing 100k records at once will probably still lead to performance problems on the frontend.
You can use a proxy service on the server side that has pagination disabled:
app.use('organizations-unpaginated', {
find(params){
const _params = Object.assign({}, params, { paginate: false });
return app.service('organizations').find(_params);
}
})
As Daff said, you will run into performance issues and I'd generally say that rendering 100k DOM elements (almost all of which are invisible anyway) is bad practice.
Related
I'm using Laravel and VueJs,
I'm trying the following: I 've created a search bar to find users by their names, last name or email.
I used computed to write my filter but I've realized that my filter only filters over the 10 first elements (because I'm using paginate to show all users stored in my database)
...what can I do to make my filter works over all my users instead each ten that gives me paginate (if it's possible keeping paginate, please)?
This is my script and template (thank you very much):
<script>
import UpdateProfile from './users/UpdateProfile';
import CreateUser from './users/CreateUser';
import User from '../models/user';
export default {
components: {UpdateProfile, CreateUser},
data() {
return {
showUpdateModal: false,
showCreateModal: false,
users: [],
user: new User(),
search:'',
paginator: {
current: 1,
total: 1,
limit: 10,
}
}
},
mounted() {
this.goToPage(1);
},
methods: {
userPermissions(user) {
return this.CONSTANTS.getUserType(user.permissions);
},
addUser(user) {
this.showCreateModal = false;
this.api.post('/users', user).then(() => {
this.goToPage(this.paginator.current);
});
},
editUser(user) {
this.user = JSON.parse(JSON.stringify(user));
this.showUpdateModal = true;
},
updateUser(user) {
this.showUpdateModal = false;
this.api.put('/users/' + user.id, user).then(() => {
this.goToPage(this.paginator.current)
});
},
deleteUser(user) {
this.api.delete('/users/' + user.id).then(() => {
this.goToPage(this.paginator.current)
});
},
navigatePrev(page) {
this.goToPage(page)
},
navigateNext(page) {
this.goToPage(page)
},
goToPage(page) {
this.api.get('/users?page=' + page + '&limit=' + this.paginator.limit).then(response => {
this.users = response.data;
this.paginator = response.paginator;
});
}
},
computed:{
filteredUsers: function () {
return this.users.filter((user) => {
var searchByName = user.name.toLowerCase().match(this.search.toLowerCase());
var searchByLastName = user.lastname.toLowerCase().match(this.search.toLowerCase());
var searchByEmail = user.email.toLowerCase().match(this.search.toLowerCase());
if(searchByName){
return searchByName;
}
if(searchByLastName){
return searchByLastName;
}
if(searchByEmail){
return searchByEmail;
}
});
}
}
}
</script>
<template>
<div class="container">
<div class="button is-primary" #click="showCreateModal=true" v-if="CONSTANTS.hasRootPermissions()">
<span class="icon"><i class="fas fa-plus fa-lg"></i></span>
<span>Add User</span>
</div>
<br><br>
<create-user v-if="CONSTANTS.hasRootPermissions()"
:show="showCreateModal"
v-on:save="addUser"
v-on:close="showCreateModal=false"/>
<!--Search Users-->
<div class="control is-expanded">
<h1>Search users</h1>
<input class="input" type="text" v-model="search" placeholder="Find a user"/>
</div>
<br><br>
<!--Search Users-->
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Admin</th>
<th>Permissions</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="user in filteredUsers">
<td>{{user.name}}</td>
<td>{{user.lastname}}</td>
<td>{{user.email}}</td>
<td>{{user.isAdmin ? 'yes' : 'no'}}</td>
<td>{{userPermissions(user)}}</td>
<td>
<div class="button is-info" #click="editUser(user)">
<span class="icon"><i class="far fa-edit"></i></span>
<span>Edit</span>
</div>
</td>
<td>
<div class="button is-danger" #click="deleteUser(user)">
<span class="icon"><i class="far fa-trash-alt"></i></span>
<span>Delete</span>
</div>
</td>
</tr>
</tbody>
</table>
<paginator :paginator="paginator" v-on:prev="navigatePrev" v-on:next="navigateNext"/>
<update-profile :data="user" :show="showUpdateModal" v-on:save="updateUser" v-on:close="showUpdateModal=false"/>
</div>
</template>
You can get all your users (if that's not too much data) at start and then paginate them on a clientside.
Something like:
mounted() {
this.api.get('/users').then(response => {
this.users = response.data;
this.paginator.total = Math.ceil(this.users.length / this.paginator.limit);
});
},
methods: {
goToPage(page) {
this.paginator.current = page;
}
},
computed:{
filteredUsers: function () {
return this.users.filter((user) => {
var searchByName = user.name.toLowerCase().match(this.search.toLowerCase());
var searchByLastName = user.lastname.toLowerCase().match(this.search.toLowerCase());
var searchByEmail = user.email.toLowerCase().match(this.search.toLowerCase());
if(searchByName){
return searchByName;
}
if(searchByLastName){
return searchByLastName;
}
if(searchByEmail){
return searchByEmail;
}
}).filter((el, index) => {
return ( index >= (this.paginator.current - 1) * this.paginator.limit
&& index < this.paginator.current * this.paginator.limit);
});
}
}
}
Update
Other option would be to perform serching on a serverside and to send a search string with every page request:
methods: {
goToPage(page) {
this.api.get('/users?page=' + page + '&limit=' + this.paginator.limit
+ '&search=' + this.search).then(response => {
this.users = response.data;
this.paginator = response.paginator;
});
},
performSearch() {
this.goToPage(1);
},
},
}
with search block in a template:
<!--Search Users-->
<div class="control is-expanded">
<h1>Search users</h1>
<input class="input" type="text"
v-model="search" placeholder="Find a user"
#change="performSearch"/>
</div>
You can add debouncing to get results after you type or add a "search!" button after your search input field to trigger performSearch().
<!--Search Users-->
<div class="control is-expanded">
<h1>Search users</h1>
<input class="input" type="text"
v-model="search" placeholder="Find a user"/>
<button #click="performSearch">Search!</button>
</div>
I need to add an additional field to my custom form, I want to add the name of the credit card.
I tried in the following way:
var cardNameElement = elements.create('cardName', {
style: style
//, placeholder: 'Custom card number placeholder',
});
cardNameElement.mount('#card-name-element');
<div id="card-name-element" class="field"></div>
But this does not work, in its documentation only allows to perform these procedures validating only four elements or data: cardNumber, cardExpiry, cardCvc, postalCode.
How can I add the name of the credit card and validate it using stripe.js
My code:
var stripe = Stripe('pk_test_6pRNASCoBOKtIshFeQd4XMUh');
var elements = stripe.elements();
var style = {
base: {
iconColor: '#666EE8',
color: '#31325F',
lineHeight: '40px',
fontWeight: 300,
fontFamily: 'Helvetica Neue',
fontSize: '15px',
'::placeholder': {
color: '#CFD7E0',
},
},
};
var cardNumberElement = elements.create('cardNumber', {
style: style
//, placeholder: 'Custom card number placeholder',
});
cardNumberElement.mount('#card-number-element');
var cardExpiryElement = elements.create('cardExpiry', {
style: style
});
cardExpiryElement.mount('#card-expiry-element');
var cardCvcElement = elements.create('cardCvc', {
style: style
});
cardCvcElement.mount('#card-cvc-element');
/*var postalCodeElement = elements.create('postalCode', {
style: style
});
postalCodeElement.mount('#postal-code-element');*/
function setOutcome(result) {
var successElement = document.querySelector('.success');
var errorElement = document.querySelector('.error');
successElement.classList.remove('visible');
errorElement.classList.remove('visible');
if (result.token) {
// In this example, we're simply displaying the token
successElement.querySelector('.token').textContent = result.token.id;
successElement.classList.add('visible');
// In a real integration, you'd submit the form with the token to your backend server
//var form = document.querySelector('form');
//form.querySelector('input[name="token"]').setAttribute('value', result.token.id);
//form.submit();
} else if (result.error) {
errorElement.textContent = result.error.message;
errorElement.classList.add('visible');
}
}
document.querySelector('form').addEventListener('submit', function(e) {
e.preventDefault();
stripe.createToken(cardNumberElement).then(setOutcome);
});
<script src="https://code.jquery.com/jquery-2.0.2.min.js"></script>
<script src="https://js.stripe.com/v3/"></script>
<form action="" method="POST">
<input type="hidden" name="token" />
<div class="group">
<div class="card-container1">
<label>
<span class="title-card">Card number</span>
<div id="card-number-element" class="field"></div>
<span class="brand"><i class="pf pf-credit-card" id="brand-icon"></i></span>
</label>
</div>
<div class="card-details">
<div class="expiration">
<label>
<span class="title-card">Expiry date</span>
<div id="card-expiry-element" class="field"></div>
</label>
</div>
<div class="cvv">
<label>
<span class="title-card">CVC</span>
<div id="card-cvc-element" class="field"></div>
</label>
</div>
</div>
</div>
<button type="submit">Pay $25</button>
<div class="outcome">
<div class="error"></div>
<div class="success">Success! Your Stripe token is <span class="token"></span></div>
</div>
</form>
What I want to do:
Elements does not support collecting the cardholder's name at the moment. It focuses on collecting:
Card number
Expiration date
CVC
ZIP code (in some countries)
If you want to collect the cardholder's name you have to build your own field for the name and submit it to the API during token creation:
var card_name = document.getElementById('card_name').value;
stripe.createToken(card, {name: card_name}).then(setOutcome);
You can see a live example on jsfiddle here: https://jsfiddle.net/7w2vnyb5/
As I struggled like an idoit on this for a while. As of Feb 2019 you can add tokenData object with information on the details of the card. For Example:
let custData = {
name: 'Firstname Lastname',
address_line1: '21 Great Street',
address_line2: 'Shilloong',
address_city: 'Chicago',
address_state: 'Illinois',
address_zip: '12345',
address_country: 'US'
};
stripe.createToken(card, custData).then(function(result) {
if (result.error) {
// Inform the user if there was an error.
var errorElement = document.getElementById('card-errors');
errorElement.textContent = result.error.message;
} else {
// Send the token to your server.
stripeTokenHandler(result.token);
}
});
});
If you're using "PaymentIntents", which you probably should be if you're EU based / SCA compliant, then the format for this has changed again slightly...
stripe.confirmCardPayment(
'{PAYMENT_INTENT_CLIENT_SECRET}',
{
payment_method: {
card: cardElement,
billing_details: {
name: 'Jenny Rosen'
}
}
}
).then(function(result) {
// Handle result.error or result.paymentIntent
});
stripe.confirmCardPayment docs:
https://stripe.com/docs/stripe-js/reference#stripe-confirm-card-payment
billing_details object docs:
https://stripe.com/docs/api/payment_methods/create#create_payment_method-billing_details
I use Meta-Data for custom fields such as cardholder name:
... create({
amount: myAmount,
currency: 'USD,
description: "Put your full discription here",
source: tokenid,
metedata: {any: "set of", key: "values", that: "you want", cardholder: "name"}
},
idempotency_key "my_idempotency_key"
)}
resource: https://stripe.com/docs/payments/charges-api#storing-information-in-metadata
I have a vue component which prints out a list of radio buttons. I have a watch on internalValue which sends the selected value to the root
I am trying to send a console.log on a click event using a method called doSomething but it is not working. Furthermore I am not getting any errors or warnings.
Load Component
Vue.component('topic', require('./components/Topicselect.vue'));
Use Component
<div class="form-group" id="topic">
<topic v-model="selectedTopic"></topic>
</div>
Initialise Vue
new Vue({
el: '#topic',
data: {
selectedTopic: null
}
});
Component
<template>
<div>
<label v-for="topic in topics" class="radio-inline radio-thumbnail" style="background-image: url('http://s3.hubsrv.com/trendsideas.com/profiles/74046767539/photo/3941785781469144249_690x460.jpg')">
<input type="radio" v-model="internalValue" :click="doSomething" name="topics_radio" :id="topic.id" :value="topic.name">
<span class="white-color lg-text font-regular text-center text-capitalize">{{ topic.name }}</span>
</label>
</div>
</template>
<script>
export default {
props: ['value'],
data () {
return {
internalValue: this.value,
topics: []
}
},
mounted(){
axios.get('/vuetopics').then(response => this.topics = response.data);
},
watch: {
internalValue(v){
this.$emit('input', v);
console.log('the value is ' + this.value);
}
},
methods: {
doSomething: function (){
console.log('doSomething is firing');
}
}
}
</script>
So I am using Meteor/React, but I used Blaze's login template for its convenience. It works great on the homepage, but when I try to login from any other page on the site, the page reloads and the login appears to have been unsuccessful.
This is my implementation.
AccountsUI.jsx
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
export class AccountsUI extends React.Component {
componentDidMount(){
this.view = Blaze.render(Template.loginButtons, this.refs.loginContainer);
}
componentWillUnmount(){
Blaze.remove(this.view);
}
render(){
return(
<span ref="loginContainer" />
)
}
}
mainLayout.jsx
<div className="container-fluid">
<a className="navbar-btn pull-left panel-body"><b>FAQ</b></a>
<a className="navbar-btn pull-right panel-body"><b>Category</b></a>
<a className="navbar-btn pull-right panel-body"><b>Notifications</b></a>
<a className="navbar-btn pull-right panel-body"><b><AccountsUI /></b></a>
</div>
</div>
Why would this work only on certain pages?
Blaze
Your code looks ok, are you importing all components correctly?
Try: https://atmospherejs.com/gadicc/blaze-react-component
and do:
import Blaze from 'meteor/gadicc:blaze-react-component';
....
<a className="navbar-btn pull-right panel-body"><b><Blaze template="loginButtons" /></b></a>
....
Without trying to change your choice of tools too much, I have been exploring React, Meteor and Authentication for a little while, often getting stuck in state management and other dark holes. Below is a overview of some options:
React Accounts-UI package
Personally as a quick tool I am a big fan of the React Accounts-UI package https://atmospherejs.com/std/accounts-ui
It's easy to implement and has many React specific config options.
Check out 'Create your own styled version' to implement in Navbar at https://github.com/studiointeract/accounts-ui/blob/master/README.md
React with Kadira FlowRouter and ReactLayout
For something within the Navbar, here is a stab with flow router.
From the Meteor Guide User/Authentication section:
While a router is optional and the basic functionality will work without it, it’s also a good idea to pick a router integration:
For Navbar login (Not React Accounts-UI).
You need Flowrouter and Reactlayout
Routes
We create 2 route groups which allow us to build auth logic into Flow router easily:
const publicRoutes = FlowRouter.group( { name: 'public' } );
publicRoutes.route( '/login', {
name: 'login',
action() {
ReactLayout.render( App, {
yield: <Login /> }
);
}
}
);
const authenticatedRoutes = FlowRouter.group( { name: 'authenticated' } );
authenticatedRoutes.route( '/hidden', {
name: 'hidden',
action() {
ReactLayout.render( App, {
yield: <Hidden /> }
);
}
}
);
App:
You can modify this to suit your own setup. The approach here is to grab the reactmeteordata mixing which allows us to test if the user is logged or logging in. The isPublic function allows us to test if the user should be allowed on the current route. The rest should be self explanatory.
App = React.createClass({
mixins: [ ReactMeteorData ],
getMeteorData() {
return {
loggingIn: Meteor.loggingIn(),
hasUser: !!Meteor.user(),
isPublic( route ) {
let publicRoutes = [
'login'
];
return publicRoutes.indexOf( route ) > -1;
},
canView() {
return this.isPublic( FlowRouter.current().route.name ) || !!Meteor.user();
}
};
},
loading() {
return <div className="loading"></div>;
},
getView() {
return this.data.canView() ? this.props.yield : <Login />;
},
render() {
return <div className="app-root">
<AppHeader hasUser={this.data.hasUser} />
<div className="container">
{this.data.loggingIn ? this.loading() : this.getView()}
</div>
</div>;
}
}
);
Header:
Nothing cosmic, we change the brandLink depending on user state. We then check hasUser (passed as a prop from the App component) to change which nav component to display.
AppHeader = React.createClass({
mixins: [ ReactMeteorData ],
getMeteorData() {
return { brandLink: !!Meteor.user() ? '/hidden' : '/login' }; },
render() {
return ( <nav className="navbar navbar-default" role="navigation">
<div className="container">
<div className="navbar-header">
<button type="button" className="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse"><span className="sr-only">Toggle navigation</span><span className="icon-bar"></span> <span className="icon-bar"></span><span className="icon-bar"></span>
</button>
<a className="navbar-brand" href={this.data.brandLink}>AuthExample</a>
</div>
{this.props.hasUser ? <AuthenticatedNavigation /> : <PublicNavigation />}
</div>
</nav> );
}
});
AuthenticatedNavigation component :
AuthenticatedNavigation = React.createClass({
currentUserEmail() {
return Meteor.user().emails[0].address;
},
logout( event ) {
event.preventDefault();
return Meteor.logout( () =>
FlowRouter.go( '/login' ) );
},
render() {
return <div id="navbar-collapse" className="collapse navbar-collapse">
<ul className="nav navbar-nav">
<li className={FlowHelpers.currentRoute( 'hidden' )}>Hidden
</li>
</ul>
<ul className="nav navbar-nav navbar-right">
<li className="dropdown">
<a href="#" className="user-profile-toggle dropdown-toggle clearfix" data-toggle="dropdown">{this.currentUserEmail()} <span className="caret"></span>
</a>
<ul className="dropdown-menu" role="menu">
<li>Account Preferences</li>
<li className="logout" onClick={this.logout}>Logout</li>
</ul>
</li>
</ul>
</div>;
}
});
PublicNavigation Component:
PublicNavigation = React.createClass({
render() {
return (
<div id="navbar-collapse" className="collapse navbar-collapse">
<ul className="nav navbar-nav navbar-right">
<li className={FlowHelpers.currentRoute( 'login' )}>
<a href={FlowHelpers.pathFor( 'login' )}>Login</a>
</li>
</ul>
</div> );
}
}
);
Look at https://themeteorchef.com/snippets/authentication-with-react-and-flow-router/ for more details.
I am an absolute beginner in node.js and geddy. I've followed a few tutorials and now I try to write something similar for my purposes.
When I try to create a new item, though, I get the following message:
/arithmetic_problem_types/function%20(id)%20%7B%20%20%20%20%20%20options.id%20=%20id;%20%20%20%20%20%20return%20helpersBase.urlFor.action(options);%20%20%20%20%7D not found.
I have no idea where this could come from. I've looked through the code and found nothing.
Controller:
var ArithmeticProblemTypes = function () {
this.respondsWith =[ 'html', 'json', 'xml', 'js', 'txt'];
this.index = function (req, resp, params) {
var self = this;
geddy.model.ArithmeticProblemType.all(function (err, arithmetic_problem_types) {
self.respond({
params: params, arithmetic_problem_types: arithmetic_problem_types
});
});
};
this.add = function (req, resp, params) {
this.respond({
params: params
});
};
this.create = function (req, resp, params) {
var self = this, arithmetic_problem_type = geddy.model.ArithmeticProblemType.create({
name: '1', title: 'open', numberType: '1', numberRanges: '1', operators: '1', askedFor: '1', specialProblemCategory: '1', askedForNumDenomOrBoth: '1',
reducedFractions:'1', mixedFractions: '1'
});
arithmetic_problem_type.save(function (err, data) {
if (err) {
params.errors = err;
self.transfer('add');
} else {
self.redirect({
controller: self.name
});
}
});
};
....................................................................
};
exports.ArithmeticProblemTypes = ArithmeticProblemTypes;
add.html.ejs
<div class="hero-unit">
<%= partial('_form', {params: params}); %>
</div>
index.html.ejs
<div class="hero-unit">
<h2>Arithmetic Problem Types List</h2>
<%- linkTo('Create a new Aritmetic Problem Type', addArithmeticProblemTypePath, {class: 'btn pull-right'}) %>
</div>
<% if (arithmetic_problem_types && arithmetic_problem_types.length) { %>
<% for (var i in arithmetic_problem_types) { %>
<div class="row todo-item">
<div class="span8">
<h3><%- linkTo(arithmetic_problem_types[i].title, arithmeticProblemTypePath(arithmetic_problem_types[i].id)) %></h3>
</div>
<div class="span4"><h3><i class="icon-list-alt"></i><%= arithmetic_problem_types[i].status; %></h3></div>
</div>
<% } %>
<% } %>
How can I get rid of that message and make it work?
EDIT:
This is the beginning of the _form.html.ejs file:
<%
var isUpdate = params.action == 'edit'
, formTitle = isUpdate ? 'Update this Arithmetic Problem Type' : 'Create a new Arithmetic Problem Type'
, action = isUpdate ? arithmeticProblemTypePath(params.id) + '?_method=PUT' : arithmeticProblemTypePath
, deleteAction = isUpdate ? arithmeticProblemTypePath(params.id) + '?_method=DELETE' : ''
, btnText = isUpdate ? 'Update' : 'Add'
, nameValue = isUpdate ? arithmeticProblemTypePath.name : ''
, errors = params.errors;
%>
<form id="arithmetic-problem-type-form" class="form-horizontal" action="<%= action %>" method="POST">
....
</form>
EDIT2:
Inspecting the page where I should write the name of the item and click the add button, I've found this
<div class="hero-unit">
<form id="arithmetic-problem-type-form" class="form-horizontal" action="function (id) {
options.id = id;
return helpersBase.urlFor.action(options);
}" method="POST">
<fieldset>
<legend>Create a new Arithmetic Problem Type</legend>
<div class="control-group">
<label for="title" class="control-label">Title</label>
<div class="controls">
<input class="span6" name="name" placeholder="enter name" type="text">
</div>
</div>
<div class="form-actions">
<input class="btn btn-primary" type="submit" value="Add">
</div>
</fieldset>
</form>
</div>
Indeed the message comes from the action attribute of the form element, but how can I solve it?
The message is telling you that the requested URL could not be found. AKA 404
/arithmetic_problem_types/function%20(id)%20%7B%20%20%20%20%20%20options.id%20=%20id;%20%20%20%20%20%20return%20helpersBase.urlFor.action(options);%20%20%20%20%7D
is definitely not a nice looking url. So i'm assuming there's something wrong with your form's action attribute. If that's what happened when you validate the form.
If that's what happened when you click the link to "Create a new arithmetic problem type" then you should probably put parenthesis after addArithmeticProblemTypePath