Mithril.js m.withAttr: Passing event to another function - javascript

I am going through the Mithril tutorial and am having trouble understanding m.withAttr. The guide has the following line in the view layer:
m("input[type=checkbox]", {onclick: m.withAttr("checked", task.done), checked: task.done()})
I have two questions.
1) I understand that the first half, onclick: m.withAttr("checked", task.done) means, essentially:
'set task.done, using m.prop, to the value of the "checked" attribute'. But what is the purpose of the second half, checked: task.done()? It seems like it is simply repeating the first half.
2) As I went through the tutorial, I wanted to add the functionality of persisting my Todos to a persistence layer. I created a save function, so that I could refactor the line that I referenced above into something like:
m("input[type=checkbox]", { onclick: todo.vm.markAsDone.bind(todo.vm, task)})
And in my view-model, I had the function:
vm.markAsDone = function(todo) {
m.withAttr("checked", todo.done), checked: todo.done();
todo.save();
};
But this did not work; I get an Uncaught SyntaxError: Unexpected token : error. I think the problem is that the event is not being properly bound to the markAsDone function, so it doesn't understand the "checked" attribute; but I'm not sure how to fix this problem (if that even is the problem).
Thanks for any help.

Question 1
The second parameter of the m() function defines attributes on the HTML element, in this case an <input type=checkbox> will be decorated. (The exception is the special config field)
checked determines if the input checkbox is checked, so it is required to display the state of the task.
onclick is the event handler that will modify the state.
So the attributes do different things, therefore both of them are needed.
Question 2
Since markAsDone is passed a todo model, you don't have to do any m.withAttr call there. Simply modify the model, and let Mithril redraw the view. The redraw happens automatically if you call markAsDone through an event like onclick.
If you want more information about the redraw procedure, I summarized it in a previous SO question.
Edit: markAsDone will probably look like this:
vm.markAsDone = function(todo) {
todo.done(true);
todo.save();
};

Related

KnockoutJS Binding not updating in checkbox click event

