Manually convert from XML view to Javascript view in sapui5 - javascript

The documentation at https://sapui5.hana.ondemand.com/#/controls provides many SAPUI5 samples. But all the views are written in XML. I can find examples written in Javascript elsewhere but I'm asking for a general rule to apply on XML code. Here is an example List.view.xml wich I need to manually convert to List.view.js
<mvc:View
height="100%"
controllerName="sap.m.sample.ListSelectionSearch.List"
xmlns:l="sap.ui.layout"
xmlns:core="sap.ui.core"
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m">
<Page
showHeader="false" >
<subHeader>
<Toolbar>
<SearchField
liveChange="onSearch"
width="100%" />
</Toolbar>
</subHeader>
<content>
<List
id="idList"
items="{/ProductCollection}"
selectionChange="onSelectionChange"
mode="MultiSelect"
includeItemInSelection="true" >
<infoToolbar>
<Toolbar
visible="false"
id="idInfoToolbar" >
<Label id="idFilterLabel" />
</Toolbar>
</infoToolbar>
<items>
<StandardListItem
title="{Name}"
description="{ProductId}"
icon="{ProductPicUrl}"
iconDensityAware="false"
iconInset="false" />
</items>
</List>
</content>
</Page>
Any help will be appreciated.

Here is the same in SAPUI5's JSView with list aggregation done in the controller:
Alternatively, have a look at this full application Example SAPUI5 JSView Application
OR
Make use of the Diagnostic tool by pressing CTRL+SHIFT+ALT+S and API References
sap.ui.define(["sap/m/Page", "sap/m/List", "sap/m/Toolbar", "sap/m/SearchField", "sap/m/Label", "sap/m/Text"], function(Page, List, Toolbar, SearchField, Label, Text) {
"use strict";
return sap.ui.jsview("sap.m.sample.ListSelectionSearch.View", {
getControllerName: function() {
return "sap.m.sample.ListSelectionSearch.List";
},
createContent: function(oController) {
var oToolbar = new Toolbar({
visible: true,
content: [
new SearchField({
liveChange: function() {
oController.onSearch();
},
width: "100%"
})
]
});
var oInfoToolbar = new Toolbar({
content: new Toolbar(this.createId("idInfoToolbar"), {
visible: true,
content: new Text({
text: "Label Text"
})
})
});
var oList = new List(this.createId("idList"), {
mode: "MultiSelect",
includeItemInSelection: true,
infoToolbar: oInfoToolbar
});
var oPage = new Page(this.createId("oPageId"), {
height: "100%",
title: "Page Title",
showHeader: true,
showSubHeader: true,
headerContent: oToolbar,
content: [oList]
});
var app = new sap.m.App();
app.addPage(oPage);
app.placeAt("content");
return app;
}
});
});
//in Controller
sap.ui.define(["sap/m/StandardListItem", "sap/ui/model/json/JSONModel"], function(StandardListItem, JSONModel) {
"use strict";
var oData = {
"ProductCollection": [{
"titleId": 0,
"Name": "Olayinka Otuniyi",
"ProductId": "001",
"ProductPicUrl": "sap-icon://competitor"
}, {
"titleId": 1,
"Name": "Maria Anders",
"ProductId": "002",
"ProductPicUrl": "sap-icon://badge"
}, {
"titleId": 2,
"Name": "Ana Trujillo",
"ProductId": "003",
"ProductPicUrl": "sap-icon://broken-link"
}, {
"titleId": 3,
"Name": "Thomas Hardy",
"ProductId": "004",
"ProductPicUrl": "sap-icon://create"
}, {
"titleId": 4,
"Name": "Christina Berglund",
"ProductId": "005",
"ProductPicUrl": "sap-icon://pending"
}, {
"titleId": 5,
"Name": "Hanna Moos",
"ProductId": "006",
"ProductPicUrl": "sap-icon://decision"
}, {
"titleId": 6,
"Name": "Martín Sommer",
"ProductId": "007",
"ProductPicUrl": "sap-icon://process"
}, {
"titleId": 7,
"Name": "Laurence Lebihans",
"ProductId": "008",
"ProductPicUrl": "sap-icon://accept"
}, {
"titleId": 8,
"Name": "Elizabeth Lincoln",
"ProductId": "009",
"ProductPicUrl": "sap-icon://alert"
}]
};
return sap.ui.controller("sap.m.sample.ListSelectionSearch.List", {
// onInit: function() {
// },
onAfterRendering: function() {
var oModel = new JSONModel(oData);
this.getView().setModel(oModel, "products");
var oTemplate = new StandardListItem({
title: "{products>Name}",
description: "{products>ProductId}",
icon: "{products>ProductPicUrl}",
iconDensityAware: false,
iconInset: false,
type: "Active"
});
oTemplate.attachPress(this.onSelectionChange, this);
this.getView().byId("idList").bindItems({
path: "/ProductCollection",
template: oTemplate,
model: "products"
});
},
onSearch: function() {
console.log("Searching");
},
onSelectionChange: function() {
console.log("changing Selection");
}
});
});

