I have a stream where the events look something like this:
{
endOfSequence : false,
sequenceId: 12345,
data: [.....]
}
I need to terminate the sequence when endOfSequence === true. I started with takeWhile:
seq = stream.takeWhile( function(event){
return !event.endOfSeq;
});
but the problem is that I miss the last event.
I can obviously write code that accomplishes the same thing, for example:
function beforeEnd(event){
return !event.endOfSeq;
}
seq = stream.takeWhile(beforeEnd)
.merge(stream.skipWhile(beforeEnd).take(1));
But this is a bit ugly. Is there a better way?
You can write a custom handler with Bacon.withHandler that emits a Bacon.End() when you get the endOfSequence.
seq.withHandler(function(event) {
var ret = this.push(event)
if(event.hasValue() && event.value().endOfSequence) {
ret = this.push(new Bacon.End())
}
return ret
})
A working example can be found in this jsFiddle.
Accepted answer adapted for for baconjs v3:
takeUntilLastEventSatisfies = (predicate, observable) =>
observable.transform(
(baconEvent, sink) => {
if (!Bacon.hasValue(baconEvent)) {// error or end
return sink(baconEvent);
}
if (predicate(baconEvent.value)) {
sink(baconEvent);
return sink(new Bacon.End());
}
return sink(baconEvent);
})
);
Related
I’m Rahul and I’m new to coding. I have a query related to DOM event. Please look at the following code snippet -
let door1 = document.getElementById('one');
door1.src = "closed_door.svg";
const isClicked = (door) => {
if(door.src === "closed_door.svg") {
return true;
}
else {
return false;
}
};
door1.onclick = () => {
if(isClicked(door1)) {
door1.src = "beach.svg";}
};
To give you brief, one is an id for an element. Without isClicked, I am able to successfully change the src from closed door to beach on clicking. But when I introduce isClick, it doesn’t change. Can someone please tell me what I’m missing. I’ll be very thankful
Note - I'm building game similar to this - https://s3.amazonaws.com/codecademy-content/projects/chore-door/chore-door-final/index.html They are using the same process as mine. So please suggest a solution that tells me about the error I'm making here rather than an alternative to the problem
Regards
Rahul
As reported here:
the src reflected property will be the resolved URL — that is, the absolute URL that that turns into. So if that were on the page http://www.example.com, document.getElementById("foo").src would give you "http://www.example.com/images/example.png".
so to get the real src attribute you should use .getAttribute('src') like so:
const isClicked = (door) => {
if(door.getAttribute('src') === "closed_door.svg") {
return true;
}
else {
return false;
}
};
Ans also BTW, you can just shortcut it to:
const isClicked = (door) => door.getAttribute('src') === "closed_door.svg";
Replace your code with this..
let door1 = document.getElementById('one');
door1.src = "https://s3.amazonaws.com/codecademy-content/projects/chore-door/images/closed_door.svg";
const isClicked = (door1) => {
if(door1.src === "https://s3.amazonaws.com/codecademy-content/projects/chore-door/images/closed_door.svg") {
return true;
}
else {
return false;
}
};
door1.onclick = () => {
if(isClicked(door1)) {
door1.src = "https://s3.amazonaws.com/codecademy-content/projects/chore-door/images/beach.svg";}
};
trying to find a way to condense this. wasnt sure of the best way to do it. basically if criteria is met i display an alert with a parameter that is the message. i was thinking of maybe trying it in function. this is part of a larger function react component. i was also thinking if i could find a way to condense the else if's i could use a ternary. thanks in advance for the assistance.
const handleUpdatePassword = () => {
const allFilled = !reject(passwords).length;
const passwordsMatch = newPassword === conPassword;
const isDifferent = curPassword !== newPassword;
const meetsPasswordRequirements = validatePassword();
const usesName = isUsingName();
const usesUserID = isPartOfUserID();
const isValidPassword = meetsPasswordRequirements && isDifferent;
if (allFilled) {
if (!isDifferent) {
Alert.alert(difPassWord);
} else if (!passwordsMatch) {
Alert.alert(noMatch);
} else if (!meetsPasswordRequirements) {
Alert.alert(pasReqs);
} else if (usesName || usesUserID) {
Alert.alert(pasName);
}
} else {
Alert.alert(fieldNotComplete);
}
if (isValidPassword) {
changePasswordPost(
{
userId,
curPassword,
newPassword
},
partyId
);
}
};
You can create an array of objects for your validation rules, each containing a function which returns a boolean indicating whether that validation passes, and a string with the error message to display.
Then loop over the rules array and alert the message for the first rule that returns false. If they all return true, do the post.
You can split each if statement into a function, then chain them. For example
// here we make a closure to validate, and return a Promise
// condition can be a function
const validate = (condition, error) => ()=> new Promise((res, rej)=>{
if(condition()){
res();
}else{
rej(error);
}
});
const handleUpdatePassword = () => {
const validateFieldsComplete = validate(
()=>!reject(passwords).length,
fieldNotComplete
);
const validateDifPassword = validate(
()=> curPassword !== newPassword,
difPassWord
);
// ...
validateFieldsComplete()
.then(validateDifPassword)
.then(...)
.catch(Alert.alert)
}
It would be much cleaner with pipe. You can take a look at ramda. Or if you are intrested in functional way, you might consider using Monad.
I'd recommend DRYing up the Alert.alert part since all branches have that in common, and just come up with an expression that evaluates to the alert message. Compactness isn't always everything, but if you want it, then nested conditional operators can fit the bill. I'm also rearranging your conditions so that it can be a flat chain of if/elses:
const message
= reject(passwords).length ? fieldNotComplete
: curPassword === newPassword ? difPassWord
: newPassword !== conPassword ? noMatch
: !validatePassword() ? pasReqs
: (isUsingName() || isPartOfUserID()) ? pasName
: null;
const isValid = !message;
if (!isValid) {
Alert.alert(message);
}
(feel free to use any other sort of code formatting pattern; nested conditionals always look awkward no matter which pattern you use, IMO.)
Edit:
Also inlined conditionals which will short-circuit evaluation and make it even more compact.
I'd setup a validations object that has the tests and error messages and then loop over it. If validation fails, it'll throw the last validation error message. Using this method, you only have to maintain your tests in one place and not mess with a block of conditional statements.
const handleUpdatePassword = () => {
const validations = {
allFilled: {
test() {
return newPass && oldPass
},
error: 'Must fill out all fields'
},
correct: {
test() {
return curPass === oldPass
},
error: 'Incorrect password'
},
[...]
}
const invalid = () => {
let flag = false
for (let validation in validations) {
if (!validations[validation].test()) {
flag = validations[validation].error
}
}
return flag
}
if (invalid()) {
Alert.alert(invalid())
} else {
changePasswordPost(
{
userId,
curPass,
newPass
},
partyId
)
}
}
hi everyone this was the method i used for a solution
const messages = [
{
alertMessage: difPassWord,
displayRule: different()
},
{
alertMessage: noMatch,
displayRule: match()
},
{
alertMessage: pasReqs,
displayRule: validatePassword()
},
{
alertMessage: pasName,
displayRule: !isUsingName() || !isPartOfUserID()
}
];
if (allFilled) {
const arrayLength = messages.length;
for (let i = 0; i < arrayLength; i++) {
if (messages[i].displayRule === false) {
Alert.alert(messages[i].alertMessage);
}
}
I would like test my Array (input value) before submit my form.
My array with value :
const fields = [
this.state.workshopSelected,
this.state.countrySelected,
this.state.productionTypeSelected,
this.state.numEmployeesSelected,
this.state.startAt
];
I've try this :
_.forEach(fields, (field) => {
if (field === null) {
return false;
}
});
alert('Can submit !');
...
I think my problem is because i don't use Promise. I've try to test with Promise.all(fields).then(());, but i'm always in then.
Anyone have idea ?
Thank you :)
The problem is that even though you're terminating the lodash _.forEach loop early, you don't do anything else with the information that you had a null entry.
Instead of lodash's _.forEach, I'd use the built-in Array#includes (fairly new) or Array#indexOf to find out if any of the entries is null:
if (fields.includes(null)) { // or if (fields.indexOf(null) != -1)
// At least one was null
} else {
// All were non-null
alert('Can submit !');
}
For more complex tests, you can use Array#some which lets you provide a callback for the test.
Live example with indexOf:
const state = {
workshopSelected: [],
countrySelected: [],
productionTypeSelected: [],
numEmployeesSelected: [],
startAt: []
};
const fields = [
state.workshopSelected,
state.countrySelected,
state.productionTypeSelected,
state.numEmployeesSelected,
state.startAt
];
if (fields.indexOf(null) != -1) {
console.log("Before: At least one was null");
} else {
console.log("Before: None were null");
}
fields[2] = null;
if (fields.indexOf(null) != -1) {
console.log("After: At least one was null");
} else {
console.log("After: None were null");
}
You do not need to use promises unless there is an asynchronous operation (for example if you are getting that array from your server).
If you already have that array you can do something like:
// Using lodash/underscore
var isValid = _.every(fields, (field) => (field!==null)}
// OR using the Array.every method
var isValid = fields.every((field)=>(field!==null))
// Or using vanilla JS only
function checkArray(array){
for(var i = 0; i < array.length ; i ++){
if(array[i]===null){
return false;
}
}
return true;
}
var isValid = checkArray(fields);
// After you get that value, you can execute your alert based on it
if(!isValid){
alert('Something went wrong..');
}
Try this simple snippet
var isAllowedToSubmit = true;
_.forEach(fields, (field) => {
if (!field) {
isAllowedToSubmit = false;
}
});
if(isAllowedToSubmit)
alert('Can submit !');
You can do that without library:
if (fields.some(field => field === null)) {
alert('Cannot submit');
} else {
alert('Can submit');
}
You don't need to use lodash, you can do this in simple vanilla javascript. Simply iterate over each field and if an error occurs set your errors bool to true
let errors = false;
fields.forEach(field) => {
if(field === null || field === '') {
errors = true;
}
});
if (!errors) {
alert('Yay no errors, now you can submit');
}
For an es6 you can use.
const hasNoError = fields.every((field, index, selfArray) => field !== null);
if (!hasNoError) {
alert('yay It works');
};
Have a look at Array.every documentation Array every MDN documentation
Current Working Code
I have the following rxjs/Observable:
findMessages(chatItem: any): Observable<any[]> {
return Observable.create((observer) => {
let processing: boolean = false;
this.firebaseDataService.findMessages(chatItem).forEach(firebaseItems => {
if (!processing) {
processing = true;
this.localDataService.findMessages(chatItem).then((localItems: any[]) => {
let mergedItems: any[] = this.arrayUnique(firebaseItems.concat(localItems), false);
mergedItems.sort((a, b) => {
return parseFloat(a.negativtimestamp) - parseFloat(b.negativtimestamp);
});
if (this.me && mergedItems && mergedItems[0] && this.me.uid === mergedItems[0].memberId2) {
this.updateChatWithMessage(chatItem, mergedItems[0], false);
}
observer.next(mergedItems);
processing = false;
});
}
});
});
}
and
this.firelist = this.dataService.findMessages(this.chatItem);
this.firelist.subscribe(items => {
...
});
As you can see, it returns a list of firebaseItems and localItems, which are merged to mergedItems. This works perfectly.
Performance Enhancement
However, I am trying to increase the performance that the items load. So figure, I would like to first load the localItems, and then add to the list with the firebaseItems.
So I try add the following function:
findLocalMessages(chatItem: any): Observable<any[]> {
return Observable.create((observer) => {
this.localDataService.findMessages(chatItem).then((localItems: any[]) => {
localItems.sort((a, b) => {
return parseFloat(a.negativtimestamp) - parseFloat(b.negativtimestamp);
});
observer.next(localItems);
});
});
}
and call it as follows:
this.firelist = this.dataService.findLocalMessages(this.chatItem);
this.firelist = this.dataService.findMessages(this.chatItem);
this.firelist.subscribe(items => {
...
});
Problem
This has now introduced a bug, that there are now 2 Observables and the results are not as expected. The sort order is incorrect, and some of the items are not being added to the this.firelist for some reason.
Question
Whats the best way to handle this?
I was thinking if it's possible to make the findLocalMessages Observable only get fired once, and then it never works again, as the findMessages Observable will maintain the list. Is this possible? I have been looking at the Observable api, and can't seem to figure out how to do that.
Any help appreciated.
With the risk of oversimplifying the problem statement, you have two streams of data that you want to merge and sort in an efficient manner.
The separation you have made is a step in the right direction.
The reason why you are not getting all the messages is that you are overriding the first observable with the second.Have a look at the following example and see what happens if you try and assign the second observable to move instead of move2.
let move = Observable.fromEvent(document.getElementById("1"), 'mousemove');
let move2 = Observable.fromEvent(document.getElementById("2"), 'mousemove');
move
.subscribe((event:any) => {
if (event) {
console.log(event.path[0].id)
}
});
move2
.subscribe((event:any) => {
if (event) {
console.log(event.path[0].id)
}
});
<h1>
<div id="1">
aaaaaaaaaaaaaaaaaaaaaaaaa
</div>
<div id="2">
bbbbbbbbbbbbbbbbbbbbbbbbb
</div>
</h1>
In order to merge the two streams together properly you need to use the merge operator as shown below:
let move = Observable.fromEvent(document.getElementById("1"), 'mousemove');
let move2 = Observable.fromEvent(document.getElementById("2"), 'mousemove');
move.merge(move2)
.subscribe((event:any) => {
if (event) {
console.log(event.path[0].id)
}
});
Now all you need to do is sort them. I would advice that you do the sort only after the merge because otherwise, you will end up with two streams that are sorted locally, not globally.
Currently I get back a JSON response like this...
{items:[
{itemId:1,isRight:0},
{itemId:2,isRight:1},
{itemId:3,isRight:0}
]}
I want to perform something like this (pseudo code)
var arrayFound = obj.items.Find({isRight:1})
This would then return
[{itemId:2,isRight:1}]
I know I can do this with a for each loop, however, I am trying to avoid this. This is currently server side on a Node.JS app.
var arrayFound = obj.items.filter(function(item) {
return item.isRight == 1;
});
Of course you could also write a function to find items by an object literal as a condition:
Array.prototype.myFind = function(obj) {
return this.filter(function(item) {
for (var prop in obj)
if (!(prop in item) || obj[prop] !== item[prop])
return false;
return true;
});
};
// then use:
var arrayFound = obj.items.myFind({isRight:1});
Both functions make use of the native .filter() method on Arrays.
Since Node implements the EcmaScript 5 specification, you can use Array#filter on obj.items.
Have a look at http://underscorejs.org
This is an awesome library.
http://underscorejs.org/#filter
edited to use native method
var arrayFound = obj.items.filter(function() {
return this.isRight == 1;
});
You could try find the expected result is using the find function, you can see the result in the following script:
var jsonItems = {items:[
{itemId:1,isRight:0},
{itemId:2,isRight:1},
{itemId:3,isRight:0}
]}
var rta = jsonItems.items.find(
(it) => {
return it.isRight === 1;
}
);
console.log("RTA: " + JSON.stringify(rta));
// RTA: {"itemId":2,"isRight":1}
Actually I found an even easier way if you are using mongoDB to persist you documents...
findDocumentsByJSON = function(json, db,docType,callback) {
this.getCollection(db,docType,function(error, collection) {
if( error ) callback(error)
else {
collection.find(json).toArray(function(error, results) {
if( error ) callback(error)
else
callback(null, results)
});
}
});
}
You can then pass {isRight:1} to the method and return an array ONLY of the objects, allowing me to push the heavy lifting off to the capable mongo.