Unobtrusive JavaScript with jQuery is available in MVC3.But how can I use the unobtrusive Javascript ajax with mootools?
yeah, this is trivial to do. have a look at the recently released http://mootools.net/blog/2011/12/20/mootools-behavior/, I think it supports it.
I have used this approach in my Modal.BootStrap (view source on github, link's there) as well whereby it uses data attributes to fetch data from an ajax resource, it's not quite the same but it certainly is a start.
I just spent 10 mins making this and it's a good start:
http://jsfiddle.net/dimitar/zYLtQ/
(function() {
var ajaxify = this.ajaxify = new Class({
Implements: [Options,Events],
options: {
mask: "form[data-ajax=true]",
props: {
ajaxLoading: "data-ajax-loading",
ajaxMode: "data-ajax-mode",
ajaxUpdate: "data-ajax-update",
ajaxSuccessEvent: "data-event-success"
}
},
initialize: function(options) {
this.setOptions(options);
this.elements = document.getElements(this.options.mask);
this.attachEvents();
},
attachEvents: function() {
this.elements.each(function(form) {
var props = {};
Object.each(this.options.props, function(value, key) {
props[key] = form.get(value) || "";
});
form.store("props", props);
form.addEvent("submit", this.handleSubmit.bind(this));
}, this);
},
handleSubmit: function(e) {
e && e.stop && e.stop();
var form = e.target, props = form.retrieve("props"), self = this;
var updateTarget = document.getElement(props.ajaxUpdate);
new Request({
url: form.get("action"),
data: form,
onRequest: function() {
if (props.ajaxLoading) {
var loading = document.getElement(props.ajaxLoading);
if (loading && updateTarget) {
updateTarget.set("html", loading.get("html"));
}
}
},
onSuccess: function() {
if (!updateTarget)
return;
if(props.ajaxMode != 'append') {
updateTarget.set("html", this.response.text);
}
else {
updateTarget.adopt(new Element("div", { html: this.response.text }));
}
if (props.ajaxSuccessEvent)
self.fireEvent(props.ajaxSuccessEvent, this.response);
}
}).send();
}
});
})();
new ajaxify({
onContactFormSuccess: function(responseObj) {
console.log(responseObj.text);
alert("we are done.");
}
});
works with a DOM of:
<form action="/echo/html/" data-ajax="true" data-ajax-loading="#loading" data-ajax-mode="replace" data-ajax-update="#update" data-event-success="contactFormSuccess" method="post">
<input name="delay" value="4" type="hidden" />
<input name="html" value="Thanks for your submission, this is the jsfiddle testing response" type="hidden" />
<input name="name" placeholder="your name" />
<button>submit</button>
</form>
<div id="update">The update will go here.</div>
<div id="loading">loading...</div>
you should be able to build on that. on refactor i'd move the request events into their own methods and add some more proofing etc but it's fine. i don't know all mvc does but one thing that is missing is form validation events. i also added a custom event that is fired when done so your ajaxifier instance can do something particular to that form (see data-event-success="contactFormSuccess")
also, it can use default request options if not implicitly specified, even what request object to create - Request, Request.HTML, Request.JSON etc. Events like onRequest, spinners etc are also feasible... I think you just need to work your way through the options that mvc provides and build them to get started.
Confirm data-ajax-confirm
HttpMethod data-ajax-method
InsertionMode data-ajax-mode *
LoadingElementDuration data-ajax-loading-duration **
LoadingElementId data-ajax-loading
OnBegin data-ajax-begin
OnComplete data-ajax-complete
OnFailure data-ajax-failure
OnSuccess data-ajax-success
UpdateTargetId data-ajax-update
Url data-ajax-url
Related
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 4 years ago.
I have a form in an AngularJS controller, I would like to automatically fill the form with data collected with a XHTMLRequest.
Form has simply inputs with data-ng-model
page.html
<div id="PersonalAppID" data-ng-controller="PersonalAppCtrl as ctrl">
<form action="/" method="post" novalidate>
<div class="input-group">
<div class="form-group col-md-5">
<label>First name: </label>
<input name="fname" type="text" data-ng-model="ctrl.fname" placeholder="first name">
</div>
</div>
When controller init itself, it creates a request, downloads the data and put it into variables ctrl.variable
page.js
angular.controller('PersonalAppCtrl',function() { var ctrl = this;
ctrl.$onInit= function(){
var req=XMLHttpRequest();
req.onreadystatechange = function(){
if (req.status == 200&req.readyState==4){
var ret = convertJSON(req.responseText);
ctrl.fname = ret.FirstName;
return (true);
}
}
req.open();
req.send();
}
My problem is that input are filled only if user touch an input and then touch outside it.
I'd like to have form filled as soon as the page is loaded.
I also tried to use ng-load and ng-init but the behaviour is pretty the same in this case.
Any ideas?
Update your code like below:
angular.controller('PersonalAppCtrl',function() { var ctrl = this;
ctrl.$onInit= function(){
var req=XMLHttpRequest();
req.onreadystatechange = function(){
if (req.status == 200&req.readyState==4){
var ret = convertJSON(req.responseText);
$scope.$apply(function () {
ctrl.fname = ret.FirstName;
});
return (true);
}
}
req.open();
req.send();
}
You are making an XMLHTTPRequest which is a non angular code. Angular is not aware when the response is received so you need to make that angular aware by calling $scope.$apply()
Example:
function Ctrl($scope) {
$scope.message = "Waiting 2000ms for update";
setTimeout(function () {
$scope.message = "Timeout called!";
// AngularJS unaware of update to $scope
}, 2000);
}
But, if we wrap the code for that turn in $scope.$apply(), the change will be noticed, and the page is updated.
function Ctrl($scope) {
$scope.message = "Waiting 2000ms for update";
setTimeout(function () {
$scope.$apply(function () {
$scope.message = "Timeout called!";
});
}, 2000);
}
A very detailed explaination on this is given here - http://jimhoskins.com/2012/12/17/angularjs-and-apply.html
Its better to use Angular $http for all HTTP requests. So you dont have to bother about using $scope.$apply()
We are using Restlet for our API on the client side, everything server side is written in Laravel 5. We are having trouble in one portion. We have a couple endpoints that require you to upload a file. In Angular, I have only gotten that to work using the following method thus far:
var fd = new FormData();
fd.append('image', $scope.file);
$http.post(apiURL + "/upload", fd, {
transformRequest: angular.identity,
headers: {
'Content-Type': undefined
}
}).then(function(response) {
//yay it worked
}, function(response) {
//try again
});
I don't know why that is the only way I have been successful, but I would like to change it over to the Restlet endpoint I have created. For most of my calls, it is as simple as this:
$rootScope.restlet.getCompanyAll().then(function(response) {
$scope.companies = response.data;
});
and
var config = {
params: {
start: "2016-01-01",
end: "2016-01-31"
}
};
var id = 1;
$rootScope.restlet.getCompanyStatsCompany_id(id, config).then(function(response) {
$scope.companies = response.data;
});
Pretty simple, but when I try to implement the post of an image, it doesn't recognize it, and leaves the image out completely. Here is the code I am attempting to use, it works with the non-Restlet way, but doesn't work with Restlet.
var config = {
params: {
name: $scope.newCompany.name,
rep_id: $scope.newCompany.rep_id,
image: $scope.image_input
}
};
var id = 1;
$rootScope.restlet.postCompanyCreate(config).then(function(response) {
$scope.companies = response.data;
});
Has anyone gotten something like this to work? And if so, how does it work? Thanks! :)
EDIT 1:
Here is the HTML of the page I have set up. It does have a file input, but for some reason it Restlet doesn't like the file. I have tried a plain file input, along with an input with a directive on it. The current version I am using is an image, that when clicked is linked to an file input that is hidden. I am also using a custom directive on it currently.
HTML:
<div class="modal-body">
<form ng-submit="createCompany()">
<!-- OTHER FORM STUFF GOES HERE -->
<div class="col-sm-12">
<img src="" id="imagePreview" onClick="$('#imageUpload').trigger('click');" style="max-width: 100%;" />
<input type="file" style="display: none;" id="imageUpload" file="file" />
</div>
<!-- FORM SUBMIT AND RESET BUTTONS HERE -->
</form>
</div>
Custom Directive:
app.directive('file', function() {
return {
scope: {
file: '='
},
link: function(scope, el, attrs) {
el.bind('change', function(event) {
var file = event.target.files[0];
scope.file = file ? file : undefined;
scope.$apply();
});
}
};
});
You didn't post your HTML but I assume that you are using an input with a type of file to specify the file to upload and binding to it with ng-model or some native Angular binding mechanism. I have no idea why, but Angular doesn't support this. There are 2 common ways to implement this.
The 1st is to use a directive that works around this issue. There's one here: https://github.com/danialfarid/ng-file-upload.
The 2nd, and at least where I would start, is you can simply use document.getElementById to retrieve the file name from the input in your controller. IOW, $scope.file = document.getElementById("myFileThingy").value.
In order to use the view-models written at server-side at client-side, I am using knockoutjs mapping plugin. But I didn't wanted to write any js code in my view. For this reason, as such I don't have access to Model, I am now getting the model via ajax call with no values in the properties in the code below. This code is written in a external js file:
var Person = function () {
var self = this;
self.SetupKOBindings = function (flagkey) {
var source = null;
if (flagkey.val() === "True") {
this.GetViewModelFromServer = function () {
$.ajax(
{
url: "/Person/LoadData",
type: "GET",
async: false
}).
success(function (data) {
if (data !== null && data !== undefined) {
source = data;
flagkey.val("false");
}
});
}();
return ko.mapping.fromJS(source);
}
return null;
};
self.ViewModel = function (flagkey) {
this.model = self.SetupKOBindings(flagkey);
this.model.FullName = ko.computed(function () {
return this.model.FirstName() + " " + this.model.LastName();
});
this.model.ShouldShowFullName = ko.computed(function () {
return (this.model.FirstName() === null || this.model.LastName() === null) ? false : true;
});
this.model.Save = function () {
if ($(form).valid()) {
$.ajax(
{
url: "/Person/Index",
type: "POST",
contentType: "application/json",
data: ko.mapping.toJSON(model),
async: true
}).
success(function (data) {
ko.mapping.fromJS(model, data);
});
}
}
return this.model;
};
self.ApplyKOBindings = function (vm) {
ko.applyBindings(vm);
};
return this;
};
$(function () {
var PersonPage = Person();
var viewModel = PersonPage.ViewModel($('#GetViewModelFromServer'));
if (viewModel !== null) PersonPage.ApplyKOBindings(viewModel);
});
The problem I faced with this approach was everytime I did a post action, when the page loads, the same ajax request was fired to get the viewmodels from server and the same code runs which then binds the form with vm's properties which are empty.
To avoid this, I am using a hidden control's value as a flag to whether convert the server-side viewmodel to js object or not. So, On the first call, I set the value of flag to false.
Now to get the validation messages mentioned using data annotations, I have made the form as partial view and it uses ajax call to replace the content a div with id as Sample. The client-side validation using unobtrusive-validation and server-side validations works very well and also the knockout bindings.
Controller Code:
[HttpPost]
public ActionResult Index(PersonViewModel viewModel)
{
if (viewModel.Age < 10)
{
ModelState.AddModelError("Age", "bal bla bla");
}
if (ModelState.IsValid)
{
return PartialView("_Form", viewModel);
}
else
{
viewModel.GetViewModelFromServer = false;
return PartialView("_Form", viewModel);
}
}
Index View:
<div id="sample">
#Html.Partial("_Form")
</div>
Partial View:
#model MVCKOPractise.Models.PersonViewModel
<fieldset>
<legend>PeopleViewModel</legend>
#using (Ajax.BeginForm("Index", "Person", new AjaxOptions() { InsertionMode = InsertionMode.Replace, HttpMethod = "POST", UpdateTargetId = "sample" }))
{
<div>
First Name:
#Html.TextBoxFor(model => model.FirstName, new { data_bind = "value: FirstName,valueUpdate:['afterkeydown','propertychange','input']" })<br />
#Html.ValidationMessageFor(model => model.FirstName)
</div>
<div>
Last Name:
#Html.TextBoxFor(model => model.LastName, new { data_bind = "value: LastName,valueUpdate:['afterkeydown','propertychange','input']" })<br />
#Html.ValidationMessageFor(model => model.LastName)
</div>
<div data-bind="visible: ShouldShowFullName">
Full Name:
<div data-bind="text: FullName"></div>
</div>
<div>
Address:
#Html.TextBoxFor(model => model.Address, new { data_bind = "value: Address" })<br />
#Html.ValidationMessageFor(model => model.Address)
</div>
<div>
Age:
#Html.TextBoxFor(model => model.Age, new { data_bind = "value: Age" })<br />
#Html.ValidationMessage("Age")
</div>
#Html.HiddenFor(model => model.GetViewModelFromServer)
<input type="submit" value="Save" />
}
</fieldset>
Although the above little sample works for me but I would like to know is this a good way to proceed to
Not to write js code in view and use ajax call to create a copy of viewmodel at client, use data- attributes to access any server-side stuff in javascript like I answered this.
Using the same validation written using data-annotations at server-side for the views. I know there is a knockout-validation and jquery-validation plugin available. But as mvc data-annotations convert validations to data- attributes which is then read by jquery.
I think I understand what you're trying to achieve and have done something similar in the past.
First off, I'm not sure where your model data is coming from - obviously server side but is this always a blank viewmodel? Personally I have implemented something along these lines to retrieve server side data:
In the controller:
[HttpGet]
public ActionResult MyAction(int id)
{
Return View();
}
[HttpGet]
public JsonResult MyActionData(int id)
{
return Json(Something.GetMyData(id) ?? new MyViewModel(), AllowGet);
}
That allows me to retrieve the data for the page as I perhaps normally would with a standard MVC request but allow it to be request in JSON. This way I'd get either the data for whatever I'm querying or I get a blank view model.
You can then add a standard post action with the viewmodel as the parameter without worrying about anything special for the ajax side of things.
Secondly, to handle server side only validation (I'm assuming here that you've got client side stuff working fine but there are more complex rules on the server side which require a post back) I actually started returning the validation data in the ModelState back to the client.
Not sure what the best transit for that would be in your scenario (dedicated post method for validation or a view model wrapper with validation data perhaps) but either way the validation object itself provides the field name that has failed validation and the relevant message to be displayed.
Using that data you could create a custom knockout mapping handler that takes the validation data and calculates it back to validation properties on your model (bearing in mind the field names map directly to the object structure in question) or you could have a jQuery handler that loops through the results looking for the fields with the relevant names and adds the validation classes on the fly.
I'm having trouble submitting a form with knockout js.
I receive the error "Pass a function that returns the value of the ko.computed."
The code is as follows:
(function(records,$,undefined){
records.models={
student:function(data){
var self=this;
self.id=ko.observable(data.id);
self.fname=ko.observable(data.fname);
self.lname=ko.observable(data.lname);
if(data.initial==='undefined'||data.initial===null){
self.initial=ko.observable("");
}else{
self.initial=ko.observable(data.initial);
}
self.fullname=ko.computed(function(){
return self.fname()+" "+" "+self.initial()+" "+self.lname();
});
},
students_model:function(){
var self=this;
self.selectedStudent=ko.observable();
self.students=ko.observableArray([]);
getStudents();
self.save=function(){
var form=$("#student-form");
$.ajax({
type:"POST",
url:"/Student/create",
data:ko.toJSON(form[0]), //This line here is the exact point of failue
success:function(response){
records.general.handleSuccess(response);
if(response.status){
getStudents();
}
}
});
return false;
};
function getStudents(){
$.getJSON("/Student/data",function(result){
var mapped=$.map(result,function(item){
return new records.models.student(item);});
self.students(mapped);
});
}
}
};
return records;
}(window.records=window.records||{},jQuery));
HTML
#using (Ajax.BeginForm("Create", "Student",
new AjaxOptions
{
HttpMethod = "Post"
},
new { #class = "student-form", name = "student-form", id = "student-form" }))
{
<input type="text" data-bind="value:$root.fname" id="student.fname" name="student.fname" />
<input type="text" data-bind="value:$root.lname" id="student.lname" name="student.lname"/>
<input type="text" data-bind="value:$root.initial" id="student.initial" name="student.initial"/>
<input type="text" data-bind="value:$root.dob" id="dob" name="dob" />
<button data-bind="click:save">Save</button>
}
<script type="text/javascript">
ko.applyBindings(new records.models.students_model());
</script>
What am I doing wrong here? I'm aware of this question here:Pass a function that returns the value of the ko.computed
But it seems like that individual had a different problem. My code fails when starting in the save method. Specifically the line:
data:ko.toJSON(form[0])
ko.toJSON is expecting you to pass it your viewModel, but you're passing it an element from the DOM, thus the error.
You need to pass a javascript object (a viewmodel or part of your viewmodel) to ko.toJSON. For example, if you wanted to send up the array of students, you could do this:
ko.toJSON(self.students);
I see that your form has some inputs bound to $root.fname, $root.lname, $root.initial, and $root.dob, but I'm not sure where those exist in your viewmodel, so I can't tell you exactly what to pass. But I can give you an example of one way could could solve this.
If you have a viewmodel that looks like this:
var data = ...;
var vm = {
newStudent : {
fname : ko.observable(data.fname),
lname: ko.observable(data.lname),
initial: ko.observable(data.initial ?? ""),
dob: ko.observable(data.dob)
}
}
and then you bound this to your dom by calling
ko.applyBindings(vm);
You could then call ko.toJSON like this:
...
data:ko.toJSON(vm.newStudent),
...
I have been playing with angularjs for couple of days and like it so far. I am trying to build a chrome extension which attaches a small widget below every gmail message when the user is on gmail.com. So far so good. As part of authentication code, I handle 401 error in this way. Whenever there is a 401 error, I use $location.path( "/login" ) to redirect the user to the login screen/template. This changes browser address bar which seems to be the default behavior. So, if the current address was https://mail.google.com/mail/u/0/, it becomes https://mail.google.com/mail/u/0/#/login. But mine is not standalone app, its more like widget that attaches to a div when on gmail.com site. My app should not mess with the browser address bar. I am now starting to think if I can really use angularjs for my app as I am going against the default behavior. Should I use angularjs at all?
I also posted it here
https://groups.google.com/forum/?fromgroups#!topic/angular/TrT54r_IYmg
You can emit/broadcast events on rootScope and subscribe to them in your login directive.
Here is little clue http://www.espeo.pl/2012/02/26/authentication-in-angularjs-application
it uses interceptors to catch 401
myapp.config(function($httpProvider) {
var interceptor = ['$rootScope','$q', function(scope, $q) {
function success(response) {
return response;
}
function error(response) {
var status = response.status;
if (status == 401) {
var deferred = $q.defer();
var req = {
config: response.config,
deferred: deferred
}
scope.requests401.push(req);
scope.$broadcast('event:loginRequired');
return deferred.promise;
}
// otherwise
return $q.reject(response);
}
return function(promise) {
return promise.then(success, error);
}
}];
$httpProvider.responseInterceptors.push(interceptor);
});
And simplest directive could be this
myapp.directive("loginForm",function($http){
return function(scope,element,attrs){
element.hide();
scope.$root.$on('event:loginRequired', function(event) {
element.show();
});
scope.login=function(){
// You can set controller for this directive, but I skiped that part for sake of simplicity
var payload = $.param({username: scope.username, password: scope.password});
var config = {
headers: {'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8'}
}
$http.post('/some/login/url', payload, config).success(function(data) {
if (data === 'AUTHENTICATION_SUCCESS') {
element.hide();
}else{
alert("username or password was wrong, please try again");
elements.find("form").reset(); // reset form, or you coud reset just password field
}
});
};
};
});
Now, directive in action
<div login-form>
<form ng-submit="login()">
<label for="username">Username</label>
<input type="text" id="username" ng-model="username"/>
<br />
<label for="password">Password</label>
<input type="password" id="password" ng-model="password" />
<hr/>
<input type="submit" value="Login" />
</form>
</div>
Please note code above is not tested, probably there is some misspell or something. But let me know if you have trouble implementing this, I will try to take some time and effort to make it work.