I've got a form up and working with a Vue frontend and DRF backend. It's a form for adding (creating) a new model - and has a dropdown of the related models that are FK to the model being created.
I need to access attributes of the selected FK item.
My serializers look like this:
class SubdomainSerializer(serializers.ModelSerializer):
class Meta:
model = Subdomain
fields = [
"id",
"domain",
"short_description",
"long_description",
"character_code",
]
# def get_absolute_url(self, obj):
# return obj.get_absolute_url()
class EvidenceSerializer(serializers.ModelSerializer):
created_by = serializers.HiddenField(
default=serializers.CurrentUserDefault()
)
updated_by = serializers.HiddenField(
default=serializers.CurrentUserDefault()
)
absolute_url = serializers.SerializerMethodField()
created_by_name = serializers.SerializerMethodField()
updated_by_name = serializers.SerializerMethodField()
class Meta:
model = Evidence
fields = "__all__"
The form is to create a new 'Evidence' item, and the 'Subdomain' is a dropdown on the form that contains all related subdomains.
The models look like this:
class Subdomain(CreateUpdateMixin):
domain = models.ForeignKey(Domain, on_delete=models.PROTECT)
short_description = models.CharField(max_length=100)
long_description = models.CharField(max_length=250)
character_code = models.CharField(max_length=5)
class Evidence(CreateUpdateMixin, CreateUpdateUserMixin, SoftDeletionModel):
subdomain = models.ForeignKey(Subdomain, on_delete=models.PROTECT)
evaluation = models.ForeignKey(
Evaluation, related_name="evidences", on_delete=models.PROTECT
)
published = models.BooleanField(default=False)
comments = models.CharField(max_length=500)
In my form, I just want to include the short_description of each subdomain when the user chooses it from the dropdown - I may also want to use the long_description as well.
Here is the bit in the form where I render the dropdown:
<div class="form-group col-sm-4">
<label class="" for="subdomain">Subdomain</label>
<select name="subdomain" id="subdomain" class="form-control" v-model="element.subdomain">
<option v-for="choice in subdomains" :value="choice.id" >{{ choice.character_code }}</option>
</select>
</div>
<div class="small" v-if="element.subdomain">
<!-- THIS IS WHERE I WOULD LIKE TO DISPLAY THE SHORT DESCRIPTION FOR THE CHOICE IN THE DROPDOWN -->
{{ choice.short_description }}
</div>
The Form Data looks like this when I POST:
evaluation: 2037
subdomain: 448
comments: Test comments to add to the subdomain
published: true
csrfmiddlewaretoken: 382796ryfuasiodfgyhakljyht37yaisdfaslk3r
Things I have tried - some of which worked for display purposes but seem to have broken the form/POST:
Adding depth=1 to the Meta of the EvidenceSerializer, which worked but made the form no longer submit appropriately. I think it's because it wanted the entire subdomain instead of just the ID? I couldn't get it working - the subdomain always threw an error.
Adding the following to my EvidenceSerializer, which again seemed to break the POST operation, it would cause the subdomain dropdown to throw an error.
subdomain = SubdomainSerializer(read_only=True)
Using both of those methods above the dropdown doesn't recognize the subdomain_id being selected and both end up throwing this error behind the scenes:
Cannot insert the value NULL into column 'subdomain_id', table 'local_app.dbo.myapp_evidence'; column does not allow nulls. INSERT fails.
Any advice on how to proceed would be fantastic.
TLDR; Need to be able to access attributes on a FK relationship for a dropdown using DRF, and be able to submit that item in a form.
Thanks to #bdbd for pointing me in the right direction.
For anyone curious, I resolved it using those links - turns out I needed to change my serializers a little bit:
class SubdomainSerializer(serializers.ModelSerializer):
class Meta:
model = Subdomain
fields = [
"id",
"domain",
"short_description",
"long_description",
"character_code",
]
class EvidenceSerializer(serializers.ModelSerializer):
created_by = serializers.HiddenField(
default=serializers.CurrentUserDefault()
)
updated_by = serializers.HiddenField(
default=serializers.CurrentUserDefault()
)
absolute_url = serializers.SerializerMethodField()
created_by_name = serializers.SerializerMethodField()
updated_by_name = serializers.SerializerMethodField()
# add the 'subdomain' as read only - but with all the attributes
subdomain = SubdomainSerializer(read_only=True)
# add the 'subdomain_id' as a PrimaryKeyRelatedField with the source being the subdomain
subdomain_id = serializers.PrimaryKeyRelatedField(
queryset=Subdomain.objects.all(), source="subdomain"
)
class Meta:
model = Evidence
fields = "__all__"
Then I updated the HTML a little bit:
<div class="form-group col-sm-4">
<label class="" for="subdomain_id">Subdomain</label>
<select name="subdomain_id" id="subdomain" class="form-control" v-model="element.subdomain">
<option v-for="choice in subdomains" :value="choice" >{{ choice.character_code }}</option>
</select>
</div>
<div class="small" v-if="element.subdomain_id">
{{ element.subdomain.short_description }}
</div>
Then in the ajax call I simply assign the subdomain_id to the subdomain.id
data: {
evaluation : evaluationId,
subdomain_id : vm.element.subdomain.id,
comments : vm.element.comments,
published: vm.element.published,
csrfmiddlewaretoken: vm.sharedStore.state.csrftoken,
},
I would like to populate a few dropdown lists using jQuery when the page loads, rather than using ajax responding to an event, which I have currently and is working.
My Controller builds up several List objects for these dropdowns.
Currently I use ajax and my actions return the data, but this needs to be in my view when it renders.
C# code:
return Json(ddlVals1, JsonRequestBehavior.AllowGet)
return Json(ddlValAnother, JsonRequestBehavior.AllowGet)
Example of what I wish to do in my jQuery Code;
$(function ()
{
LoadUserDropDown();
LoadAnotherDropDown();
});
Thank You.
Is possible as long as you pass the list values to view. For example, you can use ViewBag to transfer temporary data to View.
// Action
public ActionResult Index()
{
ViewBag.FirstDropDownList = new List<string>() { "Item 1", "Item 2" };
ViewBag.FirstDropDownListJson = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(ViewBag.FirstDropDownList);
return View("Index");
}
// View using razor
<select>
#foreach(var item in ViewBag.FirstDropDownList)
{
<option value="#item">#item</option>
}
</select>
// View using jQuery
<select id="dropdown1"></select>
<script>
$(function(){
var dropdown1List = JSON.parse('#Html.Raw(ViewBag.FirstDropDownListJson)');
$('#dropdown1').html($.map(dropdown1List, a => `<option value="${a}">${a}</option>`));
});
</script>
I have a view that uses the select method in Ruby on Rails to return the following code:
<select class="form-control" name="xyz" id="food_id">
<option value="1">Peach</option>
<option value="2">Apple</option>
<option value="3">Orange</option>
</select>
To pass this information over to the JavaScript file, I am basically using a link_to method in my view, such as the following:
<%= link_to "Import text", "#/", :onclick => "fillTextBox()" %>
In the JS file, how can I basically get the name and description of whatever value is selected in the dropdown? This is as far as I've gotten before I got confused:
function fillTextBox(){
var dropdown = document.getElementById('food_id');
var foodID = dropdown[dropdown.selectedIndex].value;
}
I need to do something like:
var foodName = Food.find(foodID).name;
var foodDescription = Food.find(foodID).description;
Is this possible? The rest of the JS function would basically fill in a textbox using foodName and foodDescription, but I first need to be able to query the database for this information in the first place.
Have you ever tried Gon gem?
In your controller:
def some_method
gon.foods = Food.all
end
Then in your js
gon.foods
In your case, you can attach the complete Food collection in the controller and then find the Food by its id in javascript using somethig like this
var food = gon.foods.filter(function(food) {
return food.id === foodID;
})[0];
This has been one of the most frustrating online searches-for-an-answer-or-solution EVER! And I still haven't found anything out there that can perform this basic task - with that being said, this IS a search facility that's used very often all over the net ... so it comes as a surprise as to why there aren't many (any) demos / scripts for sale that can do this.
I want to have a search facility on my website whereby the user can select a country > province > town (3 drop downs).
Obviously if the user selects the USA (for example), the next dropdown populates / updates the provinces (states) relevant to USA and so forth with the next drop down.
I see a lot of people using ASP.net and AngularJS to perform this funtion but I am using neither languages and don't want to use them.
This guy here has developed a great solution for people who'd like their results to dynamically load up as they select items in the dropdowns - however this isn't what I want.
The Javascript and Json approach is where I like to go.
Now this guy here made a great / simple solution for populating dropdowns (I am going to post the code for this script later on).
But with ALL of these demos and scripts online, they are ALL missing one thing - the SEARCH facility. It's great populating a dropdown to select correct items but that's half the job done and that's all it does.
I want the user to be able to click a SEARCH button AFTER the last item in a dropdown is selected and go to it's respective page (because isn't that what is supposed to be for? - Of course that also depends on the project at hand).
So lets take the code of the populated dropdown created by the guy in the second link:
HTML:
<head>
<meta charset="utf-8">
<title></title>
<meta name="description" content="">
<script src="js/jquery-1.8.1.min.js"></script>
<script src="js/outils.js"></script>
</head>
<body>
<div>
<select id="Marque">
<option value="0">Choix du marque</option>
<option value="bmw">bmw</option>
<option value="mercedes">mercedes</option>
<option value="audi">audi</option>
<option value="volswagen">volswagen</option>
</select>
<select id="Serie"></select>
</div>
</body>
Javascript:
jQuery().ready(function(){
var tabMarque=[];
$.getJSON('file.json', function(data) {
$.each(data, function(index, val) {
tabMarque[index]=val;
});
});
$('#Marque').change(function(event) {
$marque=$(this).val();
$htmlOption='<option value="0">Choix du serie</option>';
if($marque!=0)
{
$.each(tabMarque[$marque], function(key, value) {
$htmlOption+='<option
value="'+value[0]+'">'+value[1]+'</option>';
});
}
$('#Serie').html($htmlOption);
});
});
JSON:
{
"bmw":[
["1","serie 1"],
["2","serie 3"],
["3","serie 5"],
["4","serie 7"]
],
"mercedes":[
["6","class A"],
["7","class B"],
["8","class C"],
["9","class E"]
],
"audi":[
["10","a3"],
["11","a4"],
["12","a5"],
["13","a6"]
],
"volswagen":[
["14","polo"],
["15","golf"],
["16","cady"]
]
}
(Sorry, I'd like to put this on JSfiddle but there's json involved and I don't know where to put the json code).
So after the 3 dropdown boxes, I'd like to have a button saying "Go" or "Search" and once it's clicked it takes the user to the page of the last selected item.
EG (using the example code above - understand there's only 2 dropdowns in his code):
I select:
BMW
1 Series
... and then when I click "GO" - it take me to bmw-1-series.htm
How can this be done?
Surely one could add urls to the items in the json file eg:
"bmw":[
["1","serie 1","http://www.example.com/bmw-1-series.htm"],
["2","serie 3","http://www.example.com/bmw-2-series.htm"],
["3","serie 5","http://www.example.com/bmw-3-series.htm"],
["4","serie 7","http://www.example.com/bmw-4-series.htm"]
],
and then when you click "GO", it will take you to the respective url. Obviously this needs extra code I can imagine to be able to grab the url of the selected item and take the user to that page (something I wouldn't know how to do)?
Is there a better way to do this?
UPDATE TO MAKE THIS CLEARER:
Go to CSS TRICKS DEMO
This is what I want (to be able to populate the dropdowns - this is the only thing the demo does) however if the user wants to search for Coffee's ... they would click on BEVERAGES > then choose COFFEE and then I'd like them to be able to click a button (just below the 2 dropdowns) saying SEARCH ... which will take them to a page with all the coffees listed on
As you requested, I looked at the code and have corrected it. It can be found at:
http://plnkr.co/edit/RnCdfnrX0jy3RvmXjF56
Link-only answers are frowned upon in StackOverflow, so I will write a little more:
As I wrote in my original comment:
If you are generally following along with the video you posted, you
should be able to use that as a foundation. You can do one of two
things with your links, either maintain your own datastructure (even a
basic Object) to map between the value of the leaf node select items
and then navigation destination, or add them directly as a data
attribute when adding them to the menu. Then your button code can
lookup the URL in whichever place you put it. In his line 15 (in the
'marque' handler) you could have:
$htmlOption+='<option value="'+val[0]+'" data-theurl="'+val[2]+'"...'
if URL is in spot 2
Although C Fasolin took the code you pointed to and my advice and attempted to convert it to an answer, you are correct that the code provided in the answer has errors in it.
Since SO requires code for Plunkr links, I now also have to paste some code in here as well. This is the corrected JS code minus the silly number of alerts injected into the code (which I only bothered to comment out in the Plunkr, but removed here to be tidier). The HTML and JSON were fine and are also on the Plunkr. Note that the JSON data only contains URLs for the BMW examples so you will get a 404 when clicking "go" for other makes, ou marques, en français.
$(document).ready(function() {
var tabMarque = [];
$.getJSON('data.json', function(data) {
$.each(data, function(index, val) {
tabMarque[index] = val;
});
});
$('#Marque').change(function(event) {
var marque = $(this).val();
var htmlOption = '<option value="0">Choix du serie</option>';
if (marque !== '0') {
var itemsMarque = tabMarque[marque];
$.each(itemsMarque, function(key, value) {
htmlOption += '<option value="' + value[0] + '" data-href="' + value[2] + '">' + value[1] + '</option>';
});
}
$('#Serie').html(htmlOption);
});
$('#go').click(function() {
var selected = $('#Serie').find('option:selected');
var href = selected.data('href');
window.location = href;
});
});
Happy menuing!
this solution work for me:
JAVASCRIPT:
$(document).ready(function(){
var tabMarque=[];
$.getJSON('file.data.txt', function (data) {
alert(data);
$.each(data, function(index, val) {
tabMarque[index]=val;
});
});
$('#Marque').change(function(event) {
alert('Marque_change');
var marque=$(this).val();
alert(marque);
var htmlOption='<option value="0">Choix du serie</option>';
if(marque!="0")
{
var itemsMarque = tabMarque[marque];
alert(JSON.stringify(itemsMarque));
$.each(itemsMarque, function (key, value) {
//alert("k=" + key + " v=" + JSON.stringify(value));
htmlOption+='<option value="'+value[0]+'" data-href="'+value[2]+'">'+value[1]+'</option>';
});
}
$('#Serie').html(htmlOption);
});
$('#go').click(function () {
alert('go_click');
var selected = $('#Serie').find('option:selected');
var href = selected.data('href');
alert('goto:'+href);
});
});
HTML
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8">
<title></title>
<meta name="description" content="">
<script src="js/jquery-1.8.1.min.js"></script>
<script src="js/outils.js"></script>
</head>
<body>
<div>
<select id="Marque">
<option value="0">Choix du marque</option>
<option value="bmw">bmw</option>
<option value="mercedes">mercedes</option>
<option value="audi">audi</option>
<option value="volswagen">volswagen</option>
</select>
<select id="Serie"></select>
<input type="button" id="go" value="Go!" />
</div>
</body>
</html>
JSON:
{
"bmw":[
["1","serie 1","http://www.example.com/bmw-1-series.htm"],
["2","serie 3","http://www.example.com/bmw-2-series.htm"],
["3","serie 5","http://www.example.com/bmw-3-series.htm"],
["4","serie 7","http://www.example.com/bmw-4-series.htm"]
],
"mercedes":[
["6","class A"],
["7","class B"],
["8","class C"],
["9","class E"]
],
"audi":[
["10","a3"],
["11","a4"],
["12","a5"],
["13","a6"]
],
"volswagen":[
["14","polo"],
["15","golf"],
["16","cady"]
]
}
I tried to follow the other similar questions on Stack Overflow but thus far have been unsuccessful in fixing the problem.
I am using jQuery AJAX to retrieve several items: a contact and its associated information, all available salutation types, all available email types and all available phone types.
I have successfully bound the options to the select boxes. However, it appears to overwrite the 'value' binding that holds the initial view model value.
Could any of you help me solve this? Please let me know if you have any questions for clarification.
Please see the code below:
View Model:
function contactPageViewModel() {
var self = this;
self.contact = ko.observable();
self.availableSalutations = ko.observableArray();
self.availableEmailTypes = ko.observableArray();
self.availablePhoneTypes = ko.observableArray();
self.availableAddressTypes = ko.observableArray();
}
where contact is an object coming from the server, which includes the element contact.salutation.
The json coming back for contact is:
{
//...
"createdBy":null,
"createdOn":1392848929000,
"updatedBy":null,
"updatedOn":1392848929000,
"contactId":305,
"salutation":{"salutationId":102,"salutation":"Mrs."},
"firstName":"Laura",
"middleInitial":"K",
"lastName":"Ritchey"
//...
}
the json coming back from availableSalutations (which is a property of a json object wrapper 'listObject') is:
[{"salutationId":41,"salutation":"Ms."},
{"salutationId":101,"salutation":"Mr."},
{"salutationId":66,"salutation":"CDR"},
{"salutationId":81,"salutation":"LCDR"},
{"salutationId":102,"salutation":"Mrs."},
{"salutationId":121,"salutation":"Mr."},
{"salutationId":64,"salutation":"LTC"}]
The code to map the JSON result to the knockout observables:
contactPageViewModel.contact = ko.mapping.fromJS(data.listObject[0]);
contactPageViewModel.availableEmailTypes = ko.mapping
.fromJS(data.listObject[1]);
contactPageViewModel.availableSalutations = ko.mapping
.fromJS(data.listObject[2]);
....
applyBindings();
The HTML:
<label for="rank"> Rank / Title: </label>
<select data-bind="optionsText: 'salutation',
options: availableSalutations,
value: contactPageViewModel.contact.salutation"
class="rankList"
name="Rank"
id="rankSelect">
</select>
Try value: $root.contact().salutation instead of value: contactPageViewModel.contact.salutation.
Or:
<label for="rank"> Rank / Title: </label>
<!-- ko with: contact -->
<select data-bind="options: $root.availableSalutations, optionsText: 'salutation', value: salutation" class="rankList" name="Rank" id="rankSelect">
</select>
<!-- /ko -->
Update:
You could look at this Fiddle. May be it contains a lot of excess code and you can simplify it, but the main things is to separate initial and selected salutations and add optionsCaption to select bindings:
var initialSalutation = new salutationViewModel(data.salutation);
And:
self.salutation = ko.observable();
self.displayedSalutation = ko.computed(function () {
if (self.salutation()) {
return self.salutation();
} else {
return initialSalutation;
}
})
Update 2:
Look at this Fiddle. I've added optionsValue: 'salutationId' to select bindings and move displayedSalutation to contactPageViewModel.
I think problem was with matching objects (select item and salutation from contact). When value of select is salutationId and contact salutation also salutationId (number value, not object) all working good.