Possible to bind virtual element without binding to a container? - jquery-mobile

In the current version of jQuery Mobile it seems to be necessary to keep all data-role="page" elements on one level (at least when one wants to establish links between them). Since I am using knockout to dynamically produce pages, I have the requirement to bind virtual elements directly, e.g. in
<div data-role="page" id="page1"><h1 data-bind="text: name"></h1></div>
<!-- ko text: name --><!-- /ko -->
I want to bind one model A to page1 and one model B to the virtual element (which is a template: foreach binding in the real application). More detailed fiddle: http://jsfiddle.net/timvdh/t3Cyd/3/
Is this doable somehow?

With the limited information I would say you have 3 options:
(1) Wrap your virtual element in another virtual element or div and use the with binding.
<!-- ko with: modelForPage2 -->
<!-- ko text: name --><!-- /ko -->
<!-- /ko -->
(2) Use the template binding in the virtual element and use the data property to supply a new context.
<!-- ko template: { name: 'page2_template', data: modelForPage2 } --><!-- /ko -->
(3) Create your own custom binding that supports virtual elements and apply bindings to descendants. This is just an example to lead you in the right direction:
ko.bindingHandlers.bindModel = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, context) {
var newContext = new ModelA();
ko.applyBindingsToDescendants(newContext, element);
return { controlsDescendantBindings: true };
}
};
ko.virtualElements.allowedBindings.bindModel = true;

Related

Rendering a content tag as part of a template in polymer and dart

I wish to make a generic list using polymer and dart. I am extending the UL element to do so. I want to place template variables within the content of this custom element.
<ul is="data-ul">
<li>{{item['first_name']}}</li>
</ul>
The custom element
<polymer-element name="data-ul" extends="ul">
<template repeat="{{item in items}}">
<content></content>
</template>
<script type="application/dart" src="data-ul.dart"></script>
</polymer-element>
I was expecting the template variable to be interpolated however it simply gets outputted to the DOM as is. How do I output the content tag to be rendered as a template and not just directly outputted?
Unfortunately, there are two issues here.
<content> cannot be used like this. It's a placeholder for rendering light DOM nodes at specific locations in the Shadow DOM. The first <content> that selects nodes, wins [1]. Stamping out a bunch like you're doing, while very intuitive, won't work as expected.
You're mixing the internal world of Polymer with the external world outside the element. What this really means is that bindings (e.g. {{}}) only work in the context of <polymer-element>.
One thing you can do is create a copy of the distributed light DOM children as the items property of your element. In JavaScript this looks like:
<template repeat="{{item in items}}">
<li>{{item['first_name']}}</li>
</template>
<content id="content" select="li"></content>
<script>
Polymer('data-ul', {
ready: function() {
this.items = this.$.content.getDistributedNodes();
}
});
</script>
Note: The only reason I've used <content select="li"> is to insure the element only takes in <li> nodes. If you're not worried about users using other types of elements, just use this.items = [].slice.call(this.children);.
To do that you should override the parseDeclaration method. This method is in charge of parsing/creating the needed html that will be bound. For example, let say that you have next template
<polymer-element name="data-ul" extends="ul" attributes="items">
<template>
<template repeat="{{item in items}}" ref="itemTemplate"></template> <!-- this is the replacement of content tag -->
</template>
<script type="application/dart" src="data-ul.dart"></script>
</polymer-element>
Or if you want to have some default elements:
<polymer-element name="data-ul" extends="ul" attributes="items">
<template>
<template repeat="{{item in items}}">
<!-- Def elements -->
<template bind="{{item}}" ref="itemTemplate"></template> <!-- this is the replacement of content tag -->
<!-- Def elements -->
</template>
</template>
<script type="application/dart" src="data-ul.dart"></script>
</polymer-element>
then you should have next class:
#CustomTag('data-ul')
class DataUl extends LiElement with Polymer, Observable {
DataUl.created() : super.created();
#published List items;
void parseDeclaration(Element elementElement) {
// We need to remove previous template from element.templateContent
// in that way it no continues adding a new content every time that we instantiate
// this component.
var previousTemplate = element.templateContent.querySelector('template#item');
if(previousTemplate != null)
previousTemplate.remove();
var t = this.querySelector('#itemTemplate'); // Gets the template with id itemTemplate from the content html
if(t != null) // if not null
element.templateContent.append(t); // append itemTemplate to element.templateContent
else
element.templateContent.append(new TemplateElement()..id='itemTemplate'); //if no template is added append an empty template to avoid errors
super.parseDeclaration(elementElement); // call super
}
}
And finally use the custom element as follow:
<ul is="data-ul" items="{{[{'first_name': 'jay'}, {'first_name': 'joy'}]}}">
<template id="itemTemplate">
<li>{{item['first_name']}}</li>
</template>
</ul>

