function FriendlyChat() {
// statements
}
FriendlyChat.protoype.somemethod = function() {
// statements
};
FriendlyChat.protoype.somemethod2 = function() {
//statements
};
window.onload = function() {
window.friendlyChat = new FriendlyChat();
};
So i noticed the above structure for js while working on a google codelab.
And I have two ques.
in normal objects you have to call the function i.e Object.somemethod()
How does this structure call the methods assigned to it.
From my limited understanding, Firendlychat.protoype.the method treats the
function as an object and the methods are passed to the new object created on
window.onload.Via
inheritance, The object created i.e friendlychat has all these methods.
Yet none of the methods are called in any way. How does this work?
Is there any advantage to structuring your code in this way other than
readability
Note :
Main function
function FriendlyChat() {
this.checkSetup();
// Shortcuts to DOM Elements.
this.messageList = document.getElementById('messages');
this.messageForm = document.getElementById('message-form');
// Saves message on form submit.
this.messageForm.addEventListener('submit', this.saveMessage.bind(this));
this.signOutButton.addEventListener('click', this.signOut.bind(this));
this.signInButton.addEventListener('click', this.signIn.bind(this));
// Toggle for the button.
var buttonTogglingHandler = this.toggleButton.bind(this);
this.messageInput.addEventListener('keyup', buttonTogglingHandler);
this.messageInput.addEventListener('change', buttonTogglingHandler);
// Events for image upload.
this.submitImageButton.addEventListener('click', function(e) {
e.preventDefault();
this.mediaCapture.click();
}.bind(this));
this.mediaCapture.addEventListener('change',
this.saveImageMessage.bind(this));
this.initFirebase();
}
//the methods are setup here
// Sets up shortcuts to Firebase features and initiate firebase auth.
FriendlyChat.prototype.initFirebase = function() {
this.auth = firebase.auth();
this.database = firebase.database();
this.storage = firebase.storage();
// Initiates Firebase auth and listen to auth state changes.
this.auth.onAuthStateChanged(this.onAuthStateChanged.bind(this));
};
// Saves a new message on the Firebase DB.
FriendlyChat.prototype.saveMessage = function(e) {
e.preventDefault();
}
};
FriendlyChat.prototype.setImageUrl = function(imageUri, imgElement) {
imgElement.src = imageUri;
};
// Saves a new message containing an image URI in Firebase.
// This first saves the image in Firebase storage.
FriendlyChat.prototype.saveImageMessage = function(event) {
event.preventDefault();
var file = event.target.files[0];
// Clear the selection in the file picker input.
this.imageForm.reset();
// Check if the file is an image.
if (!file.type.match('image.*')) {
var data = {
message: 'You can only share images',
timeout: 2000
};
this.signInSnackbar.MaterialSnackbar.showSnackbar(data);
return;
}
// Check if the user is signed-in
if (this.checkSignedInWithMessage()) {
// TODO(DEVELOPER): Upload image to Firebase storage and add message.
}
};
// Signs-in Friendly Chat.
FriendlyChat.prototype.signIn = function() {
var provider = new firebase.auth.GoogleAuthProvider();
this.auth.signInWithRedirect(provider);
};
// Signs-out of Friendly Chat.
FriendlyChat.prototype.signOut = function() {
this.auth.signOut();
};
One of the advantages I've seen when using prototype inheritance was that you can control all instances of an object. For ex:
function FriendlyChat() {
this.chatIsActive = true;
}
FriendlyChat.prototype.deactivateChat = function(...rooms) {
for (chatRoom of rooms) {
chatRoom.chatIsActive = false;
}
};
var chat1 = new FriendlyChat();
var chat2 = new FriendlyChat();
var chatController = new FriendlyChat();
chatController.deactivateChat(chat1, chat2)
console.log(chat1.chatIsActive)
In ES6, however, you can do it:
class FriendlyChat {
constructor() {
this.chatIsActive = true;
}
static deactivateChat(...rooms) {
for (let chatRoom of rooms) {
chatRoom.chatIsActive = false;
}
}
}
var chat1 = new FriendlyChat();
var chat2 = new FriendlyChat();
FriendlyChat.deactivateChat(chat1, chat2)
console.log(chat1.chatIsActive)
And the another advantage of using prototype is that you can save memory spaces when you make an object from new keyword. For instance, the code in ES5 above, you can see chat1 and chat2 I've made by using new. Then chat1 and chat2 will be able to access deactivateChat() method which is in a sharing-space. It's because of the concept, called prototype-chaining.
And the next ES6 version is just a syntactic sugar - under the hood it does the same as ES5 version
I post this as a reference to others who have been faced with this dilemma.
First of all, ONe of the core issues for me was migrating from java, I seemed to be familiar territory but things work a bit different in js.I strongly recommend these links:
Objects in Detail
js Prototype
So the key to why this method works is due to the
window.friendlyapp =new friendlychat()
Now normally in most languages you have an object
obj() {
attr : value
method: function() {}
}
And then to use the method you do
var child = new obj();
child.method();
but in this method the var is made an instance of the window object and thats why none of the methods of the app need to be explicitly called.
Related
In the below code snippet, how can I use osList and featureList using typescript can you please help me with this. I need to know how to work with a list of elements in the page object model like submitName function.
import { Selector, t } from 'testcafe';
const label = Selector('label');
class Feature {
constructor (text) {
this.label = label.withText(text);
this.checkbox = this.label.find('input[type=checkbox]');
}
}
class OperatingSystem {
constructor (text) {
this.label = label.withText(text);
this.radioButton = this.label.find('input[type=radio]');
}
}
export default class Page {
constructor () {
this.nameInput = Selector('#developer-name');
this.triedTestCafeCheckbox = Selector('#tried-test-cafe');
this.populateButton = Selector('#populate');
this.submitButton = Selector('#submit-button');
this.results = Selector('.result-content');
this.commentsTextArea = Selector('#comments');
this.featureList = [
new Feature('Support for testing on remote devices'),
new Feature('Re-using existing JavaScript code for testing'),
new Feature('Running tests in background and/or in parallel in multiple browsers'),
new Feature('Easy embedding into a Continuous integration system'),
new Feature('Advanced traffic and markup analysis')
];
this.osList = [
new OperatingSystem('Windows'),
new OperatingSystem('MacOS'),
new OperatingSystem('Linux')
];
this.slider = {
handle: Selector('.ui-slider-handle'),
tick: Selector('.slider-value')
};
this.interfaceSelect = Selector('#preferred-interface');
this.interfaceSelectOption = this.interfaceSelect.find('option');
this.submitButton = Selector('#submit-button');
}
async submitName (name) {
await t
.typeText(this.nameInput, name)
.click(this.submitButton);
}
}
I can describe this to you based on the TestCafe's basic example. First, you need to change the page-model file extension to ts and create this file according to the TypeScript rules (for example, see my gist). Then, you can change the test file extension to ts and start test execution like you do it for any js tests, e. g. testcafe chrome test.ts.
As an example on basic setup one index is created.
db.onupgradeneeded = function(event) {
var db = event.target.result;
var store = db.createObjectStore('name', { keyPath: 'id' });
store.createIndex('by name', 'name', { unique: false });
};
Question:
Is it possible to create/append more indexes to the same objectStore on the future versionupdate? Since if I try:
db.onupgradeneeded = function(event) {
var db = event.target.result;
var store = db.createObjectStore('name', { keyPath: 'id' });
store.createIndex('by newName', 'newName', { unique: false });
};
It throws an error that current objectStore does already exist. An if I try to create store reference using transaction:
db.onupgradeneeded = function(event) {
var db = event.target.result;
var store = db.transaction('name', 'readwrite').objectStore('name');
store.createIndex('by newName', 'newName', { unique: false });
};
It throws that version change transaction is currently running
Yes it is possible. It can be a bit confusing at first. You want to get the existing object store via the implicit transaction created for you within onupgradeneeded. This is a transaction of type versionchange which is basically like a readwrite transaction but specific to the onupgradeneeded handler function.
Something like this:
var request = indexedDB.open(name, oldVersionPlusOne);
request.onupgradeneeded = myOnUpgradeNeeded;
function myOnUpgradeNeeded(event) {
// Get a reference to the request related to this event
// #type IDBOpenRequest (a specialized type of IDBRequest)
var request = event.target;
// Get a reference to the IDBDatabase object for this request
// #type IDBDatabase
var db = request.result;
// Get a reference to the implicit transaction for this request
// #type IDBTransaction
var txn = request.transaction;
// Now, get a reference to the existing object store
// #type IDBObjectStore
var store = txn.objectStore('myStore');
// Now, optionally inspect index names, or create a new index
console.log('existing index names in store', store.indexNames);
// Add a new index to the existing object store
store.createIndex(...);
}
You also will want to take care to increment the version so as to guarantee the onupgradeneeded handler function is called, and to represent that your schema (basically the set of tables and indices and properties of things) has changed in the new version.
You will also need to rewrite the function so that you only create or make changes based on the version. You can use event.oldVersion to help with this, or things like db.objectStoreNames.contains.
Something like this:
function myOnUpgradeNeeded(event) {
var is_new_db = isNaN(event.oldVersion) || event.oldVersion === 0;
if(is_new_db) {
var db = event.target.result;
var store = db.createObjectStore(...);
store.createIndex('my-initial-index');
// Now that you decided you want a second index, you also need
// to do this for brand new databases
store.createIndex('my-second-new-index');
}
// But if the database already exists, we are not creating things,
// instead we are modifying the existing things to get into the
// new state of things we want
var is_old_db_not_yet_current_version = !isNaN(event.oldVersion) && event.oldVersion < 2;
if(is_old_db_not_yet_current_version) {
var txn = event.target.transaction;
var store = txn.objectStore('store');
store.createIndex('my-second-new-index');
}
}
Pay close attention to the fact that I used event.target.transaction instead of db.transaction(...). These are not at all the same thing. One references an existing transaction, and one creates a new one.
Finally, and in addition, a personal rule of mine and not a formal coding requirement, you should never be using db.transaction() from within onupgradeneeded. Stick to modifying the schema when doing upgrades, and do all data changes outside of it.
It appears that the Safari and iPhone web browsers are incapable of allowing the user to create different object stores from separate transactions. This is even the case when the user closes the database, increments the version number and then uses createObjectStore() within the onupgradedneeded callback.
Is there a workaround?
For example, visit http://bl.ocks.org/redgeoff/1dea140c52397d963377 in Safari and you'll get an alert with the "AbortError" when Safari attempts to create the 2nd object store.
For convenience, here is the same snippet of code:
var idb = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB
|| window.msIndexedDB;
// Generate a unique db name as IndexedDB is very delicate and we want our test
// to focus on a new DB
var dbName = 'mydb' + '_' + (new Date()).getTime() + '_'
+ Math.round(1000000*Math.random());
var db = null;
var version = 1;
var open = function (version, onSuccess, onUpgradeNeeded) {
var request = null;
if (version) {
request = idb.open(dbName, version);
} else { // 1st time opening?
request = idb.open(dbName);
}
request.onupgradeneeded = function () {
if (onUpgradeNeeded) {
onUpgradeNeeded(request);
}
};
request.onsuccess = function () {
db = request.result;
if (onSuccess) {
onSuccess(request);
}
};
request.onerror = function () {
console.log('error=', request.error);
alert('error=' + JSON.stringify(request.error));
};
};
var createObjectStore = function (name, callback) {
db.close(); // synchronous
version++; // increment version to trigger onupgradeneeded
open(version, callback, function (request) {
request.result.createObjectStore(name, {
keyPath: 'id'
});
});
};
// NOTE: we could create the first store when opening the DB for the first time, but we'll keep
// things simple and reuse our createObjectStore code for both object stores
open(null, function () {
createObjectStore('store1', function () {
createObjectStore('store2', function () {
console.log('done creating both stores');
});
});
});
I tried using a sleep of 2 secs after the DB is closed and reopened and that doesn't appear to work. If there is no workaround then this essentially means that you cannot use the IndexedDB implementation in Safari to dynamically create object stores, which means that you need to know all your object stores before creating a DB.
Unless I am mistaken and someone has a workaround, the best way to dynamically add object stores is to implement a db-per-object-store design. In other words, you should create a new database whenever you need to create a new object store.
Another good option is to use https://github.com/axemclion/IndexedDBShim to emulate IndexedDB with WebSQL.
I have simple image uploader in a website and a javascript function which uses FileReader and converts image to base64 to display it for user without uploading it to actual server.
function generateThumb(file) {
var fileReader = new FileReader();
fileReader.readAsDataURL(file);
fileReader.onload = function (e) {
// Converts the image to base64
file.dataUrl = e.target.result;
};
}
Now I am trying to write tests for this method using Mocha and Chai. My thinking is that I want to check if the file.dataUrl was successfully created and it is base64. So I would like to mock the local file somehow in testing environment (not sure how to do this). Or I should not test this at all and assume that this is working ?
The answer here depends a little. Does your "generateThumbs" method have other logic than loading a files contents and assign that to a property of a passed in object? Or does it have other logic such as generating the thumbnail from the image data, reading out file properties and assigning them to the file object? and so on?
If so then I would infact suggest you mock out the FileReader object with your own, so that you can control your test results. However, it isn't the FileReaders functionality you want to test, it is your own logic. So you should assume that FileReader works and test that your code that depends upon it works.
Now since the method you posted was a bit on the small side, In that case I would just assume it works, rename the method and work on testing the rest of your code. But there is a place for having such a mock, and I must admit it was quite fun to figure out how to mock the event target, so I will give an example here, using your method as a basis:
//This implements the EventTarget interface
//and let's us control when, where and what triggers events
//and what they return
//it takes in a spy and some fake return data
var FakeFileReader = function(spy, fakeData) {
this.listeners = {};
this.fakeData = fakeData;
this.spy = spy;
this.addEventListener('load', function () {
this.spy.loaded = true;
});
};
//Fake version of the method we depend upon
FakeFileReader.prototype.readAsDataURL = function(file){
this.spy.calledReadAsDataURL = true;
this.spy.file = file;
this.result = this.fakeData;
this.dispatchEvent({type:'load'}); //assume file is loaded, and send event
};
FakeFileReader.prototype.listeners = null;
FakeFileReader.prototype.addEventListener = function(type, callback) {
if(!(type in this.listeners)) {
this.listeners[type] = [];
}
this.listeners[type].push(callback);
};
FakeFileReader.prototype.removeEventListener = function(type, callback) {
if(!(type in this.listeners)) {
return;
}
var stack = this.listeners[type];
for(var i = 0, l = stack.length; i < l; i++) {
if(stack[i] === callback){
stack.splice(i, 1);
return this.removeEventListener(type, callback);
}
}
};
FakeFileReader.prototype.dispatchEvent = function(event) {
if(!(event.type in this.listeners)) {
return;
}
var stack = this.listeners[event.type];
event.target = this;
for(var i = 0, l = stack.length; i < l; i++) {
stack[i].call(this, event);
}
};
// Your method
function generateThumb(file, reader){
reader.readAsDataURL(file);
reader.addEventListener('load', function (e) {
file.dataUrl = base64(e.target.result);
});
}
Now we have a nice little object that behaves like a FileReader, but we control it's behavior, and we can now use our method and inject it into, thus enabling us to use this mock for testing and the real thing for production.
So we can now write nice unit tests to test this out:
I assume you have mocha and chai setup
describe('The generateThumb function', function () {
var file = { src: 'image.file'};
var readerSpy = {};
var testData = 'TESTDATA';
var reader = new FakeFileReader(readerSpy, testData);
it('should call the readAsDataURL function when given a file name and a FileReader', function () {
generateThumb(file, reader);
expect(readerSpy.calledReadAsDataURL).to.be.true;
expect(readerSpy.loaded).to.be.true;
});
it('should load the file and convert the data to base64', function () {
var expectedData = 'VEVTVERBVEE=';
generateThumb(file, reader);
expect(readerSpy.file.src).to.equal(file.src);
expect(readerSpy.file.dataUrl).to.equal(expectedData);
});
});
Here is a working JSFiddle example:
https://jsfiddle.net/workingClassHacker/jL4xpwwv/2/
The benefits here are you can create several versions of this mock:
what happens when correct data is given?
what happens when incorrect data is given?
what happens when callback is never called?
what happens when too large of a file is put in?
what happens when a certain mime type is put in?
You can offcourse massively simplify the mock if all you need is that one event:
function FakeFileReader(spy, testdata){
return {
readAsDataURL:function (file) {
spy.file = file;
spy.calledReadAsDataURL = true;
spy.loaded = true;
this.target = {result: testdata};
this.onload(this);
}
};
}
function generateThumb(file, reader){
reader.onload = function (e) {
file.dataUrl = base64(e.target.result);
};
reader.readAsDataURL(file);
}
Which is how I would actually do this. And create several of these for different purposes.
Simple version:
https://jsfiddle.net/workingClassHacker/7g44h9fj/3/
I am writing a Javascript API library that provides consumers with an interface that enables them to interact with our backend web services. It is envisioned that a consumer will be writing a javascript client web application that draws heavily on the API provided for by the library.
I have come up with this "pattern" for maintaining state and making functionality "available" as certain criteria are met (for example, an authenticated user is logged in client-side).
Is this an appropriate way to achieve that end? Or am I unwittingly breaking some convention or best practice that will bite me later on?
// file: clientApi.js (library)
ClientObject = function () {
this.objectname = "a client class";
}
ClientObject.prototype.loginUser = function(name) {
this.loggedin = true;
if (typeof this.User === 'undefined') {
this.User = new ClientObject.User(name);
}
}
ClientObject.User = function (name) {
this.username = name;
}
ClientObject.User.prototype.getProfile = function() {
return 'user profile';
}
// file: app.js (consuming application)
var testClient = new ClientObject();
console.log('testClient.User = ' + (typeof testClient.User)); // should not exist
testClient.loginUser('Bob'); // should login 'bob'
console.log('testClient.User = ' + (typeof testClient.User)); // should exist
console.log(testClient.User.username); // bob
testClient.loginUser('Tom'); // should not do anything
console.log(testClient.User.username); // bob still
console.log(testClient.User.getProfile()); // tada, new functionality available
My question: is this approach valid? Is there a pattern that I'm touching on that might offer a better explanation or method of achieving my end goal?
I asked a similar question here with a bunch of other ones, unfortunately the above code was somewhat lost in the noise: Javascript: creation of object from an already instantiated object versus the prototype
Your API should have some secrets. That's why do not make all your functions public. Let's analyze some parts of your code:
testClient.loginUser('Tom'); // should not do anything
But your implementation allows client to do next:
testClient.User = new ClientObject.User(name);
Now user will be changed to "Tom".
Let's change your clientApi.js code, using revealing prototype pattern:
ClientObject = function () {
this.objectname = "a client class";
this.username;
this.User;
this.loggedin;
}
ClientObject.prototype = function() {
var loginUser = function(name) {
this.loggedin = true;
if (typeof this.User === 'undefined') {
this.User = new User(name);
}
};
var User = function (name) {
this.username = name;
};
User.prototype.getProfile = function() {
return 'user profile';
};
return {
loginUser : loginUser
}
}()
Now client cannot change logged in User like in first version of the library. You can use some variations, but that's the idea.