Setting the values in multi-select isteven of angular js - angularjs

I'm trying to use Angularjs multi-select into my project.
The following html is my multi-select div.
<div
multi-select
input-model="marks"
output-model="filters.marks"
button-label="name"
item-label="name"
tick-property="ticked"
selection-mode="multiple"
helper-elements="all none filter"
on-item-click="fClick( data )"
default-label="Select marks"
max-labels="1"
max-height="250px"
>
</div>
I know that I can use $scope.marks=data in the controller.
But the problem is $scope.marks is a global variable which I couldn't change..
Is there any way to set the values in multi-select without using the input-model?

Well, doing some tests here, i could get something with multiselecting:
var languages = ["C#", "Java", "Ruby", "Go", "C++", "Pascal", "Assembly"]; //The items.
var myApp = angular.module('myApp', []);
myApp.controller('MyCtrl', function($scope) {
$scope.marks = {};
for (lang in languages) {
$scope.marks[lang] = {
name: languages[lang],
marked: false
};
}
$scope.marks[3].marked = true; //mark "Go" and "C++" by default.
$scope.marks[4].marked = true;
$scope.theMarkedOnes = function() {
outp = "";
for (m in $scope.marks) {
if ($scope.marks[m].marked)
outp += $scope.marks[m].name + ", ";
}
if (outp.length == 0) {
return "(none)";
} else {
return outp.substr(0, outp.length - 2);
}
}
$scope.setMark = function(markone) {
markone.marked = !markone.marked;
}
})
*,
*:before,
*:after {
box-sizing: border-box;
}
body {
font-family: sans-serif;
font-size: 0.7em;
}
::-webkit-scrollbar {
width: 7px;
}
::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.5);
}
.multiselector {
background-color: #CCCCCC;
overflow-y: scroll;
width: 17em;
height: 13em;
border-radius: 0.7em;
}
.multiselector .item {
cursor: pointer;
padding: 0.2em 0.3em 0.2em 0.0em;
}
.itemtrue {
background-color: #9999AA;
}
.msshow {
background-color: #cccccc;
border-radius: 0.7em;
margin-top: 1em;
padding: 0.6em;
width: 17em;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MyCtrl">
<div class="multiselector">
<div ng-repeat="mark in marks" class="item item{{mark.marked}}" ng-click="setMark(mark)">{{mark.name}}</div>
</div>
<div class="msshow"> <b>Selected:</b> {{theMarkedOnes()}}</div>
</div>

Set & Get selected values, name and text of Angularjs isteven-multi-select
<div isteven-multi-select
input-model="marks"
output-model="filters.marks"
button-label="name"
item-label="name"
tick-property="ticked"
selection-mode="multiple"
helper-elements="all none filter"
on-item-click="fClick( data )"
default-label="Select marks"
max-labels="1"
max-height="250px">
</div>
Add items
$scope.marks= [
{ name: 'Mark I', value: 'Mark i', text: 'This is Mark 1', ticked: true },
{ name: 'Mark II', value: 'Mark ii', text: 'This is Mark 2' },
{ name: 'Mark III', value: 'Mark iii', text: 'This is Mark 3' }
];
Get selected item (on change)
$scope.fClick = function (data) {
console.log(data.name);
console.log(data.value);
console.log(data.text);
return;
}
Select item (with code)
$scope.abc = function (data) {
console.log(data.element1, data.element2);
angular.forEach($scope.marks, function (item) {
if (item.value == data.element1) {
item.ticked = true;
}
else {
item.ticked = false;
}
});
}
Deselect item (clear)
$scope.ClearClick = function () {
$scope.Filter = { selectMarks: 'Mark i' };
$scope.marks.map(function (item) {
if ($scope.Filter.selectMarks == item.value)
item.ticked = true;
else
item.ticked = false;
});
}

Related

AngularJS - NVDA screen reader not finding names of child elements

Apologies for the bare-bones HTML here...
I've got some AngularJS components that are rendering this HTML for a multiselectable dropdown:
<ul role="listbox">
<li>
<div ng-attr-id="ui-select-choices-row-{{ $select.generatedId }}-{{$index}}" class="ui-select-choices-row ng-scope" ng-class="{active: $select.isActive(this), disabled: $select.isDisabled(this)}" role="option" ng-repeat="opt in $select.items" ng-if="$select.open" ng-click="$select.select(opt,$select.skipFocusser,$event)" tabindex="0" id="ui-select-choices-row-0-1" style="">
<a href="" class="ui-select-choices-row-inner" uis-transclude-append="">
<span ng-class="{'strikethrough' : rendererInactive(opt)}" title="ALBANY" aria-label="ALBANY" class="ng-binding ng-scope">ALBANY</span>
</a>
</div>
(a hundred or so more options in similar divs)
</li>
</ul>
What we need is for screen reading software to speak aloud each option as it's highlighted via arrow key navigation. As it is now, NVDA says "blank" when keying through the list. If, in the directive we're using to create this HTML, I add role="presentation" to the <ul>, then NVDA will recite the entire list of options as soon as the dropdown opens, but not individually for each arrow key keystroke (and after hitting Escape to make it stop talking, keying through the options says "blank" again).
I keep thinking that the listbox and option roles are in the correct places, but is something else in the structure preventing the screen reader from finding the values correctly?
This answer got quite long, the first 3 points are most likely the problem, the rest are other considerations / observations
There are a few things that are likely to cause this issue, although without seeing the generated HTML rather than the Angular Source there could be others.
Most likely culprit is that your anchors are not valid. You cannot have a blank href (href="") for it to be valid. Looking at your source code could you not remove this and adjust your CSS or change it to a <div>?
Second most likely culprit is that role="option" should be on the direct children on role="listbox". Move it to your <li>s and make them selectable with tabindex="-1" (see below point on tabindex="0") instead. (in fact why not simply remove the surrounding <div> and apply all of your angular directives to the <li> directly).
Third most likely culprit is the fact that aria-label is not needed and may in fact be interfering, a screen reader will read the text within your <span> without this. Golden rule - do not use aria unless you can't portray the information another way.
You also need to add aria-selected="true" (or false) to each <li role="option"> to indicate whether an item is selected or not.
Also you should add aria-multiselectable="true" to the <ul> to indicate it is a multi select.
While you are at it, remove the title attribute, it doesn't add anything useful here.
aria-activedescendant="id" should be used to indicate which item is currently focused.
Be careful with tabindex="0" - I can't see if this is applied to everything but really it should be tabindex="-1" and you programatically manage focus as otherwise users could tab to items that they aren't meant to. tabindex="0" should be on the main <ul>.
Due to the complex nature of multi-selects you would be much better using a group of checkboxes as they provide a lot of the functionality for free, but that is just a suggestion.
The following example I found on codepen.io covers 95% of everything if you use a checkbox instead and would be a good base for you to pick apart and adapt to your needs, as you can see checkboxes make life a lot easier as all the selected not selected functionality is built in.
(function($){
'use strict';
const DataStatePropertyName = 'multiselect';
const EventNamespace = '.multiselect';
const PluginName = 'MultiSelect';
var old = $.fn[PluginName];
$.fn[PluginName] = plugin;
$.fn[PluginName].Constructor = MultiSelect;
$.fn[PluginName].noConflict = function () {
$.fn[PluginName] = old;
return this;
};
// Defaults
$.fn[PluginName].defaults = {
};
// Static members
$.fn[PluginName].EventNamespace = function () {
return EventNamespace.replace(/^\./ig, '');
};
$.fn[PluginName].GetNamespacedEvents = function (eventsArray) {
return getNamespacedEvents(eventsArray);
};
function getNamespacedEvents(eventsArray) {
var event;
var namespacedEvents = "";
while (event = eventsArray.shift()) {
namespacedEvents += event + EventNamespace + " ";
}
return namespacedEvents.replace(/\s+$/g, '');
}
function plugin(option) {
this.each(function () {
var $target = $(this);
var multiSelect = $target.data(DataStatePropertyName);
var options = (typeof option === typeof {} && option) || {};
if (!multiSelect) {
$target.data(DataStatePropertyName, multiSelect = new MultiSelect(this, options));
}
if (typeof option === typeof "") {
if (!(option in multiSelect)) {
throw "MultiSelect does not contain a method named '" + option + "'";
}
return multiSelect[option]();
}
});
}
function MultiSelect(element, options) {
this.$element = $(element);
this.options = $.extend({}, $.fn[PluginName].defaults, options);
this.destroyFns = [];
this.$toggle = this.$element.children('.toggle');
this.$toggle.attr('id', this.$element.attr('id') + 'multi-select-label');
this.$backdrop = null;
this.$allToggle = null;
init.apply(this);
}
MultiSelect.prototype.open = open;
MultiSelect.prototype.close = close;
function init() {
this.$element
.addClass('multi-select')
.attr('tabindex', 0);
initAria.apply(this);
initEvents.apply(this);
updateLabel.apply(this);
injectToggleAll.apply(this);
this.destroyFns.push(function() {
return '|'
});
}
function injectToggleAll() {
if(this.$allToggle && !this.$allToggle.parent()) {
this.$allToggle = null;
}
this.$allToggle = $("<li><label><input type='checkbox'/>(all)</label><li>");
this.$element
.children('ul:first')
.prepend(this.$allToggle);
}
function initAria() {
this.$element
.attr('role', 'combobox')
.attr('aria-multiselect', true)
.attr('aria-expanded', false)
.attr('aria-haspopup', false)
.attr('aria-labeledby', this.$element.attr("aria-labeledby") + " " + this.$toggle.attr('id'));
this.$toggle
.attr('aria-label', '');
}
function initEvents() {
var that = this;
this.$element
.on(getNamespacedEvents(['click']), function($event) {
if($event.target !== that.$toggle[0] && !that.$toggle.has($event.target).length) {
return;
}
if($(this).hasClass('in')) {
that.close();
} else {
that.open();
}
})
.on(getNamespacedEvents(['keydown']), function($event) {
var next = false;
switch($event.keyCode) {
case 13:
if($(this).hasClass('in')) {
that.close();
} else {
that.open();
}
break;
case 9:
if($event.target !== that.$element[0] ) {
$event.preventDefault();
}
case 27:
that.close();
break;
case 40:
next = true;
case 38:
var $items = $(this)
.children("ul:first")
.find(":input, button, a");
var foundAt = $.inArray(document.activeElement, $items);
if(next && ++foundAt === $items.length) {
foundAt = 0;
} else if(!next && --foundAt < 0) {
foundAt = $items.length - 1;
}
$($items[foundAt])
.trigger('focus');
}
})
.on(getNamespacedEvents(['focus']), 'a, button, :input', function() {
$(this)
.parents('li:last')
.addClass('focused');
})
.on(getNamespacedEvents(['blur']), 'a, button, :input', function() {
$(this)
.parents('li:last')
.removeClass('focused');
})
.on(getNamespacedEvents(['change']), ':checkbox', function() {
if(that.$allToggle && $(this).is(that.$allToggle.find(':checkbox'))) {
var allChecked = that.$allToggle
.find(':checkbox')
.prop("checked");
that.$element
.find(':checkbox')
.not(that.$allToggle.find(":checkbox"))
.each(function(){
$(this).prop("checked", allChecked);
$(this)
.parents('li:last')
.toggleClass('selected', $(this).prop('checked'));
});
updateLabel.apply(that);
return;
}
$(this)
.parents('li:last')
.toggleClass('selected', $(this).prop('checked'));
var checkboxes = that.$element
.find(":checkbox")
.not(that.$allToggle.find(":checkbox"))
.filter(":checked");
that.$allToggle.find(":checkbox").prop("checked", checkboxes.length === checkboxes.end().length);
updateLabel.apply(that);
})
.on(getNamespacedEvents(['mouseover']), 'ul', function() {
$(this)
.children(".focused")
.removeClass("focused");
});
}
function updateLabel() {
var pluralize = function(wordSingular, count) {
if(count !== 1) {
switch(true) {
case /y$/.test(wordSingular):
wordSingular = wordSingular.replace(/y$/, "ies");
default:
wordSingular = wordSingular + "s";
}
}
return wordSingular;
}
var $checkboxes = this.$element
.find('ul :checkbox');
var allCount = $checkboxes.length;
var checkedCount = $checkboxes.filter(":checked").length
var label = checkedCount + " " + pluralize("item", checkedCount) + " selected";
this.$toggle
.children("label")
.text(checkedCount ? (checkedCount === allCount ? '(all)' : label) : 'Select a value');
this.$element
.children('ul')
.attr("aria-label", label + " of " + allCount + " " + pluralize("item", allCount));
}
function ensureFocus() {
this.$element
.children("ul:first")
.find(":input, button, a")
.first()
.trigger('focus')
.end()
.end()
.find(":checked")
.first()
.trigger('focus');
}
function addBackdrop() {
if(this.$backdrop) {
return;
}
var that = this;
this.$backdrop = $("<div class='multi-select-backdrop'/>");
this.$element.append(this.$backdrop);
this.$backdrop
.on('click', function() {
$(this)
.off('click')
.remove();
that.$backdrop = null;
that.close();
});
}
function open() {
if(this.$element.hasClass('in')) {
return;
}
this.$element
.addClass('in');
this.$element
.attr('aria-expanded', true)
.attr('aria-haspopup', true);
addBackdrop.apply(this);
//ensureFocus.apply(this);
}
function close() {
this.$element
.removeClass('in')
.trigger('focus');
this.$element
.attr('aria-expanded', false)
.attr('aria-haspopup', false);
if(this.$backdrop) {
this.$backdrop.trigger('click');
}
}
})(jQuery);
$(document).ready(function(){
$('#multi-select-plugin')
.MultiSelect();
});
* {
box-sizing: border-box;
}
.multi-select, .multi-select-plugin {
display: inline-block;
position: relative;
}
.multi-select > span, .multi-select-plugin > span {
border: none;
background: none;
position: relative;
padding: .25em .5em;
padding-right: 1.5em;
display: block;
border: solid 1px #000;
cursor: default;
}
.multi-select > span > .chevron, .multi-select-plugin > span > .chevron {
display: inline-block;
transform: rotate(-90deg) scale(1, 2) translate(-50%, 0);
font-weight: bold;
font-size: .75em;
position: absolute;
top: .2em;
right: .75em;
}
.multi-select > ul, .multi-select-plugin > ul {
position: absolute;
list-style: none;
padding: 0;
margin: 0;
left: 0;
top: 100%;
min-width: 100%;
z-index: 1000;
background: #fff;
border: 1px solid rgba(0, 0, 0, 0.15);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
display: none;
max-height: 320px;
overflow-x: hidden;
overflow-y: auto;
}
.multi-select > ul > li, .multi-select-plugin > ul > li {
white-space: nowrap;
}
.multi-select > ul > li.selected > label, .multi-select-plugin > ul > li.selected > label {
background-color: LightBlue;
}
.multi-select > ul > li.focused > label, .multi-select-plugin > ul > li.focused > label {
background-color: DodgerBlue;
}
.multi-select > ul > li > label, .multi-select-plugin > ul > li > label {
padding: .25em .5em;
display: block;
}
.multi-select > ul > li > label:focus, .multi-select > ul > li > label:hover, .multi-select-plugin > ul > li > label:focus, .multi-select-plugin > ul > li > label:hover {
background-color: DodgerBlue;
}
.multi-select.in > ul, .multi-select-plugin.in > ul {
display: block;
}
.multi-select-backdrop, .multi-select-plugin-backdrop {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 900;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<label id="multi-select-plugin-label" style="display:block;">Multi Select</label>
<div id="multi-select-plugin" aria-labeledby="multi-select-plugin-label">
<span class="toggle">
<label>Select a value</label>
<span class="chevron"><</span>
</span>
<ul>
<li>
<label>
<input type="checkbox" name="selected" value="0"/>
Item 1
</label>
</li>
<li>
<label>
<input type="checkbox" name="selected" value="1"/>
Item 2
</label>
</li>
<li>
<label>
<input type="checkbox" name="selected" value="2"/>
Item 3
</label>
</li>
<li>
<label>
<input type="checkbox" name="selected" value="3"/>
Item 4
</label>
</li>
</ul>
</div>
Also you will see that gov.uk uses a checkbox pattern (within the organisation filter on the left on the linked page) for their multi-selects (with a filter - something you may consider with 100 different options as they have highlighted some key concerns in this article).
As you can see (and I wasn't finished) there is a lot to consider.
Hope I haven't scared you too much and the first few points solve the issue you originally asked about!

Buttons in a panel with "itemArray" binding are not displayed

I want to display a drop-down list of buttons in the left panel, one button for one "need".
Later, the user will be able to add a new need-button to the list.
I use a panel with "itemArray" binding.
But the button is not displayed when sentence: addNeed("My new need"); is executed.
I checked with the "dynamicPorts sample but I can't understand why it doesn't work.
<!DOCTYPE html>
<html>
<head>
<meta name="minimumCode" content="width=device-width, initial-scale=1">
<title>minimumCode</title>
<meta name="description" content="Iso prototype Leon Levy" />
<!-- Copyright 1998-2017 by Northwoods Software Corporation. -->
<meta charset="UTF-8">
<script src="https://unpkg.com/gojs/release/go-debug.js"></script>
<span id="diagramEventsMsg" style="color: red"></span>
<script id="code">
var ellipseStrokeWidth=3;
var ellipseWidth = 80;
var ellipseHeight = 25;
var myFont = "16px sans-serif";
var myFontMedium = "23px sans-serif";
var myFontLarge = "30px sans-serif";
var needWidth = 170;
var needHeight = 20;
var needStrokeWidth = 0;
var needColor = 'purple';
var portSize = new go.Size(8, 8);
function init() {
var $ = go.GraphObject.make; //for conciseness in defining node templates
myDiagram =
$(go.Diagram, "myDiagramDiv",
{
"undoManager.isEnabled": true
});
myBannerNeeds =
$(go.Diagram, "myBannerNeedsDiv",
{ layout: $(go.GridLayout, { wrappingColumn: 1, alignment: go.GridLayout.Position
})}
);
myBannerNeeds.nodeTemplate =
$(go.Node,
$(go.Panel, "Vertical", //needs list buttons
{ alignment: new go.Spot(0, 0), row: 0, column: 0},
new go.Binding("itemArray", "needsDataArray"),
{ itemTemplate:
$(go.Panel,
$(go.Shape, "Rectangle",
{ stroke: "red", strokeWidth: 2,
height: 30, width: 30}
),
$(go.TextBlock, { text: "...", stroke: "gray" },
new go.Binding("text", "key"))
) // end itemTemplate
}
) // end Vertical Panel
);
// Add a button to the needs panel.
function addNeed(newNeedName) {
myDiagram.startTransaction("addNeed");
var button = $('Button',
$(go.Shape, "Rectangle",
{ width: needWidth, height: needHeight, margin: 4, fill: "white",
stroke: "rgb(227, 18, 18)", strokeWidth: needStrokeWidth}),
$(go.TextBlock, newNeedName, // the content is just the text label
{stroke: needColor, font: myFont }),
{click: function(e, obj) { needSelected(newNeedName); } }
);
var needsNode = needsDataArray; //document.getElementById("ForNeeds");
if (needsNode) { showMessage("needsNode is true; " + button)}
else {showMessage("needsNode is false")};
myDiagram.model.insertArrayItem(needsNode, -1, button);
myDiagram.commitTransaction("addNeed");
}// end function addNeed
var needsDataArray = [];
var linksNeedsDataArray = []; // always empty
myBannerNeeds.model = new go.GraphLinksModel( needsDataArray, linksNeedsDataArray);
myDiagram.grid.visible = true;
myDiagram.model.copiesArrays = true;
myDiagram.model.copiesArrayObjects = true;
addNeed("My new need");
function needSelected(e,obj) {
alert("e:" + e + "; obj:" + obj + ' selected')
}; //end function flowTypeSelected
function showMessage(s) {
document.getElementById("diagramEventsMsg").textContent = s;
}
}// end function init
</script>
</head>
<body onload="init()">
<div id="container" style= "display: grid; grid-template-columns: 1fr 5fr; margin:0 ; height: 800px; width:1080px; font-size:0; position: relative; ">
<div id="ForNeeds">
<div id="myBannerNeedsDiv" style="display: inline-block; width: 200px; min-height: 400px; background: whitesmoke; margin-right: 0px; border: solid 1px purple;">
</div>
</div>
<div id="myDiagramDiv" style="flex-grow: 1; width: 804px;height: 100%; border: solid 1px black;">
</div>
</div>
</body>
</html>
Here's a basic demonstration of what I think you are asking for:
<!DOCTYPE html>
<html>
<head>
<title>Minimal GoJS Sample</title>
<!-- Copyright 1998-2019 by Northwoods Software Corporation. -->
<meta charset="UTF-8">
<script src="go.js"></script>
<script id="code">
function init() {
var $ = go.GraphObject.make;
myDiagram =
$(go.Diagram, "myDiagramDiv",
{ "undoManager.isEnabled": true });
myDiagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape,
{ fill: "white" }),
$(go.Panel, "Vertical",
$(go.TextBlock,
{ margin: 4 },
new go.Binding("text")),
$(go.Panel, "Vertical",
new go.Binding("itemArray", "buttons"),
{
itemTemplate:
$("Button",
$(go.TextBlock, new go.Binding("text", "")),
{
click: function(e, button) {
alert(button.data);
}
}
)
}
)
)
);
myDiagram.model = new go.GraphLinksModel(
[
{ key: 1, text: "Alpha", buttons: ["one", "two"] },
{ key: 2, text: "Beta", buttons: ["one"] }
],
[
{ from: 1, to: 2 }
]);
}
function test() {
myDiagram.commit(function(diag) {
diag.selection.each(function(n) {
if (n instanceof go.Node) {
diag.model.addArrayItem(n.data.buttons, "another");
}
})
})
}
</script>
</head>
<body onload="init()">
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
<button onclick="test()">Test</button>
</body>
</html>
Select a Node and then click the HTML "Test" Button. It will add an item to the node's data.buttons Array, which causes a copy of the Panel.itemTemplate to be added to that Panel. In this case, that item template is just a GoJS "Button" which when clicked calls alert with the value of the item, a string.
Note how the value added to the JavaScript Array in the data is just a simple object -- in this case just a string, although it is commonplace to have each Array item be a JavaScript Object with various properties. I think your problem is that you are trying to add GraphObjects to the Array. That's a no-no -- you should not be mixing the Diagram's GraphObjects with the Model data.

AngularJS Material Slide Left to Right CSS3 Animation

I am trying to "translate" this AngularJS slide left / right example to an AngularJS Material one.
The latter link consists of the following code snippets:
HTML code:
<div ng-controller="ExampleController" ng-app="switchExample">
<!--<select ng-model="slide" ng-options="item as item.name for item in slides">
</select>-->
<code>slide={{slide}}</code>
<code>moveToLeft={{mtl}}</code>
<md-button ng-click="prev()"><</md-button>
<md-button ng-click="next()">></md-button>
<div class="">
<div class="ngSwitchItem" ng-if="slide.name == 'first'" ng-class="{'moveToLeft' : mtl}">
<div class="firstPage page" md-swipe-left="selectPage(1)">
first
</div>
</div>
<div class="ngSwitchItem" ng-if="slide.name == 'second'" ng-class="{'moveToLeft' : mtl}">
<div class="secondPage page" md-swipe-right="selectPage(0)" md-swipe-left="selectPage(2)">
second
</div>
</div>
<div class="ngSwitchItem" ng-if="slide.name == 'third'" ng-class="{'moveToLeft' : mtl}">
<div class="thirdPage page" md-swipe-right="selectPage(1)" md-swipe-left="selectPage(3)">
third
</div>
</div>
<div class="ngSwitchItem" ng-if="slide.name == 'fourth'" ng-class="{'moveToLeft' : mtl}">
<div class="fourthPage page" md-swipe-right="selectPage(2)">
fourth
</div>
</div>
</div>
</div>
JS code
(function(angular) {
'use strict';
angular.module('switchExample', ['ngMaterial', 'ngAnimate'])
.controller('ExampleController', ['$scope', function($scope) {
$scope.slides = [
{ index: 0, name: 'first' },
{ index: 1, name: 'second' },
{ index: 2, name: 'third' },
{ index: 3, name: 'fourth' }
];
$scope.selectPage = selectPage;
/**
* Initialize with the first page opened
*/
$scope.slide = $scope.slides[0];
$scope.prev = () => {
if ($scope.slide.index > 0) {
selectPage($scope.slide.index - 1);
}
}
$scope.next = () => {
if ($scope.slide.index < 3) {
selectPage($scope.slide.index + 1);
}
}
/**
* #name selectPage
* #desc The function that includes the page of the indexSelected
* #param indexSelected the index of the page to be included
*/
function selectPage(indexSelected) {
if ($scope.slides[indexSelected].index > $scope.slide.index) {
$scope.mtl = false;
} else {
$scope.mtl = true;
}
$scope.slide = $scope.slides[indexSelected];
}
}]);
})(window.angular);
CSS code
body {
overflow-x: hidden;
}
.ngSwitchItem {
position: absolute;
top: 50px;
bottom: 0;
right: 0;
left: 0;
animation-duration: 10.30s;
animation-timing-function: ease-in-out;
-webkit-animation-duration: 10.30s;
-webkit-animation-timing-function: ease-in-out;
}
.page {
position: inherit;
top: 0;
right: inherit;
bottom: inherit;
left: inherit;
}
.firstPage {
background-color: blue;
}
.secondPage {
background-color: red;
}
.thirdPage {
background-color: green;
}
.fourthPage {
background-color: yellow;
}
/* When the page enters, slide it from the right */
.ngSwitchItem.ng-enter {
animation-name: slideFromRight;
-webkit-animation-name: slideFromRight;
}
/* When the page enters and moveToLeft is true, slide it from the left(out of the user view) to the right (left corner) */
.ngSwitchItem.moveToLeft.ng-enter {
animation-name: slideFromLeft;
-webkit-animation-name: slideFromLeft;
}
/* When the page leaves, slide it to left(out of the user view) from the left corner,
in other words slide it from the left(out of the view) to the left corner but in reverse order */
.ngSwitchItem.ng-leave {
animation-name: slideFromLeft;
animation-direction: reverse;
-webkit-animation-name: slideFromLeft;
-webkit-animation-direction: reverse;
}
/* When the page leaves, slide it to the right(out of the user view) from the the left corner,
in other words, slide it from the right but in reverse order */
.ngSwitchItem.moveToLeft.ng-leave {
animation-name: slideFromRight;
animation-direction: reverse;
-webkit-animation-name: slideFromRight;
-webkit-animation-direction: reverse;
}
#keyframes slideFromRight {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(0);
}
}
#keyframes slideFromLeft {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(0);
}
}
#-webkit-keyframes slideFromRight {
0% {
-webkit-transform: translateX(100%);
}
100% {
-webkit-transform: translateX(0);
}
}
#-webkit-keyframes slideFromLeft {
0% {
-webkit-transform: translateX(-100%);
}
100% {
-webkit-transform: translateX(0);
}
}
As seen, however, the second one doesn't behave as the first one, WHEN slide direction has changed.
For instance:
I slide to left the first one --> second slide loads with the correct animation
Then, I slide to right the second one --> it is supposed the first slide to start appearance from the left side, while the second one to start disappearance to to the right side. Instead, as you may see, the second one start to disappear to the left and from the right side a white slide is shown. At some point, the first slide starts its appearance from the middle of the content.
Please note, I deliberately delay the animations on the second example, just to see the undesired side effect mode clearly.
Actually, after a few more research hours, I found where the problem was buried - it seems, I have to move scope variable change for the next tick, to give time ng-class change to make its "magic".
Long story short - adding the following is what made the thing work:
$timeout(() => {
$scope.slide = $scope.slides[indexSelected];
}, 0)
Here is the updated example and the code snippet below:
JS code
(function(angular) {
'use strict';
angular.module('switchExample', ['ngMaterial', 'ngAnimate'])
.controller('ExampleController', ['$scope', '$timeout', function($scope, $timeout) {
$scope.slides = [
{ index: 0, name: 'first' },
{ index: 1, name: 'second' },
{ index: 2, name: 'third' },
{ index: 3, name: 'fourth' }
];
$scope.selectPage = selectPage;
/**
* Initialize with the first page opened
*/
$scope.slide = $scope.slides[0];
$scope.prev = () => {
if ($scope.slide.index > 0) {
selectPage($scope.slide.index - 1);
}
}
$scope.next = () => {
if ($scope.slide.index < 3) {
selectPage($scope.slide.index + 1);
}
}
/**
* #name selectPage
* #desc The function that includes the page of the indexSelected
* #param indexSelected the index of the page to be included
*/
function selectPage(indexSelected) {
if ($scope.slides[indexSelected].index > $scope.slide.index) {
$scope.mtl = false;
} else {
$scope.mtl = true;
}
// this will move a scope variable change to the next tick,
// hence will give time $scope.mtl to be handled by ng-class
$timeout(() => {
$scope.slide = $scope.slides[indexSelected];
}, 0)
}
}]);
})(window.angular);

