Remove item from cart React - ruby-on-rails

I have 2 problems regarding the below code. I want to remove specific items from my cart in React. ( backend Rails). I know that the splice approach is one of the most used but in my case, it does not delete the specific clicked one, but it deletes the last item all the time regardless of the one that I click one.
My second problem is that my Total does not get updated even if the items do get deleted in the wrong way but still get deleted.
This is the code:
import React, { Component } from 'react';
import BasketPic from './images/cart.png';
import StripePayment from './stripePayment';
class MainBasket extends React.Component {
constructor(props) {
super(props);
this.state = {
toggle: true,
showBasket: false,
items: this.props.items
}
this.toggleBasket = this.toggleBasket.bind(this)
this.removeItemFromBasket = this.removeItemFromBasket.bind(this)
console.log(this.state.items)
}
toggleBasket(){
this.setState({toggle: !this.state.toggle});
}
showCheckout(){
this.setState({showBasket: !this.state.showBasket})
}
addItemToBasket(itemId){
fetch(`http://localhost:3000/books/${itemId}`)
.then( item => item.json())
.then( item => {
this.state.items.push(item);
this.state.total += item.price;
this.setState(this.state);
})
}
removeItemFromBasket(itemId){
var itemToBeDeleted = this.state.items.indexOf(itemId)
var deleted = this.state.items.splice(itemToBeDeleted, 1)
this.setState(this.state)
}
render(){
var count_items = this.props.items.length
var total = this.props.total
return(
<div className="basketInfo">
<div className="mainBasketDiv">
<img src={BasketPic} onClick={this.toggleBasket} />
<strong><p className="itemsCart">{count_items}</p></strong>
<strong><p className="checkOutConf">CheckOut</p></strong>
<div className={"toggleDiv-" + this.state.toggle}>
<h3>{"Total: " + this.props.total}</h3>
<span><h4>{"Items: " + count_items }</h4></span>
<hr/>
{this.props.items.map( item =>
<div className="animated fadeInRight" key={"item-" + item.id}>
<h2>{item.title}</h2>
<h6 onClick={this.removeItemFromBasket} className="remvProd">{"Remove " + item.title}</h6>
</div>
)}
<button onClick={function(){this.showCheckout()}.bind(this)}> Check out</button>
</div>
</div>
<div className="container">
<div className={"checkOutStripe-" + this.state.showBasket}>
<div className="totalBar">
<p className="totalBarTypography"> Total {this.props.items.length} {this.props.items.length < 2 ? "item" : "items"}</p>
<p className="totalBarTypography"> Total {this.props.total} GBP</p>
</div>
<div className="row">
{this.props.items.map(eachItem =>
<div className="col-sm" key={"eachItem-" + eachItem.id}>
<img src={eachItem.image.url} className="checkOutImg"/>
<div className="prodDetails">
<h3>{"Product: " + eachItem.title }</h3>
<h3>{"Price: " + eachItem.price }</h3>
</div>
</div>
)}
</div>
<div>
<StripePayment
description={'BooksMania'}
amount={total}
/>
</div>
</div>
</div>
</div>
)
}
}
// module.exports = MainBasket;
export default MainBasket;
and this is the code where i set my total and my items initial state:
import React, { Component } from 'react';
import BuyButton from './images/buyButton.jpg';
import MainBasket from './mainBasket';
import CheckOutBasket from './checkOutBasket';
import Reviews from './reviews';
class EachBook extends React.Component {
constructor(props){
super(props);
this.state = {
newReview: [],
items: [],
total: 0
}
}
seeAllReviews(bookId){
fetch(`http://localhost:3000/books/${bookId}/reviews`)
.then( reviews => reviews.json())
.then( reviews => {
this.setState({
bookReviews: reviews
})
})
}
addReview(bookId){
fetch(`http://localhost:3000/books/${bookId}/reviews`,{
method: 'POST',
mode: 'cors',
body: JSON.stringify({comment: this.refs.comment.value}),
headers: new Headers({
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRF-Token' : this.props.parent.props.parent.props.csrf
}),
credentials: 'same-origin'
}).then(response => response.json())
.catch(error => alert("There is something wrong with this request"))
.then( response => {
this.setState({newReview: response})
})
}
addItemToBasket(itemId){
fetch(`http://localhost:3000/books/${itemId}`)
.then( item => item.json())
.then( item => {
this.state.items.push(item);
this.state.total += item.price;
this.setState(this.state);
})
}
render(){
return(
<div>
<MainBasket items={this.state.items} total={this.state.total} parent={this}/>
<div className="container">
{this.props.singleBook.map(indBook =>
<div className="indBook" key={indBook.id}>
<h1>{indBook.title}</h1> <br />
<h2>{"Author: " + indBook.author}</h2> <br />
<h4>{"Genre: " + indBook.genre}</h4>
<h4>{"Price: " + indBook.price}£</h4>
<img src={indBook.image.url} />
<div className="button"><img src={BuyButton} onClick={function(){this.addItemToBasket(indBook.id)}.bind(this)}/></div>
<div className="description">{indBook.description}</div>
<h3>{this.state.newReview.comment}</h3>
<div>
<h4>Leave a new review</h4>
<textarea ref="comment" type="text" placeholder='Tell us your thoughts '></textarea>
<button onClick={ function(){this.addReview(indBook.id)}.bind(this) } >Submit</button>
</div>
</div>
)}
</div>
</div>
)
}
}
export default EachBook;
Many thanks for your help!

