If I create an observer for all changes on a structure object, the observer will get called unless the the binding is a change to a value in a computed binding.
Is this the expected behavior? If so, how can I capture changes to the property in the computed binding?
Example:
<link rel="import" href="../../bower_components/paper-input/paper-input.html">
<dom-module id="binding-test">
<template>
<paper-input label="Not computed" value="{{myObject.prop1}}"></paper-input>
<paper-input label="Computed" value="{{computeIt(myObject.prop2)}}"></paper-input>
</template>
<script>
Polymer({
is:"binding-test",
properties: {
myObject: {
type: Object,
notify: true,
value: {
prop1: 1,
prop2: 2
}
}
},
observers: [
'somethingChanged(myObject.*)'
],
somethingChanged: function(changeRecord) {
// This code is never executed when the Computed input field is changed
console.log(changeRecord);
},
computeIt: function(value) {
return value;
}
});
</script>
</dom-module>
I could be wrong with this one but I think computed binding is one-way, same as computed property.
If you really want to notify the change on a paper-input like that, you can listen to the value-changed event and then do a notifyPath/set on "myObject.prop2".
<paper-input label="Computed" on-value-changed="valueChanged" value="{{computeIt(myObject.prop2)}}"></paper-input>
valueChanged: function(e) {
this.set("myObject.prop2", e.detail.value);
}
Check out this plunker.
Update
I think there's a better solution for your problem. Instead of converting values back and forth using expressions/filters, paper-input now allows you to define prefix and suffix like the following -
<paper-input label="revenue" type="number">
<div prefix>$</div>
</paper-input>
<paper-input label="email">
<div suffix>#email.com</div>
</paper-input>
You can even define complex inputs like this (you will need to create your own date-input element though) -
<paper-input-container auto-validate>
<label>Social Security Number</label>
<ssn-input class="paper-input-input"></ssn-input>
<paper-input-error>SSN invalid!</paper-input-error>
</paper-input-container>
Code samples above are taken from here. You can read more about it on Polymer's official website over here.
Related
I would like to show a tooltip on a text input that has a ui-state-disabled class.
I took a peek to the tooltip source code and I couldn't find something that checks against that particular class. So I don't know why it won't show.
As far as I can tell, the elements aren't disabled per se, they just have a class applied to them.
So, how can I show a tooltip on elements that have that class? I don't want to use a wrapper or anything like that. Maybe extending through widget factory...
Here's a sample code
HTML
<input name="#1" class="text" data-tooltip="message A">
<input name="#2" class="text" data-tooltip="message B">
<br>
<button id="disable">disable input #2</button>
<button id="enable">enable input #2</button>
JS
$(".text").each(function()
{
$(this).tooltip({
content: $(this).data("tooltip"),
items: ".text"
});
});
$("#disable").click(function()
{
$("input[name='#2']").addClass("ui-state-disabled");
});
$("#enable").click(function()
{
$("input[name='#2']").removeClass("ui-state-disabled");
});
FIDDLE: https://jsfiddle.net/hn1o4qs2/
See the doc (http://api.jqueryui.com/tooltip/):
In general, disabled elements do not trigger any DOM events.
Therefore, it is not possible to properly control tooltips for
disabled elements, since we need to listen to events to determine when
to show and hide the tooltip. As a result, jQuery UI does not
guarantee any level of support for tooltips attached to disabled
elements. Unfortunately, this means that if you require tooltips on
disabled elements, you may end up with a mixture of native tooltips
and jQuery UI tooltips.
Solution with wrapper
Your HTML:
<span class="input-container" data-tooltip="message A">
<input name="#1" class="text">
</span>
<span class="input-container" data-tooltip="message B">
<input name="#2" class="text">
</span>
<br>
<button id="disable">
disable input #2
</button>
<button id="enable">
enable input #2
</button>
Your Javascript
$(".input-container").each(function()
{
$(this).tooltip({
content: $(this).data("tooltip"),
items: ".input-container"
});
});
// ... The rest is the same
Solution with fake disabled-property
Here you can use a readonly attribute and a custom class for disabled input.
Playground: https://jsfiddle.net/5gkx8qec/
As I've stated in my question, I needed to get this working without adding a container or anything like that. And I was willing to extend the widget somehow...
So I read the source code more carefully and searched throught the whole repository for ui-state-disabled, and found that in widget.js there is an _on() method that at some point performs a check against that class and a flag called suppressDisabledCheck
A comment in code says
// Allow widgets to customize the disabled handling
// - disabled as an array instead of boolean
// - disabled class as method for disabling individual parts
This was very important, it gave me the clue that this check could be overriden. So a quick search in google and the widget factory had the answer:
Automatically handles disabled widgets: If the widget is disabled or
the event occurs on an element with the ui-state-disabled class, the
event handler is not invoked. Can be overridden with the
suppressDisabledCheck parameter.
So basically I did this:
$.widget("ui.tooltip", $.ui.tooltip,
{
options: {
allowOnDisabled: false
},
_on: function()
{
var instance = this;
this._super(instance.options.allowOnDisabled, {
mouseover: "open",
focusin: "open",
mouseleave: "close",
focusout: "close"
});
}
});
And then used it like this:
$(".text").each(function()
{
$(this).tooltip({
allowOnDisabled: true,
content: $(this).data("tooltip"),
items: ".text"
});
});
EDIT 2022-09-15
I was having some trouble with this implementation, so I've changed it a little bit
$.widget("ui.tooltip", $.ui.tooltip,
{
options: {
allowOnDisabled: false
},
_create: function()
{
this._super();
var instance = this;
this._on(instance.options.allowOnDisabled, {
mouseover: "open",
focusin: "open",
mouseleave: "close",
focusout: "close"
});
}
});
I use Quasar framework in Version 1.6.2. and want to use a component (Drawer.vue) for my drawer. The component is used in my layout file (MainLayout.vue).
I get the following error message in my console:
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "rightDrawerOpen"
The drawer works, but not on the first click – only on subsequent clicks.
What is the correct way to pass the parents model to my drawer?
Component: Drawer.vue
<template>
<q-drawer show-if-above v-model="rightDrawerOpen" side="right" bordered>
<q-list>
<q-item-label header>Menü</q-item-label>
</q-list>
</q-drawer>
</template>
<script>
export default {
name: "Drawer",
props: {
rightDrawerOpen: Boolean
}
};
</script>
Parent: MainLayout.vue
<Drawer :right-drawer-open="rightDrawerOpen" />
I would move the q-drawer component back into the MainLayout.vue component, then put the q-list into the child component. Then toggling "rightDrawerOpen" will occur in the "data" of MainLayout.vue instead of on the props of the child component which is where the error is coming from. Example:
MainLayout.vue:
<template>
<q-layout>
<q-drawer show-if-above v-model="rightDrawerOpen" side="right" bordered>
<DrawerContents />
</q-drawer>
<q-page-container>
<router-view />
</q-page-container>
</q-layout>
</template>
<script>
import DrawerContents from './DrawerContents.vue';
export default {
components: {
DrawerContents,
},
data() {
return {
rightDrawerOpen: true,
};
},
};
</script>
DrawerContents.vue:
<template>
<q-list>
<q-item-label header>Menü</q-item-label>
</q-list>
</template>
There're some times when we could need adding a custom element dynamically into a context. Then:
The inserted polymer could receive some properties bound to another
property inside the context, so it can change accordingly.
At polymer 0.5 we could use PathObserver to binding a property to a
context property for a recently added component. However, I did not
find a workaround or equivalent at polymer 1.0.
I have created an example for 0.5 and just the same for 1.0. See below the code of the polymer that it makes the injection. Also you can see the full plunker examples for clarity.
Ej 0.5:
<polymer-element name="main-context">
<template>
<one-element foo="{{foo}}"></one-element>
<div id="dynamic">
</div>
</template>
<script>
Polymer({
domReady: function() {
// injecting component into polymer and binding foo via PathObserver
var el = document.createElement("another-element");
el.bind("foo", new PathObserver(this,"foo"));
this.$.dynamic.appendChild(el);
}
});
</script>
</polymer-element>
Please, see the full plunkr example: http://plnkr.co/edit/2Aj3LcGP1t42xo1eq5V6?p=preview
Ej 1.0:
<dom-module id="main-context">
<template>
<one-element foo="{{foo}}"></one-element>
<div id="dynamic">
</div>
</template>
</dom-module>
<script>
Polymer({
is: "main-context",
ready: function() {
// injecting component into polymer and binding foo via PathObserver
var el = document.createElement("another-element");
// FIXME, there's no a path observer: el.bind("foo", new PathObserver(this,"foo"));
this.$.dynamic.appendChild(el);
}
});
</script>
Please, see the full plunkr example: http://plnkr.co/edit/K463dqEqduNH10AqSzhp?p=preview
Do you know some workaround or equivalent with polymer 1.0?
Right now, there is no direct way to do it. You should manually do the double binding by listening to changes in the foo property of the parent element and listening to the foo-changed event of the programmatically created element.
<script>
Polymer({
is: "main-context",
properties: {
foo: {
type: String,
observer: 'fooChanged'
}
},
ready: function() {
var self = this;
var el = this.$.el = document.createElement("another-element");
//set the initial value of child's foo property
el.foo = this.foo;
//listen to foo-changed event to sync with parent's foo property
el.addEventListener("foo-changed", function(ev){
self.foo = this.foo;
});
this.$.dynamic.appendChild(el);
},
//sync changes in parent's foo property with child's foo property
fooChanged: function(newValue) {
if (this.$.el) {
this.$.el.foo = newValue;
}
}
});
</script>
You can see a working example here: http://plnkr.co/edit/mZd7BNTvXlqJdJ5xSq0o?p=preview
Unfortunately I think it's not possible to do this by a "clean" way. To replace the Path Observer, we have to add link on the "foo" value changes to the dynamic elements. The first step is observe the "foo" property value changes. The second step is replicate the changes to each dynamic elements created.
<dom-module id="main-context">
<template>
<one-element foo="{{foo}}"></one-element>
<div id="dynamic">
</div>
</template>
</dom-module>
<script>
Polymer({
is: "main-context",
// Properties used to make the link between the foo property and the dynamic elements.
properties: {
foo: {
type: String,
observer: 'fooChanged'
},
dynamicElements: {
type: Array,
value: []
}
},
ready: function() {
// injecting component into polymer and binding foo via PathObserver
var el = document.createElement("another-element");
// Keeps a reference to the elements dynamically created
this.dynamicElements.push(el);
this.$.dynamic.appendChild(el);
},
// Propagates the "foo" property value changes
fooChanged: function(newValue) {
this.dynamicElements.forEach(function(el) {
el.foo = newValue;
});
}
});
</script>
See the full Plunkr example: http://plnkr.co/edit/TSqcikNH5bpPk9AQufez
I was trying to write a simple directive to generate a (potentially) more complex dom element. I am quite confused about what is going on here but I think the directive I use inside my directive get linked first? Anyway the element I am generating is not visible where it should.
Sorry for all that confusion, here is the plunkr:
http://plnkr.co/edit/vWxTmA1tQ2rz6Z9dJyU9?p=preview
I think the directive I use inside my directive get linked first?
Yes. A child directive's link function will execute before the parent's link function.
Here is a fiddle that shows two nested directives,
<div d1>
<div d2></div>
</div>
and it logs when the directives' controller and link functions are called.
There are a few issues with your Plunker:
Since you are using # for your isolate scopes, you need to use {{}}s in your attribute values:
<visible value='{{visible}}'>plop</visible>
<invisible value='{{visible}}'>plop</invisible>
Since $scope.visible is defined in your controller, I assume you meant to use that value, and not test.
In the invisible directive, you need to use isolate scope property value in your link function. Property visible is available to the transcluded scope (which is in affect if you use a template in your directive like #Langdon has) but not the isolate scope, which is what the link function sees.
var template = "<span ng-show='value'>{{value}}</span>";
Plunker.
If you want a simple directive, you're better off letting Angular do most of the work through ngTransclude, and $watch.
http://plnkr.co/edit/xYTNIUKYuHWhTrK80qKJ?p=preview
HTML:
<!doctype html>
<html ng-app="app">
<head>
<meta charset="utf-8">
<title>trying to compile stuff</title>
<script src="http://code.angularjs.org/1.1.1/angular.js"></script>
<script src="app.js"></script>
</head>
<body>
<div ng-controller="AppCtrl">
<input type="checkbox" ng-model="test" id="test" /><label for="test">Visibility (currently {{test}})</label>
<br />
<br />
<visible value='test'>visible tag</visible>
<invisible value='test'>invisible tag</invisible>
</div>
</body>
</html>
JavaScript:
angular
.module('app', [])
.controller('AppCtrl', function($scope) {
$scope.test = false;
})
.directive('visible', function() {
return {
restrict: 'E',
transclude: true,
template: '<span ng-transclude></span>',
replace: true,
scope: {
value: '='
},
link: function(scope, element, attrs) {
console.log(attrs);
scope.$watch('value', function (value) {
element.css('display', value ? '' : 'none');
});
console.log(attrs.value);
}
};
})
.directive('invisible', function() {
return {
restrict: 'E',
transclude: true,
template: '<span ng-transclude></span>',
replace: true,
scope: {
value: '='
},
link: function(scope, element, attrs) {
scope.$watch('value', function (value) {
element.css('display', value ? 'none' : '');
});
}
};
});
I got a Ember.Handlerbars.JSON helper, which formats a given value to an indented JSON string.
I'd like to set the content (value) of a textarea like this:
{{#view Ember.TextArea}}
{{JSON someValue}}
{{/view}}
This does not work, since what I should set the textareas' "value" attribute instead.
However, this also doe not work
{{view Ember.TextArea valueBinding="JSON someValue"}}
You could solve this by using a computed property, see http://jsfiddle.net/pangratz666/3A33H/:
Handlebars:
<script type="text/x-handlebars" >
{{#with App.jsonController}}
{{view Ember.TextArea valueBinding="formatted" rows="10" }}
{{/with}}
</script>
JavaScript:
App = Ember.Application.create({
formatJSON: function(obj) {
return JSON.stringify(obj, null, '\t');
}
});
App.jsonController = Ember.Object.create({
content: {
abc: 123,
foo: 'hello'
},
formatted: function() {
var obj = this.get('content');
return App.formatJSON(obj);
}.property('content')
});
Update to your comments:
In the fiddle in the comment ( http://jsfiddle.net/4QNur/ ) your are declaring {{view Ember.TextArea valueBinding="JSON App.someComplexValue"}}: this does not work since valueBinding takes a path as argument and not an expression, like JSON App.someComplexValue. If you want to bind to a transformed value, just create a computed property and bind to this. That's the Ember way of doing such stuff...
In your original question you have the following code:
{{#view Ember.TextArea}}
{{JSON someValue}}
{{/view}}
This does not work in this case, since the value for the Ember.TextArea can only be set via a value respectively valueBinding:
{{view Ember.TextArea valueBinding="App.controller.transformedComplexValue" }}