I'm about to build a simple "mortgage calculator" where a user is to adjust some sliders OR edit values in input fields in order to calculate some final value based on the provided data.
Schematically it will look something like this:
Slider1 - Input1
Slider2a - Input2a
Slider2b - Input2b
The idea is that the value of the input must be reflected in the slider, and vice versa. In addition, the values and limits of slider 2a/2b and input 2a/2b depend on each other, according to some simple rule.
It has to be done in Dojo, which I've never used before, and, even though Dojo has quite good documentation, it is a little overwhelming, so I'd appreciate if someone could point me in the right direction.
First of all, here is my solution working at jsFiddle: http://jsfiddle.net/phusick/HCx3w/
You can use dojo/aspect, dojo/topic and dojo/Stateful and directly connect those widgets to each other in various ways. You will probably end up with a tightly coupled set of widgets, i.e. those widgets will know about each other, even if there is no reason a particular widget should have any knowledge about the fact its value is being synchronized with another widget.
Contrary to the aforementioned you can apply loose coupling principle, which will allow you to synchronize any number of widgets without any mutual references among them. Here is my solution:
Obtain references to widgets and couple them into sets (arrays):
var slider1 = registry.byId("slider1");
var slider2 = registry.byId("slider2");
var spinner1 = registry.byId("spinner1");
var spinner2 = registry.byId("spinner2");
var set1 = [slider1, spinner1];
var set2 = [slider2, spinner2];
synchronize function:
var synchronize = function(/*Array*/ widgets, /*String*/ topicName) {
var synchronized = function() {
var count = 0;
array.forEach(widgets, function(widget) {
if(widget.get("synchronized") === true) { count++}
});
return (count == widgets.length);
}
array.forEach(widgets, function(w) {
w.set("synchronized", false);
// register onchange handler for each widget in the set
w.on("change", function(value) {
array.forEach(widgets, function(widget) {
if(this !== widget) {
widget.set("value", value);
widget.set("synchronized", true);
}
}, this);
// needed to publish topic just once per value change across all the widgets in the set
if(synchronized()) {
array.forEach(widgets, function(widget) {
widget.set("synchronized", false);
});
// publish topic if any
if(topicName) { topic.publish(topicName, value)};
}
});
});
}
Register sets of widgets to synchronize via sychronize function:
synchronize(set1, "value1-changed"); // synchronize and publish topic when value changes
synchronize(set2); // just synchronize
Subscribe to the topic you registered above:
topic.subscribe("value1-changed", function(value) {
console.log("value1-changed", value);
// here you can change value and limits of of `set2` widgets
});
dojo. Stateful is your friend... http://dojotoolkit.org/reference-guide/1.7/dojo/Stateful.html
Have you tried dojo.connect. This can be used method chaining. So when the event is fired in control multiple methods can be invoked. Beside this there is publish\subscribe mechanism in dojo. In pub\sum model you can write method to subscribe for simple message strings. When some method published that string, than subscriber method will be invoked.
Related
In my project I need to save the data to .txt or .xml or .json file. I could not find any answer from vis.js website/issues blog. It might be simple, do not know. Really helpful if anyone help me out with example code. Thank you so much in advance.
function saveData(data,callback) {
data.id = document.getElementById('node-id').value;
data.label = document.getElementById('node-label').value;
clearPopUp();
callback(data);
}
If I understand you correctly, you are looking for a way to save data and options of a graph. In my graph editor adaptation for TiddlyWiki Classic I use the following method to extract data (the full implementation can be found in the repo, see config.macros.graph.saveDataAndOptions, here's a simplified one):
config.macros.graph.saveDataAndOptions = function(network,newOptions) {
newOptions = newOptions || {};
// get nodes and edges
var nodes = network.body.data.nodes._data; // contains id, label, x,y, custom per-node options and doesn't contain options from options.nodes; presumably contains option values set when network was created, not current ones (it is so for x,y)
// no suitable getter unfortunately
var edges = network.body.data.edges._data; // map; for edges to/from? certain node use network.getConnectedNodes(id)
// network.body.data.edges._data is a hash of { id: , from: , to: }
// get node positions, options
var positions = network.getPositions(), // map
options = // get options stored previously
// merge newOptions into options
for(var nodeId in nodes) {
// nodes[nodeId].x is the initial value, positions[nodeId].x is the current one
if(positions[nodeId]) { // undefined for hidden
nodes[nodeId].x = positions[nodeId].x;
nodes[nodeId].y = positions[nodeId].y;
}
storedNode = copyObjectProperties(nodes[nodeId]);
storedNodes.push(storedNode);
}
//# do whatever you need with storedNodes, edges and options
// (pack them with JSON.stringify, store to a file etc)
};
However, while this works ok for storing data, this only helps to save options passed for storing explicitly which can be not very nice for some cases. I use this method in manipulation helpers and on dragEnd (network.on("dragEnd",this.saveToTiddlerAfterDragging), config.macros.graph.saveToTiddlerAfterDragging = function(stuff) { config.macros.graph.saveDataAndOptions(this,{ physics: false }); };). I haven't recieved any better suggestions, though.
If you need to get data and options reactively and setting such helper to handle certain edit events can't solve your problem, then I suggest wrapping nodes, edges and options as vis.DataSet and save those when needed. This is related too.
To answer the question about events/other ways to use such methods. Here's how I use them:
I save data after drag&drop moving of nodes, this is done using an event handler. Namely, I introduced
config.macros.graph.saveToTiddlerAfterDragging = function(stuff) {
config.macros.graph.saveDataAndOptions(this,{ physics: false });
};
(when drag&drop is used, physics should be switched off, otherwise coordinates won't be preserved anyway) and then I use
network.on("dragEnd",this.saveToTiddlerAfterDragging);
so that changes are saved.
As for saving after adding/editing a node/edge, I apply saving not by an event (although it's nice thinking, and you should try events of DataSet, since there's no special graph events for that). What I do is I add an elaborated hijack to the manipulation methods. Take a look at the source I've linked after the
var mSettings = options.manipulation;
line: for each manipulation method, like options.manipulation.addNode I hijack it so that its callback is hijacked to call config.macros.graph.saveDataAndOptions in the end. Here's a simplified version of what I'm doing:
var nonSaving_addNode = options.manipulation.addNode;
options.manipulation.addNode = function(data,callback) {
// hijack callback to add saving
arguments[1] = function() {
callback.apply(this,arguments); // preserve initial action
config.macros.graph.saveDataAndOptions(network); // add saving
};
nonSaving_addNode.apply(this,arguments);
}
The thing is, addNode is actually called when the add node button is clicked; though, I'm using a customized one to create a popup and apply changes once user is happy with the label they chose.
This is my first pass at this task i have. I need to update my UI based on the field. The field can be of different types. Here I am just checking for a memo or boolean type.
// UI Field Rule set.
var UIFieldRules = {
isMemo: function() {
return this.DataType === DataTypeKVP("Memo");
},
isBoolean: function() {
return this.DataType === DataTypeKVP("Boolean");
},
MapToList: function() {
if (UIFieldRules.isMemo.call(this) || UIFieldRules.isBoolean.call(this)) {
console.log("memo or bool");
console.log(UIFieldRules.isMemo.call(this));
console.log(this);
MAPTOLIST_SELECTOR.prop('disabled', true);
return;
} else {
MAPTOLIST_SELECTOR.prop('disabled', false);
console.log("UI field rules found memo");
}
}
};
I then call this object upon loading all the fields.
UIFieldRules.MapToList.call(field);
This works fine and satisfied the task, but now i need to apply more rules to the fields. (stop me if you heard this one before)
How can I get this set where i can just add a rule to a collection and have them all applied dynamically in javascript?
Update provide example:
function MapToList(field){
isBoolean:function(){}
isMemo : function(){}
execute : function(){
if (UIFieldRules.isMemo.call(this) || UIFieldRules.isBoolean.call(this)) {
console.log("memo or bool");
console.log(UIFieldRules.isMemo.call(this));
console.log(this);
MAPTOLIST_SELECTOR.prop('disabled', true);
return;
} else {
MAPTOLIST_SELECTOR.prop('disabled', false);
console.log("UI field rules found memo");
}
}
}
Then if i want to create more rules (which I do) should I create another object like the one above? Is there a best practice way of doing this in JS?
var rules = [];
rules.push(new MapToList(field));
rules.push(new RegExEnabled(field));
$.each(rules,function(item){
item.execute();
});
Your example approach is exactly fine. Create multiple objects that all implement the same interface, put them in a list, and then call a common method on each of them:
var rules = [MapToList, RegExEnabled];
rules.forEach(function(item){
item.execute(field);
});
However, you might want to notice that you typically you don't need a constructor + new if your object is not stateful or does not have any parameterisation, a simple object literal is enough.
And similarly, if your shared interface boils down to a single execute method, what you actually want is not a list of objects but just a list of functions you can call. It's not Java :-)
Suppose I am working with a directive that is given a date in form of a unix timestamp via two-way binding, but also offers a calendar widget to change the selection.
The calendar widget works with a date object, and I am unable to change the input data format and I do not want to rework the calendar to support unix timestamp. Also this is just an example and the question is about general way of working with circular watchers.
The scope would look like this:
scope.selectedUnixTimestamp; // this comes from the outside
scope.selectedDate;
scope.$watch('selectedUnixTimestamp', function(newV, oldV) {
$scope.selectedDate = new Date(newV*1000);
});
scope.$watch('selectedDate', function(newV, oldV) {
$scope.selectedUnixTimestamp = Math.floor(newV.getTime()/1000 + 0.000001);
});
My question is: what do I do in order to avoid extra calls to $watch callbacks? Obviously if I choose a new date, the flow will be following:
Watcher #2 is called - it modifies selectedUnixTimestamp
Watcher #1 is called - it modifies selectedDate
Watcher #2 is called again (new object reference) - it modifies selectedUnixTimestamp
But I don't want any of those calls besides the first one. How do can I achieve it?
Obviously one way would be to do something like:
scope.selectedUnixTimestamp;
scope.selectedDate;
var surpressWatch1 = false;
var surpressWatch2 = false;
scope.$watch('selectedUnixTimestamp', function(newV, oldV) {
if(surpressWatch1) { surpressWatch1 = false; return; }
$scope.selectedDate = new Date(newV*1000);
surpressWatch2 = true;
});
scope.$watch('selectedDate', function(newV, oldV) {
if(surpressWatch2) { surpressWatch2 = false; return; }
$scope.selectedUnixTimestamp = Math.floor(newV.getTime()/1000 + 0.000001);
surpressWatch1 = true;
});
But it quickly becomes a hell to maintain a code like that.
Another way would be to do something like:
scope.selectedUnixTimestamp;
scope.selectedDate;
scope.$watch('selectedUnixTimestamp', function(newV, oldV) {
if(newV*1000 === scope.selectedDate.getTime()) { return; }
$scope.selectedDate = new Date(newV*1000);
});
scope.$watch('selectedDate', function(newV, oldV) {
if(scope.selectedUnixTimestamp*1000 === newV.getTime()) { return; }
$scope.selectedUnixTimestamp = Math.floor(newV.getTime()/1000 + 0.000001);
});
But it might be very costful if the data transformation is more complicated than * 1000
Another way would be to watch on primitive value instead of a date object:
scope.$watch('selectedDate.getTime()', function(newV, oldV) {
But this only works with this particular example and does not solve the general issue
How to work with circular watches? I guess answer is, try not to do it.
You can try this, although I am sure there are better solutions to your example.
Use only one watch function:
You can use a function as first parameter to the watch. This function will be called until the value it returns settles (is the same as last time). You can hence create a $watch like this:
$scope.$watch(function() {
return {
timestamp: scope.selectedUnixTimestamp,
date: scope.selectedDate
}
}, function(newVal, oldVal) {
// Note that newVal and oldVal here is on the form of the object you return in the watch function, and hence have properties: timestamp and date.
// You can compare newVal.date to oldVal.date (same with timestamp) to see which one has actually changed if you need to do that.
}
true); // You need a deep watch (the true param) to watch the properties on the object
The Angular framework is built on the following assumption:
The true and trustable value of something, ready to be synchronized with a REST service for example, exists once in the model.
Keeping this in mind, you never write circular watchers.
And in case you have two different ways to alter a model value, you would write directives requiring ngModelController instance and providing the right formatter and parser functions.
i have a subgrid on a custom entity form where i am showing related records for Case Entity. I want to restrict user to select only one record. How can i achieve this using javascript in crm 2011
Sometimes unsupported should be supported!!! Especially when one needs to go the whole distance to implement such trivial UI requests.
The Subgird has all these nice methods that you can use that for some reason Microsoft insist on not exposing as SDK. That’s silly.
I would also look for a javascript solution. Here is some pseudo code that can help you with the task. (not tested but it should put you on the right track)
The code creates a simple wrapper on the internal crm grid control and utilizes its methods.
function xGrid(sId) {
var o = this;
o.Dom = document.getElementById(sId);
if (!o.Dom)
return alret("this subgrid: " + sId + " is not on the form!");
o.Grid = o.Dom.contorl;
o.GetSelectedIds = function () {
return o.Grid && o.Grid.get_selectedIds();
}
o.AddOnSelectionChange = function (fCallback) {
o.Grid && o.Grid.add_onSelectionChange(fCallback);
return o;
}
}
You can create the xGrid when the page loads i.e.
function OnCrmPageLoad() {
window.MyGrid = new xGrid("SubGrid_Test");
MyGrid.AddOnSelectionChange(SubGridTestChanged);
}
And call the function bellow then the selection changes
function SubGridTestChanged() {
if (MyGrid.GetSelectedIds().length > 1)
alert("You’re only allowed to pick 1 record at a time");
}
A supported way to implement this check is to create a synchrnous plugin on the associate/disassociate message that will check if more than one record is associated and throw and exception, in order to display a warning to the user to select only one record.
Ember.LinkView, the the view class behind the handlebars {{linkTo}} helper is now public in Ember 1.0 RC2. I want to extend it so I can create a custom view without having an extra nested tag for linkTo.
For example:
App.MyLinkView = Ember.LinkView.extend({
namedRoute: 'another'
});
then
{{#view App.MyLinkView}}LinkView to another route{{/view}}
Looked through the source a bit without much luck, as it constantly throws an error.
Here's a jsfiddle with the standard {{linkTo}} working, and the LinkView attempt commented out so it doesn't throw an error.
http://jsfiddle.net/HgmEy/1/
Edit:
Here is a more realistic example of why you would want to do this:
http://jsfiddle.net/HgmEy/3/
The desired functionality is working here using a regular view, however using a LinkView would be preferred to avoid the extra dom element.
LinkView is intended to be created via a helper, which passes (and provides default values for) some options.
Your error occurs when trying to determine whether your custom class is active or not. You'll need to do one of the following
pass or supply the expected default options when using your App.MyLinkView
override the active function and implement what you need
just pass options to {{linkTo}} for the behavior you want
reopen Ember.LinkView to provide the app-wide behavior you'd want
I needed to do this to override Ember.LinkView's call to transitionTo in order to come up with a solution for jQuery animations between transitions. It seems to me that there are a couple of viable ways to override LinkView. The second one I succeeded with is Trek's last option, and is simpler. This is method #2:
Method #2
{{#linkTo 'items' this eventName="myEvent"}} Link to {{title}} {{/linkTo}}
Now rewrite the app-wide LinkView:
Ember.LinkView.reopen({
// this handler is still called on click, but
// if we specify eventName in our template,
// we can call that handler only when we need to,
// or not at all
click: function (e) {
var evtName = this.get('eventName');
// transitionTo was already invoked by
// this._invoke() if evtName was `click`
if (evtName === 'click') return;
e.preventDefault();
// do some stuff here
var args = [].slice.call(arguments);
this.trigger.apply(this, [evtName].concat(args));
}
});
Method #1
The first method I came up with was to extend Ember.LinkView and create a custom Handlebars helper. The Ember source was really handy here for reading, but I had to override a private method, so I don't think this is really ideal. Here's the implementation. Keep in mind I was trying to control when the View triggered a transitionTo:
{{#appLinkTo 'items' this}} Link to {{title}} {{/appLinkTo}}
Now code it up!
App.LinkView = Ember.LinkView.extend({
// always called after this.invoke(),
// which calls transitionTo
click: function (e) {
e.preventDefault();
},
// already bound to the click event by this.init().
// our click handler above always gets called after this one
_invoke: function (event) {
// we need to simulate the old _invoke if we
// want to override its call to transitionTo
//
// https://github.com/emberjs/ember.js/blob/v1.0.0/packages/ember-routing/lib/helpers/link_to.js#L297
var isSimpleClick = Ember.ViewUtils.isSimpleClick;
if (!isSimpleClick(event)) { return true; }
event.preventDefault();
if (this.bubbles === false) { event.stopPropagation(); }
if (this.get('_isDisabled')) { return false; }
if (this.get('loading')) {
Ember.Logger.warn("This link-to is in an inactive loading state because at least one of its parameters presently has a null/undefined value, or the provided route name is invalid.");
return false;
}
// now we can start messing around
var routeArgs = this.get('routeArgs');
// routeArgs seems to have format ['routeName', models for dynamic segments]
this.set('routeArgs', ['group', routeArgs[1]]);
// if we use:
this.get('controller').send('someAction', routeArgs);
// the controller can do in its `someAction` handler:
// `this.transitionToRoute.apply(this, routeArgs);`
}
});
// besides the naming, this is verbatim from the end of:
// https://github.com/emberjs/ember.js/blob/v1.0.0/packages/ember-routing/lib/helpers/link_to.js
Ember.Handlebars.registerHelper('app-link-to', function(name) {
var options = [].slice.call(arguments, -1)[0],
params = [].slice.call(arguments, 0, -1),
hash = options.hash;
hash.disabledBinding = hash.disabledWhen;
hash.parameters = {
context: this,
options: options,
params: params
};
return Ember.Handlebars.helpers.view.call(this, App.LinkView, options);
});
Ember.Handlebars.registerHelper('appLinkTo', Ember.Handlebars.helpers['app-link-to']);
Method #3
If you want the best of both, you could combine both methods and extend Ember.LinkView, create a custom Handlebars helper, and use custom event names to signify which actions you want to take. That way, overriding Ember.LinkView, and overwriting _invoke aren't necessary.
Good luck!