In protractor I'm trying to clean up my locators, and organize things a little better. Currently, I have a variable that contains the element locator for a dialog, and a save button:
var StoryPage = function(){
this.dialog = element(by.css('md-dialog'));
this.saveButton = element(by.buttonText('Save'));
}
My question is, is there a way to chain these element variables, so that I can find the save button within the dialog like so:
this.dialog.saveButton.click()
or
this.dropdown.saveButton.click()
Thanks in advance!
Yes, you can chain the element finders in Protractor:
var StoryPage = function() {
this.dialog = element(by.css('md-dialog'));
this.saveButton = this.dialog.element(by.buttonText('Save'));
}
Now the Save button would be located within/in the scope/inside the md-dialog element.
If you want to "scale" that to multiple page objects, you can define a base page object:
var BasePage = function() {
this.getSaveButton = function () {
return this.dialog.element(by.buttonText('Save'));
}
}
module.exports = new BasePage();
Then, use prototypical inheritance to inherit your other page objects from the base one which would allow you have save buttons inside different dialog containers:
var BasePage = require("base");
var StoryPage = function(){
this.dialog = element(by.css('md-dialog'));
}
StoryPage.prototype = Object.create(BasePage);
module.exports = new StoryPage();
Related
So i was trying to structure my code inside a class so it can be more organized, but iam struggling. I have the code:
class App {
constructor() {
// Get elements from DOM
const titleBox = document.getElementById('titleBox');
const navBox = document.getElementById('navBox');
const navLinks = document.querySelectorAll('.header__listLink');
const headerTitle = document.getElementById('headerTitle');
const headerSubtitle = document.getElementById('headerSubtitle');
const ulNav = document.getElementById('ulNav');
const ulNavLinks = document.querySelectorAll('.ulNavLink');
// for each nav link, add an event listener, expanding content area
navLinks.forEach((link) => {
link.addEventListener('click', this.clickedLinkState);
});
}
clickedLinkState(e) {
e.preventDefault();
titleBox.classList.remove("header__title-box");
titleBox.classList.add("header__title-box--linkClicked");
headerTitle.classList.remove("header__title");
headerTitle.classList.add("header__title--linkClicked");
headerSubtitle.classList.remove("header__subtitle");
headerSubtitle.classList.add("header__subtitle--linkClicked");
ulNav.classList.remove("header__listInline");
ulNav.classList.add("header__listInline--linkClicked");
navBox.classList.remove("header__nav-box");
navBox.classList.add("header__nav-box--linkClicked");
ulNavLinks.forEach((navLink) => {
navLink.classList.remove("header__listLink");
navLink.classList.add("header__listLink--linkClicked");
});
}
}
const app = new App();
And i got the error: "main.js:40 Uncaught ReferenceError: ulNavLinks is not defined
at HTMLLIElement.clickedLinkState (main.js:40)". the 'ulNavLinks' is a nodeList.
I was trying to define the elements using 'this.titleBox = ...', for exemple, but it got even worse, i could not access it from my clickedLinkState method. Outside the class it was working.
Why i cant access the 'ulNavLinks' inside my method? and why i cant access my propesties inside the method if i declare them 'this.titleBox', 'this.navBox'?
In JavaScript, as for now, instance properties can only being defined inside class methods using keyword this (here is the doc).
Also there is an experimental feature of supporting public/private fields, which you may use with some build steps due to poor browser support.
Make sure to use this:
class App {
constructor() {
// Get elements from DOM
this.titleBox = document.getElementById('titleBox');
this.navBox = document.getElementById('navBox');
this.navLinks = document.querySelectorAll('.header__listLink');
this.headerTitle = document.getElementById('headerTitle');
this.headerSubtitle = document.getElementById('headerSubtitle');
this.ulNav = document.getElementById('ulNav');
this.ulNavLinks = document.querySelectorAll('.ulNavLink');
// for each nav link, add an event listener, expanding content area
this.navLinks.forEach((link) => {
link.addEventListener('click', this.clickedLinkState.bind(this));
});
}
clickedLinkState(e) {
e.preventDefault();
this.titleBox.classList.remove("header__title-box");
this.titleBox.classList.add("header__title-box--linkClicked");
this.headerTitle.classList.remove("header__title");
this.headerTitle.classList.add("header__title--linkClicked");
this.headerSubtitle.classList.remove("header__subtitle");
this.headerSubtitle.classList.add("header__subtitle--linkClicked");
this.ulNav.classList.remove("header__listInline");
this.ulNav.classList.add("header__listInline--linkClicked");
this.navBox.classList.remove("header__nav-box");
this.navBox.classList.add("header__nav-box--linkClicked");
this.ulNavLinks.forEach((navLink) => {
navLink.classList.remove("header__listLink");
navLink.classList.add("header__listLink--linkClicked");
});
}
}
const app = new App();
Really asking this to get a better understanding of object-oriented javascript and to uncover some best practices for this scenario. Let's say I have a javascript object, such as:
SideSlider = {
rightArrow: '.controls i.right',
dot: '.controls .dot',
slide: '.slide',
init: function() {
$(this.rightArrow).click(this.nextSlide.bind(this));
$(this.leftArrow).click(this.prevSlide.bind(this));
$(this.dot).click(this.dotClick.bind(this));
},
nextSlide: function() {
var activeSlide = $('.slide.active-slide'),
firstSlide = $(this.slide).first(),
lastSlide = $(this.slide).last(),
nextUp = activeSlide.next(),
activeDot = $(".active-dot"),
nextDot = activeDot.next();
activeSlide.removeClass("active-slide");
nextUp.addClass("active-slide");
activeDot.removeClass("active-dot");
nextDot.addClass("active-dot");
$(this.leftArrow).removeClass("inactive");
if ( lastSlide.hasClass("active-slide")) {
$(this.rightArrow).addClass("inactive");
}
}
}
What is the proper way to use this object on multiple instances of DOM modules? In other words, what is the 'best-practice' way of using this object's functionality on two 'slide' instances in the same DOM
You could create a constructor for your object, and then pass a container element to that constructor, so it will be acting on that DOM-slider only. Everywhere where you perform a jQuery selector to retrieve certain element(s), you should set the scope to the given container element. You can do this by providing that container as second argument to $(..., ...).
The object instances are created with new SideSlider(container). It could look something like this:
function SideSlider(container) {
// Perform the jQuery selections with the second argument
// so that the selection returns only elements within the container:
this.$rightArrow = $('.controls i.right', container);
this.$dot = $('.controls .dot', container);
this.$slide = $('.slide', container);
this.container = container;
// ... etc
// Perform init-logic immediately
this.$rightArrow.click(this.nextSlide.bind(this));
this.$leftArrow.click(this.prevSlide.bind(this));
this.$dot.click(this.dotClick.bind(this));
// ... etc
}
// Define methods on the prototype
SideSlider.prototype.nextSlide = function() {
var activeSlide = $('.slide.active-slide', this.container),
firstSlide = $(this.slide, this.container).first(),
lastSlide = $(this.slide, this.container).last(),
nextUp = activeSlide.next(),
activeDot = $(".active-dot", this.container),
nextDot = activeDot.next();
activeSlide.removeClass("active-slide");
nextUp.addClass("active-slide");
activeDot.removeClass("active-dot");
nextDot.addClass("active-dot");
$(this.leftArrow, this.container).removeClass("inactive");
if (lastSlide.hasClass("active-slide")) {
$(this.rightArrow, this.container).addClass("inactive");
}
// ... etc
};
// Create & use the two objects:
var slider1 = new SideSlider($('#slider1'));
var slider2 = new SideSlider($('#slider2'));
// ...
slider1.nextSlide();
// ...etc.
If you have ES6 support, use the class notation.
Seeing as it looks like you are using jQuery, I would recommend looking into turning your project into a jQuery plugin. This would allow you to assign your code per use, and it's used quite commonly by developers of sliders, and other sorts of JavaScript powered widgets. The jQuery website has a great tutorial on how to accomplish this, and it can be found here:
https://learn.jquery.com/plugins/basic-plugin-creation/
Having an object, say Book which has a collection of other objects Page. So I instantiate pages from raw data passed to Book:
function Book(data){
this.pages = [];
var self = this;
data.forEach(function(item){
self.add(item);
});
}
Book.prototype.add = function(data){
this.pages.push(new Page(data));
}
function Page(data){
// some validation code
this.prop = data.prop;
}
Page.prototype...
from lectures on testability I heard that it is a bad practice to instantiate(use new) in another object. What is the right way to do the same?
If it is okay - is there any difference if I instantiate a new Page in add() method or pass to it as an object already(this.add(new Page(data)))?
The problem is when you want to write unit tests for you code.
Let's take an actual example. Some times later, your code is this one :
function Book(data){
this.pages = [];
var self = this;
data.forEach(function(item){
self.add(item);
});
}
Book.prototype.add = function(data){
var page = new Page(data);
// Because the page need to know its book
page.book = this;
this.pages.push(page);
}
function Page(data){
// some validation code
this.data = data;
}
Page.prototype...
Now you want to write an unit test for the add method, and you want to check that after you added a new page in your book, the page.book is the book. But with a code like that, you can't do it. Becase the page is created inside the method, you can't check anything.
Buf if we rewrite the code like that :
Book.prototype.add = function(page){
page.book = this;
this.pages.push(page);
}
We can now write a unit test :
describe('book.add', function() {
it('should set the current book in the book property of the page', function() {
var book = new Book(),
page = new Page();
book.add(page);
expect(page.book).toBe(book);
});
});
But, if you do not want to write any unit test like this one, there's no reason to not create the new page inside the method add.
In objective c we can pass data between two classes very easily by nextClassname.recievedVariable = passedVariable;
i tried same with titanium, but failed
I tried as follows
in Second Class
$.table.addEventListener('click', function(e) {
var selected = e.row;
alert(e.row.title);
var TodayStatus = Titanium.UI.createWindow({ url:'TodayStatus.js' });
TodayStatus.seletedProj = e.row.title;
// var TodayStatus = new Alloy.createController("TodayStatus");
TodayStatus.getView().open();
});
in the first Calss whic we have to recieve string from another class
var win = Ti.UI.currentWindow;
Ti.API.info(win.seletedProj);
But causes errors like
message = "'undefined' is not an object (evaluating 'win.seletedProj')";
[ERROR] : name = TypeError;
You can pass the data by passing parameter like this.
x.addEVentListener('click', function(e){
var controller = require('controllerPath').createWindow(e.value);
controller.open();
})
And in controller.js
exports.createWindow = function(value)
{
//whatever You like to do with UI
}
If you create a new window by using the 'url' parameter it automatically puts that code into it's own Sub-context and it isn't possible to pass complex objects accross, see here:
http://docs.appcelerator.com/titanium/3.0/#!/api/Titanium.UI.Window
I don't tend to do it this way anymore.
The way i would do it would be to create your todayStatus window as a common js class:
// todayStatus.js
var win = Ti.UI.createWindow({ top:0, left: 0, right: 0, bottom:0, etc... });
//any extra visual building code can go here
win.open();
exports.seletedProj = function(rowTtl){
//this function is available outside the class
}
Then you can reference it from your main class like this:
// main.js
var TodayStatus = require('todayStatus');
TodayStatus.seletedProj(e.row.title);
etc...
Hope that helps
see link here for how to do it in Alloy,
https://github.com/aaronksaunders/alloy_fugitive/blob/master/app/controllers/Fugitives.js#L29
but basic idea is to pass the object as a parameter when creating the new controller.
$.table.addEventListener('click', function(_e) {
var detailController = Alloy.createController('FugitiveDetail', {
parentTab : $.fugitiveTab,
data : fugitiveCollection.get(_e.rowData.model)
});
$.fugitiveTab.open(detailController.getView());
});
The link I provided has a complete solution using Alloy
I have a few questions about Best Practises using javascript in external files and namespacing.
Let's have a namespace MyCompany, global configuration stuff, code for individual pages and maybe some "API"s.
var MyCompany = {};
Global configuration in HTML
MyCompany.root = "/";
Which approach is better
First
MyCompany.Page = {};
(function(ns} {
ns.init = function() {
var root = MyCompany.root;
ajax(root+"somepage.html");
};
}(MyCompany.Page.Home = MyCompany.Page.Home || {});
and in html use
<script>
$( function() {
MyCompany.Page.Home.init();
});
</script>
Second (Page as an Class and its instance)
MyCompany.Page.Home = function() {
var root = MyCompany.root;
this.init = function() {
ajax(root + "somepage.html");
};
};
in html
<script>
var page = new MyCompany.Page.Home();
$( function() {
page.init();
});
</script>
Submodules and Mixing API with Page javascript
If our Homepage has some reviews.
MyCompany.Page.Home.Reviews = function() {
this.init = function() {
load_stuff();
}
};
And now inside Page init use
MyCompany.Home.Page = function(data) {
var reviews = new MyCompany.Home.Page.Reviews();
this.init = function() {
reviews.init();
};
};
Could that cause troubles?
It's obvious that Reviews extends MyCompany.Home.Page, but MyCompany.Home.Page requires Reviews.
It shouldn't cause troubles if instance on MyCompany.Home.Page is created after MyCompany.Home.Page.Reviews are loaded, right? Because Reviews in fact will extend the function object, is that right?
I guess this depends on answer to first question.
It also could be
(function(ns) {
ns.init = function() { MyCompany.Page.Home.Reviews.init(); };
})(MyCompany.Page.Home = MyCompany.Page.Home || {} );
(function(ns) {
ns.init = function() { load_stuff(); };
})(MyCompany.Page.Home.Reviews = MyCompany.Page.Home.Reviews || {});
Also should I somehow separate API of Page javascript?
Such as
MyCompany.APIS.Maps = function(location) {
/* Private variables */
var _location = location;
/* Private functions */
function search_address(address) { .. do search .. }
/* Public interface */
this.search = search_address;
do some initialization ...
};
I'd be glad if anyone reads it all to leave some comment.
Thank you in advance.
Which approach is better? Revealing singleton module (first) or a constructor function/class and its instance (second)?
Depends on your use case. If you don't expect multiple page objects to exist at once (and you hardly seem to), the singleton (with an init function) is really fine. Everything else could be considered wrong or at least overkill.
Same thing holds true for your MyCompany.Page.Home.Reviews (or MyCompany.Home.Page.Reviews?) class module, of which you seem to need only one instance.
It shouldn't cause troubles if instance on MyCompany.Home.Page is created after MyCompany.Home.Page.Reviews are loaded, right? Because Reviews in fact will extend the function object, is that right?
Yes.
(function(ns) {
ns.init = function() { MyCompany.Page.Home.Reviews.init(); };
})(MyCompany.Page.Home = MyCompany.Page.Home || {} );
If you have that ns shortcut available, you should use it:
(function(ns) {
ns.init = function() { ns.Reviews.init(); };
})(MyCompany.Page.Home = MyCompany.Page.Home || {} );
Also should I somehow separate API of Page javascript?
For development: Yes, in every case. Each module should have its own file. When deploying, you might concatenate them together for faster loading, but that's a different question.