UX workflows aren't all of JS. Classes exist for many more reasons than that, and 99% of my classes are for abstracting non-trivial business logic and ensuring *those* are easily testable, not directly UI/UX.
----- Isiah Meadows [email protected] www.isiahmeadows.com On Sun, Mar 17, 2019 at 2:35 AM kai zhu <[email protected]> wrote: > *rant warning* > > -1 because classes generally have little-value in UX-workflow programming. > > the example class RequestManager (given in this discussion), realistically > has little reusability-value -- its no better than employing a throwaway > static-function (both are equally likely to get rewritten each time > UX-workflow features are added.). > > for example, a common feature-request is adding visual-progress-bar. > there is no "simple" way to extend RequestManager to do this, other than > significant-refactoring of the base-class (and risk breaking > class-dependencies downstream). > > some other common UX feature-requests that would likely invalidate > "reusability" of your class-based design include: > > 1. needing to upload a binary-file (social-images, receipt-signatures, > screenshots, etc...) > 2. needing to upload multiple binary-files in parallel (keeping track of > timeouts of each) > 3. needing to upload multiple binary-files in parallel (keeping track of > timeouts of each), > and then download their thumbnail-previews from server to visually > confirm uploads were correct > 4. needing to make parallel http-requests from 3rd-party sources and > "joining" the response-data > 5. needing the sign-up-page to additionally pre-validate > email / username / mobile-number / credit-card / etc... before > form-submission to server > > many frontend-engineers with experience "extending" products with > additional UX-workflow features, know its rarely as simple as modifying > some class-methods and be done with it -- it oftentimes require rewriting > nearly every-piece-of-code that touches the given workflow needing > enhancement. > > p.s. -- here's a "simple" fully-working UX-example [1] on how to add > visual-progress-bar to http-requests. if i had to additionally add some of > the other UX-features mentioned above, it would likely entail me completely > rewriting the throwaway static-function, rather than waste time trying to > extend it. > > [1] https://jsfiddle.net/kaizhu256/t9ubdenf/ > > ```html > <style> > /* jslint utility2:true */ > /* csslint ignore:start */ > *, > *:after, > *:before { > box-sizing: border-box; > } > /* csslint ignore:end */ > body { > background: #eee; > font-family: Arial, Helvetica, sans-serif; > font-size: small; > } > input { > width: 100%; > } > textarea { > font-family: Consolas, Menlo, monospace; > font-size: smaller; > overflow: auto; > width: 100%; > } > </style> > > <div id="ajaxProgressDiv1" style="background: #d00; height: 5px; left: 0; > margin: 0; padding: 0; position: fixed; top: 0; transition: background > 500ms, width 1500ms; width: 0%; z-index: 1;"></div> > > <label>ajax-request</label><br> > <textarea id="input1" style="height: 10rem;">{ > "method": "GET", > "url": "https://api.github.com/orgs/octokit/repos", > "headers": { > "accept": "application/vnd.github.v3+json" > }, > "data": "hello world!" > }</textarea><br><br> > > <button id="submit1">submit ajax-request</button><br><br> > > <label>ajax-response</label><br> > <textarea id="output1" style="height: 20rem;"></textarea><br><br> > > <script> > /*jslint browser*/ > (function () { > "use strict"; > var local; > local = {}; > window.local = local; > > local.ajax = function (opt, onError) { > /* > * simple, throwaway ajax-function that can be easily rewritten > * to accomodate new [async] ux-features > */ > var resHandler; > var xhr; > opt.headers = opt.headers || {}; > opt.method = opt.method || "GET"; > xhr = new XMLHttpRequest(); > // open url > xhr.open(opt.method, opt.url); > // set req-headers > Object.entries(opt.headers).forEach(function (entry) { > xhr.setRequestHeader(entry[0], entry[1]); > }); > // send data > xhr.send(opt.data); > // init request-handling > resHandler = function (evt) { > /* > * this function will handle ajax-response > */ > switch (evt.type) { > case "abort": > case "error": > // decrement ajaxProgressCounter > local.ajaxProgressCounter = Math.max( > local.ajaxProgressCounter - 1, > 0 > ); > onError(new Error(evt.type), xhr); > break; > case "load": > // decrement ajaxProgressCounter > local.ajaxProgressCounter = Math.max( > local.ajaxProgressCounter - 1, > 0 > ); > onError(null, xhr); > break; > } > // increment ajax-progress-bar > local.ajaxProgressUpdate(); > }; > // increment ajaxProgressCounter > local.ajaxProgressCounter = local.ajaxProgressCounter || 0; > local.ajaxProgressCounter += 1; > // increment ajax-progress-bar > local.ajaxProgressUpdate(); > xhr.addEventListener("abort", resHandler); > xhr.addEventListener("error", resHandler); > xhr.addEventListener("load", resHandler); > xhr.addEventListener("loadstart", resHandler); > xhr.addEventListener("progress", resHandler); > }; > > local.ajaxProgressUpdate = function () { > /* > * this function will update ajax-progress-bar > */ > var ajaxProgressDiv1; > // init state > local.ajaxProgressCounter = local.ajaxProgressCounter || 0; > local.ajaxProgressState = local.ajaxProgressState || 0; > ajaxProgressDiv1 = document.querySelector( > "#ajaxProgressDiv1" > ); > // init ajaxProgressDiv1StyleBackground > local.ajaxProgressDiv1StyleBackground = ( > local.ajaxProgressDiv1StyleBackground > || ajaxProgressDiv1.style.background > ); > // show ajaxProgress > ajaxProgressDiv1.style.background = ( > local.ajaxProgressDiv1StyleBackground > ); > // increment ajaxProgress > if (local.ajaxProgressCounter > 0) { > local.timerIntervalAjaxProgressHide = ( > local.timerIntervalAjaxProgressHide > || setInterval(local.ajaxProgressUpdate, 2000) > ); > // this algorithm will indefinitely increment ajaxProgressBar > // with successively smaller increments without ever reaching > 100% > if ((ajaxProgressDiv1.style.width.slice(0, -1) | 0) > 95) { > ajaxProgressDiv1.style.width = "0%"; > local.ajaxProgressState = 0; > } > local.ajaxProgressState += 1; > ajaxProgressDiv1.style.width = Math.max( > 100 - 75 * Math.exp(-0.125 * local.ajaxProgressState), > ajaxProgressDiv1.style.width.slice(0, -1) | 0 > ) + "%"; > } else { > // finish ajaxProgress > ajaxProgressDiv1.style.width = "100%"; > } > // cleanup timerTimeout > clearTimeout(local.timerTimeoutAjaxProgressHide); > // hide ajaxProgress > local.timerTimeoutAjaxProgressHide = setTimeout(function () { > ajaxProgressDiv1.style.background = "transparent"; > local.ajaxProgressCounter = 0; > local.ajaxProgressState = 0; > // reset ajaxProgress > clearInterval(local.timerIntervalAjaxProgressHide); > local.timerIntervalAjaxProgressHide = null; > setTimeout(function () { > if (!local.ajaxProgressState) { > ajaxProgressDiv1.style.width = "0%"; > } > }, 500); > }, ( > local.ajaxProgressCounter > 0 > ? local.timeoutDefault > : 1000 > )); > }; > > // init event-handling > document.querySelector( > "#submit1" > ).addEventListener("click", function () { > var output1; > output1 = document.querySelector( > "#output1" > ); > // reset #output1 > output1.innerHTML = ""; > try { > local.ajax(JSON.parse(document.querySelector( > "#input1" > ).textContent), function (err, xhr) { > // handler err > if (err) { > output1.textContent = err.stack; > return; > } > // output response-headers > output1.textContent = ( > "status-code:\n" > + xhr.status > + "\n\nresponse-headers:\n" > + xhr.getAllResponseHeaders() > + "\n\nresponse-text:\n" > + xhr.responseText > ); > }); > // handler errCaught > } catch (errCaught) { > output1.textContent = errCaught.stack; > } > }); > }()); > </script> > ``` > > > On 12 Mar 2019, at 15:49, guest271314 <[email protected]> wrote: > > ```class RequestManager { > __THIS__(method) { > return new Proxy(method, { > apply: (target, thisArg, argumentsList) => { > return method.apply(thisArg, [...argumentsList, { > THIS: this > }]) > } > }) > } > constructor() { > this.successMessage = "Xhr successful."; > } > makeRequest() { > var oReq = new XMLHttpRequest(); > oReq.addEventListener("load", this.__THIS__(this.responseHandler)); > oReq.open("GET", "data:,"); > oReq.send(); > } > > responseHandler(e, {THIS} = {}) { > console.log(this, e); > window.alert(THIS.successMessage) > } > } > > var reqManager = new RequestManager(); > > reqManager.makeRequest();``` > > On Mon, Mar 11, 2019 at 8:59 AM john larson <[email protected]> > wrote: > >> First of all, thank you all for taking the time to review the proposal >> and sharing your valuable opinions. I would like to note that the proposal >> aims not to add a new capability that was not possible to do before but >> rather move the standard forward on the way for a modern, better and easier >> to use language for all the developers. Advancements of the language, or >> any language in that matter, throughout the last decade also followed a >> similar path because we were already able to do everything in one way or >> the other. We actually strive for the same thing, a better Javascript. >> >> >> That being said, let me try to clarify my proposal further by walking >> you through my thought process: >> >> >> Normally if I try to write a class similar to the sample code I have >> given in my first email using an object oriented programming language like >> Java, C# etc., I would be writing something similar to the following: >> >> class RequestManager >> { >> string successMessage = "Xhr successful."; >> >> >> void makeRequest() >> { >> var oReq = new XMLHttpRequest(); >> oReq.addEventListener("load", responseHandler); >> oReq.open("GET", "*www.google.com*"); >> oReq.send(); >> } >> >> >> void responseHandler() >> { >> window.alert(successMessage); >> } >> >> } >> >> >> As you can see, I do not even have to use a special keyword for referring >> to methods from inside the class. Because they are already in lexical >> scope. Now, if this can be accomplished in Javascript without hitting >> some limitation/restriction due to the current state of the language, I >> think it would be the ideal solution. (This limitation might be the class >> syntax being just a syntactical sugar or some other reason that I cannot >> foresee right now and that would require a breaking change.) And I would >> happily change the proposal that way: “A no-keyword alternative for the >> “this””. If I should summarize this approach, I can say that every method >> of the class is going to assume the behavior we now have with arrow >> functions, but without requiring the use of the “this” and the arrow >> function syntax. >> >> >> As contrary to the ideal solution, the last thing I would want would be >> to use a context-dependant keyword like the “this” to refer to >> methods/properties of the object and then try to set the context right by >> using binding or arrow functions. This referral should be lexical, not >> context-dependant. If I have the intent of referring to the instance >> method/property, that intent should manifest itself right there where I am >> using this method/property. I shouldn’t be looking at if this takes place >> inside an arrow function, or if the enclosing method is called with a >> binding or not. Why should I care about the enclosing of the call, right? >> >> By the way, MDN also mentions the following about the use of arrow >> functions: “Arrow function expressions are ill suited as methods”. >> >> *@Yulia:* Thanks for pointing out the decorator approach. But that also >> seems to deal with the enclosing and tries to solve the problem with a >> “context binding” approach. The only difference is the way it determines >> the binding. I am against this binding approach all together. Only the >> lexical scope of the code should be taken into consideration. >> >> >> >> So far, I have laid out what I think the ideal solution is and what I >> think the problematic state we are in right now. And as a middle-ground, >> in case the ideal solution cannot be applied, I proposed a new keyword to >> use instead of the “this” so that it will always refer to the instance, >> regardless of execution context binding. In which case, when you replace >> the “this” in the problematic sample code in my initial email, it will work >> just fine. Let’ assume for the sake of this example that the new keyword is >> “self”: >> >> class RequestManager{ >> >> >> constructor(){ >> *self*.successMessage = "Xhr successful."; >> } >> >> >> >> >> makeRequest() { >> var oReq = new XMLHttpRequest(); >> oReq.addEventListener("load", *self*.responseHandler); >> oReq.open("GET", "*www.google.com*"); >> oReq.send(); >> } >> >> >> responseHandler() { >> window.alert(*self*.successMessage); >> } >> } >> >> >> var reqManager = new RequestManager(); >> reqManager.makeRequest(); >> >> >> As you can see, self.responseHandler will always point to the >> responseHandler method no matter whether the enclosing is a method, an >> arrow function or if it is called using a bind syntax or not. >> >> I would be happy to further address your concerns about this explanation >> if you have any. >> >> >> >> On Sun, Mar 10, 2019 at 10:30 PM guest271314 <[email protected]> >> wrote: >> >>> This is probably not the pattern that is being proposed though outputs >>> the expected result >>> >>> ```class RequestManager { >>> constructor() { >>> this.successMessage = "Xhr successful."; >>> RequestManager.THIS = this; >>> } >>> >>> makeRequest() { >>> var oReq = new XMLHttpRequest(); >>> oReq.addEventListener("load", this.responseHandler); >>> oReq.open("GET", ""); >>> oReq.send(); >>> } >>> >>> responseHandler(e) { >>> console.log(e, this); // `e`: event, `this`: XMLHttpRequest >>> instance >>> console.log(RequestManager.THIS.successMessage); >>> } >>> >>> } >>> >>> var reqManager = new RequestManager(); >>> >>> reqManager.makeRequest();``` >>> >>> On Sat, Mar 9, 2019 at 11:42 AM john larson <[email protected]> >>> wrote: >>> >>>> *Summary of the problem:* >>>> >>>> “this” keyword in Javascript is context dependent. And this is one of >>>> the culprits of most subtle and latent errors in Javascript. Moreover, use >>>> of “this” cannot be avoided if we are using classes and trying to reference >>>> instance properties. >>>> >>>> When “this” is used in callback functions or in functions given >>>> to forEach as argument, IDEs rightfully cannot raise any design-time >>>> errors, giving developers the false sense of security, but we get run-time >>>> errors because “this” is undefined. >>>> >>>> There seem to be two work-arounds: >>>> 1. Using arrow functions >>>> >>>> 2. Using .bind(this) syntax >>>> >>>> Just assuming we forgot to use an arrow function or a .bind(), the IDE >>>> will not be able to raise an error and we will encounter the error in >>>> run-time. >>>> >>>> >>>> *What I propose:* >>>> >>>> I am proposing a new keyword that will be the alternative of "this" and >>>> will always point to the instance of the class. The name of the new keyword >>>> can be chosen with consensus from the community such that it would >>>> minimize/eliminate collision in existing codebases. >>>> >>>> >>>> Here is a sample js code: >>>> >>>> >>>> class RequestManager{ >>>> >>>> >>>> >>>> constructor(){ >>>> >>>> this.successMessage = "Xhr successful."; >>>> >>>> } >>>> >>>> >>>> >>>> >>>> >>>> makeRequest() { >>>> >>>> var oReq = new XMLHttpRequest(); >>>> >>>> oReq.addEventListener("load", this.responseHandler); >>>> >>>> oReq.open("GET", "www.google.com"); >>>> >>>> oReq.send(); >>>> >>>> } >>>> >>>> >>>> >>>> responseHandler() { >>>> >>>> window.alert(this.successMessage); >>>> >>>> } >>>> >>>> } >>>> >>>> >>>> >>>> var reqManager = new RequestManager(); >>>> >>>> reqManager.makeRequest(); >>>> >>>> >>>> >>>> This piece of code will alert “undefined” because “this” is undefined >>>> in the callback function in strict mode. >>>> >>>> Now let’s assume a new keyword is used insetead of “this” that will >>>> always point to the class instance. >>>> >>>> As per its implementation, as described on >>>> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes >>>> : >>>> >>>> *“JavaScript classes, introduced in ECMAScript 2015, are primarily >>>> syntactical sugar over JavaScript's existing prototype-based inheritance. >>>> The class syntax does not introduce a new object-oriented inheritance model >>>> to JavaScript.”* >>>> >>>> So with the new keyword introduced, behind the scenes, previous class >>>> could be interpreted as a piece of code along the lines of: >>>> >>>> >>>> var RequestManager = function () { >>>> >>>> var self = this; >>>> >>>> self.successMessage = "Xhr successful"; >>>> >>>> >>>> >>>> self.makeRequest = function () { >>>> >>>> var oReq = new XMLHttpRequest(); >>>> >>>> oReq.addEventListener("load", responseHandler); >>>> >>>> oReq.open("GET", "www.google.com"); >>>> >>>> oReq.send(); >>>> >>>> }; >>>> >>>> >>>> >>>> var responseHandler = function () { >>>> >>>> window.alert(self.successMessage); >>>> >>>> }; >>>> >>>> }; >>>> >>>> var reqManager = new RequestManager(); >>>> >>>> >>>> >>>> reqManager.makeRequest(); >>>> >>>> >>>> >>>> I believe this way, we would not have to resort to work-arounds for >>>> such a fundamental construct of the language and this would ease >>>> developers’ lives as someone forgetting to have used an arrow function or >>>> the .bind(this) syntax will not be a problem anymore. >>>> >>>> >>>> Best Regards, >>>> >>>> John >>>> >>>> >>>> <https://www.avast.com/sig-email?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=webmail&utm_term=icon> >>>> Virus-free. >>>> www.avast.com >>>> <https://www.avast.com/sig-email?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=webmail&utm_term=link> >>>> _______________________________________________ >>>> es-discuss mailing list >>>> [email protected] >>>> https://mail.mozilla.org/listinfo/es-discuss >>>> >>> _______________________________________________ > es-discuss mailing list > [email protected] > https://mail.mozilla.org/listinfo/es-discuss > > > _______________________________________________ > es-discuss mailing list > [email protected] > https://mail.mozilla.org/listinfo/es-discuss >
_______________________________________________ es-discuss mailing list [email protected] https://mail.mozilla.org/listinfo/es-discuss

