Deal with the result of postJSON - javascript

I'm trying to implement something with jQuery and Vue.js:
And here is my script part:
<script>
function initVM(result) {
// alert(result.num)
var vm2 = new Vue({
el: '#vm2',
data: {
// ③bind one item of the result as example
rrr: result.num
}
});
$('#vm2').show();
}
$(function() {
var vm = new Vue({
el: '#vm',
data: {
content: ''
},
methods: {
submit: function(event) {
event.preventDefault();
var
$form = $('#vm'),
content = this.content.trim();
// ①post textarea content to backend
$form.postJSON('/api/parse', {
content: content
}, function(err, result) {
if (err) {
$form.showFormError(err);
}
else {
// ②receive a result dictionary
$('#vm').hide();
initVM(result);
}
});
}
}
});
});
</script>
Here is my html part:
<html>
<form id="vm", v-on="submit: submit">
<textarea v-model="content" name="content"></textarea>
<button type="submit">Have a try!</button>
</form>
<div id="vm2" style="diplay:none;">
<!-- ④show the result-->
The result:
{{ rrr }}
</div>
</html>
Here is the definition of postJSON
<script>
// ...
postJSON: function (url, data, callback) {
if (arguments.length===2) {
callback = data;
data = {};
}
return this.each(function () {
var $form = $(this);
$form.showFormError();
$form.showFormLoading(true);
_httpJSON('POST', url, data, function (err, r) {
if (err) {
$form.showFormError(err);
$form.showFormLoading(false);
}
callback && callback(err, r);
});
});
// ...
// _httpJSON
function _httpJSON(method, url, data, callback) {
var opt = {
type: method,
dataType: 'json'
};
if (method==='GET') {
opt.url = url + '?' + data;
}
if (method==='POST') {
opt.url = url;
opt.data = JSON.stringify(data || {});
opt.contentType = 'application/json';
}
$.ajax(opt).done(function (r) {
if (r && r.error) {
return callback(r);
}
return callback(null, r);
}).fail(function (jqXHR, textStatus) {
return callback({'error': 'http_bad_response', 'data': '' + jqXHR.status, 'message': 'something wrong! (HTTP ' + jqXHR.status + ')'});
});
}
</script>
The process can be described as:
Post the content to backend
Receive the result and hide the form
Create a new Vue with the result
Show the result in a div which is binding with the new Vue instance
Actually, I do receive the result successfully, which is proved by the alert(result.num) statement, but nothing find in vm2's div except The result:
Where is the problem? Or please be free to teach me a simpler approach if there is, because I don't think what I am using is a good one.

Here's questioner.
Finally I found where is the problem.
The problem lays in Mustache: {{ }}
I use jinja2, a template engine for Python and Vue.js, a MVVM frontend framework. Both of them use {{ }} as delimiters.
So if anyone got trapped in the same situation with me, which I don't think there will be, please:
app.jinja_env.variable_start_string = '{{ '
app.jinja_env.variable_end_string = ' }}' # change jinjia2 config
OR
Vue.config.delimiters = ['${', '}'] # change vue config

Related

How to sort object by key value in javascript?

I'm making a code to fetch content from contentful using AJAX. I've success retrieve data and display it, but something is not quite what I want. Because the content that I get is not in the same order as the contentful cms, so I add another field called sequence. So in my code I added a sort() and Object.keys() function before forEach(), but there is no error and data not appears ,does anyone know why data not appears?
If you want to try debugging, you can look at This Codepen.
function renderContentBySection(sectionName, appendElement, numberOfSkeleton, elementAttribute, elementClass){
$.ajax({
url : 'https://cdn.contentful.com/spaces/r5mgd95bqsb5/environments/master/entries/1bI13SpZBBvgOgIk4GhYEg?access_token=CVel_r57GUqeTeaLyIsseXEAM1z1f-spXNKR-a2-huA',
type: 'GET',
success: function(data){
const getData = data.fields
if(getData[sectionName]) {
if(getData[sectionName] && getData[sectionName].length) {
getData[sectionName].forEach((item, index) => {
getSingleEntry(item.sys.id)
});
}
}
}
});
}
function getSingleEntry(contentId){
$.ajax({
url : `https://cdn.contentful.com/spaces/r5mgd95bqsb5/environments/master/entries/${contentId}?access_token=CVel_r57GUqeTeaLyIsseXEAM1z1f-spXNKR-a2-huA`,
type: 'GET',
success: function(dataKat){
getAssetData(dataKat.fields.image.sys.id, dataKat.fields.sequence)
$('.data-banner').append(JSON.stringify(dataKat.fields, null, 4))
$('.data-banner').append('<br>');
}
});
}
function getAssetData(assetsId, sequenceId){
$.ajax({
url : `https://cdn.contentful.com/spaces/r5mgd95bqsb5/environments/master/assets/${assetsId}?access_token=CVel_r57GUqeTeaLyIsseXEAM1z1f-spXNKR-a2-huA`,
type: 'GET',
success: function(getAssetsData){
$('.data-image').append(JSON.stringify(getAssetsData.fields, null, 4))
$('.data-image').append('<br>');
}
});
}
$(document).ready(function(){
renderContentBySection('mainBannerImage', '#carousel-inner', 1, 'banner', 'main-banner-item');
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<pre class="data-banner">
<h4>Get Data Main Banner:</h4>
</pre>
<br>
<pre class="data-image">
<h4>Get Data for Each Image in Main Banner:</h4>
</pre>
Because you completely changed the criteria, I will provide an answer for your second ask.
The key to working with multiple batches of asynchronous requests is to gather all the requests, and then listen for them all to complete. Then, do the same thing again with the next batch of requests.
Otherwise, your HTML will print in the order the responses are returned and it will seem random.
Once you have gathered all the completed requests, you can sort() them, then do a forEach through them.
function listenForEntries(arrAllEntryRequests) {
//set up a listener for when all requests have finished, using "spread operator" (...) to send all requests as parameters to when():
$.when(...arrAllEntryRequests).done(
//done:
function (...arrAllEntryResponses) {
let arrAllEntries = [];
//console.log("arrAllEntryResponses", arrAllEntryResponses);
arrAllEntryResponses.forEach((e) => {
arrAllEntries.push(e[0].fields);
});;
//all images loaded, sort:
arrAllEntries.sort((a, b) => (a.sequence < b.sequence ? -1 : 1));
//sorting done, get asset data for each. This is also asyncronous so you need to do the same thing as above:
let arrAllAssetRequests = [];
arrAllEntries.forEach((entryData) => {
//console.log("entryData", entryData);
$('.data-sequence').append(`
<ul>
<li>
Sequence ID: ${entryData.sequence}<br>
Title Banner: ${entryData.title}
</li>
</ul>`)
let assetReqObj = getAssetData(entryData.image.sys.id, entryData.sequence);
arrAllAssetRequests.push(assetReqObj);
});
listenForAssets(arrAllAssetRequests);
}
);
}
function listenForAssets(arrAllAssetRequests) {
$.when(...arrAllAssetRequests).done(
//done:
function (...arrAllAssetResponses) {
//all assets loaded, sort:
arrAllAssetResponses.sort((a, b) => (a[2].sequence < b[2].sequence ? -1 : 1));
arrAllAssetResponses.forEach((assetData) => {
//console.log("assetData", assetData);
if(assetData.length > 0) {
$('.data-assets').append(`
<ul>
<li>
Sequence ID: ${assetData[2].sequence}<br>
Title Banner: ${assetData[0].fields.title}<br>
<img class="img-fluid" src="${assetData[0].fields.file.url}">
</li>
</ul>`);
} else {
console.error("Something wrong with assetData", assetData);
}
});
}
);
}
function renderContentBySection(sectionName, appendElement, numberOfSkeleton, elementAttribute, elementClass) {
$.ajax({
url: 'https://cdn.contentful.com/spaces/r5mgd95bqsb5/environments/master/entries/1bI13SpZBBvgOgIk4GhYEg?access_token=CVel_r57GUqeTeaLyIsseXEAM1z1f-spXNKR-a2-huA',
type: 'GET',
success: function (data) {
const getData = data.fields
//move array to inside this function as it's the only place it will be used:
let arrAllEntryRequests = [];
if (getData[sectionName]) {
if (getData[sectionName] && getData[sectionName].length) {
getData[sectionName].forEach((item, index) => {
arrAllEntryRequests.push(getSingleEntry(item.sys.id));
});
//once array of requests is created, listen for it to finish:
listenForEntries(arrAllEntryRequests);
}
}
}
});
}
function getSingleEntry(contentId) {
return $.ajax({
url: `https://cdn.contentful.com/spaces/r5mgd95bqsb5/environments/master/entries/${contentId}?access_token=CVel_r57GUqeTeaLyIsseXEAM1z1f-spXNKR-a2-huA`,
type: 'GET',
success: function (dataKat) {
//do nothing
}
});
}
function getAssetData(assetsId, sequenceId) {
let $xhr = $.ajax({
url: `https://cdn.contentful.com/spaces/r5mgd95bqsb5/environments/master/assets/${assetsId}?access_token=CVel_r57GUqeTeaLyIsseXEAM1z1f-spXNKR-a2-huA`,
type: 'GET',
success: function (assetData) {
//do nothing
}
});
$xhr.sequence = sequenceId; //store the sequence for later
return $xhr;
}
$(document).ready(function () {
renderContentBySection('mainBannerImage', '#carousel-inner', 1, 'banner', 'main-banner-item');
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container-fluid">
<div class="row">
<div class="col-6">
<div class="data-sequence">
<span> This is sequence data:</span>
</div>
</div>
<div class="col-6">
<div class="data-assets">
<span> This is assets data:</span>
</div>
</div>
</div>
</div>
Because your data is loaded asyncronously, you will need to create a queue of your requests, and listen for them to all finish.
I have commented my code below so you can understand how it works.
First, you need to use the spread operator a lot ..., to work with an unknown number of array elements.
(https://stackoverflow.com/a/35169449/1410567)
Second, you need to use $.when(...array).done(function(...results) { to know when the requests have finished.
(https://blog.kevinchisholm.com/javascript/jquery/using-jquery-deferred-to-manage-multiple-ajax-calls/)
Third, you need to use Array.sort() to sort the array of objects, comparing their sequence, and returning 1 or -1 to sort them.
(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
//create an empty array to hold the queue:
let allImageRequests = [];
function listenForImages() {
//set up a listener for when all requests have finished, using "spread operator" (...) to send all requests as parameters to when():
$.when(...allImageRequests).done(
//done:
function (...arrAllImagesResp) {
let arrAllImages = [];
console.log("arrAllImagesResp", arrAllImagesResp);
arrAllImagesResp.forEach((e) => {
console.log(e);
arrAllImages.push(e[0].fields);
});;
//all images loaded, sort:
arrAllImages.sort((a, b) => (a.sequence < b.sequence ? -1 : 1));
console.log("done", arrAllImages);
//sorting done, display results:
$('.data-image').append("\n\n<strong>All Images Sorted:</strong> \n\n" + JSON.stringify(arrAllImages, null, 4));
}
);
}
$.ajax({
url: 'https://cdn.contentful.com/spaces/r5mgd95bqsb5/environments/master/entries/1bI13SpZBBvgOgIk4GhYEg?access_token=CVel_r57GUqeTeaLyIsseXEAM1z1f-spXNKR-a2-huA',
type: 'GET',
success: function (data) {
console.log("got data", data);
const getData = data.fields.mainBannerImage
$('.data-banner').append(JSON.stringify(getData, null, 4))
$('.data-banner').append('<br>');
getData.forEach((item, index) => {
//add requests to our queue:
allImageRequests.push(getImageAssets(item.sys.id));
});
listenForImages();
}
})
function getImageAssets(assetId) {
//I added a return here, so the XHR objects will be push()'d to the allImageRequests queue array:
return $.ajax({
url: `https://cdn.contentful.com/spaces/r5mgd95bqsb5/environments/master/entries/${assetId}?access_token=CVel_r57GUqeTeaLyIsseXEAM1z1f-spXNKR-a2-huA`,
type: 'GET',
success: function (assetsData) {
const getAssetsData = assetsData.fields
$('.data-image').append(JSON.stringify(getAssetsData, null, 4))
$('.data-image').append('<br>');
}
})
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<pre class="data-banner">
<h4>Get Data Main Banner:</h4>
</pre>
<br>
<pre class="data-image">
<h4>Get Data for Each Image in Main Banner:</h4>
</pre>

Knockout JS How to Access a Value from Second View Model / Binding

I am new to Knockout JS and think it is great. The documentation is great but I cannot seem to use it to solve my current problem.
The Summary of my Code
I have two viewmodels represented by two js scripts. They are unified in a parent js file. The save button's event is outside
both foreach binders. I can save all data in the details foreach.
My Problem
I need to be able to include the value from the contacts foreach binder with the values from the details foreach binder.
What I have tried
I have tried accessig the data from both viewmodels from the parent viewmodel and sending the POST request to the controller from there but the observeableArrays show undefined.
Create.CSHTML (Using MVC5 no razor)
<div id="container1" data-bind="foreach: contacts">
<input type="text" data-bind="value: contactname" />
</div>
<div data-bind="foreach: details" class="card-body">
<input type="text" data-bind="value: itemValue" />
</div>
The save is outside of both foreach binders
<div class="card-footer">
<button type="button" data-bind="click: $root.save" class="btn
btn-success">Send Notification</button>
</div>
<script src="~/Scripts/ViewScripts/ParentVM.js" type="text/javascript"></script>
<script src="~/Scripts/ViewScripts/ViewModel1.js" type="text/javascript"></script>
<script src="~/Scripts/ViewScripts/ViewModel2.js" type="text/javascript"></script>
ViewModel1
var ViewModel1 = function (contacts) {
var self = this;
self.contacts = ko.observableArray(ko.utils.arrayMap(contacts, function (contact) {
return {
contactName: contact.contactName
};
}));
}
ViewModel2
var ViewModel2 = function (details) {
var self = this;
self.details = ko.observableArray(ko.utils.arrayMap(details, function (detail) {
return {
itemNumber: detail.itemValue
};
}));
}
self.save = function () {
$.ajax({
url: baseURI,
type: "POST",
data: ko.toJSON(self.details),
dataType: "json",
contentType: "application/json",
success: function (data) {
console.log(data);
window.location.href = "/Home/Create/";
},
error: function (error) {
console.log(error);
window.location.href = "/Homel/Create/";
}
});
};
ParentViewModel
var VM1;
var VM2;
var initialContactInfo = [
{
contactPhone: ""
}
]
var initialForm = [
{
itemValue: ""
]
}
$(document).ready(function () {
if ($.isEmptyObject(VM1)) {
ArnMasterData = new ViewModel1(initialContactInfo);
ko.applyBindings(VM1, document.getElementById("container1"));
}
if ($.isEmptyObject(VM2)) {
VM2 = new ViewModel2(initialForm);
ko.applyBindings(VM2, document.getElementById("container2"));
}
});

How to display the information from the input box

I am trying to learn how to work with angular and javascript more. Please let me know what I am doing wrong here.
When I input something into the text box, it should display
"hello {name} , would you like to play a game?
It displays the string without the input.
Also, when I run it, it says
object Object
.
(function (app) {
var JakesController = function ($scope, $http) {
$scope.JakesSampleModel = {name: ' '};
$scope.theSampleReturn = null;
var sendResponseData = function (response) {
if (response.data.error) {
console.log(data);
}
else {
$scope.theSampleReturn = response.data;
}
};
var sendResponseError = function (data) {
console.log(data);
}
$scope.senddata = function (params) {
return $http({
method: 'post',
url: '/home/servercall',
data: params
})
.then(sendResponseData)
.catch(sendResponseError);
};
};
app.controller("JakesController",['$scope', '$http', JakesController]);
}(angular.module("JakesFirstApp")));
Here is the HTML:
<div id="OutterDiv" ng-controller="JakesController" ng-app="JakesFirstApp">
<div id="JakesButton" class="button" ng-click="senddata()">Submit</div>
<input type="text" id="JakesTextBox" ng-model="theSampleReturn" />
{{theSampleReturn.result}}
Json result:
public JsonResult servercall(string name)
{
return Json(new { result = $"Hello {name}, Would you like to play a game?" }, JsonRequestBehavior.AllowGet);
}
In your html try to use {{theSampleReturn}} instead of {{theSampleReturn.result}} because you don't seem to have theSampleReturn.result set anywhere
If I understand you question correctly, then it looks like the solution is to update your template like so:
<input type="text" id="JakesTextBox" ng-model="JakesSampleModel.name" />
And then update your controller to correctly send the name to the server when senddata() is called:
$scope.senddata = function () {
// Construct params for post by getting data from your scope/model that's
// wired up to your input field
var params = { name : $scope.JakesSampleModel.name };
return $http({
method: 'post',
url: '/home/servercall',
data: params
})
.then(sendResponseData)
.catch(sendResponseError);
};

VueJS doesn't work on mobile

I have a problem with running VueJS on mobile devices. I created a weather prediction app on copepen.io
Here is the link for the project:
http://codepen.io/techcater/pen/xOZmgv
HTML code:
<div class="container-fluid text-center">
<h1>Your Local Weather</h1>
<p>
{{location}}
</p>
<p>
{{temperature}}
<a #click="changeDegree">{{degree}}</a>
</p>
<p>
{{weather | capitalize}}
</p>
<img :src="iconURL" alt="" />
<br>
by Dale Nguyen
<!-- <pre>{{$data | json}}</pre> -->
</div>
JS code:
new Vue({
el: '.container-fluid',
data: {
location: "",
temperature: "",
degree: "C",
weather: "",
iconURL: ""
},
created: function(){
this.getWeather();
},
methods: {
getWeather: function(){
var that = this;
this.$http.get("http://ipinfo.io").then((response) => {
console.log(response.data);
that.location = response.data.city + ", " + response.data.country;
// Get weather informaiton
var api = 'ebd4d312f85a230d5dc1db91e20c2ace';
var city = response.data.city;
var url = "http://api.openweathermap.org/data/2.5/weather?q={CITY}&APPID={APIKEY}&units=metric";
url = url.replace("{CITY}",city);
url = url.replace("{APIKEY}", api);
that.$http.post(url,{dataType: 'jsonp'},{
headers : {
'Content-Type' : 'application/x-www-form-urlencoded; charset=UTF-8'
}}).then((response) => {
console.log(response.data);
that.temperature = response.data.main.temp;
that.weather = response.data.weather[0]['description'];
that.iconURL = "http://openweathermap.org/img/w/" + response.data.weather[0]['icon'] + ".png";
}, (response) => {
// error callback
});
}, (response) => {
console.log(response.data);
});
},
changeDegree: function() {
if(this.degree == "C"){
this.degree = "F";
this.temperature = Math.round((this.temperature*9/5 + 32)*100)/100;
}else {
this.degree = "C";
this.temperature = Math.round(((this.temperature - 32)*5 /9)* 100)/100;
}
}
}
})
It works well on my laptop but not on mobile. At first, I thought that it is because of Codepen. It may cause something when running through the site. However, when I created a project on my website, it also doesn't work.
Can you help to find the issue? Thanks,
Your code seems to be working well, except that on codepen it gives me error XMLHttpRequest cannot load http://ipinfo.io/. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://s.codepen.io' is therefore not allowed access..
You can put your domain name on headers options to enable cross-origin, here is example:
this.$http.get('http://ipinfo.io', {
'headers': {
'Origin': 'http://yourdomain.com'
}
})
See example: http://bozue.com/weather.html
I also noticed you put vue.min.js and vue-resource.js scripts in wrong order that might trigger some error, vue.min.js should be on the first place.
I found a solution for this. I works on my mobile now. I believe that I will work on other browses too. The problem is that some browsers doesn't recognize the operation ">", so I changed it.
Here is the new code:
getWeather: function(){
var that = this;
this.$http.get('http://ipinfo.io', {'headers': {
'Origin': 'http://yourdomain.com'}
}).then(function(response) {
console.log(response.data);
that.location = response.data.city + ", " + response.data.country;
// Get weather informaiton
var api = 'ebd4d312f85a230d5dc1db91e20c2ace';
var city = response.data.city;
var url = "https://crossorigin.me/http://api.openweathermap.org/data/2.5/weather?q={CITY}&APPID={APIKEY}&units=metric";
url = url.replace("{CITY}",city);
url = url.replace("{APIKEY}", api);
that.$http.post(url,{dataType: 'jsonp'},{
headers : {
'Content-Type' : 'application/x-www-form-urlencoded; charset=UTF-8'
}}).then(function(response) {
console.log(response.data);
that.temperature = response.data.main.temp;
that.weather = response.data.weather[0]['description'];
that.iconURL = "http://openweathermap.org/img/w/" + response.data.weather[0]['icon'] + ".png";
}).then(function(){
// error callback
});
}).then(function(){
console.log(response.data);
});
},

Passing an error message from controller to view with Backbone

I have the following error message available as HTML:
<div class="alert alert-error">
<div class="content">
<p>
<i class="fa fa-warning"></i>
User not found.
</p>
</div>
</div>
How can I display it for the user without losing good practices, in Backbone?
I mean, what's the best way to make my view render it with reliability?
When is this message displayed?
user.save({}, {
success: function(model, response) {
if (response.error) {
// hey, the message goes here!
}
}
});
You can extend Backbone View with a new method to display errors like this :
_.extend(Backbone.View.prototype, {
showError: function(message) {
// Here you render your error, may be like this : $('#errorInfo').html(message);
}
});
and in your view :
var self = this; // I assume that you are in the view
user.save({}, {
success: function(model, response) {
if (response.error) {
self.showError(/* things you want to pass */);
}
}
});
For me the best practice is to create an object errorMessageView
HTML :
<div id="errorInfo"></div>
JS :
var errorMessageView = Backbone.View.extend({
el: "#errorInfo",
initialize: function() {
},
renderInnerHTML: function(message) {
this.el.innerHTML = message;
}
});
var errorMessage = new errorMessageView ;
user.save({}, {
success: function(model, response) {
if (response.error) {
errorMessage.renderInnerHTML(response.error);
}
}
});

Categories