2-way data binding in native web components - html

I've been reading up on web components and am pretty intrigued by the nascent spec. Does anyone know if there is any support for 2-way data binding in the DOM, without having to use Polymer? An example would be appreciated.

Object.observe is a potential new way to do databinding in javascript. This feature is scheduled for Ecmascript 7(javascript), but some browsers currently support it, check here. Also check out this html5rocks article on object.observe

No, data binding isn't part of the Web Components spec.
You can of course implement data binding yourself using native JavaScript event listeners, and possibly the Proxy object, but it's probably best not to re-invent the wheel: if you want data binding, choose one of the many JavaScript frameworks out there which supports that. Polymer, React, Angular, and Vue are some recent examples of such libraries.

I've been playing around with this over the last few days. You can create a StateObserver class, and extend your web components from that. A minimal implementation looks something like this:
// create a base class to handle state
class StateObserver extends HTMLElement {
constructor () {
super()
StateObserver.instances.push(this)
}
stateUpdate (update) {
StateObserver.lastState = StateObserver.state
StateObserver.state = update
StateObserver.instances.forEach((i) => {
if (!i.onStateUpdate) return
i.onStateUpdate(update, StateObserver.lastState)
})
}
}
StateObserver.instances = []
StateObserver.state = {}
StateObserver.lastState = {}
// create a web component which will react to state changes
class CustomReactive extends StateObserver {
onStateUpdate (state, lastState) {
if (state.someProp === lastState.someProp) return
this.innerHTML = `input is: ${state.someProp}`
}
}
customElements.define('custom-reactive', CustomReactive)
class CustomObserved extends StateObserver {
connectedCallback () {
this.querySelector('input').addEventListener('input', (e) => {
this.stateUpdate({ someProp: e.target.value })
})
}
}
customElements.define('custom-observed', CustomObserved)
<custom-observed>
<input>
</custom-observed>
<br />
<custom-reactive></custom-reactive>
fiddle here
I like this approach because it occurs directly between precisely those elements you want to communicate with, no dom traversal to find data- properties or whatever.

One way: $0.model = {data}; setter on $0 assigns $0.data, responding to the update, and the other way: $1.dispatchEvent(new CustomEvent('example', {detail: $1.data, cancelable: true, composed: true, bubbles: true})); with $0.addEventListenever('example', handler) gives 2 way data binding. The data object is the same, shared on 2 elements, events and setters allow responding to updates. To intercept updates to an object a Proxy works model = new Proxy(data, {set: function(data, key, value){ data[key] = value; ...respond... return true; }}) or other techniques. This addresses simple scenarios. You might also consider looking at and reading the source for Redux, it provides conventions that seem relatively popular. As Ajedi32 mentions reinventing the wheel for more complex scenarios is not so practical, unless it's an academic interest.

Related

How to write custom sort logic on sort column event in ng2-smart-table

I'm looking to hook-up sort events performed on ng2-smart-table. Followed https://akveo.github.io/ng2-smart-table/#/documentation, I see bunch of events that are exposed like rowSelect, mouseover etc but I don't see sort events published/emitted by the library. I'm thinking of changing Ng2SmartTableComponent and emit an event when (sort) is called internally. May I know if anyone did it already or is there a hack I can rely upon.
The source of the sort in ng2-smart-table is shown on GitHub (link to code).
If you want to change the compare-Function (as used by default) you can add your own custom function in your ng2-smart-table-configuration:
columns: {
group_name: {
title: 'Groupname',
compareFunction(direction: any, a: any, b: any) => {
//your code
}
}
}
I was searching for an event to sort my data remotely and I have found a solution. Also I have some logic for page change event (remote paging). Here is what works for me.
ts
source: LocalDataSource = new LocalDataSource();
ngOnInit() {
this.source.onChanged().subscribe((change) => {
if (change.action === 'sort') {
this.sortingChange(change.sort);
}
else if (change.action === 'page') {
this.pageChange(change.paging.page);
}
});
}
html
<ng2-smart-table [settings]="settings" [source]="source"></ng2-smart-table>
This solution won't replace custom logic but it might help you solve your problem.

React upgrade: "this" visibility in getDefaultProps

