Pass arguments to custom component - nativescript

I created component and it will be nice to use it ;) but I don't know how
to pass arguments;
Use:
<Page xmlns="http://www.nativescript.org/tns.xsd"
xmlns:masterMenu="includes/customComponents/masterMenu">
<masterMenu:masterMenu selected="1"/>
</Page>
How can I catch selected in component? Currently I have only masterMenu.xml and masterManu.css files.
[ EDIT ]
Let say I have component named question (pseudo code):
xml
< StackLayout loaded="loaded">
< Label text="Question: loremipsum" />
< Label text="Answer 1" id="answer1" />
< Label text="Answer 2" id="answer2"/>
< Label text="Answer 3" id="answer3"/>
< /StackLayout>
js
exports.loaded = function(args) {
var answerNo = args.answerNo,
page = args.object;
page.getElementById(page, "answer"+answerNo).addClass('correct')
}
used :
< Page xmlns="http://www.nativescript.org/tns.xsd"
xmlns:question="includes/customComponents/question">
< question:question answerNo="2" />
< question:question answerNo="1" />
< question:question answerNo="3" />
< /Page>

In your javascript you would access it like this:
var menu = page.getViewById("idOfMenuComponent");
menu.selected;
That is assuming it's available to be accessed. That would all depend on how you have this component created. If it's not available you need to add the property to your component. Have a look at the <Button> module to see how this can be done.

Related

NativeScript index in the list of Repeater items

I am trying to get and use index of the list in NativeScript, in this case using Repeater.
Here is my code:
<ScrollView>
<Repeater items="{{ sviArtikli }}" itemTemplateSelector="$index">
<Repeater.itemsLayout>
<WrapLayout orientation="horizontal"/>
</Repeater.itemsLayout>
<Repeater.itemTemplate>
<Card:CardView margin="10" width="45%" height="250" item="{{ id }}" myIndex="{{ $index }}" tap="detaljiArtikla">
<grid-layout rows="250, *" columns="*, 50">
<Image loaded="slikaUcitana" class="lista-katalozi-slika" id="slicice" opacity="1" stretch="aspectFill" colSpan="3" row="0" src="{{ 'https://url_location/' + slika_image }}" />
</grid-layout>
</Card:CardView>
</Repeater.itemTemplate>
</Repeater>
</ScrollView>
The part with myIndex="{{ $index }}" does not work as index can not be calculated in Repeater.
Is there a way I can get index for every item in repeater? - ListView works, but I can not use listview in this case.
I have created a plyground for you here. You can pass the unique ID in your dataprovider and can access in item.
P.S. There is an open Feature-request to support index in repeater, you can also cast your vote there.
Accessing item index is not yet supported in Repeater, but if it's crucial for you, you could extend the Repeater class to support index.
indexed-repeater.ts (tested against tns-core-modules v6.0.1)
import { Repeater } from "tns-core-modules/ui/repeater";
import { CSSType } from "tns-core-modules/ui/layouts/layout-base";
import { parse } from "tns-core-modules/ui/builder";
export module knownTemplates {
export const itemTemplate = "itemTemplate";
}
#CSSType("Repeater")
export class IndexedRepeater extends Repeater {
public refresh() {
if (this.itemsLayout) {
this.itemsLayout.removeChildren();
}
if (!this.items) {
return;
}
const length = this.items.length;
for (let i = 0; i < length; i++) {
const viewToAdd = this.itemTemplate ? parse(this.itemTemplate, this) : (<any>this)._getDefaultItemContent(i);
const dataItem = (<any>this)._getDataItem(i);
viewToAdd.bindingContext = {
item: dataItem,
index: i
};
this.itemsLayout.addChild(viewToAdd);
}
(<any>this)._isDirty = false;
}
}
Sample Usage
<ScrollView xmlns:comps="components/indexed-repeater">
<comps:IndexedRepeater items="{{ items }}">
<comps:IndexedRepeater.itemTemplate>
<Label text="{{ index + ' ' + item.name }}" class="m-10 h3" />
</comps:IndexedRepeater.itemTemplate>
</comps:IndexedRepeater>
</ScrollView>
Playground Sample
Hi guys thanks and it helped. What I did, without changing any code base is pushing locally generated ID's into JSON element and used them for indexes.
Now json has { "my_index": "0" ..... } which I change in for loop when fetch is over. I am using those indexes just to scroll the view on certain element in the list.
http.getJSON("https://....")
.then(function (response){
for(var i = 0; i < response.length; i++) {
response[i].my_index = i; // change response on the fly.
// do something with response....
}
}, function(error) {
console.error(JSON.stringify(error));
})
I guess this could help to someone as well.

