Emberjs, data-source, twitter bootstrap typeahead - javascript

While this may be specific to the "typeahead" situation, and my example has static content, really this would apply to any bootstrap usage of "data-source". I want to someday when I grow up use dynamic content for my typeahead implementation, so am trying the binding way for now:
Ember.TextField.reopen({
//add some bootstrap specific stuff
attributeBindings: ['data-provide', 'data-items', 'dataSourceBinding:data-source'],
'dataSourceBinding': Ember.Binding.oneWay('App.AddStoreTemplateController.statesArray')
});
I have a router with connectOutlets which attaches my template:
{{view Ember.TextField elementId="state" placeholder="NY/New York" valueBinding="state" data-provide="typeahead" data-items="4" data-source="App.router.addStoreTemplateController.statesArray"}}
My controller:
AddStoreTemplateController: Ember.ArrayController.extend({
statesArray: ['Alabama', 'Washington']
}),
What I expect to see rendered in HTML:
<input id="state" class="ember-view ember-text-field" placeholder="NY/New York" type="text" data-provide="typeahead" data-items="4" data-source="['Alabama', 'Washington']">
What it actually renders in HTML:
<input id="state" class="ember-view ember-text-field" placeholder="NY/New York" type="text" data-provide="typeahead" data-items="4" data-source="App.router.addStoreTemplateController.statesArray">
Typeahead docs
http://twitter.github.com/bootstrap/javascript.html#typeahead
Thanks so much. I really enjoy EmberJS!!

After fiddling with this a bit more, I figured out an easy way to do this. It doesn't require a 3rd party library and you can use Ember.TextField to keep your inputs pretty:
I created a new extended TextField object to keep things separate:
Ember.TextFieldTypeahead = Ember.TextField.extend({
//add some bootstrap specific stuff
attributeBindings: ['data-provide', 'data-items', 'data-source'],
'data-source': function(){
return JSON.stringify(["Alabama", "Washington"]);
}.property()
});
Then in my template:
{{view Ember.TextFieldTypeahead elementId="state" placeholder="NY/New York" valueBinding="state" data-provide="typeahead" data-items="4" data-source=on}}
Things worked fine. Only confusing thing to me, and this may be an Ember bug or just my noob status of the framework, is that data-source= in the template can be anything, it still references the function that I declared. just leaving it as "data-source" in the template yields an error on the handlebars build, so I just opted to make the value "on" so I'm not confused in 6 months time when I revisit the code for some reason. Curious.
I'm also guessing I can extend this even more to observe "value" and then on value change populate the 'data-source' property with whatever ajax call my server responds with to satisfy the dynamic requirement.

You can also do something like this (when you want to load the data dynamically as you type from the server):
ember-bootstrap
EEPD.EbedMedicationArticleTypeAhead = Bootstrap.Forms.TypeAhead.extend({
init: function () {
this._super();
this.set('idProperty', 'id');
},
valueChanged: function () {
var id = this.get('value');
var self = this;
var label = this.get('_childViews')[1].$()
.val();
if (Ember.empty(label) && !Ember.empty(id)) {
var articleDescription = this.get('item.articleDescription');
self.get('_childViews')[1].$()
.val(articleDescription)
.change();
}
} .observes('value'),
getLabel: function (item) {
return '%# (%#)'.fmt(Ember.get(item, 'description'), Ember.get(item, 'amount'));
},
getQueryPromise: function (query) {
//get some data from SignalR
return $.connection.ccprCardioArticles.server.getAllByDescriptionLike(query);
}
});
the handlebar will look like this:
{{view EEPD.EbedMedicationArticleTypeAhead
label="Medicament:"
name="articleNumber"}}
Result:

For this I wouldn't use the Ember.TextField. You could do something like:
<input ... data-items="4" {{bindAttr data-source="formattedDataSource"}}/>
In your controller:
formattedDataSource: function(){
.. format your states array as a string or dump to json...
}.property()

Related

Binding nested objects with a string key