I am upgrading some older react component I inherited (v0.10.0) to work with the latest version of react (v0.14.8).
The following scenario stopped working:
// within a react component
onClick: function() {
// DO SOMETHING
}
getDefaultProps: function () {
return {
someProp: 'prop',
onClick: this.onClick
}
}
This is easily resolved moving the code into an anonymous function, like the following:
getDefaultProps: function () {
return {
someProp: 'prop',
onClick: function() {
//DO SOMETHING
}
}
}
My question is: why has the visibility of 'this' changed at that level and what's the best way to refactor this code? And what if I had-to/wanted-to use 'this' at that level?
Any help appreciated, as a disclaimer I am a react super-beginner!
The result of getDefaultProps() is shared across all instances of a component. That means that the result can't rely on any particular instance of the component. The reason it changed is likely because of the performance benefit from caching, although I can't say for sure.
As for refactoring the code, I'm not sure there's a silver-bullet here. From my perspective what you currently have seems like an anti-pattern. Props are meant to be passed in by consumers that have no knowledge of the inner workings of the component, so it seems odd that a default value for a prop would depend on the inner workings. Without knowing exactly what you're doing, I would say your best bet is to just use null as the default value for the prop, then check the value at runtime when you do have access to the this context.
handleSomeAction() {
if (!this.props.onClick) {
// DO SOMETHING
}
}

Notify polymer complex object has changed

