I try to build a simple form-like UI where I want to dynamically add rows of multiple TextFields. I built a custom component with XML and JS, because there is some interaction in these TextFields (start, end, duration).
I am a starter in NativeScript, but I got my databinding and event-structure going. I was able to add my custom component via ui/builder. Where I am totally stuck is getting "options/arguments" to this component-instance.
What I try to accomplish is: Adding my custom component and giving it some data which should be filled into the components Obsverable. At a later (save) action I want to be able to retrieve these data from my "main" view.
My structure is something like this:
main.js
exports.loaded = function(args) {
var page = args.object;
page.bindingContext = model;
var outerContainer = page.getViewById('outerContainer');
var vma = builder.load({
path: 'widgets/durationRow',
name: 'durationRow'
});
outerContainer.addChild(vma);
};
custom component "durationRow.js"
var componentModel = new Observable({
label: "",
start: "",
end: "",
duration: ""
});
exports.onLoad = function(args){
var container = args.object;
container.bindContext = componentModel;
};
I tried to export the componentModel via exports.componentModel and address vma.componentModel from main. But vma is a view not the "JS-module".
I am really stuck and didn't find any example doing something like my scenario.
Can anyone point me in the right direction how I can do this stuff the right (or any) way?
thanks!
So the answer to that problem is quite simple: read the documentation :)
There is an option attributes which can be used exactly for that. I ended up with this:
var row = builder.load({
path: 'widgets/durationRow',
name: 'durationRow',
attributes: {
bindingContext: bindingModel
}
});
container.addChild( row );
And in the loaded widget I'll get my "inserted" model as bindingContext:
exports.onLoad = function(args){
page = args.object;
var model = page.bindingContext;
};
I hope this helps!
Related
I'm in the initial stages of developing a plugin that will allow the user to insert placeholder elements into HTML content that will be processed server-side and used to incorporate some simple logic into a generated PDF document. To this end, I'm attempting to insert a custom element that I've defined using the web components API.
class NSLoop extends HTMLElement {
constructor() {
super();
}
get source() {
return this.getAttribute('source');
}
get as() {
return this.getAttribute('as');
}
}
window.customElements.define('ns-loop', NSLoop);
The contents of loopediting.js:
import Plugin from "#ckeditor/ckeditor5-core/src/plugin";
import Widget from "#ckeditor/ckeditor5-widget/src/widget";
import {viewToModelPositionOutsideModelElement} from "#ckeditor/ckeditor5-widget/src/utils";
import LoopCommand from "./loopcommand";
export default class LoopEditing extends Plugin {
static get requires() {
return [Widget];
}
constructor(editor) {
super(editor);
}
init() {
this._defineSchema();
this._defineConverters();
this.editor.commands.add('loop', new LoopCommand(this.editor));
this.editor.editing.mapper.on('viewToModelPosition', viewToModelPositionOutsideModelElement(this.editor.model, viewElement => viewElement.is('element', 'ns-loop')));
}
_defineSchema() {
const schema = this.editor.model.schema;
schema.register('loop', {
isBlock: false,
isLimit: false,
isObject: false,
isInline: false,
isSelectable: false,
isContent: false,
allowWhere: '$block',
allowAttributes: ['for', 'as'],
});
schema.extend( '$text', {
allowIn: 'loop'
} );
schema.extend( '$block', {
allowIn: 'loop'
} );
}
_defineConverters() {
const conversion = this.editor.conversion;
conversion.for('upcast').elementToElement({
view: {
name: 'ns-loop',
},
model: (viewElement, {write: modelWriter}) => {
const source = viewElement.getAttribute('for');
const as = viewElement.getAttribute('as');
return modelWriter.createElement('loop', {source: source, as: as});
}
});
conversion.for('editingDowncast').elementToElement({
model: 'loop',
view: (modelItem, {writer: viewWriter}) => {
const widgetElement = createLoopView(modelItem, viewWriter);
return widgetElement;
}
});
function createLoopView(modelItem, viewWriter) {
const source = modelItem.getAttribute('source');
const as = modelItem.getAttribute('as');
const loopElement = viewWriter.createContainerElement('ns-loop', {'for': source, 'as': as});
return loopElement;
}
}
}
This code works, in the sense that an <ns-loop> element is successfully inserted into the editor content; however, I am not able to edit this element's content. Any keyboard input is inserted into a <p> before the <ns-loop> element, and any text selection disappears once the mouse stops moving. Additionally, it is only possible to place the cursor at the beginning of the element.
If I simply swap out 'ns-loop' as the tag name for 'div' or 'p', I am able to type within the element without issue, so I suspect that I am missing something in the schema definition to make CKEditor aware that this element is "allowed" to be typed in, however I have no idea what I may have missed -- as far as I'm aware, that's what I should be achieving with the schema.extend() calls.
I have tried innumerable variations of allowedIn, allowedWhere, inheritAllFrom, isBlock, isLimit, etc within the schema definition, with no apparent change in behaviour.
Can anyone provide any insight?
Edit: Some additional information I just noticed - when the cursor is within the <ns-loop> element, the Heading/Paragraph dropdown menu is empty. That may be relevant.
Edit 2: Aaand I found the culprit staring me in the face.
this.editor.editing.mapper.on('viewToModelPosition', viewToModelPositionOutsideModelElement(this.editor.model, viewElement => viewElement.is('element', 'ns-loop')));
I'm new to the CKE5 plugin space, and was using other plugins as a reference point, and I guess I copied that code from another plugin. Removing that code solves the problem.
As noted in the second edit, the culprit was the code,
this.editor.editing.mapper.on('viewToModelPosition', viewToModelPositionOutsideModelElement(this.editor.model, viewElement => viewElement.is('element', 'ns-loop')));
which I apparently copied from another plugin I was using for reference. Removing this code has solved the immediate problem.
I'll accept this answer and close the question once the 2-day timer is up.
I am pretty new to SAPUI5 and have a little problem with my App. I set up a Master-Detail Application which shows a list of Customers in the Master View. My goal is to filter this list to any property of my oData Service (I am using the Northwind Webservice).
Here you can see a snippet with the list of my view (MasterView.xml):
<List
id="list"
items="{
path: '/Customers',
sorter: {
path: 'CompanyName',
descending: false
},
groupHeaderFactory: '.createGroupHeader'
}"
busyIndicatorDelay="{masterView>/delay}"
noDataText="{masterView>/noDataText}"
mode="{= ${device>/system/phone} ? 'None' : 'SingleSelectMaster'}"
growing="true"
growingScrollToLoad="true"
updateFinished="onUpdateFinished"
selectionChange="onSelectionChange">
<infoToolbar>
<Toolbar
active="true"
id="filterBar"
visible="{masterView>/isFilterBarVisible}"
press="onOpenViewSettings">
<Title
id="filterBarLabel"
text="{masterView>/filterBarLabel}" />
</Toolbar>
</infoToolbar>
<items>
<ObjectListItem
type="{= ${device>/system/phone} ? 'Active' : 'Inactive'}"
press="onSelectionChange"
title="{CompanyName}"
numberUnit="{CustomerID}">
</ObjectListItem>
</items>
</List>
And here is what I have done in my controller (Master.controller.js):
onInit : function () {
// Control state model
var oList = this.byId("list"),
oViewModel = this._createViewModel(),
// Put down master list's original value for busy indicator delay,
// so it can be restored later on. Busy handling on the master list is
// taken care of by the master list itself.
iOriginalBusyDelay = oList.getBusyIndicatorDelay();
// Tryout Filter
var equals = FilterOperator.EQ;
var aFilterFoo = [];
aFilterFoo.push(new Filter("Country", equals, "Germany"));
var oBinding = oList.getBinding("items");
oBinding.filter(aFilterFoo);
// End tryout Filter
this._oList = oList;
// keeps the filter and search state
this._oListFilterState = {
aFilter : [],
aSearch : []
};
this.setModel(oViewModel, "masterView");
// Make sure, busy indication is showing immediately so there is no
// break after the busy indication for loading the view's meta data is
// ended (see promise 'oWhenMetadataIsLoaded' in AppController)
oList.attachEventOnce("updateFinished", function(){
// Restore original busy indicator delay for the list
oViewModel.setProperty("/delay", iOriginalBusyDelay);
});
this.getView().addEventDelegate({
onBeforeFirstShow: function () {
this.getOwnerComponent().oListSelector.setBoundMasterList(oList);
}.bind(this)
});
this.getRouter().getRoute("master").attachPatternMatched(this._onMasterMatched, this);
this.getRouter().attachBypassed(this.onBypassed, this);
},
This was all set up automatically by SAP Web IDE. I only changed the code inbetween the comments //Tryout Filter and //End Tryout
When i want to run my application, debugger says: "Cannot read property 'filter' of undefined" because oBinding is undefined.
I hope any of you can help me.
Thanks a lot and best regards
Solved this problem by getting into the sap lifecycle methods. onBeforeRendering() was the function I used in my Master.Controller.js
Long story short: I'm trying to add a front-end app to my portfolio site that uses React. I would like to integrate the app into the component as it renders. What I have setup right now is:
React component:
class Giphy extends Component {
constructor(props) {
super(props);
this.state = {src: 1}
this.handleClick = this.handleClick.bind(this);
}
handleClick(event) {
this.setState({src: event.target.value})
}
componentDidMount() {
const script = document.createElement("script");
script.type = "text/javascript";
script.src = "/scripts/giphyLogic.js";
document.body.appendChild(script);
}
...and a bunch of stuff in the render() method that doesn't matter
the script that I want to load involves a bunch of jQuery and simple JS stuff.
function displayButtons() {
$("#buttons").empty();
for (i=0; i<buttonArray.length; i++){
var a = $("<button type='button' class='btn btn-info'>");
var btnID = buttonArray[i].replace(/\s+/g, "+")
a.attr("id", btnID);
a.text(buttonArray[i]);
$("#buttons").append(a);
}
}
$("#addButton").on("click", function() {
var newButton = $(".form-control").val();
buttonArray.push(newButton);
displayButtons();
})
function displayGIFs() {
$(".btn-info").on("click", function() {
$("#resultsContainer").empty();
var subject = $(this).attr("id");
var giphyURL = "http://api.giphy.com/v1/gifs/search?q=" + subject + "&api_key=dc6zaTOxFJmzC";
$.ajax({ url: giphyURL, method: "GET"}).done(function(res) {
for (t=0; t<25; t++) {
var rating = res.data[t].rating;
var image = $("<img>");
var imgURLmoving = res.data[t].images.fixed_height.url;
var imgURLstill = res.data[t].images.fixed_height_still.url;
image.attr("src", imgURLstill);
image.attr("data-still", imgURLstill);
image.attr("data-moving", imgURLmoving);
image.attr("data-state", "still")
image.addClass("gif");
$("#resultsContainer").append("<p>" + rating + "</p");
$("#resultsContainer").append(image);
}
})
$(document.body).on("click", ".gif", function() {
var state = $(this).attr("data-state");
if (state === "still") {
$(this).attr("src", $(this).data("moving"));
$(this).attr("data-state", "moving");
} else {
$(this).attr("src", $(this).data("still"));
$(this).attr("data-state", "still");
}
})
})
}
displayButtons();
displayGIFs();
This all works on a standalone HTML document, but I can't seem to get the script to work properly. When the component loads and I inspect the page,
<script type="text/javascript" src="/scripts/giphyLogic.js"></script>
is there under the bundle.js script tag, but nothing from the script happens.
I get an "Uncaught SyntaxError: Unexpected token <" error that is attributed to giphyLogic.js:1 even though in the actual .js file, that line is blank. I've looked around, and this apparently happens when a file is included that doesn't exist, but the file is definitely there. I've double checked the path (by including an image in the same folder and loading the image successfully on the page) and it's correct.
Is there a way to resolve this, or am I going to have to create methods within the React component that I'm creating?
Do not mix jQuery and react. Learn how to use react properly by reading the well-written documentation. They can guide you through the many examples to get a simple app up and running.
Once again, do NOT use jQuery and react. jQuery wants to manually manipulate the DOM, and react manages a virtual DOM. The two will conflict more often than not, and you're going to have a bad time. If you have a very deep understanding of react, there are very few scenarios in which you could maybe use some jQuery, but nearly all of the time, it is to be avoided at all costs.
Obviously things like $.ajax() are fine, but for anything dealing with DOM manipulation, stay away. And if you only end up using jQuery for $.ajax() calls... you should switch to a leaner library like axios or use the native fetch API.
I am trying to test this piece of HTML generated by React JS:
<div class="media-img">
<img src='images-name.jpg' />
</div>
As you can see, the image has no class name, and I can't put one on. I am using Jasmine to try and test that the src of the image is correct. But I can't seem to get hold of the image element in React. In jQuery this would be really easy, I could use $('.media-img img'), is there a way to traverse the DOM in React?
This is what I have so far:
var React = require('react'),
TestUtils = React.addons.TestUtils,
Component = require('component'),
renderedComponent;
function renderComponent(data) {
data = data || {};
renderedComponent = TestUtils.renderIntoDocument(
React.createElement(Component, data)
);
}
var imageURL = 'http://lorempixel.com/g/400/200/',
image,
imageSection;
renderComponent({
src: imageURL
});
imageSection = TestUtils.scryRenderedComponentsWithType(renderedComponent, 'media-img');
image = TestUtils.findRenderedDOMComponentWithTag(imageSection, 'img');
expect(image.src).toBe(imageURL);
But it isn't working. It seems that you can't pass a React component into another as I've tried to do at the bottom. So how can you traverse the virtual DOM?
I found an answer. Instead of attempting to pass a React component into another to test a subsection of the rendered DOM, I looked through the entire rendered DOM for 'img'. Normally this would be a bit inefficient as the rendered DOM could be huge and full of 'img' tags, but as this is a unit test with limited data, the rendered DOM is small and predictable. I used this:
image = TestUtils.findRenderedDOMComponentWithTag(renderedContributor, 'img');
Then I didn't need the 'imageSection' variable at all. Then I could call the DOM Node, then look at its 'src', by adding:
image.getDOMNode().src;
So my complete amended code looks like this:
var React = require('react'),
TestUtils = React.addons.TestUtils,
Component = require('component'),
renderedComponent;
function renderComponent(data) {
data = data || {};
renderedComponent = TestUtils.renderIntoDocument(
React.createElement(Component, data)
);
}
var imageURL = 'http://lorempixel.com/g/400/200/',
image;
renderComponent({
src: imageURL
});
image = TestUtils.findRenderedDOMComponentWithTag(renderedComponent, 'img');
expect(image.getDOMNode().src).toBe(imageURL);
I've done the Tutorial for building Fiori-like UIs with SAPUI5 and tried adding a sort-function.
The Dropdown-Box is filled with the Names of the JSON-Model-Data:
{
"SalesOrderCollection": [
{
"SoId": "300000097",
"ApprovalStatus": "",
"Status": "Initial",
"ConfirmationStatus": "",
"BillingStatus": "Initial",
...
} ...
I implemented this function in my View Controller to fill the dropdown:
fillDropdown : function(evt) {
// Get current view and dropdownbox
var oView = this.getView();
var oDropdown = oView.byId("ddbox");
// Get names of nodes in model
var metaArray = Object.getOwnPropertyNames(oView.getModel()
.getProperty("/SalesOrderCollection/0"));
// Add every name to dropdownbox
var arrayLength = metaArray.length;
for ( var i = 0; i < arrayLength; i++) {
oDropdown.addItem(new sap.ui.core.ListItem("item" + i)
.setText(metaArray[i]));
}
}
Finally, here comes my problem:
How can I run this function automatically when the View gets rendered? I know the lifecycle hook functions onInit & onBeforeRendering but I can't use them in my XML-View:
I can register the eventhandler for UI-Elements like here:
<uicom:DropdownBox id="ddbox" editable="true" change="handleListSort">
But not for the View or the Page like I tried here:
<Page title="myPage" onBeforeRendering="fillDropdown">
<core:View controllerName="sap.ui.demo.myFiori.view.Master" (...) onBeforeRendering="fillDropdown">
Possible dirty workaround: Call the function when clicking on the Sort-Button in the IconTabBar
<IconTabBar select="fillDropdown">
Thanks for your help!
Update
If I used a JavaScript-View instead of an XML-View, I could simply implement my fillDropdown function in the onAfterRendering function of my View.
But why do XML Views not throw lifecycle hook events?
Update 2
I also can't use the onAfterRendering of my View Controller; oView.getModel().getProperty("/SalesOrderCollection/0"); returns no object because the content of my model is (whyever) only available after the hook functions.
onInit, onBeforeRendering, onAfterRendering, onExit are lifecycle events. You do not need to register from view. Your function should be called when you implement independent from the view type. Please have a look at here http://jsbin.com/hiyanuqu/1/edit
BTW, there is a similar built in control in sap.m library called Input with Suggestion.
https://openui5.hana.ondemand.com/test-resources/sap/m/demokit/explored/index.html#/sample/sap.m.sample.InputSuggestionsCustomFilter