Ionic 2 Validating username at signup - validation

I'm new to Ionic/Angular 2..
Try to use custom validators to validate the username but at server but i´m stack into this problem...
Property 'getUserByLogin' does not exist on type 'typeof
UserValidator'.
I have an sigunp.html page ...
<ion-header>
<ion-navbar>
<button ion-button menuToggle>
<ion-icon name="menu"></ion-icon>
</button>
<ion-title>{{ "SIGNUP" | translate }}</ion-title>
</ion-navbar>
</ion-header>
<ion-content class="login-page">
<div class="logo">
<img src="assets/img/appicon.svg" alt="Ionic Logo">
</div>
<ion-card>
<p ion-text>Teste</p>
<ion-card-content *ngFor="let user of users">
<h1>{{user.nome}}</h1>
<p>{{user.password}}</p>
</ion-card-content>
</ion-card>
<form [formGroup]="signupForm" (ngSubmit)="postSignupForm()" >
<ion-list no-lines>
<ion-item>
<ion-label floating>{{ "USER_NAME" | translate }}</ion-label>
<ion-input type="text" formControlName="login" clearInput></ion-input>
</ion-item>
<ion-item text-wrap *ngIf="!signupForm.controls.login.valid && (signupForm.controls.login.dirty || submitAttempt)">
<p ion-text color="danger">{{ "INVALID_USER_NAME" | translate }}</p>
</ion-item>
<ion-item *ngIf="signupForm.controls.login.pending">
<p ion-text color="danger">{{ "VALIDATION_IN_PROGRESS" | translate }}</p>
</ion-item>
<ion-item *ngIf="!signupForm.controls.login.valid && !signupForm.controls.login.pending && (signupForm.controls.login.dirty || submitAttempt)">
<p ion-text color="danger">{{ "USER_NAME_IN_USE" | translate }}</p>
</ion-item>
<ion-item>
<ion-label floating>{{ "PASSWORD" | translate }}</ion-label>
<ion-input type="password" formControlName="password" clearInput></ion-input>
</ion-item>
<ion-item text-wrap *ngIf="!signupForm.controls.password.valid && (signupForm.controls.password.dirty || submitAttempt)">
<p ion-text color="danger">{{ "INVALID_PASSWORD" | translate }}</p>
</ion-item>
<ion-item>
<ion-buttons end>
<ion-row responsive-sm>
<ion-col>
<button [disabled]="signupForm.invalid" type="submit" ion-button icon-left block>
<ion-icon name="add"></ion-icon>
<label>{{ "CREATE" | translate }}</label>
</button>
</ion-col>
<ion-col>
<button (click)="onFacebook(signupForm)" ion-button icon-left block>
<ion-icon name="logo-facebook"></ion-icon>
<label>{{ "FACEBOOK" | translate }}</label>
</button>
</ion-col>
</ion-row>
</ion-buttons>
</ion-item>
</ion-list>
</form>
</ion-content>
And a signup.ts with
import { Component } from '#angular/core';
import {Validators, FormBuilder } from '#angular/forms';
import { NgForm } from '#angular/forms';
import { NavController } from 'ionic-angular';
import { TabsPage } from '../tabs/tabs';
import { UserData } from '../../providers/user-data';
import { UserProvider } from '../../providers/user-provider';
import { UserValidator } from '../../validators/user-validator';
#Component({
selector: 'page-user',
templateUrl: 'signup.html'
})
export class SignupPage {
signupForm : any = {};
users :any [];
id : number = 0;
signup: {username?: string, password?: string} = {};
submitted = false;
constructor(public navCtrl: NavController, public formBuilder : FormBuilder, public userData: UserData, public userService : UserProvider ) {
this.signupForm = this.formBuilder.group({
login : ['', Validators.compose( [ Validators.minLength(6)
, Validators.required
, Validators.pattern('[a-zA-Z]*') ] )
, UserValidator.checkUsername
],
password : ['', Validators.compose( [ Validators.minLength(6)
, Validators.required
] )
]
});
}
getAllUsers() {
return this.userService.getAllUsers().subscribe(
data=>this.users=data,
err=>console.log(err)
)
}
getUser( id : number ) {
return this.userService.getUser( id ).subscribe(
data=>this.users=data,
err=>console.log(err)
)
}
getUserByLogin( login : string ) {
return this.userService.getUserByLogin( login ).subscribe(
data=>this.users=data,
err=>console.log(err)
)
}
postSignupForm (){
console.log( this.signupForm.value );
}
onSignup(form: NgForm) {
this.submitted = true;
if (form.valid) {
this.userData.signup(this.signup.username);
this.navCtrl.push(TabsPage);
}
}
onFacebook(form: NgForm) {
this.getUser(2);
console.log( form.value );
}
}
Where i call an UserValidator.checkUsername that is created at...
import { FormControl } from '#angular/forms';
import { UserProvider } from '../providers/user-provider';
export class UserValidator {
wsreturn : any = [];
constructor( public userService : UserProvider ) {
}
getUserByLogin( login : string ) {
return this.userService.getUserByLogin( login ).subscribe(
data=>this.wsreturn=data,
err=>console.log(err)
)
};
static checkUsername(control: FormControl): any {
return new Promise(resolve => {
//console.log ( '------->' ); console.log ( control.value );
this.getUserByLogin( control.value ); /// I NEED HELP!
// Fake a slow response from server
setTimeout(() => {
if(control.value.toLowerCase() === "luciano"){
resolve({
"username taken": true
});
} else {
resolve(null);
}
}, 2000);
});
}
}
My user-privider is this one...
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import 'rxjs/add/operator/map';
import { Observable } from 'rxjs/Observable';
import { GlobalProvider } from '../providers/global-provider';
#Injectable()
export class UserProvider {
constructor(public http: Http, public global: GlobalProvider) {
}
getAllUsers() {
return this.http.get(this.global.serverAdress + '/usuario', { headers: this.global.headers } ).map(res=>res.json())
}
getUser( id : number ) {
return this.http.get(this.global.serverAdress + '/usuario/'+ id, { headers: this.global.headers } ).map(res=>res.json())
}
getUserByLogin( login : string ) {
return this.http.get(this.global.serverAdress + '/usuario/porlogin/'+ login, { headers: this.global.headers } ).map(res=>res.json())
}
postUser( id : number ) {
return this.http.get(this.global.serverAdress + '/usuario/'+ id, { headers: this.global.headers } ).map(res=>res.json())
}
}