How can I trigger a refresh of the template if a member of a data-bound complex object changes?
In the template:
<other-component data="{{complexObject}}"></other-object>
In the component:
_onChange: function(newData) {
//callback from some event-system
this.set("complexObject", newData);
}
The _onChange-Method is triggered when the complexObject is changed, but newData is always a reference to the same object, just members of this object changed - because of this, polymer doesn't update the view and doesn't pass the data down to other-component.
Is there a way to let polymer know that there is indeed some new data and it has to re-evaluate the template? It is working if I create a shallow clone of newData, but that seems like a hack and could hurt performance for big objects.
I can't use the set method to change the properties of the object via the path because the change happens outside of polymer-elements and I can't control it.
Call render()
this.$.yourTemplateID.render();
Here is one working example where sibling elements are interchanging data between each other: Plunk.
Docs: https://www.polymer-project.org/1.0/docs/devguide/data-binding.html#array-binding
<template>
<button on-tap="btnTapped">change Emploees from Second</button>
second: empl: <span>{{empl.employees.0.firstName}}</span>
</template>
...
<script>
btnTapped: function () {
console.log('Second: btnTapped');
//Propagate array subproperty
this.set('empl.employees.0.firstName', 'Test');
console.log(this.empl.employees[0].firstName);
//Object set
this.set('temp.firstName', 'Test');
}
</script>
this.complexObject = _.clone(this.complexObject)
cloning & reassigning might solve this problem`

Modernizr Feature Detection with v3

I would like to ask if there's any significant difference/performance issue for detecting using the following two methods:
Let's say we're testing for 3d transforms.
Using the new function that Modernizr 3 offers:
Modernizr.on('csstransforms3d', function(result) {
if (result) {
element.className = 'myFirstClass';
}
else {
element.className = 'mySecondClass';
}
});
And with the standard way:
if (Modernizr.csstransforms3d) {
element.className = 'myFirstClass';
} else {
element.className = 'mySecondClass'
}
The Modernizr.on function is only (or mainly) for asynchronous detects and deferred actions. See the full explanation and example by #stucox for more details.
csstransforms3d is not async, and available right away. There would be no reason to be using the on event callback method for it. The function is rather inefficient with setTimeout() calls which aren't good for performance.
Only use on for deferred events on async detects.
They aren't really comparable because depending on the situation, one will always be better suited than the other. Performance isn't really the issue.
The standard way, checking Boolean values in a Dictionary is an extremely fast operation. If you have a function that gets executed in reaction to some user interaction, this will be the best way to get feature info. For example:
$('#showVideo').on('click', function() {
if (Modernizr.video) {
// load HTML5 video
}
else {
// load Flash video
}
});
Similarly, listening to JS events is very efficient. What the new event-based model in Modernizr allows is for you to react to the Modernizr tests completing. This is great if your site needs to make changes ASAP when feature detection data is available. For example:
Modernizr.on('draganddrop', function(result) {
if (!result) {
alert('This site requires Drag and Drop. Please update your browser.')
}
});
Previously you had to watch for DOM updates on the <body> and check the classes in order to get this information.

With ng-bind-html-unsafe removed, how do I inject HTML?

I'm trying to use $sanitize provider and the ng-bind-htm-unsafe directive to allow my controller to inject HTML into a DIV.
However, I can't get it to work.
<div ng-bind-html-unsafe="{{preview_data.preview.embed.html}}"></div>
I discovered that it is because it was removed from AngularJS (thanks).
But without ng-bind-html-unsafe, I get this error:
http://errors.angularjs.org/undefined/$sce/unsafe
Instead of declaring a function in your scope, as suggested by Alex, you can convert it to a simple filter :
angular.module('myApp')
.filter('to_trusted', ['$sce', function($sce){
return function(text) {
return $sce.trustAsHtml(text);
};
}]);
Then you can use it like this :
<div ng-bind-html="preview_data.preview.embed.html | to_trusted"></div>
And here is a working example : http://jsfiddle.net/leeroy/6j4Lg/1/
You indicated that you're using Angular 1.2.0... as one of the other comments indicated, ng-bind-html-unsafe has been deprecated.
Instead, you'll want to do something like this:
<div ng-bind-html="preview_data.preview.embed.htmlSafe"></div>
In your controller, inject the $sce service, and mark the HTML as "trusted":
myApp.controller('myCtrl', ['$scope', '$sce', function($scope, $sce) {
// ...
$scope.preview_data.preview.embed.htmlSafe =
$sce.trustAsHtml(preview_data.preview.embed.html);
}
Note that you'll want to be using 1.2.0-rc3 or newer. (They fixed a bug in rc3 that prevented "watchers" from working properly on trusted HTML.)
You need to make sure that sanitize.js is loaded. For example, load it from https://ajax.googleapis.com/ajax/libs/angularjs/[LAST_VERSION]/angular-sanitize.min.js
you need to include ngSanitize module on your app
eg: var app = angular.module('myApp', ['ngSanitize']);
you just need to bind with ng-bind-html the original html content. No need to do anything else in your controller. The parsing and conversion is automatically done by the ngBindHtml directive. (Read the How does it work section on this: $sce). So, in your case <div ng-bind-html="preview_data.preview.embed.html"></div> would do the work.
For me, the simplest and most flexible solution is:
<div ng-bind-html="to_trusted(preview_data.preview.embed.html)"></div>
And add function to your controller:
$scope.to_trusted = function(html_code) {
return $sce.trustAsHtml(html_code);
}
Don't forget add $sce to your controller's initialization.
The best solution to this in my opinion is this:
Create a custom filter which can be in a common.module.js file for example - used through out your app:
var app = angular.module('common.module', []);
// html filter (render text as html)
app.filter('html', ['$sce', function ($sce) {
return function (text) {
return $sce.trustAsHtml(text);
};
}])
Usage:
<span ng-bind-html="yourDataValue | html"></span>
Now - I don't see why the directive ng-bind-html does not trustAsHtml as part of its function - seems a bit daft to me that it doesn't
Anyway - that's the way I do it - 67% of the time, it works ever time.
You can create your own simple unsafe html binding, of course if you use user input it could be a security risk.
App.directive('simpleHtml', function() {
return function(scope, element, attr) {
scope.$watch(attr.simpleHtml, function (value) {
element.html(scope.$eval(attr.simpleHtml));
})
};
})
You do not need to use {{ }} inside of ng-bind-html-unsafe:
<div ng-bind-html-unsafe="preview_data.preview.embed.html"></div>
Here's an example: http://plnkr.co/edit/R7JmGIo4xcJoBc1v4iki?p=preview
The {{ }} operator is essentially just a shorthand for ng-bind, so what you were trying amounts to a binding inside a binding, which doesn't work.
I've had a similar problem. Still couldn't get content from my markdown files hosted on github.
After setting up a whitelist (with added github domain) to the $sceDelegateProvider in app.js it worked like a charm.
Description: Using a whitelist instead of wrapping as trusted if you load content from a different urls.
Docs: $sceDelegateProvider and ngInclude (for fetching, compiling and including external HTML fragment)
Strict Contextual Escaping can be disabled entirely, allowing you to inject html using ng-html-bind. This is an unsafe option, but helpful when testing.
Example from the AngularJS documentation on $sce:
angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) {
// Completely disable SCE. For demonstration purposes only!
// Do not use in new projects.
$sceProvider.enabled(false);
});
Attaching the above config section to your app will allow you inject html into ng-html-bind, but as the doc remarks:
SCE gives you a lot of security benefits for little coding overhead.
It will be much harder to take an SCE disabled application and either
secure it on your own or enable SCE at a later stage. It might make
sense to disable SCE for cases where you have a lot of existing code
that was written before SCE was introduced and you're migrating them a
module at a time.
You can use filter like this
angular.module('app').filter('trustAs', ['$sce',
function($sce) {
return function (input, type) {
if (typeof input === "string") {
return $sce.trustAs(type || 'html', input);
}
console.log("trustAs filter. Error. input isn't a string");
return "";
};
}
]);
usage
<div ng-bind-html="myData | trustAs"></div>
it can be used for other resource types, for example source link for iframes and other types declared here