Updating Vue Data Object Off AJAX response - ajax

What I am trying to do is loading a template using AJAX and then building a data model from an AJAX JSON response. I want it to be reactive. From what I've read on the Vue documentation, all the instance object properties have to be set at initialization for them to be reactive. I was curious how I could go about doing that.
I am generating the object model based off an array of dot notation strings, converting a csv file to JSON, then parsing that data into the new model object. This is how I envision the process
var vm = new Vue({
el: '#app',
data: function() {
return {
model: {}
};
}
});
/**
-Load template,
-build model object,
-update Vue data,
-have template react to new data
*/
$(function() {
// Load in html
$(#model).load('./template.html', function(response, status, xhr) {
var modelStructureAsArray = [
'meta',
'data',
'data.details'
];
// Update Vue data for model
vm.model = buildObjectByArray(modelStructureAsArray);
/**
// Expected structure
vm.model = {
meta: {
},
data: {
details: {
}
}
}
**/
// This would be where I set all the data, im using a function to parse and
// return a full model, below is simplified for brevity
vm.model.meta.name = 'Daniel';
vm.nextTick(function() {
vm.$el.textContent === 'Daniel';
};
}
});
Loaded HTML
<!-- template.html -->
<template v-if="model.meta">
<header>
<h1>{{ model.meta.name }}</h1>
</header>
</template>
I'm not understanding how to use vue.nextTick() to update the vue model and make it reactive. The documentation shows setting the property, then immediately calling Vue.nextTick() to update it.
I'm not sure what the textContent property of $el is and cannot find it simply googling. Is it updating all text within brackets within the root element?
Edit: Added HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue: Reactive</title>
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
</head>
<body>
<header>
<h1>Reactive Vue</h1>
</header>
<main id="app"></main>
<script src="./app.js" charset="utf-8"></script>
</body>
</html>

textContent gets and sets the text contents of a DOM node. You should not need to directly manipulate the DOM for what you are doing.
Your assignment
vm.model = buildObjectByArray(modelStructureAsArray);
should be sufficient to update the component. Since model is a data item, it is reactive, and assignment to it is a reactive operation. Setting its members, though, as in
vm.model.meta.name = 'Daniel';
runs into a change detection caveat since name wasn't present in the assigned structure. So instead, use
vm.$set(vm.model.meta, 'name', 'Daniel');
and the update will be reactive. You don't need to do anything with $nextTick for this. An example is below. Incidentally, you might as well put your code in the created hook rather than the jQuery $.
var vm = new Vue({
el: '#app',
data: function() {
return {
model: {}
};
},
created() {
setTimeout(() => {
vm.model = {
meta: {},
data: {
details: {}
}
}
vm.$set(vm.model.meta, 'name', 'Daniel');
}, 500);
}
});
<script src="//unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<template v-if="model.meta">
<header>
<h1>{{ model.meta.name }}</h1>
</header>
</template>
</div>

Related

Thymeleaf th:inline="javascript" issue

I don't know how to solve the following: I'd like to let my Model generate real javascript dynamically based on some model logic.
This final piece of javascript code then should be added inside the $(document).ready { } part of my html page.
The thing is: If I use inline="javascript", the code gets quoted as my getter is a String (that is how it is mentioned in the Thymeleaf doc but it's not what I need ;-)
If I use inline="text" in is not quoted but all quotes are escaped instead ;-) - also nice but unusable 8)
If I try inline="none" nothing happens.
Here are the examples
My model getter created the following Javascript code.
PageHelper class
public String documentReady() {
// do some database operations to get the numbers 8,5,3,2
return "PhotoGallery.load(8,5,3,2).loadTheme(name='basic')";
}
So if I now try inline="javascript"
<script th:inline="javascript">
/*<![CDATA[*/
jQuery().ready(function(){
/*[[${pageHelper.documentReady}]]*/
});
/*]]>*/
</script>
it will be rendered to
<script>
/*<![CDATA[*/
jQuery().ready(function(){
'PhotoGallery.load(8,5,3,2).loadTheme(name=\'basic\')'
});
/*]]>*/
</script>
Which doesn't help as it is a String literal, nothing more (this is how Thymeleaf deals with it).
So if I try inline="text" instead
<script>
/*<![CDATA[*/
jQuery().ready(function(){
PhotoGallery.load(8,5,3,2).loadTheme(name='basic')
});
/*]]>*/
</script>
Which escapes the quotes.
inline="none" I do not really understand, as it does nothing
<script>
/*<![CDATA[*/
jQuery().ready(function(){
[[${pageHelper.documentReady}]]
});
/*]]>*/
</script>
To be honest I have no idea how to solve this issue and hopefully anybody out there knows how to deal with this.
Many thanks in advance
Cheers
John
I would change the approach.
Thymeleaf easily allows you to add model variables in your templates to be used in Javascript. In my implementations, I usually put those variables somewhere before the closing header tag; to ensure they're on the page once the JS loads.
I let the template decide what exactly to load, of course. If you're displaying a gallery, then render it as you would and use data attributes to define the gallery that relates to some JS code. Then write yourself a nice jQuery plugin to handle your gallery.
A relatively basic example:
Default Layout Decorator: layout/default.html
<!doctype html>
<html xmlns:layout="http://www.thymeleaf.org" xmlns:th="http://www.thymeleaf.org">
<head>
<title>My Example App</title>
<object th:remove="tag" th:include="fragments/scripts :: header" />
</head>
<body>
<div layout:fragment="content"></div>
<div th:remove="tag" th:replace="fragments/scripts :: footer"></div>
<div th:remove="tag" layout:fragment="footer-scripts"></div>
</body>
</html>
The thing to notice here is the inclusion of the generic footer scripts and then a layout:fragment div defined. This layout div is what we're going to use to include our jQuery plugin needed for the gallery.
File with general scripts: fragments/scripts.html
<div th:fragment="header" xmlns:th="http://www.thymeleaf.org">
<script type="text/javascript" th:inline="javascript">
/*<![CDATA[*/
var MY_APP = {
contextPath: /*[[#{/}]]*/,
defaultTheme: /*[[${theme == null} ? null : ${theme}]]*/,
gallery: {
theme: /*[[${gallery == null} ? null : ${gallery.theme}]]*/,
images: /*[[${gallery == null} ? null : ${gallery.images}]]*/,
names: /*[[${gallery == null} ? null : ${gallery.names}]]*/
}
};
/*]]>*/
</script>
</div>
<div th:fragment="footer" xmlns:th="http://www.thymeleaf.org">
<script type="text/javascript" src="/js/jquery.js"></script>
<script type="text/javascript" src="/js/my_app.js"></script>
</div>
In the scripts file, there are 2 fragments, which are included from the decorator. In the header fragment, a helpful context path is included for the JS layer, as well as a defaultTheme just for the hell of it. A gallery object is then defined and assigned from our model. The footer fragment loads the jQuery library and a main site JS file, again for purposes of this example.
A page with a lazy-loaded gallery: products.html
<html layout:decorator="layout/default" xmlns:layout="http://www.thymeleaf.org/" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Products Landing Page</title>
</head>
<body>
<div layout:fragment="content">
<h1>Products</h1>
<div data-gallery="lazyload"></div>
</div>
<div th:remove="tag" layout:fragment="footer-scripts">
<script type="text/javascript" src="/js/my_gallery.js"></script>
</div>
</body>
</html>
Our products page doesn't have much on it. Using the default decorator, this page overrides the page title in the head. Our content fragment includes a title in an h1 tag and an empty div with a data-gallery attribute. This attribute is what we'll use in our jQuery plugin to initialize the gallery.
The value is set to lazyload, so our plugin knows that we need to find the image IDs in some variable set somewhere. This could have easily been empty if the only thing our plugin supports is a lazyloaded gallery.
So the layout loads some default scripts and with cleverly placed layout:fragments, you allow certain sections of the site to load libraries independent of the rest.
Here's a basic Spring controller example, to work with our app: MyController.java
#Controller
public class MyController {
#RequestMapping("/products")
public String products(Model model) {
class Gallery {
public String theme;
public int[] images;
public String[] names;
public Gallery() {
this.theme = "basic";
this.images = new int[] {8,5,3,2};
this.names = new String[] {"Hey", "\"there's\"", "foo", "bar"};
}
}
model.addAttribute("gallery", new Gallery());
return "products";
}
}
The Gallery class was tossed inline in the products method, to simplify our example here. This could easily be a service or repository of some type that returns an array of identifiers, or whatever you need.
The jQuery plugin that we created, could look something like so: my_gallery.js
(function($) {
var MyGallery = function(element) {
this.$el = $(element);
this.type = this.$el.data('gallery');
if (this.type == 'lazyload') {
this.initLazyLoadedGallery();
}
};
MyGallery.prototype.initLazyLoadedGallery = function() {
// do some gallery loading magic here
// check the variables we loaded in our header
if (MY_APP.gallery.images.length) {
// we have images... sweet! let's fetch them and then do something cool.
PhotoGallery.load(MY_APP.gallery.images).loadTheme({
name: MY_APP.gallery.theme
});
// or if load() requires separate params
var imgs = MY_APP.gallery.images;
PhotoGallery.load(imgs[0],imgs[1],imgs[2],imgs[3]).loadTheme({
name: MY_APP.gallery.theme
});
}
};
// the plugin definition
$.fn.myGallery = function() {
return this.each(function() {
if (!$.data(this, 'myGallery')) {
$.data(this, 'myGallery', new MyGallery(this));
}
});
};
// initialize our gallery on all elements that have that data-gallery attribute
$('[data-gallery]').myGallery();
}(jQuery));
The final rendering of the products page would look like so:
<!doctype html>
<html>
<head>
<title>Products Landing Page</title>
<script type="text/javascript">
/*<![CDATA[*/
var MY_APP = {
contextPath: '/',
defaultTheme: null,
gallery: {
theme: 'basic',
images: [8,5,3,2],
names: ['Hey','\"there\'s\"','foo','bar']
}
};
/*]]>*/
</script>
</head>
<body>
<div>
<h1>Products</h1>
<div data-gallery="lazyload"></div>
</div>
<script type="text/javascript" src="/js/jquery.js"></script>
<script type="text/javascript" src="/js/my_app.js"></script>
<script type="text/javascript" src="/js/my_gallery.js"></script>
</body>
</html>
As you can see, Thymeleaf does a pretty good job of translating your model to valid JS and actually adds the quotes where needed and escapes them as well. Once the page finishes rendering, with the jQuery plugin at the end of the file, everything needed to initialize the gallery should be loaded and ready to go.
This is not a perfect example, but I think it's a pretty straight-forward design pattern for a web app.
instead of ${pageHelper.documentReady} use ${pageHelper.documentReady}