I have a project that generates forms based on the data that is passed in.
We support may field types but, for example, here is the input template:
<label>
{{fieldSchema.label}}
<input type="{{fieldSchema.attributes.type}}"
name="{{fieldSchema.attributes.name}}"
ng-model="model[ fieldSchema.attributes.name ]" />
</label>
This works great for flat models, however if the model is nested, it falls apart, eg:
$scope.model = {
manager: {
first_name: 'John'
}
}
$scope.fieldSchema.attributes.name = 'manager.first_name';
Is there a way to use $parse or $interpolate or something similar within the ng-model? I've seen examples on how to fetch data in this structure, but I haven't been able to find a two-way binding solution.
(Note: Using angular version 1.5.0)
Edit: Here is a plunk, hopefully this makes it more clear. http://plnkr.co/edit/4E8jlsnuy5HkCZPxSf5z?p=preview
If template input template html can be controlled by code, then before rendering/appending html to DOM do manipulate, it in below way.
ng-model="model[fieldSchema.attributes.name]"
to
ng-model="model['manager']['first_name']"
Code
function createNgModel(model){
var values = model.split('.')
//and then create a format it do have below format
//model['manager']['first_name']
};
var template = '<label>'+
'{{fieldSchema.label}}'+
'<input type="{{fieldSchema.attributes.type}}"'+
'name="{{fieldSchema.attributes.name}}"'+
'ng-model="+ createNgModel(fieldSchema.attributes.name) +" />'+
'</label>';
Or rather a good option would be just append the string value returned by fieldSchema.attributes.name as suggested by #Amit in comments
var template = '<label>'+
'{{fieldSchema.label}}'+
'<input type="{{fieldSchema.attributes.type}}"'+
'name="{{fieldSchema.attributes.name}}"'+
'ng-model="model.'+ fieldSchema.attributes.name+'" />'+
'</label>';
I ended up doing what Amit suggested in the comments. Thanks, Amit!
First, I created a getter/setter function in my directive:
function getterSetter( newValue ) {
var getter = $parse( 'model.' + $scope.wxFormField.attributes.name ),
setter = getter.assign;
return arguments.length ? setter( $scope, newValue ) : getter( $scope );
}
Then, I changed the ng-model to a that function, and added the ng-model-options directive.
<label>
{{fieldSchema.label}}
<input type="{{fieldSchema.attributes.type}}"
name="{{fieldSchema.attributes.name}}"
ng-model="formFieldFunctions.getterSetter"
ng-model-options="{ getterSetter: true }" />
</label>

Group results in autocompleted dropdown [Meteor]

I try to do a dropdown list in my app. First of all I use a Meteor, so that's specific kind of app ofc :)
Second thing is that I use sebdah/meteor-autocompletion package, because I want my results to be sorted in specific way and limited.
The last thing I need is to group my results.
For example: If I have 2 products named "blah" I want to get only 1 "blag" in my dropdown "autocompletion" list.
Some code:
HTML:
<template name="InvoicesEditInsertInsertForm">
<input id="descriptionautocomplete" type="text" name="description" value="" class="form-control" autofocus="autofocus" placeholder="New Item...">
</template>
JS:
Template.InvoicesEditInsertInsertForm.rendered = function() {
AutoCompletion.init("input#descriptionautocomplete");
};
Template.InvoicesEditInsertInsertForm.events({
'keyup input#descriptionautocomplete': function () {
AutoCompletion.autocomplete({
element: 'input#descriptionautocomplete', // DOM identifier for the element
collection: InvoicesItem, // MeteorJS collection object
field: 'description', // Document field name to search for
limit: 5, // Max number of elements to show
sort: { modifiedAt: -1 },
}); // Sort object to filter results with
},
});
I need to use function that could group my "description" here.
I tried to do it in helper and I get it on my screen, but to be honest I don't know how to put that into my dropdown :(
try: function() {
var item= InvoicesItem.find({},{sort:{modifiedAt:-1}}).fetch();
var descriptions={};
_.each(item,function(row){
var description = row.description;
if(descriptions[description]==null)
descriptions[description]={description:description};
});
return _.values(descriptions);
},
I don't think you can do what you want with that package. If you have a look at the current limitations of the package documentation, you can see other potential solutions to your problem.
You can do addtional filtering as follows:
filter: { 'gender': 'female' }});
but I don't think this will allow you to demand only unique options.
The code you wrote above for try won't do anything. Autocomplete doesn't take a field called try.

How to update data in Meteor using Reactivevar

