I'm trying to create a collaborative story-making app, using Angular and Firebase. Follow this link to get an idea of where I'm headed so far. You click on the "plus" icon to show a textarea, and add to the parts that are already there. I'm sure there are many ways to improve what I've coded so far, but my specific problem right now relates to switching between stories.
I have another Firebase reference with two stories, and each of the stories has different parts. To create a way to switch between stories I tried the following:
html:
<!doctype html>
<html lang="en" ng-app = "StoryApp">
<head>
<script src="https://cdn.firebase.com/v0/firebase.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angularFire/0.1.0/angularfire.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="app.js"></script>
</head>
<body>
<div class="wrapper" ng-controller="WrapperCtrl">
<div class="nav">
<a ng-repeat="story in storyList" ng-click="switchStory(story.location)" href="#" id="{{story.identity}}" style="margin-left:0.5em;">{{story.title}} Button</a>
</div>
</br>
<div class="insideWrapper">
<span class="item" id="{{part.number}}" ng-repeat="part in parts" ng-controller="ItemCtrl">{{part.text}}
<span ng-click="show=!show" style="margin-left:0.25em;color:blue;cursor:pointer;">+(Add Part Here)</span>
<form ng-show="show" ng-submit="addItem()">
<textarea ng-model="itemText" placeholder="Your story"></textarea>
<br>
<button type="submit" class="submit-button" value="submit" ng-click="show=!show">add</button>
<button ng-click="show=!show">cancel</button>
</form>
</span>
</div>
</div>
</body>
javascript:
var gApp = angular.module('StoryApp', ['firebase']);
function WrapperCtrl($scope, angularFire){
var urlStories = 'https://allstory.firebaseio.com/stories';
$scope.storyList = angularFire(urlStories, $scope, 'storyList', {});
function getStory(url){
var urlParts = url;
$scope.parts = angularFire(urlParts, $scope, 'parts', []);
}
$scope.switchStory = function(location){
getStory(location);
};
getStory('https://allstory.firebaseio.com/stories/story1/parts');
}
function ItemCtrl($scope){
$('.wrapper').on('click', '.submit-button', function(event) {
var idNum = function() {
return event.target.parentNode.parentNode.id;
};
$scope.addItem = function(){
$scope.parts.splice(Number(idNum())+1, 0, {text:$scope.itemText, number:Number(idNum())+1});
$scope.itemText = '';
reNumber();
};
function reNumber() {
var i = Number(idNum())+2, len=$scope.parts.length;
for (; i < len; i++) {
$scope.parts[i].number = i;
}
}
});
}
The above code isn't working for me. When "Story 1" or "Story 2" are clicked in the view I expected that the view would change to reflect the change in Firebase reference location (url). However, rather than the appropriate parts of the respective story appearing, nothing appears, and the parts locations (e.g. https://allstory.firebaseio.com/stories/story1/parts) for both stories are removed from my Firebase reference. My problem may be similar to this one.
I need the parts for "Story 1" or "Story 2" to appear when clicked. What can I change in my code to make this work? Should I try an entirely different approach to switching between stories?
AngularFire retrieves data from Firebase asynchronously and returns a promise, not the actual data itself. Therefore, you have a bug in your code where you're assigning the promise to the scope variable but using it before the promise has been resolved.
I would fetch both stories first before allowing the user to switch between them. For example:
function WrapperCtrl($scope, angularFire) {
$scope.showStories = false;
var urlStories = 'https://allstory.firebaseio.com/stories';
angularFire(urlStories, $scope, 'storyList', {}).then(function() {
$scope.showStories = true;
});
$scope.switchStory = function(location) {
// var name = manipulate location to extract story number or name, like "story1".
$scope.parts = $scope.storyList[name]["parts"];
}
}
Related
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 5 years ago.
Improve this question
i'm wrote some todo list app, to understand how to be more expert.
what i'm try to understand:
my problem is when user click on task to edit, because it passed by reference so if user edit task, it will change directly the task object.
(i attached my code here).
my questions:
1) in my code i wrote one way to fix it, by clone object every time.
it good practice ? if no how you recommend me to fix it?
2) because i do not want my code only work, i want to be more expert.
if you think my thinking and planning of this code is bad? how you write this app? ( i talk here only about functional, add, edit, list of task)
thanks for help :)
link to plunker: https://plnkr.co/edit/CA99iiydbD4TWaGtJZZf?p=preview
code:
HTML
<html ng-app="todos">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>
<script src="app.js"></script>
</head>
<body>
<div ng-controller="main">
<ul>
<li ng-repeat="task in todosBL.tasks" ng-click="editMode.edit(task)">{{ task.content}}</li>
</ul>
<input type="text" ng-model="task">
<input type="button" value="add task" ng-click="add(task)">
<!--//for edit-->
<div>
<input type="text" ng-model="editMode.task.content">
<input type="button" value="save task" ng-click="editMode.save(editMode.task)">
</div>
</div>
</body>
</html>
SCRIPT:
(function() {
var Task = (function() {
var counter = 0;
return function(content, isDone) {
this.id = counter++;
this.content = content;
this.isDone = isDone || false;
}
}());
var app = angular.module('todos',[])
.service('todosBL', function() {
this.tasks = [];
this.add = function(content) {
this.tasks.push(new Task(content));
}
this.update = function(editedTask) {
var i = this.tasks.findIndex(function(task){
return task.id === editedTask.id
});
this.tasks[i] = editedTask;
}
})
.controller('main', function($scope, todosBL) {
$scope.todosBL = todosBL;
$scope.add = function(task) {
todosBL.add(task);
$scope.task = null;
}
$scope.editMode = {
task: {},
edit: function(task) {
this.task = task;
//BECAUSE I PASS TASK BY REFERNCE WHEN USER EDIT TASK IT CHANGE TASK DIRECTLY ,
// IF I CLONE OBJECT EVERY TIME, IT FIX BY REFERENCE PROBLEM.
// MY QUESTION IF HAVE OTHER WAY TO SLOVE THIS. MABY OTHER WAY TO THINK ABOUT APP LIKE THIS.
// for(key in task) {
// this.task[key] = task[key];
// }
},
save: function(task) {
todosBL.update(task);
this.task = {};
}
};
});
}());
I think that your controller is over complicated. The service should implement some BL like data verification and posting it to the server and/or local storage but not searching for index, it does silly things now!
In order to satisfy all your requirements one just needs a controller:
app.controller('MainCtrl', function($scope) {
$scope.tasks = [];
$scope.add = function(content){
$scope.tasks.push(new Task(content));
$scope.content = null;
}
$scope.edit = function(task){
$scope.currentlyEditing = task;
$scope.editText = task.content;
}
$scope.save= function(){
$scope.currentlyEditing.content = $scope.editText;
$scope.editText = null;
$scope.currentlyEditing = null;
mySuperSeriousService.postToServer.then(result=> {
alert('Success');
})
}
});
and template like this:
<body ng-controller="MainCtrl">
<ul>
<li ng-repeat="task in tasks" ng-click="edit(task)">{{ task.content}}</li>
</ul>
<input type="text" ng-model="content">
<button ng-click="add(content)">Add Task</button>
<!--//for edit-->
<div>
<input type="text" ng-model="editText" ng-disabled="!currentlyEditing">
<button ng-click="save()">Save</button>
</div>
</body>
So it's 2 times shorter. Here's the plunker (https://plnkr.co/edit/nN8kd5ErSDsBu6Exm1YO)
my problem is when user click on task to edit, because it passed by reference so if user edit task, it will change directly the task object. (i attached my code here).
For solving this problem, you should make a copy of your model and change it (in edit function): this.task = angular.copy(task);
in my code i wrote one way to fix it, by clone object every time. it good practice ? if no how you recommend me to fix it?
As I said, making copy is much more logical !
because i do not want my code only work, i want to be more expert. if you think my thinking and planning of this code is bad? how you write this app? ( i talk here only about functional, add, edit, list of task)
1) I don't know why you are using an array of objects ? your tasks are just strings ! so it can be better if you use an array of strings. then you won't have the struggle with sth like editMode.task.content, you just use editMode.task !
2) Don't work with ids . cause when you add the 'Deleting Task' feature, you'll got problems ...
3) What does Task() function do ? ( In this case, you don't need sth like this)
4) ...
I have a list of checkboxes for people, and I need to trigger an event that will display information about each person selected in another area of the view. I am getting the event to run in my controller and updating the array of staff information. However, the view is not updated with this information. I think this is probably some kind of scope issue, but cannot find anything that works. I have tried adding a $watch, my code seems to think that is already running. I have also tried adding a directive, but nothing in there seems to make this work any better. I am very, very new to Angular and do not know where to look for help on this.
My view includes the following:
<div data-ng-controller="staffController as staffCtrl" id="providerList" class="scrollDiv">
<fieldset>
<p data-ng-repeat="person in staffCtrl.persons">
<input type="checkbox" name="selectedPersons" value="{{ physician.StaffNumber }}" data-ng-model="person.isSelected"
data-ng-checked="isSelected(person.StaffNumber)" data-ng-change="staffCtrl.toggleSelection(person.StaffNumber)" />
{{ person.LastName }}, {{ person.FirstName }}<br />
</p>
</fieldset>
</div>
<div data-ng-controller="staffController as staffCtrl">
# of items: <span data-ng-bind="staffCtrl.infoList.length"></span>
<ul>
<li data-ng-repeat="info in staffCtrl.infoList">
<span data-ng-bind="info.staffInfoItem1"></span>
</li>
</ul>
</div>
My controller includes the following:
function getStaffInfo(staffId, date) {
staffService.getStaffInfoById(staffId)
.then(success)
.catch(failed);
function success(data) {
if (!self.infoList.length > 0) {
self.infoList = [];
}
var staffItems = { staffId: staffNumber, info: data };
self.infoList.push(staffItems);
}
function failed(err) {
self.errorMessage = err;
}
}
self.toggleSelection = function toggleSelection(staffId) {
var idx = self.selectedStaff.indexOf(staffId);
// is currently selected
if (idx >= 0) {
self.selectedStaff.splice(idx, 1);
removeInfoForStaff(staffId);
} else {
self.selectedStaff.push(staffId);
getStaffInfo(staffId);
}
};
Thanks in advance!!
In the code you posted, there are two main problems. One in the template, and one in the controller logic.
Your template is the following :
<div data-ng-controller="staffController as staffCtrl" id="providerList" class="scrollDiv">
<!-- ngRepeat where you select the persons -->
</div>
<div data-ng-controller="staffController as staffCtrl">
<!-- ngRepeat where you show persons info -->
</div>
Here, you declared twice the controller, therefore, you have two instances of it. When you select the persons, you are storing the info in the data structures of the first instance. But the part of the view that displays the infos is working with other instances of the data structures, that are undefined or empty. The controller should be declared on a parent element of the two divs.
The second mistake is the following :
if (!self.infoList.length > 0) {
self.infoList = [];
}
You probably meant :
if (!self.infoList) {
self.infoList = [];
}
which could be rewrited as :
self.infoList = self.infoList || [];
I have a list of students.
For some students I have to show a link, and on clicking this link their progress card should load (it is a .pdf file). For others, the link should not be shown.
I have done this by giving the same student id to their progress card and if it is this particular student then show the link; otherwise, don't show the link. I can do it this way for only 5 or 6 students. I cant do this for 1000 students.
The following is my AngularJS code:
if (response.student_id == 'SD0001' || response.student_id == 'SD0002' || response.student_id == 'SD0004') {
$scope.pdfData = true;
$scope.StudentPDFFile = response.student_id+'.pdf';
} else {
$scope.pdfData = false;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.6/angular.min.js"></script>
<body id="ng-app" ng-app>
<div ng-switch on="pdfData">
<div ng-switch-when="false"></div>
<div ng-switch-when="true">
<span class="font-10">Download Performance Report</span>
click here
</div>
</div>
</body>
Here I haven't specified the controller, this is skeleton functionality is working for me.
For 1000 records how I can do this?
You can make a http request to determine if the file exists as long as you are on the same domain as the pdf file.
For a given student ID (which in your code appears to be response.student_id) it looks like this:
if(response.student_id) {
var url = 'http://www.example.com/s-pdf/' + response.student_id + '.pdf';
var request = new XMLHttpRequest();
request.open('HEAD', url, false);
request.send();
if(request.status == 200) {
$scope.pdfData = true;
$scope.StudentPDFFile = response.student_id+'.pdf';
} else {
$scope.pdfData = false;
}
} else {
$scope.pdfData = false;
}
The 'HEAD' is all that is necessary to ensure that the file exists; you could also use 'GET' but it doesn't seem like you need the whole file in this situation.
This is another way.
You can make a directive and verify an attrib with this directive and set anything you want in the element.
JS
angular.module('myApp')
.directive('imgCheck', ['$http', function($http){
return {
link: function(scope, element, attrs){
$http.get('/img/'+attrs.imgCheck)
.success(function(data, status){
if(status==200){
attrs.$set('src','/img/'+attrs.imgCheck)
}else{
attrs.$set('src','/img/not-found.png')
}
})
.error(function(data,status){
if(status==200){
attrs.$set('src','/img/'+attrs.imgCheck)
}else{
attrs.$set('src','/img/not-found.png')
}
});
}
};
}]);
HTML
<figure class="podcast-item col-md-2" ng-repeat="image in images">
<a href="#">
<img class="img-thumbnail" img-check="{{image.url}}" />
</a>
</figure>
ng-show seems more appropriate:
<div ng-show="pdfData">
<span class="font-10">Download Performance Report</span>
click here
</div>
For 1000, put it in a ng-repeat then you should better, test all the PDFs in one request and keep it in a cached list.
Its very simple bro, you just have to use ng-reapeat and ng-show. Below is my code with fiddle example, I hope it works for you.
HTML:
<div ng-app="studentApp">
<h2>List of Students</h2>
<div ng-controller="studentController">
<div ng-repeat="student in students">
Student Name : <strong>{{student.name}}</strong><br>
<div ng-show="{{student.url}}"><a href="{{student.url}}" title="" >Download PDF</a><br></div>
<hr>
</div>
</div>
</div>
JS:
angular.module('studentApp', [])
.controller('studentController', ['$scope', function($scope) {
$scope.students = [
{name: 'John', url:'abc.com/files/1.pdf'},
{name: 'Peter', url:''},
{name: 'Kerry', url:'abc.com/files/1.pdf'},
{name: 'Silly', url:'f'},
]
}]);
Example: http://jsfiddle.net/asadalikanwal/3a2zvrht/3/
Just change the PDF path with you once. If you have any question shoot
this is my code: (Im using a CMS based in MVC).
html:
<div class="item_group_title">
<a id="title_{$group.title}" data-tip="{lang("show", "store")}" class="hide_group" href="javascript:void(0)" onClick="Store.toggleGroup(this)">
<img src="{$url}application/images/icons/{$group.title}.png">
</a>
</div>
...
<section class="item_group" id="group_{$group.title}" {if $minimize}style="display:none"{/if}>
</section>
js:
toggleGroup: function(field)
{
var titleId = $(field).attr('id');
var groupId = $titleId.text().replace('title_', 'group_');
var group = $($groupId);
if(group.is(":visible"))
{
$(field).attr('data-tip', lang("hide", "store"));
}
else
{
$(field).attr('data-tip', lang("show", "store"));
}
group.css("display","visible");
},
What im trying to get is: When i click on the link the section below should be visible. There will be some links and some sections and each link will be affect one section.
My problem is that i receive this error: $titleId is not defined
Any ideas? Thank you so much
titleId and $titleId is not the same thing, the dollarsign is just another character that is part of the variable name.
How about
var $titleId = $(field).attr('id');
var $groupId = $titleId.text().replace('title_', 'group_');
var group = $($groupId);
It's the same with a lot of your variables, make sure they either have the dollarsign, or they don't.
I am having two problems with the order that things are happening in angular and the browser. I am using laravel as the backend and the following is happening.
User clicks a link and laravel routes the user to /trainers/all. trainers/all includes html which includes html which includes trainersController and ratingsCtrl. trainerscontroller makes a get request to the server for the trainers json object. ng-repeat loops through this object and displays information for each trainer including an image and their rating.
ratingsCtrl then takes their rating and turns it into a number of stars using angular.ui rating functionality.
The problem is two fold, the browser is requesting the images before angular has put the url slug from the json object into the img object. The browser seems to try multiple times failing until angular has done its work.
The second is that angular tries to take the value of the rating before trainersController has put it in the dom element as well. this second error stops the ng repeat so only one trainer is displayed, if I remove the ratingctrl then the ngrepeat completes with no issues besides the img issue. If I hardcode the rating value it works also.
here is the error
TypeError: undefined is not a function
at Ia.% (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.7/angular.min.js:145:85)
at Ia.% (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.7/angular.min.js:145:85)
at t.constant (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.7/angular.min.js:157:136)
at Ia.% (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.7/angular.min.js:145:85)
at t.constant (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.7/angular.min.js:157:136)
at https://ajax.googleapis.com/ajax/libs/angularjs/1.2.7/angular.min.js:48:165
at q (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.7/angular.min.js:7:380)
at E (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.7/angular.min.js:47:289)
at https://ajax.googleapis.com/ajax/libs/angularjs/1.2.7/angular.min.js:56:32
at f (https://ajax.googleapis.com/ajax/libs/angularjs/1.2.7/angular.min.js:42:399)
here is the relevant code
app.js
var trainercompareapp = angular.module("trainercompare", ['ui.bootstrap']);
trainercompareapp.config(function($interpolateProvider) {
$interpolateProvider.startSymbol('%%');
$interpolateProvider.endSymbol('%%');
});
function trainersController($scope, $http) {
$http.get('/trainers').success(function(trainers) {
$scope.trainers = trainers;
});
}
var RatingCtrl = function ($scope) {
$scope.rate = 7;
$scope.max = 10;
$scope.isReadonly = false;
$scope.hoveringOver = function(value) {
$scope.overStar = value;
$scope.percent = 100 * (value / $scope.max);
};
$scope.ratingStates = [
{stateOn: 'glyphicon-ok-sign', stateOff: 'glyphicon-ok-circle'},
{stateOn: 'glyphicon-star', stateOff: 'glyphicon-star-empty'},
{stateOn: 'glyphicon-heart', stateOff: 'glyphicon-ban-circle'},
{stateOn: 'glyphicon-heart'},
{stateOff: 'glyphicon-off'}
];
};
html
<div class="col-xs-12 container" ng-controller="trainersController">
<div ng-repeat="trainer in trainers">
<div class="col-xs-6">
<div class="row">
<img class="col-xs-6"src="%% trainer.user.images[0]['s3Url'] %%">
</div>
<div class="row">
<div class="col-xs-7" ng-controller="RatingCtrl">
<rating
class="rating"
value="%% trainer.rating %%"
max="max"
readonly="true"
on-hover="hoverOver(value)"
on-leave="overStar = null">
</rating>
</div>
<div class="col-xs-4">
View Profile
</div>
</div>
<hr>
</div>
</div>
</div>
Ugh, changing value="%% trainer.rating %% to just value="trainer.rating" made it work like a charm