you can not call instance method in static method with this keyword. create directive instead
import { Directive, forwardRef } from '#angular/core';
import { NG_VALIDATORS, FormControl, Validator } from '#angular/forms';
#Directive({
selector: '[checkUsername][ngModel],[checkUsername][formControl]',
providers: [
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => CheckUsernameValidator), multi: true }
]
})
export class CheckUsernameValidator implements Validator {
constructor(public userService: UserProvider) {
}
getUserByLogin(login : string) {
return this.userService.getUserByLogin(login);
};
validate(c: FormControl) {
return new Promise(resolve => { //
this.getUserByLogin( control.value ).subscribe(
data => {
if(control.value.toLowerCase() === "luciano"){
resolve({
"username taken": true
});
} else {
resolve(null);
}
},
err => {
console.log(err);
resolve(null);
}
);
}
}
in your html:
<ion-input type="text" formControlName="login" checkUsername clearInput></ion-input>

I move checkUsername to signup.ts with and made some changes to "bind(this)"... its working as i want but i´m still reading more about "creating directives"...
thanks #tiep-phan and #suraj.
....
constructor(public navCtrl: NavController, public formBuilder : FormBuilder, public userData: UserData, public userService : UserProvider ) {
this.signupForm = this.formBuilder.group({
login : ['', Validators.compose( [ Validators.minLength(6)
, Validators.required
, Validators.pattern('[a-zA-Z]*') ] )
, this.checkUsername.bind(this)
],
password : ['', Validators.compose( [ Validators.minLength(6)
, Validators.required
] )
]
});
}
checkUsername(control: FormControl): any {
return new Promise( resolve => {
this.userService.getUserByLogin( control.value ).subscribe(
data=>{
// console.log('Sucesso:'); console.log(data[0]);
if ( typeof data[0] !== "undefined") {
if ( typeof data[0].login === "undefined") {
resolve(null);
} else {
// username encontrado no banco de dados
resolve( { "INVALID_USER_NAME": true } );
}
} else {
resolve(null);
}
},
err=>{ console.log('Erro:'); console.log(err); }
)
});
}
....

Related

EntityAdaper's method updateMany doesn't update state, even though addMany works fine, what is the reason?

I made an application in which the user passes coordinates. The function makes a request to the server according to the given coordinates and looks for the nearest available establishments. Further, the data is transferred to the formatter and finally to the state. This is what App.tsx looks like
//App.tsx
import React, { useEffect, useState } from "react";
import "./App.css";
import { useAppSelector } from "./hook";
import { useRequestPlaces } from "./hooks/index";
import { useAppDispatch } from "./hook";
const cities = [
{ name: "New York", latlong: "40.760898,-73.961219" },
{ name: "London", latlong: "51.522479,-0.104528" },
{ name: "London Suburb", latlong: "51.353340,-0.032366" },
{ name: "Desert", latlong: "22.941602,25.529665" },
];
const defaultLatlong = "40.760898,-73.961219";
function App() {
const dispatch = useAppDispatch();
const fetchPlaces = useRequestPlaces();
const { ids, entities } = useAppSelector((state) => state.places);
const [latlong, setLatlong] = useState(defaultLatlong);
const minRadius = 50;
useEffect(() => {
fetchPlaces(minRadius, latlong, dispatch);
console.log(entities);
}, [fetchPlaces, latlong, entities, ids]);
return (
<div className="App">
<header className="App-header">
{cities.map((city) => {
return (
<button
type="button"
className="btn btn-outline-light"
onClick={() => {
setLatlong(city.latlong);
console.log(latlong);
}}
>
{city.name}
</button>
);
})}
</header>
<main>
{ids.map((id, index) => {
const place = entities[id];
return (
<div
className="card mx-auto mt-2"
key={index}
style={{ width: "18rem" }}
>
<div className="card-body">
<h5 className="card-title">{place?.name}</h5>
<h6 className="card-subtitle mb-2 text-muted">
<ul>
{place?.categories.map((category) => {
return <li key={category.id}>{category.name}</li>;
})}
</ul>
</h6>
<p className="card-text">
Distance: {place?.distance} meters
<br />
Adress: {place?.location}
</p>
</div>
</div>
);
})}
</main>
</div>
);
}
export default App;
At this stage, the user transmits the coordinates by clicking on the buttons with cities. Next, the coordinates are passed to the API handler functions.
//fetch.ts
import { Dispatch } from "react";
import { getClosestPlaces } from "./getClosestPlaces";
import { placesActions } from "../../slices";
import { Action } from "redux";
import client from "./client";
const fetch = async (
radius: number,
latlong: string,
dispatch: Dispatch<Action>
) => {
const { fetchPlaces } = client();
const params = {
client_id: `${process.env.REACT_APP_CLIENT_ID}`,
client_secret: `${process.env.REACT_APP_CLIENT_SECRET}`,
ll: latlong,
radius: radius.toString(),
limit: "50",
};
const response = await fetchPlaces(new URLSearchParams(params).toString());
const { results } = response.data;
if (results.length !== 0) {
const closestPlaces = getClosestPlaces(results);
// AND HERE IS THE MAIN ISSUE! At this point all reqired data is ok it's an array of objects so I pass it to Action addPlaces which is addMany method.
dispatch(placesActions.addPlaces(closestPlaces));
} else if (results.length === 0 && radius < 1600) {
fetch(radius + 50, latlong, dispatch);
}
return [];
};
export { fetch };
And finally I want to show you Slice, where the method is stored. All the payloads are OK, but it doesn't work with updateMany ???
import {
createSlice,
EntityState,
createEntityAdapter,
} from "#reduxjs/toolkit";
import { FormattedPlace } from "./index";
import { RootState } from "./index";
import { Slice } from "#reduxjs/toolkit/src/createSlice";
import { SliceActions } from "#reduxjs/toolkit/dist/query/core/buildSlice";
const placesAdapter = createEntityAdapter<FormattedPlace>();
const initialState = placesAdapter.getInitialState();
type PlacesReducerActions = {
addPlaces(state: any, { payload }: { payload: any }): void;
};
export type PlacesSliceType = Slice<
EntityState<FormattedPlace>,
PlacesReducerActions,
"places"
>;
const placesSlice: PlacesSliceType = createSlice({
name: "places",
initialState,
reducers: {
addPlaces(state, { payload }) {
// HERE
placesAdapter.updateMany(state, payload);
},
},
});
export const selectors = placesAdapter.getSelectors<RootState>(
(state) => state.places
);
export const { actions } = placesSlice;
export default placesSlice.reducer;
Problem was solved with method setAll. I’m stupid, cause didn’t realise that method updateMany updates only those entities which had been added to state before. So if you want to rewrite your state totally use setAll()

The localStorage is not refreshing in Vuex

I write codes with Vuex to login and logout in my Laravel single page application it's working well but when i login to an account the profiles information (name, address, Email, ...)doesn't show in profile but after i reload the page the profile information loads, and when another user try the profile the data of the last person that login shown to him/her
auth.js:
export function registerUser(credentials){
return new Promise((res,rej)=>{
axios.post('./api/auth/register', credentials)
.then(response => {
res(response.data);
})
.catch(err => {
rej('Somthing is wrong!!')
})
})
}
export function login(credentials){
return new Promise((res,rej)=>{
axios.post('./api/auth/login', credentials)
.then(response => {
res(response.data);
})
.catch(err => {
rej('The Email or password is incorrect!')
})
})
}
export function getLoggedinUser(){
const userStr = localStorage.getItem('user');
if(!userStr){
return null
}
return JSON.parse(userStr);
}
store.js:
import {getLoggedinUser} from './partials/auth';
const user = getLoggedinUser();
export default {
state: {
currentUser: user,
isLoggedIn: !!user,
loading: false,
auth_error: null,
reg_error:null,
registeredUser: null,
},
getters: {
isLoading(state){
return state.loading;
},
isLoggedin(state){
return state.isLoggedin;
},
currentUser(state){
return state.currentUser;
},
authError(state){
return state.auth_error;
},
regError(state){
return state.reg_error;
},
registeredUser(state){
return state.registeredUser;
},
},
mutations: {
login(state){
state.loading = true;
state.auth_error = null;
},
loginSuccess(state, payload){
state.auth_error = null;
state.isLoggedin = true;
state.loading = false;
state.currentUser = Object.assign({}, payload.user, {token: payload.access_token});
localStorage.setItem("user", JSON.stringify(state.currentUser));
},
loginFailed(state, payload){
state.loading = false;
state.auth_error = payload.error;
},
logout(state){
localStorage.removeItem("user");
state.isLoggedin = false;
state.currentUser = null;
},
registerSuccess(state, payload){
state.reg_error = null;
state.registeredUser = payload.user;
},
registerFailed(state, payload){
state.reg_error = payload.error;
},
},
actions: {
login(context){
context.commit("login");
},
}
};
general.js:
export function initialize(store, router) {
router.beforeEach((to, from, next) => {
const requiresAuth = to.matched.some(record => record.meta.requiresAuth);
const currentUser = store.state.currentUser;
if(requiresAuth && !currentUser) {
next('/login');
} else if(to.path == '/login' && currentUser) {
next('/');
} else {
next();
}
if(to.path == '/register' && currentUser) {
next('/');
}
});
axios.interceptors.response.use(null, (error) => {
if (error.resposne.status == 401) {
store.commit('logout');
router.push('/login');
}
return Promise.reject(error);
});
if (store.getters.currentUser) {
setAuthorization(store.getters.currentUser.token);
}
}
export function setAuthorization(token) {
axios.defaults.headers.common["Authorization"] = `Bearer ${token}`
}
I think that this issue is relate to my localstorage, how can i fix this?
I'm novice at the Vue and don't have any idea what is the problem.
Login Component:
<template>
<main>
<form #submit.prevent="authenticate">
<div class="grid-x grid-padding-x">
<div class="small-10 small-offset-2 cell" v-if="registeredUser">
<p class="alert success">Welcome {{registeredUser.name}}</p>
</div>
<div class="small-10 small-offset-2 cell" v-if="authError">
<p class="alert error">
{{authError}}
</p>
</div>
<div class="small-2 cell">
<label for="email" class="text-right middle">Email:</label>
</div>
<div class="small-10 cell">
<input type="email" v-model="formLogin.email" placeholder="Email address">
</div>
<div class="small-2 cell">
<label for="password" class="text-right middle">Password:</label>
</div>
<div class="small-10 cell">
<input type="password" v-model="formLogin.password" placeholder="Enter password">
</div>
<div class="small-10 small-offset-2 cell">
<div class="gap"></div>
<input type="submit" value="Login" class="button success expanded">
</div>
</div>
</form>
</main>
</template>
<script>
import {login} from '../../partials/auth';
export default {
data(){
return {
formLogin: {
email: '',
password: ''
},
error: null
}
},
methods:{
authenticate(){
this.$store.dispatch('login');
login(this.$data.formLogin)
.then(res => {
this.$store.commit("loginSuccess", res);
this.$router.push({path: '/profile'});
})
.catch(error => {
this.$store.commit("loginFailed", {error});
})
}
},
computed:{
authError(){
return this.$store.getters.authError
},
registeredUser(){
return this.$store.getters.registeredUser
}
}
}
</script>
Localstorage data is once loaded on page load, so when you use setItem, this won't be visible until the next time.
You should store the data to vuex store, and use that as the source. Only set and get the data from localstorage on page loads.
Otherwise use something like: https://github.com/robinvdvleuten/vuex-persistedstate
I solved the problem.I have this code in my EditProfile component.
methods: {
getAuthUser () {
axios.get(`./api/auth/me`)
.then(response => {
this.user = response.data
})
},
}
this.user = response.data is wrong, I changed to this:
getAuthUser () {
this.user = this.$store.getters.currentUser
},

Use tab key not working on Prime-react editable Data table

I used an editable data Table of prime-react , and it worked mouse click. But Problem is tab key not work.I want to enable editable mode one cell to another cell using tab key. I use Prime react version 1.4.0
A full code are given below:-
index.js
where my editable table code contains
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { FormattedMessage } from 'react-intl';
import { createStructuredSelector } from 'reselect';
import { compose } from 'redux';
import injectSaga from 'utils/injectSaga';
import injectReducer from 'utils/injectReducer';
import reducer from './reducer';
import saga from './saga';
import messages from './messages';
import { Messages } from 'primereact/components/messages/Messages';
import { Growl } from 'primereact/components/growl/Growl';
import { Panel } from 'primereact/components/panel/Panel';
import { Button } from 'primereact/components/button/Button';
import { DataTable } from 'primereact/components/datatable/DataTable';
import { Column } from 'primereact/components/column/Column';
import { InputText } from 'primereact/components/inputtext/InputText';
import { Dropdown } from 'primereact/components/dropdown/Dropdown';
import CustomDataTable from 'components/CustomDataTable';
import { makeSelectSelectedStudent, makeSelectSectionName, makeSelectStudentUpdateBasicInformation, makeSelectSectionList, makeSelectStdBasicInfo, makeSelectEditorStudent, makeSelectSetMessage, makeSelectSetErrMessage, makeSelectLoaderOff, makeSelectLoaderOn } from './selectors';
import { selectStudent, setEditorData, submitUpdate, getMessage, getErrMessage, submitForm, changeSectionName, getLoaderOn, getLoaderOff } from './actions';
import AppPrivateLayout from '../AppPrivateLayout';
export class StudentUpdateBasicInformation extends React.Component {
componentDidUpdate() {
this.props.changeMessage('');
}
rollBody(rowData) {
return <InputText type="text" value={rowData.studentRoll} />;
}
nameBody(rowData) {
return <InputText type="text" value={rowData.studentName} />;
}
fatherNameBody(rowData) {
return <InputText type="text" value={rowData.fatherName} />;
}
motherNameBody(rowData) {
return <InputText type="text" value={rowData.motherName} />;
}
contactBody(rowData) {
return <InputText type="text" value={rowData.guardianMobile} />;
}
genderBody(rowData) {
let gender = [
{ label: 'Male', value: 'Male' },
{ label: 'Female', value: 'Female' },
{ label: 'Others', value: 'Others' }
];
return <Dropdown value={rowData.studentGender} options={gender} style={{ width: '100%' }} placeholder="Select" />
}
religionBody(rowData) {
let religion = [
{ label: 'Islam', value: 'Islam' },
{ label: 'Hindu', value: 'Hindu' },
{ label: 'Buddhist', value: 'Buddhist' },
{ label: 'Christian', value: 'Christian' },
{ label: 'Others', value: 'Others' }
];
return <Dropdown value={rowData.studentReligion} options={religion} style={{ width: '100%' }} placeholder="Select" />
}
bloodBody(rowData) {
let blood = [
{ label: 'A+', value: 'A+' },
{ label: 'A-', value: 'A-' },
{ label: 'B+', value: 'B+' },
{ label: 'B-', value: 'B-' },
{ label: 'O+', value: 'O+' },
{ label: 'O-', value: 'O-' }
];
return <Dropdown value={rowData.bloodGroup} options={blood} style={{ width: '100%' }} placeholder="Select" />
}
render() {
let rollEditor = (row) => {
return <InputText onChange={(evt) => { this.props.onEditorValueChange(row, evt.target.value); }} placeholder="Roll" value={row.rowData.studentRoll} />
}
let nameEditor = (row) => {
return <InputText onChange={(evt) => { this.props.onEditorValueChange(row, evt.target.value); }} placeholder="Student name" value={row.rowData.studentName} />
}
let fatherNameEditor = (row) => {
return <InputText onChange={(evt) => { this.props.onEditorValueChange(row, evt.target.value); }} placeholder="Father name" value={row.rowData.fatherName} />
}
let motherNameEditor = (row) => {
return <InputText onChange={(evt) => { this.props.onEditorValueChange(row, evt.target.value); }} placeholder="Mother name" value={row.rowData.motherName} />
}
let contactEditor = (row) => {
return <InputText onChange={(evt) => { this.props.onEditorValueChange(row, evt.target.value); }} placeholder="Contact Number" value={row.rowData.guardianMobile} />
}
let genderEditor = (row) => {
let gender = [
{ label: 'Male', value: 'Male' },
{ label: 'Female', value: 'Female' },
{ label: 'Others', value: 'Others' }
];
return <Dropdown value={row.rowData.gender} options={gender} onChange={(evt) => this.props.onEditorValueChange(row, evt.value)} style={{ width: '100%' }} placeholder="Select" />
}
let religionEditor = (row) => {
let religion = [
{ label: 'Islam', value: 'Islam' },
{ label: 'Hindu', value: 'Hindu' },
{ label: 'Buddhist', value: 'Buddhist' },
{ label: 'Christian', value: 'Christian' },
{ label: 'Others', value: 'Others' }
];
return <Dropdown value={row.rowData.religion} options={religion} onChange={(evt) => { this.props.onEditorValueChange(row, evt.value); }} style={{ width: '100%' }} placeholder="Select" />
}
let bloodEditor = (row) => {
let blood = [
{ label: 'A+', value: 'A+' },
{ label: 'A-', value: 'A-' },
{ label: 'B+', value: 'B+' },
{ label: 'B-', value: 'B-' },
{ label: 'O+', value: 'O+' },
{ label: 'O-', value: 'O-' }
];
return <Dropdown value={row.rowData.blood} options={blood} onChange={(evt) => { this.props.onEditorValueChange(row, evt.value); }} style={{ width: '100%' }} placeholder="Select" />
}
let msg = "";
if (this.props.setMessage) {
msg = { severity: 'success', detail: this.props.setMessage.message };
this.growl.show(msg);
}
else if (this.props.setErrMessage) {
msg = { severity: 'error', summary: 'Failed', detail: this.props.setErrMessage };
this.growl.show(msg);
}
if(this.props.loaderOn){
if(this.props.loaderOn === 'On') {
$('.loaderDiv').show();
} else if(this.props.loaderOn === 'Off'){
$('.loaderDiv').hide();
}
}
let content = '';
if (this.props.stdBasicInfo && this.props.stdBasicInfo.length) {
$('#UpdateBtnID').show();
let selectedStudentArr = [];
if (this.props.selectedStudent.length) {
Array.prototype.push.apply(selectedStudentArr, this.props.selectedStudent);
}
let columnData = [
<Column selectionMode="multiple" header="Mark" style={{ width: '3em' }} />,
<Column field="studentRoll" header="Roll No." editor={rollEditor} body={this.rollBody} style={{ width: '55px' }} />,
<Column field="studentName" header="Name" editor={nameEditor} body={this.nameBody} style={{ width: '170px' }} />,
<Column field="fatherName" header="Father Name" editor={fatherNameEditor} body={this.fatherNameBody} style={{ width: '145px' }} />,
<Column field="motherName" header="Mother Name" editor={motherNameEditor} body={this.motherNameBody} style={{ width: '145px' }} />,
<Column field="guardianMobile" header="Contact No." editor={contactEditor} style={{ width: '100px' }} body={this.contactBody} />,
<Column field="studentGender" header="Gender" editor={genderEditor} body={this.genderBody} style={{ width: '85px' }} />,
<Column field="studentReligion" header="Religion" editor={religionEditor} body={this.religionBody} style={{ width: '85px' }} />,
<Column field="bloodGroup" header="Blood Group" editor={bloodEditor} style={{ width: '80px' }} body={this.bloodBody} />
];
content = <CustomDataTable
info={this.props.stdBasicInfo}
onSelectionChange={this.props.onSelectionChange}
selectedData={selectedStudentArr}
columnData={columnData}
isSelectionOn={true}
editable={true}
header={'Student List'}
rows={10}
/>
}
//FOR SECTION LIST
let sectionListOptions = [];
if (this.props.sectionList && this.props.sectionList.length) {
sectionListOptions = this.props.sectionList.map((item) => ({
value: item.classConfigId,
label: item.classShiftSection,
}))
}
return (
<div>
<AppPrivateLayout>
<Panel header="Student Information Update">
<form method="post" onSubmit={this.props.onSubmitForm} >
<div className='ui-g form-group'>
<div className='ui-g-2 ui-lg-2 ui-md-2'></div>
<div className='ui-g-2 ui-lg-2 ui-md-2 ui-sm-12 netiLabel'>
<label> Section <span className="required"> * </span></label>
</div>
<div className='ui-g-3 ui-lg-3 ui-md-4 ui-sm-12 ui-fluid'>
<Dropdown value={this.props.sectionname} onChange={this.props.onChangeSectionList} options={sectionListOptions} placeholder="Select Section" autoWidth={false} />
</div>
<div className='ui-g-2 ui-lg-2 ui-md-3 ui-sm-12 ui-fluid'>
<Button icon="ui-icon-search" title="Search" label='Search'></Button>
</div>
<div className='ui-g-2 ui-lg-2 ui-fluid'></div>
</div>
</form>
{content}
<div className='ui-g'>
<Growl ref={(el) => this.growl = el} />
<div className='ui-g-4 ui-lg-4 ui-md-4 ui-sm-12 ui-fluid'>
</div>
<div className='ui-g-6 ui-lg-6 ui-md-5 ui-sm-12 ui-fluid'></div>
<div className='ui-g-2 ui-lg-2 ui-md-3 ui-sm-12 ui-fluid'>
<Button id="UpdateBtnID" style={{ display: 'none' }} onClick={this.props.onUpdate} icon='ui-icon-autorenew' label='Update'></Button>
</div>
</div>
</Panel>
<div class="loaderDiv" style={{display: 'none'}}>
<img className="sticky" src="https://loading.io/spinners/harmony/lg.harmony-taiji-spinner.gif" />
</div>
</AppPrivateLayout>
</div>
);
}
}
StudentUpdateBasicInformation.propTypes = {
stdBasicInfo: PropTypes.any,
onSubmitForm: PropTypes.func,
onEditorValueChange: PropTypes.func,
value: PropTypes.any,
onUpdate: PropTypes.func,
setMessage: PropTypes.any,
setErrMessage: PropTypes.any,
changeMessage: PropTypes.func,
sectionList: PropTypes.any,
onChangeSectionList: PropTypes.func,
sectionname: PropTypes.any,
loaderOn: PropTypes.any,
};
const mapStateToProps = createStructuredSelector({
stdBasicInfo: makeSelectStdBasicInfo(),
selectedStudent: makeSelectSelectedStudent(),
value: makeSelectEditorStudent(),
setMessage: makeSelectSetMessage(),
setErrMessage: makeSelectSetErrMessage(),
sectionList: makeSelectSectionList(),
sectionname: makeSelectSectionName(),
loaderOn: makeSelectLoaderOn(),
});
function mapDispatchToProps(dispatch) {
return {
changeMessage: (evt) => {
dispatch(getMessage());
dispatch(getErrMessage());
dispatch(getLoaderOn(evt));
},
onSubmitForm: (evt) => {
if (evt !== undefined && evt.preventDefault)
evt.preventDefault();
dispatch(submitForm());
},
onSelectionChange: (evt) => dispatch(selectStudent(evt.data)),
onEditorValueChange: (row, value) => {
dispatch(setEditorData(row, value));
},
onUpdate: (evt) => dispatch(submitUpdate()),
onChangeSectionList: (evt) => dispatch(changeSectionName(evt.value)),
};
}
const withConnect = connect(mapStateToProps, mapDispatchToProps);
const withReducer = injectReducer({ key: 'studentUpdateBasicInformation', reducer });
const withSaga = injectSaga({ key: 'studentUpdateBasicInformation', saga });
export default compose(
withReducer,
withSaga,
withConnect,
)(StudentUpdateBasicInformation);
constants.js
export const DEFAULT_ACTION = 'app/StudentUpdateBasicInformation/DEFAULT_ACTION';
export const SUBMIT_FORM = 'app/StudentUpdateBasicInformation/SUBMIT_FORM';
export const SET_STD_BASIC_INFO = 'app/StudentUpdateBasicInformation/SET_STD_BASIC_INFO';
export const SELECT_STUDENT = 'app/StudentUpdateBasicInformation/SELECT_STUDENT';
export const SET_EDITOR_DATA = 'app/StudentUpdateBasicInformation/SET_EDITOR_DATA';
export const GET_MESSAGE = 'app/StudentUpdateStudentId/GET_MESSAGE';
export const GET_ERR_MESSAGE = 'app/StudentUpdateStudentId/GET_ERR_MESSAGE';
export const SUBMIT_UPDATE = 'app/StudentUpdateBasicInformation/SUBMIT_UPDATE';
export const SET_SECTION_LIST = 'app/StudentUpdateBasicInformation/SET_SECTION_LIST';
export const CHANGE_SECTIONNAME = 'app/StudentUpdateBasicInformation/CHANGE_SECTIONNAME';
export const GET_LOADER_ON = 'app/StudentUpdateBasicInformation/GET_LOADER_ON';
actions.js
import {
DEFAULT_ACTION, SUBMIT_FORM, SET_STD_BASIC_INFO, SELECT_STUDENT, SET_EDITOR_DATA, SUBMIT_UPDATE, GET_MESSAGE, GET_ERR_MESSAGE, CHANGE_SECTIONNAME, SET_SECTION_LIST, GET_LOADER_OFF, GET_LOADER_ON
} from './constants';
export function defaultAction() {
return {
type: DEFAULT_ACTION,
};
}
export function setStdBasicInfo(item) {
return {
type: SET_STD_BASIC_INFO,
item,
}
}
export function selectStudent(data) {
return {
type: SELECT_STUDENT,
data,
};
}
export function setEditorData(row, value) {
return {
type: SET_EDITOR_DATA,
row,
value,
};
}
export function submitForm() {
return {
type: SUBMIT_FORM,
};
}
export function submitUpdate() {
return {
type: SUBMIT_UPDATE,
};
}
export function getMessage(message) {
return {
type: GET_MESSAGE,
message,
}
}
export function getErrMessage(errmessage) {
return {
type: GET_ERR_MESSAGE,
errmessage,
}
}
export function setSectionList(sectionList) {
return {
type: SET_SECTION_LIST,
sectionList,
};
}
export function changeSectionName(sectionname) {
return {
type: CHANGE_SECTIONNAME,
sectionname,
};
}
export function getLoaderOn(loaderOn){
return{
type: GET_LOADER_ON,
loaderOn,
}
}
reducer.js
import { fromJS } from 'immutable';
import {
DEFAULT_ACTION, SET_STD_BASIC_INFO, SELECT_STUDENT, SET_EDITOR_DATA, GET_MESSAGE, GET_ERR_MESSAGE, SET_SECTION_LIST, CHANGE_SECTIONNAME, GET_LOADER_ON, GET_LOADER_OFF
} from './constants';
const initialState = fromJS({
selectedStudent: [],
value: [],
sectionList: {},
sectionname: '',
});
function studentUpdateBasicInformationReducer(state = initialState, action) {
switch (action.type) {
case DEFAULT_ACTION:
return state;
case SET_STD_BASIC_INFO:
return state.set('stdBasicInfo', action.item);
case SELECT_STUDENT:
return state.set('selectedStudent', action.data);
case SET_EDITOR_DATA:
let updatedInfost = [...action.row.value];
updatedInfost = [...action.row.value];
updatedInfost[action.row.rowIndex][action.row.field] = action.value;
return state.set('stdBasicInfo', updatedInfost);
case GET_MESSAGE:
return state.set('setMessage', action.message);
case GET_ERR_MESSAGE:
return state.set('setErrMessage', action.errmessage);
case SET_SECTION_LIST:
return state.set('sectionList', action.sectionList);
case CHANGE_SECTIONNAME:
return state.set('sectionname', action.sectionname);
case GET_LOADER_ON:
return state.set('loaderOn', action.loaderOn);
// case GET_LOADER_OFF:
// return state.set('loaderOff', action.loaderOff);
default:
return state;
}
}
export default studentUpdateBasicInformationReducer;
selectors.js
import { createSelector } from 'reselect';
const selectStudentUpdateBasicInformationDomain = (state) => state.get('studentUpdateBasicInformation');
const makeSelectStudentUpdateBasicInformation = () => createSelector(
selectStudentUpdateBasicInformationDomain,
(substate) => substate.toJS()
);
const makeSelectStdBasicInfo = () => createSelector(selectStudentUpdateBasicInformationDomain, (abc) => abc.get('stdBasicInfo'));
const makeSelectSelectedStudent = () => createSelector(selectStudentUpdateBasicInformationDomain, (substate) => substate.get('selectedStudent'));
const makeSelectEditorStudent = () => createSelector(selectStudentUpdateBasicInformationDomain, (substate) => substate.get('value'));
const makeSelectSetMessage = () => createSelector(selectStudentUpdateBasicInformationDomain, (abc) => abc.get('setMessage'));
const makeSelectSetErrMessage = () => createSelector(selectStudentUpdateBasicInformationDomain, (abc) => abc.get('setErrMessage'));
const makeSelectSectionName = () => createSelector(selectStudentUpdateBasicInformationDomain, (abc) => abc.get('sectionname'));
const makeSelectSectionList = () => createSelector(selectStudentUpdateBasicInformationDomain,(substate) => substate.get('sectionList'));
const makeSelectLoaderOn = () => createSelector(selectStudentUpdateBasicInformationDomain, (substate) => substate.get('loaderOn'));
export {
selectStudentUpdateBasicInformationDomain,
makeSelectStdBasicInfo,
makeSelectSelectedStudent,
makeSelectEditorStudent,
makeSelectSetMessage,
makeSelectSetErrMessage, makeSelectSectionName, makeSelectSectionList, makeSelectLoaderOn,
//makeSelectLoaderOff
};
saga.js
import { take, call, put, select, takeLatest } from 'redux-saga/effects';
import { BASE_URL, FETCH_BASIC_INFO_LIST, UPDATE_ID, GET_CLASS_CONFIGURATION_URL, STUDENT_CONFIG_LIST } from '../../utils/serviceUrl';
import { setStdBasicInfo, getMessage, getErrMessage, selectStudent, setSectionList, changeSectionName, getLoaderOn } from './actions';
import request from '../../utils/request';
import { SUBMIT_FORM, SUBMIT_UPDATE } from './constants';
import { makeSelectEditorStudent, makeSelectSelectedStudent, makeSelectSectionList, makeSelectSectionName } from './selectors';
import { getTokenData } from '../../utils/authHelper';
//FOR SECTION LIST
export function* fetchSectionList() {
const tokenData = JSON.parse(getTokenData());
const requestUrl = BASE_URL.concat(GET_CLASS_CONFIGURATION_URL);
const options = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': tokenData.token_type+" "+tokenData.access_token,
},
};
try {
const response = yield call(request, requestUrl, options);
if (response.item) {
yield put(setSectionList(response.item));
}
} catch (err) {
console.dir(err);
}
}
//FOR STUDENT BASIC INFO LIST
export function* fetchStudentBasicInfoList() {
const tokenData = JSON.parse(getTokenData());
const classConfigId = yield select(makeSelectSectionName());
let msgg;
if (classConfigId == '') {
let msg = "An error has occured. Please fill up all required fields";
yield put(getErrMessage(msg));
}
else {
msgg = 'On';
yield put(getLoaderOn(msgg));
const requestURL = BASE_URL.concat(STUDENT_CONFIG_LIST).concat('?classConfigId=').concat(classConfigId);
const options = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': tokenData.token_type+" "+tokenData.access_token,
},
};
const info = yield call(request, requestURL,options);
yield put(setStdBasicInfo(info.item));
msgg = 'Off';
yield put(getLoaderOn(msgg));
}
}
//FOR UPDATE STUDENT INFORMATION
export function* updateStdBasicInfo() {
const tokenData = JSON.parse(getTokenData());
const selectedCheckData = yield select(makeSelectSelectedStudent());
let selectedData = [];
if (selectedCheckData.length === undefined || selectedCheckData.length === 0) {
const errresult = "An error has occured. Please fill up all required fields";
yield put(getErrMessage(errresult));
} else {
for (const i in selectedCheckData) {
const DataList = selectedCheckData[i];
selectedData.push(DataList);
}
const requestURL = BASE_URL.concat(UPDATE_ID);
const options = {
method: 'PUT',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': tokenData.token_type+" "+tokenData.access_token,
},
body: JSON.stringify(selectedData),
}
try {
const result = yield call(request, requestURL, options);
yield put(selectStudent([]));
yield fetchStudentBasicInfoList();
yield put(getMessage(result));
} catch (err) {
const errresult = "Something went wrong. Please try again.";
yield put(setStdBasicInfo(info.item));
yield put(getErrMessage(errresult));
}
}
}
export default function* defaultSaga() {
yield fetchSectionList();
yield takeLatest(SUBMIT_FORM, fetchStudentBasicInfoList);
yield takeLatest(SUBMIT_UPDATE, updateStdBasicInfo);
}
Dušan's answer worked for me but for 2 exceptions:
In onEditorKeyDown instead of
var table = this.dt.container.childNodes[1].childNodes[0].childNodes[1];
use
let table = this.dt.container.childNodes[0].childNodes[0].childNodes[1];
In addition, (event.which === 9) should be used instead of (event.keyCode == 9) and all the functions must be bound in the constructor:
this.inputTextEditor = this.inputTextEditor.bind(this)
this.xxxEditor = this.xxxEditor.bind(this)
this.onEditorKeyDown = this.onEditorKeyDown.bind(this)
I have an idea how you can do this:
while you are in editable cell, you need to intercept TAB key and then to simulate click on next cell to the right or, if current cell is the last one, on first cell in next row.
Steps in following example are based on PrimeReact's show case editable page. You can adopt them for your particular case.
FUNCTIONAL EXAMPLE
Step 1:
define onRowClickhandler and add it to DataTable component to be able to catch currently clicked rowIndex (we need to know what is the row index of the cell currently edited)
onRowClick(event) {
this.rowIndex = event.index;
}
...
<DataTable ref={(el) => this.dt = el} editable={true} onRowClick={(e) => this.onRowClick(e)} ...>
Step 2:
Define column index (ordinal number) to each of column editors: 1st column has index 0, 2nd column index 1, etc. For example
vinEditor(props) {
return this.inputTextEditor(props, 'vin', 0);
}
yearEditor(props) {
return this.inputTextEditor(props, 'year', 1);
}
brandEditor(props) {
return this.inputTextEditor(props, 'brand', 2);
}
where inputTextEditor now look like this
inputTextEditor(props, field, columnIndex) {
return <InputText type="text" value={props.rowData.year}
onKeyDown={(e) => this.onEditorKeyDown(e, columnIndex)} onChange={(e) => this.onEditorValueChange(props, e.target.value)} />;
}
Note that I've added onKeyDown handler and passed columnIndex arg to it so that we can recognize the column where some key is typed.
Step 3:
Finally we can define onEditorKeyDown to do the magic (check out code comments for additional explanations)
onEditorKeyDown(event, columnIndex) {
console.log("onKeyDown", event);
console.log("key code", event.keyCode);
console.log("columnIndex", columnIndex);
console.log("rowIndex", this.rowIndex);
//change following 2 constants to to fit your case
const columnCount = 4;
const rowCount = 10;
//check if TAB (key code is 9) is pressed on InputText
if (event.keyCode == 9) {
//prevent default behaviour on TAB press
event.preventDefault();
//this.dt is reference to DataTable element
//get reference to `table` node only
var table = this.dt.container.childNodes[1].childNodes[0].childNodes[1];
//check if we are not in last column
if (columnIndex < columnCount - 1) {
//simulate click on next column
table.childNodes[this.rowIndex].childNodes[columnIndex + 1].click();
} else {
//we are in the last column, check if we are not in last row
if (this.rowIndex < rowCount - 1) {
//we are not in the last row
// select next row
this.rowIndex += 1;
//simulate click on first column in next row
table.childNodes[this.rowIndex].childNodes[0].click();
}
}
}
}

