How do I focus an input with Cycle? Do I need to reach inside the DOM and call .focus() either with or without jQuery, or is there some other way with Cycle/RxJS?
Yes, you do need to reach inside the DOM and call .focus() either with or without jQuery. However this is a side-effect and it is Cycle.js convention to move these kinds of side effects to a so-called driver.
The two questions the driver needs to know are:
which element do you want to focus?
when do you want to focus the element?
The answer to both questions can be provided by a single stream of DOM elements.
Create the driver
First make your driver. Let's call it SetFocus. We'll make it a so-called read-only driver. It will read from the app's sinks but it will not provide a source to the app. Because it is reading, the driver's function will need to accept a formal parameter that will be a stream, call it elem$:
function makeSetFocusDriver() {
function SetFocusDriver(elem$) {
elem$.subscribe(elem => {
elem.focus();
});
}
return SetFocusDriver;
}
This driver takes whatever DOM element arrives in the stream and calls .focus() on it.
Use the Driver
Add it to the list of drivers provided to the Cycle.run function:
Cycle.run(main, {
DOM: makeDOMDriver('#app'),
SetFocus: makeSetFocusDriver() // add a driver
});
Then in your main function:
function main({DOM}) {
// setup some code to produce the elem$ stream
// that will be read by the driver ...
// [1]: say _when_ we want to focus, perhaps we need to focus when
// the user clicked somewhere, or maybe when some model value
// has changed
// [2]: say _what_ we want to focus
// provide the textbox dom element as actual value to the stream
// the result is:
// |----o-----o-----o--->
// where each o indicates we want to focus the textfield
// with the class 'field'
const textbox$ = DOM.select('.field').observable.flatMap(x => x); // [2]
const focusNeeded = [
clickingSomewhere$, // [1]
someKindofStateChange$ // [1]
];
const focus$ = Observable.merge(...focusNeeded)
.withLatestFrom(textbox$, (_, textbox) => textbox); // [2]
// ...
// [*]: Add driver to sinks, the driver reads from sinks.
// Cycle.js will call your driver function with the parameter
// `elem$` being supplied with the argument of `focus$`
return {
DOM: vtree$,
SetFocus: focus$, // [*]
};
}
You can then configure focusNeeded to say when you want .field to be focused.
You can tailor for your own situation, but this should illustrate how to solve your problem. Let's assume you have a text input and a button. When the button is clicked, you want the focus to remain on the text input.
First write the intent() function:
function intent(DOMSource) {
const textStream$ = DOMSource.select('#input-msg').events('keyup').map(e => e.target);
const buttonClick$ = DOMSource.select('#send-btn').events('click').map(e => e.target);
return buttonClick$.withLatestFrom(textStream$, (buttonClick, textStream) => {
return textStream;
});
}
Then the main which has a sink to handle the lost focus side effect
function main(sources) {
const textStream$ = intent(sources.DOM);
const sink = {
DOM: view(sources.DOM),
EffectLostFocus: textStream$,
}
return sink;
}
Then the driver to handle this side effect would look something like
Cycle.run(main, {
DOM: makeDOMDriver('#app'),
EffectLostFocus: function(textStream$) {
textStream$.subscribe((textStream) => {
console.log(textStream.value);
textStream.focus();
textStream.value = '';
})
}
});
The entire example is in this codepen.
Here's one example, written by Mr. Staltz himself: https://github.com/cyclejs/cycle-examples/blob/master/autocomplete-search/src/main.js#L298
Related
I need to check without further action if an element on my page is currently visible (this is being set either by button click or function call).
The observer seems to be the right tool from what I have read so far.
On my local machine everything works fine. But on my Azure test server I am getting the following error:
Uncaught TypeError: Failed to execute 'observe' on 'ResizeObserver': parameter 1 is not of type 'Element'
Here is the code I am using:
function setStatus() {
var target = $('#sidebar_container');
var ro = new ResizeObserver(() => {
if (target.is(':visible')) {
$("i.fa-user").addClass("active");
} else {
$("i.fa-user").removeClass("active");
}
});
// Observe element
ro.observe(target);
}
Is there something wrong with the code (although it's working on localhost) or is there a setting on the Azure server I would have to check?
From the code you posted, it looks like you are testing this functionality on localhost by resizing your window.
I'm saying this because, to check if an element has come into the viewport, you should use the Intersection Observer, not the Resize Observer.
You'll find a deep dive into how this observer works in the MDN link above.
To simply check if an element is inside the viewport (so it should be "visible") this is a possible solution:
// Get a reference for the target element
let element = document.querySelector('#sidebar_container');
// Create a function that will handle any intersection between some elements and the viewport.
let handleIntersection = function (entries) {
// Loop through all the observed elements
for (let entry of entries) {
// Check if the element is intersecting the viewport
if (entry.isIntersecting) {
console.log("The following element is visible in the viewport: ", entry.target);
// ...
}
}
}
const observer = new IntersectionObserver(handleIntersection);
observer.observe(element);
Also, you should pass to the observer an actual DOM element, not the jQuery wrapper. For this, it would be probably better to just use document.querySelector to select the element rather then jQuery.
In the devtools the $ sign is a shortcut to the querySelector, so if you were trying this code directly through the devtools, this might have triggered some confusion.
The cypress docs(https://docs.cypress.io/guides/core-concepts/variables-and-aliases.html#Elements) are pretty unclear on how alias and variables can be used to store information during a test.
I'm trying to store the text of a div on one page to use later, for example:
// let vpcName;
it('Store current name to use later', () => {
// save name for later use - doesn't work
// cy.get('#value').then(elem => {
// vpcName = Cypress.$(elem).text;
// });
// using alias - also doesn't work
cy.get('#value')
.invoke('text')
.as('vpcName');
});
it('Use previous value to return to correct page', () => {
cy.contains(this.vpcName).click();
});
I just came across this article that explains how and why you store a variable and then use it later in the "Cypress way":
https://www.stevenhicks.me/blog/2020/02/working-with-variables-in-cypress-tests/
In respect to how it should work, here is my example which first, collects the message (the message is shown for only 3 secs then disappears). Secondly, it gets the value using the # sign. Lastly my code passes the stored message through to an empty function constructed to assert that the value Portsmouth is contained within the message.
it('Current Port change', () => {
cy.get('#viewport').find('div[id=message]').then(message => {
let wags = message;
cy.wrap(wags).as('wags')
});
cy.get('#wags').then(wags => {
expect(wags).to.contain("Portsmouth")
});
});
let me know if you need further clarification
Try this:
cy.get('button').then(($btn) => {
const txt = $btn.text()
// $btn is the object that the previous command yielded
})
Source: https://docs.cypress.io/guides/core-concepts/variables-and-aliases.html#Return-Values
I had to resort to
writeFile
readFile
When I was trying to save cookies from one test to another in Cypress
An alternative solution which doesn't actually store the element to a variable but serves some of the same purpose is to wrap the cy.get call in a function. Such as:
const btn = () => cy.get('[data-testid="reset-password-button"]')
btn().should('have.attr', 'disabled')
cy.get('[data-testid="new-password-input"]').type('someNewPW')
btn().should('not.have.attr', 'disabled')
This could be a good option if readability is your main goal.
I've been struggling with this for few days. Here is my approach if you want to use the saved text (for example) multiple times. However it seems too long to me and I think it could be optimized. Tested on Cypress v11.2.0
cy.xpath("//tbody/tr[2]/td[2]").then(($text) => {
let txt = $text.text()
//expect(txt).to.eq('SomeText')
cy.wrap(txt).as('txt')
})
cy.get('#txt').then(txt => {
//expect(txt).to.contain("SomeText")
cy.xpath("xpath of search field").type(txt)
})
I'm developing a Chrome extension, and I'm adding an onmouseover handler to each of the images on a page. When the user mouses over an image, it's URL should be stored in a variable. I know I can easily get the value of the src attribute of the image, but I want the full URL. The src attribute stores the path of the image on the server. For example, when you right click an image in Google Chrome, you get the "Copy Image URL" option, which copies the image's URL to the clipboard.
Is there any way to achieve this? Thanks.
Instead of imageElement.getAttribute("src") or $("img.something").attr("src"), which reads the original markup, use imageElement.src property which will always give you the full URL.
var imgFullURL = document.querySelector('img.something').src;
or:
var imgFullURL = $('img.something')[0].src;
To extract host name, path name etc. - parse the url with URL() constructor, which works in modern browsers or use the legacy method via creating a temporary a node.
You can use window.location to get the page you are currently on and the following will give you the URL parts you need:
window.location.protocol = "http:"
window.location.host = "stackoverflow.com"
window.location.pathname = "/questions/32828681/how-to-get-url-of-an-image-in-javascript"
So, likely, you will need protocol, then "//", then host and finally the image src.
So the TL;DR is this:
(function() {
const imageInfo = new Object();
imageInfo.source = '';
window.addEventListener('mouseover', function (event) {
var currentElement = event.target;
// console.log(event.target);
if (currentElement.tagName === 'IMG') {
// console.log(currentElement.outerHTML + "is a photo");
imageInfo.source = currentElement.src;
// console.log("src is :" + imageInfo.source)
return imageInfo.source;
}
})
})();
See CodePen:
How to find the src URL for a photo by Trevor Rapp on
CodePen
This is how I thought about solving the problem in the most basic steps:
get the function to fire.
get the function to add an event listener that will perform an action on a mouseover event.
make that action know what the mouse is currently over.
figure out if what the mouse is currently over is an image or not.
create logic that will respond if it is.
that action that logic should do is return the source URL.
I will need to store that source URL if I am going to have to return it.
Here are how each of those solutions looked:
get the function to fire.
An IFFE is a great way to get a function to fire without having to worry about polluting the name space.
//skeleton for an IFFE statement
(function() {
})();
get the function to add an event listener that will perform an action on a mouseover event.
An event listener that could fire anywhere would have to be attached to the window or the document.
make that action know what the mouse is currently over.
This part will be combined with part 2. Event listener's first parameter is what type of event you want to listen for -- in this case 'mouseover. So now our code looks like this
(function () {
window.addEventListener('mouseover', function (event) {
//do stuff here
}
})()
figure out if what the mouse is currently over is an image or not.
*To figure out which element the mouse if currently over you would use Event.target.
The MDN definition for that is: *
The target property of the Event interface is a reference to the object onto which the event was dispatched. It is different from Event.currentTarget when the event handler is called during the bubbling or capturing phase of the event. --Event.Target
*So the code would then look like this: *
(function () {
window.addEventListener('mouseover', function (event) {
//get the current element the mouse is over
var currentElement = event.target;
}
})()
create logic that will respond if it is.
This was a little trickier since a photo or IMG can be presented in various ways.
I chose to create a solution for the simplest way, which is assuming that the web developer used the more syntactically correct version of an tag. However, there are many times when they may choose to apply a 'background-image' CSS property to a normal . Other things to consider could be the use of iframes, which can make detecting the attributes of child elements very frustrating since they don't allow bubbling to occur. To tell if an element is an , you can simply use elem.tagName === "IMG" for your logic check. While not included in the above code, if you wanted to check if a div is using the 'background-image', you could use something like element.getAttribute('style').includes('term') and switch out 'term' for something like 'url' or 'jpg' or 'png.' Kind of clunky and hacky, but just a thought. Anyway, the code would then become
(function () {
window.addEventListener('mouseover', function (event) {
//get the current element the mouse is over
var currentElement = event.target;
if (currentElement.tagName === 'IMG') {
//do stuff
}
}
})()
that action that logic should do is return the source URL.
Once you get the logic done and you have properly selected the element, then you can use element.src to get the source URL.
I will need to store that source URL if I am going to have to return it.
You can do this anyway you want, but I played around with instantiating an object since it sounded like the value would need to change often, but you didn't necessarily need to store previous values.
And so the final product could be something like this
(function() {
const imageInfo = new Object();
imageInfo.source = '';
window.addEventListener('mouseover', function (event) {
var currentElement = event.target;
// console.log(event.target);
if (currentElement.tagName === 'IMG') {
// console.log(currentElement.outerHTML + "is a photo");
imageInfo.source = currentElement.src;
// console.log("src is :" + imageInfo.source)
return imageInfo.source;
}
})
})();
Let's say that I want to pass a Scheduler to an RxJS operator that makes it emit notifications every 5 seconds. Of course, this is very easy to do by just using interval or other existing operators. But if I really want to use a scheduler to accomplish that, how would I go about it?
My first thought is to subclass Rx.Scheduler.default. Would that be the way to go? And if so, how could that subclass look? Again, I understand that this is a complicated way to accomplish something that's easy using operators, but I am just curious about custom schedulers.
Operations should always be independent of the Schedulers that are used to implement them. Schedulers only know about one thing, time. Every scheduler is specifically built to deal with its own notion of time. They are expressly not built to handle specific operators since that would be a conflation of concerns.
So for your stated goal of creating a recurring task, I wouldn't recommend trying to actually create your own scheduler, it simply isn't needed. Schedulers come with an interface that already supports this.
You can use either the schedulePeriodic or the scheduleRecursiveFuture to accomplish this.
//Using periodic
Rx.Observable.interval = function(period, scheduler) {
return Rx.Observable.create(function(observer) {
return scheduler.schedulePeriodic(0, period, function(count) {
observer.onNext(count);
return count + 1;
});
});
};
//Using scheduleRecursive
Rx.Observable.interval = function(period, scheduler) {
return Rx.Observable.create(function(observer) {
return scheduler.scheduleRecursiveFuture(0, period, function(count, self) {
observer.onNext(count);
self(period, count + 1);
});
});
};
Reference 1,
Reference 2;
The former should be easier to wrap your head around, essentially it is just scheduling something to occur repeatedly spaced in time based on the period parameter.
The latter is usually a little more difficult to explain, but essentially you are scheduling a task and then sometime during the execution of that task you are rescheduling it (which is what the self parameter) is doing. This allows you do get the same effect using the period parameter.
The timing of this work is all directly affected by which scheduler you decide to pass into the operator. For instance, if you pass in the default it will try to use the best method for an asynchronous completion, whether that be setTimeout, setInterval or some other thing I can't remember. If you pass in a TestScheduler or a HistoricalScheduler this actually won't do anything until you increment each of their respective clocks, but doing so gives fine grained control over how time flows.
tl;dr Only implement new Schedulers if you have some new overall notion of time to express, otherwise use the existing API to do work on whatever Scheduler best fits how you want time to pass.
Should you roll your own?
Plainly: No. Most likely you can get done what you need done with an existing operator. Something like buffer, window, sample, etc. Scheduler development is not completely straightforward.
How to roll your own RxJS 4 Scheduler
If you want to implement your own Scheduler, in RxJS 4, you'd subclass Rx.Scheduler, then override each schedule method: schedule, scheduleFuture, schedulePeriodic, scheduleRecursive, scheduleRecursiveFuture... You'd also likely want to override now to return something relevant to your schedule.
Here is an example of a custom scheduler that uses button clicks inside of real time
/**
NOTE: This is REALLY fast example. There is a lot that goes into implementing a
Scheduler in RxJS, for example what would `now()` do in the scheduler below? It's also missing a number of scheduling methods.
*/
class ButtonScheduler extends Rx.Scheduler {
/**
#param {string} the selector for the button (ex "#myButton")
*/
constructor(selector) {
super();
this.button = document.querySelector(selector);
}
schedule(state, action) {
const handler = (e) => {
action(state);
};
const button = this.button;
// next click the action will fire
button.addEventListener('click', handler);
return {
dispose() {
// ... unless you dispose of it
button.removeEventListener('click', handler);
}
};
}
// Observable.interval uses schedulePeriodic
schedulePeriodic(state, interval, action) {
const button = this.button;
let i = 0;
const handler = (e) => {
const count = i++;
if(count > 0 && count % interval === 0) {
state = action(state);
}
};
// next click the action will fire
button.addEventListener('click', handler);
return {
dispose() {
// ... unless you dispose of it
button.removeEventListener('click', handler);
}
};
}
}
Rx.Observable.interval(1, new ButtonScheduler('#go'))
.subscribe(x => {
const output = document.querySelector('#output');
output.innerText += x + '\n';
});
How to do it in RxJS 5 (alpha)
Scheduling changed again in RxJS 5, since that version was rewritten from the ground up.
In RxJS5, you can create any object that adheres to the following interface:
interface Scheduler {
now(): number
schedule(action: function, delay: number = 0, state?: any): Subscription
}
Where Subscription is just any object with an unsubscribe function (same as dispose, really)
Once again, though, I don't advise creating a scheduler unless it's completely necessary.
I really hope that helps answer your question.
This always gets me. After initializing all lovely UI elements on a web page, I load some content in (either into a modal or tabs for example) and the newly loaded content does not have the UI elements initialized. eg:
$('a.button').button(); // jquery ui button as an example
$('select').chosen(); // chosen ui as another example
$('#content').load('/uri'); // content is not styled :(
My current approach is to create a registry of elements that need binding:
var uiRegistry = {
registry: [],
push: function (func) { this.registry.push(func) },
apply: function (scope) {
$.each(uiRegistry.registry, function (i, func) {
func(scope);
});
}
};
uiRegistry.push(function (scope) {
$('a.button', scope).button();
$('select', scope).chosen();
});
uiRegistry.apply('body'); // content gets styled as per usual
$('#content').load('/uri', function () {
uiRegistry.apply($(this)); // content gets styled :)
});
I can't be the only person with this problem, so are there any better patterns for doing this?
My answer is basically the same as the one you outline, but I use jquery events to trigger the setup code. I call it the "moddom" event.
When I load the new content, I trigger my event on the parent:
parent.append(newcode).trigger('moddom');
In the widget, I look for that event:
$.on('moddom', function(ev) {
$(ev.target).find('.myselector')
})
This is oversimplified to illustrate the event method.
In reality, I wrap it in a function domInit, which takes a selector and a callback argument. It calls the callback whenever a new element that matches the selector is found - with a jquery element as the first argument.
So in my widget code, I can do this:
domInit('.myselector', function(myelement) {
myelement.css('color', 'blue');
})
domInit sets data on the element in question "domInit" which is a registry of the functions that have already been applied.
My full domInit function:
window.domInit = function(select, once, callback) {
var apply, done;
done = false;
apply = function() {
var applied, el;
el = $(this);
if (once && !done) {
done = true;
}
applied = el.data('domInit') || {};
if (applied[callback]) {
return;
}
applied[callback] = true;
el.data('domInit', applied);
callback(el);
};
$(select).each(apply);
$(document).on('moddom', function(ev) {
if (done) {
return;
}
$(ev.target).find(select).each(apply);
});
};
Now we just have to remember to trigger the 'moddom' event whenever we make dom changes.
You could simplify this if you don't need the "once" functionality, which is a pretty rare edge case. It calls the callback only once. For example if you are going to do something global when any element that matches is found - but it only needs to happen once. Simplified without done parameter:
window.domInit = function(select, callback) {
var apply;
apply = function() {
var applied, el;
el = $(this);
applied = el.data('domInit') || {};
if (applied[callback]) {
return;
}
applied[callback] = true;
el.data('domInit', applied);
callback(el);
};
$(select).each(apply);
$(document).on('moddom', function(ev) {
$(ev.target).find(select).each(apply);
});
};
It seems to me browsers should have a way to receive a callback when the dom changes, but I have never heard of such a thing.
best approach will be to wrap all the ui code in a function -even better a separate file -
and on ajax load just specify that function as a call back ..
here is a small example
let's say you have code that bind the text fields with class someclass-for-date to a date picker then your code would look like this ..
$('.someclass-for-date').datepicker();
here is what i think is best
function datepickerUi(){
$('.someclass-for-date').datepicker();
}
and here is what the load should look like
$('#content').load('/uri', function(){
datepickerUi();
})
or you can load it at the end of your html in script tag .. (but i dont like that , cuz it's harder to debug)
here is some tips
keep your code and css styles as clean as possible .. meaning that for text fields that should be date pickers give them one class all over your website ..
at this rate all of your code will be clean and easy to maintain ..
read more on OOCss this will clear what i mean.
mostly with jquery it's all about organization ... give it some thought and you will get what you want done with one line of code ..
edit
here is a js fiddle with something similar to your but i guess it's a bit cleaner click here