You’re biggest issue is that you’re mutating items in state, so state doesn’t cause a re-render. I would consider using filter instead of splice and just setting items from that like so:
removeItemFromBasket(itemId) {
const items = this.stat.items.filter(item => item.id !== itemId)
this.setState({ items })
}
The addItemToBasket should also have that issue. You should not mutate state as JavaScript passes objects by reference. You should be using this.state.total instead of this.props.total in the render method.
Hope that helps.

Related

Images not loading on page change using Link in NextJS

This is hard to explain without uploading my full project likely, but here goes. I think I've narrowed it down to some combination of getInitialProps() and getStaticProps(). When I use next/link to change pages images are not being loaded. If I browse directly to the page images will load fine. Project is fairly simple with only 2 pages, index.js and [slug].js. Here's both:
index.js
import React from 'react';
import Layout from '../components/layout';
import Seo from '../components/seo';
import Hero from '../components/hero';
import Forcast from '../components/forcast';
import { fetchAPI } from '../lib/api';
import ReactMarkdown from 'react-markdown';
const Home = ({ pages, homepage }) => {
return (
<Layout pages={pages}>
<Seo seo={homepage.seo} />
<Hero hero={homepage.hero} />
<Forcast />
<main className='main-content'>
<div className='fullwidth-block'>
<div className='container'>
<div className='post single'>
<div className='entry-content'>
<ReactMarkdown
source={homepage.Content}
escapeHtml={false}
transformImageUri={uri =>
uri.startsWith('http') ? uri : `${process.env.REACT_APP_IMAGE_BASE_URL}/${uri}`
}
/>
</div>
</div>
</div>
</div>
</main>
</Layout>
);
};
export async function getStaticProps() {
// Run API calls in parallel
const [pages, homepage] = await Promise.all([
fetchAPI('/pages'),
fetchAPI('/homepage'),
]);
return {
props: { pages, homepage },
revalidate: 1,
};
}
export default Home;
[slug].js
import ReactMarkdown from 'react-markdown';
import Layout from '../components/layout';
import Seo from '../components/seo';
import { fetchAPI } from '../lib/api';
const Page = ({ page, pages }) => {
const seo = {
metaTitle: page.Title,
metaDescription: page.seo.metaDescription,
shareImage: page.seo.shareImage,
}
return (
<Layout pages={pages}>
<Seo seo={page.seo} />
<main className='main-content'>
<div className='container'>
<div className='breadcrumb'>
</div>
</div>
<div className='fullwidth-block'>
<div className='container'>
<div className='row'>
<div className='content col-md-8'>
<div className='post single'>
<h2 className='entry-title'>{page.Title}</h2>
<ReactMarkdown
source={page.Content}
escapeHtml={false}
transformImageUri={uri =>
uri.startsWith('http') ? uri : `${process.env.REACT_APP_IMAGE_BASE_URL}${uri}`
}
/>
</div>
</div>
</div>
</div>
</div>
</main>
</Layout>
);
};
export async function getStaticPaths() {
const pages = await fetchAPI('/pages');
return {
paths: pages.map((page) => ({
params: {
slug: page.slug,
},
})),
fallback: false,
};
}
export async function getStaticProps({ params }) {
const pages = await fetchAPI(
`/pages?slug=${params.slug}`
);
return {
props: { page: pages[0], pages },
revalidate: 1,
};
}
export default Page;
This might also be a Strapi issue though I'm not sure.
The issue happens because the REACT_APP_IMAGE_BASE_URL is not exposed to the browser, and only available on the server.
To have it exposed to the browser you'll need to add the NEXT_PUBLIC_ prefix to it.
# .env.development
NEXT_PUBLIC_REACT_APP_IMAGE_BASE_URL=http://localhost:1337
Then in your code reference it using process.env.NEXT_PUBLIC_REACT_APP_IMAGE_BASE_URL.

Having lots of trouble with React and Redux setup and props

