I installed hottowel. Modified Shell.html as below
<section id="content" >
<!--ko compose: {view: 'master'} --><!--/ko-->
</section>
In master.html file i call another html file called search.html
<section >
<div id="SearchDetails">
<section>
<!--ko compose: {view: 'search'} --><!--/ko-->
</section>
</div>
It loads search.html fine. But it is not calling my restservice which i have written search.js as below
define(['services/dataservice'], function (dataservice) {
var productVersionData = ko.observableArray();
var initialized = false;
var vm = {
activate: activate,
productVersionData: productVersionData
};
return vm;
function activate() {
alert(" i am hit");
if (initialized) { return; }
initialized = true;
return refresh();
}
function refresh() {
return dataservice.getProductVersionPartials(productVersionData);
}
});
In my shell.js i am calling all view models like below
define(['durandal/system', 'durandal/plugins/router', 'services/logger'],
function (system, router, logger) {
var shell = {
activate: activate,
router: router
};
return shell;
//#region Internal Methods
function activate() {
return boot();
}
function boot() {
router.mapNav('master');
router.mapNav('search');
log('Hot Towel SPA Loaded!', null, true);
return router.activate('master');
}
function log(msg, data, showToast) {
logger.log(msg, data, system.getModuleId(shell), showToast);
}
});
I put debugger statement, console.log statements inside search.js but it is not somehow loading search.js. That is why it is not calling my service defined in dataservice.
What could be issue?
Your problem is the change you made in the shell.html file within the content section. Make the following change and your 404 should go away:
<section id="content" class="main container-fluid">
<!--ko compose: {model: 'viewmodels/master', activate: true} -->
<!--/ko-->
</section>
I'm not familiar with the config.js pattern you're using, but I would think you'd want to go back to the using the router for dynamically setting the model parameter in shell.html and configure the activated route in the shell.js boot function that comes with the Hot Towel template.
Related
Following code, is a very simple Firebase - VueJS app, (codeSandBox demo)
app.vue
<template>
<div class="container">
<!-- Adding Quote -->
<add-quote/>
<!-- Display Quotes -->
<quote-list/>
</div>
</template>
<script>
import addQuote from "./components/AddQuote.vue";
import quoteList from "./components/QuoteList.vue";
export default {
components: {
addQuote,
quoteList
},
methods: {
get_allQuotes: function() {
// var vm = this;
var localArr = [];
quotesRef
.once("value", function(snapshot) {
snapshot.forEach(function(snap) {
localArr.push({
key: snap.key,
category: snap.val().category,
quoteTxt: snap.val().quoteTxt
});
});
})
.then(data => {
this.$store.commit("set_allQuotes", localArr);
});
}
},
mounted() {
this.get_allQuotes();
console.log("App: mounted fired");
}
};
</script>
store.js(vuex store)
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
quotesList: []
},
getters: {
get_quotesList(state) {
return state.quotesList;
}
},
mutations: {
set_allQuotes(state, value) {
state.quotesList = value;
}
}
});
AddQuote.vue
<template>
<div class="row quote-edit-wrapper">
<div class="col-xs-6">
<textarea v-model.lazy="newQuoteTxt"
rows="4"
cols="50"></textarea>
<button #click="addQuote">Add Quote</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
newQuoteTxt: '',
}
},
computed: {
allQuotes() {
return this.$store.getters.get_quotesList;
},
newQuoteIdx() {
var localArr = [...this.allQuotes]
if(localArr.length > 0) {
var highestKEY, currKEY
localArr.forEach((element, idx) => {
currKEY = parseInt(element.key)
if(idx == 0) {
highestKEY = currKEY
} else {
if(highestKEY < currKEY) {
highestKEY = currKEY
}
}
})
return highestKEY + 1
} else {
return 1
}
}
},
methods: {
// ADD new Quote in DB
addQuote: function() {
var vm = this
var localArr = [...this.allQuotes]
//1. First attach 'value' event listener,
// Snapshot will contain data from that ref
// when any child node is added/updated/delete
quotesRef.on('value', function (snapshot) {
snapshot.forEach(function(snap) {
var itemExists = localArr.some(function (item, idx) {
return item.key == snap.key
})
// If newly added item doesn't yet exists then add to local array
if (!(itemExists)) {
localArr.push({
key: snap.key,
category: snap.val().category,
quoteTxt: snap.val().quoteTxt })
vm.$store.commit('set_allQuotes', localArr)
}
})
})
//2. Second set/create a new quotes in Firebase,
// When this quote gets added in Firebase,
// value event (attached earlier) gets fired
// with
var newQuoteRef = quotesRef.child(this.newQuoteIdx)
newQuoteRef.set({
category: 'motivation',
quoteTxt: this.newQuoteTxt
})
}
}
}
</script>
quoteList.vue
<template>
<div class="row">
<div class="col-xs-12 quotes-list-wrapper">
<template v-for="(quote,idx) in allQuotes">
<!-- Quote block -->
<div class="quote-block-item">
<p class="quote-txt"> {{quote.quoteTxt}} </p>
</div>
</template>
</div>
</div>
</template>
<script>
export default {
computed: {
allQuotes() {
return this.$store.getters.get_quotesList;
}
}
}
</script>
Note: The main code of concern is of addQuote.vue
User enter newQuoteTxt that gets added to Firebase (addQuote()) as a quote item under quotesRef. As soon as quote is added (on firebase), Firebase client side SDK's value event fires, and adds the new quote (via callback) to localArray (allQuotes). VueJS then updates the DOM with newly added Quote.
The addQuote() method works in the following manner:
First, attach a callback/listener to 'value' event on quotesRef
quotesRef.on('value', function (snapshot) {
....
})
Next, A firebase ref (child of quotesRef) is created with a ID this.newQuoteIdx
var newQuoteRef = quotesRef.child(this.newQuoteIdx)
Then set() is called (on this newly created Ref) adding newquote to firebase RealTime DB.
value event gets triggered (attached from step 1) and listener /callback is called.
The callback looks for this new quote's key in existing list of items by matching keys of localArr and snap.key, if not found, adds the newly quote to localArr. localArr commits to a vuex store.
`vm.$store.commit('set_allQuotes', localArr)`
VueX then updates all subscriber component of this array. VueJS then adds the new quote to the existing list of quotes (updates the DOM)
While debugging the addQuote method, the problem I notice, the execution/flow of script (via F8 in chrome debugger) first steps into the listener/callback attached to value event before the code newQuoteRef.set({ ... }) that adds new quote (on firebase), which in turn will cause 'value' event to trigger.
I am not sure why this occurs. Can anybuddy explain why the listener/callback is called before the quotes is created.
Are child nodes (of QuotesRef) are cached at clientside such that 'value' fires even before new quote is added.
Thanks
If I correctly understand your question (Your code is not extremely easy to follow! :-)) it is the normal behaviour. As explained in the documentation:
The value event will trigger once with the initial data stored at
this location, and then trigger again each time the data
changes.
Your sandbox demo does not actually shows how the app works, but normally you should not set-up the listener in the method that saves a new node to the database. These two things should be decoupled.
One common approach is to set the listener in the created hook of a component (see https://v2.vuejs.org/v2/guide/instance.html#Instance-Lifecycle-Hooks and https://v2.vuejs.org/v2/api/#created) and then in your addQuote method you just write to the database. As soon as you write, the listener will be fired.
When running helper brings values are stored in the variable verCandidatos.postulados.
Once I get me the information I need to get a document that is linked (using the function ng-init="candidato = la postulado.candidato()) wich runs on the helper from file: collection.js.
Sometimes the html shows the properties: {{candidato.nombre}}, {{candidato.apellidos}} and {{candidato.sexo}} correctly, and sometimes appear empty, why?
Is very strange, like a bug or something. How is possible that behavior?
The information is being obtained, because the ng-repeat works and shows elements.
Below is the publishComposite(), collection.js, html and js client
html client
my-app/imports/ui/components/vacantes/verCandidatos/ verCandidatos.html
<div ng-repeat="postulado in verCandidatos.postulados">
<div ng-init="candidato = postulado.candidato();">
{{candidato.nombre}}
{{candidato.apellidos}}
{{candidato.sexo}}
</div>
</div>
js in client
my-app/imports/ui/components/vacantes/verCandidatos/ verCandidatos.js
imports ...
class VerCandidatos {
constructor($scope, $reactive, $stateParams) {
'ngInject';
$reactive(this).attach($scope);
this.vacanteId = $stateParams.vacanteId;
this.subscribe('vacantes.candidatosOseleccionados', ()=> [{vacanteId: this.vacanteId}, {estado: 1}]);
this.helpers({
postulados (){
return Postulaciones.find();
}
});
}
}
collection.js
my-app/imports/api/postulaciones/ collection.js
imports...
export const Postulaciones = new Mongo.Collection('postulaciones');
Postulaciones.deny({...});
Postulaciones.helpers({
candidato(){
return Candidatos.findOne({_id: this.candidatoId});
}
});
publish.js:
my-app/imports/api/vacantes/server/ publish.js
imports...
if (Meteor.isServer) {
Meteor.publishComposite('vacantes.candidatosOseleccionados', function (vacanteId, estado) {
const selector = {$and: [estado, vacanteId]};
return {
find: function () {
return Postulaciones.find(selector);
},
children: [
{
find: function (postulacion) {
return Candidatos.find({_id: postulacion.candidatoId}, {
fields: {
nombre: 1,
apellidos: 1,
sexo: 1,
}
});
}
}
]
};
});
}
Any ideas?
- Thanks,
The ISSUE was in html
The solution was deteted ng-init and call directly the helpers inside collection.js, the other files (js in client, collection.js, publish.js) aren't modify.
The html file is as follows:
<div ng-repeat="postulado in verCandidatos.postulados">
{{postulado.candidato().nombre}}
{{postulado.candidato().apellidos}}
{{postulado.candidato().sexo}}
</div>
Thanks for read.
And I hope you will be useful.
I have custom polymer component that will load my translations for whole application. Here is the code:
<link rel="import" href="../polymer/polymer-expressions/polymer-expressions.html">
<link rel="import" href="../polymer/core-ajax/core-ajax.html">
<polymer-element name="nz-i18n" hidden>
<script type="text/javascript">
Polymer('nz-i18n', {
/**
* our messages container
*/
messages: {},
/**
* what loading is in progress
*/
loading: {},
created: function() {
var self = this;
// create new expression, to be used for translate method
PolymerExpressions.prototype.$$ = function(key, params) {
// IMPORTANT !!! the scope here is the element we call this function from
// set this element as parent of the translator
self.parent = this;
// get translation
return self.translateMessage(key, params);
};
// restore loaded messages from local storage
//this.restoreFromLocalStorage();
},
/**
* Load messages from local storage
*/
restoreFromLocalStorage: function() {
// check if we have translations already loaded
try {
if (translations = localStorage.getItem('nz-messages')) {
// convert JSON string representation to object
this.messages = JSON.parse(translations);
return true;
}
} catch (e) {
// nothing to do
// we will load translations on demand
}
},
/**
* Translate message by given key and additional parameters
*
* IMPORTANT !!!do not use translate as the method name
* there is such a property in the element
*
* #param key - key to be translated
* #param params - additional parameters
*/
translateMessage: function(key, params) {
// set default parameters if not defined
if (!params || params == 'undefined') {
var params = {};
}
if (!params.module) {
params.module = 'System';
}
var msg;
if (this.messages[params.module]) {
// module messages are already loaded
try {
// try to get translation
msg = this.messages[params.module].messages[key] || key;
// key with multiple forms has been provided
if (typeof(msg) == "object") {
if (params.n != '' && params.n != 'undefined') {
//get index if the translation in function of the rules
eval('idx = ' + this.messages[params.module].pluralRules.replace('n', params.n) + ';');
msg = msg[idx] || key;
} else {
msg = msg[0];
}
}
} catch (e) {
//no translation - return the key
msg = key;
}
} else {
// module messages are not loaded
// start loading
this.loadTranslations(params.module);
// this will be processed very customly
msg = '';
}
return msg;
},
/**
* Load messages for the module requested
*
* #param module - messages module
* #param force - if we have to force loading even if
* messages for the module are already loaded
*/
loadTranslations: function(module, force) {
// set defaults
if (!module) {
module = 'System';
}
// check if translations for this module are loaded
// or if loading is in progress
if (!this.loading[module] && (force || !this.messages[module])) {
// noooooooo - we will load them
this.loading[module] = true;
// create ajax request
ajax = document.createElement('core-ajax');
ajax.auto = true;
ajax.method = 'GET';
ajax.handleAs = 'json';
ajax.headers = {
"Accept": "application/json",
"Content-type": "application/json"
};
ajax.url = window.basePath + 'api/translations';
ajax.params = {"module": module};
// register event listeners for the response and post response processing
ajax.addEventListener('core-response', this.handleResponse);
ajax.parent = this;
// do not uncomment this - http://xhr.spec.whatwg.org/
//ajax.xhrArgs = {sync: true};
}
},
/**
* Process messages loading request
*/
handleResponse: function() {
// IMPORTANT !!!! core-ajax scope is here
if (this.response) {
for (module in this.response) {
// add current response to the translations
this.parent.messages[module] = this.response[module];
// remove loading flag for this module messages
delete this.parent.loading[module];
}
// set translations in local storage
localStorage.setItem('nz-messages', JSON.stringify(this.parent.messages));
}
}
});
</script>
</polymer-element>
I have also another element that will be used as a frameset and will host all my other application elements:
<link href="../../polymer/core-header-panel/core-header-panel.html" rel="import">
<link href="../../polymer/core-toolbar/core-toolbar.html" rel="import">
<polymer-element name="nz-frameset">
<template>
<link href="nz-frameset.css" rel="stylesheet">
<core-header-panel flex>
<!-- HEADER -->
<core-toolbar justify="between">
<img id="logo" src="../../images/logo.png" />
<div id="title">{{ $$('header_title') }}</div>
</core-toolbar>
<!-- CONTENT -->
<div class="content">{{ $$('num', {n: 4}) }}</div>
</core-header-panel>
<!-- FOOTER -->
<core-toolbar bottomJustify="around">
<footer class="bottom">
{{ $('footer') }}
</footer>
</core-toolbar>
</template>
<script type="text/javascript">
Polymer('nz-frameset', {
ready: function() {
},
});
</script
</polymer-element>
And here is my body(all imports needed are in the HEAD):
<body fullbleed vertical layout unresolved>
<!-- INITIALIZE TRANSLATOR -->
<nz-i18n></nz-i18n>
<!-- LOAD FRAMESET -->
<nz-frameset flex vertical layout></nz-frameset>
</body>
The problem is that when I open my APP for the first time and no translations are loaded yet, after I update my messages container the expressions does not re-bind and i can not see any text. On refresh(messages are in the local storage already), everything works like a charm.
Any help? Thanks!
One issue that I saw right from the get-go is that the expressions will only be evaluated once since there is no observable value in it, e.g. the observer doesn't see a variable reference and can detect changes.
This might be a hack, but I would pass a "changeable variable" and filter it in the expression, e.g.
<div id="title">{{ n_translate | $$('header_title') }}</div>
Now you need to fire an event whenever you load a new translation, in your handle handleResponse just add:
this.fire("translationChanged");
In every module that uses translations, you need to add a event observer:
this.addEventListener("translationChanged", function() {
this.n_translate = new Date(); // some changed value
});
There is no easy way to re-trigger polymer expressions, actually I don't know of any other than little hacks (for template lists, etc) that in the end cause more problems then help.
I know this is long, but it appears something specific in what I'm doing in the more complex scenario is causing the issue. All simple examples I attempt work fine.
I have an application setup using angularjs and laravel four.
The home page is rendered via laravel routing:
Route::get('/', function() {
return View::make('app');
});
app.php returns the website skeleton with the following structure:
<!doctype html>
<html lang="en" ng-app="app">
<head>
<script src="/js/angular.js"></script>
<script src="/js/angular-sanitize.js"></script>
<script src="/js/angular-ui-router.js"></script>
<script src="/js/app.js"></script>
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top" ng-controller="NavController">
<div class="navbar-inner">
<div class="container-fluid">
<button class="button pull-right" ng-click="logout()">Logout</button>
<p class="navbar-text pull-right" >
Logged in as {{ currentUser.email }} id is : {{ currentUser.userId }}
</p>
</div>
</div>
</div>
</div>
<div class="row-fluid offset05">
<div id="view" ng-view></div>
</div>
</div>
</body>
</html>
basics of app.js:
var app = angular.module("app", ['ngSanitize','ui.state','ui.bootstrap']);
the user is initially routed to a login template, at which point a LoginController updates the user information which resides in UserService.
app.controller("LoginController", function($scope,$rootScope, $location, AuthenticationService, UserService) {
$scope.credentials = { email: "", password: "" };
$scope.login = function() {
AuthenticationService.login($scope.credentials).success(function() {
$location.path('/home');
});
};
});
Authentication Service updates the UserService variable appropriately:
app.factory("AuthenticationService", function($rootScope, $http, $sanitize, SessionService, FlashService, CSRF_TOKEN, UserService) {
return {
login: function(credentials) {
var login = $http.post("/auth/login", sanitizeCredentials(credentials));
login.success(function(data){
UserService.currentUser = data.user;
});
return login;
}
});
NavController (controller for navigation bar seen above) binds its scope to the UserService.currentUser.
app.controller("NavController",function($scope, UserService, AuthenticationService,$location){
$scope.currentUser = UserService.getCurrentUser();
});
Relevant parts of UserService:
app.factory("UserService", function($http){
var _currentUser = {}
return{
currentUser: _currentUser,
getCurrentUser: function() {
return _currentUser;}
};
});
When the user logs in, their user email and userid should appear in the navigation bar.
If I create an example which strictly uses javascript/html there are no issues with the binding.
With the mechanics/structure mentioned above, the navbar does not respond to the changes in the UserService current user variable until the entire page is reloaded.
After the user logs in, I can verify that the UserController and UserService both update the currentUser appropriately. In spite of this, the NavController will not reflect the updated user unless I reload the whole page.
I assume this is becuase the NavController is now re-running with the updated information, but why isn't normal binding working?
I guess this has something to do with the fact that the navigation bar is loaded via php.
My question is how can I either:
a) make the binding via the service work appropriately
or
b) reload the NavController when necessary (post login/logout)
There are a couple ways you can handle this.
You can bind your $scope to the service itself, in which case any changes to that model will be picked up automatically
You can observe changes to the service using $watch
In this example, you can see both techniques: (http://plnkr.co/edit/bhBXMr?p=preview):
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope, AuthenticationService, UserService) {
$scope.user = UserService;
$scope.login = function(){
AuthenticationService.login();
}
// watch the service for changes to currentUser
$scope.$watch(function(){
return UserService.currentUser;
}, function(currentUser){
$scope.currentUser = currentUser;
}, true);
});
app.service('AuthenticationService', function($http, UserService){
return {
login: function(){
$http.get('data.json').success(function(data){
UserService.currentUser = data;
});
}
};
});
app.service('UserService', function($rootScope){
return {
currentUser: null
};
});
HTML Markup:
<body ng-controller="MainCtrl">
<button ng-click="login()">Login</button>
<div>Current User: {{user.currentUser}}</div>
<!-- from the watch -->
<div>Current User: {{currentUser}}</div>
</body>
I have this iframe working with basic JavaScript:
<iframe id="upload_iframe" name="upload_iframe" onLoad="uploadDone();"></iframe>
Which triggers the method uploadDone(); when the content of the iframe has been loaded.
How do I do the same thing in Angular?. I want to call a function on the controller when the iframe loads, but I haven't seen a ng-onload so far.
Commenting on a year old question.
For cases where there are more than 1 iframes, I needed to bind "onload" events on to.
I did this approach.
Directive
APP.directive('iframeOnload', [function(){
return {
scope: {
callBack: '&iframeOnload'
},
link: function(scope, element, attrs){
element.on('load', function(){
return scope.callBack();
})
}
}}])
Controller
APP.controller('MyController', ['$scope', function($scope){
$scope.iframeLoadedCallBack = function(){
// do stuff
}
}]);
DOM
<div ng-controller="MyController">
<iframe iframe-onload="iframeLoadedCallBack()" src="..."></iframe>
</div>
try defining the function within controller as:
window.uploadDone=function(){
/* have access to $scope here*/
}
For anyone using Angular 2+,
It's simply:
<iframe (load)="uploadDone()"></iframe>
No global function, works with multiple iframe.
For anyone ending up here, the ng-onload plugin is the perfect solution to this issue. It doesn't pollute the global namespace and doesn't require you to create one-off directives when all you want is to call a simple scope function.
for those that inject the iframe dynamically, you could do the following
html...
<div #iframeContainer></div>
ts...
#Component({
selector: 'app-iframe-onload',
templateUrl: './iframe-onload.component.html'
})
export class IframeOnload implements AfterViewInit {
#ViewChild('iframeContainer') iframeContainer: ElementRef;
ngAfterViewInit(): void {
this.injectIframe();
}
private injectIframe(): void {
const container = this.iframeContainer.nativeElement;
const iframe = document.createElement('iframe');
iframe.setAttribute('width', '100%');
iframe.setAttribute('src', 'https://example.com/');
iframe.setAttribute('height', 'auto');
iframe.setAttribute('frameBorder', '0');
iframe.addEventListener('load', this.iframeOnLoadtwo);
container.appendChild(iframe);
}
public iframeOnLoadtwo(): void {
console.log('iframe loaded...');
}
}