AngularJs/Javascript , Copy Object is good practice? [closed] - javascript

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) ...

Related

How to write this code cleaner, without repetition? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 5 years ago.
Improve this question
$("#yl").click(function(){updateYear("sub")});
$("#yr").click(function(){updateYear("add")});
$("#ml").click(function(){updateMonth("sub")});
$("#mr").click(function(){updateMonth("add")});
$("#dl").click(function(){updateDay("sub")});
$("#dr").click(function(){updateDay("add")});
Is there a way to write this code cleaner, smarter without repetitions?
If you change your elements a bit, you could do something like this:
<button id='yr' data-type='update' data-date-part='year' data-date-action='add'>
Then you create an update function that starts off like this:
function update() {
const el = $(this);
const datePart = el.attr('data-date-part');
const dateAction = el.attr('data-date-action');
// do your logic to update the date based on what part and action
}
Then your click handler just needs to be:
$('button[data-type="update"]').click(update);
I forgot to mention, that newer versions of jquery will also let you just use the .data() function instead of spelling out the full data- attribute
The code is fine as it stands as it is very clear what is happening.
If you really want to do it differently, then you should probably also modify your function(s) and look into HTML attributes. It all depends what you are actually doing in those functions.
If for instance you want the user to enter a date just by pressing add/sub buttons, then the basics could look like this:
$('.change-value').click(updateDatePart);
function updateDatePart() {
// Read current date
var dateParts = $.map($(this).closest(".dateinput").find(".part-value"), function (span) {
return +$(span).text();
});
// Which part of the date needs incrementing/decrementing?
var part = $(this).closest('.part').index();
// Apply change to that part
dateParts[part] += $(this).data("inc");
// Create a date with this
var date = new Date(dateParts[0], dateParts[1]-1, dateParts[2]);
// Get the parts for the new date (which may have resolved some overflow)
dateParts = [date.getFullYear(), date.getMonth()+1, date.getDate()];
// Output the result
$('.part-value').each(function (i, elem) {
$(elem).text(dateParts[i]);
});
}
.change-value { font-size: 50% }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span class="dateinput">
<span class="part">
<button class="change-value" data-inc="-1">-</button>
<span class="part-value">2017</span>
<button class="change-value" data-inc="1">+</button>
</span>-
<span class="part">
<button class="change-value" data-inc="-1">-</button>
<span class="part-value">12</span>
<button class="change-value" data-inc="1">+</button>
</span>-
<span class="part">
<button class="change-value" data-inc="-1">-</button>
<span class="part-value">24</span>
<button class="change-value" data-inc="1">+</button>
</span>
</span>

why is my list not rendering?

by default it shows 10 elements, but when i change the input it does not update, below is the code and fiddle.
JS Code:
var app = angular.module('myapp',[]);
app.controller('ctrlParent',function($scope){
$scope.listItems = 10;
$scope.newTotal = function(){
$scope.$apply(function(){$scope.lisItemsTotal})
}
$scope.lisItemsTotal = function(num) {
return new Array($scope.listItems);
}
});
http://jsfiddle.net/0dwmqn8y/1/
The reason is that after changing the input $scope.listItems is a string, as the input type is text. Change it to number and all will work. Working plunker: http://jsfiddle.net/yv0z9q8L/
<input type="number" name="red" ng-model="listItems" onchange="angular.element(this).scope().newTotal()">
Also note, that you don't need onchange attribute at all here, however I am unsure whether this is a good thing. Using a function in for ng-repeat is quite dangerous unless you know what you are doing, as this function will be called in each digest cycle. If you are planning to loop over large set of data, it will pretty much kill your performance.
Personally, I would rather go with:
<div ng-app="myapp">
<div ng-controller="ctrlParent">
<input type="number" name="red" ng-model="itemCount">
<ul>
<li ng-repeat="i in items track by $index"><span>{{$index+1}}</span></li>
</ul>
</div>
</div>
var app = angular.module('myapp',[]);
app.controller('ctrlParent',function($scope){
$scope.$watch('itemCount', function(val) {
$scope.items = Array.new(val)
});
$scope.itemCount = 10;
});
plunker: http://jsfiddle.net/m1zq3zqv/1/
The data, that you get from the input field is a string, so you can either change the input type to number or apply a bit of parsing in your code by changing the following line
return new Array($scope.listItems);
to this:
return new Array(parseInt($scope.listItems));

