why doesn't the handlebars template reflect change in ember property - javascript

i'm having some trouble with the handlebars template not updating every time the contents of this component's 'subAllocations' change.
it does change the first time the event is triggered ie adding or removing subAllocations, but not after that, even though i can see in the Ember debugger addon for FireFox developer tools that the computed value has changed for 'hasSubAllocations'
App.AllocationAllocationRowComponent = App.CollapsableComponent.extend({
layoutName: 'components/allocation/allocation/row',
tagName: "tr",
hasSubAllocations: Em.computed.notEmpty("allocation.subAllocations"),
updateAllocation: function (data) {
var newAllo = data.allocation;
this.set("allocation.subAllocations", Em.copy(newAllo.subAllocations));
this.sendAction("onRefreshAirSpaces", data.updatedAirSpaces);
},
actions: {
addSubAllocation: function(){
var alloc = this.get("allocation");
var self = this;
Em.$.ajax({
type: 'POST',
url: '/allocation/addSubAllocations',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify(alloc),
}).then(function (data) {
self.updateAllocation(data);
});
},
subAllocationRemoved: function (subAllocation) {
var self = this;
Ember.$.ajax({
type: 'POST',
url: '/allocation/RemoveSubAllocation/',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify(subAllocation),
}).then(function (data) {
self.updateAllocation(data);
});
},
},
});
from handlebars template
`<span {{bind-attr class="hasSubAllocations:hidden:"}}>content here</span>`
am i missing something?
we are not using ember data, so if you could avoid suggesting that as a solution that would be great

Related

vue 2.3 AJAX data binding not updating

After a successful ajax call the data rendered on the page is not updating. It is remaining empty / null.
It seems that I am missing how to connect the data returned as a front end variable and the dynamically / live rendered html elements.
Here is the relevant code snippets for context. Is it clear from this what is missing / incorrect?
Javascript
page = new Vue({
el: "#container",
data: {
option_id: null,
option_name: null
},
created:function() {
$.ajax({
type: 'POST',
contentType: 'application/json',
dataType: 'json',
url: 'ajax_methods/get_option',
success: function (ajax_data) {
self = this;
self.option_id = ajax_data.option_id;
self.option_name = ajax_data.option_name;
},
error: function (e) {
console.log(e)
}
})
}
})
HTML
<script type="text/javascript" src="https://unpkg.com/vue#2.3.3"></script>
<div id="container">
<p>{{ option_name }}</p>
<button v-on:click="send_option()"
type="button"
id="left_button"
name="left_button"
v-bind:value="option_id">
</div>
Checking AJAX success
When entering the following in the console, non null values come back as expected:
self.option_id
self.option_name
You need to capture this outside the callback.
created: function(){
const self = this;
$.ajax({
type: 'POST',
contentType: 'application/json',
dataType: 'json',
url: 'ajax_methods/get_option',
success: function (ajax_data) {
self.option_id = ajax_data.option_id;
self.option_name = ajax_data.option_name;
},
error: function (e) {
console.log(e)
}
})
}
first, if that is the exact code, then self I don't think is initialized. Use var self = this or let self = this
But mainly, you need to define self outside of the ajax call. In javascript the this keyword refers to the calling object. directly inside of the created() function, it's the Vue instance. However, this will NOT refer to the Vue instance once inside the ajax callback.
Understand JavaScript’s “this” With Clarity, and Master It
created:function() {
var self = this
$.ajax({
type: 'POST',
contentType: 'application/json',
dataType: 'json',
url: 'ajax_methods/get_option',
success: function (ajax_data) {
self.option_id = ajax_data.option_id;
self.option_name = ajax_data.option_name;
},
error: function (e) {
console.log(e)
}
})
}

How I do initial JavaScript Ajax initialization?

