I am trying to perform a client side form validation using an episerver xform
the compiled html looks like this: http://codepen.io/anon/pen/ojGGJw
Any guidance on how to achieve that?
I am thinking about using .validate library but I will have an issue if we add a new control to the form through epi.
Also i tried to use an AJAX call with something like this:
$.ajax({
url: "/industry/XFormPost?XFormId=0643b992-56c6-40a5-91eb-c557443630e0&failedAction=Failed&successAction=Success&contentId=36",
data: $(this).serialize(),
type: "POST",
success: function () {
alert('Hello this is a valid form');
}
});
it fires the event but does not save my form into the DB. even though all the fields i passed are valid
Regrettably XForms in its current state is notoriously cumbersome to work with when it comes to customization. For custom rendering and/or validation we often implement our own rendering completely.
There are numerous articles about how to achieve it, but it can be time-consuming. :/
Client-side validation only can of course be achieved by attaching event handlers to the default generated HTML, but that's usually not enough. Although this can combined with server-side events, it is difficult to properly customize how XForms work for the end-user without custom rendering.
Here's what i ended up doing , to include a full client side validation for my form using .validate() js library.
I am sure there is other ways to achieve that, so here's my version:
EpiServer has a class field that you can use for all of your controls (shamefully there is no hidden field by the way, but that's a different story).
So i added a css class named 'requiredField' and added extra classes for different kind of validations , such as 'emailField' for email validation and 'halfWidthField' for CSS layout purposes for my form.
In order to .Validate to work , i need to add necessary attributes. to achieve that , I created a small script to add those attributes based on the class names I already assigned.
my JS code looks something like this:
$(".contact-form").find(".requiredfield").each(function () {
$(this).prop('required', true);
});
$(".contact-form").find(".emailfield").each(function () {
$(this).attr('type', 'email');
});
lastly: for the ajax call to happen , I changed the view and made an Ajax call instead of a regular post call.
#using (Ajax.BeginForm("", "",
new AjaxOptions
{
HttpMethod = "POST",
Url = Model.ActionUri
}
))
{
Html.RenderXForm(Model.Form);
}
It works as expected and I can customize the validation as needed.
The final form looks something like this:
Related
I have a Blazor Server app that is a multi-step "wizard" form. After each relevant step the state is adjusted, and new HTML is shown/hidden via conditional statements (simple example below).
if (IsStepSignature)
{
<div>Signature HTML here</div>
}
This all works just fine. My problem comes when I need to invoke some JS logic on the dynamically generated HTML from above (e.g. click handlers to hook up external JS libraries). When I handle the "Next" click, I can invoke the JS just fine...but it is not yet seeing the dynamic HTML from above. Is there a way to invoke some JS, and control it so that it doesn't execute until after the page is redrawn from the C# code execution?
5/18/2020 Update from Nik P
Can leverage some flags and use OnAfterRenderAsync to control this ordering. This does work, but it does require some extra hops to/from the server. Below is what I see when implementing this. This may just be the nature of Blazor Server, as one of the pros/cons is some known added chattiness. In total these requests were 2.5K, so extremely small.
CLIENT --> Browser Dispatch Event (NEXT CLICK)
Render <-- SERVER
CLIENT --> Render Complete
Invoke JS <-- SERVER
CLIENT --> Invoke Complete
The issue you are having has to do with the element not existing in the client side HTML at all until after the re-render takes place. So one way to do this is to set a boolean flag in your C# code that says there is code that needs to be run after the render, and populate support fields that you will need for your JS Interop. Whenever you need to run the JS interop, set your flag to true, set your support fields to the values you need for the JS interop call, and then do something that kicks a DOM diff calculation. (Even StateHasChanged should be enough, but adding your items conditionally as you mentioned will also do it) Then, override your OnAfterRenderAsync method as follows:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if(firstRender)
{
// any first render code
}
if(yourFlag)
{
YourJSInteropMethod(supportField1, supportfield2);
yourflag = false;
}
}
The simplicity in this approach is that the DOM update will always happen ahead of the OnAfterRenderAsync call, so your HTML will be populated with what you are targeting with JS.
I'm trying to understand the usage and limitations of server side rendering with vuejs when using aspnet core.
I used this starter kit for aspnet core and vuejs to setup a simple vue site, which is running based on the code here: https://github.com/selaromdotnet/aspnet-vue-ssr-test/tree/master
I then modified the project to update the aspnet-prerendering and added vue-server-renderer, compiling a hodgepodge of sources to cobble together this update: https://github.com/selaromdotnet/aspnet-vue-ssr-test/tree/ssr
If I run this project, the site appears to load fine, and if I turn off the javascript in the browser, I can see that it does appear that the server-side rendering executed and populated the html result:
however, because JavaScript is disabled, the content isn't moved into the dom as it looks like it is trying to...
My understanding of server-side rendering is that it would populate the html entirely and serve a completed page to the user, so that even if JS was disabled, they'd at least be able to see the page (specifically for SEO purposes). Am I incorrect?
Now I believe modern search engines will execute simple scripts like this to get the content, but I still don't want a blank page rendered if js is disabled...
Is this a limitation of server-side rendering, or perhaps specifically ssr with vue and/or aspnet core?
or am I just missing a step somewhere?
Edit: more information
I looked at the source code for what I believe is the method that prerenders the section here: https://github.com/aspnet/JavaScriptServices/blob/dev/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs
The line
output.Content.SetHtmlContent(result.Html);
has a null value for result.Html. However, when I manually edit this value to put a test value, it also doesn't render to the output html, and the app div tag is still empty...
If I'm doing something wrong to populate the result.Html value with the expected output, that's one thing, and I would appreciate some help in doing that, especially since the output html appears to be found, since it's in the script that immediately follows...
However, even if I were to populate it, it appears it's being skipped, as evidenced by me manually changing the value. is this a bug in the code or am I doing somethigng wrong, or perhaps both?
As you correctly noticed, for your project, result.Html inside the tag helper is null. So that line cannot be the location where the output is being generated. Since the HTML output from your prerendering script also does not include a script tag, it is clear that something has to generate that. The only other line that could possible do this is the following from the PrerenderTagHelper:
output.PostElement.SetHtmlContent($"<script>{globalsScript}</script>");
That would fit the observed output, so we should figure out where the globalsScript comes from.
If you look at the PrerenderTagHelper implementation, you can see that it will call Prerenderer.RenderToString which returns a RenderToStringResult. This result object is deserialized from JSON after calling your Node script.
So there are two properties of interest here: Html, and Globals. The former is responsible for containing the HTML output that finally gets rendered inside the tag helper. The latter is a JSON object containing additional global variables that should be set for the client side. These are what will be rendered inside that script tag.
If you look at the rendered HTML from your project, you can see that there are two globals: window.html and window.__INITIAL_STATE__. So these two are set somewhere in your code, although html shouldn’t be a global.
The culprit is the renderOnServer.js file:
vue_renderer.renderToString(context, (err, _html) => {
if (err) { reject(err.message) }
resolve({
globals: {
html: _html,
__INITIAL_STATE__: context.state
}
})
})
As you can see, this will resolve the result containing just a globals object with both html and __INITIAL_STATE__ properties. That’s what gets rendered inside of the script tag.
But what you want to do instead is have html not as part of globals but on the layer above, so that it gets deserialized into the RenderToStringResult.Html property:
resolve({
html: _html,
globals: {
__INITIAL_STATE__: context.state
}
})
If you do it like that, your project will properly perform server-side rendering, without requiring JavaScript for the initial view.
Is there a way to pass messages from the popover to global page using the dispatchMethod() instead of calling the global page's functions using safari.extension.globalPage.contentWindow.
Currently i use a dynamically created iframe inside the web page to simulate a popover. This communicates with the global page using Safari's message passing. So i want to support this as well as the new popover in the later Safari versions.
Message passing between the popover and the global page will help me reuse the code.
Thanks
It doesn't look like there is a way for a popover to dispatch a message to the global page, or vice versa, using dispatchMessage. However, you could use the HTML5 standard window.postMessage to do the equivalent, although then you could not reuse your existing code without some modification.
To use postMessage from the popover, you would do something like this:
var gw = safari.extension.globalPage.contentWindow;
gw.postMessage("hello there", window.location.origin);
And to receive it in the global page:
window.addEventListener('message', function (msg) {
if (msg.origin == window.location.origin) {
msg.source.postMessage("got your message", window.location.origin);
doSomethingWithMessageData(msg.data);
}
}, false);
This messaging protocol is similar enough to the extension-specific one that you could probably reuse most of your existing code, with just a thin abstraction layer added.
I am using Behat/Mink with Selenium for acceptance testing. I need to determine if my web page is making a badly formed call to the server via Ajax. The problem is, the server will attempt to "correct" badly-formed code and return valid data nonetheless.
Is there a way to "intercept" and validate ajax calls made from my website?
Right now my FeatureContext class looks like:
public function performAnAction()
{
$this->enterInField('test', 'field');
$this->hitOKButton();
$this->assertResponseContains('success');
}
I would like to do something like:
public function performAnAction()
{
$this->enterInField('test', 'field');
$this->hitOKButton();
$ajax = $this->getAllAjaxCalls();
foreach ($ajax as $call) {
// perform some validation
}
$this->assertResponseContains('success');
}
Here are two great resources for doing checks on ajax calls
The behat mink way
http://docs.behat.org/cookbook/behat_and_mink.html#defining-our-own-featurecontext
This is a neat solution using _before and _after overrides to getting deeper inside the base functionality and is very interesting but reading through it will help you get a better understanding of what the framework is really doing under the covers
http://blog.scur.pl/2012/06/ajax-callback-support-behat-mink/
I think you could simply use the built in wait function with the js callback to get what you want in your custom step def by putting your response into a jquery data[] object on any html element and verify your expected output that way.
$this->getSession()->wait(5000,
"$('.someCssClassSelectorToElementWithResponseStuff').length > 0"
);
If your element were a js object that would work
Or if it comes back as a jquery object you could use .size() instead of length
just make sure your injected js evaluates to true of false to get your pass/fail
I have a page containing 2 controllers: one which manages a list of so-called 'apps', and another that is to place the new Angular template into the innerHTML of its Div element.
<div ng-controller="appList"></div>
<div ng-controller="appPane"> Dynamic content should be loaded here! </div>
I have tried using the standard {{expression}} bindings, but they do not work with html, I have also tried the ng-bind-html-unsafe directive (Binding the innerhtml to that of the App request's return) but controllers are not executed within this new code.
The problem seems to be that by using a Binding, Angular is not re-parsing the contents of the html in question to use it as an angular app. Any ideas of how to get it to parse dynamic content?
It appears that the $compile service, when fed the elements you wish to recompile along with your current scope, does what I was looking for.
Example from my source:
var appPane = $('#AppPane');//JQuery request for the app pane element.
appPane.html(data);//The dynamically loaded data
$compile(appPane.contents())($scope);//Tells Angular to recompile the contents of the app pane.
This should help anyone experiencing my problem, I hope.
Look at $routes and ngView in angularjs.
Here's a very basic example:
http://jsfiddle.net/pXpja/3/
Take a look at the uiRouter module (from the AngularUI project). It adds the concept of states, which are pretty similar to routes, but with other goodies, such as the ability to nest them. For example:
$stateProvider
.state('myState', {
url: '/mystate',
templateUrl: '...',
controller: 'MyCtrl'
})
.state('myState.substate', {
url: '/{substate}',
templateUrl: '...',
controller: 'MySubCtrl'
});
MySubCtrl will be activated whenever you go to /mystate/something and you can use that "something" as a parameter (see $stateParams). You can nest states to any amount of levels.