edit update existing array in javascript

I am making CRUD app for learning purpose. I need to update existing javascript array on click of edit button. However currently its not updating the existing array rather then its creating new record. Below is the JS code of controller
For Add screen below is the controller code
.controller('addController', ['$scope','$location','$rootScope', function(scope,location,rootScope){
scope.save = function (){
scope.personName = document.getElementById('name').value;
scope.personDesc = document.getElementById('desc').value;
scope.person = {'name' : scope.personName, 'desc' : scope.personDesc};
if(typeof rootScope.crew === 'undefined'){
rootScope.crew = [];
}
rootScope.crew.push(scope.person);
location.path("/");
}
}])
For Edit Screen, below is the code of controller :-
.controller('editController',['$scope','$location','$routeParams','$rootScope', function(scope,location,routeParams,rootScope){
var oldName = scope.crew[routeParams.id].name;
document.getElementById('name').value = scope.crew[routeParams.id].name;
document.getElementById('desc').value = scope.crew[routeParams.id].desc;
scope.editSave = function(){
scope.person = {
'name' : document.getElementById('name').value,
'desc' : document.getElementById('desc').value
}
rootScope.crew.push(scope.person);
location.path("/");
}
}])
Currently I am adding record in existing array rather updating.
Please suggest
The problem is you are pushing a new item to the array. You need to just update the existing person with the person in scope.
.controller('editController',['$scope','$location','$routeParams','$rootScope', function(scope,location,routeParams,rootScope){
var person = scope.crew[routeParams.id]
scope.person = {
name = person.name,
desc = person.desc
};
scope.editSave = function(){
scope.crew[routeParams.id] = scope.person;
location.path("/");
}
}])
In your edit view you would have this:
<input type="text" id="name" ng-model="person.name"/>
<input type="text" id="desc" ng-model="person.desc"/>
It's also worth mentioning that there is no need to have code such as document.getElementById as angular will handle the model binding for you so you don't have to interact with the dom using javascript.
Every object that you are pushing in array must be identified by some id.So assign one id attribute to the person object that you are pushing.
Now come to the edit.html
<tr ng-repeat="p in person">
{{p.name}}
//In button I am passing id which I used in editing the person object
<button ng-click="edit(p.id)"></button>
</tr>
//In controller
edit(id){
//firstly search for the person which is going to be updated
for(i=0;i<arrayInWhichYouArePushingData.length;i++)
{
if(arrayInWhichYouArePushingData[i].id==id)
{
arrayInWhichYouArePushingData[i]=UpdatedData;
break;
}
}
This is just an algorithm to solve this type of problem.You have to modify little bit.

Having issues tying together basic javascript chat page

I have the skeleton of a chat page but am having issues tying it all together. What I'm trying to do is have messages sent to the server whenever the user clicks send, and also, for the messages shown to update every 3 seconds. Any insights, tips, or general comments would be much appreciated.
Issues right now:
When I fetch, I append the <ul class="messages"></ul> but don't want to reappend messages I've already fetched.
Make sure my chatSend is working correctly but if I run chatSend, then chatFetch, I don't retrieve the message I sent.
var input1 = document.getElementById('input1'), sendbutton = document.getElementById('sendbutton');
function IsEmpty(){
if (input1.value){
sendbutton.removeAttribute('disabled');
} else {
sendbutton.setAttribute('disabled', '');
}
}
input1.onkeyup = IsEmpty;
function chatFetch(){
$.ajax({
url: "https://api.parse.com/1/classes/chats",
dataType: "json",
method: "GET",
success: function(data){
$(".messages").clear();
for(var key in data) {
for(var i in data[key]){
console.log(data[key][i])
$(".messages").append("<li>"+data[key][i].text+"</li>");
}
}
}
})
}
function chatSend(){
$.ajax({
type: "POST",
url: "https://api.parse.com/1/classes/chats",
data: JSON.stringify({text: $('input1.draft').val()}),
success:function(message){
}
})
}
chatFetch();
$("#sendbutton").on('click',chatSend());
This seems like a pretty good project for Knockout.js, especially if you want to make sure you're not re-appending messages you've already sent. Since the library was meant in no small part for that sort of thing, I think it would make sense to leverage it to its full potential. So let's say that your API already takes care of limiting how many messages have come back, searching for the right messages, etc., and focus strictly on the UI. We can start with our Javascript view model of a chat message...
function IM(msg) {
var self = this;
self.username = ko.observable();
self.message = ko.observable();
self.timestamp = ko.observable();
}
This is taking a few liberties and assuming that you get back an IM object which has the name of the user sending the message, and the content, as well as a timestamp for the message. Probably not too far fetched to hope you have access to these data elements, right? Moving on to the large view model encapsulating your IMs...
function vm() {
var self = this;
self.messages = ko.observableArray([]);
self.message = ko.observable(new IM());
self.setup = function () {
self.chatFetch();
self.message().username([user current username] || '');
};
self.chatFetch = function () {
$.getJSON("https://api.parse.com/1/classes/chats", function(results){
for(var key in data) {
// parse your incoming data to get whatever elements you
// can matching the IM view model here then assign it as
// per these examples as closely as possible
var im = new IM();
im.username(data[key][i].username || '');
im.message(data[key][i].message || '');
im.timestamp(data[key][i].message || '');
// the ([JSON data] || '') defaults the property to an
// empty strings so it fails gracefully when no data is
// available to assign to it
self.messages.push(im);
}
});
};
}
All right, so we have out Javascript models which will update the screen via bindings (more on that in a bit) and we're getting and populating data. But how do we update and send IMs? Well, remember that self.message object? We get to use it now.
function vm() {
// ... our setup and initial get code
self.chatSend = function () {
var data = {
'user': self.message().username(),
'text': self.message().message(),
'time': new Date()
};
$.post("https://api.parse.com/1/classes/chats", data, function(result) {
// do whatever you want with the results, if anything
});
// now we update our current messages and load new ones
self.chatFetch();
};
}
All right, so how do we keep track of all of this? Through the magic of bindings. Well, it's not magic, it's pretty intense Javascript inside Knockout.js that listens for changes and the updates the elements accordingly, but you don't have to worry about that. You can just worry about your HTML which should look like this...
<div id="chat">
<ul data-bind="foreach: messages">
<li>
<span data-bind="text: username"></span> :
<span data-bind="text: message"></span> [
<span data-bind="text: timestamp"></span> ]
</li>
</ul>
</div>
<div id="chatInput">
<input data-bind="value: message" type="text" placeholder="message..." />
<button data-bind="click: $root.chatSend()">Send</button>
<div>
Now for the final step to populate your bindings and keep them updated, is to call your view model and its methods...
$(document).ready(function () {
var imVM = new vm();
// perform your initial search and setup
imVM.setup();
// apply the bindings and hook it all together
ko.applyBindings(imVM.messages, $('#chat')[0]);
ko.applyBindings(imVM.message, $('#chatInput')[0]);
// and now update the form every three seconds
setInterval(function() { imVM.chatFetch(); }, 3000);
});
So this should give you a pretty decent start on a chat system in an HTML page. I'll leave the validation, styling, and prettifying as an exercise to the programmer...

AngularFire removes Firebase locations

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"];
}
}

Categories