how can I call a function in the viewModel from within a ko.binding?
I want clicking on an editorable area to also show the selected text in a select-box.
I have also run into some other problems:
Full-text doesn´t show at until user has chosen from "select".
Editing the text in the full-text inline editor cause the outline editor to collapse.
Editing the header cause the observable to include all HTML-code for edited header.
In the binding: "tinymceInstance.remove()" returns an error message. Commenting it out however causes the desired highlighting function, but inline editor cannot be accessed.
Highlighting does not occur as wanted.
Here´s the html:
<select size="2" style="width: 170px;" data-bind="options: Textbatches, optionsCaption: 'Choose...', optionsText: 'TextbatchTitle', value: SelectedText, click: $root.showIt2Me"></select>
<div>
<fieldset>
<legend>Textbatches:<img id="btnMetatoggle" class="ui-icon ui-icon-arrow-4-diag" style="display: inline-block" /></legend>
<div id="details" class="textbatchdetails" data-bind="with: SelectedText">
<div class="editor" data-bind="tinymce: TextbatchText, tinymceOptions: { selector: 'div.editor', inline: false }"></div>
</div>
</fieldset>
</div>
<div id="full-text">
<fieldset>
<legend >Full text:</legend>
<div>
<!-- ko with: $root.SelectedText -->
<!-- ko foreach: $root.Textbatches() -->
<div data-bind="attr: {'id': 'Text' + TextbatchId}, event:{ click: $root.clickedThis }, css: { 'inverted-text': TextbatchId === $root.SelectedText().TextbatchId}">
<h2 class="editableArea" data-bind="tinymce: TextbatchTitle, tinymceOptions: { selector: 'h2.editableArea', inline: true }"></h2>
<div class="editableArea" data-bind="tinymce: TextbatchText, tinymceOptions: { selector: 'div.editableArea', inline: true }"></div>
</div>
<!-- /ko -->
<!-- /ko -->
</div>
</fieldset>
</div>
See Fiddle: http://jsfiddle.net/x8581f1y/23/
Any help highly appreciated.
Thanx in advance!
The parameters you have for your binding handlers are incorrect. You're misusing one of the parameters which is likely causing issues down the line. For both init() and update(), the parameters are:
element
valueAccessor
allBindings
viewModel
bindingContext
Since you ultimately want a reference to the view model, there's no need to use the binding context, you have a direct reference to the view model right in the fourth parameter.
function (element, valueAccessor, allBindings, viewModel, bindingContext) {
...
viewModel.someFunction(someArgs); // call directly on the view model
}
As for your other issues, I don't know much about tiny mce but you're making some assumptions that are invalid. There is no id on the elements so it will always be undefined in your handler. The id gets assigned after tiny mce is initialized on the element. But since you just need to access the reference to the instance, all you need use is the element itself, not the id.
ko.utils.domNodeDisposal.addDisposeCallback(element, function (element) {
var tinymceInstance = $(element).tinymce(); // get the instance
if (tinymceInstance) {
tinymceInstance.remove();
}
});
Related
I have implemented a knockout foreach binding, with multiple templates in the same page, one of the example is given here, what I am interested is in finding out when a block finishes rendering, I have tried afterRender and afterAdd, but I guess it runs for each element, and not after the whole loop is finished.
<ul data-bind="foreach: {data: Contacts, afterAdd: myPostProcessingLogic}">
<li>
<div class="list_container gray_bg mrgT3px">
<div class="list_contact_icon"></div>
<div class="contact_name"><span data-bind="text: first_name"></span> <span data-bind="text: last_name"></span></div>
<div class="contact_number"><span data-bind="text: value"></span></div>
<div class="callsms_container">
<a href="#notification-box" class="notifcation-window">
<div class="hover_btn tooltip_call">
<div class="hover_call_icon"></div>
<span>Call</span></div>
</a>
<a class="sendsms" href="#sendsms" rel="#sendsms">
<div class="hover_btn tooltip_sms">
<div class="hover_sms_icon"></div>
<span>SMS</span></div>
</a>
<a href="#">
<div class="hover_more_btn"></div>
</a>
</div>
<!-- close callsms container -->
<div id="notification-box" class="notification-popup">
<img class="btn_close" src="images/box_cross.png" /> <img class="centeralign" src="images/notification_call.png" /> <span>Calling... +44 7401 287366</span> </div>
<!-- close notification box -->
<!-- close list gray bg -->
<div class="tooltip_description" style="display:none" id="disp"> asdsadaasdsad </div>
</div>
</li>
</ul>
I am interested in finding out just the success callback, when a loop finishes rendering.
here is my afterAdd function, which basically attaches some jQuery events, and nothing much.
myPostProcessingLogic = function(elements) {
$(function(){
$(".list_container_callog").hover(function(){
$(".callsms_container", this).stop().animate({left:"0px"},{queue:false,duration:800});
}, function() {
$(".callsms_container", this).stop().animate({left:"-98%"},{queue:false,duration:800});
});
});
}
thanks in advance, and tell me there is a success callback :)
You have the afterRender callback in knockout.js:
foreach: { data: myItems, afterRender: renderedHandler }
Here's documentation.
Inside your handler check whether the length of the rendered collection is equal to the length of the items collection. If not don't execute the full rendered logic that you intend to use.
renderedHandler: function (elements, data) {
if ($('#containerId').children().length === this.myItems().length) {
// Only now execute handler
}
}
Try wrapping the ul with
<div data-bind='template: {afterRender: myPostProcessingLogic }'>
It will only work the first time everything within the template is rendered. But you will only get the one call to myPostProcessingLogic. Here's a fiddle
<div data-bind='template: {afterRender: myPostProcessingLogic }'>
<ul data-bind="foreach: Contacts">
<li>
<div class="list_container gray_bg mrgT3px">
<div class="list_contact_icon"></div>
<div class="contact_name"><span data-bind="text: first_name"></span> <span data-bind="text: last_name"></span></div>
<div class="contact_number"><span data-bind="text: value"></span></div>
<div class="callsms_container">
<a href="#notification-box" class="notifcation-window">
<div class="hover_btn tooltip_call">
<div class="hover_call_icon"></div>
<span>Call</span></div>
</a>
<a class="sendsms" href="#sendsms" rel="#sendsms">
<div class="hover_btn tooltip_sms">
<div class="hover_sms_icon"></div>
<span>SMS</span></div>
</a>
<a href="#">
<div class="hover_more_btn"></div>
</a>
</div>
<!-- close callsms container -->
<div id="notification-box" class="notification-popup">
<img class="btn_close" src="images/box_cross.png" /> <img class="centeralign" src="images/notification_call.png" /> <span>Calling... +44 7401 287366</span> </div>
<!-- close notification box -->
<!-- close list gray bg -->
<div class="tooltip_description" style="display:none" id="disp"> asdsadaasdsad </div>
</div>
</li>
</ul>
</div>
Chuck Schneider's answer above is the best.
I had to use containerless control as the foreach is on a tbody element:
<!-- ko template: {afterRender: SetupCheckboxes } -->
<tbody data-bind="foreach: selectedItems" id="gridBody">
<tr>
<td>
<input type="checkbox" />
</td>
</tr>
</tbody>
<!-- /ko -->
Just wrap the foreach into another foreach loop using Knockout's container less method like this:
<!-- ko foreach:{data: Contacts, afterRender: myPostProcessingLogic }-->
<ul data-bind="foreach: $data}">
<li>
<div class="list_container gray_bg mrgT3px">
<div class="list_contact_icon"></div>
<div class="contact_name"><span data-bind="text: first_name"></span> <span data-bind="text: last_name"></span></div>
<div class="contact_number"><span data-bind="text: value"></span></div>
<div class="callsms_container">
<a href="#notification-box" class="notifcation-window">
<div class="hover_btn tooltip_call">
<div class="hover_call_icon"></div>
<span>Call</span></div>
</a>
<a class="sendsms" href="#sendsms" rel="#sendsms">
<div class="hover_btn tooltip_sms">
<div class="hover_sms_icon"></div>
<span>SMS</span></div>
</a>
<a href="#">
<div class="hover_more_btn"></div>
</a>
</div>
<!-- close callsms container -->
<div id="notification-box" class="notification-popup">
<img class="btn_close" src="images/box_cross.png" /> <img class="centeralign" src="images/notification_call.png" /> <span>Calling... +44 7401 287366</span> </div>
<!-- close notification box -->
<!-- close list gray bg -->
<div class="tooltip_description" style="display:none" id="disp"> asdsadaasdsad </div>
</div>
</li>
</ul>
<!-- /ko -->
The solution above works great. Additionally, if you need to use the foreach "as" option you can do it as so:
data-bind="foreach: { data: myItems, afterRender: renderedHandlet, as: 'myItem'}">
In version 3.5 Knockout provides events to notify when the contents of a node have been bound
HTML
<div data-bind="childrenComplete: bindingComplete">...</div>
JavaScript
function bindingComplete(){
...
}
If you an create event binding expression at a point in the DOM that encapsulates all your child data binding expression, then it's tantamount to a page binding complete event
reference
https://knockoutjs.com/documentation/binding-lifecycle-events.html
I have just recently made a pull request with knockout for them to add two events to define in the binding, unwrap, then call in the correct spots before rendering the items and after all items have rendered. I haven't heard anything back from them but this does exactly what you want to do but you don't have to write hacky code to get it to work. I'm surprised that nobody has made this request before. I used these callbacks that I added to the source to destroy and reinitialize a knockout bound jquery datatable. This was the simplest solution. I have seen many attempts online that try and do it differently but this is the simplest solution.
Pull request: --> pr 1856
ko.bindingHandlers.DataTablesForEach = {
init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var nodes = Array.prototype.slice.call(element.childNodes, 0);
ko.utils.arrayForEach(nodes, function(node) {
if (node && node.nodeType !== 1) {
node.parentNode.removeChild(node);
}
});
return ko.bindingHandlers.foreach.init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
},
update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var value = ko.unwrap(valueAccessor()),
key = "DataTablesForEach_Initialized";
var newValue = function() {
return {
data: value.data || value,
beforeRenderAll: function(el, index, data) {
if (ko.utils.domData.get(element, key)) {
$(element).closest('table').DataTable().destroy();
}
},
afterRenderAll: function(el, index, data) {
$(element).closest('table').DataTable(value.options);
}
};
};
ko.bindingHandlers.foreach.update(element, newValue, allBindingsAccessor, viewModel, bindingContext);
//if we have not previously marked this as initialized and there is currently items in the array, then cache on the element that it has been initialized
if (!ko.utils.domData.get(element, key) && (value.data || value.length)) {
ko.utils.domData.set(element, key, true);
}
return {
controlsDescendantBindings: true
};
}
};
Knockout Datatables JSFiddle
Try afterRenderAll callback in knockout.js:
foreach: { data: myItems, afterRenderAll: myPostProcessingLogic }
I am having some trouble displaying a clicked items hidden elements.
Find below my clicked events code.
'click .stylishTiles'(event, instance) {
event.preventDefault();
var selectedTile = this._id;
Session.set("selectedTile_id2", selectedTile);
$("#hidden").show(300);
$(".selected").addClass('show');
},
'click .show'(event, instance) {
event.preventDefault();
$("#hidden").hide(300);
$(".stylishTiles").removeClass('show');
}
The Session.set("selectedTile_id2", selectedTile) in the clicked event is passed on to the helper via Session.get('selectedTile_id2').
Find below the aimed helper code:
'selectedTile': function () {
var selectedTileId = this._id;
var selectedTile_id2 = Session.get('selectedTile_id2');
if(selectedTileId == selectedTile_id2){
return "selected"
}
}
And find below the code for the targeted template:
<template name="invoicesV2C">
{{#each pendingInvoicesV2C}}
<div class="well well-sm stylishTiles {{selectedTile}}">
<div id ="invoiceAmount"> KES: {{formatCurrency recipientAmount}} </div>
<div id="hidden"> tel: {{recipientNumber}} <br> purpose: {{invoicePurpose}} </div>
</div>
{{/each}}
</template>
The {{#each pendingInvoicesV2C}} in the template correctly generates several items, while correctly hiding their hidden elements by default due to the CSS code
#hidden{
display: none;
}
The much desired effect is that I when I click on any item, its hidden elements, being: <div id="hidden"> tel: {{recipientNumber}} <br> purpose: {{invoicePurpose}} </div> should show, hence
$("#hidden").show(300);
$(".selected").addClass('show');
And in reverse, whenever I click on an item already showing its elements,again being: <div id="hidden"> tel: {{recipientNumber}} <br> purpose: {{invoicePurpose}} </div> it should be hidden.
Currently, regardless of the item I click on, only the first element in the list of items will show/hide its elements.
Kindly help me understand how I can get a clicked element to show its hidden details?
You have numerous errors along with really twisted logic.
Let me help you to simplify that:
CSS:
We're using class instead of id
.hidden {
display: none;
}
Template:
<template name="invoicesV2C">
{{#each pendingInvoicesV2C}}
<div class="well well-sm stylishTiles">
<div class="invoiceAmount">
KES: {{formatCurrency recipientAmount}}
</div>
<div class="toggled hidden">
tel: {{recipientNumber}}
<br>
purpose: {{invoicePurpose}}
</div>
</div>
{{/each}}
</template>
Template Code:
Template.invoicesV2C.events({
'click .stylishTiles'(event) {
event.preventDefault();
const $e = $(event.target).closest('.stylishTiles');
$e.find('.toggled').toggle(300);
}
});
That's all. You don't even need helper and using Session at all.
Here is tiny fiddle, representing how it works: https://jsfiddle.net/iStyx/ff6hvorz/
I am using knockoutjs and in my Template i have a foreach loop that will display the data it grabs from the JSON data i want to set the focus to the first input on the screen it works perfectly when there is just on input on the screen but when it gets to the second screen there are two in puts it only sets focus to the last input on the screen
here is my HTML:
<div id="section">
<span data-bind="template: { name: 'screenTemplate', foreach: screens, as: 'screen'}"></span>
<script type="text/html" id="screenTemplate">
<!-- ko if: showFlds -->
<!-- ko if: showNote -->
<span data-bind="template: { name: 'fldTemplate', foreach: flds}"></span>
<!--/ko-->
<!--/ko-->
</script>
<script type="text/html" id="fldTemplate">
<form>
<span class="text" data-bind="text: fieldName"></span>
<br/>
<input type="text" class="inputVal" data-bind="valueUpdate: 'afterkeydown', value: inputValue, disable: (inputValue() == expectedValue()), visible:(inBool() != false)" />
<br/>
</form>
</script>
<div data-bind="visible:screens()[cScreen()].rdtNote() === true">
<h2 style="color: red"><span data-bind="text: rdtNotification()[0].description()"></span></h2>
<button data-bind="click: makeHidden">Acknowledged</button>
</div>
As shown the hasFocus is on the input in the field Template
I want to know if there is a way I can make it set focus on the first input and then move to the next input once that input is correct
If any more information is needed please ask I will do my best to provide
P.s
Any answer to this must also work in IE6 as it will eventually be running on a IE6 scanning gun
Current Progress:
I have used jQuery
$(document).ready(function(){
$('form').find('input[type=text],textarea,select').filter(':input:enabled:visible:first').focus();
})
But when using this it does not automatically select the next input when the screen changes is there a way to get that to work ?
SOLVED MY PROBLEM :
$(document).ready(function() {
$('form').find('input[type=text],textarea,select').filter(':input:enabled:visible:first').focus();
});
$(document).keyup(function(e){
$('form').find('input[type=text],textarea,select').filter(':input:enabled:visible:first').focus();
});
var e = $.Event("keyup", { keyCode: 13}); //"keydown" if that's what you're doing
$("body").trigger(e);
Using this I am able to set the focus to the first visible enabled input and then using jQuery to trigger the Enter Key key up I was able to make sure that the next input was always focused
Thanks #neps for the push in the right direction
Working Plunker
i'm migrating one of my older jquery plugins from DOM jungle to this fancy mvvm framework knockout.
Which technique would i use to properly display a popup container? I ahve to populate it 'by call' since i get a json feed every time.
I tried an approach using the with binding, but it still attempts to populate the partial at its first runtime.
<!-- ko with: daySubmitFormViewModel -->
<div class="ec-consulation-lightbox">
<form id="cForm" class="form-container">
// Some bindings here.
</form>
</div>
<!-- /ko with: -->
It can be done without custom binding as well. Example is below
<div class="modalWindowBackground" data-bind="visible: popupDialog" >
<div class="modalWindow" data-bind="with:popupDialog">
<div class="content">
<h2 data-bind="text: title"></h2>
<p>
<span data-bind="text: message"></span>
</p>
<div class="buttonSpace">
<input type="button" class="closeButton" data-bind="value: closeButtonText, click: $root.hidePopupDialog" />
</div>
</div>
</div>
</div>
Viewmodel code:
self.showAlert = function (title, message, closeButtonText) {
self.popupDialog({ title: title, message: message, closeButtonText: closeButtonText });
};
self.hidePopupDialog = function () {
self.popupDialog(null);
};
//Code which opens a popup
self.remove = function () {
.... some code ...
if (someCondition) {
self.showAlert('SomeTitle', 'Message', 'OK');
return;
}
.... some code ...
};
Create a custom binding, have its open / close function trigger on a observable.
I've done a custom binding for jQuery Dialog that uses this approuch in combination with KO
templates.
<div id="dialog" data-bind="dialog: { autoOpen: false, modal: true, title: dialogTitle }, template: { name: 'dialog-template', data: dialogItem, 'if': dialogItem }, openDialog: dialogItem"></div>
You can find my binding here along with some others
https://github.com/AndersMalmgren/Knockout.Bindings
Live demo http://jsfiddle.net/H8xWY/102/
https://github.com/One-com/knockout-popupTemplate
That pretty much does what you ask for. It's deeply configurable, and under steady development (we use it in our web applications ourselves).
Disclaimer: I'm a One.com developer. I am also the person who originated the above mentioned lib.
I have nested loops with Knockout. I would like to refer to something in a parent "scope". If you see below I always want to refer to the same parent/grandparent regardless how deep I nest the loops. I have seen the "with" binding, not sure it will help me. Is there any way I can create an alias to a particular scope, so further down in the nested loop I can refer to this alias and still be able to refer to the scope of the current loop also?
<!-- Somewhere up there is the "scope" I want to capture -->
<!-- ko foreach: getPages() -->
<span data-bind="text: pageName" ></span>
<button data-bind="click: $parents[1].myFunction()" >Press me</button>
<!-- ko foreach: categories -->
<span data-bind="text: categoryName" ></span>
<button data-bind="click: $parents[2].myFunction()" >Press me</button>
<!-- ko foreach: questions -->
<span data-bind="text: questionText" ></span>
<button data-bind="click: $parents[3].myFunction()" >Press me</button>
<!-- /ko -->
<!-- /ko -->
<!-- /ko -->
The foreach binding supports as aliases as does the (custom) withProperties binding.
<!-- ko withProperties: { book: $root.getBook() } -->
<!-- ko foreach: {data: book.pages, as: 'page'} -->
<span data-bind="text: page.pageName" ></span>
<button data-bind="click: book.bookClicked" >Press me</button>
<!-- ko foreach: {data: page.categories, as: 'category'} -->
<span data-bind="text: category.categoryName" ></span>
<button data-bind="click: page.pageClicked" >Press me</button>
<!-- etc -->
<!-- /ko -->
<!-- /ko -->
<!-- /ko -->
None of my declarative bindings directly use $parent.
The problem with the #user2864740 answer is that it doesn't work out of the box jsFiddle.
The first issue:
The binding 'withProperties' cannot be used with virtual elements
To fix that simply add the following code:
ko.virtualElements.allowedBindings.withProperties = true;
After that, you'll get another exception:
Unable to parse bindings. Message: ReferenceError: 'book' is
undefined; Bindings value: foreach: {data: book.pages, as: 'page'}
Which indicates that the withProperties isn't working at all - it hasn't created the book property in the binding context for its child bindings as you might expect.
Below is the fully working and reusable custom binding (jsFiddle):
ko.bindingHandlers.withProperties = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
// Make a modified binding context, with a extra properties, and apply it to descendant elements
var value = ko.utils.unwrapObservable(valueAccessor()),
innerBindingContext = bindingContext.extend(value);
ko.applyBindingsToDescendants(innerBindingContext, element);
// Also tell KO *not* to bind the descendants itself, otherwise they will be bound twice
return { controlsDescendantBindings: true };
}
};
ko.virtualElements.allowedBindings.withProperties = true;
I think this will help you
Calling a function in a parent's scope in nested view model
and the jsfiddle demo
jsfiddle