Lazy loading with Linqjs and Knockout - javascript

Is it possible to consume the Enumerable at the knockout databinding stage?
I have only got it to work if I first do ToArray, which will consume the Enumerable
http://jsfiddle.net/xZmWx/

You could write your own foreach variant to work with Enumerables.
ko.bindingHandlers['foreach2'] = {
makeTemplateValueAccessor: function(valueAccessor) {
return function() {
var bindingValue = ko.utils.unwrapObservable(valueAccessor());
if (bindingValue instanceof Enumerable) {
bindingValue = bindingValue.ToArray();
}
// If bindingValue is the array, just pass it on its own
if ((!bindingValue) || typeof bindingValue.length == "number")
return { 'foreach': bindingValue, 'templateEngine': ko.nativeTemplateEngine.instance };
// If bindingValue.data is the array, preserve all relevant options
return {
'foreach': bindingValue['data'],
'includeDestroyed': bindingValue['includeDestroyed'],
'afterAdd': bindingValue['afterAdd'],
'beforeRemove': bindingValue['beforeRemove'],
'afterRender': bindingValue['afterRender'],
'templateEngine': ko.nativeTemplateEngine.instance
};
};
},
'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
return ko.bindingHandlers['template']['init'](element, ko.bindingHandlers['foreach2'].makeTemplateValueAccessor(valueAccessor));
},
'update': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
return ko.bindingHandlers['template']['update'](element, ko.bindingHandlers['foreach2'].makeTemplateValueAccessor(valueAccessor), allBindingsAccessor, viewModel, bindingContext);
}
};
http://jsfiddle.net/madcapnmckay/QeEQg/
Hope this helps.

Related

Access knockout bound value directly from target element in event handler