I'm pulling information with ajax. I want it my document.ready function(ajax) starting first because knockout file starting first and my "var initialData" value going null. How my Ajax start first ?
Here is my F12 Source
My script:
<script type="text/javascript">
var initialData;
function functionViewModel() {
$(document).ready(function () {
$.ajax({
type: "POST",
url: "KnockoutGrid2.aspx/GonderUrunler",
data: "{}",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (msg) {
console.log(msg.d);
initialData = msg.d;
}
});
});
var fn = {
friends: ko.observableArray(initialData)
};
fn.removeUser = function (item) {
fn.friends.remove(item);
};
return fn;
};
ko.applyBindings(functionViewModel());
</script>
Update 2
The answer of #user3297291 is better than mine, because is Knockout who handles all the state of this form. Please, don't do the applybindings in the answer of the ajax request.
The user need to know that the data isn't loaded yet, and this can be handled with knockout.
Original answer
Perhaps if you move the initialization of Knockout inside the success function:
<script type="text/javascript">
$(document).ready(function () {
$.ajax({
type: "POST",
url: "KnockoutGrid2.aspx/GonderUrunler",
data: "{}",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (msg) {
console.log(msg.d);
initialData = msg.d;
// All initialization inside the 'success' function
function functionViewModel(initialData) {
var fn = {
friends: ko.observableArray(initialData)
};
fn.removeUser = function (item) {
fn.friends.remove(item);
};
return fn;
};
ko.applyBindings(functionViewModel(initialData));
}
});
});
</script>
You could show a div with the message: "loading data...".
And when success run, hide this div.
Update 1
After your comment, I don't know why you need the return fn. I propose this solution:
<script type="text/javascript">
// Updating 'functionViewModel()' to add 'self'.
// Move functionViewModel()' outside ajax response
function functionViewModel(initialData) {
var self = this;
self.friends = ko.observableArray(initialData);
self.removeUser = function (item) {
self.friends.remove(item);
};
};
$(document).ready(function () {
$.ajax({
type: "POST",
url: "KnockoutGrid2.aspx/GonderUrunler",
data: "{}",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (msg) {
console.log(msg.d);
initialData = msg.d;
// All initialization inside the 'success' function
ko.applyBindings(functionViewModel(initialData));
}
});
});
</script>
Here I'm using self ( see Managing ‘this’ ) and don't return fn, because Knockout handles its state.
Use async:false in your code
$.ajax({
type: "POST",
url: "KnockoutGrid2.aspx/GonderUrunler",
data: "{}",
async : false,
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (msg) {
console.log(msg.d);
initialData = msg.d;
}
});
You do not want to wait with applyBindings until your ajax response is handled... Your document will look ugly if you let knockout wait with applying bindings and your users will have nothing to look at.
What you should do:
Apply bindings as soon as $(document).ready triggers
Make sure your viewmodels use observable properties that allow you to easily inject data later on
Make sure you define some sort of loading state to show your users the data is being downloaded
I.e.:
function functionViewModel() {
var friends = ko.observableArray([]);
var loading = ko.observable(true);
var removeUser = function(user) {
friends.remove(user);
}
// Get the data and write it to an observable property once done
$.ajax({
type: "POST",
url: "KnockoutGrid2.aspx/GonderUrunler",
data: "{}",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(msg) {
friends(JSON.parse(msg.d));
loading(false);
}
});
return {
friends: friends,
loading: loading,
removeUser: removeUser
};
};
$(document).ready(function() {
ko.applyBindings(functionViewModel());
});

Using Nunjucks in the browser, how do I only request a template once to iterate on using different values?

I am rendering a Nunjucks template multiple times in the browser:
$.ajax({
url: '/products',
contentType: 'application/json',
type: 'GET',
success: function(response) {
var $tableBody = $('#products-table-body');
response.products.forEach(function(product) {
$tableBody.append(
nunjucks.render( '_item-row.html', { id: product.id, name: product.name } )
);
});
}
});
That is creating an HTTP request for each iteration which just receives the same template each time, which seems unnecessary if I just want to use the same template to insert different values for each iteration.
How can I only request the template once and then use that single template request to fill in the values in each iteration?
Thanks to the helpful Nunjucks documentation, I figured it out.
var env = new nunjucks.Environment(new nunjucks.WebLoader('../views'));
$.ajax({
url: '/products',
contentType: 'application/json',
type: 'GET',
success: function(response) {
var $tableBody = $('#products-table-body');
var template = env.getTemplate('_item-row.html');
response.products.forEach(function(product) {
$tableBody.append(
nunjucks.render( template, { id: product.id, name: product.name } )
);
});
}
});

Use an objects method as the call back to an AJAX call