Angular - how to make $interval work from user input

I am taking date and time from user as input and then wanted to display it interval in label
After that datetime completes I want to make that label color change.
Please guide me how can i achieve this.
https://codepen.io/shreyag020/pen/vKvmdx
$interval(function(){
todoTime=$scope.datetime;
});
Here is an example of how you could change the background color of a div over time. Clicking the start button begins a countdown timer from 1 minute. It starts out with no background, once the timer is started it turns green, at 15 seconds before time runs out it turns orange and when the time has run out it turns red. The change of the background color is animated using ng-class, ngAnimate and the animation hooks.
angular.module('app', ['ngAnimate'])
.controller('ctrl', function($scope, $interval) {
var timerPromise;
$scope.reset = function() {
$scope.userTime = new Date;
$scope.userTime.setMinutes(1);
$scope.userTime.setSeconds(0);
$scope.resetVisible = false;
$scope.started = false;
}
$scope.start = function() {
$scope.started = true;
$scope.resetVisible = false;
timerPromise = $interval(function() {
if ($scope.userTime.getSeconds() > 0 || $scope.userTime.getMinutes() > 0) {
$scope.userTime.setTime($scope.userTime.getTime() - 1000);
}
}, 1000);
}
$scope.pause = function() {
$interval.cancel(timerPromise);
$scope.started = false;
$scope.resetVisible = true;
}
$scope.timeBackground = function() {
if ($scope.started) {
if ($scope.userTime.getMinutes() === 0 && $scope.userTime.getSeconds() === 0) {
$scope.resetVisible = true;
$interval.cancel(timerPromise);
return 'expired';
}
if ($scope.userTime.getMinutes() === 0 && $scope.userTime.getSeconds() <= 15) {
return 'warning';
}
return 'ok';
}
return 'clear';
}
$scope.reset();
});
.timeDisplay {
color: white;
font-weight: bold;
}
.ok-add,
.ok-remove,
.warning-add,
.warning-remove,
.expired-add,
.expired-remove {
transition: background-color 1000ms linear;
}
.warning,
.warning-add.pre-warning-add-active {
background-color: orange;
}
.expired,
.expired-add.expired-add-active {
background-color: red;
}
.ok,
.ok-add.ok-add-active {
background-color: green;
}
.clear {
background-color: none;
color: black;
}
div {
padding: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular-animate.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<div class="timeDisplay" ng-class="timeBackground()">
Time remaining: {{userTime | date: 'mm:ss'}}
</div>
<div ng-if="!started">
<button ng-click="start()">Start</button>
</div>
<div ng-if="started">
<button ng-click="pause()">Pause</button>
</div>
<div ng-if="resetVisible">
<button ng-click="reset()">Reset</button>
</div>
</div>

Custom Directive is not working with div id

I have a custom directive called packageHeader, When the user scrolls through the list, the header must show at top of the list until the next one is reached. It's working with only window element not with div id, here i'm attaching the html
<div id="list">
<package-header>
<div>Header 1</div>
</package-header>
<div>content Header 1</div>
<package-header>
<div>Header2</div>
</package-header>
<div>content Header2</div>
<package-header>
<div>Header 3</div>
</package-header>
<div>content Header 3</div>
<div>
my custom directive
angular.module('myApp')
.directive('packageHeader',
['$window', function($window) {
var stickies = [],
scroll = function scroll() {
var header= angular.element(document.querySelector("#List"))[0];
console.log("scroll _ scroll");
angular.forEach(stickies, function($sticky, index) {
var sticky = $sticky[0],
pos = $sticky.data('pos');
if (pos <= header.pageYOffset) {
console.log("scroll offset Y");
var $next = stickies[index + 1],
next = $next ? $next[0] : null,
npos = $next.data('pos');
$sticky.addClass("fixed");
if (next && next.offsetTop >= npos - next.clientHeight)
$sticky.addClass("absolute").css("top", npos - sticky.clientHeight + 'px');
} else {
console.log("scroll offset X");
var $prev = stickies[index - 1],
prev = $prev ? $prev[0] : null;
$sticky.removeClass("fixed");
if (prev && header.pageYOffset <= pos - prev.clientHeight)
$prev.removeClass("absolute").removeAttr("style");
}
});
},
link = function($scope, element, attrs) {
var sticky = element.children()[0],
$sticky = angular.element(sticky);
element.css('height', sticky.clientHeight + 'px');
$sticky.data('pos', sticky.offsetTop);
stickies.push($sticky);
};
angular.element(document.querySelector("#List"))
.off('scroll', scroll)
.on('scroll', scroll);
return {
restrict: 'E',
transclude: true,
//sticky - getting from style sheet
template: '<sticky ng-transclude></sticky>',
link: link
};
}]);
CSS
package-header,
sticky {
display: block;
}
package-header{
opacity:.8;
}
package-header>sticky {
background: #9aa2a8;
line-height: 24px;
z-index: 1;
color: #fff;
font-weight: 700;
}
package-header>sticky.fixed {
position: fixed;
top: 0;
width: 100%;
z-index: 0;
}
package-header>sticky.fixed.absolute {
position: absolute;
}
Please help me to resolve my issue

Resources