Mixing MVC Razor and Knockout

Throughout my app, I'd like to use Razor for all functionality related to Reading, and leverage knockoutJS and AJAX for CUD operations.
In my profile view, I've done the following:
<div>
<h4>About Me</h4>
<!-- ko if: !isEditingAboutMe() -->
<p>#Model.User.AboutMe</p>
#if (Model.CurrentUserCanEdit)
{
<a data-bind="click: editAboutMe">edit</a>
}
<!-- /ko -->
<!-- ko if: isEditingAboutMe() -->
#Html.TextBoxFor(model => model.User.AboutMe, new { data_bind = "value: aboutMe" })
<a data-bind="click: saveAboutMe">save</a>
<a data-bind="click: cancelAboutMe">cancel</a>
<!-- /ko -->
</div>
This way search engines can at least crawl the content, and users with javascript enabled can perform CUD operations.
My problem above is that - using AJAX, if I click "save", how to I bind the new value to the Razor model (fourth line down)?
Corresponding JS:
function ProfileVm() {
var self = this;
self.aboutMe = ko.observable();
self.saveAboutMe = function() {
// AJAX call, after success close the field
self.isEditingAboutMe(false);
};
self.cancelAboutMe = function() {
// just for testing, would revert the value in practice
self.isEditingAboutMe(false);
};
self.isEditingAboutMe = ko.observable(false);
self.editAboutMe = function() {
self.isEditingAboutMe(true);
};
}
$(document).ready(function () {
ko.applyBindings(new ProfileVm());
})
Also - feedback regarding the "properness" of this approach is welcome.
It is unclear why you're using Knockout only for write operations (CUD) and not also for read. doing so can solve your problem automatically:
Instead of:
<p>#Model.User.AboutMe</p>
You should follow the Knockout way, so you'll have a two-way model binding:
<p data-bind="text: aboutMe()"></p>
Now, once the aboutMe field is updated, Knockout will make sure the data within the <p> element will be updated as well.
Edit:
Since you want search engines visibility of the user details, you can try this:
<p data-bind="text: aboutMe()">#Model.User.AboutMe</p>
This way, simple retrieval of the server response (without Script invocation) would indeed include the desired text, but once Knockout will kick-in (almost immediately) you'll still have full Model Binding with fresh data.

knockout virtual elements and using "with" without a container element

Can someone tell me why this works:
<div data-bind="with: selectedItem">
<div id="dialog" data-bind="jqDialog: {title: drugName}">
//contents of a jquery ui modal
</div>
</div>
But trying to do the same with KO virtual elements does not work:
<!-- ko with: selectedItem -->
<div id="dialog" data-bind="jqDialog: {title: drugName}">
//contents of a jquery ui modal
</div>
<!-- /ko -->
I get the following error:
Error: Unable to parse bindings.
Message: ReferenceError: drugName is not defined;
Bindings value: jqDialog: {title: drugName}
I'd like to get rid of that div wrapper which just holds the data-bind attribute. I figured the virtual elements would make this a breeze.
Edit: Here's kind of the core to the page (though it won't run in the fiddle), http://jsfiddle.net/B8g5J/
I'm not sure if you have sorted this out, but is it possible that your server is configured to remove comments?
See: Knockout.js Virtual Elements Work Locally But Not Remotely

knockoutjs jquery mobile checkbox list issue: cannot check

