Prevent datepicker values from being stripped from params - javascript

I'm using the bootstrap-datepicker.js (http://www.eyecon.ro/bootstrap-datepicker/) to set the date range for a table in a rails view.
calls.html.erb
<form class="form-horizontal daterange">
<input type="text" data-date-format="yyyymmdd" id="start-date" name="start">
<span class="add-on">-</span>
<input type="text" data-date-format="yyyymmdd" id="end-date" name="end">
<button type="submit" class="btn submit-date">Submit</button>
</form>
<script type="text/javascript">
$(document).ready(function() {
$('#start-date').datepicker({
format: 'yyyy-mm-dd'
});
$('#end-date').datepicker({
format: 'yyyy-mm-dd'
});
});
</script>
It passes params[:start] and params[:end] into the URL that I use to set the date range for Twilio API calls.
Ex. /twilio/calls/?start=2013-03-01&end=2014-01-06
Ever since I created the following routes to paginate the api calls:
routes.rb
match 'twilio/calls' => 'twilio#calls', :as => :twilio_page
match 'twilio/calls/:page_id' => 'twilio#calls', :as => :twilio_page
Whenever I advance to the next page of results, it strips out the date range values.
How do I preserve the :start/:end parameters?
twilio_controller.rb Update - added start/end data
if params[:start].nil?
#start_date = DateTime.parse((Date.today - 7).to_s).strftime("%Y-%m-%d")
#end_date = DateTime.parse((Date.today - 1).to_s).strftime("%Y-%m-%d")
else
#start_date = params[:start]
#end_date = params[:end]
end
#user = current_user
#account_sid = #user.twilio_account_sid
#auth_token = #user.twilio_auth_token
#page_size = 5
#page = params[:page_id] || 0
#sub_account_client = Twilio::REST::Client.new(#account_sid, #auth_token)
#subaccount = #sub_account_client.account
#recordings = #subaccount.recordings
#recordingslist = #recordings.list({:page_size => #page_size, :page => #page, :"date_created<" => #end_date, :"date_created>" => #start_date})

