Input search filter not working (React) - ajax

Have component that works with React, Redux and AJAX:
class IncomeProfile extends Component {
constructor(props) {
super(props)
this.state = {
items: this.props.items || []
}
}
componentDidMount() {
this.props.IncomeListProfile();
}
componentWillReceiveProps(nextProps) {
this.setState({ items: nextProps.items });
}
filterList(event) {
var updatedList = this.state.items;
updatedList = updatedList.filter(function(item) {
return item.toLowerCase().search(event.target.value.toLowerCase()) !== -1;
});
this.setState({items: updatedList}); // now this.state.items has a value
}
render() {
var elems = this.props.items;
if (typeof elems == 'undefined') {
elems = [];
}
//console.log(elems);
return (
<div>
<table className='table'>
<thead className='theadstyle'>
<th>date
<input></input>
</th>
<th>
<input onChange={this.filterList} type='text' placeholder='keyword search'></input>
</th>
<th>
<input type='text' placeholder='amount search'></input>
</th>
<th>amount</th>
<th>Show archived</th>
</thead>
<div className='tbodymar'></div>
<tbody >
{elems.map((item) => (
<tr key={item.course_id}>
<td>{item.created_at}</td>
<td>{item.name}</td>
<td>{item.remark}</td>
<td>{item.income_amount}</td>
<td>more options</td>
</tr>
))}
</tbody>
</table>
</div>
)
}
}
const mapDispatchToProps = function(dispatch) {
return {
IncomeListProfile: () => dispatch(IncomeProfileList())
}
}
const mapStateToProps = function(state) {
//var mystore = state.toJS()
var mystore = state.getIn(['incomeProfileList'])['course_list'];
//console.log(mystored.hh);
var copy = Object.assign({}, mystore);
return {items: copy.course_list};
}
export default connect(mapStateToProps, mapDispatchToProps)(IncomeProfile);
When I enter something in input, I get error Cannot read property 'state' of undefined, but console.log show's my state. What wrong with filter ? Where mistake? if I had right state?

this.props.items only get populate after componentDidMount?
If so, and you want to set the items in the state too. You can use componentWillReceiveProps method to set the new props to you state.
componentWillReceiveProps(nextProps) {
this.setState({ items: nextProps.items });
}

Related

.NET Core 6 Pull Down Menu Selection to Group through View Model

