Update view model inside .done() callback of $.post method call, is possible? - javascript

I'm starting to use KnockoutJS and of course doubts comes to me. So I have this jQuery code:
$.post("someUrl", $form.serialize(), 'json').done(function (data, textStatus, jqXHR) {
$.cookie("stepOneSave", true);
// here I should update the attributes from response in data
}).fail(function () {
return false;
}).always();
And then in my view (it's a Twig template since this is part of Symfony2 project) I have this:
<fieldset class="rpni-border">
<legend class="rpni-border">Datos de Solicitud</legend>
<div class="spacer10"></div>
<div class="col-md-3">
<p><strong>Number:</strong></p>
<p><strong data-bind="text: currIDSolicitud"></strong></p>
</div>
<div class="col-md-3">
<p>Request Type: </p>
<p><strong data-bind="text: currTipoSolicitud"></strong></p>
</div>
<div class="col-md-3">
<p>Office: </p>
<p><strong data-bind="text: currOficinaRegional"></strong></p>
</div>
<div class="col-md-3">
<p>Status: </p>
<p><strong data-bind="text: currEstadoSolicitud"></strong></p>
</div>
</fieldset>
Using the info provided how do I update the attributes and bind them to the view? This is my first time with Knockout and I start reading here but it's not clear to me this, can any give me some help?

Ok, let say you have this data returned from server using ajax request
data: [
{
id: 1,
name: 'John',
age: 17
},
{
id: 2,
name: 'Doe',
age: 20
}
];
inside a viewmodel function you need to define one property as a observableArray to handle above data:
var viewModel = function()
{
var self = this;
self.friends = ko.observableArray([]);
};
now from code above you already have empty friends observableArray, next you need to write your ajax request to fetch data from server then insert it to observableArray:
var ViewModel = function()
{
var self = this;
self.friends = ko.observableArray([]);
};
$(document).ready(function()
{
var viewmodel = new ViewModel();
ko.applyBindings(new viewmodel());
$.ajax({
url: '/example',
// more ajax options...
success: function(response)
{
viewmodel.friends(response.data);
}
});
});
and here is the view will look alike:
<div data-bind="foreach: $root.friends">
<div class="row">
<div class="col-md-6" data-bind="text: name"></div>
<div class="col-md-6" data-bind="text: age"></div>
</div>
</div>
So, if you want to add class attribute, do something like this:
<div data-bind="foreach: $root.friends">
<div class="row" data-bind="css: age < 18 ? 'kid' : 'adult'">
<div class="col-md-6" data-bind="text: name"></div>
<div class="col-md-6" data-bind="text: age"></div>
</div>
</div>
Or maybe you want to add href attribute, do something like this:
<div data-bind="foreach: $root.friends">
<div class="row" data-bind="css: age < 18 ? 'kid' : 'adult'">
<div class="col-md-12" data-bind="text: name"></div>
<a data-bind="text: age, attr: { href: age < 18 ? 'http://kid.com' : 'http://adult.com' }"></a>
</div>
</div>
Read more about attr binding here
p/s: This is not a good approach, but it should be working !

Related

KnockoutJS filtering an array

I have an observable array that contains a list of object that I want to filter through based on a user input. If the user searches a word that appears in the array in two different places then the filter function should return the title of both objects and delete or hide all other objects in the array that did not match the input from the user. I must use knockout js to preform this feature which is still new to me. Currently my filter function checks to see if the user input is included in a title of one of the objects within the array and if it is not then it removes the object. However, this not providing me what I need as it does not accurately filter the list.
My ViewMode
var viewModel = function() {
var self = this;
self.filter = ko.observable('');
self.locationList = ko.observableArray(model);
self.filterList = function(){
return ko.utils.arrayFilter(self.locationList(), function(location) {
if(location.title == self.filter()){
return location.title
}
else if( location.title.includes(self.filter()) ){
return location.title
}
else{
return self.locationList.remove(location)
}
});
};
}
The View
<section class="col-lg-2 sidenav">
<div class="row">
<div class="col-lg-12">
<div class="input-group">
<input data-bind="textInput: filter"
type="text" class="form-control" placeholder="Filter Places"
aria-describedby="basic-addon2" id="test">
<button data-bind="click: filterList id="basic-addon2">
<i class="glyphicon glyphicon-filter"></i>
Filter
</button>
</div>
</div>
<div class="col-lg-12">
<hr>
<div data-bind="foreach: locationList">
<p data-bind="text: $data.title"></p>
</div>
</div>
</div>
</section>
The answer to the question can be found here answered by Viraj Bhosale
ViewModel
var viewModel = function() {
var self = this;
self.filter = ko.observable('');
self.locationList = ko.observableArray(model);
self.filterList = ko.computed(function(){
return self.locationList().filter(
function(location){
return (self.filter().length == 0 || location.title.toLowerCase().includes(self.filter().toLowerCase()));
}
);
});
}
View
<main class="container-fluid">
<div class="row">
<section class="col-lg-2 sidenav">
<div class="row">
<div class="col-lg-12">
<div class="input-group">
<input data-bind="textInput: filter, valueUpdate: 'keyup'"
type="text" class="form-control" placeholder="Filter Places"
aria-describedby="basic-addon2" id="test">
</div>
</div>
<div class="col-lg-12">
<hr>
<div data-bind="foreach: filterList">
<p data-bind="text: $data.title"></p>
</div>
</div>
</div>
</section>
<section class="col-lg-10" id="map"></section>
</div>

Input not binding correctly with Vue.js

I'm still relatively new to Vue.js and am having an issue binding one of my inputs to my viewmodel.
Here is my JavaScript:
var viewModel = new Vue({
el: "#InventoryContainer",
data: {
upcCode: "",
component: {
Name: ""
}
},
methods: {
upcEntered: function (e) {
if (this.upcCode.length > 0){
$.ajax({
url: "/Component/GetByUpc",
type: "GET",
data: {
upc: this.upcCode
}
}).done(function (response) {
if (response.exists) {
$("#ComponentInformation").toggleClass("hidden");
this.component = response.component;
} else {
alert("No component found.");
}
});
}
}
}
});
Here is my HTML:
<div class="form-horizontal row">
<div class="col-sm-12">
<div class="form-group">
<label class="control-label col-md-4">UPC Code</label>
<div class="col-md-8">
<input id="ComponentUPC" class="form-control" placeholder="Scan or enter UPC Code" v-on:blur="upcEntered" v-model="upcCode" />
</div>
</div>
<div id="ComponentInformation" class="hidden">
<input type="text" class="form-control" readonly v-model="component.Name" />
</div>
</div>
</div>
Now the issue is that even when I enter a valid UPC code and I assign the component to my ViewModel, the input that is bound to component.Name does not update with the component name. And when I enter into the console viewModel.component.Name I can see that it returns "".
But if I put an alert in my ajax.done function after I've assigned the component and it looks like this alert(this.component.Name) it alerts the name of the component.
Any ideas of where I'm going wrong here?
You cannot use that line
this.component = response.component;
because of the this-variable.
You should put the line
var self = this
before your ajax call and use self.component instead of this.component
in order for vue to work you need to define the parent container with id InventoryContainer
<div id="InventoryContainer" class="form-horizontal row">
<div class="col-sm-12">
<div class="form-group">
....
here is the updated code: https://jsfiddle.net/hdqdmscv/
here is the updated fiddle based on your comment
https://jsfiddle.net/hdqdmscv/2/
(replace this with name of vue variable in ajax)

Knockoutjs computed not updated from observableArray

I want a computed to be updated when an observable array is updated. The array is populated with questions and answer (Yes or No). When the user change the answer of a question, I want some region to be visible or not.
So the computed is5B should be true if one of the question is answered "oui" and this should make the sections visible.
The is5B computed is only calculated at initialization and is not fired when the array is updated (it is updated, I checked with a breakpoint)
Here's the view model:
var section5Model = ko.validatedObservable({
Questions5A: ko.observableArray(GetQuestions('5A')),
Questions5B: ko.observableArray(),
Questions5C: ko.observableArray(),
ContactAQ: ko.observable(),
Date: ko.observable(''),
Heure: ko.observable(''),
CategorisePar: ko.observable(''),
DateCategorise: ko.observable(''),
RepOuiNon: [{ label: 'Oui', value: 0 }, { label: 'Non', value: 1 }]
});
section5Model().is5B = ko.computed(function () {
this.Questions5A().forEach(function (item) {
if (item.reponse == 'Oui') {
return true;
}
});
}, section5Model());
Here's the markup:
<div id="sectionContainer">
<div id='S5FormBlock1' class="formSection5">
<div id="QSection5A" data-bind="foreach: Questions5A">
<div class='mockTable'>
<div class="column200 centerLabel"><span data-bind="text: Texte"></span></div>
<div class="grayRoundBorder padR10" data-bind="foreach: $parent.RepOuiNon">
<input type="radio" data-bind="value: label, attr : {name: $parent.ID}, checked: $parent.reponse" /><span data-bind="text: label"></span>
</div>
</div>
<p />
</div>
<div data-bind="visible: is5B">Il s'agit d'une plainte qualité</div>
<div id="QSection5B" data-bind="visible: is5B,foreach: Questions5B">
<div class='mockTable'>
<div class="column200 centerLabel"><span data-bind="text: Texte"></span></div>
<div class="grayRoundBorder padR10" data-bind="foreach: $parent.RepOuiNon">
<input type="radio" data-bind="value: label, attr : {name: $parent.ID}, checked: $parent.reponse" /><span data-bind="text: label"></span>
</div>
</div>
<p />
</div>
<div data-bind="visible: is5C">Il s'agit d'une plainte d'insatisfaction</div>
<div id="QSection5C" data-bind="visible: is5C,foreach: Questions5C">
<div class='mockTable'>
<div class="column200 centerLabel"><span data-bind="text: Texte"></span></div>
<div class="grayRoundBorder padR10" data-bind="foreach: $parent.RepOuiNon">
<input type="radio" data-bind="value: label, attr : {name: $parent.ID}, checked: $parent.reponse" /><span data-bind="text: label"></span>
</div>
</div>
<p />
</div>
</div>
</div>
The problem that you have is that item.response is not observable. So if it change KnockoutJS doesn't know about that. To fix this you have to change that to observable
section5Model().is5B = ko.computed(function () {
this.Questions5A().forEach(function (item) {
if (item.reponse() == 'Oui') {
return true;
}
});
}, section5Model());
Computed are functions that are dependent on one or more other observables, and will automatically update whenever any of these dependencies change. so in your case no observable inside your computed function. so make at-least one variable in side computed as observable. in your case please make the item.response as observable. for that you need to return observable variable on GetQuestions('5A')
Please do Questions5A observableArray as like
var section5Model = ko.validatedObservable({
Questions5A: ko.observableArray([
{reponse : ko.observable('reponse 1 ') },
{reponse : ko.observable('reponse 2') },
/* other objects */
]),
/* other code */

KeyPress event not working as expected in Ember

What I want to do is, make an ajax call whenever user stops entering something in the 'projectname' field & check it against database & show an kind of error message saying, "It exists". But the keypress event is not working as expected, first of all it omits the first letter entered & as a result word is not sent to database completely.
Here's my Controller:
App.ProjectController = Ember.ArrayController.extend({
actions : {
createNew : function() {
data = {
projectname : this.get('projectname'),
projectdesc : this.get('projectdesc'),
projectbudget : this.get('projectbudget'),
};
console.log(JSON.stringify(data));
//console.log(id);
$.ajax({
type : "POST",
url : "http://ankur.local/users/createNewProject",
data : data,
dataType : "json",
success : function(data) {
console.log('success');
//alert('');
}
});
alertify.success("Project Created");
this.set('projectname', "");
this.set('projectdesc', "");
this.set('projectbudget', "")
return false;
},
checkName: function(){
data = {
projectname : this.get('projectname'),
};
var checkedName = $.ajax({
type : "POST",
url : "http://ankur.local/users/checkProjectName",
data : data,
dataType : "json",
success : function(data) {
console.log('Yes it');
}
});
console.log(data);
console.log(checkedName);
}
}
});
and Here's the HTML,
<script type="text/x-handlebars" id="project">
<div class="row" style="padding-left: 30px">
<div class="span12" id="form-container">
<div class="well well-small">
<p style="text-align: center">
You can create a new Project by filling this simple form.
</p>
<p style="text-align: center"> Project Name should be minimum 10 characters & maximum 50 characters.
Project Description
10 to 300 characters.
</p>
</div>
<div class="row" id="test">
<div class="offset3 span8">
<form class="form-horizontal" id="projectform">
<div class="control-group">
<label class="control-label" for="projectname">Project Name: </label>
<div class="controls">
{{view Ember.TextField valueBinding='projectname' style="max-width: 100%" onEvent="keyUp" action=checkName}}
</div>
</div>
<div class="control-group">
<label class="control-label" for="projectdesc">Project Description:</label>
<div class="controls">
{{view Ember.TextArea valueBinding='projectdesc' style="max-width: 100%"}}
</div>
</div>
<div class="control-group">
<label class="control-label" for="projectbudget">Project Budget($)</label>
<div class="controls">
{{view Ember.TextField valueBinding='projectbudget' id="budget" style="max-width: 100%"}}
</div>
</div>
<div class="control-group">
<div class="controls">
<button class="btn"
{{action 'createNew' }}>Add Project</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
What improvements I can make to achieve the desired result?
Key press is working as expected, key press happens before the textbox value has changed.
It looks like key up isn't supported in the manner that you want tho. Fortunately it's really easy to override:
App.KeyUpTextField = Em.TextField.extend({
keyUp:function(event){
this.sendAction('upKeyAction', event);
}
});
{{view App.KeyUpTextField value=projectname upKeyAction='checkName'}}
BTW I'd do debounce or something like that in your keyUp function, it seems like it'd get a bit chatty to send the request on every keyup event.

What is the right approach to reload the model for a non-dynamic route with ember.js?

I'm having a simple array of models which I display in a list (path: /things). The models get loaded from a REST-API.
In a nested route I have the functionality to add a new model. (path: /things/add). The new model is persisted over a REST-API.
After adding the new model, I do a transitionTo('things') to get back to the list.
Following the ember documentation, by using "transitionTo" neither the model hook nor the setupController-Hook are called for non dynamic routes.
What is the right approach to refresh the model on a non-dynamic route, when using transitionTo? Or: what is the best way to reload a model on a non-dynamic route without using transitionTo?
app.js
// App Init
App = Ember.Application.create();
// Routes
App.Router.map(function() {
this.resource('things', function() {
this.route('add');
})
});
App.IndexRoute = Ember.Route.extend({
redirect : function() {
this.transitionTo('things');
}
});
App.ThingsRoute = Ember.Route.extend({
model : function(param) {
return App.Thing.findAll();
},
});
App.ThingsAddRoute = Em.Route.extend({
setupController : function(controller) {
controller.set('content', App.Thing.create());
}
});
// Models
App.Thing = Ember.Object.extend({
name : null,
description : null
});
App.Thing.reopenClass({
findAll : function() {
var result;
$.ajax({
url : 'http://path/app_dev.php/api/things',
dataType : 'json',
async : false,
success : function(data) {
result = data.things;
}
});
return result;
},
save : function(content) {
console.log(content);
$.ajax({
type : 'post',
url : 'http://path/app_dev.php/api/things',
data : {
name : content.name,
description : content.description
},
async : false
});
}
});
// Controller
App.ThingsAddController = Em.ObjectController.extend({
add : function() {
App.Thing.save(this.content);
this.transitionToRoute('things');
},
cancelAdding : function() {
this.transitionToRoute('things');
}
});
index.html
<script type="text/x-handlebars">
<div class="container">
<div class="row">
<div class="span12">
<h1>List of things</h1>
</div>
{{outlet}}
</div>
</div>
</script>
<script type="text/x-handlebars" data-template-name="things/add">
<div class="span12">
<form class="form-horizontal">
<fieldset>
<div id="legend">
<legend class="">Add new thing</legend>
</div>
<!-- Name -->
<div class="control-group">
<label class="control-label" for="name">Name</label>
<div class="controls">
{{view Ember.TextField id="name" placeholder="Enter Name" valueBinding="name"}}
</div>
</div>
<!-- Description -->
<div class="control-group">
<label class="control-label" for="description">Description</label>
<div class="controls">
{{view Ember.TextArea id="description" placeholder="Enter description" valueBinding="description"}}
</div>
</div>
<!-- Submit -->
<div class="control-group">
<div class="controls">
<button class="btn btn-success" {{action add}}>Save</button>
<button class="btn" {{action cancelAdding}}>Cancel</button>
</div>
</div>
</fieldset>
</form>
</div>
</script>
<script type="text/x-handlebars" data-template-name="things">
<div class="span12">
<div class="btn-toolbar">
<div class="btn-group">
{{#linkTo things.add}}<i class="icon-plus"></i> add new thing{{/linkTo}}
</div>
</div>
</div>
{{outlet}}
<div class="span12">
<table cellpadding="0" cellspacing="0" border="0" class="table table-striped ">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{{#each item in model}}
<tr>
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.description}}</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
</script>
So if you were using ember-data, a side effect of saving the record would be that the results of findAll() get updated. You can accomplish the same by either manually updating the array or triggering a refresh when a new record is added. In either case, suggest doing that from ThingsAddController's add fx. For example:
App.ThingsAddController = Em.ObjectController.extend({
needs: [things],
add : function() {
newThing = App.Thing.save(this.content);
this.get('controllers.things').pushObject(newThing);
this.transitionToRoute('things');
},
});

Categories