I need to render a URL for a JavaScript search that I am doing. Unfortunately Url.Action renders not only the action but the current id. This occurs when presently on page utilizing the action with the id.
To illustrate Url.Action("List", "Org"); will first render Org/List from which I can append an org to be listed. However, after the location has been moved to Org/List/12345 Url.Action("List", "Org"); will render Org/List/12345 and appending to that creates an issue where I end up with Org/List/12345/6789.
Is there a different method I can use other than Url.Action? I've thought about using JavaScript to check for the number of / and removing part of the string but that seems a bit hackish.
// appears in my Site.Master & utilizes the AutoComplete plugin for jQuery
$(document).ready(function() {
$("input#FindOrg").autocomplete('<%= Url.Action("Find", "Org") %>', {
minChars: 3,
maxItemsToShow: 25
}).result(function(evt, data, formatted) {
var url = '<%= Url.Action("List", "Org") %>/';
window.location.href = url + data;
});
});
Couple of suggestions:
What happens if you use Url.Action("Find", "Org", new { id = "" })?
Alternately, try manually building the url with Url.Content("~/Find/Org").
You could 'cheat' and access the 'Controller' and 'Action' entries in the Routing Dictionary. This would then allow you to construct only the part of the url you need. The only caveat is that if you change your routing model these routines may become incorrect.
Related
In Rails 6, I want the profiles/_form to have two dropdown lists, country and city. When I pick a value from country this is supposed to change the city choices. I want this to happen without
refreshing the page. My solution is below, and it kind of works for the new action, but it doesn't work for the edit action. Is this the right approach or am I totally missing the idiomatic rails 6 solution?
A route to return the option tags for the city select box:
# config/routes.rb
get 'cities_by_country/:id', to: 'profiles#cities_by_country'
The action that runs
# profiles_controller
def cities_by_country
#city_list = Profile::CITY_LIST[params[:id].to_i]
respond_to do |format|
format.js { render :cities_by_country}
end
end
The js file to generate the option tags
#views/profiles/cities_by_country.js.erb
<%= options_for_select(#city_list) %>
The javascript to attach the "change" event on the country select tag:
# app/javascript/packs/country_cities.js
import Rails from '#rails/ujs';
var country_select, city_select, selected_country;
window.addEventListener("load", () => {
country_select = document.querySelector("select#profile_country");
city_select = document.querySelector("select#profile_city");
country_select.addEventListener('change', (event) => {
selected_country = country_select.selectedIndex;
Rails.ajax({
url: "/cities_by_country/" + selected_country,
type: "get",
data: "",
success: function (data) {
city_select.innerHTML = data;
},
error: function (data) { }
})
})
});
Sorry to pile it on you but this is really broken.
Lets start with the controller/route. The idiomatic way to do this through a nested route - GET /countries/:country_id/cities. Nor should you really be shoehorning this into your Profile model / ProfilesController.
You can declare the route with:
get '/countries/:country_id/cities', to: 'countries/cities#index'
Or by using resources with a block:
resources :countries, only: [] do
resources :cities, only: [:index], module: :countries
end
And the controller like so:
module Countries
class CitiesController < ApplicationController
# GET /countries/:country_id/cities
def index
#cities = Profile::CITY_LIST[params[:city_id].to_i]
end
end
end
Not sure I can really get behind why you would want to use a constant in a model that should not be responsible for this at all instead of actually creating Country and City models.
The biggest issue with your JavaScript is that its completely non-idempotent. It all runs on window.addEventListener("load") so that it works on the intitial page load and then breaks down completely when Turbolinks replaces the page contents with AJAX since those event handlers were attached directly to the elements themselves.
To write JavaScript that works with Turbolinks you need to think differently. Create idempotent handlers that catch the event as it bubbles up the DOM.
# app/javascript/packs/country_cities.js
import Rails from '#rails/ujs';
document.addEventListener('change', (event) => {
let input = event.target;
if (input.matches('#profile_country')) {
Rails.ajax({
url: `/cities/${input.value}/country`,
type: 'get',
dataType : 'script'
});
}
});
If you want to use a js.erb template you also need to rewrite your view so that it transforms the page:
// app/views/countries/cities.js.erb
document.getElementById("#profile_city").innerHTML = "<%= j options_for_select(#cities) %>";
But you could also just use JSON and create the option elements on the client if you want to avoid making your server responsible for client side transformations.
I have a custom Menu which loads a new MVC View for each click as I want.
I load the new View by setting window.location.href. To make it work I have to set the baseURL (the name of the website) each time. To Store the state of the menu I use URL's querystring.
My concerns is in the use of:
'/WebConsole53/' // hardcode baseurl i have to apply each time manually
Setting window.location.href to load the new View from JavaScript // Is this the best way or should I use some URL/Html helpers instead?
I store the state of the selected menuItem in the querystring ("menu") // Is it more common to store that kind in Session/Cookie?
Any thoughts, corrections and suggestions would be much appriciated - thanks.
_Layout.cshtml
var controller = $self.data('webconsole-controller');
var action = $self.data('webconsole-action');
var menu = "?menu=" + $self.attr('id');
var relUrl = controller + "/" + action + menu;
var url = urlHelper.getUrl(relUrl);
window.location.href = url;
UrlHelper.js
var urlHelper = function () {
var getBaseUrl = '/WebConsole53/',
buildUrl = function(relUrl) {
return getBaseUrl + relUrl;
};
var getUrl = function(relUrl) { // relUrl format: 'controller/action'
return buildUrl(relUrl);
};
return {
getUrl: getUrl
};
}();
I Use MVC 5.
You can save this problem using Route. Through the route you know exactly where you are located in you application.
_Layout.cshtml is definetely not the place to have this javascript. Maybe you are missing some MVC concepts, I would recommend you to read a bit more about routes and routelinks
I hope this helps you a bit: RouteLinks in MVC
'/WebConsole53/' // hardcode baseurl I have to apply each time manually
sometimes you need to access your root from javascript where you don't have access to server-side code (eg #Html). While refactoring may be the best option you can get around this by storing the baseurl once, using server-side code, eg in _layout.cshtml:
<head>
<script type="text/javascript">
var basePath = '#Url.Content("~")'; // includes trailing /
</script>
... load other scripts after the above ...
</head>
you can then reference this everywhere and it will always be valid even if you move the base / migrate to test/live.
Setting window.location.href to load the new View from JavaScript // Is this the best way or should I use some URL/Html helpers instead?
Depends on your requirements - you could use $.ajax (or shortcuts $.get or $.load) to load PartialViews into specific areas on your page. There's plenty of examples on SO for this and the jquery api help.
Or just use <a> anchors or #Html.ActionLink as already suggested. Without needing menu= (see next) you don't need to control all your links.
I store the state of the selected menuItem in the querystring ("menu") // Is it more common to store that kind in Session/Cookie?
If you change the page, then you could query the current url to find which menu item points to it and highlight that one (ie set the menu dynamically rather than store it).
This would also cover the case where you user enters the url directly without the menu= part ... or where your forget to add this... not that that would happen :)
Additional: You can specify which layout to use in your view by specifying the Layout at the top of the view, eg:
#{
Layout = "~/Views/Shared/AltLayout.cshtml";
}
(which is also one of the options when you right click Views and Add View in visual studio)
Without this, MVC uses a configuration-by-convention and looks at Views/_ViewStart.cshtml which specifies the default _layout.cshtml.
If you don't want a layout at all, then just return PartialView(); instead
I have an <a> tag which I'm using to redirect the user to another xpage.
Its href property is:
<a target="_blank" href="http://serv/MyBase.nsf">
I use a simple view listing a doc. which contains the server and the name of the application.
So, I want to use some #DbLookup function in javascript to get into 2 var the above server and app name:
var server = #Unique(#DbColumn(#DbName(), "myVw", 1);
var name = #Unique(#DbColumn(#DbName(), "myVw", 2);
var concat = server+"/"+name;
return concat;
How can I compute the href property to return the concat variable?
Create a Link control xp:link and calculate the URL in attribute value:
<xp:this.value><![CDATA[#{javascript:var server .... }]]></xp:this.value>
Knut's approach is correct, but your code isn't :-). For every XPages load (or refresh) you do 4 #DbLookup. You can do a set of optimisations here:
Combine the result you want in the view itself, so you only need one lookup
Cache the value in the session (or application scope)
something like this (add nice error handling):
if (sessionScope.myHref) {
// Actually do nothing here
} else {
sessionScope.myHref = #Unique(#DbColumn(#DbName(), "myVw", 3);
}
return sessionScope.myHref;
The 3rd column would have the concatenation in the view already. That little snippet does a lookup only once per session. If it is the same for all users, use the applicationScope then it is even less.
I have the following server-side URL mappings defined:
/main/item1
/main/item2
I've added SammyJS routing support so that I am able to do the following:
/main/item1#/ /* main view */
/main/item1#/topups /* topup view */
I've set up SammyJS like so:
s.app = Sammy(function() {
this.get('#/topups', function() {
console.log('Initializing topups view.');
});
this.get('#/', function() {
console.log('Initializing main view.');
});
});
The problem is, I have a summary section in my page that redirects to the topup view of a different "item". E.g., I am at the url /main/item1#/, and in this page, there exists a tag item 2's topups.
I expect to be redirected (page refresh) to the new URL, however, it seems like SammyJS is intercepting the /main/item2#/topups call and simply running the this.get('#/topups') route I've defined.
I expect that since the URL paths before the hash, /main/item1 and /main/item2 are different, the SammyJS routing won't be triggered.
Is there a way to prevent this behavior from happening in SammyJS?
I don't know much about Sammy but I can tell you from the way any router behaves, is that it catches the first match in the routing possibilities, and so, anything that ends with #/topups will be considered the same as long as it's after the hash sign.
so you better define the router this way:
this.get('#/topups/:item', function() {
console.log('Initializing topups view for item: '+ item);
})
and then call the pages with URLs like:
item 2's topups
I hope this is what you're looking for
I'm pretty sure using the full URL redirect you.
Item 2 top ups for the lazy coder
However, that will cause the page to reload. If you modify Labib's answer you can have an even better solution:
this.get('#/topups/:item', function () {
console.log('Doing some post processing on current item');
console.log('Now redirecting you to ' + this.params.item);
window.location.href = 'http://example.com/menu/# + this.params.item +#/topups';
});
Again this will cause the page to reload, but if you do not mind that, then just either method.
NOTE: Sammy will also check form submission for you. This trips me up EVERY time I use it. This post has the solution.
Backstory: To specify the correct route for a jqGrid that I show on my ASP.NET MVC 3 page, I do something like so:
$('#jqgFlavors').jqGrid({
url: '#Url.Action("FlavorData", "IceCream")',
etc...
and that will produce the correct route either when running locally out of Visual Studio (where things live at something like "http://localhost:90125/IceCream" or on the deployed site where things live at something like "http://thehostsite/mydeployedsitename/IceCream".
Great. Now the issue I'm having is that I use the onSelectRow in the grid to do a master/details thing based on the selected row's flavor id value. First, I tried doing this to just get the route correct:
onSelectRow: function(theRow){
$('#flavorDetails').load('#Url.Action("Details","IceCream", new {id = 42)})');
}
So that I can pass the value 42 in as the 'id' parameter in the Details action of the IceCream controller. And that works fine, but of course I don't want to hard code the value 42, rather pull the flavor id from the grid itself. So I have tried to reference the flavorID but can't seem to get the syntax correct:
onSelectRow: function(theRow){
var grid = jQuery('#jqgFlavors');
var flavorID = grid.jqGrid('getCell', theRow, 'FlavorID');
$('#flavorDetails').load('#Url.Action("Details","IceCream", new {id = flavorID)})');
}
I'm sure you get what I'm going for here - referencing the flavorID value I extract from the grid. But what I get is a compilation error:
The name 'flavorID' does not exist in the current context.
I suspect this is really simple. How do I reference correctly that variable?
You could use the second argument of the .load() method which allows you to pass additional parameters:
var flavorID = grid.jqGrid('getCell', theRow, 'FlavorID');
$('#flavorDetails').load('#Url.Action("Details", "IceCream")', { id: flavorID });
This might probably use the following url: /IceCream/Details?id=123 instead of what you might want /IceCream/Details/123 because javascript doesn't know anything about your routes but why care? It will still map correctly to the controller action:
public ActionResult Details(int id)
{
...
}
But if you are really anal about urls and insist on having the first type of url I've seen people doing the following:
var flavorID = grid.jqGrid('getCell', theRow, 'FlavorID');
var url = '#Url.Action("Details", "IceCream", new { id = "_TOREPLACE_" })';
url = url.replace('_TOREPLACE_', flavorID);
$('#flavorDetails').load(url);
Personally I wouldn't do it but providing it just for the record.