How to make a ajax loading component by using angularjs service?

I want to make a specific component for showing loading icon when the browser waits to load data from json. Is there any possible way to do it by using a service ??
html :
<html ng-app="app">
<head>
<title> ERP </title>
</head>
<body >
<div ng-controller="data"><span class="loading" ng-show="loader"><img src="ajax-loader.gif"></span>
<table>
<tr><td>{{data1}}</td><td>{{data1}}</td><td>{{data1}}</td></tr>
</table>
</div>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.15/angular.min.js"></script>
<script type="text/javascript" src="ctrl.js"></script>
</body>
</html>
js :
var app = angular.module('app',[]);
app.controller('data', ['$scope','$http',function($scope,$http){
$scope.loader = null ;
if($scope.loader == null )
{
$scope.loader = true ;
}
$http.get('events.json').
success(function(data) {
$scope.loader = false ;
console.log(data);
$scope.data1 = data ;
console.log($scope.data1);
}).
error(function(data, status, headers, config) {
});
}]);
You can wrap the original $http to provide a customizedHttp service. in this service, provide the same interface with $http. when sending GET request to the server, the customizedHttp broadcast an event to show the loading flag. and after receiving response, broadcast another event to hide the loading flag.
to control the flag, you'd better to write a directive.

Read/write to Parse Core db from Google Apps Script