I am having partial success searching / grouping data through a viewmodel:
Partial Success:
URL Value
If I search on "B"
https://localhost:7207/Class01Name/Index2?String02NameSelected=B&SearchString=
Problem:
Not filtering data...simply changes pull down menu back to "All," displaying all data. Data not filtered.
**Question:
**
What in the code has to be changed to have the data filtered successfully?
Question is based on Tutorial at:
https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/search?view=aspnetcore-6.0
Model
using System.ComponentModel.DataAnnotations; // Date Format
namespace Project01Name.Models
{
public class Class01Name
{
public int Id { get; set; }
public string? String01Name { get; set; }
public string? String02Name { get; set; }
public int? Int01Name { get; set; }
public bool? Bool01Name { get; set; }
[DataType(DataType.Date)]
public DateTime? DateTime01Name { get; set; }
}
}
**
View Model
**
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace Project01Name.Models.ViewModelsName
{
public class SearchByGroupName
{
public List<Class01Name>? Class01NameList { get; set; } // A list of movies.
public SelectList? String02NameSelection { get; set; } // A SelectList containing the list of genres. This allows the user to select a genre from the list.
public string? String02NameSelected { get; set; } // MovieGenre, which contains the selected genre.
public string? SearchString { get; set; } // SearchString, which contains the text users enter in the search text box.
}
}
Controller Action Method
// GET: String01Names
public async Task<IActionResult> Index2(string class01NameGroup, string searchString)
{
// Use LINQ to get list of genres.
IQueryable<string> string02NameQuery = from m in _context.Class01Name
orderby m.String02Name
select m.String02Name;
var selectVariable = from m in _context.Class01Name
select m;
if (!string.IsNullOrEmpty(searchString))
{
selectVariable = selectVariable.Where(s => s.String01Name!.Contains(searchString));
}
if (!string.IsNullOrEmpty(class01NameGroup))
{
selectVariable = selectVariable.Where(x => x.String02Name == class01NameGroup);
}
var string02NameVM = new SearchByGroupName
{
String02NameSelection = new SelectList(await string02NameQuery.Distinct().ToListAsync()),
Class01NameList = await selectVariable.ToListAsync()
};
return View(string02NameVM);
}
View
#model Project01Name.Models.ViewModelsName.SearchByGroupName
#{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-action="Index2" method="get">
<div class="form-actions no-color">
<p>
<select asp-for="String02NameSelected" asp-items="Model.String02NameSelection"> <option value="">All</option></select>
Title: <input type="text" asp-for="SearchString" />
<input type="submit" value="Filter" />
#*<input type="submit" value="Search" class="btn btn-default" /> |
<a asp-action="Index">Back to Full List</a> *#
</p>
</div>
</form>
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.Class01NameList[0].String01Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Class01NameList[0].String02Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Class01NameList[0].Int01Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Class01NameList[0].DateTime01Name)
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.Class01NameList)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.String01Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.String02Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Int01Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.DateTime01Name)
</td>
<td>
<a asp-action="Edit" asp-route-id="#item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="#item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="#item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Partial Success:
URL Value
If I search on "B"
https://localhost:7207/Class01Name/Index2?String02NameSelected=B&SearchString=
Problem:
Not filtering data...simply changes pull down menu back to "All," displaying all data. Data not filtered.
**Question:
**
What in the code has to be changed to have the data filtered successfully?
Question is based on Tutorial at:
https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/search?view=aspnetcore-6.0
Not filtering data...simply changes pull down menu back to "All,"
displaying all data. Data not filtered.
**Question: ** What in the code has to be changed to have the data filtered successfully?
Well, seems you wanted to implement searching functionality in way, so that you can filter with the dropdown and search box and finally if you select All as dropdown value you want to load all the list without any filter and shorting means the full list which comes at first view.
If so, you need to use javascript for your dropdown change event as cshtml doesn't deal with change event. In addition, as you are using asp.net core MVC which would return HTML View altough, we need json data for Ajax reponse but we are would bee getting HTML View. So Ajax success Function will through an error where we would use filter with All parameter.
Modification Required:
Javascript:
#section scripts {
<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.2.1.min.js"></script>
<script src="https://cdn.datatables.net/1.11.3/js/jquery.dataTables.min.js"></script>
<script>
$(document).ready(function () {
$("#allId").change(function () {
alert("Click");
var allId = $('#allId').val();
console.log(allId);
if (allId == "All") {
alert("Alert");
$.ajax({
url: 'http://localhost:5094/Search/Index2',
type: 'GET',
dataType: 'json',
data: { String02NameSelected: "All", searchString: "" },
success: function (response) {
},
error: function () {
window.location.href = "#Url.Action("Index2", "Search")?String02NameSelected=All&SearchString=";
}
});
}
});
});
</script>
}
Note:
As you can see, in success function we are doing nothing, because it will always throuh an error because we are not returning json. Thus, we will work in error section. indow.location.href = "#Url.Action("Index2", "Search")?String02NameSelected=All&SearchString=";. Here, for your understanding, we will call this function when we select All as our dropdown value in that scenario, we will pass All and nothing , nothing will convert into null and all will be our search key.
Modify Your Existing View:
In your existing view, replace blow dropdown code snippet , means the select items
<select asp-for="String02NameSelected" id="allId" asp-items="Model.String02NameSelection"> <option value="All">All</option></select>
Note: If you notice I hav introduced a id id="allId" which will be using on dropdown change event.
Controller:
public async Task<IActionResult> Index2(string String02NameSelected, string searchString)
{
if (String02NameSelected == "All" && searchString == null)
{
var dataWithoutfileter = new SearchByGroupName();
dataWithoutfileter.String02NameSelection = new SelectList(String02NameSelectionList, "Text", "Value");
dataWithoutfileter.Class01NameList = listOfClass01Name;
return View(dataWithoutfileter);
}
if (!String.IsNullOrEmpty(String02NameSelected) && String02NameSelected !="All")
{
var objOfClass = new SearchByGroupName();
var string02NameQuery = listOfClass01Name.Where(m => m.String01Name.ToLower().Contains(String02NameSelected.ToLower()) || m.String02Name.ToLower().Contains(String02NameSelected.ToLower()));
objOfClass.Class01NameList = string02NameQuery.ToList();
objOfClass.String02NameSelection = new SelectList(String02NameSelectionList, "Text", "Value");
return View(objOfClass);
}
if (!String.IsNullOrEmpty(searchString))
{
var objOfClass = new SearchByGroupName();
var string02NameQuery = listOfClass01Name.Where(m => m.String01Name.ToLower().Contains(searchString.ToLower()) || m.String02Name.ToLower().Contains(searchString.ToLower()));
objOfClass.Class01NameList = string02NameQuery.ToList();
objOfClass.String02NameSelection = new SelectList(String02NameSelectionList, "Text", "Value");
return View(objOfClass);
}
//First loading
var objSearchByGroupName = new SearchByGroupName();
objSearchByGroupName.String02NameSelection = new SelectList(String02NameSelectionList, "Text", "Value");
objSearchByGroupName.Class01NameList = listOfClass01Name;
return View(objSearchByGroupName);
}
}
Complete Demo:
Full Controller With Seed Model Class Value:
public class SearchController : Controller
{
public static List<Class01Name> listOfClass01Name = new List<Class01Name>()
{
new Class01Name() { Id =101, String01Name ="Titanic",String02Name = "Romantic", Int01Name =01, Bool01Name = false, DateTime01Name = new DateTime(2023-01-15) },
new Class01Name() { Id =102, String01Name ="Forest gump",String02Name = "Motivational", Int01Name =02, Bool01Name = true, DateTime01Name = new DateTime(2023-01-12) },
new Class01Name() { Id =103, String01Name ="Spider Man",String02Name = "Action", Int01Name =03, Bool01Name = false, DateTime01Name = new DateTime(2023-01-10) },
new Class01Name() { Id =104, String01Name ="Harry Potter",String02Name = "Suspense", Int01Name =04, Bool01Name = true, DateTime01Name = new DateTime(2023-01-13)},
};
public List<SelectListItem> String02NameSelectionList = new List<SelectListItem>()
{
new SelectListItem { Text = "Motivational", Value = "Motivational" },
new SelectListItem { Text = "Romantic", Value = "Romantic" },
new SelectListItem { Text = "Action", Value = "Action" },
new SelectListItem { Text = "Comedy", Value = "Comedy" }
};
public async Task<IActionResult> Index2(string String02NameSelected, string searchString)
{
if (String02NameSelected == "All" && searchString == null)
{
var dataWithoutfileter = new SearchByGroupName();
dataWithoutfileter.String02NameSelection = new SelectList(String02NameSelectionList, "Text", "Value");
dataWithoutfileter.Class01NameList = listOfClass01Name;
return View(dataWithoutfileter);
}
if (!String.IsNullOrEmpty(String02NameSelected) && String02NameSelected !="All")
{
var objOfClass = new SearchByGroupName();
var string02NameQuery = listOfClass01Name.Where(m => m.String01Name.ToLower().Contains(String02NameSelected.ToLower()) || m.String02Name.ToLower().Contains(String02NameSelected.ToLower()));
objOfClass.Class01NameList = string02NameQuery.ToList();
objOfClass.String02NameSelection = new SelectList(String02NameSelectionList, "Text", "Value");
return View(objOfClass);
}
if (!String.IsNullOrEmpty(searchString))
{
var objOfClass = new SearchByGroupName();
var string02NameQuery = listOfClass01Name.Where(m => m.String01Name.ToLower().Contains(searchString.ToLower()) || m.String02Name.ToLower().Contains(searchString.ToLower()));
objOfClass.Class01NameList = string02NameQuery.ToList();
objOfClass.String02NameSelection = new SelectList(String02NameSelectionList, "Text", "Value");
return View(objOfClass);
}
//First loading
var objSearchByGroupName = new SearchByGroupName();
objSearchByGroupName.String02NameSelection = new SelectList(String02NameSelectionList, "Text", "Value");
objSearchByGroupName.Class01NameList = listOfClass01Name;
return View(objSearchByGroupName);
}
}
Full View:
#model DotNet6MVCWebApp.Controllers.SearchByGroupName
#{
ViewData["Title"] = "Index";
}
<form asp-action="Index2" method="get">
<div class="form-actions no-color">
<p>
<select asp-for="String02NameSelected" id="allId" asp-items="Model.String02NameSelection"> <option value="All">All</option></select>
Title: <input type="text" asp-for="SearchString" />
<input type="submit" name="searchString" />
</p>
</div>
</form>
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.Class01NameList[0].String01Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Class01NameList[0].String02Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Class01NameList[0].Int01Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Class01NameList[0].DateTime01Name)
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.Class01NameList)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.String01Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.String02Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Int01Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.DateTime01Name)
</td>
<td>
<a asp-action="Edit" asp-route-id="#item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="#item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="#item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
#section scripts {
<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.2.1.min.js"></script>
<script src="https://cdn.datatables.net/1.11.3/js/jquery.dataTables.min.js"></script>
<script>
$(document).ready(function () {
$("#allId").change(function () {
alert("Click");
var allId = $('#allId').val();
console.log(allId);
if (allId == "All") {
alert("Alert");
$.ajax({
url: 'http://localhost:5094/Search/Index2',
type: 'GET',
dataType: 'json',
data: { String02NameSelected: "All", searchString: "" },
success: function (response) {
},
error: function () {
window.location.href = "#Url.Action("Index2", "Search")?String02NameSelected=All&SearchString=";
}
});
}
});
});
</script>
}
Output:

