laravel 5.5 Property [libros] does not exist on this collection instance - laravel-5.5

I keep getting an error saying property [libros] does not exist on this collection instance. What am I doing wrong? What is the correct way to retrieve all data of my libros?
I have tried with several answers that have given on the site, such as:
-Property [stats] does not exist on this collection instance.
-Laravel - Property [name] does not exist on this collection instance.
- "Property [registration_types] does not exist on this collection instance."
-Property [comment] does not exist on this collection instance.
And with none, I have been able to obtain my results.
These are my models
Almacen Model
Libro Model
Almacen Model
<?php
namespace App\Models\Administracion\Almacenes;
use Illuminate\Database\Eloquent\Model;
use App\Models\Administracion\Almacenes\Libro;
class Almacen extends Model
{
protected $table = 'almacenes';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'clave','nombre', 'calle','colonia','cp','telefono','estado',
];
//Relaciones
//Un almacén tiene muchos libros
public function libros()
{
// return $this->belongsToMany('App\Models\Administracion\Almacenes\Libro','almacen_libro')
return $this->belongsToMany(Libro::class,'almacen_libro', 'almacen_id', 'libro_id')
->withPivot('stock')
->withTimestamps()
->using('App\Models\Administracion\Almacenes\AlmacenLibro');
}
}
Libro Model
<?php
namespace App\Models\Administracion\Almacenes;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
class Libro extends Model
{
protected $table = 'libros';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'clave', 'nombre', 'descripcion','imagen','nivelLibro_id'
];
//Relaciones
//Muchos libros hay en muchos almacenes
public function almacenes()
{
return $this->belongsToMany('App\Models\Administracion\Almacenes\Almacen', 'almacen_libro', 'almacen_id', 'libro_id')
->withPivot('stock')
->withTimestamps()
->using('App\Models\Administracion\Almacenes\AlmacenLibro');
}
}
This is the code in my controller
Almacen Controller
<?php
namespace App\Http\Controllers\Administracion\Almacenes;
use App\Models\Administracion\Almacenes\Almacen;
use App\User;
use App\Models\Administracion\Almacenes\AlmacenUser;
use App\Models\Administracion\Almacenes\AlmacenLibro;
use Illuminate\Http\Request;
use App\Http\Requests\Administracion\Almacenes\AlmacenFormRequest;
use App\Http\Controllers\Controller;
use DB;
use Barryvdh\DomPDF\Facade as PDF;
use Maatwebsite\Excel\Facades\Excel;
use App\Exports\AlmacenesExport;
use App\Models\Administracion\Almacenes\Libro;
use App\Models\Administracion\Almacenes\NivelLibro;
// use App\AlmacenPrueba;
// use App\LibroPrueba;
class AlmacenesController extends Controller
{
public function pdfDetalle()
{
//Selecciono el almacén a mostrar para la cabecera
$almacenes = Almacen::with('libros')->get();
// $almacenes = Almacen::has('libros')->get();
// $almacenes = Almacen::find(1)->libros();
dd($almacenes);
// $libros = $almacenes->libros->toArray();
$pdf = PDF::loadView('administracion.almacenes.almacenes.almacenesDetallePdf', [
'almacenes'=>$almacenes,
// 'libros'=>$libros
]);
return $pdf->download('DetallesAlmacenes.pdf');
}
}
and this is my sight.
AlmacenesDetalleVista
Almacen Vista
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
.page-break {
page-break-after: always;
}
</style>
<div class="page-break"></div>
<title>Detalle del almacén</title>
</head>
<body>
#foreach ($almacenes->libros as $libro)
<h1>Detalle del almacén</h1>
<table>
<tr>
<th>Clave</th>
<th>Descripción</th>
<th>Calle</th>
</tr>
<tr>
<td> {{$libro->clave }} </td>
<td> {{$libro->descripcion }} </td>
</tr>
</table>
#endforeach
<div class="page-break"></div>
</body>
</html>
What I want is to obtain the data from my stores, and of each store, each one of the books that it has.
When I perform the dd method on my warehouse variable, I get this.
dd method
Collection {#824 ▼
#items: array:5 [▼
0 => Almacen {#689 ▼
#table: "almacenes"
#fillable: array:7 [▶]
#connection: "mysql"
#primaryKey: "id"
#keyType: "int"
+incrementing: true
#with: []
#withCount: []
#perPage: 15
+exists: true
+wasRecentlyCreated: false
#attributes: array:10 [▼
"id" => 1
"clave" => "Deleg"
"nombre" => "Delegación"
"calle" => "Francisco I Madero"
"colonia" => "Sindurio"
"cp" => "58500"
"telefono" => "44332586"
"estado" => 1
"created_at" => "2018-09-13 04:05:56"
"updated_at" => "2018-09-13 04:05:56"
]
#original: array:10 [▶]
#changes: []
#casts: []
#dates: []
#dateFormat: null
#appends: []
#dispatchesEvents: []
#observables: []
#relations: array:1 [▼
"libros" => Collection {#822 ▼
#items: array:3 [▼
0 => Libro {#803 ▼
#table: "libros"
#fillable: array:5 [▶]
#connection: "mysql"
#primaryKey: "id"
#keyType: "int"
+incrementing: true
#with: []
#withCount: []
#perPage: 15
+exists: true
+wasRecentlyCreated: false
#attributes: array:8 [▼
"id" => 1
"clave" => "B1ELP"
"nombre" => "La Palabra"
"descripcion" => "Libro que enseña a leer y a escribir a los adultos"
"imagen" => ""
"nivelLibro_id" => 1
"created_at" => "2018-09-13 04:05:55"
"updated_at" => "2018-09-21 03:45:03"
]
#original: array:13 [▶]
#changes: []
#casts: []
#dates: []
#dateFormat: null
#appends: []
#dispatchesEvents: []
#observables: []
#relations: array:1 [▶]
#touches: []
+timestamps: true
#hidden: []
#visible: []
#guarded: array:1 [▶]
}
1 => Libro {#804 ▶}
2 => Libro {#805 ▶}
]
}
]
#touches: []
+timestamps: true
#hidden: []
#visible: []
#guarded: array:1 [▶]
}
1 => Almacen {#690 ▶}
2 => Almacen {#691 ▶}
3 => Almacen {#692 ▶}
4 => Almacen {#693 ▶}
]
}
How can I get the data from my books?
Thanks for your help and answers

I have been able to solve the problem. Thank you very much in any way.
The solution was to perform a double foreach.
The view is as follows:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Detalle del almacén</title>
</head>
<body>
#foreach ($almacenes as $almacen)
<h1>Detalle del almacén</h1>
<table>
<tr>
<th>Clave</th>
<th>Nombre</th>
<th>Calle</th>
</tr>
<tr>
<td> {{$almacen->clave }} </td>
<td> {{$almacen->nombre }} </td>
<td> {{$almacen->calle}} </td>
</tr>
<tr>
<th>Clave</th>
<th>Título</th>
<th>Nivel</th>
<th>Stock</th>
</tr>
#foreach ($almacen->libros as $libro)
<tr>
<td> {{ $libro->clave }} </td>
<td> {{ $libro->nombre }} </td>
<td> {{ $libro->nivel->nombre }} </td>
<td> {{ $libro->pivot->stock }} </td>
</tr>
#endforeach
</table>
#endforeach
</body>
</html>

Related

PayPal API PopUp closes immediately after clicking PayPal button (Sandbox)

I am trying to implement the PayPal API into my Django / Vue checkout system, but everytime i try to access checkout via the paypal checkout buttons, thepopup closes immediately and i get these errors:
Error messages in developer tools
Obviously it has something to do with the cart's items' attributes and i tried to adjust them accordingly, but i can't figure out how to fix it. My Code:
<template>
<div class="page-checkout">
<div class="columns is-multiline">
<div class="column is-12">
<h1 class="title">Kasse</h1>
</div>
<div class="column is-12 box">
<table class="table is-fullwidth">
<thead>
<tr>
<th>Artikel</th>
<th>Preis</th>
<th>Anzahl</th>
<th>Gesamt</th>
</tr>
</thead>
<!-- vue for loop for looping through items in cart and displaying them in a table -->
<tbody>
<tr
v-for="item in cart.items"
v-bind:key="item.product.id"
>
<td>{{ item.product.name }}</td>
<td>{{ item.product.price }}€</td>
<td>{{ item.quantity }}</td>
<td>{{ getItemTotal(item).toFixed(2) }}€</td>
</tr>
</tbody>
<tfoot>
<tr style="font-weight: bolder; font-size: larger;">
<td colspan="2">Insgesamt</td>
<td>{{ cartTotalLength }}</td>
<td>{{ cartTotalPrice.toFixed(2) }}€</td>
</tr>
</tfoot>
</table>
</div>
<!-- Fields for user information -->
<div class="column is-12 box">
<h2 class="subtitle">Versanddetails</h2>
<p class="has-text-grey mb-4">*Felder müssen ausgefüllt sein</p>
<div class="columns is-multline">
<div class="column is-6">
<div class="field">
<label>*Vorname</label>
<div class="control">
<input type="text" class="input" v-model="first_name">
</div>
</div>
<div class="field">
<label>*Nachname</label>
<div class="control">
<input type="text" class="input" v-model="last_name">
</div>
</div>
<div class="field">
<label>*E-Mail</label>
<div class="control">
<input type="email" class="input" v-model="email">
</div>
</div>
<div class="field">
<label>*Telefonnummer</label>
<div class="control">
<input type="text" class="input" v-model="phone">
</div>
</div>
<div class="column is-6">
<div class="field">
<label>*Adresse</label>
<div class="control">
<input type="text" class="input" v-model="address">
</div>
</div>
<div class="field">
<label>*Postleitzahl</label>
<div class="control">
<input type="text" class="input" v-model="zipcode">
</div>
</div>
<div class="field">
<label>*Ort</label>
<div class="control">
<input type="text" class="input" v-model="place">
</div>
</div>
</div>
<!-- looping through errors for authorization -->
<div class="notification is-danger mt-4" v-if="errors.length">
<p v-for="error in errors" v-bind:key="error">{{ error }}</p>
</div>
<hr>
<div id="card-element" class="mb-5"></div>
<!-- Part for PayPal implementation -->
<template v-if="cartTotalLength">
<hr>
<!-- Set up a container element for the button -->
<div id="paypal-button-container"></div>
<!-- Include the PayPal JavaScript SDK -->
<div ref="paypal"></div>
<!-- <button class="button is-dark" #click="submitForm">Weiter mit PayPal</button> -->
</template>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'Checkout',
data() {
return {
loaded: false,
paidFor: false,
cart: {
items: []
},
stripe: {},
card: {},
first_name: '',
last_name: '',
email: '',
phone: '',
address: '',
zipcode: '',
place: '',
errors: []
}
},
mounted() {
document.title = 'Kasse | RELOAD'
this.cart = this.$store.state.cart
// Imported PayPal functionality from https://fireship.io/lessons/paypal-checkout-frontend/
const script = document.createElement("script");
script.src = "https://www.paypal.com/sdk/js?client-id=AeAKmRDX7HTesdUvfqYqCQz3fZHIkjAoQ5-BG6K-7xnL5GBMVvwQba53v-I8Fx1p9wurUtoBuk7D6bV1"; // Replace YOUR-CLIENT-ID with paypal credentials
script.addEventListener("load", this.setLoaded);
document.body.appendChild(script); // Adds script to the document's body
},
watch: {
$route(to, from,) {
if (to.name === 'Category') {
this.getCategory()
}
}
},
methods: {
getItemTotal(item) {
return item.quantity * item.product.price
},
// Authorization methods
submitForm() {
this.errors = []
if (this.first_name === '') {
this.errors.push('Bitte Vornamen angeben')
}
if (this.last_name === '') {
this.errors.push('Bitte Nachnamen angeben')
}
if (this.email === '') {
this.errors.push('Bitte E-Mail angeben')
}
if (this.phone === '') {
this.errors.push('Bitte Telefonnummer angeben')
}
if (this.adress === '') {
this.errors.push('Bitte Adresse angeben')
}
if (this.zipcode === '') {
this.errors.push('Bitte Postleitzahl angeben')
}
if (this.place === '') {
this.errors.push('Bitte Ort angeben')
}
},
// Imported PayPal functionality from https://fireship.io/lessons/paypal-checkout-frontend/
setLoaded() {
this.loaded = true;
window.paypal
.Buttons({
// createOrder: (data, actions) => {
// return actions.order.create({
// purchase_units: [
// {
// description: this.product.description,
// amount: {
// currency_code: "EUR",
// value: this.product.price
// }
// }
// ]
createOrder: (data, actions) => {
return actions.order.create({
"purchase_units": [{
"amount": {
"currency_code": "EUR",
"value": item.product.price,
"breakdown": {
"item_total": { /* Required when including the items array */
"currency_code": "EUR",
"value": item.quantity * product.price
}
}
},
"items": [
{
"name": item.product.name, /* Shows within upper-right dropdown during payment approval */
"description": item.product.description, /* Item details will also be in the completed paypal.com transaction view */
"unit_amount": {
"currency_code": "EUR",
"value": item.product.price
},
"quantity": item.product.quantity
},
]
}]
});
},
onApprove: async (data, actions) => {
const order = await actions.order.capture();
this.paidFor = true;
console.log(order);
},
onError: err => {
console.log(err);
}
})
.render(this.$refs.paypal);
}
},
computed: {
// Computing total price and total amount of all items
cartTotalPrice() {
return this.cart.items.reduce((acc, curVal) => {
return acc += curVal.product.price * curVal.quantity
}, 0)
},
cartTotalLength() {
return this.cart.items.reduce((acc, curVal) => {
return acc += curVal.quantity
}, 0)
}
}
}
</script>
The createOrder function inside setLoaded() is assigning properties using item although it's not clear where item comes from as it's not being passed in from anywhere:
setLoaded() {
this.loaded = true;
window.paypal.Buttons({
createOrder: (data, actions) => {
return actions.order.create({
purchase_units: [
{
amount: {
currency_code: "EUR",
value: item.product.price, // <--- item is not defined here
breakdown: {
item_total: {
currency_code: "EUR",
value: item.quantity * product.price, // <--- or here
},
},
},
...
If you mean to reference the items in the cart data property you need to use this.cart.items and possibly loop through it if you're trying to send individual item details.

How can I generate access error and error message, leaving user on the page?

In laravel / jquery apps if I need to make checks if user is logged I make in controller:
$loggedUser = Auth::user();
if ( empty($loggedUser->id) ) {
return response()->json(['error_code'=> 1, 'message'=> "You must be logged!"],HTTP_RESPONSE_INTERNAL_SERVER_ERROR);
}
as I do not need to leave the user from the page, but only restrict some functionality
I show error message above using bootstrapGrowl library.
Now with laravel 7 /livewire 1.3 / turbolinks:5 / alpine#v2 I search how can I generate error and
show similar error message, leaving user on the page ?
UPDATED :
Let me explain it with detailed example :
In laravel / jquery apps I have in JS code :
var quiz_quality_radio= $('input[name=quiz_quality_radio]:checked').val()
var href = this_frontend_home_url + "/make-quiz-quality";
$.ajax( {
type: "POST",
dataType: "json",
url: href,
data: {"quiz_quality_id": quiz_quality_radio, "vote_id": this_vote_id, "_token": this_csrf_token},
success: function( response )
{
$('input[name=quiz_quality_radio]:checked').prop('checked', false);
frontendVote.showQuizQualityResults()
popupAlert("Thank you for rating ! Your rate was added!", 'success')
},
error: function( error )
{
$('input[name=quiz_quality_radio]:checked').prop('checked', false);
popupAlert(error.responseJSON.message, 'danger') // 'info', 'success'
}
});
and relative action in control :
public function make_quiz_quality(Request $request)
{
$requestData = $request->all();
$quiz_quality_id = ! empty($requestData['quiz_quality_id']) ? $requestData['quiz_quality_id'] : '';
$vote_id = ! empty($requestData['vote_id']) ? $requestData['vote_id'] : '';
if ( ! Auth::check()) {
return response()->json(['message' => "To rate you must login to the system !"], HTTP_RESPONSE_BAD_REQUEST);
}
if (empty($quiz_quality_id)) {
return response()->json([
'message' => "To rate you must select quiz quality !",
'quiz_quality_id' => $quiz_quality_id
], HTTP_RESPONSE_OK);
}
$vote = Vote::find($vote_id);
if ($vote === null) {
return response()->json([ 'message' => "Vote Item # " . $vote_id . " not found !"],HTTP_RESPONSE_NOT_FOUND);
}
$loggedUser = Auth::user();
$found_count = QuizQualityResult
::getByVoteIdAndUserId($vote_id, $loggedUser->id)
->count();
if ($found_count > 0) {
return response()->json(['message' => "You have already rated '" . $vote->name . "' # vote !", 'vote_id' => $vote_id],
HTTP_RESPONSE_BAD_REQUEST);
}
$newVoteItemUsersResult = new QuizQualityResult();
try {
$newVoteItemUsersResult->quiz_quality_id = $quiz_quality_id;
$newVoteItemUsersResult->vote_id = $vote_id;
$newVoteItemUsersResult->user_id = $loggedUser->id;
DB::beginTransaction();
$newVoteItemUsersResult->save();
DB::commit();
} catch (Exception $e) {
DB::rollBack();
return response()->json(['message' => $e->getMessage(), 'voteCategory' => null], HTTP_RESPONSE_INTERNAL_SERVER_ERROR);
}
return response()->json(['message' => '', 'id' => $newVoteItemUsersResult->id], HTTP_RESPONSE_OK_RESOURCE_CREATED);
} // public function make_quiz_quality(Request $request)
and in case of error generated in error block I show message with function popupAlert
(implemented with bootstrapGrowl), without leaving the page.
That is what I want to make in livewire / turbolinks / alpine app. How can I do it?
UPDATED # 2:
That is just listing of items user can vote for:
<div class="table-responsive">
<table class="table text-primary">
#foreach($quizQualityOptions as $key=>$next_quiz_quality_option)
<tr>
<td>
<input class="" type="radio" name="quiz_quality_radio" id="quiz_quality_radio_{{ $next_quiz_quality_option }}" value="{{ $key }}">
<label class="col-form-label" for="quiz_quality_radio_{{ $next_quiz_quality_option }}">{{ $next_quiz_quality_option }}</label>
</td>
</tr>
#endforeach
</table>
</div>
<div class="row p-3">
<a class="btn btn-primary a_link" onclick="javascript:frontendVote.MakeQuizQuality()">Rate !</a>
</div>
with 2 restrictions :
User must be logged
Any logged user can vote only once
these 2 errors were genarated at server.
UPDATED # 3:
I found decision with using of axios, like :
<button type="submit" class="btn btn-primary btn-sm m-2 ml-4 mr-4 action_link" #click.prevent="submitNewTodo()">
Submit
</button>
submitNewTodo() {
console.log('submitNewTodo::')
let is_insert= 1
let current_toto_id= 1
axios({
method: (is_insert ? 'post' : 'patch'),
url: '/api/todos' + (!is_insert ? "/" + current_toto_id : ''),
data: {
text : this.new_todo_text,
priority : this.new_todo_priority
},
}).then((response) => {
this.new_todo_text= ''
this.new_todo_priority= ''
this.loadTodosRows()
popupAlert( 'Todo ' + (is_insert ? 'added' : 'updated') + ' successfully !', 'success' )
}).catch((error) => {
var validationErrors= convertObjectToArray(error.response.data.errors.text)
this.validation_errors_text= ''
validationErrors.map((next_error, index) => {
if(next_error && typeof next_error[1] != "undefined" ) {
this.validation_errors_text += '<li>'+next_error[1]+'</li>'
}
})
popupErrorMessage(error.response.data.message)
});
},
With it I show message both on success and failure as I need but I see big disadvantage with it
as I use livewire and I would like to use livewire here, if that is possible...
Hope I explained what I want clearly...
Thanks!
With Alpine.js and axios you could do something like this, note that I'm not sure whether or not this_frontend_home_url, this_vote_id and this_csrf_token will be defined.
<div x-data="quiz()">
<div>
<div class="table-responsive">
<table class="table text-primary">
#foreach($quizQualityOptions as $key=>$next_quiz_quality_option)
<tr>
<td>
<input x-model="selected_quiz" class="" type="radio" name="quiz_quality_radio"
id="quiz_quality_radio_{{ $next_quiz_quality_option }}" value="{{ $key }}">
<label class="col-form-label"
for="quiz_quality_radio_{{ $next_quiz_quality_option }}">{{ $next_quiz_quality_option }}</label>
</td>
</tr>
#endforeach
</table>
</div>
<div class="row p-3">
<a class="btn btn-primary a_link" #click="submitQuizQuality()">Rate !</a>
</div>
</div>
</div>
<script>
function quiz() {
return {
selected_quiz: null,
submitQuizQuality() {
const url = this_frontend_home_url + "/make-quiz-quality";
axios.post(url, {
quiz_quality_id: this.selected_quiz,
vote_id: this_vote_id, // no idea where this is coming from,
_token: this_csrf_token // no idea where this is coming from
}).then(() => {
// reset "checked" state
this.selected_quiz = null;
frontendVote.showQuizQualityResults();
popupAlert("Thank you for rating ! Your rate was added!", 'success');
}).catch(error => {
// reset "checked" state
this.selected_quiz = null;
if (error && error.response) {
popupAlert(error.response.message, 'danger')
}
});
}
}
}
</script>

Facing issues with passing list of Ids from one View to another controller

I am trying to pass a list of int Ids from one View to another on button click. I am able to hit the controller but its passing null.
Html:
#foreach (var business in Model.Businesses)
{
<tr>
<td>#business.Name</td>
<td>
<p>
#foreach (var bt in #business.BusinessTypes)
{
#bt.Name;
}
</p>
</td>
<td><button type="button" id="btnCalcVS"> Calculate Validator Score</button></td>
</tr>
}
JQUery:
<script type="text/javascript">
$(document).ready(function () {
var businessTypeIds = [];
$(document).on("click", "#btnCalcVS", function () {
$.ajax({
type: 'POST',
url: '/BusinessType/Index',
contentType: "application/json",
data: JSON.stringify( #foreach (var business in Model.Businesses)
{
#foreach (var bt in #business.BusinessTypes)
{
#bt.Id;
}
}),
success: function (data) {
alert("success");
},
error: function (e) {
alert(e);
}
});
});
});
</script>
Controller:
[HttpPost]
[Route("/BusinessType/Index")]
public IActionResult Index([FromBody] List<int> businessTypeIds)
{
//logic
}
As mentioned, I am hitting the controller but it's having null values. As can be seen from the HTML code, i have a list inside a list. (Business Types inside Businesses, so 2 many to many relationships)
Can someone tell me where i am going wrong?
You could try get the id from html and then pass it to controller.
<form method="post">
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.DenniPlan2[0].Id)
</th>
<th>
#Html.DisplayNameFor(model => model.DenniPlan2[0].CisloZakazky)
</th>
<th>
#Html.DisplayNameFor(model => model.DenniPlan2[0].CisloProduktu)
</th>
<th>
#Html.DisplayNameFor(model => model.DenniPlan2[0].Poradi)
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.DenniPlan2)
{
<tr>
<td>
#Html.EditorFor(model => item.Id)
</td>
<td>
#Html.EditorFor(model => item.CisloZakazky)
</td>
<td>
#Html.EditorFor(model => item.CisloProduktu)
</td>
<td>
#Html.EditorFor(model => item.Poradi)
</td>
</tr>
}
</tbody>
</table>
<input type="button" id="btnCalcVS" value="submit" />
</form>
#section Scripts{
<script type="text/javascript">
$(document).ready(function () {
var businessTypeIds = [];
$("[id=item_Id]").each(function () {
businessTypeIds.push($(this).val());
});
console.log(businessTypeIds);
$(document).on("click", "#btnCalcVS", function () {
$.ajax({
type: 'POST',
url: '/BusinessType/Index',
contentType: "application/json",
data: JSON.stringify(businessTypeIds),
success: function (data) {
alert("success");
},
error: function (e) {
alert(e);
}
});
});
});
</script>
}
For this line $("[id=item_Id]").each(function () {, you could get the id value item_Id by check the generated html in the web browser.

Binding List in View Model using ASP.NET Model Binding

I'm working on an application to track orders that are coming through a web application. The application won't sell products, and it's designed to track orders as they come in via phone.
I'm still new to ASP.NET MVC, so I'm still learning the ropes. I'm trying to use model binding to get this to work.
Here's my view model:
public class OrderDetailViewModel
{
public Order Order { get; set; }
public List<OrderLine> OrderLinesList { get; set; }
public OrderDetailViewModel()
{
this.Order = new Order();
List<OrderLine> OrderLines = new List<OrderLine>();
OrderLines = new List<OrderLine>();
OrderLines.Add(new OrderLine());
this.OrderLinesList = OrderLines;
}
public OrderDetailViewModel(Order order)
{
this.Order = order;
}
public string SalesRepName
{
get
{
ApplicationDbContext db = new ApplicationDbContext();
ApplicationUser Rep = db.Users.First(u => u.UserName == Order.SalesRep);
return Rep.FirstName + " " + Rep.LastName;
}
}
public IEnumerable<SelectListItem> CustomerList
{
get
{
var Db = new OrderContext();
var Customers = Db.Customers;
var AllCustomers = Customers.Select(c => new SelectListItem
{
Value = c.Id.ToString(),
Text = c.FirstName + " " + c.LastName
});
return AllCustomers;
}
}
public IEnumerable<SelectListItem> ProductList
{
get
{
var Db = new OrderContext();
var Products = Db.Products;
var AllProducts = Products.Select(p => new SelectListItem
{
Value = p.Id.ToString(),
Text = p.Name + ", " + p.Airshow + " - " + p.AirshowDate
});
return AllProducts;
}
}
}
Here's the data model for OrderLine:
public class OrderLine
{
[Key]
public int Id { get; set; }
[Required, Display(Name="Order Number")]
public int OrderId { get; set; }
[Required]
public int ProductId { get; set; }
[Required]
public int Quantity { get; set; }
[Required]
[DisplayFormat(ApplyFormatInEditMode=true, DataFormatString="{0:C}")]
public float Price { get; set; }
[Required, Display(Name="Tax Rate"), DisplayFormat(DataFormatString="{0:P}")]
public float TaxRate { get; set; }
public Nullable<float> Discount { get; set; }
public virtual Product Product { get; set; }
public virtual Order Order { get; set; }
}
And here's the View:
#model AirshowCouponsMvc.Models.OrderDetailViewModel
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>OrderDetailViewModel</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<table class="table table-striped table-responsive table-hover">
<tr>
<th colspan="2">Order Date: #Html.Label(String.Format("{0:MMMM d, yyyy}", DateTime.Now))</th>
<th colspan="2">Customer: #Html.DropDownListFor(model => model.Order.CustomerId, Model.CustomerList)</th>
</tr>
<tr>
<th>
#Html.LabelFor(model => model.Order.OrderLines.FirstOrDefault().Product)
</th>
<th>
#Html.LabelFor(model => model.Order.OrderLines.FirstOrDefault().Quantity)
</th>
<th>
#Html.LabelFor(model => model.Order.OrderLines.FirstOrDefault().TaxRate)
</th>
<th>
#Html.HiddenFor(model => model.OrderLinesList)
</th>
</tr>
#foreach (AirshowCouponsMvc.Models.OrderLine item in Model.OrderLinesList)
{
<tr>
<td>
#Html.DropDownListFor(model => item.ProductId, Model.ProductList)
</td>
<td>
#Html.EditorFor(model => item.Quantity)
</td>
<td>
#Html.EditorFor(model => item.TaxRate)
</td>
<td>
#Html.HiddenFor(model => item.Price)
#Html.HiddenFor(model => item.OrderId)
#Html.HiddenFor(model => item.Id)
#Html.HiddenFor(model => item.Discount)
#Html.HiddenFor(model => item.Order)
#Html.HiddenFor(model => item.Product)
</td>
</tr>
}
<tr>
<td></td>
<td>#Html.ActionLink("Add line", "AddLine")</td>
<td>#if (Model.OrderLinesList.Count > 1) { #Html.ActionLink("Remove line", "RemoveLine") }</td>
</tr>
</table>
<div class="form-group">
<div class="col-md-offset-2 col-md-10 pull-right">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Finally, here's the code for the controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(OrderDetailViewModel model)
{
if (ModelState.IsValid)
{
OrderContext db = new OrderContext();
model.Order.OrderDate = DateTime.Now;
model.Order.SalesRep = User.Identity.Name;
db.Orders.Add(model.Order);
foreach (var line in model.Order.OrderLines)
{
line.Price = line.Product.Price;
line.Discount = OrderDetailViewModel.DiscountRate(line.Quantity);
line.OrderId = model.Order.Id;
db.OrderLines.Add(line);
}
return RedirectToAction("Index");
}
return View(model);
}
Yes, I know that the controller doesn't process the list. I had used the property from the data model, then I read that you need to define a List as a property in the view model. I was in the middle of changing it to this, but when I hit a break at the beginning of the Create action, I noticed that the OrderLinesList is null.
Also, I read that in older version of ASP.NET MVC (at least I haven't seen it in reference to more recent versions of ASP.NET MVC) that you have to use a for loop instead of a foreach loop. I tried going that route, but I still had the problem of a null List.
Here's the code that I tried as a for loop in the Create view. This was inserted in place of the foreach loop currently shown:
#for (int i = 0; i < Model.OrderLines.Count(); i++)
{
<tr>
<td>
#Html.DropDownListFor(model => model.OrderLines[i].ProductId, Model.ProductList)
</td>
<td>
#Html.EditorFor(model => model.OrderLines[i].Quantity)
</td>
<td>
#Html.EditorFor(model => model.OrderLines[i].TaxRate)
</td>
<td>
#Html.HiddenFor(model => model.OrderLines[i].Id)
#Html.HiddenFor(model => model.OrderLines[i].Price)
#Html.HiddenFor(model => model.OrderLines[i].Discount)
#Html.HiddenFor(model => model.OrderLines[i].OrderId)
#Html.HiddenFor(model => model.OrderLines[i].Order)
#Html.HiddenFor(model => model.OrderLines[i].Product)
</td>
</tr>
}
My end goal is to be able to track multiple line items for each order, preferably with model binding.
I've read through quite a few references to try to get this to work, including:
http://seesharpdeveloper.blogspot.com/2012/05/mvc-model-binding-to-list-of-complex.html
Model binding generic list is null in asp.net mvc
MVC3 Non-Sequential Indices and DefaultModelBinder
ASP.NET MVC 4 - for loop posts model collection properties but foreach does not
I've followed the advice in these, and it's not working for me.
I would appreciate any help on getting this fixed.

Knockoutjs list in a list select binding

I have seen so many example of this problem but not as a list. Here's my code:
<div>
<form>
<p>You have asked for <span data-bind='text: gifts().length'> </span> gift(s)</p>
<input id='giftname' type='text'/><button data-bind='click: createGift'>Add Gift</button>
<table>
<thead>
<tr>
<th>Gift name</th>
<th>Pack</th>
<th>Price</th>
<th />
</tr>
</thead>
<tbody data-bind='foreach: gifts'>
<tr>
<td><input data-bind='value: name' readonly='readonly'/></td>
<td><select data-bind="options: packs,
optionsText: 'pack',
value: price" /></td>
<td><input data-bind="value: price ? price.packprice : 'unknown'" readonly="readonly"/></td>
<td><a href='#' data-bind='click: $root.removeGift'>Delete</a></td>
</tr>
</tbody>
</table>
<button data-bind='enable: gifts().length > 0' type='submit'>Submit</button>
</form>
</div>
<script type="text/javascript">
var GiftModel = function(gifts) {
var self = this;
self.gifts = ko.observableArray(gifts);
self.addGift = function(gift) {
var newGift = {
name: gift.name,
packs: gift.packs,
price: gift.price
};
self.gifts.push(newGift);
};
self.removeGift = function(gift) {
self.gifts.remove(gift);
};
self.createGift = function() {
var gname = $('#giftname').val();
//should be getting gift options from webservice
var newGift = {name: gname,
packs: [{pack:'Each', packprice: '2'}, {pack:'Dozen', packprice: '12'}], price: {pack:'', packprice: ''}};
self.addGift(newGift);
$('#giftname').val('');
};
};
var viewModel = new GiftModel([]);
ko.applyBindings(viewModel);
</script>
When I add gifts, it creates options of packs. Each pack has a certain price. My problem is just simple. How will I show the price on the next column for the selected pack of the gift line? Sorry im just new to knockoutjs. Thanks!
System will automatically update price after selecting package if you make it observable. I made a little refactoring to you code, now it works:
<div>
<form>
<p>You have asked for <span data-bind='text: gifts().length'> </span> gift(s)</p>
<input
id='giftname' type='text' data-bind='value: giftName' />
<button data-bind='click: createGift'>Add Gift</button>
<table>
<thead>
<tr>
<th>Gift name</th>
<th>Pack</th>
<th>Price</th>
<th />
</tr>
</thead>
<tbody data-bind='foreach: gifts'>
<tr>
<td>
<input data-bind='value: name' readonly='readonly' />
</td>
<td>
<select data-bind="options: packs,
optionsText: 'pack',
optionsValue: 'packprice',
value: price" />
</td>
<td>
<input data-bind="value: price() || 'unknown'" readonly="readonly"
/>
</td>
<td><a href='#' data-bind='click: $root.removeGift'>Delete</a>
</td>
</tr>
</tbody>
</table>
<button data-bind='enable: gifts().length > 0' type='submit'>Submit</button>
</form>
</div>
function GiftModel(name) {
var self = this;
self.name = ko.observable(name);
self.price = ko.observable();
self.packs = ko.observable([{
pack: 'Each',
packprice: '2'
}, {
pack: 'Dozen',
packprice: '12'
}]);
}
var ViewModel = function (gifts) {
var self = this;
self.gifts = ko.observableArray(gifts);
self.giftName = ko.observable();
self.removeGift = function (gift) {
self.gifts.remove(gift);
};
self.createGift = function () {
self.gifts.push(new GiftModel(self.giftName()));
};
};
var viewModel = new ViewModel([]);
ko.applyBindings(viewModel);
Here is working fiddle: http://jsfiddle.net/vyshniakov/pzJaH/
P.S. Don't use jQuery to get field value if you using knockout. It is much easier to do this with knockout.