I'm just starting to use Parse Core (as Google'e ScriptDB is being decommissioned soon) and am having some trouble.
So I'm able to get Parse Core db to read/write using just a standard HTML page as shown below:
<!doctype html>
<head>
<meta charset="utf-8">
<title>My Parse App</title>
<meta name="description" content="My Parse App">
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="css/reset.css">
<link rel="stylesheet" href="css/styles.css">
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script type="text/javascript" src="http://www.parsecdn.com/js/parse-1.2.18.min.js"></script>
</head>
<body>
<div id="main">
<h1>You're ready to use Parse!</h1>
<p>Read the documentation and start building your JavaScript app:</p>
<ul>
<li>Parse JavaScript Guide</li>
<li>Parse JavaScript API Documentation</li>
</ul>
<div style="display:none" class="error">
Looks like there was a problem saving the test object. Make sure you've set your application ID and javascript key correctly in the call to <code>Parse.initialize</code> in this file.
</div>
<div style="display:none" class="success">
<p>We've also just created your first object using the following code:</p>
<code>
var TestObject = Parse.Object.extend("TestObject");<br/>
var testObject = new TestObject();<br/>
testObject.save({foo: "bar"});
</code>
</div>
</div>
<script type="text/javascript">
Parse.initialize("PyMFUxyBxR8IDgndjZ378CeEXH2c6WLK1wK2JHYX", "IgiMfiuy3LFjzH0ehmyf5Rkti8AmVtwcGqc6nttN");
var TestObject = Parse.Object.extend("TestObject");
var testObject = new TestObject();
testObject.save({foo: "bar"}, {
success: function(object) {
$(".success").show();
},
error: function(model, error) {
$(".error").show();
}
});
</script>
</body>
</html>
However, when I try to serve that up using the HtmlService shown below, I get no response from Parse. Parse Core.html basically has all of the code I have above ( only thing I changed was to remove the css calls).
function doGet() {
var htmlPage = HtmlService.createTemplateFromFile('Parse Core.html')
.evaluate()
.setSandboxMode(HtmlService.SandboxMode.NATIVE)
.setTitle('Parse Core Test');
return htmlPage;
}
Link to ParseDb Library for Apps Script
Here is the key to add the library: MxhsVzdWH6ZQMWWeAA9tObPxhMjh3Sh48
Install that library and it allows you to use most of the same methods that were used by ScriptDb. As far as saving and querying go they almost identical. Make sure to read the Library's notes, how to add the applicationId and restApiKey. It is a little different that you can silo data by classes which must be defined in the call to Parse.
Bruce here is leading the way on database connection for Apps Script, he has plenty of documentation on using Parse.com, and also his own DbConncection Drive that would allow you to use a number of back-end systems.
Excel Liberation - Bruce's Site.