How to manipulate axios returned data to print in desired formate

<template>
<div>
<table class="table table-responsive">
<tbody>
<tr v-for="(gameresults, index) in gameresults" :key="index">
<td style="color: #082ad4; font-size: 24px;">{{ gameresults.game_name }}</br>
<h3 style="color:#00d2f1; font-size: 18px;">{{ gameresults.cards }}</h3></td>
<h3 style="color:#00d2f1; font-size: 18px;">{{ this.formattedArray }}</h3></td>
</tr>
</tbody>
</table>
</div></template>
<script>
export default {
props: [''],
mounted() {
console.log('Component mounted.')
},
data() {
return {
gameresults:0,
};
},
methods: {
changeResult() {
let formattedArray = [];
this.gameresults.cards.forEach(str => {
const subs = str.split('');
const subsTwo = subs[2].split(',');
const formattedString = `${subs[1]} - ${subs[0]}-${subsTwo[0]}${subs[4]}-${subsTwo[1]}`;
formattedArray.push(formattedString);
});
console.log('Formatted Data', formattedArray);
}
// this.gameresults[0].cards
},
computed: {
chkgameresults() {
axios.post('/gameresults')
.then(response => {
this.gameresults = response.data ;
this.changeResult();
});
},
},
created () {
this.chkgameresults();
}
};
</script>
ref code axios fetches mysql concat data in array format having 2 keys [game_name and card ] i want card key to be manipulated . when i print card array using this.gameresults[0].card its giving me 123-9,897-0 using {{ gameresults.cards }} inside vu template , i want value to get manipulated like 123-90-897 ( only last 0 gets before the second exp and become 0-897 removing ',' separator
Assuming that your data is an array lets define a method...........
formatData(myData) {
let formattedArray = [];
myData.myValue.forEach(str => {
const subs = str.split('');
const subsTwo = subs[2].split(',');
const formattedString = `${subs[1]} - ${subs[0]}-${subsTwo[0]}${subs[4]}-${subsTwo[1]}`;
formattedArray.push(formattedString);
});
console.log('Formatted Data', formattedArray);
}

React MovieDB API problem. Setting this.setState twice breaks my component

I followed a youtube tutorial using the MovieDB API with React. I can search for movies and pull up a title, description, and movie image. I want to add the ability for a youtube trailer to be played. I have successfully fetched the youtube ID's and store them. I'm having trouble displaying them. If I uncomment in my App.js
this.setState({ rows: videoRows });
Then the youtube videos work. But my title, description, and movie image are undefined and vice versa.
App.js
import React, { Component } from "react";
import "./App.css";
import MovieRow from "./MovieRow.js";
import $ from "jquery";
class App extends Component {
constructor(props) {
super(props);
this.state = {};
this.performSearch("woman");
}
performSearch(searchTerm) {
// console.log("perform search");
const API_KEY = "625914798003ef54176364f32c232968";
const urlString = `https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&query=${searchTerm}`;
const urlVideoString = `https://api.themoviedb.org/3/movie/297762/videos?api_key=${API_KEY}&language=en-US`;
//fetch generic info
$.ajax({
url: urlString,
success: searchResults => {
console.log("fetch basic success");
const results = searchResults.results;
var videoRows = [];
var movieRows = [];
// call next ajax function
$.ajax({
url: urlVideoString,
success: searchResults => {
console.log("fetch Youtube video key success");
const results = searchResults.results;
results.forEach(movie => {
movie.video_src = movie.key;
console.log(movie.video_src);
var videoRow = <MovieRow key={movie.id} movie={movie} />;
videoRows.push(videoRow);
});
//If I run this line below it will break
//my generic basic info(title, movie description, and picture) ,
//but it makes the youtube player work
// this.setState({ rows: videoRows });
},
error: (xhr, status, err) => {
console.log("failed video fetch");
}
});
results.forEach(movie => {
movie.poster_src =
"https://image.tmdb.org/t/p/w185" + movie.poster_path;
console.log(movie.poster_path);
const movieRow = <MovieRow key={movie.id} movie={movie} />;
movieRows.push(movieRow);
});
this.setState({ rows: movieRows });
},
error: (xhr, status, err) => {
console.log("failed fetch");
}
});
}
searchChangeHandler(event) {
console.log(event.target.value);
const boundObject = this;
const searchTerm = event.target.value;
boundObject.performSearch(searchTerm);
}
render() {
return (
<div>
<table className="titleBar">
<tbody>
<tr>
<td>
<img alt="app icon" width="100" src="green_app_icon.svg" />
</td>
<td width="8" />
<td>
<h1>MoviesDB Search</h1>
</td>
</tr>
</tbody>
</table>
<input
style={{
fontSize: 24,
display: "block",
width: "99%",
paddingTop: 8,
paddingBottom: 8,
paddingLeft: 16
}}
onChange={this.searchChangeHandler.bind(this)}
placeholder="Search for movie by title..."
/>
{this.state.rows}
</div>
);
}
}
export default App;
MovieRow.js
import React from "react";
import YouTube from "react-youtube";
class MovieRow extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
viewMovie() {
const url = "https://www.themoviedb.org/movie/" + this.props.movie.id;
window.location.href = url;
}
viewTrailer() {
//const trailerURL = "https://www.youtube.com/watch?v=1Q8fG0TtVAY";
}
_onReady(event) {
// access to player in all event handlers via event.target
event.target.pauseVideo();
}
render() {
const opts = {
height: "390",
width: "50%",
playerVars: {
// https://developers.google.com/youtube/player_parameters
autoplay: 1
}
};
return (
<table key={this.props.movie.id}>
<tbody>
<tr>
<td>
<img alt="poster" width="180" src={this.props.movie.poster_src} />
</td>
<td>
<h1>src {this.props.movie.video_src}</h1>
<h3>{this.props.movie.title}</h3>
<p>{this.props.movie.overview}</p>
<YouTube
videoId={this.props.movie.video_src}
opts={opts}
onReady={this._onReady}
/>
<input
className="btn btn-primary"
type="button"
onClick={this.viewTrailer.bind(this)}
value="Play Trailer"
/>
<input
className="btn btn-primary"
type="button"
onClick={this.viewMovie.bind(this)}
value="View"
/>
</td>
</tr>
</tbody>
</table>
);
}
}
export default MovieRow;
I see a lot of problems going on here. One is that row state is not initialized.
You should always initialize a state on the constructor
this.state = {
row: []
};
The other problem I saw is that you shouldn't be storing components in state.
See this
State should contain data that a component's event handlers may change to trigger a UI update. In real apps this data tends to be very small and JSON-serializable. When building a stateful component, think about the minimal possible representation of its state, and only store those properties in this.state. Inside of render() simply compute any other information you need based on this state. You'll find that thinking about and writing applications in this way tends to lead to the most correct application, since adding redundant or computed values to state means that you need to explicitly keep them in sync rather than rely on React computing them for you.
What you can do instead is rewriting your state like
this.state = {
movies: []
}
And inside your ajax request
// Change these lines to
results.forEach(movie => {
movie.video_src = movie.key;
console.log(movie.video_src);
var videoRow = <MovieRow key={movie.id} movie={movie} />;
videoRows.push(videoRow);
});
// To this
results.forEach(movie => {
movie.video_src = movie.key;
videoRows.push(movie);
});
this.setState({ movies: videoRows });
And lastly inside your render method, loop all the movies in your state and and return
your MovieRow component
{this.state.movies.map(movie => <MovieRow key={movie.id} movie={movie}/>)}

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();
}
}
}

