I have a computed function that notifies a button if it should be disabled or not.
I also have a subscribe to this computed function that updates an another observable. When this observable is updated, it runs a custom binding.
The problem is that the subscribe() is running before the button is disabled. I want the computed function to run first, then the button is enabled/disabled, and finally it runs the subscription..
Is this possible? setTimeout() is not a good option.
this.enableButton = ko.computed(function() { return true or false });
this.enableButton.subscribe(function() { myself.triggerBinding(true) });
html:
<button data-bind="enable: enableButton" />
If I understand you correctly I hope this helps:
http://jsfiddle.net/UHgTS/1
var viewModel = {
buttonIsEnabled : ko.observable(true)
};
viewModel.enableButton = ko.computed({
read : function () {
log("read enableButton");
return this.buttonIsEnabled();
},
write : function (isEnabled) {
log("write enableButton");
this.buttonIsEnabled(isEnabled);
},
owner : viewModel
});
viewModel.otherObservable = ko.computed({
read : function(){
log("read otherObservable");
return this.buttonIsEnabled();
},
write : function () {
log("write otherObservable");
},
owner : viewModel
});
viewModel.otherObservable.subscribe(function(){
log("run subscription");
log("----------------");
});
function log(message){
$("#log").append(message + "\n");
}
ko.applyBindings(viewModel);
Related
In my javascript file, I have defined an app object that takes an initialization function which is triggered upon document ready via JQuery.
$(document).ready(function() {
console.log("JQuery ready");
app.initialize();
});
The app is defined as
var app = {
_GPS_ENABLED: false,
initialize: function() {
var self = this;
// deviceready Event Handler
$(document).on('deviceready', function() {
... ...
// BIND A CLICK EVENT TO A FUNCTION DEFINED IN A LATER STEP
$('#isGPSenabled').on("click", self.isGPSenabled);
... ...
});
},
isGPSenabled: function() {
cordova.plugins.diagnostic.isGpsLocationEnabled(function(enabled) {
// HERE I NEED TO ACCESS THE "APP" ATTRIBUTE "_GPS_ENABLED"
._GPS_ENABLED = enabled; // HOW CAN I ACCESS THE _GPS_ENABLED ATTRIBUTE ON APP
});
}
}
The HTML part has:
<button id = "isGPSenabled">IS GPS ENABLED</button>
How can I access the app's attribute from the function attached to a button?
Previously I've referenced the object by it's name within itself. I think it was a pattern I saw once which worked for my needs at the time. Haven't really thought about the positives or negatives much but it has never caused me any issues in previous work.
Here is an example to to demonstrate:
const app = {
isEnabled: null,
init: () => {
app.isEnabled = false;
},
toggleEnabled: () => {
app.isEnabled = !app.isEnabled;
},
displayEnabled: () => {
console.log('isEnabled?:', app.isEnabled);
}
}
app.displayEnabled(); // null
app.init();
app.displayEnabled(); // false
app.toggleEnabled();
app.displayEnabled(); // true
How can I efficiently render a component into the grid's summary row?
The summaryRenderer only returns the raw value, which is then put into the template. So this is my summary renderer:
summaryRenderer:function(summaryValue, values, dataIndex) {
return summaryValue + '<br><div id="btn-' + dataIndex + '">';
}
And somehow I have to insert a component after the renderer is through. I have tried to do it in store.load callback, but the renderer is done only after the load.
me.getStore().load({
callback:function(records, operation, success) {
Ext.each(me.getColumns(),function(column) {
Ext.create('Ext.Button',{
text:'Use this column',
handler:function() {
me.createEntryFromColumn(column);
}
}).render('btn-'+column.dataIndex);
// throws "Uncaught TypeError: Cannot read property 'dom' of null",
// because the div is not yet in the dom.
});
}
});
Which event can I attach to, that is fired only after the summaryRenderer is through?
Maybe you can do it this way:
init: function () {
this.control({
'myGrid': {
afterrender: this.doStuff
}
});
},
doStuff: function (myGrid) {
var self = this;
myGrid.getStore().on({
load: function (store, records) {
// do stuff
}
});
myGrid.getStore().load();
}
I suggest to check at : Component Template (http://skirtlesden.com/ux/ctemplate) for the component rendering.
I did not check myself, but just looking in the code: https://docs.sencha.com/extjs/6.0/6.0.1-classic/source/Column2.html#Ext-grid-column-Column-method-initComponent (ExtJs 6.0.1), you can see that the summary is render via a function.
See in initComponent
me.setupRenderer('summary');
Then in setupRenderer
// Set up the correct property: 'renderer', 'editRenderer', or 'summaryRenderer'
me[me.rendererNames[type]] = me.bindFormatter(format);
And bindFormatter produce a function.
So I suppose, you can extend column, and extend initComponent, after the this.callParent(), you can override me[me.rendererNames[type]] and put your own function which take v (the value) in parameter and which return the product of a CTemplate.
Something like
me["summaryRenderer"] = function(v) { return ctpl.apply({component:c, value:v})}
I think #CD.. suggestion to set a fiddle is a good idea.
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
I have one form for saving and editing records. On clicking on a record, the form should be filled with the data. After filling, I want to do some UI actions (call jQuery Plugin etc.).
The pre-filling works, but when I'm trying to access the values, it works only at the second click. On the first click, the values are empty or the ones from the record clicked before.
This action is stored in the controller:
edit: function(id) {
var _this = this;
// prefill form for editing
var customer = this.store.find('customer', id).then(function(data) {
_this.set('name',data.get('name'));
_this.set('number',data.get('number'));
_this.set('initial',data.get('initial'));
_this.set('description',data.get('description'));
_this.set('archived',data.get('archived'));
// store user for save action
_this.set('editedRecordID',id);
_this.set('isEditing',true);
$('input[type="text"]').each(function() {
console.log(this.value)
});
});
},
I need a generic way to check if the input field is empty, because I want to include this nice UI effect: http://codepen.io/aaronbarker/pen/tIprm
Update
I tried to implement this in a View, but now I get always the values from the record clicked before and not from the current clicked element:
View
Docket.OrganizationCustomersView = Ember.View.extend({
didInsertElement: function() {
$('input[type="text"]').each(function() {
console.log(this.value)
});
}.observes('controller.editedRecordID')
});
Controller
Docket.OrganizationCustomersController = Ember.ArrayController.extend({
/* ... */
isEditing: false,
editedRecordID: null,
actions: {
/* ... */
edit: function(id) {
var _this = this;
// prefill form for editing
var customer = this.store.find('customer', id).then(function(data) {
_this.set('name',data.get('name'));
_this.set('number',data.get('number'));
_this.set('initial',data.get('initial'));
_this.set('description',data.get('description'));
_this.set('archived',data.get('archived'));
// store user for save action
_this.set('editedRecordID',id);
_this.set('isEditing',true);
});
},
/* ... */
});
Update 2
OK, I think I misunderstood some things.
At first, my expected console output should be:
1.
2.
3.
but is:
1.
3.
2.
Secondly: I can use any name, even foobar, for the observed method in my view. Why?
Controller
edit: function(id) {
var _this = this;
// prefill form for editing
var customer = this.store.find('customer', id).then(function(data) {
_this.set('name',data.get('name'));
_this.set('number',data.get('number'));
_this.set('initial',data.get('initial'));
_this.set('description',data.get('description'));
_this.set('archived',data.get('archived'));
console.log('1.')
// store user for save action
_this.set('editedRecordID',id);
_this.set('isEditing',true);
console.log('2.')
});
},
View
Docket.OrganizationCustomersView = Ember.View.extend({
foobar: function() {
console.log('3.')
$('input[type="text"]').each(function() {
console.log(this.value)
});
}.observes('controller.editedRecordID')
});
Update 3
I think I "figured it out" (but I don't know why):
Docket.OrganizationCustomersView = Ember.View.extend({
movePlaceholder: function() {
$('input[type="text"], textarea').bind("checkval",function() {
var $obj = $(this);
setTimeout(function(){
console.log($obj.val());
},0);
}.observes('controller.editedRecordID')
});
setTimeout(function(){ ... }, 0); does the trick. But why?!
You can convert use that jquery code in a component, this is the best way to create a reusable view, without putting ui logic in controllers, routers etc.
Template
<script type="text/x-handlebars" data-template-name="components/float-label">
<div class="field--wrapper">
<label >{{title}}</label>
{{input type="text" placeholder=placeholder value=value}}
</div>
</script>
FloatLabelComponent
App.FloatLabelComponent = Ember.Component.extend({
onClass: 'on',
showClass: 'show',
checkval: function() {
var label = this.label();
if(this.value !== ""){
label.addClass(this.showClass);
} else {
label.removeClass(this.showClass);
}
},
label: function() {
return this.$('input').prev("label");
},
keyUp: function() {
this.checkval();
},
focusIn: function() {
this.label().addClass(this.onClass);
},
focusOut: function() {
this.label().removeClass(this.onClass);
}
});
Give a look in that jsbin http://emberjs.jsbin.com/ILuveKIv/3/edit
I'm not sure that I understand how to keep my web application reactive with Meteor.
I have this very simple template
<body>
{{> simple}}
</body>
<template name="simple">
Counter: {{counter}} <br/>
<button>Increase</button>
</template>
And client side script
var counter = 0;
Template.simple.counter = function () {
return counter;
}
Template.simple.events({
'click button': function () {
counter++;
console.log("counter is ", counter);
Meteor.flush();
}
});
When clicking on button I can see in console that counter variable is increasing properly but nothing happens on UI. Why? I thought it's exactly what Meteor.flush() is intended to do.
The UI isn't reactive all by itself, you need to use one of Meteor's reactive items. In this case, you probably want to use Session. Try the following, instead of the second script you pasted:
Session.set('counter', 0); // Initialize a *reactive* variable
Template.simple.counter = function () {
return Session.get('counter'); // This will automatically update, because Session is reactive
}
Template.simple.events({
'click button': function () {
Session.set('counter', Session.get('counter') + 1);
}
});
You could also build your own reactive data source:
if (Meteor.isClient) {
Sort = {
sort: 1,
dep: new Deps.Dependency,
get: function () {
this.dep.depend();
return this.sort;
},
toggle: function () {
this.sort *= -1;
this.dep.changed();
}
};
Template.leaderboard.players = function () {
return Players.find({}, {sort: {score: Sort.get(), name: 1}});
};
Template.leaderboard.events({
'click input.sort': function () {
Sort.toggle();
}
});
}
Recent versions of Meteor provides the reactive-var package, see here : http://docs.meteor.com/#/full/reactivevar_pkg
To use ReactiveVar, add the reactive-var package to your project by running in your terminal:
meteor add reactive-var