I'm building a Dashboard style app that would show data service outages. The backend is Rails 6 and I'm using React/Redux in the frontend (within Rails). I'm having lots of trouble (due to my greenness in Redux) with getting the data into the front end and mapping state to props. Would love for someone to look at my app and see where I'm going wrong. It seems like I'm also having issues with Lexical behaviour as well.
Here is the top of the app:
import React from 'react';
import { render } from 'react-dom'
import Dashboard from './Dashboard';
import { Provider } from "react-redux";
import { createStore, applyMiddleware, compose } from 'redux'; // we get our store from redux library and we need middleware to wire up Thunk
import thunk from 'redux-thunk';
import reducers from './reducers/rootReducer';
import "bootstrap/dist/css/bootstrap.css";
import "bootstrap/dist/css/bootstrap.min.css";
const storeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducers, storeEnhancers(applyMiddleware(thunk)));
// this is how you hook up
store.subscribe(() => {
console.log('the new state is', store.getState());
console.log('----------');
});
render(
<Provider store={store}>
<Dashboard />
</Provider>,
document.body.appendChild(document.createElement('div')),
)
This is the top visible component Dashboard.js
import React, { Component } from "react";
import RecurringOutagesContainer from "./containers/RecurringOutagesContainer";
import FutureOutagesContainer from "./containers/FutureOutagesContainer";
import CurrentOutagesContainer from "./containers/CurrentOutagesContainer";
import CreateModalComponent from "./components/CreateModalComponent";
import { Container, Row, Col, Image } from "react-bootstrap";
import { getFutureOutages } from "./actions/fetchFutureOutagesAction";
import { getRecurringOutages } from "./actions/fetchRecurringOutagesAction";
import { getServices } from "./actions/fetchServicesAction";
import { connect } from 'react-redux';
class Dashboard extends Component {
state = {
services: [],
outages: [],
showModal: false
};
componentDidMount() {
this.props.getFutureOutages()
this.props.getRecurringOutages()
this.props.getServices()
}
render() {
console.log(this.props)
return (
<div>
<Container>
<Row>
<Col sm={1}>
<img
src={require("./public/logo-2-dashboard.png")}
alt="logo"
id="logo"
></img>
</Col>
<Col md={8}></Col>
</Row>
</Container>
<div className="container">
<div className="d-flex justify-content-md-end bd-highlight">
</div>
</div>
<div className="d-flex justify-content-center bd-highlight dashboard">
<div className="d-flex justify-content-start bd-highlight">
<div className="d-fliex pastOutages">
<h4>Past Outages</h4>
</div>
</div>
<div className="d-flex justify-content-center bd-highlight">
<div className="d-fliex currentOutages">
<h4>Current Outages</h4>
<div className="container">
<div className="col-12">
<CurrentOutagesContainer currentOutages={this.props.services} />
</div>
</div>
</div>
</div>
<div className="d-flex align-items-center flex-column bd-highlight">
<div className="d-fliex justify-content-center">
<h4>Future Outages</h4>
<div className="container" id="futureOutages">
<div className="col-12">
<FutureOutagesContainer futureOutages={this.props.futureOutages} />
</div>
</div>
<h4>Recurring Outages</h4>
<div className="container" id="recurringOutages">
<div className="col-12">
<RecurringOutagesContainer recurringOutages={this.props.recurringOutages} />
</div>
</div>
</div>
</div>
</div>
</div>
);
}
}
const mapStateToProps = state => {
return {
futureOutages: state.futureOutages,
recurringOutages: state.recurringOutages,
services: state.services
}
};
const mapDispatchToProps = dispatch => {
return {
getFutureOutages: () => dispatch(getFutureOutages()),
getRecurringOutages: () => dispatch(getRecurringOutages()),
getServices: () => dispatch(getServices())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Dashboard); // this connects Dashboard to store
Here is one example of an action file:
\\ fetchFutureOutagesAction.js
import axios from 'axios';
export const getFutureOutages = () => dispatch => {
axios.get("/future_outages")
.then(res => {
const futureOutages = res.data;
dispatch({ type: 'FUTURE_OUTAGES', payload: futureOutages });
})
.catch(res => console.log(res.errors));
};
I have a rootReducer like so:
import { combineReducers } from 'redux';
import { futureOutagesReducer } from './futureOutagesReducer';
import { recurringOutagesReducer } from './recurringOutagesReducer';
import { servicesReducer } from './servicesReducer';
export default combineReducers({
futureOutages: futureOutagesReducer,
recurringOutages: recurringOutagesReducer,
services: servicesReducer
});
and here is an example of a reducer file:
const initialState = {
futureOutages: []
}
export const futureOutagesReducer = (state = initialState, action) => {
switch (action.type) {
case 'FUTURE_OUTAGES':
return { futureOutages: [...state.futureOutages, action.payload] };
default:
return state;
}
}
The errors are occuring in the container files that I pass props down to from `Dashboard.jsx':
import React from "react";
import FutureOutagesComponent from "../components/FutureOutagesComponent"
const FutureOutagesContainer = props => {
return (
<div>
{props.futureOutages && props.futureOutages.map((futureOutage, idx) => (
<FutureOutagesComponent key={idx} futureOutage={futureOutage} />
))
}
</div>
)
};
export default FutureOutagesContainer;
When I start ./bin/webpack-dev-server.js, here is a snapshot of errors in console I'm getting:
So clearly the props are not being passed down correctly. Can someone give me some pointers on implementing this better? I had everything working with just a React app but really want to have more flexibility accessing state through out the app.
Based on your reducer, I think you will need to access future outages like this.
const mapStateToProps = state => {
return {
futureOutages: state.futureOutages.futureOutages
}
};
The name of your reducer is futureOutages and it also has a property by the same name whose value is an array.
export default combineReducers({
// state.futureOutages
futureOutages: futureOutagesReducer,
...
})
Accessing state.futureOutages will give you an object which is the full piece of state for that reducer from the Refux store. But you want a specific property. Because it's an object and not array, Array.prototype.map is not a func. HTH.

How to create Multi-Level Treeview in ASP.NET MVC

I have implemented a code to create Tree View and also save it into database.
Controller
public ActionResult IndexMda()
{
using (BackendEntities context = new BackendEntities())
{
var plist = context.MDA.Where(p => p.PARENT_MDA_ID == null).Select(a => new
{
a.MDA_ID,
a.MDA_NAME,
a.MDA_DESCRIPTION,
a.ORGANIZATION_TYPE
}).ToList();
ViewBag.plist = plist;
}
GetHierarchy();
return View();
}
public JsonResult GetHierarchy()
{
List<MDA2> hdList;
List<MdaViewModel> records;
using (BackendEntities context = new BackendEntities())
{
hdList = context.MDA.ToList();
records = hdList.Where(l => l.PARENT_MDA_ID == null)
.Select(l => new MdaViewModel
{
MDA_ID = l.MDA_ID,
text = l.MDA_NAME,
MDA_DESCRIPTION = l.MDA_DESCRIPTION,
ORGANIZATION_TYPE = l.ORGANIZATION_TYPE,
PARENT_MDA_ID = l.PARENT_MDA_ID,
children = GetChildren(hdList, l.MDA_ID)
}).ToList();
}
return this.Json(records, JsonRequestBehavior.AllowGet);
// return View();
}
private List<MdaViewModel> GetChildren(List<MDA2> hdList, long PARENT_MDA_ID)
{
return hdList.Where(l => l.PARENT_MDA_ID == PARENT_MDA_ID)
.Select(l => new MdaViewModel
{
MDA_ID = l.MDA_ID,
text = l.MDA_NAME,
MDA_DESCRIPTION = l.MDA_DESCRIPTION,
ORGANIZATION_TYPE = l.ORGANIZATION_TYPE,
PARENT_MDA_ID = l.PARENT_MDA_ID,
children = GetChildren(hdList, l.MDA_ID)
}).ToList();
}
[HttpPost]
public JsonResult ChangeNodePosition(long MDA_ID, long PARENT_MDA_ID)
{
using (BackendEntities context = new BackendEntities())
{
var Hd = context.MDA.First(l => l.MDA_ID == MDA_ID);
Hd.PARENT_MDA_ID = PARENT_MDA_ID;
context.SaveChanges();
}
return this.Json(true, JsonRequestBehavior.AllowGet);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddNewNode(AddNode model)
{
try
{
if (ModelState.IsValid)
{
using (BackendEntities db = new BackendEntities())
{
MDA2 hierarchyDetail = new MDA2()
{
MDA_NAME = model.NodeName,
PARENT_MDA_ID = model.ParentName,
MDA_DESCRIPTION = model.NodeDescription,
ORGANIZATION_TYPE = model.NodeOrganizationType
};
db.MDA.Add(hierarchyDetail);
db.SaveChanges();
}
return Json(new { success = true }, JsonRequestBehavior.AllowGet);
}
}
catch (Exception ex)
{
throw ex;
}
return Json(new { success = false }, JsonRequestBehavior.AllowGet);
}
The partial view is where the Tree View is created
Partial View
#model BPP.CCSP.Admin.Web.ViewModels.AddNode
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button"
class="close"
data-dismiss="modal"
aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<h4 class="modal-title">Add Node</h4>
</div>
<div class="modal-body">
#using (Html.BeginForm("AddNewNode", "Mda", FormMethod.Post, new { #id = "formaddNode", #class = "form-horizontal", role = "form", enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<div class="col-md-12">
<div class="col-md-6 row">
<div class="input-group">
<input type="text" class="form-control" value="Perent Node" readonly="readonly">
<span class="input-group-addon">
#Html.RadioButtonFor(model => model.NodeTypeRbtn, "Pn", new { #class = "btn btn-primary rbtnnodetype" })
</span>
</div>
</div>
<div class="col-md-6">
<div class="input-group ">
<input type="text" class="form-control" value="Child Node" readonly="readonly">
<span class="input-group-addon">
#Html.RadioButtonFor(model => model.NodeTypeRbtn, "Cn", new { #class = "rbtnnodetype" })
</span>
</div>
</div>
<br />
#Html.ValidationMessageFor(m => m.NodeTypeRbtn, "", new { #class = "alert-error" })
</div>
<div class="clearfix">
</div>
<div class="col-md-12">
<div class="petenddiv hidden">
#Html.Label("Select Parent")
#Html.DropDownList("ParentName", new SelectList(ViewBag.plist, "MDA_ID", "MDA_NAME"), "--select--", new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.ParentName, "", new { #class = "alert-error" })
</div>
</div>
<div class="clearfix">
</div>
<div class="col-md-12">
<div>
#Html.Label("MDA Name")
#Html.TextBoxFor(model => model.NodeName, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.NodeName, "", new { #class = "alert-error" })
</div>
</div>
<div class="col-md-12">
<div>
#Html.Label("Description")
#Html.TextBoxFor(model => model.NodeDescription, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.NodeDescription, "", new { #class = "alert-error" })
</div>
</div>
<div class="col-md-12">
<div>
#Html.Label("Organization Type")
#Html.DropDownListFor(model => model.NodeOrganizationType, new List<SelectListItem>
{
new SelectListItem{Text = "Agency", Value = "Agency"},
new SelectListItem{Text = "Commission", Value = "Commission"},
new SelectListItem{Text = "Department", Value = "Department"},
new SelectListItem{Text = "Ministry", Value = "Ministry"}
}, "Select Error Type", new { #style = "border-radius:3px;", #type = "text", #class = "form-control", #placeholder = "Enter Organization Type", #autocomplete = "on" })
#Html.ValidationMessageFor(model => model.NodeDescription, "", new { #class = "alert-error" })
</div>
</div>
<div class="clearfix">
</div>
<br />
<br />
<div class="col-md-12">
<div>
<div class="pull-left">
<input type="submit" id="savenode" value="S A V E" class="btn btn-primary" />
</div>
<div class="pull-right">
<input type="button" id="closePopOver" value="C L O S E" class="btn btn-primary" />
</div>
</div>
</div>
<div class="clearfix">
</div>
}
</div>
</div>
View
<div class="col-md-12" style="margin:100px auto;">
<div class="modal fade in" id="modalAddNode" role="dialog" aria-hidden="true">
#Html.Partial("_AddNode")
</div>
<div class="col-md-12">
<div class="panel panel-primary">
<div class="panel-heading">Ministries, Departments and Agencies -: [ Add MDA and its Members ]</div>
<div class="panel-body">
<div id="tree"></div>
<div class="clearfix">
</div>
<br />
<div>
<button id="btnDeleteNode" data-toggle="modal" class='btn btn-danger'> Delete Node <span class="glyphicon glyphicon-trash"></span> </button>
<button id="btnpopoverAddNode" data-toggle="modal" class='btn btn-warning'> Add Node <span class="glyphicon glyphicon-plus"></span> </button>
</div>
</div>
</div>
</div>
Scipts
#section Scripts {
#System.Web.Optimization.Scripts.Render("~/bundles/jqueryval")
<script src="#Url.Content("~/Scripts/conditional-validation.js")" type="text/javascript"></script>
<script src="~/Scripts/Gijgo/gijgo.js"></script>
<link href="http://code.gijgo.com/1.3.0/css/gijgo.css" rel="stylesheet" type="text/css" />
<script type="text/javascript">
//'Hierarchy/GetHierarchy'
$(document).ready(function () {
var Usertree = "";
var tree = "";
$.ajax({
type: 'get',
dataType: 'json',
cache: false,
url: '/Mda/GetHierarchy',
success: function (records, textStatus, jqXHR) {
tree = $('#tree').tree({
primaryKey: 'MDA_ID',
dataSource: records,
dragAndDrop: true,
checkboxes: true,
iconsLibrary: 'glyphicons',
//uiLibrary: 'bootstrap'
});
Usertree = $('#Usertree').tree({
primaryKey: 'MDA_ID',
dataSource: records,
dragAndDrop: false,
checkboxes: true,
iconsLibrary: 'glyphicons',
//uiLibrary: 'bootstrap'
});
tree.on('nodeDrop', function (e, MDA_ID, PARENT_MDA_ID) {
currentNode = MDA_ID ? tree.getDataById(MDA_ID) : {};
console.log("current Node = " + currentNode);
parentNode = PerentId ? tree.getDataById(PARENT_MDA_ID) : {};
console.log("parent Node = " + parentNode);
if (currentNode.PARENT_MDA_ID === null && parentNode.PARENT_MDA_ID === null) {
alert("Parent node is not droppable..!!");
return false;
}
// console.log(parent.HierarchyLevel);
var params = { MDA_ID: MDA_ID, PARENT_MDA_ID: PARENT_MDA_ID };
$.ajax({
type: "POST",
url: "/Mda/ChangeNodePosition",
data: params,
dataType: "json",
success: function (data) {
$.ajax({
type: "Get",
url: "/Mda/GetHierarchy",
dataType: "json",
success: function (records) {
Usertree.destroy();
Usertree = $('#Usertree').tree({
primaryKey: 'MDA_ID',
dataSource: records,
dragAndDrop: false,
checkboxes: true,
iconsLibrary: 'glyphicons',
//uiLibrary: 'bootstrap'
});
}
});
}
});
});
$('#btnGetValue').click(function (e) {
var result = Usertree.getCheckedNodes();
if (result == "") { alert("Please Select Node..!!") }
else {
alert("Selected Node id is= " + result.join());
}
});
//delete node
$('#btnDeleteNode').click(function (e) {
e.preventDefault();
var result = tree.getCheckedNodes();
if (result != "") {
$.ajax({
type: "POST",
url: "/Mda/DeleteNode",
data: { values: result.toString() },
dataType: "json",
success: function (data) {
alert("Deleted successfully ");
window.location.reload();
},
error: function (jqXHR, textStatus, errorThrown) {
alert('Error - ' + errorThrown);
},
});
}
else {
alert("Please select Node to delete..!!");
}
});
},
error: function (jqXHR, textStatus, errorThrown) {
alert('Error - ' + errorThrown);
}
});
// show model popup to add new node in Tree
$('#btnpopoverAddNode').click(function (e) {
e.preventDefault();
$("#modalAddNode").modal("show");
});
//Save data from PopUp
$(document).on("click", "#savenode", function (event) {
event.preventDefault();
$.validator.unobtrusive.parse($('#formaddNode'));
$('#formaddNode').validate();
if ($('#formaddNode').valid()) {
var formdata = $('#formaddNode').serialize();
// alert(formdata);
$.ajax({
type: "POST",
url: "/Mda/AddNewNode",
dataType: "json",
data: formdata,
success: function (response) {
// $("#modalAddNode").modal("hide");
window.location.reload();
},
error: function (response) {
alert('Exception found');
// $("#modalAddNode").modal("hide");
window.location.reload();
},
complete: function () {
// $('.ajax-loader').css("visibility", "hidden");
}
});
}
});
//Close PopUp
$(document).on("click", "#closePopup", function (e) {
e.preventDefault();
$("#modalAddNode").modal("hide");
});
$('.rbtnnodetype').click(function (e) {
if ($(this).val() == "Pn") {
$('.petenddiv').attr("class", "petenddiv hidden");
$("#ParentName").val("");
}
else {
$('.petenddiv').attr("class", "petenddiv");
}
});
});
</script>
}
As shown above, what I have created can only do one level node. I want want to create multi-level.Whereby, a child will be a parent to other children.
Please how do I achieve this.
You can see some ASP.NET examples about this at https://github.com/atatanasov/gijgo-asp-net-examples/tree/master/Gijgo.Asp.NET.Examples
Please use our examples in order to achieve that.

A public action method 'ABC' was not found on controller xyz

Background:
I'm doing some changes in already implemented project, Which uses MVC, Kendo Grid.And there is onePost action method called EmployeeSearchByName which takes one string parameter 'Name'.Kendo Grid's pages size is 10 records per page, when I search someones name control properly goes to that action and it fetches the employee according name and shows it in kendo grid but when I click on next page in kendo then I get the error 'A public action method 'EmployeeSearchByName ' was not found on controller xyz'.
Code Of View:
#model Silicus.Finder.Web.ViewModel.EmployeesViewModel
#using Kendo.Mvc.UI;
#using Silicus.Finder.Models.DataObjects;
<link href="~/Content/css/FinderStyle.css" rel="stylesheet" />
#{
ViewBag.Title = "Employees List";
var message = TempData["AlertMessage"] ?? string.Empty;
var importMessage = Session["ImportEmployee"] ?? string.Empty;
Session["ImportEmployee"] = string.Empty;
}
<link href="~/Content/MyKendoStyle.css" rel="stylesheet" />
<link href="~/Content/css/FinderStyle.css" rel="stylesheet" />
<div class="container-fluid" style="padding-right: 0px;padding-left: 0px;">
<div id="modal-container" class="k-popup" tabindex="-1" role="dialog" style="border-style: hidden;margin-top:-100px">
<div class="modal-content" style=" position:fixed !important;z-index:auto;width:97%;">
</div>
</div>
</div>
<div id="adv" class="container-fluid" style="margin-left: 3%;">
<div class="ContainerPanel">
<div>
<span style="color:black; font-weight: bold;">Advanced Search</span>
<div class="header1 pull-right">
<img src="~/Images/Project/down-arrow.png" style="height:20px; margin-top: -3px;" />
</div>
<div class="content">
<br />
#using (Html.BeginForm("GetEmployeesByCriteria", "Employee", FormMethod.Post))
{
var model = Model.SearchCriteria;
var i = 0;
if (i == 0)
{
#Html.EditorFor(modelItem => model, new { htmlAttributes = new { #class = "form-control" } });
++i;
}
}
</div>
</div>
</div>
</div>
<div id="Grid" class="container-fluid" style="margin: 0.5% 3% 0.5% 3%;">
#(Html.Kendo().Grid((IEnumerable<Silicus.Finder.Web.ViewModel.EmployeesListViewModel>)Model.Employees)
.Name("employeeListGrid")
.Columns(columns =>
{
columns.Bound(employee => employee.FullName).Template(#<label>
#Html.ActionLink(item.FullName, "Details", "Employee", new { id = item.EmployeeCode }, new { #class = "modal-link" })
</label>
);
columns.Bound(employee => employee.EmployeeCode).Title("Employee Code");
columns.Bound(employee => employee.Title);
columns.Bound(employee => employee.HighestQualification).Sortable(false);
columns.Bound(employee => employee.EmployeeType).Sortable(false);
columns.Bound(employee => employee.TotalExperienceInMonths);
columns.Bound(employee => employee.SilicusExperienceInMonths).Sortable(false);
})
.Scrollable(scr => scr.Height(300))
.Pageable(pageable => pageable
.Refresh(true)
.PageSizes(true)
.ButtonCount(5))
.Sortable(sortable => sortable.AllowUnsort(false))
)
</div>
#Scripts.Render("~/bundles/jquery")
<script>
//Details Modal Popup
$(function () {
$('body').on('click', '.modal-link', function (e) {
e.preventDefault();
$(this).attr('data-target', '#modal-container');
$(this).attr('data-toggle', 'modal');
$('#Grid').toggle();
$('#adv').toggle();
});
$('body').on('click', '.modal-close-btn', function () {
$('#modal-container').modal('hide');
});
$('#modal-container').on('hidden.bs.modal', function () {
$(this).removeData('bs.modal');
});
$('#CancelModal').on('click', function () {
return false;
});
});
$(document).ready(function () {
$("#modal-container").hide();
$("#dialog-import-employee").hide();
});
$(window).load(function () {
$('#moduleHeaderTitleOnDashBoardImage').text("Employees");
$('#modal-container').hide();
});
$(".header1").click(function () {
$(".ContainerPanel").toggleClass("advance-width");
$header = $(this);
$content = $header.next();
$content.slideToggle(1, function () {
});
});
</script>
Question:How to resolve this ?
Kendo grid sends additional parameters which are used for its sorting & filtering.
Try to rewrite your action method like this:
public JsonResult GetIndexContent([DataSourceRequest]DataSourceRequest request, SearchCriteriaViewModel searchCriteria)
{
// your logic ... + return json
}
What we did additionally was telling the GridBuilder which Read method it should use. You even have the possibility to add searchCriteria as parameters;
.DataSource(datasource => datasource
.Ajax()
.Model(model => model.Id("Id"))
.Read(read => read.Action("GetIndexContent", "YourControllerName").Data(fetchSearchCriteriaMethodName))
.PageSize(10)
);
The JavaScript function 'fetchSearchCriteriaMethodName' should just return your search criteria as a JSON object.

How to pass a model through a MVC controller to a bootstrap modal

I have been working on this for a while and i think i am close. I am trying edit MVC applicationUsers with a bootstrap modal. I am using Angular for the binding, When I click on the User the id is being passed to the Angular controller then to the MVC controller. I thought I could post to the Edit Action in the controller and return the modal with the model. I can not get that work. I created a Action for the modal and it opens just fine. But there is no model attached. How can I can I make this work?
Angular
$scope.editUser = function (id) {
var modalInstance = $modal.open({
templateUrl: 'UsersAdmin/EditUserModal',
controller: $scope.modalEdit,
//matches of the id of your item to recover object model in the controller of the modal
resolve: {
id: function () {
return id
}
}
});
};
//controller of the modal. Inside you can recover your object with ajax request
$scope.modalEdit = function ($scope, $modalInstance, id) {
if (angular.isDefined(id)) {
var reqGetCustomer = $http({ url: '/UsersAdmin/Edit/' + id, method: 'GET' });
reqGetCustomer.success(function (dataResult) {
$scope.model = dataResult;
});
} else { alert('id is undefined'); }
//function to close modal
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
}
}
View with List of Users
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.FullName)
</td>
<td class="text-left" style="width:225px">
#Html.ActionLink("Edit", "Edit", null, new { ng_click = "editUser('" + #item.Id + "')" })
</td>
</tr>
}
</tbody>
MVC controller
public ActionResult EditUserModal(string id)
{
return View();
}
// GET: /Users/Edit/1
public async Task<ActionResult> Edit(string id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
ViewBag.RoleId = new SelectList(RoleManager.Roles, "Id", "Name");
var user = await UserManager.FindByIdAsync(id);
if (user == null)
{
return HttpNotFound();
}
var userRoles = await UserManager.GetRolesAsync(user.Id);
var companies = await _userCompanyService.GetCompaniesAsync();
var selectedCompanies = companies.Where(c => c.Users.Any(u => u.Id == user.Id)).Select(c => c.Id).ToArray();
var model = new EditUserViewModel()
{
Id = user.Id,
UserName = user.UserName,
FullName = user.FullName,
RolesList = RoleManager.Roles.ToList().Select(x => new SelectListItem()
{
Selected = userRoles.Contains(x.Name),
Text = x.Name,
Value = x.Name
}),
CompanyList = new MultiSelectList(companies.Select(c => new
{
Name = c.Name,
Id = c.Id
}),
"Id", "Name", selectedCompanies),
SelectedCompanies = selectedCompanies
};
return View(model);
}
BootstrAP MODAL
#model TransparentEnergy.Models.EditUserViewModel
#{
Layout = null;
}
<div class="modal-header">
<h3 class="modal-title">Edit User</h3>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-12">
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.Id)
<div class="card-body card-padding">
<div class="form-group">
<label for="UserName" class="col-md-2 control-label">UserName</label>
<div class="col-md-10">
<div class="fg-line">
#Html.TextBoxFor(m => m.UserName, new { #class = "form-control fg-input" })
#Html.ValidationMessageFor(model => model.UserName)
</div>
</div>
</div>
<div class="col-sm-9">
<div class="form-group fg-line">
<label for="SelectedRoles" class="control-label">Roles</label>
#foreach (var item in Model.RolesList)
{
<input type="checkbox" name="SelectedRoles" value="#item.Value" checked="#item.Selected" class="checkbox-inline" />
#Html.Label(item.Value, new { #class = "control-label" })
}
</div>
</div>
<div class="form-group">
<div class="col-md-2">
<button type="submit" class="btn bgm-blue waves-effect btn-width">Save</button>
</div>
</div>
</div>
}
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="modal-footer">
<button class="btn bgm-orange waves-effect btn-width" ng-click="cancel()">Close</button>
</div>
</div>
</div>
the best practice is:
1) Write method in controller (or better if you will write WebApi for pass data to client)
2) Write Angular SERVICE which will work against your API.
3) Write Controller and directives for you page.
the all will very easy. When you need data - you just call your angular service from directive. Anf in view (bootstrap model) you work with your directive properties.
This a little example:
public class DoorsController : ApiContollerBase
{
[HttpGet]
public IEnumerable<DoorViewModel> AdminGetDictionaries()
{
//here i just return List of my doors
return Doors.GetDoors();
}
}
Client side:
Service:
angular
.module('adminApp.services.adminData', ['ngResource'])
.factory('AdminData', ['$resource', 'AppConfig', function ($resource, AppConfig) {
return function (dataName, customActionNames) {
customActionNames = customActionNames || {};
var actions = {
getItem: { method: 'GET', url: AppConfig.apiUrl + 'api/' + dataName + '/' + (customActionNames['getItem'] || 'Test') + '/:id' },
getItems: { method: 'GET', isArray: true, url: AppConfig.apiUrl + dataName + '/' + (customActionNames['getItems'] || 'AdminGet') + '/' },
getItemsForTable: { method: 'POST', url: AppConfig.apiUrl + 'api/' + dataName + '/' + (customActionNames['getItemsForTable'] || 'AdminGetForTable') + '/' },
getDictionaries: { method: 'GET', isArray: true, url: AppConfig.apiUrl + 'api/' + dataName + '/' + (customActionNames['getDictionaries'] || 'AdminGetDictionaries') + '/' },
postItem: { method: 'POST', url: AppConfig.apiUrl + 'api/' + dataName + '/' + (customActionNames['postItem'] || 'AdminPost') + '/' },
putItem: { method: 'PUT', url: AppConfig.apiUrl + 'api/' + dataName + '/' + (customActionNames['putItem'] || 'AdminPut') + '/:id' },
deleteItem: { method: 'DELETE', url: AppConfig.apiUrl + dataName + '/' + (customActionNames['deleteItem'] || 'AdminDelete') + '/:id' },
};
var resource = $resource(AppConfig.apiUrl + dataName + '/:id', null, actions);
return {
getItem: function (id) {
return resource.getItem({ id: id });
},
getItems: function () {
return resource.getItems();
},
getItemsForTable: function (params) {
return resource.getItemsForTable(params);
},
getDictionaries: function (params) {
return resource.getDictionaries(params);
},
createItem: function (item) {
return resource.postItem({}, item);
},
updateItem: function (id, item) {
return resource.putItem({ id: id }, item);
},
deleteItem: function (id) {
return resource.deleteItem({ id: id });
}
}
}
}]);
Directive:
(function () {
'use strict';
angular
.module('adminApp.directives.adminTableArea', ['adminApp.directives', 'adminApp.services'])
.directive('adminTableArea', function () {
return {
restrict: 'E',
replace: true,
templateUrl: '/App/Admin/views/General/admin-table-area.html',
scope: {
options: "="
},
controller: ['$scope', '$translate', 'AdminData', 'DictionaryProvider', '$state', '$window',
function ($scope, $translate, AdminData, DictionaryProvider, $state, $window) {
var vm = this;
var data = AdminData(vm.dataSource, vm.dataActionNames);
....etc...
I hope its will help you.
Good luck.
Regards,
David
Finally got it figured out in the simplest way possible. no custom MVC helpers or unnecessary jquery.
Here is the Index View with the table
<div class="table-responsive">
<table class="table table-vmiddle">
<thead>
<tr>
<th>Full Name</th>
<th>Email</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.FullName)
</td>
<td>
#Html.DisplayFor(modelItem => item.Email)
</td>
<td class="text-left" style="width:100px">
#Html.ActionLink("Edit", "Edit", new { id = item.Id }, new { #class = "btn bgm-gray waves-effect edit" })
</td>
<td class="text-left" style="width:100px">
#Html.ActionLink("Delete", "Edit", null, new { ng_click = "deleteUser('" + #item.Id + "')", #class = "btn" })
</td>
</tr>
}
</tbody>
</table>
</div>
here is the angular controller
$('a.edit').on('click', function () {
$.ajax({
url: this.href,
type: 'GET',
cache: false,
success: function (result) {
$('#myModal').html(result).find('.modal').modal({
show: true,
backdrop: false
});
}
});
return false;
});
$scope.deleteUser = function (id) {
ApplicationUserDelete.remove(id).success(function (result) {
}).error(function (err, result) {
console.log(err, result);
});
};
MVC controller action returning PartialView
// GET: /Users/Edit/1
public async Task<ActionResult> Edit(string id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
ViewBag.RoleId = new SelectList(RoleManager.Roles, "Id", "Name");
var user = await UserManager.FindByIdAsync(id);
if (user == null)
{
return HttpNotFound();
}
var userRoles = await UserManager.GetRolesAsync(user.Id);
var companies = await _userCompanyService.GetCompaniesAsync();
var selectedCompanies = companies.Where(c => c.Users.Any(u => u.Id == user.Id)).Select(c => c.Id).ToArray();
var model = new EditUserViewModel()
{
Id = user.Id,
UserName = user.UserName,
FullName = user.FullName,
RolesList = RoleManager.Roles.ToList().Select(x => new SelectListItem()
{
Selected = userRoles.Contains(x.Name),
Text = x.Name,
Value = x.Name
}),
CompanyList = new MultiSelectList(companies.Select(c => new
{
Name = c.Name,
Id = c.Id
}),
"Id", "Name", selectedCompanies),
SelectedCompanies = selectedCompanies
};
return PartialView(model);
}
Partial Modal View
#model TransparentEnergy.Models.EditUserViewModel
#{
Layout = null;
}
<div class="modal fade" tabindex="-1" role="dialog" aria- labelledby="myModalLabel" data-ng-controller="ApplicationUserController">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Edit User</h4>
</div>
<div class="modal-body margin-top30px">
<div class="row">
<div class="col-md-12">
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.Id)
<div class="z-depth-1 padding20 background-white">
<div class="card-body card-padding">
<div class="form-group margin-bottom10px">
<label for="FullName" class="col-md-2 control-label">Full Name</label>
<div class="col-md-10">
<div class="fg-line">
#Html.TextBoxFor(m => m.FullName, new { #class = "form-control fg-input" })
#Html.ValidationMessageFor(model => model.FullName)
</div>
</div>
<td> </td>
</div>
<div class="form-group margin-bottom10px">
<label for="UserName" class="col-md-2 control-label">Email</label>
<div class="col-md-10">
<div class="fg-line">
#Html.TextBoxFor(m => m.UserName, new { #class = "form-control fg-input" })
#Html.ValidationMessageFor(model => model.UserName)
</div>
</div>
<td> </td>
</div>
<div class="form-group margin-bottom10px">
<label for="SelectedRoles" class="col-md-2 control-label">Roles</label>
<div class="col-md-10">
#foreach (var item in Model.RolesList)
{
<label class="checkbox checkbox-inline m-r-20">
<input type="checkbox" name="SelectedRoles" value="#item.Value" checked="#item.Selected" />
<i class="input-helper"></i>
#Html.Label(item.Value, new { #class = "control-label" })
</label>
}
</div>
<td> </td>
</div>
<div class="form-group margin-bottom10px">
<label for="SelectedCompanies" class="col-md-2 control-label">Companies</label>
<div class="col-md-10">
<div class="fg-line">
#Html.ListBoxFor(model => model.SelectedCompanies, Model.CompanyList, new { #class = "form-control" })
</div>
</div>
<td> </td>
</div>
<div class="form-group" style="margin-bottom:60px">
<div class="col-md-2">
<button type="submit" class="btn bgm-blue waves-effect btn-width">Save</button>
</div>
</div>
</div>
</div>
}
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="modal-footer">
<button class="btn bgm-orange waves-effect btn-width" data-dismiss="modal">Close</button>
</div>
</div>
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
</div>
</div>
</div>
The View model was untouched in regards to any of this. hopefully this helps someone. cant believe there isnt a streamlined way to do this!

Resources