Datatable not refreshing on save

I want my table to refresh on save click, it's working for normal table with *ngFor, but I'm using Smartadmin angular template. I think the solution may be related to table.ajax.reload() , but how do i execute this in angular way.
save-tax.component.ts
import { Component, OnInit, Input ,Output, EventEmitter } from '#angular/core';
// Forms related packages
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
import { FormValidation } from 'app/shared/validators/formValidation'; //custom form validation
import { ConfigurationService } from 'app/+configuration/configuration.service';
import { FlashMessagesService } from 'angular2-flash-messages';
#Component({
selector: 'save-tax',
templateUrl: './save-tax.component.html'
})
export class SaveTaxComponent implements OnInit {
#Output() reloadTableData = new EventEmitter();
saveTaxForm: FormGroup;
constructor(private _fb: FormBuilder,
private _config: ConfigurationService,
private _flash: FlashMessagesService) { }
ngOnInit() {
this.saveTaxForm_builder();
}
saveTaxForm_builder() {
this.saveTaxForm = this._fb.group({
tax_title: [null, [
Validators.required
]],
tax_rate: [null, [
Validators.required,
Validators.pattern(FormValidation.patterns().price),
]],
});
}
tTitle = "input"; tRate = "input";
validInput(val) {
var classSuccess = "input state-success";
val == 'tax_title' ? this.tTitle = classSuccess : null;
val == 'tax_rate' ? this.tRate = classSuccess : null;
}
invalidInput(val) {
var classError = "input state-error";
val == 'tax_title' ? this.tTitle = classError : null;
val == 'tax_rate' ? this.tRate = classError : null;
}
classReset() {
this.tTitle = "input";
this.tRate = "input";
}
save_tax() {
if (this.saveTaxForm.value) {
this._config.createTax(this.saveTaxForm.value).subscribe(data => {
if (data.success) {
this._flash.show(data.msg, { cssClass: 'alert alert-block alert-success', timeout: 1000 });
this.saveTaxForm.reset();
this.classReset();
this.reloadTableData.emit(); // Emitting an event
} else {
this.saveTaxForm.reset();
this.classReset();
this._flash.show(data.msg, { cssClass: 'alert alert-block alert-danger', timeout: 3500 });
}
},
error => {
this.saveTaxForm.reset();
this.classReset();
this._flash.show("Please contact customer support. " + error.status + ": Internal server error.", { cssClass: 'alert alert-danger', timeout: 5000 });
});
} else {
this._flash.show('Something went wrong! Please try again..', { cssClass: 'alert alert-warning', timeout: 3000 });
}
}
}
datatable.component.ts
import {Component, Input, ElementRef, AfterContentInit, OnInit} from '#angular/core';
declare var $: any;
#Component({
selector: 'sa-datatable',
template: `
<table class="dataTable responsive {{tableClass}}" width="{{width}}">
<ng-content></ng-content>
</table>
`,
styles: [
require('smartadmin-plugins/datatables/datatables.min.css')
]
})
export class DatatableComponent implements OnInit {
#Input() public options:any;
#Input() public filter:any;
#Input() public detailsFormat:any;
#Input() public paginationLength: boolean;
#Input() public columnsHide: boolean;
#Input() public tableClass: string;
#Input() public width: string = '100%';
constructor(private el: ElementRef) {
}
ngOnInit() {
Promise.all([
System.import('script-loader!smartadmin-plugins/datatables/datatables.min.js'),
]).then(()=>{
this.render()
})
}
render() {
let element = $(this.el.nativeElement.children[0]);
let options = this.options || {}
let toolbar = '';
if (options.buttons)
toolbar += 'B';
if (this.paginationLength)
toolbar += 'l';
if (this.columnsHide)
toolbar += 'C';
if (typeof options.ajax === 'string') {
let url = options.ajax;
options.ajax = {
url: url,
complete: function (xhr) {
options.ajax.reload();
}
}
}
options = $.extend(options, {
"dom": "<'dt-toolbar'<'col-xs-12 col-sm-6'f><'col-sm-6 col-xs-12 hidden-xs text-right'" + toolbar + ">r>" +
"t" +
"<'dt-toolbar-footer'<'col-sm-6 col-xs-12 hidden-xs'i><'col-xs-12 col-sm-6'p>>",
oLanguage: {
"sSearch": "<span class='input-group-addon'><i class='glyphicon glyphicon-search'></i></span> ",
"sLengthMenu": "_MENU_"
},
"autoWidth": false,
retrieve: true,
responsive: true,
initComplete: (settings, json)=> {
element.parent().find('.input-sm', ).removeClass("input-sm").addClass('input-md');
}
});
const _dataTable = element.DataTable(options);
if (this.filter) {
// Apply the filter
element.on('keyup change', 'thead th input[type=text]', function () {
_dataTable
.column($(this).parent().index() + ':visible')
.search(this.value)
.draw();
});
}
//custom functions
element.on('click', 'delete', function () {
var tr = $(this).closest('tr');
var row = _dataTable.row( tr );
if ( $(this).hasClass('delete') ) {
row.remove().draw(false);
console.log(row);
}
else {
//$(table).$('tr.selected').removeClass('selected');
$(this).addClass('selected');
}
console.log($(this).attr("class"))
});
//end custom functions
if (!toolbar) {
element.parent().find(".dt-toolbar").append('<div class="text-right"><img src="assets/img/logo.png" alt="SmartAdmin" style="width: 111px; margin-top: 3px; margin-right: 10px;"></div>');
}
if(this.detailsFormat){
let format = this.detailsFormat
element.on('click', 'td.details-control', function () {
var tr = $(this).closest('tr');
var row = _dataTable.row( tr );
if ( row.child.isShown() ) {
row.child.hide();
tr.removeClass('shown');
}
else {
row.child( format(row.data()) ).show();
tr.addClass('shown');
}
})
}
}
}
tax-list.component.html
<!-- NEW COL START -->
<article class="col-sm-12 col-md-12 col-lg-12">
<!-- Widget ID (each widget will need unique ID)-->
<div sa-widget [editbutton]="false" [custombutton]="false">
<header>
<span class="widget-icon"> <i class="fa fa-percent"></i> </span>
<h2>Tax Rule List</h2>
</header>
<!-- widget div-->
<div>
<!-- widget content -->
<div class="widget-body no-padding">
<sa-datatable [options]="tableData" paginationLength="true" tableClass="table table-striped table-bordered table-hover" width="100%">
<thead>
<tr>
<th data-hide="phone"> ID </th>
<th data-hide="phone,tablet">Tax Title</th>
<th data-class="expand">Tax Rate</th>
<th data-hide="phone,tablet">Status</th>
<th data-hide="phone,tablet"> Action </th>
</tr>
</thead>
</sa-datatable>
</div>
<!-- end widget content -->
</div>
<!-- end widget div -->
</div>
<!-- end widget -->
</article>
<!-- END COL -->
tax-list.component.ts
import { FlashMessagesService } from 'angular2-flash-messages';
import { ConfigurationService } from 'app/+configuration/configuration.service';
import { Component, OnInit } from '#angular/core';
declare var $: any;
#Component({
selector: 'tax-list',
templateUrl: './tax-list.component.html'
})
export class TaxListComponent implements OnInit {
tableData: any;
constructor(private _config: ConfigurationService, private _flash: FlashMessagesService) { }
ngOnInit() {
this.fetchTableData();
this.buttonEvents();
}
fetchTableData() {
this.tableData = {
ajax: (data, callback, settings) => {
this._config.getTaxRules().subscribe(data => {
if (data.success) {
callback({
aaData: data.data
});
} else {
alert(data.msg);
}
},
error => {
alert('Internal server error..check database connection.');
});
},
serverSIde:true,
columns: [
{
render: function (data, type, row, meta) {
return meta.row + 1;
}
},
{ data: 'tax_title' },
{ data: 'tax_rate' },
{ data: 'status' },
{
render: function (data, type, row) {
return `<button type="button" class="btn btn-warning btn-xs edit" data-element-id="${row._id}">
<i class="fa fa-pencil-square-o"></i> Edit</button>
<button type="button" class="btn btn-danger btn-xs delete" data-element-id="${row._id}">
<i class="fa fa-pencil-square-o"></i> Delete</button>`;
}
}
],
buttons: [
'copy', 'pdf', 'print'
]
};
}
buttonEvents(){
document.querySelector('body').addEventListener('click', (event) => {
let target = <Element>event.target; // Cast EventTarget into an Element
if (target.tagName.toLowerCase() === 'button' && $(target).hasClass('edit')) {
this.tax_edit(target.getAttribute('data-element-id'));
}
if (target.tagName.toLowerCase() === 'button' && $(target).hasClass('delete')) {
this.tax_delete(target.getAttribute('data-element-id'));
}
});
}
tax_edit(tax_id) {
}
tax_delete(tax_id) {
this._config.deleteTaxById(tax_id).subscribe(data => {
if (data.success) {
this._flash.show(data.msg, { cssClass: 'alert alert-info fade in', timeout: 3000 });
this.fetchTableData();
} else {
this._flash.show(data.msg, { cssClass: 'alert alert-warning fade in', timeout: 3000 });
}
},
error => {
this._flash.show(error, { cssClass: 'alert alert-warning fade in', timeout: 3000 });
});
}
reloadTable(){
this.ngOnInit();
}
}
You can add a refresh button in your widget using <div class='widget-toolbar'>...</div> and using (click) event binding, attach a method with it. I named it as onRefresh() ...
<div sa-widget [editbutton]="false" [colorbutton]="false">
<header>
<span class="widget-icon">
<i class="fa fa-chart"></i>
</span>
<h2>Sample Datatable</h2>
<div class="widget-toolbar" role="menu">
<a class="glyphicon glyphicon-refresh" (click)="onRefresh('#studentTable table')"></a>
</div>
</header>
<div>
<div class="widget-body no-padding">
<sa-datatable id="studentTable" [options]="datatableOptions" tableClass="table table-striped table-bordered table-hover table-responsive">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Rank</th>
<th>Options</th>
</tr>
</thead>
</sa-datatable>
</div>
</div>
</div>
Focus at the method which I have given in the click event binding, I passed the id of the <sa-datatable> with table, that is #studentTable table that mentions the table tag according to the datatable implementation of the smartadmin.
Now in the component, add a method 'onRefresh()' which should be like
onRefresh(id: any) {
if ($.fn.DataTable.isDataTable(id)) {
const table = $(id).DataTable();
table.ajax.reload();
}
}
In this method, that #studentTable table will come in this id which is a parameter in the method. Using jQuery you can do the table.ajax.reload().
But you need to declare the jQuery at the top.
declare var $ : any;
app.component.ts
import { Component, OnInit, OnDestroy } from '#angular/core';
declare var $: any;
#Component({
selector: 'app-order',
templateUrl: './order.component.html',
styleUrls: ['./order.component.css']
})
export class OrderComponent implements OnInit, OnDestroy {
datatableOptions:{
...
}
constructor(){
...
}
ngOnInit(){
...
}
onRefresh(){
if ($.fn.DataTable.isDataTable(id)) {
const table = $(id).DataTable();
table.ajax.reload();
}
}
}