I am struggling a bit with implementing a custom tooltip binding that is invoked with a mouseover, so I need an event handler to display the tooltip. Now, the content of the tooltip can be either a static string, or an observable with content that can change - but the tooltip module itself is a singleton. Plus, there can be more than one tooltip elements on the page, so the tooltip show function just receives the event and the content to display. For me, the easiest way would be if I could access the actual bound value from the event target in the listener alone.
Now I know there is the ko.dataFrom function, but for the static variant, I cannot see the actual bound value anywhere (i.e. the same that is returned with valueAccessor() in the init function), just the whole viewmodel.
Here is some simplified code to illustrate it:
<span class="icon help" data-bind="tooltip: 'some static string'"></span>
(But as I said there also might be an observable behind the tooltip binding)
ko.bindingHandlers.tooltip = {
init: function (element, valueAccessor, allBindingsAccessor) {
element.addEventListener("mouseover", ko.bindingHandlers.tooltip.showTooltip);
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
element.removeEventListener("mouseover", ko.bindingHandlers.tooltip.showTooltip);
});
},
showTooltip: function(event) {
var data = ko.dataFor(this);
tooltip.show(event, data);
}
}
The problem is now that "data" only contains the whole viewmodel the view was bound to. But where is 'some static string' to be found?
I also tried with "ko.contextFor", but then $root, $data and $rawData were all the same. I am sure I overlooked something here, or is that not possible without adding the string to a data attribute or something similar, which I would like to avoid.
The fundamental issue here is that you shouldn't be trying to reuse the event handler, or at least, if you're going to, bind it.
I'd just move showTooltip into init:
ko.bindingHandlers.tooltip = {
init: function (element, valueAccessor, allBindingsAccessor) {
var showTooltip = function(event) {
tooltip.show(event, ko.unwrap(valueAccessor()));
};
element.addEventListener("mouseover", showTooltip);
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
element.removeEventListener("mouseover", showTooltip);
});
}
};
Small functions are very cheap.
Live Example (using click instead of mouseover and just appending to the document):
var tooltip = {
show: function(event, value) {
var div = document.createElement('div');
div.innerHTML = "tooltip: " + value;
document.body.appendChild(div);
}
};
ko.bindingHandlers.tooltip = {
init: function (element, valueAccessor, allBindingsAccessor) {
var showTooltip = function(event) {
tooltip.show(event, ko.unwrap(valueAccessor()));
};
element.addEventListener("click", showTooltip);
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
element.removeEventListener("click", showTooltip);
});
}
};
var vm = {
items: [
{text: "one", tooltip: "uno"},
{text: "two", tooltip: "due"},
{text: "three", tooltip: "tre"},
{text: "four", tooltip: "quattro"}
]
};
ko.applyBindings(vm, document.body);
// To prove the observables work, update one after a delay
setTimeout(function() {
vm.items[2].tooltip = "TRE";
}, 100);
<div data-bind="foreach: items">
<div data-bind="text: text, tooltip: tooltip"></div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
Alternately, use bind:
ko.bindingHandlers.tooltip = {
init: function (element, valueAccessor, allBindingsAccessor) {
var boundHandler = ko.bindingHandlers.tooltip.showTooltip.bind(
null,
valueAccessor
);
element.addEventListener("click", boundHandler);
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
element.removeEventListener("click", boundHandler);
});
},
showTooltip: function(valueAccessor, event) {
tooltip.show(event, ko.unwrap(valueAccessor()));
}
};
var tooltip = {
show: function(event, value) {
var div = document.createElement('div');
div.innerHTML = "tooltip: " + value;
document.body.appendChild(div);
}
};
ko.bindingHandlers.tooltip = {
init: function (element, valueAccessor, allBindingsAccessor) {
var boundHandler = ko.bindingHandlers.tooltip.showTooltip.bind(
null,
valueAccessor
);
element.addEventListener("click", boundHandler);
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
element.removeEventListener("click", boundHandler);
});
},
showTooltip: function(valueAccessor, event) {
tooltip.show(event, ko.unwrap(valueAccessor()));
}
};
var vm = {
items: [
{text: "one", tooltip: "uno"},
{text: "two", tooltip: "due"},
{text: "three", tooltip: "tre"},
{text: "four", tooltip: "quattro"}
]
};
ko.applyBindings(vm, document.body);
// To prove the observables work, update one after a delay
setTimeout(function() {
vm.items[2].tooltip = "TRE";
}, 100);
<div data-bind="foreach: items">
<div data-bind="text: text, tooltip: tooltip"></div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
Note how the bound value will be in front of the event argument.

Animating knockout template views

Is there a way to have transitional animations between "page" changes while using knockout for changing templates? I'm looking for something similar to Knockback-Navigators. I cant figure out a way to do this? Is there a package I can use to make this easier? Here is a JSFiddle with the same type of binding my project uses. And a sample of my javascript here:
var View = function (title, templateName, data) {
var self = this;
this.title = title;
this.templateName = templateName;
this.data = data;
this.url = ko.observable('#' + templateName);
};
var test1View = {
test: ko.observable("TEST1")
};
var test2View = {
test: ko.observable("TEST2")
};
var viewModel = {
views: ko.observableArray([
new View("Test 1", "test1", test1View),
new View("Test 2", "test2", test2View)]),
selectedView: ko.observable(),
}
//Apply knockout bindings
ko.applyBindings(viewModel);
//Set up sammy url routes
Sammy(function () {
//Handles only groups basically
this.get('#:view', function () {
var viewName = this.params.view;
var tempViewObj = ko.utils.arrayFirst(viewModel.views(), function (item) {
return item.templateName === viewName;
});
//set selectedView
viewModel.selectedView(tempViewObj);
});
}).run('#test1');
There are plenty of ways of doing this, here is one
ko.bindingHandlers.withFade = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var $element = $(element);
var observable = valueAccessor();
var wrapper = ko.observable(observable());
observable.subscribe(function(value) {
var current = wrapper();
fadeIn = function() {
wrapper(value);
$element.fadeIn();
};
if(current) {
$element.fadeOut(fadeIn);
} else {
$element.hide();
fadeIn();
}
});
ko.applyBindingsToNode(element, { with: wrapper }, bindingContext);
return { controlsDescendantBindings: true };
}
};
http://jsfiddle.net/7E84t/19/
You can abstract the effect like this
ko.transitions = {
fade: {
out: function(element, callback) {
element.fadeOut(callback);
},
in: function(element) {
element.fadeIn();
}
},
slide: {
out: function(element, callback) {
element.slideUp(callback);
},
in: function(element) {
element.slideDown();
}
}
};
html
<div data-bind="withFade: { data: selectedView, transition: ko.transitions.slide }">
http://jsfiddle.net/7E84t/23/