Should not be that difficult:
new sap.m.Page({
showHeader: false,
subHeader: new sap.m.Toolbar({
content: [ // (**)
new sap.m.SearchField({
liveChange: onSearch, // event handler
width: "100%"
})
]
}),
content: [
new sap.m.List({
//...
})
]
});
OR you can keep writing XML and then create JS instances from it using API:
sap.ui.xmlfragment
sap.ui.xmlview
(**) here is probably the trickiest part. How could you know that this should be wrapped inside "content" property? Very easy! If you see one control inside another directly (without any tags around it), it means inner control is in default aggregation of parent control. So, all you need to do is check what is the name of the default aggregation of the parent control. In case of sap.m.Toolbar it's a content.
UPDATE: however, it might be difficult to understand which aggregation is default, because in our docs we do not show this information. I will contact responsible team on this matter. As a work around it's possible to get this information from the source code, e.g. sap.m.Page - see defaultAggregation definition in metadata description.

Please see the example below
View:
sap.ui.jsview("ResourceRootName.view.ViewName", {
getControllerName: function() {
return "ResourceRootName.view.ViewName";
},
createContent : function(oController) {
this.oList = new sap.m.List({
showUnread: true,
mode: sap.ui.Device.system.phone ? sap.m.ListMode.None : sap.m.ListMode.SingleSelectMaster,
itemPress: [oController.onListSelect, oController]
});
this.page = new sap.m.Page({
title: "{i18n>pageTitle}",
navButtonText: "Home",
showNavButton: true,
navButtonPress: function() {
oController.handleNavBack();
},
subHeader: new sap.m.Bar({
contentMiddle: [
new sap.m.SearchField(oController.createId("searchFieldTasks"), {
width: "100%"
})
]
}),
content: [this.oList]
});
return this.page; //Note: if you need to return more than two controls you can do so by using array
}
});
In controller you can bind your list as below
Controller:
this.getView().oList.bindAggregation("items", {
path: "/EntityCollectionSet",
template: new sap.m.StandardListItem({
title: "{Title}",
description: "{Description}"
}),
filters: []//If you want to pass any filters
});

Related

Kendo Grid sometimes renders only title or data

