Ajax functional on category selection - javascript

I am still very weak in Ajax, but they told me that this is a way out of the situation that I have developed.
I have a javascript function that filters categories. There is also alternation of lists in php.blade.
When I click on "All", all blogs are displayed and the alternation is working properly. When I select the "desired category", all blogs from this category are displayed, but alternation does not work.
I was prompted that you can is to call one ajax functional on category selection and return the HTML response on that ajax call. But I don't know how to do this, can anyone help?
JavaScript
$('.category-filter_item').click(function(){
$('.category-filter_item').removeClass('active')
$(this).addClass('active')
var dataFilter = $(this).attr('data-filter');
$('.blog-list').hide()
$(dataFilter).show()
})
php.blade
#extends('layouts.app')
#section('content')
<div class="container">
<div class="category-filter" id="filter">
<div class="category-filter_item active" data-filter="*">All</div>
#foreach($categories as $category)
<div class="category-filter_item" data-filter=".category_{{$category->id}}">{{ $category->title }}</div>
#endforeach
</div>
#foreach ($blogs as $index => $blog)
<div class="blog-list">
#if ($index % 2 === 1) //Alternation
<div class="blog blog--left" >
<h2 class="blog_title">{{ $blog->title }}</h2>
</div>
#else
<div class="blog blog--right">
<h2 class="blog_title">{{ $blog->title }}</h2>
</div>
#endif
</div>
#endforeach
</div>
#endsection
Controller
public function index()
{
$blogs = Blog::all();
$categories = Category:all();
return view('blog', compact('blogs', 'categories'));
}

Need to create new route and controller function to return only the selected category blogs and that should be in json format
Once that is done need to use the ajax call to the above route in the click function defined. Capture the response and then iterate and generate the html that should be inside the blog-list in your script. then update the html inside blog-list value.
refer this link
refer this for making ajax call link

Related

Conditionally hide the nth element of a v-for loop without modifying the array. vue 3 composition api search function

I have a ref variable (foxArticles ), which holds a list that contains 100 items. In a v-for loop i loop over each value. As a result, i have 100 values rendered on the page.
<template>
<div class="news_container">
<div
v-for="article in foxArticles"
v-bind:key="article"
class="article_single_cell"
>
<div
class="news_box shadow hover:bg-red-100 "
v-if="containsKeyword(article, keywordInput)"
>
<div class="news_box_right">
<div class="news_headline text-red-500">
<a :href="article.url" target="_blank">
{{ article.title }}
</a>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
const foxArticles = ref([]);
</script>
I also have a search function, which returns the value, if it includes the passed in keyword. The function is used in the child of the v-for loop.
<div class="search_input_container">
<input
type="text"
class="search_input"
v-model="keywordInput"
/>
</div>
<script>
const keywordInput = ref("");
function containsKeyword(article, keywordInput) {
if (article.title.toLowerCase().includes(keywordInput.toLowerCase())) {
return article;
}
}
</script>
The problem is, i can't use .slice() on the foxArticles array in the v-for loop, because that screws up the search functionality, as it returns only the values from the sliced range.
How can i have the access the all of the values of the array, while not rendering all 100 of returned articles on the initial load?
Any suggestions?
I think your approach will make it incredibly complex to achieve. It would be simpler to always iterate over some set, this set is either filtered based on a search-term, or it will be the first 100 items.
I'm not very familiar yet with the Vue 3 composition api so I'll demonstrate with a regular (vue 2) component.
<template>
<div class="news_container">
<div
v-for="article in matchingArticles"
v-bind:key="article"
class="article_single_cell"
>
... news_box ...
</div>
</div>
</template>
<script>
export default {
...
computed: {
matchingArticles() {
var articles = this.foxArticles;
if (this.keywordInput) {
articles = articles.filter(article => {
return this.containsKeyword(article, this.keywordInput)
})
} else {
// we will limit the result to 100
articles = articles.slice(0, 100);
}
// you may want to always limit results to 100
// but i'll leave that up to you.
return articles;
}
},
....
}
</script>
Another benefit is that the template does not need to worry about filtering results.
ok, so i kind of came up with another solution, for which you don't have to change the script part...
instead of having one v-for loop , you can make two of them, where each one is wrapped in a v-if statement div
The first v-if statement says, If the client has not used the search (keywordInput == ''), display articles in the range of (index, index)
The second one says = If the user has written something (keywordInput != ''), return those articles.
<template>
<div class="news_container">
<!-- if no search has been done -->
<div v-if="keywordInput == ''">
<div
v-for="article in foxArticles.slice(0, 4)"
v-bind:key="article"
class="article_single_cell"
>
<div class="news_box shadow hover:bg-red-100 ">
<div class="news_box_right">
<div class="news_headline text-red-500">
<a :href="article.url" target="_blank">
{{ article.title }}
</a>
</div>
</div>
</div>
</div>
</div>
<!-- if searched something -->
<div v-else-if="keywordInput != ''">
<div
v-for="article in foxArticles"
v-bind:key="article"
class="article_single_cell"
>
<div
class="news_box shadow hover:bg-red-100 "
v-if="containsKeyword(article, keywordInput) && keywordInput != ''"
>
<div class="news_box_right">
<div class="news_headline text-red-500">
<a :href="article.url" target="_blank">
{{ article.title }}
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
im not sure how this impacts performance tho, but that's a problem for another day