knockout and Select2 get selected object

I'm working on a project where im using .net web api, knockout and in this example, the jquery plugin select2.
What im trying to do is to set some field values after the change of the selection.
The select2 control list is loaded after ajax call and the objects contain more data than just id and text.
How can i get the rest of the data, so i can fill the other inputs with it?
Shortly, im trying to update a viewmodel after the change of the selection (but i get the data when this plugin makes the ajax call).
Here is a sample data that the selected object should contain:
{
"Customer":{
"ID":13,
"No":"0000012",
"Name":"SomeName",
"Address":"SomeAddress",
"ZipCode":"324231",
"City":"SimCity",
"Region":"SS",
"Phone":"458447478",
"CustomerLocations":[]
}
}
Here is where i am for now:
Sample html:
<input type="hidden" data-bind="select2: { optionsText: 'Name', optionsValue: 'ID', sourceUrl: apiUrls.customer, model: $root.customer() }, value: CustomerID" id="CustomerName" name="CustomerName" />
<input type="text" data-bind="........" />
<input type="text" data-bind="........" />
etc...
and this is the custom binding:
ko.bindingHandlers.select2 = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var obj = valueAccessor(),
allBindings = allBindingsAccessor();
var optionsText = ko.utils.unwrapObservable(obj.optionsText);
var optionsValue = ko.utils.unwrapObservable(obj.optionsValue);
var sourceUrl = ko.utils.unwrapObservable(obj.sourceUrl);
var selectedID = ko.utils.unwrapObservable(allBindings.value);
var model = ko.utils.unwrapObservable(obj.model);//the object that i need to get/set
$(element).select2({
placeholder: "Choose...",
minimumInputLength: 3,
initSelection: function (element, callback) {
if (model && selectedID !== "") {
callback({ id: model[optionsValue](), text: model[optionsText]() });
}
},
ajax: {
quietMillis: 500,
url: sourceUrl,
dataType: 'json',
data: function (search, page) {
return {
page: page,
search: search
};
},
results: function (data) {
var result = [];
$.each( data.list, function( key, value ) {
result.push({ id: value[optionsValue], text: value[optionsText] });
});
var more = data.paging.currentPage < data.paging.pageCount;
return { results: result, more: more };
}
}
});
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).select2('destroy');
});
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var obj = valueAccessor(),
allBindings = allBindingsAccessor();
var model = ko.utils.unwrapObservable(obj.model);//the object that i need to get/set
var selectedID = ko.utils.unwrapObservable(allBindings.value);
$(element).select2('val', selectedID);
$(element).on("change", function (e) {
//...
});
}
};
Getting the selected id or text is not a problem, but how not to loose the rest of the information after the ajax call?
Where can i set/get this object so i can have all the data that it contains?
Thank you
When you build a object literal for your results, added the full object as a "data" property.
result.push({ id: value[optionsValue], text: value[optionsText], data: value });
Then handle the select2-selected event thrown by select2. The event object this should contain your object literal as the choice property.
$element.on('select2-selected', function(eventData) {
if ( eventData.choice ) {
// item selected
var dataObj = eventData.choice.data;
var selectedId = eventData.choice.id;
} else {
// item cleared
}
});
For select2 v4, you can use $(elem).select2('data') to get the selected objects.
$('selected2-enabled-elements').on('change', function(e) {
console.log($(this).select2('data'));
});
Example: https://jsfiddle.net/calvin/p1nzrxuy/
For select2 versions before v4.0.0 you can do:
.on("select2-selecting", function (e) {
console.log(e.object);
})
From v4.0.0 on and upwards the following should work:
.on("select2-selecting", function (e) {
$(this).select2('data')
})

