jeudi 1 juin 2017

Am I the only person struggling with how prototype is used in the Firebase Codelab (web)

I am not the most experienced javascript programmer around, but I have worked through the Firebase Web CodeLab and found the way that EVERYTHING (including Template) is included in the Prototype to be very confusing. Is this approach recommended by the Firebase Team?

Can anyone familiar with this CodeLab, and who knows the pattern that is being applied, I would like to better understand this and why this is used in Firebase? I am guessing that the firebase.auth() is particularly fond of remaining in the prototype??? not really sure but did find it very difficult to extract portions of code that refer to the firebase.auth().currentUser.

So that this seems less like a complaint masked in a question, I have attached some code, using my own idiosyncratic style and not suggesting HOW this should be done, I am sharing what I have come up with after a week of extracting what I could from the FriendlyChat.prototype, and minimizing binds. It is my hope that this is an aide to someone else and possibly someone more skilled than I can fork the referenced "CodeLab" and help those confused souls like myself better understand how to work with Firebase, without the FriendlyChat.prototype and the associated bindings getting in the way.

Let's keep in mind that this Codelab is presented as a tutorial of sorts, and while I can copy and paste with the best of them, it seems to me the integration with prototype and reliance on 'this' get's in the way of learning Firebase.

Note of warning, I modified the snackbar messages to suit my mood of this "FriendlyChat" with some insults from "The Princess Bride" please take these modifications in the spirit they were intended.

'use strict';

const MESSAGE_TEMPLATE =
    `<div class="message-container">
        <div class="spacing">
            <div class="pic"></div>
        </div>
        <div class="message"></div>
        <div class="name"></div>
      </div>`;

const LOADING_IMAGE_URL = 'http://ift.tt/2jLqAyu';
const onlyImageFilesMessageData = {
  message: `You can only share images...  
            Friendless. Brainless. Helpless. Hopeless! 
            Do you want me to send you back to where you were? 
            Unemployed? In Greenland?`,
  timeout: 2000
};
const mustSignInMessageData = {
  message: `You must sign-in first...  
            I\'ll use small words so that 
            you will be sure to understand; 
            you warthog faced buffoon`,
  timeout: 2000
};
const toAuthStateLoggedIn = () => {
    // Show user's profile and sign-out button.
    document.getElementById('user-name').removeAttribute('hidden');
    document.getElementById('user-pic').removeAttribute('hidden');
    document.getElementById('sign-out').removeAttribute('hidden');
    // Hide sign-in button.    
    document.getElementById('sign-in').setAttribute('hidden', 'true');
};
const toAuthStateLoggedOut = () => {
    // Hide user's profile and sign-out button.
    document.getElementById('user-name').setAttribute('hidden', 'true');
    document.getElementById('user-pic').setAttribute('hidden', 'true');
    document.getElementById('sign-out').setAttribute('hidden', 'true');
    // Show sign-in button.
    document.getElementById('sign-in').removeAttribute('hidden');
};
const testAndErrorOnImageFileTypes = (fileType) =>  //  Side Effects only
  (!fileType.match('image.*')) 
    ? document.getElementById('must-signin-snackbar').MaterialSnackbar.showSnackbar(onlyImageFilesMessageData)
    : console.log("The file is in an acceptable image format");

const messageData = (currentUser) => { 
  return {
    name: currentUser.displayName,
    text: document.getElementById('message').value,
      photoUrl: (!currentUser || !currentUser.photoURL) 
        ?  '/images/profile_placeholder.png'
        :  currentUser.photoURL
  }
};
const pushMessage = function() {
  console.log(messageData(this.auth.currentUser));
  (document.getElementById('message').value && this.auth.currentUser) 
    ? this.messagesRef.push(messageData(this.auth.currentUser))
        .then( resetMaterialTextfield() )
          .catch((error) => consoleError('Error writing new message to Firebase Database', error))
    : showSnackBarMessage(mustSignInMessageData);
};
const pushImageMessage = function() {
  (document.getElementById('message').value && this.auth.currentUser) 
    ? this.messagesRef.push(messageData(this.auth.currentUser))
        .then( resetMaterialTextfield() )
          .catch((error) => consoleError('Error writing new message to Firebase Database', error))
    : showSnackBarMessage(mustSignInMessageData);
};
// Saves a new message containing an image URI in Firebase.
// This first saves the image in Firebase storage.
const saveImageMessage = function(event) {
  event.preventDefault();
  const fc = this;  //  Note Bind on function call
  const file = event.target.files[0];
  testAndErrorOnImageFileTypes(file.type);
  document.getElementById('image-form').reset();
  // Check if the user is signed-in
  if (fc.auth.currentUser) {
    // We add a message with a loading icon that will get updated with the shared image.
    fc.messagesRef.push({
      name: fc.auth.currentUser.displayName,
      imageUrl: LOADING_IMAGE_URL,
      photoUrl: fc.auth.currentUser.photoURL || '/images/profile_placeholder.png'
    }).then( function(data) {
      // Upload the image to Cloud Storage.
      var filePath = fc.auth.currentUser.uid + '/' + data.key + '/' + file.name;
      return fc.storage.ref(filePath).put(file).then(function(snapshot) {
        // Get the file's Storage URI and update the chat message placeholder.
        var fullPath = snapshot.metadata.fullPath;
        return data.update({imageUrl: fc.storage.ref(fullPath).toString()});
      }.bind(fc));
    }.bind(fc)).catch(function(error) {
      console.error('There was an error uploading a file to Cloud Storage:', error);
    });
  }
};
// Triggers when the auth state change for instance when the user signs-in or signs-out.
const onChangeInAuthState = function(user) {
  if (user) { // User is signed in!
    // Set the user's profile pic and name.
    document.getElementById('user-pic').style.backgroundImage = 
        'url(' + (user.photoURL || '/images/profile_placeholder.png') + ')';
    document.getElementById('user-name').textContent = user.displayName;
    toAuthStateLoggedIn();
    this.loadMessages();

    // We save the Firebase Messaging Device token and enable notifications.
    this.saveMessagingDeviceToken();
  } else { // User is signed out!
    toAuthStateLoggedOut();
  }
};