According to the canonical Rails guides:
The params will also include any parameters from the query string.
Therefore, you want to eliminate the first route, which doesn't allow for any parameters:
# config/routes.rb
match 'twilio/calls/:page_id' => 'twilio#calls', :as => :twilio_page
To illustrate, suppose you are trying to access /twilio/calls/1?start=2013-03-01&end=2014-01-06. The query string is actually parsed, such that you have access to not only the page_id parameter, but also start and end.
If you want to maintain a default route, e.g., twilio/calls, you can declare a route redirect as follows:
# config/routes.rb
get '/twilio/calls', to: redirect('/twilio/calls/1')
This will automatically reroute all requests for /twilio/calls to /twilio/calls/1.
UPDATE:
To clarify, URL query parameters are parameterized and fed to the corresponding controller action as members of the params hash. Therefore, given the path /twilio/calls/1?start=2013-03-01&end=2014-01-06, you'll have access to params[:page_id], params[:start], and params[:end]. You'll need to pass the latter two to your API call in your action:
#recordingslist = #recordings.list({:page_size => #page_size,
:page => #page,
:"date_created<" => params[:start],
:"date_created>" => params[:end]
})
UPDATE:
In order to persist the query parameters across page views, you can concatenate the argument passed to twilio_page_path with the query string:
twilio_page_path((#page.to_i + 1).to_s + "?start=" + params[:start] + "&end=" + params[:end]) %>

Related

Add Javascript string variable INSIDE a django template variable

I have a chart that is populated by django template variables. I have an update chart function that updates the data after certain buttons are clicked. Instead of writing the same js function 7x, can I update the javascript function to call the correct data within a Django template variable?
Basically, the btn_id will either be A, B, C, D, E, or F and I want the django template variable to display the correct data based on the btn_id value.
var results = {{ all_test_results.A.curve_series.curve_series }}.map(function(val, i) {
return val === 'None' ? null : val;
});
becomes
const btn_id = this.id.slice(-1); # A, B, C, D, etc
var results = {{ all_test_results.{{ btn_id }}.curve_series.curve_series }}.map(function(val, i) {
return val === 'None' ? null : val;
});
I am sure there is a simple way to inject a js string into a Django template variable, right?
Simple, no
I am sure there is a simple way to inject a js string into a Django
template variable, right?
Here's the problem. {{ all_test_results.A.curve_series.curve_series }} is handled by the server, which is handled by Django, which takes this tag, and fills it in with the data and returns an HTML document back to the client. Now it is the client, that is the end user, who clicks on say a button C, but how does Django, who lives on the server know about this? It doesn't, and that's why this is not so simple.
Solution
When the user clicks C, you can have a simple AJAX request sent from the client to the server, with this information. The server can then send back the value of all_test_results.C.curve_series.curve_series (you will have to write a query for this, you wouldn't be able to use a variable in place of C here), which can be received by your JavaScript, so that you don't have to do this 7 times.
Details
# views.py
from django.http import JsonResponse
def get_result(request, letter):
return JsonResponse({'answer': [result of your query that uses the variable, "letter", which corresponds to the letter the user clicked, here]})
Add a path to this view
# urls.py
urlpatterns = [
...
path('get_result/<str:letter>', views.get_result, name='get_result')
]
In your template you have your buttons with values that can be targeted by JavaScript, and the container that will show the result:
<button type="button" class="result_btns" value="A" id="A">A</button>
<button type="button" class="result_btns" value="B" id="B">B</button>
<button type="button" class="result_btns" value="C" id="C">C</button>
<button type="button" class="result_btns" value="D" id="D">D</button>
<button type="button" class="result_btns" value="E" id="E">E</button>
<button type="button" class="result_btns" value="F" id="F">F</button>
<div id="result">Result will appear here</div>
And finally the JavaScript, which will make the AJAX request, get the response from the server, and finally update the HTML with the result:
async function get_result(e) {
// Make the AJAX request to the server:
const response = await fetch('/get_result/' + e.target.value);
// Once the response is received, turn it into json format:
const result = await response.json();
// Update the div you want to carry the result:
// NOTE: you should not use innerHTML here.
document.getElementById('result').innerHTML = result.answer;
}
// Get all the buttons of class result_btns from the HTML
let btns = document.querySelectorAll("button");
// For each button, create an event listener, that will
// send to the function get_result, it's value
btns.forEach((btn) => {
btn.addEventListener('click', get_result);
});

Variable returned by Symfony controller always undefined

Ok, so I have a text field in which I type a string and I have a button next to it.
<div class="sidebar-search">
<div class="input-group custom-search-form">
<<label for="riot-summoner-input">Search a Summoner</label><br>
<input type="text" id="riot-summoner-input" class="form-control" placeholder="Type summoner name..." style="margin-bottom: 20px">
<button type="button" id="valid-summoner">Search</button>
</div>
</div>
By Clicking on this button, the following script gets executed
let res = {{ summoner.summonerLevel }}
$(document).ready(function() {
// Get value on button click and pass it back to controller
$("#valid-summoner").click(function () {
const summoner_input = $("#riot-summoner-input").val();
console.log(summoner_input)
let url = `/coach/?summonerName=${summoner_input}`
history.replaceState(summoner_input, 'Coach Index', url);
console.log(url)
function loadXMLDoc()
{
document.getElementById("display-summonerLevel").innerHTML = `Summoner Level: <h2>${res}</h2>`
}
loadXMLDoc();
});
});
Now as far as I can understand this will change my page url to include the value inserted in the text field and will send it back to my controller without refreshing the page, which it does.
Now in my Controller I'm using that value to do some logic with it
/**
* #Route("/", name="app_coach_index", methods={"GET"})
*/
public function index(CoachRepository $coachRepository, riotApi $callRiot, Request $request): ?Response
{
$value = $request->request->get('summoner_input');
if($value != null){
$this->debug_to_console($value . "Hi");
return $this->render('coach/index.html.twig', [
'coaches' => $coachRepository->findAll(), 'summoner'=> $this->showSummoner("$value")
]);}
else{
$this->debug_to_console($value);
return $this->render('coach/index.html.twig', [
'coaches' => $coachRepository->findAll()
]);
}
}
Now it's interesting to note that I'm doing this in the index function.
Here's the function I'm calling within the index function which is actually the one that gets the value from the script
/**
* #Route("/?summonerName={summoner_input}", name="show_summoner", methods={"GET"})
*/
public function showSummoner($summoner_input)
{
$call = new ApiClient(ApiClient::REGION_EUW, 'API-KEY-HERE');
return $call->getSummonerApi()->getSummonerBySummonerName($summoner_input)->getResult();
}
Now that I'm seeing this I can see that the issue is I'm getting the value in the showSummoner() function but trying to use it in the index function. Which is why I'm not getting a value when I print it to console and the variable is undefined.
Honestly I can't think of any logic I can do to overcome this issue.
EDIT!!!!!!!
Okay, so I know where the problem is arising, the issue is when I'm calling showSummoner($value) within index function. I'm using $value = $request->query->get('summoner_input');
I thought I was getting that value in the index function when in fact I'm getting it in the showSummoner() function. You can tell by the annotations
For index I don't have a parameter in its url, whereas in showSummoner() I have a parameter in the annotations as such.
/**
* #Route("/?summonerName={summoner_input}", name="show_summoner", methods={"GET"})
*/
This is indeed the fact because I'm using that url in the script as such
let url = `/coach/?summonerName=${summoner_input}`
The reason for this is I can't use the parameter in the index url because then I would have to provide the parameter in all the other places I'm using index in even when I don't have a parameter meaning I didn't search for anything.
I hope this gives more clarification
You're trying to get a value from $_GET global, not $_POST.
You can replace :
$value = $request->request->get('summoner_input');
by:
$value = $request->query->get('summoner_input');
You are trying to access the GET parameter using the wrong name ('summoner_input').
$value = $request->request->get('summoner_input');
When you are setting it as summonerName here:
let url = `/coach/?summonerName=${summoner_input}`
You will also want to pass a default value to check for, as the second parameter.
Try this:
$value = $request->request->get('summonerName', false);
if(false !== $value){
/* the parameter is in the url */
}

AJAX way of handling filters in a Django based ecommerce application is not working upon copy pasting the url in new tab

So I ran into this problem two days back and still haven't got a proper solution. I would highly Appreciate any help in here.
Let me explain the scenario first, so the idea is I have one django based ecommerce site and I want to render the product showcase page through ajax call (without reloading) and same time also update the url as the selected filters for example (http://vps.vanijyam.com:8000/customerlist?category=category_1).
I want to achieve similar to this site - shutterstock.
My Scenario -
http://vps.vanijyam.com:8000/customerlist this page to showcase all the available products and also have the filters option and pagination.
Now when I change the page or apply some filter I am e.g. http://vps.vanijyam.com:8000/customerlist?category_slug=category_1 then it is working, but when I refresh the page it is not.. the reason behind this the way I am handling this ajax call in the backend.
def customer_categories(request):
# Get attributes from request url
current_page = request.GET.get('page' ,'1')
category_slug = request.GET.get('category_slug')
sortby_filter = request.GET.get('sortby_filter')
price_filter = request.GET.get('price_filter')
search_query= request.GET.get('term')
line_filter_min = request.GET.get('price_min')
line_filter_max = request.GET.get('price_max')
# Set limit and offset for queryset
limit = REQUEST_PER_PAGE * int(current_page)
offset = limit - REQUEST_PER_PAGE
categories = Category.objects.all()
# products = Product.objects.filter(available=True)[offset:limit] # limiting products based on current_page
# products_count = Product.objects.filter(available=True).count()
# Check product already in cartlist
cartlist_check = []
cart_item_count = cart_count(request)
cart_items = cart_list(request)
for cart in cart_items:
cartlist_check.append(cart['product'].id)
# Check product already in wishlist, only if user logged in
wishlist_check =[]
if request.user.is_authenticated:
wishlist_items_check = WishList.objects.filter(user=request.user)
for item in wishlist_items_check:
wishlist_check.append(item.product_id)
wishlist_count = wishlist_counts(request.user)
else:
wishlist_count = 0
# If category_slug True
if category_slug:
category = get_object_or_404(Category, slug=category_slug).\
get_descendants(include_self=True)
else:
category = None
time1 = time.time()
# Filters for multiselect, retun products and products_count
products, products_count, search_list = attribute_filter(category=category,
search_query=search_query,
sortby_filter=sortby_filter,
price_filter=price_filter,
line_filter_min=line_filter_min,
line_filter_max=line_filter_max,
offset=offset,
limit=limit)
time2= time.time()
print('Time Elapsed')
print(time2-time1)
if len(products) > 0:
# adding one more page if the last page will contains less products
total_pages = math.ceil(products_count / REQUEST_PER_PAGE )
cart_product_form = CartAddProductForm()
wish_list_form = WishListForm()
total_pages = math.ceil(products_count / REQUEST_PER_PAGE ) # adding one more page if the last page will contains less products
if not current_page == '1' or category_slug:
print('------------------------------')
return render(request, 'customer/products/products_by_category.html',\
{'products': products,
'wishlist_item_check': wishlist_check,
'cartlist_item_check': cartlist_check,
'current_page': current_page,
'total_products': products_count,
'request_per_page': REQUEST_PER_PAGE,
'total_pages':total_pages
})
else:
return render(request, 'customer/home/customer_showcase.html',\
{'products': products,
'categories':categories,
'cart_product_form': cart_product_form,
'wish_list_form': wish_list_form,
'wishlist_item_check': wishlist_check,
'wishlist_count': wishlist_count,
'cart':cart_items,
'items_count':cart_item_count,
'cartlist_item_check': cartlist_check,
'current_page': current_page,
'total_pages':total_pages,
'total_products': products_count,
'request_per_page': REQUEST_PER_PAGE,
})
Ajax part of the code is here
$('.selected_subcategory').on('click', function () {
send_data['selected_subcategory'] = $(this).attr('data-id');
getPopularProductsData($(this).attr('data-id'));
// getAPIData();
var params = window.location.search;
var path = window.location.pathname;
var old_url = path + params;
var url = old_url;
const state = {}
const title = ''
console.log('old urll', old_url)
let new_url=''
if(params){
new_url = removeDuplicate(old_url)
}
console.log('new url', new_url)
history.pushState(state, title, url)
$.ajax({
method: 'GET',
url: old_url,
data: {
category_slug: send_data['selected_subcategory']
},
beforeSend: function () {
$('#products').html('<div class="alert alert-success">Loading...</div>');
// $('#spinner3').addClass('d-block');
},
success: function (result) {
if (result['error']) {
let message =
'<div class="alert alert-danger">' +
result['error'] +
' <a class="" href="http://vps.vanijyam.com:8000/customerlist/" style="text-decoration: underline">click here</a>' +
'</div>';
$('#products').html(message);
} else {
document.getElementById('products').innerHTML = result;
}
const state = {}
const title = ''
const url = this.url
history.pushState(state, title, url)
},
error: function (response) {
$('html,body').animate({
scrollTop: 200,
});
$('#products').html(
'<div class="alert alert-danger">Something went wrong!!!</div>'
);
$('#list_data').hide();
// $('#spinner3').addClass('d-none');
},
});
});
My expectation is when I browse this http://vps.vanijyam.com:8000/customerlist?page=2&category_slug=category_1 link it would render the same which matches with the query params, but in a ajax way.
Sorry for the long explanation. Hope my point is clear through this explanation. Thanks in advance
Here in your Django part, you are returning two different HTML outputs when there is category_slug and not.
If there is category_slug in your request you returning 'customer/products/products_by_category.html'
But when there is no category slug you are returning 'customer/home/customer_showcase.html'.
Both HTML files are different in their layout. The first one doesn't provide the header or container elements. This is the central problem of your issue.
You can Fix this issue by
You can put a special parameter in ajax api call, by which Django can realize that is is an api call, this time it returns products_by_category.html.
And you want to make unique all other returns to customer_showcase.html, but if there is a category filter you can pass filtered content to the products list. If category_slug is None or empty you can pass all products without the filter to the same HTML file.
You can also differentiate ajax api call by making it a POST request and all other web requests remains GET requests. So you can easily identify the traffic.
Here is the changes in Django:
if request.method == "POST"::
print('------------------------------')
return render(request, 'customer/products/products_by_category.html',\
{'products': products,
'wishlist_item_check': wishlist_check,
'cartlist_item_check': cartlist_check,
'current_page': current_page,
'total_products': products_count,
'request_per_page': REQUEST_PER_PAGE,
'total_pages':total_pages
})
else:
return render(request, 'customer/home/customer_showcase.html',\
{'products': products,
'categories':categories,
'cart_product_form': cart_product_form,
'wish_list_form': wish_list_form,
'wishlist_item_check': wishlist_check,
'wishlist_count': wishlist_count,
'cart':cart_items,
'items_count':cart_item_count,
'cartlist_item_check': cartlist_check,
'current_page': current_page,
'total_pages':total_pages,
'total_products': products_count,
'request_per_page': REQUEST_PER_PAGE,
})
And make your ajax call to "POST", change in your front end code: method: 'POST',
Don't forget to add slash(/) at the end of url when you change to POST.

Split string when comma is inserted and add it to a string array

I have an input that converts strings into 'styled tags' when the user types a comma, then, when the form is submitted, the strings are pushed into an array called 'content'.
On the EJS views, I am printing the result like <%= course.content %> but the result I am getting is 'string1,string2,string3,string4' and what I am after is that when is pushed into the array, each string must be a different element:
content ['string1','string2','string3','string4']
only then it will render properly in my views by looping the array. I want to achieve this by javaScript or jQuery only, please.
UPDATE: this is how I am rendering in my view
<ul>
<% var i; for (i = 0; i < course.content.length; i++) { %>
<li><i class="fas fa-check"></i> <%= course.content %></li>
<% }; %>
</ul>
UPDATE: this is my route where this POST request is being done
router.post("/", middleware.isLoggedIn, function(req, res) {
Course.create(req.body.course, function(err, course) {
if (err) {
req.flash("error", err.message);
return res.redirect("back");
}
res.redirect("/courses/" + course.id);
});
});
SOLVED! by using split on the server side like this:
router.post("/", middleware.isLoggedIn, function(req, res) {
Course.create(req.body.course, function(err, course) {
if (err) {
req.flash("error", err.message);
return res.redirect("back");
} else {
var content = req.body.course.content.toString().split(",");
course.content = content;
course.save();
console.log(course.content);
res.redirect("/courses/" + course.id);
}
});
});
Here is another solution in javaScript using function beforesubmit() by #garry man see below.
codepen
Long way
Otherwise there's one work around is as many tags you enter, that much input elements you should generate.
For e.g. my input tags are foo,bar then 2 input tags will be generated like
Note square brackets below
<input id="hiddenInput" type="hidden" name="course[content][]" required>
<input id="hiddenInput" type="hidden" name="course[content][]" required>
This is long way.
Another way
If you submit form via AJAX, then you can manipulate data before submitting and convert string into array with the use of .split(',').
Another way (Server side)
Split string by , on server side.
Okay so the problem is that you are submitting a form containing a single input which can only contain string values.
HTML Form practice is to have a single input for each array element to be posted, like:
<input name="course[content]"/> //value 1
<input name="course[content]"/> //value 2
<input name="course[content]"/> //value 3
So, in order to achieve that, before submit, you can call this function that generates those elements for you:
function beforesubmit(){
let submitVal = document.getElementById('hiddenInput');
let values = submitVal.value.split(',');
//let's get the container of the params passed to post
let paramsContainer = submitVal.parentElement;
// remove the param containting the string with commas as we're generating new ones
paramsContainer.removeChild(submitVal);
for(let i =0; i<values.length;i++){
//for each value, add a submit param containing each value
let tmp = document.createElement('input');
tmp.setAttribute('type','hidden');
tmp.setAttribute('name','course[content]');
tmp.setAttribute('value',values[i]);
paramsContainer.appendChild(tmp);
}
document.getElementsByTagName('form')[0].submit();
}
in order to call this function, replace your submit input with this:
<input type="button" value="submit" onclick="beforesubmit()">
Using this code you can already see the POST request difference between before and after. In your codepen it sends a single parameter, while with this snippet of code you are going to send an array of course['content'].
Now it's all up to how you are going retrieve data server side, you should be retrieving the course['content'] param as an array.

Rails: Returning a blank select option with observe_field?

I have an observe_field call in my app like this:
= observe_field "marketSelect", :update => "merchantSelect", :with => "id", :url => { :controller => "markets", :action => "get_merchants" }
That code activates the following action in my markets controller:
#merchants = #market.merchants.sort_by { |merchants| merchants.name }
That code returns a list of every merchant associated with the selected market, and populates them into another selection list.
How can I insert an empty "Select merchant..." along with the merchants?
Thank you
Hmm.. I found this at http://joshhuckabee.com/rails-observe-field-function-and
observe_field 'text_field_id',
{:function => "(value == '') ?
$('email_preview').innerHTML =
'<em>not entered yet</em>' :
$('email_preview').innerHTML =
value.replace(/\\n/g, '<br/>');",
:on => 'keyup'}
The author did this with prototype. He had added a little snippet that will replace carriage returns with HTML line breaks and also set a default value if no text is entered or the field gets cleared out.
Thanks for the suggestion. I ended up just building a straight javascript solution.

Categories