I've been creating some tests for my Dojo widget to check that boolean flags are being set correctly. However, I've found that since I have altered my constructor to pass in an object, previously ran tests seem to affect the subsequent tests.
I've tried destroying the widget in the tear down methods, but whatever I seem to do, the value persists.
Can anyone suggest what I might be doing wrong?
My widget code:
var showControls = true;
return declare([WidgetBase, TemplatedMixin, _WidgetsInTemplateMixin], {
templateString: template,
constructor: function (params) {
this.showControls = (typeof params.showControls === "undefined" || typeof params.showControls != "boolean") ? this.showControls : params.showControls;
}
});
My test class is:
var customWidget;
doh.register("Test controls", [
{
name: "Test controls are not visible when set in constructor",
runTest: function() {
var params = { showControls: false };
customWidget = new CustomWidget(params);
doh.assertFalse(customWidget.getShowControls());
}
},
{
name: "Test controls are visible when set in constructor with string instead of boolean",
runTest: function() {
var params = { showControls: "wrong" };
customWidget= new CustomWidget(params);
doh.assertTrue(customWidget.getShowControls());
}
}
]);
So, the first test passes, as showControls is set to false, however the 2nd test attempts to create a new instance, in which the constructor will check that the value is a boolean. When I debug this however, it thinks showControls starts out as 'false', not true.
Any clues?!
Thanks
dijit/_WidgetBase has a mechanism of mixing in constructor parameters and it is the reason of the behavior you described. One of the possible solutions is to define a custom setter as a method _set[PropertyName]Attr:
var defaults = {
showControls: true
}
var CustomWidget = declare([_WidgetBase, _TemplatedMixin], {
templateString: "<div></div>",
constructor: function(params) {
declare.safeMixin(this, defaults);
},
_setShowControlsAttr: function(value) {
this.showControls = (typeof value === "boolean") ? value : defaults.showControls;
}
});
See it in action: http://jsfiddle.net/phusick/wrBHp/
I would suggest you list any members of your widget, if you do not, things passed into the constructor may not be properly recognised. It seems you want to use this.showControls, so you should have a showControls member. like this :
return declare([WidgetBase, TemplatedMixin, _WidgetsInTemplateMixin], {
templateString: template,
showControls: true, // default value
constructor: function (params) {
// no further action, params are automatically mixed in already
}
});
Be careful when listing members, dojo interprets arrays and objects as class members (like static in Java, AFAIK they're attached to the prototype) so if you want each object to have e.g., a separate array of values, list it as null and initialize in your constructor.
Related
Coming from an old-school way of handling my defaults, I am trying to wrap my head around how to allow default values within an object literal for a constructor, while calling the constructor with a partial object literal. Note: I am still not a fan of "class" syntax for constructors, but I intend to use it, so please indulge me while I learn!
Code speaks louder than words. Here was my first attempt:
class ProxyManager {
constructor(
proxy = {
proxyID: 12345,
proxyHost: '',
proxyPort: 8080,
proxySSL: false,
proxyPath: ''
}
) {
this.proxy = proxy;
}
getProxy() {
return this.proxy;
}
}
const foo = new ProxyManager();
foo.getProxy(); // returns the full default object defined in the constructor
const bar = new ProxyManager({proxyID: 67890});
foo.getProxy(); // returns {proxyID: 67890}
None of this is a surprise; you can see right from the syntax that as long as something is passed in as the first paramater, it becomes "proxy". So, while I wasn't expecting it to work, it was my starting point.
Out of familiarity, I fell back to an older-school way of doing it, which is something like this:
class ProxyManager {
constructor(proxy) {
this.proxy = proxy || {};
const defaults = {
proxyID: 12345,
proxyHost: '',
proxyPort: 8080,
proxySSL: false,
proxyPath: ''
}
// swap in more thorough sanity-check if needed
if (Object.prototype.toString.call(this.proxy) === '[object Object]') {
this.proxy = Object.assign(defaults, this.proxy)
}
}
getProxy() {
return this.proxy;
}
}
const foo = new ProxyManager();
foo.getProxy(); // returns the full default object defined in the constructor
const bar = new ProxyManager({proxyID: 67890, proxyPort: 16500});
foo.getProxy(); // returns full object with updated proxyID and proxyPort
It works, and I guess I could move on... but I am interested to see if there is a pattern I'm missing. I did some searching, and kept coming up short.
Based on comments, option #2 in the original question isn't such a bad solution. It's probably better than the following. But just to be thorough, here's what we arrived at:
class ProxyManager {
constructor(
{
proxyID = 12345,
proxyHost = '',
proxyPort = 8080,
proxySSL = false,
proxyPath = ''
} = {}
) {
this.proxy = {
proxyID,
proxyHost,
proxyPort,
proxySSL,
proxyPath
}
}
getProxy() {
return this.proxy;
}
}
It's possibly harder to grok for older-school JS users like me, and it is certainly needlessly repetitive for the end result. But it fits my original question criteria, so here it is as an answer. As a side benefit, you don't have to do a bunch of sanity-checks to ensure the defaults work. If the constructor is called with invalid paramaters (for example, passing in a simple string or int), the defaults just apply. On the other hand, that's also the drawback... there's no warning that you have used the constructor incorrectly.
(I'm sorry if my question title isn't very good, I couldn't think of a better one. Feel free to suggest better options.)
I'm trying to create a reusable "property grid" in Angular, where one can bind an object to the grid, but in such a way that presentation of the object can be customized somewhat.
This is what the directive template looks like (the form-element isn't important to my question, so I'll leave it out):
<div ng-repeat="prop in propertyData({object: propertyObject})">
<div ng-switch on="prop.type">
<div ng-switch-when="text">
<form-element type="text"
label-translation-key="{{prop.key}}"
label="{{prop.key}}"
name="{{prop.key}}"
model="propertyObject[prop.key]"
focus-events-enabled="false">
</form-element>
</div>
</div>
</div>
and, the directive code:
angular.module("app.shared").directive('propertyGrid', ['$log', function($log) {
return {
restrict: 'E',
scope: {
propertyObject: '=',
propertyData: '&'
}
templateUrl: 'views/propertyGrid.html'
};
}]);
Here's an example usage:
<property-grid edit-mode="true"
property-object="selectedSite"
property-data="getSitePropertyData(object)">
</property-grid>
And the getSitePropertyData() function that goes with it:
var lastSite;
var lastSitePropertyData;
$scope.getSitePropertyData = function (site) {
if (site == undefined) return null;
if (site == lastSite)
return lastSitePropertyData;
lastSite = site;
lastSitePropertyData = [
{key:"SiteName", value:site.SiteName, editable: true, type:"text"},
//{key:"Company.CompanyName", value:site.Company.CompanyName, editable: false, type:"text"},
{key:"Address1", value:site.Address1, editable: true, type:"text"},
{key:"Address2", value:site.Address2, editable: true, type:"text"},
{key:"PostalCode", value:site.PostalCode, editable: true, type:"text"},
{key:"City", value:site.City, editable: true, type:"text"},
{key:"Country", value:site.Country, editable: true, type:"text"},
{key:"ContactName", value:site.ContactName, editable: true, type:"text"},
{key: "ContactEmail", value: site.ContactEmail, editable: true, type:"email"},
{key: "ContactPhone", value: site.ContactPhone, editable: true, type:"text"},
{key: "Info", value: site.Info, editable: true, type:"text"}
];
return lastSitePropertyData;
};
The reason I'm going through such a "property data" function and not just binding directly to properties on the object is that I need to control the order of the properties, as well as whether they should even be shown to the user at all, and also what kind of property it is (text, email, number, date, etc.) for the sake of presentation.
At first, as you can tell from the value property remnant in the getSitePropertyData() function, I first tried providing the values directly from this function, but that wouldn't bind to the object, so changes either in the object or form the property grid didn't sync back and forth. Next up, then, was using the key idea, which lets me do this: propertyObject[prop.key]—which works great for direct properties, but as you can see, I had to comment out the "Company" field, because it's a property of a property, and propertyObject["a.b"] doesn't work.
I'm struggling to figure out what to do here. I need the bindings to work, and I need to be able to use arbitrarily deep properties in my bindings. I know this kind of thing is theoretically possible; I've seen it done for instance in UI Grid, but such projects have so much code that I would probably spend days finding out how they do it.
Am I getting close, or am I going about this all wrong?
You want to run an arbitrary Angular expression on an object. That is exactly the purpose of $parse (ref). This service can well... parse an Angular expression and return a getter and setter. The following example is an oversimplified implementation of your formElement directive, demonstrating the use of $parse:
app.directive('formElement', ['$parse', function($parse) {
return {
restrict: 'E',
scope: {
label: '#',
name: '#',
rootObj: '=',
path: '#'
},
template:
'<label>{{ label }}</label>' +
'<input type="text" ng-model="data.model" />',
link: function(scope) {
var getModel = $parse(scope.path);
var setModel = getModel.assign;
scope.data = {};
Object.defineProperty(scope.data, 'model', {
get: function() {
return getModel(scope.rootObj);
},
set: function(value) {
setModel(scope.rootObj, value);
}
});
}
};
}]);
I have altered slightly the way the directive is used, hopefully without changing the semantics:
<form-element type="text"
label-translation-key="{{prop.key}}"
label="{{prop.key}}"
name="{{prop.key}}"
root-obj="propertyObject"
path="{{prop.key}}"
focus-events-enabled="false">
Where root-obj is the top of the model and path is the expression to reach the actual data.
As you can see, $parse creates the getter and setter function for the given expression, for any root object. In the model.data property, you apply the accessor functions created by $parse to the root object. The entire Object.defineProperty construct could be replaced by watches, but that would only add overhead to the digest cycle.
Here is a working fiddle: https://jsfiddle.net/zb6cfk6y/
By the way, another (more terse and idiomatic) way to write the get/set would be:
Object.defineProperty(scope.data, 'model', {
get: getModel.bind(null, scope.rootObj),
set: setModel.bind(null, scope.rootObj)
});
If you are using lodash you can use the _.get function to achieve this.
You can store _.get in the controller of your property-grid and then use
model="get(propertyObject,prop.key)"
in your template. If you need this functionality in multiple places in your application (and not just in property-grid) you could write a filter for this.
The problem with this is that you can't bind your model this way and thus you can't edit the values. You can use the _.set function and an object with a getter and a setter to make this work.
vm.modelize = function(obj, path) {
return {
get value(){return _.get(obj, path)},
set value(v){_.set(obj, path,v)}
};
}
You can then use the function in the template:
<div ng-repeat="prop in propertyData({object: propertyObject})">
<input type="text"
ng-model="ctrl.modelize(propertyObject,prop.key).value"
ng-model-options="{ getterSetter: true }"></input>
</div>
For a reduced example see this Plunker.
If you don't use lodash you can use this simplified version of the _.get function that I extracted from lodash.
function getPath(object, path) {
path = path.split('.')
var index = 0
var length = path.length;
while (object != null && index < length) {
object = object[path[index++]];
}
return (index && index == length) ? object : undefined;
}
This function makes sure that you won't get any Cannot read property 'foo' of undefined errors. This is useful especially if you have long chains of properties where there might be an undefined value. If you want to be able to use more advanced paths (like foo.bar[0]) you have to use the full _.get function from lodash.
And here is a simplified version of _.set also extracted form lodash:
function setPath(object, path, value) {
path = path.split(".")
var index = -1,
length = path.length,
lastIndex = length - 1,
nested = object;
while (nested != null && ++index < length) {
var key = path[index]
if (typeof nested === 'object') {
var newValue = value;
if (index != lastIndex) {
var objValue = nested[key];
newValue = objValue == null ?
((typeof path[index + 1] === 'number') ? [] : {}) :
objValue;
}
if (!(hasOwnProperty.call(nested, key) && (nested[key] === value)) ||
(value === undefined && !(key in nested))) {
nested[key] = newValue;
}
}
nested = nested[key];
}
return object;
}
Keep in mind that these extracted functions ignore some edge cases that lodash handles. But they should work in most cases.
When you creating the lastSitePropertyData you can create the object in this way to not hardcode it
function createObject (){
for(var key in site){
lastSitePropertyData.push({key:key, value:site[key], editable: true, type:"text"});
} }
And later use function to get data something like this
function getKey(prop){
if(typeof prop.value === 'object'){
return prop.value.key; //can run loop create a go deep reccursive method - thats upto u
}
else return prop.key;
}
function getValue(prop){
if(typeof prop === 'object'){
return prop.value.value; //have tp run loop get value from deep reccursive method - thats upto u
}
else return prop.value;
}
That way can be use in html {{getKey(prop)}} and {{getValue(prop}}
For working demo please have look this link - https://jsfiddle.net/718px9c2/4/
Note: Its just idea for accessing json data in better way, I am not using angular in demo.
Another idea is to do smth like this.
If you wont to avoid making object.proto dirty (this is always good idea) just move this functionality into the other module.
(function () {
'use strict';
if (Object.hasOwnProperty('getDeep')) {
console.error('object prototype already has prop function');
return false;
}
function getDeep(propPath) {
if (!propPath || typeof propPath === 'function') {
return this;
}
var props = propPath.split('.');
var result = this;
props.forEach(function queryProp(propName) {
result = result[propName];
});
return result;
}
Object.defineProperty(Object.prototype, 'getDeep', {
value: getDeep,
writable: true,
configurable: true,
enumerable: false
});
}());
I have something similar used to show data in grids, those grids may show the same objects yet no the same columns. however I don't handle that in one go.
I have a type service where I declare my types and some default configuration
I have a grid service which generates the grid definition options according to what I specified.
In the controller I instantiate the grid using the grid service, specifying the ordering of the columns and some specific configurations, which override the default ones. The grid service itself generate appropriate configuration for filtering, ordering using the type definition of the fields.
I am having some issues trying to work out what is going ok with MVC SPA and Knockout.
When you create a new project some files are created for knockout.js as examples, but I am struggling to understand what is going on.
Primarily the issue is with the app.viewmodel.js and the function AddViewModel.
Here is some code which I will attempt to breakdown:
self.addViewModel = function (options) {
var viewItem = {},
navigator;
// Example options
//{
// name: "Home",
// bindingMemberName: "home",
// factory: HomeViewModel
//}
// Add view to AppViewModel.Views enum (for example, app.Views.Home).
self.Views[options.name] = viewItem; // Don't really get this, seems to add a blank object to app.Views.Home
// Add binding member to AppViewModel (for example, app.home);
self[options.bindingMemberName] = ko.computed(function () {
//if (self.view() !== viewItem) {
// console.log(self.view()); // returns {}
// console.log(viewItem); // returns {}
// return null; // should never hit this?
//}
return new options.factory(self, dataModel); // This adds our ViewModel to app.home, app.login, etc
});
// This checks to see if we have defined a navigatorFactory in our viewmodel (AddViewModel)
if (typeof (options.navigatorFactory) !== "undefined") {
navigator = options.navigatorFactory(self, dataModel);
} else {
navigator = function () {
console.log(viewItem);
self.view(viewItem);
};
}
// Add navigation member to AppViewModel (for example, app.NavigateToHome());
self["navigateTo" + options.name] = navigator;
};
ok, so let's start. First of all we declare 2 variables:
var viewItem = {},
navigator;
viewItem is set as a blank object and navigator is undefined.
The first thing we do, is set self.Views[options.name] to our viewItem, so in my understanding, this would mean:
self.Views.Home = {}
If we look at the declaration in app.viewmodel.js self.Views looks like this:
self.Views = {
Loading: {} // Other views are added dynamically by app.addViewModel(...).
};
So in here there is already a view called Loading. So I am confused as to what is actually happening here.
The next bit of code creates a function:
self[options.bindingMemberName] = ko.computed(function () {
return new options.factory(self, dataModel);
});
This is a lot easier to understand. It basically takes our ViewModel and adds it to a function under the name of self.home (or whatever the bindingMemberName of our ViewModel is.
This next piece is what confuses me:
if (typeof (options.navigatorFactory) !== "undefined") {
navigator = options.navigatorFactory(self, dataModel);
} else {
navigator = function () {
console.log(viewItem);
self.view(viewItem);
};
}
// Add navigation member to AppViewModel (for example, app.NavigateToHome());
self["navigateTo" + options.name] = navigator;
If I strip this down, it basically says if we define a navigatorFactory, then the navigator (which is currently undefined!) is equal to our navigatorFactory. That bit is easy.
It's the next bit I don't get.
It says, else, the navigator is a function that returns our self.view(viewItem) (remember that viewItem is just a blank object.
Then we set self["navigateTo" + options.name] = navigator.
So in english, this looks like it is saying, get our blank viewItem, assign it to self.view for every ViewModel we add. Then assign a function returning our self.view(viewItem) to our navigator variable (which is currently undefined) and assign this to our self.naviateToHome() (or whatever).
So to me, that looks like self.navigateToHome(), self.navigateToLogin(), self.navigateToTimbucktoo() would all return the same function with the same self.view.
So, can anyone explain to me what is actually happening?
Update 1
So, I have figured some things out. First things first, the navigator is setting the current view, so basically self.Views looks like this after all the models are added:
self.Views = {
Loading: { },
Home: { },
Login: { }
}
So even though self.view() returns an empty object, it isn't the same as the viewItem because it is stored with the name into self.Views.
So, the navigator is actually applying the viewItem to self.views.
I tested this out by changing the viewItem to this:
var viewItem = { options.name }
and sure enough, self.Views looked liked this:
self.Views = {
Loading: { },
Home: { name: "Home" },
Login: { name: "Login" }
}
so when we set self.view using our navigator, the function is called (app.home for example) and it runs the code to return our factory or null if it isn't the current view.
I am trying to make a preset list of options that are allowed in my object list. Here is code
var a = function(cmd, options){
var objList = [options.search ,options.demand];
if(!(options in objList)){
console.warn('Not an Allowed * in the options Property');
}
};
or should I do
var a = function(cmd, options){
var objList = [search , demand];
if(!(options in objList)){
console.warn('Not an Allowed option in the options Property');
}
};
Basically what I want to do is set that search and demand are allowed options in the options Property so later than can do
a('cmd',{
search:'',
demand:function() {
alert('Hello');
},
//warn that the next option is not allowed
quote: function() {
alert('quote of user');
}
});
If you are having trouble understanding what I am asking please ask and I will do my best to explain a bit more.
maybe writing it like so would be better?
var a = function(cmd, options){
options = {
theme: function(color) {
$('body').css('backgroundColor',color);
},
color:''
};
};
a('cmd',{
theme:'#000'//though this is not working?
});
You could check each property in options against an array of allowed options like this:
var a = function(cmd, options){
var allowedOptions = ["search", "demand"];
var hasDisallowedOptions = false;
for (option in options) {
if(allowedOptions.indexOf(option) === -1 ) {
hasDisallowedOptions = true;
break;
}
}
// if hasDisallowedOptions is true, then there is a disallowed option
};
jsfiddle with a couple test cases/examples
A one idea of passing arguments in an object is, that it allows you to choose which argument you want to use in a function, you can simply ignore extra properties in the options object. Hence you don't need to "filter" the properties of the argument either.
Let's assume you've a function like this:
var a = function (cmd, options) {
var theme = {
backgroundColor: options.bgColor,
color: options.color
}
// Do something with theme
// Notice also, that there was no use for options.extra in this function
}
Then you invoke a like this:
a('cmd', {
bgColor: '#ff0',
color: '#000',
extra: 'This is an extra property'
});
Now you can see, extra is not used in a at all, though it was a property of the anonymous object passed to a as an argument. Also all arguments passed to a are garbage collected, unless you're not going to create a closure, i.e. returning a local value or a function from a.
How do you achieve the following thing in Javascript
1) var MyObject={
2) tableView:true,
3) chartView:!(this.tableView)
4) }
The code at line number 3 is not working. Whats wrong in that line ?
What i basically want to do is set "chartView" to opposite of "tableView" whenever tableView is set from code.
Since you're in the process of creating the object, this is not bound to that object. Moreover, since you want chartView to always evaluate to the opposite of tableView, even if the latter changes further down the line, a function would be a better approach:
var MyObject = {
tableView: true,
chartView: function() {
return !this.tableView;
}
};
Now you can do:
var chartView = MyObject.chartView(); // false.
MyObject.tableView = false;
chartView = MyObject.chartView(); // true.
You can't use this to refer to an object in an object literal's properties. You can use this inside a function that is a method of that object:
var MyObject = {
tableView: true,
chartView: function () {
return !this.tableView;
}
}
Based on your requirement, this may be an answer too,
var MyObject = {
view : function(bool){
this.tableView = bool;
this.chartView = !(bool);
}
tableView: true,
chartView: false
}
MyObject.view(false)
console.log(MyObject.tableView); // Outputs false
console.log(MyObject.chartView) // Outputs true
This way you will always have opposite of tableView in chartView with a single function call.