I'm running into an issue with KnockoutJS where it appears the data-bindings aren't updating as expected.
In my view model, I have an array of objects with an Enabled property that I initialize to True, and have that property bound to enable on a checkbox with a click handler CheckItems:
self.Answers(data.filter(function (step) {
if (step.Type == 0) {
step.Enabled = true;
return true;
}
else {
return false;
}
}));
...
<ul data-bind="foreach: $root.Answers">
<input type="checkbox" data-bind="click: $root.CheckItems, ..., enable: Enabled>
</ul>
Everything work fine and dandy, until the CheckItems handler runs and I try to set the Enabled property to false:
self.CheckItems = function (step) {
...
self.Answers().forEach(function (option) {
<call my handler>.done(function (data) {
option.Enabled = data;
}
);
});
// EDIT: added valueHasMutated() call here
self.Answers.valueHasMutated();
return true;
}
When I hit return true, I can inspect the self.Answers() object and it shows that the answers that should be disabled have Enabled = false (correctly), but once we're the handler, none of the checkboxes are disabled, and clicking on any other checkbox present and going through the handler shows that the Enabled property seems to have been reset to True. I triple checked and made sure there's nothing else touching the Enabled property anywhere in the code between checkbox clicks.
The binding itself seems to be working, too, since when I switch the initial Enable set to false, all of the checkboxes are disabled.
I'm making changes to the self.Answers array various other places in the script as well, and those go through fine, when a change is made it goes into knockout-latest.debug.js and lands in the notifySubscribers function with the appropriate updates to make. Maybe there's something special about the click event for input fields in knockout?
Any ideas? Still fairly new to Knockout overall, but I thought this was the entire point, that I could update a property in the Answers() observable array and it would update in the corresponding UI.
Edit: based on comments I tried calling valueHasMutated() after the edits were made, but it still isn't updating properly.
Edit2: tried making a copy of the self.Answers array, doing the handler calls to update Enabled, then setting with self.Answers(newAnswers), and this had the same result.
I was advised that the issue was occurring because properties of the objects in CurrentAnswers() weren't observable as well. Since there weren't changes to the array itself, knockout wasn't registering that it had to rebind anything, which is why calling valueHasMutated() didn't change anything as the array looked exactly the same before and after CheckItems() from an array standpoint (also why updating with a copy of the array didn't work, same issue). When setting CurrentAnswers() to a blank array (self.CurrentAnswers([])), then setting it to the updated value, things changed properly since KO recognized the actual array changes.
The solution I ended up using was to utilize ko.mapping.fromJS() method to fill CurrentAnswers() with objects that had observable properties. After some syntax changes in the HTML and JS to properly reference the new observables, everything worked properly.

How to use reactive forms inside ng-template

I have just started with Angular 4 and I need to develop a CRUD grid, where the user can add, edit or delete rows.
During my research I found this article where it shows how to create the grid and also the actions: Angular 4 Grid with CRUD operations.
Looking at his code, what called my attention was the way he is using the ng-template to toggle between edit/view mode.
<tr *ngFor="let emp of EMPLOYEES;let i=idx">
<ng-template [ngTemplateOutlet]="loadTemplate(emp)" [ngOutletContext]="{ $implicit: emp, idx: i }"></ng-template>
</tr>
On the article he uses template driven forms to edit the row. However, I was trying to change to reactive forms.
In my attempt to do that, I tried to replace the [(ngModel)] to formControlName and I got some errors. My first attempt I tried to add the [formGroup] at the beginning of the template html inside form element. But when I tried to run and edit the row, I got the following error:
Error: formControlName must be used with a parent formGroup directive. You'll want to add a formGroup directive and pass it an existing FormGroup instance (you can create one in your class).
When I tried to move the [formGroup] inside the ng-template it works, however I was not able to bind the value to the fields and I had to set the values in the loadTemplate function:
loadTemplate(emp: Employee) {
if (this.selemp && this.selemp.id === emp.id) {
this.rForm.setValue({
id: emp.id,
name: emp.name
});
return this.editTemplate;
} else {
return this.readOnlyTemplate;
}
}
This works and show the values inside the fields in a read only mode :(
Here is the Plunker of what I have got so far.
How can I make a reactive form work with ng-template and how to set values to edit the entries?
Any help is appreciated! Thanks
Actually your form is not readonly, you are just constantly overwriting the input you are entering. Since you are having a method call in template (which is usually not a good idea), loadTemplate gets called whenever changes happen, which in it's turn means that
this.rForm.setValue({
id: emp.id,
name: emp.name
});
gets called over and over whenever you try and type anything. We can overcome this with instead setting the form values when you click to edit. Here we also store the index so that we can use it to set the modified values in the correct place in array, utilizing the index could perhaps be done in a smarter way, but this is a quick solution to achieve what we want.
editEmployee(emp: Employee) {
this.index = this.EMPLOYEES.indexOf(emp)
this.selemp = emp;
this.rForm.setValue({
id: emp.id,
name: emp.name
});
}
so when we click save, we use that index...
saveEmp(formValues) {
this.EMPLOYEES[this.index] = formValues;
this.selemp = null;
this.rForm.setValue({
id: '',
name: ''
});
}
Your plunker: https://plnkr.co/edit/6QyPmqsbUd6gzi2RhgPp?p=preview
BUT notice...
I would suggest you perhaps rethink this idea, having the method loadTemplate in template, will cause this method to fire way too much. You can see in the plunker, where we console log fired! whenever it is fired, so it is a lot! Depending on the case, this can cause serious performance issues, so keep that in mind :)
PS. Made some other changes to code for adding a new employee to work properly (not relevant to question)

Ember.js - Updating a Bound Property without Triggering Local Observer

I've created an ember component that wraps an editor (CKEditor). The editor's values are updated via setData() and getData() accessors. I want to implement two-directional binding in my ember control so that edits to the component's "content" field flow in and out of the control.
So far, I'm able to get it going one way easily - but my attempts to go bidirectional are very messy. I can set up an observer on the property and have it update the control. However, when I try to set the property when the controller's "change" event is called, it causes the observer to be triggered. That, in turn causes the editors "change" event to trigger and so on. Welcome to Loopy Land.
I know that there are ways to get around this - but everything that I've been trying has me coming up short. It seems hacky - not elegant like the rest of Ember. Can anyone suggest some examples that demonstrates the preferred pattern for this?
Thanks!
--
(Thanks David - Here is some Additional Information)
I've been trying the bound property thing. It works great for outbound updates (from the editor control to another bound textarea on the page) but when inbound the page starts to bog down.
When I initialize the CKEditor, I reference a component that I installed that adds a 'change' event:
editor.on('change', this.updateContent.bind(this));
Here is the update content event:
updateContent: function() {
this.set('_content', this.get('editor').getData());
},
And then, the bound property:
content: function(key, val, previous)
{
if (arguments.length > 1)
{
this.set('_content', val);
var editor = this.get('editor');
if (editor) editor.setData(val);
}
return this.get('_content');
}.property('_content'),
It sounds like you are attempting to update a computed property from your control. If you have a computed property of fullName which depends on firstName and lastName, then it gets confusing if your UI updates the dependencies and not the computed property.
But if you really need to update the computed result, then look at the "Setting Computed Properties" section in the Ember docs (http://emberjs.com/guides/object-model/computed-properties/) and it shows you how you can use the input to the computed property to update its dependencies.
Not sure if this addresses your requirement, but if not pls submit a snippet of what's looping and what needs to be updated.

Custom knockout binding fires twice, unexpectedly

I have written a custom binding handler to bind my viewmodel data to a highcharts chart. This really has 2 parts, one binds the initial config required for highcharts, the second binds the series to the chart.
here is the bindingHandler code
ko.bindingHandlers.highchart = {
update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var value = valueAccessor();
var valueUnwrapped = ko.unwrap(value);
console.log ('update',element.id,valueUnwrapped);
if(allBindings.get('series')){
var series = allBindings.get('series');
var seriesUnwrapped = ko.unwrap(series);
if(!$.isArray(seriesUnwrapped)){
seriesUnwrapped = [seriesUnwrapped];
}
console.log('seriesUnwrapped',element.id,seriesUnwrapped)
valueUnwrapped.series = seriesUnwrapped;
}
$(element).highcharts(valueUnwrapped);
}
}
Now I have 2 tests set up for this, the first works as expected. It binds a chart with multiple series, and when I add to the observable array bound to the series it updates the chart just once. Look at this fiddle and watch the console as you click the "add" button. The output you'll get is
update container Object { chart={...}}
seriesUnwrapped container [Object { name="Scenario0", color="red", data=[9]}, Object { name="Scenario1", color="green", data=[9]}]
Indicating that we've been through the above code only once.
Now check my second fiddle: http://jsfiddle.net/8j6e5/9/ . This is slightly different as the highcharts initial config is a computed observable, as is the series. When you click the "add" button on this one you'll see the binding is executed twice:
update container2 Object { chart={...}, xAxis={...}, series=[1]}
seriesUnwrapped container2 [Object { name="Scenario2", color="blue", data=[2]}]
update container2 Object { chart={...}, xAxis={...}}
seriesUnwrapped container2 [Object { name="Scenario2", color="blue", data=[2]}]
I'm guessing that using allBindings.get('series') within my highcharts binding handler I set up a dependency to it, and when both bindings change its executing the highcharts binding twice. My question is, is there any way to stop this, or write this functionality any other way as to not have this happen?
I'm not sure this will help you, as nemesv answers in the comments above seem to be very close to what you wanted to achieve, ie. stopping the double update.
However, I've spent a bit of time on it, so I'll show you what I came up with anyway and hope it helps.
Fiddle here
I didn't know about the peek() method that nemesv mentioned (great tip), so I looked into why it was updating twice based on the computeds etc.
I saw that your self.breakdownChart was accessing the currentScenario observable, and when I removed that as a test, the second update didn't occur.
So that got me thinking, why you needed that in there for the x axis setting.
So I added a new property to your scenario to return the current scenario name
self.name='Scenario' + self.number;
And then for the base scenario, changed this to "Base Scenario" to ensure that title appears correctly for just that series.
To ensure the legend/axis is correct, I added a new property to the chart object called baseSeriesName
self.breakdownChart = ko.computed(function(){
return {
baseSeriesTitle: baseScenario.name,
and that is set to the baseScenario's name.
Finally, to tie that all together in the BindingHandler, I update the xAxis in there:
//set the xAxis titles, only add the second title if different from the base
valueUnwrapped.xAxis={
categories: [valueUnwrapped.baseSeriesTitle, valueUnwrapped.baseSeriesTitle!=seriesUnwrapped[0].name ? seriesUnwrapped[0].name:'']
}
It's a bit of refactoring, but it achieves your goal; hope it helps.
Oh, I also added a chartType observable to the view model, and used that in the chart definition (breakdownChart computed), to test the double update wouldn't happen if the chart refreshed on a different observable and that it still initialised correctly - so the fiddle shows the chartType updating, without a double update.
You get two updates because Knockout updates computed observables immediately when their dependencies change, and your binding has two dependencies, each of which gets updated in turn.
One way to solve this is to use a technique to delay updates of the binding. An easy way to do so is to use the Deferred Updates plugin, as demonstrated here: http://jsfiddle.net/mbest/8j6e5/15/
Deferred Updates uses setTimeout to perform updates, which means that the update happens asynchronously. If you want it to be synchronous for a specific update, you can use ko.tasks.processImmediate:
ko.tasks.processImmediate(function() {
self.scenarios.push(newScenario);
self.currentScenario(newScenario);
});

Missing view.context or templateContext in an Ember event handler

I'm trying to push the object that populated a view into an array, but the reference is somehow getting lost. I've got an Ember view, with a defined eventManager:
FrontLine.NewProductButton = Em.View.extend({
tagName: 'button',
classNames: ['addtl_product',],
templateName: 'product-button',
eventManager: Ember.Object.create({
click: function(event, view) {
FrontLine.ProductsController.toggleProductToCustomer(event, view);
}
})
})
That view renders a bunch of buttons that are rendered with properties that come from objects in the ProductsController using the #each helper. That part works great. And when I click on any of those buttons, the click event is firing and doing whatever I ask, including successfully calling the handler function (toggleProductToCustomer) I've designated from my ProductsController:
FrontLine.ProductsController = Em.ArrayController.create({
content: [],
newProduct: function(productLiteral) {
this.pushObject(productLiteral);
},
toggleProductToCustomer: function(event, view){
FrontLine.CustomersController.currentCustomer.productSetAdditional.pushObject(view.context);
}
});
I'm trying to use that function to push the object whose properties populated that view into an array. Another place in my app (a simple search field), that works perfectly well, using pushObject(view.context). Here, however, all that gets pushed into the array is undefined. I tried using view.templateContext instead, but that doesn't work any better. When I try console.log-ing the button's view object from inside those functions, I get what I'd expect:
<(subclass of FrontLine.NewProductButton):ember623>
But either view.context or view.templateContext return undefined. How do I access the object I'm after, so I can add it to my array?
The simple answer is that it was one letter's difference:
view.content
or:
view.get('content')
provides the source object in that particular situation, rather than view.context.
(My only real challenge with Ember so far is that accessors for objects and properties vary so much from situation to situation, and there's no real documentation for that. Sometimes the object is at view.context, sometimes it's at view.content, sometimes _parentView.content, etc., etc. It would be awesome if there were a chart with the umpteen different syntaxes for accessing the same data, depending on which particular aperture you're reaching through to get it. I'm still discovering them...)

Categories

Resources