I have a pretty simple Kendo Grid that displays a list of data with titles and is conditionally editable on a field. This is rendered and attached to the DOM using JQuery in a function that is called in the "show" action of a Kendo View.
The issue is that either the data does not render or the grid column headers don't render every time I load the page. It's always one or the other, the only time it renders properly is if I refire the function that attaches it as I occasionally do when the state of the page changes.
Here's where I attach it to the page:
form.find("#approvals").kendoGrid({
columns: [
{ title: "Service", field: "PartDescription" },
{ title: "Component", field: "Component", width: "250px" },
{ title: "Status Last Modified", width: "250px", template: "#= kendo.toString(StatusModifiedDate, 'g') #", },
{ title: "Status", field: "Status", width: "135px", editor: statusDropDownEditor }
],
editable: modifyState,
edit: function (e) {
if (e.container.find("input").attr("name") !== "Status") {
this.closeCell();
}
}
});
This comes from the function that is fired when the Kendo View is shown.
The issue is that either the data does not render or the grid column
headers don't render every time I load the page.
Without knowing what "form" is in your js code, try using this to make sure it loads at the 'correct' time. Please feel free to tinker the gridData variable to match your code and preg_replace the fields for your fields.
<!DOCTYPE html>
<html>
<head>
<script src="https://kendo.cdn.telerik.com/2017.1.118/js/jquery.min.js"></script>
<script src="https://kendo.cdn.telerik.com/2017.1.118/js/kendo.all.min.js"></script>
<meta charset="utf-8"/>
<title>Kendo UI Snippet</title>
</head>
<body id="theBod">
<div id="approvals"></div>
<script type="text/javascript">
$(function () { // the dom is ready for jquery parsing
var gridData = [{
"Name": "daya",
"Role": "Developer",
"Dept": "Dev",
"Date": "\/Date(836438400000)\/",
"Balance": 23
}, {
"Name": "siva",
"Role": "Developer",
"Dept": "Dev",
"Date": "\/Date(836438400000)\/",
"Balance": 23
}, {
"Name": "sivadaya",
"Role": "Developer",
"Dept": "Dev",
"Date": "\/Date(836438400000)\/",
"Balance": 23
}, {
"Name": "dayasiva",
"Role": "Developer",
"Dept": "Dev",
"Date": "\/Date(836438400000)\/",
"Balance": 23
}];
var form = $("#theBod");
var foundForm = $(form).find("#approvals");
if (typeof (foundForm) != "undefined") {
// process grid component
var grid = $(foundForm).kendoGrid({
dataSource: {
data: gridData,
schema: {
model: {
fields: {
Name: { type: "string" },
Role: { type: "string" }
}
}
},
pageSize: 10,
},
editable: true,
sortable: true,
columns: [
{
field: "Name",
title: "Name",
},
{
field: "Role",
title: "TheRole"
}
]
});
} else {
alert('no dom element located');
}
});
</script>
</body>
</html>
I have added a jsBin file for you.
The jsBin was not saved correctly, apologies.

Vue.JS Vue-Tables Laravel Relationship

I am using https://github.com/matfish2/vue-tables with Laravel.
This is the vue code:
Vue.use(VueTables.client, {
compileTemplates: true,
highlightMatches: true,
pagination: {
dropdown:true,
chunk:5
},
filterByColumn: true,
texts: {
filter: "Search:"
},
datepickerOptions: {
showDropdowns: true
}
});
new Vue({
el: "#people",
methods: {
deleteMe: function(id) {
alert("Delete " + id);
}
},
data: {
options: {
columns: ['created_at', 'name', 'profession', 'footage_date', 'type', 'link', 'note'],
dateColumns: ['footage_date'],
headings: {
created_at: 'Added',
name: 'Name',
profession: 'Profesion',
footage_date: 'Footage Date',
type: 'Type',
link: 'Link',
note: 'Note',
edit: 'Edit',
delete: 'Delete'
},
templates: {
edit: "<a href='#!/{id}/edit'><i class='glyphicon glyphicon-edit'></i></a>",
delete: "<a href='javascript:void(0);' #click='$parent.deleteMe({id})'><i class='glyphicon glyphicon-erase'></i></a>"
},
},
tableData: [{ InsertDataHere }],
}
});
How do I get the data from DB for tableData ? Vue-resources?
I have a route /api/footage that gives me the following
[
{
"id": 2,
"user_id": 11,
"profession": "profession",
"type": "GvG",
"footage_date": {
"date": "2016-04-01 00:00:00.000000",
"timezone_type": 2,
"timezone": "GMT"
},
"link": "some link",
"note": "description",
"created_at": "1 hour ago",
"updated_at": "2016-04-03 23:06:32"
}
]
Now, User and Footage have a one to many relationship. How would I go about to show the user for each entry as well? ( also the ID for edit and delete )
This is the blade code
<div id="people" class="container">
<v-client-table :data="tableData" :options="options"></v-client-table>
</div>
Thank you in advance.
You can add a ready() function to call the API when the component is built:
ready:function(){
this.$http.get('/api/footage')
.then(function(response){
this.tableData = response.data
}.bind(this))
}
You may have to tweak the code based on the format of your API response. Cleaner version if youre using es2016:
ready(){
this.$http.get('/api/footage')
.then(({data})=>{
this.tableData = data
})
}
You should include vue-resource before this, yes. That allows you to use this.$http
As per #BillCriswell you could do this in the created() function to fire off the API call even sooner
When fetching async data use v-if along with some "loaded" flag on the component to ensure smooth compilation of templates.
<v-client-table v-if="loaded" :data="tableData" :options="options"></v-client-table>
See: https://github.com/matfish2/vue-tables/issues/20, last comment