registerEventHandler not triggered before subscription

In my code I first use registerEventHandler to use the jquery currency plugin so it maps "€ 123,00" to 123,00.
ko.bindingHandlers.currency = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
ko.bindingHandlers.value.init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).currency && $(element).currency("destroy");
});
ko.utils.registerEventHandler(element, "change", function () {
var observable = valueAccessor();
observable($(element).asNumber({ region: 'nl-BE' }));
});
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
/*snip snip*/
}
};
Now when elsewhere I subscribe to the change event as follows, I still receive the string rather then the number:
self.initial.subscribe(function(newValue) {
console.log("newValue", newValue);
}, null, "change");
When setting a breakpoint at the registerEventHandler for "change", I notice it gets hit after my subscription.
What am I missing?
You can do this with a computed either in a binding handler or as a extender
As binding handler
http://jsfiddle.net/mJ7Vw/
ko.bindingHandlers.currency = {
init: function(element, accessor) {
var computed = ko.computed(function() {
return ko.unwrap(accessor()) +" do custom formating";
});
ko.applyBindingsToNode(element, { text: computed });
}
};
As extender
http://jsfiddle.net/mJ7Vw/1/
ko.extenders.currency = function(observable) {
return ko.computed(function() {
return observable() +" do custom formating";
});
};

Knockout - error with binding to template wrapper