How to solve the pure render/function binding pr0blem in vanilla React?

Let's start with some code:
export default class BookingDriverContainer extends React.PureComponent {
static propTypes = {
bookingId: PropTypes.number.isRequired,
};
constructor(props) {
super(props);
this.state = {
loading: true,
saving: false,
};
}
componentDidMount() {
ajax(Router.route('api.bookingDrivers', {
id: this.props.bookingId,
})).then(res => {
if(res.ok) {
this.setState({
loading: false,
segments: res.data.segments,
drivers: res.data.drivers,
});
}
});
}
driverChanged = segmentId => ev => {
console.log(`Driver for segment ${segmentId} changed to ${ev.target.value}`);
};
render() {
if(this.state.loading) {
return <img src={loadingGif} width="220" height="20" alt="Loading..."/>;
}
return (
<table className="index-table">
<thead>
<tr>
<th style={{width: '50px'}}>Seg.</th>
<th style={{width: '70px'}}>Unit</th>
<th style={{width: '140px'}}>Driver</th>
<th style={{width: '100px'}}>Driver #</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{this.state.segments.map(seg => (
<tr key={seg.id}>
<td>*snip*</td>
<td>*snip*</td>
<td>
<SelectBox onChange={this.driverChanged(seg.id)}>
<option value="" className="not-set">TBD</option>
{this.state.drivers.map(([id, name]) => (
<option key={id} value={id}>{name}</option>
))}
</SelectBox>
</td>
<td>*snip*</td>
<td>*snip*</td>
</tr>
))}
</tbody>
</table>
)
}
}
In this example, my component is not pure because of this: onChange={this.driverChanged(seg.id)}. i.e., every time my component renders, that creates a new function, which will cause <SelectBox> to be re-rendered even though nothing has changed.
How can this be fixed without introducing a large framework like Redux?
Best way I've found so far is just to memoize the function.
driverChanged = memoize(segmentId => ev => {
// ...
});
This way, given the same segmentId it will return the same function instance, which will allow the PureRenderMixin et al. to work their magic.
Here's my memoize function:
export default function memoize(fn, options={
serialize: fn.length === 1 ? x => x : (...args) => JSON.stringify(args),
}) {
let cache = new Map();
return (...args) => {
let key = options.serialize(...args);
if(cache.has(key)) {
return cache.get(key);
}
let value = fn(...args);
cache.set(key, value);
return value;
}
}
It only supports JSON-serializable variables, but you can find a more advanced memoizer on npm if you wish.
If all you want is to prevent creating new functions every time the component renders, you could simply create another component to contain SelectBox component:
class MySelectBox extends React.Component {
static propTypes = {
drivers: PropTypes.arrayOf(PropTypes.object).isRequired,
segId: PropTypes.string.isRequired,
driverChanged: PropTypes.function.isRequired,
}
render() {
const {
drivers,
segId,
driverChanged,
} = this.props;
return (
<SelectBox onChange={ev => driverChanged(ev, segId)}>
<option value="" className="not-set">TBD</option>
{drivers.map(([id, name]) => (
<option key={id} value={id}>{name}</option>
))}
</SelectBox>
);
}
}
Then use this new component inside BookingDriverContainer with the proper properties:
export default class BookingDriverContainer extends React.Component {
// ...
driverChanged = (ev, segId) => {
console.log(`Driver for segment ${segId} changed to ${ev.target.value}`);
}
render() {
// ...
return (
<table className="index-table">
<thead>
{/* ... */}
</thead>
<tbody>
{this.state.segments.map(seg => (
<tr key={seg.id}>
<td>*snip*</td>
<td>*snip*</td>
<td>
<MySelectBox
driverChanged={this.driverChanged}
segId={seg.id}
drivers={this.state.drivers}
/>
</td>
<td>*snip*</td>
<td>*snip*</td>
</tr>
))}
</tbody>
</table>
)
}
}
This way you do not create new functions at every render times.

Resources