How can I filter results in typeahead.js using a second variable?

I am trying to filter results using Typeahead.js. I can currently filter the results using a field called activity_title. This works fine.
How can I filter my results by a second value? In this case, I would like to select only the results that have a certain value for activity_level. I need to set this when the typeahead is initialised rather than hard coding it into the Bloodhound initialisation (e.g. I don't want to use url: 'api/activity/&range=1,3')
I have the following valid JSON that I access remotely:
{
"meta": [
{
"name": "activity_id",
"table": "table",
"max_length": 4
},
{
"name": "activity_title",
"table": "table",
"max_length": 91
},
{
"name": "activity_level",
"table": "table",
"max_length": 2
}
],
"detail": [
{
"activity_id": "57",
"activity_title": "Help old ladies to cross the road.",
"activity_level": "2"
},
{
"activity_id": "58",
"activity_title": "Help mum with the washing up.",
"activity_level": "3"
},
{
"activity_id": "59",
"activity_title": "Shine my shoes",
"activity_level": "1"
},
{
"activity_id": "60",
"activity_title": "Put the bins out",
"activity_level": "1"
}
]
}
I set up a Bloodhound instance like this:
var activities = new Bloodhound({
datumTokenizer: function (datum) {
return Bloodhound.tokenizers.whitespace(datum.activity_title);
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
prefetch: {
url: '/api/activity/',
filter: function(data) {
return $.map(data['detail'], function(detail) {
return {
activity_id: detail.activity_id,
activity_title: detail.activity_title,
objective_level: detail.objective_level
};
});
}
}
});
I use Typeahead.js to do a lookup on the data as I type.
$( document ).on( "focus", ".typeahead-init", function() {
// + '&range=' + minimum + ',' + maximum
var minimum = $('#group-level-min-1').val();
var maximum = $('#group-level-max-1').val();
$(this).typeahead({
highlight: true
},
{
name: 'activity_title',
displayKey: 'activity',
source: activities.ttAdapter(),
templates: {
header: '<div class="header-name">Activities</div>',
empty: [
'<div class="empty-message">',
'No activities match your search',
'</div>'
].join('\n'),
suggestion: Handlebars.compile('<div class="typeahead-activity" id="typeahead-activity-{{activity_id}}"><strong>{{objective_level}}</strong> - {{activity_title}}</div>')
}
})
//info on binding selection at https://github.com/twitter/typeahead.js/issues/300
.bind('typeahead:selected', function(obj, datum, name) {
var target = $(this).closest('.activity-container');
var activityId = datum['activity_id'];
var url = '/api/activity/id/'+activityId;
$(target).children('.activity-id').val(activityId);
//http://runnable.com/UllA9u8MD5wiAACj/how-to-combine-json-with-handlebars-js-for-javascript-ajax-and-jquery
var raw_template = $('#activity-output').html();
// Compile that into an handlebars template
var template = Handlebars.compile(raw_template);
// Fetch all data from server in JSON
$.get(url,function(data,status,xhr){
$.each(data,function(index,element){
// Generate the HTML for each post
var html = template(element);
// Render the posts into the page
target.append(html);
});
});
});
$(this).removeClass("typeahead-init");
$(this).focus();
});
This has been cobbled together from several answers on Stackoverflow and others. Any help greatly appreciated.

How get extra information from json?

I use can.Component to dispay JSON on the page.
can.Component.extend({
tag: "some-app",
scope: {
items: new Items.List({}),
displayedItems: function () {
...
return items;
}
},
helpers: {
...
},
events: {
"{Items} created": function (Items, ev, newItem) {
...
}
}
})
How can I get "meta" section of received JSON (below) to the scope or helpers?
{
"data": [
{
"description": "Some text",
"id": 1,
"measurement": "pcs",
"name": "Name of item",
"resource_uri": "/api/v1/item/1/"
},
{....}, {....}
}
],
"meta": {
"limit": 20,
"next": null,
"offset": 0,
"previous": null,
"total_count": 3
}
}
I can get it in console with Items.findAll().then(function(info){console.log(info.attr('meta'))}) , but I'm noob in (can.)js and can't understand how to get it in the place I need.
Instead of this:
scope: {
items: new Items.List({})
}
make the request:
scope: {
items: Items.findAll()
}
There are other ways to do this as well, in the template(not advised), or creating the request in another controller or component and passing in to the instantiation of the component.
If you want more specifics, you would nee to update your question with more details on your model.

Backbone.js: Fetching specific JSON sections

I'm trying to build some foundations for a single page web app in Backbone.js. I've structured my JSON into "screens", with each screen being given an ID.
I would like to be able to render data from specific screens, both for the initial page load, and also after on.click events.
I've been trying to pass in an ID when I create a new model instance, but so far I'm getting erratic results: It is rendering different sections of JSON than I indicate, or simply rendering all of it. Any pointers on how to select a specific 'screen' (via its ID) would be greatly appreciated.
Here's an indicative JSON sample code:
[{
"id": 0,
"options": [
{ "text": "Tackle player", "next": [ 0, 1 ] },
{ "text": "Dribble the ball", "next": [ 1 ] }
],
"results": [
{ "text": "Tackle successful", "next": [ 0 ] },
{ "text": "You are close enough to shoot", "next": [ 0, 1 ] }
]
},
{
"id": 1,
"options": [
{ "text": "BLAH", "next": [ 0, 1 ] },
{ "text": "BLAH2", "next": [ 1 ] }
],
"results": [
{ "text": "BLAH3", "next": [ 0 ] },
{ "text": "BLAH4", "next": [ 0, 1 ] }
]
}
]
And here's my backbone code:
var app = app || {};
app.Screen = Backbone.Model.extend({
url: '/api',
parse: function(response){
return response;
}
});
var Screens = Backbone.Collection.extend({
model: app.Screen,
url: '/api'
});
app.AppView = Backbone.View.extend({
initialize: function(parameters){
this.listenTo(this.collection, 'add', this.addOne);
},
render: function(){
this.$el.html("test");
this.addAll();
return this;
},
addAll: function(){
this.collection.each(this.addOne, this);
},
addOne: function(model){
var screen_view = new app.ScreenView({
model: model});
screen_view.render();
this.$el.append(screen_view.el);
}
});
app.ScreenView = Backbone.View.extend({
template: _.template(
'<ul id="options">' +
'<% _.each(options, function(info) { %>' +
'<li id="optionA"><%= info.text %></li>' +
'<% }); %>' +
'</ul>'
),
initialize: function(options) {
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'destroy', this.remove);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
$(function() {
var screen = new app.Screen({id:0}); //CURRENTLY BEHAVING VERY STRANGELY - change ID to 1 and you will get id 0 expected responses
app.screenCollection = new Screens([screen]);
app.screenCollection.fetch();
new app.AppView({
collection: app.screenCollection, el: $('.gameWrapper')
}).render();
});
Why not just reference the array index in your parsed JSON data where screens is the array of screens:
var screen = new app.Screen(screens[index]);
The alternative is to use something like JSONPath to access the object based on the id which is a little more complicated.

Categories