Is it possible to bind sub-properties automatically with Google Polymer? Something like AngularJS.
Here is a small example:
This is the element that contains the property prop1 and updates it after 2sec:
<dom-module is="test-elem">
<script>
TestElem = Polymer({
is: "test-elem",
properties: {
prop1: { type: String, value: "original value", notify: true }
},
factoryImpl: function() {
var that = this;
setTimeout(function(){
that.prop1 = "new value";
}, 2000);
}
});
</script>
</dom-module>
And this is the main page, which creates an element and show prop1 in the dom:
<template is="dom-bind" id="main-page">
<span>{{test.prop1}}</span>
</template>
<script>
var scope = document.getElementById('main-page');
var t = new TestElem();
scope.test = t;
</script>
Unfortunately, the page doesn't update with the new value.
Is there a way to bind this automatically?
Here is a jsfiddle: http://jsfiddle.net/xkqt00a7/
The easiest way I found was to add an event handler:
t.addEventListener('prop1-changed', function(e){
scope.notifyPath('test.prop1', e.currentTarget.prop1)
});
http://jsfiddle.net/83hjzoc3/
But it's not automatic like AngularJS.
I can't explain why, but switching your example from setTimeout to this.async makes it work.
Binding it as a sub property of the page scope doesn't work automatically because you need to manually call notifyPath('test.prop1', value) on the binding scope. One way to do that is to pass the binding scope to the TestElem constructor. Note that you don't need to worry about that if you bind to a parent element automatically. In that latter case the update is automatic.
The following works and shows two binding, and update, methods. One is automatic, the other requires a notifyPath call on the binding scope passed to the constructor.
TestElem = Polymer({
is: "test-elem",
properties: {
prop1: {
type: String,
value: "original value",
notify: true
}
},
ready: function() {
var that = this;
setTimeout(function () {
that.prop1 = "new value (via ready)";
}, 2000);
},
factoryImpl: function (app) {
setTimeout(function () {
app.notifyPath('test.prop1', 'new value (via app scope)');
}, 2000);
}
});
.. then reference the element one of two ways:
<test-elem prop1="{{prop1value}}">{{prop1value}}</test-elem>
<div>{{test.prop1}}</div>
<script>
app.test = new TestElem(app);
</script>
Where "app" is the main page's global binding scope (e.g. a as the a top level element in index.html, as is done in the polymer starter kit demo application).
After two seconds both properties update and the automatically updated html appears follows:
new value (via ready)
new value (via app scope)
Related
I'm trying to make a call from a on.("change") event to a vue method and that works fine but trying to give the received data from the change event to a Vue variable, the console log says that the variable has the new data, but it doesn't really change the variable correctly, it changes the last variable when you duplicate the components.
here is some of my code:
Vue.component('text-ceditor',{
props:['id'],
data: function (){
return {
dataText: "this is something for example"
}
},
template: '#text-ceditor',
methods: {
setData: function(data){
console.log(data)
this.dataText = data
console.log(this.dataText)
}
},
mounted: function(){
CKEDITOR.replace(this.$refs.text);
self = this;
CKEDITOR.instances.texteditor.on('change', function() {
self.setData(this.getData())
})
}
})
the component works correctly but the variable just change the last one
here is the fiddle: https://jsfiddle.net/labradors_/3snmcu84/1/
Your problem isn't with Vue but with CKEDITOR and its instances (with the ids you defined in the template and the way you reference them).
First problem is that you're duplicating ids in the text-ceditor component:
<textarea ref="text" v-model="dataText" id="texteditor" rows="10" cols="80"></textarea>
Why do we need to fix this? Because CKEDITOR instances in Javascript are id-based.
So now we need to change the id attribute to use the one passed in the component's props, like this:
<textarea ref="text" v-model="dataText" :id="id" rows="10" cols="80"></textarea>
Once we took care of that, let's reference the correct CKEDITOR instance from within the mounted method in the component.
We want to reference the one that matches with the id in our component.
From:
mounted: function(){
CKEDITOR.replace(this.$refs.text);
self = this;
CKEDITOR.instances.texteditor.on('change', function() {
self.setData(this.getData())
})
}
To:
mounted: function () {
CKEDITOR.replace(this.$refs.text);
var self = this;
var myCKEInstance = CKEDITOR.instances[self.id]
myCKEInstance.on('change', function () {
self.dataText = myCKEInstance.getData()
})
}
Notice that I also removed the call to setData as there is no need to have it and also declared self as a variable avoiding the global scope (which would overwrite it everytime and reference the latest one in all different components).
Now everything is updating correctly, here's the working JSFiddle.
I have a button:
var oButton = new sap.m.Button({
text: "Press me",
press: function (oEvent) {
if (! this._oPopover) {
this._oPopover = sap.ui.xmlfragment("sap.my.library.Popover", this);
this._oPopover.setModel(true);
}
var oButton = oEvent.getSource();
var oMyControl= this._oPopover.getAggregation('content')[0];
oMyControl.setPlaceholder("Hello World");
jQuery.sap.delayedCall(0, this, function () {
this._oPopover.openBy(oButton);
});
}
})
And i have my xml Fragment:
<core:FragmentDefinition
xmlns="sap.m"
xmlns:core="sap.ui.core"
xmlns:d="sap.my.library">
<Popover>
<d:myControl value=""/>
<!--<Input></Input>-->
</Popover>
</core:FragmentDefinition>
Now, when I click the button, nothing happens, only if I click it many times, my control appears.
I can say that for every 10 quick buttons clicks, my control appears maybe once.
Any suggestions?
Thanks!
this._oPopover is defined within an anonymous function declared within the button declaration.
IT CANNOT refer to a view field as you probably expect it to.
My suggestion :
var onButtonPress = function (oEvent) {
if (! this._oPopover) {
this._oPopover = sap.ui.xmlfragment("sap.my.library.Popover", this);
this._oPopover.setModel(true);
}
var oButton = oEvent.getSource();
var oMyControl= this._oPopover.getAggregation('content')[0];
oMyControl.setPlaceholder("Hello World");
jQuery.sap.delayedCall(0, this, function () {
this._oPopover.openBy(oButton);
});
}
var oButton = new sap.m.Button({
text: "Press me",
press: this.onButtonPressed.bind(this)
});
[code not tested!]
Could it be that 'this' is not referring to what you expect because your object is created within an anonymous function? See this explanation (ahem)
I believe the preferred pattern is to have the button press call a function in the main view where 'this' refers to the view scope.
I have a working example as follows: in then main view I have a button...
<Button
press="showDialogue"
/>
and the code of the view controller...
showDialogue: function(){
if(!this._oConfirmDialog){
this._oConfirmDialog = sap.ui.xmlfragment("sapui5.muSample.view.ConfirmDialog", this);
this.getView().addDependent(this._oConfirmDialog);
}
this._oConfirmDialog.open();
},
Note the use of view.addDependent() which lets the dialogue fragment access the view model AND destroys the dialogue instance in line the main view lifecycle events (according to the docs).
Or...you could create a local variable to capture the 'this' you actually want to refer to...
var contextThis = this;
just before your line starting
var oButton = ...
then change references to this inside the anon function to contextThis if you can't change the pattern.
I would like to access javascript object data from a custom polymer element nested inside a list. The host page has the following:
<core-list id="eventData">
<template>
<event-card event="{{model}}"></event-card>
</template>
</core-list>
With the following script:
<script type="text/javascript">
var data = [
// server side code to populate the data objects here
];
var theList = document.querySelector('core-list')
theList.data = data;
function navigate(event, detail, sender) {
window.location.href = '/events/show/?eventId=' + event.detail.data.id
}
theList.addEventListener("core-activate", navigate, false);
</script>
Inside event-card.html the following markup in the template achieves the desired result:
<core-toolbar>
<div class="title">{{event.title}}</div>
</core-toolbar>
<div class="responses">{{event.numResponses}} responses</div>
However when I run the following in the template script:
Polymer('event-card', {
ready: function() {
var eventDates = [];
var theEvent = this.getAttribute('event');
console.log(theEvent);
console.log(theEvent.proposedDate);
console.log(theEvent.possibleStartDate);
console.log(theEvent.possibleEndDate);
if (theEvent.proposedDate) {
eventDates[0] == theEvent.proposedDate;
} else {
if (theEvent.possibleStartDate && theEvent.possibleEndDate) {
var startDate = moment(theEvent.possibleStartDate);
var endDate = moment(theEvent.possibleEndDate);
var difference = endDate.diff(startDate, 'days');
for (i = 0; i < difference; i++) {
eventDates[i] = startDate.add(i, days);
}
}
}
console.log(eventDates);
this.dates = eventDates;
},
created: function() {
// hint that event is an object
this.event = {};
}
});
</script>
the log statements print
{{model}}
undefined
undefined
undefined
Array[0]
So I seem to be getting caught by the different ways that properties and attributes are evaluated in different contexts but I am not sure whether it is a bug in my code or even what approach to try next.
Because the "event" attribute is set by Polymer when the template is processed, the ready event handler is the wrong place to do your stuff (by the way, using Polymer you should consider using the "domReady" event handler).
Anyway, to get your scenario working, just add a "eventChanged" method to your Polymer component. Whenever an attribute changes (which is also the case when Polymer executes the template element) a "[propertyName]Changed(oldVal, newVal)" will be called (if existing) to signal the change to your component.
So implement this method and you're done.
One more caveat in your code : You should consider using "this.event" to access the attribute value (or as best solution the "newVal" parameter of your eventChanged(oldVal,newVal) method).
Why not using Polymer's attribution model from the get go?
<polymer-element attributes="event">
<script>
Polymer("event-card", {
ready: function() {
var theEvent = this.event;
}
});
</script>
</polymer-element>
I have the following JavaScript code, which works as expected...
/** #jsx React.DOM */
var TreeView = React.createClass({
render: function() {
return <div ref="treeview"></div>;
},
componentDidMount: function() {
console.log(this.props.onChange);
var tree = $(this.refs.treeview.getDOMNode()).kendoTreeView({
dataSource: ...,
dataTextField: "Name",
select: this.props.onChange
}
});
}
});
var MyApp = React.createClass({
onChange: function(e) {
console.log(e);
this.setState({key: e});
},
render: function() {
return (
<div>
<TreeView onChange={this.onChange}/>
<GridView />
</div>
);
}
});
However, with the kendo treeview, on selecting a tree node, the whole node is passed. To get at the underlying key, I would need to process the node as follows:
select: function(e) {
var id = this.dataItem(e.node).id;
this.props.onChange(id);
}
However I've obviously not quite got it right since, and here please excuse my noobness, it seems that in the working instance a reference to the function is being used, whereas in the non-working instance, the function is actually being executed... Or something like that: the error message being returned is:
Cannot call method 'onChange' of undefined.
So what would I need to do to be able to reference the function which extracts the key before calling the onChange method? Note that, if my understanding is correct, onChange needs to be executed in the context of the MyApp class so that any child components will get notified on the change.
EDIT: I've tried using partial application but am still not quite there. I've updated the onChange routine to take a function which returns the key from the node
onChange: function(getKey, e) {
this.setState({Key: getKey(e)});
},
But am not sure how to wire it all up.
Your code looks mostly right. I believe your only problem is that the select callback you're passing to the treeview is being called with the wrong scope. Note that you're using this to mean two different things within the function (once for the actual tree view object and the other for the React component). Easiest solution is probably to store a reference to the onChange function like so:
componentDidMount: function() {
var onChange = this.props.onChange;
var tree = $(this.refs.treeview.getDOMNode()).kendoTreeView({
dataSource: ...,
dataTextField: "Name",
select: function(e) {
var id = this.dataItem(e.node).id;
onChange(id);
}
});
}
Hope this helps.
I'm using require.js with backbone.js to structure my app. In one of my views:
define(['backbone', 'models/message', 'text!templates/message-send.html'], function (Backbone, Message, messageSendTemplate) {
var MessageSendView = Backbone.View.extend({
el: $('#send-message'),
template: _.template(messageSendTemplate),
events: {
"click #send": "sendMessage",
"keypress #field": "sendMessageOnEnter",
},
initialize: function () {
_.bindAll(this,'render', 'sendMessage', 'sendMessageOnEnter');
this.render();
},
render: function () {
this.$el.html(this.template);
this.delegateEvents();
return this;
},
sendMessage: function () {
var Message = Message.extend({
noIoBind: true
});
var attrs = {
message: this.$('#field').val(),
username: this.$('#username').text()
};
var message = new Message(attrs);
message.save();
/*
socket.emit('message:create', {
message: this.$('#field').val(),
username: this.$('#username').text()
});
*/
this.$('#field').val("");
},
sendMessageOnEnter: function (e) {
if (e.keyCode == 13) {
this.sendMessage();
}
}
});
return MessageSendView;
});
When keypress event is triggered by jquery and sendMessage function is called - for some reason Message model is undefined, although when this view is first loaded by require.js it is available. Any hints?
Thanks
Please see my inline comments:
sendMessage: function () {
// first you declare a Message object, default to undefined
// then you refrence to a Message variable from the function scope, which will in turn reference to your Message variable defined in step 1
// then you call extend method of this referenced Message variable which is currently undefined, so you see the point
var Message = Message.extend({
noIoBind: true
});
// to correct, you can rename Message to other name, e.g.
var MessageNoIOBind = Message.extend ...
...
},
My guess is that you've bound sendMessageOnEnter as a keypress event handler somewhere else in your code. By doing this, you will change the context of this upon the bound event handler's function being called. Basically, when you call this.sendMessage(), this is no longer your MessageSendView object, it's more than likely the jQuery element you've bound the keypress event to. Since you're using jQuery, you could more than likely solve this by using $.proxy to bind your sendMessageOnEnter function to the correct context. Something like: (note - this was not tested at all)
var view = new MessageSendView();
$('input').keypress(function() {
$.proxy(view.sendMessageOnEnter, view);
});
I hope this helps, here is a bit more reading for you. Happy coding!
Binding Scopes in JavaScript
$.proxy