So I have a products page with different products. There I have some checkboxes for color and size. On checkbox change I want to make an ajax call to my Laravel controller with the needed colors and sizes to query the products and return them back as json and just change the old products with my new ones using javascript. When copying/pasting the full url in another tab, I want to get the products page with the queried products. How can I do that? Here is my code:
<ul class="color-list">
#foreach($availableColors as $color)
<li><input type="checkbox" name="color" value="{{ $color->name }}"></li>
#endforeach
#foreach($availableSizes as $size)
<li><input type="checkbox" name="size" value="{{ $size }}">{{ $size }}</li>
#endforeach
Here is my route:
Route::get('/products/{gender}/{subcategory_name}', [
'uses' => 'ProductsController#showProducts',
'as' => 'products']);
Here is my controller:
public function showProducts($gender, $subcategory_name)
{
$subcategory = Subcategory::where('name', $subcategory_name)->first();
$gender = Gender::where('gender', $gender)->first();
$availableSubcategories = $this->getAvailableSubcategories($subcategory->category->id);
$availableColors = $this->getAvailableColors($gender, $subcategory);
$availableSizes = $this->getAvailableSizes($gender, $subcategory);
$priceRange = $this->getPriceRange($gender, $subcategory);
return view('products', [
'gender' => $gender,
'subcategory' => $subcategory,
'availableSubcategories' => $availableSubcategories,
'availableColors' => $availableColors,
'availableSizes' => $availableSizes,
'priceRange' => $priceRange
]);
}
Code, advice, solutions would be appriciated. Thanks in advance :)
Hmm, I'd maybe look at using Session or Cache. I wouldn't manipulate the url (it's ugly and can be modified by user).
So in your controller method (note the Request injection):
public function showProducts(Request $request, $gender, $subcategory_name)
{
// if a session already exists, clear it
if ($request->session()->has('some_name'))
$request->session()->forget('some_name');
// your current logic
...
// store new session ($products = save products array to variable)
$request->session()->put('some_name', $products);
}
Then in your view check if session exists:
if (Session::has('some_name')) {
#foreach(Session('some_name') as $some_name)
...
#endforeach
}
else {
...
}
You could even do a ternary on your loops, etc:
#foreach(((Session::has('some_name')) ? Session('some_name')['available_sizes'] : $availableSizes) as $size)
<li><input type="checkbox" name="size" value="{{ $size }}">{{ $size }</li>
#endforeach
None of this was tested. I just wrote it out by hand. But it should give you an idea to work with!
You need to manipulate the browser history of the browser by calling history.pushState to modify the url without reloading the whole page every ajax call request.
Check this link https://developer.mozilla.org/en-US/docs/Web/API/History_API or you can use this javascript plugin https://github.com/browserstate/history.js/
Related
I have an alpine.js component with livewire data save. https://github.com/TerrePorter/CustomCountrySelectBox.git
I am trying to load two and eventually three of these in sequence on the page. First, the country then when that is selected, I update the state/region, and finally when that is updated I update the city.
The problem is when I update the state/region the alpine.js code does not execute when livewire refreshed that part of the page.
I also tried to make it work all on one page, a select box that updates the next select box, but it would always fail to load the second array.
Here is the relevant part of the template
<div class="p-6">
<lable>Please select your location: </lable>
#if ($country == '')
#component('components.country-select-box', ['countryJson' => $countryJson])#endcomponent
#endif
#if ($stateJson != '')
#component('components.state-select-box', ['stateJson' => $stateJson])#endcomponent
#endif
</div>
Here is part of the livewire code where I update the state's JSON file.
public function updatedCountry() {
$this->stateJson = $this->getStateJson();
}
public function getStateJson() {
$r = Countries::where('short_name', '=', $this->country)->with('states')->first();
$ret = [];
foreach ($r->states as $index => $item) {
$ret[] = ['title' => $item->state_name, 'value' => $item->id];
}
return json_encode($ret);
}
And finally as mentioned before this is the select box dropdown I am using, https://github.com/TerrePorter/CustomCountrySelectBox.git
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 have 3 seperate dataProviders for my Gridview, one with Saved data, one with Unsaved data and one with both.
Now this is what I'm trying to accomplish:
If you click on saved, the dataProvider changes to the one with saved data.
I'm trying it like this:
<?php
if($i == 1){
$dataProvider = $dataProviderSaved;
} elseif($i == 2) {
$dataProvider = $dataProviderNotsaved;
} else {
$dataProvider = $dataProviderBoth;
};
\yii\widgets\Pjax::begin(['id' => 'gridview', 'timeout' => false,
'enablePushState' => false, 'clientOptions' => ['method' => 'POST']]) ?>
<?= GridView::widget([
'dataProvider' => $dataProvider,
//regular gridview..
\yii\widgets\Pjax::end(); ?>
Javascript:
var i = $i;
$("#saved").click(function(){
i=1;
$.pjax.defaults.timeout = false;//IMPORTANT
$.pjax.reload({container:"#gridview"});
});
', \yii\web\View::POS_READY);
So, I've just read that changing PHP variables inside JS is 'impossible'.
How would I accomplish this?
Is there a better way?
Do I need 3
DataProviders? (This means 3 find()'s inside of the controller)
If I understood properly you don't need 3 dataProviders. You should use GridView's FilterSelector option to treat that external element as part of GridView's filter.
For example
echo GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'filterSelector' => "input[name='ModelSearch[_selection]'],",
...
Then, in your Filter Model you filter depending on that value
switch($this->_selection) {
case 'Saved':
$query->andFilterWhere([...]);
break;
case 'Unsaved':
$query->andFilterWhere([...]);
break;
case 'Both':
$query->andFilterWhere([...]);
break;
Don't forget to add the _selection attribute to your Model class and to rules() as 'safe' in the Search Model class.
You can try in two ways:
The first the simpler you assign to each button (saved, unsaved, both) the call of three separate cation of your controller
that invoke each a respective view connected to a single gridview each of these latter with the appropriate dataprovider
The second consists of controller you have the three dataprovider different as this example
return $this->render('viewYourView', [
'/modelContribuente' =>$modelContribuente,
'dataProviderOne' => $providerOne,
'dataProviderTwo' => $providerTwo,
'dataProviderThree' => $providerThree,
]);(
In a single View you can create your three gridview everyone who uses the dataprovider appropriate and then visalizzare or hide gridviewn with JQuery functions controlled by buttons
So I have a shopping cart. I can add, delete and view the products in the cart. The problem now comes with increasing and decreasing the quantities. If for example the user wants to buy more than one of the same product.
BACKGROUND:
There are two ways on how you can increase your quantity:
You can increase it every time you add a product from its details page. This part works and im doing this like here:
if( isset($cart[$id]) ) {
$qtyAvailable = $product->getStock();
if ( $qtyAvailable > $cart[ $id ]) {
$cart[ $id ] = $cart[ $id ] + 1;
} else {
return $this->redirect($this->generateUrl('cart'));
}
} else {
// if it doesnt make it 1
$cart = $session->get('cart', array());
$cart[$id] = 1;
}
What is the $cart here?
AS you can see I am checking if the id of the product already exists in the cart. If it does then increase its value. My array Cart only has one key which is and id of the product and I increase the ids value everytime I add the same product.
The other way is to increase the quantity in the cart itself with the buttons +,-. I am stuck at this part.
I am guessing there are two ways I can increase the quantity here, either by using javascript or writing the similar function like on the top(i prefer this one). Since I am not used to javascript the only way I think i can do this is by writing this:
<div class="input-append"><input class="span1" style="max-width:34px" placeholder="{{ key }}" id="appendedInputButtons" size="16" type="text">
<button class="btn" type="button" name="add" onclick='javascript: document.getElementById("appendedInputButtons").value++;'><i class="icon-minus"></i></button>
<button class="btn" type="button" name='subtract' onclick='javascript: document.getElementById("appendedInputButtons").value--;'><i class="icon-plus"></i></button>
However this only changes the number on the screen, it doesnt increase or decrease in the array itself. Maybe there is a way you can change the array values with javascript?
UPDATE
Ok so I am trying to understand the code one of the StackOverflow users have provided, however I am getting some problems.
So in my twig I have made this:
<div class="input-append"><input class="span1" style="max-width:34px" name="quantity" placeholder="{{ key }}" id="appendedInputButtons" size="16" type="text" data-id="12">
<button class="btn" type="submit" value="increase"><a href="javascript:void(0)" class="plus"><i class="icon-minus"></i></button>
<button class="btn" type="button"><i class="icon-plus"></i></button>
Then in my controller i done like in the example:
/**
* #Route("/cart/update", name="cart_update")
*/
public function cartUpdateAction( Request $request )
{
$response = new JsonResponse();
$requestData = $request->request->all();
$productid = $requestData['product'];/** first put all validations not empty/ is numeric and exists in your db etc*/
$quantity = $requestData['quantity'];/** first put all validations not empty/ is numeric etc */
/** if all is good then put your logic*/
$product = $em->getRepository('MpShopBundle:Product')->find($productid);
$qtyAvailable = $product->getStock();
$session = $this->getRequest()->getSession();
$cart = $session->get('cart', array());
if ( $qtyAvailable > $cart[ $productid ] ) {
$cart[ $productid ] = $cart[ $productid ] + 1;
$response->setData(array('success'=>true,'message'=>'Qunatity increased'));
} else {
$response->setData(array('success'=>false,'message'=>'Out of stock'));
}
return $response;
}
Now I am having problems on where do I have to write the javascript code? I read that you have to create a new .js file and add it with assetic, so I did it like this:
I have added the jquery library and my ajax script file to assets in my base.twig.html file like this:
{% block javascripts %}
{# Le Javascript #}
{% javascripts
'bundles/mpFrontend/assets/js/jquery-1.11.2.min.js'
'Mp/ShopBundle/Resources/js/scripts.js'
%}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}
And then I extend the base.html.twig in the twig that I want to use the ajax.
However nothing happens when I press the button. SO the ajax code is added wrong probably?
You can use jQuery and Ajax to pick and post values to server you have to include the library first in order to use .
sample markup will be each item has its own container one input either text or hidden if you are using any js effect to increment quantity make sure this input gets updated each time increment or decrement action performed by user
<div class="item">
<input type="text" data-id="product id here" />
+
-
</div>
<div class="item">...</div>
<div class="item">...</div>
initialize click handler for anchor with class plus so that you can post values to server to update your cart array
$('.plus').on('click', function (e) {
$this = $(this);
$.ajax({
type: 'POST',
url: 'server side action url here',// /cart/update
async: false,
dataType: 'JSON',
data: {product: $this.parent('.item').find('input').data('id'),quantity: $this.parent('.item').find('input').val()},
success: function (data) {
if(data.success == false){
alert('error')
}
}
});
});
On your server side action just a sample code for increment quantity if you get the idea you can implement same for decrement scenario
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request; /** At the top of your controller*/
/**
* #Route("/cart/update", name="cart_update")
*/
public function cartUpdateAction( Request $request ) {
$response = new JsonResponse();
$requestData = $request->request->all();
$productid = $requestData['product'];/** first put all validations not empty/ is numeric and exists in your db etc*/
$quantity = $requestData['quantity'];/** first put all validations not empty/ is numeric etc */
/** if all is good then put your logic*/
$product = $em->getRepository('MpShopBundle:Product')->find($productid);
$qtyAvailable = $product->getStock();
$session = $this->getRequest()->getSession();
$cart = $session->get('cart', array());
if ( $qtyAvailable > $cart[ $productid ] ) {
$cart[ $productid ] = $cart[ $productid ] + 1;
$response->setData(array('success'=>true,'message'=>'Qunatity increased'));
} else {
$response->setData(array('success'=>false,'message'=>'Out of stock'));
}
return $response;
}
I have a list of checkboxes for people, and I need to trigger an event that will display information about each person selected in another area of the view. I am getting the event to run in my controller and updating the array of staff information. However, the view is not updated with this information. I think this is probably some kind of scope issue, but cannot find anything that works. I have tried adding a $watch, my code seems to think that is already running. I have also tried adding a directive, but nothing in there seems to make this work any better. I am very, very new to Angular and do not know where to look for help on this.
My view includes the following:
<div data-ng-controller="staffController as staffCtrl" id="providerList" class="scrollDiv">
<fieldset>
<p data-ng-repeat="person in staffCtrl.persons">
<input type="checkbox" name="selectedPersons" value="{{ physician.StaffNumber }}" data-ng-model="person.isSelected"
data-ng-checked="isSelected(person.StaffNumber)" data-ng-change="staffCtrl.toggleSelection(person.StaffNumber)" />
{{ person.LastName }}, {{ person.FirstName }}<br />
</p>
</fieldset>
</div>
<div data-ng-controller="staffController as staffCtrl">
# of items: <span data-ng-bind="staffCtrl.infoList.length"></span>
<ul>
<li data-ng-repeat="info in staffCtrl.infoList">
<span data-ng-bind="info.staffInfoItem1"></span>
</li>
</ul>
</div>
My controller includes the following:
function getStaffInfo(staffId, date) {
staffService.getStaffInfoById(staffId)
.then(success)
.catch(failed);
function success(data) {
if (!self.infoList.length > 0) {
self.infoList = [];
}
var staffItems = { staffId: staffNumber, info: data };
self.infoList.push(staffItems);
}
function failed(err) {
self.errorMessage = err;
}
}
self.toggleSelection = function toggleSelection(staffId) {
var idx = self.selectedStaff.indexOf(staffId);
// is currently selected
if (idx >= 0) {
self.selectedStaff.splice(idx, 1);
removeInfoForStaff(staffId);
} else {
self.selectedStaff.push(staffId);
getStaffInfo(staffId);
}
};
Thanks in advance!!
In the code you posted, there are two main problems. One in the template, and one in the controller logic.
Your template is the following :
<div data-ng-controller="staffController as staffCtrl" id="providerList" class="scrollDiv">
<!-- ngRepeat where you select the persons -->
</div>
<div data-ng-controller="staffController as staffCtrl">
<!-- ngRepeat where you show persons info -->
</div>
Here, you declared twice the controller, therefore, you have two instances of it. When you select the persons, you are storing the info in the data structures of the first instance. But the part of the view that displays the infos is working with other instances of the data structures, that are undefined or empty. The controller should be declared on a parent element of the two divs.
The second mistake is the following :
if (!self.infoList.length > 0) {
self.infoList = [];
}
You probably meant :
if (!self.infoList) {
self.infoList = [];
}
which could be rewrited as :
self.infoList = self.infoList || [];