How to inject dependencies in jasmine test for an angular item

Here is the test spec file:
describe('Test main controller', function(){
it('Should initialize value to Loading', function(){
$scope = {}
ctrl = new mainNavController($scope)
expect($scope.wksp_name).toBe('Loading')
})
})
Here is the controller file
function mainNavController($scope) {
$scope.wksp_name = 'Loading...'
$scope.$on('broadCastWkspNameEvent', function (e, args) {
$scope.wksp_name = args
})
}
mainNavController.$inject=['$scope']
But my test fails saying Object #<Object> has no method '$on'
I am using the basic setup of jasmine.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Jasmine Spec Runner</title>
<link rel="shortcut icon" type="image/png" href="testlib/jasmine-1.2.0/jasmine_favicon.png">
<link rel="stylesheet" type="text/css" href="testlib/jasmine-1.2.0/jasmine.css">
<script type="text/javascript" src="testlib/jasmine-1.2.0/jasmine.js"></script>
<script type="text/javascript" src="testlib/jasmine-1.2.0/jasmine-html.js"></script>
<!-- include source files here... -->
<script type="text/javascript" src="/static_files/js/test-specs/main-nav-spec.js"></script>
<!-- include spec files here... -->
<script type="text/javascript" src="/static_files/js/common/jquery/latest.js"></script>
<script type="text/javascript" src="/static_files/js/common/angular/angular-1.0.1.min.js"></script>
<script type="text/javascript" src="/static_files/js/common/angular/angular-resource-1.0.1.min.js"></script>
<script type="text/javascript" src="/static_files/js/section/main-nav-controller.js"></script>
<script type="text/javascript">
(function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);
jasmineEnv.specFilter = function(spec) {
return htmlReporter.specFilter(spec);
};
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
execJasmine();
};
function execJasmine() {
jasmineEnv.execute();
}
})();
</script>
</head>
<body>
</body>
</html>
What is it that I am doing wrong? I am not able to understand how this thing is supposed to work :)
The main problem with your test code is that it tries to create a controller's instance "by hand" using the new operator. When doing so AngularJS has no chance to inject dependencies. What you should be doing is to allow AngularJS inject dependencies:
var $scope, ctrl;
//you need to inject dependencies first
beforeEach(inject(function($rootScope) {
$scope = $rootScope.$new();
}));
it('Should initialize value to Loading', inject(function($controller) {
ctrl = $controller('MainNavController', {
$scope: $scope
});
expect($scope.wksp_name).toBe('Loading...');
}));
Here is the link to a complete jsFiddle: http://jsfiddle.net/pkozlowski_opensource/7a7KR/3/
There are 2 things worth noting in the above example:
You can use the inject() method from the ngMock module to inject dependencies: https://docs.angularjs.org/api/ngMock/function/angular.mock.inject
To create a controller instance (that supports dependency injection) you would use the $controller service: http://docs.angularjs.org/api/ng.$controller
As the last remark: I would advise naming controllers starting with an uppercase letter - this way we won't confuse them with variable names.
Great answer by #pkozlowski.opensource. To elaborate a bit more... Sometimes it could be also handy to assert that $scope.$on was really called by your controller. In this case you can spy on $scope.$on as pointed out below:
beforeEach(inject(function($rootScope) {
$scope = $rootScope.$new();
spyOn($scope, '$on').andCallThrough();
}));
And then you can assert that $on was called with your event name and some function as arguments:
it('Should bind to "broadCastWkspNameEvent"', inject(function($controller) {
ctrl = $controller('MainNavController', {
$scope: $scope
});
expect($scope.$on).toHaveBeenCalledWith('broadCastWkspNameEvent', jasmine.any(Function));
}));
I agree with pkozowski's response, but to answer your question more directly, you need to stub out '$on'
Your example would pass if your $scope looked like:
$scope = {
$on: function() {}
}