I am trying to create a knockout template for my text count tracker so that I can repeat this for each text area on the page I am creating.
I have created two fiddles that demonstrate different approaches I have been taking:
1. http://jsfiddle.net/ajdisalvo/9W84x/3/ and
2. http://jsfiddle.net/ajdisalvo/ex2uJ/
In both, I am have created a bindinghandler that is a wrapper for the template that I am attempting to bind to.
In the first approach I create a separate viewModel, attach it to the existing main viewModel and it seems to behave until it attempts to update the value 'successes' in the main viewModel. At this point, it sets the value to [Object object]. Although, this fiddle seems to be really close to working, I am concerned that I could be creating a recursive loop that might be an inherent flaw to this approach.
(function (ko) {
ko.bindingHandlers.templateWrapper = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, context) {
var existingOptions = ko.utils.unwrapObservable(valueAccessor());
viewModel[existingOptions.itemName] = new subModel(existingOptions.dataInput(), existingOptions.maxLength);
viewModel[existingOptions.itemName].itemText.subscribe(function (value) {
existingOptions.dataInput(value);
});
newOptions = ko.bindingHandlers.templateWrapper.buildTemplateOptions(existingOptions, viewModel[existingOptions.itemName]);
//return ko.bindingHandlers.template.init(element, function () { return newOptions; }, allBindingsAccessor, viewModel, context);
return { controlsDescendantBindings: true };
},
'update': function (element, valueAccessor, allBindingsAccessor, viewModel, context) {
newOptions = ko.bindingHandlers.templateWrapper.buildTemplateOptions(valueAccessor(), viewModel[valueAccessor().itemName]);
ko.bindingHandlers.template.update(element, function () { return newOptions; }, allBindingsAccessor, viewModel, context);
},
//extend the existing options by adding a name
buildTemplateOptions: function (options, vm) {
return { data: vm, name: ko.bindingHandlers.templateWrapper.templateName };
},
templateName: "textTrackerTemplate"
};
var viewModel = {
successes: ko.observable("Test input")
};
var subModel = function (value, maxLength) {
var self = this;
self.itemText = ko.observable(value);
self.maxLength = ko.observable(maxLength);
self.currentLength = ko.observable(self.itemText().length);
self.remainingLength = ko.computed(function () { return self.maxLength() - self.currentLength() });
self.hasFocus = ko.observable(false);
self.updateRemaining = function (data, event) {
var e = $(event.target || event.srcElement);
self.currentLength(e.val().length);
if (self.currentLength() > self.maxLength()) {
e.val(e.val().substr(0, self.maxLength()));
self.ItemText(e.val());
self.currentLength(self.itemText().length);
}
};
};
ko.applyBindings(viewModel);
} (ko));
In my second approach, I am using an extender in my bindinghandler to add the necessary properties to populate my count tracker, but it seems that the objects created in the extender are not instantiated at the time the knockout renders the page.
(function (ko) {
ko.bindingHandlers.templateWrapper = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, context) {
var existingOptions = ko.utils.unwrapObservable(valueAccessor());
newOptions = ko.bindingHandlers.templateWrapper.buildTemplateOptions(existingOptions, valueAccessor);
return ko.bindingHandlers.template.init(element, function () { return newOptions; }, allBindingsAccessor, viewModel, context);
},
'update': function (element, valueAccessor, allBindingsAccessor, viewModel, context) {
var existingOptions = ko.utils.unwrapObservable(valueAccessor());
newOptions = ko.bindingHandlers.templateWrapper.buildTemplateOptions(existingOptions, valueAccessor);
ko.bindingHandlers.template.update(element, function () { return newOptions; }, allBindingsAccessor, viewModel, context);
},
buildTemplateOptions: function (options, valueAccessor) {
valueAccessor().dataInput = valueAccessor().dataInput.extend({ textTracker: options.maxLength });
return { data: valueAccessor, name: ko.bindingHandlers.templateWrapper.templateName };
},
templateName: "textTrackerTemplate"
};
var viewModel = {
successes: ko.observable("Test input")
};
ko.extenders.textTracker = function (target, maxLength) {
target.itemText = ko.computed({
read: function () {
return target();
},
write: function (value) {
target(value);
}
});
target.maxLength = ko.observable(maxLength);
target.currentLength = ko.observable(target.itemText().length);
target.remainingLength = ko.computed(function () {
return target.maxLength() - target.currentLength(); });
target.hasFocus = ko.observable(false);
target.updateRemaining = function (data, event) {
var e = $(event.target || event.srcElement);
target.currentLength(e.val().length);
if (target.currentLength() > target.maxLength()) {
e.val(e.val().substr(0, target.maxLength()));
target(e.val());
target.currentLength(target.itemText().length);
}
};
return target;
};
ko.applyBindings(viewModel);
} (ko));
Thanks in advance for any help/suggestions that you might provide..
I figured it out. I knew there were issues with my bindinghandler. I was actually making my binding more complicated that it needed to be. I just needed to extend the the value accessor only in the init method and then pass it on to the template init binding method. In the update statement, I just needed to use the existing value accessor, since it had already been extended. Here is the new binding handler:
ko.bindingHandlers.textTracker = {
init: function (element, valueAccessor, allBindings, viewModel, context) {
var options = ko.utils.unwrapObservable(allBindings());
var observable = valueAccessor();
var newValueAccessor = observable.extend({ textTracker: options });
return ko.bindingHandlers.template.init(element,
function () {
return { data: newValueAccessor,
name: ko.bindingHandlers.textTracker.templateName
};
},
allBindings, viewModel, context);
},
update: function (element, valueAccessor, allBindings, viewModel, context) {
return ko.bindingHandlers.template.update(element,
function () {
return { data: valueAccessor,
name: ko.bindingHandlers.textTracker.templateName
};
},
allBindings, viewModel, context);
},
templateName: "textTrackerTemplate"
};
Special thanks to RPNiemeyer's answer to 19732545 which helped me re-look at what I was doing which helped helped simplify the problem.

Categories