// Sets the URL of the given img element with the URL of the image stored in Cloud Storage.
const setImageUrlSRB = function(imageUri, imgElement) {
  // If the image is a Cloud Storage URI we fetch the URL.
  if (imageUri.startsWith('gs://')) {
    imgElement.src = LOADING_IMAGE_URL;
    firebase.storage().refFromURL(imageUri).getMetadata().then(function(metadata) {
      imgElement.src = metadata.downloadURLs[0];
    });
  } else {
    imgElement.src = imageUri;
  }
};

const createMessageDiv = (key, name, text, picUrl, imageUri) => {
        const container = document.createElement('div');
        container.innerHTML = MESSAGE_TEMPLATE;
        let div = container.firstChild;
        div.setAttribute('id', key);
        document.getElementById('messages').appendChild(div);
        return div;
     };

const loadTextMessage = (text, messageElement) => {
  messageElement.textContent = text;
  messageElement.innerHTML = messageElement.innerHTML.replace(/\n/g, '<br>');
};

const loadImageMessage = (imageUri, messageElement) => {
  const image = document.createElement('img');
    image.addEventListener('load', function() {
      document.getElementById('messages').scrollTop = document.getElementById('messages').scrollHeight;
    }.bind(this));
    setImageUrlSRB(imageUri, image);
    messageElement.innerHTML = '';
    messageElement.appendChild(image);
};
const displayMessageSRB = function(key, name, text, picUrl, imageUri ) {
  const div = (document.getElementById(key)) 
    ? document.getElementById(key)
    : createMessageDiv(key, name, text, picUrl, imageUri);
  console.log(picUrl);
  const userPicBacgroundImage = 'url(' + picUrl + ')';
  (picUrl) ? div.querySelector('.pic').style.backgroundImage = userPicBacgroundImage: false;

  div.querySelector('.name').textContent = name;
  var messageElement = div.querySelector('.message');
  (text) ? loadTextMessage(text, messageElement) // If the message is text.
         : loadImageMessage(imageUri, messageElement);
  // Show the card fading-in and scroll to view the new message.
  setTimeout(function() {div.classList.add('visible')}, 1);
  document.getElementById('messages').scrollTop = document.getElementById('messages').scrollHeight;
  document.getElementById('message').focus();
};
const toggleButtonFunction = () => (!!(document.getElementById('message').value)) 
    ? document.getElementById('submit').removeAttribute('disabled')
    : document.getElementById('submit').setAttribute('disabled', 'true');

const resetMaterialTextfield = () => {
  document.getElementById('message').value = '';
  toggleButtonFunction();
//  document.getElementById('message').MaterialTextfield.boundUpdateClassesHandler();
};

const showSnackBarMessage = (messageData) => 
  document.getElementById('must-signin-snackbar')
    .MaterialSnackbar.showSnackbar(messageData);

