I'm trying to respond to a change in one of the properties of the model in a widget. To be clear, when the value of the property changes, I want to run some code to react to the change. In a parent widget I have a date picker which changes the date in the model.
I cannot get the custom setter to be called _setParentPropertyAttr...
If I include this in my widget
<span data-dojo-type="dojox/mvc/Output" data-dojo-props="value: at(rel:, 'ParentProperty')">
It works nicely. Changing the date picker outputs the current value to the page. So I can supply the value property to the output widget when the date changes in the model. But what I need to do (I think) is supply a custom property with the date property in the model when the date picker changes the value.
I realise this question is a bit vague but I can't provide the code as it's proprietary.
I've tried to break the problem down by setting a property manually within my widget as:
myProperty:0,
...
constructor
...
_setMyPropertyAttr: function(value):
{
console.log("setting myproperty");
}
....
this.set('myProperty', 5);
....
but that isn't working either.
If you set a property within a widget does that not call the custom setter?
I'm struggling a bit because there aren't so many dojo examples out there any help is much appreciated.
You can bind an event to be called when a widget's property is set/update or you can even use watch to do that.
But this only works using the set function, using someWidget.someProperty = 5; wont work.
Let me show you how dojo do it. The basic about the magic setters and getters is explained here.
_set: function(/*String*/ name, /*anything*/ value){
// summary:
// Helper function to set new value for specified property, and call handlers
// registered with watch() if the value has changed.
var oldValue = this[name];
this[name] = value;
if(this._created && !isEqual(oldValue, value)){
if(this._watchCallbacks){
this._watchCallbacks(name, oldValue, value);
}
this.emit("attrmodified-" + name, {
detail: {
prevValue: oldValue,
newValue: value
}
});
}
}
This peace of code is from dijit/_WidgetBase, the _set function is what dojo calls after a set is called, and is where it finally set the property value this[name] = value; and as you can see, it emit an event that will be called attrmodified-propertyName and also call a watchCallbacks.
For example, if in some place, we have this:
on(someWidget, 'attrmodified-myproperty', function(){
console.log(someWidget.get('myProperty'));
});
and then we use:
someWidget.set('myProperty', 'Hello World!');
The event will be triggered. Note that someWidget.myProperty = 'Hello World!' wont trigger the event registration. Also note that if in our widget we define the magic setter:
_setMyPropertyAttr: function(value) {
//do something here with value
// do more or less with other logic
//but some where within this function we need to cal "_set"
this._set('myProperty', value);
}
without calling _set, the magic wont happen.
As i said at the beginning, we can also use watch:
someWidget.watch('myProperty', function(){
console.log(someWidget.get('myProperty'));
});
Note that we can register to this events or the watch function within the same widget.
Also, as a plus, the magic setter can be triggered when creating the widget with just passing the property name in the constructor object param, this work for the declarative syntax too, for example:
var someWidget = new MyWidget({
'myProperty': 'Hello World!!'
});
and
<div data-dojo-type="MyWidget" data-dojo-props="myProperty: 'Hello World!!'"></div>
both will triggered a call to the _setMyPropertyAttr if exist, or dojo will use the magic setter in the case it doesn't exist.
Hope it helps
Consider using custom setter on your widget, where you can add your custom logic.
Example of definition of custom setter on your widget:
_setOpenAttr: function(/*Boolean*/ open){
// execute some custom logic here
this._set("open", open);
}
Example of setting a property on your widget:
widgetRef.set('open', true);
Alternatively you can could consider using dojo/store/Observable.
dojo/store/Observable store wrapper that adds support for notification of data changes.
You can read more about it on the followign link:
https://dojotoolkit.org/reference-guide/1.10/dojo/store/Observable.html
If figured out the problem. If I set a watch on the model I can then check if indiviual properties have changed in the watch function. I knew it would be something simple!
Related
The Problem
I just cannot figure out the view model in NativeScript
I am having a hard time understanding how view-models work in NativeScript. I understand the high level concept - that the MVVM pattern allows us to create observable objects - and our UI is updated when values change.
Here is a simple example:
main-page.js
var createViewModel = require("./main-view-model").createViewModel;
function onNavigatingTo(args) {
var page = args.object;
page.bindingContext = createViewModel();
}
exports.onNavigatingTo = onNavigatingTo;
main-view-model.js
var Observable = require("tns-core-modules/data/observable").Observable;
function getMessage(counter) {
if (counter <= 0) {
return "Hoorraaay! You unlocked the NativeScript clicker achievement!";
} else {
return counter + " taps left";
}
}
function createViewModel() {
var viewModel = new Observable();
viewModel.counter = 42;
viewModel.message = getMessage(viewModel.counter);
viewModel.onTap = function() {
this.counter--;
this.set("message", getMessage(this.counter));
}
return viewModel;
}
exports.createViewModel = createViewModel;
I understand , some what, what is happening. But not everything.
Questions I Have ...
How would you add a new function , for instance, an email validation function? Would it go into the View Model page, or just plain Javscript page?
Let's say I added a new textfield to the UI. I have a tap function. Where does my function go?
So in this case, everything related to the UI should go in the createViewModel function? Is that correct?
I have also seen in sample apps, where the developer doesn't use view models at all - it appears he just creates it as an observable object.
Thank you for looking. I know I am close to understanding, but that bindingContext and the viewmodel has me a bit confused. [ I have read everything in NS docs ]
John
The answer is either of it should work. You may put the validation or tap function in view model or in the code behind file, it's upto you to decide which works best for you.
If you put it in the view model, you will use event binding (tap="{{ functionName }}" Or if you put it in code behind file, you will just export the function name and simply refer the function name on XML (tap="functionName").
By giving this flexibility you are allowed to separate your code, keep the files light weighted.
Hey guys I have the following function its working ok but I think it could be better.
methods: {
onFileChange(e, filedName) {
console.log(e.target.files);
console.log(filedName);
const file = e.target.files[0];
const fileToCheck=document.getElementById(filedName);
console.log(fileToCheck);
if(filedName=='thumbnail1'){
if(fileToCheck.value!=''){
this.thumbnail1 = fileToCheck;
this.thumbnail1Url= URL.createObjectURL(file);
} else {
this.thumbnail1=null;
this.thumbnail1Url=null;
}
}
if(filedName=='thumbnail2'){
if(fileToCheck.value!=''){
console.log(fileToCheck);
this.thumbnail2=fileToCheck;
this.thumbnail2Url = URL.createObjectURL(file);
} else {this.thumbnail2=fileToCheck; this.thumbnail2Url=null;}
}
},
Instead of checking the value for
if(fieldName == "something"){
this.something = URL.createObjectURL(file)
}
I would simply pass in a string of the fieldName and bind to it dynamically by just typing this.fieldName (filedName could equal thumbnail1 or thumbnail2 or chicken for all I care I just want to be able to pass in the name of the data atrribute and bind to it that way) but when ever I do this it doesn't work. Any help here would be great.
It's not completely clear to me what you want to accomplish, but I think you're asking about creating a dynamic data property for a view component. If that's the case, there are a couple of things to consider.
First, the example you cite, this.fieldName is not correct JavaScript syntax if fieldName is a string that contains a property name. The correct version is this[fieldName].
Note, though, that you can't simply define a new data property for a Vue component by setting it to a value. That's a limitation of JavaScript that's described in the Vue documentation. If data[fieldName] is an existing property that's defined in the component's data object, then you'll be okay. Even if you don't know the value of the property, you can initialize it, for example, with a value of null and then update the value in your method. Otherwise, you'll need to add the property to an existing non-root-level property as the documentation explains.
The v-model directive is limited to input event. but i want it to support jquery change events too, so that i can use jquery plugins like bootstrap-toggle without having to write separate codes to manipulate those fields.
The main challenge i'm facing is how to update the value bound to the element, on jquery change event. I tried triggering input event on change event is fired, but it didn't work.
Here is what i'm trying to achieve:
HTML:
<input id="dayparting_switch" v-observe="options.dayparting" v-model="options.dayparting" :cheked="options.dayparting" data-off="Disabled" data-on="Enabled" data-toggle="toggle" type="checkbox">
Custom directive:
Vue.directive('observe', {
bind: function(el, bind, vnode) {
$(el).is(':checkbox') ? $(el).prop('checked', !!bind.value) : $(el).val(bind.value);
$(el).change(function() {
var newVal = $(el).is(':checkbox') ? $(el).prop('checked') : $(el).val();
// Here comes problem: how to set new value to options.dayparting ?
// 1) bind.value = newVal won't trigger any update
// 2) this.dispatchEvent(new Event('input')) also doesn't work
// 3) Only quirky way of doing this is to parse bind.expression to get object and keys and then use Vue.set
var lastDot = bind.expression.lastIndexOf('.');
var object = bind.expression.substring(0, lastDot);
var key = bind.expression.substr(lastDot+1);
Vue.set(eval('vnode.context.' + object), key, newVal);
});
}
});
The above method actually worked for me, but i think it is not a perfect method. for eg, it won't work for v-observe="options[option_name]"
Is there any simple or standard method to achieve this ?
In Vue 1, it was possible to write two-way binding directives (like v-model), but in Vue 2, you do it with components.
Have a look at the Wrapper Component Example.
I'm using a dijit DropDownButton with an application I'm developing. As you know, if you click on the button once, a menu appears. Click again and it disappears. I can't seem to find this in the API documentation but is there a property I can read to tell me whether or not my DropDownButton is currently open or closed?
I'm trying to use a dojo.connect listener on the DropDownButton's OnClick event in order to perform another task, but only if the DropDownButton is clicked "closed."
THANK YOU!
Steve
I had a similar problem. I couldn't find such a property either, so I ended up adding a custom property dropDownIsOpen and overriding openDropDown() and closeDropDown() to update its value, like this:
myButton.dropDownIsOpen = false;
myButton.openDropDown = function () {
this.dropDownIsOpen = true;
this.inherited("openDropDown", arguments);
};
myButton.closeDropDown = function () {
this.dropDownIsOpen = false;
this.inherited("closeDropDown", arguments);
};
You may track it through its CSS classes. When the DropDown is open, the underlying DOM node that gets the focus (property focusNode) receives an additional class, dijitHasDropDownOpen. So, for your situation:
// assuming "d" is a dijit.DropDownButton
dojo.connect(d, 'onClick', function() {
if (dojo.hasClass(d.focusNode, 'dijitHasDropDownOpen') === false) {
performAnotherTask(); // this fires only if the menu is closed.
}
});
This example is for dojo 1.6.2, since you didn't specify your version. It can, of course, be converted easily for other versions.
I am working on an application and was doing something like this:
dojo.ready(
function(){ require['dojo/parser','dijit/registry','dojo/on'],function(.....){
//find a dijit and wrap it in event handling code.});
I was getting an error indicating that dojo was trying to register a widget with an id that was already in use. To solve the problem I entered this line of code:
//before finding the dijit destroy the existing registry.
However, logically this prevents the next line from working because now no widget exists to which I can connect an event. How can I recover the dijit ids?
The best solution is to find out why your code is trying to register a widget with an id that is already in use and change it to not to do so.
The #mschr's solution should work, but I would advise again using it, as it can break your code in many other places and you are likely to spend hours investigating strange behavior of your application.
Anyway, if you are willing to do it that way and automatically destroy widgets with the same ID, do not override registry.add() method. You could do it, but it does not mean, you should do it (especially in programming). Employ dojo/aspect instead to call a function that will destroy the widget with the same ID before registry.add() is called:
require([
"dojo/aspect",
"dijit/registry"
], function(
aspect,
registry
) {
aspect.before(registry, "add", function(widget) {
if(registry.byId(widget.id)) {
registry.byId(widget.id).destroy();
// this warning can save you hours of debugging:
console.warn("Widget with id==" + widget.id + " was destroyed to register a widget with the same id.");
}
return [widget];
});
});
I was myself curious how to accomplish #mschr solution without that override, so I created an jsFiddle to experiment: http://jsfiddle.net/phusick/feXVT/
What happens once you register a dijit is the following; it is referenced by the dijit.registry._hash:
function (widget) {
if (hash[widget.id]) {
throw new Error("Tried to register widget with id==" + widget.id + " but that id is already registered");
}
hash[widget.id] = widget;
this.length++;
}
Now, every now and then you would have a contentpane in which you would put a widget programatically (programatically, hence dojo.parser handles cpane.unload and derefences / destroys parser-instantiated widgets).
When this happens, you need to hook onto some form of 'unload', like, when your call cpane.set('content' foo) or cpane.set('href', bar). Hook is needed to destroy and unregister the instances you keep of widgets - otherwise you would have a memoryleak in your program.
Normally, once an object has no references anywhere - it will get cleaned out of memory however with complex objects such as a widget might be, 'class-variables' often have reference to something _outside _widget scope which flags the widget unsafe to delete to the garbage collector... Once you get this point, you will know to perform proper lifecycles, yet not before the concept is fully understood..
What you could do is to override the dijit.registry with your own handler and have any widgets that are doublets destroyed automatically like so:
// pull in registry in-sync and with global scoped
// accees (aka dijit.registry instead of dj_reg)
require({
async:false,
publishRequireResult:true
}, [
"dijit.registry"
], function(dj_reg) {
dijit.registry.add = function(widget) {
// lets change this bit
if (this._hash[widget.id]) {
this._hash[widget.id].destroy(); // optinally destroyRecursively
this.remove(widget.id)
}
this._hash[widget.id] = widget;
this.length++;
}
});