knockout - notify when 'with' directive is fired - dom

Hi I have the following scenario.
Some elements are nested within a with
<!-- ko with: model.selected_item -->
<tr>
<td style="width:20%">Name:</td>
<td style="width:80%" class="field" data-bind="text: name"></td>
<td style="width:10px"><div class="btn_edit"></div></td>
</tr>
<tr>
etc...
<!-- /ko -->
$("div.btn_edit", component.context).on("vclick", function(e){
//edit it
}
The problem I have is that if the selected_item changes - I loose the bindings on the edit button.
In this scenario, it is a little difficult to subscribe to the model.selected_item and reapply the bindings - so I'd like to get notified when the elements within the with directive are updated and reapply then.
Is there a knockout specific way to achieve this?
An help much appreciated.

The reason you are getting this problem is that the click handler element is being destroyed by the template engine each time you update item.
Here is an example of the way to achieve what you want without resorting to jquery delegate bindings, which while they do work they are poluting your otherwise nice KO model with needless dom operations. Everytime I find myself using the old $() selector with a KO app I have to seriously consider whether there is a better model oriented way.
http://jsfiddle.net/madcapnmckay/EFQ9S/
The gist of this is to convert your model into true js classes and use those to bind KO click events rather than jquery click handlers. The beauty of this approach is that KO will rebind for you elements when they get destroyed.
var item = function (config) {
var self = this;
this.name = ko.observable(config.name);
this.edit = function () {
$("body").append("<div>lets edit " + self.name()+"</div>");
};
};
var model = function() {
this.item = ko.observable();
this.pushNew = function () {
this.item(new item({name: "new name"}));
};
}
$(document).ready(function(e) {
var mymodel = new model();
ko.applyBindings(mymodel);
mymodel.item(new item({ name: "the_name" }));
})
Hope this helps.

Related

Passing multiple data points to an action? VUEX

So I have a form that collects 3 data points that looks like this:
<form class="" >
<table>
<tr>
<td>Maximum Temperature</td>
<td><input class="setSettings" v-model="settings.maxTMP" type="number" max="30" min="5"></td>
</tr>
<tr>
<td>Minimum Humidity</td>
<td><input class="setSettings" v-model="settings.minHUM" type="number" max="95" min="20"></td>
</tr>
<tr>
<td>Maximum CO2</td>
<td><input class="setSettings" v-model="settings.maxCO2" type="number" min="200" max="5000" step="10"></td>
</tr>
</table>
<button type="button" v-on:click="whatSettings(settings)">Update Settings</button>
</form>
My data in the script tag is stored in the components local data, not the store, ike this:
data: function() {
return {
settings: {
maxTMP: "",
minHUM: "",
maxCO2: ""
}
}
}
There's no reason to put it through a mutation and commit it to the store as the purpose for the action is merely to send the data via post request to receiving api.
My methods for the component look like this:
methods: {
...mapActions({
setSettings: 'setSettings'
}),
whatSettings(settings){
let foo = settings.maxTMP;
let bar = settings.minHUM;
let uhm = settings.maxCO2;
console.log(foo);
console.log(bar);
console.log(uhm);
this.setSettings(foo,bar,uhm)
}
},
And that action from the store is write like this:
setSettings(foo,bar,uhm) {
console.log("Set these settings");
console.log(foo);
console.log(bar);
console.log(uhm);
},
Please forgive all the console.log()'s. I have it like this cause I was trying to test out different combinations of things to figure out where it goes wrong. Right now when I click the Update Settings button the console prints the whatSettings() correctly so I know that foo, bar, uhm are the correct value as they are passed into the setSettings() action. The problem is in the action's logs. "Set these settings" and foo are printed correctly followed by a single undefined, not two. So I'm not sure exactly what's happening with bar & uhm. When I rearrange the order it's always the first that gets printed.
Is it a problem with multiple arguments being passed to the action? Ideally I would just like it to look like this directly in the button tag for neatness but that didn't work so I tried trouble shooting like this:
v-on:click="this.setSettings(settings.maxTMP, settings.minHUM, settings.maxCO2)"
Thanks for reading and I appreciate the help!
Well, after a little more looking around I found this and it fixed my problem: Axios post request with multiple parameters in vuex action
correct button tag:
<button type="button"
v-on:click="setSettings({
foo: settings.maxTMP,
bar: settings.minHUM,
uhm: settings.maxCO2
})"
>Update Settings</button>
correct vuex store action:
setSettings({commit}, {foo, bar, uhm}) {
console.log("Set these settings");
console.log(foo);
console.log(bar);
console.log(uhm);
},
Not entirely sure why the {commit} needs to be present, but it does otherwise I just get 3 undefined results in the log. But it works now!

dynamically create bound datas with polymer

