Trying to put some pieces together here.
Piece 1: I know that I can get the current username in MVC by using:
#HttpContext.Current.User.Identity.Name
in my razor view.
Piece 2: I have some MVVM-style code in a separate JS file, relevant parts shown below:
var FeedbackViewModel = function () {
var self = this;
self.username = ko.observable("");
self.feedbackText = ko.observable("");
self.userNameCaptured = ko.computed(function () { return self.username().length > 3; }, self);
};
var feedbackViewModel = new FeedbackViewModel();
ko.applyBindings(feedbackViewModel, document.getElementById("feedbackModal"));
Question: How do I pass the current username from MVC3 to the Knockout viewModel so that I can observe and take action based upon it?
I assume feedbackViewModel is defined as a global variable, so you could simply add some code to the CSHTML file to populate it inline:
<script type="text/javascript">
feedbackViewmodel.username("#HttpContext.Current.User.Identity.Name");
</script>
Another option would be to pass the variable into the ViewModel function as a constructor element.
(I'd rather post this as comment than answer but somehow don't have option here)
I've faced similar problems in the past (handoff between razor and javascript). The best answer I've found is to create a hidden control (#hiddenval or #username) which can then be "read" by a JQuery selector.
This approach works for me every time. It's inelegant and may potentially expose info to a user sourcing the html...
if it's not obvious, #HttpContext.Current.User.Identity.Name should be populated as part of the markup
Related
I'm working on a Kendo Mobile project with a number of:
Kendo Views (external to root html)
Modal Views (in the root html).
The external files load on demand and everything works fine. But i'd like to have the same load on demand behavior for some of the modal views, because the root/based html file is becoming too large and not manageable.
Is there a way to either:
Store a modal view in an external file? If so is it possible to load via javascript syntax (app.navigate()) rather than the declarative syntax (href='externalmodal').
Manually pre-load an external view without navigating to it first.
This code lets you manually create a view:
var viewUrl = 'blahblahblah';
var element = $.parseHTML('<div data-role=view>test</div>')[0];
element.style.display = 'none';
$(document.body).append(element);
var options = $.extend({}, kendo.parseOptions(element, kendo.mobile.ui.View.fn.options));
var view = new kendo.mobile.ui.View(element, options);
view.element[0].setAttribute('data-url', viewUrl);
kendo.mobile.application.navigate(viewUrl, '');
Depending on what features you use, you may need to instead use code similar that that used for ModalView below so that Kendo creates the subclass (changes: substitute View for ModalView, substitute view for modalview, add data-url, remove call to show(), maybe check that view not already created by checking for element with matching data-url). We haven't tested setting roles.view this way, but we did something similar while testing this stuff out and it worked.
Don't try settings the options - Kendo got confused (at least trying to set useNativeScrolling didn't work, also don't try setting the options object on the subclass unless you really know what you are doing).
Caveat: This was using browserHistory:false (which disables routing) when the kendo.mobile.Application was created. The technique should still work when using browser history if you use a valid url fragment (same as would be created by Kendo for the pushstate/hashchange url).
This is a also way to cleanly subclass kendo.mobile.ui.View that works well - although you must still use data-role=view even though your subclass is a "different" component. Note that you can't just use you cant use your own subclassed component with its own name like role=myview to subclass a view because there are hard-coded checks specifically for data-role=view in the kendo codebase. Same if you wish to subclass: layout modalview drawer splitview page (amongst other hard-coded kendo ui component names - search kendo code for kendo.roleSelector - ugly). e.g.
MyView = kendo.mobile.ui.View.extend({
init: function(element, options) {
kendo.mobile.ui.View.prototype.init.apply(this, arguments);
...
var myView = new MyView('<div data-role=view>test</div>');
Why it works: The relevant function in the Kendo source code is _findViewElement which does element = this.container.children("[" + attr("url") + "='" + urlPath + "']"); to see if the view already exists for a url, before creating a new one. A unique init function is always required as it ends up being the constructor function.
If you want to subclass a modalview, you need to do something different due to the way kendo works:
var MyModalView = kendo.mobile.ui.ModalView.extend({
html: '<div data-role=modalview style="width:90%;display:none;">Foobar</div>',
init: function() {
kendo.mobile.ui.ModalView.prototype.init.apply(this, arguments);
}
});
function makeModalView() {
$(document.body).append($.parseHTML(MyModalView.prototype.html));
var roles = $.extend({}, kendo.mobile.ui.roles);
roles.modalview = MyModalView;
var modalView = kendo.initWidget($(element), {}, roles);
modalView.open();
return modalView;
}
I'm having some trouble integrating the appear-plugin for jQuery with my Knockout viewmodel. My first attempt only involved a single appear-item and a single global action but now I have multiple number of items to appear on and multiple action to trigger. I was thinking of making a binding-handler for this purpuse which could be used like this:
<div class="loadMoreTrigger" data-bind="appear: loadMore" data-appear-top-offset="200" />
My skills with custom bindinghandlers is very limited, and maybee there is existing code for this already? Haven't found any...
Would this be possible at all? And the right way of doing it? (I'm trying to lazy load items)
This is what I have now:
ko.bindingHandlers.appear = {
init: function (element, valueAccessor) {
$(element).appear();
var action = valueAccessor();
$(element).on("appear", action);
}
};
But it doesn't work...
Using MVC2 I have created a form using the Ajax helper in a view. The form posts to a controller which binds to a model object. A PartialViewResult is returned by the controller and the HTML gets updated into a div. So far, so good.
I now need to submit the same form and return the results in a generated file for the user to download. Obviously I don't want the file contents going into my div.
Is there an elegant way to handle this situation without having to hack it to bits? I'm fairly new to MVC / AJAX and it's still a point of confusion for me.
You may not use ajax call to download files. Following links may help you to do what you are trying to do
JQuery Ajax call for PDF file download
http://forums.asp.net/t/1683990.aspx/1
OK, so I couldn't find any simple solutions anywhere so I came up with my own. I remove the Ajax event handlers from the form when I want the download, put them back when I want the Ajax. I'm guessing there's a more elegant way to do this, as this feels like a 'clever trick'. I'm open to better suggestions but so far this is my preferred method.
Reference ToggleAjax.js on my page:
var ToggleAjax = function ($, form) {
var onclick = form.onclick,
onsubmit = form.onsubmit;
$('input[class*="ajax-enabled"]').click(function () {
form.onclick = onclick;
form.onsubmit = onsubmit;
});
$('input[class*="ajax-disabled"]').click(function () {
form.onclick = function () { };
form.onsubmit = function () { };
});
};
Then I call ToggleAjax on my page and pass in the form:
$(function () {
ToggleAjax($, $('form')[0]);
});
And of course I add the class ajax-enabled or ajax-disabled to the input controls.
I have a Partial View that renders WebGrid. My controller looks like
public ActionResult Index()
{
return View();
}
public ActionResult GetUserList(int? page, string sort, string sortdir)
{
var model = UserModel.getList(page,sort,sortdir);
return PartialView("_UserList",model);
}
Index.cshtml :
....
#Html.Action("GetUserList")
The problem is that every time I click on grid navigation or sort links it calls Index method. How can I make Webgrid to execute a different action (GetUserList in this case)? I'm sure I can prepend GetUserList to all links in grid using jquery, but I believe it should be a better way.
It's also possible that what I'm doing is completely wrong, so thanks for your suggestions.
After lot of monkeying around and digging (and even fiddling with Reflector with WebGrid's source code), I came to the conclusion that with WebGrid, you cannot control/change the Header link action.
To create the header link URL, the path is taken from HttpContext.Request.Path, so there is no way to customize it to point to a different route.
One very ugly hack would be to tap into to jQuery Ajax's events (since the header link uses jQuery.load to sort) and overwrite the URL:
Album Id
Better solution would be to use:
Telerik Grid which lets you specify custom routes and also offers much more flexibility in rendering your layout
or MvcContrib Grid (not sure if this lets you modify header links but definitely offers more flexibility than WebGrid)
#MrChief had the idea above about the ugly hack...I put that together. Here is the main code that I used to do this. It does, indeed, hijack the ajax call before it is put on the wire. The key is to modify the URL that is getting sent because the grid will grab that URL from HttpContext.Request.Path. and plug it into the onclick for the anchor element.
I put this into my main common.js and will simply attach a function to capture the ajaxSend event which happens just before the data is sent.
// Used to hijack the sending of all AJAX calls. Before it sends the call to the server, it checks to see if the
// active element (the element that prompted the call) is marked with a given class. If so, then it will perform
// the given operation.
$(document).ajaxSend(function (event, jqXHR, ajaxOptions) {
var activeElement = document.activeElement;
if ($(activeElement).attr('redosorturl') != null) {
// If this is a sort anchor link from a grid that needs to have the sort link redone, do it here.
// the code is in the eipGrip.js file.
if ($(activeElement).attr('redosorturl').toString() == 'redoSortURL') {
var newURL = RedoGridSortURL(activeElement, ajaxOptions.url.toString());
ajaxOptions.url = newURL.toString();
}
}
return false;
});
When rendering the page, I have marked the tag in column header that contains the incorrect URL with a class named "redosorturl', so I know when I hijack the ajax call, the operation has to be done on this element. I then call a custom function that gives me the correct URL, then the ajaxOptions.url is then rewritten with that new URL.
I have to pass the activeElement to that rewrite function so I can traverse up the DOM to get the grid information, where I have put data like the controller and action method that is used along with and IDs and other info that I use for the URL. Likewise, I pass in the current url string because the grid will inject a token at the end of the url that I parse off and put on the new url.
Your conclusion isn't right. You just need to wrap your webgrid in a Get form:
using (Html.BeginForm("GetUserList", "ThingaMaBob", System.Web.Mvc.FormMethod.Get))
{
var grid = new WebGrid(
...
));
Html.Hidden(grid.SortFieldName, grid.SortColumn);
Html.Hidden(grid.SortDirectionFieldName, grid.SortDirection == SortDirection.Ascending ? "ASC" : "DESC");
}
The hiddens are so that the sort dir and sort field end up in parseable form in the querystring. You end up with urls like localhost/ThingaMaBob/GetUserList?someotherfields=whatever=&sort=city&sortdir=ASC
If you remove [HttpPost] attribute and let the route come to the same function. you'll find the Request["page"] value in your method. this will allow you to put a check on Request["Page"] value.
I am making an Ajax call and adding content to a form inside a MVC2 app.
I need to update the Client Validation Metadata with the validation for my new content.
<script type="text/javascript">
//<![CDATA[
if (!window.mvcClientValidationMetadata) { window.mvcClientValidationMetadata = []; }
window.mvcClientValidationMetadata.push({"Fields":[{"
...
</script>
Is there a way to generate this metadata for a partial view ?
Thanks in advance.
I was banging my head against a wall for a few days on this too and was going to go down the route of removing the form tag, but have just got it working in a slightly less hacky way if you are still interested. My scenario was similar in that I have a form with a collection of elements to validate initially, but users can dynamically add new rows via ajax.
I'll break it down so hopefully it'll be easier to see what is going on. Looking at the MVC source code, the form and validation works roughly as so:
Html.BeginForm() outputs the opening form tag then creates and returns a new instance of MvcForm, which doesn't outwardly do much except make the scope of the form easier to manage for you.
It does however create a new FormContext and stores this within ViewContext.FormContext. It is this FormContext that tracks the client validation.
The last thing Html.BeginForm() does is set the FormId property of the new FormContext, using the id of the form tag. This is required so the client script can match up forms and validation rules.
Html.EndForm() disposes the MvcForm. This Dispose method outputs the form closing tag and then calls ViewContext.OutputClientValidation() which is resposible for outputting the javascript. Lastly it removes the current FormContext and sets it back to the parent FormContext or null if there isn't one.
So to not output the form tag we somehow need to take some of the FormContext management out of the MvcForm constructor/destructor.
So within my Partial View I did the following:
At the top I check if the ViewContext.FormContext has a value. If so we we are in the initial load so no need to mess around. If not, it is an ajax call, so I enable client validation, create a new MvcForm directly (not with BeginForm) - this causes a FormContext to be created - and set the FormContext.FormId to the same as my parent page
At the end of the view, I check if I have a form instance and if so, call ViewContext.OutputClientValidation() and reset the ViewContext.FormContext to null. I do not Dispose() the MvcForm as this would output the closing tag and MvcForm does not contain disposable objects.
The skeleton of the view looks as so:
<%
MvcForm dummyForm = null;
if (this.ViewContext.FormContext == null)
{
Html.EnableClientValidation();
dummyForm = new MvcForm(this.ViewContext);
this.ViewContext.FormContext.FormId = "mainform";
}
%>
// standard partial view markup goes here
<%
if (dummyForm != null)
{
this.ViewContext.OutputClientValidation();
this.ViewContext.FormContext = null;
}
%>
You could quite easily wrap this up into an extension method
Phil
Finally got it to work.
The answer is simple: don't waist time with MicrosoftMvcValidation.js. It is generated with Script# which makes it difficult to extend.
Switch to xVal and jQuery Validation.
It doesn't need a form to generate the client validation metadata.
Also in order to load validation for a AJAX request all you have to do is to call the following after you have the new Html:
lForm.find("#placeholder").empty();
lForm.valid();
lForm.find("#placeholder").html(responseHtml);
That does it. First you remove the old content. Than re-run validation to get rid of potentially obsolete validation errors. Than add the new content. Works like a cham.
Also jQuery Validation makes it really easy to enable or disable validation for a certain field (conditional validation).
I have the same problem and resolve using the Future files, and in MicrosoftMvcJQueryValidation.js I change the and of file, this:
$(document).ready(function () {
var allFormOptions = window.mvcClientValidationMetadata;
if (allFormOptions) {
while (allFormOptions.length > 0) {
var thisFormOptions = allFormOptions.pop();
__MVC_EnableClientValidation(thisFormOptions);
}
}
});
for:
function chargeValidation() {
var allFormOptions = window.mvcClientValidationMetadata;
if (allFormOptions) {
while (allFormOptions.length > 0) {
var thisFormOptions = allFormOptions.pop();
__MVC_EnableClientValidation(thisFormOptions);
}
}
}
and in content after close form using I call the 'chargeValidation()', this resolve for me the problem I have using $.get(action) containing a form validation.
I hope to help you!
Finally found it.
After content is loaded in dynamically you will need to register the new form.
Since I am using Facebox, I added it to the facebox code, however you can add it wherever you need, or in a callback if your modal or whatever you are loading into has an afterLoaded event.
I wrapped them in a try/catch just in case i ever use facebox without the validation stuff.
Just run these two lines AFTER your content has been loaded:
try {
Sys.Application.remove_load(arguments.callee);
Sys.Mvc.FormContext._Application_Load();
} catch (err) {/* MVC Clientside framework is likely not loaded*/ }
I made some progress but I am not quite happy.
Problem #1: The client validation metadata is not generated unless you have a Html.BeginForm() in your partial. Which in my case is false because I do not update the entire form, I update portions of it.
Solution for Problem #1: Add a form in the partial view, let MVC generate the client validation MetaData and remove the form tags with a action filter. Let's call this Hack #1.
public class RemoveFormFilterAttribute : ActionFilterAttribute
{
private static readonly MethodInfo SwitchWriterMethod = typeof(HttpResponse).GetMethod("SwitchWriter", BindingFlags.Instance | BindingFlags.NonPublic);
private TextWriter _OriginalWriter;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
_OriginalWriter = (TextWriter)SwitchWriterMethod.Invoke(HttpContext.Current.Response, new object[] {new HtmlTextWriter(new StringWriter())});
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (_OriginalWriter != null)
{
HtmlTextWriter lTextWriter =(HtmlTextWriter) SwitchWriterMethod.Invoke(HttpContext.Current.Response, new object[] {_OriginalWriter});
string lOriginalHTML = lTextWriter.InnerWriter.ToString();
string lNewHTML = RemoveFormTags(lOriginalHTML);
filterContext.HttpContext.Response.Write(lNewHTML);
}
}
Problem #2: The initial client validation metadata for the page is gone by the time I have the metaData for the new content.
Solution for Problem #2: Store the initial metadata (hard copy) and update it with the new fieds, than call the methods you mentioned to let MVC know new stuff arrived. Let's call this Hack #2.
<script type="text/javascript">
var pageMvcClientValidationMetadata;
$(document).ready(function() {
$("input[name='PaymentTypeName']").change(PaymentTypeChanged);
//create a back-up of the ValidationMetadata
pageMvcClientValidationMetadata = JSON.parse(JSON.stringify(window.mvcClientValidationMetadata));
});
function PaymentTypeChanged() {
var selectedPaymentType = $("input[name='PaymentTypeName']:checked").val();
$.ajax(
{
url: 'PersonalData/GetPaymentTypeHtml?&paymentType=' + selectedPaymentType,
type: "GET",
cache: false,
success: GetPaymentTypeHtml_Success
});
}
function GetPaymentTypeHtml_Success(result) {
$('#divPaymentTypeDetails').html(result);
UpdateValidationMetaData();
}
function UpdateValidationMetaData() {
//update the ValidationMetadata
for (i = 0; i < window.mvcClientValidationMetadata[0].Fields.length; i++) {
pageMvcClientValidationMetadata[0].Fields.push(window.mvcClientValidationMetadata[0].Fields[i]);
}
//restore the ValidationMetadata
window.mvcClientValidationMetadata = JSON.parse(JSON.stringify(pageMvcClientValidationMetadata));
//Notify the Validation Framework that new Metadata exists
Sys.Application.remove_load(arguments.callee);
Sys.Mvc.FormContext._Application_Load();
}
Now. Any improvements would be appreciated.
Hack #1: How can I generate the client validation metadata without having an actual form ?
HAck #2: How can I appent to the page validation metadata ?