I am trying to pass a new created object to my dot.js partial snippet like this :
try {
var tempFn = doT.template($('#myTpl').text());
var resultText = tempFn({
"foo": "this snippet"
});
$('#result').html(resultText);
} catch (e) {
$('#error').show().html(e);
throw e;
}
#error {
font-weight: bold;
border: 1px solid red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dot/1.0.3/doT.js"></script>
<!-- Template HERE -->
<script id="myTpl" type="template/doT.js">//<![CDATA[
{{##def.snippet:obj:
<div>How to use {{=obj.x}}</div>
#}}
{{#def.snippet:{"x":it.foo}}}
// ]]></script>
<div id="result"></div>
<div id="error" style="display: none;"></div>
But I get Template has an error: SyntaxError: Unexpected token :
So how can I create and pass object (or multiple params) to a defined partial ?
What's going wrong ?
EDIT:
I have a schedule structure that come from a REST webservice like that :
"schedule": {
"MONDAY": {
"amOpenTime": {
"hours": 8,
"minutes": 30
},
"amCloseTime": null,
"pmOpenTime": null,
"pmCloseTime": {
"hours": 17,
"minutes": 0
}
},
"TUESDAY": {
"amOpenTime": {
"hours": 8,
"minutes": 31
},
"amCloseTime": null,
"pmOpenTime": null,
"pmCloseTime": {
"hours": 17,
"minutes": 40
}
},
....
}
I would to do not repeat my templating for each day because they must be processed in same way (DRY!)
So I consider using partial snippet to print each line of my schedule (morning open/close time and afternoon open/close time).
So as morning and afternoon div sould be threated in same way, I would like to create a second snippet to handle that. But for morning I need to pass only am prefixed data and pm prefixed data for afternoon like that :
{{##def.scheduleHalfDay:fday:
// multiple condition that I ommited
<div class="closed {{fday.type}}">{{fday.openTime.hours}}:{{fday.openTime.minutes}} - {{fday.closeTime.hours}}:{{fday.closeTime.minutes}}</div>
#}}
{{##def.scheduleRow:hday:
{{? (typeof hday.amOpenTime === "undefined" || hday.amOpenTime === null) && (typeof hday.pmCloseTime === "undefined" || hday.pmCloseTime == null) }}
<div class="closed">Closed</div>
{{??}}
{{#def.scheduleHalfDay:{"type": "morning", "openTime": hday.amOpenTime, "closeTime": hday.amCloseTime}}}--{{#def.scheduleHalfDay:{"type": "afternoon", "openTime": hday.pmOpenTime, "closeTime": hday.pmCloseTime}}}
{{?}}
#}}
<div class="agency-schedules">
<div class="line"><div class="agency-schedules-day">Monday</div>{{#def.scheduleRow:it.horaires.MONDAY}}</div>
<div class="line"><div class="agency-schedules-day">Tuesday</div>{{#def.scheduleRow:it.horaires.TUESDAY}}</div>
...
</div>
scheduleHalfDay are not working. So how can I pass my 3 parameters properly (without change data structure) ?
Another way to make it work is to declare your param as a variable.
{{ var param = {"x":it.foo}; }}
{{#def.snippet:param}}
First, your template has HTML in it so you should use .html() to get the template text.
Second, brackets, { and }, are reserved for doT template borders so you can't have them in templates.
Third, you don't need to use a partial there if all you're doing is passing an object to a template:
<script id="myTpl" type="template/doT.js">
{{##def.snippet:
<div>How to use {{=it.foo}}</div>
#}}
{{#def.snippet}}
</script>
Here's the simple working example: https://jsfiddle.net/silkster/wq5tjzrt/
Here's an example of transforming your position data and passing it to doT: https://jsfiddle.net/silkster/wq5tjzrt/2/
EDIT
From the new data you added, it seems what you need is an array of days. What you have in the original JSON data is a regular object with day names as properties. In my 3rd example below, I transform the object into an array of days and pass that to doT to output a schedule and use a partial template to show the opening and closing times for each day.
One edit I had to do in the partial template was to change the snippet so the correct properties are used in the template.
This might be the answer you're looking for:
When you define the partial template, use the variable that will be available when the partial is used. "it" is the default, but if you use the partial in a loop as in my example, then define the partial with the variable as defined within the loop template.
Also, while transforming the data, add any data from calculations that you might need in your template. For example, if you need to determine if the opening time and closing time happen in am or pm, then do that while transforming the data and add the new key/value pairs to the transformed data.
Example 3 using your schedule data: https://jsfiddle.net/silkster/nq6guog9/
Related
I am new to Javascript and I've been learning how to import a country's attributes into an HTML element. Some of you might recognize this code, it's from a tutorial, which is now outdated. I've been searching around for an updated solution, but couldn't find any.
First I have the function to fetch the data:
const getCountryData = function (country) {
fetch(`https://restcountries.com/v3.1/name/${country}`)
.then(response => response.json())
.then(data => renderCountry(data[0]));
};
Then I call that function, supplying a country getCountryData('czechia') to infuse it into an element like this:
const renderCountry = function(data, className = '') {
const html = `
<article class="country ${className}">
<img class="country__img" src="${data.flags.svg}" />
<div class="country__data">
<h3 class="country__name">${data.name.common}</h3>
<h4 class="country__region">${data.region}</h4>
<p class="country__row">${(+data.population / 1000000).toFixed(1)} people</p>
<p class="country__row">${data.fifa}</p>
</div>
</article>
`
countriesContainer.insertAdjacentHTML
('beforeend', html);
countriesContainer.style.opacity = 1;
}
This works fine, but the issue is that at the end of the HTML, where I input {data.fifa} I want to have the name of the country's main currency instead. Unfortunately, the data is structured in a way, that in order to have the currency's name displayed, I first have to call it's short name, as shown below:
"currencies": {
"CZK": {
"name": "Czech koruna",
"symbol": "Kč"
}
},
If I call the {data.currencies} into the string, I'm just gonna get an empty object back. If I call it as {currencies.CZK.name}, it works, but the issue is that if I call Sweden, for example, it won't display anything, because then it'd need to be {currencies.SEK.name}. How do I get around this? How can I can call a currency's name without having to incorporate CZK, SEK, USD, EUR etc. into the variable?
Any help is appreciated.
You can transform that object into an array:
const currencyArray = Object.values(data.currencies)
console.log(currencyArray[0].name)
If the country has many currencies, just change the index from 0 to 1, 2, ...
So the question is how can I keep the HTML elements in sync while I add and delete from an existing array.
If I have an array of javascript objects say element 1 is:
{
"firstName": "John",
"address": {
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": "10021-3100"
},
"phoneNumbers": [
{
"type": "home",
"number": "212 555-1234"
},
{
"type": "office",
"number": "646 555-4567"
}
....
]
.... ETC ETC COMPLICATED ....
}
Then my initial html might be generated by using the array index for each object of initial size 3 elements [ {}, {}, {} ]:
<div id="arrayPos-0">
<div>John</div>
<input>PROCESS<input>
<input>DELETE<input>
</div>
<div id="arrayPos-1">
<div>Sam</div>
<input>PROCESS<input>
<input>DELETE<input>
</div>
<div id="arrayPos-2">
<div>Timmy</div>
<input>PROCESS<input>
<input>DELETE<input>
</div>
If I add to the Javascript Array, and I expect to increment it and become [ {}, {}, {}, {} ]:
<div id="arrayPos-0">
...
</div>
<div id="arrayPos-1">
...
</div>
<div id="arrayPos-2">
...
</div>
<div id="arrayPos-3">
<div>Simone</div>
<input>PROCESS<input>
<input>DELETE<input>
</div>
However as soon as I delete from the javascript array say index 2 (arrayPos-2) I get the following HTML:
<div id="arrayPos-0">
...
</div>
<div id="arrayPos-1">
...
</div>
<div id="arrayPos-3">
...
</div>
This is all messed up and I'm unable to match "arrayPos-3" to now index 2. In addition when I add a new javascript object which is index 3:
<div id="arrayPos-0">
...
</div>
<div id="arrayPos-1">
...
</div>
<div id="arrayPos-3">
...
</div>
<div id="arrayPos-3">
...
</div>
I am not able to use AngularJS and hence ng-repeat can't be used due to support needed on older browsers. It would be pretty simple to use an observer to bind the javascript objects directly to the html markup.
I can only use jQuery and regular javascript.
But surely this can be solved in a simpler manner? Do I even need to bind by using IDs? Do I need to autogenerate GUIDs for IDs and use a dictionary to match ID with Javascript object index (I hope not)?
The simplest way is to have a generator function which will regenerate the HTML after any changes have been made (adding/deleting/editing).
That way you won't have any confusions with IDs and will always have your current JS object represented in the DOM. That's quite the "Angularish" way to do it - building the DOM from the JS data without really caring what's in the DOM.
Here's a super-simple example to see what I meant:
var data = [
{name: 'John'},
{name: 'Shomz'},
{name: 'Jimmy'}
]
var c = document.getElementById('c');
function build() {
var output = "";
for(var i = 0; i < data.length; i++) {
output += '<div id="arrayPos-' + i + '"><button onclick="del(' + i + ')">Del</button><button onclick="edit(' + i + ')">Edit</button>' + data[i].name + '</div>';
}
c.innerHTML = output;
}
function del(id) {
data.splice(id ,1);
document.getElementById('arrayPos-' + id).className = 'del';
setTimeout(build, 200);
}
function add() {
data.push({name: prompt('Enter name')});
build();
}
function edit(id) {
data[id].name = prompt('Enter name', data[id].name);
build();
}
build();
#arrayPos-0 {color: green}
button {margin: 4px}
#c div {opacity: 1; transition: all 0.2s linear}
#c div:hover {background: #eee}
#c div.del {opacity: 0}
<div id="c"></div>
<br>
<button onclick="add()">Add</button>
You could use JQuery's .attr() method to dynamically set the id property when looping through the objects in the array - if that's the approach you want to take. You could also look at dynamically setting the inner html using JQuery's .html() to set the contents of the divs. If you review traversing the DOM using JQuery it will help with the latter approach. Hope that helps -
If you want to keep it this way and keep them in order then you caan create a function that:
1.deletes the current divs
2. Re generates the divs in the new order with the corresponding new id's
And just have this function fire every time you add or delete something from the array. It's relatively simple and wont take all that long for javascript to do.
Or:
Create a for loop that loops through the array and identifies which position(number of loops) the object with "x" name is in the array and assigns it to the div.
You can use the jQuery .index() method to get the index of the element relative to its siblings. With that, you can match it to the index in the array. No need for adding id's to the elements.
Let's say you already have an index and you want to access the dom element. You can do this:
$('.class-name').get(index).remove();
Or if you're responding to some event and you're not sure which element in the array it corresponds to, you can do this:
$('.class-name').click(function(e){
var index = $('.class-name').index(e.target);
});
In my example I'm using a click event, but it can be any other way.
In-case, if you can modify your existing js object, I suggest to use id for both html element and the js object.
Probably you could go for something:
var id = Math.random().toString(16).slice(-6); // for random id, aplha-numeric
Your html:
<div id="23xc45">
...
</div>
<div id="cd567u">
...
</div>
Your js object:
[{
"id" : "23xc45",
"firstName": "John",
...
},{
"id" : "cd567u",
"firstName": "Sam",
...
}]
I have a column in a Kendo grid that I want to perform some specific logic for when rendering, and am using Angular. I have the grid columns set up using the k-columns directive.
After looking at the documentation, it seemed simple: I could add the template option to my column, define the function to perform my logic, and pass the dataItem value in. What I have looks something like this:
k-columns='[{ field: "Name", title: "Name",
template: function (dataItem){
// Perform logic on value with dataItem.Name
// Return a string
}
}]'
However, running this causes a syntax error complaining about the character '{' that forms the opening of the block in my function.
I have seen several examples of defining a template function in this format. Is there something else that needs to be done for this to work? Am I doing something incorrectly? Is there another way of defining the template as a function and passing the column data to it? (I tried making a function on my $scope, which worked, except I couldn't figure out how to get data passed into the function.)
Thank you for your help.
It appears that defining a column template in this fashion isn't supported when using AngularJS and Kendo. This approach works for projects that do not use Angular (standard MVVM), but fails with its inclusion.
The workaround that a colleague of mine discovered is to build the template using ng-bind to specify a templating function on the $scope, all inside of a span:
template: "<span ng-bind=templateFunction(dataItem.Name)>#: data.Name# </span>"
This is the default column templating approach that is implemented by Telerik in their Kendo-Angular source code. I don't know yet if the data.Name value is required or not, but this works for us.
Warning: Don't have access to Kendo to test the code at the moment, but this should be very close
In your case, you are assigning a a string to the value of k-columns and that string contains the the word function and your curly brace {
You need to make sure the function gets executed ... something like this:
k-columns=[
{
field: "Name",
title: "Name",
template: (function (dataItem){
// Perform logic on value with dataItem.Name
// Return a string
}())
}
];
Note the difference:
We create an object -- a real honest-to-goodness object, and we use an IIFE to populate the template property.
Maybe, it will be useful for someone - this code works for me too:
columns: [
{
field: "processed",
title:"Processed",
width: "100px",
template: '<input type="checkbox" ng-model="dataItem.processed" />'
},
and you get the two-way binding with something like this:
<div class="col-md-2">
<label class="checkbox-inline">
<input type="checkbox" ng-model="vm.selectedInvoice.processed">
processed
</label>
</div>
This can be done via the columns.template parameter by supplying a callback function whose parameter is an object representing the row. If you give the row a field named name, this will be the property of the object you reference:
$("#grid").kendoGrid({
columns: [ {
field: "name",
title: "Name",
template: function(data) {
return data.name + "has my respect."
}
}],
dataSource: [ { name: "Jane Doe" }, { name: "John Doe" } ]
});
More information is available on Kendo's columns.template reference page.
After hours of searching. Here is the conclusion that worked:
access your grid data as {{dataItem.masterNoteId}} and your $scope data as simply the property name or function.
Example
template: '<i class="fa fa-edit"></i>',
I really hope this safes somebody life :)
just use like my example:
}, {
field: "TrackingNumber",
title: "#T("Admin.Orders.Shipments.TrackingNumber")",
//template: '<a class="k-link" href="#Url.Content("~/Admin/Shipment/ShipmentDetails/")#=Id#">#=kendo.htmlEncode(TrackingNumber)#</a>'
}, {
field: "ShippingMethodName",
title: "#T("Admin.Orders.Shipments.ShippingMethodName")",
template:function(dataItem) {
var template;
var ShippingMethodPluginName = dataItem.ShippingMethodPluginName;
var IsReferanceActive = dataItem.IsReferanceActive;
var ShippingMethodName = dataItem.ShippingMethodName;
var CargoReferanceNo = dataItem.CargoReferanceNo;
var ShipmentStatusId = dataItem.ShipmentStatusId;
if (ShipmentStatusId == 7) {
return "<div align='center'><label class='label-control'><b style='color:red'>Sipariş İptal Edildi<b></label></div>";
} else {
if (ShippingMethodPluginName == "Shipping.ArasCargo" || ShippingMethodPluginName == "Shipping.ArasCargoMP") {
template =
"<div align='center'><img src = '/content/images/aras-kargo-logo.png' width = '80' height = '40'/> <label class='label-control'><b>Delopi Aras Kargo Kodu<b></label>";
if (IsReferanceActive) {
template =
template +
"<label class='label-control'><b style='color:red; font-size:20px'>"+CargoReferanceNo+"<b></label></div>";
}
return template;
}
I'm trying to use a wysiwyg in my angular app. What I want to happen is, I query an end point for an array of objects. Then based on the property name the data inside it gets wrapped into a different html tag. Then all the data is concatenated into a string which gets displayed on a wysiwyg in my app.
So JSON that looks like
[
{
"name": "steve",
"age": 33,
"profile": "http://www.profile.com/steve",
"location": "New York"
},{
"name": "john",
"age": 25,
"profile": "http://www.profile.com/john",
"location": "LA"
},
]
Spits out:
"<b>Steve - New York</b>
<br>
<a href='http://www.profile.com/steve'>Url</a>
<br>
<span>33</span>
<br>
<br>
<b>John - LA</b>
<br>
<a href='http://www.profile.com/john'>Url</a>
<br>
<span>25</span>
<br>
<br>"
While this part isn't Angular specific. I think I need to add this code into a service so that it can be reused whenever I would need it an an app.
I'm not sure what this code would look like. Any ideas?
EDIT: For clarification the reason why I'm doing this is because the page has a WYSIWYG on it. A non-technical user needs to be able to edit the data in the WYSIWYG then click a button and the app exports a PDF. The WYSIWYG requires a single string with HTML tags as does the backend of the app which generates the PDF file.
It seems to me that there's really no reason to reinvent the wheel here... why can't you just use a fully supported WYSIWYG editor like TinyMCE? It's got an angular extension through the Angular-UI project here:
https://github.com/angular-ui/ui-tinymce
Then you could write this in your html:
<textarea ui-tinymce="tinyMceOptions" ng-model="parsedResponse"></textarea>
<button ng-click="convertToPdf()">Y U NO DOWNLOAD PDF?</button>
In your controller (or directive):
$scope.tinyMceOptions = {/*Customize here*/};
$scope.parseMyResponse = function(data) {
// do something with data and return as html string
return data;
};
$http.get('/your/resource/url').success(function(result) {
$scope.parsedResponse = $scope.parseMyResponse(result);
});
$scope.convertToPdf = function() {
// do something with $scope.parsedResponse here to get a PDF
};
Edit:
I guess I didn't get what you were asking exactly at first? If you want it in a service all you have to do is something like:
angular.module('app')
.service('Util', function() {
return {
wrapInTag: function(tagName, value) {
return '<' + tagName + '>' + value + '<' + tagName + '/>';
},
parseMyResource: function(response) {
htmlString = '';
angular.each(response, function(item) {
htmlString += this.wrapInTag('h1', item.title);
htmlString += this.wrapInTag('b', item.id);
// Other manipulation...
});
return htmlString;
}
};
});
Then in your controller:
angular.module('app')
.controller('MyCtrl', function(Util){
// Use Util functions here...
});
I've given you some example code for parsing JSON into HTML strings, but I'm afraid I couldn't be more specific as to how to write it. The logic if your parsing function really depends on what you're trying to accomplish as well as what your data model looks like. There's really no straight-forward way to convert JSON to HTML.
I am trying to dynamically build the structure of a kendo-angular grid. My problem is that the grid options are not known when the k-options attribute is evaluated, so the grid is binding to ALL of the columns on the datasource.
Here is the HTML:
<div kendo-grid k-options="{{gridModel.options}}"
k-data-source="gridModel.myDataSource">
</div>
And here is the javascript in the controller:
// this is called after the api call has successfully returned with data
function getSucceeded(){
...
$scope.gridModel.options = function(){
// function that properly builds options object with columns, etc.
}
// this is just shown for example... the data is properly loading
$scope.gridModel.myDataSource.data(ds.data());
}
The data is properly loading, but because gridModel.options was evaluated in the HTML prior to being set by the success method, it is essentially ignored and all of the columns from the datasource are being rendered.
This works like a champ when gridModel.options is static.
How can I defer the evaluation of k-options and/or force a reevaluation after they've been set by the controller?
I was able to figure it out. I had to do four things:
Update my version of angularjs (I was on 1.08 which does not have the ng-if directive). I updated to 1.2.0rc3.
Wrap my kendo-grid div in an ng-if div
Invoke my function! I was just setting $scope.gridModel.options to a function - I needed to actually invoke the function so I'd be setting the variable to the value returned from the function.
I had to update my angular.module declaration to include ngRoute (based on it being separated into it's own module in 1.2.x).
Here's the updated HTML:
<div data-ng-if="contentAvailable">
<div kendo-grid k-options="{{gridModel.options}}"
k-data-source="gridModel.myDataSource">
</div>
</div>
And here's the updated controller (not shown: I set $scope.contentAvailable=false; at the beginning of the controller):
// this is called after the api call has successfully returned with data
function getSucceeded(){
...
$scope.gridModel.options = function(){
// function that dynamically builds options object with columns, etc.
}(); // <----- NEED to invoke function!!
// this is just shown for example... the data is properly loading
$scope.gridModel.myDataSource.data(ds.data());
$scope.contentAvailable=true; // trigger the ng-if
}
I actually moved the function into a config file so I'm not polluting the controller with too much configuration code. Very happy to have figured this out.
Here is a sample using 'Controller As' syntax, dynamic columns and paging.
var app = angular.module("app", ["kendo.directives"]);
function MyCtrl() {
var colsList = [{
name: "col1"
}, {
name: "col2"
}, {
name: "col3"
}, {
name: "col4"
}];
var gridCols = [];
var iteration = 1;
var vm = this;
vm.gridOptions = {
columns: gridCols,
dataSource: new kendo.data.DataSource({
pageSize: 10
}),
pageable: true
};
vm.buildGrid = function() {
var data = {};
vm.gridOptions.columns = [];
for (var x = 0; x < colsList.length; x++) {
if (iteration % 2 === 0 && x === colsList.length - 1) continue;
var col = {};
col.field = colsList[x].name;
col.title = colsList[x].name;
data[col.field] = "it " + iteration + " " + (1111 * (x + 1));
vm.gridOptions.columns.push(col);
}
// add one row to the table
vm.gridOptions.dataSource.add(data);
iteration++;
};
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<link rel="stylesheet" href="http://cdn.kendostatic.com/2015.1.318/styles/kendo.common.min.css" />
<link rel="stylesheet" href="http://cdn.kendostatic.com/2015.1.318/styles/kendo.default.min.css" />
<script src="http://cdn.kendostatic.com/2015.1.318/js/kendo.all.min.js"></script>
<body ng-app="app">
<div ng-controller="MyCtrl as vm">
<button ng-click="vm.buildGrid()">Build Grid</button>
<div kendo-grid="grid" k-options="vm.gridOptions" k-rebind="vm.gridOptions"></div>
</div>
</body>
We can use the k-rebind directive for this. From the docs:
Widget Update upon Option Changes
You can update a widget from controller. Use the special k-rebind attribute to create a widget which automatically updates when some scope variable changes. This option will destroy the original widget and will recreate it using the changed options.
Apart from setting the array of columns in the GridOptions as we normally do, we have to hold a reference to it:
vm.gridOptions = { ... };
vm.gridColumns = [{...}, ... ,{...}];
vm.gridOptions.columns = vm.gridColumns;
and then pass that variable to the k-rebind directive:
<div kendo-grid="vm.grid" options="vm.gridOptions" k-rebind="vm.gridColumns">
</div>
And that's it when you are binding the grid to remote data (OData in my case). Now you can add or remove elements to/from the array of columns. The grid is going to query for the data again after it is recreated.
When binding the Grid to local data (local array of objects), we have to somehow postpone the binding of the data until the widget is recreated. What worked for me (maybe there is a cleaner solution to this) is to use the $timeout service:
vm.gridColumns.push({ ... });
vm.$timeout(function () {
vm.gridOptions.dataSource.data(vm.myArrayOfObjects);
}, 0);
This has been tested using AngularJS v1.5.0 and Kendo UI v2016.1.226.