-1
I have made 2 models:
Project model -
public function project()
{
return $this->hasMany(Gallery::class, 'project_id' , 'id');
}
Gallery model -
public function galle()
{
return $this->belongsTo(Projects::class, 'id' , 'project_id');
}
I want to display image gallery for each project, I have made a foreign key in images table that references projects table and i want to access all the images for a specific project and display it in my view
here are the pictures of my db tables:
Gallery table
Project table
pagecontroller -
public function viewProject($slug)
{
$gallery = Gallery::get();
$projectview = Projects::where('slug', $slug)->get();
return view('web.project-child', compact('projectview','gallery'));
}
blage.php -
#foreach ($gallery as $gal)
<div class="col-lg-4 col-md-6 col-sm-6 project-gal-image">
<img src="{{ asset(''.$gal->gallery_image) }}" alt="First slide" data-target="#carouselExample" data-slide-to="{{ $loop->index }}" class="{{ $loop->first ? 'active' : '' }}">
</div>
#endforeach
this is the question anyone can help me fort this..
You should rename your relationship methods to match the relationship they represent:
class Project ...
{
...
public function galleries()
{
return $this->hasMany(Gallery::class);
}
}
class Gallery ...
{
...
public function project()
{
return $this->belongsTo(Project::class);
}
}
Your Controller method can be simplified if you also use Implicit Route Model Binding starting with the route definition:
Route::get('project/{project:slug}', ...);
Controller:
public function view(Project $project)
{
return view(..., ['project' => $project]);
}
View:
#foreach ($project->galleries as $gallery)
{{ $gallery->gallery_image }}
#endforeach
Laravel 8.x Docs - Routing - Route Model Binding - Implicit Binding - Customizing The Key
Related
I'm still new to Inertia/VUE3 & Laravel. I'm more familiar with PHP but I want to use best practices for what I'm doing here. I'm sure it's relatively simple so I appreciate all your help. I just wasn't sure what to search in looking for a solution.
I'm essentially creating a facade for a somewhat spaghetti ASP.NET app my company uses. There are several data points that are integers that point to another table for translation in their app. I do not want to recreate their database structure completely with Eloquent so I'd like to create a few functions on the model (I think that's what I need to do) to interpret some of this data being returned from my initial query for display. I'm wondering whether I should do this in my model, controller, or vue file.
In this example, the "StatusID" is an integer which is then translated to something like "Posted", "Cancelled", etc. I don't want to make a call to the DB just to get these translations.
Controller
<?php
namespace App\Http\Controllers;
use Inertia\Inertia;
use Illuminate\Http\Request;
use App\Models\eRequester\Requisition;
use App\Models\eRequester\ProjectManager;
class RequisitionController extends Controller
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
return Inertia::render('Requisition/Index',[
'requisitions' => Requisition::with(
'lines:RequisitionLineID,ItemID,Description,ProjectNumber,RequisitionID',
'owner:UserID,UserName,FirstName,LastName')
->latest()
->where('StatusID','!=','2')
->limit(15)
->select('RequisitionID','Title','UserID')
->get(),
'projectmanagers' => ProjectManager::where('AttributeID','siica3')
->orWhere('AttributeID','ckwca7')
->orWhere('AttributeID','orica3')
->get(),
]);
}
/**
* Display the specified resource.
*
* #param \App\Models\eRequester\Requisition $requisition
* #return \Illuminate\Http\Response
*/
public function show(Requisition $requisition)
{
//
}
}
Model
<?php
namespace App\Models\eRequester;
use App\Models\eRequester\ERequesterUser;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOne;
class Requisition extends Model
{
protected $connection = '{redacted}';
protected $table = 'Requisition';
protected $primaryKey = 'RequisitionID';
const CREATED_AT = 'DateRequested';
const UPDATED_AT = 'DateSubmitted';
public function lines(){
return $this->hasMany(RequisitionLine::class, 'RequisitionID');
}
public function owner(){
return $this->belongsTo(ERequesterUser::class, 'UserID');
}
}
Vue
<script setup>
import { Link, button } from '#inertiajs/inertia-vue3';
defineProps(['req','pm']);
</script>
<template>
<div class="p-4 border-2 mb-4 rounded-lg">
<div class="text-white font-bold uppercase flex justify-between">
<span class="">
<a target="_BLANK" :href="'https://[redacted]/req/review.aspx?reqid=' + req.RequisitionID">
<button class="bg-white hover:bg-blue-200 text-black font-bold py-0 px-2 mr-2 mb-2 rounded transition-all">
{{ req.RequisitionID }}
</button></a>
{{ req.Title }}
</span>
<span class="">
{{ req.owner.FirstName}} {{req.owner.LastName}}
</span>
</div>
<div v-for="line in req.lines" class="m-2"
:key="line.RequisitionLineID"
:line="line">
{{ line.RequisitionLineID }}: {{ line.ItemID }} | {{ line.Description }} | {{ line.ProjectNumber }}
</div>
</div>
I've tried to translate at the controller level in my inertia render and also tried to translate via the model, but I know there's probably some really simple solution which I'm missing.
On my model I thought maybe I could use a public function, but I wasn't sure how to supply the data from the controller. I also wasn't sure whether this was the smartest way to do this. I'd like for it to use PHP as opposed to JS since it won't be changing and with a basic understanding of progressive enhancement.
public function status($statusID){
// Translation switch/case
}
I would like to be able to use PHP to translate some simple integer data into human-readable format.
I would probably use some sort of enum
https://www.php.net/manual/en/language.enumerations.backed.php
https://github.com/spatie/laravel-enum
// app/Enums/StatusEnum.php
enum StatusEnum: int
{
case Posted = 1;
case Cancelled = 2;
case SomethingElse = 3;
}
// app/Models/Requisition.php
class Requisition extends Model
{
protected $casts = [
'statusID' => StatusEnum::class
];
// $requisition->status->value === 1
public function getStatusAttribute()
{
return $this->getAttribute('statusID');
}
// magic method to make `Requisition::whereStatus(StatusEnum::Posted)` work
public function scopeWhereStatus($query, $value)
{
$query->where('statusId', $value);
}
// Requisition::with('...')->wherePosted()->latest()
public function scopeWherePosted($query)
{
$query->where('statusId', StatusEnum::Posted);
}
}
Requisition::where('statusID', StatusEnum::Posted)
https://enversanli.medium.com/how-to-use-enums-with-laravel-9-d18f1ee35b56
So I figured this out, it turns out that Accessors are what I was needing. I am able to define a constant on the model and then a function and appends to add the translation to the Eloquent query grab.
Model
protected $appends = [
'status',
];
public const STATUS = [
0 => "Incomplete",
1 => "Unsubmitted",
2 => "Cancelled",
3 => "Rejected",
4 => "Waiting",
5 => "Request Info",
6 => "Approved",
7 => "Posted",
8 => "Partially Received",
9 => "Closed",
10 => "Require Change",
11 => "Resubmitted for Post Approval Routing",
12 => "Edited (Post Approval)",
13 => "Fully Received",
14 => "Posted - On Hold"
];
...
public function status(): Attribute
{
return Attribute::get(fn() => self::STATUS[ $this->attributes['StatusID']]);
}
Controller
return Inertia::render('Requisition/Index',[
'requisitions' => Requisition::with(
'lines:RequisitionLineID,ItemID,Description,ProjectNumber,RequisitionID',
'owner:UserID,UserName,FirstName,LastName')
->latest()
->where('StatusID','!=','2')
->limit(15)
->select('RequisitionID','Title','UserID','StatusID')
->get(),
'projectmanagers' => ProjectManager::where('AttributeID','siica3')
->orWhere('AttributeID','ckwca7')
->orWhere('AttributeID','orica3')
->get(),
'companies' => [
['CompanyID' => '3', 'CompanyName' => 'Spitzer'],
['CompanyID' => '2', 'CompanyName' => 'Curtis Kelly'],
['CompanyID' => '4', 'CompanyName' => 'Orizon']],
]);
}
Hope this helps someone. Seems like enumerators are slightly phased out in favor of accessors & mutators.
I have an Extbase module with a new/create-action. The model has the #validate annotation in it.
So far so good, everything's working.
But: I don't like how the form-errors are presented in the view. I'd like to add a CSS class like error in the view to the fields that are not correctly filled in.
But the only way to access the errors in the form seems to be through the <f:form.validationResults>-Viewhelper.
When I try to debug the results with <f:debug>{validationResults}</f:debug> I get a NULL value.
How do I access an error for a single field?
Actually, I'd prefer to access the errors in the controller, so I could pass an array to the view with the fields that contain an error.
I'm using Fluid and TYPO3 9.5
Such Fluid template should work out-of-the-box:
<f:form.validationResults>
<f:if condition="{validationResults.flattenedErrors}">
<ul>
<f:for each="{validationResults.flattenedErrors}" key="propertyPath" as="errors">
<li>
{propertyPath}: <ul>
<f:for each="{errors}" as="error">
<li>{error}</li>
</f:for>
</ul>
</li>
</f:for></ul>
</f:if>
</f:form.validationResults>
Another approach,
You can also write own ViewHelper to display error messages as you want in your own form HTML markup:
<?php
namespace VENDOR\Yourext\ViewHelpers;
use TYPO3\CMS\Extbase\Utility\DebuggerUtility;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
/**
* #author Marcus Biesioroff biesior#gmail.com>
*
* ViewHelper for displaying custom-designed errors
*
* Usage:
* {namespace yvh=VENDOR\Yourext\ViewHelpers}
* or in ext_tables.php:
* $GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['namespaces']['yvh'] = ['VENDOR\Yourext\ViewHelpers'];
*
* <yvh:myError key="yourObj.somefield" flattenedErrors="{validationResults.flattenedErrors}"/>
*/
class MyErrorViewHelper extends AbstractViewHelper
{
protected $escapeOutput = false;
public function initializeArguments()
{
parent::initializeArguments();
$this->registerArgument('key', 'sring', 'Name of the field for which errors should be displayed');
$this->registerArgument('flattenedErrors', 'mixed', 'Flatenned errors if any');
}
public function render()
{
$flattenedErrors = $this->arguments['flattenedErrors'];
if (is_null($flattenedErrors)) return null;
// DebuggerUtility::var_dump($flattenedErrors);
$key = $this->arguments['key'];
if (is_array($flattenedErrors) && array_key_exists($key, $flattenedErrors)) {
$errMsg = $flattenedErrors[$key][0]->getMessage();
return "<div class='my-very-own-error-class'>$errMsg</div>";
}
return null;
}
}
Remember, that you'll need to wrap your fields with <f anyway to get the flattenErrors array;
{namespace yvh=VENDOR\Yourext\ViewHelpers}
<f:form.validationResults>
<div>
<label for="name">Name (required)</label>
<f:form.textfield property="name"/>
<yvh:myError key="yourObj.name" flattenedErrors="{validationResults.flattenedErrors}"/>
</div>
<div>
<label for="slug">Slug (required)</label><br/>
<f:form.textfield property="slug"/>
<yvh:myError key="yourObj.slug" flattenedErrors="{validationResults.flattenedErrors}"/>
</div>
<div>
<label for="buildYear">Year of build (required)</label>
<f:form.textfield property="buildYear"/>
<yvh:myError key="yourObj.buildYear" flattenedErrors="{validationResults.flattenedErrors}"/>
</div>
</f:form.validationResults>
I want to be able to display a form which changes depending on the value of a select on Dot.Net Core. I've seen many things like dynamic forms, View Components, razor and partials and also there is a lot of info out there but very confusing. Any info about the proper way to do what I want would be very appreciated.
I Have Categories>SubCategories>>Products
An order can be for one Category>>SubCategory only. So I want to display a select and depending on what category the user selects for the new Order, the products that I have to display. So I dont want to pick the user selection and go back to the controller and back to the view again and so on. I want to be able to dynamically display data according to the user selection.
Here an extract of the code just to briefly figure out what Im doing..I am not pasting the classes like Products,etc..
public class OrderCreateViewModel
{
public IEnumerable<Category> Categories { get; set; }
public IEnumerable<Branch> Branches { get; set; }
public int BranchId { get; set; }
public int CategoryId { get; set; }
}
Controller :
[HttpGet]
public IActionResult Create()
{
//Create vm
IEnumerable<Branch> branchList = _branchDataService.GetAll();
IEnumerable<Category> categoryList = _categoryDataService.GetAll();
OrderCreateViewModel vm = new OrderCreateViewModel
{
Categories = categoryList,
Branches = branchList
};
return View(vm);
}
View:
#model OrderCreateViewModel
<p>Create New Order </p>
<form asp-controller="Order" asp-action="Create" method="post">
<div class="form-group">
<label >Select Category</label>
<select class="form-control col-md-2" asp-for="CategoryId"
asp-items="#(new SelectList(Model.Categories ,"CategoryId","Name"))">
</select>
</div>
<div class="form-group">
<label>Select Branch</label>
<select class="form-control col-md-2" asp-for="BranchId"
asp-items="#(new SelectList(Model.Branches,"BranchId","Name"))">
</select>
</div>
<div >
<input type="submit" value="Save" />
</div>
</form>
Im just filling the select on the viewside and depending on what the user picks, the products I want to display. I am not passing the product list yet because I don't know where the "filter" for that particular Category takes place.
Hope you understand the idea of what i need :)
You've got two options here:
# 1 Use a Partial View And AJAX to get your data
Go have a look at the link below, it describes exactly what you want to achieve.
Populating Dropdown using partial view
# 2 Use Javascript to populate your second select:
First off you need to persist your data when the page loads
At the start of your view, add this:
#{
<text>
<script>
var data = "#Newtonsoft.Json.JsonConvert.SerializeObject(Model)";
</script>
</text>
}
Next use your on change event on the branch selector:
At the bottom of your view, do the following in the page ready event:
<script>
(function ()
{
var sltBranch = document.getElementsByName("BranchId")[0];
var sltCat = document.getElementsByName("CategoryId")[0]
sltCat.onchange = function () {
var dataAsObj = JSON.parse(data);
var branches = "";
for (i = 0; i < dataAsObj.Branches.length; i++)
{
if (dataAsObj.Branches[i].CategoryId == sltCat.value)
{
branches += "<option value='" + dataAsObj.Branches[i].BranchId + "'>" + dataAsObj.Branches[i].BranchName + "</option>"; //Whatever The branch name may be
}
}
sltBranch.innerHTML = branches;
}
}
)(document, window);
</script>
I would however advise you to follow option 1 as it is a lot more future proof strategy. This would mean that you need to change your view model etc, but if you plan on making extensive use of this strategy you need to make it more robust with something like option 1.
Good luck anyhow - happy coding.
I'm having trouble getting Aurelia to iterate over a map where the keys are strings (UUIDs).
Here is an example of the data I'm getting from an API running somewhere else:
my_data = {
"5EI22GER7NE2XLDCPXPT5I2ABE": {
"my_property": "a value"
},
"XWBFODLN6FHGXN3TWF22RBDA7A": {
"my_property": "another value"
}
}
And I'm trying to use something like this:
<template>
<div class="my_class">
<ul class="list-group">
<li repeat.for="[key, value] of my_data" class="list-group-item">
<span>${key} - ${value.my_property}</span>
</li>
</ul>
</div>
</template>
But Aurelia is telling me that Value for 'my_data' is non-repeatable.
I've found various answer by googling, but they have not been clearly explained or incomplete. Either I'm googling wrong or a good SO question and answer is needed.
As another resource to the one supplied by ry8806, I also use a Value Converter:
export class KeysValueConverter {
toView(obj) {
if (obj !== null && typeof obj === 'object') {
return Reflect.ownKeys(obj).filter(x => x !== '__observers__');
} else {
return null;
}
}
}
It can easily be used to do what you're attempting, like this:
<template>
<div class="my_class">
<ul class="list-group">
<li repeat.for="key of my_data | keys" class="list-group-item">
<span>${key} - ${my_data[key]}</span>
</li>
</ul>
</div>
</template>
The easiest method would be to convert this into an array yourself (in the ViewModel code)
Or you could use a ValueConverter inside repeat.for as described in this article Iterating Objects
The code...
// A ValueConverter for iterating an Object's properties inside of a repeat.for in Aurelia
export class ObjectKeysValueConverter {
toView(obj) {
// Create a temporary array to populate with object keys
let temp = [];
// A basic for..in loop to get object properties
// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/for...in
for (let prop in obj) {
if (obj.hasOwnProperty(prop)) {
temp.push(obj[prop]);
}
}
return temp;
}
}
/**
* Usage
* Shows how to use the custom ValueConverter to iterate an objects properties
* aka its keys.
*
* <require from="ObjectKeys"></require>
* <li repeat.for="prop of myVmObject | objectKeys">${prop}</li>
*/
OR, you could use the Aurelia Repeat Strategies provided by an Aurelia Core Team member
You'd have to import the plugin into your app.
Then you'd use it using the pipe syntax in your repeat.for....like so....
<div repeat.for="[key, value] of data | iterable">
${key} ${value.my_property}
</div>
How can we write a function to use in a *.cshtml page. We used to be able to use #helper or #function within the view. How do we do this? For instance, I would like to write a recursive function to show all configuration values. How could I do this?
<dl>
#foreach(var k in config.GetSubKeys())
{
<dt>#k.Key</dt>
<dd>#config.Get(k.Key)</dd>
#* TODO How can we make this a helper function/recursive? *#
#foreach(var sk in config.GetSubKey(k.Key).GetSubKeys())
{
<dt>#sk.Key</dt>
<dd>#config.Get(sk.Key)</dd>
}
}
</dl>
I imagine that we need to add a dependency in project.json and then opt-in to using it in Startup.cs.
Quick and dirty using razor views assuming your view component provides a recursive model.
Component view:
#model YourRecursiveDataStructure
<ul class="sidebar-menu">
<li class="header">MAIN NAVIGATION</li>
#foreach (var node in Model.RootNodes)
{
#Html.Partial("~/YourPath/RenderElement.cshtml", node)
}
</ul>
Render element in a view :
#model YourRecursiveNode
<li>
<a href="#Model.Href">
<span>#Model.Display</span>
</a>
#Html.Partial("~/YourPath/RenderChildren.cshtml", Model)
</li>
Then loop node's children in another view:
#model YourRecursiveNode
#if (Model.HasChildren)
{
<ul>
#foreach (var child in Model.Children)
{
#Html.Partial("~/YourPath/RenderElement.cshtml", child)
}
</ul>
}
Referring to a few design discussions that we only have glimpses of online, #helper was removed for design reasons; the replacement is View Components.
I'd recommend a View Component that looked like the following:
public class ConfigurationKeysViewComponent : ViewComponent
{
private readonly IConfiguration config;
public ConfigurationKeysViewComponent(IConfiguration config)
{
this.config = config;
}
public IViewComponentResult Invoke(string currentSubKey = "")
{
return View(new ConfigurationData
{
Key = currentSubKey,
Value = config.Get(currentSubKey),
SubKeys = config.GetSubKey(currentSubKey).GetSubKeys().Select(sk => sk.Key)
});
}
}
Your ViewComponent's View would then be relatively simple:
<dt>#Model.Key</dt>
<dd>#config.Get(Model.Key)</dd>
#foreach (var sk in Model.SubKeys)
{
#Component.Invoke("ConfigurationKeys", sk)
}
You could then invoke it from your root view as follows:
#Component.Invoke("ConfigurationKeys")
Disclaimer: I wrote this in the SO editor, there may be compiler errors. Also, I'm uncertain if View Components support default parameters - you may need to add a default "" to the root view's call to the view component.
Alternatively, if this is just debugging code, you can unwrap your recursiveness by using a Stack<>.