Hi I would like to be able to delay the splash of my app for a bit here is what I mean.
This is my shell view:
<div>
<!-- ko compose: { view : header }-->
<!--/ko-->
<!-- ko compose: { view : content }-->
<!--/ko-->
This is my shell viewmodel:
define(['plugins/router', 'services/dataService' , 'models/appViewModels'],
function (router,dataService , appViewModels) {
var vm = {
header: ko.observable(),
content: ko.observable(),
activate: activate
};
function activate() {
setActivePage();
}
function setActivePage() {
$.when(dataService.account.isAuthenticated())
.done(function(isAuthenticated) {
setDefaultDisplayPage(isAuthenticated)
}).fail(function(data) {
alert(data);
});
}
function setDefaultDisplayPage(isAuthenticated) {
if (isAuthenticated) {
setHeaderAndContentObservables(appViewModels.header.generalHeader, appViewModels.content.homeContent);
} else {
setHeaderAndContentObservables(appViewModels.header.loginHeader, appViewModels.content.loginContent);
}
}
}
The reson I am using observables instead of a string representation of a path for my view is because in my shell I am deciding if I should sent my user on the login or homepage
.
This works perfectly except for one thing in the time that it takes to get the isAuthenticated property from the server the splash screen dissapears and the user is left waiting on a blank page until the data is recieved.
Now I could try getting the data in the main.js file and cache it but I figure main.js should have only app configuration responsibility.
Is there any way to make the call to the server for the data before the shell actually get's binded and the splash screen dissapears?
I agree with what PW Kad suggested. Durandal is packed with Q.js.
I had the same scenario
[Check if the user is authenticated=> Load user menus from database through Durandal router which returns a promise => Load the content]
However, I can't see the router in your code.
Anyway, In the activate method you could do something like this:
var vm = {
header: ko.observable(),
content: ko.observable(),
isAuthenticated: ko.observable(false),
activate: activate
};
function activate() {
return Authenticate()
.then(setDefaultDisplayPage)
.fail(failed);
}
function Authenticate() {
// pass an observable as a parameter to your function
dataService.account.isAuthenticated(isAuthenticated);
return Q.resolve();
}
function setDefaultDisplayPage() {
if(isAuthenticated()) {
setHeaderAndContentObservables(appViewModels.header.generalHeader,appViewModels.content.homeContent);
}
else { setHeaderAndContentObservables(appViewModels.header.loginHeader,appViewModels.content.loginContent);
}
}
function failed(){
// failure code goes here
}
This way, your viewmodel won't get binded until data is fetched from the server.
Related
I am really struggling with waiting on a subscription to load for a specific route before returning the data to the template. I can see on from the publish on the server that a document is found, but on the client there is no document.
If I do a find().count() on the publish, it shows 1 document found, which is correct, but when I do the count on the subscription, it shows 0 documents.
I have tried a number of different methods, like using subscriptions:function() instead of waitOn:function(), but nothing works.
Collections.js lib:
SinglePackage = new Mongo.Collection("SinglePackage");
SinglePackage.allow({
insert: function(){
return true;
},
update: function(){
return true;
},
remove: function(){
return true;
}
});
Publications.js server:
Meteor.publish("SinglePackage", function(pack_id) {
return Packages.find({shortId: pack_id});
});
Iron Router:
Router.route('/package/:id', {
name: 'package.show',
template: 'Package_page',
layoutTemplate: 'Landing_layout',
waitOn: function() {
return Meteor.subscribe('SinglePackage', this.params.id);
},
data: function() {
return SinglePackage.find();
},
action: function () {
if (this.ready()) {
this.render();
} else {
this.render('Loading');
}
}
});
Am I doing something very wrong, or is this just a complicated thing to achieve? One would think that waitOn would make the rest of the function wait until the subscription is ready.
Any help would be highly appreciated.
It appears that the data function is running before the subscription is ready. Even if the data function did run after the subscription was ready, it wouldn't be a reactive data source rendering the pub/sub here pointless. Here's a great article on reactive data sources.
Referring to the example from the Iron Router Docs for subscriptions, you would do something like this:
Router.route('/package/:id', {
subscriptions: function() {
// returning a subscription handle or an array of subscription handles
// adds them to the wait list.
return Meteor.subscribe('SinglePackage', this.params.id);
},
action: function () {
if (this.ready()) {
this.render();
} else {
this.render('Loading');
}
}
});
Then in your template.js:
Template.Package_page.helpers({
singlePackage() {
// This is now a reactive data source and will automatically update whenever SinglePackage changes in Mongo.
return Package.find().fetch();
}
});
In your template.html you can now use singlePackage:
<template name="Package_page">
{#with singlePackage} <!-- Use #each if you're singlePackage is an array -->
ID: {_id}
{/with}
</template>
I'm using angular-confirm to display confirmation messages in a lot of places in my app. For example:
$confirm({
text: 'content',
title: 'title text',
ok: 'Yes',
cancel: 'No'})
.then(function() {
doSomething();
});
I want to globally change the layout in which these dialogs appear in the app. I know that angular-confirm allows you to make a global change like this:
$confirmModalDefaults.templateUrl = 'path/to/your/template';
However, this also overrides the template for all $modal.open() calls, which is not what I want.
I think that the way to do this is by a using a provider to append a template url to every $confirm call in the app but I'm not sure exactly how to do that.
How do I create a provider for $confirm and append a templateUrl parameter to every call?
Figured it out:
function config($provide) {
$provide.decorator('$confirm', confirmProvider);
}
/* #ngInject */
function confirmProvider($delegate) {
return function (data, settings) {
if (settings && !settings.template && !settings.templateUrl) {
settings.template = '<div>my confirm content</div>';
} else {
settings = {
template: '<div>my confirm content</div>'
}
}
return $delegate(data, settings);
};
So now, when $confirm() gets called with data and/or settings, it will apply my custom template unless it's been specified by the caller
Im struggling with an issue using Meteor JS.
I call an api wich return me a Json array wich look like the one returned on this url (I don't put the whole array here cause of the size): https://blockchain.info/address/12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?format=json&offset=0
I call it server side like :
if (Meteor.isServer) {
Meteor.methods({
getWalletPreviousTx: function() {
var url = "https://blockchain.info/address/12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?format=json&offset=0";
var result = Meteor.http.get(url);
if(result.statusCode==200) {
var tx = JSON.parse(result.content);
return tx;
} else {
console.log("Response issue: ", result.statusCode);
var errorJson = JSON.parse(result.content);
throwError("Couldn't fetch wallet balance from Blockchain, try again later !");
}
}
});
}
And i retrieve it to my view via an helper in a specific template :
Template.wallet.helpers({
addrTxs: function () {
Meteor.call('getWalletPreviousTx', function(err, tx) {
console.log(tx);
return [tx];
});
}
});
The console.log in the helper actually log my Json array wich mean it have access to it.
Now the part im struggling with is to retrieve this Json to my view, i've tried a lot of way and none of them works, actually i have this in my view :
<template name="wallet">
<table>
{{#each addrTxs}}
<ul>
{{> addrTx}}
</ul>
{{/each }}
</table>
</template>
The part of the Json I want to display is the "addr" and "value" of each transactions :
"inputs":[
{
"sequence":4294967295,
"prev_out":{
"spent":true,
"tx_index":97744124,
"type":0,
"addr":"1AWAsn8rhT555RmbMDXXqzrCscPJ5is5ja",
"value":50000,
"n":0,
"script":"76a914683d704735fd591ba9f9aebef27c6ef00cbd857188ac"
}
}
]
Fact is, i never managed to display anything from this Json array in my view, even puting directly this in my view doesn't show anything :
{{addrTxs}}
What am I doing wrong ? Can anyone help with this ?
Thanks for reading.
----------------------- Edit ---------------------
I think the problem is more that my helper and template are loaded before the api call is finished (because the console.log appear in my console like 3seconds after my page is rendered). How can i make my helper wait until the api call is finished before rendering it in the view ? I use iron router.
I have tried to add a waitOn action on my route in order to wait until my api call is finished :
Router.route('/wallet', {
name: 'wallet',
template: 'wallet',
loadingTemplate: 'loading',
waitOn: function () {
Meteor.call('getWalletPreviousTx', function(error, result) {
if(!error) {
Ready.set(result)
}
});
return [
function () { return Ready.get(); }
];
},
action: function () {
if (this.ready())
this.render();
else
this.render('loading');
}
});
The above code with the waitOn action seems to work (i have no errors) but i don't know the way to display in my view the specific result from :
if(!error) {
Ready.set(result)
}
Transactions are contained in tx.txs, iterates through that.
Template.wallet.helpers({
addrTxs: function () {
Meteor.call('getWalletPreviousTx', function(err, tx) {
console.log(tx);
return tx.txs;
});
}
});
You're right, you need to use the sessions variables with async call.
First, call method on created :
Template.wallet.created = function () {
Meteor.call('getWalletPreviousTx', function(err, tx) {
console.log(tx.txs);
Session.set('tx', tx.txs);
});
};
Helper should look like this :
Template.wallet.helpers({
addrTxs: function () {
return Session.get('tx');
}
});
I'm giving Vue.js a try and so far I'm loving it because it's much simpler than angular. I'm currently using vue-router and vue-resource in my single page app, which connects to an API on the back end. I think I've got things mostly working with a the primary app.js, which loads vue-router and vue-resource, and several separate components for each route.
Here's my question: How do I use props to pass global data to the child components when the data is fetched using an asynchronous AJAX call? For example, the list of users can be used in just about any child component, so I would like the primary app.js to fetch the list of users and then allow each child component to have access to that list of users. The reason I would like to have the app.js fetch the list of users is so I only have to make one AJAX call for the entire app. Is there something else I should be considering?
When I use the props in the child components right now, I only get the empty array that the users variable was initialized as, not the data that gets fetched after the AJAX call. Here is some sample code:
Simplified App.js
var Vue = require('vue');
var VueRouter = require('vue-router')
Vue.use(VueRouter);
var router = new VueRouter({
// Options
});
router.map({
'*': {
component: {
template: '<p>Not found!</p>'
}
},
'/' : require('./components/dashboard.js'),
});
Vue.use(require('vue-resource'));
var App = Vue.extend({
ready: function() {
this.fetchUsers();
},
data: function() {
return {
users: [],
};
},
methods: {
fetchUsers: function() {
this.$http.get('/api/v1/users/list', function(data, status, response) {
this.users = data;
}).error(function (data, status, request) {
// handle error
});
}
}
});
router.start(App, '#app')
Simplified app.html
<div id="app" v-cloak>
<router-view users = "{{ users }}">
</router-view>
</div>
Simplified dashboard.js
module.exports = {
component: {
ready: function() {
console.log(this.users);
},
props: ['users'],
},
};
When dashboard.js gets run, it prints an empty array to the console because that's what app.js initializes the users variable as. How can I allow dashboard.js to have access to the users variable from app.js? Thanks in advance for your help!
p.s. I don't want to use the inherit: true option because I don't want ALL the app.js variables to be made available in the child components.
I believe this is actually working and you are being misled by the asynchronous behavior of $http. Because your $http call does not complete immediately, your console.log is executing before the $http call is complete.
Try putting a watch on the component against users and put a console.log in that handler.
Like this:
module.exports = {
component: {
ready: function() {
console.log(this.users);
},
props: ['users'],
watch: {
users: {
handler: function (newValue, oldValue) {
console.log("users is now", this.users);
},
deep: true
}
}
}
};
In the new version of Vue 1.0.0+ you can simply do the following, users inside your component is automatically updated:
<div id="app" v-cloak>
<router-view :users="users"></router-view>
</div>
I am building a SPA app with the default Durandal setup. I have multiple views returning data with ajax calls however, it is not working perfectly. I created my shell page with a search box so I can search through a list of employees shown here.
Shell.js
define(['require', 'durandal/plugins/router', 'durandal/app', 'config'],
function (require, router, app, config) {
var shell = {
router: router,
searchData: searchData,
employees: ko.observable(),
search: search,
activate: activate,
};
var searchData = ko.observable('');
function search(searchData) {
var url = '#/employeeSearch/' + searchData.searchData;
router.navigateTo(url);
}
return shell;
function activate() {
router.map(config.routes);
return router.activate(config.startModule);
}
});
shell.html
<div class="input-group">
<input type="text" class="form-control" placeholder="Search Employees" data-bind="value: searchData" />
<span class="input-group-btn">
<button type="submit" class="btn btn-default" data-bind="click: search">Search</button>
</span>
</div>
The user puts in a search and when they click the search button the view below navigates to the employeeSearch page. This does work and return the data and view I need it to here.
define(['require', 'durandal/plugins/router', 'durandal/app', 'config', 'services/logger'],
function(require, router, app, config, logger) {
var goBack = function() {
router.navigateBack();
};
function details(employee) {
var url = '#/employee/' + employee.Id + '/profile';
router.navigateTo(url);
}
var vm = {
goBack: goBack,
employees: ko.observable(),
details: details,
};
return {
activate: function (route) {
var self = this;
return self.getEmployees(route.q);
},
getEmployees: function (query) {
return $.ajax(app.url('/employees?q=' + query),
{
type: "GET",
contentType: 'application/json',
dataType: 'json',
}).then(querySucceeded).promise;
function querySucceeded(result) {
self.employees = result;
logger.log(query + ' Search Activated!', null, 'employeeSearch', true);
}
},
};
});
So then, if I try to search for another name, the url will navigate, the logger will show the value I searched for, however the view itself will not show the new results. Exploring in the Chrome debugger, I view the employees object to contain the new result set, but the view has still not updated. If I refresh the page however, the view does properly show up I have viewed multiple questions on here with similar issues about keeping your data calls in the activate method, make sure the data returns a promise before completing the activate method, and putting DOM manipulation in the viewAttached.
Javascript is not rendering in my SPA
How to use observables in Durandal?
HotTowel: Viewmodel lose mapping when navigating between pages
After putting those practices in my code, I am still having problems getting the view to update correctly.
Are there any other Durandal/Knockout properties I need to be aware of? How can I get the bindings to update every time I navigate to a view with (router.navigateTo(url);). I have set my shell properties (cacheViews: true) to true and false but nothing seems to change.
This is one of many problems I have been having with building SPA apps with Durandal. Trying not to give up yet.
I cant test this quick but a think you handle the observable wrong.
I suspect the "result" var is an array of employees. In this case you might handle this with an observableArray (http://knockoutjs.com/examples/collections.html)
And you cant set the value directly like self.employees
You must call the observable function to set the value like
function querySucceeded(result) {
self.employees(result)
logger.log(query + ' Search Activated!', null, 'employeeSearch', true);
}
In your samples you have not shown/mentioned where knockout is being loaded. If you are using Durandal 2.0 then add the following line to the top of your main.js file above your existing define statement
define('knockout', ko);