Nativescript how to make a if condition in View XML

Is there anyway to do a if condition in the XML file of nativescript? (without angular)
if
<Card:CardView class="cardStyle" margin="10" elevation="40" radius="5">
<Slider value="18" minValue="5" maxValue="30" />
</Card:CardView>
else
<Card:CardView class="cardStyle" margin="10" elevation="40" radius="5">
<Label text="Example" class="h3" margin="10" />
</Card:CardView>
What you could do is use a boolean property (which in its get function has the condition you want) and bind it to the visibility of the CardView.
Looks like you can show/hide from the template file using visibility:
In XML file: ie. 'sample-page.xml'
<Button text="{{ isShowing ? 'Hide Me' : 'Show Me' }}" tap="toggleShowing"/> <!-- Notice missing interpolation in Button tag -->
<Label text="Showing Hidden Element" visibility="{{ isShowing ? 'visible' : 'collapsed' }}"/>
In Page file: ie. 'sample-page.ts'
let model = new ViewModel();
// Event handler for Page 'loaded' event attached in main-page.xml
export function pageLoaded(args: observable.EventData) {
const page = <Page>args.object;
page.bindingContext = model;
}
export function toggleShowing() {
model.set('isShowing', !model.get('isShowing'));
}
In View Model: ie. 'sample-view-model.ts'
isShowing:boolean = false;
The only way I found to do to do this was to use the navigatingTo event of the page:
export function navigatingTo(args: EventData) {
let page = <Page>args.object;
var myLayout = <StackLayout>page.getViewById("myLayout");
if (condition) {
let segmentedBar = new SegmentedBar;
...
myLayout.addChild(segmentedBar);
}
else {
let button: Button = new Button;
...
myLayout.addChild(button);
}
No possibility in the template file :-/
It can be done using ng-container.For example
<ng-container *ngIf="true">Your logic here....</ng-container>

autocapitalize="words" broken on mobile Safari in iOS 8,9 - Is there a workaround?

The <input> attribute autocapitalize="words" is broken in mobile Safari under iOS 8,9 with the default iOS keyboard. It uppercases the first 2 letters of the field, not the first letter of each word.
Official documentation says is supported: https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/Attributes.html
To test, open the following field on iOS emulator or real device:
First name: <input type="text" autocorrect="off" autocapitalize="words" value="First Name">
You can use https://www.w3schools.com/tags/tryit.asp?filename=tryhtml_form_submit to test, or this snippet on iOS 8 or 9:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Test autocapitalize</title>
</head>
<body>
<form>
<label for="words">autocapitalize="words"</label>
<input type="text" autocapitalize="words" name="text1" id="words" /><br />
<label for="sentences">autocapitalize="sentences"</label>
<input type="text" autocapitalize="sentences" name="text2" id="sentences" /><br />
<label for="none">autocapitalize="none"</label>
<input type="text" autocapitalize="none" name="text3" id="none" />
</form>
</body>
</html>
I'm amazed this has been present since 8.x and has passed under the radar.
Is there a known workaround?
Update 10/13:
iPhone 6s+ Safari completely ignores any HTML attribute set on the input field.
There seems to be a workaround for this issue if you are willing to (temporarily) include this library: https://github.com/agrublev/autocapitalize. It does however require jQuery, so might not be ideal on a mobile device. I've created this small piece of code which does the same thing just for words without the use of jQuery. It can ofcourse be extented to also include other cases.
The example below also capitalizes the words initially on page ready, instead of just on the 'keyup' event. I've tested the code on several devices and haven't gotten an error. But feel free to comment if something doesn't work or you feel something can be done better.
Note that the 'domReady' function I added works for IE9 and up. Please see this if you require support for an older version.
// Create one global variable
var lib = {};
(function ( lib ) {
lib.autocapitalize_element = function (element) {
var val = element.value.toLowerCase();
var split_identifier = " ";
var split = val.split(split_identifier);
for (var i = 0; i < split.length; i ++) {
var v = split[i];
if ( v.length ) {
split[i] = v.charAt(0).toUpperCase() + v.substring(1);
}
};
val = split.join(split_identifier);
element.value = val;
}
lib.autocapitalize_helper = function(element) {
element.onkeyup = function(e) {
var inp = String.fromCharCode(e.keyCode);
if (/[a-zA-Z0-9-_ ]/.test(inp)) {
lib.autocapitalize_element(element);
}
};
}
lib.autocapitalize = function() {
var elements = document.querySelectorAll("input[autocapitalize], textarea[autocapitalize]");
for(var i = 0; i < elements.length; i++) {
lib.autocapitalize_helper(elements[i]);
lib.autocapitalize_element(elements[i]);
}
}
lib.domReady = function(callback) {
document.readyState === "interactive" || document.readyState === "complete" ? callback() : document.addEventListener("DOMContentLoaded", callback);
};
}( lib ));
// This function gets called when the dom is ready. I've added it to the lib variable now because I dislike adding global variables, but you can put it anywhere you like.
lib.domReady(function() {
lib.autocapitalize();
});

Titanium Alloy: Can't Get Row Data To Print To A New Row It Is Always Overwriting The Previously Written Data

I was hoping one of you could help me figure out why my row data keeps writing to the same row and overwriting the previously written data instead of starting on a new row after every iteration. Here's what I have so far:
This alloy.js file below gets the information I need for the app from the server and passes it to the formateData() function in the index.js file.
alloy.js
GetDataRequest("http:somejsondata/data.json");
function GetDataRequest(url)
{
var xhr = Ti.Network.createHTTPClient({
onload: function(e)
{
Alloy.Globals.formateAnimalData(JSON.parse(xhr.responseText));
},
onerror: function(e) {
Ti.API.debug("STATUS: " + this.status);
Ti.API.debug("TEXT: " + this.responseText);
Ti.API.debug("ERROR: " + e.error);
alert('There was an error retrieving the remote data. Try again.');
},
timeout:5000
});
xhr.open("GET", url);
xhr.send();
}
index.js
var appData = [];
Alloy.Globals.formateAnimalData = function(data)
{
console.log("I was also called");
for (var i = 1; i <=8; i++)
{
var row = {
name: data.animals[i].name,
animal: data.animals[i].animal,
food: data.animals[i].food
};
appData.push(Alloy.createController('index', row).getView());
//console.log(appData);
}
};
var args = arguments[0] || {};
$.name.text = args.name || '';
$.animal.text = args.animal || '';
$.food.text = args.food || '';
$.table.setData(appData);
$.index.open();
index.xml
<Alloy>
<TabGroup id='index'>
<Tab title="Tab 1" icon="KS_nav_ui.png">
<Window id='main' class='' title="Zoo">
<TableView>
<TableViewRow id='table'>
<Label id ='name' class ='label'></Label>
<Label id ='animal' class ='label'></Label>
<Label id ='food' class ='label'></Label>
</TableViewRow>
</TableView>
</Window>
</Tab>
</TabGroup>
</Alloy>
I should note that the:
$.table.setData(appData);
line in the index.js file doesn't work. The data still prints to the screen even if this line is not in there. The reason for this I think is because the following line:
appData.push(Alloy.createController('index', row).getView());
is not pushing a row of objects into the appData array and infact when I log the contents of the appData array to the screen I get:
(
[INFO] : "[object index]",
[INFO] : "[object index]",
[INFO] : "[object index]",
[INFO] : "[object index]",
[INFO] : "[object index]",
[INFO] : "[object index]",
[INFO] : "[object index]",
[INFO] : "[object index]"
[INFO] :
)
I am just trying to get it so that it prints the animals: Name, Type and Food Preference to row 1 and the next animals Name, Type and Food Preference to row 2 and so on.
It's an ugly way to do. In this case, your main problem is this line :
appData.push(Alloy.createController('index', row).getView());
using getView() on your index controller will give you the top-level view, in this case the TabGroup of id index.
If you really want to keep it that way, you have to supply 'table' as arguments to ask for the TableViewRow
appData.push(Alloy.createController('index', row).getView('table'));
However, you are assigning to the newly created index controller and not to the original one instantiated by Alloy at starting....
You can try to create another controller named for instance row, and create an associated xml view this way :
row.xml
<Alloy>
<TableViewRow id="row">
<Label id="name" class="label"></Label>
<Label id="animal" class="label"></Label>
<Label id="food" class="label"></Label>
</TableViewRow>
</Alloy>
row.js
var args = arguments[0] || {};
$.row.text = args.name || '';
$.row.text = args.animal || '';
$.row.text = args.food || '';
And, refactor the rest the following way. I'm using events to communicate easily with the original instantiated index controller.
index.xml
<Alloy>
<TabGroup id='index'>
<Tab title="Tab 1" icon="KS_nav_ui.png">
<Window id='main' class='' title="Zoo">
<TableView id="table"/>
</Window>
</Tab>
</TabGroup>
</Alloy>
index.js
/* Store rows somewhere */
$._rows = [];
/* Append row each time it's needed */
$.addEventListener('newrow', function (newrowEvent) {
$._rows.push(newrowEvent.row);
});
/* Refresh only on demand */
$.addEventListener('refreshrows', function () {
$.table.setData($._rows);
});
$.index.open();
alloy.js
/* ...
Your previous function with the call to formateAnimal updated
*/
function formateAnimalData(data) {
for (var i = 1; i <=8; i++) {
var rowContent = {
name: data.animals[i].name,
animal: data.animals[i].animal,
food: data.animals[i].food
};
Ti.app.fireEvent('newrow', {
row: Alloy.createController('row', rowContent).getView()
});
}
Ti.app.fireEvent('refreshrows');
};
This should be a workaround a bit nicer. Keep in mind that, when you create a controller, the code that you put in the file's scope is executed only once; Also, each time you're calling createController, you'll get a new instance of that kind of controller; Do not try to modify other instances from the new instance.. This is weird and inefficient.
Finally, avoid putting bloc scopes above functions or control structures.
for ( ...; ...; ...)
{
}
or
function ()
{
}
are bad ideas. You're in JavaScript and semicolons aren't necessary for each statement or at least, the compiler will not complain and you're likely to obtain strange silent errors ...

How to add required validator to ajax AsyncFileUpload?

How to add client side required validator to asyncfileupload ,to enforce user to select file before submitting the page.
You could also set the text of a the hidden textbox in a server side method using C# or VB, rather than a client side Javascript or JQuery function.
protected void afu_UploadedComplete(object sender, AjaxControlToolkit.AsyncFileUploadEventArgs e)
{
afu.SaveAs(Server.MapPath("Uploads\\") + e.FileName);
txt.Text = e.FileName;
}
I use a RequiredFieldValidator that validates an invisible TextBox. The TextBox is filled with an arbitrary text in the OnClientUploadComplete function.
The only thing you cannot is setting the focus when it is validated.
The example uses jQuery.
<ajaxToolkit:AsyncFileUpload runat="server" ID="afu" ClientIDMode="AutoID" UploaderStyle="Traditional" OnClientUploadComplete="asyncUploadComplete" OnClientUploadStarted="asyncUploadStarted" />
<asp:RequiredFieldValidator runat="server" ID="rfv" ControlToValidate="txt" Text="The file is required!" SetFocusOnError="false" />
<asp:TextBox runat="server" ID="txt" style="display:none" MaxLength="0" />
<script type="text/javascript">
// AsyncFileUpload - OnClientUploadComplete
function asyncUploadComplete(sender, args) {
// Assemble info of uploaded file
var contentType = args.get_contentType();
var info = args.get_length() + " bytes";
if (contentType.length > 0) {
info += " - " + contentType;
}
info += " - " + args.get_fileName();
// Put info in the first input field after the AsyncFileUpload control
var source = $(sender.get_element());
source.nextAll('input').val(info);
// Validate immediately
ValidatorEnable(source.nextAll('span')[0], true);
}
// AsyncFileUpload - OnClientUploadStarted
function asyncUploadStarted(sender, args) {
// Clear the first input field after the AsyncFileUpload control
var source = $(sender.get_element());
source.nextAll('input').val('');
}
</script>

Resources