Trouble with jQuery Ajax timing

I have a very simple javascript class that does an ajax call to a web service of mine via jquery. It returns the data successfully, but I am unable to retrieve it via a variable I set the data to. I don't think it is a matter of the ajax call being asynchronous or not because I have set up event handlers for all the ajax events, but some of them do not fire. I have no idea what is wrong. Here is the complete code:
Javascript:
function testClass(){
this.returnData = "";
this.FireAjax = function(){
$.getJSON("http://localhost/mywebapp/webservices/service.asmx/Initialize?userID=12&jsoncallback=?",
function(data){
this.returnData = data.d;
alert(data.d);
}
);
}
}
HTML page:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Untitled Document</title>
<script type="text/javascript" src="http://localhost/mywebapp/js/jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="testClass.js"></script>
<script type="text/javascript">
$(document).ready(function(){
var obj = new testClass();
$("#debug").ajaxError(function(event, request, settings){
$(this).append("<b>Ajax Error!</b><br />"); //this does not fire
});
$("#debug").ajaxSend(function(evt, request, settings){
$(this).append("<b>Ajax Send!</b><br />"); //this does not fire!?
});
$("#debug").ajaxStop(function(){
$(this).append("<b>Ajax Stopped</b><br />"); //this fires
});
$("#debug").ajaxComplete(function(event,request, settings){
$(this).append("<b>Ajax Completed!</b><br />"); //this fires
$(this).append("<h2>" + obj.returnData + "</h2>"); //this returns an empty string!!!!!!
});
$("#debug").ajaxSuccess(function(evt, request, settings){
$(this).append("<b>Ajax Successful!</b><br />"); //this fires
});
$("#debug").ajaxStart(function(){
$(this).append("<b>Ajax Started!</b><br />"); //this fires
});
obj.FireAjax();
});
</script>
</head>
<body>
<div id="debug">
</div>
</body>
</html>
Additional Info:
If I remove the complete event in my html page and place the call to obj.returnData in my stop event (thinking that perhaps my html complete event overwrites my testClass complete function), i get the same results.
Your problem is here:
this.returnData = data.d;
this inside the anonymous function refers to the jQuery Options object, not the instance of your object.
Try this:
function testClass(){
this.returnData = "";
var that = this;
this.FireAjax = function(){
$.getJSON("http://localhost/mywebapp/webservices/service.asmx/Initialize?userID=12&jsoncallback=?",
function(data){
that.returnData = data.d;
alert(data.d);
}
);
}
}

Resources