With knockoutjs and jquery mobile, I need to create a list of checkboxes from an array. It seems the checkbox list is rendered, but it did not respond to click. http://jsfiddle.net/9zx7F/
I used a fieldset tag with data-role of controlgroup to build the list. I tried ul with listview as well, same issue.
Edit: further details - I found it seems related to timing of ko.applyBindings happens. I created a page with same code running on my localhost, it was okay. Then added a timer around ko.applyBindings, the issue happened again. http://jsfiddle.net/gonglei/9zx7F/12/
I solved this with two steps;
1) unwrapping the label from the input and hooking them together with 'for' attribute
<input type="checkbox" data-role="checkbox" data-bind="uniqueName: true, uniqueID: true, value: ID />
<label data-bind="uniqueIDFor: true" >Click me</label>
ko.bindingHandlers.uniqueIDFor = {
init: function (element) {
element.setAttribute("for", "ko_unique_" + ko.bindingHandlers.uniqueName.currentIndex);
}
};
ko.bindingHandlers.uniqueID = {
init: function (element) {
element.setAttribute("id", "ko_unique_" + ko.bindingHandlers.uniqueName.currentIndex);
}
};
2) telling jqm to update the new content
$('input:checkbox').trigger('create');
I would change the model for this:
<!-- ko foreach: listItems-->
<input type="checkbox" name="itemsList" value="name" />
<span data-bind="text: name"></span>
<!-- /ko -->
the main thing to consider is the "value" property in the input control to render in the proper way.
Regards.
#tredder's solution works! Here's a fork of your fiddle using the attr data-bind attribute to bind the label, which to me looks cleaner: http://jsfiddle.net/aib42/AnKR6/

Is it possible to add dynamic jQuery tabs using KnockoutJS Templates?

I'm trying to build a dynamic display in a web page. The site is built in ASP.NET MVC 3, but I'm not sure if this matters...
Essentially, I have a base "_Editor.cshtml" page which uses partials to render the specific editor content in my display using query parameters. Consider an editor for a chart (line chart vs. bar chart).
_Editor.cshtml
<div id='tabs'>
<ul>
<li><a href='#queryTab'>Define Query</a></li>
<!-- THIS IS WHERE I NEED TO SHOW ALL OF MY 'CUSTOM' TAB NAMES -->
<!-- ko template: 'tabNames' -->
<!-- /ko -->
<li><a href='#themeTab'>Styles and Themes</a></li>
</ul>
<div id='queryTab'>
<!-- configure db connection, tables, fields, etc... (not important) -->
</div>
<!-- THIS IS WHERE I WANT TO SHOW CONTENT FOR MY CUSTOM TABS -->
<!-- ko template: 'tabContent' -->
<!-- /ko -->
<div id='themeTab'>
<!-- show various style options here (not important) -->
</div>
</div>
<script type="text/javascript">
$(function(){
var vm = { /* define my model here */ };
ko.applyBindings(vm);
$("#tabs").tabs();
});
</script>
#if (Model.EditorType == ChartTypes.Bar) {
#Html.Partial("_BarChartEditor")
} else if (Model.EditorType == ChartTypes.Line) {
#Html.Partial("_LineChartEditor")
}
_BarChartEditor.cshtml
<script id="tabNames" type="text/html">
<li>Bar Chart Tab!</li>
<!-- I could have many more tab names here -->
</script>
<script id="tabContent" type="text/html">
<div id="barChartTab">
<h1>Add controls for bar chart configuration</h1>
</div>
<!-- I would create a div for each tab name above here -->
</script>
_LineChartEditor.cshtml
<script id="tabNames" type="text/html">
<li>Line Chart Tab!</li>
</script>
<script id="tabContent" type="text/html">
<div id="lineChartTab">
<h1>Add controls for line chart configurations</h1>
</div>
</script>
Sorry for the lengthy code drop (it took a long time to write it all here, so have mercy on me). :) I wanted to make sure that my problem was understood because of the way I'm building my editors using custom partials. Perhaps it's cludgy, but it works for the most part...
Really, everything works great except for the tab content. The tab rendering appears to be happening before I'm able to bind my view model to the UI. When the 'ko.applyBindings()' method is called, the tab shows up on different tabs.
Has anybody tried to do this? Has anybody had success? I've created a jsfiddle to show a simple scenario to show exactly what I'm talking about:
http://jsfiddle.net/TrailHacker/j2nhm/
Thanks for any help!
I actually got it working with your example, your content template was just structured incorrectly. It was missing the <div> tags.
If you modify this for your example, just remember that the div id needs to match the link's ref. You can throw both of these values into your viewmodel, to allow for multiple custom tabs.
http://jsfiddle.net/tyrsius/UCGRZ/

Resources