Knockouters,
I have come to rely on the with binding for establishing context nesting. while I like the way KO manipulates the DOM based on the state of the bound elements in some instances, sometime I just want the binding implications without the DOM removal.
Does anyone know if it is possible way to prevent the DOM manipulation on an individual element binding level?
Thanks,
Vinney
Version 2.2+ of Knockout won't clear the DOM element when with is bound initially to an object (or other truthy value). Alternatively, you can use the withlight binding that I put together some time ago. It only will bind to an object (not an observable).
ko.bindingHandlers['withlight'] = {
'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var bindingValue = valueAccessor();
if (typeof bindingValue != 'object' || bindingValue === null)
throw new Error('withlight must be used with an object');
var innerContext = bindingContext['createChildContext'](bindingValue);
ko.applyBindingsToDescendants(innerContext, element);
return { 'controlsDescendantBindings': true };
}
};
Related
Restangular offers a feature, extendModel, which lets you add functionality onto objects returned from the server. Is there any way to get these methods added to an empty / new model, that hasn't yet been saved to the server?
I wanted to do the same thing but didn't find an example. Here's how I ended up doing it:
models.factory('User', function(Restangular) {
var route = 'users';
var init = {a:1, b:2}; // custom User properties
Restangular.extendModel(route, function(model) {
// User functions
model.myfunc = function() {...}
return model;
});
var User = Restangular.all(route);
User.create = function(obj) {
// init provides default values which will be overridden by obj
return Restangular.restangularizeElement(null, _.merge({}, init, obj), route);
}
return User;
}
Some things to be aware of:
Use a function like _.merge() instead of angular.extend() because it clones the init variable rather than simply assigning its properties.
There is a known issue with Restangular 1.x that causes the Element's bound data to not be updated when you modify its properties (see #367 and related). The workaround is to call restangularizeElement() again before calling save(). However this call will always set fromServer to false which causes a POST to be sent so I wrote a wrapper function that checks if id is non-null and sets fromServer to true.
I am currently using ReSharper V8.1. I've only recently began using ReSharper and have found some interest in their LiveTemplate Macros. I've conjured up a solution to return a list of HotspotItems from a constant, similar to ReSharper's predefined macro "Comma-delimited list of values". In the method I take the constant variable of the template parameter and do a split string on them to provide a collection of HotSpotItems. Unfortunately it doesn't work if I use the macro more than one time within a template. Below is an extreme hack job showing my implementation of the method HotspotItems of IMacroImplementation.
I am hoping that someone out there may have done some work in this area and could possibly provide an example of how they've implemented IMacroImplementation which provides a list of items from a constant and also allows for multiple uses within a single template.
Thank you.
public override HotspotItems GetLookupItems(IHotspotContext context)
{
HotspotItems hotSpotItems = null;
foreach (var hotspot in context.HotspotSession.Hotspots)
{
if (hotspot.Expression != null && ((MacroCallExpressionNew)hotspot.Expression).Definition is Macros.DisplayMultipleItems)
{
//hotspot.CurrentValue
var multiItems = ((MacroCallExpressionNew) hotspot.Expression).Definition as DisplayMultipleItems;
if (!multiItems.ItemSet)
{
var expression = hotspot.Expression as MacroCallExpressionNew;
IMacroParameterValueNew baseValue = expression.Parameters[0].GetValue(context.SessionContext.Solution.GetLifetime(), context.HotspotSession);
string templateValue = baseValue.GetValue();
multiItems.ItemSet = true;
if (!string.IsNullOrEmpty(templateValue) && templateValue.Split(',').Any())
{
var lookupItems = templateValue.Split(',').Select(param => new TextLookupItem(param)).Cast<ILookupItem>().ToList();
if (hotSpotItems == null)
hotSpotItems = new HotspotItems(lookupItems);
else
{
foreach (var item in lookupItems)
{
hotSpotItems.Items.Add(item);
}
}
}
}
}
}
return hotSpotItems;
}
You should fire up dotPeek and point it to the ReSharper bin directory and take a look at ListMacroDef and ListMacroImpl, which is the implementation for the comma-delimited list macro.
The definition derives from SimpleMacroDefinition. It gets given the parameters in the call to GetPlaceholder, looks at the first and splits it by comma, returning the first item as the placeholder.
ListMacroImpl is just as simple. Its constructor has an [Optional] parameter of type MacroParameterValueCollection. This is the list of parameter values specified in the hotspot editor. You'll want to check for null and take the first parameter, which will be your delimited list. It then overrides GetLookupItems and returns HotspotItems.Empty if the parameter value is null, or parses the value and returns a list of TextLookupItem.
You don't need to look at the session and list of hotspots - that will get you all hotspots in the session, when you're only interested in the current hotspot, and ReSharper will create a new IMacroImplementation for each hotspot and give you those values in your constructor.
What would be the most efficient way to find all ExtJS components that are rendered as descendants of a given HTML Element? Note that this Element is not part of a component itself and is not managed by Ext in any way, it is just hardcoded raw HTML. And this Element may not only contain just ext components, it may have other non-ext managed html in it as well, and that html can also contain ext components. So that means that the solution must traverse all the way down the DOM, not just look at direct children.
My suggestion would be to walk the dom, checking the id of each element against Ext.getCmp, which is a hash-map lookup. You could then switch to walking through the Component methods, but I think it would be basically the same speed, and if you're already walking the dom to begin with you might as well keep at it:
var dom = ...;
var components = [];
Ext.Array.each(Ext.get(dom).query('*'), function(dom) {
var cmp = Ext.getCmp(dom.id);
if (cmp)
components.push(cmp);
});
Ext.get(dom).query('*') may do more work than you'd like, it might be more efficient to have your own walker, like this:
function walk(dom, callback) {
if (dom.nodeType === 1) {
callback(dom);
Ext.Array.each(dom.childNodes, function(child) {
walk(child, callback);
});
}
}
var dom = ...;
var components = [];
walk(dom, function(dom) {
var cmp = Ext.getCmp(dom.id);
if (cmp)
components.push(cmp);
});
All this assumes that the dom ids match the component ids, which I don't know if that is something you can rely on in future versions of Ext.
I'm using Knockout with jQuery and jQuery templates. Assume that I have a template which expects a person object
<script type="text/html" id="person_template">
<tr><td>Forename</td><td><input type="textbox" data-bind="value:FORENAME" /></td></tr>
<tr><td>Surname</td><td><input type="textbox" data-bind="value: SURNAME"/></td></tr>
</script>
Now, if I pass an object with just a FORENAME to this template, I will get an error:
SURNAME is not defined error
I tried to create a custom binding in Knockout, but the error is thrown before it even gets there.
If I fill in these empty fields before passing the object to the template, I know everything will work out, but I would like to have the solution in my template rather than in my javascript.
Does anyone know a method that might help for situations like these?
This is a bit challenging, because you are within a template. While preparing the template, KO accesses the variable (well, actually it is accessed in jQuery Templates by a function that KO built).
One option is to pass your property as a string to a custom binding and make sure that it is initialized.
It would be like:
ko.bindingHandlers.valueWithInit = {
init: function(element, valueAccessor, allBindingsAccessor, context) {
var value = valueAccessor();
if (!context[value]) {
context[value] = ko.observable();
}
var realValueAccessor = function() {
return context[value];
}
//call the real value binding
ko.bindingHandlers.value.init(element, realValueAccessor, allBindingsAccessor, context);
},
update: function (element, valueAccessor, allBindingsAccessor, context) {
var realValueAccessor = function() {
return context[valueAccessor()];
}
//call the real value binding
ko.bindingHandlers.value.update(element, realValueAccessor);
}
}
So, this would validate that your object has the field, if it does not it creates a new observable for that field. Then, it hands it off to the real value binding.
A very similar (but less verbose) alternative to this would be to have the binding ensure that the field is there and then rewrite the binding attribute to use the real value binding. Something like:
//Another option: rewrite binding after making sure that it is initialized
ko.bindingHandlers.valueWithInit = {
init: function(element, valueAccessor, allBindingsAccessor, context) {
var value = valueAccessor();
if (!context[value]) {
context[value] = ko.observable();
}
$(element).attr("data-bind", "value: " + value);
ko.applyBindings(context, element);
}
}
Both of these assume that the field that you are passing is directly off of the object that is the context of your template (so, it wouldn't work if you passed something with global scope like 'viewModel.someProperty').
Here is a working sample with both options: http://jsfiddle.net/rniemeyer/dFSeB/
I would rather not pass the field as a string, but there is not really a good way around it that I see.
You'll be better off ensuring that the object passed to the template has all the parameters set in. If they are not then you can add default values but putting all this logic in the template is going against the MVVM pattern. The templates (like Views in mvc) are not supposed to contain any logic IMO.
I have 2 main questions.
Does extending things like Object count?
What is DOM wrapping?
http://perfectionkills.com/whats-wrong-with-extending-the-dom/
After reading that article I couldn't find anything about DOM wrapping, and no specification and what exactly is and isn't DOM extension.
No, Object is specified as part of the Javascript language, while the DOM is an API only relevant in a browser environment and is used to "access and update the content, structure and style of documents" (W3C).
However, one of the reasons provided in that article arguing against the extension of DOM objects still applies to extending native types such as Object - namely the chance of collisions.
Wrapping an object refers to creating a new object that references the original, but providing additional functionality through the new, wrapper object.
For example, rather than extending a DOM Element object with a cross-browser addClass function like this:
var element = document.getElementById('someId');
element.addClass = function (className) {
...
};
You can instead define a wrapper function:
var ElementWrapper = function (element) {
this.element = element;
};
And add the function to its prototype:
ElementWrapper.prototype.addClass = function (className) {
...
};
And "wrap" elements like this:
var element = document.getElementById('someId');
var wrapped = new ElementWrapper(element);
wrapped.addClass('someClass');