I have a javascript Object called Company, which may be instantiated multiple times.
I want each Company to handle its own communication with the server, so that its ajax calls will be contained within the class.
So far I have:
Company = new Object();
Company.SendData = function(){
$.ajax({
type: "POST",
url: "<some url>",
data: <some data>,
dataType: "json",
success: this.ReceiveData,
error: ErrorFunc
});
}
Company.ReceiveData = function(data){
alert("Received some data");
}
The Ajax works fine, and the server correctly receives the data and returns the response, but the call back function does not trigger.
My guess would be because it no longer has any frame of reference to what "this" is.
Is there a way that I can keep the functions attached to an individual instance of the class?
There are several different choices for how to solve this issue. The crux of the problem is that you need to get the value of this set properly when your callback is called as it won't be correct by default.
In modern browsers, you can use the broadly useful .bind():
Company = new Object();
Company.SendData = function(){
$.ajax({
type: "POST",
url: "<some url>",
data: <some data>,
dataType: "json",
success: this.ReceiveData.bind(this),
error: ErrorFunc
});
}
Company.ReceiveData = function(data){
alert("Received some data");
}
Or, you can instruct jQuery to do the work for you with the context option to $.ajax():
Company = new Object();
Company.SendData = function(){
$.ajax({
type: "POST",
url: "<some url>",
data: <some data>,
dataType: "json",
context: this,
success: this.ReceiveData,
error: ErrorFunc
});
}
Company.ReceiveData = function(data){
alert("Received some data");
}
Or you can create your own stub function using a saved value of this (this is my least favorite option):
Company = new Object();
Company.SendData = function(){
var self = this;
$.ajax({
type: "POST",
url: "<some url>",
data: <some data>,
dataType: "json",
success: function(result) {return self.ReceiveData(result);},
error: ErrorFunc
});
}
Company.ReceiveData = function(data){
alert("Received some data");
}
Try this:
Company.SendData = function(){
var self = this;
$.ajax({
type: "POST",
url: "<some url>",
data: <some data>,
dataType: "json",
success: function(res) {self.ReceiveData(res);},
error: ErrorFunc
});
}
This SO post might help to understand.
Update
Fixed the example by encapsulating the function.

Ember beginner: Router and Outlets with ajax

I'm just new to Ember and sure this is an easy question but cannot figure out the best way to achieve what I want.
The scenario is the following:
App.Router = Ember.Router.extend({
root: Ember.Route.extend({
home: Ember.Route.extend({
route: '/',
connectOutlets: function(router) {
router.get('applicationController').connectOutlet('home');
router.get('homeController').connectOutlet('menu', 'menu', App.Channels.find());
router.get('homeController').connectOutlet('video_overview', 'videoOverview', App.Featured.find());
}
})
})
App.Featured = Ember.Object.extend();
App.Featured.reopenClass({
find: function() {
setRequestUrl('featured');
establishSecureConnection();
$.ajaxQueue({
type: "GET",
url: connect.url,
data: "",
dataType: 'jsonp',
jsonp: false,
jsonpCallback: "fjsonp",
cache: true,
context: this,
success: function(response){
this.findOne(response[0].mediaId); // get the first featured object and retrieve details
//
console.log('Featured Video:');
console.log(response);
}
});
},
findOne: function(item) {
var featuredVideo = App.Featured.create({});
setRequestUrl('media/'+item);
establishSecureConnection();
$.ajaxQueue({
type: "GET",
url: connect.url,
data: "",
dataType: 'jsonp',
jsonp: false,
jsonpCallback: "fjsonp",
cache: true,
context: featuredVideo,
success: function(response){
this.setProperties(response);
//
console.log('Featured Video details:');
console.log(response);
}
});
return featuredVideo;
}
So when executing the app and Router connects the outlet video_overview with content retrieved from App.Featured.find() I can see the response on the browser logs but values never arrives to the template.
I guess is related with the 'double' request I'm doing on App.Featured (first find() and then findOne()) so when I'm returning values with return featuredVideo is not being notified.
Thanks a lot for your time.
The App.Featured.find does seem to return anything. I think you have to return the result of the findOne, don't you ?
Hy everyone again,
Finally I was messing around and take sly7_7 advices and got working!
This is the way I achieved but if anyone could explain in detail the behaviour of Ember will be appreciated.
App.Featured = Ember.Object.extend();
App.Featured.reopenClass({
find: function(singleItem) {
if (singleItem){
console.log('Returning the single item...');
console.log(singleItem);
//
featuredContent.setProperties(singleItem);
} else {
setRequestUrl('featured');
establishSecureConnection();
$.ajaxQueue({
type: "GET",
url: connect.url,
data: "",
dataType: 'jsonp',
jsonp: false,
jsonpCallback: "fjsonp",
cache: true,
context: featuredContent,
success: function(response){
this.setProperties(App.Featured.findOne(response[0].mediaId)); // this.setProperties({'category':'hey arrives here success'});
//
console.log('Featured Video:');
console.log(response);
}
});
}
return featuredContent;
},
findOne: function(item) {
setRequestUrl('media/'+item);
establishSecureConnection();
$.ajaxQueue({
type: "GET",
url: connect.url,
data: "",
dataType: 'jsonp',
jsonp: false,
jsonpCallback: "fjsonp",
cache: true,
context: this,
success: function(response){
console.log('Featured Video content:');
console.log(response);
//
this.find(response);
}
});
}
});
var featuredContent = App.Featured.create({});
Then the template recieves the changes made on featuredContent object.

Categories

Resources