I have a page with a form. In this form user can add multiple rows with key and values. There is a restriction that the customFields is created on the fly, not from any subscribed collection.
...html
<template name="main">
{{#each customFields}}
<div>
<input type="text" value="{{key}}"/>
<input type="text" style="width: 300px;" value="{{value}}"/>
</div>
{{/each}}
</template
.... router.js
Router.route 'products.add',
path: '/products/add/:_id'
data:
customFields:[]
....products.js
#using customFieldSet as Reactive Var from meteor package
Template.product.created = ->
#customFieldSet = new ReactiveVar([])
Template.product.rendered = ->
self = this
Tracker.autorun ->
arr = self.customFieldSet.get()
self.data.customFields = arr
Template.product.events(
'click .productForm__addField': (e)->
t = Template.instance()
m = t.customFieldSet.get()
console.log t
m.push(
key: ''
value: ''
)
t.customFieldSet.set m
....
The last event will be trigger when I click the button. And it add another row with key and value empty to the page.
Please advise me why I actually see the reactive variable customFieldSet updated, but there is nothing changed dynamically in html.
P/s: I guess customFields is not updated via Iron router.
Basically, you're doing the thing right. However, you shouldn't be assigning the new reactive data to your template's data context, but rather access it directly from your helpers:
Template.product.helpers({
customFileds: function () {
return Template.instance().customFiledsSet.get();
},
});
Now you can use {{customFields}} in your template code and it should work reactively. Just remember that {{this.customFileds}} or {{./customFileds}} will not work in this case.

creating a dynamic form that builds a multi dimensional array

I am a php developer that is new to angular and javascript in general but finding it really powerful and fast for creating interactive UIs
I want to create a form for a user to create an object called a program, a program has some basic info like title and description and it can have many weeks. I want their to be an 'add week' button that when pressed displays a group of form fields related to weeks, if pushed again it shows another group of form fields to fill in the second weeks information.
edit1: specifically, how I am adding the objects to scope.program with the addWeeks method.
secondly when I console.log the $scope.program it just looks very messy a lot of arrays within objects within objects. it just dosnt look like a clean array of data but maybe thats just because I am not used to javascript and or json? Each week is going to have up to 7 days obviously and each day can have numerous events so it just seems to me like it is going to be quite messy but maybe I should just have faith :p
finally how the addProgram method is creating the json object to be sent to the server
when the form is submitted it should post a json object that looks something like this
program {
title: 'name of programme',
desc: 'description of programme',
weeks: [
{
item1: 'foo',
item2: 'more foo'
},
{
item1: 'foo2',
item2: 'more foo 2'
}
]
]
}
here is a codepen of what I am doing right now but I am not sure it is the best or even an ok way to do it, particularly how I am appending the arrays/objects in teh addWeek method.
there are going to be many more layers to the form and the object it is posting(days, sessions, excersises etc) so I want to get the basics of doing this right before adding all of that.
html
<div ng-app="trainercompare">
<div ng-controller="programsController">
<input type="text" placeholder="Program Title" ng-model="program.title"></br>
<input type="text" placeholder="Program Focus" ng-model="program.focus"></br>
<input type="text" placeholder="Program Description" ng-model="program.desc"></br>
<button ng-click="addWeek()"> add week</button>
<div ng-repeat="week in program.weeks">
<input type="text" placeholder="Name the week" ng-model="week.name">
<input type="text" placeholder="Describe It" ng-model="week.desc">
{{ week.name }}</br>
{{ week.desc }}</br>
</div>
<button ng-click="addProgram()"> add program</button>
</div>
</div>
app.js
var myModule = angular.module("trainercompare", ['ui.bootstrap']);
function programsController($scope, $http) {
$scope.program = {
weeks: [{
}]
};
$scope.addWeek = function() {
$scope.program.weeks.push(
{
}
);
};
function isDefined(x) {
var undefined;
return x !== undefined;
}
$scope.addProgram = function() {
var program = {
title: $scope.program.title,
focus: $scope.program.focus,
desc: $scope.program.desc,
weeks: []
};
angular.forEach($scope.program.weeks, function(week, index){
var weekinfo = {
name: week.name,
desc: week.desc
};
program.weeks.push(weekinfo);
});
$http.post('/programs', program).success(function(data, status) {
if(isDefined(data.errors)) {
console.log(data.errors);
}
if(isDefined(data.success)) {
console.log(data.success);
}
});
};
}
Looks to me like you've got a good grasp on it. The addWeek code looks correct. The extra data you see when you console.log your model is some of Angular's internal stuff to track bindings. When you post that to your server it should be cleaned up by Angular.
Angular has a JSON function that removes all of the hash values and other 'angular' things from your JSON. That's why they start with a $ so it knows to remove them.
This happens automatically when you use $http, it's in the documentation here:
If the data property of the request configuration object contains an object, serialize it into JSON format.
Since Angular will clean up the hashes and things, you don't need to "rebuild" the model when you're posting it... just set data to $scope.program and remove 70% of the code in $scope.addProgram.
To learn more specifically how Angular cleans up the JSON, look at this answer: Quick Way to "Un-Angularize" a JS Object

How to bind click handlers to templates in knockoutjs without having a global viewModel?

I'm very new to KnockoutJs so I'm hoping that there is a well known best practice for this kind of situation that I just haven't been able to find.
I have a view model that contains an array of items. I want to display these items using a template. I also want each item to to be able to toggle between view and edit modes in place. I think what fits best with Knockout is to create the relevant function on either the main view model or (probably better) on each item in the array and then bind this function in the template. So I have created this code on my page:
<ul data-bind="template: {name: testTemplate, foreach: items}"></ul>
<script id="testTemplate" type="text/x-jquery-tmpl">
<li>
<img src="icon.png" data-bind="click: displayEditView" />
<span data-bind="text: GBPAmount"></span>
<input type="text" data-bind="value: GBPAmount" />
</li>
</script>
<script>
(function() {
var viewModel = new TestViewModel(myItems);
ko.applyBindings(viewModel);
})();
</script>
And this in a separate file:
function TestViewModel(itemsJson) {
this.items = ko.mapping.fromJS(itemsJson);
for(i = 0; i < this.items.length; ++i) {
this.items[i].displayEditView = function () {
alert("payment function called");
}
}
this.displayEditView = function () {
alert("viewmodel function called");
}
};
Due to the environment my JS is running in I can't add anything to the global namespace, hence the annonymous function to create and set up the view model. (There is a namespace that I can add things to if it is necessary.) This restriction seems to break all the examples I've found, which seem to rely on a global viewModel variable.
P.S. If there's an approach that fits better with knockoutJS than what I am trying to do please feel free to suggest it!
When your viewModel is not accessible globally, there are a couple of options.
First, you can pass any relevant methods using the templateOptions parameter to the template binding.
It would look like (also note that a static template name should be in quotes):
data-bind="template: {name: 'testTemplate', foreach: items, templateOptions: { vmMethod: methodFromMainViewModel } }"
Then, inside of the template vmMethod would be available as $item.vmMethod. If you are using templateOptions as the last parameter, then make sure that there is a space between your braces { { or jQuery templates tries to parse it as its own.
So, you can bind to it like:
<img src="icon.png" data-bind="click: $item.vmMethod" />
The other option is to put a method and a reference to anything relevant from the view model on each item. It looks like you were exploring that option.
Finally, in KO 1.3 (hopefully out in September and in beta soon) there will be a nice way to use something like jQuery's live/delegate functionality and connect it with your viewModel (like in this sample: http://jsfiddle.net/rniemeyer/5wAYY/)
Also, the "Avoiding anonymous functions in event bindings" section of this post might be helpful to you as well. If you are looking for a sample of editing in place using a dynamically chosen template, then this post might help.
This is for those asking how to pass variable methods (functions) to Knockout Template. One of the core features of Templating is the consuming of variable data, which can be String or function. In KO these variables can be embedded in data or foreach properties for the Template to render. Objects embedded in data or foreach, be it String, function etc, can be accessed at this context using $data.
You can look at this code and see if it can help you to pass functions to Knockout Template.
function ViewModel() {
this.employees = [
{ fullName: 'Franklin Obi', url: 'employee_Franklin_Obi', action: methodOne },
{ fullName: 'John Amadi', url: 'employee_John_Amadi', action: methodTwo }
],
this.methodOne = function(){ alert('I can see you'); },
this.methodTwo = function(){ alert('I can touch you'); }
}
ko.applyBindings(new ViewModel());
<ul data-bind="template: { name: employeeTemplate, foreach: employees }" ></ul>
<script type="text/html" id="employeeTemplate">
<li><a data-bind="attr: { href: '#/'+url }, text: fullName, click: $data.action"></a></li>
</script>
If you want to serve multiple Template constructs you can introduce a switch method to your ViewModel like this, and use as property to introduce alias for each item (employee). Make sure you add the switch key, linkable, to the item object.
...
this.employees = [
{ fullName: 'Franklin Obi', linkable : false },
{ fullName: 'John Amadi', url: 'employee_John_Amadi', action: methodTwo, linkable : true }
],
this.methodLinkTemplate = function(employee){return employee.linkable ? "link" : "noLink"; } //this is a two way switch, many way switch is applicable.
...
Then the id of the Template forms will be named thus;
<ul data-bind="template: { name: employeeTemplate, foreach: employees, as: 'employee' }" ></ul>
<script type="text/html" id="noLink">
<li data-bind="text: fullName"></li>
</script>
<script type="text/html" id="link">
<li><a data-bind="attr: { href: '#/'+url }, text: fullName, click: $data.action"></a></li>
</script>
I have not ran this codes but I believe the idea can save someones time.

Categories