const consoleError = (messageString, error) => console.error(messageString, error);
FriendlyChat.prototype.auth = firebase.auth();
FriendlyChat.prototype.database = firebase.database();
FriendlyChat.prototype.storage = firebase.storage();
// Saves a new message on the Firebase DB.
FriendlyChat.prototype.saveMessage = function() {
  const fc = this;  // per Binding
  (document.getElementById('message').value && fc.auth.currentUser) 
    ? fc.messagesRef.push(messageData(fc.auth.currentUser))
        .then( resetMaterialTextfield() )
          .catch((error) => consoleError('Error writing new message to Firebase Database', error))
    : showSnackBarMessage(mustSignInMessageData);
};
// Initializes FriendlyChat.
function FriendlyChat() {
  const fc = this;
  fc.googleAuthProvider = new firebase.auth.GoogleAuthProvider();
//  fc.signingOut = () => fc.auth.signOut();
//  fc.signingIn = () =>  fc.auth.signInWithPopup(fc.googleAuthProvider)
//  fc.messageReady = () => document.getElementById('message').value && this.auth.currentUser;
  document.getElementById('sign-out')
    .addEventListener('click', () => fc.auth.signOut());
  document.getElementById('sign-in')
    .addEventListener('click', () =>  fc.auth.signInWithPopup(fc.googleAuthProvider));
  document.getElementById('message-form').addEventListener('submit', pushMessage.bind(fc));
  document.getElementById('message').addEventListener('keyup', toggleButtonFunction);
  document.getElementById('message').addEventListener('change', toggleButtonFunction);

  // Events for image upload.
  document.getElementById('submitImage').addEventListener('click', function() {
      document.getElementById('mediaCapture').click();
    }.bind(fc));
  document.getElementById('mediaCapture').addEventListener('change', saveImageMessage.bind(fc));
  console.log(this);
  firebase.auth().onAuthStateChanged(this.onAuthStateChangedFC.bind(this));
}
// Loads chat messages history and listens for upcoming ones.
FriendlyChat.prototype.loadMessages = function() {
  // Reference to the /messages/ database path.
  this.messagesRef = this.database.ref('messages');
  // Make sure we remove all previous listeners.
  this.messagesRef.off();
  // Loads the last 12 messages and listen for new ones.
  var setMessage = function(data) {
    var val = data.val();
    displayMessageSRB(data.key, val.name, val.text, val.photoUrl, val.imageUrl);
  }.bind(this);
  this.messagesRef.limitToLast(12).on('child_added', setMessage);
  this.messagesRef.limitToLast(12).on('child_changed', setMessage);
};
// Triggers when the auth state change for instance when the user signs-in or signs-out.
FriendlyChat.prototype.onAuthStateChangedFC = function(user) {
  console.log(user);
  if (user) { // User is signed in!
    // Set the user's profile pic and name.
    document.getElementById('user-pic').style.backgroundImage = 'url(' + (user.photoURL || '/images/profile_placeholder.png') + ')';
    document.getElementById('user-name').textContent = user.displayName;
    // Show user's profile and sign-out button.
    document.getElementById('user-name').removeAttribute('hidden');
    document.getElementById('user-pic').removeAttribute('hidden');
    document.getElementById('sign-out').removeAttribute('hidden');
    // Hide sign-in button.
    document.getElementById('sign-in').setAttribute('hidden', 'true');
    // We load currently existing chant messages.
    this.loadMessages();
    // We save the Firebase Messaging Device token and enable notifications.
    this.saveMessagingDeviceToken();
  } else { // User is signed out!
    // Hide user's profile and sign-out button.
    document.getElementById('user-name').setAttribute('hidden', 'true');
    document.getElementById('user-pic').setAttribute('hidden', 'true');
    document.getElementById('sign-out').setAttribute('hidden', 'true');
    // Show sign-in button.
    document.getElementById('sign-in').removeAttribute('hidden');
  }
};
// Saves the messaging device token to the datastore.
FriendlyChat.prototype.saveMessagingDeviceToken = function() {
  firebase.messaging().getToken().then(function(currentToken) {
    if (currentToken) {
      console.log('Got FCM device token:', currentToken);
      // Saving the Device Token to the datastore.
      firebase.database().ref('/fcmTokens').child(currentToken)
          .set(firebase.auth().currentUser.uid);
    } else {
      // Need to request permissions to show notifications.
      this.requestNotificationsPermissions();
    }
  }.bind(this)).catch(function(error){
    console.error('Unable to get messaging token.', error);
  });
};

// Requests permissions to show notifications.
FriendlyChat.prototype.requestNotificationsPermissions = function() {
  console.log('Requesting notifications permission...');
  firebase.messaging().requestPermission().then(function() {
    // Notification permission granted.
    this.saveMessagingDeviceToken();
  }.bind(this)).catch(function(error) {
    console.error('Unable to get permission to notify.', error);
  });
};

window.onload = function() {
//  this.checkSetup();
  if (!window.firebase || !(firebase.app instanceof Function) || !firebase.app().options) {
    window.alert(`You have not configured and imported the Firebase SDK. 
                  Make sure you go through the codelab setup instructions and make 
                  sure you are running the codelab using 'firebase serve'`);
  } else {
    window.friendlyChat = new FriendlyChat(); }
};




Aucun commentaire:

Enregistrer un commentaire