26
CLIENT CODE BEST PRACTICES Yuval Dagai

Client Best Practices

Embed Size (px)

Citation preview

CLIENT CODE BEST PRACTICESYuval Dagai

Agenda

• Why?• Performance as a feature• Request the minimal data needed for operation• Minimize the use of observables• Bindings• Batch queries• Knockout tips• Style

Why?• Lessons learned from R2

• Raise awareness

• Performance as a feature

• Knowledge sharing

• Writing better code

Performance as a feature• Critical rendering path

• Optimizing the Critical Rendering Path – By Ilya Grigorick (Google)

• Youtube - http://www.youtube.com/watch?v=YV1nKLWoARQ

• Perceived performance vs. Actual performance

http://www.mobify.com/blog/beginners-guide-to-perceived-performance/

Request minimal data needed – Why?

• Improve performance

• Promote perceived performance.

• Reduce the network bandwidth required for the request.

• Reduce server time handling the request.

• Simple requests may help optimizing cache control.

• Give better control on data.

• Control data bindings.

• Memory considerations

• Less data received less memory to consume.

• Reduce GC cycles when loading and removing the view.

Request minimal data needed - how• Only get data for the immediate visible view or operation.

• Try to split large query into smaller queries.

• This will enable a more flexible control on data requests.

• For lists limit the request for the amount of possible visible elements plus 50%.

• This will create overflow that enables live scroll to get the next results.

• For extensive calculations:

• Best if server can do it.

• Try to do a special background request for calculation logic.

• Defer the calculation until after applyBinding (after calling loading.resolve()) and run it in a setTimeout(calc, 0) so it will not block any rendering.

• Consider web worker (threading).

Request minimal data needed - how• Any user click data related should be requested when the

user clicks.

• Use projection wherever possible.

• Projection limits the number of properties retrieved for the entity request.

• It is equivalent to the select clause in SQL. Represent as $select parameter in OData querystring.

• In JayData use the map function.

Projection usage• JayData map function defines

the object retrieved in the results.

• Note that the object in the results will not be JayData entity.

Request minimal needed data - how• Example: Goal page

• Ux requirement:

• View mode:

• Show expanded goal hierarchy down to initiative

• Only name, short description, description and status is visible

• Investments are collapsed but their count should be visible.

• Some strategy info is needed for authorization

• Investment allocation aggregated calculation is needed

• On user menu selection

• Edit entity form

• Add entity form

• Delete entity

• Entity details popup

Example - Goal Page

Example – Autocomplete solution• R1 autocomplete (filter box)

• Get everything on load

• Enable filtering on the full list

• Practically it was filter box not an autocomplete.

• R2 autocomplete

• On load get enough data to create the overflow and scrollbars.

• As a rule of thumb we can use 150% of visible items.

• Query server on user typing to filter the list.

• Use live scroll to get next paged data.

Minimize the use of observables – why?

• Observables are great way to sync between view and view-model, However, they have costs.

• Each observable is a function that internally can be related to other observables by subscriptions.

• This is great when we gain the advantages of data synchronization but its better to avoid it’s overkill when unnecessary.

Minimize the use of observables - how

• Use observables only when necessary.

• 2 way binding:

• Edit modes when user input is needed.

• Updated content:

• Show or hide according to user action.

• Updated counter or calculations.

• etc...

• Any static binding use simple js object.

• Do not use it as variable

• komap is evil

• komap.fromJS (fromJD) is a brut force – it maps whole object with observables

• komap on JD entity clone the JD object and creates observables on each property destroying the JD behavior.

• The process create a block and produce many GC events. This can be illustrated in next slide…

komap is evil – Test case

There are 3 lists of initiatives include strategy all get the first 200 items.

The only binded property is the initiative name

1. Left list use projection on the initiative name.

2. Middle use no projection and no komap.

3. Right list do komap.

The width of the flame chart express the time to do the rendering of the list after response.

komap alternatives• There is a JayData module for Knockout that integrates

both libraries we name it JDKO.

• JDKO add behavior to JD toArray function that detects an observableArray passed as parameter and then add knockout observable behavior to it, BUT keep JD behavior in tact.

• komap have a mapping options object that you can pass as an optional parameter to the fromJS function.

• Using this mapping you can define what properties should be observed, ignored, copy or include.

Bindings• Use on demand binding.

• Defer binding of hidden elements to display time.

• Views that are not visible should be fetched and bind at the time they needed mainly when user click to show.

• Handle data processing before setting binded observable.

• Example: A list is binded to an observableArray. The list needs to be processed or even filtered.

• In this case do the filter or process the array and then when it is ready for display set it on the observableArray to be displayed.

• This will do the rendering process in one pass and not force the browser to parse html, calculate style layout and paint on each item pushed into the observableArray.

Bindings• Binded observable register to the change event by default. Therefore:

• Avoid binding change events. Use subscribe or computed observables instead.

• Bad

• totalValueHasChanged is a function – could be subscribe to totalPlanned, which includes the validateNum logic as well. Alternatively use the writable computed observable.

• Good

• totalPeriod is a computed observable.

Bindings• The subscribe function can be done on any event. Just

pass the event name as the third parameter to the subscribe callback.

• Example: The beforeChange event can be used to get the old value before the change. See how it is done for attach detach:

Bindings• One last thing… please write maintainable bindings!

• Bad… bad example: Its only 3 <a> tags

Batch queries - why• Batch query aggregate multiple queries into one and send

it to the server as one query. Server execute all queries and create one response.

• Reduce the amount of requests and all it’s overhead.

• Reduce the risk of server/db deadlocks.

• Reduce the risk of cache synchronization issues.

Batch queries - how• Currently server supports only POST/PUT queries in

batch queries.

• Batch query protocol allows queries in batch to reference each other and have dependencies between them.

• Batch query URL:http://localhost:8080/sp/osvc/Batch/$batch?$format=json

• JayData create batch query automatically when context (db) has more than one entity to change/add.

Batch queries - how• Example for JayData query that creates batch query:

Knockout tips• ko.utils.unwrapObservable(x) – get the value of x

whether it is observable or not. No need to check for function.

• ko.isObservable(x) – check if x is observable.

• ko.isWriteableObservable(x) – check if x is a writable observable. Non writable observable can be a read only computed observable.

• ko.dataFor(element) - returns the data that was available for binding against the element.

• ko.contextFor(element) - returns the context that was available for binding against the element.

Style• Avoid using selectors

specific to the view’s DOM

• Favor element, classes and id’s selectors

• Page view should have a specific id

Performance of Style JS changes• When changing style in a loop, cache the object style

property before the loop and use cached value.

• Bad example, Good Example

Q & A