Jow to enable the circular progress when the user clicks on submit in login page? [admin-on-rest]

How to enable the circular progress when user clicks on submit on the login page? I can able to see the loader symbol in the app bar on other pages but I'm not able to activate it on the login page.
We need to add Custom reducer for login page. I did it in the following way.
1.1. Create a new login page. Just copy and paste the admin-on-rest login page code.
1.2. Update the propTypes like below
Login.propTypes = {
...propTypes,
authClient: PropTypes.func,
previousRoute: PropTypes.string,
theme: PropTypes.object.isRequired,
translate: PropTypes.func.isRequired,
userLogin: PropTypes.func.isRequired,
isLogging: PropTypes.bool.isRequired,
};
1.3. Add the below line
function mapStateToProps(state, props) {
return {
isLogging: state.loginReducer > 0
};
}
1.4. Update the login page with below code.
const enhance = compose(
translate,
reduxForm({
form: 'signIn',
validate: (values, props) => {
const errors = {};
const { translate } = props;
if (!values.username) errors.username = translate('aor.validation.required');
if (!values.password) errors.password = translate('aor.validation.required');
return errors;
},
}),
connect(mapStateToProps, { userLogin: userLoginAction }),
);
export default enhance(Login);
1.5. Replace the submit button code
<CardActions>
<RaisedButton type="submit" primary disabled={isLogging} icon={isLogging && <CircularProgress size={25} thickness={2} />} label={translate('aor.auth.sign_in')} fullWidth />
</CardActions>
1.6 The complete code for the login page is
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { propTypes, reduxForm, Field } from 'redux-form';
import { connect } from 'react-redux';
import compose from 'recompose/compose';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import { Card, CardActions } from 'material-ui/Card';
import Avatar from 'material-ui/Avatar';
import RaisedButton from 'material-ui/RaisedButton';
import TextField from 'material-ui/TextField';
import CircularProgress from 'material-ui/CircularProgress';
import { cyan500, pinkA200, white } from 'material-ui/styles/colors';
import defaultTheme, {translate, Notification, userLogin as userLoginAction } from 'admin-on-rest';
const styles = {
main: {
display: 'flex',
flexDirection: 'column',
minHeight: '100vh',
alignItems: 'center',
justifyContent: 'center',
},
card: {
minWidth: 300,
},
avatar: {
margin: '1em',
textAlign: 'center ',
},
avatarText:{
verticalAlign:'middle',
fontSize:20,
},
form: {
padding: '0 1em 1em 1em',
},
input: {
display: 'flex',
},
};
function getColorsFromTheme(theme) {
if (!theme) return { primary1Color: cyan500, accent1Color: pinkA200 };
const {
palette: {
primary1Color,
accent1Color,
},
} = theme;
return { primary1Color, accent1Color };
}
// see http://redux-form.com/6.4.3/examples/material-ui/
const renderInput = ({ meta: { touched, error } = {}, input: { ...inputProps }, ...props }) =>
<TextField
errorText={touched && error}
{...inputProps}
{...props}
fullWidth
/>;
class Login extends Component {
login = (auth) => this.props.userLogin(auth, this.props.location.state ? this.props.location.state.nextPathname : '/');
render() {
const { handleSubmit, submitting, theme, translate, isLogging } = this.props;
const muiTheme = getMuiTheme(theme);
const { primary1Color } = getColorsFromTheme(muiTheme);
return (
<MuiThemeProvider muiTheme={muiTheme}>
<div style={{ ...styles.main, backgroundColor: primary1Color }}>
<Card style={styles.card}>
<div style={styles.avatar}>
<div>
<Avatar backgroundColor={white} src="EnsembleGreenLogo.png" size={45} />
</div>
<div>
<span style={styles.avatarText}>Ensemble SmartWAN Manager</span>
</div>
</div>
<form onSubmit={handleSubmit(this.login)}>
<div style={styles.form}>
<div style={styles.input} >
<Field
name="username"
component={renderInput}
floatingLabelText={translate('aor.auth.username')}
disabled={submitting}
/>
</div>
<div style={styles.input}>
<Field
name="password"
component={renderInput}
floatingLabelText={translate('aor.auth.password')}
type="password"
disabled={submitting}
/>
</div>
</div>
<CardActions>
<RaisedButton
type="submit"
primary
disabled={isLogging}
icon={isLogging && <CircularProgress size={25} thickness={2} />}
label={translate('aor.auth.sign_in')}
fullWidth
/>
</CardActions>
</form>
</Card>
<Notification />
</div>
</MuiThemeProvider>
);
}
}
Login.propTypes = {
...propTypes,
authClient: PropTypes.func,
previousRoute: PropTypes.string,
theme: PropTypes.object.isRequired,
translate: PropTypes.func.isRequired,
userLogin: PropTypes.func.isRequired,
isLogging: PropTypes.bool.isRequired,
};
Login.defaultProps = {
theme: defaultTheme,
};
function mapStateToProps(state, props) {
return {
isLogging: state.loginReducer > 0
};
}
const enhance = compose(
translate,
reduxForm({
form: 'signIn',
validate: (values, props) => {
const errors = {};
const { translate } = props;
if (!values.username) errors.username = translate('aor.validation.required');
if (!values.password) errors.password = translate('aor.validation.required');
return errors;
},
}),
connect(mapStateToProps, { userLogin: userLoginAction }),
);
export default enhance(Login);
2.1. Add a new file (src/loginReducer.js) in src folder with the below content
import { USER_LOGIN_LOADING, USER_LOGIN_SUCCESS, USER_LOGIN_FAILURE, USER_CHECK } from 'admin-on-rest';
export default (previousState = 0, { type }) => {
switch (type) {
case USER_LOGIN_LOADING:
return previousState + 1;
case USER_LOGIN_SUCCESS:
case USER_LOGIN_FAILURE:
case USER_CHECK:
return Math.max(previousState - 1, 0);
default:
return previousState;
}
};
3.1 Update the app.js admin tag.
<Admin
menu={createMenus}
loginPage={Login}
dashboard={Dashboard}
appLayout={Layout}
customReducers={{ loginReducer }}
>
3.2 import the login page and login reducers in app.js
import loginReducer from './loginReducer';
import Login from "./Login";

Resources