I would like to create a dynamic form using polymer, meaning that everytime the user press "add" button,it will add a new field in the form. Or, more specifically, it will add a paper-dropdown-menu, where all of the options come from a dom-repeat fed by an ajax call.
this is what i've done so far:
<div id="filterContainer">
<div class="flex rulesForm" id="filter1">
<paper-dropdown-menu name="rule1A" no-label-float>
<paper-listbox attr-for-selected="value" selected="{{filter1A}}" class="dropdown-content" id="thirdPartyFilter1A">
<template is="dom-repeat" items="{{rule1A}}">
<paper-item value="[[item]]">[[item]]</paper-item>
</template>
</paper-listbox>
</paper-dropdown-menu>
</div>
</div>
<paper-button raised on-tap="addFilterField">Add</paper-button>
<div>
and in the JS:
addFilterField: function () {
let dropdown = document.createElement('paper-dropdown-menu');
dropdown.name = "";
dropdown.noLabelFloat = true;
let listbox = document.createElement('paper-listbox');
listbox.class = "dropdown-content";
listbox.attrForSelected = "value";
listbox.selected = "{{filter1A}}";
let paperItem = document.createElement('paper-item');
paperItem.value = "[[item]]";
var itemNode = document.createTextNode('[[item]]');
paperItem.appendChild(itemNode);
listbox.appendChild(paperItem);
dropdown.appendChild(listbox);
console.log(dropdown);
filterContainer.appendChild(dropdown);
my problem is about the data-binding... If I use createTextNode with [[item]], it will simply write it as a string in the document. Is there a way to fix this? (or a way easier solution to add field in a form?)
first of all you cannot use binding notation in javascript. it is markup
2nd, polymer doesn't yet support creating data bindings dynamically. however I'm sure you can accomplish what you are trying to do.
3rd,
you have to use the Polymer Dom API. https://www.polymer-project.org/1.0/docs/devguide/local-dom#dom-api
instead of paperItem.appendChild(itemNode)
you would use
Polymer.dom(listbox).appendChild(itemNode);

Programmatical changes will not reflect in knockout viewmodel

To change the status of a checkbox with javascript doesn't correspond to the spirit of MVVM. But I'm creating a general javascript library for better looking standard controls like checkbox, radio button or selectbox.
Based on the following viewmodel:
function MyViewModel() {
var self = this;
self.ok = ko.observable();
};
var vm = new MyViewModel();
ko.applyBindings(vm);
But I get a problem in conjunction with knockout when I change the checked status of a checkbox programmatically:
document.getElementById('chk').checked = true
The change will not appear in the property of the viewmodel. But when I click the checkbox all works fine.
Look at http://jsfiddle.net/KWdZB/1/
Is there any workaround?
Your problem is that ko subscribes on the click event inside the checked binding:
ko.utils.registerEventHandler(element, "click", updateHandler);
But changing the checked attribute won't trigger the click event so ko won't be notified.
If you manually trigger the click event after the attribute change it can work...
I don't know how to do it with pure javascript but with jQuery you can write:
$('#chk').attr('checked', true).triggerHandler('click')
You can test it in this JSFiddle.
This is normal because the checked binding handlers doesn't subscribe to the checked change event but subscribe to the click event handler (You can see on source file at the checked binding handlers code).
If you need to change value with click, you must to do with the ok observable value.
There is the HTML
<div>
<input type="checkbox" id="chk" data-bind="checked: ok"/><br>
<input type="button" id="btnCheck" value="Check" data-bind="click: Check"/>
<input type="button" id="btnUnCheck" value="Uncheck" data-bind="click:Uncheck"/>
</div>
<div>
Value: <span data-bind="text: ok"></span>
</div>
And the javascript :
function MyViewModel() {
var self = this;
self.ok = ko.observable();
self.Check = function(){
self.ok(true);
}
self.Uncheck = function(){
self.ok(false);
}
};
vm = new MyViewModel();
ko.applyBindings(vm);
​
​
You can see it in this fiddle.

how to reinitialize an angularjs application

Here is my code:
function MainCtrl($scope) {
$scope.thisarray = globalObj;
$scope.loadthis = function (index) {
return thisarray[index];
}
}
Here is the markup:
<table ng-controller="MainCtrl">
<tr>
<td ng-repeat="element in thisarray">
{{loadthis($index)}}
</td>
</tr>
</table>
I'm loading some data through a SOAP callback:
SOAPCLient.invoke(url, methodName, pl, true, function(obj) {globalObj = obj;
**angular.bootstrap("html");**//here is where I try to reinitialize the app
});
If you look at the SOAP call, I use bootstrap to reinitialize the app, but it has not been working. loadthis does not display any data inside the HTML table. Can anyone help me here?
You could listen for changes to the SOAP using $.watch and then $.apply when trying to force a push. See examples of how to use here
You should not be re-initializing AngularJS application every time you've got data arriving from an external source. Instead of destroying and re-creating AngularJS the way to go would be to use $apply method on a scope object.
The pseudo-code (in your controller) would be like:
SOAPCLient.invoke(url, methodName, pl, true, function(obj) {
$scope.apply(function(){
$scope.thisarray = obj;
});
});
Also, if I understand your code correctly you would invoke a SOAP call for each item in a repeater (ng-repeat) while your SOAP call is bringing back data for all objects. So the better approach would be to do a SOAP call (as in a snippet above), assign data to a variable in a scope and let the ngRepeat do its magic.
I might be misunderstanding what you are trying to achieve here so having a jsFiddle / plunker with your code would help to provide more detailed response.

JavaScript DOM object to jQuery object

How can I convert a JavaScript DOM object to a jQuery object?
<tr onclick="changeStatus(this)">
function changeStatus(myObject) {
XXX.removeClass();
}
What should I write in place of XXX?
I know I can make a workaround using an id and an id selector, but it is not so elegant.
Is there any way to convert a js DOM object to a jQuery object or using the this keyword in jQuery?
var $this = $(myObject);
$this is a jQuery object. You can create jQuery objects from DOM elements.
<tr onclick="changeStatus(this)">
function changeStatus(myObject) {
$(myObject).removeClass();
}
I would like to recommend doing your event binding with jQuery as well:
<tr class="change-status">
$('.change-status').on('click', function () {
$(this).removeClass( ... );
});
This is nice because now all the JS code is in one place and can be updated (in my opinion) more easily. Note that the class I added to the <tr> element is not necessary if you want to bind to all <tr> elements in the DOM.
Simply wrap the pass the DOM object in as the first argument.
$(myObject).removeClass('foo');