I have a project in ASP.NET MVC 4 that uses knockoutjs to handle client-side stuff like keeping track of if a certain field has changed.
In the class declaration for my ViewModel, i have 2 observables, both initialized to "":
private _observables = {
query: ko.observable(""),
object: ko.observable("")
}
In the close function of a dialog box, I check both observables using the isDirty() method to find out if they've changed, and prompt the user about saving if changes are detected.
I've stepped through and figured out that object doesn't appear to be tracked correctly, despite the line above. Inside isDirty(), knockout pulls the current state like this:
target.isDirty = function () {
var currentState = ko.toJS(target);
....logic
returns dirty/notdirty
After the ko.toJS() call, the object field of currentState is always undefined, which causes it to fail the state check- because the initial state is properly recorded as "".
Even if I use self._observables.object("") to explicitly set object before the call to isDirty(), it's still undefined after the ko.toJS() call inside the 'dirty-checker'.
I thought the issue might be the binding in the view- it's bound to a hidden field that doesn't get user input, however as long as object is initialized, I don't see how that initial value is being lost/overwritten.
Related
I have a form field that has the following generic autocomplete:
<generic-autocomplete
v-model='c.heuristicId'
v-on:change='heuristicSelected(i, $event)'
label='Fuente de datos'
:apiFunction='heuristicsFetchFunction'
:disabled='disabled'/>
When saving the form and sending it with this field in blank heuristicIdgets sent as nullwhich is what is desired.
However, when selecting a value from the generic autocomplete and then deleting it, heuristicIdgets sent with the id of the previously selected value.
Inside the function that calls axios I'm logging console.log("heuristicId to be sent: " + campaign.heuristicId);and undefinedgets logged in both scenarios.
EDIT: After inquiring a little bit more I believe that the error might be in one of <generic-autocomplete/>'s computed properties:
isValid: function () {
// this checks that the name in display equals the object selected in
// memory so no invalid labels are displayed while a valid object is 'selected'
let vc = this
let result = false
if (vc.object) {
result = vc.objectName === vc.object.name
console.log('generic-autocomplete#isValid', result)
}
return result
}
An empty vc.objectwill return a falseresult and I haven't been able to satisfactorily handle that.
After logging a really complex control flow I finally got to the point where I knew which properties I had to mutate. However, it turns out that in Vue 2 mutating inline properties is an anti-pattern. The capability of mutating inline props was deprecated from the previous Vue version in order to be consistent with the library's fully reactive paradigm. So what happens is that if you try to mutate an inline property, when the component re-renders, such property will be set back to its default (or previous) value.
This is solved by adding a computed property that's a copy from the property that you want to mutate.
//Somewhere else in the code the property is defined
property: null
//Add the computed property
data: function () {
return {
mutableProperty: this.property,
}
}
I want to pass custom property while creating select2. Example (my custom property being myFilterEnabled):
$('#mySelId2').select2({
myFilterEnabled: false, //Pass my initial state
query: function(query) {
var res = {
results: CityFilter.cities
};
query.callback(res);
}
});
And use it in the query or render functions. Like:
$('#mySelId2').select2({
myFilterEnabled: false,
query: function(query) {
var fltEnabled = this.myFilterEnabled; //Read current state
var res = {
results: fltEnabled ? [] : CityFilter.cities
};
query.callback(res);
}
});
This is so that, there is an initial state for the variable. But, it can change externally, and I want to check that state during each re-render/query.
Edit: Seems I made a mistake before posting. Above code seems to work. I am planning to add a common prefix like 'my' or 'myProj' so that it doesn't conflict with any variables of select2 itself.
Edit2: As mentioned, passing initial state and reading current state are working. I still need a way to change that state from outside. If select2 doesn't have a method for that I could set a data attribute on the element.
This is the full cycle that I wanted:
Set custom state -> Read custom state during query/render -> Change custom state on user action -> Trigger re-render on state change
This is how I managed to do it as of now:
1) I can pass a custom parameter in options while setting up select2
$('#mySelId2').select2({
myFilterEnabled: false,
query: function(query){ ...
2) I am able to read the custom parameter within the callbacks as
this.myFilterEnabled
3) I can set the custom parameter from outside as
$('#s2id_<myId>').data('select2').opts.myFilterEnabled = true;
3) After setting the property as shown above, i want select2 to
re-apply the query function. I can trigger change on
input.select2-input. But, there is a check to prevent re-execution
of query while the text remains the same. So, I go a step further
and call the updateResults function with a 'true' argument. That
forces updateResult to proceed to run query again. Example:
$('#s2id_<myId>').data('select2').updateResults(true);
I currently have a test where I have an input element with value.bind="someProperty" which works fine and correctly shows the value of that property in the input field.
However if I were to subscribe to the DOM input event on that field and when its invoked check the value of the property it is not the same as the DOM value, even if I were to do a delay of 100ms (via setTimeout(checkPropertyValue, 100);) the value is not always propagated to the model, so when exactly does the value get updated from the input field?
== Edit ==
Here is example code for the sort of thing that is occurring, a good point was raised in a passing conversation with someone that with view filters you may throttle value bindings so you cannot rely upon the model being updated straight away so you may have to listen out for model changes using ObserverLocator.
#customAttribute('some-atribute')
export class SomeAttribute
{
#bindable someObjectWhichUsesVM;
constructor(element) {
this.element = element;
}
attached() {
this.element.addEventListener("input", doSomethingWithModel);
}
doSomethingWithModel() {
this.someObjectWhichUsesVM.doSomething();
}
}
So the above code would check for input changes on an element (not the model bound) and then try to do something using data on the model, which wont be propagated.
I think I have already found the answer which is that I need to subscribe to the model for changes and basically make sure the change has occurred before I run the logic, but if there is a better way I am all ears.
I have an object in an array
var
sidelist = [
{
name:"MURICA",
types:[...]
}
];
I have a box that displays the object's name. Then I have a text field and a button. On button press the object's name gets set to text field value. But I don't know how to make the name in the box change accordingly.
As I understand putting the object in a session variable is not an option since I will not be able to modify properties of objects inside of it without resetting the whole session var. I tried it and failed.
html
<template name="asdf">
{{#with object}}
<div>{{name}}</div>
{{/with}}
</template>
js
Template.asdf.object = function() {
return Objects.findOne(...);
};
EDIT
I think I've got your question wrong, sorry. If you have a value in memory that you'd like to change and have the DOM updated, use dependencies:
html
<template name="asdf">
{{property}}
</template>
js
var property;
// Create new dependency object that will manage refreshing property value:
var _dep = new Deps.Dependency;
updateProperty = function(value) {
property = value;
// Whenever you change value of the property, call changed() function:
_dep.changed();
};
Template.asdf.value = function() {
// Within reactive function, call depend() to rerun the function
// each time the value is changed:
_dep.depend();
return value;
};
How about a different and in my opinion simpler solution - using a local collection for your data.
I am not sure exactly why do you keep that sort of data into an array, but if it is because you only need it on the client then you can instead create a local collection and have all the reactivity benefits without writing all that code for making the array reactive. The data stored in a local collection is never sent to the server, so no communication or storage overhead.
You'd do it like that:
Sidelist = new Meteor.Collection(null);
[EDIT] Put the above line in your client-side-only part of the code.
Notice the null parameter. This will give you a collection that is only stored on the client and is a regular Meteor reactive source. Then you go about using it in your code and html just as you would a normal collection.
Hope that helps.
I have the following html that is bound to an object containing id and status. I want to translate status values into a specific color (hence the converter function convertStatus). I can see the converter work on the first binding, but if I change status in the binding list I do not see any UI update nor do I see convertStatus being subsequently called. My other issue is trying to bind the id property of the first span does not seem to work as expected (perhaps it is not possible to set this value via binding...)
HTML:
<span data-win-bind="id: id">person</span>
<span data-win-bind="textContent: status converter.convertStatus"></span>
Javascript (I have tried using to modify the status value):
// persons === WinJS.Binding.List
// updateStatus is a function that is called as a result of status changing in the system
function updateStatus(data) {
persons.forEach(function(value, index, array) {
if(value.id === data.id) {
value.status = data.status;
persons.notifyMutated(index);
}
}, this);
}
I have seen notifyMutated(index) work for values that are not using a converter.
Updating with github project
Public repo for sample (not-working) - this is a really basic app that has a listview with a set of default data and a function that is executed when the item is clicked. The function attempts to randomize one of the bound fields of the item and call notifyMutated(...) on the list to trigger a visual updated. Even with defining the WinJS.Binding.List({ binding: true }); I do not see updates unless I force it via notifyReload(), which produces a reload-flicker on the listview element.
To answer your two questions:
1) Why can't I set id through binding?
This is deliberately prevented. The WinJS binding system uses the ID to track the element that it's binding to (to avoid leaking DOM elements through dangling bindings). As such, it has to be able to control the id for bound templates.
2) Why isn't the converter firing more than once?
The Binding.List will tell the listview about changes in the contents of the list (items added, removed, or moved around) but it's the responsibility of the individual items to notify the listview about changes in their contents.
You need to have a data object that's bindable. There are a couple of options:
Call WinJS.Binding.as on the elements as you add them to the collection
Turn on binding mode on the Binding.List
The latter is probably easier. Basically, when you create your Binding.List, do this:
var list = new WinJS.Binding.List({binding: true});
That way the List will call binding.as on everything in the list, and things should start updating.
I've found that if I doing the following, I will see updates to the UI post-binding:
var list = new WinJS.Binding.List({binding: true});
var item = WinJS.Binding.as({
firstName: "Billy",
lastName: "Bob"
});
list.push(item);
Later in the application, you can change some values like so:
item.firstName = "Bobby";
item.lastName = "Joe";
...and you will see the changes in the UI
Here's a link on MSDN for more information:
MSDN - WinJS.Binding.as
Regarding setting the value of id.
I found that I was able to set the value of the name attribute, for a <button>.
I had been trying to set id, but that wouldn't work.
HTH
optimizeBindingReferences property
Determines whether or not binding should automatically set the ID of an element. This property should be set to true in apps that use Windows Library for JavaScript (WinJS) binding.
WinJS.Binding.optimizeBindingReferences = true;
source: http://msdn.microsoft.com/en-us/library/windows/apps/jj215606.aspx