I'm having issues with my ember app when i build it on production, using ember serve all components work beautifully, but when I deploy it in my Digital Ocean droplet with Ubuntu 16.04 the app crashes with one component.
Here you had the code of the component that crashes:
import Ember from 'ember';
import pagedArray from 'ember-cli-pagination/computed/paged-array';
export default Ember.Component.extend({
init(){
this._super(...arguments);
this.send('fillQuestions');
},
didDestroyElement(){
this.send('reset');
},
last: false,
toggleModal: false,
aciertos: 0,
errores: 0,
contenido: Ember.computed('questions', function () {
let i = 0,
content = [],
contenido = [];
for (i; i < this.get('questions.length'); i++) {
content.push(this.get('questions.' + i + '.id_question_group.questions'));
}
contenido = [].concat.apply([], content);
return contenido;
}),
count: 0,
page: 1,
perPage: 1,
pagedContent: pagedArray('contenido', {
pageBinding: "page",
perPageBinding: "perPage"
}),
totalPagesBinding: "pagedContent.totalPages",
progressValue: 0,
color: Ember.computed('count', function () {
return new Ember.String.htmlSafe("width: " + this.get('progressValue') + "%;");
}),
actions: {
...(all my actions)
}
});
Inside my view I had this:
{{#if exam.show_progressbar}}
<div class="progress-bar progress-bar-info" role="progressbar" aria-valuenow={{progressValue}} aria-valuemin="0" aria-valuemax="100" style={{color}}>
<span>{{progressValue}}%</span>
</div>
{{/if}}
{{#if exam.show_time}}
{{countdown-timer autoStart='false' startTime=exam.duration action='saveresponse'}}
{{/if}}
{{#each pagedContent as |result index|}}
<div class="text-center">
<p>{{result.questionID.description}}</p>
<br/><br/>
</div>
<form {{action 'saveresponse' on="submit"}}>
{{radio-group group=result.questionID.temp elements=result.questionID.rows action="updateValues" nombre=result.questionID.description}}
<br/>
<div class="row">
<div class="column text-left">
<button type="button" {{action 'showAlert'}} class="btn btn-primary">
Cancelar
</button>
</div>
</div>
{{#if last}}
<div class="row">
<div class="col-md-12 col-sm-12 col-xs-12 text-right">
<div class="column text-right">
<button type="submit" class="btn btn-primary">
Guardar examen
</button>
</div>
</div>
</div>
{{/if}}
</form>
{{/each}}
<div class="pager">
{{page-numbers content=pagedContent currentPage=page action='checkLast'}}
</div>
The error its in the next components: {{countdown-timer}} and {{radio-group}}
The countdown-timer is a component based on ember-cli-timer which counts from a set time to 0 and the radio-group component only had inside a radio-button helper.
Any ideas of why in production is not working and locally it's working?
UPDATE 03-23-2017
Using the chrome developer tools I've got this error, maybe this will explain a little more my problem.
Property set failed: You passed an empty path
UPDATE 04-24-2017
I just had found the exact error in the component, it's the next action:
actions: {
...
fillQuestions(){
let questions = this.get('contenido'); //Filled with exam questions
for(let i = 0; i < questions.length; i++){
if(questions[i].questionID !== null && questions[i].questionID !== undefined){
console.log(questions[i].questionID.description); //consoles the description of the question
this.set(questions[i].questionID.description, ''); //here it's the problem.
}
}
}
...
}
The line with this.set() its making the problem, and it's because questions[i].questionID.description it's the empty path it's there a way to create new properties in the component with an action of the same?
I finally found the error, the error was displayed when the component tried to set the new property.
The real problem was:
when I attempted to save the property of the question some questions got "." and it seems to be that Ember.computed does not allow to use them for a property, so when I tried this.set('some.question.with.dots', '') the error was triggered and display the Property set failed.
The solution was simple, I only had to use .replace() function of javascript to replace all the dots in the string.
The final code is next:
actions: {
...
fillQuestions(){
let questions = this.get('contenido'); //Filled with exam questions
for(let i = 0; i < questions.length; i++){
if(questions[i].questionID !== null && questions[i].questionID !== undefined){
let desc = questions[i].questionID.description.replace(/\./g,'');
this.set(desc, ''); //the problem has gone
}
}
}
...
}
Thank to all of the people that support me with they points of view.
Related
Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 10 months ago.
Improve this question
I am working on a simple "Green Light, Red light" game using Angular, and I am storing players with their score and maxScore using localStorage.
I can already read each property from the array stored in the localStorage, but now I am stuck on modifying those values once I click a button.
This is the test array I am currently working with:
[{"name":"test1","score":3,"maxScore":8},{"name":"test2","score":10,"maxScore":22}]
This array is stored with a single key named "players", so it is an array of players.
My component looks like this:
game.component.ts
export class GameComponentComponent implements OnInit {
highScoreLS: number = this.getHighScoreData();
scoreLS: number = this.getScoreData();
highScore: number = 0;
score: number = 0;
state: string = 'RUN';
faArrowRightFromBracket = faArrowRightFromBracket;
faShoePrints = faShoePrints;
constructor() {}
ngOnInit(): void {}
addPoint() {
this.score++;
if (this.score > this.highScore) {
this.highScore = this.score;
}
this.changeHighScore();
this.changeScore();
}
removePoint() {
this.score--;
if (this.score < 0) {
this.score = 0;
}
this.changeHighScore();
this.changeScore();
}
changeState() {
if (this.state === 'RUN') {
this.state = 'PAUSE';
} else {
this.state = 'RUN';
}
}
getScoreData() {
let localStorageItem = JSON.parse(localStorage.getItem('players') || '[]');
let item = localStorageItem.find(
(item: { name: string }) => item.name === 'test1'
);
let sc = item.score;
return sc;
}
getHighScoreData() {
let localStorageItem = JSON.parse(localStorage.getItem('players') || '[]');
let item = localStorageItem.find(
(item: { name: string }) => item.name === 'test1'
);
let hs = item.maxScore;
return hs;
}
changeHighScore() {
let localStorageItem = JSON.parse(localStorage.getItem('players') || '[]');
let item = localStorageItem.find(
(item: { name: string }) => item.name === 'test1'
);
item.maxScore = this.highScore;
localStorage.setItem('players', JSON.stringify(item));
}
changeScore() {
let localStorageItem = JSON.parse(localStorage.getItem('players') || '[]');
let item = localStorageItem.find(
(item: { name: string }) => item.name === 'test1'
);
item.score = this.score;
localStorage.setItem('players', JSON.stringify(item));
}
}
And the html looks like this:
game.component.html
<div class="navbar navbar-dark bg-dark">
<div class="container">
<h2>Hi! 👋</h2>
<a class="navbar-brand" routerLink=""
><fa-icon [icon]="faArrowRightFromBracket"></fa-icon
></a>
</div>
</div>
<div class="container flex vh-100">
<div class="row m-3">
<h3>HIGH SCORE: {{ highScoreLS }}</h3>
</div>
<div class="row m-3">
<div class="card p-3">
<h3>{{ state }}</h3>
</div>
</div>
<div class="row m-3">
<h3>SCORE: {{ scoreLS }}</h3>
</div>
<div class="row m-3">
<div class="col">
<button class="btn btn-outline-success" (click)="addPoint()">
<fa-icon [icon]="faShoePrints"></fa-icon>
Left
</button>
<button class="btn btn-outline-success" (click)="removePoint()">
Right
<fa-icon [icon]="faShoePrints"></fa-icon>
</button>
</div>
</div>
</div>
The problem is, when I click the button to add or remove a point, it deletes the whole array of players, and creates a new one like the following:
{"name":"test1","score":0,"maxScore":1}
I have been working for a couple of days with localStorage so I do not know exactly what I am missing or what I am doing wrong.
My idea is to edit those values, score and maxScore, but I can't figure it out how.
EDIT
The first time I click on add a point, it edits only the maxScore, but once. The next time I click, it gives me this error:
ERROR TypeError: localStorageItem.find is not a function
at GameComponentComponent.changeScore (game-component.component.ts:83:33)
at GameComponentComponent.addPoint (game-component.component.ts:34:10)
You're calling localStorage.setItem with just the single item and not the whole array so every subsequent "finds" you're trying will fail.
Try this instead:
localStorage.setItem('players', JSON.stringify(localStorageItem));
Though I have to say, there's loads of duplicate code in just that one component. You should read some articles on data structures and state management.
This is my first experience with Meteor and Dragula.
I'm trying to do a dynamic list, with database data.
What I need is:
To mount the 'divs' with the data from database
To become these 'divs' draggable
"To insert" some itens "inside" these 'divs' (from database to)
To become these itens draggable, inside the 'divs' and between other 'divs'
To resume, is something like columns and cards in Trello.
I'm using Dragula and, first, I saw the documentation here, and this and this examples.
To solve the numbers 1 and 2, I'm trying this code.
And, the situation now is: the 'drake' contain the array with lists name, but I can't 'transform' them in 'divs'.
The result in the log is:
"element: (3) [{…}, {…}, {…}]
main.js:36 counter: 3
main.js:39 i: 0
main.js:41 listName: Lista 1
main.js:39 i: 1
main.js:41 listName: Lista 2
main.js:39 i: 2
main.js:41 listName: Lista 3
main.js:45 dragula: {containers: Array(3), start: ƒ, end: ƒ, cancel: ƒ, remove: ƒ, …}
containers: (3) ["Lista 1", "Lista 2", "Lista 3"]"
Can you help me ?
Thank you a lot.
PS1: the "draglist template" is only to show that Dragula is working.
PS2: sorry for English mistakes.
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import dragula from 'dragula';
import '../node_modules/dragula/dist/dragula.css';
import './main.html';
Listas = new Mongo.Collection('listas');
Tarefas = new Mongo.Collection('tarefas');
console.log("Before enter drag");
Template.dragList.onRendered(function(){
console.log("entrou no onRendered");
dragula([document.querySelector('#left1'), document.querySelector('#right1')]);
});
Template.lists.helpers({
'list': function(){
return Listas.find({});
},
'tasks': function(){
return Tarefas.find({});
},
'mount': function(){
console.log("Inside mount");
var drake = dragula({});
var element = Listas.find({}).fetch();
var counter = Listas.find({}).count();
console.log("element: ", element);
console.log("counter: ", counter);
var i;
for (i = 0; i < counter; i++) {
console.log("i: ", i);
var listName = element.[i].nome;
console.log("listName: ", listName);
drake.containers.push(listName);
// dragula([document.getelemententById(listName)]);
};
console.log("dragula: ", drake);
},
});
<head>
<title>Dragula test</title>
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
</head>
<body>
<h1>Welcome to Meteor!</h1>
<h1>Draglist</h1>
{{> dragList}}
<br>
<h1>Lists</h1>
{{> lists}}
<br>
</body>
<template name="dragList">
<h5 class="card-title">Container 1</h5>
<div id="left1">
<p class="card-text">This is a draggable p</p>
<button class="btn btn-primary">First draggable button</button>
<button class="btn btn-primary">Second draggable button</button>
</div>
<h5 class="card-title">Container 2</h5>
<div id="right1">
<p class="card-text">This is another draggable p</p>
<button class="btn btn-primary">Third draggable button</button>
<button class="btn btn-primary">Fourth draggable button</button>
</div>
</template>
<template name="lists">
<div id=container>
{{mount}}
</div>
</template>
It is not like it is slow on rendering many entries. The problem is that whenever the $scope.data got updated, it adds the new item first at the end of the element, then reduce it as it match the new $scope.data.
For example:
<div class="list" ng-repeat="entry in data">
<h3>{{entry.title}}</h3>
</div>
This script is updating the $scope.data:
$scope.load = function() {
$scope.data = getDataFromDB();
}
Lets say I have 5 entries inside $scope.data. The entries are:
[
{
id: 1,
title: 1
},
{
id: 2,
title: 2
},
......
]
When the $scope.data already has those entries then got reloaded ($scope.data = getDataFromDB(); being called), the DOM element for about 0.1s - 0.2s has 10 elements (duplicate elements), then after 0.1s - 0.2s it is reduced to 5.
So the problem is that there is delay about 0.1s - 0.2s when updating the ng-repeat DOM. This looks really bad when I implement live search. Whenever it updates from the database, the ng-repeat DOM element got added up every time for a brief millisecond.
How can I make the rendering instant?
EDITED
I will paste all my code here:
The controller:
$scope.search = function (table) {
$scope.currentPage = 1;
$scope.endOfPage = false;
$scope.viewModels = [];
$scope.loadViewModels($scope.orderBy, table);
}
$scope.loadViewModels = function (orderBy, table, cb) {
if (!$scope.endOfPage) {
let searchKey = $scope.page.searchString;
let skip = ($scope.currentPage - 1) * $scope.itemsPerPage;
let searchClause = '';
if (searchKey && searchKey.length > 0) {
let searchArr = [];
$($scope.vmKeys).each((i, key) => {
searchArr.push(key + ` LIKE '%` + searchKey + `%'`);
});
searchClause = `WHERE ` + searchArr.join(' OR ');
}
let sc = `SELECT * FROM ` + table + ` ` + searchClause + ` ` + orderBy +
` LIMIT ` + skip + `, ` + $scope.itemsPerPage;
sqlite.query(sc, rows => {
$scope.$apply(function () {
var data = [];
let loadedCount = 0;
if (rows != null) {
$scope.currentPage += 1;
loadedCount = rows.length;
if (rows.length < $scope.itemsPerPage)
$scope.endOfPage = true
for (var i = 0; i < rows.length; i++) {
let item = rows.item(i);
let returnObject = {};
$($scope.vmKeys).each((i, key) => {
returnObject[key] = item[key];
});
data.push(returnObject);
}
$scope.viewModels = $scope.viewModels.concat(data);
}
else
$scope.endOfPage = true;
if (cb)
cb(loadedCount);
})
});
}
}
The view:
<div id="pageContent" class="root-page" ng-controller="noteController" ng-cloak>
<div class="row note-list" ng-if="showList">
<h3>Notes</h3>
<input ng-model="page.searchString" id="search"
ng-keyup="search('notes')" type="text" class="form-control"
placeholder="Search Notes" style="margin-bottom:10px">
<div class="col-12 note-list-item"
ng-repeat="data in viewModels track by data.id"
ng-click="edit(data.id)"
ontouchstart="touchStart()" ontouchend="touchEnd()"
ontouchmove="touchMove()">
<p ng-class="deleteMode ? 'note-list-title w-80' : 'note-list-title'"
ng-bind-html="data.title"></p>
<p ng-class="deleteMode ? 'note-list-date w-80' : 'note-list-date'">{{data.dateCreated | displayDate}}</p>
<div ng-if="deleteMode" class="note-list-delete ease-in" ng-click="delete($event, data.id)">
<span class="btn fa fa-trash"></span>
</div>
</div>
<div ng-if="!deleteMode" ng-click="new()" class="add-btn btn btn-primary ease-in">
<span class="fa fa-plus"></span>
</div>
</div>
<div ng-if="!showList" class="ease-in">
<div>
<div ng-click="back()" class="btn btn-primary"><span class="fa fa-arrow-left"></span></div>
<div ng-disabled="!isDataChanged" ng-click="save()" class="btn btn-primary" style="float:right">
<span class="fa fa-check"></span>
</div>
</div>
<div contenteditable="true" class="note-title"
ng-bind-html="selected.title" id="title">
</div>
<div contenteditable="true" class="note-container" ng-bind-html="selected.note" id="note"></div>
</div>
</div>
<script src="../js/pages/note.js"></script>
Calling it from:
$scope.loadViewModels($scope.orderBy, 'notes');
The sqlite query:
query: function (query, cb) {
db.transaction(function (tx) {
tx.executeSql(query, [], function (tx, res) {
return cb(res.rows, null);
});
}, function (error) {
return cb(null, error.message);
}, function () {
//console.log('query ok');
});
},
It is apache cordova framework, so it uses webview in Android emulator.
My Code Structure
<html ng-app="app" ng-controller="pageController">
<head>....</head>
<body>
....
<div id="pageContent" class="root-page" ng-controller="noteController" ng-cloak>
....
</div>
</body>
</html>
So there is controller inside controller. The parent is pageController and the child is noteController. Is a structure like this slowing the ng-repeat directives?
Btw using track by is not helping. There is still delay when rendering it. Also I can modify the entries as well, so when an entry was updated, it should be updated in the list as well.
NOTE
After thorough investigation there is something weird. Usually ng-repeat item has hash key in it. In my case ng-repeat items do not have it. Is it the cause of the problem?
One approach to improve performance is to use the track by clause in the ng-repeat expression:
<div class="list" ng-repeat="entry in data track by entry.id">
<h3>{{entry.title}}</h3>
</div>
From the Docs:
Best Practice: If you are working with objects that have a unique identifier property, you should track by this identifier instead of the object instance, e.g. item in items track by item.id. Should you reload your data later, ngRepeat will not have to rebuild the DOM elements for items it has already rendered, even if the JavaScript objects in the collection have been substituted for new ones. For large collections, this significantly improves rendering performance.
For more information, see
AngularJS ngRepeat API Reference -- Tracking and Duplicates
In your html, try this:
<div class="list" ng-repeat="entry in data">
<h3 ng-bind="entry.title"></h3>
</div>
After thorough research, I found my problem. Every time I reset / reload my $scope.viewModels I always assign it to null / empty array first. This what causes the render delay.
Example:
$scope.search = function (table) {
$scope.currentPage = 1;
$scope.endOfPage = false;
$scope.viewModels = []; <------ THIS
$scope.loadViewModels($scope.orderBy, table);
}
So instead of assigning it to null / empty array, I just replace it with the new loaded data, and the flickering is gone.
I'm trying to wrap my head around this PubNub ChatEngine example using Angular for first time https://github.com/pubnub/chat-engine-examples/tree/master/angular/simple
In demo when you click on user from the list new channel with random name is generated and user invited to it. So if you leave chat click on user again you connect to new channel.
I'm trying to do 1-1 chat rooms, that you could leave than join back, so changed channel name to be clicked user uuid. Now if I leave the channel, join back to it and try to send message it's not being shown in list, though it's being sent and user you are chatting with receives it.
In console I'm getting "Uncaught (in promise) TypeError: Converting circular structure to JSON" when starting to type (as have chat engine typing indicator running) and after submit.
I guess that's something to do with removing chat from global scope using splice() method and after joining back new chat being added. It works in demo because there are getting new channel each time and removing it, but not if using same channel now.
I tried to use splice() instead to see what happens. If I close chat and join back to it, it stays in DOM, and new one is added to scope, so have two same chat elements in DOM. If I type and send message on second one, it's not being displayed at it, but instead on first one that tried to close.
How could I get this working properly, can splice() be used in my case and I'm missing something else that is causing the error?
$scope.leave = (index) => {
$scope.chat.leave();
$scope.chats.splice(index, 1);
//$scope.chats.slice(index, 1);
//$scope.chats.splice( $scope.chats.indexOf($scope.chat), 1 );
}
angular.module('chatApp', ['open-chat-framework'])
.run(['$rootScope', 'ngChatEngine', function($rootScope, ngChatEngine) {
$rootScope.ChatEngine = ChatEngineCore.create({
publishKey: 'pub-c-d8599c43-cecf-42ba-a72f-aa3b24653c2b',
subscribeKey: 'sub-c-6c6c021c-c4e2-11e7-9628-f616d8b03518'
}, {
debug: true,
globalChannel: 'chat-engine-angular-simple'
});
// bind open chat framework angular plugin
ngChatEngine.bind($rootScope.ChatEngine);
// set a global array of chatrooms
$rootScope.chats = [];
}])
.controller('Chat', function($scope) {
$scope.chat.plugin(ChatEngineCore.plugin['chat-engine-typing-indicator']({
timeout: 5000
}));
// every chat has a list of messages
$scope.messages = [];
// we store the id of the lastSender
$scope.lastSender = null;
// leave a chatroom and remove from global chat list
$scope.leave = (index) => {
$scope.chat.leave();
$scope.chats.splice(index, 1);
}
// send a message using the messageDraft input
$scope.sendMessage = () => {
$scope.chat.emit('message', {
text: $scope.messageDraft
});
$scope.messageDraft = '';
}
// when we get notified of a user typing
$scope.chat.on('$typingIndicator.startTyping', (event) => {
event.sender.isTyping = true;
});
// when we get notified a user stops typing
$scope.chat.on('$typingIndicator.stopTyping', (event) => {
event.sender.isTyping = false;
});
// function to add a message to messages array
let addMessage = (payload, isHistory) => {
// if this message was from a history call
payload.isHistory = isHistory;
// if the last message was sent from the same user
payload.sameUser = $scope.messages.length > 0 && payload.sender.uuid == $scope.messages[$scope.messages.length - 1].sender.uuid;
// if this message was sent by this client
payload.isSelf = payload.sender.uuid == $scope.me.uuid;
// add the message to the array
$scope.messages.push(payload);
}
// if this chat receives a message that's not from this sessions
$scope.chat.search({
event: 'message'
}).on('message', function(payload) {
// render it in the DOM with a special class
addMessage(payload, true);
})
// when this chat gets a message
$scope.chat.on('message', function(payload) {
// render it in the DOM
addMessage(payload, false);
});
})
.controller('OnlineUser', function($scope) {
// create a new chat
$scope.newChat = function(user) {
// define a channel
let chan = user.uuid;
// create a new chat with that channel
let newChat = new $scope.ChatEngine.Chat(chan);
// we need to auth ourselves before we can invite others
newChat.on('$.connected', () => {
// this fires a private invite to the user
newChat.invite(user);
// add the chat to the list
$scope.chats.push(newChat);
});
};
})
.controller('ChatAppController', function($scope) {
// create a user for myself and store as ```me```
$scope.ChatEngine.connect(new Date().getTime(), {}, 'auth-key');
$scope.ChatEngine.on('$.ready', (data) => {
$scope.me = data.me;
$scope.me.plugin(ChatEngineCore.plugin['chat-engine-random-username']($scope.ChatEngine.global));
$scope.ChatEngine.global.plugin(ChatEngineCore.plugin['chat-engine-online-user-search']());
// when I get a private invit
$scope.me.direct.on('$.invite', (payload) => {
let chat = new $scope.ChatEngine.Chat(payload.data.channel);
chat.onAny((a,b) => {
console.log(a)
});
// create a new chat and render it in DOM
$scope.chats.push(chat);
});
// bind chat to updates
$scope.chat = $scope.ChatEngine.global;
// hide / show usernames based on input
$scope.userSearch = {
input: '',
fire: () => {
// get a list of our matching users
let found = $scope.ChatEngine.global.onlineUserSearch.search($scope.userSearch.input);
// hide every user
for(let uuid in $scope.chat.users) {
$scope.chat.users[uuid].hideWhileSearch = true;
}
// show all found users
for(let i in found) {
$scope.chat.users[found[i].uuid].hideWhileSearch = false;
}
}
};
$scope.userAdd = {
input: '',
users: $scope.userAdd,
fire: () => {
if($scope.userAdd.input.length) {
$scope.userAdd.users = $scope.ChatEngine.global.onlineUserSearch.search($scope.userAdd.input);
} else {
$scope.userAdd.users = [];
}
}
};
});
});
<div class="container-fluid" ng-controller="ChatAppController">
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-block">
<h4 class="card-title">ChatEngine</h4>
<p class="card-text">Your are {{me.state.username}} with uuid {{me.uuid}}</p>
</div>
<ul id="online-list" class="list-group list-group-flush">
<li class="list-group-item" ng-repeat="(uuid, user) in chat.users" ng-hide="user.hideWhileSearch" ng-controller="OnlineUser">
{{user.state.username}}
<span class="show-typing" ng-show="user.isTyping">is typing...</span>
</li>
</ul>
<div class="card-block">
<form class="send-message" ng-submit="userSearch.fire()">
<div class="input-group">
<input id="usernameSearch" type="text" class="form-control message" placeholder="Search for Username" ng-change="userSearch.fire()" ng-model="userSearch.input">
<span class="input-group-btn">
<button class="btn btn-primary" type="submit">Search</button>
</span>
</div>
</form>
</div>
</div>
</div>
<div class="col-md-6">
<div id="chats" class="row" ng-repeat="chat in chats" ng-controller="Chat">
<div class="chat col-xs-12">
<div class="card">
<div class="card-header">
<div class="col-sm-6">
{{chat.channel}}
</div>
<div class="col-sm-6 text-right">
x
</div>
</div>
<ul class="list-group list-group-flush online-list-sub">
<li class="list-group-item" ng-repeat="(uuid, user) in chat.users" ng-hide="user.hideWhileSearch" ng-controller="OnlineUser">
{{user.state.username}}
<span class="show-typing" ng-show="user.isTyping">is typing...</span>
</li>
</ul>
<div class="card-block">
<div class="log">
<div ng-repeat="message in messages" ng-class="{'hide-username': message.sameUser, 'text-muted': message.isHistory, 'text-xs-right': !message.isSelf}">
<p class="text-muted username">{{message.sender.state.username}}</p>
<p>{{message.data.text}}</p>
</div>
</div>
<p class="typing text-muted"></p>
<form class="send-message" ng-submit="sendMessage(chat)">
<div class="input-group">
<input ng-model="messageDraft" ng-change="chat.typingIndicator.startTyping()" type="text" class="form-control message" placeholder="Your Message...">
<span class="input-group-btn">
<button class="btn btn-primary" type="submit">Send</button>
</span>
</div>
</form>
</div>
<hr />
<div class="card-block">
<h6>Add a user to this chat</h6>
<fom ng-submit="userAdd.fire()">
<div class="input-group">
<input id="usernameSearch" type="text" class="form-control message" placeholder="Add User" ng-change="userAdd.fire()" ng-model="userAdd.input">
</div>
</form>
<ul class="list-group list-group-flush online-list-sub">
<li class="list-group-item" ng-repeat="(uuid, user) in userAdd.users" ng-controller="OnlineUser">
{{user.state.username}}
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
Issue is resolved by upgrading to the latest ChatEngine version. As per the ChatEngine github repo issue:
it's working correctly with chat-engine#0.9.5, I was using chat-engine#0.8.4
Im trying to build a learning app where at the moment it needs to get the first 7 posts from a subreddit and then if that is not already in the database add it to the database. However it runs both the if and else 7 times each for some reason and I cannot figure out why. Here is the helper method:
Template.posts.helpers({
posts : function () {
Meteor.call('getPosts', "tifu", function(e, results){
var result = JSON.parse(results.content).data.children;
Session.set('postsResults', result);
});
for(var i=0; i<7; i++){
var result = Session.get('postsResults')[i].data;
if(Posts.find({r_id: result.id}).count() == 0){
console.log("if");
} else {
console.log("else");
};
};
return Posts.find();
}
});
and the html side:
<template name="posts">
<div class="col-md-12 posts-div">
{{#each posts }}
<div class="col-md-8">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">{{ title }}</h3>
</div>
<div class="panel-body">{{{ body }}}</div>
<div class="panel-footer">
<div class="col-md-2">{{ score }}</div>
<div class="col-md-2 col-md-offset-3">{{ subreddit }}</div>
<div class="col-md-2 col-md-offset-3">{{ createdBy }}</div>
<div class="clearfix"></div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="panel panel-primary">
</div>
</div>
{{/each}}
</div>
<hr>
</template>
I have replaced the insert code with simple console logs and these are the results I get:
if
if
if
if
if
if
if
else
else
else
else
else
else
else
When I run the Posts.find({r_id: result.id}).count() == 0 in the console I get false same with Posts.findOne({r_id: result.id}) == null but for some reason in javascript file it still runs the true portion and I then end up with like 50 copies of the same post which is what I am trying to avoid.
It is not optimal to use Meteor.call in a helper. The helper's function will re-run every time there is a reactive change. This is why it runs so many times.
Use the Template.onCreated callback instead:
Template.posts.onCreated(function() {
Meteor.call('getPosts', "tifu", function(e, results){
var result = JSON.parse(results.content).data.children;
Session.set('postsResults', result);
});
});
And your helper:
Template.posts.helpers({
posts : function () {
var r = Session.get('postsResults')
for(var i=0; i<7; i++){
if(!r) continue;
var result = r[i].data;
if(Posts.find({r_id: result.id}).count() == 0){
console.log("if");
} else {
console.log("else");
};
};
return Posts.find();
}
});
Second thing to keep in mind is Session.get('postsResults') will be null while the result of Meteor.call is returned, for a few hundred milliseconds. If you do Session.get('postsResults')[i].data you will get an exception.
This is why i added a conditional check to continue the loop if r is null. This way you wait for the result and the posts function will re-run and recalculate the results with the new data.