Overwriting existing object properties - javascript

I am having trouble with two small blocks of code and understanding why one works and one doesn't.
I have an array of 'tasks'.
When a user updates a task, I need to update the AuditLog, to show the update.
I have two blocks of code, first one works, second one doesn't.
1st block - when I view the updated task, it shows the updated log.
2nd block - when I view the updated task, the log is not updated.
I suspect that it is something to do with the way I am referencing, assigning things.
This block correctly updates the "task" and viewing it shows the updated "audit log".
//update task with server response 'a'
this.updateTask(task).subscribe(a => {
let updatedTask = initializeTask(a);
task.auditLog = updatedTask.auditLog;
});
Then I thought I could simplify the code a bit...
but when using this block, when I view the task, it does not show the updated log.
(I should mention that printing 'intializeTask(a)' works correctly and prints the updated log values, so it's not to do with that).
//update task with server response 'a'
this.updateTask(task).subscribe(a => {
task = initializeTask(a);
});
I feel like I may be missing something fundamental in the way JavaScript references/assigns variables etc. Perhaps someone can shed some light on it.
Thanks in advance!
PS: initializeTask simply ensures that nothing is left undefined and that dates are formatted correctly:
export function initializeTask(t): Task {
t.subject = t.subject || '';
t.startTime = initDate(t.startTime);
t.state = t.state || 'Published';
t.createdBy = t.createdBy || '';
if (t.auditLog) {
t.auditLog.forEach(d => {
d.createdOn = initDate(d.createdOn);
});
}
return t;
}

Related

Angular 4 weird error: query doesn't end when -not- showing a message toast

Here this is, my weirdest error in my whole programming career. I've been struggling through this, yet I can't find what's going on in this code. It just seems not to make any sense in any way.
I'm using the following tools:
Ionic 3
Angular 4
Typescript / ES6
I'm trying to do a method, "assignChat(user)", which assigns a chat to a user. It has to use several APIs, geolocation... it's a big method, actually. That's why I've split it in two parts connected by promises, and used them after, so my method looks pretty much like this:
assignChat(user){
const getLocationName = () => {
return new Promise((resolve,reject) => {
// 30 lines of code
});
}
const assignOrCreateChat= (area) => {
return new Promise((resolve,reject) => {
// 40 lines of code
});
}
const getLocationName = () => {
return new Promise((resolve,reject) => {
// 30 lines of code
});
}
// then I use the inner functions here and write an extra 60-70 lines of code
}
Ok! This works neat. Didn't have much problems with this algorithm after some several testing, although is quite heavy and takes ~0.5s to properly execute, finish it's queries, and show the result.
Thing is... I had some toasts displaying some information, like where you're located. I wanted to remove them, and started by this one, in the inner function getLocationName(). This is the code I want to talk you about:
const getLocationName = () => {
return new Promise( (resolve, reject) => {
const ADDRESS_LEVEL = 2;
this.reverseGeocode(ADDRESS_LEVEL).then( address => {
---> this.toastify("You have been located at: "+address, 1500);
let query = new Parse.Query("PoliticalArea");
// more code
The line I marked with an arrow, is the line which is giving me problems. I mean, you probably think the code fails because of the line, but it's totally the oposite! If I remove that line, the algorithm suddenly stops working and fails to display any result.
The "toastify" method is a quick way I did for myself for displaying toasts. It works well, actually! This is the implementation:
toastify(message, duration){
this.toastCtrl.create({
message: message,
duration: duration
}).present();
}
Not like the most dangerous method. Well, in fact, it seems that the code won't work without it. If I comment the line, or erase it, I never get any result, or any error, from the big algorithm I showed you before. I've got every possible exception catched, although the API connectors don't have timeout, but it's like it gets stuck every time it doesn't display the toast.
I just don't understand what's going on. Seems like a very serious thing the Angular team should look into, in my very honest opinion.
Any idea of what kind of black magic is going there?
UPDATE:
Some further info: when I navigate through the "bugged" view (without the toastify line, and therefore not displaying the chat result), and per example, click in another chat (which pushes a view into the Navigation Controller), it somehow starts showing the chat result I expected. When I pop the new view from the navCtrl, and get back to the page, the expected result is now visible.
Is this some problem with angular watches?
Ok, the solution was not obvious.
It seems that the view was being rendered before the task completed. It was a tough task, so maybe that's the reason why Angular didn't work properly. Tried executing it both in the constructor and in ionViewDidEnter(), though nothing worked.
My final solution was to force component's re-rendering, through ApplicationRef, using the .tick() method at the dead end of my method.
That fixed it all!

Strange behavior concerning the sample Ionic ToDo app in the docs

I'm new to the Ionic framework and was trying to follow through the Ionic ToDo app demonstration. Before starting I want to mention I manually installed the latest ionic release from its GitHub page and included the following in my index.html: ionic.bundle.js, angular.min.js, angular-animate.min.js, angular-resource.min.js, angular-sanitize.min.js, angular-ui-router.min.js. In chapter 5, the author details the controller code to create a new task. I tried adding some extra functionality to the .createTask()method defined as
$scope.createTask = function(task) {
$scope.tasks.push({
title: task.title
});
$scope.taskModal.hide();
task.title = "";
};
by changing it to
$scope.createTask = function(task) {
if (task.title == "") {
window.alert("Empty Task!");
$scope.taskModal.hide();
} else {
$scope.tasks.push({title: task.title})
$scope.taskModal.hide();
task.title = "" ;
};
};
Now, if you run this new code with the accompanying view outlined in the tutorial, you will see that the controller calls the empty task alert even if task.title == "" fails! Maybe I'm missing something really obvious, but I'm not sure. Furthermore, and the main reason I'm writing this post, if you simply comment out the line task.title = ""; in the else clause and add a task via the live view, it adds two instances of the task created! This is bizarre, or so I thought, as it seems like $scope.tasks.push({title: task.title}); is being executed twice. So just like any good programmer would do, I tried to make the compiler (in this case interpreter) spill its secrets. I declared a variable in the controller scope, $scope.i = 0, to keep track of what was happening. Leaving //task.title = "" in its place, I inserted
$scope.i += 1;
window.alert($scope.i);
right before it. When you run this code, you will notice two things:
The controller no longer calls the empty task alert on non-empty tasks.
The view is now updated with only one instance of the created task.
So, I'm not really sure what's happening. Maybe it's just some careless error I've overlooked, but any help on this will be really appreciated. Many thanks!
EDIT:
I tried experimenting further and it seems the whole thing hinges on window.alert(). Even if
$scope.i += 1;
window.alert($scope.i);
is replaced with
$window.alert("1");
the "solution" still works. Although if you use window.alert() or window.alert("") the original problem remains.

array reference javascript angular

i'm trying to reference one item in an array, and i have no idea why this is not working,
console.log($scope.Times);
console.log($scope.Times[0]);
these two lines of code are EXACTLY after eachother, but the output i get from the console is the following..
Output from the console
any ideas why this is not working? the commands are exactly after each other as I mentioned before and in the same function, the variable is global in my controller.
i can add more code if you think it can help, but i don't really understand how..
some more code:
$scope.Times = [];
$scope.getStatus = function(timer){
$http.post('getStatus.php', {timer : timer})
.success(function(response){
$scope.response = response;
if ($scope.response.Running === "0"){
$scope.model = { ItemNumber : $scope.response.Part };
$scope.loadTiming($scope.response.Part);
console.log($scope.Times);
console.log($scope.Times[0]);
}
});
};
$scope.loadTiming = function(itemNumber) {
$http.post('getPartTimings.php', {itemNumber : itemNumber})
.success(function(response){
$scope.selectedTiming = response;
$scope.Times.splice(0);
var i = 0;
angular.forEach($scope.selectedTiming, function(value) {
if (value !== 0)
$scope.Times.push({
"Process" : $scope.procedures[i],
"Duration" : value*60
});
i++;
});
});
};
<?php
$postData = file_get_contents("php://input");
$request = json_decode($postData);
require "conf/config.php";
mysqli_report(MYSQLI_REPORT_STRICT);
try {
$con=mysqli_connect(DBSERVER,DBUSER,DBPASS,DBNAME);
} catch (Exception $exp) {
echo "<label style='font-weight:bold; color:red'>MySQL Server Connection Failed. </label>";
exit;
}
$query = 'SELECT *,
TIME_TO_SEC(TIMEDIFF(NOW(),Timestamp))
FROM live_Timers
WHERE Timer='.$request->timer;
$result = mysqli_query($con, $query);
$data = mysqli_fetch_assoc($result);
echo JSON_ENCODE($data);
thanks for your help.
OK, so more code does help. It looks like you have asynchronous logic happening here. loadTiming is fired, which does a POST and then a splice on the Times array. One console.log could be firing before this POST and the other after. There's no easy way to tell.
One possible fix would be to only log these once the loadTiming async process runs. Return a promise from the loadTiming function and then in the then callback of the promise, log your array.
$scope.getStatus = function(timer){
$http.post('getStatus.php', {timer : timer})
.success(function(response){
$scope.response = response;
if ($scope.response.Running === "0"){
$scope.model = { ItemNumber : $scope.response.Part };
$scope.loadTiming($scope.response.Part).then(function () {
console.log($scope.Times);
console.log($scope.Times[0]);
});
}
});
};
$scope.loadTiming = function(itemNumber) {
return $http.post('getPartTimings.php', {itemNumber : itemNumber})
.success(function(response){
$scope.selectedTiming = response;
$scope.Times.splice(0);
var i = 0;
angular.forEach($scope.selectedTiming, function(value) {
if (value !== 0)
$scope.Times.push({
"Process" : $scope.procedures[i],
"Duration" : value*60
});
i++;
});
});
};
I think your issue is a $scope reference issue.
I would try this:
$scope.vm = {};
$scope.vm.Times = [];
Adding the "." is Angular best practice when attaching to $scope. This is best described here Understanding Scopes
I have experienced a similar situation a while ago, related with this issue.
Since then, I've encountered related issues a bunch of times (AngularJS, due to its cyclic nature seems prone to produce this behaviour).
In your case, using JSON.stringify($scope.Times) might "fix" this.
Context
Usually this happens in this context:
An async call or a expensive DOM manipulation is made.
You make 2 (or more) calls to console.log in between.
The state of the DOM or object is changed
The output shows inconsistent (and strange) results
How
Take this example:
console.log(someObject);
console.log(someObject.property);
After digging a lot (and talking to Webkit developers) this is what I've found:
The second call to console.log is "resolved" first.
Why?
In your case, this has to do how Console handles objects and "expressions" in a different way:
An "expression" is resolved in the time of call, while with objects, a reference to said object is stored instead
Note that expression is used loosely here. You can observe this behaviour in this fiddle
More in depth analysis
Regarding display discrepancies, the behaviour posted above is not the only gotcha with Console. In fact, it is related in how Console works.
Console is an external tool
First you must realize that Console is an external tool and not part of the ECMAScript spec. Implementations differ between browsers and it shouldn't be used in production. It certainly won't work the same for every user.
Console is a non-standard external tool and is not on a standards track.
Console is dynamic
Console as a very dynamic tool. With console you can make assertions (test), time and profile your code, group log entries, remote connect to your server and debug Server Side Code. You can even change code itself, at runtime. So..
Console is not just a static log displayer... Its dynamic nature is one its most features
Console has a slight delay
Being an external dynamic tool, Console works as a watcher process attached to the javascript engine.
This is useful in debugging and among other things prevents Console to inadvertently block the execution of the script. A simple and crude way of thinking about this is picturing console.log as a kind of non-blocking async call. This means that:
With Console, there's a slight delay between 1)call, 2)processing and 3)output.
However, calling Console is not "instant" per se. In fact, by itself, can delay script execution. If you mix this with complex DOM manipulations and events, it can cause weird behaviours.
I've encountered an issue with Chrome, when using MutationObserver and console.log. This happened because the DOM Painting was delaying the update of the DOM object but the event triggered by that DOM change was fired nevertheless. This meant the event callback was executed and finished before the DOM Object was fully updated, resulting in an invalid reference to the DOM object.
Using console.log in the observer caused a brief delay in the callback execution, that, in most of the times, was enough to let the DOM Object update first. This proves that console.log delays code execution.
But even when an invalid reference error occurred, console.log ALWAYS showed a valid object. Since the object couldn't have been changed by code itself, this proves there is a delay delay between the call of console.log and the processing.
Console log order matches the code path
Console log entries order is unaffected by entries update status. In other words,
The order of the log entries reflect the order in which they are called, not their "freshness"
So, if an object is updated, it does not move to the end of the log. (makes sense to me)
Counterintuitive behaviour
This can lead to a number of possible counterintuitive behaviours because one might expect a console.log to be some kind of snapshot of the object, not a reference to it.
For instance, in your case, the object is changed between the the call to console.log and the end of the script.
At the time of calling, $scope.Times is empty, so $scope.Times[0] is undefined.
However, the $scope.Time object is updated posteriorly.
When the Console report is displayed, it shows an updated version of the object.
Fix
In your case, transforming the object in an "expression" can solve the "issue". For instance, you can use JSON.stringify($scope.Times).
Debate
It is debatable if the way console handles objects is a Bug or a Feature. Some propose that, when called with an object, console.log should clone that object making a kind of snapshot. Some argue that storing a reference to the object is preferable, since you can easily create a snapshot yourself if you wish to do so.

Strange behavior from ko.editables

I'm using the ko.editables plugin for knockout, and it doesn't appear to be caching the previous value correctly. Does anyone have experience with this plugin?
If I do something like this:
var item = { Name: ko.observable("initial") };
selectedItem = ko.observable(item);
ko.editable(selectedItem);
selectedItem.beginEdit();
selectedItem().Name("second");
selectedItem.rollback();
What ends up happening is that selectedItem().Name is still "second", even though it should be "initial".
I looked into the source file, but I don't understand enough about the way JavaScript handles variables to know if what I'm seeing is right or wrong.
I set a breakpoint in the following place within ko.editables.js:
result.rollback = function () {
if (inTransaction()) {
result(oldValue); //breakpoint
inTransaction(false);
}
};
What I found was that oldValue had picked up the new value of the observable, even though commit was never called. Everything I've tried looks exactly like the samples. What am I missing?
Update:
I've updated the code sample. My original code does have the ko.editable() line in it, but thank you to Robert.westerland for pointing it out. It still doesn't work with this extra line.
I know this is an old post, but could be useful for others. I think you might need to call "commit" before the "beginEdit", and I also had to include "true" as a second parameter when calling ko.editable.
Your updated code::
var item = { Name: ko.observable("initial") };
selectedItem = ko.observable(item);
ko.editable(selectedItem, true);
selectedItem.commit();
selectedItem.beginEdit();
selectedItem().Name("second");
selectedItem.rollback();

Running multiple Javascript scripts only when certain HTML elements exist?

Here's the situation. I'm building a site which uses lots of scripts looking somewhat like this:
function getRandomArrayIndex(source_array) {
return Math.floor(Math.random() * source_array.length);
}
function getRandomArrayEntry(source_array) {
var random_index = getRandomArrayIndex(source_array);
return source_array[random_index];
}
function getRandomBlah() {
var blahs = [
["A"],
["B"],
["C"],
["D"],
["E"],
["F"],
["G"],
["H"],
["I"],
["L"],
["M"],
["N"],
["O"],
["P"],
["R"],
["S"],
["T"],
["V"],
["W"],
["Y"],
]; var random_blah = getRandomArrayEntry(blahs);
return random_blah;
}
function displayBlah(blah) {
const TEXT_ROW = 0;
const LINK_ROW = 1;
var blah_text = blah[TEXT_ROW];
var blah_link = blah[LINK_ROW]; if (blah_link != null) {
document.getElementById("blah").innerHTML = '' + blah_text + '';
} else {
document.getElementById("blah").innerHTML = blah_text;
}
}
function generateRandomBlah(){
var random_blah = getRandomBlah();
displayBlah(random_blah);
}
And this will, when called with <body onload="generateRandomBlah()">, insert one of the letters at random into <span id="blah"></span>.
So there's about 15 of these scripts, each with their own functions named slightly differently for different uses - generateRandomBlah2, etc, with a corresponding different place in the HTML for each script to do its work.
Because I'm not a very good coder, the way the whole thing works is that in the 'body onload' tag, there's about 15 different 'generateRandomBlah()' functions just within this one tag. The nature of the site means that on any one page, I will only need 2 or 3 of these scripts at once, but I require the ability to call any of them on any page. As you can see, my current tactic is to just call them all at once, and if the corresponding doesn't exist for a script, it'll just ignore that fact and move onto the next one.
Except that it doesn't ignore the fact that there's no corresponding <span>.
As soon as one isn't present, the rest of the scripts break and don't actually do what they're supposed to do. Looking at the code in Chrome's 'inspect code' shows an error at the first script which happens to break: "Uncaught TypeError: Cannot set property 'innerHTML' of null". I see a couple of potential solutions, but I might be completely off:
1) Add some code into each script which tells it that, if there's no <span id> on the page to insert its code, it ends gracefully and moves onto the next one - gradually (obviously in less than a second speed-wise) going through the scripts and only running them if actually exists. (As you can see, the problem is that a script will get 'snagged' on the fact that there's no place to insert its code and doesn't just end gracefully if that happens.
2) Get rid of the 'onload' stuff and just make each script self-containing, calling its own function. I don't know if this would fix the problem, though.
Anyway, some help would be much appreciated, as I'm stumped.
As you said in your first solution, testing for null before trying to "do" anything is probably your best bet short of completely recoding.
function getRandomArrayIndex(source_array) {
if(source_array === null) return; // similar lines in each function should fix everything
return Math.floor(Math.random() * source_array.length);
}
That said, a much better approach than your current solution of making a bunch of very similar functions and running them all is to create a single function that can take parameters indicating what it should be doing. If they really are quite similar, it shouldn't be difficult and will result in a lot fewer lines of code. It would also be nice if you can find a way for your page to not call all of the functions every time, though with fixing the null pointers that's a much smaller issue.
Not sure if I'm missing something here, but since even the error message complains about the value being null the obvious solution is to check for null:
var element = document.getElementById("blah");
if( element !== null ) {
element.innerHTML = '' + blah_text + '';
}
If you need to do that often you can make a function for it:
function updateIfExists( id, content ) {
var element = document.getElementById( id );
if( element !== null ) {
element.innerHTML = content;
}
}
updateIfExists( 'blah', '' + blah_text + '' );

Categories

Resources