Pre-populate current value of WTForms field in order to edit it

I have a form inside a modal that I use to edit a review on an item (a perfume). A perfume can have multiple reviews, and the reviews live in an array of nested documents, each one with its own _id.
I'm editing each particular review (in case an user wants to edit their review on the perfume once it's been submitted) by submitting the EditReviewForm to this edit_review route:
#reviews.route("/review", methods=["GET", "POST"])
#login_required
def edit_review():
form = EditReviewForm()
review_id = request.form.get("review_id")
perfume_id = request.form.get("perfume_id")
if form.validate_on_submit():
mongo.db.perfumes.update(
{"_id": ObjectId(perfume_id), <I edit my review here> })
return redirect(url_for("perfumes.perfume", perfume_id=perfume_id))
return redirect(url_for("perfumes.perfume", perfume_id=perfume_id))
And this route redirects to my perfume route, which shows the perfume and all the reviews it contains.
This is the perfume route:
#perfumes.route("/perfume/<perfume_id>", methods=["GET"])
def perfume(perfume_id):
current_perfume = mongo.db.perfumes.find_one({"_id": ObjectId(perfume_id)})
add_review_form = AddReviewForm()
edit_review_form = EditReviewForm()
cur = mongo.db.perfumes.aggregate(etc)
edit_review_form.review.data = current_perfume['reviews'][0]['review_content']
return render_template(
"pages/perfume.html",
title="Perfumes",
cursor=cur,
perfume=current_perfume,
add_review_form=add_review_form,
edit_review_form=edit_review_form
)
My issue
To find a way to get the review _id in that process and have it in my perfume route, so I can pre-populate my EditReviewForm with the current value. Otherwise the form looks empty to the user editing their review.
By hardcoding an index (index [0] in this case):
edit_review_form.review.data = current_perfume['reviews'][0]['review_content']
I am indeed displaying current values, but of course the same value for all reviews, as the reviews are in a loop in the template, and I need to get the value each review_id has.
Is there a way to do this, before I give up with the idea of allowing users to edit their reviews? :D
Please do let me know if my question is clear or if there's more information needed.
Thanks so much in advance!!
UPDATE 2:
Trying to reduce further my current template situation to make it clearer:
The modal with the review is fired from perfume-reviews.html, from this button:
<div class="card-header">
<button type="button" class="btn edit-review" data-perfume_id="{{perfume['_id']}}" data-review_id="{{review['_id']}}" data-toggle="modal" data-target="#editReviewPerfumeModal" id="editFormButton">Edit</button>
</div>
And that opens the modal where my form with the review is (the field in question is a textarea currently displaying a WYSIWYG from CKEditor:
<div class="modal-body">
<form method=POST action="{{ url_for('reviews.edit_review') }}" id="form-edit-review">
<div class="form-group" id="reviewContent">
{{ edit_review_form.review(class="form-control ckeditor", placeholder="Review")}}
</div>
</form>
</div>
Currently this isn't working:
$(document).on("click", "#editFormButton", function (e) {
var reviewText = $(this)
.parents(div.card.container)
.siblings("div#reviewContent")
.children()
.text();
$("input#editReviewContent").val(reviewText);
});
and throws a ReferenceError: div is not defined.
Where am I failing here? (Perhaps in more than one place?)
UPDATE 3:
this is where the button opens the modal, and underneath it's where the review content displays:
<div class="card container">
<div class="row">
<div class="card-header col-9">
<h5>{{review['reviewer'] }} said on {{ review.date_reviewed.strftime('%d-%m-%Y') }}</h5>
</div>
<div class="card-header col-3">
<button type="button" class="btn btn-success btn-sm mt-2 edit-review float-right ml-2" data-perfume_id="{{perfume['_id']}}" data-review_id="{{review['_id']}}" data-toggle="modal" data-target="#editReviewPerfumeModal" id="editFormButton">Edit</button>
</div>
</div>
<div class="p-3 row">
<div class=" col-10" id="reviewContent">
<li>{{ review['review_content'] | safe }}</li>
</div>
</div>
</div>
You can do this with jQuery as when you open the form, the form will automatically show the review content in there. It will be done by manipulating the dom.
Also, add an id to your edit button, in this example, I have given it an id "editFormButton".
Similarly, add an id to the div in which review content lies so that it is easier to select, I have given it an id "reviewContent"
Similarly, add an id to edit_review_form.review like this edit_review_form.review(id='editReviewContent')
<script>
$(document).on("click", "#editFormButton", function (e) {
var reviewText = $(this)
.parents("div.row")
.siblings("div.p-3.row")
.children("div#reviewContent")
.children()
.text();
$("input#editReviewContent").val(reviewText);
});
</script>
Don't forget to include jQuery.
Also, you can do it with pure javascript. You can easily search the above equivalents on google. This article is a good start!

Delete object on laravel

I'm learning laravel, and now i trying to delete object without form.
i want to use js to detect when user click delete's button and return notify to controller. Then controller with delete object with id has returned from JS file.
This is blade file
#extends ('layouts.master')
#section ('head.title')
Blog
#stop
#section ('body.content')
<div class="container">
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
</div>
</div>
<form class="form-show">
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
<h2> {{ $article->title}} </h2>
<p> {{ $article->content}} </p>
</div>
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
Update
<button id="delete-button" type="submit" class="btn btn-primary">Delete</button>
</div>
</div>
</div>
</form>
</div>
<script src="{{ asset('/js/jshow.js') }}"></script>
#stop
And this is controller file
class ArticlesController extends Controller
{
protected $articleModel;
public function __construct(Article $article){
$this->articleModel = $article;
}
public function index(){
$articles = $this->articleModel->getListArticles();
// $articles = Article::paginate(10);
return view('articles.index',compact('articles'));
}
public function show($id){
// $article = Article::find($id);
$article = $this->articleModel->getArticleWithID($id);
return view('articles.show',compact('article'));
}
public function delete($id){
$this->articleModel->deleteArticle($id);
return redirect()->route('articles.index');
}
}
And here is JS file
var deleteButton = document.getElementById("delete-button");
var idPost = document.getElementById("")
deleteButton.onclick = function() {
alert('Click to delete');
return false;
}
You need to use $.ajax from the jQuery JS library.
Try to understand and to do something, then come back if you any have difficulties.
Like Jerodev said, AJAX calls can be done without jQuery, but I find the jQuery method more understandable. Check here to read more about AJAX using plain JavaScript.
AJAX Request in Jquery is that for you ...
first learn how AJAX Work to request server .
use jquery to easy that for you .
remake delete function to make it more able with AJAX Request to just delete the object and response data .
public function delete(Request $req)
{
$this->articleModel->deleteArticle($id);
return response()->json(['msg' => 'some Msg help]);
}
make route for this function .
then make the ajax request to get this route and delete the object and return the msg you make .
but you must first Learn AJAX .

Form modal binding in laravel with vue js

I have 2 models Tour.php
public function Itinerary()
{
return $this->hasMany('App\Itinerary', 'tour_id');
}
and Itinerary.php
public function tour()
{
return $this->belongsTo('App\Tour', 'tour_id');
}
tours table:
id|title|content
itineraries table:
id|tour_id|day|itinerary
In tour-edit.blade.php view I have used vue js to create or add and remove input field for day and plan dynamically.
Code in tour-create.blade.php
<div class="row input-margin" id="repeat">
<div class="col-md-12">
<div class="row" v-for="row in rows">
<div class="row">
<div class="col-md-2">
<label >Day:</label>
<input type="text" name="day[]"
class="form-control">
</div>
<div class="col-md-8">
{{ Form::label('itinerary', " Tour itinerary:", ['class' => 'form-label-margin'])}}
{{ Form::textarea('itinerary[]',null, ['class' => 'form-control','id' => 'itinerary']) }}
</div>
<div class="col-md-2">
<button class="btn btn-danger" #click.prevent="deleteOption(row)">
<i class="fa fa-trash"></i></button>
</div>
</div>
</div>
<div class="row">
<button class="btn btn-primary add" #click.prevent="addNewOption" >
<i class="fa fa-plus"></i> Add Field</button>
</div>
</div>
</div>
I want to populate these fields with their respective data. But all data i.e itinerary belonging to a tour are being displayed in itinerary textbox in JSON format.
My vue js sript is:
<script>
var App = new Vue({
el: '#repeat',
data: {
day:1 ,
rows:[
#foreach ($tour->itinerary as $element)
{day: '{{$element->day}}', plan: '{{$element->plan}}'},
#endforeach
]
},
methods: {
addNewOption: function() {
var self = this;
self.rows.push({"day": "","itinerary":""});
},
deleteOption: function(row) {
var self = this;
self.rows.splice(row,1);
},
}
});
</script>
I would avoid mixing blade into JavaScript, instead the best option is to make an ajax call to an api route which returns your data in json, which can then be processed by Vue:
methods:{
getItinerary(){
axios.get('api/itinerary').then(response => {
this.itinerary = response.data;
})
}
}
However, with this approach you will likely need to use vue-router rather than laravel web routes, which puts us into SPA territory.
If that's not an option (i.e. you still want to use blade templates), you should take a look at this answer I gave the other day which shows you how to init data from your blade templates.
What you seem to be doing is using laravel's form model binding to populate your forms, not Vue, so your model data is not bound to the view. So, you will need to decide which one you want to use. If it's vue you just want to use a normal form and bind the underlying data to it using v-model:
Now any updates in the view will automatically be updated by Vue. I've put together a JSFiddle that assumes you will want to continue using Laravel web routes and blade templates to show you one approach to this problem: https://jsfiddle.net/w6qhLtnh/

Editing property from Object in ng-repeat from LocalStorage

I'm listing an array of objects saved into Localstorage in a table-like layout.
Each row displays data saved in a particular object. I want to be able to edit and update certain properties from the object once it has already been saved into LocalStorage.
This is how a couple of my objects looks like:
[{
"date":"2014 10 16",
"time":"20.22",
"car":"396",
"driver":"Seb",
"from":"A",
"destination":"B",
"pax":"3",
"arrival":"23.10"
},
{
"date":"2014 10 16",
"time":"23.22",
"car":"46",
"driver":"Eric",
"from":"C",
"destination":"E",
"pax":"3",
"arrival":"00.10"
}]
So far my frontend code displaying the Destination property looks like this:
HTML
<div class="col-md-3"
ng-show="editItem == false"
ng-hide="editItem">{{record.destination}}</div>
// Shows current value
<div class="col-md-3"
ng-show="editItem == true"
ng-hide="!editItem">
<select class="form-control"
ng-model="locationList2"
ng-options="location.place for location in locationlist | orderBy:'place'">
<option value="">Destination</option>
</select>
</div>
// Shows select with options to be picked to update property
<div class="col-md-1">
<button ng-click="editItem = !editItem"
ng-show="!editItem">Edit</button>
<button ng-click="editData(record); editItem = !editItem"
ng-show="editItem">Ok</button>
</div>
//Toggles between current value and select and triggers editData function
Relevant JS:
$scope.editData = function (record) {
record.destination = $scope.locationList2;
jsonToRecordLocalStorage($scope.recordlist);
}
So far when I trigger editData it just deletes the Destination property, it doesn't update it with the model of locationList2 from the Select.
What am I missing?
EDIT
Here's the complete ng-repeat piece of code:
<div class="row msf-row" ng-repeat="record in recordlist | filter: search">
<div class="col-md-1">{{record.time}}</div>
<div class="col-md-1"><strong>{{record.car}}</strong></div>
<div class="col-md-1">{{record.driver}}</div>
<div class="col-md-3">{{record.from}}</div>
<div class="col-md-3"
ng-show="editItem == false"
ng-hide="editItem">
{{record.destination}}
</div>
<div class="col-md-3"
ng-show="editItem == true"
ng-hide="!editItem">
<select class="form-control"
ng-model="locationList2"
ng-options="location.place for location in locationlist | orderBy:'place'">
<option value="">Destination</option>
</select>
</div>
<div class="col-md-1">{{record.pax}}</div>
<div class="col-md-1">
<button
ng-click="editItem = !editItem"
ng-show="!editItem">
<i class="fa fa-pencil"></i>
</button>
<button
ng-click="editData(record); editItem = !editItem"
ng-show="editItem">
<i class="fa fa-check"></i>
</button>
</div>
</div>
Also, I here's a Plunkr to ilustrate the issue!
Add a driver, car code and location before starting to see the app running and the mentioned problem.
You could use angular-local-storage as an abstraction over LocalStorage API.
If you want to just hack it, you can do something along localStorage.setItem('data', JSON.stringify(data)) when setting data and use JSON.parse(localStorage.getItem('data')) to extract it. LocalStorage doesn't deal with objects by default so we have to serialize it.
Regardless of the solution you choose, it could be a good idea to extend your edit a bit:
$scope.editData = function (recordlist) {
$scope.recordlist.destination = $scope.locationList2;
// replace whole LocalStorage data here now. no need to "patch" it
updateLocalStorage('data', <data containing your objects goes here>);
}
If you have multiple ways to modify the data and want to avoid explicit update, you could set up a watcher instead:
$scope.$watch(<data name goes here>, function(newVal) {
// update your LocalStorage now
});
Why it fails with ng-repeat?
The reason you see the behavior is quite simple. $scope.locationList2 is a single variable that gets bound for each member created by ng-repeat. That explains why it stays empty during edit.
You will need to bind the data using some other way. Consider binding it directly to your record models. Example: AngularJS - Using $index in ng-options .
Solution
The original code had bits like this:
JS:
$scope.editData = function (record) {
record.destination = $scope.location;
jsonToRecordLocalStorage($scope.recordlist);
};
HTML:
<select class="form-control" ng-model="location" ng-options="location.place for location in locationlist | orderBy:'place'">
<option value="">Destination</option>
</select>
Note that the markup is inside a ng-repeat and effectively each item created by it points at the same location! This isn't good.
To make it work I changed it like this:
JS:
$scope.editData = function () {
jsonToRecordLocalStorage($scope.recordlist);
};
HTML:
<select class="form-control" ng-model="record.destination" ng-options="location.place as location.place for location in locationlist | orderBy:'place'">
<option value="">Destination</option>
</select>
As mentioned above the JS could be replaced by a watcher. The important thing to note here is that I bind the data directly to the records. That avoid hassle at editData and more importantly gets rid of the problematic ng-model reference.

Categories

Resources